carray 3.0.2 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +304 -0
  3. data/README.md +3 -4
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +225 -0
  6. data/ext/ca_axis_group.c +177 -99
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +66 -71
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +663 -214
  14. data/ext/ca_kernel_iterator.h +231 -167
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +132 -22
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +1 -0
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +96 -25
  25. data/ext/ca_obj_face.c +406 -64
  26. data/ext/ca_obj_face.h +92 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +21 -21
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +189 -107
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +543 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +47 -49
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -118
  46. data/ext/ca_obj_stride.c +195 -34
  47. data/ext/ca_obj_string.c +17 -18
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +149 -27
  54. data/ext/ca_op_ipower.c +24 -11
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_sort_kernels.h +70 -0
  58. data/ext/ca_sweep_engine.c +102 -79
  59. data/ext/ca_sweep_engine.h +7 -20
  60. data/ext/ca_triop_dispatch.c +4 -1
  61. data/ext/ca_triop_dispatch.h +5 -2
  62. data/ext/caframe_csv_reader.c +463 -0
  63. data/ext/carray.h +70 -27
  64. data/ext/carray_access.c +455 -211
  65. data/ext/carray_address_basis.c +177 -36
  66. data/ext/carray_attribute.c +45 -3
  67. data/ext/carray_bincount.c +32 -2
  68. data/ext/carray_broadcast.c +46 -14
  69. data/ext/carray_call_cfunc.c +0 -28
  70. data/ext/carray_cast.c +1444 -209
  71. data/ext/carray_cast_func.rb +34 -21
  72. data/ext/carray_conversion.c +197 -73
  73. data/ext/carray_copy.c +32 -11
  74. data/ext/carray_core.c +514 -122
  75. data/ext/carray_count.c +140 -87
  76. data/ext/carray_element.c +18 -27
  77. data/ext/carray_factorize.c +569 -277
  78. data/ext/carray_generate.c +76 -40
  79. data/ext/carray_histogram.c +152 -94
  80. data/ext/carray_hold.c +2 -1
  81. data/ext/carray_index_classifier.c +63 -96
  82. data/ext/carray_internal.h +385 -3
  83. data/ext/carray_lazy.c +834 -79
  84. data/ext/carray_loop.c +81 -65
  85. data/ext/carray_mask.c +682 -316
  86. data/ext/carray_math_kernel.h +1 -0
  87. data/ext/carray_median_percentile.c +221 -76
  88. data/ext/carray_memory_view.c +254 -248
  89. data/ext/carray_operator.c +1089 -359
  90. data/ext/carray_order.c +133 -69
  91. data/ext/carray_partition.c +102 -56
  92. data/ext/carray_random.c +273 -239
  93. data/ext/carray_scatter.c +308 -65
  94. data/ext/carray_segment.c +257 -0
  95. data/ext/carray_slab.c +170 -70
  96. data/ext/carray_sort.c +215 -218
  97. data/ext/carray_sort_kernel.c +8 -1
  98. data/ext/carray_struct.c +99 -21
  99. data/ext/carray_test.c +167 -64
  100. data/ext/carray_time_text.c +1182 -0
  101. data/ext/carray_utils.c +363 -98
  102. data/ext/extconf.rb +27 -2
  103. data/ext/mk_call_cfunc.rb +0 -2
  104. data/ext/mk_pow5_table.rb +35 -0
  105. data/ext/mkkernel.rb +1076 -846
  106. data/ext/ruby_carray.c +7 -1
  107. data/ext/version.h +4 -4
  108. data/ext/version.rb +4 -2
  109. data/lib/carray/attribute.rb +39 -0
  110. data/lib/carray/autoload_carray.rb +19 -0
  111. data/lib/carray/autoload_method_extension.rb +6 -0
  112. data/lib/carray/axis_group.rb +174 -26
  113. data/lib/carray/basics.rb +91 -25
  114. data/lib/carray/bincount_nd.rb +6 -0
  115. data/lib/carray/block_iterator.rb +118 -48
  116. data/lib/carray/boolean_reduce.rb +21 -29
  117. data/lib/carray/categorical.rb +94 -26
  118. data/lib/carray/categorical_iterator.rb +333 -826
  119. data/lib/carray/complex.rb +36 -29
  120. data/lib/carray/conditional.rb +94 -7
  121. data/lib/carray/const_string.rb +46 -0
  122. data/lib/carray/frame/concat.rb +157 -6
  123. data/lib/carray/frame/convert.rb +13 -7
  124. data/lib/carray/frame/csv_parser.rb +589 -82
  125. data/lib/carray/frame/describe.rb +79 -0
  126. data/lib/carray/frame/frame.rb +124 -25
  127. data/lib/carray/frame/group.rb +189 -63
  128. data/lib/carray/frame/io.rb +412 -72
  129. data/lib/carray/frame/join.rb +134 -46
  130. data/lib/carray/frame/records.rb +37 -16
  131. data/lib/carray/frame/reshape.rb +448 -0
  132. data/lib/carray/frame/sort.rb +26 -2
  133. data/lib/carray/frame/verbs.rb +715 -48
  134. data/lib/carray/frame.rb +2 -0
  135. data/lib/carray/fuse_source.rb +111 -2
  136. data/lib/carray/fusion.rb +229 -38
  137. data/lib/carray/inspect.rb +29 -11
  138. data/lib/carray/iterator.rb +14 -2
  139. data/lib/carray/lazy.rb +54 -30
  140. data/lib/carray/mask_gap_fill.rb +66 -52
  141. data/lib/carray/math.rb +32 -19
  142. data/lib/carray/meld_reduce.rb +40 -7
  143. data/lib/carray/methods/align_addr.rb +8 -4
  144. data/lib/carray/methods/bincount.rb +2 -2
  145. data/lib/carray/methods/bit_string.rb +14 -3
  146. data/lib/carray/methods/composition.rb +16 -10
  147. data/lib/carray/methods/factorize.rb +2 -2
  148. data/lib/carray/methods/gather_nd.rb +13 -2
  149. data/lib/carray/methods/insert_block.rb +9 -14
  150. data/lib/carray/methods/is_in.rb +117 -4
  151. data/lib/carray/methods/join.rb +1 -1
  152. data/lib/carray/methods/locate_addr.rb +177 -36
  153. data/lib/carray/methods/mode.rb +18 -1
  154. data/lib/carray/methods/repeat.rb +1 -20
  155. data/lib/carray/methods/resize.rb +21 -23
  156. data/lib/carray/methods/snap.rb +34 -12
  157. data/lib/carray/methods/unique.rb +11 -2
  158. data/lib/carray/record_order.rb +208 -0
  159. data/lib/carray/runtime.rb +14 -11
  160. data/lib/carray/segment_iterator.rb +990 -0
  161. data/lib/carray/serialize.rb +197 -27
  162. data/lib/carray/slab_iterator.rb +51 -27
  163. data/lib/carray/stack.rb +10 -4
  164. data/lib/carray/string_operation_extension.rb +4 -3
  165. data/lib/carray/struct.rb +142 -51
  166. data/lib/carray/struct_builder.rb +110 -14
  167. data/lib/carray/table.rb +2 -2
  168. data/lib/carray/time.rb +517 -103
  169. data/lib/carray/window_iterator.rb +127 -72
  170. data/lib/carray.rb +2 -2
  171. data/yard-stubs/ca_obj_array.rb +4 -4
  172. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  173. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  174. data/yard-stubs/ca_obj_fake.rb +1 -1
  175. data/yard-stubs/ca_obj_field.rb +1 -1
  176. data/yard-stubs/ca_obj_refer.rb +3 -3
  177. data/yard-stubs/ca_obj_roll.rb +0 -1
  178. data/yard-stubs/ca_obj_stride.rb +10 -7
  179. data/yard-stubs/ca_obj_transpose.rb +1 -1
  180. data/yard-stubs/ca_obj_window.rb +1 -2
  181. data/yard-stubs/carray_access.rb +5 -4
  182. data/yard-stubs/carray_broadcast.rb +1 -1
  183. data/yard-stubs/carray_cast.rb +78 -43
  184. data/yard-stubs/carray_conversion.rb +13 -1
  185. data/yard-stubs/carray_copy.rb +8 -3
  186. data/yard-stubs/carray_core.rb +3 -3
  187. data/yard-stubs/carray_count.rb +9 -0
  188. data/yard-stubs/carray_lazy.rb +5 -1
  189. data/yard-stubs/carray_loop.rb +4 -1
  190. data/yard-stubs/carray_mask.rb +34 -10
  191. data/yard-stubs/carray_math.rb +25 -13
  192. data/yard-stubs/carray_median_percentile.rb +10 -6
  193. data/yard-stubs/carray_order.rb +34 -13
  194. data/yard-stubs/carray_scatter.rb +10 -3
  195. data/yard-stubs/carray_segment.rb +72 -0
  196. data/yard-stubs/carray_slab.rb +2 -1
  197. data/yard-stubs/carray_sort.rb +43 -3
  198. data/yard-stubs/carray_test.rb +10 -0
  199. data/yard-stubs/carray_utils.rb +0 -26
  200. metadata +11 -19
  201. data/ext/ca_for_each_element.h +0 -239
  202. data/ext/carray_build_flags.h +0 -3
  203. data/ext/carray_config.h +0 -28
  204. data/ext/carray_kernels_bincmp.c +0 -4446
  205. data/ext/carray_kernels_binop.c +0 -11001
  206. data/ext/carray_kernels_init.c +0 -1131
  207. data/ext/carray_kernels_map.c +0 -3467
  208. data/ext/carray_kernels_moncmp.c +0 -2097
  209. data/ext/carray_kernels_monop.c +0 -18313
  210. data/ext/carray_kernels_reduce_aggregate.c +0 -26005
  211. data/ext/carray_kernels_reduce_boolean.c +0 -330
  212. data/ext/carray_kernels_reduce_cumulative.c +0 -14862
  213. data/ext/carray_kernels_reduce_extreme.c +0 -17494
  214. data/ext/carray_kernels_reduce_variance.c +0 -3910
  215. data/ext/carray_kernels_scan.c +0 -3693
  216. data/ext/carray_kernels_search.c +0 -32218
  217. data/ext/carray_kernels_sort.c +0 -10626
  218. data/ext/carray_kernels_triop.c +0 -1392
@@ -104,6 +104,7 @@ class CAWindowIterator < CAIterator
104
104
  "windows: expected #{@source.ndim} ranges (one per axis), " \
105
105
  "got #{@ranges.size}"
106
106
  end
107
+ @ranges = @ranges.each_with_index.map { |r, k| window_offsets(r, k) }
107
108
 
108
109
  @sndim = @source.ndim
109
110
  @widths = @ranges.map { |r| r.end - r.begin + 1 }
@@ -158,11 +159,38 @@ class CAWindowIterator < CAIterator
158
159
  # the padded entity.
159
160
  # @return [CArray]
160
161
  def sliding_view
161
- @sliding_view ||= anchored_buffer.sliding_windows(*@widths)
162
+ @sliding_view ||= @shape.include?(0) ? no_windows : anchored_buffer.sliding_windows(*@widths)
162
163
  end
163
164
 
164
165
  private
165
166
 
167
+ # An axis's offsets as an inclusive Integer range, as Ruby reads the range
168
+ # given (`-1...1` is the offsets -1 and 0).
169
+ def window_offsets (r, k)
170
+ unless r.is_a?(Range) && r.begin.is_a?(Integer) && r.end.is_a?(Integer)
171
+ raise TypeError,
172
+ "windows: axis #{k} takes a Range of Integer offsets, got #{r.inspect}"
173
+ end
174
+ last = r.exclude_end? ? r.end - 1 : r.end
175
+ if last < r.begin
176
+ raise ArgumentError, "windows: axis #{k} offsets #{r.inspect} are empty"
177
+ end
178
+ r.begin..last
179
+ end
180
+
181
+ # With no anchors (an empty source, or a :truncate window wider than the
182
+ # source) there is nothing to slide over, and sliding_windows refuses a
183
+ # window wider than its buffer. The view is then an empty one of the same
184
+ # shape: the source's unit tiles cut to the output and stretched along the
185
+ # window axes, so every reduction answers with an empty array of its type.
186
+ def no_windows
187
+ unit = @source.block_view(*[1] * @sndim)
188
+ cut = @shape.each_with_index.map { |n, k| n == unit.shape[k] ? nil : 0...n }
189
+ unit = unit[*cut, *[nil] * @sndim] unless cut.all?(&:nil?)
190
+ view = unit.broadcast_to(*(@shape + @widths))
191
+ @source.face? ? view.face_lift(@source) : view
192
+ end
193
+
166
194
  # The stretch of the padded buffer the windows are taken from: it begins at
167
195
  # the first window and holds one window per anchor. For a window that covers
168
196
  # its own anchor and a boundary policy that pads, that is the whole buffer.
@@ -210,10 +238,11 @@ class CAWindowIterator < CAIterator
210
238
  # One allocation, the source copied into the interior region, then the
211
239
  # margins filled per mode.
212
240
  def pad_source (src, lefts, rights, mode, value)
241
+ return pad_face(src, lefts, rights, mode, value) if src.face?
213
242
  nd = src.ndim
214
243
  shape = src.shape
215
244
  pshape = nd.times.map { |i| shape[i] + lefts[i] + rights[i] }
216
- pad = CArray.new(src.data_type, pshape)
245
+ pad = CArray.new(src.data_type, pshape, bytes: src.bytes)
217
246
 
218
247
  # Fill the whole buffer first, then overwrite the interior with the source.
219
248
  case mode
@@ -222,8 +251,8 @@ class CAWindowIterator < CAIterator
222
251
  when :masked
223
252
  pad[] = UNDEF
224
253
  when :edge
225
- # Provisional fill; the edge margins are written below from the source.
226
- pad[] = 0
254
+ # Nothing to fill: every margin cell is written from the source below
255
+ # (a provisional 0 cannot be stored in a fixlen buffer).
227
256
  end
228
257
 
229
258
  interior = nd.times.map { |i| lefts[i]...(lefts[i] + shape[i]) }
@@ -234,6 +263,23 @@ class CAWindowIterator < CAIterator
234
263
  pad
235
264
  end
236
265
 
266
+ # A Face is padded on its storage and put back on at the end, so the margins
267
+ # hold storage -- UNDEF, a copy of an edge cell, or a `:constant` value the
268
+ # Face has turned into storage -- and the reductions over the windows are
269
+ # the Face's own. A buffer of the Face's surface type could not take the
270
+ # margin values at all.
271
+ def pad_face (src, lefts, rights, mode, value)
272
+ storage = src.__send__(:__face_storage__)
273
+ if mode == :constant && !value.nil?
274
+ cell = src.__send__(:__blank_like__, [1])
275
+ cell[0] = value
276
+ padded = pad_source(storage, lefts, rights, :constant, cell.parent[0])
277
+ else
278
+ padded = pad_source(storage, lefts, rights, mode, value)
279
+ end
280
+ padded.face_lift(src)
281
+ end
282
+
237
283
  # Edge-replicate the margins of a padded buffer whose interior already holds
238
284
  # the source. Per axis, the left margin rows copy the first interior row and
239
285
  # the right margin rows copy the last; done axis by axis over the whole
@@ -356,19 +402,13 @@ class CAWindowIterator < CAIterator
356
402
  # @param fill_value [Object, nil] value to put in place of a masked
357
403
  # result instead of leaving it masked.
358
404
  # @return [CArray] reference-shaped (or shrunk, for :truncate)
359
- # @!method all(min_count: nil, fill_value: nil)
405
+ # @!method all
360
406
  # Whether every cell of each rolling is true.
361
- # @param min_count [Integer, nil] fewest cells that must be present
362
- # for a result; a piece with fewer comes back masked.
363
- # @param fill_value [Object, nil] value to put in place of a masked
364
- # result instead of leaving it masked.
407
+ # Takes no `min_count:` or `fill_value:`, as the core `all` does not.
365
408
  # @return [CArray] reference-shaped (or shrunk, for :truncate)
366
- # @!method any(min_count: nil, fill_value: nil)
409
+ # @!method any
367
410
  # Whether any cell of each rolling is true.
368
- # @param min_count [Integer, nil] fewest cells that must be present
369
- # for a result; a piece with fewer comes back masked.
370
- # @param fill_value [Object, nil] value to put in place of a masked
371
- # result instead of leaving it masked.
411
+ # Takes no `min_count:` or `fill_value:`, as the core `any` does not.
372
412
  # @return [CArray] reference-shaped (or shrunk, for :truncate)
373
413
  # @!method variancep(min_count: nil, fill_value: nil)
374
414
  # Rolling population variance (divisor `n`).
@@ -405,11 +445,11 @@ class CAWindowIterator < CAIterator
405
445
  # @param fill_value [Object, nil] value to put in place of a masked
406
446
  # result instead of leaving it masked.
407
447
  # @return [CArray] reference-shaped (or shrunk, for :truncate)
408
- [:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
448
+ [:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev,
409
449
  :variancep, :stddevp, :minmax, :min_index, :max_index].each do |op|
410
450
  class_eval <<~RUBY, __FILE__, __LINE__ + 1
411
451
  def #{op} (min_count: nil, fill_value: nil)
412
- folded = fold_by_offset(:#{op}, min_count, fill_value)
452
+ folded = @shape.include?(0) ? nil : fold_by_offset(:#{op}, min_count, fill_value)
413
453
  return folded unless folded.nil?
414
454
  kw = {}
415
455
  kw[:min_count] = min_count unless min_count.nil?
@@ -419,6 +459,19 @@ class CAWindowIterator < CAIterator
419
459
  RUBY
420
460
  end
421
461
 
462
+ # all / any take no min_count: or fill_value:, as the core ones do not.
463
+ [:all, :any].each do |op|
464
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
465
+ def #{op} (**kw)
466
+ if kw.empty?
467
+ folded = @shape.include?(0) ? nil : fold_by_offset(:#{op}, nil)
468
+ return folded unless folded.nil?
469
+ end
470
+ sliding_view.#{op}(axis: @window_axes, **CAIterator.reduce_keywords(:#{op}, kw))
471
+ end
472
+ RUBY
473
+ end
474
+
422
475
  # @overload min_addr
423
476
  # Rolling flat SOURCE address of the window minimum — which source cell holds
424
477
  # it, so `source.reshape(source.elements)[sw.min_addr]` are the window minima.
@@ -428,12 +481,12 @@ class CAWindowIterator < CAIterator
428
481
  # source cell it replicates, and with `bounds: :constant` (or `fill_value:`)
429
482
  # a winning margin cell has no source address and is a masked result.
430
483
  # @return [CArray] reference-shaped (or shrunk, for :truncate)
431
- def min_addr; window_winner_addr(:min_index); end
484
+ def min_addr (**kw); window_winner_addr(:min_index, kw); end
432
485
 
433
486
  # @overload max_addr
434
487
  # Rolling flat source address of the window maximum. See {#min_addr}.
435
488
  # @return [CArray] reference-shaped (or shrunk, for :truncate)
436
- def max_addr; window_winner_addr(:max_index); end
489
+ def max_addr (**kw); window_winner_addr(:max_index, kw); end
437
490
 
438
491
  private
439
492
 
@@ -470,16 +523,22 @@ class CAWindowIterator < CAIterator
470
523
  # Runs the fold by offset, or returns nil when this window is not one it can
471
524
  # answer for -- in which case the caller delegates as before.
472
525
  def fold_by_offset (op, min_count, fill_value = nil)
526
+ return nil unless CArray.__fast_paths_enabled__
527
+ need = CArray.__min_count_need__(op, min_count, 0)
473
528
  return nil unless op == :mean || OFFSET_FOLD.key?(op)
529
+ # The elementwise kernels act on a Face's storage; its reductions are its own.
530
+ return nil if @source.face?
474
531
  return nil if @widths.any? { |width| width > OFFSET_FOLD_MAX_WIDTH }
475
532
  return nil if neutral_value(op).nil?
533
+ # pmin / pmax let NaN lose, so a window holding only NaN would fold to the
534
+ # neutral value; the core answers NaN there.
535
+ return nil if (op == :min || op == :max) && source_has_nan?
476
536
 
477
537
  folded = op == :mean ? fold_mean : accumulate_offsets(op)
478
538
  folded = mask_empty_windows(folded) if EMPTY_WINDOW_IS_UNDEFINED.include?(op)
479
- folded = apply_min_count(folded, min_count)
539
+ folded = apply_min_count(folded, need)
480
540
  # A `fill_value:` on the call replaces a result that came out undefined.
481
- folded = folded.strip_mask(fill_value) if !fill_value.nil? && folded.has_mask?
482
- folded
541
+ CArray.__reduce_fill__(folded, fill_value)
483
542
  end
484
543
 
485
544
  # The mean is the sum over the same offsets, divided by the number of cells
@@ -540,14 +599,19 @@ class CAWindowIterator < CAIterator
540
599
  end
541
600
  end
542
601
 
543
- # `min_count` asks for a result only where the window held that many cells.
544
- def apply_min_count (folded, min_count)
545
- return folded if min_count.nil?
602
+ def source_has_nan?
603
+ return false unless @source.float? || @source.data_type == CA_OBJECT
604
+ @source.is_nan.any
605
+ end
606
+
607
+ # A result only where the window held `need` cells (CArray.__min_count_need__).
608
+ def apply_min_count (folded, need)
609
+ return folded if need == 0
546
610
  counts = window_cell_counts
547
611
  if counts.is_a?(Integer)
548
- folded[] = UNDEF if counts < min_count
612
+ folded[] = UNDEF if counts < need
549
613
  else
550
- folded[counts.lt(min_count)] = UNDEF
614
+ folded[counts.lt(need)] = UNDEF
551
615
  end
552
616
  folded
553
617
  end
@@ -644,7 +708,7 @@ class CAWindowIterator < CAIterator
644
708
  def offset_fold_data_type (op)
645
709
  @offset_fold_data_type ||= {}
646
710
  @offset_fold_data_type[op] ||=
647
- CArray.new(@source.data_type, [1, 1]).send(op, axis: [1]).data_type
711
+ CArray.new(@source.data_type, [1, 1], bytes: @source.bytes).send(op, axis: [1]).data_type
648
712
  end
649
713
 
650
714
  # Every offset within the window, as a list of per-axis positions into the
@@ -662,8 +726,9 @@ class CAWindowIterator < CAIterator
662
726
  # offset is the range's begin for a padded margin, 0 for :truncate). A margin
663
727
  # winner (coordinate out of bounds) resolves per the boundary policy:
664
728
  # :nearest clamps to the edge source cell, :constant / :skip mask the result.
665
- def window_winner_addr (idx_op)
666
- mi = send(idx_op) # window-local flat index
729
+ def window_winner_addr (idx_op, kw = {})
730
+ kw = CAIterator.reduce_keywords(idx_op, kw)
731
+ mi = send(idx_op, **kw.slice(:min_count)) # window-local flat index
667
732
  n = @source.shape
668
733
  wstride = Array.new(@sndim); acc = 1
669
734
  (@sndim - 1).downto(0) { |i| wstride[i] = acc; acc *= @widths[i] }
@@ -687,7 +752,7 @@ class CAWindowIterator < CAIterator
687
752
  end
688
753
  addr[oob] = UNDEF unless @bounds == :nearest
689
754
  addr[mi.is_masked.eq(1)] = UNDEF if mi.has_mask?
690
- addr
755
+ CArray.__reduce_fill__(addr, kw[:fill_value])
691
756
  end
692
757
 
693
758
  public
@@ -697,26 +762,29 @@ class CAWindowIterator < CAIterator
697
762
  # cells (the effective tap count, which drops near a :skip edge);
698
763
  # `count(UNDEF)` counts masked cells; `count(v)` counts cells equal to `v`.
699
764
  # @return [CArray]
700
- def count (*args)
701
- return count_not_masked if args.empty?
765
+ def count (*args, **kw)
766
+ return count_not_masked(**kw) if args.empty?
702
767
  # The sliding_windows view is a CAStride, so its #count is not shadowed;
703
768
  # dispatch CArray#count explicitly anyway, matching the family regularity.
704
- CArray.instance_method(:count).bind_call(sliding_view, *args, axis: @window_axes)
769
+ CArray.instance_method(:count).bind_call(sliding_view, *args, axis: @window_axes,
770
+ **CAIterator.reduce_keywords(:count, kw))
705
771
  end
706
772
 
707
773
  # @overload count_not_masked
708
774
  # Rolling count of present (non-masked) cells -- the denominator of a
709
775
  # renormalizing convolution.
710
776
  # @return [CArray]
711
- def count_not_masked
712
- sliding_view.count_not_masked(axis: @window_axes)
777
+ def count_not_masked (**kw)
778
+ sliding_view.count_not_masked(axis: @window_axes,
779
+ **CAIterator.reduce_keywords(:count_not_masked, kw))
713
780
  end
714
781
 
715
782
  # @overload count_masked
716
783
  # Rolling count of masked cells.
717
784
  # @return [CArray]
718
- def count_masked
719
- sliding_view.count_masked(axis: @window_axes)
785
+ def count_masked (**kw)
786
+ sliding_view.count_masked(axis: @window_axes,
787
+ **CAIterator.reduce_keywords(:count_masked, kw))
720
788
  end
721
789
 
722
790
  # @overload elements
@@ -788,40 +856,31 @@ class CAWindowIterator < CAIterator
788
856
 
789
857
  # ---- order statistics (median / percentile / quantile) ----------------
790
858
  #
791
- # Core per-axis order statistics take a single axis and do not accept a
792
- # masked input, so the window mode is dispatched:
793
- #
794
- # single window axis + unmasked margin -> `sliding_view.op(axis: window_axis)`
795
- # multi window axes + unmasked margin -> materialize the windows, flatten
796
- # the window axes into one, single-
797
- # axis core order-stat
798
- # :skip (UNDEF margin) -> raise (core has no masked per-axis
799
- # order-stat); guide to :nearest /
800
- # :truncate
801
- #
802
- # When core gains masked per-axis order statistics (a tracked refactor), the
803
- # :skip guard can be dropped and :skip served directly.
859
+ # Core per-axis order statistics take a single axis, so a single window
860
+ # axis goes straight to `sliding_view.op(axis: window_axis)`, and several
861
+ # are materialized with the window axes flattened into one. The UNDEF
862
+ # margin of :skip is left out of each window as any masked cell is.
804
863
 
805
- # @overload median
806
- # Rolling median. Requires an unmasked margin (`bounds: :nearest` or
807
- # `:truncate`); with the default `:skip` it raises.
864
+ # @overload median(min_count: nil, fill_value: nil)
865
+ # Rolling median.
808
866
  # @return [CArray]
809
- def median
810
- order_stat { |view, axis| view.median(axis: axis) }
867
+ def median (**kw)
868
+ kw = CAIterator.reduce_keywords(:median, kw)
869
+ order_stat { |view, axis| view.median(axis: axis, **kw) }
811
870
  end
812
871
 
813
- # @overload percentile(*pers)
872
+ # @overload percentile(*pers, min_count: nil, fill_value: nil)
814
873
  # Rolling percentile(s). One argument returns one CArray, several return
815
- # an array of CArrays (as `CArray#percentile`). Requires an unmasked
816
- # margin.
874
+ # an array of CArrays (as `CArray#percentile`).
817
875
  # @return [CArray, Array<CArray>]
818
- def percentile (*pers)
819
- order_stat { |view, axis| view.percentile(*pers, axis: axis) }
876
+ def percentile (*pers, **kw)
877
+ kw = CAIterator.reduce_keywords(:percentile, kw)
878
+ order_stat { |view, axis| view.percentile(*pers, axis: axis, **kw) }
820
879
  end
821
880
 
822
881
  # @overload quantile
823
882
  # Rolling five-number summary `[min, Q1, median, Q3, max]` (five CArrays),
824
- # as `CArray#quantile`. Requires an unmasked margin.
883
+ # as `CArray#quantile`.
825
884
  # @return [Array<CArray>]
826
885
  def quantile
827
886
  order_stat { |view, axis| view.quantile(axis: axis) }
@@ -830,14 +889,8 @@ class CAWindowIterator < CAIterator
830
889
  private
831
890
 
832
891
  # Drive an order statistic (yielded as `block.call(view, axis)`) through the
833
- # single-axis / multi-axis / :skip-reject dispatch above.
892
+ # single-axis / multi-axis dispatch above.
834
893
  def order_stat
835
- if @bounds == :skip
836
- raise ArgumentError,
837
- "windowed order statistics need an unmasked margin; " \
838
- "use bounds: :nearest (edge-extend) or bounds: :truncate (valid). " \
839
- "For an UNDEF-margin median use reduce { |w| w.median } (slower)."
840
- end
841
894
  sv = sliding_view
842
895
  if @window_axes.size == 1
843
896
  yield sv, @window_axes[0]
@@ -858,15 +911,17 @@ class CAWindowIterator < CAIterator
858
911
  # @overload wsum(weights)
859
912
  # Rolling weighted sum, `weights` shaped like a single window.
860
913
  # @return [CArray]
861
- def wsum (weights)
862
- weighted(weights) { |sv, w, axis| sv.wsum(w, axis: axis) }
914
+ def wsum (weights, **kw)
915
+ kw = CAIterator.reduce_keywords(:wsum, kw)
916
+ weighted(weights) { |sv, w, axis| sv.wsum(w, axis: axis, **kw) }
863
917
  end
864
918
 
865
919
  # @overload wmean(weights)
866
920
  # Rolling weighted mean, `weights` shaped like a single window.
867
921
  # @return [CArray]
868
- def wmean (weights)
869
- weighted(weights) { |sv, w, axis| sv.wmean(w, axis: axis) }
922
+ def wmean (weights, **kw)
923
+ kw = CAIterator.reduce_keywords(:wmean, kw)
924
+ weighted(weights) { |sv, w, axis| sv.wmean(w, axis: axis, **kw) }
870
925
  end
871
926
 
872
927
  private
data/lib/carray.rb CHANGED
@@ -24,7 +24,7 @@ require 'carray/runtime' # load-bearing Ruby support the core depends on
24
24
  require 'carray/basics' # frequently-used convenience methods kept eager
25
25
  require 'carray/conditional' # then_else / replace_where / conditional
26
26
  require 'carray/mask_gap_fill' # unmask/strip_mask method: keyword (hold / linear gap-fill)
27
- require 'carray/attribute' # per-instance metadata Hash (#attribute / #has_attribute?)
27
+ require 'carray/attribute' # per-instance metadata Hash (#attr / #set_attr / #attrs / #has_attr?)
28
28
  require 'carray/boolean_reduce' # all/any/none skip_masked: keyword (Kleene fold)
29
29
  require 'carray/meld_reduce' # CAMeld per-parent reduce fast path (sum/mean/min/max along meld_axis)
30
30
  # carray/ordering CIFY (2026-06-21): translated to ext/carray_order.c
@@ -37,7 +37,7 @@ require 'carray/complex' # real / imag accessors; MUST precede carray/lazy
37
37
  require 'carray/lazy' # the lazy elementwise view layer
38
38
  # carray/face.rb was deleted (the Phase 1 skeleton CArray::Face module
39
39
  # became dead weight, replaced by C-layer macro deploy +
40
- # copy_state/storage_to_scalar convention).
40
+ # copy_state/storage_to_element convention).
41
41
  require 'carray/time' # CATime / CATimedelta
42
42
  # carray/methods/* (bincount / broadcast / gather_nd+put_nd) are small
43
43
  # single-feature method files, loaded lazily via autoload_carray.
@@ -43,9 +43,9 @@ class CArray
43
43
  # `data_type`, shape, mask, and element data into `self`. Result
44
44
  # is an independent entity.
45
45
  #
46
- # Note that for views, `dup`/`clone` produce a new view sharing
47
- # the parent (Ruby shallow-copy semantics); use {#copy} when an
48
- # independent owned array is required.
46
+ # For views, `dup` / `clone` produce a new view sharing the parent
47
+ # (Ruby shallow-copy semantics), so they do not copy. Don't use
48
+ # them to copy a CArray; use {#copy}.
49
49
  # @param other [CArray]
50
50
  # @return [self]
51
51
  # @api private
@@ -66,7 +66,7 @@ class CArray
66
66
  # @return [Class]
67
67
  # @overload fixlen(*shape, bytes:) { ... }
68
68
  # Equivalent to `CArray.new(:fixlen, shape, bytes: bytes) { ... }`.
69
- # The `bytes:` keyword is required for `:fixlen`.
69
+ # The `bytes:` keyword is required for `:fixlen`, and is 1 or more.
70
70
  # @param shape [Array<Integer>]
71
71
  # @param bytes [Integer]
72
72
  # @return [CArray]
@@ -11,6 +11,11 @@
11
11
  # On big-endian hosts the bit axis of a non-fixlen parent reflects
12
12
  # network byte order (parent bytes are walked in reverse within each
13
13
  # element); single-byte parents are linear on either endian.
14
+ #
15
+ # A bit view has no mask. The bits of one byte would share that byte's
16
+ # one mask cell, so the view refuses a masked parent and refuses to be
17
+ # masked (`mask=`, storing `UNDEF`, storing a masked array). Read the
18
+ # bits of a masked array through `value.bitarray`.
14
19
  class CABitarray < CAView
15
20
  end
16
21
 
@@ -27,6 +32,8 @@ class CArray
27
32
  # @return [CABitarray]
28
33
  # @raise [CADataTypeError] when `self.data_type` is a complex or
29
34
  # object type (bit-level access is not defined for those).
35
+ # @raise [ArgumentError] when `self` has a mask; use
36
+ # `value.bitarray` to read the bits without it.
30
37
  def bitarray; end
31
38
 
32
39
  # Alias for {#bitarray}.
@@ -19,24 +19,35 @@ class CArray
19
19
  # of the returned array.
20
20
  #
21
21
  # `range` may be an integer (a single bit — the resulting view has
22
- # `data_type :boolean`) or a `Range` covering the bit positions.
23
- # The `data_type` of the view is chosen from the bit width:
24
- # 1 bit → `:boolean`, 2..8 → `:uint8`, 9..16 → `:uint16`,
25
- # 17..32 → `:uint32`, 33..64 → `:uint64`.
26
- #
27
- # `type` is accepted but currently ignored (the width-derived type
28
- # is always used).
22
+ # `data_type :boolean`) or a `Range` covering the bit positions. A
23
+ # negative bit or range end counts from the last bit of the cell.
24
+ # A field is read with one 8-byte load from the byte it starts in, so
25
+ # it may not reach more than 64 bits past the start of that byte.
26
+ # `type` is the integer type the field is read as. A signed type
27
+ # (`:int8` .. `:int64`) reads the field's top bit as its sign: the
28
+ # 3-bit field `0b101` is 5 as `:uint8` and -3 as `:int8`. A type
29
+ # wider than the field only widens the value. Without `type`, the
30
+ # narrowest unsigned type that holds the field is used: 1 bit →
31
+ # `:boolean`, 2..8 → `:uint8`, 9..16 → `:uint16`, 17..32 → `:uint32`,
32
+ # 33..64 → `:uint64`. A write keeps the low bits of the value that
33
+ # fit the field, signed or not (-3 into a 3-bit field is `0b101`).
29
34
  #
30
35
  # @overload bitfield(range, type = nil)
31
36
  # @param range [Integer, Range] bit position (single bit) or bit
32
37
  # range within one parent cell.
33
- # @param type [Symbol, nil] reserved, currently ignored.
38
+ # @param type [Symbol, Integer, nil] the integer type the field is
39
+ # read as, or `nil` for the narrowest unsigned type.
34
40
  # @return [CABitfield]
35
41
  # @raise [IndexError] when `range` extends past the parent's bit
36
- # width, when the range has a step != 1, or when the bit length
37
- # is outside `1..64`.
38
- # @raise [ArgumentError] when the derived bit length exceeds the
39
- # resolved data type.
42
+ # width, when the range has a step != 1, when the bit length
43
+ # is outside `1..64`, or when the field reaches more than 64 bits
44
+ # past the start of its first byte.
45
+ # @raise [CArray::DataTypeError] when `self` is an object or complex
46
+ # array.
47
+ # @raise [ArgumentError] when `type` is narrower than the field
48
+ # (or `:boolean` for a field of more than one bit).
49
+ # @raise [CArray::DataTypeError] when `type` is not an integer type
50
+ # or `:boolean`.
40
51
  def bitfield(range, type = nil); end
41
52
 
42
53
  # @!endgroup
@@ -22,7 +22,7 @@ class CArray
22
22
  # reinterpret the storage on purpose. A Numeric Face, whose surface *is*
23
23
  # its storage, is unaffected.
24
24
  # @param data_type [Symbol, Integer, Class, String]
25
- # @param bytes [Integer] element byte size for `:fixlen`.
25
+ # @param bytes [Integer] element byte size for `:fixlen` (1 or more).
26
26
  # @return [CAFake]
27
27
  # @raise [TypeError] when `self` is a Face and the request would read its
28
28
  # storage under another type.
@@ -18,7 +18,7 @@ class CArray
18
18
  # @param bytes [Integer, nil] element size; required for `:fixlen`,
19
19
  # inferred otherwise.
20
20
  # @return [CAField]
21
- # @raise [RuntimeError] when `offset` is negative or the
21
+ # @raise [ArgumentError] when `offset` is negative or the
22
22
  # `offset + bytes` window falls outside one parent record.
23
23
  # @overload field(offset, template)
24
24
  # Returns a CARefer over a {CAField}: takes `template.elements *
@@ -32,10 +32,10 @@ class CArray
32
32
  # defaults to `self.bytes`.
33
33
  # @param offset [Integer] offset in parent elements.
34
34
  # @return [CARefer, CARecord]
35
- # @raise [RuntimeError] when the byte widths do not divide
35
+ # @raise [ArgumentError] when the byte widths do not divide
36
36
  # evenly, when `offset` is negative, or when the requested
37
37
  # view extends past the parent's data.
38
- # @raise [RuntimeError] when reinterpreting a CA_OBJECT parent
38
+ # @raise [CArray::DataTypeError] when reinterpreting a CA_OBJECT parent
39
39
  # with a non-object `data_type`.
40
40
  def refer(*argv); end
41
41
 
@@ -54,7 +54,7 @@ class CArray
54
54
  # @return [CArray] the reshape view.
55
55
  # @raise [ArgumentError] when the number of dims exceeds
56
56
  # `CA_RANK_MAX`.
57
- # @raise [RuntimeError] when the product does not equal
57
+ # @raise [ArgumentError] when the product does not equal
58
58
  # `self.elements` (with no placeholder), when the placeholder
59
59
  # cannot be inferred, when more than one placeholder is
60
60
  # given, or when a `nil` has no matching source axis.
@@ -38,7 +38,6 @@ class CArray
38
38
  # shorter than `ndim` (missing axes default to `0`).
39
39
  # @return [CARoll]
40
40
  # @raise [ArgumentError] when more than `ndim` shifts are given.
41
- # @raise [IndexError] when any parent dimension is non-positive.
42
41
  def roll(*shifts); end
43
42
 
44
43
  # @!endgroup
@@ -20,7 +20,8 @@ class CArray
20
20
 
21
21
  # Returns a {CAStride} view of `self` with the given byte strides and
22
22
  # starting byte offset, inheriting the receiver's data type and element
23
- # size. Memory is shared with the receiver.
23
+ # size. Memory is shared with the receiver, and a Face receiver stays the
24
+ # Face.
24
25
  #
25
26
  # This is a low-level escape hatch: the strides and offset are **not**
26
27
  # bounds-checked against the receiver's memory, so a combination that
@@ -46,7 +47,8 @@ class CArray
46
47
  #
47
48
  # Truncate mode: a trailing partial window is dropped rather than padded.
48
49
  # Memory is shared with the parent, and because windows overlap, one parent
49
- # cell is visible from several positions of the view.
50
+ # cell is visible from several positions of the view. A Face parent stays
51
+ # the Face.
50
52
  #
51
53
  # Reduce over the trailing `ndim` axes for a rolling statistic.
52
54
  #
@@ -70,7 +72,7 @@ class CArray
70
72
  # The window axes are inserted before the trailing axes, so the result rank
71
73
  # is `ndim + S`. With `S == ndim` there are no trailing axes and the
72
74
  # result is identical to {#sliding_windows}. Truncate mode; memory is
73
- # shared with the parent.
75
+ # shared with the parent, and a Face parent stays the Face.
74
76
  #
75
77
  # @overload unfold(window, step: nil)
76
78
  # @overload unfold(w0, w1, ..., step: nil)
@@ -93,13 +95,14 @@ class CArray
93
95
  # tile size: nothing is truncated and no cell is aliased twice. Reduce over
94
96
  # the trailing `ndim` axes (e.g. `v.mean(-1, -2)` for a 2-D parent) for
95
97
  # per-tile statistics such as pooling or block-wise aggregation. Memory is
96
- # shared with the parent.
98
+ # shared with the parent, and a Face parent stays the Face.
97
99
  #
98
100
  # @overload block_view(block)
99
101
  # @overload block_view(b0, b1, ...)
100
102
  # @param block [Array<Integer>, Integer] tile length per axis, one per
101
103
  # dimension, as an Array or as variadic arguments.
102
- # @return [CAStride] of rank `2 * ndim`.
104
+ # @return [CAStride] of rank `2 * ndim` (the parent's Face class when
105
+ # the parent is a Face).
103
106
  # @raise [ArgumentError] when the tile count does not equal `ndim`, when
104
107
  # a tile length is not positive, when an axis is not divisible by its
105
108
  # tile length, or when `2 * ndim` exceeds `CA_RANK_MAX`.
@@ -148,8 +151,8 @@ class CArray
148
151
  # @overload flip(axis)
149
152
  # @overload flip(a0, a1, ...)
150
153
  # @overload flip([a0, a1, ...])
151
- # @param axis [Array<Integer>, Integer] axes to reverse; negative indices
152
- # count from the last axis.
154
+ # @param axis [Array<Integer>, Integer, nil] axes to reverse; negative
155
+ # indices count from the last axis. `flip(nil)` is the same as `flip`.
153
156
  # @return [CAStride]
154
157
  # @raise [ArgumentError] when an axis is out of range or repeated.
155
158
  def flip(*axis); end
@@ -29,7 +29,7 @@ class CArray
29
29
  # @param imap [Array<Integer>] permutation of `0 ... ndim`.
30
30
  # @return [CATranspose]
31
31
  # @raise [ArgumentError] when `imap.length != ndim`.
32
- # @raise [RuntimeError] when an entry of `imap` is out of range
32
+ # @raise [ArgumentError] when an entry of `imap` is out of range
33
33
  # or duplicated.
34
34
  def transpose(*imap); end
35
35
 
@@ -48,8 +48,7 @@ class CArray
48
48
  # is passed (the block form was removed in 3.0), or when `bounds` is
49
49
  # `"periodic"` / `"reflect"` (both removed in 3.0; use {#roll} for a
50
50
  # cyclic shift).
51
- # @raise [IndexError] when a range selects zero cells.
52
- # @raise [RuntimeError] when `bounds` conflicts with `fill_value: UNDEF`,
51
+ # @raise [ArgumentError] when `bounds` conflicts with `fill_value: UNDEF`,
53
52
  # or when `bounds` is not a recognised value.
54
53
  def window(*ranges, fill_value: 0, bounds: "fill"); end
55
54
 
@@ -82,9 +82,10 @@ class CArray
82
82
  # Array of length `self.ndim`, so `i, j = ca.addr2index(x)` unpacks
83
83
  # uniformly for scalar and vector inputs.
84
84
  # @param addr [Integer, CArray] a flat address in `0...elements`,
85
- # or a CArray of such addresses (arbitrary shape).
85
+ # or a CArray of such addresses (arbitrary shape). A negative
86
+ # address counts from the end.
86
87
  # @return [Array<Integer>, Array<CArray>] one entry per axis.
87
- # @raise [ArgumentError] when any `addr` is out of range.
88
+ # @raise [IndexError] when any `addr` is out of range.
88
89
  def addr2index(addr); end
89
90
 
90
91
  # @overload index2addr(*index)
@@ -106,8 +107,8 @@ class CArray
106
107
  # @param addr [Integer, CArray] a flat address or a CArray of them.
107
108
  # @param shape [Array<Integer>] the row-major shape defining the grid.
108
109
  # @return [Array<Integer>, Array<CArray>] one entry per axis.
109
- # @raise [ArgumentError] when any `addr` is out of range or `shape:`
110
- # is missing.
110
+ # @raise [IndexError] when any `addr` is out of range.
111
+ # @raise [ArgumentError] when `shape:` is missing.
111
112
  def self.addr2index(addr, shape:); end
112
113
 
113
114
  # @overload index2addr(*index, shape:)
@@ -23,7 +23,7 @@ class CArray
23
23
  # @param shape [Array<Integer>] target shape, with
24
24
  # `length >= self.ndim`.
25
25
  # @return [CArray]
26
- # @raise [RuntimeError] if a source axis cannot be paired with a
26
+ # @raise [ArgumentError] if a source axis cannot be paired with a
27
27
  # target axis (cross-ndim expansion is intentionally strict —
28
28
  # see {CArray.broadcast} for the explicit-`:_` axis
29
29
  # declaration form).