carray 3.0.3 → 3.0.4

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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -0
  3. data/README.md +3 -2
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +1 -6
  7. data/ext/ca_axis_group.c +27 -27
  8. data/ext/ca_binop_dispatch.c +1 -29
  9. data/ext/ca_binop_dispatch.h +0 -1
  10. data/ext/ca_categorical_iterator.c +205 -134
  11. data/ext/ca_compare.c +7 -18
  12. data/ext/ca_group_iter.c +16 -17
  13. data/ext/ca_kernel_iterator.c +1234 -1994
  14. data/ext/ca_kernel_iterator.h +391 -437
  15. data/ext/ca_monop_dispatch.c +82 -59
  16. data/ext/ca_monop_dispatch.h +1 -0
  17. data/ext/ca_obj_array.c +96 -39
  18. data/ext/ca_obj_bincmp.c +24 -21
  19. data/ext/ca_obj_binop.c +15 -26
  20. data/ext/ca_obj_bitarray.c +3 -2
  21. data/ext/ca_obj_bitfield.c +5 -6
  22. data/ext/ca_obj_block.c +18 -9
  23. data/ext/ca_obj_byte_swap.c +5 -6
  24. data/ext/ca_obj_const_string.c +173 -19
  25. data/ext/ca_obj_face.c +238 -57
  26. data/ext/ca_obj_face.h +19 -0
  27. data/ext/ca_obj_fake.c +15 -29
  28. data/ext/ca_obj_farray.c +6 -3
  29. data/ext/ca_obj_field.c +17 -5
  30. data/ext/ca_obj_fixlen_string.c +2 -3
  31. data/ext/ca_obj_grid.c +406 -132
  32. data/ext/ca_obj_meld.c +172 -34
  33. data/ext/ca_obj_moncmp.c +23 -8
  34. data/ext/ca_obj_monop.c +67 -57
  35. data/ext/ca_obj_object.c +157 -51
  36. data/ext/ca_obj_record.c +12 -7
  37. data/ext/ca_obj_reduce.c +13 -8
  38. data/ext/ca_obj_refer.c +25 -27
  39. data/ext/ca_obj_remap.c +30 -9
  40. data/ext/ca_obj_repeat.c +9 -10
  41. data/ext/ca_obj_roll.c +6 -5
  42. data/ext/ca_obj_select.c +14 -9
  43. data/ext/ca_obj_select_axis.c +15 -47
  44. data/ext/ca_obj_shift.c +11 -1
  45. data/ext/ca_obj_stack.c +179 -34
  46. data/ext/ca_obj_stride.c +162 -115
  47. data/ext/ca_obj_string.c +71 -4
  48. data/ext/ca_obj_tile.c +6 -5
  49. data/ext/ca_obj_time.c +22 -6
  50. data/ext/ca_obj_timedelta.c +22 -6
  51. data/ext/ca_obj_transpose.c +5 -3
  52. data/ext/ca_obj_triop.c +23 -32
  53. data/ext/ca_obj_window.c +190 -26
  54. data/ext/ca_sweep_engine.c +2 -2
  55. data/ext/caframe_csv_reader.c +80 -1
  56. data/ext/carray.h +181 -75
  57. data/ext/carray_access.c +184 -96
  58. data/ext/carray_address_basis.c +343 -87
  59. data/ext/carray_attribute.c +89 -6
  60. data/ext/carray_bincount.c +10 -8
  61. data/ext/carray_broadcast.c +13 -8
  62. data/ext/carray_build_flags.h +3 -0
  63. data/ext/carray_call_cfunc.c +108 -0
  64. data/ext/carray_cast.c +142 -24
  65. data/ext/carray_cast_func.rb +46 -6
  66. data/ext/carray_class.c +4 -3
  67. data/ext/carray_config.h +28 -0
  68. data/ext/carray_conversion.c +16 -7
  69. data/ext/carray_copy.c +51 -14
  70. data/ext/carray_core.c +147 -55
  71. data/ext/carray_count.c +50 -20
  72. data/ext/carray_element.c +76 -33
  73. data/ext/carray_factorize.c +118 -64
  74. data/ext/carray_generate.c +191 -50
  75. data/ext/carray_histogram.c +62 -17
  76. data/ext/carray_hold.c +12 -15
  77. data/ext/carray_index_classifier.c +388 -176
  78. data/ext/carray_internal.h +336 -29
  79. data/ext/carray_kernels_bincmp.c +4453 -0
  80. data/ext/carray_kernels_binop.c +11368 -0
  81. data/ext/carray_kernels_init.c +1734 -0
  82. data/ext/carray_kernels_moncmp.c +2104 -0
  83. data/ext/carray_kernels_monop.c +18369 -0
  84. data/ext/carray_kernels_reduce_aggregate.c +23818 -0
  85. data/ext/carray_kernels_reduce_boolean.c +406 -0
  86. data/ext/carray_kernels_reduce_cumulative.c +10086 -0
  87. data/ext/carray_kernels_reduce_extreme.c +17634 -0
  88. data/ext/carray_kernels_reduce_variance.c +3849 -0
  89. data/ext/carray_kernels_scan.c +4302 -0
  90. data/ext/carray_kernels_search.c +33829 -0
  91. data/ext/carray_kernels_sort.c +10906 -0
  92. data/ext/carray_kernels_triop.c +1864 -0
  93. data/ext/carray_key_windows.c +216 -0
  94. data/ext/carray_lazy.c +170 -111
  95. data/ext/carray_mask.c +292 -72
  96. data/ext/carray_median_percentile.c +82 -28
  97. data/ext/carray_memory_view.c +669 -561
  98. data/ext/carray_operator.c +124 -47
  99. data/ext/carray_order.c +384 -76
  100. data/ext/carray_partition.c +28 -27
  101. data/ext/carray_random.c +67 -13
  102. data/ext/carray_round.c +273 -0
  103. data/ext/carray_scatter.c +63 -38
  104. data/ext/carray_segment.c +3 -3
  105. data/ext/carray_slab.c +91 -28
  106. data/ext/carray_sort.c +35 -35
  107. data/ext/carray_struct.c +19 -18
  108. data/ext/carray_test.c +26 -5
  109. data/ext/carray_undef.c +4 -0
  110. data/ext/carray_utils.c +246 -45
  111. data/ext/mk_call_cfunc.rb +19 -2
  112. data/ext/mkkernel.rb +198 -240
  113. data/ext/ruby_carray.c +10 -4
  114. data/ext/version.h +4 -4
  115. data/lib/carray/attribute.rb +126 -22
  116. data/lib/carray/autoload_carray.rb +8 -31
  117. data/lib/carray/autoload_method_extension.rb +14 -5
  118. data/lib/carray/axis_group.rb +255 -106
  119. data/lib/carray/basics.rb +78 -10
  120. data/lib/carray/bincount_nd.rb +12 -2
  121. data/lib/carray/block_iterator.rb +126 -23
  122. data/lib/carray/boolean_reduce.rb +17 -17
  123. data/lib/carray/categorical.rb +165 -43
  124. data/lib/carray/categorical_iterator.rb +130 -27
  125. data/lib/carray/complex.rb +5 -5
  126. data/lib/carray/conditional.rb +67 -18
  127. data/lib/carray/const_string.rb +14 -5
  128. data/lib/carray/construct.rb +15 -11
  129. data/lib/carray/data_type_extension.rb +1 -0
  130. data/lib/carray/distance_limit.rb +77 -0
  131. data/lib/carray/fixlen_string.rb +7 -1
  132. data/lib/carray/frame/column_verbs.rb +70 -0
  133. data/lib/carray/frame/convert.rb +69 -0
  134. data/lib/carray/frame/csv_parser.rb +111 -17
  135. data/lib/carray/frame/describe.rb +3 -2
  136. data/lib/carray/frame/frame.rb +246 -21
  137. data/lib/carray/frame/group.rb +510 -95
  138. data/lib/carray/frame/io.rb +115 -28
  139. data/lib/carray/frame/join.rb +312 -51
  140. data/lib/carray/frame/records.rb +83 -7
  141. data/lib/carray/frame/reshape.rb +42 -35
  142. data/lib/carray/frame/sort.rb +7 -19
  143. data/lib/carray/frame/verbs.rb +188 -48
  144. data/lib/carray/frame/windows.rb +117 -0
  145. data/lib/carray/frame.rb +2 -0
  146. data/lib/carray/fuse_source.rb +11 -4
  147. data/lib/carray/fusion.rb +40 -16
  148. data/lib/carray/histogram.rb +6 -1
  149. data/lib/carray/inspect.rb +31 -8
  150. data/lib/carray/iterator.rb +85 -7
  151. data/lib/carray/key_windows.rb +620 -0
  152. data/lib/carray/lazy.rb +182 -183
  153. data/lib/carray/mask_gap_fill.rb +63 -57
  154. data/lib/carray/meld_reduce.rb +12 -14
  155. data/lib/carray/methods/bin.rb +26 -18
  156. data/lib/carray/methods/bincount.rb +16 -0
  157. data/lib/carray/methods/bit_string.rb +1 -1
  158. data/lib/carray/methods/choose.rb +16 -3
  159. data/lib/carray/methods/composition.rb +3 -1
  160. data/lib/carray/methods/gather_nd.rb +14 -5
  161. data/lib/carray/methods/insert_block.rb +2 -2
  162. data/lib/carray/methods/is_between.rb +59 -0
  163. data/lib/carray/methods/is_in.rb +53 -36
  164. data/lib/carray/methods/join.rb +1 -6
  165. data/lib/carray/methods/lag.rb +49 -0
  166. data/lib/carray/methods/locate_addr.rb +47 -51
  167. data/lib/carray/methods/mode.rb +1 -1
  168. data/lib/carray/methods/nunique.rb +11 -6
  169. data/lib/carray/methods/resize.rb +3 -3
  170. data/lib/carray/methods/value_counts.rb +4 -11
  171. data/lib/carray/record_order.rb +3 -3
  172. data/lib/carray/rng.rb +3 -0
  173. data/lib/carray/runtime.rb +53 -8
  174. data/lib/carray/segment_iterator.rb +313 -82
  175. data/lib/carray/serialize.rb +24 -10
  176. data/lib/carray/slab_iterator.rb +50 -0
  177. data/lib/carray/string.rb +105 -5
  178. data/lib/carray/string_operation_extension.rb +8 -1
  179. data/lib/carray/struct.rb +9 -21
  180. data/lib/carray/struct_builder.rb +19 -8
  181. data/lib/carray/time.rb +726 -104
  182. data/lib/carray/window_iterator.rb +122 -16
  183. data/lib/carray.rb +7 -0
  184. data/yard-stubs/ca_obj_bitfield.rb +1 -1
  185. data/yard-stubs/ca_obj_field.rb +4 -0
  186. data/yard-stubs/ca_obj_grid.rb +21 -10
  187. data/yard-stubs/ca_obj_refer.rb +2 -1
  188. data/yard-stubs/ca_obj_roll.rb +2 -2
  189. data/yard-stubs/ca_obj_shift.rb +5 -1
  190. data/yard-stubs/ca_obj_tile.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +8 -3
  192. data/yard-stubs/carray_access.rb +6 -1
  193. data/yard-stubs/carray_attribute.rb +0 -5
  194. data/yard-stubs/carray_cast.rb +10 -0
  195. data/yard-stubs/carray_conversion.rb +2 -1
  196. data/yard-stubs/carray_copy.rb +4 -2
  197. data/yard-stubs/carray_count.rb +4 -4
  198. data/yard-stubs/carray_element.rb +10 -4
  199. data/yard-stubs/carray_generate.rb +21 -1
  200. data/yard-stubs/carray_mask.rb +26 -2
  201. data/yard-stubs/carray_math.rb +27 -1
  202. data/yard-stubs/carray_median_percentile.rb +5 -2
  203. data/yard-stubs/carray_memory_view.rb +40 -23
  204. data/yard-stubs/carray_order.rb +29 -18
  205. data/yard-stubs/carray_random.rb +13 -4
  206. data/yard-stubs/carray_scatter.rb +14 -9
  207. data/yard-stubs/carray_slab.rb +9 -2
  208. data/yard-stubs/carray_sort.rb +6 -5
  209. data/yard-stubs/carray_test.rb +5 -3
  210. metadata +25 -3
  211. data/lib/carray/arrow_tensor.rb +0 -401
  212. data/lib/carray/table.rb +0 -108
@@ -0,0 +1,620 @@
1
+ # ----------------------------------------------------------------------------
2
+ #
3
+ # carray/key_windows.rb
4
+ #
5
+ # CAKeyWindowIterator: a moving window bounded by a width of a key (a time
6
+ # or a number) rather than by a count of cells. Built by
7
+ # `value.windows(on: key, before:, after:)`.
8
+ #
9
+ # ----------------------------------------------------------------------------
10
+
11
+ require "carray/segment_iterator"
12
+ require "carray/distance_limit"
13
+
14
+ # A moving window per row, bounded by a width of a key: the window of a row
15
+ # whose key is t holds the rows whose key lies between t - before and
16
+ # t + after. Each reduction answers once per row, in row order.
17
+ #
18
+ # Given `at:`, the windows sit at those points instead of at the rows: the
19
+ # window of a point t holds the rows whose key lies between t - before and
20
+ # t + after, and each reduction answers once per point, in the order of the
21
+ # points. A grid of points gives windows on a grid (`after:` the width,
22
+ # `exclude: :upper` for [t, t + width)); a window with no row answers as an
23
+ # empty reduction does.
24
+ #
25
+ # Besides the reductions, the iterator answers what each window covered:
26
+ # {#first_addr} / {#last_addr} (the rows), {#first_key} / {#last_key} (their
27
+ # keys), {#elements} (the number of rows) and {#bounds} (the range the
28
+ # window was asked to cover), and, given `interval:`, {#is_complete}
29
+ # (whether the window holds exactly the keys of that grid).
30
+ #
31
+ # The keys need not be sorted. A row whose key (or `by:` key) is masked, or
32
+ # a float key that is NaN or infinite, is in no window and has no window: its
33
+ # answers are UNDEF (a distance to an infinite key has no value). A masked
34
+ # value is in the windows (it counts in {#elements}) but, as in any
35
+ # reduction, takes no part in the reductions.
36
+ #
37
+ # The windows overlap, so the per-cell operations of the partition members
38
+ # (`map`, the scans, `lag`, `order`, `strip_mask`) are not offered.
39
+ class CAKeyWindowIterator < CAIterator
40
+
41
+ EXCLUDE = [nil, :lower, :upper, :both].freeze
42
+ private_constant :EXCLUDE
43
+
44
+ # value : the 1-D CArray whose windows are reduced.
45
+ # on : the key, one value per row: a CATime, a CATimedelta or a
46
+ # numeric CArray.
47
+ # before : how far the window reaches back from a row's key.
48
+ # after : how far it reaches forward.
49
+ # exclude : which end of the window is left out (:lower by default).
50
+ # by : a CArray, or an Array of them, that must match exactly:
51
+ # a row's window holds only the rows with the same by values.
52
+ # interval : the grid step of the key, for {#is_complete}.
53
+ # at : nil, or the points the windows sit at, a 1-D CArray of the
54
+ # key's kind; the answers are then one per point.
55
+ def initialize (value, on:, before: nil, after: nil, exclude: :lower,
56
+ by: nil, interval: nil, at: nil)
57
+ @value = value
58
+ unless on.is_a?(CArray) && on.ndim == 1 && on.elements == value.dim0
59
+ raise ArgumentError,
60
+ "windows: on: is a CArray of one key per row " \
61
+ "(#{value.dim0} rows)"
62
+ end
63
+ unless EXCLUDE.include?(exclude)
64
+ raise ArgumentError,
65
+ "windows: exclude: must be nil, :lower, :upper or :both " \
66
+ "(got #{exclude.inspect})"
67
+ end
68
+ if before.nil? && after.nil?
69
+ raise ArgumentError, "windows: give before:, after: or both"
70
+ end
71
+ unless at.nil?
72
+ unless by.nil?
73
+ raise ArgumentError, "windows: at: and by: cannot be given together"
74
+ end
75
+ unless at.is_a?(CArray) && at.ndim == 1
76
+ raise ArgumentError, "windows: at: is a one-dimensional CArray of points"
77
+ end
78
+ end
79
+ @key = on
80
+ @n = value.dim0
81
+ # Taken as they are now, as the windows are: bounds reads them later.
82
+ @points = at&.copy
83
+ @nout = at.nil? ? @n : at.elements
84
+ @ndim = 1
85
+ @shape = [@nout]
86
+ before, after, interval = { before: before, after: after, interval: interval }.map do |kw, w|
87
+ CArray::DistanceLimit.read(w, on, "windows: #{kw}:")
88
+ end
89
+ @interval = interval
90
+ keys, bw, aw, iv, points = key_numbers(on, before, after, interval, at)
91
+ @before_value = before
92
+ @after_value = after
93
+ groups = group_codes(by)
94
+
95
+ # The rows that have a window, sorted by (group, key, row).
96
+ valid = keys.is_not_masked & groups.is_not_masked
97
+ valid &= keys.is_finite if keys.float?
98
+ valid = valid.strip_mask(false)
99
+ rows = CArray.int64(@n).seq[valid].copy
100
+ perm = CArray.sort_addr(groups.project(rows), keys.project(rows), rows)
101
+ @order = rows.project(perm)
102
+ m = @order.elements
103
+ sk = keys.project(@order).copy
104
+ sg = groups.project(@order).int64.copy
105
+
106
+ # The windows' anchors, sorted: the rows themselves, or the points that
107
+ # have a window (one that is masked, NaN or infinite has none).
108
+ if points
109
+ pvalid = points.is_not_masked
110
+ pvalid &= points.is_finite if points.float?
111
+ pvalid = pvalid.strip_mask(false)
112
+ prow = CArray.int64(@nout).seq[pvalid].copy
113
+ @anchor_order = prow.project(CArray.sort_addr(points.project(prow), prow))
114
+ sp = points.project(@anchor_order).copy
115
+ else
116
+ @anchor_order = @order
117
+ sp = nil
118
+ end
119
+ nwin = @anchor_order.elements
120
+
121
+ ex_lo = (exclude == :lower || exclude == :both) && !@fraction_lower
122
+ ex_hi = (exclude == :upper || exclude == :both) && !@fraction_upper
123
+ expected = iv && (bw / iv + aw / iv + 1 - (ex_lo ? 1 : 0) - (ex_hi ? 1 : 0)).round
124
+ if expected && expected < 1
125
+ raise ArgumentError,
126
+ "windows: before:, after: and exclude: #{exclude.inspect} leave no key " \
127
+ "in a window, so interval: has no grid point to check"
128
+ end
129
+ @lo, @hi, @complete =
130
+ CArray.__find_key_window_row_ranges__(sk, sg, bw, aw, ex_lo, ex_hi, iv, expected, sp)
131
+
132
+ # Each answer's position among the sorted windows (UNDEF for a row or a
133
+ # point with no window), to put the answers back in row or point order.
134
+ @position = CArray.int64(@nout).fill(UNDEF)
135
+ @position[@anchor_order] = CArray.int64(nwin).seq if nwin > 0
136
+
137
+ # The cells of every window, window after window, as rows of self.
138
+ counts = @hi - @lo
139
+ total = nwin > 0 ? counts.sum.to_i : 0
140
+ starts = nwin > 0 ? counts.cumsum.int64 - counts : CArray.int64(0)
141
+ @src_addr = window_cells(counts, starts, total)
142
+ @offsets = CArray.int64(nwin + 1)
143
+ @offsets[0] = 0
144
+ @offsets[1..-1] = starts + counts if nwin > 0
145
+ @segments = segments_of(value)
146
+ end
147
+
148
+ # The same windows over another value with one row per key (a CAFrame
149
+ # column): the sorting and the window bounds are not computed again.
150
+ def for_value (value) # :nodoc:
151
+ unless value.dim0 == @n
152
+ raise ArgumentError,
153
+ "windows: the value has shape #{value.shape.inspect}; " \
154
+ "windows on a key take one row per key (#{@n} rows)"
155
+ end
156
+ other = dup
157
+ other.instance_variable_set(:@value, value)
158
+ other.instance_variable_set(:@segments, segments_of(value))
159
+ other
160
+ end
161
+
162
+ # @overload inspect
163
+ # Returns a one-line summary.
164
+ # @return [String]
165
+ def inspect
166
+ "#<#{self.class} rows=#{@n} before=#{@before_value.inspect} " \
167
+ "after=#{@after_value.inspect}>"
168
+ end
169
+
170
+ # @!group What each window covered
171
+
172
+ # @overload first_addr
173
+ # Returns the first row of each row's window (int64), UNDEF where the
174
+ # window is empty or the row has none.
175
+ # @return [CArray]
176
+ def first_addr
177
+ in_rows(edge_addr(@lo))
178
+ end
179
+
180
+ # @overload last_addr
181
+ # Returns the last row of each row's window (int64), UNDEF where the
182
+ # window is empty or the row has none.
183
+ # @return [CArray]
184
+ def last_addr
185
+ in_rows(edge_addr(@hi - 1))
186
+ end
187
+
188
+ # @overload first_key
189
+ # Returns the key of each window's first row.
190
+ # @return [CArray]
191
+ def first_key
192
+ @key.project(first_addr)
193
+ end
194
+
195
+ # @overload last_key
196
+ # Returns the key of each window's last row.
197
+ # @return [CArray]
198
+ def last_key
199
+ @key.project(last_addr)
200
+ end
201
+
202
+ # @overload elements
203
+ # Returns the number of rows in each window (int64), masked values
204
+ # included; UNDEF for a row with no window.
205
+ # @return [CArray]
206
+ def elements
207
+ in_rows(@hi - @lo)
208
+ end
209
+
210
+ # @overload bounds
211
+ # Returns the range each window was asked to cover, as two arrays of the
212
+ # key's kind: the key minus `before` and the key plus `after`. Which
213
+ # end is left out is the `exclude:` given. A time key whose unit does
214
+ # not divide the width gives bounds on the finer unit the two share,
215
+ # so a bound is the one the windows were cut at.
216
+ # @return [Array(CArray, CArray)]
217
+ def bounds
218
+ [window_bound(@before_value, :-), window_bound(@after_value, :+)]
219
+ end
220
+
221
+ # @overload is_complete
222
+ # Returns whether each window holds exactly the keys of the `interval`
223
+ # grid that starts at the row's own key: every grid point once (several
224
+ # rows at one point count once) and no key off the grid. UNDEF for a
225
+ # row with no window. Needs `interval:`. A float key is on the grid
226
+ # within a relative 1e-9; the window itself compares a float key with
227
+ # its bounds exactly, so on a decimal grid (keys i * 0.1) a key that
228
+ # misses a bound in the last bit can make a window hold one key more.
229
+ # A window that can hold no key (before: 0 and after: 0 with an end
230
+ # left out) has no grid point to check, so windows refuses it together
231
+ # with interval:.
232
+ # @return [CArray] boolean
233
+ def is_complete
234
+ unless @complete
235
+ raise ArgumentError,
236
+ "is_complete: give interval: to windows, the step of the key's grid"
237
+ end
238
+ in_rows(@complete)
239
+ end
240
+
241
+ # @!endgroup
242
+
243
+ # @!group Reductions
244
+
245
+ CArray.send(:define_named_methods, self,
246
+ [:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev,
247
+ :variancep, :stddevp, :median, :first, :last, :nunique,
248
+ :count, :count_not_masked, :count_masked, :all, :any,
249
+ :min_index, :max_index], :segments_in_rows)
250
+
251
+ private def segments_in_rows (op, *args, **kw)
252
+ over_columns { |seg| in_rows(seg.public_send(op, *args, **kw)) }
253
+ end
254
+
255
+ # @overload percentile(p)
256
+ # Returns each window's p-th percentile.
257
+ # @return [CArray, Array<CArray>]
258
+ def percentile (p, **kw)
259
+ over_columns { |seg| in_rows(seg.percentile(p, **kw)) }
260
+ end
261
+
262
+ # @overload quantile
263
+ # Returns each window's five-number summary `[min, Q1, median, Q3, max]`.
264
+ # @return [Array<CArray>]
265
+ def quantile (**kw)
266
+ over_columns { |seg| in_rows(seg.quantile(**kw)) }
267
+ end
268
+
269
+ # @overload minmax
270
+ # Returns `[min, max]` of each window.
271
+ # @return [Array(CArray, CArray)]
272
+ def minmax (**kw)
273
+ over_columns { |seg| in_rows(seg.minmax(**kw)) }
274
+ end
275
+
276
+ # @overload wsum(weights)
277
+ # Returns each window's weighted sum; `weights` is shaped like the value.
278
+ # @return [CArray]
279
+ def wsum (weights, **kw)
280
+ over_columns { |seg, j| in_rows(seg.wsum(cells_of(weights, j), **kw)) }
281
+ end
282
+
283
+ # @overload wmean(weights)
284
+ # Returns each window's weighted mean.
285
+ # @return [CArray]
286
+ def wmean (weights, **kw)
287
+ over_columns { |seg, j| in_rows(seg.wmean(cells_of(weights, j), **kw)) }
288
+ end
289
+
290
+ # @overload min_addr(min_count: nil, fill_value: nil)
291
+ # Returns the row of each window's minimum (int64).
292
+ # @return [CArray]
293
+ def min_addr (fill_value: nil, **kw)
294
+ over_columns { |seg| winner_row(seg.min_addr(**kw), fill_value) }
295
+ end
296
+
297
+ # @overload max_addr(min_count: nil, fill_value: nil)
298
+ # Returns the row of each window's maximum (int64).
299
+ # @return [CArray]
300
+ def max_addr (fill_value: nil, **kw)
301
+ over_columns { |seg| winner_row(seg.max_addr(**kw), fill_value) }
302
+ end
303
+
304
+ # @overload reduce(*args, data_type: nil) { |piece| ... }
305
+ # Folds each window with the block, as {CASegmentIterator#reduce}.
306
+ # @return [CArray]
307
+ def reduce (*args, **kw, &block)
308
+ if @segments.is_a?(Array)
309
+ raise NotImplementedError,
310
+ "reduce: windows over a value of several columns (shape " \
311
+ "#{@value.shape.inspect}) have no block fold; reduce a column " \
312
+ "at a time (value[nil, j])"
313
+ end
314
+ in_rows(@segments.reduce(*args, **kw, &block))
315
+ end
316
+
317
+ # @overload each { |piece| ... }
318
+ # Yields each row's window, in row order; a row with no window yields an
319
+ # empty array.
320
+ # @return [self, Enumerator]
321
+ def each
322
+ return to_enum(:each) unless block_given?
323
+ pieces = []
324
+ if @segments.is_a?(Array)
325
+ rest = [nil] * (@value.ndim - 1)
326
+ (@offsets.elements - 1).times do |p|
327
+ rows = @src_addr[@offsets[p]...@offsets[p + 1]]
328
+ pieces << @value[rows, *rest]
329
+ end
330
+ else
331
+ @segments.each { |piece| pieces << piece }
332
+ end
333
+ empty = @value[0...0, *[nil] * (@value.ndim - 1)]
334
+ @nout.times do |r|
335
+ p = @position[r]
336
+ yield(UNDEF.equal?(p) ? empty : pieces[p])
337
+ end
338
+ self
339
+ end
340
+
341
+ # @!endgroup
342
+
343
+ private
344
+
345
+ # Answers per sorted row, put back in row order.
346
+ def in_rows (result)
347
+ return result.map { |r| in_rows(r) } if result.is_a?(Array)
348
+ result.project(@position)
349
+ end
350
+
351
+ # A winner's address among the gathered cells, as a row of self, in row
352
+ # order; fill_value fills the windows with none, as the core fills it.
353
+ def winner_row (addr, fill_value)
354
+ out = in_rows(@src_addr.project(addr))
355
+ out[out.is_masked] = fill_value unless fill_value.nil?
356
+ out
357
+ end
358
+
359
+ # The row at sorted position pos[i] of the window of sorted row i, UNDEF
360
+ # where the window is empty.
361
+ def edge_addr (pos)
362
+ return CArray.int64(0) if pos.elements == 0
363
+ out = @order.project(pos)
364
+ out[(@hi - @lo).le(0)] = UNDEF
365
+ out
366
+ end
367
+
368
+ # A weights array taken through the same cells as the value: one weight
369
+ # per row, or for a value of several columns, weights shaped like it
370
+ # (column +j+ of them).
371
+ def cells_of (weights, j = nil)
372
+ return weights unless weights.is_a?(CArray)
373
+ if j && weights.ndim > 1 && weights.dim0 == @n
374
+ weights = weights.reshape(@n, weights.elements / @n)[nil, j]
375
+ end
376
+ return weights unless weights.elements == @n
377
+ weights.project(@src_addr)
378
+ end
379
+
380
+ # The segment iterators the reductions run on: one for a value with one
381
+ # cell per row, and for a value of several columns (each row a profile)
382
+ # one per trailing column, every one over the same windows.
383
+ def segments_of (value)
384
+ return CASegmentIterator.new(value.project(@src_addr), offsets: @offsets) if value.ndim == 1
385
+ width = value.shape[1..].inject(1, :*)
386
+ flat = value.reshape(@n, width)
387
+ Array.new(width) do |j|
388
+ CASegmentIterator.new(flat[nil, j].project(@src_addr), offsets: @offsets)
389
+ end
390
+ end
391
+
392
+ # A reduction of the windows: as it is for a value with one cell per row;
393
+ # for a value of several columns, of each column, the answers laid side
394
+ # by side in the value's trailing shape, as a group reduces a profile
395
+ # column along the rows.
396
+ def over_columns (&block)
397
+ return block.call(@segments, nil) unless @segments.is_a?(Array)
398
+ trail = @value.shape[1..]
399
+ if @segments.empty?
400
+ one = block.call(CASegmentIterator.new(CArray.new(@value.data_type, [0], bytes: @value.bytes),
401
+ offsets: CArray.int64(1)), nil)
402
+ return lay_out(Array.new(0), one, trail)
403
+ end
404
+ results = @segments.each_with_index.map { |seg, j| block.call(seg, j) }
405
+ lay_out(results, results.first, trail)
406
+ end
407
+
408
+ # Answers of the columns (each one per row) as one array of the rows and
409
+ # the trailing shape; an Array of answers (minmax, quantile) answer by
410
+ # answer.
411
+ def lay_out (results, sample, trail)
412
+ if sample.is_a?(Array)
413
+ return sample.each_index.map { |k| lay_out(results.map { |r| r[k] }, sample[k], trail) }
414
+ end
415
+ out = CArray.new(sample.data_type, [@nout, *trail], bytes: sample.bytes)
416
+ flat = out.reshape(@nout, results.size)
417
+ results.each_with_index { |r, j| flat[nil, j] = r }
418
+ out
419
+ end
420
+
421
+ # The rows of every window, window after window.
422
+ def window_cells (counts, starts, total)
423
+ return CArray.int64(0) if total == 0
424
+ nz = counts.gt(0)
425
+ owner = CArray.int64(counts.elements).seq[nz].copy
426
+ first = starts[nz].copy
427
+ mark = CArray.int64(total).fill(0)
428
+ mark[first] = 1
429
+ run = mark.cumsum.int64 - 1
430
+ anchor = owner.project(run)
431
+ pos = @lo.project(anchor) + (CArray.int64(total).seq - first.project(run))
432
+ @order.project(pos)
433
+ end
434
+
435
+ # The keys and widths as numbers the walk compares: ticks for a time or a
436
+ # duration key, the values for a numeric one. Widths that are not whole
437
+ # in the key's ticks scale both up to a finer grid. A window is decided
438
+ # by differences of keys alone, so integer keys are measured from their
439
+ # smallest value: that is what lets a uint64 key, or ticks on a finer
440
+ # grid, be compared as int64.
441
+ #
442
+ # Points (at:) are converted together with the keys -- joined, converted
443
+ # once, and split -- so that both are measured from the same origin on
444
+ # the same grid.
445
+ def key_numbers (key, before, after, interval, points = nil)
446
+ if key.is_a?(CATime) || key.is_a?(CATimedelta)
447
+ if points
448
+ unless points.instance_of?(key.class)
449
+ raise ArgumentError,
450
+ "windows: at: holds points of the key's kind, a #{key.class} " \
451
+ "(got #{CArray::Inspector.type_label(points)})"
452
+ end
453
+ unless CATimeUnitAlgebra.same_group?(key.unit.base, points.unit.base)
454
+ raise ArgumentError,
455
+ "windows: at: holds points in #{points.unit.count} #{points.unit.base}, " \
456
+ "which cannot be measured on a key in #{key.unit.count} #{key.unit.base} " \
457
+ "(months and years have no fixed length); convert one with to_unit"
458
+ end
459
+ unit = CATimeUnitAlgebra.finer(key.unit, points.unit)
460
+ key, points = key.to_unit(unit), points.to_unit(unit)
461
+ end
462
+ widths = [before, after, interval].map { |w| w.nil? ? nil : duration_in_ticks(w, key.unit) }
463
+ scale = widths.compact.map(&:denominator).inject(1, :lcm)
464
+ ticks = from_smallest(joined(key.ticks, points&.ticks), scale)
465
+ bw, aw, iv = widths.map { |w| w && (w * scale).to_i }
466
+ bw ||= 0
467
+ aw ||= 0
468
+ check_widths(bw, aw, iv)
469
+ ticks, points = split(ticks, points)
470
+ return [ticks, bw, aw, iv, points]
471
+ end
472
+ unless key.numeric? && !key.complex?
473
+ raise ArgumentError,
474
+ "windows: on: is a time, a duration or a real number key " \
475
+ "(got #{CArray::Inspector.type_label(key)})"
476
+ end
477
+ if points && (points.is_a?(CATime) || points.is_a?(CATimedelta) ||
478
+ !points.numeric? || points.complex?)
479
+ raise ArgumentError,
480
+ "windows: at: holds points of the key's kind, real numbers " \
481
+ "(got #{CArray::Inspector.type_label(points)})"
482
+ end
483
+ both = joined(key, points)
484
+ widths = [before, after, interval]
485
+ if both.integer? && (interval.nil? || whole?(interval))
486
+ # Integer keys are compared as integers whatever the widths are
487
+ # given as: k >= t - w holds for an integer k exactly when
488
+ # k >= t - floor(w), and when w is not whole, k > t - w does too, so
489
+ # a bound at a fraction is the bound at its floor, included. An
490
+ # infinite width leaves its side open; the keys are then measured
491
+ # from the smallest, so that a bound at the int64 limit is beyond
492
+ # every key.
493
+ open = [before, after].any? { |w| w.is_a?(Float) && w.infinite? }
494
+ bw, aw = [before, after].map { |w| w.nil? ? 0 : integer_width(w) }
495
+ iv = interval && interval.to_r.to_i
496
+ check_widths(bw, aw, iv)
497
+ @fraction_lower = !before.nil? && !whole?(before)
498
+ @fraction_upper = !after.nil? && !whole?(after)
499
+ numbers, points = split(from_smallest(both, 1, shift: open), points)
500
+ [numbers, bw, aw, iv, points]
501
+ elsif both.integer?
502
+ # A fractional interval on integer keys: compared as float64, which
503
+ # is exact while the keys span less than 2**53.
504
+ bw, aw, iv = widths.map { |w| w && w.to_f }
505
+ bw ||= 0.0
506
+ aw ||= 0.0
507
+ check_widths(bw, aw, iv)
508
+ numbers = from_smallest(both, 1, shift: true)
509
+ if numbers.elements > 0 && (span = numbers.max) != UNDEF && span > 2**53
510
+ raise ArgumentError,
511
+ "windows: integer keys spanning #{span} cannot be compared with " \
512
+ "a fractional interval: exactly; give a whole interval:"
513
+ end
514
+ numbers, points = split(numbers.float64, points)
515
+ [numbers, bw, aw, iv, points]
516
+ else
517
+ bw, aw, iv = widths.map { |w| w && w.to_f }
518
+ bw ||= 0.0
519
+ aw ||= 0.0
520
+ check_widths(bw, aw, iv)
521
+ numbers, points = split(both.float64, points)
522
+ [numbers, bw, aw, iv, points]
523
+ end
524
+ end
525
+
526
+ # Whether a width is a whole number (an infinite one is not).
527
+ def whole? (w)
528
+ !(w.is_a?(Float) && w.infinite?) && w.to_r.denominator == 1
529
+ end
530
+
531
+ # A width on integer keys as an integer: its floor, or the int64 limit
532
+ # for an infinite one.
533
+ def integer_width (w)
534
+ return 2**63 - 1 if w.is_a?(Float) && w.infinite?
535
+ [w.to_r.floor, 2**63 - 1].min
536
+ end
537
+
538
+ # The keys followed by the points, one array, when there are points.
539
+ def joined (keys, points)
540
+ points ? CArray.concatenate([keys, points]) : keys
541
+ end
542
+
543
+ # The keys and the points (nil without them) back out of a joined array.
544
+ def split (numbers, points)
545
+ return [numbers, nil] unless points
546
+ [numbers[0...@n].copy, numbers[@n...numbers.elements].copy]
547
+ end
548
+
549
+ def window_bound (width, op)
550
+ key = @points || @key
551
+ return key if width.nil?
552
+ if key.is_a?(CATime) || key.is_a?(CATimedelta)
553
+ key = key.to_unit(CATimeUnitAlgebra.finer(key.unit, width.unit))
554
+ end
555
+ key.public_send(op, width)
556
+ end
557
+
558
+ # A duration in ticks of the key's unit, as a Rational.
559
+ def duration_in_ticks (w, unit)
560
+ count, base = w.value * w.unit.count, w.unit.base
561
+ unless CATimeUnitAlgebra.same_group?(base, unit.base)
562
+ raise ArgumentError,
563
+ "windows: a width of #{count} #{base} cannot be measured on a key in " \
564
+ "#{unit.count} #{unit.base} (months and years have no fixed length)"
565
+ end
566
+ Rational(count) * CATimeUnitAlgebra.base_ratio(base) /
567
+ (unit.count * CATimeUnitAlgebra.base_ratio(unit.base))
568
+ end
569
+
570
+ # An integer key as int64 numbers scaled by `scale`: the values
571
+ # themselves when they fit, else measured from the smallest value,
572
+ # refused when even the span does not fit.
573
+ def from_smallest (key, scale, shift: false)
574
+ lo = key.min
575
+ return key.int64 if lo == UNDEF
576
+ hi = key.max
577
+ if !shift && lo * scale >= -2**63 && hi * scale <= 2**63 - 1
578
+ return scale == 1 ? key.int64 : key.int64 * scale
579
+ end
580
+ span = hi - lo
581
+ if span * scale > 2**63 - 1
582
+ raise ArgumentError,
583
+ "windows: the keys span #{span}#{scale == 1 ? "" : " ticks, #{span * scale} " "on the grid the widths need,"} more than an int64 holds"
584
+ end
585
+ # uint64 subtracts in its own type, which holds every difference; any
586
+ # other integer type is widened first, so the subtraction cannot wrap.
587
+ numbers = key.data_type == CA_UINT64 ? (key - lo).int64 : key.int64 - lo
588
+ scale == 1 ? numbers : numbers * scale
589
+ end
590
+
591
+ def check_widths (bw, aw, iv)
592
+ return unless iv
593
+ unless iv > 0
594
+ raise ArgumentError, "windows: interval: is positive"
595
+ end
596
+ [bw, aw].each do |w|
597
+ q = w.to_f / iv
598
+ next if (q - q.round).abs <= 1e-9 * [1.0, q.abs].max
599
+ raise ArgumentError,
600
+ "windows: before: and after: are whole numbers of interval:, " \
601
+ "so a complete window has a definite set of keys"
602
+ end
603
+ end
604
+
605
+ # A code per row for the by: keys, equal codes for equal keys, none for a
606
+ # row with a masked by: cell; zeros when there is no by:.
607
+ def group_codes (by)
608
+ return CArray.int64(@n).fill(0) if by.nil?
609
+ keys = by.is_a?(Array) ? by : [by]
610
+ if keys.empty?
611
+ raise ArgumentError, "windows: by: names no key"
612
+ end
613
+ keys.each do |k|
614
+ unless k.is_a?(CArray) && k.ndim == 1 && k.elements == @n
615
+ raise ArgumentError, "windows: by: is a CArray of one value per row (#{@n} rows)"
616
+ end
617
+ end
618
+ CACategorical.send(:from_keys, keys).codes
619
+ end
620
+ end