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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +304 -0
- data/README.md +3 -4
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +225 -0
- data/ext/ca_axis_group.c +177 -99
- data/ext/ca_binop_dispatch.c +54 -32
- data/ext/ca_binop_dispatch.h +1 -0
- data/ext/ca_categorical_iterator.c +66 -71
- data/ext/ca_compare.c +8 -2
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +27 -11
- data/ext/ca_kernel_iterator.c +663 -214
- data/ext/ca_kernel_iterator.h +231 -167
- data/ext/ca_monop_dispatch.c +4 -4
- data/ext/ca_monop_dispatch.h +4 -0
- data/ext/ca_obj_array.c +132 -22
- data/ext/ca_obj_bincmp.c +152 -121
- data/ext/ca_obj_binop.c +107 -210
- data/ext/ca_obj_bitarray.c +37 -24
- data/ext/ca_obj_bitfield.c +95 -62
- data/ext/ca_obj_block.c +1 -0
- data/ext/ca_obj_byte_swap.c +40 -40
- data/ext/ca_obj_const_string.c +96 -25
- data/ext/ca_obj_face.c +406 -64
- data/ext/ca_obj_face.h +92 -47
- data/ext/ca_obj_fake.c +21 -50
- data/ext/ca_obj_farray.c +1 -0
- data/ext/ca_obj_field.c +16 -3
- data/ext/ca_obj_fixlen_string.c +21 -21
- data/ext/ca_obj_grid.c +181 -124
- data/ext/ca_obj_meld.c +189 -107
- data/ext/ca_obj_moncmp.c +61 -61
- data/ext/ca_obj_monop.c +125 -107
- data/ext/ca_obj_object.c +543 -233
- data/ext/ca_obj_record.c +51 -16
- data/ext/ca_obj_reduce.c +5 -8
- data/ext/ca_obj_refer.c +117 -33
- data/ext/ca_obj_remap.c +57 -17
- data/ext/ca_obj_repeat.c +21 -5
- data/ext/ca_obj_roll.c +115 -11
- data/ext/ca_obj_select.c +47 -49
- data/ext/ca_obj_select_axis.c +58 -60
- data/ext/ca_obj_shift.c +22 -102
- data/ext/ca_obj_stack.c +223 -118
- data/ext/ca_obj_stride.c +195 -34
- data/ext/ca_obj_string.c +17 -18
- data/ext/ca_obj_tile.c +32 -16
- data/ext/ca_obj_time.c +23 -25
- data/ext/ca_obj_timedelta.c +21 -23
- data/ext/ca_obj_transpose.c +12 -3
- data/ext/ca_obj_triop.c +110 -117
- data/ext/ca_obj_window.c +149 -27
- 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 +102 -79
- 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 +463 -0
- data/ext/carray.h +70 -27
- data/ext/carray_access.c +455 -211
- data/ext/carray_address_basis.c +177 -36
- data/ext/carray_attribute.c +45 -3
- data/ext/carray_bincount.c +32 -2
- data/ext/carray_broadcast.c +46 -14
- data/ext/carray_call_cfunc.c +0 -28
- data/ext/carray_cast.c +1444 -209
- data/ext/carray_cast_func.rb +34 -21
- data/ext/carray_conversion.c +197 -73
- data/ext/carray_copy.c +32 -11
- data/ext/carray_core.c +514 -122
- data/ext/carray_count.c +140 -87
- data/ext/carray_element.c +18 -27
- data/ext/carray_factorize.c +569 -277
- data/ext/carray_generate.c +76 -40
- data/ext/carray_histogram.c +152 -94
- data/ext/carray_hold.c +2 -1
- data/ext/carray_index_classifier.c +63 -96
- data/ext/carray_internal.h +385 -3
- data/ext/carray_lazy.c +834 -79
- data/ext/carray_loop.c +81 -65
- data/ext/carray_mask.c +682 -316
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +221 -76
- data/ext/carray_memory_view.c +254 -248
- data/ext/carray_operator.c +1089 -359
- data/ext/carray_order.c +133 -69
- data/ext/carray_partition.c +102 -56
- data/ext/carray_random.c +273 -239
- data/ext/carray_scatter.c +308 -65
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +170 -70
- data/ext/carray_sort.c +215 -218
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +99 -21
- data/ext/carray_test.c +167 -64
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_utils.c +363 -98
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +0 -2
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1076 -846
- data/ext/ruby_carray.c +7 -1
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +39 -0
- data/lib/carray/autoload_carray.rb +19 -0
- data/lib/carray/autoload_method_extension.rb +6 -0
- data/lib/carray/axis_group.rb +174 -26
- data/lib/carray/basics.rb +91 -25
- data/lib/carray/bincount_nd.rb +6 -0
- data/lib/carray/block_iterator.rb +118 -48
- data/lib/carray/boolean_reduce.rb +21 -29
- data/lib/carray/categorical.rb +94 -26
- data/lib/carray/categorical_iterator.rb +333 -826
- data/lib/carray/complex.rb +36 -29
- data/lib/carray/conditional.rb +94 -7
- data/lib/carray/const_string.rb +46 -0
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +13 -7
- data/lib/carray/frame/csv_parser.rb +589 -82
- data/lib/carray/frame/describe.rb +79 -0
- data/lib/carray/frame/frame.rb +124 -25
- data/lib/carray/frame/group.rb +189 -63
- data/lib/carray/frame/io.rb +412 -72
- data/lib/carray/frame/join.rb +134 -46
- data/lib/carray/frame/records.rb +37 -16
- data/lib/carray/frame/reshape.rb +448 -0
- data/lib/carray/frame/sort.rb +26 -2
- data/lib/carray/frame/verbs.rb +715 -48
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +111 -2
- data/lib/carray/fusion.rb +229 -38
- data/lib/carray/inspect.rb +29 -11
- data/lib/carray/iterator.rb +14 -2
- data/lib/carray/lazy.rb +54 -30
- data/lib/carray/mask_gap_fill.rb +66 -52
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +40 -7
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bincount.rb +2 -2
- data/lib/carray/methods/bit_string.rb +14 -3
- data/lib/carray/methods/composition.rb +16 -10
- data/lib/carray/methods/factorize.rb +2 -2
- data/lib/carray/methods/gather_nd.rb +13 -2
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_in.rb +117 -4
- data/lib/carray/methods/join.rb +1 -1
- data/lib/carray/methods/locate_addr.rb +177 -36
- data/lib/carray/methods/mode.rb +18 -1
- 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/record_order.rb +208 -0
- data/lib/carray/runtime.rb +14 -11
- data/lib/carray/segment_iterator.rb +990 -0
- data/lib/carray/serialize.rb +197 -27
- data/lib/carray/slab_iterator.rb +51 -27
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string_operation_extension.rb +4 -3
- data/lib/carray/struct.rb +142 -51
- data/lib/carray/struct_builder.rb +110 -14
- data/lib/carray/table.rb +2 -2
- data/lib/carray/time.rb +517 -103
- data/lib/carray/window_iterator.rb +127 -72
- data/lib/carray.rb +2 -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 +1 -1
- data/yard-stubs/ca_obj_refer.rb +3 -3
- data/yard-stubs/ca_obj_roll.rb +0 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +1 -2
- data/yard-stubs/carray_access.rb +5 -4
- data/yard-stubs/carray_broadcast.rb +1 -1
- data/yard-stubs/carray_cast.rb +78 -43
- data/yard-stubs/carray_conversion.rb +13 -1
- data/yard-stubs/carray_copy.rb +8 -3
- data/yard-stubs/carray_core.rb +3 -3
- data/yard-stubs/carray_count.rb +9 -0
- data/yard-stubs/carray_lazy.rb +5 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +34 -10
- data/yard-stubs/carray_math.rb +25 -13
- data/yard-stubs/carray_median_percentile.rb +10 -6
- data/yard-stubs/carray_order.rb +34 -13
- data/yard-stubs/carray_scatter.rb +10 -3
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +2 -1
- data/yard-stubs/carray_sort.rb +43 -3
- data/yard-stubs/carray_test.rb +10 -0
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +11 -19
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_build_flags.h +0 -3
- data/ext/carray_config.h +0 -28
- data/ext/carray_kernels_bincmp.c +0 -4446
- data/ext/carray_kernels_binop.c +0 -11001
- data/ext/carray_kernels_init.c +0 -1131
- data/ext/carray_kernels_map.c +0 -3467
- data/ext/carray_kernels_moncmp.c +0 -2097
- data/ext/carray_kernels_monop.c +0 -18313
- data/ext/carray_kernels_reduce_aggregate.c +0 -26005
- data/ext/carray_kernels_reduce_boolean.c +0 -330
- data/ext/carray_kernels_reduce_cumulative.c +0 -14862
- data/ext/carray_kernels_reduce_extreme.c +0 -17494
- data/ext/carray_kernels_reduce_variance.c +0 -3910
- data/ext/carray_kernels_scan.c +0 -3693
- data/ext/carray_kernels_search.c +0 -32218
- data/ext/carray_kernels_sort.c +0 -10626
- data/ext/carray_kernels_triop.c +0 -1392
|
@@ -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,9 +302,12 @@ 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,
|
|
305
|
+
[:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev,
|
|
287
306
|
:variancep, :stddevp].each do |op|
|
|
288
307
|
define_method(op) do |min_count: nil, fill_value: nil|
|
|
308
|
+
if FACE_WINNER_INDEX.key?(op) && @source.face?
|
|
309
|
+
return face_winner(FACE_WINNER_INDEX[op], min_count, fill_value)
|
|
310
|
+
end
|
|
289
311
|
kw = {}
|
|
290
312
|
kw[:min_count] = min_count unless min_count.nil?
|
|
291
313
|
kw[:fill_value] = fill_value unless fill_value.nil?
|
|
@@ -293,6 +315,17 @@ class CABlockIterator < CAIterator
|
|
|
293
315
|
end
|
|
294
316
|
end
|
|
295
317
|
|
|
318
|
+
[:all, :any].each do |op|
|
|
319
|
+
define_method(op) { |**kw| fold(op, **CAIterator.reduce_keywords(op, kw)) }
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# A Face's minimum and maximum are cells of the Face, so they are gathered
|
|
323
|
+
# from the source by the winner's address. Assembling the per-region results
|
|
324
|
+
# instead copies the Face's storage into an array that no longer knows it is
|
|
325
|
+
# one: a CATime came back as 8-byte strings.
|
|
326
|
+
FACE_WINNER_INDEX = { min: :min_index, max: :max_index }.freeze
|
|
327
|
+
private_constant :FACE_WINNER_INDEX
|
|
328
|
+
|
|
296
329
|
# ---- count family and elements ----------------------------------------
|
|
297
330
|
|
|
298
331
|
# @overload count(v = <none>)
|
|
@@ -300,15 +333,16 @@ class CABlockIterator < CAIterator
|
|
|
300
333
|
# partial edge tile); `count(UNDEF)` counts masked cells; `count(v)` counts
|
|
301
334
|
# cells equal to `v`.
|
|
302
335
|
# @return [CArray] tile-grid shaped
|
|
303
|
-
def count (*args)
|
|
304
|
-
return count_not_masked if args.empty?
|
|
336
|
+
def count (*args, **kw)
|
|
337
|
+
return count_not_masked(**kw) if args.empty?
|
|
338
|
+
kw = CAIterator.reduce_keywords(:count, kw)
|
|
305
339
|
out = nil
|
|
306
340
|
each_region do |strip_ranges, tiles, out_ranges|
|
|
307
341
|
view = @source[*strip_ranges].block_view(*tiles)
|
|
308
342
|
# block_view is a CAStride, so #count is not shadowed; dispatch
|
|
309
343
|
# 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)
|
|
344
|
+
red = CArray.instance_method(:count).bind_call(view, *args, axis: @tile_axes, **kw)
|
|
345
|
+
out ||= CArray.new(red.data_type, @shape, bytes: red.bytes)
|
|
312
346
|
out[*out_ranges] = red
|
|
313
347
|
end
|
|
314
348
|
out
|
|
@@ -318,16 +352,21 @@ class CABlockIterator < CAIterator
|
|
|
318
352
|
# Per-tile count of present (non-masked) cells. For a partial edge tile
|
|
319
353
|
# this is the real cell count (the OOB cells are not present).
|
|
320
354
|
# @return [CArray]
|
|
321
|
-
def count_not_masked
|
|
322
|
-
fold(:count_not_masked)
|
|
355
|
+
def count_not_masked (**kw)
|
|
356
|
+
fold(:count_not_masked, **CAIterator.reduce_keywords(:count_not_masked, kw))
|
|
323
357
|
end
|
|
324
358
|
|
|
325
359
|
# @overload count_masked
|
|
326
360
|
# Per-tile count of masked cells. Equals `elements - count_not_masked`;
|
|
327
361
|
# the OOB cells of a partial edge tile count here.
|
|
328
362
|
# @return [CArray]
|
|
329
|
-
def count_masked
|
|
330
|
-
|
|
363
|
+
def count_masked (**kw)
|
|
364
|
+
kw = CAIterator.reduce_keywords(:count_masked, kw)
|
|
365
|
+
need = CArray.__min_count_need__(:count_masked, kw[:min_count], 0)
|
|
366
|
+
out = elements - count_not_masked
|
|
367
|
+
# A count of the mask reads every cell of the tile.
|
|
368
|
+
out[elements.lt(need)] = UNDEF if need > 0
|
|
369
|
+
CArray.__reduce_fill__(out, kw[:fill_value])
|
|
331
370
|
end
|
|
332
371
|
|
|
333
372
|
# @overload elements
|
|
@@ -349,6 +388,10 @@ class CABlockIterator < CAIterator
|
|
|
349
388
|
# Per-tile `[min, max]` (two tile-grid-shaped CArrays, a single fused pass).
|
|
350
389
|
# @return [Array<CArray>]
|
|
351
390
|
def minmax (min_count: nil, fill_value: nil)
|
|
391
|
+
if @source.face?
|
|
392
|
+
return [min(min_count: min_count, fill_value: fill_value),
|
|
393
|
+
max(min_count: min_count, fill_value: fill_value)]
|
|
394
|
+
end
|
|
352
395
|
kw = {}
|
|
353
396
|
kw[:min_count] = min_count unless min_count.nil?
|
|
354
397
|
kw[:fill_value] = fill_value unless fill_value.nil?
|
|
@@ -364,8 +407,9 @@ class CABlockIterator < CAIterator
|
|
|
364
407
|
# @return [CArray]
|
|
365
408
|
[:min_index, :max_index].each do |op|
|
|
366
409
|
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
367
|
-
def #{op}
|
|
368
|
-
|
|
410
|
+
def #{op} (**kw)
|
|
411
|
+
kw = CAIterator.reduce_keywords(:#{op}, kw)
|
|
412
|
+
assemble { |view, _| view.#{op}(axis: @tile_axes, **kw) }
|
|
369
413
|
end
|
|
370
414
|
RUBY
|
|
371
415
|
end
|
|
@@ -376,26 +420,49 @@ class CABlockIterator < CAIterator
|
|
|
376
420
|
# minima. Unlike `min_index` (the tile-local position) this indexes back
|
|
377
421
|
# into the original array. An all-masked tile is a masked cell.
|
|
378
422
|
# @return [CArray] tile-grid shaped int64
|
|
379
|
-
def min_addr; winner_addr(:min_index); end
|
|
423
|
+
def min_addr (**kw); winner_addr(:min_index, kw); end
|
|
380
424
|
|
|
381
425
|
# @overload max_addr
|
|
382
426
|
# Per-tile flat source address of the maximum. See {#min_addr}.
|
|
383
427
|
# @return [CArray] tile-grid shaped int64
|
|
384
|
-
def max_addr; winner_addr(:max_index); end
|
|
428
|
+
def max_addr (**kw); winner_addr(:max_index, kw); end
|
|
385
429
|
|
|
386
430
|
private
|
|
387
431
|
|
|
432
|
+
# Per-tile minimum or maximum of a Face source, gathered from the source by
|
|
433
|
+
# the winner's address so the result is the Face itself. An all-masked tile,
|
|
434
|
+
# and with `min_count` a tile with too few present cells, is a masked cell.
|
|
435
|
+
def face_winner (idx_op, min_count, fill_value)
|
|
436
|
+
addr = winner_addr(idx_op)
|
|
437
|
+
need = CArray.__min_count_need__(idx_op == :min_index ? :min : :max, min_count, 0)
|
|
438
|
+
addr[count_not_masked.lt(need)] = UNDEF if need > 0
|
|
439
|
+
src = @source.reshape(@source.elements)
|
|
440
|
+
empty = addr.is_masked
|
|
441
|
+
return src[addr].copy unless empty.any
|
|
442
|
+
# A masked index drops the cell rather than masking it, so an empty tile
|
|
443
|
+
# needs a placeholder address. Pointing it at a masked cell of the source
|
|
444
|
+
# carries the mask through the gather, which a read-only Face (whose copy
|
|
445
|
+
# cannot be written) needs. Otherwise mask the placeholder afterwards.
|
|
446
|
+
if fill_value.nil? && src.has_mask? && (hole = src.is_masked.where).elements > 0
|
|
447
|
+
return src[addr.strip_mask(hole[0])].copy
|
|
448
|
+
end
|
|
449
|
+
out = src[addr.strip_mask(0)].copy
|
|
450
|
+
out[empty] = fill_value.nil? ? UNDEF : fill_value
|
|
451
|
+
out
|
|
452
|
+
end
|
|
453
|
+
|
|
388
454
|
# Per-tile source address of the winner. The tile-local flat index (min_index /
|
|
389
455
|
# max_index) is looked up in a source-address companion tiled the same way, so
|
|
390
456
|
# the tile geometry is reused rather than recomputed. An all-masked tile yields
|
|
391
457
|
# a masked index; it is gathered at 0 and re-masked in the result.
|
|
392
|
-
def winner_addr (idx_op)
|
|
458
|
+
def winner_addr (idx_op, kw = {})
|
|
459
|
+
kw = CAIterator.reduce_keywords(idx_op, kw)
|
|
393
460
|
saddr = CArray.int64(*@source.shape).seq!
|
|
394
461
|
out = nil
|
|
395
462
|
each_region do |strip_ranges, tiles, out_ranges|
|
|
396
463
|
vview = @source[*strip_ranges].block_view(*tiles)
|
|
397
464
|
sview = saddr[*strip_ranges].block_view(*tiles)
|
|
398
|
-
mi = vview.public_send(idx_op, axis: @tile_axes) # tile-local flat index
|
|
465
|
+
mi = vview.public_send(idx_op, axis: @tile_axes, **kw.slice(:min_count)) # tile-local flat index
|
|
399
466
|
grid = (0...@sndim).map { |i| vview.shape[i] }
|
|
400
467
|
cells = tiles.inject(1) { |p, t| p * t }
|
|
401
468
|
safe = mi.copy
|
|
@@ -407,7 +474,7 @@ class CABlockIterator < CAIterator
|
|
|
407
474
|
out ||= CArray.int64(*@shape)
|
|
408
475
|
out[*out_ranges] = picked
|
|
409
476
|
end
|
|
410
|
-
out
|
|
477
|
+
CArray.__reduce_fill__(out || CArray.int64(*@shape), kw[:fill_value])
|
|
411
478
|
end
|
|
412
479
|
|
|
413
480
|
public
|
|
@@ -441,15 +508,17 @@ class CABlockIterator < CAIterator
|
|
|
441
508
|
# Per-tile weighted sum; `weights` is shaped like one full tile (`Π b_i`).
|
|
442
509
|
# At a partial edge tile the weight kernel is sliced to the present cells.
|
|
443
510
|
# @return [CArray]
|
|
444
|
-
def wsum (weights)
|
|
445
|
-
|
|
511
|
+
def wsum (weights, **kw)
|
|
512
|
+
kw = CAIterator.reduce_keywords(:wsum, kw)
|
|
513
|
+
weighted(weights) { |view, w| view.wsum(w, axis: @tile_axes, **kw) }
|
|
446
514
|
end
|
|
447
515
|
|
|
448
516
|
# @overload wmean(weights)
|
|
449
517
|
# Per-tile weighted mean; `weights` shaped like one full tile.
|
|
450
518
|
# @return [CArray]
|
|
451
|
-
def wmean (weights)
|
|
452
|
-
|
|
519
|
+
def wmean (weights, **kw)
|
|
520
|
+
kw = CAIterator.reduce_keywords(:wmean, kw)
|
|
521
|
+
weighted(weights) { |view, w| view.wmean(w, axis: @tile_axes, **kw) }
|
|
453
522
|
end
|
|
454
523
|
|
|
455
524
|
private
|
|
@@ -479,21 +548,22 @@ class CABlockIterator < CAIterator
|
|
|
479
548
|
# Core per-axis order statistics take a single integer axis, so a 1-D tile
|
|
480
549
|
# delegates straight to the C path; a multi-axis tile is materialized per
|
|
481
550
|
# 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.
|
|
551
|
+
# order-stat. Masked cells are left out, as in `CArray#median(axis:)`.
|
|
484
552
|
|
|
485
553
|
# @overload median
|
|
486
554
|
# Per-tile median. @return [CArray]
|
|
487
|
-
def median
|
|
488
|
-
|
|
555
|
+
def median (**kw)
|
|
556
|
+
kw = CAIterator.reduce_keywords(:median, kw)
|
|
557
|
+
order_stat { |v, axis| v.median(axis: axis, **kw) }
|
|
489
558
|
end
|
|
490
559
|
|
|
491
560
|
# @overload percentile(*pers)
|
|
492
561
|
# Per-tile percentile(s). One argument returns one CArray, several an array
|
|
493
562
|
# of CArrays (as `CArray#percentile`).
|
|
494
563
|
# @return [CArray, Array<CArray>]
|
|
495
|
-
def percentile (*pers)
|
|
496
|
-
|
|
564
|
+
def percentile (*pers, **kw)
|
|
565
|
+
kw = CAIterator.reduce_keywords(:percentile, kw)
|
|
566
|
+
order_stat { |v, axis| v.percentile(*pers, axis: axis, **kw) }
|
|
497
567
|
end
|
|
498
568
|
|
|
499
569
|
# @overload quantile
|
|
@@ -585,7 +655,7 @@ class CABlockIterator < CAIterator
|
|
|
585
655
|
# @return [CArray, Enumerator] source-shaped
|
|
586
656
|
def map
|
|
587
657
|
return to_enum(:map) unless block_given?
|
|
588
|
-
pout = CArray.new(padded_source.data_type, padded_source.shape)
|
|
658
|
+
pout = CArray.new(padded_source.data_type, padded_source.shape, bytes: padded_source.bytes)
|
|
589
659
|
pgv = pout.block_view(*@sizes)
|
|
590
660
|
tgv = tile_grid_view
|
|
591
661
|
nils = Array.new(@sndim, nil)
|
|
@@ -640,7 +710,7 @@ class CABlockIterator < CAIterator
|
|
|
640
710
|
tile = tgv[*g, *nils]
|
|
641
711
|
scanned = tile.copy.reshape(tile.elements).public_send(op).reshape(*@sizes)
|
|
642
712
|
unless pout
|
|
643
|
-
pout = CArray.new(scanned.data_type, padded_source.shape)
|
|
713
|
+
pout = CArray.new(scanned.data_type, padded_source.shape, bytes: scanned.bytes)
|
|
644
714
|
pout[] = UNDEF
|
|
645
715
|
pgv = pout.block_view(*@sizes)
|
|
646
716
|
end
|
|
@@ -659,7 +729,7 @@ class CABlockIterator < CAIterator
|
|
|
659
729
|
if pshape == @source.shape.to_a
|
|
660
730
|
@source
|
|
661
731
|
else
|
|
662
|
-
pad = CArray.new(@source.data_type, pshape)
|
|
732
|
+
pad = CArray.new(@source.data_type, pshape, bytes: @source.bytes)
|
|
663
733
|
pad[] = UNDEF
|
|
664
734
|
pad[*@sndim.times.map { |i| 0...@source.shape[i] }] = @source
|
|
665
735
|
pad
|
|
@@ -69,41 +69,33 @@ class CArray
|
|
|
69
69
|
# @return [Boolean, CArray] a scalar, or an array when an axis is given.
|
|
70
70
|
def none (skip_masked: true, **opts)
|
|
71
71
|
return __none_skipna__(**opts) if skip_masked
|
|
72
|
-
|
|
73
|
-
r = __kleene_fold(:any, opts)
|
|
74
|
-
r.is_a?(CArray) ? r.not : (r.equal?(UNDEF) ? UNDEF : !r)
|
|
72
|
+
__kleene_fold(:none, opts)
|
|
75
73
|
end
|
|
76
74
|
|
|
77
75
|
private
|
|
78
76
|
|
|
79
|
-
# Three-valued fold
|
|
80
|
-
#
|
|
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.
|
|
81
82
|
def __kleene_fold (kind, opts)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
r = case kind
|
|
84
|
+
when :any then __any_skipna__(**opts)
|
|
85
|
+
when :all then __all_skipna__(**opts)
|
|
86
|
+
else __none_skipna__(**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
|
data/lib/carray/categorical.rb
CHANGED
|
@@ -297,7 +297,7 @@ class CACategorical < CAObject
|
|
|
297
297
|
|
|
298
298
|
# Face hook: decode a per-cell code into its category label, through the one
|
|
299
299
|
# decode every code-to-label path shares (see #label_at).
|
|
300
|
-
def
|
|
300
|
+
def storage_to_element(raw)
|
|
301
301
|
code = raw.is_a?(String) ? raw.unpack1(UNPACK_FORMAT.fetch(parent.data_type)) : raw
|
|
302
302
|
label_at(code)
|
|
303
303
|
end
|
|
@@ -317,6 +317,27 @@ class CACategorical < CAObject
|
|
|
317
317
|
# an all-false result.
|
|
318
318
|
# @param label [Object] category to match.
|
|
319
319
|
# @return [CArray]
|
|
320
|
+
# @overload resize(*newdim, fill_value: UNDEF)
|
|
321
|
+
# Returns a categorical resized to `newdim` (see {CArray#resize}).
|
|
322
|
+
# The new cells are excluded (UNDEF) unless `fill_value` names one of
|
|
323
|
+
# the labels, since a zero code would read as the first label.
|
|
324
|
+
# @raise [ArgumentError] when `fill_value` is not a label.
|
|
325
|
+
def resize (*newdim, fill_value: UNDEF)
|
|
326
|
+
CACategorical.from_codes(codes.resize(*newdim, fill_value: code_for_fill(fill_value)),
|
|
327
|
+
labels)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# @overload insert_block(offset, bsize, &block)
|
|
331
|
+
# Returns a categorical with a block inserted (see
|
|
332
|
+
# {CArray#insert_block}). The inserted cells are excluded (UNDEF)
|
|
333
|
+
# unless the block returns one of the labels.
|
|
334
|
+
# @raise [ArgumentError] when the block returns something that is not
|
|
335
|
+
# a label.
|
|
336
|
+
def insert_block (offset, bsize, &block)
|
|
337
|
+
fill = block ? code_for_fill(block.call) : UNDEF
|
|
338
|
+
CACategorical.from_codes(codes.insert_block(offset, bsize) { fill }, labels)
|
|
339
|
+
end
|
|
340
|
+
|
|
320
341
|
def eq(label)
|
|
321
342
|
codes.eq(@labels.index(label) || @labels.size)
|
|
322
343
|
end
|
|
@@ -356,11 +377,11 @@ class CACategorical < CAObject
|
|
|
356
377
|
# so `labels.zip(category_sizes.to_a)` always pairs up.
|
|
357
378
|
# @return [CArray]
|
|
358
379
|
def category_sizes
|
|
359
|
-
return @
|
|
380
|
+
return @category_sizes if @category_sizes
|
|
360
381
|
bc = codes.bincount
|
|
361
|
-
out = CArray.new(bc.data_type, [@labels.size]) # new zero-fills
|
|
382
|
+
out = CArray.new(bc.data_type, [@labels.size], bytes: bc.bytes) # new zero-fills
|
|
362
383
|
out[0...bc.elements] = bc if bc.elements > 0
|
|
363
|
-
@
|
|
384
|
+
@category_sizes = out
|
|
364
385
|
out
|
|
365
386
|
end
|
|
366
387
|
|
|
@@ -463,6 +484,14 @@ class CACategorical < CAObject
|
|
|
463
484
|
# that has none. Every code-to-label path goes through here rather than
|
|
464
485
|
# indexing @labels itself, because two decodes are two chances to disagree,
|
|
465
486
|
# and disagreeing is how #unique came to contradict #to_a on the same cell.
|
|
487
|
+
# The code a fill value stands for: UNDEF stays UNDEF, a label is its code.
|
|
488
|
+
def code_for_fill (value)
|
|
489
|
+
return UNDEF if UNDEF.equal?(value)
|
|
490
|
+
code = labels.index(value)
|
|
491
|
+
raise ArgumentError, "#{value.inspect} is not one of the labels #{labels.inspect}" unless code
|
|
492
|
+
code
|
|
493
|
+
end
|
|
494
|
+
|
|
466
495
|
def label_at (code)
|
|
467
496
|
(code < 0 || code >= @labels.size) ? nil : @labels[code]
|
|
468
497
|
end
|
|
@@ -541,13 +570,11 @@ class CACategorical < CAObject
|
|
|
541
570
|
# addresses that order the cells by category (excluded cells last).
|
|
542
571
|
# @return [CArray]
|
|
543
572
|
def sort_addr
|
|
544
|
-
return @
|
|
573
|
+
return @sort_addr if @sort_addr
|
|
545
574
|
n = elements
|
|
546
575
|
k = @labels.size
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
cur = CArray.int64(k > 0 ? k : 0) # segment starts, consumed as cursor
|
|
550
|
-
cur[1..-1] = cs.cumsum.int64[0..-2] if k > 1
|
|
576
|
+
nvalid = category_sizes.sum
|
|
577
|
+
cur = reduceat_index.copy # segment starts, consumed as cursor
|
|
551
578
|
flat = codes.reshape(n)
|
|
552
579
|
seq = CArray.int64(n).seq! # source indices, scattered as payload
|
|
553
580
|
out = CArray.int64(n)
|
|
@@ -566,7 +593,7 @@ class CACategorical < CAObject
|
|
|
566
593
|
excluded = excluded.or(flat.is_masked) if flat.has_mask?
|
|
567
594
|
out[nvalid..-1] = seq[excluded] # excluded cells, source order
|
|
568
595
|
end
|
|
569
|
-
@
|
|
596
|
+
@sort_addr = out
|
|
570
597
|
out
|
|
571
598
|
end
|
|
572
599
|
|
|
@@ -581,17 +608,26 @@ class CACategorical < CAObject
|
|
|
581
608
|
# offsets aligned to {#labels}. Pair with {#category_sizes} for lengths.
|
|
582
609
|
# @return [CArray]
|
|
583
610
|
def reduceat_index
|
|
584
|
-
return @
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
611
|
+
return @reduceat_index if @reduceat_index
|
|
612
|
+
# Every boundary but the last; the copy makes it an entity the
|
|
613
|
+
# reduceat kernels can read directly.
|
|
614
|
+
@reduceat_index = CArray.segment_offsets(lengths: category_sizes)[0...-1].copy
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# @overload categorize(labels: nil, sort_labels: false)
|
|
618
|
+
# Returns a {CACategorical} keyed on the labels, as categorizing any
|
|
619
|
+
# key array does: the labels that occur, in first-appearance order (or
|
|
620
|
+
# sorted with `sort_labels:`), or the fixed `labels:` vocabulary. A
|
|
621
|
+
# category of `self` with no cells does not carry over; a masked cell
|
|
622
|
+
# is excluded.
|
|
623
|
+
# @return [CACategorical]
|
|
624
|
+
def categorize (labels: nil, sort_labels: false)
|
|
625
|
+
if labels || sort_labels
|
|
626
|
+
return label_values.categorize(labels: labels, sort_labels: sort_labels)
|
|
592
627
|
end
|
|
593
|
-
|
|
594
|
-
|
|
628
|
+
# Distinctness rides the codes; only the k labels cross the surface.
|
|
629
|
+
recoded = codes.categorize
|
|
630
|
+
CACategorical.from_codes(recoded.codes, recoded.labels.map { |c| label_at(c) })
|
|
595
631
|
end
|
|
596
632
|
|
|
597
633
|
# @overload inspect
|
|
@@ -629,6 +665,24 @@ class CArray
|
|
|
629
665
|
# @return [CACategorical]
|
|
630
666
|
# @raise [ArgumentError] when explicit `labels` contain
|
|
631
667
|
# duplicates.
|
|
668
|
+
# @raise [CArray::DataTypeError] when `sort_labels: true` discovers
|
|
669
|
+
# the labels of a complex array, which have no order.
|
|
670
|
+
# The order sort_labels: puts two labels in: as sort orders the cells of an
|
|
671
|
+
# array, a Float NaN after every other value; two labels <=> cannot order
|
|
672
|
+
# are refused, by name.
|
|
673
|
+
private def label_order(a, b)
|
|
674
|
+
na = a.is_a?(Float) && a.nan?
|
|
675
|
+
nb = b.is_a?(Float) && b.nan?
|
|
676
|
+
return (na ? 1 : 0) - (nb ? 1 : 0) if na || nb
|
|
677
|
+
c = a <=> b
|
|
678
|
+
if c.nil?
|
|
679
|
+
raise ArgumentError,
|
|
680
|
+
"categorize(sort_labels: true): cannot order the labels " \
|
|
681
|
+
"#{a.inspect} and #{b.inspect}"
|
|
682
|
+
end
|
|
683
|
+
c
|
|
684
|
+
end
|
|
685
|
+
|
|
632
686
|
def categorize(labels: nil, sort_labels: false)
|
|
633
687
|
# Automatic appearance-order vocabulary: one linear pass (C
|
|
634
688
|
# __factorize_appearance__) returns both codes and levels directly, over the
|
|
@@ -636,12 +690,26 @@ class CArray
|
|
|
636
690
|
# lane). Distinctness is the hash-key judgement shared with the discovery
|
|
637
691
|
# family: Float NaN collapses to one category and -0.0 == +0.0, while mixed
|
|
638
692
|
# Integer / Float keys stay distinct (eql?, so 1 and 1.0 are separate
|
|
639
|
-
# categories).
|
|
640
|
-
#
|
|
641
|
-
#
|
|
642
|
-
if labels.nil? &&
|
|
693
|
+
# categories). Complex values are distinct by value with the same NaN and
|
|
694
|
+
# -0.0 rules, and have no order, so sort_labels refuses them. The discovery
|
|
695
|
+
# path below is reserved for an explicit labels list.
|
|
696
|
+
if labels.nil? && sort_labels && complex?
|
|
697
|
+
raise CArray::DataTypeError,
|
|
698
|
+
"categorize(sort_labels: true): a #{data_type_name} value has no " \
|
|
699
|
+
"order (complex numbers are not ordered); without sort_labels: the " \
|
|
700
|
+
"labels are in first-appearance order"
|
|
701
|
+
end
|
|
702
|
+
if labels.nil? && (integer? || float? || complex? || object? || fixlen? || boolean?)
|
|
643
703
|
codes, levels = __factorize_appearance__
|
|
644
|
-
|
|
704
|
+
levels = levels.to_a
|
|
705
|
+
return CACategorical.from_codes(codes, levels) unless sort_labels
|
|
706
|
+
# Sort the k levels as Ruby sorts them, then renumber every code through
|
|
707
|
+
# the rank in one gather. The sentinel of an excluded cell is out of the
|
|
708
|
+
# rank's range, so project makes it UNDEF and from_codes writes it back.
|
|
709
|
+
order = (0...levels.size).sort { |i, j| label_order(levels[i], levels[j]) }
|
|
710
|
+
rank = CArray.new(codes.data_type, [levels.size])
|
|
711
|
+
order.each_with_index { |old, new_code| rank[old] = new_code }
|
|
712
|
+
return CACategorical.from_codes(rank.project(codes), order.map { |i| levels[i] })
|
|
645
713
|
end
|
|
646
714
|
|
|
647
715
|
if labels.nil?
|
|
@@ -650,7 +718,7 @@ class CArray
|
|
|
650
718
|
# masked keys stay excluded), so the non-masked cells are the levels.
|
|
651
719
|
# Only the final list is Ruby, since labels are Ruby objects.
|
|
652
720
|
labels_arr = mask_duplicates[:is_not_masked].to_a
|
|
653
|
-
labels_arr.sort! if sort_labels
|
|
721
|
+
labels_arr.sort! { |a, b| label_order(a, b) } if sort_labels
|
|
654
722
|
else
|
|
655
723
|
labels_arr = labels.respond_to?(:to_a) ? labels.to_a : Array(labels)
|
|
656
724
|
if labels_arr.uniq.size != labels_arr.size
|