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
@@ -27,39 +27,56 @@
27
27
  /* vv kernel lookup */
28
28
  /* ------------------------------------------------------------------- */
29
29
 
30
+ /* The kernel table of each op id. The eager operators hold the tables
31
+ themselves; ca_binop_op_id_of_table maps one back to its op id, so that
32
+ both ask ca_binop_is_trapping the same question. */
33
+ static ca_binop_func_t * const ca_binop_tables[CA_BINOP_COUNT] = {
34
+ [CA_BINOP_ADD] = ca_binop_add,
35
+ [CA_BINOP_SUB] = ca_binop_sub,
36
+ [CA_BINOP_MUL] = ca_binop_mul,
37
+ [CA_BINOP_DIV] = ca_binop_div,
38
+ [CA_BINOP_POW] = ca_binop_power,
39
+ [CA_BINOP_BIT_AND] = ca_binop_bit_and_i,
40
+ [CA_BINOP_BIT_OR] = ca_binop_bit_or_i,
41
+ [CA_BINOP_BIT_XOR] = ca_binop_bit_xor_i,
42
+ [CA_BINOP_BIT_LSHIFT]= ca_binop_bit_lshift,
43
+ [CA_BINOP_BIT_RSHIFT]= ca_binop_bit_rshift,
44
+ [CA_BINOP_MOD] = ca_binop_mod,
45
+ [CA_BINOP_QUO] = ca_binop_quo_i,
46
+ [CA_BINOP_RCP_MUL] = ca_binop_rcp_mul,
47
+ [CA_BINOP_IPOWER] = ca_binop_ipower,
48
+ [CA_BINOP_COPYSIGN] = ca_binop_copysign,
49
+ [CA_BINOP_LOGADDEXP] = ca_binop_logaddexp,
50
+ [CA_BINOP_NEXTAFTER] = ca_binop_nextafter,
51
+ [CA_BINOP_FMOD] = ca_binop_fmod,
52
+ [CA_BINOP_ATAN2] = ca_binop_atan2,
53
+ [CA_BINOP_HYPOT] = ca_binop_hypot,
54
+ [CA_BINOP_PMAX] = ca_binop_pmax,
55
+ [CA_BINOP_PMIN] = ca_binop_pmin,
56
+ [CA_BINOP_MAXIMUM] = ca_binop_maximum,
57
+ [CA_BINOP_MINIMUM] = ca_binop_minimum,
58
+ [CA_BINOP_AND] = ca_binop_and,
59
+ [CA_BINOP_OR] = ca_binop_or,
60
+ [CA_BINOP_XOR] = ca_binop_xor,
61
+ };
62
+
30
63
  ca_binop_func_t
31
64
  ca_binop_kernel_lookup_vv (uint16_t op_id, int8_t common_dt)
32
65
  {
33
- switch (op_id) {
34
- case CA_BINOP_ADD: return ca_binop_add [common_dt];
35
- case CA_BINOP_SUB: return ca_binop_sub [common_dt];
36
- case CA_BINOP_MUL: return ca_binop_mul [common_dt];
37
- case CA_BINOP_DIV: return ca_binop_div [common_dt];
38
- case CA_BINOP_POW: return ca_binop_power [common_dt];
39
- case CA_BINOP_BIT_AND: return ca_binop_bit_and_i [common_dt];
40
- case CA_BINOP_BIT_OR: return ca_binop_bit_or_i [common_dt];
41
- case CA_BINOP_BIT_XOR: return ca_binop_bit_xor_i [common_dt];
42
- case CA_BINOP_BIT_LSHIFT: return ca_binop_bit_lshift [common_dt];
43
- case CA_BINOP_BIT_RSHIFT: return ca_binop_bit_rshift [common_dt];
44
- case CA_BINOP_MOD: return ca_binop_mod [common_dt];
45
- case CA_BINOP_QUO: return ca_binop_quo_i [common_dt];
46
- case CA_BINOP_RCP_MUL: return ca_binop_rcp_mul [common_dt];
47
- case CA_BINOP_IPOWER: return ca_binop_ipower [common_dt];
48
- case CA_BINOP_COPYSIGN: return ca_binop_copysign [common_dt];
49
- case CA_BINOP_LOGADDEXP: return ca_binop_logaddexp [common_dt];
50
- case CA_BINOP_NEXTAFTER: return ca_binop_nextafter [common_dt];
51
- case CA_BINOP_FMOD: return ca_binop_fmod [common_dt];
52
- case CA_BINOP_ATAN2: return ca_binop_atan2 [common_dt];
53
- case CA_BINOP_HYPOT: return ca_binop_hypot [common_dt];
54
- case CA_BINOP_PMAX: return ca_binop_pmax [common_dt];
55
- case CA_BINOP_PMIN: return ca_binop_pmin [common_dt];
56
- case CA_BINOP_MAXIMUM: return ca_binop_maximum [common_dt];
57
- case CA_BINOP_MINIMUM: return ca_binop_minimum [common_dt];
58
- case CA_BINOP_AND: return ca_binop_and [common_dt];
59
- case CA_BINOP_OR: return ca_binop_or [common_dt];
60
- case CA_BINOP_XOR: return ca_binop_xor [common_dt];
61
- default: return NULL;
66
+ if ( op_id >= CA_BINOP_COUNT || ! ca_binop_tables[op_id] ) return NULL;
67
+ return ca_binop_tables[op_id][common_dt];
68
+ }
69
+
70
+ /* The op id whose kernel table is `table`, or -1 for a table no op id
71
+ names. */
72
+ int
73
+ ca_binop_op_id_of_table (ca_binop_func_t table[])
74
+ {
75
+ int op_id;
76
+ for ( op_id = 0; op_id < CA_BINOP_COUNT; op_id++ ) {
77
+ if ( ca_binop_tables[op_id] == table ) return op_id;
62
78
  }
79
+ return -1;
63
80
  }
64
81
 
65
82
  /* ------------------------------------------------------------------- */
@@ -106,10 +123,12 @@ ca_binop_kernel_input_data_types (uint16_t op_id, int8_t l_dt, int8_t r_dt,
106
123
  int
107
124
  ca_binop_is_trapping (uint16_t op_id, int8_t common_dt)
108
125
  {
109
- /* Integer DIV / MOD / QUO / FMOD can SIGFPE on a zero divisor. Float
110
- DIV returns NaN/Inf and does NOT trap, so it is not classified
111
- trapping. */
126
+ /* An object cell calls Ruby, which can raise for any op. Integer
127
+ DIV / MOD / QUO / FMOD / RCP_MUL raise on a zero divisor, and POW /
128
+ IPOWER on zero to a negative power. Float division returns NaN/Inf
129
+ and does not trap. */
112
130
  int is_integer = ( common_dt >= CA_INT8 && common_dt <= CA_UINT64 );
131
+ if ( common_dt == CA_OBJECT ) return 1;
113
132
  if ( ! is_integer ) return 0;
114
133
 
115
134
  switch (op_id) {
@@ -117,6 +136,9 @@ ca_binop_is_trapping (uint16_t op_id, int8_t common_dt)
117
136
  case CA_BINOP_MOD:
118
137
  case CA_BINOP_QUO:
119
138
  case CA_BINOP_FMOD:
139
+ case CA_BINOP_RCP_MUL:
140
+ case CA_BINOP_POW:
141
+ case CA_BINOP_IPOWER:
120
142
  return 1;
121
143
  default:
122
144
  return 0;
@@ -148,5 +148,6 @@ void ca_binop_kernel_input_data_types (uint16_t op_id, int8_t l_dt, int8_t r_dt,
148
148
  a slab mask to the kernel so masked cells are skipped (otherwise
149
149
  masked-zero divisors crash with SIGFPE). See §3.4 rev3. */
150
150
  int ca_binop_is_trapping (uint16_t op_id, int8_t common_dt);
151
+ int ca_binop_op_id_of_table (ca_binop_func_t table[]);
151
152
 
152
153
  #endif /* CA_BINOP_DISPATCH_H */
@@ -35,6 +35,7 @@
35
35
  --------------------------------------------------------------------------- */
36
36
 
37
37
  #include "carray.h"
38
+ #include "carray_internal.h" /* CA_EXTREMUM_REPLACES */
38
39
  #include <stdlib.h> /* qsort */
39
40
  #include <math.h> /* floor */
40
41
 
@@ -82,8 +83,7 @@ rb_ca_categorical_scatter (VALUE self, VALUE rvalue, VALUE rcursor,
82
83
  rb_raise(rb_eArgError, "__categorical_scatter__: grouped/value data type mismatch");
83
84
  }
84
85
 
85
- ca_attach(codes);
86
- ca_attach(value);
86
+ ca_attach_n(2, codes, value); /* window: detached before each raise inside */
87
87
  cmask = ca_mask_ptr(codes);
88
88
  vmask = ca_mask_ptr(value);
89
89
  cur = (int64_t *) cursor->ptr;
@@ -138,6 +138,19 @@ rb_ca_categorical_scatter (VALUE self, VALUE rvalue, VALUE rcursor,
138
138
 
139
139
  --------------------------------------------------------------------------- */
140
140
 
141
+ /* NaN loses every contest, which is what CArray#min / #max do (mkkernel
142
+ declares it as `all_nan_result: :nan`): a NaN never displaces a number, the
143
+ first number displaces a NaN that seeded the accumulator, and a segment
144
+ holding nothing but NaN answers NaN. Without this the answer depends on
145
+ where in the segment the NaN sits, and a reduction that is order-free
146
+ stopped being order-free.
147
+
148
+ The predicate is passed per instantiation rather than written inline so an
149
+ integer body never compares a value with itself, which is a tautology the
150
+ compiler is right to warn about. */
151
+ #define CAT_NAN_NEVER(v) (0)
152
+ #define CAT_NAN_CHECK(v) ((v) != (v))
153
+
141
154
  #define REDUCEAT_MOMENTS_BODY(T) \
142
155
  do { \
143
156
  const T *gp = (const T *) grouped->ptr; \
@@ -152,8 +165,11 @@ rb_ca_categorical_scatter (VALUE self, VALUE rvalue, VALUE rcursor,
152
165
  if ( gm && gm[j] ) continue; /* masked value cell */ \
153
166
  { T v = gp[j]; \
154
167
  sacc += (double) v; \
155
- if ( ! seen ) { mn = v; mx = v; seen = 1; } \
156
- else { if ( v < mn ) mn = v; if ( v > mx ) mx = v; } \
168
+ if ( ! seen ) { mn = v; mx = v; seen = 1; } \
169
+ else { \
170
+ if ( CA_EXTREMUM_REPLACES(v, mn, <) ) mn = v; \
171
+ if ( CA_EXTREMUM_REPLACES(v, mx, >) ) mx = v; \
172
+ } \
157
173
  cnt++; } \
158
174
  } \
159
175
  countp[c] = (int64_t) cnt; \
@@ -278,14 +294,31 @@ ca_nth_element_double (double *a, ca_size_t n, ca_size_t kth)
278
294
  { double f = (double) (m - 1) * p / 100.0; \
279
295
  ca_size_t ki = (ca_size_t) floor(f); \
280
296
  double vlo, vhi; \
281
- ca_nth_element_double(scratch, m, ki); /* scratch[ki] = ki-th */ \
282
- vlo = scratch[ki]; \
283
- if ( ki + 1 < m ) { /* (ki+1)-th = min of upper partition */ \
297
+ /* Push NaN to the tail, which is where CArray's sort puts it, so the \
298
+ ki-th element here is the ki-th of the sorted segment. Selection \
299
+ runs on the numeric prefix, since a comparison-based quickselect \
300
+ has nothing to say about a NaN; a position or an upper neighbour \
301
+ landing in the tail is NaN. The blend is CArray#percentile's, so \
302
+ both answer alike for NaN and infinities. */ \
303
+ ca_size_t nnum = m, t0 = 0; \
304
+ while ( t0 < nnum ) { \
305
+ if ( scratch[t0] != scratch[t0] ) { \
306
+ double tmp = scratch[--nnum]; \
307
+ scratch[nnum] = scratch[t0]; scratch[t0] = tmp; \
308
+ } else t0++; \
309
+ } \
310
+ if ( ki < nnum ) { \
311
+ ca_nth_element_double(scratch, nnum, ki); /* scratch[ki] = ki-th */ \
312
+ vlo = scratch[ki]; \
313
+ } else vlo = (double) NAN; \
314
+ if ( ki + 1 < nnum ) { /* (ki+1)-th = min of upper partition */ \
284
315
  double mn = scratch[ki+1]; ca_size_t t; \
285
- for ( t = ki + 2; t < m; t++ ) if ( scratch[t] < mn ) mn = scratch[t]; \
316
+ for ( t = ki + 2; t < nnum; t++ ) if ( scratch[t] < mn ) mn = scratch[t]; \
286
317
  vhi = mn; \
287
- } else vhi = vlo; \
288
- outp[c] = vlo + (f - (double) ki) * (vhi - vlo); } \
318
+ } else vhi = (double) NAN; \
319
+ { double r = f - (double) ki; \
320
+ outp[c] = ( r == 0.0 || ki + 1 >= m ) ? vlo \
321
+ : vlo * (1.0 - r) + vhi * r; } } \
289
322
  } \
290
323
  } while (0)
291
324
 
@@ -302,6 +335,11 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
302
335
  GetCArray(roffsets, offsets);
303
336
  GetCArray(rout, out);
304
337
  p = NUM2DBL(rp);
338
+ if ( !(p >= 0.0 && p <= 100.0) ) {
339
+ rb_raise(rb_eArgError,
340
+ "percentile: p must be Numeric in [0,100] (got %"PRIsVALUE")",
341
+ rb_inspect(rp));
342
+ }
305
343
  k = (int64_t) offsets->elements;
306
344
  n = grouped->elements;
307
345
  if ( offsets->data_type != CA_INT64 || out->data_type != CA_FLOAT64 ||
@@ -350,7 +388,7 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
350
388
  accumulates the per-segment centred sum of squares; variance = SS / (n-1).
351
389
  Centred (not the one-pass sum-of-squares) so the ε-close contract holds — a
352
390
  one-pass formula cancels catastrophically. Matches CArray#variance per group:
353
- count 0 -> masked (undefined), count 1 -> 0.0 (the n=1 contract), count >= 2 ->
391
+ count 0 or 1 -> masked (the sample variance is undefined), count >= 2 ->
354
392
  SS / (count-1).
355
393
 
356
394
  Surface (private): grouped.__reduceat_variance__(offsets, means, counts, out)
@@ -358,7 +396,7 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
358
396
  offsets = int64[k] segment STARTS
359
397
  means = float64[k] per-segment mean (ignored where count < 2)
360
398
  counts = int64[k] per-segment present count
361
- out = float64[k] output: variance; count 0 masked, count 1 -> 0.0
399
+ out = float64[k] output: variance; count 0 or 1 masked
362
400
 
363
401
  --------------------------------------------------------------------------- */
364
402
 
@@ -367,8 +405,7 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
367
405
  const T *gp = (const T *) grouped->ptr; \
368
406
  for ( c = 0; c < k; c++ ) { \
369
407
  int64_t cnt = countp[c]; \
370
- if ( cnt == 0 ) { outp[c] = 0.0; outm[c] = 1; continue; } /* -> masked */ \
371
- if ( cnt == 1 ) { outp[c] = 0.0; continue; } /* n=1 contract: 0.0 */ \
408
+ if ( cnt < 2 ) { outp[c] = 0.0; outm[c] = 1; continue; } /* undefined */ \
372
409
  { double mean = meanp[c], ss = 0.0; \
373
410
  ca_size_t lo = (ca_size_t) offs[c]; \
374
411
  ca_size_t hi = (c + 1 < k) ? (ca_size_t) offs[c+1] : n, j; \
@@ -510,7 +547,7 @@ rb_ca_reduceat_prod (VALUE self, VALUE roffsets, VALUE rout)
510
547
  /* __reduceat_argminmax__(offsets, min_idx, max_idx) — per-segment GROUP-LOCAL
511
548
  index of the min / max (position within the segment, first occurrence on
512
549
  ties). Empty / all-masked segments are masked. */
513
- #define REDUCEAT_ARGMINMAX_BODY(T) \
550
+ #define REDUCEAT_ARGMINMAX_BODY(T, ISNAN) \
514
551
  do { \
515
552
  const T *gp = (const T *) grouped->ptr; \
516
553
  for ( c = 0; c < k; c++ ) { \
@@ -520,10 +557,13 @@ rb_ca_reduceat_prod (VALUE self, VALUE roffsets, VALUE rout)
520
557
  for ( j = lo; j < hi; j++ ) { \
521
558
  if ( gm && gm[j] ) continue; \
522
559
  { T v = gp[j]; ca_size_t li = j - lo; \
560
+ if ( ISNAN(v) ) continue; /* wins no contest, so marks no position */\
523
561
  if ( ! seen ) { mn = mx = v; mni = mxi = li; seen = 1; } \
524
562
  else { if ( v < mn ) { mn = v; mni = li; } \
525
563
  if ( v > mx ) { mx = v; mxi = li; } } } \
526
564
  } \
565
+ /* A segment of nothing but NaN has no position to report, the same as a \
566
+ segment with no present cell at all -- both are UNDEF, as in the core.*/\
527
567
  if ( seen ) { minp[c] = (int64_t) mni; maxp[c] = (int64_t) mxi; } \
528
568
  else { minp[c] = 0; maxp[c] = 0; minm[c] = 1; maxm[c] = 1; } \
529
569
  } \
@@ -558,16 +598,16 @@ rb_ca_reduceat_argminmax (VALUE self, VALUE roffsets, VALUE rminidx, VALUE rmaxi
558
598
  maxm = (boolean8_t *) maxidx->mask->ptr;
559
599
 
560
600
  switch ( grouped->data_type ) {
561
- case CA_INT8: REDUCEAT_ARGMINMAX_BODY(int8_t); break;
562
- case CA_UINT8: REDUCEAT_ARGMINMAX_BODY(uint8_t); break;
563
- case CA_INT16: REDUCEAT_ARGMINMAX_BODY(int16_t); break;
564
- case CA_UINT16: REDUCEAT_ARGMINMAX_BODY(uint16_t); break;
565
- case CA_INT32: REDUCEAT_ARGMINMAX_BODY(int32_t); break;
566
- case CA_UINT32: REDUCEAT_ARGMINMAX_BODY(uint32_t); break;
567
- case CA_INT64: REDUCEAT_ARGMINMAX_BODY(int64_t); break;
568
- case CA_UINT64: REDUCEAT_ARGMINMAX_BODY(uint64_t); break;
569
- case CA_FLOAT32: REDUCEAT_ARGMINMAX_BODY(float32_t); break;
570
- case CA_FLOAT64: REDUCEAT_ARGMINMAX_BODY(float64_t); break;
601
+ case CA_INT8: REDUCEAT_ARGMINMAX_BODY(int8_t, CAT_NAN_NEVER); break;
602
+ case CA_UINT8: REDUCEAT_ARGMINMAX_BODY(uint8_t, CAT_NAN_NEVER); break;
603
+ case CA_INT16: REDUCEAT_ARGMINMAX_BODY(int16_t, CAT_NAN_NEVER); break;
604
+ case CA_UINT16: REDUCEAT_ARGMINMAX_BODY(uint16_t, CAT_NAN_NEVER); break;
605
+ case CA_INT32: REDUCEAT_ARGMINMAX_BODY(int32_t, CAT_NAN_NEVER); break;
606
+ case CA_UINT32: REDUCEAT_ARGMINMAX_BODY(uint32_t, CAT_NAN_NEVER); break;
607
+ case CA_INT64: REDUCEAT_ARGMINMAX_BODY(int64_t, CAT_NAN_NEVER); break;
608
+ case CA_UINT64: REDUCEAT_ARGMINMAX_BODY(uint64_t, CAT_NAN_NEVER); break;
609
+ case CA_FLOAT32: REDUCEAT_ARGMINMAX_BODY(float32_t, CAT_NAN_CHECK); break;
610
+ case CA_FLOAT64: REDUCEAT_ARGMINMAX_BODY(float64_t, CAT_NAN_CHECK); break;
571
611
  default:
572
612
  rb_raise(rb_eCADataTypeError,
573
613
  "__reduceat_argminmax__: numeric value required (got data_type %d)",
@@ -631,6 +671,12 @@ static int
631
671
  cmp_double (const void *a, const void *b)
632
672
  {
633
673
  double x = *(const double *) a, y = *(const double *) b;
674
+ /* NaN sorts last, which is where CArray's own sort puts it, so picking a
675
+ position out of the sorted segment gives the core's answer. A bare
676
+ < / > comparison returns 0 for every pair involving a NaN, which is not
677
+ a strict weak ordering and leaves qsort free to produce anything. */
678
+ if ( x != x ) return (y != y) ? 0 : 1;
679
+ if ( y != y ) return -1;
634
680
  return (x < y) ? -1 : (x > y) ? 1 : 0;
635
681
  }
636
682
 
@@ -648,13 +694,18 @@ cmp_double (const void *a, const void *b)
648
694
  } \
649
695
  if ( m == 0 ) { for ( t = 0; t < 5; t++ ) { outp[t][c] = 0.0; outm[t][c] = 1; } continue; } \
650
696
  qsort(scratch, (size_t) m, sizeof(double), cmp_double); \
697
+ { ca_size_t nnum = m; \
698
+ while ( nnum > 0 && scratch[nnum-1] != scratch[nnum-1] ) nnum--; \
651
699
  for ( t = 0; t < 5; t++ ) { \
652
700
  double f = (double) (m - 1) * P[t] / 100.0; \
653
701
  ca_size_t ki = (ca_size_t) floor(f); \
654
702
  double vlo = scratch[ki]; \
655
- double vhi = (ki + 1 < m) ? scratch[ki+1] : vlo; \
703
+ /* Clamp the upper neighbour into the numeric prefix: with NaN sorted \
704
+ last, a position whose neighbour is a NaN interpolates against \
705
+ itself, which is what the core answers. */ \
706
+ double vhi = (ki + 1 < nnum) ? scratch[ki+1] : vlo; \
656
707
  outp[t][c] = vlo + (f - (double) ki) * (vhi - vlo); \
657
- } \
708
+ } } \
658
709
  } \
659
710
  } while (0)
660
711
 
@@ -847,8 +898,8 @@ rb_ca_reduceat_wsum_wmean (VALUE self, VALUE roffsets, VALUE rwg,
847
898
  if ( countp[out_off] == 0 ) { \
848
899
  minv[out_off] = v; maxv[out_off] = v; \
849
900
  } else { \
850
- if ( v < minv[out_off] ) minv[out_off] = v; \
851
- if ( v > maxv[out_off] ) maxv[out_off] = v; \
901
+ if ( CA_EXTREMUM_REPLACES(v, minv[out_off], <) ) minv[out_off] = v;\
902
+ if ( CA_EXTREMUM_REPLACES(v, maxv[out_off], >) ) maxv[out_off] = v;\
852
903
  } \
853
904
  countp[out_off]++; \
854
905
  sump[out_off] += (double) v; \
@@ -951,8 +1002,7 @@ rb_ca_fiber_scatter_moments (VALUE self, VALUE rcodes, VALUE raxis, VALUE rk,
951
1002
  band_size = outer_prod * inner_prod;
952
1003
  total = (ca_size_t)(K * band_size);
953
1004
 
954
- ca_attach(h);
955
- ca_attach(codes);
1005
+ ca_attach_n(2, h, codes); /* window: detached before each raise inside */
956
1006
  hm = ca_mask_ptr(h);
957
1007
  cm = ca_mask_ptr(codes);
958
1008
  countp = (int64_t *) counts->ptr;
@@ -1118,8 +1168,7 @@ rb_ca_fiber_scatter_prod (VALUE self, VALUE rcodes, VALUE raxis,
1118
1168
  band_size = outer_prod * inner_prod;
1119
1169
  total = (ca_size_t)(K * band_size);
1120
1170
 
1121
- ca_attach(h);
1122
- ca_attach(codes);
1171
+ ca_attach_n(2, h, codes); /* window: detached before each raise inside */
1123
1172
  hm = ca_mask_ptr(h);
1124
1173
  cm = ca_mask_ptr(codes);
1125
1174
  outp = (double *) out->ptr;
@@ -1227,6 +1276,7 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1227
1276
  ca_size_t ax, inn, outer, cell;
1228
1277
  ca_size_t axis_size, inner_prod, outer_prod, band_size, total;
1229
1278
  double *wp, *wsp, *wsw, *wmp;
1279
+ volatile VALUE hwsw = 0, hcnt = 0;
1230
1280
  boolean8_t *hm, *cm, *wm, *wmm;
1231
1281
  int8_t i, j;
1232
1282
  int64_t *cnt_scratch = NULL;
@@ -1287,9 +1337,7 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1287
1337
  band_size = outer_prod * inner_prod;
1288
1338
  total = (ca_size_t)(K * band_size);
1289
1339
 
1290
- ca_attach(h);
1291
- ca_attach(codes);
1292
- ca_attach(weights);
1340
+ ca_attach_n(3, h, codes, weights); /* window: detached before each raise inside */
1293
1341
  hm = ca_mask_ptr(h);
1294
1342
  cm = ca_mask_ptr(codes);
1295
1343
  wm = ca_mask_ptr(weights);
@@ -1298,8 +1346,9 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1298
1346
  wmp = (double *) wmean->ptr; /* wmean output (temp = sum-of-weights, then divide) */
1299
1347
 
1300
1348
  /* Two auxiliary scratches: sum-of-weights (per cell) and present-pair count. */
1301
- wsw = (double *) xmalloc((size_t) total * sizeof(double));
1302
- cnt_scratch = (int64_t *) xmalloc((size_t) total * sizeof(int64_t));
1349
+ /* ALLOCV, so a raise below (codes of the wrong type) leaves nothing. */
1350
+ wsw = ALLOCV_N(double, hwsw, total);
1351
+ cnt_scratch = ALLOCV_N(int64_t, hcnt, total);
1303
1352
 
1304
1353
  memset(wsp, 0, (size_t) total * sizeof(double));
1305
1354
  memset(wsw, 0, (size_t) total * sizeof(double));
@@ -1318,7 +1367,7 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1318
1367
  case CA_FLOAT32: FIBER_SCATTER_WSUM_DISPATCH_C(float32_t); break;
1319
1368
  case CA_FLOAT64: FIBER_SCATTER_WSUM_DISPATCH_C(float64_t); break;
1320
1369
  default:
1321
- xfree(wsw); xfree(cnt_scratch);
1370
+ ALLOCV_END(hwsw); ALLOCV_END(hcnt);
1322
1371
  ca_detach(h); ca_detach(codes); ca_detach(weights);
1323
1372
  rb_raise(rb_eCADataTypeError,
1324
1373
  "__fiber_scatter_wsum_wmean__: numeric value required (got %d)",
@@ -1337,8 +1386,8 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1337
1386
  }
1338
1387
  }
1339
1388
 
1340
- xfree(wsw);
1341
- xfree(cnt_scratch);
1389
+ ALLOCV_END(hwsw);
1390
+ ALLOCV_END(hcnt);
1342
1391
  ca_detach(h);
1343
1392
  ca_detach(codes);
1344
1393
  ca_detach(weights);
data/ext/ca_compare.c CHANGED
@@ -14,6 +14,7 @@
14
14
  #include "ruby.h"
15
15
  #include "carray.h"
16
16
  #include "ca_compare.h"
17
+ #include "carray_internal.h" /* ca_obj_cmp, CA_OBJ_ISNAN */
17
18
  #include <math.h>
18
19
 
19
20
  #define DEFINE_ELEM_CMP(type) \
@@ -53,13 +54,18 @@ DEFINE_ELEM_CMP(uint64_t)
53
54
  DEFINE_ELEM_CMP_FLOAT(float32_t)
54
55
  DEFINE_ELEM_CMP_FLOAT(float64_t)
55
56
 
56
- /* CA_OBJECT: compare via Ruby Comparable (`<=>`). */
57
+ /* CA_OBJECT: compare by `<=>`, as sort orders object cells: a Float NaN
58
+ after every other value, and a pair `<=>` cannot order raises
59
+ "comparison of A with B failed". */
57
60
  static int
58
61
  elem_cmp_VALUE (const void *va, const void *vb)
59
62
  {
60
63
  const VALUE *a = (const VALUE *) va;
61
64
  const VALUE *b = (const VALUE *) vb;
62
- return NUM2INT(rb_funcall(*a, rb_intern("<=>"), 1, *b));
65
+ int nan_a = CA_OBJ_ISNAN(*a);
66
+ int nan_b = CA_OBJ_ISNAN(*b);
67
+ if ( nan_a || nan_b ) return nan_a - nan_b;
68
+ return ca_obj_cmp(*a, *b);
63
69
  }
64
70
 
65
71
  /* FIXLEN / retired / otherwise unorderable data_types: callers must handle
data/ext/ca_for_buffer.h CHANGED
@@ -1,43 +1,33 @@
1
1
  /* ---------------------------------------------------------------------------
2
2
  *
3
- * ca_for_buffer.h -- sweep WHOLE_BUFFER macro family (whole contig buffer
4
- * handed to author / third-party library)
3
+ * ca_for_buffer.h -- whole contig buffer handed to author / third-party
4
+ * library
5
5
  *
6
- * PROPOSAL_L0_AUTHOR_SURFACE L0.2c (2026-06-11). C-side counterpart of
7
- * Ruby's `ca.attach! { |a| ... }` block: scope the ca_attach + ca_sync +
8
- * ca_detach lifecycle to a block, hand the author a contig buffer ptr
9
- * and element count, let the author do whatever (typically pass the
10
- * ptr to a third-party library: FFTW, akima init, fitpack surf1, ...).
6
+ * C-side counterpart of Ruby's `ca.attach! { |a| ... }` block: the
7
+ * attach / sync / detach lifecycle is scoped to a body function, which
8
+ * receives a contig buffer ptr and element count and may do whatever
9
+ * (typically pass the ptr to a third-party library: FFTW, akima init,
10
+ * fitpack surf1, ...).
11
11
  *
12
- * Two forms (Q8 = M1 alias-when-possible; non-contig source materialises
13
- * into a transient buffer; writable form syncs back on exit):
12
+ * The buffer aliases ca->ptr when the array is a contig entity and is a
13
+ * materialised scratch otherwise. A writable call syncs the buffer back
14
+ * to the array's storage when the body returns.
14
15
  *
15
- * CA_WITH_BUFFER(ca, T, ptr, n)
16
- * Read-only access. ptr : T const * pointing at native contig
17
- * layout of `ca` (= ca->ptr alias when contig entity, scratch
18
- * otherwise). n : ca->elements. No write-back on exit.
19
- *
20
- * CA_WITH_BUFFER_WRITABLE(ca, T, ptr, n)
21
- * Writable; same ptr/n semantics, plus ca_sync on block exit so
22
- * author writes propagate back to the view's storage.
16
+ * The body may raise. The view is closed however the body is left: a
17
+ * writable view is synced first, so what the body wrote before raising
18
+ * reaches the view's storage, and the view is detached even if that sync
19
+ * raises. The body's exception is the one that propagates.
23
20
  *
24
21
  * Author pattern:
25
22
  *
26
- * double *ptr;
27
- * ca_size_t n;
28
- * CA_WITH_BUFFER_WRITABLE(ca, double, ptr, n) {
23
+ * static void
24
+ * fft_body (void *user_data, void *ptr, ca_size_t n)
25
+ * {
26
+ * fftw_plan plan = (fftw_plan) user_data;
29
27
  * fftw_execute_dft(plan, ptr, ptr);
30
28
  * }
31
29
  *
32
- * Constraints (same as CA_FOR_EACH_FIBER family):
33
- * - `break;` from body exits cleanly (= outer for's advance clause
34
- * runs ca_sync / ca_detach).
35
- * - `return;` from body LEAKS the attach; restructure to break.
36
- * - Macros are NOT statement-equivalent (= nested for); no trailing
37
- * `else`.
38
- * - For Ruby-exception-safe lifecycle (e.g. when calling Ruby code or
39
- * anything that may raise from inside the body), use the function
40
- * form `rb_ca_call_with_buffer` instead (= rb_ensure-protected).
30
+ * rb_ca_call_with_buffer(rca, 1, fft_body, plan);
41
31
  *
42
32
  * --------------------------------------------------------------------------- */
43
33
 
@@ -47,46 +37,9 @@
47
37
  #include "carray.h"
48
38
  #include "ca_sweep_engine.h"
49
39
 
50
- /* ---------- macro forms ---------- */
51
-
52
- /* Read-only: ca_attach (alias-when-possible) + author body + ca_detach. */
53
- #define CA_WITH_BUFFER(_ca, T, _ptr, _n) \
54
- for ( CArray *__cwv_ca = (CArray *)(_ca); \
55
- __cwv_ca; \
56
- ca_detach(__cwv_ca), __cwv_ca = NULL ) \
57
- for ( int __cwv_once = (ca_attach(__cwv_ca), \
58
- (_ptr) = (T *)__cwv_ca->ptr, \
59
- (_n) = __cwv_ca->elements, \
60
- 1); \
61
- __cwv_once; \
62
- __cwv_once = 0 )
63
-
64
- /* Writable: same as above + ca_sync on block exit. */
65
- #define CA_WITH_BUFFER_WRITABLE(_ca, T, _ptr, _n) \
66
- for ( CArray *__cwvw_ca = (CArray *)(_ca); \
67
- __cwvw_ca; \
68
- ca_sync(__cwvw_ca), ca_detach(__cwvw_ca), __cwvw_ca = NULL ) \
69
- for ( int __cwvw_once = (ca_attach(__cwvw_ca), \
70
- (_ptr) = (T *)__cwvw_ca->ptr, \
71
- (_n) = __cwvw_ca->elements, \
72
- 1); \
73
- __cwvw_once; \
74
- __cwvw_once = 0 )
75
-
76
- /* ---------- function form (rb_ensure-protected, AC8) ----------
77
- *
78
- * Use this when the body may raise a Ruby exception (= calling rb_funcall,
79
- * type-checking with rb_check_type, indirect Ruby code, etc.). The
80
- * engine ca_attach's the view, runs body via rb_ensure, then guarantees
81
- * ca_sync (if writable) and ca_detach run before the exception
82
- * propagates.
83
- *
84
- * body_fn(user_data, ptr, n_elements) -> may raise
40
+ /* body_fn(user_data, ptr, n_elements) -> may raise
85
41
  *
86
- * Returns whatever body_fn returns via its own propagation (= the
87
- * function itself returns Qnil since rb_ensure body must be VALUE).
88
- * For richer return semantics, box your result in user_data.
89
- */
42
+ * Returns nothing; box a result in user_data. */
90
43
  typedef void (*ca_with_buffer_body_fn) (void *user_data, void *ptr,
91
44
  ca_size_t n_elements);
92
45
 
data/ext/ca_group_iter.c CHANGED
@@ -40,10 +40,14 @@ static ID id_axis_group_reduce, id_reshape, id_transpose, id_labels;
40
40
  static VALUE
41
41
  rb_ca_register_axis_group_classes (VALUE klass, VALUE cat, VALUE ag)
42
42
  {
43
+ static int registered = 0;
43
44
  rb_cCategorical = cat;
44
45
  rb_cAxisGroup = ag;
45
- rb_gc_register_address(&rb_cCategorical);
46
- rb_gc_register_address(&rb_cAxisGroup);
46
+ if ( ! registered ) { /* one GC root per address, not per call */
47
+ rb_gc_register_address(&rb_cCategorical);
48
+ rb_gc_register_address(&rb_cAxisGroup);
49
+ registered = 1;
50
+ }
47
51
  return Qnil;
48
52
  }
49
53
 
@@ -172,7 +176,7 @@ group_iter_shape_output (VALUE result, VALUE group_dims, VALUE perm, VALUE squee
172
176
  nband = cr->ndim - 1;
173
177
  nd = ng + nband;
174
178
  if ( nd > CA_RANK_MAX ) {
175
- rb_raise(rb_eRuntimeError, "axis_group: output ndim %ld too large", nd);
179
+ rb_raise(rb_eArgError, "axis_group: output ndim %ld too large", nd);
176
180
  }
177
181
  for ( i = 0; i < ng; i++ ) {
178
182
  dims[i] = RARRAY_AREF(group_dims, i);
@@ -233,17 +237,19 @@ group_iter_reduce (int argc, VALUE *argv, VALUE self)
233
237
  parsed = rb_funcall(rb_cAxisGroup, id_parse_axis,
234
238
  1, (vaxis == Qundef) ? Qnil : vaxis);
235
239
  if ( RTEST(RARRAY_AREF(parsed, 0)) == 0 ) {
236
- /* no :group -> plain reduction on the value (grouping not engaged) */
237
- if ( vaxis == Qundef ) {
240
+ /* no :group -> plain reduction on the value (grouping not engaged),
241
+ with every keyword the caller gave */
242
+ if ( NIL_P(kw) ) {
238
243
  return rb_funcall(value, op_id, 0);
239
244
  }
240
- else {
241
- VALUE h = rb_hash_new();
242
- rb_hash_aset(h, ID2SYM(rb_intern("axis")), vaxis);
243
- return rb_funcallv_kw(value, op_id, 1, &h, RB_PASS_KEYWORDS);
244
- }
245
+ return rb_funcallv_kw(value, op_id, 1, &kw, RB_PASS_KEYWORDS);
245
246
  }
246
247
 
248
+ /* min_count: / fill_value: are applied to the grouped result; any other
249
+ keyword is refused before the work is done. */
250
+ rb_funcall(rb_cAxisGroup, rb_intern("__check_reduce_keywords__"), 2,
251
+ op_sym, NIL_P(kw) ? rb_hash_new() : kw);
252
+
247
253
  /* group reduction */
248
254
  fused = RARRAY_AREF(parsed, 1);
249
255
  plan = rb_funcall(spec, id_reduce_plan, 1, fused);
@@ -253,9 +259,19 @@ group_iter_reduce (int argc, VALUE *argv, VALUE self)
253
259
  perm = RARRAY_AREF(plan, 3);
254
260
  squeeze = RARRAY_AREF(plan, 4);
255
261
 
262
+ /* a count reads only the mask, so a payload the kernel cannot read
263
+ (object, fixlen, ...) is counted through a boolean stand-in */
264
+ if ( op_id == rb_intern("count") || op_id == rb_intern("count_not_masked") ) {
265
+ value = rb_funcall(rb_cAxisGroup, rb_intern("__count_stand_in__"), 1, value);
266
+ }
256
267
  result = rb_funcall(value, id_axis_group_reduce, 3,
257
268
  group_axes, bundles, op_sym);
258
- return group_iter_shape_output(result, group_dims, perm, squeeze);
269
+ result = group_iter_shape_output(result, group_dims, perm, squeeze);
270
+ if ( ! NIL_P(kw) ) {
271
+ result = rb_funcall(rb_cAxisGroup, rb_intern("__finish_reduction__"), 4,
272
+ self, op_sym, kw, result);
273
+ }
274
+ return result;
259
275
  }
260
276
 
261
277
  void