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
@@ -114,6 +114,7 @@ VALUE ca_math_call (VALUE mod, VALUE arg, ID id);
114
114
 
115
115
  #include "ca_monop_dispatch.h"
116
116
  #include "ca_binop_dispatch.h"
117
+ #include "ca_triop_dispatch.h"
117
118
  #include "ca_bincmp_dispatch.h"
118
119
  #include "ca_moncmp_dispatch.h"
119
120
 
@@ -41,6 +41,8 @@
41
41
 
42
42
  #include "ruby.h"
43
43
  #include "carray.h"
44
+ #include "carray_internal.h" /* ca_integer_arg, ca_calling_method_name */
45
+ #include "ca_obj_face.h" /* ca_face_refuse_numeric */
44
46
  #include <math.h>
45
47
 
46
48
  /* Externals (link-time; kept out of carray.h to keep the header lean). */
@@ -50,6 +52,7 @@ extern VALUE rb_ca_insert_axis (int argc, VALUE *argv, VALUE self); /* c
50
52
  static ID id_axis, id_sort_copy, id_is_not_masked;
51
53
  /* object lane (arbitrary Ruby objects -> arithmetic is irreducibly funcall) */
52
54
  static ID id_plus, id_div, id_mul, id_min, id_max, id_sort, id_copy;
55
+ static ID id_rationalize;
53
56
  static ID id_aref, id_aset;
54
57
  static VALUE sym_linear, sym_lower, sym_higher, sym_nearest, sym_midpoint;
55
58
 
@@ -71,13 +74,14 @@ mp_is_numeric (int8_t dt)
71
74
  _(CA_INT64, int64_t) _(CA_UINT64, uint64_t) \
72
75
  _(CA_FLOAT32, float32_t) _(CA_FLOAT64, double)
73
76
 
74
- /* [numeric] min over the upper region [k+1, n-1] of a partitioned fiber,
75
- skipping NaN (= matches production .min(axis:): NaN sorts to the tail
76
- and is ignored). stride = element stride along the reduce axis.
77
- fmin ignores NaN (fmin(x,NaN)==x), so a +INFINITY seed naturally skips
78
- NaN and stays branch-free -> the compiler vectorises this loop, matching
79
- the production SIMD min(axis:). An all-NaN (or empty) upper region
80
- leaves hi == +INFINITY; fall back to the kth value (degenerate). */
77
+ /* [numeric] sorted[k+1] of a fiber partitioned at k = the min over the
78
+ upper region [k+1, n-1]. NaN sorts after every number, so it is the
79
+ upper element only when the region holds nothing else. stride = element
80
+ stride along the reduce axis. fmin ignores NaN (fmin(x,NaN)==x), so a
81
+ +INFINITY seed skips NaN and the loop stays branch-free and vectorised.
82
+ A result of +INFINITY is either a +INFINITY in the region or a region of
83
+ NaN only; the rescan that tells them apart runs only in that case. An
84
+ empty region (k == n-1) answers the kth value. */
81
85
  #define MP_GEN_MINUP(CT, T) \
82
86
  static double \
83
87
  mp_minup_##T (const T *b, ca_size_t f0, long k, long n, long stride) { \
@@ -86,8 +90,11 @@ mp_is_numeric (int8_t dt)
86
90
  double v = (double) b[f0 + (ca_size_t)(ai * stride)]; \
87
91
  hi = fmin(hi, v); \
88
92
  } \
89
- return ( hi == (double) INFINITY ) \
90
- ? (double) b[f0 + (ca_size_t)(k * stride)] : hi; \
93
+ if ( hi != (double) INFINITY ) return hi; \
94
+ if ( k + 1 >= n ) return (double) b[f0 + (ca_size_t)(k * stride)]; \
95
+ for ( long ai = k + 1; ai < n; ai++ ) \
96
+ if ( (double) b[f0 + (ca_size_t)(ai * stride)] == hi ) return hi; \
97
+ return (double) NAN; \
91
98
  }
92
99
  MP_TYPES(MP_GEN_MINUP)
93
100
 
@@ -137,7 +144,12 @@ static VALUE
137
144
  mp_axis_all_masked (VALUE self, long axis, int keep_axis)
138
145
  {
139
146
  int8_t ax = (int8_t) axis;
140
- VALUE out = rb_ca_new_reduced(self, &ax, 1, CA_FLOAT64, keep_axis);
147
+ CArray *ca;
148
+ GetCArray(self, ca);
149
+ VALUE out = rb_ca_new_reduced(self, &ax, 1,
150
+ (ca->data_type == CA_OBJECT) ? CA_OBJECT
151
+ : CA_FLOAT64,
152
+ keep_axis);
141
153
  CArray *co;
142
154
  GetCArray(out, co);
143
155
  ca_create_mask(co);
@@ -240,7 +252,7 @@ median_fill (double *op, int8_t dt, const char *base,
240
252
  MP_TYPES(MP_CASE)
241
253
  #undef MP_CASE
242
254
  default:
243
- rb_raise(rb_eCADataTypeError, "median: unsupported data_type %d", dt);
255
+ rb_raise(rb_eCADataTypeError, "median: source data_type :%s not supported", ca_type_name[dt]);
244
256
  }
245
257
  }
246
258
 
@@ -298,13 +310,13 @@ median_flat (VALUE self, long min_count, VALUE fill_value, int keep_axis)
298
310
  if ( RTEST(rb_ca_has_mask(self)) ) {
299
311
  CArray *mc; GetCArray(self, mc);
300
312
  long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
301
- if ( cnt < min_count ) filled = 1;
313
+ if ( ca_too_few_present(cnt, min_count, 1) ) filled = 1;
302
314
  else src = rb_ca_fetch(self, ID2SYM(id_is_not_masked));
303
315
  }
304
316
  if ( !filled ) {
305
317
  CArray *sc;
306
318
  GetCArray(src, sc);
307
- if ( sc->elements == 0 ) filled = 1;
319
+ if ( ca_too_few_present(sc->elements, min_count, 1) ) filled = 1;
308
320
  }
309
321
 
310
322
  if ( filled ) {
@@ -336,6 +348,91 @@ median_flat (VALUE self, long min_count, VALUE fill_value, int keep_axis)
336
348
  Every function in this block (down to the median entry) is [object].
337
349
  ===================================================================== */
338
350
 
351
+ /* [object] Is this operand's arithmetic the arithmetic interpolation means?
352
+ `lo` / `hi` are a stored object on the flat path and an object CArray of
353
+ them on the per-axis one, so look through a CArray to an element. An
354
+ empty one has nothing to interpolate and passes. */
355
+ static int
356
+ mp_object_interpolates (VALUE v)
357
+ {
358
+ if ( rb_obj_is_kind_of(v, rb_cNumeric) ) {
359
+ return 1;
360
+ }
361
+ if ( rb_obj_is_kind_of(v, rb_cCArray) ) {
362
+ CArray *ca;
363
+ ca_size_t i;
364
+ GetCArray(v, ca);
365
+ for ( i = 0; i < ca->elements; i++ ) {
366
+ VALUE e = rb_ca_fetch_addr(v, i);
367
+ if ( e != CA_UNDEF && ! rb_obj_is_kind_of(e, rb_cNumeric) ) {
368
+ return 0;
369
+ }
370
+ }
371
+ return 1;
372
+ }
373
+ return 0;
374
+ }
375
+
376
+ /* [object] The element an interpolation refusal names: lo itself, or the
377
+ first element of a CArray of them that has no arithmetic. */
378
+ static VALUE
379
+ mp_first_non_numeric (VALUE v)
380
+ {
381
+ if ( rb_obj_is_kind_of(v, rb_cCArray) ) {
382
+ CArray *ca;
383
+ ca_size_t i;
384
+ GetCArray(v, ca);
385
+ for ( i = 0; i < ca->elements; i++ ) {
386
+ VALUE e = rb_ca_fetch_addr(v, i);
387
+ if ( e != CA_UNDEF && ! rb_obj_is_kind_of(e, rb_cNumeric) ) {
388
+ return e;
389
+ }
390
+ }
391
+ }
392
+ return v;
393
+ }
394
+
395
+ /* [object] An order statistic answers in the elements' own terms: a pick
396
+ is the element itself, and an average or an interpolation is done with
397
+ the elements' own arithmetic and an exact weight, so a Rational or a
398
+ BigDecimal stays exact and a Float stays a Float. */
399
+
400
+ /* The interpolation weight r (0 <= r < 1) as a Rational. */
401
+ static VALUE
402
+ obj_weight (double r)
403
+ {
404
+ return rb_funcall(DBL2NUM(r), id_rationalize, 0);
405
+ }
406
+
407
+ /* The midpoint of lo and hi, (lo + hi) * (1/2): exact for an Integer or a
408
+ Rational, and the same for a cell (the flat path) and for a CArray of
409
+ them (the per-axis path). */
410
+ static VALUE
411
+ obj_midpoint (VALUE lo, VALUE hi)
412
+ {
413
+ return rb_funcall(rb_funcall(lo, id_plus, 1, hi), id_mul, 1,
414
+ rb_rational_new(INT2FIX(1), INT2FIX(2)));
415
+ }
416
+
417
+ /* [object] Refuse to interpolate between two elements that have no
418
+ arithmetic meaning it. A String has both a `*` and a `+`, and
419
+ String#* truncates the Float weight to zero -- so :linear quietly
420
+ returned "" instead of failing, which is the reason this guard is
421
+ here rather than a rescue. The three picking methods need no
422
+ arithmetic and are the way through. */
423
+ static void
424
+ mp_require_interpolable (const char *who, VALUE method, VALUE lo)
425
+ {
426
+ if ( mp_object_interpolates(lo) ) {
427
+ return;
428
+ }
429
+ rb_raise(rb_eCADataTypeError,
430
+ "%s: method: :%"PRIsVALUE" interpolates between two elements, "
431
+ "which %"PRIsVALUE" does not support; use method: :lower, :higher "
432
+ "or :nearest to pick an element instead",
433
+ who, rb_sym2str(method), rb_obj_class(mp_first_non_numeric(lo)));
434
+ }
435
+
339
436
  /* [object] recv.mid(axis: raxis) -- e.g. min/max/sort along an axis. */
340
437
  static VALUE
341
438
  obj_call_axis (VALUE recv, ID mid, VALUE raxis)
@@ -419,10 +516,20 @@ median_object_axis (VALUE self, long axis, long n, int keep_axis)
419
516
  long k = n / 2 - 1;
420
517
  VALUE lo, hi;
421
518
  obj_kth_pair(self, axis, k, n, &lo, &hi);
422
- result = rb_funcall(rb_funcall(lo, id_plus, 1, hi), id_div, 1, DBL2NUM(2.0));
519
+ /* An even count has no middle element, only the average of two -- so
520
+ a column whose objects have no arithmetic has a median only at odd
521
+ length. percentile(50, method: :lower) names one either way. */
522
+ if ( ! mp_object_interpolates(lo) ) {
523
+ rb_raise(rb_eCADataTypeError,
524
+ "median: an even number of elements has no middle one, and "
525
+ "%"PRIsVALUE" cannot be averaged; use percentile(50, method: "
526
+ ":lower) (or :higher / :nearest) to pick an element instead",
527
+ rb_obj_class(mp_first_non_numeric(lo)));
528
+ }
529
+ result = obj_midpoint(lo, hi);
423
530
  } else {
424
531
  VALUE kv = obj_kth_one(self, axis, (n - 1) / 2);
425
- result = rb_funcall(kv, id_mul, 1, DBL2NUM(1.0)); /* force float promote */
532
+ result = kv;
426
533
  }
427
534
  if ( keep_axis ) {
428
535
  VALUE ia[1] = { LONG2NUM(axis) };
@@ -440,13 +547,13 @@ median_object_flat (VALUE self, long min_count, VALUE fill_value, int keep_axis)
440
547
  if ( RTEST(rb_ca_has_mask(self)) ) {
441
548
  CArray *mc; GetCArray(self, mc);
442
549
  long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
443
- if ( cnt < min_count ) filled = 1;
550
+ if ( ca_too_few_present(cnt, min_count, 1) ) filled = 1;
444
551
  else src = rb_ca_fetch(self, ID2SYM(id_is_not_masked));
445
552
  }
446
553
  if ( !filled ) {
447
554
  CArray *sc;
448
555
  GetCArray(src, sc);
449
- if ( sc->elements == 0 ) filled = 1;
556
+ if ( ca_too_few_present(sc->elements, min_count, 1) ) filled = 1;
450
557
  }
451
558
  if ( filled ) {
452
559
  result = NIL_P(fill_value) ? CA_UNDEF : fill_value;
@@ -464,29 +571,28 @@ median_object_flat (VALUE self, long min_count, VALUE fill_value, int keep_axis)
464
571
  pre-fetched). Reached ONLY by CA_OBJECT percentile/quantile -- never
465
572
  by numeric (that path uses pct_compute) and never by median.
466
573
  The :linear / :midpoint interpolation funcalls are irreducible for
467
- arbitrary objects; the *1.0 in the pick methods is float-promotion to
468
- match the numeric "always Float" output contract. */
574
+ arbitrary objects; the pick methods return the element itself. */
469
575
  static VALUE
470
576
  pct_compute_object (VALUE method, long k, double r, long n, VALUE lo, VALUE hi)
471
577
  {
472
- if ( method == sym_lower ) return rb_funcall(lo, id_mul, 1, DBL2NUM(1.0));
473
- if ( method == sym_higher ) {
474
- VALUE v = (r == 0.0) ? lo : hi;
475
- return rb_funcall(v, id_mul, 1, DBL2NUM(1.0));
476
- }
578
+ if ( method == sym_lower ) return lo;
579
+ if ( method == sym_higher ) return (r == 0.0) ? lo : hi;
477
580
  if ( method == sym_nearest ) {
478
581
  int use_k = (r < 0.5) || (r == 0.5 && (k % 2 == 0));
479
- return rb_funcall(use_k ? lo : hi, id_mul, 1, DBL2NUM(1.0));
582
+ return use_k ? lo : hi;
480
583
  }
481
584
  if ( method == sym_linear ) {
482
- if ( r == 0.0 || k + 1 >= n ) return rb_funcall(lo, id_mul, 1, DBL2NUM(1.0));
483
- VALUE a = rb_funcall(lo, id_mul, 1, DBL2NUM(1.0 - r));
484
- VALUE b = rb_funcall(hi, id_mul, 1, DBL2NUM(r));
585
+ if ( r == 0.0 || k + 1 >= n ) return lo;
586
+ mp_require_interpolable("percentile", method, lo);
587
+ VALUE w = obj_weight(r);
588
+ VALUE a = rb_funcall(lo, id_mul, 1, rb_funcall(INT2FIX(1), rb_intern("-"), 1, w));
589
+ VALUE b = rb_funcall(hi, id_mul, 1, w);
485
590
  return rb_funcall(a, id_plus, 1, b);
486
591
  }
487
592
  if ( method == sym_midpoint ) {
488
- if ( k + 1 >= n ) return rb_funcall(lo, id_mul, 1, DBL2NUM(1.0));
489
- return rb_funcall(rb_funcall(lo, id_plus, 1, hi), id_div, 1, DBL2NUM(2.0));
593
+ if ( k + 1 >= n ) return lo;
594
+ mp_require_interpolable("percentile", method, lo);
595
+ return obj_midpoint(lo, hi);
490
596
  }
491
597
  rb_raise(rb_eArgError, "percentile: invalid method (BUG)");
492
598
  }
@@ -495,11 +601,8 @@ pct_compute_object (VALUE method, long k, double r, long n, VALUE lo, VALUE hi)
495
601
  static VALUE
496
602
  pct_object_one_partition (VALUE self, long axis, long n, double p, VALUE method)
497
603
  {
498
- if ( p == 100.0 )
499
- return rb_funcall(obj_call_axis(self, id_max, LONG2NUM(axis)),
500
- id_mul, 1, DBL2NUM(1.0));
501
604
  if ( n == 1 )
502
- return rb_funcall(obj_slice(self, axis, 0), id_mul, 1, DBL2NUM(1.0));
605
+ return obj_slice(self, axis, 0);
503
606
  double f = (n - 1) * p / 100.0;
504
607
  long k = (long) floor(f);
505
608
  double r = f - k;
@@ -515,9 +618,9 @@ static VALUE
515
618
  pct_object_one_sorted (VALUE sorted, long axis, long n, double p, VALUE method)
516
619
  {
517
620
  if ( p == 100.0 )
518
- return rb_funcall(obj_slice(sorted, axis, n - 1), id_mul, 1, DBL2NUM(1.0));
621
+ return obj_slice(sorted, axis, n - 1);
519
622
  if ( n == 1 )
520
- return rb_funcall(obj_slice(sorted, axis, 0), id_mul, 1, DBL2NUM(1.0));
623
+ return obj_slice(sorted, axis, 0);
521
624
  double f = (n - 1) * p / 100.0;
522
625
  long k = (long) floor(f);
523
626
  double r = f - k;
@@ -597,7 +700,7 @@ pct_object_axis_masked_one (VALUE self, VALUE sorted, long axis, double p,
597
700
  for ( long j = 0; j < M; j++ )
598
701
  if ( !sm[f0 + (ca_size_t)(j * INNER)] ) np++;
599
702
  }
600
- if ( np == 0 || np < min_count ) {
703
+ if ( ca_too_few_present(np, min_count, 1) ) {
601
704
  if ( is_undef ) {
602
705
  if ( !co->mask ) ca_create_mask(co);
603
706
  ((boolean8_t *) co->mask->ptr)[oc] = 1;
@@ -683,13 +786,13 @@ percentile_object_flat (VALUE self, VALUE pers, long min_count, VALUE fill_value
683
786
  if ( RTEST(rb_ca_has_mask(self)) ) {
684
787
  CArray *mc; GetCArray(self, mc);
685
788
  long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
686
- if ( cnt < min_count ) filled = 1;
789
+ if ( ca_too_few_present(cnt, min_count, 1) ) filled = 1;
687
790
  else src = rb_ca_fetch(self, ID2SYM(id_is_not_masked));
688
791
  }
689
792
  if ( !filled ) {
690
793
  CArray *sc;
691
794
  GetCArray(src, sc);
692
- if ( sc->elements == 0 ) filled = 1;
795
+ if ( ca_too_few_present(sc->elements, min_count, 1) ) filled = 1;
693
796
  }
694
797
  if ( filled ) {
695
798
  VALUE fill = NIL_P(fill_value) ? CA_UNDEF : fill_value;
@@ -710,7 +813,7 @@ static VALUE median_axis_masked (VALUE self, long axis, long min_count,
710
813
  /* [entry] median(axis:, min_count:, fill_value:, keep_axis:). Dispatches
711
814
  fixlen -> reject, object -> object lane, numeric -> numeric lane. */
712
815
  static VALUE
713
- rb_ca_median_m (int argc, VALUE *argv, VALUE self)
816
+ rb_ca_median_m_body (int argc, VALUE *argv, VALUE self)
714
817
  {
715
818
  VALUE ropt = rb_pop_options(&argc, &argv);
716
819
  VALUE raxis = Qnil, rmin_count = INT2FIX(0), fill_value = Qnil, rkeep = Qfalse;
@@ -721,6 +824,15 @@ rb_ca_median_m (int argc, VALUE *argv, VALUE self)
721
824
 
722
825
  CArray *ca;
723
826
  GetCArray(self, ca);
827
+ /* A lazy expression is computed whole here, so a registered evaluator
828
+ may compute it instead. */
829
+ {
830
+ volatile VALUE evaluated = ca_lazy_evaluated(self);
831
+ if ( ! NIL_P(evaluated) ) {
832
+ self = evaluated;
833
+ GetCArray(self, ca);
834
+ }
835
+ }
724
836
  /* Boolean rides the f64 lane: 0/1 -> 0.0/1.0, so median interpolates and
725
837
  returns a float (a 2-element median averages to 0.5), matching how the
726
838
  numeric lane treats integer input. */
@@ -729,17 +841,25 @@ rb_ca_median_m (int argc, VALUE *argv, VALUE self)
729
841
  GetCArray(self, ca);
730
842
  }
731
843
  int8_t dt = ca->data_type;
732
- if ( ca_is_fixlen_type(ca) )
844
+ if ( ca_is_fixlen_type(ca) ) {
845
+ ca_face_refuse_numeric(self, "median", rb_eCADataTypeError);
733
846
  rb_raise(rb_eCADataTypeError,
734
847
  "median: not defined for fixlen (no numeric midpoint); "
735
848
  "use a numeric or object array");
849
+ }
736
850
  int is_obj = (dt == CA_OBJECT);
737
851
  if ( !is_obj && !mp_is_numeric(dt) )
738
- rb_raise(rb_eCADataTypeError, "median: unsupported data_type %d", dt);
852
+ rb_raise(rb_eCADataTypeError, "median: source data_type :%s not supported", ca_type_name[dt]);
739
853
 
740
- long min_count = NUM2LONG(rmin_count);
741
- if ( min_count < 0 )
742
- rb_raise(rb_eArgError, "min_count must be non-negative; got %ld", min_count);
854
+ long min_count = ca_min_count_arg(rmin_count, NULL);
855
+ /* fill_value: UNDEF leaves the result undefined; any other fill is what
856
+ a store into the float64 result would make of it. */
857
+ if ( fill_value == CA_UNDEF ) {
858
+ fill_value = Qnil;
859
+ }
860
+ else if ( ! NIL_P(fill_value) && ! is_obj ) {
861
+ fill_value = ca_fill_as(fill_value, CA_FLOAT64);
862
+ }
743
863
  int keep_axis = RTEST(rkeep);
744
864
 
745
865
  if ( !NIL_P(raxis) ) {
@@ -803,7 +923,7 @@ pct_fill (double *op, int8_t dt, const char *base, int from_sorted,
803
923
  MP_TYPES(MP_CASE)
804
924
  #undef MP_CASE
805
925
  default:
806
- rb_raise(rb_eCADataTypeError, "percentile: unsupported data_type %d", dt);
926
+ rb_raise(rb_eCADataTypeError, "percentile: source data_type :%s not supported", ca_type_name[dt]);
807
927
  }
808
928
  }
809
929
 
@@ -831,7 +951,7 @@ pct_fill (double *op, int8_t dt, const char *base, int from_sorted,
831
951
  for ( long j = 0; j < M; j++ ) \
832
952
  if ( !sm[f0 + (ca_size_t)(j * INNER)] ) np++; \
833
953
  } \
834
- if ( np == 0 || np < min_count ) { \
954
+ if ( ca_too_few_present(np, min_count, 1) ) { \
835
955
  if ( is_undef ) { \
836
956
  if ( !co->mask ) ca_create_mask(co); \
837
957
  ((boolean8_t *) co->mask->ptr)[oc] = 1; \
@@ -872,7 +992,7 @@ pct_fill_masked (CArray *co, int8_t dt, const char *base, const boolean8_t *sm,
872
992
  MP_TYPES(MP_CASE)
873
993
  #undef MP_CASE
874
994
  default:
875
- rb_raise(rb_eCADataTypeError, "percentile: unsupported data_type %d", dt);
995
+ rb_raise(rb_eCADataTypeError, "percentile: source data_type :%s not supported", ca_type_name[dt]);
876
996
  }
877
997
  }
878
998
 
@@ -1061,7 +1181,7 @@ pct_flat (VALUE self, VALUE pers_ary, long min_count, VALUE fill_value,
1061
1181
  if ( RTEST(rb_ca_has_mask(self)) ) {
1062
1182
  CArray *mc; GetCArray(self, mc);
1063
1183
  long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
1064
- if ( cnt < min_count ) {
1184
+ if ( ca_too_few_present(cnt, min_count, 1) ) {
1065
1185
  masked_out = 1; is_undef = NIL_P(fill_value);
1066
1186
  if ( !is_undef ) fillv = NUM2DBL(fill_value);
1067
1187
  } else {
@@ -1071,7 +1191,7 @@ pct_flat (VALUE self, VALUE pers_ary, long min_count, VALUE fill_value,
1071
1191
  if ( !masked_out ) {
1072
1192
  CArray *sc;
1073
1193
  GetCArray(src, sc);
1074
- if ( sc->elements == 0 ) {
1194
+ if ( ca_too_few_present(sc->elements, min_count, 1) ) {
1075
1195
  masked_out = 1; is_undef = NIL_P(fill_value);
1076
1196
  if ( !is_undef ) fillv = NUM2DBL(fill_value);
1077
1197
  }
@@ -1119,7 +1239,10 @@ pct_flatten_validate_pers (VALUE pers)
1119
1239
  rb_raise(rb_eArgError, "percentile: at least one p value required");
1120
1240
  for ( long i = 0; i < len; i++ ) {
1121
1241
  VALUE p = rb_ary_entry(pers, i);
1122
- if ( !rb_obj_is_kind_of(p, rb_cNumeric) || NUM2DBL(p) < 0.0 || NUM2DBL(p) > 100.0 )
1242
+ /* Written as "inside", so that NaN -- which fails every comparison --
1243
+ is refused rather than let through. */
1244
+ if ( !rb_obj_is_kind_of(p, rb_cNumeric) ||
1245
+ !(NUM2DBL(p) >= 0.0 && NUM2DBL(p) <= 100.0) )
1123
1246
  rb_raise(rb_eArgError,
1124
1247
  "percentile: p must be Numeric in [0,100] (got %"PRIsVALUE")",
1125
1248
  rb_inspect(p));
@@ -1131,9 +1254,8 @@ pct_flatten_validate_pers (VALUE pers)
1131
1254
  keep_axis:). Dispatches fixlen -> reject, object -> object lane,
1132
1255
  numeric -> numeric lane. */
1133
1256
  static VALUE
1134
- rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1257
+ rb_ca_percentile_kw (int argc, VALUE *argv, VALUE ropt, VALUE self)
1135
1258
  {
1136
- VALUE ropt = rb_pop_options(&argc, &argv);
1137
1259
  VALUE raxis = Qnil, rmin_count = INT2FIX(0), fill_value = Qnil,
1138
1260
  method = sym_linear, rkeep = Qfalse;
1139
1261
  rb_scan_options(ropt, "axis,min_count,fill_value,method,keep_axis",
@@ -1141,6 +1263,15 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1141
1263
 
1142
1264
  CArray *ca;
1143
1265
  GetCArray(self, ca);
1266
+ /* A lazy expression is computed whole here, so a registered evaluator
1267
+ may compute it instead. */
1268
+ {
1269
+ volatile VALUE evaluated = ca_lazy_evaluated(self);
1270
+ if ( ! NIL_P(evaluated) ) {
1271
+ self = evaluated;
1272
+ GetCArray(self, ca);
1273
+ }
1274
+ }
1144
1275
  /* Boolean rides the f64 lane (0/1 -> 0.0/1.0), so percentile / quantile
1145
1276
  interpolate and return a float, matching the integer lane. */
1146
1277
  if ( ca->data_type == CA_BOOLEAN ) {
@@ -1148,16 +1279,24 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1148
1279
  GetCArray(self, ca);
1149
1280
  }
1150
1281
  int8_t dt = ca->data_type;
1151
- if ( ca_is_fixlen_type(ca) )
1282
+ if ( ca_is_fixlen_type(ca) ) {
1283
+ ca_face_refuse_numeric(self, "percentile", rb_eCADataTypeError);
1152
1284
  rb_raise(rb_eCADataTypeError,
1153
1285
  "percentile: not defined for fixlen; use a numeric or object array");
1286
+ }
1154
1287
  int is_obj = (dt == CA_OBJECT);
1155
1288
  if ( !is_obj && !mp_is_numeric(dt) )
1156
- rb_raise(rb_eCADataTypeError, "percentile: unsupported data_type %d", dt);
1289
+ rb_raise(rb_eCADataTypeError, "percentile: source data_type :%s not supported", ca_type_name[dt]);
1157
1290
 
1158
- long min_count = NUM2LONG(rmin_count);
1159
- if ( min_count < 0 )
1160
- rb_raise(rb_eArgError, "min_count must be non-negative; got %ld", min_count);
1291
+ long min_count = ca_min_count_arg(rmin_count, NULL);
1292
+ /* fill_value: UNDEF leaves the result undefined; any other fill is what
1293
+ a store into the float64 result would make of it. */
1294
+ if ( fill_value == CA_UNDEF ) {
1295
+ fill_value = Qnil;
1296
+ }
1297
+ else if ( ! NIL_P(fill_value) && ! is_obj ) {
1298
+ fill_value = ca_fill_as(fill_value, CA_FLOAT64);
1299
+ }
1161
1300
 
1162
1301
  VALUE pers = pct_flatten_validate_pers(rb_ary_new_from_values(argc, argv));
1163
1302
  pct_validate_method(method);
@@ -1175,7 +1314,7 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1175
1314
  flattened first and follows the same rule (length 1 unwraps). */
1176
1315
  VALUE result;
1177
1316
  if ( !NIL_P(raxis) ) {
1178
- long axis = rb_ca_normalize_axis_value(self, raxis, "percentile");
1317
+ long axis = rb_ca_normalize_axis_value(self, raxis, NULL);
1179
1318
  /* mask / min_count / fill_value require the per-fiber select (each fiber
1180
1319
  has its own n_present); the plain partition/sort path assumes a
1181
1320
  uniform n. These paths bake keep_axis into the reduced output. */
@@ -1216,11 +1355,18 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1216
1355
  return result;
1217
1356
  }
1218
1357
 
1358
+ static VALUE
1359
+ rb_ca_percentile_m_body (int argc, VALUE *argv, VALUE self)
1360
+ {
1361
+ VALUE ropt = rb_pop_options(&argc, &argv);
1362
+ return rb_ca_percentile_kw(argc, argv, ropt, self);
1363
+ }
1364
+
1219
1365
  /* [entry] quantile(axis:, keep_axis:) = percentile(0, 25, 50, 75, 100, ...).
1220
1366
  Return shape: Array<Float> len 5 (flat) or Array<CArray> len 5 (per-axis),
1221
1367
  matching percentile's multi-p wrapping since we pass 5 fixed p values. */
1222
1368
  static VALUE
1223
- rb_ca_quantile_m (int argc, VALUE *argv, VALUE self)
1369
+ rb_ca_quantile_m_body (int argc, VALUE *argv, VALUE self)
1224
1370
  {
1225
1371
  VALUE ropt = rb_pop_options(&argc, &argv);
1226
1372
  if ( argc != 0 )
@@ -1230,9 +1376,62 @@ rb_ca_quantile_m (int argc, VALUE *argv, VALUE self)
1230
1376
  VALUE opts = rb_hash_new();
1231
1377
  if ( !NIL_P(raxis) ) rb_hash_aset(opts, ID2SYM(rb_intern("axis")), raxis);
1232
1378
  rb_hash_aset(opts, ID2SYM(rb_intern("keep_axis")), rkeep);
1233
- VALUE pa[6] = { INT2FIX(0), INT2FIX(25), INT2FIX(50), INT2FIX(75), INT2FIX(100),
1234
- opts };
1235
- return rb_ca_percentile_m(6, pa, self);
1379
+ VALUE pa[5] = { INT2FIX(0), INT2FIX(25), INT2FIX(50), INT2FIX(75), INT2FIX(100) };
1380
+ return rb_ca_percentile_kw(5, pa, opts, self);
1381
+ }
1382
+
1383
+
1384
+ typedef struct { int argc; VALUE *argv; VALUE self; } mp_median_call_t;
1385
+
1386
+ static VALUE
1387
+ mp_median_answer_body (VALUE p)
1388
+ {
1389
+ mp_median_call_t *c = (mp_median_call_t *) p;
1390
+ return rb_ca_median_m_body(c->argc, c->argv, c->self);
1391
+ }
1392
+
1393
+ /* An error raised by the methods this one calls names this one. */
1394
+ static VALUE
1395
+ rb_ca_median_m (int argc, VALUE *argv, VALUE self)
1396
+ {
1397
+ mp_median_call_t c = { argc, argv, self };
1398
+ return ca_answer_as_caller(mp_median_answer_body, (VALUE) &c);
1399
+ }
1400
+
1401
+
1402
+ typedef struct { int argc; VALUE *argv; VALUE self; } mp_percentile_call_t;
1403
+
1404
+ static VALUE
1405
+ mp_percentile_answer_body (VALUE p)
1406
+ {
1407
+ mp_percentile_call_t *c = (mp_percentile_call_t *) p;
1408
+ return rb_ca_percentile_m_body(c->argc, c->argv, c->self);
1409
+ }
1410
+
1411
+ /* An error raised by the methods this one calls names this one. */
1412
+ static VALUE
1413
+ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1414
+ {
1415
+ mp_percentile_call_t c = { argc, argv, self };
1416
+ return ca_answer_as_caller(mp_percentile_answer_body, (VALUE) &c);
1417
+ }
1418
+
1419
+
1420
+ typedef struct { int argc; VALUE *argv; VALUE self; } mp_quantile_call_t;
1421
+
1422
+ static VALUE
1423
+ mp_quantile_answer_body (VALUE p)
1424
+ {
1425
+ mp_quantile_call_t *c = (mp_quantile_call_t *) p;
1426
+ return rb_ca_quantile_m_body(c->argc, c->argv, c->self);
1427
+ }
1428
+
1429
+ /* An error raised by the methods this one calls names this one. */
1430
+ static VALUE
1431
+ rb_ca_quantile_m (int argc, VALUE *argv, VALUE self)
1432
+ {
1433
+ mp_quantile_call_t c = { argc, argv, self };
1434
+ return ca_answer_as_caller(mp_quantile_answer_body, (VALUE) &c);
1236
1435
  }
1237
1436
 
1238
1437
  void
@@ -1242,6 +1441,7 @@ Init_carray_median_percentile (void)
1242
1441
  id_sort_copy = rb_intern("sort_copy");
1243
1442
  id_is_not_masked = rb_intern("is_not_masked");
1244
1443
  id_plus = rb_intern("+"); id_div = rb_intern("/"); id_mul = rb_intern("*");
1444
+ id_rationalize = rb_intern("rationalize");
1245
1445
  id_min = rb_intern("min"); id_max = rb_intern("max");
1246
1446
  id_sort = rb_intern("sort"); id_copy = rb_intern("copy");
1247
1447
  id_aref = rb_intern("[]"); id_aset = rb_intern("[]=");