carray 3.0.3 → 3.0.5
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 +469 -0
- data/README.md +3 -2
- data/carray.gemspec +1 -1
- data/ext/ca_array_pool.c +25 -1
- data/ext/ca_axis_descriptor.h +27 -24
- data/ext/ca_axis_dispatch.c +14 -26
- data/ext/ca_axis_group.c +27 -27
- data/ext/ca_binop_dispatch.c +1 -29
- data/ext/ca_binop_dispatch.h +0 -1
- data/ext/ca_categorical_iterator.c +231 -153
- data/ext/ca_compare.c +9 -20
- data/ext/ca_composite_dispatch.c +9 -9
- data/ext/ca_group_iter.c +16 -17
- data/ext/ca_kernel_iterator.c +1240 -2000
- data/ext/ca_kernel_iterator.h +502 -519
- data/ext/ca_monop_dispatch.c +82 -59
- data/ext/ca_monop_dispatch.h +1 -0
- data/ext/ca_obj_array.c +152 -45
- data/ext/ca_obj_bincmp.c +25 -21
- data/ext/ca_obj_binop.c +16 -26
- data/ext/ca_obj_bitarray.c +8 -3
- data/ext/ca_obj_bitfield.c +63 -49
- data/ext/ca_obj_block.c +18 -9
- data/ext/ca_obj_byte_swap.c +7 -7
- data/ext/ca_obj_const_string.c +188 -22
- data/ext/ca_obj_face.c +280 -58
- data/ext/ca_obj_face.h +19 -0
- data/ext/ca_obj_fake.c +15 -29
- data/ext/ca_obj_farray.c +6 -3
- data/ext/ca_obj_field.c +30 -8
- data/ext/ca_obj_fixlen_string.c +2 -3
- data/ext/ca_obj_grid.c +455 -134
- data/ext/ca_obj_meld.c +172 -34
- data/ext/ca_obj_moncmp.c +23 -8
- data/ext/ca_obj_monop.c +113 -67
- data/ext/ca_obj_object.c +210 -52
- data/ext/ca_obj_record.c +15 -7
- data/ext/ca_obj_reduce.c +17 -8
- data/ext/ca_obj_refer.c +42 -35
- data/ext/ca_obj_remap.c +30 -9
- data/ext/ca_obj_repeat.c +9 -10
- data/ext/ca_obj_roll.c +31 -10
- data/ext/ca_obj_select.c +48 -14
- data/ext/ca_obj_select_axis.c +15 -47
- data/ext/ca_obj_shift.c +41 -7
- data/ext/ca_obj_stack.c +180 -34
- data/ext/ca_obj_stride.c +190 -134
- data/ext/ca_obj_string.c +80 -4
- data/ext/ca_obj_tile.c +32 -11
- data/ext/ca_obj_time.c +22 -6
- data/ext/ca_obj_timedelta.c +22 -6
- data/ext/ca_obj_transpose.c +13 -7
- data/ext/ca_obj_triop.c +26 -32
- data/ext/ca_obj_window.c +267 -74
- data/ext/ca_op_ipower.c +4 -4
- data/ext/ca_sort_kernels.h +6 -3
- data/ext/ca_sweep_engine.c +2 -2
- data/ext/caframe_csv_reader.c +80 -1
- data/ext/carray.h +214 -75
- data/ext/carray_access.c +200 -99
- data/ext/carray_address_basis.c +343 -87
- data/ext/carray_attribute.c +113 -6
- data/ext/carray_bincount.c +10 -8
- data/ext/carray_broadcast.c +13 -8
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +108 -0
- data/ext/carray_cast.c +375 -96
- data/ext/carray_cast_func.rb +90 -20
- data/ext/carray_class.c +4 -3
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +18 -9
- data/ext/carray_copy.c +51 -14
- data/ext/carray_core.c +150 -55
- data/ext/carray_count.c +56 -20
- data/ext/carray_element.c +81 -33
- data/ext/carray_factorize.c +138 -84
- data/ext/carray_generate.c +199 -52
- data/ext/carray_histogram.c +63 -18
- data/ext/carray_hold.c +12 -15
- data/ext/carray_index_classifier.c +388 -176
- data/ext/carray_internal.h +348 -29
- data/ext/carray_kernels_bincmp.c +4453 -0
- data/ext/carray_kernels_binop.c +11368 -0
- data/ext/carray_kernels_init.c +1734 -0
- data/ext/carray_kernels_moncmp.c +2104 -0
- data/ext/carray_kernels_monop.c +18369 -0
- data/ext/carray_kernels_reduce_aggregate.c +23818 -0
- data/ext/carray_kernels_reduce_boolean.c +406 -0
- data/ext/carray_kernels_reduce_cumulative.c +10086 -0
- data/ext/carray_kernels_reduce_extreme.c +17634 -0
- data/ext/carray_kernels_reduce_variance.c +3849 -0
- data/ext/carray_kernels_scan.c +4302 -0
- data/ext/carray_kernels_search.c +33829 -0
- data/ext/carray_kernels_sort.c +10897 -0
- data/ext/carray_kernels_triop.c +1864 -0
- data/ext/carray_key_windows.c +220 -0
- data/ext/carray_lazy.c +170 -111
- data/ext/carray_mask.c +337 -94
- data/ext/carray_median_percentile.c +82 -28
- data/ext/carray_memory_view.c +669 -561
- data/ext/carray_operator.c +129 -52
- data/ext/carray_order.c +604 -77
- data/ext/carray_partition.c +28 -27
- data/ext/carray_random.c +85 -31
- data/ext/carray_round.c +273 -0
- data/ext/carray_scatter.c +87 -45
- data/ext/carray_segment.c +3 -3
- data/ext/carray_slab.c +123 -37
- data/ext/carray_sort.c +259 -51
- data/ext/carray_sort_kernel.c +5 -7
- data/ext/carray_struct.c +54 -61
- data/ext/carray_test.c +26 -5
- data/ext/carray_time_text.c +4 -2
- data/ext/carray_undef.c +4 -0
- data/ext/carray_utils.c +309 -66
- data/ext/mk_call_cfunc.rb +19 -2
- data/ext/mkkernel.rb +403 -350
- data/ext/ruby_carray.c +10 -4
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +139 -22
- data/lib/carray/autoload_carray.rb +8 -31
- data/lib/carray/autoload_method_extension.rb +14 -5
- data/lib/carray/axis_group.rb +255 -106
- data/lib/carray/basics.rb +105 -17
- data/lib/carray/bincount_nd.rb +12 -2
- data/lib/carray/block_iterator.rb +126 -23
- data/lib/carray/boolean_reduce.rb +17 -17
- data/lib/carray/categorical.rb +195 -59
- data/lib/carray/categorical_iterator.rb +132 -29
- data/lib/carray/complex.rb +5 -5
- data/lib/carray/conditional.rb +67 -18
- data/lib/carray/const_string.rb +24 -5
- data/lib/carray/construct.rb +15 -11
- data/lib/carray/data_type_extension.rb +9 -1
- data/lib/carray/distance_limit.rb +77 -0
- data/lib/carray/fixlen_string.rb +48 -3
- data/lib/carray/frame/column_verbs.rb +71 -0
- data/lib/carray/frame/concat.rb +17 -3
- data/lib/carray/frame/convert.rb +69 -0
- data/lib/carray/frame/csv_parser.rb +111 -17
- data/lib/carray/frame/describe.rb +3 -2
- data/lib/carray/frame/frame.rb +252 -28
- data/lib/carray/frame/group.rb +603 -101
- data/lib/carray/frame/io.rb +117 -28
- data/lib/carray/frame/join.rb +312 -51
- data/lib/carray/frame/records.rb +83 -7
- data/lib/carray/frame/reshape.rb +42 -35
- data/lib/carray/frame/sort.rb +7 -19
- data/lib/carray/frame/verbs.rb +188 -48
- data/lib/carray/frame/windows.rb +117 -0
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +42 -6
- data/lib/carray/fusion.rb +40 -16
- data/lib/carray/histogram.rb +6 -1
- data/lib/carray/inspect.rb +31 -8
- data/lib/carray/iterator.rb +85 -7
- data/lib/carray/key_windows.rb +620 -0
- data/lib/carray/lazy.rb +217 -183
- data/lib/carray/mask_gap_fill.rb +63 -57
- data/lib/carray/meld_reduce.rb +12 -14
- data/lib/carray/methods/bin.rb +26 -18
- data/lib/carray/methods/bincount.rb +16 -0
- data/lib/carray/methods/bit_string.rb +1 -1
- data/lib/carray/methods/choose.rb +16 -3
- data/lib/carray/methods/composition.rb +3 -1
- data/lib/carray/methods/gather_nd.rb +14 -5
- data/lib/carray/methods/insert_block.rb +2 -2
- data/lib/carray/methods/is_between.rb +59 -0
- data/lib/carray/methods/is_in.rb +61 -37
- data/lib/carray/methods/join.rb +1 -6
- data/lib/carray/methods/lag.rb +49 -0
- data/lib/carray/methods/locate_addr.rb +47 -51
- data/lib/carray/methods/mode.rb +1 -1
- data/lib/carray/methods/nunique.rb +11 -6
- data/lib/carray/methods/resize.rb +3 -3
- data/lib/carray/methods/value_counts.rb +4 -11
- data/lib/carray/record_order.rb +3 -3
- data/lib/carray/rng.rb +3 -0
- data/lib/carray/runtime.rb +53 -8
- data/lib/carray/segment_iterator.rb +323 -88
- data/lib/carray/serialize.rb +45 -14
- data/lib/carray/slab_iterator.rb +50 -0
- data/lib/carray/stack.rb +15 -12
- data/lib/carray/string.rb +113 -5
- data/lib/carray/string_operation_extension.rb +21 -6
- data/lib/carray/struct.rb +9 -21
- data/lib/carray/struct_builder.rb +24 -11
- data/lib/carray/time.rb +819 -158
- data/lib/carray/window_iterator.rb +122 -16
- data/lib/carray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +4 -0
- data/yard-stubs/ca_obj_grid.rb +21 -10
- data/yard-stubs/ca_obj_refer.rb +2 -1
- data/yard-stubs/ca_obj_roll.rb +2 -2
- data/yard-stubs/ca_obj_shift.rb +5 -1
- data/yard-stubs/ca_obj_tile.rb +1 -1
- data/yard-stubs/ca_obj_transpose.rb +5 -4
- data/yard-stubs/ca_obj_window.rb +8 -3
- data/yard-stubs/carray_access.rb +6 -1
- data/yard-stubs/carray_attribute.rb +3 -6
- data/yard-stubs/carray_cast.rb +12 -0
- data/yard-stubs/carray_conversion.rb +7 -2
- data/yard-stubs/carray_copy.rb +4 -2
- data/yard-stubs/carray_count.rb +4 -4
- data/yard-stubs/carray_element.rb +17 -10
- data/yard-stubs/carray_generate.rb +21 -1
- data/yard-stubs/carray_mask.rb +32 -3
- data/yard-stubs/carray_math.rb +41 -5
- data/yard-stubs/carray_median_percentile.rb +5 -2
- data/yard-stubs/carray_memory_view.rb +40 -23
- data/yard-stubs/carray_order.rb +129 -22
- data/yard-stubs/carray_random.rb +13 -4
- data/yard-stubs/carray_scatter.rb +17 -10
- data/yard-stubs/carray_slab.rb +9 -2
- data/yard-stubs/carray_sort.rb +6 -5
- data/yard-stubs/carray_test.rb +5 -3
- metadata +25 -3
- data/lib/carray/arrow_tensor.rb +0 -401
- data/lib/carray/table.rb +0 -108
data/lib/carray/basics.rb
CHANGED
|
@@ -13,9 +13,19 @@
|
|
|
13
13
|
|
|
14
14
|
class CArray
|
|
15
15
|
|
|
16
|
+
# The order sort puts two object values in (ca_obj_order_cmp in C): a
|
|
17
|
+
# Float NaN after every other value, NaN ties NaN, other pairs by `<=>`.
|
|
18
|
+
# nil when `<=>` cannot order the pair; the caller says what refused.
|
|
19
|
+
private_class_method def self.object_order_cmp (a, b)
|
|
20
|
+
a_nan = a.is_a?(Float) && a.nan?
|
|
21
|
+
b_nan = b.is_a?(Float) && b.nan?
|
|
22
|
+
return (a_nan ? 1 : 0) - (b_nan ? 1 : 0) if a_nan || b_nan
|
|
23
|
+
a <=> b
|
|
24
|
+
end
|
|
25
|
+
|
|
16
26
|
# reshape / flatten / transpose! are defined in C
|
|
17
27
|
# (ext/ca_obj_refer.c and ext/ca_obj_transpose.c). The bare size-1
|
|
18
|
-
# insertion primitive is `
|
|
28
|
+
# insertion primitive is `insert_size_one_axes` (C); `insert_axis` below
|
|
19
29
|
# is the user-facing method that owns the name and adds `repeat:`.
|
|
20
30
|
|
|
21
31
|
# @overload insert_axis(*positions, repeat: nil)
|
|
@@ -58,7 +68,7 @@ class CArray
|
|
|
58
68
|
|
|
59
69
|
# No repeat: -> plain size-1 insertion. The source-frame C primitive
|
|
60
70
|
# handles normalization, range check and multiplicity directly.
|
|
61
|
-
return
|
|
71
|
+
return insert_size_one_axes(*flat) if repeat.nil?
|
|
62
72
|
|
|
63
73
|
# Source frame: each position names the source axis the new axis goes
|
|
64
74
|
# before. Gaps live in [0, ndim] (ndim = append at end); negatives count
|
|
@@ -98,7 +108,7 @@ class CArray
|
|
|
98
108
|
final = {}
|
|
99
109
|
order.each_with_index { |i, k| final[i] = gaps[i] + k }
|
|
100
110
|
|
|
101
|
-
inter =
|
|
111
|
+
inter = insert_size_one_axes(*order.map { |i| gaps[i] })
|
|
102
112
|
return inter unless order.any? { |i| reps[i] > 1 }
|
|
103
113
|
|
|
104
114
|
shp = inter.shape
|
|
@@ -107,13 +117,15 @@ class CArray
|
|
|
107
117
|
end
|
|
108
118
|
|
|
109
119
|
# @overload drop_axis
|
|
110
|
-
# Returns a view of `self` with every size-1 axis dropped.
|
|
120
|
+
# Returns a view of `self` with every size-1 axis dropped. When every
|
|
121
|
+
# axis has size 1, one axis of size 1 is kept.
|
|
111
122
|
# @return [CArray] view with reduced `ndim`.
|
|
112
123
|
def drop_axis
|
|
113
124
|
if ndim == 1
|
|
114
125
|
return self[]
|
|
115
126
|
else
|
|
116
127
|
newdim = shape.reject{|x| x == 1 }
|
|
128
|
+
newdim = [1] if newdim.empty? # every axis has size 1: keep one
|
|
117
129
|
return ( ndim != newdim.size ) ? reshape(*newdim) : self[]
|
|
118
130
|
end
|
|
119
131
|
end
|
|
@@ -146,7 +158,7 @@ class CArray
|
|
|
146
158
|
# out of range (a time array reads its bounds as times, a record array
|
|
147
159
|
# orders by its declared members, a Face that cannot be ordered refuses),
|
|
148
160
|
# and its store writes the bound or the fill value into them.
|
|
149
|
-
private def
|
|
161
|
+
private def face_clip (min, max, fill_value, lfill, ufill)
|
|
150
162
|
lfill = fill_value if lfill.nil?
|
|
151
163
|
ufill = fill_value if ufill.nil?
|
|
152
164
|
out = copy
|
|
@@ -158,8 +170,8 @@ class CArray
|
|
|
158
170
|
# An array of +shape+ of the same kind as self: its data type and cell
|
|
159
171
|
# width, or its Face over a zero storage. Nothing is written into it, so
|
|
160
172
|
# it can be built for a read-only Face too.
|
|
161
|
-
private def
|
|
162
|
-
storage =
|
|
173
|
+
private def blank_like (shape)
|
|
174
|
+
storage = face_storage
|
|
163
175
|
out = CArray.new(storage.data_type, shape, bytes: storage.bytes)
|
|
164
176
|
face? ? out.face_lift(self) : out
|
|
165
177
|
end
|
|
@@ -179,7 +191,13 @@ class CArray
|
|
|
179
191
|
# @param src [CArray] source array.
|
|
180
192
|
# @return [self]
|
|
181
193
|
# @raise [ArgumentError] when `offset.length != self.ndim`.
|
|
194
|
+
# @raise [FrozenError] when `self` is frozen.
|
|
182
195
|
def paste (offset, src)
|
|
196
|
+
# The write goes through a window view, which is not frozen itself:
|
|
197
|
+
# refuse a frozen self here, as a write into it directly is refused.
|
|
198
|
+
if frozen?
|
|
199
|
+
raise FrozenError.new("can't modify frozen #{self.class}: #{inspect}", receiver: self)
|
|
200
|
+
end
|
|
183
201
|
raise ArgumentError, "offset length must equal ndim" if offset.length != ndim
|
|
184
202
|
# A zero-length source covers no cell, so there is nothing to write --
|
|
185
203
|
# and the empty range it would ask for is not a window CAWindow can
|
|
@@ -300,8 +318,7 @@ class CArray
|
|
|
300
318
|
# @overload clip(min, max = nil, fill_value = nil, lfill: nil, ufill: nil)
|
|
301
319
|
# Returns `self` with every element clamped to `[min, max]`.
|
|
302
320
|
#
|
|
303
|
-
# Either bound may be `nil` for a one-sided clip
|
|
304
|
-
# dispatches to the `pmax` / `pmin` binop kernels. When
|
|
321
|
+
# Either bound may be `nil` for a one-sided clip. When
|
|
305
322
|
# `fill_value` (or `lfill` / `ufill`) is given, out-of-range
|
|
306
323
|
# cells are replaced by the fill instead of clamped -- pass
|
|
307
324
|
# `UNDEF` to mask that end. `fill_value` is sugar for symmetric
|
|
@@ -310,7 +327,11 @@ class CArray
|
|
|
310
327
|
#
|
|
311
328
|
# `min` may not exceed `max` (in any cell, when a bound is an
|
|
312
329
|
# array); equal bounds give every unmasked cell that value.
|
|
313
|
-
# A NaN
|
|
330
|
+
# A NaN cell of `self` stays NaN, one-sided or two-sided (unlike
|
|
331
|
+
# `pmax` / `pmin`, which answer the other operand for it).
|
|
332
|
+
# A NaN in a bound array leaves that cell unbounded on that side,
|
|
333
|
+
# as `nil` does for the whole side. A bound is never UNDEF or
|
|
334
|
+
# masked: a missing bound is not read as either, so fill it first.
|
|
314
335
|
#
|
|
315
336
|
# Boundary is strict `[min, max]`: values equal to a bound
|
|
316
337
|
# remain unchanged in both the clamped and filled variants.
|
|
@@ -325,7 +346,8 @@ class CArray
|
|
|
325
346
|
# @param ufill [Object, nil] override above-range fill.
|
|
326
347
|
# @return [CArray] new CArray with clamped or filled values.
|
|
327
348
|
# @raise [ArgumentError] when both `min` and `max` are `nil`,
|
|
328
|
-
# or when `min`
|
|
349
|
+
# when either is `UNDEF` or has a masked cell, or when `min`
|
|
350
|
+
# exceeds `max`.
|
|
329
351
|
# @example
|
|
330
352
|
# a.clip(0, 10) # strict clamp
|
|
331
353
|
# a.clip(0, 10, -1) # both ends -> -1
|
|
@@ -334,7 +356,16 @@ class CArray
|
|
|
334
356
|
if min.nil? && max.nil?
|
|
335
357
|
raise ArgumentError, "clip: at least one of (min, max) must be given"
|
|
336
358
|
end
|
|
337
|
-
|
|
359
|
+
[min, max].each do |bound|
|
|
360
|
+
undefined = UNDEF.equal?(bound) ||
|
|
361
|
+
(bound.is_a?(CArray) && bound.any_masked?) ||
|
|
362
|
+
(bound.is_a?(Array) && bound.flatten.any? { |v| UNDEF.equal?(v) })
|
|
363
|
+
next unless undefined
|
|
364
|
+
raise ArgumentError, "clip: a bound can not be UNDEF or masked " \
|
|
365
|
+
"(fill the bound first; a NaN bound leaves its cell " \
|
|
366
|
+
"unbounded, and UNDEF as the fill masks the cells out of range)"
|
|
367
|
+
end
|
|
368
|
+
return face_clip(min, max, fill_value, lfill, ufill) if face?
|
|
338
369
|
if !min.nil? && !max.nil?
|
|
339
370
|
reversed = if min.is_a?(CArray) then min.gt(max).any
|
|
340
371
|
elsif max.is_a?(CArray) then max.lt(min).any
|
|
@@ -350,20 +381,31 @@ class CArray
|
|
|
350
381
|
ufill = fill_value if ufill.nil?
|
|
351
382
|
|
|
352
383
|
if lfill.nil? && ufill.nil?
|
|
353
|
-
return
|
|
354
|
-
return
|
|
355
|
-
return
|
|
384
|
+
return clamp_between(min, max) if !min.nil? && !max.nil?
|
|
385
|
+
return clamp_below(min) if max.nil?
|
|
386
|
+
return clamp_above(max)
|
|
356
387
|
end
|
|
357
388
|
|
|
358
389
|
# A side without a fill still clamps.
|
|
359
390
|
out = self.copy
|
|
360
|
-
out = out.
|
|
361
|
-
out = out.
|
|
391
|
+
out = out.clamp_below(min) if !min.nil? && lfill.nil?
|
|
392
|
+
out = out.clamp_above(max) if !max.nil? && ufill.nil?
|
|
362
393
|
out[self.lt(min)] = lfill unless min.nil? || lfill.nil?
|
|
363
394
|
out[self.gt(max)] = ufill unless max.nil? || ufill.nil?
|
|
364
395
|
out
|
|
365
396
|
end
|
|
366
397
|
|
|
398
|
+
# A one-sided clip of a float array passes a NaN cell through, as the
|
|
399
|
+
# two-sided one does: the open side is a NaN bound, which leaves it
|
|
400
|
+
# unbounded. pmax / pmin would answer the bound for a NaN cell.
|
|
401
|
+
protected def clamp_below (min)
|
|
402
|
+
float? ? clamp_between(min, Float::NAN) : pmax(min)
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
protected def clamp_above (max)
|
|
406
|
+
float? ? clamp_between(Float::NAN, max) : pmin(max)
|
|
407
|
+
end
|
|
408
|
+
|
|
367
409
|
# `contains` was retired in favour of {#is_in} (value-hash membership).
|
|
368
410
|
# `a.contains(v1, v2)` -> `a.is_in([v1, v2])`;
|
|
369
411
|
# `a.contains(v1, v2, axis: k)` -> `a.is_in([v1, v2]).any(axis: k)`.
|
|
@@ -549,3 +591,49 @@ class Enumerator::ArithmeticSequence
|
|
|
549
591
|
return CA_OBJECT(to_a)
|
|
550
592
|
end
|
|
551
593
|
end
|
|
594
|
+
|
|
595
|
+
class CArray
|
|
596
|
+
# map_slab / reduce_slab over a Face answer in that Face, as its other
|
|
597
|
+
# reductions do: each block value is written into a blank of the Face
|
|
598
|
+
# (which reads a CATime::Element, a string, a Face array ...) and handed
|
|
599
|
+
# to the slab engine as the storage it became; the result is lifted back
|
|
600
|
+
# at the end. A read-only Face has no blank to write into, so its values
|
|
601
|
+
# are collected as the surface objects they are. A `data_type:` from the
|
|
602
|
+
# caller is the caller's, and passes through.
|
|
603
|
+
module FaceSlabResult
|
|
604
|
+
def map_slab (**kw, &block)
|
|
605
|
+
return super unless face? && kw[:data_type].nil? && block
|
|
606
|
+
return super(**kw, data_type: :object, &block) if read_only?
|
|
607
|
+
out = super(**kw, data_type: face_storage.data_type) { |slab|
|
|
608
|
+
face_slab_storage(block.call(slab), slab.shape)
|
|
609
|
+
}
|
|
610
|
+
out.face_lift(self)
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def reduce_slab (**kw, &block)
|
|
614
|
+
return super unless face? && kw[:data_type].nil? && block
|
|
615
|
+
return super(**kw, data_type: :object, &block) if read_only?
|
|
616
|
+
fiber = kw.key?(:init)
|
|
617
|
+
init = kw.delete(:init)
|
|
618
|
+
out = super(**kw, data_type: face_storage.data_type) { |slab|
|
|
619
|
+
value = if fiber
|
|
620
|
+
slab.to_a.flatten.inject(init) { |acc, e| block.call(acc, e) }
|
|
621
|
+
else
|
|
622
|
+
block.call(slab)
|
|
623
|
+
end
|
|
624
|
+
face_slab_storage(value, [1])[0]
|
|
625
|
+
}
|
|
626
|
+
out.face_lift(self)
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
private
|
|
630
|
+
|
|
631
|
+
def face_slab_storage (value, shape)
|
|
632
|
+
cell = blank_like(shape)
|
|
633
|
+
cell[] = value
|
|
634
|
+
cell.__send__(:face_storage)
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
prepend FaceSlabResult
|
|
639
|
+
end
|
data/lib/carray/bincount_nd.rb
CHANGED
|
@@ -78,8 +78,13 @@ class CArray
|
|
|
78
78
|
# accumulators; `nil` for pure counts (int64).
|
|
79
79
|
# @return [BincountND]
|
|
80
80
|
def initialize (lengths:, fiber_shape: [], weights_data_type: nil)
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
raise ArgumentError, "lengths must be a non-empty list" if lengths.empty?
|
|
82
|
+
lengths.each_with_index do |l, k|
|
|
83
|
+
unless l.is_a?(Integer)
|
|
84
|
+
raise TypeError, "lengths[#{k}] must be an Integer (got #{l.inspect})"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
@lengths = lengths.dup
|
|
83
88
|
@lengths.each_with_index do |l, k|
|
|
84
89
|
raise ArgumentError, "lengths[#{k}] must be >= 1" if l < 1
|
|
85
90
|
end
|
|
@@ -209,6 +214,7 @@ class CArray
|
|
|
209
214
|
|
|
210
215
|
if weights
|
|
211
216
|
raise ArgumentError, "weights given but accumulator is unweighted" unless @weighted
|
|
217
|
+
# coerce: the accumulator's type, declared by weights_data_type:
|
|
212
218
|
weights = CArray.wrap_readonly(weights, @counts_data_type)
|
|
213
219
|
expected_w_shape = chunk.shape.dup
|
|
214
220
|
expected_w_shape.delete_at(channel_ax)
|
|
@@ -344,6 +350,10 @@ class CArray
|
|
|
344
350
|
# @return [BincountND]
|
|
345
351
|
def bincount_nd (lengths:, axis: [-2, -1], weights: nil)
|
|
346
352
|
raise ArgumentError, "lengths must be an Array of per-dim extents" unless lengths.is_a?(Array)
|
|
353
|
+
axis = [-2, -1] if axis.nil?
|
|
354
|
+
unless axis.is_a?(Array) && axis.size == 2
|
|
355
|
+
raise ArgumentError, "bincount_nd: axis: is a [sample, channel] pair (got #{axis.inspect})"
|
|
356
|
+
end
|
|
347
357
|
sample_ax = normalize_axis(axis[0], "bincount_nd sample axis")
|
|
348
358
|
channel_ax = normalize_axis(axis[1], "bincount_nd channel axis")
|
|
349
359
|
fiber_shape = shape.dup
|
|
@@ -302,28 +302,47 @@ class CABlockIterator < CAIterator
|
|
|
302
302
|
# @param fill_value [Object, nil] value to put in place of a masked
|
|
303
303
|
# result instead of leaving it masked.
|
|
304
304
|
# @return [CArray] tile-grid shaped
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
kw[:min_count] = min_count unless min_count.nil?
|
|
313
|
-
kw[:fill_value] = fill_value unless fill_value.nil?
|
|
314
|
-
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)
|
|
315
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)
|
|
316
317
|
end
|
|
317
318
|
|
|
318
|
-
[:all, :any]
|
|
319
|
-
|
|
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))
|
|
320
323
|
end
|
|
321
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
|
+
|
|
322
340
|
# A Face's minimum and maximum are cells of the Face, so they are gathered
|
|
323
341
|
# from the source by the winner's address. Assembling the per-region results
|
|
324
342
|
# instead copies the Face's storage into an array that no longer knows it is
|
|
325
343
|
# one: a CATime came back as 8-byte strings.
|
|
326
344
|
FACE_WINNER_INDEX = { min: :min_index, max: :max_index }.freeze
|
|
345
|
+
Ractor.make_shareable(FACE_WINNER_INDEX)
|
|
327
346
|
private_constant :FACE_WINNER_INDEX
|
|
328
347
|
|
|
329
348
|
# ---- count family and elements ----------------------------------------
|
|
@@ -362,11 +381,11 @@ class CABlockIterator < CAIterator
|
|
|
362
381
|
# @return [CArray]
|
|
363
382
|
def count_masked (**kw)
|
|
364
383
|
kw = CAIterator.reduce_keywords(:count_masked, kw)
|
|
365
|
-
need = CArray.
|
|
384
|
+
need = CArray.__present_cells_needed_per_result_cell__(:count_masked, kw[:min_count], 0)
|
|
366
385
|
out = elements - count_not_masked
|
|
367
386
|
# A count of the mask reads every cell of the tile.
|
|
368
387
|
out[elements.lt(need)] = UNDEF if need > 0
|
|
369
|
-
CArray.
|
|
388
|
+
CArray.__fill_undefined_result_cells__(out, kw[:fill_value])
|
|
370
389
|
end
|
|
371
390
|
|
|
372
391
|
# @overload elements
|
|
@@ -434,13 +453,14 @@ class CABlockIterator < CAIterator
|
|
|
434
453
|
# and with `min_count` a tile with too few present cells, is a masked cell.
|
|
435
454
|
def face_winner (idx_op, min_count, fill_value)
|
|
436
455
|
addr = winner_addr(idx_op)
|
|
437
|
-
need = CArray.
|
|
456
|
+
need = CArray.__present_cells_needed_per_result_cell__(idx_op == :min_index ? :min : :max, min_count, 0)
|
|
438
457
|
addr[count_not_masked.lt(need)] = UNDEF if need > 0
|
|
439
458
|
src = @source.reshape(@source.elements)
|
|
440
459
|
empty = addr.is_masked
|
|
441
460
|
return src[addr].copy unless empty.any
|
|
442
|
-
# A masked index
|
|
443
|
-
#
|
|
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
|
|
444
464
|
# carries the mask through the gather, which a read-only Face (whose copy
|
|
445
465
|
# cannot be written) needs. Otherwise mask the placeholder afterwards.
|
|
446
466
|
if fill_value.nil? && src.has_mask? && (hole = src.is_masked.where).elements > 0
|
|
@@ -474,7 +494,7 @@ class CABlockIterator < CAIterator
|
|
|
474
494
|
out ||= CArray.int64(*@shape)
|
|
475
495
|
out[*out_ranges] = picked
|
|
476
496
|
end
|
|
477
|
-
CArray.
|
|
497
|
+
CArray.__fill_undefined_result_cells__(out || CArray.int64(*@shape), kw[:fill_value])
|
|
478
498
|
end
|
|
479
499
|
|
|
480
500
|
public
|
|
@@ -691,24 +711,81 @@ class CABlockIterator < CAIterator
|
|
|
691
711
|
# @!method cumcount
|
|
692
712
|
# Per-tile running count of present cells (int64), source-shaped.
|
|
693
713
|
# @return [CArray]
|
|
694
|
-
|
|
695
|
-
|
|
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) }
|
|
696
755
|
end
|
|
697
756
|
|
|
698
757
|
private
|
|
699
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
|
+
|
|
700
777
|
# Drive a within-tile segment scan: scan each tile flattened row-major and
|
|
701
778
|
# scatter back to a source-shaped result. The output data type is taken from the
|
|
702
779
|
# first tile's scan (all tiles share it); the padded margin is UNDEF and the
|
|
703
780
|
# OOB cells of a partial edge tile are cropped from the result.
|
|
704
|
-
def block_scan (op)
|
|
781
|
+
def block_scan (op, *args)
|
|
705
782
|
tgv = tile_grid_view
|
|
706
783
|
nils = Array.new(@sndim, nil)
|
|
707
784
|
pout = nil
|
|
708
785
|
pgv = nil
|
|
709
786
|
CArray.each_index(*@shape) do |*g|
|
|
710
787
|
tile = tgv[*g, *nils]
|
|
711
|
-
scanned = tile.copy.reshape(tile.elements).public_send(op).reshape(*@sizes)
|
|
788
|
+
scanned = tile.copy.reshape(tile.elements).public_send(op, *args).reshape(*@sizes)
|
|
712
789
|
unless pout
|
|
713
790
|
pout = CArray.new(scanned.data_type, padded_source.shape, bytes: scanned.bytes)
|
|
714
791
|
pout[] = UNDEF
|
|
@@ -719,6 +796,24 @@ class CABlockIterator < CAIterator
|
|
|
719
796
|
pout[*@sndim.times.map { |i| 0...@source.shape[i] }].copy
|
|
720
797
|
end
|
|
721
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
|
+
|
|
722
817
|
# A padded entity whose interior holds the source and whose partial-edge
|
|
723
818
|
# margin is masked (UNDEF), sized to exactly cover the ceil tile grid. When
|
|
724
819
|
# the source already divides evenly this is the source itself (no copy), so
|
|
@@ -729,7 +824,7 @@ class CABlockIterator < CAIterator
|
|
|
729
824
|
if pshape == @source.shape.to_a
|
|
730
825
|
@source
|
|
731
826
|
else
|
|
732
|
-
pad =
|
|
827
|
+
pad = @source.__send__(:blank_like, pshape) # a Face stays one
|
|
733
828
|
pad[] = UNDEF
|
|
734
829
|
pad[*@sndim.times.map { |i| 0...@source.shape[i] }] = @source
|
|
735
830
|
pad
|
|
@@ -742,6 +837,14 @@ class CABlockIterator < CAIterator
|
|
|
742
837
|
def tile_grid_view
|
|
743
838
|
@tile_grid_view ||= padded_source.block_view(*@sizes)
|
|
744
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)
|
|
745
848
|
end
|
|
746
849
|
|
|
747
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,8 +68,8 @@ 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
|
-
|
|
71
|
+
return none_skipping_masked(**opts) if skip_masked
|
|
72
|
+
kleene_fold(:none, opts)
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
private
|
|
@@ -79,11 +79,11 @@ class CArray
|
|
|
79
79
|
# any, a false for all, a true for none); elsewhere a masked cell leaves the
|
|
80
80
|
# answer undetermined. Going through that reduction checks the data type,
|
|
81
81
|
# the axes and the keywords exactly as `skip_masked: true` does.
|
|
82
|
-
def
|
|
82
|
+
def kleene_fold (kind, opts)
|
|
83
83
|
r = case kind
|
|
84
|
-
when :any then
|
|
85
|
-
when :all then
|
|
86
|
-
else
|
|
84
|
+
when :any then any_skipping_masked(**opts)
|
|
85
|
+
when :all then all_skipping_masked(**opts)
|
|
86
|
+
else none_skipping_masked(**opts)
|
|
87
87
|
end
|
|
88
88
|
cm = count_masked(**opts)
|
|
89
89
|
if r.is_a?(CArray)
|