carray 3.0.0 → 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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +845 -3
  4. data/{NEWS.md → CHANGELOG.v1.md} +1 -1
  5. data/README.md +33 -33
  6. data/carray.gemspec +12 -8
  7. data/ext/ca_axis_dispatch.c +33 -4
  8. data/ext/ca_axis_group.c +256 -114
  9. data/ext/ca_binop_dispatch.c +5 -6
  10. data/ext/ca_binop_dispatch.h +0 -7
  11. data/ext/ca_categorical_iterator.c +121 -67
  12. data/ext/ca_for_each_element.h +6 -8
  13. data/ext/ca_group_iter.c +1 -1
  14. data/ext/ca_kernel_iterator.c +386 -74
  15. data/ext/ca_kernel_iterator.h +145 -45
  16. data/ext/ca_obj_array.c +62 -20
  17. data/ext/ca_obj_bincmp.c +57 -27
  18. data/ext/ca_obj_binop.c +86 -30
  19. data/ext/ca_obj_block.c +4 -4
  20. data/ext/ca_obj_const_string.c +88 -27
  21. data/ext/ca_obj_face.c +104 -0
  22. data/ext/ca_obj_face.h +48 -1
  23. data/ext/ca_obj_fake.c +11 -0
  24. data/ext/ca_obj_fixlen_string.c +21 -6
  25. data/ext/ca_obj_grid.c +11 -3
  26. data/ext/ca_obj_meld.c +125 -22
  27. data/ext/ca_obj_moncmp.c +33 -14
  28. data/ext/ca_obj_monop.c +19 -7
  29. data/ext/ca_obj_object.c +107 -4
  30. data/ext/ca_obj_record.c +3 -1
  31. data/ext/ca_obj_refer.c +30 -6
  32. data/ext/ca_obj_roll.c +13 -5
  33. data/ext/ca_obj_select.c +49 -34
  34. data/ext/ca_obj_select_axis.c +16 -6
  35. data/ext/ca_obj_shift.c +3 -3
  36. data/ext/ca_obj_stack.c +3 -8
  37. data/ext/ca_obj_stride.c +178 -24
  38. data/ext/ca_obj_string.c +11 -5
  39. data/ext/ca_obj_tile.c +13 -5
  40. data/ext/ca_obj_time.c +3 -1
  41. data/ext/ca_obj_timedelta.c +3 -1
  42. data/ext/ca_obj_transpose.c +1 -1
  43. data/ext/ca_obj_triop.c +62 -23
  44. data/ext/ca_obj_window.c +94 -19
  45. data/ext/ca_op_cmplx64.h +123 -0
  46. data/ext/ca_op_ipower.c +1 -5
  47. data/ext/ca_rng_normal.h +42 -0
  48. data/ext/ca_rng_xoshiro256pp.h +105 -0
  49. data/ext/ca_sort_kernels.h +5 -5
  50. data/ext/ca_sweep_engine.c +362 -155
  51. data/ext/ca_sweep_engine.h +39 -9
  52. data/ext/ca_transform_common.c +7 -0
  53. data/ext/carray.h +148 -132
  54. data/ext/carray_access.c +83 -49
  55. data/ext/carray_address_basis.c +590 -0
  56. data/ext/carray_bincount.c +8 -8
  57. data/ext/carray_broadcast.c +102 -9
  58. data/ext/carray_build_flags.h +3 -0
  59. data/ext/carray_call_cfunc.c +2010 -335
  60. data/ext/carray_call_cfunc.h +153 -0
  61. data/ext/carray_cast.c +184 -72
  62. data/ext/carray_conversion.c +22 -22
  63. data/ext/carray_copy.c +67 -32
  64. data/ext/carray_core.c +140 -13
  65. data/ext/carray_count.c +10 -11
  66. data/ext/carray_element.c +7 -7
  67. data/ext/carray_factorize.c +72 -51
  68. data/ext/carray_hold.c +1 -1
  69. data/ext/carray_index_classifier.c +7 -21
  70. data/ext/carray_internal.h +36 -3
  71. data/ext/carray_kernels_bincmp.c +1 -0
  72. data/ext/carray_kernels_binop.c +320 -298
  73. data/ext/carray_kernels_init.c +1095 -0
  74. data/ext/carray_kernels_map.c +1 -0
  75. data/ext/carray_kernels_moncmp.c +1 -0
  76. data/ext/carray_kernels_monop.c +412 -411
  77. data/ext/carray_kernels_reduce_aggregate.c +256 -87
  78. data/ext/carray_kernels_reduce_boolean.c +1 -0
  79. data/ext/carray_kernels_reduce_cumulative.c +324 -54
  80. data/ext/carray_kernels_reduce_extreme.c +605 -58
  81. data/ext/carray_kernels_reduce_variance.c +1 -0
  82. data/ext/carray_kernels_scan.c +5 -4
  83. data/ext/carray_kernels_search.c +187 -106
  84. data/ext/carray_kernels_sort.c +1 -0
  85. data/ext/carray_kernels_triop.c +1 -0
  86. data/ext/carray_lazy.c +175 -5
  87. data/ext/carray_loop.c +7 -1
  88. data/ext/carray_mask.c +29 -13
  89. data/ext/carray_median_percentile.c +56 -1
  90. data/ext/carray_memory_view.c +46 -21
  91. data/ext/carray_operator.c +50 -53
  92. data/ext/carray_order.c +1 -1
  93. data/ext/carray_partition.c +4 -4
  94. data/ext/carray_random.c +391 -47
  95. data/ext/carray_scatter.c +1 -1
  96. data/ext/carray_slab.c +13 -0
  97. data/ext/carray_sort.c +22 -24
  98. data/ext/carray_sort_kernel.c +10 -10
  99. data/ext/carray_test.c +1 -1
  100. data/ext/extconf.rb +18 -0
  101. data/ext/mk_call_cfunc.rb +323 -145
  102. data/ext/mkkernel.rb +651 -129
  103. data/ext/ruby_carray.c +28 -17
  104. data/ext/version.h +4 -4
  105. data/lib/carray/autoload_carray.rb +24 -3
  106. data/lib/carray/autoload_method_extension.rb +14 -1
  107. data/lib/carray/axis_group.rb +84 -7
  108. data/lib/carray/basics.rb +63 -59
  109. data/lib/carray/bincount_nd.rb +12 -12
  110. data/lib/carray/block_iterator.rb +111 -25
  111. data/lib/carray/categorical.rb +154 -37
  112. data/lib/carray/categorical_iterator.rb +300 -151
  113. data/lib/carray/conditional.rb +14 -14
  114. data/lib/carray/const_string.rb +131 -27
  115. data/lib/carray/construct.rb +61 -0
  116. data/lib/carray/core_extensions.rb +62 -5
  117. data/lib/carray/data_type_extension.rb +21 -1
  118. data/lib/carray/data_type_limits.rb +91 -0
  119. data/lib/carray/fixlen_string.rb +1 -1
  120. data/lib/carray/frame/concat.rb +3 -3
  121. data/lib/carray/frame/convert.rb +1 -1
  122. data/lib/carray/frame/csv_parser.rb +11 -4
  123. data/lib/carray/frame/frame.rb +85 -21
  124. data/lib/carray/frame/group.rb +36 -3
  125. data/lib/carray/frame/io.rb +224 -15
  126. data/lib/carray/frame/records.rb +18 -4
  127. data/lib/carray/frame/sort.rb +1 -1
  128. data/lib/carray/frame/verbs.rb +32 -12
  129. data/lib/carray/fuse_source.rb +123 -0
  130. data/lib/carray/fusion.rb +218 -0
  131. data/lib/carray/histogram.rb +16 -16
  132. data/lib/carray/inspect.rb +43 -16
  133. data/lib/carray/iterator.rb +147 -3
  134. data/lib/carray/lazy.rb +88 -73
  135. data/lib/carray/mask_gap_fill.rb +3 -1
  136. data/lib/carray/meld_reduce.rb +2 -2
  137. data/lib/carray/methods/align_addr.rb +1 -1
  138. data/lib/carray/methods/composition.rb +1 -1
  139. data/lib/carray/methods/discovery_along.rb +74 -0
  140. data/lib/carray/methods/factorize.rb +50 -0
  141. data/lib/carray/methods/is_in.rb +25 -14
  142. data/lib/carray/methods/locate_addr.rb +81 -3
  143. data/lib/carray/methods/mask_duplicates.rb +36 -2
  144. data/lib/carray/methods/meshgrid.rb +4 -5
  145. data/lib/carray/methods/mode.rb +2 -2
  146. data/lib/carray/methods/nunique.rb +23 -2
  147. data/lib/carray/methods/repeat.rb +110 -0
  148. data/lib/carray/methods/snap.rb +7 -2
  149. data/lib/carray/methods/unique.rb +44 -4
  150. data/lib/carray/methods/value_counts.rb +2 -2
  151. data/lib/carray/rng.rb +86 -0
  152. data/lib/carray/runtime.rb +0 -19
  153. data/lib/carray/slab_iterator.rb +77 -19
  154. data/lib/carray/string_operation_extension.rb +10 -6
  155. data/lib/carray/time.rb +906 -491
  156. data/lib/carray/window_iterator.rb +421 -27
  157. data/lib/carray.rb +7 -5
  158. data/yard-stubs/ca_obj_array.rb +385 -0
  159. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  160. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  161. data/yard-stubs/ca_obj_block.rb +68 -0
  162. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  163. data/yard-stubs/ca_obj_fake.rb +31 -0
  164. data/yard-stubs/ca_obj_farray.rb +32 -0
  165. data/yard-stubs/ca_obj_field.rb +45 -0
  166. data/yard-stubs/ca_obj_grid.rb +35 -0
  167. data/yard-stubs/ca_obj_refer.rb +72 -0
  168. data/yard-stubs/ca_obj_roll.rb +45 -0
  169. data/yard-stubs/ca_obj_shift.rb +43 -0
  170. data/yard-stubs/ca_obj_stride.rb +181 -0
  171. data/yard-stubs/ca_obj_tile.rb +29 -0
  172. data/yard-stubs/ca_obj_transpose.rb +40 -0
  173. data/yard-stubs/ca_obj_window.rb +57 -0
  174. data/yard-stubs/carray_access.rb +131 -0
  175. data/yard-stubs/carray_attribute.rb +246 -0
  176. data/yard-stubs/carray_broadcast.rb +37 -0
  177. data/yard-stubs/carray_cast.rb +489 -0
  178. data/yard-stubs/carray_class.rb +65 -0
  179. data/yard-stubs/carray_conversion.rb +76 -0
  180. data/yard-stubs/carray_copy.rb +79 -0
  181. data/yard-stubs/carray_core.rb +34 -0
  182. data/yard-stubs/carray_count.rb +84 -0
  183. data/yard-stubs/carray_element.rb +108 -0
  184. data/yard-stubs/carray_generate.rb +66 -0
  185. data/yard-stubs/carray_lazy.rb +228 -0
  186. data/yard-stubs/carray_loop.rb +140 -0
  187. data/yard-stubs/carray_mask.rb +259 -0
  188. data/yard-stubs/carray_math.rb +1615 -0
  189. data/yard-stubs/carray_mathfunc.rb +45 -0
  190. data/yard-stubs/carray_median_percentile.rb +103 -0
  191. data/yard-stubs/carray_memory_view.rb +163 -0
  192. data/yard-stubs/carray_order.rb +252 -0
  193. data/yard-stubs/carray_random.rb +89 -0
  194. data/yard-stubs/carray_scatter.rb +106 -0
  195. data/yard-stubs/carray_slab.rb +59 -0
  196. data/yard-stubs/carray_sort.rb +165 -0
  197. data/yard-stubs/carray_test.rb +85 -0
  198. data/yard-stubs/carray_undef.rb +64 -0
  199. data/yard-stubs/carray_utils.rb +97 -0
  200. data/yard-stubs/ruby_carray.rb +193 -0
  201. metadata +67 -10
  202. data/ext/ca_obj_unbound_repeat.c +0 -496
@@ -14,9 +14,9 @@
14
14
  operands arrive at the common type. Two ops break the uniform rule:
15
15
 
16
16
  QUO -- only has a CA_OBJECT kernel; both operands promoted to
17
- CA_OBJECT regardless of input dtypes
17
+ CA_OBJECT regardless of input data types
18
18
  IPOWER -- heterogeneous (left: float/cmplx preserved, right: int64);
19
- output preserves left dtype. See ca_op_ipower.c.
19
+ output preserves left data type. See ca_op_ipower.c.
20
20
 
21
21
  ---------------------------------------------------------------------------- */
22
22
 
@@ -58,7 +58,6 @@ ca_binop_kernel_lookup_vv (uint16_t op_id, int8_t common_dt)
58
58
  case CA_BINOP_AND: return ca_binop_and [common_dt];
59
59
  case CA_BINOP_OR: return ca_binop_or [common_dt];
60
60
  case CA_BINOP_XOR: return ca_binop_xor [common_dt];
61
- case CA_BINOP_REMINDER: return ca_binop_reminder [common_dt];
62
61
  default: return NULL;
63
62
  }
64
63
  }
@@ -107,8 +106,8 @@ ca_binop_kernel_input_data_types (uint16_t op_id, int8_t l_dt, int8_t r_dt,
107
106
  int
108
107
  ca_binop_is_trapping (uint16_t op_id, int8_t common_dt)
109
108
  {
110
- /* Integer DIV / MOD / QUO can SIGFPE on a zero divisor. Float DIV
111
- returns NaN/Inf and does NOT trap, so it is not classified
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
112
111
  trapping. */
113
112
  int is_integer = ( common_dt >= CA_INT8 && common_dt <= CA_UINT64 );
114
113
  if ( ! is_integer ) return 0;
@@ -117,7 +116,7 @@ ca_binop_is_trapping (uint16_t op_id, int8_t common_dt)
117
116
  case CA_BINOP_DIV:
118
117
  case CA_BINOP_MOD:
119
118
  case CA_BINOP_QUO:
120
- case CA_BINOP_REMINDER:
119
+ case CA_BINOP_FMOD:
121
120
  return 1;
122
121
  default:
123
122
  return 0;
@@ -80,12 +80,6 @@ enum {
80
80
  CA_BINOP_OR = 25,
81
81
  CA_BINOP_XOR = 26,
82
82
 
83
- /* IEEE 754 remainder (round-half-to-even, distinct from MOD which
84
- truncates toward zero via C99 fmod on the float branch). Integer
85
- branch is identical to MOD (`%`). All numeric + object. Traps on
86
- integer zero divisor, like MOD. */
87
- CA_BINOP_REMINDER = 27,
88
-
89
83
  CA_BINOP_COUNT
90
84
  };
91
85
 
@@ -127,7 +121,6 @@ extern ca_binop_func_t ca_binop_minimum [CA_NTYPE];
127
121
  extern ca_binop_func_t ca_binop_and [CA_NTYPE];
128
122
  extern ca_binop_func_t ca_binop_or [CA_NTYPE];
129
123
  extern ca_binop_func_t ca_binop_xor [CA_NTYPE];
130
- extern ca_binop_func_t ca_binop_reminder [CA_NTYPE];
131
124
 
132
125
  /* ------------------------------------------------------------------- */
133
126
  /* Dispatch API */
@@ -21,14 +21,14 @@
21
21
  kernel_iterator macros do not model, so the flat inputs are materialised here:
22
22
  ca_attach aliases a contiguous entity (codes / a contiguous value) and gathers
23
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 dtype, so no value-dtype
24
+ move is a bytes-wide memcpy (grouped shares the value data type, so no value-type
25
25
  dispatch is needed).
26
26
 
27
27
  Surface (private): codes.__categorical_scatter__(value, cursor, grouped, k)
28
28
  self = codes (integer, carries the exclusion mask), read flat
29
- value = payload (any dtype, may carry a mask), read flat, same length
29
+ value = payload (any data type, may carry a mask), read flat, same length
30
30
  cursor = int64 length-k segment starts (mutated in place, consumed)
31
- grouped = pre-allocated contiguous entity of the value dtype, length nvalid
31
+ grouped = pre-allocated contiguous entity of the value data type, length nvalid
32
32
  k = number of categories
33
33
  Returns grouped.
34
34
 
@@ -79,7 +79,7 @@ rb_ca_categorical_scatter (VALUE self, VALUE rvalue, VALUE rcursor,
79
79
  rb_raise(rb_eArgError, "__categorical_scatter__: cursor must be int64[k]");
80
80
  }
81
81
  if ( grouped->bytes != bytes ) {
82
- rb_raise(rb_eArgError, "__categorical_scatter__: grouped/value dtype mismatch");
82
+ rb_raise(rb_eArgError, "__categorical_scatter__: grouped/value data type mismatch");
83
83
  }
84
84
 
85
85
  ca_attach(codes);
@@ -127,18 +127,31 @@ rb_ca_categorical_scatter (VALUE self, VALUE rvalue, VALUE rcursor,
127
127
  (empty / all-masked -> sum 0 identity, count 0, min/max masked).
128
128
 
129
129
  Surface (private): grouped.__reduceat_moments__(offsets, counts, sums, mins, maxs)
130
- self = grouped (numeric value dtype, may carry a mask), contiguous entity
130
+ self = grouped (numeric value data type, may carry a mask), contiguous entity
131
131
  offsets = int64[k] segment STARTS; segment c = [offsets[c], offsets[c+1]),
132
132
  the last ends at grouped.elements
133
133
  counts = int64[k] output: present (non-masked) cells per segment
134
134
  sums = float64[k] output: sum per segment (0 for empty, unmasked)
135
- mins/maxs = value-dtype[k] output: min / max per segment; the kernel masks
135
+ mins/maxs = value-type[k] output: min / max per segment; the kernel masks
136
136
  the empty/all-masked segments (no value to report)
137
137
  Derived on the Ruby side: mean = sum/count, count_masked = sizes - count, etc.
138
138
 
139
139
  --------------------------------------------------------------------------- */
140
140
 
141
- #define REDUCEAT_MOMENTS_BODY(T) \
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
+
154
+ #define REDUCEAT_MOMENTS_BODY(T, ISNAN) \
142
155
  do { \
143
156
  const T *gp = (const T *) grouped->ptr; \
144
157
  T *minv = (T *) minp, *maxv = (T *) maxp; \
@@ -147,13 +160,17 @@ rb_ca_categorical_scatter (VALUE self, VALUE rvalue, VALUE rcursor,
147
160
  ca_size_t hi = (c + 1 < k) ? (ca_size_t) offs[c+1] : n; \
148
161
  ca_size_t j, cnt = 0; \
149
162
  double sacc = 0.0; \
150
- T mn = 0, mx = 0; int seen = 0; \
163
+ T mn = 0, mx = 0; int seen = 0, seen_num = 0; \
151
164
  for ( j = lo; j < hi; j++ ) { \
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 ( ! 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; } \
171
+ } \
172
+ else if ( ! seen ) { mn = v; mx = v; } /* held until a number lands */\
173
+ seen = 1; \
157
174
  cnt++; } \
158
175
  } \
159
176
  countp[c] = (int64_t) cnt; \
@@ -190,7 +207,7 @@ rb_ca_reduceat_moments (VALUE self, VALUE roffsets, VALUE rcounts,
190
207
  if ( counts->elements != k || sums->elements != k ||
191
208
  mins->elements != k || maxs->elements != k ||
192
209
  mins->data_type != grouped->data_type || maxs->data_type != grouped->data_type ) {
193
- rb_raise(rb_eArgError, "__reduceat_moments__: output shape/dtype mismatch");
210
+ rb_raise(rb_eArgError, "__reduceat_moments__: output shape/data type mismatch");
194
211
  }
195
212
 
196
213
  offs = (int64_t *) offsets->ptr;
@@ -205,16 +222,16 @@ rb_ca_reduceat_moments (VALUE self, VALUE roffsets, VALUE rcounts,
205
222
  maxm = (boolean8_t *) maxs->mask->ptr;
206
223
 
207
224
  switch ( grouped->data_type ) {
208
- case CA_INT8: REDUCEAT_MOMENTS_BODY(int8_t); break;
209
- case CA_UINT8: REDUCEAT_MOMENTS_BODY(uint8_t); break;
210
- case CA_INT16: REDUCEAT_MOMENTS_BODY(int16_t); break;
211
- case CA_UINT16: REDUCEAT_MOMENTS_BODY(uint16_t); break;
212
- case CA_INT32: REDUCEAT_MOMENTS_BODY(int32_t); break;
213
- case CA_UINT32: REDUCEAT_MOMENTS_BODY(uint32_t); break;
214
- case CA_INT64: REDUCEAT_MOMENTS_BODY(int64_t); break;
215
- case CA_UINT64: REDUCEAT_MOMENTS_BODY(uint64_t); break;
216
- case CA_FLOAT32: REDUCEAT_MOMENTS_BODY(float32_t); break;
217
- case CA_FLOAT64: REDUCEAT_MOMENTS_BODY(float64_t); break;
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;
218
235
  default:
219
236
  rb_raise(rb_eCADataTypeError,
220
237
  "__reduceat_moments__: numeric value required (got data_type %d)",
@@ -235,7 +252,7 @@ rb_ca_reduceat_moments (VALUE self, VALUE roffsets, VALUE rcounts,
235
252
  (f = (m-1)*p/100, k = floor(f), lo + (f-k)*(hi-lo)). No per-segment view.
236
253
 
237
254
  Surface (private): grouped.__reduceat_percentile__(offsets, p, out)
238
- self = grouped (numeric value dtype, may carry a mask)
255
+ self = grouped (numeric value data type, may carry a mask)
239
256
  offsets = int64[k] segment STARTS (last ends at grouped.elements)
240
257
  p = percentile in 0..100 (median = 50, quantile(q) = q*100)
241
258
  out = float64[k] output; empty / all-masked segments are masked
@@ -278,11 +295,27 @@ ca_nth_element_double (double *a, ca_size_t n, ca_size_t kth)
278
295
  { double f = (double) (m - 1) * p / 100.0; \
279
296
  ca_size_t ki = (ca_size_t) floor(f); \
280
297
  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 */ \
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
+ } \
312
+ if ( ki < nnum ) { \
313
+ ca_nth_element_double(scratch, nnum, ki); /* scratch[ki] = ki-th */ \
314
+ vlo = scratch[ki]; \
315
+ } else vlo = (double) NAN; \
316
+ if ( ki + 1 < nnum ) { /* (ki+1)-th = min of upper partition */ \
284
317
  double mn = scratch[ki+1]; ca_size_t t; \
285
- for ( t = ki + 2; t < m; t++ ) if ( scratch[t] < mn ) mn = scratch[t]; \
318
+ for ( t = ki + 2; t < nnum; t++ ) if ( scratch[t] < mn ) mn = scratch[t]; \
286
319
  vhi = mn; \
287
320
  } else vhi = vlo; \
288
321
  outp[c] = vlo + (f - (double) ki) * (vhi - vlo); } \
@@ -354,7 +387,7 @@ rb_ca_reduceat_percentile (VALUE self, VALUE roffsets, VALUE rp, VALUE rout)
354
387
  SS / (count-1).
355
388
 
356
389
  Surface (private): grouped.__reduceat_variance__(offsets, means, counts, out)
357
- self = grouped (numeric value dtype, may carry a mask)
390
+ self = grouped (numeric value data type, may carry a mask)
358
391
  offsets = int64[k] segment STARTS
359
392
  means = float64[k] per-segment mean (ignored where count < 2)
360
393
  counts = int64[k] per-segment present count
@@ -510,7 +543,7 @@ rb_ca_reduceat_prod (VALUE self, VALUE roffsets, VALUE rout)
510
543
  /* __reduceat_argminmax__(offsets, min_idx, max_idx) — per-segment GROUP-LOCAL
511
544
  index of the min / max (position within the segment, first occurrence on
512
545
  ties). Empty / all-masked segments are masked. */
513
- #define REDUCEAT_ARGMINMAX_BODY(T) \
546
+ #define REDUCEAT_ARGMINMAX_BODY(T, ISNAN) \
514
547
  do { \
515
548
  const T *gp = (const T *) grouped->ptr; \
516
549
  for ( c = 0; c < k; c++ ) { \
@@ -520,10 +553,13 @@ rb_ca_reduceat_prod (VALUE self, VALUE roffsets, VALUE rout)
520
553
  for ( j = lo; j < hi; j++ ) { \
521
554
  if ( gm && gm[j] ) continue; \
522
555
  { T v = gp[j]; ca_size_t li = j - lo; \
556
+ if ( ISNAN(v) ) continue; /* wins no contest, so marks no position */\
523
557
  if ( ! seen ) { mn = mx = v; mni = mxi = li; seen = 1; } \
524
558
  else { if ( v < mn ) { mn = v; mni = li; } \
525
559
  if ( v > mx ) { mx = v; mxi = li; } } } \
526
560
  } \
561
+ /* A segment of nothing but NaN has no position to report, the same as a \
562
+ segment with no present cell at all -- both are UNDEF, as in the core.*/\
527
563
  if ( seen ) { minp[c] = (int64_t) mni; maxp[c] = (int64_t) mxi; } \
528
564
  else { minp[c] = 0; maxp[c] = 0; minm[c] = 1; maxm[c] = 1; } \
529
565
  } \
@@ -558,16 +594,16 @@ rb_ca_reduceat_argminmax (VALUE self, VALUE roffsets, VALUE rminidx, VALUE rmaxi
558
594
  maxm = (boolean8_t *) maxidx->mask->ptr;
559
595
 
560
596
  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;
597
+ case CA_INT8: REDUCEAT_ARGMINMAX_BODY(int8_t, CAT_NAN_NEVER); break;
598
+ case CA_UINT8: REDUCEAT_ARGMINMAX_BODY(uint8_t, CAT_NAN_NEVER); break;
599
+ case CA_INT16: REDUCEAT_ARGMINMAX_BODY(int16_t, CAT_NAN_NEVER); break;
600
+ case CA_UINT16: REDUCEAT_ARGMINMAX_BODY(uint16_t, CAT_NAN_NEVER); break;
601
+ case CA_INT32: REDUCEAT_ARGMINMAX_BODY(int32_t, CAT_NAN_NEVER); break;
602
+ case CA_UINT32: REDUCEAT_ARGMINMAX_BODY(uint32_t, CAT_NAN_NEVER); break;
603
+ case CA_INT64: REDUCEAT_ARGMINMAX_BODY(int64_t, CAT_NAN_NEVER); break;
604
+ case CA_UINT64: REDUCEAT_ARGMINMAX_BODY(uint64_t, CAT_NAN_NEVER); break;
605
+ case CA_FLOAT32: REDUCEAT_ARGMINMAX_BODY(float32_t, CAT_NAN_CHECK); break;
606
+ case CA_FLOAT64: REDUCEAT_ARGMINMAX_BODY(float64_t, CAT_NAN_CHECK); break;
571
607
  default:
572
608
  rb_raise(rb_eCADataTypeError,
573
609
  "__reduceat_argminmax__: numeric value required (got data_type %d)",
@@ -577,7 +613,7 @@ rb_ca_reduceat_argminmax (VALUE self, VALUE roffsets, VALUE rminidx, VALUE rmaxi
577
613
  }
578
614
 
579
615
  /* __reduceat_all_any__(offsets, all_out, any_out) — per-segment boolean AND / OR
580
- over present cells. Value dtype must be boolean. Empty segment: all -> true,
616
+ over present cells. Value data type must be boolean. Empty segment: all -> true,
581
617
  any -> false. */
582
618
  static VALUE
583
619
  rb_ca_reduceat_all_any (VALUE self, VALUE roffsets, VALUE rall, VALUE rany)
@@ -631,6 +667,12 @@ static int
631
667
  cmp_double (const void *a, const void *b)
632
668
  {
633
669
  double x = *(const double *) a, y = *(const double *) b;
670
+ /* NaN sorts last, which is where CArray's own sort puts it, so picking a
671
+ position out of the sorted segment gives the core's answer. A bare
672
+ < / > comparison returns 0 for every pair involving a NaN, which is not
673
+ a strict weak ordering and leaves qsort free to produce anything. */
674
+ if ( x != x ) return (y != y) ? 0 : 1;
675
+ if ( y != y ) return -1;
634
676
  return (x < y) ? -1 : (x > y) ? 1 : 0;
635
677
  }
636
678
 
@@ -648,13 +690,18 @@ cmp_double (const void *a, const void *b)
648
690
  } \
649
691
  if ( m == 0 ) { for ( t = 0; t < 5; t++ ) { outp[t][c] = 0.0; outm[t][c] = 1; } continue; } \
650
692
  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--; \
651
695
  for ( t = 0; t < 5; t++ ) { \
652
696
  double f = (double) (m - 1) * P[t] / 100.0; \
653
697
  ca_size_t ki = (ca_size_t) floor(f); \
654
698
  double vlo = scratch[ki]; \
655
- double vhi = (ki + 1 < m) ? scratch[ki+1] : vlo; \
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; \
656
703
  outp[t][c] = vlo + (f - (double) ki) * (vhi - vlo); \
657
- } \
704
+ } } \
658
705
  } \
659
706
  } while (0)
660
707
 
@@ -817,12 +864,12 @@ rb_ca_reduceat_wsum_wmean (VALUE self, VALUE roffsets, VALUE rwg,
817
864
  counts_out = int64, shape [K, ...H.band] (present cells per group)
818
865
  sums_out = float64, shape [K, ...H.band] (per-group sum, 0 for empty)
819
866
 
820
- Sums as float64 mirrors __reduceat_moments__; Ruby side casts to h dtype in
821
- #sum (matches existing empty→0 identity contract). Mins/maxs are in h dtype
867
+ Sums as float64 mirrors __reduceat_moments__; Ruby side casts to h data type in
868
+ #sum (matches existing empty→0 identity contract). Mins/maxs are in h data type
822
869
  (empty group cell → 0 + masked, matching __reduceat_moments__).
823
870
  --------------------------------------------------------------------------- */
824
871
 
825
- #define FIBER_SCATTER_BODY(H_T, C_T) \
872
+ #define FIBER_SCATTER_BODY(H_T, C_T, ISNAN) \
826
873
  do { \
827
874
  const H_T *hp = (const H_T *) h->ptr; \
828
875
  const C_T *cp = (const C_T *) codes->ptr; \
@@ -844,11 +891,18 @@ rb_ca_reduceat_wsum_wmean (VALUE self, VALUE roffsets, VALUE rwg,
844
891
  if ( c < 0 || c >= K ) continue; /* out-of-vocabulary */ \
845
892
  out_off = c * band_size + out_outer + inn; \
846
893
  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. */\
847
897
  if ( countp[out_off] == 0 ) { \
848
898
  minv[out_off] = v; maxv[out_off] = v; \
849
- } else { \
850
- if ( v < minv[out_off] ) minv[out_off] = v; \
851
- if ( v > maxv[out_off] ) 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
+ } \
852
906
  } \
853
907
  countp[out_off]++; \
854
908
  sump[out_off] += (double) v; \
@@ -857,16 +911,16 @@ rb_ca_reduceat_wsum_wmean (VALUE self, VALUE roffsets, VALUE rwg,
857
911
  } \
858
912
  } while (0)
859
913
 
860
- #define FIBER_SCATTER_DISPATCH_C(H_T) \
914
+ #define FIBER_SCATTER_DISPATCH_C(H_T, ISNAN) \
861
915
  switch ( codes->data_type ) { \
862
- case CA_INT8: FIBER_SCATTER_BODY(H_T, int8_t); break; \
863
- case CA_UINT8: FIBER_SCATTER_BODY(H_T, uint8_t); break; \
864
- case CA_INT16: FIBER_SCATTER_BODY(H_T, int16_t); break; \
865
- case CA_UINT16: FIBER_SCATTER_BODY(H_T, uint16_t); break; \
866
- case CA_INT32: FIBER_SCATTER_BODY(H_T, int32_t); break; \
867
- case CA_UINT32: FIBER_SCATTER_BODY(H_T, uint32_t); break; \
868
- case CA_INT64: FIBER_SCATTER_BODY(H_T, int64_t); break; \
869
- case CA_UINT64: FIBER_SCATTER_BODY(H_T, uint64_t); break; \
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; \
870
924
  default: \
871
925
  ca_detach(h); ca_detach(codes); \
872
926
  rb_raise(rb_eCADataTypeError, \
@@ -921,7 +975,7 @@ rb_ca_fiber_scatter_moments (VALUE self, VALUE rcodes, VALUE raxis, VALUE rk,
921
975
  }
922
976
  if ( mins->data_type != h->data_type || maxs->data_type != h->data_type ) {
923
977
  rb_raise(rb_eArgError,
924
- "__fiber_scatter_moments__: mins/maxs must match h dtype");
978
+ "__fiber_scatter_moments__: mins/maxs must match h data type");
925
979
  }
926
980
  if ( counts->ndim != h->ndim || sums->ndim != h->ndim ||
927
981
  mins->ndim != h->ndim || maxs->ndim != h->ndim ||
@@ -966,16 +1020,16 @@ rb_ca_fiber_scatter_moments (VALUE self, VALUE rcodes, VALUE raxis, VALUE rk,
966
1020
  memset(maxp, 0, (size_t) total * (size_t) h->bytes);
967
1021
 
968
1022
  switch ( h->data_type ) {
969
- case CA_INT8: FIBER_SCATTER_DISPATCH_C(int8_t); break;
970
- case CA_UINT8: FIBER_SCATTER_DISPATCH_C(uint8_t); break;
971
- case CA_INT16: FIBER_SCATTER_DISPATCH_C(int16_t); break;
972
- case CA_UINT16: FIBER_SCATTER_DISPATCH_C(uint16_t); break;
973
- case CA_INT32: FIBER_SCATTER_DISPATCH_C(int32_t); break;
974
- case CA_UINT32: FIBER_SCATTER_DISPATCH_C(uint32_t); break;
975
- case CA_INT64: FIBER_SCATTER_DISPATCH_C(int64_t); break;
976
- case CA_UINT64: FIBER_SCATTER_DISPATCH_C(uint64_t); break;
977
- case CA_FLOAT32: FIBER_SCATTER_DISPATCH_C(float32_t); break;
978
- case CA_FLOAT64: FIBER_SCATTER_DISPATCH_C(float64_t); break;
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;
979
1033
  default:
980
1034
  ca_detach(h); ca_detach(codes);
981
1035
  rb_raise(rb_eCADataTypeError,
@@ -125,8 +125,9 @@ typedef struct {
125
125
  * inner : per-cell loop within chunk_n
126
126
  *
127
127
  * Memory peak per AC2: INPUT non-alias views materialise into a single
128
- * chunk scratch (~32KB at f64). m0 (= masked form) is full size (=
129
- * n_kernel bytes) for simplicity; macro reads at m0[chunk_off + k].
128
+ * chunk scratch (~32KB at f64). m0 (= masked form) is chunk-sized too and
129
+ * re-gathered per chunk, so the macro reads at m0[k] -- indexed within the
130
+ * chunk, not by the flat cell index.
130
131
  *
131
132
  * For MASKED forms (m / m_in / m_out): when source has no mask, m0 is
132
133
  * NULL and m / m_in == 0 always. m_out writes during INOUT_MASKED are
@@ -162,8 +163,7 @@ typedef struct {
162
163
  && (((x) = *(T *)((_st1).core.base[0] \
163
164
  + __cfem_k * (_st1).core.stride[0])), \
164
165
  ((m) = (_st1).core.m0 \
165
- ? (_st1).core.m0[(_st1).core.chunk_off \
166
- + __cfem_k] \
166
+ ? (_st1).core.m0[__cfem_k] \
167
167
  : (boolean8_t)0), \
168
168
  1); \
169
169
  __cfem_k++ )
@@ -211,16 +211,14 @@ typedef struct {
211
211
  && (((in) = *(T_IN *)((_st2).core.base[0] \
212
212
  + __cfeim_k * (_st2).core.stride[0])), \
213
213
  ((m_in) = (_st2).core.m0 \
214
- ? (_st2).core.m0[(_st2).core.chunk_off \
215
- + __cfeim_k] \
214
+ ? (_st2).core.m0[__cfeim_k] \
216
215
  : (boolean8_t)0), \
217
216
  ((m_out) = (m_in)), \
218
217
  1); \
219
218
  (*(T_OUT *)((_st2).core.base[1] \
220
219
  + __cfeim_k * (_st2).core.stride[1]) = (out)), \
221
220
  (((_st2).core.m0) \
222
- ? ((_st2).core.m0[(_st2).core.chunk_off + __cfeim_k] \
223
- = (m_out)) \
221
+ ? ((_st2).core.m0[__cfeim_k] = (m_out)) \
224
222
  : (boolean8_t)0), \
225
223
  __cfeim_k++ )
226
224
 
data/ext/ca_group_iter.c CHANGED
@@ -292,7 +292,7 @@ Init_ca_group_iter (void)
292
292
  address of the extremum (there is no group-local min_index for the group
293
293
  iterator -- the order-preserving flat address is the meaningful position). */
294
294
  {
295
- const char *ops[] = { "sum", "prod", "mean", "min", "max",
295
+ const char *ops[] = { "sum", "accumulate", "prod", "mean", "min", "max",
296
296
  "variance", "stddev", "variancep", "stddevp",
297
297
  "count", "count_not_masked", "min_addr", "max_addr",
298
298
  "all", "any", NULL };