carray 3.0.3 → 3.0.5

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 (221) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +469 -0
  3. data/README.md +3 -2
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_array_pool.c +25 -1
  6. data/ext/ca_axis_descriptor.h +27 -24
  7. data/ext/ca_axis_dispatch.c +14 -26
  8. data/ext/ca_axis_group.c +27 -27
  9. data/ext/ca_binop_dispatch.c +1 -29
  10. data/ext/ca_binop_dispatch.h +0 -1
  11. data/ext/ca_categorical_iterator.c +231 -153
  12. data/ext/ca_compare.c +9 -20
  13. data/ext/ca_composite_dispatch.c +9 -9
  14. data/ext/ca_group_iter.c +16 -17
  15. data/ext/ca_kernel_iterator.c +1240 -2000
  16. data/ext/ca_kernel_iterator.h +502 -519
  17. data/ext/ca_monop_dispatch.c +82 -59
  18. data/ext/ca_monop_dispatch.h +1 -0
  19. data/ext/ca_obj_array.c +152 -45
  20. data/ext/ca_obj_bincmp.c +25 -21
  21. data/ext/ca_obj_binop.c +16 -26
  22. data/ext/ca_obj_bitarray.c +8 -3
  23. data/ext/ca_obj_bitfield.c +63 -49
  24. data/ext/ca_obj_block.c +18 -9
  25. data/ext/ca_obj_byte_swap.c +7 -7
  26. data/ext/ca_obj_const_string.c +188 -22
  27. data/ext/ca_obj_face.c +280 -58
  28. data/ext/ca_obj_face.h +19 -0
  29. data/ext/ca_obj_fake.c +15 -29
  30. data/ext/ca_obj_farray.c +6 -3
  31. data/ext/ca_obj_field.c +30 -8
  32. data/ext/ca_obj_fixlen_string.c +2 -3
  33. data/ext/ca_obj_grid.c +455 -134
  34. data/ext/ca_obj_meld.c +172 -34
  35. data/ext/ca_obj_moncmp.c +23 -8
  36. data/ext/ca_obj_monop.c +113 -67
  37. data/ext/ca_obj_object.c +210 -52
  38. data/ext/ca_obj_record.c +15 -7
  39. data/ext/ca_obj_reduce.c +17 -8
  40. data/ext/ca_obj_refer.c +42 -35
  41. data/ext/ca_obj_remap.c +30 -9
  42. data/ext/ca_obj_repeat.c +9 -10
  43. data/ext/ca_obj_roll.c +31 -10
  44. data/ext/ca_obj_select.c +48 -14
  45. data/ext/ca_obj_select_axis.c +15 -47
  46. data/ext/ca_obj_shift.c +41 -7
  47. data/ext/ca_obj_stack.c +180 -34
  48. data/ext/ca_obj_stride.c +190 -134
  49. data/ext/ca_obj_string.c +80 -4
  50. data/ext/ca_obj_tile.c +32 -11
  51. data/ext/ca_obj_time.c +22 -6
  52. data/ext/ca_obj_timedelta.c +22 -6
  53. data/ext/ca_obj_transpose.c +13 -7
  54. data/ext/ca_obj_triop.c +26 -32
  55. data/ext/ca_obj_window.c +267 -74
  56. data/ext/ca_op_ipower.c +4 -4
  57. data/ext/ca_sort_kernels.h +6 -3
  58. data/ext/ca_sweep_engine.c +2 -2
  59. data/ext/caframe_csv_reader.c +80 -1
  60. data/ext/carray.h +214 -75
  61. data/ext/carray_access.c +200 -99
  62. data/ext/carray_address_basis.c +343 -87
  63. data/ext/carray_attribute.c +113 -6
  64. data/ext/carray_bincount.c +10 -8
  65. data/ext/carray_broadcast.c +13 -8
  66. data/ext/carray_build_flags.h +3 -0
  67. data/ext/carray_call_cfunc.c +108 -0
  68. data/ext/carray_cast.c +375 -96
  69. data/ext/carray_cast_func.rb +90 -20
  70. data/ext/carray_class.c +4 -3
  71. data/ext/carray_config.h +28 -0
  72. data/ext/carray_conversion.c +18 -9
  73. data/ext/carray_copy.c +51 -14
  74. data/ext/carray_core.c +150 -55
  75. data/ext/carray_count.c +56 -20
  76. data/ext/carray_element.c +81 -33
  77. data/ext/carray_factorize.c +138 -84
  78. data/ext/carray_generate.c +199 -52
  79. data/ext/carray_histogram.c +63 -18
  80. data/ext/carray_hold.c +12 -15
  81. data/ext/carray_index_classifier.c +388 -176
  82. data/ext/carray_internal.h +348 -29
  83. data/ext/carray_kernels_bincmp.c +4453 -0
  84. data/ext/carray_kernels_binop.c +11368 -0
  85. data/ext/carray_kernels_init.c +1734 -0
  86. data/ext/carray_kernels_moncmp.c +2104 -0
  87. data/ext/carray_kernels_monop.c +18369 -0
  88. data/ext/carray_kernels_reduce_aggregate.c +23818 -0
  89. data/ext/carray_kernels_reduce_boolean.c +406 -0
  90. data/ext/carray_kernels_reduce_cumulative.c +10086 -0
  91. data/ext/carray_kernels_reduce_extreme.c +17634 -0
  92. data/ext/carray_kernels_reduce_variance.c +3849 -0
  93. data/ext/carray_kernels_scan.c +4302 -0
  94. data/ext/carray_kernels_search.c +33829 -0
  95. data/ext/carray_kernels_sort.c +10897 -0
  96. data/ext/carray_kernels_triop.c +1864 -0
  97. data/ext/carray_key_windows.c +220 -0
  98. data/ext/carray_lazy.c +170 -111
  99. data/ext/carray_mask.c +337 -94
  100. data/ext/carray_median_percentile.c +82 -28
  101. data/ext/carray_memory_view.c +669 -561
  102. data/ext/carray_operator.c +129 -52
  103. data/ext/carray_order.c +604 -77
  104. data/ext/carray_partition.c +28 -27
  105. data/ext/carray_random.c +85 -31
  106. data/ext/carray_round.c +273 -0
  107. data/ext/carray_scatter.c +87 -45
  108. data/ext/carray_segment.c +3 -3
  109. data/ext/carray_slab.c +123 -37
  110. data/ext/carray_sort.c +259 -51
  111. data/ext/carray_sort_kernel.c +5 -7
  112. data/ext/carray_struct.c +54 -61
  113. data/ext/carray_test.c +26 -5
  114. data/ext/carray_time_text.c +4 -2
  115. data/ext/carray_undef.c +4 -0
  116. data/ext/carray_utils.c +309 -66
  117. data/ext/mk_call_cfunc.rb +19 -2
  118. data/ext/mkkernel.rb +403 -350
  119. data/ext/ruby_carray.c +10 -4
  120. data/ext/version.h +4 -4
  121. data/lib/carray/attribute.rb +139 -22
  122. data/lib/carray/autoload_carray.rb +8 -31
  123. data/lib/carray/autoload_method_extension.rb +14 -5
  124. data/lib/carray/axis_group.rb +255 -106
  125. data/lib/carray/basics.rb +105 -17
  126. data/lib/carray/bincount_nd.rb +12 -2
  127. data/lib/carray/block_iterator.rb +126 -23
  128. data/lib/carray/boolean_reduce.rb +17 -17
  129. data/lib/carray/categorical.rb +195 -59
  130. data/lib/carray/categorical_iterator.rb +132 -29
  131. data/lib/carray/complex.rb +5 -5
  132. data/lib/carray/conditional.rb +67 -18
  133. data/lib/carray/const_string.rb +24 -5
  134. data/lib/carray/construct.rb +15 -11
  135. data/lib/carray/data_type_extension.rb +9 -1
  136. data/lib/carray/distance_limit.rb +77 -0
  137. data/lib/carray/fixlen_string.rb +48 -3
  138. data/lib/carray/frame/column_verbs.rb +71 -0
  139. data/lib/carray/frame/concat.rb +17 -3
  140. data/lib/carray/frame/convert.rb +69 -0
  141. data/lib/carray/frame/csv_parser.rb +111 -17
  142. data/lib/carray/frame/describe.rb +3 -2
  143. data/lib/carray/frame/frame.rb +252 -28
  144. data/lib/carray/frame/group.rb +603 -101
  145. data/lib/carray/frame/io.rb +117 -28
  146. data/lib/carray/frame/join.rb +312 -51
  147. data/lib/carray/frame/records.rb +83 -7
  148. data/lib/carray/frame/reshape.rb +42 -35
  149. data/lib/carray/frame/sort.rb +7 -19
  150. data/lib/carray/frame/verbs.rb +188 -48
  151. data/lib/carray/frame/windows.rb +117 -0
  152. data/lib/carray/frame.rb +2 -0
  153. data/lib/carray/fuse_source.rb +42 -6
  154. data/lib/carray/fusion.rb +40 -16
  155. data/lib/carray/histogram.rb +6 -1
  156. data/lib/carray/inspect.rb +31 -8
  157. data/lib/carray/iterator.rb +85 -7
  158. data/lib/carray/key_windows.rb +620 -0
  159. data/lib/carray/lazy.rb +217 -183
  160. data/lib/carray/mask_gap_fill.rb +63 -57
  161. data/lib/carray/meld_reduce.rb +12 -14
  162. data/lib/carray/methods/bin.rb +26 -18
  163. data/lib/carray/methods/bincount.rb +16 -0
  164. data/lib/carray/methods/bit_string.rb +1 -1
  165. data/lib/carray/methods/choose.rb +16 -3
  166. data/lib/carray/methods/composition.rb +3 -1
  167. data/lib/carray/methods/gather_nd.rb +14 -5
  168. data/lib/carray/methods/insert_block.rb +2 -2
  169. data/lib/carray/methods/is_between.rb +59 -0
  170. data/lib/carray/methods/is_in.rb +61 -37
  171. data/lib/carray/methods/join.rb +1 -6
  172. data/lib/carray/methods/lag.rb +49 -0
  173. data/lib/carray/methods/locate_addr.rb +47 -51
  174. data/lib/carray/methods/mode.rb +1 -1
  175. data/lib/carray/methods/nunique.rb +11 -6
  176. data/lib/carray/methods/resize.rb +3 -3
  177. data/lib/carray/methods/value_counts.rb +4 -11
  178. data/lib/carray/record_order.rb +3 -3
  179. data/lib/carray/rng.rb +3 -0
  180. data/lib/carray/runtime.rb +53 -8
  181. data/lib/carray/segment_iterator.rb +323 -88
  182. data/lib/carray/serialize.rb +45 -14
  183. data/lib/carray/slab_iterator.rb +50 -0
  184. data/lib/carray/stack.rb +15 -12
  185. data/lib/carray/string.rb +113 -5
  186. data/lib/carray/string_operation_extension.rb +21 -6
  187. data/lib/carray/struct.rb +9 -21
  188. data/lib/carray/struct_builder.rb +24 -11
  189. data/lib/carray/time.rb +819 -158
  190. data/lib/carray/window_iterator.rb +122 -16
  191. data/lib/carray.rb +7 -0
  192. data/yard-stubs/ca_obj_bitfield.rb +1 -1
  193. data/yard-stubs/ca_obj_field.rb +4 -0
  194. data/yard-stubs/ca_obj_grid.rb +21 -10
  195. data/yard-stubs/ca_obj_refer.rb +2 -1
  196. data/yard-stubs/ca_obj_roll.rb +2 -2
  197. data/yard-stubs/ca_obj_shift.rb +5 -1
  198. data/yard-stubs/ca_obj_tile.rb +1 -1
  199. data/yard-stubs/ca_obj_transpose.rb +5 -4
  200. data/yard-stubs/ca_obj_window.rb +8 -3
  201. data/yard-stubs/carray_access.rb +6 -1
  202. data/yard-stubs/carray_attribute.rb +3 -6
  203. data/yard-stubs/carray_cast.rb +12 -0
  204. data/yard-stubs/carray_conversion.rb +7 -2
  205. data/yard-stubs/carray_copy.rb +4 -2
  206. data/yard-stubs/carray_count.rb +4 -4
  207. data/yard-stubs/carray_element.rb +17 -10
  208. data/yard-stubs/carray_generate.rb +21 -1
  209. data/yard-stubs/carray_mask.rb +32 -3
  210. data/yard-stubs/carray_math.rb +41 -5
  211. data/yard-stubs/carray_median_percentile.rb +5 -2
  212. data/yard-stubs/carray_memory_view.rb +40 -23
  213. data/yard-stubs/carray_order.rb +129 -22
  214. data/yard-stubs/carray_random.rb +13 -4
  215. data/yard-stubs/carray_scatter.rb +17 -10
  216. data/yard-stubs/carray_slab.rb +9 -2
  217. data/yard-stubs/carray_sort.rb +6 -5
  218. data/yard-stubs/carray_test.rb +5 -3
  219. metadata +25 -3
  220. data/lib/carray/arrow_tensor.rb +0 -401
  221. data/lib/carray/table.rb +0 -108
@@ -50,7 +50,8 @@
50
50
  (Float#eql? treats them equal). The object lane adds one deviation from Ruby
51
51
  Hash, aligning it with the numeric lane: every Float NaN collapses to one
52
52
  distinct value (a NaN takes a fixed canonical key and matches any stored Float
53
- NaN), where Ruby Hash would keep distinct NaN objects apart.
53
+ NaN), where Ruby Hash would keep distinct NaN objects apart, and a Complex
54
+ with a NaN part is one value with any Complex whose parts match it that way.
54
55
  __factorize_appearance__ and the discovery members (unique, value_counts,
55
56
  mask_duplicates) all cover the numeric / object / fixlen lanes.
56
57
 
@@ -77,6 +78,7 @@
77
78
 
78
79
  #include "carray.h"
79
80
  #include "ca_obj_face.h"
81
+ #include "carray_internal.h" /* ca_object_cells_clear */
80
82
  #include <string.h>
81
83
  #include <math.h>
82
84
 
@@ -175,6 +177,7 @@ fz_complex_key (VALUE self)
175
177
  if ( ca_has_mask(ca) ) {
176
178
  ca_copy_mask(co, ca);
177
179
  }
180
+ RB_GC_GUARD(self); /* read through ca after rb_carray_new allocates */
178
181
  return out;
179
182
  }
180
183
 
@@ -206,24 +209,19 @@ fz_face_descend (VALUE self, volatile VALUE *pface, const char *name)
206
209
  }
207
210
  if ( ca_is_face(ca) ) {
208
211
  /* A record is its bytes: two records are the same value exactly when
209
- their bytes are, so the fixlen lane answers for it. */
210
- if ( ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE)
211
- || ca->obj_type == CA_OBJ_RECORD ) {
212
- *pface = self;
213
- return rb_ca_strip_face_value(self);
212
+ their bytes are, so the fixlen lane answers for it. Any other Face
213
+ is descended only when ORDERABLE. One that cannot be descended must
214
+ not flow on undescended: whatever reads it next reads its storage
215
+ anyway, which is the encoding rather than the values. A Face in this
216
+ position normally never arrives -- CAConstString and CACategorical
217
+ define their own members in Ruby -- but the overrides only fire when
218
+ the Face is the receiver, and this same helper descends the
219
+ *reference* for locate_addr, where it is the argument. */
220
+ if ( ca->obj_type != CA_OBJ_RECORD ) {
221
+ ca_face_require_orderable(self, name, "compare");
214
222
  }
215
- /* A Face that cannot be descended must not flow on undescended: whatever
216
- reads it next reads its storage anyway, which is the encoding rather
217
- than the values. A Face in this position normally never arrives --
218
- CAConstString and CACategorical define their own members in Ruby -- but
219
- the overrides only fire when the Face is the receiver, and this same
220
- helper descends the *reference* for locate_addr, where it is the
221
- argument. */
222
- rb_raise(rb_eArgError,
223
- "%s: %s is not orderable by storage (a cell encodes the value "
224
- "rather than being it); convert it first -- %s -- or pass "
225
- ".parent to work in storage space",
226
- name, rb_obj_classname(self), ca_face_conversion_hint(self));
223
+ *pface = self;
224
+ return rb_ca_strip_face_value(self);
227
225
  }
228
226
  *pface = Qnil;
229
227
  return self;
@@ -250,6 +248,7 @@ fz_face_reconcile (VALUE reference, VALUE operand, const char *name)
250
248
  }
251
249
  GetCArray(operand, op);
252
250
  if ( op->data_type != ca->data_type ) {
251
+ /* coerce: the callers have met both sides at result_type already */
253
252
  operand = rb_ca_to_type(operand, INT2NUM(ca->data_type), Qnil);
254
253
  }
255
254
  return fz_complex_key(operand);
@@ -402,8 +401,7 @@ fz_hash_alloc (fz_hash *h, ca_size_t cap)
402
401
  h->cap = cap;
403
402
  MEMZERO(h->used, uint8_t, cap);
404
403
  if ( has_val ) {
405
- ca_size_t i;
406
- for ( i = 0; i < cap; i++ ) h->val[i] = Qnil;
404
+ ca_object_cells_clear(h->val, cap);
407
405
  fz_tmp_values(h->hbuf, h->val, cap);
408
406
  }
409
407
  }
@@ -512,25 +510,84 @@ fz_is_float_nan (VALUE v)
512
510
  return RB_FLOAT_TYPE_P(v) && isnan(RFLOAT_VALUE(v));
513
511
  }
514
512
 
513
+ /* True when v holds a NaN that eql? would keep apart from an equal value:
514
+ a Float NaN, or a Complex with a Float NaN part. */
515
+ static int
516
+ fz_obj_has_nan (VALUE v)
517
+ {
518
+ if ( fz_is_float_nan(v) ) {
519
+ return 1;
520
+ }
521
+ return RB_TYPE_P(v, T_COMPLEX)
522
+ && ( fz_is_float_nan(rb_complex_real(v))
523
+ || fz_is_float_nan(rb_complex_imag(v)) );
524
+ }
525
+
526
+ /* A part of a NaN-bearing value: every NaN is one value, the rest go by
527
+ eql?, as in Ruby Hash. */
528
+ static uint64_t
529
+ fz_obj_part_key (VALUE v)
530
+ {
531
+ return fz_is_float_nan(v) ? FZ_NAN_KEY_DOUBLE : (uint64_t) NUM2LL(rb_hash(v));
532
+ }
533
+
534
+ static int
535
+ fz_obj_part_eql (VALUE a, VALUE b)
536
+ {
537
+ if ( fz_is_float_nan(a) || fz_is_float_nan(b) ) {
538
+ return fz_is_float_nan(a) && fz_is_float_nan(b);
539
+ }
540
+ return rb_eql(a, b);
541
+ }
542
+
543
+ /* The hash key of an object cell. A NaN-bearing value is keyed part by
544
+ part with every NaN as one value, so it matches the numeric lane: all
545
+ NaN are one value, and a complex value is one value when its parts are. */
546
+ static uint64_t
547
+ fz_obj_key (VALUE v, int has_nan)
548
+ {
549
+ if ( ! has_nan ) {
550
+ return (uint64_t) NUM2LL(rb_hash(v));
551
+ }
552
+ if ( RB_TYPE_P(v, T_COMPLEX) ) {
553
+ return fz_obj_part_key(rb_complex_real(v)) * 31u
554
+ ^ fz_obj_part_key(rb_complex_imag(v)) ^ 0x9e3779b97f4a7c15ULL;
555
+ }
556
+ return FZ_NAN_KEY_DOUBLE;
557
+ }
558
+
559
+ /* Whether a stored cell is the same value as v (has_nan from fz_obj_has_nan). */
560
+ static int
561
+ fz_obj_same (VALUE stored, VALUE v, int has_nan)
562
+ {
563
+ if ( ! has_nan ) {
564
+ return rb_eql(stored, v);
565
+ }
566
+ if ( RB_TYPE_P(v, T_COMPLEX) ) {
567
+ return RB_TYPE_P(stored, T_COMPLEX)
568
+ && fz_obj_part_eql(rb_complex_real(stored), rb_complex_real(v))
569
+ && fz_obj_part_eql(rb_complex_imag(stored), rb_complex_imag(v));
570
+ }
571
+ return fz_is_float_nan(stored);
572
+ }
573
+
515
574
  /* Object-lane intern: key = rb_hash(v) (lossy), collision re-check via rb_eql.
516
575
  Matches Ruby Hash distinctness (`hash` + `eql?`) element for element, with one
517
576
  value-based exception aligning with the numeric lane: every Float NaN collapses
518
577
  to a single distinct value (Ruby Hash keeps distinct NaN objects apart via
519
- eql?, but the discovery family unifies them). A NaN takes a fixed canonical
520
- key and matches any stored Float NaN, so its second and later occurrences fold
521
- into the first. */
578
+ eql?, but the discovery family unifies them), and so does a Complex whose
579
+ parts are the same with every NaN as one (fz_obj_key / fz_obj_same). */
522
580
  static int32_t
523
581
  fz_hash_intern_obj (fz_hash *h, VALUE v, int *is_new)
524
582
  {
525
583
  if ( (h->n + 1) * 10 >= h->cap * 7 ) { fz_hash_grow(h); } /* load factor 0.7 */
526
- int v_nan = fz_is_float_nan(v);
527
- uint64_t key = v_nan ? FZ_NAN_KEY_DOUBLE
528
- : (uint64_t) NUM2LL(rb_hash(v));
584
+ int v_nan = fz_obj_has_nan(v);
585
+ uint64_t key = fz_obj_key(v, v_nan);
529
586
  ca_size_t mask = h->cap - 1;
530
587
  ca_size_t slot = FZ_HOME(h, key);
531
588
  while ( h->used[slot] ) {
532
589
  if ( h->key[slot] == key &&
533
- ( v_nan ? fz_is_float_nan(h->val[slot]) : rb_eql(h->val[slot], v) ) ) {
590
+ fz_obj_same(h->val[slot], v, v_nan) ) {
534
591
  *is_new = 0; return h->code[slot];
535
592
  }
536
593
  slot = (slot + 1) & mask;
@@ -621,14 +678,13 @@ fz_hash_lookup (fz_hash *h, uint64_t key, int32_t *code_out)
621
678
  static int
622
679
  fz_hash_lookup_obj (fz_hash *h, VALUE v, int32_t *code_out)
623
680
  {
624
- int v_nan = fz_is_float_nan(v);
625
- uint64_t key = v_nan ? FZ_NAN_KEY_DOUBLE
626
- : (uint64_t) NUM2LL(rb_hash(v));
681
+ int v_nan = fz_obj_has_nan(v);
682
+ uint64_t key = fz_obj_key(v, v_nan);
627
683
  ca_size_t mask = h->cap - 1;
628
684
  ca_size_t slot = FZ_HOME(h, key);
629
685
  while ( h->used[slot] ) {
630
686
  if ( h->key[slot] == key &&
631
- ( v_nan ? fz_is_float_nan(h->val[slot]) : rb_eql(h->val[slot], v) ) ) {
687
+ fz_obj_same(h->val[slot], v, v_nan) ) {
632
688
  if ( code_out ) { *code_out = h->code[slot]; }
633
689
  return 1;
634
690
  }
@@ -669,9 +725,7 @@ typedef struct {
669
725
  static void
670
726
  fz_levels_values (fz_levels *l, ca_size_t from)
671
727
  {
672
- VALUE *v = (VALUE *) l->p;
673
- ca_size_t i;
674
- for ( i = from; i < l->cap; i++ ) v[i] = Qnil;
728
+ ca_object_cells_clear((VALUE *) l->p + from, l->cap - from);
675
729
  fz_tmp_values(l->hp, l->p, l->cap);
676
730
  }
677
731
 
@@ -717,7 +771,13 @@ fz_levels_free (fz_levels *l)
717
771
  in the middle of a walk. The walks' states live in this frame, outside the
718
772
  method's body, and ca_iter_ensure finishes them however the body leaves;
719
773
  the tables have owners the unwind collects (fz_tmp_alloc). Slots 0
720
- and 1 hold a method's own walks, slot 2 the walk of fz_intern_all. */
774
+ and 1 hold a method's own walks, slot 2 the walk of fz_intern_all.
775
+
776
+ A body that replaces self or an operand with an array it made (complex
777
+ keys, a Face's storage, an operand brought into self's space) writes the
778
+ new VALUE back into the frame. The walks read it only through its
779
+ CArray *, so a C local alone would leave it to the collector halfway
780
+ through. */
721
781
 
722
782
  typedef struct {
723
783
  ca_iter_state st[3];
@@ -761,7 +821,7 @@ fz_factorize_appearance_body (VALUE arg)
761
821
  VALUE self = fr->self;
762
822
  CArray *ca;
763
823
  volatile VALUE face;
764
- self = fz_face_descend(self, &face, "categorize");
824
+ fr->self = self = fz_face_descend(self, &face, "categorize");
765
825
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
766
826
 
767
827
  int8_t dt = ca->data_type;
@@ -805,7 +865,7 @@ fz_factorize_appearance_body (VALUE arg)
805
865
  element to a 64-bit key; equality within one data type is preserved. */
806
866
  #define FZ_LOOP(T, WIDEN) \
807
867
  do { \
808
- const T *ip = (const T *) p_in; \
868
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
809
869
  uint32_t *op = (uint32_t *) p_out; \
810
870
  for ( ca_size_t i = 0; i < n; i++ ) { \
811
871
  if ( m && m[i] ) { op[i] = 0xFFFFFFFFu; continue; } \
@@ -824,7 +884,7 @@ fz_factorize_appearance_body (VALUE arg)
824
884
  kept as-is), so masked source cells store the sentinel. */
825
885
  #define FZ_LOOP_FLOAT(T) \
826
886
  do { \
827
- const T *ip = (const T *) p_in; \
887
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
828
888
  uint32_t *op = (uint32_t *) p_out; \
829
889
  for ( ca_size_t i = 0; i < n; i++ ) { \
830
890
  if ( m && m[i] ) { op[i] = 0xFFFFFFFFu; continue; } \
@@ -1013,7 +1073,7 @@ fz_mask_duplicates_body (VALUE arg)
1013
1073
  interns first appearances, so is_new == 0 flags a duplicate. */
1014
1074
  #define MD_LOOP(T, WIDEN) \
1015
1075
  do { \
1016
- const T *ip = (const T *) p_in; \
1076
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
1017
1077
  boolean8_t *op = (boolean8_t *) p_out; \
1018
1078
  for ( ca_size_t i = 0; i < n; i++ ) { \
1019
1079
  if ( m && m[i] ) { op[i] = 0; continue; } \
@@ -1031,7 +1091,7 @@ fz_mask_duplicates_body (VALUE arg)
1031
1091
  before the bitwise key. */
1032
1092
  #define MD_LOOP_FLOAT(T) \
1033
1093
  do { \
1034
- const T *ip = (const T *) p_in; \
1094
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
1035
1095
  boolean8_t *op = (boolean8_t *) p_out; \
1036
1096
  for ( ca_size_t i = 0; i < n; i++ ) { \
1037
1097
  if ( m && m[i] ) { op[i] = 0; continue; } \
@@ -1132,7 +1192,7 @@ fz_unique_flat_body (VALUE arg)
1132
1192
  VALUE self = fr->self;
1133
1193
  CArray *ca;
1134
1194
  volatile VALUE face;
1135
- self = fz_face_descend(self, &face, "unique");
1195
+ fr->self = self = fz_face_descend(self, &face, "unique");
1136
1196
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1137
1197
 
1138
1198
  int8_t dt = ca->data_type;
@@ -1158,8 +1218,6 @@ fz_unique_flat_body (VALUE arg)
1158
1218
  else { fz_hash_init(&h); }
1159
1219
  fz_levels_init(&lv, (int) ca->bytes, ca->data_type == CA_OBJECT);
1160
1220
 
1161
- int8_t axis = (int8_t) (ca->ndim - 1); /* innermost fiber; hash not reset =
1162
- one seen-set over the whole array */
1163
1221
  ca_iter_state *st_in = &fr->st[0];
1164
1222
  char *p_in;
1165
1223
  boolean8_t *m;
@@ -1167,7 +1225,7 @@ fz_unique_flat_body (VALUE arg)
1167
1225
 
1168
1226
  #define UQ_LOOP(T, WIDEN) \
1169
1227
  do { \
1170
- const T *ip = (const T *) p_in; \
1228
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
1171
1229
  for ( ca_size_t i = 0; i < n; i++ ) { \
1172
1230
  if ( m && m[i] ) { continue; } \
1173
1231
  uint64_t key = (uint64_t) (WIDEN ip[i]); \
@@ -1181,7 +1239,7 @@ fz_unique_flat_body (VALUE arg)
1181
1239
  pushed level is the raw first-seen element. */
1182
1240
  #define UQ_LOOP_FLOAT(T) \
1183
1241
  do { \
1184
- const T *ip = (const T *) p_in; \
1242
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
1185
1243
  for ( ca_size_t i = 0; i < n; i++ ) { \
1186
1244
  if ( m && m[i] ) { continue; } \
1187
1245
  T v = ip[i]; \
@@ -1220,7 +1278,7 @@ fz_unique_flat_body (VALUE arg)
1220
1278
  } \
1221
1279
  } while (0)
1222
1280
 
1223
- CA_FOR_EACH_FIBER_MASKED(*st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
1281
+ CA_FOR_EACH_WHOLE_MASKED(*st_in, ca, CA_KERNEL_READ, p_in, n, m) {
1224
1282
  switch ( dt ) {
1225
1283
  case CA_INT8: UQ_LOOP(int8_t, (int64_t)); break;
1226
1284
  case CA_INT16: UQ_LOOP(int16_t, (int64_t)); break;
@@ -1265,7 +1323,7 @@ rb_ca_unique_flat (VALUE self)
1265
1323
  }
1266
1324
 
1267
1325
  /* Intern every non-masked cell of cv into h, one seen-set over the whole array
1268
- (the hash is not reset between fibers). When lv is non-NULL, the first-seen
1326
+ walked in row-major order. When lv is non-NULL, the first-seen
1269
1327
  raw element of each distinct value is pushed to it (appearance order), so the
1270
1328
  same pass builds both a probe set and the distinct-value list. Dispatches the
1271
1329
  three key lanes exactly as the discovery family: numeric widen with all NaN
@@ -1276,14 +1334,13 @@ static void
1276
1334
  fz_intern_all (ca_iter_state *st_v, fz_hash *h, CArray *cv, fz_levels *lv)
1277
1335
  {
1278
1336
  int8_t dt = cv->data_type;
1279
- int8_t vaxis = (int8_t) (cv->ndim - 1);
1280
1337
  char *p_v;
1281
1338
  boolean8_t *mv;
1282
1339
  ca_size_t nv;
1283
1340
 
1284
1341
  #define FZ_IA(T, WIDEN) \
1285
1342
  do { \
1286
- const T *ip = (const T *) p_v; \
1343
+ const CA_U(T) *ip = (const CA_U(T) *) p_v; \
1287
1344
  for ( ca_size_t i = 0; i < nv; i++ ) { \
1288
1345
  if ( mv && mv[i] ) { continue; } \
1289
1346
  uint64_t key = (uint64_t) (WIDEN ip[i]); \
@@ -1295,7 +1352,7 @@ fz_intern_all (ca_iter_state *st_v, fz_hash *h, CArray *cv, fz_levels *lv)
1295
1352
 
1296
1353
  #define FZ_IA_FLOAT(T) \
1297
1354
  do { \
1298
- const T *ip = (const T *) p_v; \
1355
+ const CA_U(T) *ip = (const CA_U(T) *) p_v; \
1299
1356
  for ( ca_size_t i = 0; i < nv; i++ ) { \
1300
1357
  if ( mv && mv[i] ) { continue; } \
1301
1358
  T v = ip[i]; \
@@ -1329,7 +1386,7 @@ fz_intern_all (ca_iter_state *st_v, fz_hash *h, CArray *cv, fz_levels *lv)
1329
1386
  } \
1330
1387
  } while (0)
1331
1388
 
1332
- CA_FOR_EACH_FIBER_MASKED(*st_v, cv, vaxis, CA_KERNEL_READ, p_v, nv, mv) {
1389
+ CA_FOR_EACH_WHOLE_MASKED(*st_v, cv, CA_KERNEL_READ, p_v, nv, mv) {
1333
1390
  switch ( dt ) {
1334
1391
  case CA_INT8: FZ_IA(int8_t, (int64_t)); break;
1335
1392
  case CA_INT16: FZ_IA(int16_t, (int64_t)); break;
@@ -1376,8 +1433,8 @@ fz_is_in_body (VALUE arg)
1376
1433
  VALUE rvalues = fr->a;
1377
1434
  CArray *ca, *cv;
1378
1435
  volatile VALUE face;
1379
- self = fz_face_descend(self, &face, "is_in");
1380
- rvalues = fz_face_reconcile(face, rvalues, "is_in");
1436
+ fr->self = self = fz_face_descend(self, &face, "is_in");
1437
+ fr->a = rvalues = fz_face_reconcile(face, rvalues, "is_in");
1381
1438
  GetCArray(self, ca);
1382
1439
 
1383
1440
  int8_t dt = ca->data_type;
@@ -1436,7 +1493,7 @@ fz_is_in_body (VALUE arg)
1436
1493
 
1437
1494
  #define IN_PROBE(T, WIDEN) \
1438
1495
  do { \
1439
- const T *ip = (const T *) p_in; \
1496
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
1440
1497
  boolean8_t *op = (boolean8_t *) p_out; \
1441
1498
  for ( ca_size_t i = 0; i < n; i++ ) { \
1442
1499
  if ( m && m[i] ) { op[i] = 0; continue; } \
@@ -1447,7 +1504,7 @@ fz_is_in_body (VALUE arg)
1447
1504
 
1448
1505
  #define IN_PROBE_FLOAT(T) \
1449
1506
  do { \
1450
- const T *ip = (const T *) p_in; \
1507
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
1451
1508
  boolean8_t *op = (boolean8_t *) p_out; \
1452
1509
  for ( ca_size_t i = 0; i < n; i++ ) { \
1453
1510
  if ( m && m[i] ) { op[i] = 0; continue; } \
@@ -1543,8 +1600,8 @@ fz_locate_addr_body (VALUE arg)
1543
1600
  output is an address, so nothing is lifted back. */
1544
1601
  {
1545
1602
  volatile VALUE ref_face;
1546
- rref = fz_face_descend(rref, &ref_face, "locate_addr");
1547
- self = fz_face_reconcile(ref_face, self, "locate_addr");
1603
+ fr->a = rref = fz_face_descend(rref, &ref_face, "locate_addr");
1604
+ fr->self = self = fz_face_reconcile(ref_face, self, "locate_addr");
1548
1605
  }
1549
1606
  GetCArray(self, ca);
1550
1607
 
@@ -1604,7 +1661,7 @@ fz_locate_addr_body (VALUE arg)
1604
1661
 
1605
1662
  #define LOC_BUILD(T, WIDEN) \
1606
1663
  do { \
1607
- const T *ip = (const T *) p_r; \
1664
+ const CA_U(T) *ip = (const CA_U(T) *) p_r; \
1608
1665
  for ( ca_size_t i = 0; i < nr; i++ ) { \
1609
1666
  if ( mr && mr[i] ) { continue; } \
1610
1667
  uint64_t key = (uint64_t) (WIDEN ip[i]); \
@@ -1617,7 +1674,7 @@ fz_locate_addr_body (VALUE arg)
1617
1674
 
1618
1675
  #define LOC_BUILD_FLOAT(T) \
1619
1676
  do { \
1620
- const T *ip = (const T *) p_r; \
1677
+ const CA_U(T) *ip = (const CA_U(T) *) p_r; \
1621
1678
  for ( ca_size_t i = 0; i < nr; i++ ) { \
1622
1679
  if ( mr && mr[i] ) { continue; } \
1623
1680
  T v = ip[i]; \
@@ -1694,7 +1751,7 @@ fz_locate_addr_body (VALUE arg)
1694
1751
 
1695
1752
  #define LOC_PROBE(T, WIDEN) \
1696
1753
  do { \
1697
- const T *ip = (const T *) p_in; \
1754
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
1698
1755
  const int64_t *ab = (const int64_t *) addr.p; \
1699
1756
  for ( ca_size_t i = 0; i < n; i++ ) { \
1700
1757
  ca_size_t o = base + i; \
@@ -1708,7 +1765,7 @@ fz_locate_addr_body (VALUE arg)
1708
1765
 
1709
1766
  #define LOC_PROBE_FLOAT(T) \
1710
1767
  do { \
1711
- const T *ip = (const T *) p_in; \
1768
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
1712
1769
  const int64_t *ab = (const int64_t *) addr.p; \
1713
1770
  for ( ca_size_t i = 0; i < n; i++ ) { \
1714
1771
  ca_size_t o = base + i; \
@@ -1807,8 +1864,8 @@ fz_set_relation_body (VALUE arg)
1807
1864
  int keep_when_hit = fr->i;
1808
1865
  CArray *ca, *co;
1809
1866
  volatile VALUE face;
1810
- self = fz_face_descend(self, &face, "set relation");
1811
- rother = fz_face_reconcile(face, rother, "set relation");
1867
+ fr->self = self = fz_face_descend(self, &face, "set relation");
1868
+ fr->a = rother = fz_face_reconcile(face, rother, "set relation");
1812
1869
  GetCArray(self, ca);
1813
1870
 
1814
1871
  int8_t dt = ca->data_type;
@@ -1872,7 +1929,7 @@ fz_set_relation_body (VALUE arg)
1872
1929
 
1873
1930
  #define SR(T, WIDEN) \
1874
1931
  do { \
1875
- const T *ip = (const T *) p; \
1932
+ const CA_U(T) *ip = (const CA_U(T) *) p; \
1876
1933
  for ( ca_size_t i = 0; i < n; i++ ) { \
1877
1934
  if ( m && m[i] ) { continue; } \
1878
1935
  uint64_t key = (uint64_t) (WIDEN ip[i]); \
@@ -1886,7 +1943,7 @@ fz_set_relation_body (VALUE arg)
1886
1943
 
1887
1944
  #define SR_FLOAT(T) \
1888
1945
  do { \
1889
- const T *ip = (const T *) p; \
1946
+ const CA_U(T) *ip = (const CA_U(T) *) p; \
1890
1947
  for ( ca_size_t i = 0; i < n; i++ ) { \
1891
1948
  if ( m && m[i] ) { continue; } \
1892
1949
  T v = ip[i]; \
@@ -2002,8 +2059,8 @@ fz_set_union_body (VALUE arg)
2002
2059
  VALUE rother = fr->a;
2003
2060
  CArray *ca, *co;
2004
2061
  volatile VALUE face;
2005
- self = fz_face_descend(self, &face, "union");
2006
- rother = fz_face_reconcile(face, rother, "union");
2062
+ fr->self = self = fz_face_descend(self, &face, "union");
2063
+ fr->a = rother = fz_face_reconcile(face, rother, "union");
2007
2064
  GetCArray(self, ca);
2008
2065
 
2009
2066
  int8_t dt = ca->data_type;
@@ -2095,7 +2152,7 @@ fz_value_counts_flat_body (VALUE arg)
2095
2152
  VALUE self = fr->self;
2096
2153
  CArray *ca;
2097
2154
  volatile VALUE face;
2098
- self = fz_face_descend(self, &face, "value_counts");
2155
+ fr->self = self = fz_face_descend(self, &face, "value_counts");
2099
2156
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
2100
2157
 
2101
2158
  int8_t dt = ca->data_type;
@@ -2131,7 +2188,7 @@ fz_value_counts_flat_body (VALUE arg)
2131
2188
 
2132
2189
  #define VC_LOOP(T, WIDEN) \
2133
2190
  do { \
2134
- const T *ip = (const T *) p_in; \
2191
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
2135
2192
  for ( ca_size_t i = 0; i < n; i++ ) { \
2136
2193
  if ( m && m[i] ) { continue; } \
2137
2194
  uint64_t key = (uint64_t) (WIDEN ip[i]); \
@@ -2149,7 +2206,7 @@ fz_value_counts_flat_body (VALUE arg)
2149
2206
 
2150
2207
  #define VC_LOOP_FLOAT(T) \
2151
2208
  do { \
2152
- const T *ip = (const T *) p_in; \
2209
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
2153
2210
  for ( ca_size_t i = 0; i < n; i++ ) { \
2154
2211
  if ( m && m[i] ) { continue; } \
2155
2212
  T v = ip[i]; \
@@ -2255,10 +2312,10 @@ rb_ca_value_counts_flat (VALUE self)
2255
2312
 
2256
2313
  /* @overload __nunique__(axis, keep_axis)
2257
2314
 
2258
- INTERNAL (CArray#nunique). Count the distinct values along axis, one linear
2259
- pass per fiber with an independent seen-set. Returns a reduced
2260
- CA_INT64 CArray of self.shape with axis removed (or kept as length-1 when
2261
- keep_axis). Masked cells do not participate; an all-masked fiber counts 0
2315
+ INTERNAL (CArray#nunique). Count the distinct values along axis (an Integer
2316
+ or an Array of them), one linear pass per fiber with an independent
2317
+ seen-set. Returns a reduced CA_INT64 CArray of self.shape with the axes
2318
+ removed (or kept as length-1 when keep_axis). Masked cells do not participate; an all-masked fiber counts 0
2262
2319
  (an empty set has zero distinct values, not UNDEF -- nunique has identity 0).
2263
2320
  Numeric distinctness collapses all NaN to one value and treats -0.0 == +0.0;
2264
2321
  object keys on rb_hash + rb_eql and fixlen on a byte-hash + memcmp, reproducing
@@ -2290,14 +2347,11 @@ fz_nunique_body (VALUE arg)
2290
2347
  rb_raise(rb_eRuntimeError, "__nunique__: need ndim >= 1");
2291
2348
  }
2292
2349
 
2293
- int axis = NUM2INT(vaxis);
2294
- if ( axis < 0 || axis >= ca->ndim ) {
2295
- rb_raise(rb_eArgError, "__nunique__: axis %d out of range", axis);
2296
- }
2350
+ int8_t axes[CA_RANK_MAX];
2351
+ int8_t naxes = rb_ca_parse_reduce_axes_kw_ctx(vaxis, ca, axes, "nunique");
2297
2352
  int keep_axis = RTEST(vkeep);
2298
2353
 
2299
- int8_t ax = (int8_t) axis;
2300
- VALUE vout = rb_ca_new_reduced(self, &ax, 1, CA_INT64, keep_axis);
2354
+ VALUE vout = rb_ca_new_reduced(self, axes, naxes, CA_INT64, keep_axis);
2301
2355
  CArray *cout;
2302
2356
  TypedData_Get_Struct(vout, CArray, &carray_data_type, cout);
2303
2357
  int64_t *op = (int64_t *) cout->ptr;
@@ -2308,7 +2362,7 @@ fz_nunique_body (VALUE arg)
2308
2362
  else { fz_hash_init(&h); }
2309
2363
 
2310
2364
  ca_iter_state *st = &fr->st[0];
2311
- int rc = ca_iter_state_init_l2(st, ca, CA_SLAB_AXES, &ax, 1, 0);
2365
+ int rc = ca_iter_state_init_l2(st, ca, CA_SLAB_AXES, axes, naxes, 0);
2312
2366
  if ( rc != CA_ITER_OK ) {
2313
2367
  fz_hash_free(&h);
2314
2368
  rb_raise(rb_eRuntimeError, "__nunique__: kernel_iterator init failed rc=%d", rc);
@@ -2462,7 +2516,7 @@ fz_is_mode_body (VALUE arg)
2462
2516
  /* Integer: WIDEN gives the key directly. Two passes over the fiber. */
2463
2517
  #define IM_BODY(T, WIDEN) \
2464
2518
  do { \
2465
- const T *ip = (const T *) p_in; \
2519
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
2466
2520
  boolean8_t *op = (boolean8_t *) p_out; \
2467
2521
  for ( ca_size_t i = 0; i < n; i++ ) { \
2468
2522
  if ( m && m[i] ) { continue; } \
@@ -2489,7 +2543,7 @@ fz_is_mode_body (VALUE arg)
2489
2543
  /* Float: NaN collapses to one canonical key; -0.0 / +0.0 share a key. */
2490
2544
  #define IM_BODY_FLOAT(T) \
2491
2545
  do { \
2492
- const T *ip = (const T *) p_in; \
2546
+ const CA_U(T) *ip = (const CA_U(T) *) p_in; \
2493
2547
  boolean8_t *op = (boolean8_t *) p_out; \
2494
2548
  for ( int pass = 0; pass < 2; pass++ ) { \
2495
2549
  int64_t mx = 0; \
@@ -2641,7 +2695,7 @@ fz_mode_axis_body (VALUE arg)
2641
2695
  VALUE vaxis = fr->a;
2642
2696
  CArray *ca;
2643
2697
  volatile VALUE face;
2644
- self = fz_face_descend(self, &face, "mode");
2698
+ fr->self = self = fz_face_descend(self, &face, "mode");
2645
2699
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
2646
2700
 
2647
2701
  int8_t dt = ca->data_type;