carray 3.0.5 → 3.0.6

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +233 -0
  3. data/README.md +1 -1
  4. data/carray.gemspec +3 -2
  5. data/ext/ca_axis_dispatch.c +6 -0
  6. data/ext/ca_axis_group.c +1 -1
  7. data/ext/ca_categorical_iterator.c +7 -7
  8. data/ext/ca_kernel_iterator.c +116 -231
  9. data/ext/ca_kernel_iterator.h +410 -254
  10. data/ext/ca_obj_bincmp.c +9 -1
  11. data/ext/ca_obj_binop.c +10 -1
  12. data/ext/ca_obj_bitfield.c +2 -0
  13. data/ext/ca_obj_byte_swap.c +2 -0
  14. data/ext/ca_obj_face.c +2 -0
  15. data/ext/ca_obj_fake.c +3 -1
  16. data/ext/ca_obj_grid.c +167 -12
  17. data/ext/ca_obj_meld.c +69 -113
  18. data/ext/ca_obj_moncmp.c +9 -1
  19. data/ext/ca_obj_monop.c +40 -2
  20. data/ext/ca_obj_object.c +54 -20
  21. data/ext/ca_obj_reduce.c +15 -1
  22. data/ext/ca_obj_refer.c +65 -20
  23. data/ext/ca_obj_remap.c +69 -10
  24. data/ext/ca_obj_roll.c +1 -0
  25. data/ext/ca_obj_select.c +4 -1
  26. data/ext/ca_obj_select_axis.c +1 -0
  27. data/ext/ca_obj_shift.c +1 -0
  28. data/ext/ca_obj_stack.c +310 -111
  29. data/ext/ca_obj_stride.c +131 -45
  30. data/ext/ca_obj_string.c +18 -2
  31. data/ext/ca_obj_tile.c +1 -0
  32. data/ext/ca_obj_triop.c +9 -1
  33. data/ext/ca_obj_window.c +1 -0
  34. data/ext/ca_op_ipower.c +8 -8
  35. data/ext/caframe_csv_reader.c +1 -1
  36. data/ext/carray.h +2 -1
  37. data/ext/carray_access.c +27 -64
  38. data/ext/carray_address_basis.c +134 -0
  39. data/ext/carray_attribute.c +8 -0
  40. data/ext/carray_broadcast.c +3 -4
  41. data/ext/carray_build_flags.h +1 -1
  42. data/ext/carray_cast.c +8 -11
  43. data/ext/carray_conversion.c +9 -5
  44. data/ext/carray_core.c +811 -72
  45. data/ext/carray_element.c +13 -9
  46. data/ext/carray_histogram.c +25 -3
  47. data/ext/carray_internal.h +147 -12
  48. data/ext/carray_kernels_bincmp.c +2007 -3
  49. data/ext/carray_kernels_binop.c +25365 -5199
  50. data/ext/carray_kernels_init.c +8 -8
  51. data/ext/carray_kernels_monop.c +10696 -3678
  52. data/ext/carray_kernels_reduce_aggregate.c +4429 -702
  53. data/ext/carray_kernels_reduce_boolean.c +1533 -15
  54. data/ext/carray_kernels_reduce_cumulative.c +1808 -248
  55. data/ext/carray_kernels_reduce_extreme.c +4508 -511
  56. data/ext/carray_kernels_reduce_variance.c +2284 -152
  57. data/ext/carray_kernels_scan.c +240 -240
  58. data/ext/carray_kernels_search.c +4211 -264
  59. data/ext/carray_kernels_sort.c +367 -450
  60. data/ext/carray_lazy.c +47 -0
  61. data/ext/carray_loop.c +1 -1
  62. data/ext/carray_mask.c +130 -139
  63. data/ext/carray_median_percentile.c +4 -4
  64. data/ext/carray_memory_view.c +13 -7
  65. data/ext/carray_operator.c +147 -44
  66. data/ext/carray_order.c +3 -2
  67. data/ext/carray_random.c +4 -1
  68. data/ext/carray_slab.c +97 -21
  69. data/ext/carray_sort.c +1 -1
  70. data/ext/carray_test.c +74 -19
  71. data/ext/carray_time_text.c +2 -2
  72. data/ext/extconf.rb +11 -0
  73. data/ext/mkkernel.rb +346 -94
  74. data/ext/ruby_carray.c +4 -1
  75. data/ext/version.h +4 -4
  76. data/lib/carray/attribute.rb +25 -1
  77. data/lib/carray/autoload_carray.rb +3 -3
  78. data/lib/carray/autoload_method_extension.rb +5 -3
  79. data/lib/carray/axis_group.rb +13 -7
  80. data/lib/carray/basics.rb +1 -1
  81. data/lib/carray/block_iterator.rb +17 -3
  82. data/lib/carray/categorical.rb +6 -8
  83. data/lib/carray/categorical_iterator.rb +2 -2
  84. data/lib/carray/complex.rb +2 -2
  85. data/lib/carray/conditional.rb +3 -2
  86. data/lib/carray/frame/column_verbs.rb +45 -14
  87. data/lib/carray/frame/convert.rb +19 -0
  88. data/lib/carray/frame/csv_parser.rb +32 -32
  89. data/lib/carray/frame/describe.rb +4 -2
  90. data/lib/carray/frame/frame.rb +399 -78
  91. data/lib/carray/frame/group.rb +679 -53
  92. data/lib/carray/frame/io.rb +209 -71
  93. data/lib/carray/frame/join.rb +146 -37
  94. data/lib/carray/frame/records.rb +3 -3
  95. data/lib/carray/frame/reshape.rb +89 -11
  96. data/lib/carray/frame/sort.rb +57 -9
  97. data/lib/carray/frame/verbs.rb +384 -49
  98. data/lib/carray/frame/windows.rb +44 -6
  99. data/lib/carray/inspect.rb +7 -2
  100. data/lib/carray/iterator.rb +32 -0
  101. data/lib/carray/lazy.rb +17 -8
  102. data/lib/carray/mask_gap_fill.rb +37 -17
  103. data/lib/carray/meld_reduce.rb +1 -1
  104. data/lib/carray/methods/choose.rb +3 -2
  105. data/lib/carray/methods/meshgrid.rb +3 -1
  106. data/lib/carray/runtime.rb +47 -8
  107. data/lib/carray/segment_iterator.rb +23 -8
  108. data/lib/carray/serialize.rb +34 -17
  109. data/lib/carray/stack.rb +18 -6
  110. data/lib/carray/time.rb +5 -0
  111. data/lib/carray/window_iterator.rb +16 -4
  112. data/yard-stubs/ca_obj_array.rb +26 -1
  113. data/yard-stubs/carray_cast.rb +8 -5
  114. data/yard-stubs/carray_conversion.rb +8 -2
  115. data/yard-stubs/carray_mask.rb +20 -1
  116. data/yard-stubs/carray_math.rb +31 -16
  117. data/yard-stubs/carray_memory_view.rb +7 -0
  118. data/yard-stubs/carray_test.rb +7 -4
  119. metadata +18 -4
data/ext/carray_lazy.c CHANGED
@@ -1084,6 +1084,53 @@ ca_lazy_settle_scalar (VALUE operand, int was_scalar)
1084
1084
  return operand;
1085
1085
  }
1086
1086
 
1087
+ typedef struct {
1088
+ ca_lazy_eval_fn eval;
1089
+ void *node;
1090
+ const ca_lazy_req_t *req;
1091
+ void *data;
1092
+ int dir;
1093
+ } ca_lazy_eval_args_t;
1094
+
1095
+ static VALUE
1096
+ ca_lazy_eval_body (VALUE varg)
1097
+ {
1098
+ ca_lazy_eval_args_t *a = (ca_lazy_eval_args_t *) varg;
1099
+ a->eval(a->node, a->req, a->data, a->dir);
1100
+ /* A cell computed as an expression over a node being evaluated (x + 1
1101
+ for a cell holding x) stays lazy, and comparing or computing it later
1102
+ evaluates the node again without end: refuse it here, while the node
1103
+ is still recorded. */
1104
+ if ( a->dir == CA_XFER_GET && ca_is_object_type((CArray *) a->node) ) {
1105
+ ca_refuse_cells_reaching_evaluation(a->req->n, (const VALUE *) a->data);
1106
+ }
1107
+ return Qnil;
1108
+ }
1109
+
1110
+ void
1111
+ ca_lazy_eval_recorded (void *node, int n, CArray **ops,
1112
+ int nchain, CArray **chain, ca_lazy_eval_fn eval,
1113
+ const ca_lazy_req_t *req, void *data, int dir)
1114
+ {
1115
+ CArray **rec;
1116
+ ca_lazy_eval_args_t a;
1117
+ int i, m = 0, object = ca_is_object_type((CArray *) node);
1118
+ for ( i = 0; i < n; i++ ) {
1119
+ if ( ops[i] && ca_is_object_type(ops[i]) ) object = 1;
1120
+ }
1121
+ if ( ! object ) {
1122
+ eval(node, req, data, dir);
1123
+ return;
1124
+ }
1125
+ rec = ALLOCA_N(CArray *, nchain + 1);
1126
+ rec[m++] = (CArray *) node;
1127
+ for ( i = 0; i < nchain; i++ ) {
1128
+ rec[m++] = chain[i];
1129
+ }
1130
+ a.eval = eval; a.node = node; a.req = req; a.data = data; a.dir = dir;
1131
+ ca_computing_run_nodes(m, rec, ca_lazy_eval_body, (VALUE) &a);
1132
+ }
1133
+
1087
1134
  void
1088
1135
  ca_lazy_req_pull (void *operand, const ca_lazy_req_t *req, void *buf, int dir)
1089
1136
  {
data/ext/carray_loop.c CHANGED
@@ -247,7 +247,7 @@ ca_map_bang_run (VALUE self, VALUE (*body)(VALUE), int mode)
247
247
  c.self = self;
248
248
  c.ridx = rb_ary_new2(c.ca->ndim);
249
249
  c.mode = mode;
250
- ca_attach_window(1, &c.ca, "w", body, (VALUE) &c);
250
+ ca_attach_window_yielding(self, 1, &c.ca, "w", body, (VALUE) &c);
251
251
  return self;
252
252
  }
253
253
 
data/ext/carray_mask.c CHANGED
@@ -13,6 +13,7 @@
13
13
  #include "carray.h"
14
14
  #include "carray_internal.h" /* ca_lazy_arena_* */
15
15
  #include "ca_obj_face.h" /* CA_FACE_LIFT_IF_FACE for the mask-lift path */
16
+ #include <ruby/ractor.h>
16
17
 
17
18
  /* ============================================================================
18
19
  * Word-bulk mask substrate helpers.
@@ -301,7 +302,13 @@ ca_has_mask (void *ap)
301
302
  if ( ca->mask ) { /* mask array already created */
302
303
  return 1;
303
304
  }
304
- else if ( ca_is_value_array(ca) ) {
305
+ else if ( ca_test_flag(ca, CA_FLAG_VALUE_ARRAY) ) {
306
+ /* Only this array's own mark, not ca_is_value_array, which walks to
307
+ the root and would make every level of a view pay again for the
308
+ levels above it. A view below a value array still answers 0: it
309
+ asks its parent, and the question reaches the marked array. No
310
+ level in between has a mask, since ca_create_mask refuses one for
311
+ anything below a value array. */
305
312
  return 0; /* array itself is returned by CArray#value */
306
313
  }
307
314
  else if ( ca_is_entity(ca) ) { /* entity array */
@@ -316,6 +323,11 @@ ca_has_mask (void *ap)
316
323
  }
317
324
  return 0;
318
325
  }
326
+ /* The one level that answers without asking its parent: a value
327
+ array above it has to be looked for here. */
328
+ if ( ca_is_value_array(ca) ) {
329
+ return 0;
330
+ }
319
331
  if ( rb_obj_respond_to(co->self, rb_intern("mask_created?"), Qtrue) ) {
320
332
  return RTEST(rb_funcall(co->self, rb_intern("mask_created?"), 0));
321
333
  }
@@ -412,6 +424,45 @@ ca_view_func_create_mask (void *ap)
412
424
  CA_BOOLEAN, ca->ndim, ca->dim, 0, 0);
413
425
  }
414
426
 
427
+ /* The sync slot of a view whose mask came from ca_view_func_create_mask
428
+ calls this after syncing its parent. The view's mask is a refer over
429
+ the parent's mask, and when the parent's mask is itself a view, both
430
+ hold a buffer while the window is open; the refer writes past the
431
+ parent's buffer to the root. Syncing the parent then writes its
432
+ buffer, from before the view's mask was written, back over the root,
433
+ so the view's mask is synced once more. */
434
+
435
+ /* The sync slot of a view that maps its cells one to one onto its parent's
436
+ (a fake, a cast, a byte swap, a Face) calls this before syncing its
437
+ parent. The view's mask may have been written past the parent's mask
438
+ buffer, straight to the root (by a view over it), so the parent's
439
+ buffer can still hold the bits from before. The parent's own sync
440
+ decides from that buffer which cells it sends back, so the view's bits
441
+ are copied into it first. */
442
+
443
+ void
444
+ ca_view_pass_mask_to_parent (void *ap)
445
+ {
446
+ CAView *ca = (CAView *) ap;
447
+ if ( ca->mask != NULL && ca->mask->ptr != NULL
448
+ && ca->parent->mask != NULL && ca->parent->mask->ptr != NULL
449
+ && ca->mask->ptr != ca->parent->mask->ptr
450
+ && ca->elements == ca->parent->elements ) {
451
+ memmove(ca->parent->mask->ptr, ca->mask->ptr, (size_t) ca->elements);
452
+ }
453
+ }
454
+
455
+ void
456
+ ca_view_sync_mask_after_parent (void *ap)
457
+ {
458
+ CAView *ca = (CAView *) ap;
459
+ if ( ca->mask != NULL && ca->mask->ptr != NULL
460
+ && ca->parent->mask != NULL && ca->parent->mask->ptr != NULL
461
+ && ca_is_view(ca->parent->mask) ) {
462
+ ca_sync(ca->mask);
463
+ }
464
+ }
465
+
415
466
  /* create mask array if array has mask but has not mask array */
416
467
 
417
468
  void
@@ -441,20 +492,27 @@ ca_mask_standin_new (CArray *parent)
441
492
  return z;
442
493
  }
443
494
 
444
- /* A value array or a mask array: a parent that can never be given a mask,
445
- whose stand-in therefore stays in place for good. */
446
- static int
447
- ca_mask_standin_parent_unmaskable (CArray *parent)
495
+ /* A stack or meld refuses a value array or a mask array as a part: such an
496
+ array can have no mask, and a part of a stack or meld is an array like
497
+ any other, which can. */
498
+ void
499
+ ca_multi_parent_check_part (CArray *part, const char *name)
448
500
  {
449
- return ca_is_value_array(parent) || ca_is_mask_array(parent);
501
+ if ( ca_is_value_array(part) ) {
502
+ rb_raise(rb_eArgError,
503
+ "%s: a value array can have no mask and can not be a part", name);
504
+ }
505
+ if ( ca_is_mask_array(part) ) {
506
+ rb_raise(rb_eArgError,
507
+ "%s: a mask array can have no mask and can not be a part", name);
508
+ }
450
509
  }
451
510
 
452
511
  CArray *
453
512
  ca_mask_standin_current (CArray *mask_view, CArray *reading, CArray *parent,
454
513
  CArray *standin)
455
514
  {
456
- if ( standin == NULL || reading != standin
457
- || ca_mask_standin_parent_unmaskable(parent) ) {
515
+ if ( standin == NULL || reading != standin ) {
458
516
  return reading;
459
517
  }
460
518
  /* Not inside an attach window whose buffer is out: that buffer and the
@@ -490,23 +548,21 @@ ca_mask_standin_settle (CArray *parent, CArray *standin)
490
548
  if ( i == n ) {
491
549
  return;
492
550
  }
493
- if ( ca_mask_standin_parent_unmaskable(parent) ) {
494
- MEMZERO(z, boolean8_t, n);
495
- rb_raise(rb_eTypeError, "can not create mask array for the %s array",
496
- ca_is_value_array(parent) ? "value" : "mask");
497
- }
498
- ca_create_mask(parent);
499
551
  /* The trues are added to the parent's mask, not copied over it: the
500
- parent may have come to have masked cells of its own meanwhile. */
552
+ parent may have come to have masked cells of its own meanwhile. The
553
+ stand-in is cleared before the parent is asked for a mask, so a
554
+ parent that refuses one leaves it all false, as its readers take it
555
+ to be at rest. */
501
556
  addrs = ALLOCV_N(ca_size_t, holder, n);
502
557
  for ( ; i < n; i++ ) {
503
558
  if ( z[i] ) {
504
559
  addrs[m++] = i;
505
560
  }
506
561
  }
562
+ MEMZERO(z, boolean8_t, n);
563
+ ca_create_mask(parent);
507
564
  ca_fill_addrs(parent->mask, m, addrs, &one);
508
565
  ALLOCV_END(holder);
509
- MEMZERO(z, boolean8_t, n);
510
566
  }
511
567
 
512
568
  CArray *
@@ -539,114 +595,37 @@ ca_multi_parent_mask_refresh (CArray *mask, CArray **parents,
539
595
  CAVIEW(mask)->parent = mp->parents[0];
540
596
  }
541
597
 
542
- typedef struct {
543
- CArray *mask;
544
- CArray **blanks;
545
- CArray **saved;
546
- CArray *saved_parent;
547
- int32_t n;
548
- int finished;
549
- VALUE (*body)(VALUE);
550
- VALUE arg;
551
- } ca_mp_trial_t;
552
-
553
- static VALUE
554
- ca_mp_trial_body (VALUE p)
555
- {
556
- ca_mp_trial_t *t = (ca_mp_trial_t *) p;
557
- t->body(t->arg);
558
- t->finished = 1;
559
- return Qnil;
560
- }
561
-
562
- static void
563
- ca_mp_trial_free_blanks (ca_mp_trial_t *t)
564
- {
565
- int32_t k;
566
- for ( k = 0; k < t->n; k++ ) {
567
- ca_free(t->blanks[k]);
568
- t->blanks[k] = NULL;
569
- }
570
- }
571
-
572
- static VALUE
573
- ca_mp_trial_restore (VALUE p)
574
- {
575
- ca_mp_trial_t *t = (ca_mp_trial_t *) p;
576
- CAMultiParent *mp = (CAMultiParent *) t->mask;
577
- int32_t k;
578
- for ( k = 0; k < t->n; k++ ) {
579
- mp->parents[k] = t->saved[k];
580
- }
581
- CAVIEW(t->mask)->parent = t->saved_parent;
582
- if ( ! t->finished ) {
583
- ca_mp_trial_free_blanks(t);
584
- }
585
- return Qnil;
586
- }
587
-
598
+ /* Before a write that puts a true into the mask of a stack or meld (not one
599
+ with its buffer out): each part still reading its stand-in that shares
600
+ its root with another part is given its own mask, and the write goes to
601
+ those parts' masks in their order. Parts that share cells then take the
602
+ write of the last of them, as they do for values; through stand-ins,
603
+ settled afterwards, the trues would be added together. A part that
604
+ shares nothing keeps its stand-in, and gets no mask from falses. */
588
605
  void
589
- ca_multi_parent_mask_refuse_first (CArray *mask, CArray **parents,
590
- CArray **standins, int32_t n,
591
- VALUE (*body)(VALUE), VALUE arg)
606
+ ca_multi_parent_mask_adopt (CArray *mask, CArray **parents,
607
+ CArray **standins, int32_t n)
592
608
  {
593
609
  CAMultiParent *mp = (CAMultiParent *) mask;
594
- volatile VALUE h_blank = 0, h_saved = 0;
595
- ca_mp_trial_t t;
596
- int32_t k, refused = -1;
597
- if ( standins == NULL ) {
610
+ int32_t k, j;
611
+ if ( standins == NULL || mask->ptr != NULL ) {
598
612
  return;
599
613
  }
600
614
  for ( k = 0; k < n; k++ ) {
601
- if ( standins[k] != NULL && mp->parents[k] == standins[k]
602
- && ca_mask_standin_parent_unmaskable(parents[k]) ) {
603
- break;
615
+ if ( standins[k] == NULL || mp->parents[k] != standins[k]
616
+ || ca_test_flag(standins[k], CA_FLAG_READ_ONLY) ) {
617
+ continue;
604
618
  }
605
- }
606
- if ( k == n ) {
607
- return;
608
- }
609
- t.mask = mask;
610
- t.n = n;
611
- t.finished = 0;
612
- t.body = body;
613
- t.arg = arg;
614
- t.saved_parent = CAVIEW(mask)->parent;
615
- t.blanks = ALLOCV_N(CArray *, h_blank, n);
616
- t.saved = ALLOCV_N(CArray *, h_saved, n);
617
- for ( k = 0; k < n; k++ ) {
618
- t.blanks[k] = NULL;
619
- }
620
- for ( k = 0; k < n; k++ ) {
621
- CArray *m = mp->parents[k];
622
- t.saved[k] = m;
623
- t.blanks[k] = carray_new(CA_BOOLEAN, m->ndim, m->dim, 0, NULL);
624
- MEMZERO(t.blanks[k]->ptr, boolean8_t, t.blanks[k]->elements);
625
- }
626
- for ( k = 0; k < n; k++ ) {
627
- mp->parents[k] = t.blanks[k];
628
- }
629
- CAVIEW(mask)->parent = t.blanks[0];
630
- rb_ensure(ca_mp_trial_body, (VALUE) &t, ca_mp_trial_restore, (VALUE) &t);
631
- for ( k = 0; k < n && refused < 0; k++ ) {
632
- CArray *z = t.blanks[k];
633
- ca_size_t i;
634
- if ( standins[k] == NULL || t.saved[k] != standins[k]
635
- || ! ca_mask_standin_parent_unmaskable(parents[k]) ) {
619
+ for ( j = 0; j < n && ( j == k || ! ca_shares_root(parents[j], parents[k]) ); j++ ) ;
620
+ if ( j == n ) {
636
621
  continue;
637
622
  }
638
- for ( i = 0; i < z->elements && ! ((boolean8_t *) z->ptr)[i]; i++ ) ;
639
- if ( i < z->elements ) {
640
- refused = k;
623
+ ca_create_mask(parents[k]);
624
+ if ( parents[k]->mask != NULL ) {
625
+ mp->parents[k] = parents[k]->mask;
641
626
  }
642
627
  }
643
- ca_mp_trial_free_blanks(&t);
644
- ALLOCV_END(h_blank);
645
- ALLOCV_END(h_saved);
646
- if ( refused >= 0 ) {
647
- rb_raise(rb_eTypeError, "can not create mask array for the %s array",
648
- ca_is_value_array(parents[refused]) ? "value" : "mask");
649
- }
628
+ CAVIEW(mask)->parent = mp->parents[0];
650
629
  }
651
630
 
652
631
  void
@@ -654,18 +633,12 @@ ca_multi_parent_mask_settle (CArray *mask, CArray **parents,
654
633
  CArray **standins, int32_t n)
655
634
  {
656
635
  CAMultiParent *mp = (CAMultiParent *) mask;
657
- int32_t k, pass;
636
+ int32_t k;
658
637
  if ( standins == NULL ) {
659
638
  return;
660
639
  }
661
- /* The parts that can have no mask first, so that a refused true leaves
662
- the other parts as they were. */
663
- for ( pass = 0; pass < 2; pass++ ) {
664
- for ( k = 0; k < n; k++ ) {
665
- if ( standins[k] == NULL || mp->parents[k] != standins[k]
666
- || ca_mask_standin_parent_unmaskable(parents[k]) != ( pass == 0 ) ) {
667
- continue;
668
- }
640
+ for ( k = 0; k < n; k++ ) {
641
+ if ( standins[k] != NULL && mp->parents[k] == standins[k] ) {
669
642
  ca_mask_standin_settle(parents[k], standins[k]);
670
643
  }
671
644
  }
@@ -797,6 +770,9 @@ typedef struct {
797
770
  int k; /* entries in list */
798
771
  int ns; /* sources */
799
772
  ca_size_t elements;
773
+ CArray *dest; /* the destination */
774
+ int overwrite; /* clear the destination's mask first */
775
+ char *modes; /* attach modes, one per entry of list */
800
776
  } ca_mask_overlay_t;
801
777
 
802
778
  static VALUE
@@ -823,6 +799,31 @@ ca_mask_overlay_body (VALUE arg)
823
799
  return Qnil;
824
800
  }
825
801
 
802
+ /* Clear the destination's mask if asked, and OR the sources into it. */
803
+ static VALUE
804
+ ca_mask_overlay_write (VALUE arg)
805
+ {
806
+ ca_mask_overlay_t *o = (ca_mask_overlay_t *) arg;
807
+ CArray *ca = o->dest;
808
+ int i;
809
+ if ( o->overwrite ) {
810
+ boolean8_t zero = 0;
811
+ ca_fill(ca->mask, &zero);
812
+ }
813
+ o->k = 0;
814
+ o->list[o->k] = ca->mask;
815
+ o->modes[o->k++] = 'w';
816
+ for (i=0; i<o->ns; i++) {
817
+ if ( o->att[i] ) {
818
+ o->list[o->k] = o->att[i];
819
+ o->modes[o->k++] = 'r';
820
+ }
821
+ }
822
+ o->modes[o->k] = '\0';
823
+ ca_attach_window(o->k, o->list, o->modes, ca_mask_overlay_body, arg);
824
+ return Qnil;
825
+ }
826
+
826
827
  /* overwrite: the destination's mask is cleared before the sources are
827
828
  OR'd in (and left alone when it has none and no source has a mask). */
828
829
  static void
@@ -907,22 +908,10 @@ ca_copy_mask_overlay_core (CArray *ca, ca_size_t elements, int n,
907
908
  }
908
909
 
909
910
  ca_create_mask(ca);
910
- if ( overwrite ) {
911
- boolean8_t zero = 0;
912
- ca_fill(ca->mask, &zero);
913
- }
914
-
915
- o.k = 0;
916
- o.list[o.k] = ca->mask;
917
- modes[o.k++] = 'w';
918
- for (i=0; i<o.ns; i++) {
919
- if ( o.att[i] ) {
920
- o.list[o.k] = o.att[i];
921
- modes[o.k++] = 'r';
922
- }
923
- }
924
- modes[o.k] = '\0';
925
- ca_attach_window(o.k, o.list, modes, ca_mask_overlay_body, (VALUE) &o);
911
+ o.dest = ca;
912
+ o.overwrite = overwrite;
913
+ o.modes = modes;
914
+ ca_mask_overlay_write((VALUE) &o);
926
915
 
927
916
  for (i=0; i<n; i++) {
928
917
  if ( holders[i] ) {
@@ -1848,7 +1837,9 @@ ca_mask_copy_where (VALUE self, int argc, VALUE *argv)
1848
1837
  obj = rb_ca_copy(self);
1849
1838
  memcpy(all_args, argv, argc * sizeof(VALUE));
1850
1839
  all_args[argc] = CA_UNDEF;
1851
- rb_funcallv(obj, rb_intern("[]="), argc + 1, all_args);
1840
+ /* a CScalar takes no index; its one-cell view does */
1841
+ rb_funcallv(ca_is_scalar(ca) ? rb_funcall(obj, rb_intern("flatten"), 0) : obj,
1842
+ rb_intern("[]="), argc + 1, all_args);
1852
1843
  return obj;
1853
1844
  }
1854
1845
 
@@ -907,10 +907,10 @@ rb_ca_median_m_body (int argc, VALUE *argv, VALUE self)
907
907
  if ( n == 0 ) return mp_axis_all_masked(self, axis, keep_axis);
908
908
  /* mask / min_count / fill_value require the per-fiber select (each fiber
909
909
  has its own n_present); the plain partition path assumes a uniform n.
910
- Switch ca_fast_paths_enabled(): off, the per-fiber select. */
910
+ Switch ca_fast_path(CA_SC_MEDIAN_PARTITION): off, the per-fiber select. */
911
911
  VALUE res;
912
912
  if ( RTEST(rb_ca_has_mask(self)) || min_count > 0 || !NIL_P(fill_value)
913
- || ! ca_fast_paths_enabled() )
913
+ || ! ca_fast_path(CA_SC_MEDIAN_PARTITION) )
914
914
  res = is_obj
915
915
  ? median_object_axis_masked(self, axis, min_count, fill_value, keep_axis)
916
916
  : median_axis_masked(self, axis, min_count, fill_value, keep_axis);
@@ -1364,9 +1364,9 @@ rb_ca_percentile_kw (int argc, VALUE *argv, VALUE ropt, VALUE self)
1364
1364
  /* mask / min_count / fill_value require the per-fiber select (each fiber
1365
1365
  has its own n_present); the plain partition/sort path assumes a
1366
1366
  uniform n. These paths bake keep_axis into the reduced output.
1367
- Switch ca_fast_paths_enabled(): off, the per-fiber select. */
1367
+ Switch ca_fast_path(CA_SC_PERCENTILE_PARTITION): off, the per-fiber select. */
1368
1368
  int per_fiber = RTEST(rb_ca_has_mask(self)) || min_count > 0
1369
- || !NIL_P(fill_value) || ! ca_fast_paths_enabled();
1369
+ || !NIL_P(fill_value) || ! ca_fast_path(CA_SC_PERCENTILE_PARTITION);
1370
1370
  if ( !is_obj ) {
1371
1371
  result = per_fiber
1372
1372
  ? pct_axis_masked(self, pers, axis, method, min_count, fill_value, keep_axis)
@@ -202,7 +202,9 @@ ca_mv_record_member_spec (VALUE info, char *out, size_t outlen,
202
202
  a member it has no item for (see above), or members that overlap or come
203
203
  out of offset order (a union).
204
204
 
205
- The result is frozen and cached on the data_class. It is an ordinary
205
+ The result is frozen and cached on the data_class. Only the main Ractor
206
+ may set an instance variable on a class, so another Ractor builds the
207
+ format each time until the main one has cached it. It is an ordinary
206
208
  String that compaction can move, so a view takes a copy of its bytes
207
209
  rather than its pointer (ca_mv_get). */
208
210
  static VALUE
@@ -245,11 +247,11 @@ ca_mv_struct_format_for_data_class (VALUE data_class)
245
247
  }
246
248
 
247
249
  rb_obj_freeze(buf);
248
- rb_ivar_set(data_class, iv, buf);
250
+ if (ca_in_main_ractor()) rb_ivar_set(data_class, iv, buf);
249
251
  return buf;
250
252
 
251
253
  unsupported:
252
- rb_ivar_set(data_class, iv, Qfalse);
254
+ if (ca_in_main_ractor()) rb_ivar_set(data_class, iv, Qfalse);
253
255
  return Qnil;
254
256
  }
255
257
 
@@ -900,7 +902,7 @@ ca_mv_release (VALUE obj, rb_memory_view_t *view)
900
902
  }
901
903
 
902
904
  /* ---------------- importer ----------------
903
- Two-tier API mirroring numo-narray-memoryview:
905
+ Two-tier API:
904
906
 
905
907
  CArray.from_memory_view(obj) -> CArray (copy, independent buffer)
906
908
  CArray.wrap_memory_view(obj) -> CAWrap (zero-copy, shared memory)
@@ -1621,9 +1623,13 @@ rb_ca_s_memory_view_reject_reason (VALUE klass, VALUE obj)
1621
1623
  "producer cannot express yet (nested struct, "
1622
1624
  "CArray template, :fixlen, or :object)");
1623
1625
  }
1624
- return rb_sprintf("element data_type (%d) is not exportable via memory_view "
1625
- "(CA_FIXLEN without data_class / CA_OBJECT)",
1626
- ca->data_type);
1626
+ if (ca->data_type == CA_OBJECT) {
1627
+ return rb_str_new_cstr("an object array holds Ruby objects, not bytes "
1628
+ "a consumer can read; convert it with to_type "
1629
+ "first");
1630
+ }
1631
+ return rb_sprintf("data_type %s has no MemoryView format",
1632
+ ca_type_name[ca->data_type]);
1627
1633
  }
1628
1634
  if (ca_has_mask(ca)) {
1629
1635
  return rb_str_new_cstr("array has a mask; use arr.value (mask-ignoring CARefer) "