carray 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -0
  3. data/README.md +2 -2
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +33 -4
  6. data/ext/ca_axis_group.c +202 -96
  7. data/ext/ca_categorical_iterator.c +108 -54
  8. data/ext/ca_kernel_iterator.c +317 -51
  9. data/ext/ca_kernel_iterator.h +142 -35
  10. data/ext/ca_obj_array.c +62 -20
  11. data/ext/ca_obj_block.c +4 -4
  12. data/ext/ca_obj_const_string.c +85 -26
  13. data/ext/ca_obj_face.c +24 -0
  14. data/ext/ca_obj_face.h +15 -0
  15. data/ext/ca_obj_fixlen_string.c +18 -5
  16. data/ext/ca_obj_meld.c +123 -25
  17. data/ext/ca_obj_object.c +8 -0
  18. data/ext/ca_obj_select.c +49 -34
  19. data/ext/ca_obj_stack.c +3 -8
  20. data/ext/ca_obj_stride.c +72 -1
  21. data/ext/ca_obj_string.c +8 -4
  22. data/ext/ca_obj_window.c +8 -2
  23. data/ext/ca_op_ipower.c +1 -2
  24. data/ext/ca_rng_normal.h +42 -0
  25. data/ext/ca_rng_xoshiro256pp.h +105 -0
  26. data/ext/ca_sweep_engine.c +307 -143
  27. data/ext/ca_sweep_engine.h +26 -5
  28. data/ext/carray.h +21 -2
  29. data/ext/carray_access.c +32 -20
  30. data/ext/carray_address_basis.c +590 -0
  31. data/ext/carray_broadcast.c +3 -3
  32. data/ext/carray_call_cfunc.c +667 -483
  33. data/ext/carray_cast.c +115 -41
  34. data/ext/carray_copy.c +55 -30
  35. data/ext/carray_core.c +83 -3
  36. data/ext/carray_count.c +9 -10
  37. data/ext/carray_factorize.c +46 -25
  38. data/ext/carray_internal.h +17 -0
  39. data/ext/carray_kernels_reduce_aggregate.c +168 -0
  40. data/ext/carray_kernels_reduce_cumulative.c +270 -1
  41. data/ext/carray_kernels_reduce_extreme.c +554 -8
  42. data/ext/carray_kernels_scan.c +4 -4
  43. data/ext/carray_kernels_search.c +94 -14
  44. data/ext/carray_loop.c +7 -1
  45. data/ext/carray_mask.c +23 -8
  46. data/ext/carray_median_percentile.c +55 -0
  47. data/ext/carray_operator.c +4 -4
  48. data/ext/carray_order.c +1 -1
  49. data/ext/carray_random.c +384 -40
  50. data/ext/carray_slab.c +13 -0
  51. data/ext/carray_sort.c +20 -22
  52. data/ext/mk_call_cfunc.rb +103 -116
  53. data/ext/mkkernel.rb +297 -29
  54. data/ext/ruby_carray.c +10 -1
  55. data/ext/version.h +4 -4
  56. data/lib/carray/autoload_carray.rb +5 -3
  57. data/lib/carray/autoload_method_extension.rb +12 -0
  58. data/lib/carray/axis_group.rb +77 -0
  59. data/lib/carray/basics.rb +4 -0
  60. data/lib/carray/block_iterator.rb +92 -16
  61. data/lib/carray/categorical.rb +150 -33
  62. data/lib/carray/categorical_iterator.rb +207 -80
  63. data/lib/carray/const_string.rb +131 -27
  64. data/lib/carray/construct.rb +40 -0
  65. data/lib/carray/data_type_extension.rb +3 -0
  66. data/lib/carray/data_type_limits.rb +91 -0
  67. data/lib/carray/fixlen_string.rb +1 -1
  68. data/lib/carray/frame/csv_parser.rb +11 -4
  69. data/lib/carray/frame/frame.rb +81 -10
  70. data/lib/carray/frame/group.rb +36 -3
  71. data/lib/carray/frame/io.rb +67 -15
  72. data/lib/carray/frame/records.rb +18 -4
  73. data/lib/carray/frame/verbs.rb +14 -11
  74. data/lib/carray/inspect.rb +42 -9
  75. data/lib/carray/iterator.rb +143 -0
  76. data/lib/carray/lazy.rb +0 -37
  77. data/lib/carray/mask_gap_fill.rb +3 -1
  78. data/lib/carray/methods/discovery_along.rb +74 -0
  79. data/lib/carray/methods/factorize.rb +50 -0
  80. data/lib/carray/methods/is_in.rb +13 -2
  81. data/lib/carray/methods/locate_addr.rb +75 -2
  82. data/lib/carray/methods/mask_duplicates.rb +35 -1
  83. data/lib/carray/methods/nunique.rb +22 -1
  84. data/lib/carray/methods/repeat.rb +110 -0
  85. data/lib/carray/methods/unique.rb +41 -1
  86. data/lib/carray/rng.rb +86 -0
  87. data/lib/carray/slab_iterator.rb +58 -13
  88. data/lib/carray/string_operation_extension.rb +5 -1
  89. data/lib/carray/time.rb +18 -2
  90. data/lib/carray/window_iterator.rb +142 -20
  91. data/lib/carray.rb +2 -0
  92. data/yard-stubs/ca_obj_block.rb +2 -7
  93. data/yard-stubs/ca_obj_window.rb +10 -2
  94. data/yard-stubs/carray_access.rb +1 -1
  95. data/yard-stubs/carray_broadcast.rb +1 -1
  96. data/yard-stubs/carray_core.rb +0 -80
  97. data/yard-stubs/carray_count.rb +7 -2
  98. data/yard-stubs/carray_lazy.rb +205 -0
  99. data/yard-stubs/carray_math.rb +1486 -3
  100. data/yard-stubs/carray_median_percentile.rb +16 -2
  101. data/yard-stubs/carray_order.rb +9 -69
  102. data/yard-stubs/carray_slab.rb +9 -7
  103. data/yard-stubs/carray_sort.rb +7 -5
  104. metadata +9 -1
data/ext/carray_sort.c CHANGED
@@ -639,12 +639,20 @@ rb_ca_sorted_view (int argc, VALUE *argv, VALUE self)
639
639
  * fiber, no pair struct, no view layer. This fast path is numeric-only
640
640
  * and mask-free (CA_KERNEL_NO_MASK below).
641
641
  *
642
- * CA_FIXLEN and masked input both delegate to {sort} + copy instead of
643
- * duplicating the fixlen dialect / mask-position split in this per-
644
- * fiber loop: masked_position: is forwarded unchanged. Masked cells
645
- * keep their masked-ness (the view's remap gather carries the mask bit
646
- * through, and .copy materializes it), clustered at masked_position:
647
- * within each fiber -- same contract as {sort}.
642
+ * Everything the fast path does not cover -- any data_type outside
643
+ * CA_INT8..CA_FLOAT64 (fixlen, boolean, complex, object), and masked
644
+ * input of any type -- delegates to {sort} + copy rather than duplicate
645
+ * the fixlen dialect / the object comparator / the mask-position split
646
+ * in this per-fiber loop; masked_position: is forwarded unchanged.
647
+ * Masked cells keep their masked-ness (the view's remap gather carries
648
+ * the mask bit through, and .copy materializes it), clustered at
649
+ * masked_position: within each fiber -- same contract as {sort}.
650
+ *
651
+ * So sort_copy answers for exactly what sort answers for, which is what
652
+ * being its eager counterpart means. It once refused everything the
653
+ * fast path could not take: object and boolean sorted through {sort}
654
+ * but not through this, and complex reported its refusal in two
655
+ * different ways depending on which of the pair was asked.
648
656
  */
649
657
  static VALUE
650
658
  rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
@@ -677,11 +685,12 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
677
685
  CArray *ca;
678
686
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
679
687
 
680
- /* CA_FIXLEN and masked input: the per-fiber path below covers
681
- unmasked numeric data types only. Delegate to {sort} (which handles
682
- both the fixlen dialect and the masked_position split) + copy to
683
- get the same shape/class contract as the fast path. */
684
- if ( ca_is_fixlen_type(ca) || ca_has_mask(ca) ) {
688
+ /* The per-fiber path below covers unmasked CA_INT8..CA_FLOAT64 only.
689
+ Everything else goes to {sort} (which handles the fixlen dialect,
690
+ the object comparator and the masked_position split) + copy, for the
691
+ same shape/class contract as the fast path. */
692
+ if ( ca->data_type < CA_INT8 || ca->data_type > CA_FLOAT64
693
+ || ca_has_mask(ca) ) {
685
694
  VALUE sv_kw = rb_hash_new();
686
695
  if ( !NIL_P(vaxis) ) rb_hash_aset(sv_kw, ID2SYM(rb_intern("axis")), vaxis);
687
696
  if ( !NIL_P(vkind) ) rb_hash_aset(sv_kw, ID2SYM(rb_intern("kind")), vkind);
@@ -717,17 +726,6 @@ rb_ca_sort_copy (int argc, VALUE *argv, VALUE self)
717
726
  NUM2INT(vaxis_use), cat->ndim);
718
727
  }
719
728
 
720
- /* data_type check: ALL_NUMERIC only (CA_INT8..CA_FLOAT64).
721
- Complex / object are rejected here; CA_OBJECT goes through the
722
- axis: lift in rb_ca_sorted_view, and complex sort semantics
723
- differ enough that we do not pick a default. */
724
- if ( cat->data_type < CA_INT8 || cat->data_type > CA_FLOAT64 ) {
725
- rb_raise(rb_eCADataTypeError,
726
- "sort_copy: data_type %d not supported "
727
- "(expected one of: i8, u8, i16, u16, i32, u32, i64, u64, f32, f64)",
728
- cat->data_type);
729
- }
730
-
731
729
  /* Allocate output: same shape and data_type as target
732
730
  (bytes=0 = preserve native bytes). */
733
731
  volatile VALUE vout = rb_ca_template_with_type(target,
data/ext/mk_call_cfunc.rb CHANGED
@@ -128,83 +128,61 @@ def indent(s, prefix = " ")
128
128
  end
129
129
 
130
130
  # --- raw ca_call_cfunc_N ----------------------------------------------------
131
+ #
132
+ # The per-cell loop runs in a walker function of its own, handed to
133
+ # ca_sweep_run: a callback that raises part way through gives back the
134
+ # operands and scratch the engine holds, rather than leaving them behind.
135
+ #
136
+ # The `_r` variant (reentrant: + void *userdata at signature tail) passes a
137
+ # trailing `void *userdata` through to every per-cell `func(...)`
138
+ # invocation as its last argument. Naming follows POSIX convention
139
+ # (qsort_r / bsearch_r / strtok_r), where `_r` marks a reentrant form that
140
+ # takes a thunk so the callback no longer depends on file-static / global
141
+ # state. Use when the callback needs to share state with the caller (e.g.
142
+ # accumulators, configuration flags, library plan handles) without
143
+ # resorting to file-static plumbing.
144
+ def emit_raw_common(n, r:)
145
+ name = r ? "ca_call_cfunc_#{n}_r" : "ca_call_cfunc_#{n}"
146
+ fsig = r ? void_p_list_r(n) : void_p_list(n)
147
+ call = "c->func(#{args_p(n)}#{r ? ", c->userdata" : ""})"
131
148
 
132
- def emit_raw(n)
133
- $src.puts sig_raw(n)
134
- $src.puts "{"
135
- $src.puts indent(<<~END_C)
136
- CArray *cx[#{n}];
137
- char *base[#{n}];
138
- ca_size_t stride[#{n}];
139
- char *owned_buf[#{n}];
140
- int attached[#{n}];
141
- ca_sweep_state_t state;
142
- int k_op;
143
-
144
- END_C
145
- # extract CArray* from VALUE
146
- (0...n).each do |k|
147
- $src.puts " TypedData_Get_Struct(rcx#{k}, CArray, &carray_data_type, cx[#{k}]);"
148
- end
149
- $src.puts ""
150
149
  $src.puts <<~END_C
151
- /* sweep engine: per-operand acquire (alias / xmalloc + ca_xfer_all),
152
- broadcast shape check, mask OR across INPUTs, mask propagate to
153
- OUTPUTs. Lifecycle template lives in ext/ca_sweep_engine.{c,h}. */
154
- state.n_ops = #{n};
155
- state.fsync = fsync;
156
- state.cx = cx;
157
- state.base = base;
158
- state.stride = stride;
159
- state.owned_buf = owned_buf;
160
- state.attached = attached;
161
- state.no_mask = 0;
162
- state.src_label = "ca_call_cfunc_#{n}";
163
-
164
- ca_sweep_acquire(&state);
165
-
166
- /* inner loop: advance per-cell ptrs and invoke user kernel func */
167
- {
168
- char *p[#{n}];
169
- ca_size_t k;
170
- if ( state.m0 ) {
171
- for ( k = 0; k < state.n_kernel; k++ ) {
172
- if ( ! state.m0[k] ) {
173
- for ( k_op = 0; k_op < #{n}; k_op++ ) {
174
- p[k_op] = base[k_op] + k * stride[k_op];
175
- }
176
- func(#{args_p(n)});
177
- }
178
- }
179
- } else {
180
- for ( k = 0; k < state.n_kernel; k++ ) {
150
+ typedef struct {
151
+ ca_sweep_state_t *st;
152
+ void (*func)(#{fsig});
153
+ void *userdata;
154
+ } #{name}_ctx_t;
155
+
156
+ static VALUE
157
+ #{name}_walk (VALUE arg)
158
+ {
159
+ #{name}_ctx_t *c = (#{name}_ctx_t *) arg;
160
+ ca_sweep_state_t *st = c->st;
161
+ char *p[#{n}];
162
+ ca_size_t k;
163
+ int k_op;
164
+ if ( st->m0 ) {
165
+ for ( k = 0; k < st->n_kernel; k++ ) {
166
+ if ( ! st->m0[k] ) {
181
167
  for ( k_op = 0; k_op < #{n}; k_op++ ) {
182
- p[k_op] = base[k_op] + k * stride[k_op];
168
+ p[k_op] = st->base[k_op] + k * st->stride[k_op];
183
169
  }
184
- func(#{args_p(n)});
170
+ #{call};
171
+ }
172
+ }
173
+ } else {
174
+ for ( k = 0; k < st->n_kernel; k++ ) {
175
+ for ( k_op = 0; k_op < #{n}; k_op++ ) {
176
+ p[k_op] = st->base[k_op] + k * st->stride[k_op];
185
177
  }
178
+ #{call};
186
179
  }
187
180
  }
188
-
189
- ca_sweep_release(&state);
190
-
191
- return rcx0;
181
+ return Qnil;
192
182
  }
193
183
 
194
184
  END_C
195
- end
196
-
197
- # --- raw ca_call_cfunc_N_r (reentrant: + void *userdata at signature tail) --
198
- #
199
- # Variant of emit_raw with a trailing `void *userdata` parameter passed
200
- # through to every per-cell `func(...)` invocation as its last argument.
201
- # Naming follows POSIX convention (qsort_r / bsearch_r / strtok_r), where
202
- # `_r` marks a reentrant form that takes a thunk so the callback no longer
203
- # depends on file-static / global state. Use when the callback needs to
204
- # share state with the caller (e.g. accumulators, configuration flags,
205
- # library plan handles) without resorting to file-static plumbing.
206
- def emit_raw_r(n)
207
- $src.puts sig_raw_r(n)
185
+ $src.puts(r ? sig_raw_r(n) : sig_raw(n))
208
186
  $src.puts "{"
209
187
  $src.puts indent(<<~END_C)
210
188
  CArray *cx[#{n}];
@@ -213,7 +191,7 @@ def emit_raw_r(n)
213
191
  char *owned_buf[#{n}];
214
192
  int attached[#{n}];
215
193
  ca_sweep_state_t state;
216
- int k_op;
194
+ #{name}_ctx_t ctx;
217
195
 
218
196
  END_C
219
197
  (0...n).each do |k|
@@ -221,8 +199,9 @@ def emit_raw_r(n)
221
199
  end
222
200
  $src.puts ""
223
201
  $src.puts <<~END_C
224
- /* sweep engine: same lifecycle as ca_call_cfunc_#{n}; the difference is
225
- the per-cell `func(...)` call has `userdata` as its last argument. */
202
+ /* sweep engine: per-operand acquire (alias / xmalloc + ca_xfer_all),
203
+ operand pairing, mask OR across INPUTs, mask propagate to OUTPUTs.
204
+ Lifecycle template lives in ext/ca_sweep_engine.{c,h}. */
226
205
  state.n_ops = #{n};
227
206
  state.fsync = fsync;
228
207
  state.cx = cx;
@@ -231,33 +210,14 @@ def emit_raw_r(n)
231
210
  state.owned_buf = owned_buf;
232
211
  state.attached = attached;
233
212
  state.no_mask = 0;
234
- state.src_label = "ca_call_cfunc_#{n}_r";
213
+ state.src_label = "#{name}";
235
214
 
236
215
  ca_sweep_acquire(&state);
237
216
 
238
- {
239
- char *p[#{n}];
240
- ca_size_t k;
241
- if ( state.m0 ) {
242
- for ( k = 0; k < state.n_kernel; k++ ) {
243
- if ( ! state.m0[k] ) {
244
- for ( k_op = 0; k_op < #{n}; k_op++ ) {
245
- p[k_op] = base[k_op] + k * stride[k_op];
246
- }
247
- func(#{args_p_r(n)});
248
- }
249
- }
250
- } else {
251
- for ( k = 0; k < state.n_kernel; k++ ) {
252
- for ( k_op = 0; k_op < #{n}; k_op++ ) {
253
- p[k_op] = base[k_op] + k * stride[k_op];
254
- }
255
- func(#{args_p_r(n)});
256
- }
257
- }
258
- }
259
-
260
- ca_sweep_release(&state);
217
+ ctx.st = &state;
218
+ ctx.func = func;
219
+ ctx.userdata = #{r ? "userdata" : "NULL"};
220
+ ca_sweep_run(&state, #{name}_walk, (VALUE) &ctx);
261
221
 
262
222
  return rcx0;
263
223
  }
@@ -265,6 +225,9 @@ def emit_raw_r(n)
265
225
  END_C
266
226
  end
267
227
 
228
+ def emit_raw(n) ; emit_raw_common(n, r: false); end
229
+ def emit_raw_r(n) ; emit_raw_common(n, r: true); end
230
+
268
231
  # --- typed dispatchers: M outputs, N inputs ---------------------------------
269
232
  #
270
233
  # The four typed emitters (cfunc / cfunc_r / cslab / cslab_r) differ in one
@@ -418,7 +381,7 @@ def sig_slab_r(n)
418
381
  "VALUE\nca_call_cslab_#{n}_r (ca_cslab_r_t func, const char *fsync,\n #{value_param_list(n)},\n void *userdata)"
419
382
  end
420
383
 
421
- def emit_slab_body(n, name, call)
384
+ def emit_slab_body(n, name, walker, userdata)
422
385
  $src.puts indent(<<~END_C)
423
386
  CArray *cx[#{n}];
424
387
  char *base[#{n}];
@@ -427,6 +390,7 @@ def emit_slab_body(n, name, call)
427
390
  char *owned_buf[#{n}];
428
391
  int attached[#{n}];
429
392
  ca_sweep_state_t state;
393
+ ca_cslab_ctx_t ctx;
430
394
 
431
395
  END_C
432
396
  (0...n).each do |k|
@@ -453,16 +417,11 @@ def emit_slab_body(n, name, call)
453
417
 
454
418
  ca_sweep_acquire_chunked(&state);
455
419
 
456
- /* outer loop: hand the author one chunk at a time. base[] is rewritten
457
- per chunk by ca_sweep_next_chunk -- for a non-alias INPUT it points
458
- at the arena scratch the chunk was just gathered into, which is
459
- packed, so stride[] is the element size and the author's inner loop
460
- sees contiguous data. */
461
- while ( ca_sweep_next_chunk(&state) ) {
462
- #{call};
463
- }
464
-
465
- ca_sweep_release_chunked(&state);
420
+ ctx.st = &state;
421
+ ctx.func = #{userdata == "NULL" ? "func" : "NULL"};
422
+ ctx.func_r = #{userdata == "NULL" ? "NULL" : "func"};
423
+ ctx.userdata = #{userdata};
424
+ ca_sweep_run_chunked(&state, #{walker}, (VALUE) &ctx);
466
425
 
467
426
  return rcx0;
468
427
  }
@@ -473,8 +432,7 @@ end
473
432
  def emit_slab(n)
474
433
  $src.puts sig_slab(n)
475
434
  $src.puts "{"
476
- emit_slab_body(n, "ca_call_cslab_#{n}",
477
- "func(base, stride, state.chunk_n, ca_sweep_chunk_mask(&state))")
435
+ emit_slab_body(n, "ca_call_cslab_#{n}", "ca_cslab_walk", "NULL")
478
436
  end
479
437
 
480
438
  # Variant of emit_slab with a trailing `void *userdata` parameter passed
@@ -483,8 +441,7 @@ end
483
441
  def emit_slab_r(n)
484
442
  $src.puts sig_slab_r(n)
485
443
  $src.puts "{"
486
- emit_slab_body(n, "ca_call_cslab_#{n}_r",
487
- "func(base, stride, state.chunk_n, ca_sweep_chunk_mask(&state), userdata)")
444
+ emit_slab_body(n, "ca_call_cslab_#{n}_r", "ca_cslab_r_walk", "userdata")
488
445
  end
489
446
 
490
447
  # --- header declaration emitters --------------------------------------------
@@ -557,14 +514,44 @@ $src.puts <<~END_C
557
514
  #include "ca_sweep_engine.h"
558
515
  #include <string.h>
559
516
 
560
- /* The chunk's iteration mask, or NULL when no INPUT operand carried one.
561
- m0 is chunk-sized and re-gathered per chunk by ca_sweep_next_chunk, so
562
- it is already the slice -- one byte per cell, indexed 0..chunk_n-1
563
- alongside base[] and stride[]. */
564
- static const boolean8_t *
565
- ca_sweep_chunk_mask (ca_sweep_state_t *st)
517
+ /* Chunk walk for the ca_call_cslab_N family, run by ca_sweep_run_chunked
518
+ so that a callback raising part way through gives back what the engine
519
+ holds. Hands the author one chunk at a time. base[] is rewritten per
520
+ chunk by ca_sweep_next_chunk -- for a non-alias INPUT it points at the
521
+ arena scratch the chunk was just gathered into, which is packed, so
522
+ stride[] is the element size and the author's inner loop sees
523
+ contiguous data. m0 is the chunk's iteration mask, or NULL when no
524
+ INPUT operand carried one: chunk-sized and re-gathered per chunk, so it
525
+ is already the slice -- one byte per cell, indexed 0..chunk_n-1
526
+ alongside base[] and stride[]. The arity does not appear here: the
527
+ operands reach the callback through base[] / stride[]. */
528
+ typedef struct {
529
+ ca_sweep_state_t *st;
530
+ ca_cslab_t func; /* ca_call_cslab_N */
531
+ ca_cslab_r_t func_r; /* ca_call_cslab_N_r */
532
+ void *userdata;
533
+ } ca_cslab_ctx_t;
534
+
535
+ static VALUE
536
+ ca_cslab_walk (VALUE arg)
566
537
  {
567
- return st->m0;
538
+ ca_cslab_ctx_t *c = (ca_cslab_ctx_t *) arg;
539
+ ca_sweep_state_t *st = c->st;
540
+ while ( ca_sweep_next_chunk(st) ) {
541
+ c->func(st->base, st->stride, st->chunk_n, st->m0);
542
+ }
543
+ return Qnil;
544
+ }
545
+
546
+ static VALUE
547
+ ca_cslab_r_walk (VALUE arg)
548
+ {
549
+ ca_cslab_ctx_t *c = (ca_cslab_ctx_t *) arg;
550
+ ca_sweep_state_t *st = c->st;
551
+ while ( ca_sweep_next_chunk(st) ) {
552
+ c->func_r(st->base, st->stride, st->chunk_n, st->m0, c->userdata);
553
+ }
554
+ return Qnil;
568
555
  }
569
556
 
570
557
  END_C