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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -0
  3. data/README.md +2 -2
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +33 -4
  6. data/ext/ca_axis_group.c +202 -96
  7. data/ext/ca_categorical_iterator.c +108 -54
  8. data/ext/ca_kernel_iterator.c +317 -51
  9. data/ext/ca_kernel_iterator.h +142 -35
  10. data/ext/ca_obj_array.c +62 -20
  11. data/ext/ca_obj_block.c +4 -4
  12. data/ext/ca_obj_const_string.c +85 -26
  13. data/ext/ca_obj_face.c +24 -0
  14. data/ext/ca_obj_face.h +15 -0
  15. data/ext/ca_obj_fixlen_string.c +18 -5
  16. data/ext/ca_obj_meld.c +123 -25
  17. data/ext/ca_obj_object.c +8 -0
  18. data/ext/ca_obj_select.c +49 -34
  19. data/ext/ca_obj_stack.c +3 -8
  20. data/ext/ca_obj_stride.c +72 -1
  21. data/ext/ca_obj_string.c +8 -4
  22. data/ext/ca_obj_window.c +8 -2
  23. data/ext/ca_op_ipower.c +1 -2
  24. data/ext/ca_rng_normal.h +42 -0
  25. data/ext/ca_rng_xoshiro256pp.h +105 -0
  26. data/ext/ca_sweep_engine.c +307 -143
  27. data/ext/ca_sweep_engine.h +26 -5
  28. data/ext/carray.h +21 -2
  29. data/ext/carray_access.c +32 -20
  30. data/ext/carray_address_basis.c +590 -0
  31. data/ext/carray_broadcast.c +3 -3
  32. data/ext/carray_call_cfunc.c +667 -483
  33. data/ext/carray_cast.c +115 -41
  34. data/ext/carray_copy.c +55 -30
  35. data/ext/carray_core.c +83 -3
  36. data/ext/carray_count.c +9 -10
  37. data/ext/carray_factorize.c +46 -25
  38. data/ext/carray_internal.h +17 -0
  39. data/ext/carray_kernels_reduce_aggregate.c +168 -0
  40. data/ext/carray_kernels_reduce_cumulative.c +270 -1
  41. data/ext/carray_kernels_reduce_extreme.c +554 -8
  42. data/ext/carray_kernels_scan.c +4 -4
  43. data/ext/carray_kernels_search.c +94 -14
  44. data/ext/carray_loop.c +7 -1
  45. data/ext/carray_mask.c +23 -8
  46. data/ext/carray_median_percentile.c +55 -0
  47. data/ext/carray_operator.c +4 -4
  48. data/ext/carray_order.c +1 -1
  49. data/ext/carray_random.c +384 -40
  50. data/ext/carray_slab.c +13 -0
  51. data/ext/carray_sort.c +20 -22
  52. data/ext/mk_call_cfunc.rb +103 -116
  53. data/ext/mkkernel.rb +297 -29
  54. data/ext/ruby_carray.c +10 -1
  55. data/ext/version.h +4 -4
  56. data/lib/carray/autoload_carray.rb +5 -3
  57. data/lib/carray/autoload_method_extension.rb +12 -0
  58. data/lib/carray/axis_group.rb +77 -0
  59. data/lib/carray/basics.rb +4 -0
  60. data/lib/carray/block_iterator.rb +92 -16
  61. data/lib/carray/categorical.rb +150 -33
  62. data/lib/carray/categorical_iterator.rb +207 -80
  63. data/lib/carray/const_string.rb +131 -27
  64. data/lib/carray/construct.rb +40 -0
  65. data/lib/carray/data_type_extension.rb +3 -0
  66. data/lib/carray/data_type_limits.rb +91 -0
  67. data/lib/carray/fixlen_string.rb +1 -1
  68. data/lib/carray/frame/csv_parser.rb +11 -4
  69. data/lib/carray/frame/frame.rb +81 -10
  70. data/lib/carray/frame/group.rb +36 -3
  71. data/lib/carray/frame/io.rb +67 -15
  72. data/lib/carray/frame/records.rb +18 -4
  73. data/lib/carray/frame/verbs.rb +14 -11
  74. data/lib/carray/inspect.rb +42 -9
  75. data/lib/carray/iterator.rb +143 -0
  76. data/lib/carray/lazy.rb +0 -37
  77. data/lib/carray/mask_gap_fill.rb +3 -1
  78. data/lib/carray/methods/discovery_along.rb +74 -0
  79. data/lib/carray/methods/factorize.rb +50 -0
  80. data/lib/carray/methods/is_in.rb +13 -2
  81. data/lib/carray/methods/locate_addr.rb +75 -2
  82. data/lib/carray/methods/mask_duplicates.rb +35 -1
  83. data/lib/carray/methods/nunique.rb +22 -1
  84. data/lib/carray/methods/repeat.rb +110 -0
  85. data/lib/carray/methods/unique.rb +41 -1
  86. data/lib/carray/rng.rb +86 -0
  87. data/lib/carray/slab_iterator.rb +58 -13
  88. data/lib/carray/string_operation_extension.rb +5 -1
  89. data/lib/carray/time.rb +18 -2
  90. data/lib/carray/window_iterator.rb +142 -20
  91. data/lib/carray.rb +2 -0
  92. data/yard-stubs/ca_obj_block.rb +2 -7
  93. data/yard-stubs/ca_obj_window.rb +10 -2
  94. data/yard-stubs/carray_access.rb +1 -1
  95. data/yard-stubs/carray_broadcast.rb +1 -1
  96. data/yard-stubs/carray_core.rb +0 -80
  97. data/yard-stubs/carray_count.rb +7 -2
  98. data/yard-stubs/carray_lazy.rb +205 -0
  99. data/yard-stubs/carray_math.rb +1486 -3
  100. data/yard-stubs/carray_median_percentile.rb +16 -2
  101. data/yard-stubs/carray_order.rb +9 -69
  102. data/yard-stubs/carray_slab.rb +9 -7
  103. data/yard-stubs/carray_sort.rb +7 -5
  104. metadata +9 -1
@@ -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 == CA_OBJECT || data_type == CA_FIXLEN
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
- # User-facing YARD docs for #locate_addr and #locate_nearest_addr live
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
- def mask_duplicates (axis: nil)
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
- def nunique (axis: nil, keep_axis: false)
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
- def unique (sort: false)
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
@@ -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
- # @overload sum
99
- # Per-slab sum, delegating to `reference.sum(axis: slab_axes)`.
100
- # @return [CArray] one value per slab (shape = self.dim minus the slab axes)
101
- # @overload accumulate
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
- # The rest are analogous: prod / mean / min / max, sample and population
107
- # variance / stddev, all / any, fused minmax ([min, max] pair), the axis-local
108
- # position min_index / max_index (index within the slab axes), and the flat
109
- # source address min_addr / max_addr (which source cell holds the extremum).
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
- # @overload cumsum
308
+ # @!method cumsum
264
309
  # Per-slab inclusive running sum (float64), reference-shaped.
265
310
  # @return [CArray]
266
- # @overload cumprod
311
+ # @!method cumprod
267
312
  # Per-slab inclusive running product (float64), reference-shaped.
268
313
  # @return [CArray]
269
- # @overload cummax
314
+ # @!method cummax
270
315
  # Per-slab inclusive running maximum (reference data type), reference-shaped.
271
316
  # @return [CArray]
272
- # @overload cummin
317
+ # @!method cummin
273
318
  # Per-slab inclusive running minimum (reference data type), reference-shaped.
274
319
  # @return [CArray]
275
- # @overload cumcount
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.days_from_civil(CA_INT64([h[:year]]), CA_INT64([h[:mon] || 1]),
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]