carray 3.0.2 → 3.0.4

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 (247) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +568 -0
  3. data/README.md +6 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +220 -0
  7. data/ext/ca_axis_group.c +201 -123
  8. data/ext/ca_binop_dispatch.c +46 -52
  9. data/ext/ca_binop_dispatch.h +1 -1
  10. data/ext/ca_categorical_iterator.c +249 -183
  11. data/ext/ca_compare.c +7 -12
  12. data/ext/ca_for_buffer.h +21 -68
  13. data/ext/ca_group_iter.c +38 -23
  14. data/ext/ca_kernel_iterator.c +1765 -2076
  15. data/ext/ca_kernel_iterator.h +584 -566
  16. data/ext/ca_monop_dispatch.c +86 -63
  17. data/ext/ca_monop_dispatch.h +5 -0
  18. data/ext/ca_obj_array.c +223 -56
  19. data/ext/ca_obj_bincmp.c +172 -138
  20. data/ext/ca_obj_binop.c +116 -230
  21. data/ext/ca_obj_bitarray.c +40 -26
  22. data/ext/ca_obj_bitfield.c +100 -68
  23. data/ext/ca_obj_block.c +19 -9
  24. data/ext/ca_obj_byte_swap.c +45 -46
  25. data/ext/ca_obj_const_string.c +269 -44
  26. data/ext/ca_obj_face.c +612 -89
  27. data/ext/ca_obj_face.h +111 -47
  28. data/ext/ca_obj_fake.c +36 -79
  29. data/ext/ca_obj_farray.c +7 -3
  30. data/ext/ca_obj_field.c +33 -8
  31. data/ext/ca_obj_fixlen_string.c +23 -24
  32. data/ext/ca_obj_grid.c +541 -210
  33. data/ext/ca_obj_meld.c +331 -111
  34. data/ext/ca_obj_moncmp.c +80 -65
  35. data/ext/ca_obj_monop.c +177 -149
  36. data/ext/ca_obj_object.c +664 -248
  37. data/ext/ca_obj_record.c +63 -23
  38. data/ext/ca_obj_reduce.c +18 -16
  39. data/ext/ca_obj_refer.c +140 -58
  40. data/ext/ca_obj_remap.c +85 -24
  41. data/ext/ca_obj_repeat.c +24 -9
  42. data/ext/ca_obj_roll.c +121 -16
  43. data/ext/ca_obj_select.c +61 -58
  44. data/ext/ca_obj_select_axis.c +73 -107
  45. data/ext/ca_obj_shift.c +32 -102
  46. data/ext/ca_obj_stack.c +372 -122
  47. data/ext/ca_obj_stride.c +348 -140
  48. data/ext/ca_obj_string.c +88 -22
  49. data/ext/ca_obj_tile.c +38 -21
  50. data/ext/ca_obj_time.c +45 -31
  51. data/ext/ca_obj_timedelta.c +43 -29
  52. data/ext/ca_obj_transpose.c +17 -6
  53. data/ext/ca_obj_triop.c +125 -141
  54. data/ext/ca_obj_window.c +330 -44
  55. data/ext/ca_op_ipower.c +24 -11
  56. data/ext/ca_op_powi.h +57 -30
  57. data/ext/ca_pow5_table.h +656 -0
  58. data/ext/ca_sort_kernels.h +70 -0
  59. data/ext/ca_sweep_engine.c +104 -81
  60. data/ext/ca_sweep_engine.h +7 -20
  61. data/ext/ca_triop_dispatch.c +4 -1
  62. data/ext/ca_triop_dispatch.h +5 -2
  63. data/ext/caframe_csv_reader.c +542 -0
  64. data/ext/carray.h +241 -92
  65. data/ext/carray_access.c +608 -276
  66. data/ext/carray_address_basis.c +494 -97
  67. data/ext/carray_attribute.c +129 -4
  68. data/ext/carray_bincount.c +39 -7
  69. data/ext/carray_broadcast.c +56 -19
  70. data/ext/carray_build_flags.h +1 -1
  71. data/ext/carray_call_cfunc.c +108 -28
  72. data/ext/carray_cast.c +1576 -223
  73. data/ext/carray_cast_func.rb +77 -24
  74. data/ext/carray_class.c +4 -3
  75. data/ext/carray_conversion.c +208 -75
  76. data/ext/carray_copy.c +82 -24
  77. data/ext/carray_core.c +646 -162
  78. data/ext/carray_count.c +171 -88
  79. data/ext/carray_element.c +94 -60
  80. data/ext/carray_factorize.c +670 -324
  81. data/ext/carray_generate.c +260 -83
  82. data/ext/carray_histogram.c +209 -106
  83. data/ext/carray_hold.c +14 -16
  84. data/ext/carray_index_classifier.c +422 -243
  85. data/ext/carray_internal.h +692 -3
  86. data/ext/carray_kernels_bincmp.c +7 -0
  87. data/ext/carray_kernels_binop.c +487 -120
  88. data/ext/carray_kernels_init.c +653 -50
  89. data/ext/carray_kernels_moncmp.c +15 -8
  90. data/ext/carray_kernels_monop.c +99 -43
  91. data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
  92. data/ext/carray_kernels_reduce_boolean.c +112 -36
  93. data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
  94. data/ext/carray_kernels_reduce_extreme.c +3032 -2892
  95. data/ext/carray_kernels_reduce_variance.c +439 -500
  96. data/ext/carray_kernels_scan.c +1097 -488
  97. data/ext/carray_kernels_search.c +3787 -2176
  98. data/ext/carray_kernels_sort.c +1200 -920
  99. data/ext/carray_kernels_triop.c +479 -7
  100. data/ext/carray_key_windows.c +216 -0
  101. data/ext/carray_lazy.c +967 -153
  102. data/ext/carray_loop.c +81 -65
  103. data/ext/carray_mask.c +910 -324
  104. data/ext/carray_math_kernel.h +1 -0
  105. data/ext/carray_median_percentile.c +299 -100
  106. data/ext/carray_memory_view.c +872 -758
  107. data/ext/carray_operator.c +1171 -364
  108. data/ext/carray_order.c +505 -133
  109. data/ext/carray_partition.c +112 -65
  110. data/ext/carray_random.c +327 -239
  111. data/ext/carray_round.c +273 -0
  112. data/ext/carray_scatter.c +346 -78
  113. data/ext/carray_segment.c +257 -0
  114. data/ext/carray_slab.c +249 -86
  115. data/ext/carray_sort.c +224 -227
  116. data/ext/carray_sort_kernel.c +8 -1
  117. data/ext/carray_struct.c +118 -39
  118. data/ext/carray_test.c +193 -69
  119. data/ext/carray_time_text.c +1182 -0
  120. data/ext/carray_undef.c +4 -0
  121. data/ext/carray_utils.c +579 -113
  122. data/ext/extconf.rb +27 -2
  123. data/ext/mk_call_cfunc.rb +19 -4
  124. data/ext/mk_pow5_table.rb +35 -0
  125. data/ext/mkkernel.rb +1202 -1014
  126. data/ext/ruby_carray.c +17 -5
  127. data/ext/version.h +4 -4
  128. data/ext/version.rb +4 -2
  129. data/lib/carray/attribute.rb +160 -17
  130. data/lib/carray/autoload_carray.rb +23 -27
  131. data/lib/carray/autoload_method_extension.rb +20 -5
  132. data/lib/carray/axis_group.rb +360 -63
  133. data/lib/carray/basics.rb +162 -28
  134. data/lib/carray/bincount_nd.rb +18 -2
  135. data/lib/carray/block_iterator.rb +231 -58
  136. data/lib/carray/boolean_reduce.rb +34 -42
  137. data/lib/carray/categorical.rb +242 -52
  138. data/lib/carray/categorical_iterator.rb +437 -827
  139. data/lib/carray/complex.rb +41 -34
  140. data/lib/carray/conditional.rb +150 -14
  141. data/lib/carray/const_string.rb +59 -4
  142. data/lib/carray/construct.rb +15 -11
  143. data/lib/carray/data_type_extension.rb +1 -0
  144. data/lib/carray/distance_limit.rb +77 -0
  145. data/lib/carray/fixlen_string.rb +7 -1
  146. data/lib/carray/frame/column_verbs.rb +70 -0
  147. data/lib/carray/frame/concat.rb +157 -6
  148. data/lib/carray/frame/convert.rb +82 -7
  149. data/lib/carray/frame/csv_parser.rb +688 -87
  150. data/lib/carray/frame/describe.rb +80 -0
  151. data/lib/carray/frame/frame.rb +369 -45
  152. data/lib/carray/frame/group.rb +631 -90
  153. data/lib/carray/frame/io.rb +506 -79
  154. data/lib/carray/frame/join.rb +411 -62
  155. data/lib/carray/frame/records.rb +116 -19
  156. data/lib/carray/frame/reshape.rb +455 -0
  157. data/lib/carray/frame/sort.rb +30 -18
  158. data/lib/carray/frame/verbs.rb +868 -61
  159. data/lib/carray/frame/windows.rb +117 -0
  160. data/lib/carray/frame.rb +4 -0
  161. data/lib/carray/fuse_source.rb +118 -2
  162. data/lib/carray/fusion.rb +257 -42
  163. data/lib/carray/histogram.rb +6 -1
  164. data/lib/carray/inspect.rb +54 -13
  165. data/lib/carray/iterator.rb +99 -9
  166. data/lib/carray/key_windows.rb +620 -0
  167. data/lib/carray/lazy.rb +216 -193
  168. data/lib/carray/mask_gap_fill.rb +112 -92
  169. data/lib/carray/math.rb +32 -19
  170. data/lib/carray/meld_reduce.rb +52 -21
  171. data/lib/carray/methods/align_addr.rb +8 -4
  172. data/lib/carray/methods/bin.rb +26 -18
  173. data/lib/carray/methods/bincount.rb +18 -2
  174. data/lib/carray/methods/bit_string.rb +15 -4
  175. data/lib/carray/methods/choose.rb +16 -3
  176. data/lib/carray/methods/composition.rb +18 -10
  177. data/lib/carray/methods/factorize.rb +2 -2
  178. data/lib/carray/methods/gather_nd.rb +25 -5
  179. data/lib/carray/methods/insert_block.rb +9 -14
  180. data/lib/carray/methods/is_between.rb +59 -0
  181. data/lib/carray/methods/is_in.rb +140 -10
  182. data/lib/carray/methods/join.rb +2 -7
  183. data/lib/carray/methods/lag.rb +49 -0
  184. data/lib/carray/methods/locate_addr.rb +182 -45
  185. data/lib/carray/methods/mode.rb +18 -1
  186. data/lib/carray/methods/nunique.rb +11 -6
  187. data/lib/carray/methods/repeat.rb +1 -20
  188. data/lib/carray/methods/resize.rb +21 -23
  189. data/lib/carray/methods/snap.rb +34 -12
  190. data/lib/carray/methods/unique.rb +11 -2
  191. data/lib/carray/methods/value_counts.rb +4 -11
  192. data/lib/carray/record_order.rb +208 -0
  193. data/lib/carray/rng.rb +3 -0
  194. data/lib/carray/runtime.rb +65 -17
  195. data/lib/carray/segment_iterator.rb +1221 -0
  196. data/lib/carray/serialize.rb +214 -30
  197. data/lib/carray/slab_iterator.rb +101 -27
  198. data/lib/carray/stack.rb +10 -4
  199. data/lib/carray/string.rb +105 -5
  200. data/lib/carray/string_operation_extension.rb +12 -4
  201. data/lib/carray/struct.rb +137 -58
  202. data/lib/carray/struct_builder.rb +124 -17
  203. data/lib/carray/time.rb +1222 -186
  204. data/lib/carray/window_iterator.rb +242 -81
  205. data/lib/carray.rb +9 -2
  206. data/yard-stubs/ca_obj_array.rb +4 -4
  207. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  208. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  209. data/yard-stubs/ca_obj_fake.rb +1 -1
  210. data/yard-stubs/ca_obj_field.rb +5 -1
  211. data/yard-stubs/ca_obj_grid.rb +21 -10
  212. data/yard-stubs/ca_obj_refer.rb +5 -4
  213. data/yard-stubs/ca_obj_roll.rb +2 -3
  214. data/yard-stubs/ca_obj_shift.rb +5 -1
  215. data/yard-stubs/ca_obj_stride.rb +10 -7
  216. data/yard-stubs/ca_obj_tile.rb +1 -1
  217. data/yard-stubs/ca_obj_transpose.rb +1 -1
  218. data/yard-stubs/ca_obj_window.rb +9 -5
  219. data/yard-stubs/carray_access.rb +11 -5
  220. data/yard-stubs/carray_attribute.rb +0 -5
  221. data/yard-stubs/carray_broadcast.rb +1 -1
  222. data/yard-stubs/carray_cast.rb +88 -43
  223. data/yard-stubs/carray_conversion.rb +15 -2
  224. data/yard-stubs/carray_copy.rb +12 -5
  225. data/yard-stubs/carray_core.rb +3 -3
  226. data/yard-stubs/carray_count.rb +9 -0
  227. data/yard-stubs/carray_element.rb +10 -4
  228. data/yard-stubs/carray_generate.rb +21 -1
  229. data/yard-stubs/carray_lazy.rb +5 -1
  230. data/yard-stubs/carray_loop.rb +4 -1
  231. data/yard-stubs/carray_mask.rb +60 -12
  232. data/yard-stubs/carray_math.rb +52 -14
  233. data/yard-stubs/carray_median_percentile.rb +15 -8
  234. data/yard-stubs/carray_memory_view.rb +40 -23
  235. data/yard-stubs/carray_order.rb +61 -29
  236. data/yard-stubs/carray_random.rb +13 -4
  237. data/yard-stubs/carray_scatter.rb +21 -9
  238. data/yard-stubs/carray_segment.rb +72 -0
  239. data/yard-stubs/carray_slab.rb +10 -2
  240. data/yard-stubs/carray_sort.rb +49 -8
  241. data/yard-stubs/carray_test.rb +15 -3
  242. data/yard-stubs/carray_utils.rb +0 -26
  243. metadata +19 -5
  244. data/ext/ca_for_each_element.h +0 -239
  245. data/ext/carray_kernels_map.c +0 -3467
  246. data/lib/carray/arrow_tensor.rb +0 -401
  247. data/lib/carray/table.rb +0 -108
@@ -167,19 +167,44 @@ class CABlockIterator < CAIterator
167
167
  # below a full tile, so `min_count:` marks them UNDEF with no masking. The
168
168
  # output data type is seeded from the first region (all regions share it).
169
169
  def assemble
170
- outs = nil
170
+ grids = nil
171
+ faces = nil
172
+ single = true
171
173
  each_region do |strip_ranges, tiles, out_ranges|
172
174
  view = @source[*strip_ranges].block_view(*tiles)
173
175
  res = yield(view, tiles)
174
- if res.is_a?(Array)
175
- outs ||= res.map { |r| CArray.new(r.data_type, @shape) }
176
- res.each_index { |k| outs[k][*out_ranges] = res[k] }
177
- else
178
- outs ||= CArray.new(res.data_type, @shape)
179
- outs[*out_ranges] = res
180
- end
176
+ single = false if res.is_a?(Array)
177
+ res = [res] unless res.is_a?(Array)
178
+ faces ||= res.map { |r| r.face? ? r : nil }
179
+ parts = res.map { |r| r.face? ? face_grid_part(r) : r }
180
+ grids ||= parts.map { |r| CArray.new(r.data_type, @shape, bytes: r.bytes) }
181
+ parts.each_index { |k| grids[k][*out_ranges] = parts[k] }
181
182
  end
182
- outs
183
+ unless grids
184
+ # An empty source has no region. A view with unit tiles has no cells
185
+ # either; reducing it gives each output's type for the empty grid.
186
+ res = yield(@source.block_view(*[1] * @sndim), [1] * @sndim)
187
+ single = !res.is_a?(Array)
188
+ res = [res] unless res.is_a?(Array)
189
+ faces = res.map { |r| r.face? ? r : nil }
190
+ grids = res.map { |r| r = face_grid_part(r) if r.face?; CArray.new(r.data_type, @shape, bytes: r.bytes) }
191
+ end
192
+ outs = grids.each_index.map { |k| faces[k] ? face_grid(grids[k], faces[k]) : grids[k] }
193
+ single ? outs[0] : outs
194
+ end
195
+
196
+ # A region result that is a Face (a CATime mean, a CATimedelta stddev) is
197
+ # assembled on its storage and the Face is put back on the whole grid;
198
+ # assembling the Face itself would copy its surface bytes into an array that
199
+ # no longer knows it is one. A CAConstString's storage points into its own
200
+ # buffer, which the regions do not share, so it is assembled as strings.
201
+ def face_grid_part (r)
202
+ r.is_a?(CAConstString) ? r.to_string.parent : r.parent
203
+ end
204
+
205
+ def face_grid (grid, face)
206
+ return CArray.const_string(grid, encoding: face.encoding) if face.is_a?(CAConstString)
207
+ grid.face_lift(face)
183
208
  end
184
209
 
185
210
  # Fold every region with a single core reduction `op` (the tier-1 shape).
@@ -255,19 +280,13 @@ class CABlockIterator < CAIterator
255
280
  # @param fill_value [Object, nil] value to put in place of a masked
256
281
  # result instead of leaving it masked.
257
282
  # @return [CArray] tile-grid shaped
258
- # @!method all(min_count: nil, fill_value: nil)
283
+ # @!method all
259
284
  # Whether every cell of each tile is true.
260
- # @param min_count [Integer, nil] fewest cells that must be present
261
- # for a result; a piece with fewer comes back masked.
262
- # @param fill_value [Object, nil] value to put in place of a masked
263
- # result instead of leaving it masked.
285
+ # Takes no `min_count:` or `fill_value:`, as the core `all` does not.
264
286
  # @return [CArray] tile-grid shaped
265
- # @!method any(min_count: nil, fill_value: nil)
287
+ # @!method any
266
288
  # Whether any cell of each tile is true.
267
- # @param min_count [Integer, nil] fewest cells that must be present
268
- # for a result; a piece with fewer comes back masked.
269
- # @param fill_value [Object, nil] value to put in place of a masked
270
- # result instead of leaving it masked.
289
+ # Takes no `min_count:` or `fill_value:`, as the core `any` does not.
271
290
  # @return [CArray] tile-grid shaped
272
291
  # @!method variancep(min_count: nil, fill_value: nil)
273
292
  # Per-tile population variance (divisor `n`).
@@ -283,16 +302,49 @@ class CABlockIterator < CAIterator
283
302
  # @param fill_value [Object, nil] value to put in place of a masked
284
303
  # result instead of leaving it masked.
285
304
  # @return [CArray] tile-grid shaped
286
- [:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
287
- :variancep, :stddevp].each do |op|
288
- define_method(op) do |min_count: nil, fill_value: nil|
289
- kw = {}
290
- kw[:min_count] = min_count unless min_count.nil?
291
- kw[:fill_value] = fill_value unless fill_value.nil?
292
- fold(op, **kw)
305
+ CArray.send(:define_named_methods, self,
306
+ [:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev,
307
+ :variancep, :stddevp], :fold_reduction)
308
+
309
+ private def fold_reduction (op, min_count: nil, fill_value: nil)
310
+ if FACE_WINNER_INDEX.key?(op) && @source.face?
311
+ return face_winner(FACE_WINNER_INDEX[op], min_count, fill_value)
293
312
  end
313
+ kw = {}
314
+ kw[:min_count] = min_count unless min_count.nil?
315
+ kw[:fill_value] = fill_value unless fill_value.nil?
316
+ fold(op, **kw)
317
+ end
318
+
319
+ CArray.send(:define_named_methods, self, [:all, :any], :fold_with_keywords)
320
+
321
+ private def fold_with_keywords (op, **kw)
322
+ fold(op, **CAIterator.reduce_keywords(op, kw))
294
323
  end
295
324
 
325
+ # @!method first
326
+ # Per-tile first present value, in row-major order within the tile;
327
+ # UNDEF for a tile with none.
328
+ # @return [CArray] tile-grid shaped
329
+ # @!method last
330
+ # Per-tile last present value; UNDEF for a tile with none.
331
+ # @return [CArray] tile-grid shaped
332
+ # @!method nunique
333
+ # Per-tile number of distinct present values (int64); 0 for a tile with
334
+ # none.
335
+ # @return [CArray] tile-grid shaped
336
+ #
337
+ # These take no `min_count:` or `fill_value:`, as the core ones do not.
338
+ CArray.send(:define_named_methods, self, [:first, :last, :nunique], :fold_with_keywords)
339
+
340
+ # A Face's minimum and maximum are cells of the Face, so they are gathered
341
+ # from the source by the winner's address. Assembling the per-region results
342
+ # instead copies the Face's storage into an array that no longer knows it is
343
+ # one: a CATime came back as 8-byte strings.
344
+ FACE_WINNER_INDEX = { min: :min_index, max: :max_index }.freeze
345
+ Ractor.make_shareable(FACE_WINNER_INDEX)
346
+ private_constant :FACE_WINNER_INDEX
347
+
296
348
  # ---- count family and elements ----------------------------------------
297
349
 
298
350
  # @overload count(v = <none>)
@@ -300,15 +352,16 @@ class CABlockIterator < CAIterator
300
352
  # partial edge tile); `count(UNDEF)` counts masked cells; `count(v)` counts
301
353
  # cells equal to `v`.
302
354
  # @return [CArray] tile-grid shaped
303
- def count (*args)
304
- return count_not_masked if args.empty?
355
+ def count (*args, **kw)
356
+ return count_not_masked(**kw) if args.empty?
357
+ kw = CAIterator.reduce_keywords(:count, kw)
305
358
  out = nil
306
359
  each_region do |strip_ranges, tiles, out_ranges|
307
360
  view = @source[*strip_ranges].block_view(*tiles)
308
361
  # block_view is a CAStride, so #count is not shadowed; dispatch
309
362
  # CArray#count explicitly anyway, matching the family regularity.
310
- red = CArray.instance_method(:count).bind_call(view, *args, axis: @tile_axes)
311
- out ||= CArray.new(red.data_type, @shape)
363
+ red = CArray.instance_method(:count).bind_call(view, *args, axis: @tile_axes, **kw)
364
+ out ||= CArray.new(red.data_type, @shape, bytes: red.bytes)
312
365
  out[*out_ranges] = red
313
366
  end
314
367
  out
@@ -318,16 +371,21 @@ class CABlockIterator < CAIterator
318
371
  # Per-tile count of present (non-masked) cells. For a partial edge tile
319
372
  # this is the real cell count (the OOB cells are not present).
320
373
  # @return [CArray]
321
- def count_not_masked
322
- fold(:count_not_masked)
374
+ def count_not_masked (**kw)
375
+ fold(:count_not_masked, **CAIterator.reduce_keywords(:count_not_masked, kw))
323
376
  end
324
377
 
325
378
  # @overload count_masked
326
379
  # Per-tile count of masked cells. Equals `elements - count_not_masked`;
327
380
  # the OOB cells of a partial edge tile count here.
328
381
  # @return [CArray]
329
- def count_masked
330
- elements - count_not_masked
382
+ def count_masked (**kw)
383
+ kw = CAIterator.reduce_keywords(:count_masked, kw)
384
+ need = CArray.__present_cells_needed_per_result_cell__(:count_masked, kw[:min_count], 0)
385
+ out = elements - count_not_masked
386
+ # A count of the mask reads every cell of the tile.
387
+ out[elements.lt(need)] = UNDEF if need > 0
388
+ CArray.__fill_undefined_result_cells__(out, kw[:fill_value])
331
389
  end
332
390
 
333
391
  # @overload elements
@@ -349,6 +407,10 @@ class CABlockIterator < CAIterator
349
407
  # Per-tile `[min, max]` (two tile-grid-shaped CArrays, a single fused pass).
350
408
  # @return [Array<CArray>]
351
409
  def minmax (min_count: nil, fill_value: nil)
410
+ if @source.face?
411
+ return [min(min_count: min_count, fill_value: fill_value),
412
+ max(min_count: min_count, fill_value: fill_value)]
413
+ end
352
414
  kw = {}
353
415
  kw[:min_count] = min_count unless min_count.nil?
354
416
  kw[:fill_value] = fill_value unless fill_value.nil?
@@ -364,8 +426,9 @@ class CABlockIterator < CAIterator
364
426
  # @return [CArray]
365
427
  [:min_index, :max_index].each do |op|
366
428
  class_eval <<~RUBY, __FILE__, __LINE__ + 1
367
- def #{op}
368
- assemble { |view, _| view.#{op}(axis: @tile_axes) }
429
+ def #{op} (**kw)
430
+ kw = CAIterator.reduce_keywords(:#{op}, kw)
431
+ assemble { |view, _| view.#{op}(axis: @tile_axes, **kw) }
369
432
  end
370
433
  RUBY
371
434
  end
@@ -376,26 +439,50 @@ class CABlockIterator < CAIterator
376
439
  # minima. Unlike `min_index` (the tile-local position) this indexes back
377
440
  # into the original array. An all-masked tile is a masked cell.
378
441
  # @return [CArray] tile-grid shaped int64
379
- def min_addr; winner_addr(:min_index); end
442
+ def min_addr (**kw); winner_addr(:min_index, kw); end
380
443
 
381
444
  # @overload max_addr
382
445
  # Per-tile flat source address of the maximum. See {#min_addr}.
383
446
  # @return [CArray] tile-grid shaped int64
384
- def max_addr; winner_addr(:max_index); end
447
+ def max_addr (**kw); winner_addr(:max_index, kw); end
385
448
 
386
449
  private
387
450
 
451
+ # Per-tile minimum or maximum of a Face source, gathered from the source by
452
+ # the winner's address so the result is the Face itself. An all-masked tile,
453
+ # and with `min_count` a tile with too few present cells, is a masked cell.
454
+ def face_winner (idx_op, min_count, fill_value)
455
+ addr = winner_addr(idx_op)
456
+ need = CArray.__present_cells_needed_per_result_cell__(idx_op == :min_index ? :min : :max, min_count, 0)
457
+ addr[count_not_masked.lt(need)] = UNDEF if need > 0
458
+ src = @source.reshape(@source.elements)
459
+ empty = addr.is_masked
460
+ return src[addr].copy unless empty.any
461
+ # A masked index would read as masked, but gathering by one gives the
462
+ # source a mask, so an empty tile takes a placeholder address instead.
463
+ # Pointing it at a masked cell of the source
464
+ # carries the mask through the gather, which a read-only Face (whose copy
465
+ # cannot be written) needs. Otherwise mask the placeholder afterwards.
466
+ if fill_value.nil? && src.has_mask? && (hole = src.is_masked.where).elements > 0
467
+ return src[addr.strip_mask(hole[0])].copy
468
+ end
469
+ out = src[addr.strip_mask(0)].copy
470
+ out[empty] = fill_value.nil? ? UNDEF : fill_value
471
+ out
472
+ end
473
+
388
474
  # Per-tile source address of the winner. The tile-local flat index (min_index /
389
475
  # max_index) is looked up in a source-address companion tiled the same way, so
390
476
  # the tile geometry is reused rather than recomputed. An all-masked tile yields
391
477
  # a masked index; it is gathered at 0 and re-masked in the result.
392
- def winner_addr (idx_op)
478
+ def winner_addr (idx_op, kw = {})
479
+ kw = CAIterator.reduce_keywords(idx_op, kw)
393
480
  saddr = CArray.int64(*@source.shape).seq!
394
481
  out = nil
395
482
  each_region do |strip_ranges, tiles, out_ranges|
396
483
  vview = @source[*strip_ranges].block_view(*tiles)
397
484
  sview = saddr[*strip_ranges].block_view(*tiles)
398
- mi = vview.public_send(idx_op, axis: @tile_axes) # tile-local flat index
485
+ mi = vview.public_send(idx_op, axis: @tile_axes, **kw.slice(:min_count)) # tile-local flat index
399
486
  grid = (0...@sndim).map { |i| vview.shape[i] }
400
487
  cells = tiles.inject(1) { |p, t| p * t }
401
488
  safe = mi.copy
@@ -407,7 +494,7 @@ class CABlockIterator < CAIterator
407
494
  out ||= CArray.int64(*@shape)
408
495
  out[*out_ranges] = picked
409
496
  end
410
- out
497
+ CArray.__fill_undefined_result_cells__(out || CArray.int64(*@shape), kw[:fill_value])
411
498
  end
412
499
 
413
500
  public
@@ -441,15 +528,17 @@ class CABlockIterator < CAIterator
441
528
  # Per-tile weighted sum; `weights` is shaped like one full tile (`Π b_i`).
442
529
  # At a partial edge tile the weight kernel is sliced to the present cells.
443
530
  # @return [CArray]
444
- def wsum (weights)
445
- weighted(weights) { |view, w| view.wsum(w, axis: @tile_axes) }
531
+ def wsum (weights, **kw)
532
+ kw = CAIterator.reduce_keywords(:wsum, kw)
533
+ weighted(weights) { |view, w| view.wsum(w, axis: @tile_axes, **kw) }
446
534
  end
447
535
 
448
536
  # @overload wmean(weights)
449
537
  # Per-tile weighted mean; `weights` shaped like one full tile.
450
538
  # @return [CArray]
451
- def wmean (weights)
452
- weighted(weights) { |view, w| view.wmean(w, axis: @tile_axes) }
539
+ def wmean (weights, **kw)
540
+ kw = CAIterator.reduce_keywords(:wmean, kw)
541
+ weighted(weights) { |view, w| view.wmean(w, axis: @tile_axes, **kw) }
453
542
  end
454
543
 
455
544
  private
@@ -479,21 +568,22 @@ class CABlockIterator < CAIterator
479
568
  # Core per-axis order statistics take a single integer axis, so a 1-D tile
480
569
  # delegates straight to the C path; a multi-axis tile is materialized per
481
570
  # region and its tile axes flattened into one before the single-axis core
482
- # order-stat. A masked source raises (the known per-axis masked limitation);
483
- # strip the mask with `ca.value` first if needed.
571
+ # order-stat. Masked cells are left out, as in `CArray#median(axis:)`.
484
572
 
485
573
  # @overload median
486
574
  # Per-tile median. @return [CArray]
487
- def median
488
- order_stat { |v, axis| v.median(axis: axis) }
575
+ def median (**kw)
576
+ kw = CAIterator.reduce_keywords(:median, kw)
577
+ order_stat { |v, axis| v.median(axis: axis, **kw) }
489
578
  end
490
579
 
491
580
  # @overload percentile(*pers)
492
581
  # Per-tile percentile(s). One argument returns one CArray, several an array
493
582
  # of CArrays (as `CArray#percentile`).
494
583
  # @return [CArray, Array<CArray>]
495
- def percentile (*pers)
496
- order_stat { |v, axis| v.percentile(*pers, axis: axis) }
584
+ def percentile (*pers, **kw)
585
+ kw = CAIterator.reduce_keywords(:percentile, kw)
586
+ order_stat { |v, axis| v.percentile(*pers, axis: axis, **kw) }
497
587
  end
498
588
 
499
589
  # @overload quantile
@@ -585,7 +675,7 @@ class CABlockIterator < CAIterator
585
675
  # @return [CArray, Enumerator] source-shaped
586
676
  def map
587
677
  return to_enum(:map) unless block_given?
588
- pout = CArray.new(padded_source.data_type, padded_source.shape)
678
+ pout = CArray.new(padded_source.data_type, padded_source.shape, bytes: padded_source.bytes)
589
679
  pgv = pout.block_view(*@sizes)
590
680
  tgv = tile_grid_view
591
681
  nils = Array.new(@sndim, nil)
@@ -621,26 +711,83 @@ class CABlockIterator < CAIterator
621
711
  # @!method cumcount
622
712
  # Per-tile running count of present cells (int64), source-shaped.
623
713
  # @return [CArray]
624
- [:cumsum, :cumprod, :cummax, :cummin, :cumcount].each do |op|
625
- define_method(op) { block_scan(op) }
714
+ CArray.send(:define_named_methods, self,
715
+ [:cumsum, :cumprod, :cummax, :cummin, :cumcount], :scan_tiles)
716
+
717
+ # A scan takes no arguments here.
718
+ private def scan_tiles (op, *args)
719
+ unless args.empty?
720
+ raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0)"
721
+ end
722
+ block_scan(op)
723
+ end
724
+
725
+ # @overload lag(n = 1)
726
+ # Per-tile value `n` steps earlier, counting the tile's cells row-major;
727
+ # UNDEF where the tile has none, so a lag never reaches into another
728
+ # tile. A partial edge tile counts only the cells it has.
729
+ # @return [CArray] source-shaped
730
+ def lag (n = 1)
731
+ unless n.is_a?(Integer)
732
+ raise TypeError, "lag: n must be an Integer (got #{n.class})"
733
+ end
734
+ tile_wise { |rows, axis| rows.lag(n, axis: axis) }
735
+ end
736
+
737
+ # @overload strip_mask(method:)
738
+ # Per-tile fill of the masked cells, counting the tile's cells row-major:
739
+ # `:forward` / `:ffill` or `:backward` / `:bfill`. A fill never reaches
740
+ # into another tile, so a masked cell with nothing to carry in its tile
741
+ # stays masked.
742
+ # @return [CArray] source-shaped
743
+ def strip_mask (method:)
744
+ CAIterator.hold_backward?(method)
745
+ tile_wise { |rows, axis| rows.strip_mask(method: method, axis: axis) }
746
+ end
747
+
748
+ # @overload order(descending: false, method: :ordinal, kind: :quick)
749
+ # Per-tile rank of each cell among the tile's cells (int64, `0` the
750
+ # smallest), as `CArray#order` ranks them; a partial edge tile ranks only
751
+ # the cells it has. The keywords are those of `CArray#order`.
752
+ # @return [CArray] source-shaped
753
+ def order (**kw)
754
+ tile_wise { |rows, axis| rows.order(axis: axis, **kw) }
626
755
  end
627
756
 
628
757
  private
629
758
 
759
+ # A source-shaped result built tile by tile: the block gets a region's
760
+ # tiles as rows (one tile's cells, row-major, along +axis+) and gives back
761
+ # rows of the same shape. Each region has whole tiles of one size, so a
762
+ # partial edge tile is its own cells rather than cells and padding.
763
+ def tile_wise
764
+ out = nil
765
+ each_region do |strip_ranges, tiles, _out_ranges|
766
+ view = @source[*strip_ranges].block_view(*tiles)
767
+ grid = view.shape[0, @sndim]
768
+ found = yield(view.copy.reshape(*grid, tiles.inject(1, :*)), @sndim)
769
+ .reshape(*grid, *tiles)
770
+ out ||= tile_output(found)
771
+ out[*strip_ranges].block_view(*tiles)[] = tile_values(out, found)
772
+ end
773
+ # a source with no cells has no region; its one-cell rows give the type
774
+ out || yield(@source.copy.reshape(*@source.shape, 1), @sndim).reshape(*@source.shape)
775
+ end
776
+
630
777
  # Drive a within-tile segment scan: scan each tile flattened row-major and
631
778
  # scatter back to a source-shaped result. The output data type is taken from the
632
779
  # first tile's scan (all tiles share it); the padded margin is UNDEF and the
633
780
  # OOB cells of a partial edge tile are cropped from the result.
634
- def block_scan (op)
781
+ def block_scan (op, *args)
635
782
  tgv = tile_grid_view
636
783
  nils = Array.new(@sndim, nil)
637
784
  pout = nil
638
785
  pgv = nil
639
786
  CArray.each_index(*@shape) do |*g|
640
787
  tile = tgv[*g, *nils]
641
- scanned = tile.copy.reshape(tile.elements).public_send(op).reshape(*@sizes)
788
+ scanned = tile.copy.reshape(tile.elements).public_send(op, *args).reshape(*@sizes)
642
789
  unless pout
643
- pout = CArray.new(scanned.data_type, padded_source.shape)
790
+ pout = CArray.new(scanned.data_type, padded_source.shape, bytes: scanned.bytes)
644
791
  pout[] = UNDEF
645
792
  pgv = pout.block_view(*@sizes)
646
793
  end
@@ -649,6 +796,24 @@ class CABlockIterator < CAIterator
649
796
  pout[*@sndim.times.map { |i| 0...@source.shape[i] }].copy
650
797
  end
651
798
 
799
+ # A source-shaped array that can hold what #tile_wise gives: of its kind,
800
+ # or an object array for a read-only Face, which cannot be written cell by
801
+ # cell.
802
+ def tile_output (lagged)
803
+ if lagged.face? && lagged.read_only?
804
+ CArray.object(*@source.shape)
805
+ else
806
+ lagged.__send__(:blank_like, @source.shape)
807
+ end
808
+ end
809
+
810
+ # The values as +out+ can hold them: a Face going into an object array
811
+ # goes as its surface values, not its storage.
812
+ def tile_values (out, lagged)
813
+ return lagged unless out.data_type == CA_OBJECT && lagged.face?
814
+ CArray.object(*lagged.shape) { lagged.to_a }
815
+ end
816
+
652
817
  # A padded entity whose interior holds the source and whose partial-edge
653
818
  # margin is masked (UNDEF), sized to exactly cover the ceil tile grid. When
654
819
  # the source already divides evenly this is the source itself (no copy), so
@@ -659,7 +824,7 @@ class CABlockIterator < CAIterator
659
824
  if pshape == @source.shape.to_a
660
825
  @source
661
826
  else
662
- pad = CArray.new(@source.data_type, pshape)
827
+ pad = @source.__send__(:blank_like, pshape) # a Face stays one
663
828
  pad[] = UNDEF
664
829
  pad[*@sndim.times.map { |i| 0...@source.shape[i] }] = @source
665
830
  pad
@@ -672,6 +837,14 @@ class CABlockIterator < CAIterator
672
837
  def tile_grid_view
673
838
  @tile_grid_view ||= padded_source.block_view(*@sizes)
674
839
  end
840
+
841
+ # lag and strip_mask(method:) give the source's values moved or held in
842
+ # place, so the result shows the source's attributes, as CArray#lag and
843
+ # CArray#strip_mask(method:) do.
844
+ prepend(Module.new do
845
+ def lag (*args, **keywords) = @source.__send__(:carry_attrs, super)
846
+ def strip_mask (**keywords) = @source.__send__(:carry_attrs, super)
847
+ end)
675
848
  end
676
849
 
677
850
 
@@ -1,6 +1,6 @@
1
1
  # Boolean folds all / any / none gain a `skip_masked:` keyword.
2
2
  #
3
- # skip_masked: true (default) -- available-case (skipna): masked cells are
3
+ # skip_masked: true (default) -- available-case: masked cells are
4
4
  # ignored, the result is always true/false.
5
5
  # This is the existing behavior; every other
6
6
  # keyword (axis:, keep_axis:, ...) forwards
@@ -14,15 +14,15 @@
14
14
  # false < unknown < true).
15
15
  #
16
16
  # The Kleene fold composes from count(true) / count(false) / count_masked, so
17
- # there is no dedicated kernel; the C reductions keep the skipna semantics
18
- # under the `__*_skipna__` aliases.
17
+ # there is no dedicated kernel; the C reductions that skip masked cells stay
18
+ # reachable as the private `*_skipping_masked` aliases.
19
19
 
20
20
  class CArray
21
21
 
22
- alias_method :__any_skipna__, :any
23
- alias_method :__all_skipna__, :all
24
- alias_method :__none_skipna__, :none
25
- private :__any_skipna__, :__all_skipna__, :__none_skipna__
22
+ alias_method :any_skipping_masked, :any
23
+ alias_method :all_skipping_masked, :all
24
+ alias_method :none_skipping_masked, :none
25
+ private :any_skipping_masked, :all_skipping_masked, :none_skipping_masked
26
26
 
27
27
  # Whether any cell is true.
28
28
  #
@@ -36,8 +36,8 @@ class CArray
36
36
  # `keep_axis:`, ...).
37
37
  # @return [Boolean, CArray] a scalar, or an array when an axis is given.
38
38
  def any (skip_masked: true, **opts)
39
- return __any_skipna__(**opts) if skip_masked
40
- __kleene_fold(:any, opts)
39
+ return any_skipping_masked(**opts) if skip_masked
40
+ kleene_fold(:any, opts)
41
41
  end
42
42
 
43
43
  # Whether every cell is true.
@@ -52,8 +52,8 @@ class CArray
52
52
  # `keep_axis:`, ...).
53
53
  # @return [Boolean, CArray] a scalar, or an array when an axis is given.
54
54
  def all (skip_masked: true, **opts)
55
- return __all_skipna__(**opts) if skip_masked
56
- __kleene_fold(:all, opts)
55
+ return all_skipping_masked(**opts) if skip_masked
56
+ kleene_fold(:all, opts)
57
57
  end
58
58
 
59
59
  # Whether no cell is true.
@@ -68,42 +68,34 @@ class CArray
68
68
  # `keep_axis:`, ...).
69
69
  # @return [Boolean, CArray] a scalar, or an array when an axis is given.
70
70
  def none (skip_masked: true, **opts)
71
- return __none_skipna__(**opts) if skip_masked
72
- # none = not any (Kleene): not(true)=false, not(false)=true, not(UNDEF)=UNDEF
73
- r = __kleene_fold(:any, opts)
74
- r.is_a?(CArray) ? r.not : (r.equal?(UNDEF) ? UNDEF : !r)
71
+ return none_skipping_masked(**opts) if skip_masked
72
+ kleene_fold(:none, opts)
75
73
  end
76
74
 
77
75
  private
78
76
 
79
- # Three-valued fold via counts. `kind` is :any (OR) or :all (AND).
80
- # `opts` carries axis: / keep_axis: (same surface as the skipna reduction).
81
- def __kleene_fold (kind, opts)
82
- axis = opts[:axis]
83
- if axis.nil?
84
- if kind == :any
85
- return true if count(true) > 0 # a known TRUE settles OR
86
- else
87
- return false if count(false) > 0 # a known FALSE settles AND
88
- end
89
- return UNDEF if count_masked > 0 # undetermined
90
- return( kind == :any ? false : true )
77
+ # Three-valued fold. `kind` is :any, :all or :none. The available-case
78
+ # reduction answers wherever an unmasked cell settles the fold (a true for
79
+ # any, a false for all, a true for none); elsewhere a masked cell leaves the
80
+ # answer undetermined. Going through that reduction checks the data type,
81
+ # the axes and the keywords exactly as `skip_masked: true` does.
82
+ def kleene_fold (kind, opts)
83
+ r = case kind
84
+ when :any then any_skipping_masked(**opts)
85
+ when :all then all_skipping_masked(**opts)
86
+ else none_skipping_masked(**opts)
87
+ end
88
+ cm = count_masked(**opts)
89
+ if r.is_a?(CArray)
90
+ settled = ( kind == :any ) ? r : r.not
91
+ out = r.copy
92
+ out[settled.not & (cm > 0)] = UNDEF
93
+ out
94
+ else
95
+ settled = ( kind == :any ) ? r : !r
96
+ return r if settled
97
+ ( cm > 0 ) ? UNDEF : r
91
98
  end
92
-
93
- keep = opts[:keep_axis]
94
- cnt_kw = keep ? { axis: axis, keep_axis: keep } : { axis: axis }
95
- ck = ( kind == :any ) ? count(true, **cnt_kw) : count(false, **cnt_kw)
96
- cm = count_masked(axis: axis) # count_masked has no keep_axis
97
- cm = cm.reshape(*ck.shape) unless cm.shape == ck.shape
98
-
99
- # A cell is undetermined only where it is masked AND the known side does
100
- # not settle it (ck counts the settling value: true for OR, false for AND).
101
- # Masking only those cells avoids leaving an all-zero mask behind.
102
- out = CArray.boolean(*ck.shape)
103
- out[] = ( kind == :any ) ? 0 : 1 # OR default false / AND default true
104
- out[ck.eq(0) & (cm > 0)] = UNDEF # genuinely undetermined
105
- out[ck > 0] = ( kind == :any ) ? 1 : 0 # known side dominates
106
- out
107
99
  end
108
100
 
109
101
  end