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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +568 -0
- data/README.md +6 -6
- data/carray.gemspec +1 -1
- data/ext/ca_axis_descriptor.h +27 -24
- data/ext/ca_axis_dispatch.c +220 -0
- data/ext/ca_axis_group.c +201 -123
- data/ext/ca_binop_dispatch.c +46 -52
- data/ext/ca_binop_dispatch.h +1 -1
- data/ext/ca_categorical_iterator.c +249 -183
- data/ext/ca_compare.c +7 -12
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +38 -23
- data/ext/ca_kernel_iterator.c +1765 -2076
- data/ext/ca_kernel_iterator.h +584 -566
- data/ext/ca_monop_dispatch.c +86 -63
- data/ext/ca_monop_dispatch.h +5 -0
- data/ext/ca_obj_array.c +223 -56
- data/ext/ca_obj_bincmp.c +172 -138
- data/ext/ca_obj_binop.c +116 -230
- data/ext/ca_obj_bitarray.c +40 -26
- data/ext/ca_obj_bitfield.c +100 -68
- data/ext/ca_obj_block.c +19 -9
- data/ext/ca_obj_byte_swap.c +45 -46
- data/ext/ca_obj_const_string.c +269 -44
- data/ext/ca_obj_face.c +612 -89
- data/ext/ca_obj_face.h +111 -47
- data/ext/ca_obj_fake.c +36 -79
- data/ext/ca_obj_farray.c +7 -3
- data/ext/ca_obj_field.c +33 -8
- data/ext/ca_obj_fixlen_string.c +23 -24
- data/ext/ca_obj_grid.c +541 -210
- data/ext/ca_obj_meld.c +331 -111
- data/ext/ca_obj_moncmp.c +80 -65
- data/ext/ca_obj_monop.c +177 -149
- data/ext/ca_obj_object.c +664 -248
- data/ext/ca_obj_record.c +63 -23
- data/ext/ca_obj_reduce.c +18 -16
- data/ext/ca_obj_refer.c +140 -58
- data/ext/ca_obj_remap.c +85 -24
- data/ext/ca_obj_repeat.c +24 -9
- data/ext/ca_obj_roll.c +121 -16
- data/ext/ca_obj_select.c +61 -58
- data/ext/ca_obj_select_axis.c +73 -107
- data/ext/ca_obj_shift.c +32 -102
- data/ext/ca_obj_stack.c +372 -122
- data/ext/ca_obj_stride.c +348 -140
- data/ext/ca_obj_string.c +88 -22
- data/ext/ca_obj_tile.c +38 -21
- data/ext/ca_obj_time.c +45 -31
- data/ext/ca_obj_timedelta.c +43 -29
- data/ext/ca_obj_transpose.c +17 -6
- data/ext/ca_obj_triop.c +125 -141
- data/ext/ca_obj_window.c +330 -44
- data/ext/ca_op_ipower.c +24 -11
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +104 -81
- data/ext/ca_sweep_engine.h +7 -20
- data/ext/ca_triop_dispatch.c +4 -1
- data/ext/ca_triop_dispatch.h +5 -2
- data/ext/caframe_csv_reader.c +542 -0
- data/ext/carray.h +241 -92
- data/ext/carray_access.c +608 -276
- data/ext/carray_address_basis.c +494 -97
- data/ext/carray_attribute.c +129 -4
- data/ext/carray_bincount.c +39 -7
- data/ext/carray_broadcast.c +56 -19
- data/ext/carray_build_flags.h +1 -1
- data/ext/carray_call_cfunc.c +108 -28
- data/ext/carray_cast.c +1576 -223
- data/ext/carray_cast_func.rb +77 -24
- data/ext/carray_class.c +4 -3
- data/ext/carray_conversion.c +208 -75
- data/ext/carray_copy.c +82 -24
- data/ext/carray_core.c +646 -162
- data/ext/carray_count.c +171 -88
- data/ext/carray_element.c +94 -60
- data/ext/carray_factorize.c +670 -324
- data/ext/carray_generate.c +260 -83
- data/ext/carray_histogram.c +209 -106
- data/ext/carray_hold.c +14 -16
- data/ext/carray_index_classifier.c +422 -243
- data/ext/carray_internal.h +692 -3
- data/ext/carray_kernels_bincmp.c +7 -0
- data/ext/carray_kernels_binop.c +487 -120
- data/ext/carray_kernels_init.c +653 -50
- data/ext/carray_kernels_moncmp.c +15 -8
- data/ext/carray_kernels_monop.c +99 -43
- data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
- data/ext/carray_kernels_reduce_boolean.c +112 -36
- data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
- data/ext/carray_kernels_reduce_extreme.c +3032 -2892
- data/ext/carray_kernels_reduce_variance.c +439 -500
- data/ext/carray_kernels_scan.c +1097 -488
- data/ext/carray_kernels_search.c +3787 -2176
- data/ext/carray_kernels_sort.c +1200 -920
- data/ext/carray_kernels_triop.c +479 -7
- data/ext/carray_key_windows.c +216 -0
- data/ext/carray_lazy.c +967 -153
- data/ext/carray_loop.c +81 -65
- data/ext/carray_mask.c +910 -324
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +299 -100
- data/ext/carray_memory_view.c +872 -758
- data/ext/carray_operator.c +1171 -364
- data/ext/carray_order.c +505 -133
- data/ext/carray_partition.c +112 -65
- data/ext/carray_random.c +327 -239
- data/ext/carray_round.c +273 -0
- data/ext/carray_scatter.c +346 -78
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +249 -86
- data/ext/carray_sort.c +224 -227
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +118 -39
- data/ext/carray_test.c +193 -69
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_undef.c +4 -0
- data/ext/carray_utils.c +579 -113
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +19 -4
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1202 -1014
- data/ext/ruby_carray.c +17 -5
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +160 -17
- data/lib/carray/autoload_carray.rb +23 -27
- data/lib/carray/autoload_method_extension.rb +20 -5
- data/lib/carray/axis_group.rb +360 -63
- data/lib/carray/basics.rb +162 -28
- data/lib/carray/bincount_nd.rb +18 -2
- data/lib/carray/block_iterator.rb +231 -58
- data/lib/carray/boolean_reduce.rb +34 -42
- data/lib/carray/categorical.rb +242 -52
- data/lib/carray/categorical_iterator.rb +437 -827
- data/lib/carray/complex.rb +41 -34
- data/lib/carray/conditional.rb +150 -14
- data/lib/carray/const_string.rb +59 -4
- data/lib/carray/construct.rb +15 -11
- data/lib/carray/data_type_extension.rb +1 -0
- data/lib/carray/distance_limit.rb +77 -0
- data/lib/carray/fixlen_string.rb +7 -1
- data/lib/carray/frame/column_verbs.rb +70 -0
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +82 -7
- data/lib/carray/frame/csv_parser.rb +688 -87
- data/lib/carray/frame/describe.rb +80 -0
- data/lib/carray/frame/frame.rb +369 -45
- data/lib/carray/frame/group.rb +631 -90
- data/lib/carray/frame/io.rb +506 -79
- data/lib/carray/frame/join.rb +411 -62
- data/lib/carray/frame/records.rb +116 -19
- data/lib/carray/frame/reshape.rb +455 -0
- data/lib/carray/frame/sort.rb +30 -18
- data/lib/carray/frame/verbs.rb +868 -61
- data/lib/carray/frame/windows.rb +117 -0
- data/lib/carray/frame.rb +4 -0
- data/lib/carray/fuse_source.rb +118 -2
- data/lib/carray/fusion.rb +257 -42
- data/lib/carray/histogram.rb +6 -1
- data/lib/carray/inspect.rb +54 -13
- data/lib/carray/iterator.rb +99 -9
- data/lib/carray/key_windows.rb +620 -0
- data/lib/carray/lazy.rb +216 -193
- data/lib/carray/mask_gap_fill.rb +112 -92
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +52 -21
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bin.rb +26 -18
- data/lib/carray/methods/bincount.rb +18 -2
- data/lib/carray/methods/bit_string.rb +15 -4
- data/lib/carray/methods/choose.rb +16 -3
- data/lib/carray/methods/composition.rb +18 -10
- data/lib/carray/methods/factorize.rb +2 -2
- data/lib/carray/methods/gather_nd.rb +25 -5
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_between.rb +59 -0
- data/lib/carray/methods/is_in.rb +140 -10
- data/lib/carray/methods/join.rb +2 -7
- data/lib/carray/methods/lag.rb +49 -0
- data/lib/carray/methods/locate_addr.rb +182 -45
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/nunique.rb +11 -6
- data/lib/carray/methods/repeat.rb +1 -20
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +11 -2
- data/lib/carray/methods/value_counts.rb +4 -11
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/rng.rb +3 -0
- data/lib/carray/runtime.rb +65 -17
- data/lib/carray/segment_iterator.rb +1221 -0
- data/lib/carray/serialize.rb +214 -30
- data/lib/carray/slab_iterator.rb +101 -27
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string.rb +105 -5
- data/lib/carray/string_operation_extension.rb +12 -4
- data/lib/carray/struct.rb +137 -58
- data/lib/carray/struct_builder.rb +124 -17
- data/lib/carray/time.rb +1222 -186
- data/lib/carray/window_iterator.rb +242 -81
- data/lib/carray.rb +9 -2
- data/yard-stubs/ca_obj_array.rb +4 -4
- data/yard-stubs/ca_obj_bitarray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +23 -12
- data/yard-stubs/ca_obj_fake.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +5 -1
- data/yard-stubs/ca_obj_grid.rb +21 -10
- data/yard-stubs/ca_obj_refer.rb +5 -4
- data/yard-stubs/ca_obj_roll.rb +2 -3
- data/yard-stubs/ca_obj_shift.rb +5 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_tile.rb +1 -1
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +9 -5
- data/yard-stubs/carray_access.rb +11 -5
- data/yard-stubs/carray_attribute.rb +0 -5
- data/yard-stubs/carray_broadcast.rb +1 -1
- data/yard-stubs/carray_cast.rb +88 -43
- data/yard-stubs/carray_conversion.rb +15 -2
- data/yard-stubs/carray_copy.rb +12 -5
- data/yard-stubs/carray_core.rb +3 -3
- data/yard-stubs/carray_count.rb +9 -0
- data/yard-stubs/carray_element.rb +10 -4
- data/yard-stubs/carray_generate.rb +21 -1
- data/yard-stubs/carray_lazy.rb +5 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +60 -12
- data/yard-stubs/carray_math.rb +52 -14
- data/yard-stubs/carray_median_percentile.rb +15 -8
- data/yard-stubs/carray_memory_view.rb +40 -23
- data/yard-stubs/carray_order.rb +61 -29
- data/yard-stubs/carray_random.rb +13 -4
- data/yard-stubs/carray_scatter.rb +21 -9
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +10 -2
- data/yard-stubs/carray_sort.rb +49 -8
- data/yard-stubs/carray_test.rb +15 -3
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +19 -5
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_kernels_map.c +0 -3467
- data/lib/carray/arrow_tensor.rb +0 -401
- 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
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
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
|
|
283
|
+
# @!method all
|
|
259
284
|
# Whether every cell of each tile is true.
|
|
260
|
-
#
|
|
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
|
|
287
|
+
# @!method any
|
|
266
288
|
# Whether any cell of each tile is true.
|
|
267
|
-
#
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
625
|
-
|
|
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 =
|
|
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
|
|
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
|
|
18
|
-
#
|
|
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 :
|
|
23
|
-
alias_method :
|
|
24
|
-
alias_method :
|
|
25
|
-
private :
|
|
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
|
|
40
|
-
|
|
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
|
|
56
|
-
|
|
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
|
|
72
|
-
|
|
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
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|