carray 3.0.2 → 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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +304 -0
  3. data/README.md +3 -4
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +225 -0
  6. data/ext/ca_axis_group.c +177 -99
  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 +66 -71
  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 +663 -214
  14. data/ext/ca_kernel_iterator.h +231 -167
  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 +132 -22
  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 +1 -0
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +96 -25
  25. data/ext/ca_obj_face.c +406 -64
  26. data/ext/ca_obj_face.h +92 -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 +21 -21
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +189 -107
  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 +543 -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 +47 -49
  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 -118
  46. data/ext/ca_obj_stride.c +195 -34
  47. data/ext/ca_obj_string.c +17 -18
  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 +149 -27
  54. data/ext/ca_op_ipower.c +24 -11
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_sort_kernels.h +70 -0
  58. data/ext/ca_sweep_engine.c +102 -79
  59. data/ext/ca_sweep_engine.h +7 -20
  60. data/ext/ca_triop_dispatch.c +4 -1
  61. data/ext/ca_triop_dispatch.h +5 -2
  62. data/ext/caframe_csv_reader.c +463 -0
  63. data/ext/carray.h +70 -27
  64. data/ext/carray_access.c +455 -211
  65. data/ext/carray_address_basis.c +177 -36
  66. data/ext/carray_attribute.c +45 -3
  67. data/ext/carray_bincount.c +32 -2
  68. data/ext/carray_broadcast.c +46 -14
  69. data/ext/carray_call_cfunc.c +0 -28
  70. data/ext/carray_cast.c +1444 -209
  71. data/ext/carray_cast_func.rb +34 -21
  72. data/ext/carray_conversion.c +197 -73
  73. data/ext/carray_copy.c +32 -11
  74. data/ext/carray_core.c +514 -122
  75. data/ext/carray_count.c +140 -87
  76. data/ext/carray_element.c +18 -27
  77. data/ext/carray_factorize.c +569 -277
  78. data/ext/carray_generate.c +76 -40
  79. data/ext/carray_histogram.c +152 -94
  80. data/ext/carray_hold.c +2 -1
  81. data/ext/carray_index_classifier.c +63 -96
  82. data/ext/carray_internal.h +385 -3
  83. data/ext/carray_lazy.c +834 -79
  84. data/ext/carray_loop.c +81 -65
  85. data/ext/carray_mask.c +682 -316
  86. data/ext/carray_math_kernel.h +1 -0
  87. data/ext/carray_median_percentile.c +221 -76
  88. data/ext/carray_memory_view.c +254 -248
  89. data/ext/carray_operator.c +1089 -359
  90. data/ext/carray_order.c +133 -69
  91. data/ext/carray_partition.c +102 -56
  92. data/ext/carray_random.c +273 -239
  93. data/ext/carray_scatter.c +308 -65
  94. data/ext/carray_segment.c +257 -0
  95. data/ext/carray_slab.c +170 -70
  96. data/ext/carray_sort.c +215 -218
  97. data/ext/carray_sort_kernel.c +8 -1
  98. data/ext/carray_struct.c +99 -21
  99. data/ext/carray_test.c +167 -64
  100. data/ext/carray_time_text.c +1182 -0
  101. data/ext/carray_utils.c +363 -98
  102. data/ext/extconf.rb +27 -2
  103. data/ext/mk_call_cfunc.rb +0 -2
  104. data/ext/mk_pow5_table.rb +35 -0
  105. data/ext/mkkernel.rb +1076 -846
  106. data/ext/ruby_carray.c +7 -1
  107. data/ext/version.h +4 -4
  108. data/ext/version.rb +4 -2
  109. data/lib/carray/attribute.rb +39 -0
  110. data/lib/carray/autoload_carray.rb +19 -0
  111. data/lib/carray/autoload_method_extension.rb +6 -0
  112. data/lib/carray/axis_group.rb +174 -26
  113. data/lib/carray/basics.rb +91 -25
  114. data/lib/carray/bincount_nd.rb +6 -0
  115. data/lib/carray/block_iterator.rb +118 -48
  116. data/lib/carray/boolean_reduce.rb +21 -29
  117. data/lib/carray/categorical.rb +94 -26
  118. data/lib/carray/categorical_iterator.rb +333 -826
  119. data/lib/carray/complex.rb +36 -29
  120. data/lib/carray/conditional.rb +94 -7
  121. data/lib/carray/const_string.rb +46 -0
  122. data/lib/carray/frame/concat.rb +157 -6
  123. data/lib/carray/frame/convert.rb +13 -7
  124. data/lib/carray/frame/csv_parser.rb +589 -82
  125. data/lib/carray/frame/describe.rb +79 -0
  126. data/lib/carray/frame/frame.rb +124 -25
  127. data/lib/carray/frame/group.rb +189 -63
  128. data/lib/carray/frame/io.rb +412 -72
  129. data/lib/carray/frame/join.rb +134 -46
  130. data/lib/carray/frame/records.rb +37 -16
  131. data/lib/carray/frame/reshape.rb +448 -0
  132. data/lib/carray/frame/sort.rb +26 -2
  133. data/lib/carray/frame/verbs.rb +715 -48
  134. data/lib/carray/frame.rb +2 -0
  135. data/lib/carray/fuse_source.rb +111 -2
  136. data/lib/carray/fusion.rb +229 -38
  137. data/lib/carray/inspect.rb +29 -11
  138. data/lib/carray/iterator.rb +14 -2
  139. data/lib/carray/lazy.rb +54 -30
  140. data/lib/carray/mask_gap_fill.rb +66 -52
  141. data/lib/carray/math.rb +32 -19
  142. data/lib/carray/meld_reduce.rb +40 -7
  143. data/lib/carray/methods/align_addr.rb +8 -4
  144. data/lib/carray/methods/bincount.rb +2 -2
  145. data/lib/carray/methods/bit_string.rb +14 -3
  146. data/lib/carray/methods/composition.rb +16 -10
  147. data/lib/carray/methods/factorize.rb +2 -2
  148. data/lib/carray/methods/gather_nd.rb +13 -2
  149. data/lib/carray/methods/insert_block.rb +9 -14
  150. data/lib/carray/methods/is_in.rb +117 -4
  151. data/lib/carray/methods/join.rb +1 -1
  152. data/lib/carray/methods/locate_addr.rb +177 -36
  153. data/lib/carray/methods/mode.rb +18 -1
  154. data/lib/carray/methods/repeat.rb +1 -20
  155. data/lib/carray/methods/resize.rb +21 -23
  156. data/lib/carray/methods/snap.rb +34 -12
  157. data/lib/carray/methods/unique.rb +11 -2
  158. data/lib/carray/record_order.rb +208 -0
  159. data/lib/carray/runtime.rb +14 -11
  160. data/lib/carray/segment_iterator.rb +990 -0
  161. data/lib/carray/serialize.rb +197 -27
  162. data/lib/carray/slab_iterator.rb +51 -27
  163. data/lib/carray/stack.rb +10 -4
  164. data/lib/carray/string_operation_extension.rb +4 -3
  165. data/lib/carray/struct.rb +142 -51
  166. data/lib/carray/struct_builder.rb +110 -14
  167. data/lib/carray/table.rb +2 -2
  168. data/lib/carray/time.rb +517 -103
  169. data/lib/carray/window_iterator.rb +127 -72
  170. data/lib/carray.rb +2 -2
  171. data/yard-stubs/ca_obj_array.rb +4 -4
  172. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  173. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  174. data/yard-stubs/ca_obj_fake.rb +1 -1
  175. data/yard-stubs/ca_obj_field.rb +1 -1
  176. data/yard-stubs/ca_obj_refer.rb +3 -3
  177. data/yard-stubs/ca_obj_roll.rb +0 -1
  178. data/yard-stubs/ca_obj_stride.rb +10 -7
  179. data/yard-stubs/ca_obj_transpose.rb +1 -1
  180. data/yard-stubs/ca_obj_window.rb +1 -2
  181. data/yard-stubs/carray_access.rb +5 -4
  182. data/yard-stubs/carray_broadcast.rb +1 -1
  183. data/yard-stubs/carray_cast.rb +78 -43
  184. data/yard-stubs/carray_conversion.rb +13 -1
  185. data/yard-stubs/carray_copy.rb +8 -3
  186. data/yard-stubs/carray_core.rb +3 -3
  187. data/yard-stubs/carray_count.rb +9 -0
  188. data/yard-stubs/carray_lazy.rb +5 -1
  189. data/yard-stubs/carray_loop.rb +4 -1
  190. data/yard-stubs/carray_mask.rb +34 -10
  191. data/yard-stubs/carray_math.rb +25 -13
  192. data/yard-stubs/carray_median_percentile.rb +10 -6
  193. data/yard-stubs/carray_order.rb +34 -13
  194. data/yard-stubs/carray_scatter.rb +10 -3
  195. data/yard-stubs/carray_segment.rb +72 -0
  196. data/yard-stubs/carray_slab.rb +2 -1
  197. data/yard-stubs/carray_sort.rb +43 -3
  198. data/yard-stubs/carray_test.rb +10 -0
  199. data/yard-stubs/carray_utils.rb +0 -26
  200. metadata +11 -19
  201. data/ext/ca_for_each_element.h +0 -239
  202. data/ext/carray_build_flags.h +0 -3
  203. data/ext/carray_config.h +0 -28
  204. data/ext/carray_kernels_bincmp.c +0 -4446
  205. data/ext/carray_kernels_binop.c +0 -11001
  206. data/ext/carray_kernels_init.c +0 -1131
  207. data/ext/carray_kernels_map.c +0 -3467
  208. data/ext/carray_kernels_moncmp.c +0 -2097
  209. data/ext/carray_kernels_monop.c +0 -18313
  210. data/ext/carray_kernels_reduce_aggregate.c +0 -26005
  211. data/ext/carray_kernels_reduce_boolean.c +0 -330
  212. data/ext/carray_kernels_reduce_cumulative.c +0 -14862
  213. data/ext/carray_kernels_reduce_extreme.c +0 -17494
  214. data/ext/carray_kernels_reduce_variance.c +0 -3910
  215. data/ext/carray_kernels_scan.c +0 -3693
  216. data/ext/carray_kernels_search.c +0 -32218
  217. data/ext/carray_kernels_sort.c +0 -10626
  218. 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 ) {
@@ -348,15 +360,60 @@ mp_object_interpolates (VALUE v)
348
360
  }
349
361
  if ( rb_obj_is_kind_of(v, rb_cCArray) ) {
350
362
  CArray *ca;
363
+ ca_size_t i;
351
364
  GetCArray(v, ca);
352
- if ( ca->elements == 0 ) {
353
- return 1;
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
+ }
354
370
  }
355
- return rb_obj_is_kind_of(rb_ca_fetch_addr(v, 0), rb_cNumeric) ? 1 : 0;
371
+ return 1;
356
372
  }
357
373
  return 0;
358
374
  }
359
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
+
360
417
  /* [object] Refuse to interpolate between two elements that have no
361
418
  arithmetic meaning it. A String has both a `*` and a `+`, and
362
419
  String#* truncates the Float weight to zero -- so :linear quietly
@@ -370,12 +427,10 @@ mp_require_interpolable (const char *who, VALUE method, VALUE lo)
370
427
  return;
371
428
  }
372
429
  rb_raise(rb_eCADataTypeError,
373
- "%s: method: :%s interpolates between two elements, which %s "
374
- "does not support; use method: :lower, :higher or :nearest to "
375
- "pick an element instead",
376
- who, rb_id2name(SYM2ID(method)),
377
- rb_obj_classname(rb_obj_is_kind_of(lo, rb_cCArray)
378
- ? rb_ca_fetch_addr(lo, 0) : lo));
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)));
379
434
  }
380
435
 
381
436
  /* [object] recv.mid(axis: raxis) -- e.g. min/max/sort along an axis. */
@@ -467,15 +522,14 @@ median_object_axis (VALUE self, long axis, long n, int keep_axis)
467
522
  if ( ! mp_object_interpolates(lo) ) {
468
523
  rb_raise(rb_eCADataTypeError,
469
524
  "median: an even number of elements has no middle one, and "
470
- "%s cannot be averaged; use percentile(50, method: :lower) "
471
- "(or :higher / :nearest) to pick an element instead",
472
- rb_obj_classname(rb_obj_is_kind_of(lo, rb_cCArray)
473
- ? rb_ca_fetch_addr(lo, 0) : lo));
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)));
474
528
  }
475
- result = rb_funcall(rb_funcall(lo, id_plus, 1, hi), id_div, 1, DBL2NUM(2.0));
529
+ result = obj_midpoint(lo, hi);
476
530
  } else {
477
531
  VALUE kv = obj_kth_one(self, axis, (n - 1) / 2);
478
- result = rb_funcall(kv, id_mul, 1, DBL2NUM(1.0)); /* force float promote */
532
+ result = kv;
479
533
  }
480
534
  if ( keep_axis ) {
481
535
  VALUE ia[1] = { LONG2NUM(axis) };
@@ -493,13 +547,13 @@ median_object_flat (VALUE self, long min_count, VALUE fill_value, int keep_axis)
493
547
  if ( RTEST(rb_ca_has_mask(self)) ) {
494
548
  CArray *mc; GetCArray(self, mc);
495
549
  long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
496
- if ( cnt < min_count ) filled = 1;
550
+ if ( ca_too_few_present(cnt, min_count, 1) ) filled = 1;
497
551
  else src = rb_ca_fetch(self, ID2SYM(id_is_not_masked));
498
552
  }
499
553
  if ( !filled ) {
500
554
  CArray *sc;
501
555
  GetCArray(src, sc);
502
- if ( sc->elements == 0 ) filled = 1;
556
+ if ( ca_too_few_present(sc->elements, min_count, 1) ) filled = 1;
503
557
  }
504
558
  if ( filled ) {
505
559
  result = NIL_P(fill_value) ? CA_UNDEF : fill_value;
@@ -517,31 +571,28 @@ median_object_flat (VALUE self, long min_count, VALUE fill_value, int keep_axis)
517
571
  pre-fetched). Reached ONLY by CA_OBJECT percentile/quantile -- never
518
572
  by numeric (that path uses pct_compute) and never by median.
519
573
  The :linear / :midpoint interpolation funcalls are irreducible for
520
- arbitrary objects; the *1.0 in the pick methods is float-promotion to
521
- match the numeric "always Float" output contract. */
574
+ arbitrary objects; the pick methods return the element itself. */
522
575
  static VALUE
523
576
  pct_compute_object (VALUE method, long k, double r, long n, VALUE lo, VALUE hi)
524
577
  {
525
- if ( method == sym_lower ) return rb_funcall(lo, id_mul, 1, DBL2NUM(1.0));
526
- if ( method == sym_higher ) {
527
- VALUE v = (r == 0.0) ? lo : hi;
528
- return rb_funcall(v, id_mul, 1, DBL2NUM(1.0));
529
- }
578
+ if ( method == sym_lower ) return lo;
579
+ if ( method == sym_higher ) return (r == 0.0) ? lo : hi;
530
580
  if ( method == sym_nearest ) {
531
581
  int use_k = (r < 0.5) || (r == 0.5 && (k % 2 == 0));
532
- return rb_funcall(use_k ? lo : hi, id_mul, 1, DBL2NUM(1.0));
582
+ return use_k ? lo : hi;
533
583
  }
534
584
  if ( method == sym_linear ) {
535
- if ( r == 0.0 || k + 1 >= n ) return rb_funcall(lo, id_mul, 1, DBL2NUM(1.0));
585
+ if ( r == 0.0 || k + 1 >= n ) return lo;
536
586
  mp_require_interpolable("percentile", method, lo);
537
- VALUE a = rb_funcall(lo, id_mul, 1, DBL2NUM(1.0 - r));
538
- VALUE b = rb_funcall(hi, id_mul, 1, DBL2NUM(r));
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);
539
590
  return rb_funcall(a, id_plus, 1, b);
540
591
  }
541
592
  if ( method == sym_midpoint ) {
542
- if ( k + 1 >= n ) return rb_funcall(lo, id_mul, 1, DBL2NUM(1.0));
593
+ if ( k + 1 >= n ) return lo;
543
594
  mp_require_interpolable("percentile", method, lo);
544
- return rb_funcall(rb_funcall(lo, id_plus, 1, hi), id_div, 1, DBL2NUM(2.0));
595
+ return obj_midpoint(lo, hi);
545
596
  }
546
597
  rb_raise(rb_eArgError, "percentile: invalid method (BUG)");
547
598
  }
@@ -550,11 +601,8 @@ pct_compute_object (VALUE method, long k, double r, long n, VALUE lo, VALUE hi)
550
601
  static VALUE
551
602
  pct_object_one_partition (VALUE self, long axis, long n, double p, VALUE method)
552
603
  {
553
- if ( p == 100.0 )
554
- return rb_funcall(obj_call_axis(self, id_max, LONG2NUM(axis)),
555
- id_mul, 1, DBL2NUM(1.0));
556
604
  if ( n == 1 )
557
- return rb_funcall(obj_slice(self, axis, 0), id_mul, 1, DBL2NUM(1.0));
605
+ return obj_slice(self, axis, 0);
558
606
  double f = (n - 1) * p / 100.0;
559
607
  long k = (long) floor(f);
560
608
  double r = f - k;
@@ -570,9 +618,9 @@ static VALUE
570
618
  pct_object_one_sorted (VALUE sorted, long axis, long n, double p, VALUE method)
571
619
  {
572
620
  if ( p == 100.0 )
573
- return rb_funcall(obj_slice(sorted, axis, n - 1), id_mul, 1, DBL2NUM(1.0));
621
+ return obj_slice(sorted, axis, n - 1);
574
622
  if ( n == 1 )
575
- return rb_funcall(obj_slice(sorted, axis, 0), id_mul, 1, DBL2NUM(1.0));
623
+ return obj_slice(sorted, axis, 0);
576
624
  double f = (n - 1) * p / 100.0;
577
625
  long k = (long) floor(f);
578
626
  double r = f - k;
@@ -652,7 +700,7 @@ pct_object_axis_masked_one (VALUE self, VALUE sorted, long axis, double p,
652
700
  for ( long j = 0; j < M; j++ )
653
701
  if ( !sm[f0 + (ca_size_t)(j * INNER)] ) np++;
654
702
  }
655
- if ( np == 0 || np < min_count ) {
703
+ if ( ca_too_few_present(np, min_count, 1) ) {
656
704
  if ( is_undef ) {
657
705
  if ( !co->mask ) ca_create_mask(co);
658
706
  ((boolean8_t *) co->mask->ptr)[oc] = 1;
@@ -738,13 +786,13 @@ percentile_object_flat (VALUE self, VALUE pers, long min_count, VALUE fill_value
738
786
  if ( RTEST(rb_ca_has_mask(self)) ) {
739
787
  CArray *mc; GetCArray(self, mc);
740
788
  long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
741
- if ( cnt < min_count ) filled = 1;
789
+ if ( ca_too_few_present(cnt, min_count, 1) ) filled = 1;
742
790
  else src = rb_ca_fetch(self, ID2SYM(id_is_not_masked));
743
791
  }
744
792
  if ( !filled ) {
745
793
  CArray *sc;
746
794
  GetCArray(src, sc);
747
- if ( sc->elements == 0 ) filled = 1;
795
+ if ( ca_too_few_present(sc->elements, min_count, 1) ) filled = 1;
748
796
  }
749
797
  if ( filled ) {
750
798
  VALUE fill = NIL_P(fill_value) ? CA_UNDEF : fill_value;
@@ -765,7 +813,7 @@ static VALUE median_axis_masked (VALUE self, long axis, long min_count,
765
813
  /* [entry] median(axis:, min_count:, fill_value:, keep_axis:). Dispatches
766
814
  fixlen -> reject, object -> object lane, numeric -> numeric lane. */
767
815
  static VALUE
768
- rb_ca_median_m (int argc, VALUE *argv, VALUE self)
816
+ rb_ca_median_m_body (int argc, VALUE *argv, VALUE self)
769
817
  {
770
818
  VALUE ropt = rb_pop_options(&argc, &argv);
771
819
  VALUE raxis = Qnil, rmin_count = INT2FIX(0), fill_value = Qnil, rkeep = Qfalse;
@@ -776,6 +824,15 @@ rb_ca_median_m (int argc, VALUE *argv, VALUE self)
776
824
 
777
825
  CArray *ca;
778
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
+ }
779
836
  /* Boolean rides the f64 lane: 0/1 -> 0.0/1.0, so median interpolates and
780
837
  returns a float (a 2-element median averages to 0.5), matching how the
781
838
  numeric lane treats integer input. */
@@ -784,17 +841,25 @@ rb_ca_median_m (int argc, VALUE *argv, VALUE self)
784
841
  GetCArray(self, ca);
785
842
  }
786
843
  int8_t dt = ca->data_type;
787
- if ( ca_is_fixlen_type(ca) )
844
+ if ( ca_is_fixlen_type(ca) ) {
845
+ ca_face_refuse_numeric(self, "median", rb_eCADataTypeError);
788
846
  rb_raise(rb_eCADataTypeError,
789
847
  "median: not defined for fixlen (no numeric midpoint); "
790
848
  "use a numeric or object array");
849
+ }
791
850
  int is_obj = (dt == CA_OBJECT);
792
851
  if ( !is_obj && !mp_is_numeric(dt) )
793
- 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]);
794
853
 
795
- long min_count = NUM2LONG(rmin_count);
796
- if ( min_count < 0 )
797
- 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
+ }
798
863
  int keep_axis = RTEST(rkeep);
799
864
 
800
865
  if ( !NIL_P(raxis) ) {
@@ -858,7 +923,7 @@ pct_fill (double *op, int8_t dt, const char *base, int from_sorted,
858
923
  MP_TYPES(MP_CASE)
859
924
  #undef MP_CASE
860
925
  default:
861
- 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]);
862
927
  }
863
928
  }
864
929
 
@@ -886,7 +951,7 @@ pct_fill (double *op, int8_t dt, const char *base, int from_sorted,
886
951
  for ( long j = 0; j < M; j++ ) \
887
952
  if ( !sm[f0 + (ca_size_t)(j * INNER)] ) np++; \
888
953
  } \
889
- if ( np == 0 || np < min_count ) { \
954
+ if ( ca_too_few_present(np, min_count, 1) ) { \
890
955
  if ( is_undef ) { \
891
956
  if ( !co->mask ) ca_create_mask(co); \
892
957
  ((boolean8_t *) co->mask->ptr)[oc] = 1; \
@@ -927,7 +992,7 @@ pct_fill_masked (CArray *co, int8_t dt, const char *base, const boolean8_t *sm,
927
992
  MP_TYPES(MP_CASE)
928
993
  #undef MP_CASE
929
994
  default:
930
- 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]);
931
996
  }
932
997
  }
933
998
 
@@ -1116,7 +1181,7 @@ pct_flat (VALUE self, VALUE pers_ary, long min_count, VALUE fill_value,
1116
1181
  if ( RTEST(rb_ca_has_mask(self)) ) {
1117
1182
  CArray *mc; GetCArray(self, mc);
1118
1183
  long cnt = (long) ca_count_not_masked(mc); /* = elements - count_masked */
1119
- if ( cnt < min_count ) {
1184
+ if ( ca_too_few_present(cnt, min_count, 1) ) {
1120
1185
  masked_out = 1; is_undef = NIL_P(fill_value);
1121
1186
  if ( !is_undef ) fillv = NUM2DBL(fill_value);
1122
1187
  } else {
@@ -1126,7 +1191,7 @@ pct_flat (VALUE self, VALUE pers_ary, long min_count, VALUE fill_value,
1126
1191
  if ( !masked_out ) {
1127
1192
  CArray *sc;
1128
1193
  GetCArray(src, sc);
1129
- if ( sc->elements == 0 ) {
1194
+ if ( ca_too_few_present(sc->elements, min_count, 1) ) {
1130
1195
  masked_out = 1; is_undef = NIL_P(fill_value);
1131
1196
  if ( !is_undef ) fillv = NUM2DBL(fill_value);
1132
1197
  }
@@ -1174,7 +1239,10 @@ pct_flatten_validate_pers (VALUE pers)
1174
1239
  rb_raise(rb_eArgError, "percentile: at least one p value required");
1175
1240
  for ( long i = 0; i < len; i++ ) {
1176
1241
  VALUE p = rb_ary_entry(pers, i);
1177
- 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) )
1178
1246
  rb_raise(rb_eArgError,
1179
1247
  "percentile: p must be Numeric in [0,100] (got %"PRIsVALUE")",
1180
1248
  rb_inspect(p));
@@ -1186,9 +1254,8 @@ pct_flatten_validate_pers (VALUE pers)
1186
1254
  keep_axis:). Dispatches fixlen -> reject, object -> object lane,
1187
1255
  numeric -> numeric lane. */
1188
1256
  static VALUE
1189
- rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1257
+ rb_ca_percentile_kw (int argc, VALUE *argv, VALUE ropt, VALUE self)
1190
1258
  {
1191
- VALUE ropt = rb_pop_options(&argc, &argv);
1192
1259
  VALUE raxis = Qnil, rmin_count = INT2FIX(0), fill_value = Qnil,
1193
1260
  method = sym_linear, rkeep = Qfalse;
1194
1261
  rb_scan_options(ropt, "axis,min_count,fill_value,method,keep_axis",
@@ -1196,6 +1263,15 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1196
1263
 
1197
1264
  CArray *ca;
1198
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
+ }
1199
1275
  /* Boolean rides the f64 lane (0/1 -> 0.0/1.0), so percentile / quantile
1200
1276
  interpolate and return a float, matching the integer lane. */
1201
1277
  if ( ca->data_type == CA_BOOLEAN ) {
@@ -1203,16 +1279,24 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1203
1279
  GetCArray(self, ca);
1204
1280
  }
1205
1281
  int8_t dt = ca->data_type;
1206
- if ( ca_is_fixlen_type(ca) )
1282
+ if ( ca_is_fixlen_type(ca) ) {
1283
+ ca_face_refuse_numeric(self, "percentile", rb_eCADataTypeError);
1207
1284
  rb_raise(rb_eCADataTypeError,
1208
1285
  "percentile: not defined for fixlen; use a numeric or object array");
1286
+ }
1209
1287
  int is_obj = (dt == CA_OBJECT);
1210
1288
  if ( !is_obj && !mp_is_numeric(dt) )
1211
- 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]);
1212
1290
 
1213
- long min_count = NUM2LONG(rmin_count);
1214
- if ( min_count < 0 )
1215
- 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
+ }
1216
1300
 
1217
1301
  VALUE pers = pct_flatten_validate_pers(rb_ary_new_from_values(argc, argv));
1218
1302
  pct_validate_method(method);
@@ -1230,7 +1314,7 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1230
1314
  flattened first and follows the same rule (length 1 unwraps). */
1231
1315
  VALUE result;
1232
1316
  if ( !NIL_P(raxis) ) {
1233
- long axis = rb_ca_normalize_axis_value(self, raxis, "percentile");
1317
+ long axis = rb_ca_normalize_axis_value(self, raxis, NULL);
1234
1318
  /* mask / min_count / fill_value require the per-fiber select (each fiber
1235
1319
  has its own n_present); the plain partition/sort path assumes a
1236
1320
  uniform n. These paths bake keep_axis into the reduced output. */
@@ -1271,11 +1355,18 @@ rb_ca_percentile_m (int argc, VALUE *argv, VALUE self)
1271
1355
  return result;
1272
1356
  }
1273
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
+
1274
1365
  /* [entry] quantile(axis:, keep_axis:) = percentile(0, 25, 50, 75, 100, ...).
1275
1366
  Return shape: Array<Float> len 5 (flat) or Array<CArray> len 5 (per-axis),
1276
1367
  matching percentile's multi-p wrapping since we pass 5 fixed p values. */
1277
1368
  static VALUE
1278
- rb_ca_quantile_m (int argc, VALUE *argv, VALUE self)
1369
+ rb_ca_quantile_m_body (int argc, VALUE *argv, VALUE self)
1279
1370
  {
1280
1371
  VALUE ropt = rb_pop_options(&argc, &argv);
1281
1372
  if ( argc != 0 )
@@ -1285,9 +1376,62 @@ rb_ca_quantile_m (int argc, VALUE *argv, VALUE self)
1285
1376
  VALUE opts = rb_hash_new();
1286
1377
  if ( !NIL_P(raxis) ) rb_hash_aset(opts, ID2SYM(rb_intern("axis")), raxis);
1287
1378
  rb_hash_aset(opts, ID2SYM(rb_intern("keep_axis")), rkeep);
1288
- VALUE pa[6] = { INT2FIX(0), INT2FIX(25), INT2FIX(50), INT2FIX(75), INT2FIX(100),
1289
- opts };
1290
- 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);
1291
1435
  }
1292
1436
 
1293
1437
  void
@@ -1297,6 +1441,7 @@ Init_carray_median_percentile (void)
1297
1441
  id_sort_copy = rb_intern("sort_copy");
1298
1442
  id_is_not_masked = rb_intern("is_not_masked");
1299
1443
  id_plus = rb_intern("+"); id_div = rb_intern("/"); id_mul = rb_intern("*");
1444
+ id_rationalize = rb_intern("rationalize");
1300
1445
  id_min = rb_intern("min"); id_max = rb_intern("max");
1301
1446
  id_sort = rb_intern("sort"); id_copy = rb_intern("copy");
1302
1447
  id_aref = rb_intern("[]"); id_aset = rb_intern("[]=");