carray 3.0.0 → 3.0.1

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 (180) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +325 -3
  4. data/{NEWS.md → CHANGELOG.v1.md} +1 -1
  5. data/README.md +32 -32
  6. data/carray.gemspec +12 -8
  7. data/ext/ca_axis_group.c +55 -19
  8. data/ext/ca_binop_dispatch.c +5 -6
  9. data/ext/ca_binop_dispatch.h +0 -7
  10. data/ext/ca_categorical_iterator.c +13 -13
  11. data/ext/ca_for_each_element.h +6 -8
  12. data/ext/ca_group_iter.c +1 -1
  13. data/ext/ca_kernel_iterator.c +69 -23
  14. data/ext/ca_kernel_iterator.h +3 -10
  15. data/ext/ca_obj_bincmp.c +57 -27
  16. data/ext/ca_obj_binop.c +86 -30
  17. data/ext/ca_obj_const_string.c +3 -1
  18. data/ext/ca_obj_face.c +80 -0
  19. data/ext/ca_obj_face.h +33 -1
  20. data/ext/ca_obj_fake.c +11 -0
  21. data/ext/ca_obj_fixlen_string.c +3 -1
  22. data/ext/ca_obj_grid.c +11 -3
  23. data/ext/ca_obj_meld.c +13 -8
  24. data/ext/ca_obj_moncmp.c +33 -14
  25. data/ext/ca_obj_monop.c +19 -7
  26. data/ext/ca_obj_object.c +99 -4
  27. data/ext/ca_obj_record.c +3 -1
  28. data/ext/ca_obj_refer.c +30 -6
  29. data/ext/ca_obj_roll.c +13 -5
  30. data/ext/ca_obj_select_axis.c +16 -6
  31. data/ext/ca_obj_shift.c +3 -3
  32. data/ext/ca_obj_stride.c +106 -23
  33. data/ext/ca_obj_string.c +3 -1
  34. data/ext/ca_obj_tile.c +13 -5
  35. data/ext/ca_obj_time.c +3 -1
  36. data/ext/ca_obj_timedelta.c +3 -1
  37. data/ext/ca_obj_transpose.c +1 -1
  38. data/ext/ca_obj_triop.c +62 -23
  39. data/ext/ca_obj_window.c +86 -17
  40. data/ext/ca_op_cmplx64.h +123 -0
  41. data/ext/ca_op_ipower.c +0 -3
  42. data/ext/ca_sort_kernels.h +5 -5
  43. data/ext/ca_sweep_engine.c +78 -35
  44. data/ext/ca_sweep_engine.h +13 -4
  45. data/ext/ca_transform_common.c +7 -0
  46. data/ext/carray.h +127 -130
  47. data/ext/carray_access.c +56 -34
  48. data/ext/carray_bincount.c +8 -8
  49. data/ext/carray_broadcast.c +99 -6
  50. data/ext/carray_build_flags.h +3 -0
  51. data/ext/carray_call_cfunc.c +1491 -0
  52. data/ext/carray_call_cfunc.h +153 -0
  53. data/ext/carray_cast.c +69 -31
  54. data/ext/carray_conversion.c +22 -22
  55. data/ext/carray_copy.c +12 -2
  56. data/ext/carray_core.c +57 -10
  57. data/ext/carray_count.c +1 -1
  58. data/ext/carray_element.c +7 -7
  59. data/ext/carray_factorize.c +28 -28
  60. data/ext/carray_hold.c +1 -1
  61. data/ext/carray_index_classifier.c +7 -21
  62. data/ext/carray_internal.h +19 -3
  63. data/ext/carray_kernels_bincmp.c +1 -0
  64. data/ext/carray_kernels_binop.c +320 -298
  65. data/ext/carray_kernels_init.c +1095 -0
  66. data/ext/carray_kernels_map.c +1 -0
  67. data/ext/carray_kernels_moncmp.c +1 -0
  68. data/ext/carray_kernels_monop.c +412 -411
  69. data/ext/carray_kernels_reduce_aggregate.c +88 -87
  70. data/ext/carray_kernels_reduce_boolean.c +1 -0
  71. data/ext/carray_kernels_reduce_cumulative.c +54 -53
  72. data/ext/carray_kernels_reduce_extreme.c +51 -50
  73. data/ext/carray_kernels_reduce_variance.c +1 -0
  74. data/ext/carray_kernels_scan.c +1 -0
  75. data/ext/carray_kernels_search.c +93 -92
  76. data/ext/carray_kernels_sort.c +1 -0
  77. data/ext/carray_kernels_triop.c +1 -0
  78. data/ext/carray_lazy.c +175 -5
  79. data/ext/carray_mask.c +6 -5
  80. data/ext/carray_median_percentile.c +1 -1
  81. data/ext/carray_memory_view.c +46 -21
  82. data/ext/carray_operator.c +46 -49
  83. data/ext/carray_partition.c +4 -4
  84. data/ext/carray_random.c +7 -7
  85. data/ext/carray_scatter.c +1 -1
  86. data/ext/carray_sort.c +3 -3
  87. data/ext/carray_sort_kernel.c +10 -10
  88. data/ext/carray_test.c +1 -1
  89. data/ext/extconf.rb +18 -0
  90. data/ext/mk_call_cfunc.rb +243 -52
  91. data/ext/mkkernel.rb +354 -100
  92. data/ext/ruby_carray.c +18 -16
  93. data/ext/version.h +4 -4
  94. data/lib/carray/autoload_carray.rb +19 -0
  95. data/lib/carray/autoload_method_extension.rb +2 -1
  96. data/lib/carray/axis_group.rb +7 -7
  97. data/lib/carray/basics.rb +59 -59
  98. data/lib/carray/bincount_nd.rb +12 -12
  99. data/lib/carray/block_iterator.rb +24 -14
  100. data/lib/carray/categorical.rb +4 -4
  101. data/lib/carray/categorical_iterator.rb +98 -76
  102. data/lib/carray/conditional.rb +14 -14
  103. data/lib/carray/construct.rb +21 -0
  104. data/lib/carray/core_extensions.rb +62 -5
  105. data/lib/carray/data_type_extension.rb +18 -1
  106. data/lib/carray/frame/concat.rb +3 -3
  107. data/lib/carray/frame/convert.rb +1 -1
  108. data/lib/carray/frame/frame.rb +4 -11
  109. data/lib/carray/frame/io.rb +159 -2
  110. data/lib/carray/frame/sort.rb +1 -1
  111. data/lib/carray/frame/verbs.rb +18 -1
  112. data/lib/carray/fuse_source.rb +123 -0
  113. data/lib/carray/fusion.rb +218 -0
  114. data/lib/carray/histogram.rb +16 -16
  115. data/lib/carray/inspect.rb +1 -7
  116. data/lib/carray/iterator.rb +4 -3
  117. data/lib/carray/lazy.rb +125 -73
  118. data/lib/carray/meld_reduce.rb +2 -2
  119. data/lib/carray/methods/align_addr.rb +1 -1
  120. data/lib/carray/methods/composition.rb +1 -1
  121. data/lib/carray/methods/is_in.rb +12 -12
  122. data/lib/carray/methods/locate_addr.rb +6 -1
  123. data/lib/carray/methods/mask_duplicates.rb +1 -1
  124. data/lib/carray/methods/meshgrid.rb +4 -5
  125. data/lib/carray/methods/mode.rb +2 -2
  126. data/lib/carray/methods/nunique.rb +1 -1
  127. data/lib/carray/methods/snap.rb +7 -2
  128. data/lib/carray/methods/unique.rb +3 -3
  129. data/lib/carray/methods/value_counts.rb +2 -2
  130. data/lib/carray/runtime.rb +0 -19
  131. data/lib/carray/slab_iterator.rb +20 -7
  132. data/lib/carray/string_operation_extension.rb +5 -5
  133. data/lib/carray/time.rb +890 -491
  134. data/lib/carray/window_iterator.rb +285 -13
  135. data/lib/carray.rb +5 -5
  136. data/yard-stubs/ca_obj_array.rb +385 -0
  137. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  138. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  139. data/yard-stubs/ca_obj_block.rb +73 -0
  140. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  141. data/yard-stubs/ca_obj_fake.rb +31 -0
  142. data/yard-stubs/ca_obj_farray.rb +32 -0
  143. data/yard-stubs/ca_obj_field.rb +45 -0
  144. data/yard-stubs/ca_obj_grid.rb +35 -0
  145. data/yard-stubs/ca_obj_refer.rb +72 -0
  146. data/yard-stubs/ca_obj_roll.rb +45 -0
  147. data/yard-stubs/ca_obj_shift.rb +43 -0
  148. data/yard-stubs/ca_obj_stride.rb +181 -0
  149. data/yard-stubs/ca_obj_tile.rb +29 -0
  150. data/yard-stubs/ca_obj_transpose.rb +40 -0
  151. data/yard-stubs/ca_obj_window.rb +49 -0
  152. data/yard-stubs/carray_access.rb +131 -0
  153. data/yard-stubs/carray_attribute.rb +246 -0
  154. data/yard-stubs/carray_broadcast.rb +37 -0
  155. data/yard-stubs/carray_cast.rb +489 -0
  156. data/yard-stubs/carray_class.rb +65 -0
  157. data/yard-stubs/carray_conversion.rb +76 -0
  158. data/yard-stubs/carray_copy.rb +79 -0
  159. data/yard-stubs/carray_core.rb +114 -0
  160. data/yard-stubs/carray_count.rb +79 -0
  161. data/yard-stubs/carray_element.rb +108 -0
  162. data/yard-stubs/carray_generate.rb +66 -0
  163. data/yard-stubs/carray_lazy.rb +23 -0
  164. data/yard-stubs/carray_loop.rb +140 -0
  165. data/yard-stubs/carray_mask.rb +259 -0
  166. data/yard-stubs/carray_math.rb +132 -0
  167. data/yard-stubs/carray_mathfunc.rb +45 -0
  168. data/yard-stubs/carray_median_percentile.rb +89 -0
  169. data/yard-stubs/carray_memory_view.rb +163 -0
  170. data/yard-stubs/carray_order.rb +312 -0
  171. data/yard-stubs/carray_random.rb +89 -0
  172. data/yard-stubs/carray_scatter.rb +106 -0
  173. data/yard-stubs/carray_slab.rb +57 -0
  174. data/yard-stubs/carray_sort.rb +163 -0
  175. data/yard-stubs/carray_test.rb +85 -0
  176. data/yard-stubs/carray_undef.rb +64 -0
  177. data/yard-stubs/carray_utils.rb +97 -0
  178. data/yard-stubs/ruby_carray.rb +193 -0
  179. metadata +59 -10
  180. data/ext/ca_obj_unbound_repeat.c +0 -496
data/ext/ca_obj_moncmp.c CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  CAMonCmp — lazy monadic element-wise comparison view: is_nan /
4
4
  is_inf / is_finite / is_invalid / signbit. Output data_type =
5
- CA_BOOLEAN (1 byte); operand keeps its native data_type (per-dtype
5
+ CA_BOOLEAN (1 byte); operand keeps its native data_type (per-type
6
6
  kernels cover integer as well, so is_nan / is_inf on integer parents
7
7
  return const-false and is_finite const-true without a cast layer).
8
8
 
@@ -20,7 +20,7 @@
20
20
  Peak scratch: 1 operand-data_type slab.
21
21
 
22
22
  Mask propagation:
23
- Per-dtype moncmp kernels write only at non-masked positions, so
23
+ Per-type moncmp kernels write only at non-masked positions, so
24
24
  the standard create_mask = parent.mask machinery carries mask bits
25
25
  to the caller-visible result. No in-flight mask handling in
26
26
  xfer_stride.
@@ -121,9 +121,8 @@ ca_moncmp_setup (CAMonCmp *ca, CArray *parent, uint16_t op_id)
121
121
 
122
122
  memcpy(ca->dim, parent->dim, parent->ndim * sizeof(ca_size_t));
123
123
 
124
- if ( ca_has_mask(parent) ) {
125
- ca_create_mask(ca);
126
- }
124
+ /* The mask is NOT built here: ca_has_mask creates a view's mask on
125
+ demand from its parent's. */
127
126
  if ( ca_is_scalar(parent) ) {
128
127
  ca_set_flag(ca, CA_FLAG_SCALAR);
129
128
  }
@@ -252,6 +251,8 @@ ca_moncmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
252
251
  int8_t k;
253
252
  void *scratch;
254
253
  ca_size_t operand_bytes;
254
+ ca_size_t operand_strides[CA_RANK_MAX];
255
+ int is_contig;
255
256
 
256
257
  if ( dir != CA_XFER_GET ) {
257
258
  rb_raise(rb_eRuntimeError, "CAMonCmp is read-only (xfer_stride PUT)");
@@ -263,26 +264,44 @@ ca_moncmp_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
263
264
 
264
265
  operand_bytes = mc->parent->bytes;
265
266
 
267
+ /* The operand's cell is operand_bytes wide where this view's is one byte
268
+ (CA_BOOLEAN), so the caller's strides have to be restated at the
269
+ operand's cell size before the pull; strides[k] / bytes is the index
270
+ step, which is what the two spaces share.
271
+
272
+ is_contig says the caller asked for the row-major slab that xfer_all
273
+ and attach send. Only then does the parent's own buffer hold the
274
+ wanted cells in the packed order the kernel reads them in, so it is
275
+ also the only case the leaf in-place path is good for. */
276
+ {
277
+ ca_size_t native = mc->bytes;
278
+ is_contig = 1;
279
+ for ( k = mc->ndim - 1; k >= 0; k-- ) {
280
+ operand_strides[k] = strides[k] / mc->bytes * operand_bytes;
281
+ if ( strides[k] != native ) {
282
+ is_contig = 0;
283
+ }
284
+ native *= mc->dim[k];
285
+ }
286
+ }
287
+
266
288
  /* === 1. pull parent (leaf-opt or scratch) === */
267
289
  int scratch_is_inplace = 0;
268
290
  {
269
291
  char *inplace = NULL;
270
- if ( ca_moncmp_try_leaf_inplace(mc->parent, starts, counts,
292
+ if ( is_contig &&
293
+ ca_moncmp_try_leaf_inplace(mc->parent, starts, counts,
271
294
  operand_bytes, &inplace) ) {
272
295
  scratch = inplace;
273
296
  scratch_is_inplace = 1;
274
297
  ca_moncmp_leaf_inplace_count++;
275
298
  }
276
299
  else {
277
- ca_size_t scratch_strides[CA_RANK_MAX];
278
- ca_size_t s = operand_bytes;
279
- for ( k = mc->ndim - 1; k >= 0; k-- ) {
280
- scratch_strides[k] = s;
281
- s *= counts[k];
282
- }
283
- scratch = ca_lazy_arena_acquire(slab_n * operand_bytes);
300
+ scratch = ( mc->parent->data_type == CA_OBJECT )
301
+ ? ca_lazy_arena_acquire_object(slab_n)
302
+ : ca_lazy_arena_acquire(slab_n * operand_bytes);
284
303
  ca_moncmp_scratch_acquire_count++;
285
- ca_xfer_stride(mc->parent, starts, counts, scratch_strides, scratch,
304
+ ca_xfer_stride(mc->parent, starts, counts, operand_strides, scratch,
286
305
  CA_XFER_GET);
287
306
  }
288
307
  }
data/ext/ca_obj_monop.c CHANGED
@@ -207,9 +207,9 @@ ca_monop_setup (CAMonOp *ca, CArray *parent, uint16_t op_id)
207
207
 
208
208
  memcpy(ca->dim, parent->dim, parent->ndim * sizeof(ca_size_t));
209
209
 
210
- if ( ca_has_mask(parent) ) {
211
- ca_create_mask(ca);
212
- }
210
+ /* The mask is NOT built here: ca_has_mask creates a view's mask on
211
+ demand from its parent's, so an expression whose mask nobody reads
212
+ never allocates one. */
213
213
 
214
214
  if ( ca_is_scalar(parent) ) {
215
215
  ca_set_flag(ca, CA_FLAG_SCALAR);
@@ -380,8 +380,8 @@ ca_size_t ca_monop_materialise_call_count = 0;
380
380
  but they are unobservable: the output mask is built separately via
381
381
  the attach lifecycle (ca_monop_func_create_mask + parent.mask
382
382
  CARefer), so reads of masked cells return UNDEF regardless of byte
383
- contents. This is the design license stated in CLAUDE.md
384
- §"design premises" ("mask is not a protection mechanism").
383
+ contents. The mask marks cells as undefined; it does not guard their
384
+ bytes, so writing garbage into a masked cell is licensed.
385
385
 
386
386
  A "partial mask slow path" (= a cell-wise branch to skip masked-cell
387
387
  compute) is a possible future micro-optimisation; it is not done
@@ -461,7 +461,9 @@ pull_leaf_with_optional_cast (CArray *leaf, uint16_t innermost_op,
461
461
  }
462
462
  }
463
463
  (void) holder;
464
- scratch = ca_lazy_arena_acquire(total_bytes);
464
+ scratch = ( leaf->data_type == CA_OBJECT )
465
+ ? ca_lazy_arena_acquire_object(slab_n)
466
+ : ca_lazy_arena_acquire(total_bytes);
465
467
  ca_monop_scratch_acquire_count++;
466
468
  ca_xfer_stride(leaf, starts, counts, parent_strides, scratch, CA_XFER_GET);
467
469
 
@@ -615,7 +617,9 @@ ca_monop_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
615
617
  → data (in target data_type). Required because in-place cast
616
618
  would overlap source/dest reads when target_bytes > src_bytes
617
619
  (forward-walk reads cells past their own write boundary). */
618
- scratch = ca_lazy_arena_acquire(total_bytes);
620
+ scratch = ( cur_dt == CA_OBJECT )
621
+ ? ca_lazy_arena_acquire_object(slab_n)
622
+ : ca_lazy_arena_acquire(total_bytes);
619
623
  ca_monop_scratch_acquire_count++;
620
624
  memcpy(scratch, data, total_bytes);
621
625
  ca_cast_block(slab_n, &src_stub, scratch, &dst_stub, data);
@@ -728,6 +732,14 @@ ca_monop_func_attach (void *ap)
728
732
  s = ca->bytes;
729
733
  for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
730
734
  for ( k = 0; k < ca->ndim; k++ ) starts[k] = 0;
735
+ /* CA_OBJECT cells are VALUEs and this buffer is about to be marked as
736
+ soon as the view is, so it must not be handed to the GC as raw
737
+ xmalloc garbage. */
738
+ if ( ca->data_type == CA_OBJECT ) {
739
+ VALUE *p = (VALUE *) ca->ptr;
740
+ ca_size_t i;
741
+ for ( i = 0; i < ca->elements; i++ ) *p++ = Qnil;
742
+ }
731
743
  ca_monop_func_xfer_stride(ca, starts, ca->dim, native, ca->ptr, CA_XFER_GET);
732
744
  }
733
745
 
data/ext/ca_obj_object.c CHANGED
@@ -352,8 +352,11 @@ ca_objmask_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
352
352
  if ( n == 0 ) return;
353
353
 
354
354
  if ( rb_obj_respond_to(ca->array, mid_block, Qtrue) ) {
355
- int aligned = 1;
356
- for ( k = 0; k < ndim; k++ ) {
355
+ /* The request is over the view's addresses, so a transposed / flat
356
+ request is legal; the per-axis copy_block dispatch below would
357
+ misread it. See ca_xfer_stride_request_is_axis_box (carray.h). */
358
+ int aligned = ca_xfer_stride_request_is_axis_box(ca, starts, counts, strides);
359
+ for ( k = 0; aligned && k < ndim; k++ ) {
357
360
  if ( strides[k] <= 0 || strides[k] % native[k] != 0 ) { aligned = 0; break; }
358
361
  steps[k] = strides[k] / native[k];
359
362
  }
@@ -937,8 +940,11 @@ ca_object_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
937
940
  before the % check
938
941
  - given %==0 + >0, strides/native >= 1 holds automatically */
939
942
  if ( rb_obj_respond_to(ca->self, mid_block, Qtrue) ) {
940
- int aligned = 1;
941
- for ( k = 0; k < ndim; k++ ) {
943
+ /* The request is over the view's addresses, so a transposed / flat
944
+ request is legal; the per-axis copy_block dispatch below would
945
+ misread it. See ca_xfer_stride_request_is_axis_box (carray.h). */
946
+ int aligned = ca_xfer_stride_request_is_axis_box(ca, starts, counts, strides);
947
+ for ( k = 0; aligned && k < ndim; k++ ) {
942
948
  if ( strides[k] <= 0 || strides[k] % native[k] != 0 ) { aligned = 0; break; }
943
949
  steps[k] = strides[k] / native[k]; /* >= 1 by construction */
944
950
  }
@@ -1001,6 +1007,93 @@ ca_object_func_fill_data (void *ap, void *ptr)
1001
1007
  ca_object_dispatch_fill((CAObject *) ap, ptr);
1002
1008
  }
1003
1009
 
1010
+ /* Partial fill. fill_data carries no region and can only say "fill
1011
+ everything I cover", so before these two slots existed the only way to
1012
+ fill part of a CAObject was the per-cell default -- one store_addr per
1013
+ cell. The region arrives in the view's own address space. With
1014
+ `fill_block` defined (and the region an axis-aligned forward sub-box of
1015
+ self) it becomes one call; with `fill_addrs` defined it becomes one call
1016
+ per address window. With neither defined the behaviour is exactly the
1017
+ old default, so an existing subclass sees no change. */
1018
+ static void
1019
+ ca_object_func_fill_addrs (void *ap, ca_size_t n, ca_size_t *addrs, void *ptr)
1020
+ {
1021
+ CAObject *ca = (CAObject *) ap;
1022
+ ID mid = rb_intern("fill_addrs");
1023
+
1024
+ if ( ca_is_face(ca) ) {
1025
+ ca_face_fill_addrs(ap, n, addrs, ptr);
1026
+ return;
1027
+ }
1028
+
1029
+ if ( n > 0 && rb_obj_respond_to(ca->self, mid, Qtrue) ) {
1030
+ volatile VALUE raddrs, rval;
1031
+ ca_size_t dim1[1] = { n };
1032
+ raddrs = ca_object_wrap_transient(CA_SIZE, sizeof(ca_size_t),
1033
+ 1, dim1, addrs, CA_XFER_PUT);
1034
+ rval = rb_ca_ptr2obj(ca->self, ptr);
1035
+ rb_funcall(ca->self, mid, 2, raddrs, rval);
1036
+ return;
1037
+ }
1038
+
1039
+ ca_fill_addrs_default(ap, n, addrs, ptr);
1040
+ }
1041
+
1042
+ /* Gate: one region axis per view axis, forward, and a whole number of
1043
+ elements per step. native is strictly decreasing, so steps[k] =
1044
+ m_k * native[k] with m_k >= 1 admits only the identity permutation --
1045
+ transpose, negative and zero (broadcast) steps, sub-element steps and
1046
+ dimension-dropping regions all fail it and take the addrs route. The
1047
+ bound check then confirms the decomposed box lies inside self. */
1048
+ static void
1049
+ ca_object_func_fill_stride (void *ap, ca_size_t base, int8_t ndim,
1050
+ ca_size_t *counts, ca_size_t *steps, void *ptr)
1051
+ {
1052
+ CAObject *ca = (CAObject *) ap;
1053
+ ca_size_t native[CA_RANK_MAX], istep[CA_RANK_MAX], start[CA_RANK_MAX];
1054
+ ca_size_t s;
1055
+ int8_t k;
1056
+ ID mid = rb_intern("fill_block");
1057
+
1058
+ if ( ca_is_face(ca) ) {
1059
+ ca_face_fill_stride(ap, base, ndim, counts, steps, ptr);
1060
+ return;
1061
+ }
1062
+
1063
+ if ( ndim == ca->ndim && rb_obj_respond_to(ca->self, mid, Qtrue) ) {
1064
+ int aligned = 1;
1065
+ s = 1;
1066
+ for ( k = ca->ndim - 1; k >= 0; k-- ) { native[k] = s; s *= ca->dim[k]; }
1067
+ for ( k = 0; k < ndim; k++ ) {
1068
+ if ( steps[k] <= 0 || steps[k] % native[k] != 0 ) { aligned = 0; break; }
1069
+ istep[k] = steps[k] / native[k];
1070
+ start[k] = ( base / native[k] ) % ca->dim[k];
1071
+ if ( start[k] + ( counts[k] - 1 ) * istep[k] >= ca->dim[k] ) {
1072
+ aligned = 0;
1073
+ break;
1074
+ }
1075
+ }
1076
+ if ( aligned ) {
1077
+ volatile VALUE rstarts, rcounts, rsteps, rval;
1078
+ rstarts = rb_ary_new_capa(ndim);
1079
+ rcounts = rb_ary_new_capa(ndim);
1080
+ rsteps = rb_ary_new_capa(ndim);
1081
+ for ( k = 0; k < ndim; k++ ) {
1082
+ rb_ary_push(rstarts, SIZE2NUM(start[k]));
1083
+ rb_ary_push(rcounts, SIZE2NUM(counts[k]));
1084
+ rb_ary_push(rsteps, SIZE2NUM(istep[k]));
1085
+ }
1086
+ rval = rb_ca_ptr2obj(ca->self, ptr);
1087
+ rb_funcall(ca->self, mid, 4, rstarts, rcounts, rsteps, rval);
1088
+ return;
1089
+ }
1090
+ }
1091
+
1092
+ /* addrs route: address windows -> ca_fill_addrs -> `fill_addrs` when the
1093
+ author defined it, else the per-cell default. */
1094
+ ca_fill_stride_via_addrs(ap, base, ndim, counts, steps, ptr);
1095
+ }
1096
+
1004
1097
  static void
1005
1098
  ca_object_func_create_mask (void *ap)
1006
1099
  {
@@ -1050,6 +1143,8 @@ ca_operation_function_t ca_object_func = {
1050
1143
  NULL, /* fold_stride: never-fold (callback boundary) */
1051
1144
  ca_object_func_xfer_stride,
1052
1145
  ca_object_func_xfer_all,
1146
+ .fill_addrs = ca_object_func_fill_addrs,
1147
+ .fill_stride = ca_object_func_fill_stride,
1053
1148
  };
1054
1149
 
1055
1150
  /* ------------------------------------------------------------------- */
data/ext/ca_obj_record.c CHANGED
@@ -202,7 +202,9 @@ ca_operation_function_t ca_record_func = {
202
202
  ca_face_xfer_addrs,
203
203
  NULL, /* fold_stride: identity Face is not foldable */
204
204
  ca_face_xfer_stride,
205
- ca_face_xfer_all
205
+ ca_face_xfer_all,
206
+ .fill_addrs = ca_face_fill_addrs,
207
+ .fill_stride = ca_face_fill_stride,
206
208
  };
207
209
 
208
210
  /* ------------------------------------------------------------------- */
data/ext/ca_obj_refer.c CHANGED
@@ -454,7 +454,7 @@ rb_ca_refer (int argc, VALUE *argv, VALUE self)
454
454
  return obj;
455
455
  }
456
456
  }
457
- CA_FACE_LIFT_IF_FACE(obj, self, ca);
457
+ CA_WRAPPER_LIFT(obj, self, ca);
458
458
  return obj;
459
459
  }
460
460
 
@@ -482,7 +482,31 @@ rb_ca_refer_new (VALUE self,
482
482
  rb_set_options(ropt, "bytes,offset", SIZE2NUM(bytes), SIZE2NUM(offset));
483
483
  rb_ary_store(list, 2, ropt);
484
484
 
485
- return rb_ca_refer(3, (VALUE *) RARRAY_CONST_PTR(list), self);
485
+ {
486
+ volatile VALUE obj = rb_ca_refer(3, (VALUE *) RARRAY_CONST_PTR(list), self);
487
+ CArray *co;
488
+
489
+ /* CAREFUL: this is the internal builder -- some fifteen call sites want
490
+ the refer itself, not a wrapper on top of it. The public `refer` it
491
+ delegates to lifts a CALazyMarker, so strip that here.
492
+
493
+ Two things go wrong otherwise. rb_ca_value_array strips the mask off
494
+ what it gets back and marks the level it is handed; with a marker in
495
+ the way the refer underneath keeps its mask and never gets
496
+ CA_FLAG_VALUE_ARRAY, so the values read back as UNDEF. And builders
497
+ that stack further views on the result -- fancy indexing goes refer,
498
+ grid, refer -- end up with a marker buried in the middle of the
499
+ chain, which is the redundant-middle-wrapper that CAFace.md section
500
+ 8.3 exists to prevent.
501
+
502
+ Faces stay lifted: rb_ca_value_array depends on that and says so
503
+ where it marks the storage level. */
504
+ TypedData_Get_Struct(obj, CArray, &carray_data_type, co);
505
+ if ( ca_is_lazy_marker(co) ) {
506
+ obj = rb_ca_parent(obj);
507
+ }
508
+ return obj;
509
+ }
486
510
  }
487
511
 
488
512
  /* CArray#reshape(*newdim) — returns a view of self with the new
@@ -605,7 +629,7 @@ rb_ca_reshape (int argc, VALUE *argv, VALUE self)
605
629
  out_strides, out_base);
606
630
  obj = ca_wrap_struct(cs);
607
631
  rb_ca_set_parent(obj, self);
608
- CA_FACE_LIFT_IF_FACE(obj, self, ca);
632
+ CA_WRAPPER_LIFT(obj, self, ca);
609
633
  return obj;
610
634
  }
611
635
  }
@@ -614,7 +638,7 @@ rb_ca_reshape (int argc, VALUE *argv, VALUE self)
614
638
  new_dim, ca->bytes, 0);
615
639
  obj = ca_wrap_struct(cr);
616
640
  rb_ca_set_parent(obj, self);
617
- CA_FACE_LIFT_IF_FACE(obj, self, ca);
641
+ CA_WRAPPER_LIFT(obj, self, ca);
618
642
  return obj;
619
643
  }
620
644
 
@@ -642,7 +666,7 @@ rb_ca_flatten (VALUE self)
642
666
  1, dim, out_strides, out_base);
643
667
  obj = ca_wrap_struct(cs);
644
668
  rb_ca_set_parent(obj, self);
645
- CA_FACE_LIFT_IF_FACE(obj, self, ca);
669
+ CA_WRAPPER_LIFT(obj, self, ca);
646
670
  return obj;
647
671
  }
648
672
  }
@@ -650,7 +674,7 @@ rb_ca_flatten (VALUE self)
650
674
  cr = ca_refer_new(ca, ca->data_type, 1, dim, ca->bytes, 0);
651
675
  obj = ca_wrap_struct(cr);
652
676
  rb_ca_set_parent(obj, self);
653
- CA_FACE_LIFT_IF_FACE(obj, self, ca);
677
+ CA_WRAPPER_LIFT(obj, self, ca);
654
678
  return obj;
655
679
  }
656
680
 
data/ext/ca_obj_roll.c CHANGED
@@ -232,10 +232,18 @@ ca_roll_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
232
232
  s = ca->bytes;
233
233
  for (k = ndim - 1; k >= 0; k--) { dstride[k] = s; s *= counts[k]; }
234
234
 
235
- for (k = 0; k < ndim; k++) {
236
- if (strides[k] % rnative[k] != 0 || strides[k] / rnative[k] != 1) {
237
- structural = 0;
238
- break;
235
+ /* The request is over the view's addresses, so a transposed / flat request
236
+ is legal and must not be composed axis-by-axis; see
237
+ ca_xfer_stride_request_is_axis_box (carray.h). */
238
+ if ( ! ca_xfer_stride_request_is_axis_box(ca, starts, counts, strides) ) {
239
+ structural = 0;
240
+ }
241
+ else {
242
+ for (k = 0; k < ndim; k++) {
243
+ if (strides[k] % rnative[k] != 0 || strides[k] / rnative[k] != 1) {
244
+ structural = 0;
245
+ break;
246
+ }
239
247
  }
240
248
  }
241
249
 
@@ -529,7 +537,7 @@ rb_ca_roll (int argc, VALUE *argv, VALUE self)
529
537
  }
530
538
  {
531
539
  VALUE obj = rb_ca_roll_new(self, shift);
532
- CA_FACE_LIFT_IF_FACE(obj, self, ca);
540
+ CA_WRAPPER_LIFT(obj, self, ca);
533
541
  return obj;
534
542
  }
535
543
  }
@@ -571,9 +571,17 @@ ca_select_axis_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
571
571
  s = ca->bytes;
572
572
  for (k = ndim - 1; k >= 0; k--) { dstride[k] = s; s *= counts[k]; }
573
573
 
574
- for (k = 0; k < ndim; k++) {
575
- if (strides[k] % cnative[k] != 0) { aligned = 0; break; }
576
- src_step[k] = strides[k] / cnative[k];
574
+ /* The request is over the view's addresses, so a transposed / flat request
575
+ is legal and must not be composed axis-by-axis; see
576
+ ca_xfer_stride_request_is_axis_box (carray.h). */
577
+ if ( ! ca_xfer_stride_request_is_axis_box(ca, starts, counts, strides) ) {
578
+ aligned = 0;
579
+ }
580
+ else {
581
+ for (k = 0; k < ndim; k++) {
582
+ if (strides[k] % cnative[k] != 0) { aligned = 0; break; }
583
+ src_step[k] = strides[k] / cnative[k];
584
+ }
577
585
  }
578
586
 
579
587
  if (!aligned) {
@@ -925,7 +933,7 @@ rb_ca_select_axis_s_new_debug (VALUE klass, VALUE rparent, VALUE rindirect,
925
933
  obj = TypedData_Make_Struct(klass, CASelectAxis, &caselectaxis_data_type, ca);
926
934
  ca_select_axis_setup(ca, parent, indirect_axis, selector,
927
935
  ap_start_buf, ap_count_buf, ap_step_buf);
928
- rb_ivar_set(obj, rb_intern("_parent"), rparent);
936
+ rb_ca_set_parent(obj, rparent);
929
937
  rb_ivar_set(obj, rb_intern("_selector"), rselector);
930
938
  return obj;
931
939
  }
@@ -1066,8 +1074,10 @@ rb_ca_select_axis (int argc, VALUE *argv, VALUE self)
1066
1074
  ca_select_axis_setup(ca, parent, indirect_axis, selector,
1067
1075
  ap_start, ap_count, ap_step);
1068
1076
 
1069
- /* Keep Ruby objects alive (parent and selector references). */
1070
- rb_ivar_set(obj, rb_intern("_parent"), self);
1077
+ /* Keep Ruby objects alive (parent and selector references). The parent
1078
+ goes through rb_ca_set_parent so #parent, #root_array and #ancestors
1079
+ read the same link here as they do through every other view. */
1080
+ rb_ca_set_parent(obj, self);
1071
1081
  rb_ivar_set(obj, rb_intern("_selector"), rselector_keep);
1072
1082
  return obj;
1073
1083
  }
data/ext/ca_obj_shift.c CHANGED
@@ -264,7 +264,7 @@ rb_ca_shift_new (VALUE cary, ca_size_t *shift, char *fill, int8_t *roll,
264
264
  VALUE
265
265
  rb_ca_shift (int argc, VALUE *argv, VALUE self)
266
266
  {
267
- volatile VALUE obj, ropt, rfval = CA_NIL, rroll = Qnil, rcs;
267
+ volatile VALUE obj, ropt, rfval = CA_UNSPECIFIED, rroll = Qnil, rcs;
268
268
  CArray *ca;
269
269
  CScalar *cs;
270
270
  ca_size_t shift[CA_RANK_MAX];
@@ -301,7 +301,7 @@ rb_ca_shift (int argc, VALUE *argv, VALUE self)
301
301
  "use fill_value: kwarg (e.g. shift(1, fill_value: -2))");
302
302
  }
303
303
 
304
- if ( rfval == CA_NIL ) {
304
+ if ( rfval == CA_UNSPECIFIED ) {
305
305
  /* Default fill value = 0 (or INT2NUM(0) for OBJECT type) */
306
306
  rcs = rb_cscalar_new(ca->data_type, ca->bytes, NULL);
307
307
  TypedData_Get_Struct(rcs, CScalar, &cscalar_data_type, cs);
@@ -343,7 +343,7 @@ rb_ca_shift (int argc, VALUE *argv, VALUE self)
343
343
 
344
344
  obj = rb_ca_shift_new(self, shift, fill, roll, fill_mask);
345
345
 
346
- CA_FACE_LIFT_IF_FACE(obj, self, ca);
346
+ CA_WRAPPER_LIFT(obj, self, ca);
347
347
  return obj;
348
348
  }
349
349
 
data/ext/ca_obj_stride.c CHANGED
@@ -192,6 +192,19 @@ void ca_xfer_stride_tiled_transpose_2d (char *src_base, ca_size_t bytes,
192
192
  ca_size_t *counts, ca_size_t *strides,
193
193
  char *data, int dir);
194
194
 
195
+ /* Public form of the family test the fold walk below uses inline. The
196
+ family is defined by the operation table, not by a class or an
197
+ obj_type list: every member was installed with a copy of
198
+ ca_stride_func, so an externally installed view that shares the
199
+ table answers true as well. See carray.h for what membership does
200
+ and does not promise. */
201
+ int
202
+ ca_is_stride_family (const void *ap)
203
+ {
204
+ const CArray *ca = (const CArray *) ap;
205
+ return ( ca_func[ca->obj_type].attach == ca_stride_func.attach );
206
+ }
207
+
195
208
  /* Compose `leaf->strides` and `leaf->base_offset` (which live in `parent`'s
196
209
  own logical row-major contig byte space) into `out_strides` and
197
210
  `out_base` expressed in `parent->parent`'s byte space.
@@ -616,9 +629,29 @@ ca_stride_func_xfer_addrs (void *ap, ca_size_t n, ca_size_t *addrs,
616
629
  hand the whole region to the root in a SINGLE ca_xfer_stride (entity -> ptr
617
630
  memcpy; boundary view -> one recursion).
618
631
 
619
- Axis-aligned, byte-matching requests only; the byte-mismatch reinterpret
620
- (CAField .real/.imag) and non-aligned access fall back to per-cell
621
- xfer_index (which handles the sub-byte case). */
632
+ CAREFUL: the request is given over this view's linear ADDRESSES (carray.h
633
+ xfer_stride contract), so request axis k does NOT have to be view axis k.
634
+ A caller is free to hand over a transposed region -- counts/strides in one
635
+ order, the packed destination in another -- which is exactly what a
636
+ column-major backend (carray-linalg's Fortran-LAPACK gather) does. Matching
637
+ request axis k to view axis k by dividing strides[k] by the axis-k native
638
+ step looks right and is wrong: an (n, 1) view has the same native step on
639
+ both axes, so a transposed request divides cleanly and then composes the
640
+ n-cell walk onto the length-1 axis, whose parent stride is 0 -- delivering
641
+ the first cell n times, with no error anywhere. Ask ca_stride_region_axes
642
+ which view axis each request axis really moves (the same question
643
+ fill_stride asks), and fall back to the per-cell walk when the region is
644
+ not a box over our axes.
645
+
646
+ Byte-matching requests only; the byte-mismatch reinterpret (CAField
647
+ .real/.imag) and non-box access fall back to per-cell xfer_index (which
648
+ handles the sub-byte case). */
649
+
650
+ static int ca_stride_region_axes (CAStride *ca, ca_size_t base, int8_t ndim,
651
+ ca_size_t *counts, ca_size_t *steps,
652
+ ca_size_t *base_idx, int8_t *axis_of,
653
+ ca_size_t *mult);
654
+
622
655
  static void
623
656
  ca_stride_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
624
657
  ca_size_t *strides, void *data, int dir)
@@ -629,7 +662,12 @@ ca_stride_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
629
662
  ca_size_t composed_base;
630
663
  ca_size_t view_native[CA_RANK_MAX];
631
664
  ca_size_t root_stride[CA_RANK_MAX];
665
+ ca_size_t steps[CA_RANK_MAX];
666
+ ca_size_t base_idx[CA_RANK_MAX];
667
+ ca_size_t mult[CA_RANK_MAX];
668
+ int8_t axis_of[CA_RANK_MAX];
632
669
  ca_size_t root_base;
670
+ ca_size_t base_addr = 0;
633
671
  ca_size_t s;
634
672
  int8_t ndim = ca->ndim, k;
635
673
  int aligned = 1;
@@ -639,26 +677,77 @@ ca_stride_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
639
677
 
640
678
  s = ca->bytes;
641
679
  for (k = ndim - 1; k >= 0; k--) { view_native[k] = s; s *= ca->dim[k]; }
680
+ for (k = 0; k < ndim; k++) base_addr += starts[k] * view_native[k];
642
681
 
643
682
  if (ca->bytes != root->bytes) {
644
683
  aligned = 0;
645
684
  }
646
685
  else {
647
686
  for (k = 0; k < ndim; k++) {
648
- if (strides[k] % view_native[k] != 0) { aligned = 0; break; }
687
+ if ( strides[k] % ca->bytes != 0 ) { aligned = 0; break; }
688
+ steps[k] = strides[k] / ca->bytes;
689
+ }
690
+ if ( aligned ) {
691
+ aligned = ca_stride_region_axes(ca, base_addr / ca->bytes, ndim,
692
+ counts, steps, base_idx, axis_of, mult);
693
+ }
694
+ }
695
+
696
+ if ( aligned ) {
697
+ /* Each request axis now names the view axis it moves (axis_of) and by how
698
+ many of that axis' cells (mult); a count-1 axis moves nothing and gets
699
+ stride 0, which the walk never follows. */
700
+ root_base = composed_base;
701
+ for (k = 0; k < ca->ndim; k++) {
702
+ root_base += base_idx[k] * composed_strides[k];
703
+ }
704
+ for (k = 0; k < ndim; k++) {
705
+ root_stride[k] = ( axis_of[k] >= 0 )
706
+ ? mult[k] * composed_strides[axis_of[k]]
707
+ : 0;
708
+ }
709
+ }
710
+
711
+ /* Cold root that answers regions: compose the request into its addresses
712
+ and hand it over whole, exactly as xfer_all does for the whole view. A
713
+ root with no memory to lend (a lazy transform, a CAObject over a file)
714
+ has no ptr to walk, but it can still produce a region on request -- and
715
+ asking it once beats asking it once per cell, which is what the per-cell
716
+ descent below would do. Chunked consumers (the binop / sweep drivers'
717
+ per-chunk gather) arrive here, so the difference is the whole cost of
718
+ the transfer, not a constant factor.
719
+
720
+ The gate is xfer_all's: the root must have the slot, share this view's
721
+ cell width (else the composed offsets are not whole root elements), and
722
+ carry the same ndim (else its index space cannot hold this request's
723
+ axes). Anything narrower keeps the per-cell descent, which is correct
724
+ for all of them. Direction is not part of the gate: a root that refuses
725
+ writes refuses them per cell as well. */
726
+ if (aligned && !root->ptr && ca_func[root->obj_type].xfer_stride
727
+ && ca->bytes == root->bytes && ndim == root->ndim) {
728
+ ca_size_t rstarts[CA_RANK_MAX];
729
+ if ( root_base % root->bytes == 0 ) {
730
+ ca_size_t raddr = root_base / root->bytes;
731
+ if ( raddr >= 0 && raddr < root->elements ) {
732
+ ca_addr2index(root, raddr, rstarts);
733
+ ca_xfer_stride(root, rstarts, counts, root_stride, d, dir);
734
+ return;
735
+ }
649
736
  }
650
737
  }
651
738
 
652
739
  /* Per-cell fallback (correct, no whole-view attach): byte-mismatch
653
- reinterpret (CAField), non-aligned access, or a cold non-entity root
654
- (whose ndim may differ from the view's -- e.g. a reshape over a boundary).
655
- ca_stride_func_xfer_index composes one hop and delegates to the parent. */
740
+ reinterpret (CAField), a region that is not a box over our axes (a
741
+ transposed request onto a degenerate axis, a flat index over several
742
+ axes), or a cold non-entity root the branch above could not hand a
743
+ region to (its ndim differs from the view's -- e.g. a reshape over a
744
+ boundary -- or it has no region slot). ca_stride_func_xfer_index
745
+ composes one hop and delegates to the parent. */
656
746
  if (!aligned || !root->ptr) {
657
- ca_size_t idx[CA_RANK_MAX], doff = 0, base = 0;
658
- for (k = 0; k < ndim; k++) base += starts[k] * view_native[k];
747
+ ca_size_t idx[CA_RANK_MAX], doff = 0;
659
748
  for (k = 0; k < ndim; k++) idx[k] = 0;
660
749
  while (1) {
661
- ca_size_t off = base, vmidx[CA_RANK_MAX];
750
+ ca_size_t off = base_addr, vmidx[CA_RANK_MAX];
662
751
  for (k = 0; k < ndim; k++) off += idx[k] * strides[k];
663
752
  ca_addr2index((CArray *) ca, off / ca->bytes, vmidx);
664
753
  ca_stride_func_xfer_index(ca, vmidx, d + doff, dir);
@@ -670,17 +759,11 @@ ca_stride_func_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
670
759
  return;
671
760
  }
672
761
 
673
- /* Structural: root has a live ptr (entity / attached). Translate the
674
- request into root's BYTE space and do a strided memcpy in the VIEW's ndim
675
- (byte offsets into root->ptr -- independent of root's own ndim, so a
676
- reshape view over a 1-D entity works). compose happened once. */
677
- root_base = composed_base;
678
- for (k = 0; k < ndim; k++) {
679
- ca_size_t req_step = strides[k] / view_native[k];
680
- root_base += starts[k] * composed_strides[k];
681
- root_stride[k] = req_step * composed_strides[k];
682
- }
683
- /* Slab-merge, tile-block and the general driver all live in the shared
762
+ /* Structural: root has a live ptr (entity / attached). The request is
763
+ already in root's BYTE space (root_base / root_stride above), so the walk
764
+ runs in the VIEW's ndim -- independent of root's own ndim, which is what
765
+ lets a reshape view over a 1-D entity through. compose happened once.
766
+ Slab-merge, tile-block and the general driver all live in the shared
684
767
  walker, which the central dispatcher's structural path also uses. */
685
768
  ca_xfer_strided_walk(root->ptr + root_base, ca->bytes, ndim,
686
769
  counts, root_stride, d, dir);
@@ -2239,7 +2322,7 @@ rb_ca_flip_build_view (VALUE self, CArray *parent, const int8_t *flip)
2239
2322
 
2240
2323
  VALUE obj = rb_ca_stride_new(self, parent->data_type, parent->bytes,
2241
2324
  ndim, outdim, outstrides, base_offset);
2242
- CA_FACE_LIFT_IF_FACE(obj, self, parent);
2325
+ CA_WRAPPER_LIFT(obj, self, parent);
2243
2326
  return obj;
2244
2327
  }
2245
2328
 
@@ -2462,7 +2545,7 @@ rb_ca_diagonal (int argc, VALUE *argv, VALUE self)
2462
2545
  {
2463
2546
  VALUE obj = rb_ca_stride_new(self, parent->data_type, parent->bytes,
2464
2547
  out_k, outdim, outstrides, base_offset);
2465
- CA_FACE_LIFT_IF_FACE(obj, self, parent);
2548
+ CA_WRAPPER_LIFT(obj, self, parent);
2466
2549
  return obj;
2467
2550
  }
2468
2551
  }