carray 3.0.1 → 3.0.2
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 +520 -0
- data/README.md +2 -2
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +33 -4
- data/ext/ca_axis_group.c +202 -96
- data/ext/ca_categorical_iterator.c +108 -54
- data/ext/ca_kernel_iterator.c +317 -51
- data/ext/ca_kernel_iterator.h +142 -35
- data/ext/ca_obj_array.c +62 -20
- data/ext/ca_obj_block.c +4 -4
- data/ext/ca_obj_const_string.c +85 -26
- data/ext/ca_obj_face.c +24 -0
- data/ext/ca_obj_face.h +15 -0
- data/ext/ca_obj_fixlen_string.c +18 -5
- data/ext/ca_obj_meld.c +123 -25
- data/ext/ca_obj_object.c +8 -0
- data/ext/ca_obj_select.c +49 -34
- data/ext/ca_obj_stack.c +3 -8
- data/ext/ca_obj_stride.c +72 -1
- data/ext/ca_obj_string.c +8 -4
- data/ext/ca_obj_window.c +8 -2
- data/ext/ca_op_ipower.c +1 -2
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sweep_engine.c +307 -143
- data/ext/ca_sweep_engine.h +26 -5
- data/ext/carray.h +21 -2
- data/ext/carray_access.c +32 -20
- data/ext/carray_address_basis.c +590 -0
- data/ext/carray_broadcast.c +3 -3
- data/ext/carray_call_cfunc.c +667 -483
- data/ext/carray_cast.c +115 -41
- data/ext/carray_copy.c +55 -30
- data/ext/carray_core.c +83 -3
- data/ext/carray_count.c +9 -10
- data/ext/carray_factorize.c +46 -25
- data/ext/carray_internal.h +17 -0
- data/ext/carray_kernels_reduce_aggregate.c +168 -0
- data/ext/carray_kernels_reduce_cumulative.c +270 -1
- data/ext/carray_kernels_reduce_extreme.c +554 -8
- data/ext/carray_kernels_scan.c +4 -4
- data/ext/carray_kernels_search.c +94 -14
- data/ext/carray_loop.c +7 -1
- data/ext/carray_mask.c +23 -8
- data/ext/carray_median_percentile.c +55 -0
- data/ext/carray_operator.c +4 -4
- data/ext/carray_order.c +1 -1
- data/ext/carray_random.c +384 -40
- data/ext/carray_slab.c +13 -0
- data/ext/carray_sort.c +20 -22
- data/ext/mk_call_cfunc.rb +103 -116
- data/ext/mkkernel.rb +297 -29
- data/ext/ruby_carray.c +10 -1
- data/ext/version.h +4 -4
- data/lib/carray/autoload_carray.rb +5 -3
- data/lib/carray/autoload_method_extension.rb +12 -0
- data/lib/carray/axis_group.rb +77 -0
- data/lib/carray/basics.rb +4 -0
- data/lib/carray/block_iterator.rb +92 -16
- data/lib/carray/categorical.rb +150 -33
- data/lib/carray/categorical_iterator.rb +207 -80
- data/lib/carray/const_string.rb +131 -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/csv_parser.rb +11 -4
- data/lib/carray/frame/frame.rb +81 -10
- data/lib/carray/frame/group.rb +36 -3
- data/lib/carray/frame/io.rb +67 -15
- data/lib/carray/frame/records.rb +18 -4
- data/lib/carray/frame/verbs.rb +14 -11
- data/lib/carray/inspect.rb +42 -9
- data/lib/carray/iterator.rb +143 -0
- data/lib/carray/lazy.rb +0 -37
- data/lib/carray/mask_gap_fill.rb +3 -1
- data/lib/carray/methods/discovery_along.rb +74 -0
- data/lib/carray/methods/factorize.rb +50 -0
- data/lib/carray/methods/is_in.rb +13 -2
- data/lib/carray/methods/locate_addr.rb +75 -2
- data/lib/carray/methods/mask_duplicates.rb +35 -1
- data/lib/carray/methods/nunique.rb +22 -1
- data/lib/carray/methods/repeat.rb +110 -0
- data/lib/carray/methods/unique.rb +41 -1
- data/lib/carray/rng.rb +86 -0
- data/lib/carray/slab_iterator.rb +58 -13
- data/lib/carray/string_operation_extension.rb +5 -1
- data/lib/carray/time.rb +18 -2
- data/lib/carray/window_iterator.rb +142 -20
- data/lib/carray.rb +2 -0
- data/yard-stubs/ca_obj_block.rb +2 -7
- data/yard-stubs/ca_obj_window.rb +10 -2
- data/yard-stubs/carray_access.rb +1 -1
- data/yard-stubs/carray_broadcast.rb +1 -1
- data/yard-stubs/carray_core.rb +0 -80
- data/yard-stubs/carray_count.rb +7 -2
- data/yard-stubs/carray_lazy.rb +205 -0
- data/yard-stubs/carray_math.rb +1486 -3
- data/yard-stubs/carray_median_percentile.rb +16 -2
- data/yard-stubs/carray_order.rb +9 -69
- data/yard-stubs/carray_slab.rb +9 -7
- data/yard-stubs/carray_sort.rb +7 -5
- metadata +9 -1
data/lib/carray/methods/is_in.rb
CHANGED
|
@@ -22,7 +22,10 @@ class CArray
|
|
|
22
22
|
# value-hash discovery family ({#unique} / {#value_counts}):
|
|
23
23
|
# numeric follows `==` with all NaN collapsed to one value and
|
|
24
24
|
# -0.0 == +0.0; `CA_OBJECT` follows Ruby `hash` / `eql?` with Float
|
|
25
|
-
# NaN collapsed; `CA_FIXLEN` follows byte equality
|
|
25
|
+
# NaN collapsed; `CA_FIXLEN` follows byte equality over the whole
|
|
26
|
+
# cell, with a short String in the set padded out to the cell width
|
|
27
|
+
# (so a 5-byte array written from `"be"` is `is_in(["be"])`). A
|
|
28
|
+
# `CA_FIXLEN` set given as a CArray must already be of that width.
|
|
26
29
|
#
|
|
27
30
|
# Masked cells of `values` do not enter the set. Masked cells of
|
|
28
31
|
# `self` stay masked in the result (membership is unknown), so
|
|
@@ -126,7 +129,15 @@ class CArray
|
|
|
126
129
|
# object / fixlen self the elements are values, not data type specifiers (a
|
|
127
130
|
# String is a value, not a type name), so build the set in self's data type.
|
|
128
131
|
def promote_elements (elems)
|
|
129
|
-
if data_type ==
|
|
132
|
+
if data_type == CA_FIXLEN
|
|
133
|
+
# A fixlen value is a cell-width blob, so the set has to be built at
|
|
134
|
+
# this array's width -- to_type(:fixlen) with no bytes: builds it at
|
|
135
|
+
# width 0, where every element is "" and the C guard then refuses the
|
|
136
|
+
# set for not matching self. Same rule the scalar operand of a
|
|
137
|
+
# comparison follows: a String standing in for a cell is padded to the
|
|
138
|
+
# cell's width.
|
|
139
|
+
[self, elems.to_ca.to_type(data_type, bytes: bytes)]
|
|
140
|
+
elsif data_type == CA_OBJECT
|
|
130
141
|
[self, elems.to_ca.to_type(data_type)]
|
|
131
142
|
else
|
|
132
143
|
t = CArray.result_type(self, *elems)
|
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
class CArray
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
# in yard-stubs/carray_order.rb (grouped with the search family).
|
|
3
|
+
# @!group Sorting and searching
|
|
5
4
|
|
|
5
|
+
# @overload locate_addr(ref)
|
|
6
|
+
# Returns, for each element of `self`, the flat address into `ref`
|
|
7
|
+
# where the value first occurs, or `UNDEF` where it is not present.
|
|
8
|
+
# Builds a value-to-first-address map from `ref` in one pass (an
|
|
9
|
+
# open-addressing hash, the same substrate as {#unique} /
|
|
10
|
+
# {#value_counts}), then probes each element of `self`; no sort,
|
|
11
|
+
# peak memory `O(distinct ref values)`.
|
|
12
|
+
#
|
|
13
|
+
# The return is `ref`'s flat address (0 to `ref.elements - 1`), so a
|
|
14
|
+
# multi-dimensional `ref` still yields a `self`-shaped result of flat
|
|
15
|
+
# addresses; downstream reads (`ref[addr]`, `model_var[addr]`, ...)
|
|
16
|
+
# apply it as a flat gather.
|
|
17
|
+
#
|
|
18
|
+
# Works on numeric, `CA_OBJECT`, and `CA_FIXLEN` values, matching the
|
|
19
|
+
# value-hash discovery family: numeric follows `==` with all NaN
|
|
20
|
+
# collapsed to one value and `-0.0 == +0.0`; object follows Ruby
|
|
21
|
+
# `hash` / `eql?` with Float NaN collapsed; fixlen follows byte
|
|
22
|
+
# equality. When `ref` holds duplicate values the returned address is
|
|
23
|
+
# the earliest (appearance-order) occurrence. Masked cells of `ref` do
|
|
24
|
+
# not enter the map but still occupy their flat address; masked cells
|
|
25
|
+
# of `self` are `UNDEF` in the result.
|
|
26
|
+
#
|
|
27
|
+
# `self` and `ref` are compared at their common type, negotiated by
|
|
28
|
+
# `CArray.result_type` — a fractional query against an integer `ref`
|
|
29
|
+
# is compared at the promoted type rather than truncated, so `1.5` no
|
|
30
|
+
# longer matches `1`. Cross-family input (numeric against fixlen)
|
|
31
|
+
# raises.
|
|
32
|
+
#
|
|
33
|
+
# Typical use is time-axis lookup: compute the address once against a
|
|
34
|
+
# reference axis, then reuse it to gather from many `ref`-shaped
|
|
35
|
+
# variables without repeating the lookup.
|
|
36
|
+
#
|
|
37
|
+
# @param ref [CArray, Array, Range] reference values to match against;
|
|
38
|
+
# any shape (used as flat). An Array or Range is coerced with `to_ca`
|
|
39
|
+
# and so lands in `:object`, where numeric matching follows Ruby
|
|
40
|
+
# `eql?` (`2.0` does not match `2`); pass a CArray to match in a
|
|
41
|
+
# numeric lane.
|
|
42
|
+
# @return [CArray] `:int64` flat addresses into `ref`, same shape as
|
|
43
|
+
# `self`; unmatched cells are masked.
|
|
44
|
+
# @raise [RuntimeError] when `self` and `ref` have no common data type.
|
|
6
45
|
def locate_addr (ref)
|
|
7
46
|
ref = ref.to_ca unless ref.is_a?(CArray)
|
|
8
47
|
# Put self and ref in a common lane via the single-source promotion rule
|
|
@@ -16,6 +55,38 @@ class CArray
|
|
|
16
55
|
q.send(:__locate_addr__, r)
|
|
17
56
|
end
|
|
18
57
|
|
|
58
|
+
# @overload locate_nearest_addr(ref, direction: :round, tolerance: nil)
|
|
59
|
+
# Returns, for each element of `self`, the flat address into `ref` of
|
|
60
|
+
# the nearest reference value. Continuous sibling of {#locate_addr};
|
|
61
|
+
# uses `linear_section` + rounding for non-exact matching against a
|
|
62
|
+
# sorted `ref`.
|
|
63
|
+
#
|
|
64
|
+
# Out-of-range cells of `self` (outside `ref`'s span) mask through the
|
|
65
|
+
# pipeline: `linear_section` returns NaN, `mask_invalid` propagates
|
|
66
|
+
# that as `UNDEF`, rounding and the int64 cast carry the mask, and
|
|
67
|
+
# `project` scatters it into the final positions. `mask_invalid` runs
|
|
68
|
+
# before rounding because `CArray#round` maps NaN to 0 and would
|
|
69
|
+
# otherwise silently match `ref[0]`.
|
|
70
|
+
#
|
|
71
|
+
# `tolerance:` (default `nil`) sets a maximum accepted absolute
|
|
72
|
+
# distance between `self[i]` and its matched `ref` value. When
|
|
73
|
+
# `|ref[addr] - self[i]| > tolerance`, the result cell is masked. Use
|
|
74
|
+
# for accuracy-controlled matching (e.g. "an observation snaps to a
|
|
75
|
+
# time step only if within N seconds").
|
|
76
|
+
#
|
|
77
|
+
# `ref` need not be given in ascending order: it is sorted internally
|
|
78
|
+
# and the returned addresses are mapped back to positions in `ref` as
|
|
79
|
+
# passed.
|
|
80
|
+
#
|
|
81
|
+
# @param ref [CArray] 1-D reference grid to match against.
|
|
82
|
+
# @param direction [Symbol] `:round`, `:floor`, or `:ceil` — rounding
|
|
83
|
+
# applied to the fractional position.
|
|
84
|
+
# @param tolerance [Numeric, nil] maximum accepted `|self - ref|`
|
|
85
|
+
# distance; cells beyond this are masked. `nil` disables the check.
|
|
86
|
+
# @return [CArray] `:int64` flat addresses into `ref`, same shape as
|
|
87
|
+
# `self`; out-of-range and beyond-tolerance cells are masked.
|
|
88
|
+
# @raise [ArgumentError] when `direction` is not one of the accepted
|
|
89
|
+
# symbols.
|
|
19
90
|
def locate_nearest_addr (ref, direction: :round, tolerance: nil)
|
|
20
91
|
unless [:round, :floor, :ceil].include?(direction)
|
|
21
92
|
raise ArgumentError,
|
|
@@ -49,4 +120,6 @@ class CArray
|
|
|
49
120
|
idx
|
|
50
121
|
end
|
|
51
122
|
|
|
123
|
+
# @!endgroup
|
|
124
|
+
|
|
52
125
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "carray/methods/discovery_along"
|
|
2
|
+
|
|
1
3
|
class CArray
|
|
2
4
|
|
|
3
5
|
# @overload mask_duplicates(axis: nil)
|
|
@@ -23,7 +25,39 @@ class CArray
|
|
|
23
25
|
# `nil` uses flatten order.
|
|
24
26
|
# @return [CArray] shape-preserving copy of `self` with
|
|
25
27
|
# duplicates masked.
|
|
26
|
-
|
|
28
|
+
# @overload mask_duplicates(along: k)
|
|
29
|
+
# Returns a shape-preserving copy of `self` with every cell of a
|
|
30
|
+
# duplicated **sub-array** masked, comparing whole sub-arrays rather
|
|
31
|
+
# than cells: `along: k` names the axis whose index enumerates them,
|
|
32
|
+
# so `z.mask_duplicates(along: 0)` masks each repeated row of a 2-D
|
|
33
|
+
# array and keeps the first occurrence.
|
|
34
|
+
#
|
|
35
|
+
# Note the contrast with `axis:`, which names the axis a *fiber runs
|
|
36
|
+
# along* and marks repeated values inside each fiber. The two cannot
|
|
37
|
+
# be given together.
|
|
38
|
+
#
|
|
39
|
+
# A sub-array holding a masked cell does not participate: it is
|
|
40
|
+
# neither judged a duplicate nor able to make a later one, and its
|
|
41
|
+
# cells keep the mask they came with. `object` arrays are refused,
|
|
42
|
+
# because their cells hold Ruby references.
|
|
43
|
+
#
|
|
44
|
+
# @param along [Integer] axis whose index enumerates the sub-arrays.
|
|
45
|
+
# @return [CArray] shape-preserving copy of `self` with duplicated
|
|
46
|
+
# sub-arrays masked.
|
|
47
|
+
def mask_duplicates (axis: nil, along: nil)
|
|
48
|
+
reject_axis_with_along(axis, along, "mask_duplicates")
|
|
49
|
+
if along
|
|
50
|
+
keys = fibers_as_cells(along, "mask_duplicates")
|
|
51
|
+
# A sub-array that did not participate (it held a masked cell) is
|
|
52
|
+
# masked in `keys` already; only the ones that did and repeated
|
|
53
|
+
# are duplicates here.
|
|
54
|
+
repeated = keys.mask_duplicates.is_masked & keys.is_not_masked
|
|
55
|
+
args = [:_] * ndim
|
|
56
|
+
args[normalize_axis(along, "mask_duplicates")] = nil
|
|
57
|
+
spread = CArray.boolean(*shape)
|
|
58
|
+
spread[] = repeated[*args]
|
|
59
|
+
return mask_where(spread)
|
|
60
|
+
end
|
|
27
61
|
dup =
|
|
28
62
|
if axis.nil?
|
|
29
63
|
# One seen-set over the flattened array, then restore shape.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "carray/methods/discovery_along"
|
|
2
|
+
|
|
1
3
|
class CArray
|
|
2
4
|
|
|
3
5
|
# @overload nunique(axis: nil, keep_axis: false)
|
|
@@ -29,7 +31,26 @@ class CArray
|
|
|
29
31
|
# axis as a length-1 axis instead of dropping it.
|
|
30
32
|
# @return [Integer, CArray] Integer for `axis: nil`, otherwise a
|
|
31
33
|
# reduced `CA_INT64` CArray.
|
|
32
|
-
|
|
34
|
+
# @overload nunique(along: k)
|
|
35
|
+
# Returns how many distinct **sub-arrays** `self` holds, comparing
|
|
36
|
+
# whole sub-arrays rather than cells: `along: k` names the axis
|
|
37
|
+
# whose index enumerates them, so `z.nunique(along: 0)` counts the
|
|
38
|
+
# distinct rows of a 2-D array. This is `len(np.unique(z, axis=k))`.
|
|
39
|
+
#
|
|
40
|
+
# Note the contrast with `axis:`, which names the axis a *fiber runs
|
|
41
|
+
# along* and counts the distinct values inside each fiber -- one
|
|
42
|
+
# count per fiber, not one count for the array. The two cannot be
|
|
43
|
+
# given together.
|
|
44
|
+
#
|
|
45
|
+
# Sub-arrays holding a masked cell do not participate and are not
|
|
46
|
+
# counted. `object` arrays are refused, because their cells hold
|
|
47
|
+
# Ruby references.
|
|
48
|
+
#
|
|
49
|
+
# @param along [Integer] axis whose index enumerates the sub-arrays.
|
|
50
|
+
# @return [Integer] the number of distinct sub-arrays.
|
|
51
|
+
def nunique (axis: nil, keep_axis: false, along: nil)
|
|
52
|
+
reject_axis_with_along(axis, along, "nunique")
|
|
53
|
+
return fibers_as_cells(along, "nunique").nunique if along
|
|
33
54
|
# Per-fiber single-pass seen-set hash (C __nunique__), one lane per data type
|
|
34
55
|
# family (numeric widen / NaN collapse, object rb_hash + rb_eql, fixlen
|
|
35
56
|
# byte-hash + memcmp). Masked cells are skipped; the accumulator is a no-op
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload repeat(count, axis: nil)
|
|
4
|
+
# @overload repeat(counts, axis: nil)
|
|
5
|
+
# Returns each element (or each sub-array) of `self` laid down
|
|
6
|
+
# `count` times in place, one after another.
|
|
7
|
+
#
|
|
8
|
+
# With an Integer, every element is repeated the same number of
|
|
9
|
+
# times. With an array of counts -- one per element, or one per
|
|
10
|
+
# sub-array when `axis:` is given -- each is repeated its own number
|
|
11
|
+
# of times, and a count of 0 drops that one:
|
|
12
|
+
#
|
|
13
|
+
# v = CA_INT([10, 20, 30])
|
|
14
|
+
# v.repeat(2) # => [ 10, 10, 20, 20, 30, 30 ]
|
|
15
|
+
# v.repeat([3, 1, 2]) # => [ 10, 10, 10, 20, 30, 30 ]
|
|
16
|
+
# v.repeat([2, 0, 1]) # => [ 10, 10, 30 ]
|
|
17
|
+
#
|
|
18
|
+
# This is `np.repeat`, and it is not {#tile}: `repeat` puts the
|
|
19
|
+
# copies of one element next to each other, `tile` lays the whole
|
|
20
|
+
# array down again -- `v.tile(2)` is `[10, 20, 30, 10, 20, 30]`.
|
|
21
|
+
#
|
|
22
|
+
# `axis: k` repeats the sub-arrays enumerated by axis `k` rather
|
|
23
|
+
# than the cells, so a 2-D array repeats whole rows and the result
|
|
24
|
+
# keeps its shape apart from that axis:
|
|
25
|
+
#
|
|
26
|
+
# t = CA_INT([[0, 1], [2, 3], [4, 5]])
|
|
27
|
+
# t.repeat([2, 1, 1], axis: 0)
|
|
28
|
+
# # => [ [ 0, 1 ],
|
|
29
|
+
# # [ 0, 1 ],
|
|
30
|
+
# # [ 2, 3 ],
|
|
31
|
+
# # [ 4, 5 ] ]
|
|
32
|
+
#
|
|
33
|
+
# Without `axis:` a multi-dimensional receiver is taken in flatten
|
|
34
|
+
# (row-major) order and the result is 1-D, as `np.repeat` does.
|
|
35
|
+
#
|
|
36
|
+
# The result is a **view** of `self` -- the same element named as
|
|
37
|
+
# many times as it was repeated -- so nothing is copied and writing
|
|
38
|
+
# through it reaches `self`, at every position that names the cell
|
|
39
|
+
# written. Take a `copy` when that is not wanted.
|
|
40
|
+
#
|
|
41
|
+
# Counts must be non-negative, and there must be exactly as many as
|
|
42
|
+
# there are elements (or sub-arrays along `axis:`). A masked count
|
|
43
|
+
# is refused: it names no number of repetitions.
|
|
44
|
+
#
|
|
45
|
+
# @param count [Integer, CArray, Array] how many times to repeat --
|
|
46
|
+
# one number for all, or one per element / sub-array.
|
|
47
|
+
# @param axis [Integer, nil] axis whose index enumerates the
|
|
48
|
+
# sub-arrays to repeat; `nil` repeats cells in flatten order.
|
|
49
|
+
# @return [CArray] the repeated elements, as a view of `self`.
|
|
50
|
+
def repeat (count, axis: nil)
|
|
51
|
+
along = axis.nil? ? nil : normalize_axis(axis, "repeat")
|
|
52
|
+
width = along ? shape[along] : elements
|
|
53
|
+
|
|
54
|
+
counts =
|
|
55
|
+
case count
|
|
56
|
+
when Integer
|
|
57
|
+
if count < 0
|
|
58
|
+
raise ArgumentError, "repeat: count must not be negative (got #{count})"
|
|
59
|
+
end
|
|
60
|
+
CArray.int64(width).fill(count)
|
|
61
|
+
else
|
|
62
|
+
given = count.to_ca.int64
|
|
63
|
+
unless given.elements == width
|
|
64
|
+
raise ArgumentError,
|
|
65
|
+
"repeat: #{given.elements} counts for #{width} " \
|
|
66
|
+
"#{along ? "sub-arrays along axis #{along}" : 'elements'}"
|
|
67
|
+
end
|
|
68
|
+
if given.has_mask? and given.count_masked > 0
|
|
69
|
+
raise ArgumentError,
|
|
70
|
+
"repeat: a masked count names no number of repetitions"
|
|
71
|
+
end
|
|
72
|
+
if given.lt(0).any
|
|
73
|
+
raise ArgumentError, "repeat: counts must not be negative"
|
|
74
|
+
end
|
|
75
|
+
given.flatten
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
source = along ? self : (ndim > 1 ? flatten : self)
|
|
79
|
+
source[*gather_args(repeat_index(counts), along)]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The source index of every position in the result: element i appears
|
|
83
|
+
# at the `counts[i]` positions starting at the exclusive prefix sum.
|
|
84
|
+
#
|
|
85
|
+
# Marking those starts with +1 and running a cumulative sum turns the
|
|
86
|
+
# marks back into indices, and does the right thing with a zero
|
|
87
|
+
# count for free -- two elements then share a start, that position
|
|
88
|
+
# carries 2, and the sum steps over the one that was asked for zero
|
|
89
|
+
# times.
|
|
90
|
+
private def repeat_index (counts)
|
|
91
|
+
total = counts.sum.to_i
|
|
92
|
+
return CArray.int64(0) if total.zero?
|
|
93
|
+
starts = counts.cumsum.int64 - counts
|
|
94
|
+
marks = CArray.int64(total)
|
|
95
|
+
# A trailing zero count starts one past the end; it marks nothing,
|
|
96
|
+
# which is what a zero count means.
|
|
97
|
+
marks.scatter_add!(starts[starts.lt(total)], 1)
|
|
98
|
+
marks.cumsum.int64 - 1
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# One index argument per axis: the gathered index on the axis being
|
|
102
|
+
# repeated, whole axes everywhere else.
|
|
103
|
+
private def gather_args (index, along)
|
|
104
|
+
return [index] unless along
|
|
105
|
+
args = [nil] * ndim
|
|
106
|
+
args[along] = index
|
|
107
|
+
args
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "carray/methods/discovery_along"
|
|
2
|
+
|
|
1
3
|
class CArray
|
|
2
4
|
|
|
3
5
|
# @overload unique(sort: false)
|
|
@@ -34,7 +36,45 @@ class CArray
|
|
|
34
36
|
#
|
|
35
37
|
# @return [CArray] 1-D CArray of the distinct values, same data type
|
|
36
38
|
# as `self`.
|
|
37
|
-
|
|
39
|
+
#
|
|
40
|
+
# @overload unique(along: k)
|
|
41
|
+
# Returns the distinct **sub-arrays** of `self`, comparing whole
|
|
42
|
+
# sub-arrays rather than cells: `along: k` names the axis whose
|
|
43
|
+
# index enumerates them, so `z.unique(along: 0)` gives the distinct
|
|
44
|
+
# rows of a 2-D array, in first-appearance order. This is
|
|
45
|
+
# `np.unique(z, axis=k)`.
|
|
46
|
+
#
|
|
47
|
+
# Note the contrast with `axis:` on {#nunique} and
|
|
48
|
+
# {#mask_duplicates}, which names the axis a *fiber runs along* and
|
|
49
|
+
# asks about the values inside each fiber. The two cannot be given
|
|
50
|
+
# together.
|
|
51
|
+
#
|
|
52
|
+
# Distinctness is the family's, widened from a cell to a sub-array:
|
|
53
|
+
# two sub-arrays are the same when every cell is, with all NaN one
|
|
54
|
+
# value and -0.0 == +0.0. A sub-array holding a masked cell does not
|
|
55
|
+
# participate and never appears in the result.
|
|
56
|
+
#
|
|
57
|
+
# The result is a **view** of `self` -- the surviving sub-arrays,
|
|
58
|
+
# not copies of them -- so writing through it reaches `self`. Take a
|
|
59
|
+
# `copy` when that is not wanted. `sort:` is not available here:
|
|
60
|
+
# sub-arrays have no order to sort by. `object` arrays are refused,
|
|
61
|
+
# because their cells hold Ruby references.
|
|
62
|
+
#
|
|
63
|
+
# @param along [Integer] axis whose index enumerates the sub-arrays.
|
|
64
|
+
# @return [CArray] the distinct sub-arrays, same shape as `self`
|
|
65
|
+
# except along `along`.
|
|
66
|
+
def unique (sort: false, along: nil)
|
|
67
|
+
if along
|
|
68
|
+
if sort
|
|
69
|
+
raise ArgumentError,
|
|
70
|
+
"unique: sort: is not available with along: -- sub-arrays have " \
|
|
71
|
+
"no order to sort by; the result is in first-appearance order"
|
|
72
|
+
end
|
|
73
|
+
keys = fibers_as_cells(along, "unique")
|
|
74
|
+
args = [nil] * ndim
|
|
75
|
+
args[normalize_axis(along, "unique")] = keys.mask_duplicates.is_not_masked
|
|
76
|
+
return self[*args]
|
|
77
|
+
end
|
|
38
78
|
# Single-pass seen-set hash (C __unique_flat__), one lane per data type family:
|
|
39
79
|
# integer widens to a 64-bit key; float uses the bitwise key with all-NaN
|
|
40
80
|
# collapsed and -0.0 / +0.0 normalized; object keys on rb_hash + rb_eql and
|
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/slab_iterator.rb
CHANGED
|
@@ -95,18 +95,63 @@ class CASlabIterator < CAIterator
|
|
|
95
95
|
# the map / reduce block surface, these are mask-aware: they route through the
|
|
96
96
|
# core reduction, which handles masked sources.)
|
|
97
97
|
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
98
|
+
# Each reduction below delegates to `reference.<op>(axis: slab_axes)`, so it
|
|
99
|
+
# inherits the core data type, mask, empty / all-masked and epsilon-close
|
|
100
|
+
# contracts unchanged. See {CAIterator} for what each one computes.
|
|
101
|
+
#
|
|
102
|
+
# @!method sum
|
|
103
|
+
# Per-slab sum.
|
|
104
|
+
# @return [CArray] one value per slab (shape = {#shape}).
|
|
105
|
+
# @!method accumulate
|
|
102
106
|
# Per-slab sum kept in the source's own data type, wrapping at its width,
|
|
103
107
|
# as the core `accumulate` does -- `sum` answers in the type the core
|
|
104
108
|
# promotes to (float64 for integers).
|
|
105
109
|
# @return [CArray] one value per slab
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
+
# @!method prod
|
|
111
|
+
# Per-slab product.
|
|
112
|
+
# @return [CArray] one value per slab
|
|
113
|
+
# @!method mean
|
|
114
|
+
# Per-slab arithmetic mean.
|
|
115
|
+
# @return [CArray] one value per slab
|
|
116
|
+
# @!method min
|
|
117
|
+
# Per-slab minimum.
|
|
118
|
+
# @return [CArray] one value per slab
|
|
119
|
+
# @!method max
|
|
120
|
+
# Per-slab maximum.
|
|
121
|
+
# @return [CArray] one value per slab
|
|
122
|
+
# @!method variance
|
|
123
|
+
# Per-slab sample variance (divisor `n - 1`).
|
|
124
|
+
# @return [CArray] one value per slab
|
|
125
|
+
# @!method stddev
|
|
126
|
+
# Per-slab sample standard deviation (divisor `n - 1`).
|
|
127
|
+
# @return [CArray] one value per slab
|
|
128
|
+
# @!method variancep
|
|
129
|
+
# Per-slab population variance (divisor `n`).
|
|
130
|
+
# @return [CArray] one value per slab
|
|
131
|
+
# @!method stddevp
|
|
132
|
+
# Per-slab population standard deviation (divisor `n`).
|
|
133
|
+
# @return [CArray] one value per slab
|
|
134
|
+
# @!method all
|
|
135
|
+
# Whether every cell of each slab is true.
|
|
136
|
+
# @return [CArray] `:boolean`, one value per slab
|
|
137
|
+
# @!method any
|
|
138
|
+
# Whether any cell of each slab is true.
|
|
139
|
+
# @return [CArray] `:boolean`, one value per slab
|
|
140
|
+
# @!method minmax
|
|
141
|
+
# Per-slab minimum and maximum, found in one pass.
|
|
142
|
+
# @return [Array<CArray>] the pair `[min, max]`
|
|
143
|
+
# @!method min_index
|
|
144
|
+
# Per-slab position of the minimum, local to the slab axes.
|
|
145
|
+
# @return [CArray] one index per slab
|
|
146
|
+
# @!method max_index
|
|
147
|
+
# Per-slab position of the maximum, local to the slab axes.
|
|
148
|
+
# @return [CArray] one index per slab
|
|
149
|
+
# @!method min_addr
|
|
150
|
+
# Per-slab flat source address of the minimum -- which source cell holds it.
|
|
151
|
+
# @return [CArray] one flat address per slab
|
|
152
|
+
# @!method max_addr
|
|
153
|
+
# Per-slab flat source address of the maximum.
|
|
154
|
+
# @return [CArray] one flat address per slab
|
|
110
155
|
[:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
|
|
111
156
|
:variancep, :stddevp, :minmax, :min_index, :max_index,
|
|
112
157
|
:min_addr, :max_addr].each do |op|
|
|
@@ -260,19 +305,19 @@ class CASlabIterator < CAIterator
|
|
|
260
305
|
# multi-axis running accumulation is ambiguous), so a multi-axis slab raises --
|
|
261
306
|
# scan one axis at a time, like the order surface.
|
|
262
307
|
|
|
263
|
-
#
|
|
308
|
+
# @!method cumsum
|
|
264
309
|
# Per-slab inclusive running sum (float64), reference-shaped.
|
|
265
310
|
# @return [CArray]
|
|
266
|
-
#
|
|
311
|
+
# @!method cumprod
|
|
267
312
|
# Per-slab inclusive running product (float64), reference-shaped.
|
|
268
313
|
# @return [CArray]
|
|
269
|
-
#
|
|
314
|
+
# @!method cummax
|
|
270
315
|
# Per-slab inclusive running maximum (reference data type), reference-shaped.
|
|
271
316
|
# @return [CArray]
|
|
272
|
-
#
|
|
317
|
+
# @!method cummin
|
|
273
318
|
# Per-slab inclusive running minimum (reference data type), reference-shaped.
|
|
274
319
|
# @return [CArray]
|
|
275
|
-
#
|
|
320
|
+
# @!method cumcount
|
|
276
321
|
# Per-slab running count of present cells (int64), reference-shaped.
|
|
277
322
|
# @return [CArray]
|
|
278
323
|
[:cumsum, :cumprod, :cummax, :cummin, :cumcount].each do |op|
|
|
@@ -229,7 +229,11 @@ class CArray
|
|
|
229
229
|
else
|
|
230
230
|
values = Array.new(elements) { |i| self[i] }
|
|
231
231
|
end
|
|
232
|
-
CArray.const_string(values, encoding: encoding)
|
|
232
|
+
out = CArray.const_string(values, encoding: encoding)
|
|
233
|
+
# The builder takes a flat list, so give the shape back -- as
|
|
234
|
+
# to_fixlen_string above already does. Without this an N-D column
|
|
235
|
+
# came back 1-D.
|
|
236
|
+
ndim > 1 ? out.reshape(*shape) : out
|
|
233
237
|
end
|
|
234
238
|
|
|
235
239
|
# In-place transforms, mixed into the mutable Faces only (CAString /
|
data/lib/carray/time.rb
CHANGED
|
@@ -55,6 +55,21 @@ module CATimeCivil
|
|
|
55
55
|
doe = yoe * 365 + yoe / 4 - yoe / 100 + doy
|
|
56
56
|
era * 146097 + doe - 719468
|
|
57
57
|
end
|
|
58
|
+
|
|
59
|
+
# Same algebra for one date, on plain Integers. A literal is parsed one
|
|
60
|
+
# at a time, and routing each one through the vectorized form costs a
|
|
61
|
+
# one-cell CArray and a kernel call per literal -- which is where nearly
|
|
62
|
+
# all the time in a bulk column parse used to go (170k rows: 3.5 s, of
|
|
63
|
+
# which 3.1 s was this). Ruby's Integer `/` floors, as CArray's does,
|
|
64
|
+
# so the two forms read alike and agree everywhere.
|
|
65
|
+
def days_from_civil_1 (y, m, d)
|
|
66
|
+
y -= 1 if m <= 2
|
|
67
|
+
era = y / 400
|
|
68
|
+
yoe = y - era * 400
|
|
69
|
+
doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + (d - 1)
|
|
70
|
+
doe = yoe * 365 + yoe / 4 - yoe / 100 + doy
|
|
71
|
+
era * 146097 + doe - 719468
|
|
72
|
+
end
|
|
58
73
|
end
|
|
59
74
|
|
|
60
75
|
# Unit algebra for the time surface: how two units relate (same group, which
|
|
@@ -110,6 +125,8 @@ module CATimeUnitAlgebra
|
|
|
110
125
|
g = rgcd(a.tick_ratio, b.tick_ratio)
|
|
111
126
|
CATime::Resolution.new(Integer(g / base_ratio(fb)), fb)
|
|
112
127
|
end
|
|
128
|
+
# Alias of {#common}, used where the caller reads the result as "the finer
|
|
129
|
+
# of the two grids" rather than "the grid they share".
|
|
113
130
|
alias_method :finer, :common
|
|
114
131
|
module_function :finer
|
|
115
132
|
|
|
@@ -1654,8 +1671,7 @@ module CATimeLiteral
|
|
|
1654
1671
|
end
|
|
1655
1672
|
when String
|
|
1656
1673
|
h = parse_date_fields(spec, format)
|
|
1657
|
-
days = CATimeCivil.
|
|
1658
|
-
CA_INT64([h[:mday] || 1]))[0]
|
|
1674
|
+
days = CATimeCivil.days_from_civil_1(h[:year], h[:mon] || 1, h[:mday] || 1)
|
|
1659
1675
|
sec = Rational(days * 86400 + (h[:hour] || 0) * 3600 +
|
|
1660
1676
|
(h[:min] || 0) * 60 + (h[:sec] || 0))
|
|
1661
1677
|
sec += h[:sec_fraction] if h[:sec_fraction]
|