carray 3.0.1 → 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 +824 -0
- data/README.md +5 -6
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +258 -4
- data/ext/ca_axis_group.c +368 -184
- data/ext/ca_binop_dispatch.c +54 -32
- data/ext/ca_binop_dispatch.h +1 -0
- data/ext/ca_categorical_iterator.c +90 -41
- 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 +960 -245
- data/ext/ca_kernel_iterator.h +352 -181
- data/ext/ca_monop_dispatch.c +4 -4
- data/ext/ca_monop_dispatch.h +4 -0
- data/ext/ca_obj_array.c +194 -42
- 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 +5 -4
- data/ext/ca_obj_byte_swap.c +40 -40
- data/ext/ca_obj_const_string.c +180 -50
- data/ext/ca_obj_face.c +428 -62
- data/ext/ca_obj_face.h +107 -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 +39 -26
- data/ext/ca_obj_grid.c +181 -124
- data/ext/ca_obj_meld.c +235 -55
- data/ext/ca_obj_moncmp.c +61 -61
- data/ext/ca_obj_monop.c +125 -107
- data/ext/ca_obj_object.c +551 -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 +88 -75
- data/ext/ca_obj_select_axis.c +58 -60
- data/ext/ca_obj_shift.c +22 -102
- data/ext/ca_obj_stack.c +223 -123
- data/ext/ca_obj_stride.c +267 -35
- data/ext/ca_obj_string.c +25 -22
- 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 +157 -29
- data/ext/ca_op_ipower.c +25 -13
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +385 -198
- data/ext/ca_sweep_engine.h +31 -23
- 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 +90 -28
- data/ext/carray_access.c +487 -231
- data/ext/carray_address_basis.c +731 -0
- data/ext/carray_attribute.c +45 -3
- data/ext/carray_bincount.c +32 -2
- data/ext/carray_broadcast.c +49 -17
- data/ext/carray_call_cfunc.c +667 -511
- data/ext/carray_cast.c +1556 -247
- data/ext/carray_cast_func.rb +34 -21
- data/ext/carray_conversion.c +197 -73
- data/ext/carray_copy.c +84 -38
- data/ext/carray_core.c +591 -119
- data/ext/carray_count.c +148 -96
- data/ext/carray_element.c +18 -27
- data/ext/carray_factorize.c +604 -291
- 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 +400 -1
- data/ext/carray_lazy.c +834 -79
- data/ext/carray_loop.c +88 -66
- data/ext/carray_mask.c +705 -324
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +263 -63
- data/ext/carray_memory_view.c +254 -248
- data/ext/carray_operator.c +1093 -363
- data/ext/carray_order.c +133 -69
- data/ext/carray_partition.c +102 -56
- data/ext/carray_random.c +582 -204
- data/ext/carray_scatter.c +308 -65
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +183 -70
- data/ext/carray_sort.c +235 -240
- 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 +103 -118
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1349 -851
- data/ext/ruby_carray.c +17 -2
- 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 +24 -3
- data/lib/carray/autoload_method_extension.rb +18 -0
- data/lib/carray/axis_group.rb +245 -20
- data/lib/carray/basics.rb +95 -25
- data/lib/carray/bincount_nd.rb +6 -0
- data/lib/carray/block_iterator.rb +200 -54
- data/lib/carray/boolean_reduce.rb +21 -29
- data/lib/carray/categorical.rb +244 -59
- data/lib/carray/categorical_iterator.rb +391 -757
- data/lib/carray/complex.rb +36 -29
- data/lib/carray/conditional.rb +94 -7
- data/lib/carray/const_string.rb +177 -27
- data/lib/carray/construct.rb +40 -0
- data/lib/carray/data_type_extension.rb +3 -0
- data/lib/carray/data_type_limits.rb +91 -0
- data/lib/carray/fixlen_string.rb +1 -1
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +13 -7
- data/lib/carray/frame/csv_parser.rb +590 -76
- data/lib/carray/frame/describe.rb +79 -0
- data/lib/carray/frame/frame.rb +205 -35
- data/lib/carray/frame/group.rb +199 -40
- data/lib/carray/frame/io.rb +452 -60
- data/lib/carray/frame/join.rb +134 -46
- data/lib/carray/frame/records.rb +54 -19
- data/lib/carray/frame/reshape.rb +448 -0
- data/lib/carray/frame/sort.rb +26 -2
- data/lib/carray/frame/verbs.rb +726 -56
- 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 +71 -20
- data/lib/carray/iterator.rb +157 -2
- data/lib/carray/lazy.rb +54 -67
- data/lib/carray/mask_gap_fill.rb +66 -50
- 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/discovery_along.rb +74 -0
- data/lib/carray/methods/factorize.rb +50 -0
- 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 +130 -6
- data/lib/carray/methods/join.rb +1 -1
- data/lib/carray/methods/locate_addr.rb +235 -21
- data/lib/carray/methods/mask_duplicates.rb +35 -1
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/nunique.rb +22 -1
- data/lib/carray/methods/repeat.rb +91 -0
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +52 -3
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/rng.rb +86 -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 +109 -40
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string_operation_extension.rb +9 -4
- 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 +535 -105
- data/lib/carray/window_iterator.rb +259 -82
- data/lib/carray.rb +4 -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_block.rb +2 -7
- 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 +11 -4
- data/yard-stubs/carray_access.rb +6 -5
- data/yard-stubs/carray_broadcast.rb +2 -2
- 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 -83
- data/yard-stubs/carray_count.rb +16 -2
- data/yard-stubs/carray_lazy.rb +210 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +34 -10
- data/yard-stubs/carray_math.rb +1499 -4
- data/yard-stubs/carray_median_percentile.rb +26 -8
- data/yard-stubs/carray_order.rb +43 -82
- data/yard-stubs/carray_scatter.rb +10 -3
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +11 -8
- data/yard-stubs/carray_sort.rb +50 -8
- data/yard-stubs/carray_test.rb +10 -0
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +19 -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 -25837
- data/ext/carray_kernels_reduce_boolean.c +0 -330
- data/ext/carray_kernels_reduce_cumulative.c +0 -14593
- data/ext/carray_kernels_reduce_extreme.c +0 -16948
- data/ext/carray_kernels_reduce_variance.c +0 -3910
- data/ext/carray_kernels_scan.c +0 -3693
- data/ext/carray_kernels_search.c +0 -32138
- data/ext/carray_kernels_sort.c +0 -10626
- data/ext/carray_kernels_triop.c +0 -1392
data/lib/carray/methods/snap.rb
CHANGED
|
@@ -66,8 +66,11 @@ class CArray
|
|
|
66
66
|
# the sample, `:ceil` at or above), and delegates to
|
|
67
67
|
# `locate_nearest_addr(direction:)`.
|
|
68
68
|
#
|
|
69
|
-
# `list` must be a 1-D ascending numeric CArray
|
|
70
|
-
#
|
|
69
|
+
# `list` must be a 1-D ascending numeric CArray, or a Ruby Array /
|
|
70
|
+
# Range. An Array grid takes `self`'s data_type, except that a Float
|
|
71
|
+
# in it makes an integer array's grid float64 (so `0.5` is not
|
|
72
|
+
# truncated); an Integer that does not fit `self`'s type raises
|
|
73
|
+
# `RangeError`. Out-of-range and NaN handling follows the
|
|
71
74
|
# same pattern as `bin_to`, but with an additional `:clamp`
|
|
72
75
|
# sentinel that snaps out-of-range cells to the nearest list end.
|
|
73
76
|
#
|
|
@@ -95,19 +98,21 @@ class CArray
|
|
|
95
98
|
# shape as `self`.
|
|
96
99
|
# @raise [ArgumentError] when `list` is not 1-D or is empty, or
|
|
97
100
|
# `direction` is not one of the accepted symbols.
|
|
101
|
+
# @raise [RangeError] when an Integer in an Array `list` does not
|
|
102
|
+
# fit `self`'s data_type.
|
|
98
103
|
# @example
|
|
99
104
|
# temp.snap_to([270.0, 280.0, 290.0, 300.0]) # clamp OOB
|
|
100
105
|
# temp.snap_to(grid, lfill: nil, ufill: nil) # mask OOB
|
|
101
106
|
# rain.snap_to([0.0, 1.0, 5.0, 20.0], direction: :floor) # list value at or below
|
|
102
107
|
def snap_to(list, lfill: :clamp, ufill: :clamp, direction: :round)
|
|
103
|
-
ref = list.is_a?(CArray) ? list :
|
|
108
|
+
ref = list.is_a?(CArray) ? list : snap_list(list)
|
|
104
109
|
raise ArgumentError, "snap_to: list must be 1-D" unless ref.ndim == 1
|
|
105
110
|
n = ref.elements
|
|
106
111
|
raise ArgumentError, "snap_to: list must have at least one value" if n < 1
|
|
107
112
|
|
|
108
113
|
if n == 1
|
|
109
114
|
# Degenerate: every finite cell snaps to the only value.
|
|
110
|
-
out = CArray.new(ref.data_type, shape).fill(ref[0])
|
|
115
|
+
out = CArray.new(ref.data_type, shape, bytes: ref.bytes).fill(ref[0])
|
|
111
116
|
out.mask = self.mask.to_ca if self.has_mask?
|
|
112
117
|
if self.float?
|
|
113
118
|
inv = self.is_invalid
|
|
@@ -118,17 +123,16 @@ class CArray
|
|
|
118
123
|
return out
|
|
119
124
|
end
|
|
120
125
|
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
# element-wise semantic on any shape.
|
|
126
|
+
# Multi-D input is snapped as its flattened cells and given its shape
|
|
127
|
+
# back.
|
|
124
128
|
if ndim > 1
|
|
125
129
|
return reshape(-1).snap_to(ref, lfill: lfill, ufill: ufill,
|
|
126
130
|
direction: direction).reshape(*shape)
|
|
127
131
|
end
|
|
128
132
|
|
|
129
|
-
# locate_nearest_addr returns int64 indices;
|
|
130
|
-
#
|
|
131
|
-
# explicit comparisons
|
|
133
|
+
# locate_nearest_addr returns int64 indices; whether a cell outside the
|
|
134
|
+
# list gets one depends on the direction, so the two sides are split with
|
|
135
|
+
# explicit comparisons and filled here independently of it.
|
|
132
136
|
idx = self.locate_nearest_addr(ref, direction: direction)
|
|
133
137
|
out = ref.project(idx)
|
|
134
138
|
|
|
@@ -137,13 +141,13 @@ class CArray
|
|
|
137
141
|
|
|
138
142
|
case lfill
|
|
139
143
|
when :clamp then out[below] = ref[0]
|
|
140
|
-
when nil then
|
|
144
|
+
when nil then out[below] = UNDEF
|
|
141
145
|
else out[below] = lfill
|
|
142
146
|
end
|
|
143
147
|
|
|
144
148
|
case ufill
|
|
145
149
|
when :clamp then out[above] = ref[-1]
|
|
146
|
-
when nil then
|
|
150
|
+
when nil then out[above] = UNDEF
|
|
147
151
|
else out[above] = ufill
|
|
148
152
|
end
|
|
149
153
|
|
|
@@ -158,4 +162,22 @@ class CArray
|
|
|
158
162
|
out
|
|
159
163
|
end
|
|
160
164
|
|
|
165
|
+
private
|
|
166
|
+
|
|
167
|
+
# A Ruby Array grid in the type a search query would get: a Float in it
|
|
168
|
+
# makes an integer array's grid float64 (so 0.5 is not truncated to 0),
|
|
169
|
+
# otherwise the grid takes self's type, and an Integer that does not fit
|
|
170
|
+
# it is refused rather than wrapped.
|
|
171
|
+
def snap_list(list)
|
|
172
|
+
vals = list.to_a
|
|
173
|
+
type = data_type
|
|
174
|
+
type = CA_FLOAT64 if (integer? || data_type == CA_BOOLEAN) && vals.any? { |v| v.is_a?(Float) }
|
|
175
|
+
ref = CArray.wrap_readonly(vals, type)
|
|
176
|
+
if ref.integer?
|
|
177
|
+
bad = vals.zip(ref.to_a).find { |v, w| v != w }
|
|
178
|
+
raise RangeError, "snap_to: #{bad[0].inspect} is out of range for #{ref.data_type_name}" if bad
|
|
179
|
+
end
|
|
180
|
+
ref
|
|
181
|
+
end
|
|
182
|
+
|
|
161
183
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "carray/methods/discovery_along"
|
|
2
|
+
|
|
1
3
|
class CArray
|
|
2
4
|
|
|
3
5
|
# @overload unique(sort: false)
|
|
@@ -24,22 +26,69 @@ class CArray
|
|
|
24
26
|
# For `CA_OBJECT` / `CA_FIXLEN` distinctness follows Ruby
|
|
25
27
|
# `eql?` / `hash`; note that Ruby does not collapse distinct NaN
|
|
26
28
|
# objects, so a `CA_OBJECT` array of Float NaN is not collapsed
|
|
27
|
-
# (unlike the numeric path).
|
|
29
|
+
# (unlike the numeric path). Complex values follow the float rules
|
|
30
|
+
# in each component; the values come back with -0.0 written as 0.0
|
|
31
|
+
# and every NaN as one NaN.
|
|
28
32
|
#
|
|
29
33
|
# @param sort [Boolean] when true, return the distinct values
|
|
30
|
-
# sorted ascending instead of in first-appearance order.
|
|
34
|
+
# sorted ascending instead of in first-appearance order. A complex
|
|
35
|
+
# array has no order and raises {CArray::DataTypeError}.
|
|
31
36
|
# A time array (`CATime` / `CATimedelta`) answers with its own type on
|
|
32
37
|
# its own unit: the distinct values are values, so the array comes back
|
|
33
38
|
# as itself rather than as raw storage ticks.
|
|
34
39
|
#
|
|
35
40
|
# @return [CArray] 1-D CArray of the distinct values, same data type
|
|
36
41
|
# as `self`.
|
|
37
|
-
|
|
42
|
+
#
|
|
43
|
+
# @overload unique(along: k)
|
|
44
|
+
# Returns the distinct **sub-arrays** of `self`, comparing whole
|
|
45
|
+
# sub-arrays rather than cells: `along: k` names the axis whose
|
|
46
|
+
# index enumerates them, so `z.unique(along: 0)` gives the distinct
|
|
47
|
+
# rows of a 2-D array, in first-appearance order. This is
|
|
48
|
+
# `np.unique(z, axis=k)`.
|
|
49
|
+
#
|
|
50
|
+
# Note the contrast with `axis:` on {#nunique} and
|
|
51
|
+
# {#mask_duplicates}, which names the axis a *fiber runs along* and
|
|
52
|
+
# asks about the values inside each fiber. The two cannot be given
|
|
53
|
+
# together.
|
|
54
|
+
#
|
|
55
|
+
# Distinctness is the family's, widened from a cell to a sub-array:
|
|
56
|
+
# two sub-arrays are the same when every cell is, with all NaN one
|
|
57
|
+
# value and -0.0 == +0.0. A sub-array holding a masked cell does not
|
|
58
|
+
# participate and never appears in the result.
|
|
59
|
+
#
|
|
60
|
+
# The result is a **view** of `self` -- the surviving sub-arrays,
|
|
61
|
+
# not copies of them -- so writing through it reaches `self`. Take a
|
|
62
|
+
# `copy` when that is not wanted. `sort:` is not available here:
|
|
63
|
+
# sub-arrays have no order to sort by. `object` arrays are refused,
|
|
64
|
+
# because their cells hold Ruby references.
|
|
65
|
+
#
|
|
66
|
+
# @param along [Integer] axis whose index enumerates the sub-arrays.
|
|
67
|
+
# @return [CArray] the distinct sub-arrays, same shape as `self`
|
|
68
|
+
# except along `along`.
|
|
69
|
+
def unique (sort: false, along: nil)
|
|
70
|
+
if along
|
|
71
|
+
if sort
|
|
72
|
+
raise ArgumentError,
|
|
73
|
+
"unique: sort: is not available with along: -- sub-arrays have " \
|
|
74
|
+
"no order to sort by; the result is in first-appearance order"
|
|
75
|
+
end
|
|
76
|
+
keys = fibers_as_cells(along, "unique")
|
|
77
|
+
args = [nil] * ndim
|
|
78
|
+
args[normalize_axis(along, "unique")] = keys.mask_duplicates.is_not_masked
|
|
79
|
+
return self[*args]
|
|
80
|
+
end
|
|
38
81
|
# Single-pass seen-set hash (C __unique_flat__), one lane per data type family:
|
|
39
82
|
# integer widens to a 64-bit key; float uses the bitwise key with all-NaN
|
|
40
83
|
# collapsed and -0.0 / +0.0 normalized; object keys on rb_hash + rb_eql and
|
|
41
84
|
# fixlen on a byte-hash + memcmp, both reproducing Ruby Hash distinctness.
|
|
42
85
|
# Masked cells are skipped in the kernel.
|
|
86
|
+
if sort && complex?
|
|
87
|
+
raise CArray::DataTypeError,
|
|
88
|
+
"unique(sort: true): a #{data_type_name} value has no order " \
|
|
89
|
+
"(complex numbers are not ordered); unique without sort: gives " \
|
|
90
|
+
"the values in first-appearance order"
|
|
91
|
+
end
|
|
43
92
|
levels = __unique_flat__
|
|
44
93
|
sort ? levels.sort : levels
|
|
45
94
|
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# ----------------------------------------------------------------------------
|
|
2
|
+
#
|
|
3
|
+
# carray/record_order.rb
|
|
4
|
+
#
|
|
5
|
+
# The order of a CARecord: the members its struct names in `order_by:`,
|
|
6
|
+
# compared in that order.
|
|
7
|
+
#
|
|
8
|
+
# A record's storage is its bytes, and comparing bytes is no order of the
|
|
9
|
+
# values in them (a little-endian float, a signed integer). So the
|
|
10
|
+
# ordering members do not run on the storage: they run on one int64 array,
|
|
11
|
+
# each record's rank among the others by the declared members, and the
|
|
12
|
+
# values they return are gathered from the record array by the positions
|
|
13
|
+
# that array gives. A struct that declares no order has none, and these
|
|
14
|
+
# raise.
|
|
15
|
+
#
|
|
16
|
+
# ----------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
class CARecord
|
|
19
|
+
|
|
20
|
+
# @overload sort(axis: nil, kind: :quick, masked_position: :last)
|
|
21
|
+
# Returns a sorted {CARecord} view, ordered by the struct's
|
|
22
|
+
# `order_by:` members. With no `axis:` the array is flattened first.
|
|
23
|
+
# @return [CARecord]
|
|
24
|
+
# @raise [ArgumentError] when the struct declares no `order_by:`.
|
|
25
|
+
def sort (*args, **kw)
|
|
26
|
+
addr = record_rank.sort_addr(*args, **kw)
|
|
27
|
+
kw[:axis].nil? ? self[addr.flatten] : self[addr]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @overload sort_copy(axis: nil, kind: :quick, masked_position: :last)
|
|
31
|
+
# @return [CARecord] an owned sorted copy.
|
|
32
|
+
def sort_copy (*args, **kw)
|
|
33
|
+
sort(*args, **kw).copy
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @!method sort_addr(axis: nil, kind: :quick, masked_position: :last)
|
|
37
|
+
# @return [CArray] the view-flat addresses of a sort by the declared order.
|
|
38
|
+
# @!method sort_index(axis: nil, kind: :quick, masked_position: :last)
|
|
39
|
+
# @return [CArray] the per-fiber indices of a sort by the declared order.
|
|
40
|
+
# @!method rank_index(axis: nil)
|
|
41
|
+
# @return [CArray] each record's rank by the declared order.
|
|
42
|
+
# @!method order(axis: nil, descending: false, method: :ordinal, kind: :quick)
|
|
43
|
+
# @return [CArray] each record's rank by the declared order.
|
|
44
|
+
# @!method min_index(axis: nil)
|
|
45
|
+
# @return [Integer, CArray] where the smallest record sits.
|
|
46
|
+
# @!method max_index(axis: nil)
|
|
47
|
+
# @return [Integer, CArray] where the largest record sits.
|
|
48
|
+
# @!method partition_index(kth, axis: nil)
|
|
49
|
+
# @return [CArray] the indices that partition about the `kth` record.
|
|
50
|
+
[:sort_addr, :sort_index, :rank_index, :order,
|
|
51
|
+
:min_index, :max_index, :partition_index].each do |op|
|
|
52
|
+
define_method(op) { |*args, **kw| record_rank.public_send(op, *args, **kw) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @overload min(axis: nil, keep_axis: false)
|
|
56
|
+
# Returns the smallest record by the declared order, skipping masked
|
|
57
|
+
# records; UNDEF when every record is masked. With `axis:` a
|
|
58
|
+
# {CARecord} of the smallest along that axis.
|
|
59
|
+
# @return [CAStruct, CARecord]
|
|
60
|
+
def min (axis: nil, keep_axis: false)
|
|
61
|
+
record_extreme(:min_index, axis, keep_axis)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @overload max(axis: nil, keep_axis: false)
|
|
65
|
+
# The largest record; see {#min}.
|
|
66
|
+
# @return [CAStruct, CARecord]
|
|
67
|
+
def max (axis: nil, keep_axis: false)
|
|
68
|
+
record_extreme(:max_index, axis, keep_axis)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @overload minmax(axis: nil, keep_axis: false)
|
|
72
|
+
# @return [Array] `[min, max]`.
|
|
73
|
+
def minmax (axis: nil, keep_axis: false)
|
|
74
|
+
[min(axis: axis, keep_axis: keep_axis), max(axis: axis, keep_axis: keep_axis)]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @overload partition_copy(kth, axis: nil)
|
|
78
|
+
# @return [CARecord] partitioned about the `kth` record in the declared
|
|
79
|
+
# order. With no `axis:` the array is flattened first.
|
|
80
|
+
def partition_copy (kth, axis: nil)
|
|
81
|
+
if axis.nil?
|
|
82
|
+
flat = flatten
|
|
83
|
+
return flat[flat.send(:record_rank).partition_index(kth)].copy
|
|
84
|
+
end
|
|
85
|
+
take_along_axis(record_rank.partition_index(kth, axis: axis), axis: axis).copy
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @!method lt(other)
|
|
89
|
+
# Compares by the declared order. `other` is a record of the same
|
|
90
|
+
# struct, or a {CARecord} of them.
|
|
91
|
+
# @return [CArray] boolean.
|
|
92
|
+
# @!method le(other)
|
|
93
|
+
# @!method gt(other)
|
|
94
|
+
# @!method ge(other)
|
|
95
|
+
[:lt, :le, :gt, :ge].each do |op|
|
|
96
|
+
define_method(op) do |other|
|
|
97
|
+
mine, theirs = record_rank_with(other)
|
|
98
|
+
mine.public_send(op, theirs)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
alias < lt
|
|
103
|
+
alias <= le
|
|
104
|
+
alias > gt
|
|
105
|
+
alias >= ge
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
# The struct's order_by: members, or a refusal naming where to declare it.
|
|
110
|
+
def record_order_by
|
|
111
|
+
data_class.order_by or
|
|
112
|
+
raise ArgumentError,
|
|
113
|
+
"#{data_class.inspect} declares no order; pass order_by: to " \
|
|
114
|
+
"CArray.struct to name the members records are ordered by"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Each record's dense rank by the declared members, shaped like self.
|
|
118
|
+
# Each member is ranked on its own (a numeric member by value, NaN last;
|
|
119
|
+
# a fixlen member by its bytes), and the ranks are folded in member order.
|
|
120
|
+
# Ranking again after each fold keeps every value below the record count.
|
|
121
|
+
def record_rank
|
|
122
|
+
record_rank_of(self).reshape(*shape)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def record_rank_of (records)
|
|
126
|
+
rank = nil
|
|
127
|
+
record_order_by.each do |key|
|
|
128
|
+
r = records[key].flatten.order(method: :dense)
|
|
129
|
+
if rank.nil?
|
|
130
|
+
rank = r
|
|
131
|
+
else
|
|
132
|
+
top = r.max
|
|
133
|
+
span = top.equal?(UNDEF) ? 1 : top + 1
|
|
134
|
+
rank = (rank * span + r).order(method: :dense)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
rank
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Ranks of self and `other` in one ordering, so they can be compared.
|
|
141
|
+
def record_rank_with (other)
|
|
142
|
+
case other
|
|
143
|
+
when CARecord
|
|
144
|
+
unless other.data_class.equal?(data_class)
|
|
145
|
+
raise ArgumentError,
|
|
146
|
+
"can not compare a record of #{data_class.inspect} " \
|
|
147
|
+
"with one of #{other.data_class.inspect}"
|
|
148
|
+
end
|
|
149
|
+
theirs = other.flatten
|
|
150
|
+
when data_class
|
|
151
|
+
theirs = CARecord.new(data_class, 1)
|
|
152
|
+
theirs[0] = other
|
|
153
|
+
else
|
|
154
|
+
raise ArgumentError,
|
|
155
|
+
"can not compare a record of #{data_class.inspect} with #{other.class}"
|
|
156
|
+
end
|
|
157
|
+
n = elements
|
|
158
|
+
both = record_rank_of(CArray.concatenate([flatten, theirs]))
|
|
159
|
+
mine = both[0...n].reshape(*shape)
|
|
160
|
+
rest = both[n..-1]
|
|
161
|
+
[mine, other.is_a?(CARecord) ? rest.reshape(*other.shape) : rest[0]]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# The record at each min_index / max_index, gathered from self.
|
|
165
|
+
def record_extreme (index_op, axis, keep_axis)
|
|
166
|
+
rank = record_rank
|
|
167
|
+
if axis.nil?
|
|
168
|
+
i = rank.flatten.public_send(index_op)
|
|
169
|
+
return i.equal?(UNDEF) ? UNDEF : flatten[i]
|
|
170
|
+
end
|
|
171
|
+
name = index_op.to_s.sub("_index", "")
|
|
172
|
+
if axis.is_a?(Array)
|
|
173
|
+
axes = axis.map { |k| normalize_axis(k, name) }.uniq
|
|
174
|
+
return record_extreme(index_op, axes[0], keep_axis) if axes.size == 1
|
|
175
|
+
return record_extreme_over(index_op, axes, keep_axis)
|
|
176
|
+
end
|
|
177
|
+
axis = normalize_axis(axis, name)
|
|
178
|
+
idx = rank.public_send(index_op, axis: axis)
|
|
179
|
+
empty = idx.is_masked
|
|
180
|
+
picked_shape = shape.dup
|
|
181
|
+
picked_shape[axis] = 1
|
|
182
|
+
out = take_along_axis(idx.strip_mask(0).reshape(*picked_shape), axis: axis).copy
|
|
183
|
+
if empty.any
|
|
184
|
+
out[empty.reshape(*picked_shape)] = UNDEF
|
|
185
|
+
end
|
|
186
|
+
return out if keep_axis
|
|
187
|
+
out.reshape(*(shape[0...axis] + shape[axis + 1..-1]))
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Over several axes: the reduced axes are moved last and merged into one.
|
|
191
|
+
def record_extreme_over (index_op, axes, keep_axis)
|
|
192
|
+
keep = (0...ndim).to_a - axes
|
|
193
|
+
kept_shape = shape.values_at(*keep)
|
|
194
|
+
if keep.empty?
|
|
195
|
+
r = record_extreme(index_op, nil, false)
|
|
196
|
+
return r unless keep_axis
|
|
197
|
+
out = flatten[0..0].copy
|
|
198
|
+
out[0] = r
|
|
199
|
+
return out.reshape(*([1] * ndim))
|
|
200
|
+
end
|
|
201
|
+
merged = transpose(*(keep + axes))
|
|
202
|
+
.reshape(*kept_shape, shape.values_at(*axes).inject(:*))
|
|
203
|
+
out = merged.send(:record_extreme, index_op, keep.size, false)
|
|
204
|
+
return out unless keep_axis
|
|
205
|
+
out.reshape(*shape.each_with_index.map { |n, k| axes.include?(k) ? 1 : n })
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
end
|
data/lib/carray/rng.rb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# A random number generator with its own state.
|
|
4
|
+
#
|
|
5
|
+
# `Rng` rather than `Random`: a `CArray::Random` would shadow `::Random`
|
|
6
|
+
# for every bare `Random` written inside `class CArray`, and the two mean
|
|
7
|
+
# different generators -- `random!(rng: Random.new(4))` draws through
|
|
8
|
+
# Ruby's MT19937 and `random!(rng: CArray::Rng.new(seed: 4))` through the
|
|
9
|
+
# one below. Two names that different should not look alike.
|
|
10
|
+
#
|
|
11
|
+
# The class itself is defined in ext/carray_random.c, which is where the
|
|
12
|
+
# state is advanced. What is added here is the generator's source text,
|
|
13
|
+
# so that another gem can run the same code rather than a second
|
|
14
|
+
# implementation of it.
|
|
15
|
+
class Rng
|
|
16
|
+
|
|
17
|
+
# The generators this carray knows, and how many int64 cells each one
|
|
18
|
+
# keeps its state in.
|
|
19
|
+
STATE_CELLS = {
|
|
20
|
+
:xoshiro256pp => 4,
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
GENERATORS = STATE_CELLS.keys.freeze
|
|
24
|
+
|
|
25
|
+
# Where a generator's C lives. Shipped: the gemspec takes ext/*.h,
|
|
26
|
+
# and `ext` is a require path, so this resolves in a checkout and in
|
|
27
|
+
# an installed gem alike -- lib/ and ext/ are siblings in both.
|
|
28
|
+
SOURCE_FILES = {
|
|
29
|
+
:xoshiro256pp => File.expand_path("../../ext/ca_rng_xoshiro256pp.h",
|
|
30
|
+
__dir__),
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
# The C each generator is, as text.
|
|
34
|
+
#
|
|
35
|
+
# CArray::Rng::SOURCE[:xoshiro256pp] #=> "/* ---- ... */\n..."
|
|
36
|
+
#
|
|
37
|
+
# This is the same text the extension compiled -- the file below is
|
|
38
|
+
# `#include`d by ext/carray_random.c -- which is the point of handing
|
|
39
|
+
# it out. A caller that pastes it into a translation unit of its own
|
|
40
|
+
# gets a generator that continues a sequence this one started, because
|
|
41
|
+
# it is running the code this one ran and not a copy of it.
|
|
42
|
+
#
|
|
43
|
+
# carray-jit is the caller this exists for: a kernel's `random(rng:)`
|
|
44
|
+
# pastes the text beside the helpers its own compiler emits, so a
|
|
45
|
+
# kernel draws from the CArray::Rng it was handed. The text needs
|
|
46
|
+
# nothing but <stdint.h> and defines only `static inline` functions
|
|
47
|
+
# under a `ca_` prefix.
|
|
48
|
+
#
|
|
49
|
+
# It is read once, when this file is first required.
|
|
50
|
+
SOURCE = SOURCE_FILES.transform_values { |path|
|
|
51
|
+
File.read(path, :encoding => "UTF-8")
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
54
|
+
# What every generator's text needs beside it, and what none of them
|
|
55
|
+
# owns: turning uniforms into a normal is the same arithmetic whichever
|
|
56
|
+
# generator the uniforms came from.
|
|
57
|
+
#
|
|
58
|
+
# Handed out apart from SOURCE because whoever pastes it pastes it once,
|
|
59
|
+
# however many generators are drawing. Folded into each generator's own
|
|
60
|
+
# text instead, two generators in one translation unit would define it
|
|
61
|
+
# twice.
|
|
62
|
+
COMMON_SOURCE = File.read(
|
|
63
|
+
File.expand_path("../../ext/ca_rng_normal.h", __dir__), :encoding => "UTF-8"
|
|
64
|
+
).freeze
|
|
65
|
+
|
|
66
|
+
# The functions in SOURCE a caller may reach, by generator and by what
|
|
67
|
+
# the draw answers: `:random` a double in [0.0, 1.0), `:randomn` a
|
|
68
|
+
# standard normal, `:bits` the raw word a draw came from. Each takes the
|
|
69
|
+
# state and advances it -- `:randomn` by two draws, the other two by one.
|
|
70
|
+
#
|
|
71
|
+
# Named here rather than worked out by whoever pastes the text: which
|
|
72
|
+
# symbols are the entry points is a fact about the generator, and the
|
|
73
|
+
# generator is CArray's. These are the three `#random`, `#randomn`
|
|
74
|
+
# and `#bits` call, so a caller pasting the text draws the sequence
|
|
75
|
+
# this one draws.
|
|
76
|
+
DRAW_FUNCTIONS = {
|
|
77
|
+
:xoshiro256pp => {
|
|
78
|
+
:random => "ca_xoshiro256pp_next_real",
|
|
79
|
+
:randomn => "ca_xoshiro256pp_next_normal",
|
|
80
|
+
:bits => "ca_xoshiro256pp_next",
|
|
81
|
+
}.freeze,
|
|
82
|
+
}.freeze
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
data/lib/carray/runtime.rb
CHANGED
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
class CArray
|
|
7
7
|
|
|
8
|
+
# A fast path answers by a different algorithm what a general path also
|
|
9
|
+
# answers. A development build defines this in C, false when run with
|
|
10
|
+
# CARRAY_GENERAL_PATH=1 so the test suite takes the general paths (see
|
|
11
|
+
# ext/carray_internal.h); a release build has no switch.
|
|
12
|
+
unless respond_to?(:__fast_paths_enabled__)
|
|
13
|
+
def self.__fast_paths_enabled__ # :nodoc:
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
8
18
|
# String fallback for the global cast functions CA_INT32(data) etc. The C
|
|
9
19
|
# dispatcher (ext/carray_cast.c) handles every other input class in C and
|
|
10
20
|
# delegates only String here, because the whitespace / `,` / `;` /
|
|
@@ -65,23 +75,16 @@ class CArray
|
|
|
65
75
|
num = prod.sum(**nopt)
|
|
66
76
|
den = wv.sum(axis: axis, keep_axis: keep_axis)
|
|
67
77
|
result =
|
|
68
|
-
if axis
|
|
78
|
+
if den.is_a?(CArray) # axis given, or keep_axis
|
|
69
79
|
den = den.copy
|
|
70
80
|
den[den.eq(0)] = UNDEF # zero / empty denominator -> undetermined
|
|
71
81
|
num / den # UNDEF propagates cell-wise
|
|
72
|
-
elsif den.equal?(UNDEF) || den == 0
|
|
73
|
-
UNDEF
|
|
82
|
+
elsif num.equal?(UNDEF) || den.equal?(UNDEF) || den == 0
|
|
83
|
+
UNDEF # min_count unmet leaves num UNDEF
|
|
74
84
|
else
|
|
75
85
|
num / den
|
|
76
86
|
end
|
|
77
|
-
|
|
78
|
-
return result if fill_value.nil?
|
|
79
|
-
if result.is_a?(CArray)
|
|
80
|
-
result[result.is_masked] = fill_value if result.has_mask?
|
|
81
|
-
result
|
|
82
|
-
else
|
|
83
|
-
result.equal?(UNDEF) ? fill_value : result
|
|
84
|
-
end
|
|
87
|
+
CArray.__reduce_fill__(result, opts[:fill_value])
|
|
85
88
|
end
|
|
86
89
|
private :__wmean_object__
|
|
87
90
|
|