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
@@ -5,10 +5,11 @@ class CArray
5
5
  # each cell whose value appears in the set `values`.
6
6
  #
7
7
  # `values` is treated as a set, not as an operand to broadcast: it
8
- # may be any shape (or an Array / Range) and is flattened to a
8
+ # may be any shape (or an Array) and is flattened to a
9
9
  # single seen-set, so its shape need not match `self`. Only one
10
10
  # argument is accepted; to test a few immediate values pass an
11
- # Array (`a.is_in([0, -1])`).
11
+ # Array (`a.is_in([0, -1])`). A single value is a set of one, as
12
+ # `[value]` is (`a.is_in(2)` is `a.is_in([2])`).
12
13
  #
13
14
  # When `self` and `values` have different numeric data types they are
14
15
  # promoted to a common type first (the same promotion binops use,
@@ -46,8 +47,10 @@ class CArray
46
47
  # For a per-fiber "does this fiber contain any of these values"
47
48
  # reduction, compose with {#any}: `a.is_in(values).any(axis: k)`.
48
49
  #
49
- # @param values [CArray, Array, Range] the set to test membership
50
+ # @param values [CArray, Array] the set to test membership
50
51
  # against. Promoted with `self` to a common data type.
52
+ # @raise [ArgumentError] for a Range, which reads as an interval rather
53
+ # than the set of its steps.
51
54
  # Between two time arrays the question is about instants, not ticks:
52
55
  # `values` is reconciled into `self`'s unit first, so a `:D` array and an
53
56
  # `:h` array match on the instants they share. The same holds for the set
@@ -55,6 +58,7 @@ class CArray
55
58
  #
56
59
  # @return [CArray] boolean CArray of the same shape as `self`.
57
60
  def is_in (values)
61
+ refuse_range_set(values, "is_in")
58
62
  a, b = promote_value_set(values)
59
63
  a.__send__(:__is_in__, b)
60
64
  end
@@ -69,11 +73,12 @@ class CArray
69
73
  # always flat, like {#unique}, because the distinct values of a
70
74
  # fiber vary in number.
71
75
  #
72
- # @param other [CArray, Array, Range] promoted with `self`.
76
+ # @param other [CArray, Array] promoted with `self` (a Range is refused, as by {#is_in}).
73
77
  # @param sort [Boolean] when true, return the values sorted
74
78
  # ascending instead of in first-appearance order.
75
79
  # @return [CArray] 1-D CArray of the common distinct values.
76
80
  def intersection (other, sort: false)
81
+ refuse_range_set(other, "intersection")
77
82
  a, b = promote_value_set(other)
78
83
  r = a.__send__(:__intersection__, b)
79
84
  r = r.to_unit(unit) if !a.equal?(self) && respond_to?(:in_common_unit_with)
@@ -85,10 +90,11 @@ class CArray
85
90
  # absent from `other`, in `self`'s first-appearance order.
86
91
  # See {#intersection} for the shared semantics and options.
87
92
  #
88
- # @param other [CArray, Array, Range] promoted with `self`.
93
+ # @param other [CArray, Array] promoted with `self` (a Range is refused, as by {#is_in}).
89
94
  # @param sort [Boolean] when true, return the values sorted ascending.
90
95
  # @return [CArray] 1-D CArray of the self-only distinct values.
91
96
  def difference (other, sort: false)
97
+ refuse_range_set(other, "difference")
92
98
  a, b = promote_value_set(other)
93
99
  r = a.__send__(:__difference__, b)
94
100
  r = r.to_unit(unit) if !a.equal?(self) && respond_to?(:in_common_unit_with)
@@ -103,11 +109,12 @@ class CArray
103
109
  # holds the values of both ({#intersection} and {#difference} answer in
104
110
  # `self`'s unit, since their values are `self`'s).
105
111
  #
106
- # @param other [CArray, Array, Range] promoted with `self`.
112
+ # @param other [CArray, Array] promoted with `self` (a Range is refused, as by {#is_in}).
107
113
  # @param sort [Boolean] when true, return the values sorted ascending
108
114
  # (a merged, ordered set — e.g. a common time axis).
109
115
  # @return [CArray] 1-D CArray of the combined distinct values.
110
116
  def union (other, sort: false)
117
+ refuse_range_set(other, "union")
111
118
  a, b = promote_value_set(other)
112
119
  r = a.__send__(:__union__, b)
113
120
  sort ? r.sort : r
@@ -115,6 +122,16 @@ class CArray
115
122
 
116
123
  private
117
124
 
125
+ # A Range reads as an interval in Ruby, which a set of its steps is not
126
+ # (2.5 is not in [2, 3, 4]), so the set-valued verbs refuse one rather
127
+ # than answer for the steps.
128
+ def refuse_range_set (values, verb)
129
+ return unless values.is_a?(Range)
130
+ raise ArgumentError,
131
+ "#{verb}: a Range is not taken as a set; pass its values as an Array " \
132
+ "((#{values.inspect}).to_a), or test an interval with is_between(#{values.inspect})"
133
+ end
134
+
118
135
  # Reconcile a set-valued argument with self to a common data_type via
119
136
  # CArray.result_type (the single-source promotion rule the eager binop and
120
137
  # lazy CABinOp share), returning [self', set']. Only data types are reconciled,
@@ -141,36 +158,38 @@ class CArray
141
158
  return fixlen_widened_with(values)
142
159
  end
143
160
  # A Face that reconciles operands (CATime) reads a bare Array as values
144
- # of its own kind -- instants written as Time / String / elements.
145
- if values.is_a?(Array) && face? && respond_to?(:to_comparable)
146
- return [self, to_comparable(values)]
161
+ # of its own kind -- instants written as Time / String / elements -- and
162
+ # meets them as a comparison does (comparable_pair).
163
+ if values.is_a?(Array) && face? && respond_to?(:comparable_pair)
164
+ return comparable_pair(values)
147
165
  end
148
166
  # A single value (a Time, an element, a bare number) is a set of one,
149
167
  # read the same way; a bare number is refused like any other operand
150
168
  # that is not of its kind.
151
169
  if !values.is_a?(CArray) && !values.is_a?(Range) && face? &&
152
- respond_to?(:to_comparable)
153
- return [self, to_comparable([values])]
170
+ respond_to?(:comparable_pair)
171
+ return comparable_pair([values])
154
172
  end
155
173
  # ...and so does an array of values that are not of its kind (an object
156
174
  # column of Time values, a string column).
157
- if values.is_a?(CArray) && respond_to?(:to_comparable) &&
158
- !values.respond_to?(:to_comparable)
159
- return [self, values_in_face(self, values) || to_comparable([])]
175
+ if values.is_a?(CArray) && respond_to?(:comparable_pair) &&
176
+ !values.respond_to?(:comparable_pair)
177
+ return comparable_pair(values)
160
178
  end
161
179
  # The same from the other side: values of a time array looked for in an
162
180
  # array that is not one.
163
- if values.is_a?(CArray) && values.respond_to?(:to_comparable) &&
164
- !respond_to?(:to_comparable)
165
- return [values_in_face(values, self) || values.to_comparable([]), values]
181
+ if values.is_a?(CArray) && values.respond_to?(:comparable_pair) &&
182
+ !respond_to?(:comparable_pair)
183
+ set, own = values.comparable_pair(self)
184
+ return [own, set]
166
185
  end
167
186
  # A Face whose storage is not of its surface type. When its cells are
168
187
  # its values, the set meets it as it would meet an array of the storage
169
188
  # type, and the Face stays while that type does not change. Any other
170
189
  # Face leaves the set to the lookup, which brings it into the Face's
171
190
  # space or refuses it.
172
- if face? && (storage = __face_storage__).data_type != data_type
173
- unless __comparable_storage__
191
+ if face? && (storage = face_storage).data_type != data_type
192
+ unless comparable_storage?
174
193
  set = values.is_a?(Range) ? values.to_a : values
175
194
  return [self, set.is_a?(CArray) ? set : CArray.wrap_readonly(set)]
176
195
  end
@@ -181,9 +200,16 @@ class CArray
181
200
  when Array then return promote_elements(values)
182
201
  when Range then return promote_elements(values.to_a)
183
202
  end
203
+ # A single value is a set of one, as [value] is. wrap_readonly would make
204
+ # it an object array, which takes self into the object lane, where 2.0 is
205
+ # not eql? 2.
206
+ if !values.is_a?(CArray) && !values.respond_to?(:to_ca) &&
207
+ !CArray.memory_view_available?(values)
208
+ return promote_elements([values])
209
+ end
184
210
  set = values.is_a?(CArray) ? values : CArray.wrap_readonly(values)
185
211
  return [self, set] if set.data_type == data_type # common fast path
186
- t = value_match_type(CArray.result_type(self, set), set.data_type)
212
+ t = query_match_type(set)
187
213
  [coerce_self(t), set.to_type(t)]
188
214
  end
189
215
 
@@ -200,12 +226,17 @@ class CArray
200
226
  # set for not matching self. Same rule the scalar operand of a
201
227
  # comparison follows: a String standing in for a cell is padded to the
202
228
  # cell's width.
229
+ # coerce: a fixlen or object set holds values of self's kind (see above)
203
230
  [self, elems.to_ca.to_type(data_type, bytes: bytes)]
204
231
  elsif data_type == CA_OBJECT
232
+ # coerce: an object set holds values of self's kind
205
233
  [self, elems.to_ca.to_type(data_type)]
234
+ elsif elems.any? { |v| v.is_a?(String) || v.is_a?(Symbol) }
235
+ # A String is a value, which result_type would read as the name of a
236
+ # data type; a numeric self meets it in the object lane.
237
+ [to_type(CA_OBJECT), elems.to_ca.to_type(CA_OBJECT)]
206
238
  else
207
239
  t = CArray.result_type(self, *elems)
208
- t = value_match_type(t, CA_INT64) if elems.any? { |v| v.is_a?(Integer) && v < 0 }
209
240
  [coerce_self(t), elems.to_ca.to_type(t)]
210
241
  end
211
242
  end
@@ -216,7 +247,7 @@ class CArray
216
247
  # their values; anything else, or the same kind of array as self, is
217
248
  # taken as it is. A record is left to be refused.
218
249
  def values_for_lookup (other)
219
- return other if other.send(:__orderable_storage__)
250
+ return other if other.send(:orderable_storage?)
220
251
  return other if face? && other.class == self.class
221
252
  if other.respond_to?(:to_string)
222
253
  other.to_string
@@ -244,20 +275,6 @@ class CArray
244
275
  x.bytes == w ? x : x.to_fixlen_string(bytes: w)
245
276
  end
246
277
 
247
- # The values of +other+, which has no to_comparable, read as values of +face+'s
248
- # kind (instants written as Time / Date / String for a time key), in an
249
- # array of +other+'s shape; a masked cell stays masked. nil when every
250
- # cell is masked: there is nothing to read.
251
- private def values_in_face(face, other)
252
- return face.to_comparable(other) if other.is_a?(Array)
253
- vals = other.flatten.to_a
254
- filler = vals.find { |v| !UNDEF.equal?(v) }
255
- return nil if filler.equal?(nil) && vals.all? { |v| UNDEF.equal?(v) }
256
- out = face.to_comparable(vals.map { |v| UNDEF.equal?(v) ? filler : v })
257
- out[other.is_masked.flatten] = UNDEF if other.has_mask?
258
- out.reshape(*other.shape)
259
- end
260
-
261
278
  def coerce_self (t)
262
279
  CArray.result_type(self) == t ? self : to_type(t)
263
280
  end
@@ -46,12 +46,7 @@ class CArray
46
46
  return sep.nil? ? to_a.join : to_a.join(sep)
47
47
  end
48
48
 
49
- ax = Integer(axis)
50
- ax += ndim if ax < 0
51
- if ax < 0 || ax >= ndim
52
- raise ArgumentError,
53
- "axis #{axis.inspect} out of range for ndim=#{ndim}"
54
- end
49
+ ax = normalize_axis(axis, "join")
55
50
 
56
51
  sep_str = sep || ""
57
52
 
@@ -0,0 +1,49 @@
1
+ class CArray
2
+
3
+ # @overload lag(n = 1, axis: nil)
4
+ # Returns the value `n` steps earlier along `axis`, cell by cell: cell
5
+ # `i` of the result is cell `i - n` of `self`. A cell with no value `n`
6
+ # steps back is UNDEF, so a difference against it is UNDEF rather than
7
+ # a difference against a stand-in value:
8
+ #
9
+ # ```
10
+ # x = CA_FLOAT64([1.0, 4.0, 9.0])
11
+ # x.lag # => [UNDEF, 1.0, 4.0]
12
+ # x - x.lag # => [UNDEF, 3.0, 5.0]
13
+ # x.lag(-1) # => [4.0, 9.0, UNDEF] (the value one step later)
14
+ # ```
15
+ #
16
+ # The cell `n` steps back is taken by position: when it is masked, the
17
+ # result is UNDEF there too. It is `shift` with `UNDEF` as the fill
18
+ # value, and returns the same kind of view: it shares storage with
19
+ # `self`. Use `copy` for an independent array.
20
+ #
21
+ # The steps run along one axis. A 1-D array has only one, so `axis:`
22
+ # may be left out; an array of more dimensions must name it, since
23
+ # stepping back through the cells in flatten order would carry a value
24
+ # across from the end of one row to the start of the next.
25
+ #
26
+ # @param n [Integer] steps back; a negative `n` looks ahead.
27
+ # @param axis [Integer, nil] the axis the steps run along.
28
+ # @return [CArray] a view, shaped like `self`.
29
+ # @raise [TypeError] when `n` or `axis` is not an Integer.
30
+ # @raise [ArgumentError] when `axis` is out of range, or left out for an
31
+ # array of more than one dimension.
32
+ def lag (n = 1, axis: nil)
33
+ unless n.is_a?(Integer)
34
+ raise TypeError, "lag: n must be an Integer (got #{n.class})"
35
+ end
36
+ if axis.nil?
37
+ unless ndim == 1
38
+ raise ArgumentError,
39
+ "lag: name the axis to step along (axis:) for an array of " \
40
+ "#{ndim} dimensions"
41
+ end
42
+ axis = 0
43
+ end
44
+ steps = Array.new(ndim, 0)
45
+ steps[normalize_axis(axis, "lag")] = n
46
+ shift(*steps, fill_value: UNDEF)
47
+ end
48
+
49
+ end
@@ -1,4 +1,5 @@
1
- require "carray/methods/is_in" # fixlen_widened_with, values_in_face, values_for_lookup
1
+ require "carray/methods/is_in" # fixlen_widened_with, values_for_lookup
2
+ require "carray/distance_limit"
2
3
 
3
4
  class CArray
4
5
 
@@ -42,10 +43,9 @@ class CArray
42
43
  # variables without repeating the lookup.
43
44
  #
44
45
  # @param ref [CArray, Array, Range] reference values to match against;
45
- # any shape (used as flat). An Array or Range is coerced with `to_ca`
46
- # and so lands in `:object`, where numeric matching follows Ruby
47
- # `eql?` (`2.0` does not match `2`); pass a CArray to match in a
48
- # numeric lane.
46
+ # any shape (used as flat). An Array or Range is the list of its
47
+ # values, met at a common type with `self` as {#is_in} meets one, so
48
+ # `2` finds `2.0`.
49
49
  # @return [CArray] `:int64` flat addresses into `ref`, same shape as
50
50
  # `self`; unmatched cells are masked.
51
51
  # @raise [CArray::DataTypeError] when `self` and `ref` have no common data type.
@@ -58,30 +58,35 @@ class CArray
58
58
  a, b = in_common_unit_with(ref)
59
59
  return a.locate_addr(b)
60
60
  end
61
- if respond_to?(:to_comparable) && !ref.respond_to?(:to_comparable)
62
- ref = values_in_face(self, ref)
63
- return CArray.int64(*shape).fill(UNDEF) unless ref
64
- elsif ref.respond_to?(:to_comparable) && !respond_to?(:to_comparable)
65
- q = values_in_face(ref, self)
66
- return CArray.int64(*shape).fill(UNDEF) unless q
67
- return q.locate_addr(ref)
61
+ # They meet as a comparison does (comparable_pair).
62
+ if respond_to?(:comparable_pair) && !ref.respond_to?(:comparable_pair)
63
+ key, r = comparable_pair(ref)
64
+ return key.locate_addr(r)
65
+ elsif ref.respond_to?(:comparable_pair) && !respond_to?(:comparable_pair)
66
+ r, key = ref.comparable_pair(self)
67
+ return key.locate_addr(r)
68
68
  end
69
69
  unless ref.is_a?(CArray) || ref.respond_to?(:to_ca)
70
70
  raise TypeError, "locate_addr: the reference is an array of values (got #{ref.class})"
71
71
  end
72
+ # An Array or Range of values meets self as is_in's does: the common type
73
+ # comes from self and the elements, so 2 finds 2.0 in a float array. The
74
+ # conversion keeps the order, so the addresses stay positions in ref.
75
+ if ref.is_a?(Array) || ref.is_a?(Range)
76
+ q, r = promote_elements(ref.is_a?(Range) ? ref.to_a : ref)
77
+ return q.send(:__locate_addr__, r)
78
+ end
72
79
  ref = ref.to_ca unless ref.is_a?(CArray)
73
80
  ref = values_for_lookup(ref)
74
81
  if data_type == CA_FIXLEN && ref.data_type == CA_FIXLEN && bytes != ref.bytes
75
82
  a, b = fixlen_widened_with(ref)
76
83
  return a.locate_addr(b) unless a.equal?(self) && b.equal?(ref)
77
84
  end
78
- # Put self and ref in a common lane via the single-source promotion rule
79
- # (CArray.result_type), so a fractional query against an int ref is compared
80
- # at the promoted type instead of truncating (1.5 no longer matches 1).
81
- # to_type is elementwise and order-preserving, so the addresses stay valid
82
- # indices into ref. result_type raises for cross-family input; an integer
83
- # pair of mixed sign is matched by value (see value_match_type).
84
- t = value_match_type(CArray.result_type(self, ref), ref.data_type)
85
+ # Put self and ref in the type they match in (query_match_type), so a
86
+ # fractional query against an int ref is compared as a float (1.5 does
87
+ # not match 1) and a pair of mixed sign by value. to_type is elementwise
88
+ # and order-preserving, so the addresses stay valid indices into ref.
89
+ t = ref.send(:query_match_type, self)
85
90
  q = (data_type == t) ? self : to_type(t)
86
91
  r = (ref.data_type == t) ? ref : ref.to_type(t)
87
92
  q.send(:__locate_addr__, r)
@@ -137,6 +142,8 @@ class CArray
137
142
  "locate_nearest_addr: direction must be :round / :floor / " \
138
143
  ":ceil (got #{direction.inspect})"
139
144
  end
145
+ [self, ref].each { |key| nearest_refuse_bytes(key) }
146
+ limit = tolerance && DistanceLimit.read(tolerance, self, "tolerance:")
140
147
  return CArray.int64(*shape) if elements == 0
141
148
  # A masked reference value matches nothing, as in locate_addr: sorting
142
149
  # puts the masked ones last, and only the others are searched. A NaN
@@ -169,13 +176,29 @@ class CArray
169
176
  si = si.reshape(*shape)
170
177
  si[is_masked] = UNDEF if has_mask?
171
178
  idx = ri.project(si)
172
- if tolerance
179
+ if limit
173
180
  dist = (ref.project(idx) - self).abs
174
- idx[dist > nearest_tolerance(tolerance, dist)] = UNDEF
181
+ idx[dist > limit] = UNDEF
175
182
  end
176
183
  idx
177
184
  end
178
185
 
186
+ # A key held as fixed-length bytes (raw fixlen, or a fixlen string) has
187
+ # no nearest: the search counts keys as numbers. A Face whose storage is
188
+ # a number (a time) is searched by that number.
189
+ # +subject+ and +remedy+ let a caller (CAFrame#join_asof) say it in its
190
+ # own words.
191
+ private def nearest_refuse_bytes(key, subject = nil, remedy = "match exact values with locate_addr")
192
+ # A Face whose cells encode the value (a const string, a categorical)
193
+ # is refused later by its own reconcile, which says why.
194
+ return unless key.is_a?(CArray) && key.__send__(:orderable_storage?)
195
+ storage = key.face? ? key.__send__(:face_storage) : key
196
+ return unless storage.data_type == CA_FIXLEN
197
+ subject ||= "locate_nearest_addr: a #{CArray::Inspector.type_label(key)} key"
198
+ raise CArray::DataTypeError,
199
+ "#{subject} is held as fixed-length bytes, which have no nearest; #{remedy}"
200
+ end
201
+
179
202
  # The keys locate_nearest_addr orders by: the storage of the sorted
180
203
  # reference and of the query, in one numeric type. A Face key is first
181
204
  # brought into the reference's space (two time keys of different units
@@ -189,10 +212,10 @@ class CArray
189
212
  rs = rs.to_unit(u)
190
213
  q = q.to_unit(u)
191
214
  end
192
- q = rs.__send__(:__reconcile_operand__, q, "locate_nearest_addr")
193
- rs = rs.__send__(:__face_storage__)
215
+ q = rs.__send__(:reconcile_operand, q, "locate_nearest_addr")
216
+ rs = rs.__send__(:face_storage)
194
217
  end
195
- t = value_match_type(CArray.result_type(q, rs), rs.data_type)
218
+ t = rs.send(:query_match_type, q)
196
219
  rk = (rs.data_type == t) ? rs : rs.to_type(t)
197
220
  qk = (q.data_type == t) ? q : q.to_type(t)
198
221
  [rk, qk.flatten]
@@ -234,33 +257,6 @@ class CArray
234
257
  si
235
258
  end
236
259
 
237
- # The tolerance in the units of the distance. A time key measures its
238
- # distance as a CATimedelta, so its tolerance is a duration: a String in
239
- # the spelling CArray.time takes for a unit ("10 minutes"), or a
240
- # CATimedelta value. A bare number would mean a different length in each
241
- # unit, so it is refused there; any other key takes a number.
242
- private def nearest_tolerance(tolerance, dist)
243
- unless dist.is_a?(CATimedelta)
244
- if tolerance.is_a?(String)
245
- raise ArgumentError,
246
- "tolerance: #{tolerance.inspect} is a duration, but the key is " \
247
- "#{data_type_name}; give a number"
248
- end
249
- return tolerance
250
- end
251
- case tolerance
252
- when String
253
- res = CATime::Resolution.parse(tolerance)
254
- CATimedelta::Element.new(res.count, res.base)
255
- when CATimedelta::Element
256
- tolerance
257
- else
258
- raise ArgumentError,
259
- "tolerance: a time key needs a duration such as \"10 minutes\" " \
260
- "or a CATimedelta value (got #{tolerance.inspect})"
261
- end
262
- end
263
-
264
260
  # @!endgroup
265
261
 
266
262
  end
@@ -74,7 +74,7 @@ class CArray
74
74
  # reduced CArrays, one per mode rank (per-axis).
75
75
  def mode (axis: nil)
76
76
  # An error from the methods mode is made of (sort, ...) names mode.
77
- CArray.__answer_as__(:mode) { mode_answered(axis) }
77
+ CArray.__name_errors_after_method__(:mode) { mode_answered(axis) }
78
78
  end
79
79
 
80
80
  private def mode_answered (axis)
@@ -12,7 +12,9 @@ class CArray
12
12
  # whole array and returns an Integer. With `axis: k` it counts per
13
13
  # fiber along axis `k`, returning a reduced `CA_INT64` CArray
14
14
  # (shape = self.shape with axis `k` removed; `keep_axis: true` keeps
15
- # it as a length-1 axis). The shape rule matches other per-axis
15
+ # it as a length-1 axis). An Array of axes counts over all of them
16
+ # together, one count per remaining position; naming every axis
17
+ # returns an Integer. The shape rule matches other per-axis
16
18
  # reductions such as `sum(axis:)`.
17
19
  #
18
20
  # The distinct count has identity 0: an empty array, an all-masked
@@ -25,12 +27,12 @@ class CArray
25
27
  # distinctness follows Ruby `eql?` / `hash` (which, unlike numeric,
26
28
  # does not collapse distinct NaN objects).
27
29
  #
28
- # @param axis [Integer, nil] axis to count along; `nil` counts over
29
- # the whole array.
30
+ # @param axis [Integer, Array<Integer>, nil] axis or axes to count
31
+ # over; `nil` counts over the whole array.
30
32
  # @param keep_axis [Boolean] when `axis` is given, keep the reduced
31
33
  # axis as a length-1 axis instead of dropping it.
32
- # @return [Integer, CArray] Integer for `axis: nil`, otherwise a
33
- # reduced `CA_INT64` CArray.
34
+ # @return [Integer, CArray] Integer for `axis: nil` or every axis,
35
+ # otherwise a reduced `CA_INT64` CArray.
34
36
  # @overload nunique(along: k)
35
37
  # Returns how many distinct **sub-arrays** `self` holds, comparing
36
38
  # whole sub-arrays rather than cells: `along: k` names the axis
@@ -60,7 +62,10 @@ class CArray
60
62
  # then read the single reduced cell as an Integer.
61
63
  flatten.send(:__nunique__, 0, false)[0]
62
64
  else
63
- __nunique__(normalize_axis(axis, "nunique"), keep_axis)
65
+ axes = Array(axis).map { |a| normalize_axis(a, "nunique") }
66
+ counts = __nunique__(axes, keep_axis)
67
+ # naming every axis reduces to one count, an Integer as sum(axis:) gives
68
+ (axes.size == ndim && ! keep_axis) ? counts[0] : counts
64
69
  end
65
70
  end
66
71
 
@@ -43,11 +43,11 @@ class CArray
43
43
  # const string, a categorical) resizes too. The new area starts as zero
44
44
  # storage; a given fill_value is written as a value of the array, which a
45
45
  # read-only Face refuses.
46
- out = __blank_like__(newdim)
47
- out_storage = out.__send__(:__face_storage__)
46
+ out = blank_like(newdim)
47
+ out_storage = out.__send__(:face_storage)
48
48
  out[] = fill_value unless fill_value.equal?(NO_FILL_VALUE)
49
49
  out_storage.mask.paste(offset, self.false) if out_storage.has_mask?
50
- out_storage.paste(offset, __face_storage__)
50
+ out_storage.paste(offset, face_storage)
51
51
  out
52
52
  end
53
53
 
@@ -52,17 +52,10 @@ class CArray
52
52
  [ values[CArray.int64(order.size) { |i| order[i] }],
53
53
  counts[CArray.int64(order.size) { |i| order[i] }] ]
54
54
  when :value
55
- # Ascending value; NaN (numeric) or non-comparable last. Build the
56
- # permutation with an explicit NaN-last key so float NaN doesn't blow up
57
- # the Ruby sort, then gather both arrays through it.
58
- v = values.to_a
59
- order = (0...v.size).sort_by do |i|
60
- x = v[i]
61
- nan = x.is_a?(Float) && x.nan?
62
- [nan ? 1 : 0, nan ? 0 : x, i]
63
- end
64
- [ values[CArray.int64(order.size) { |i| order[i] }],
65
- counts[CArray.int64(order.size) { |i| order[i] }] ]
55
+ # Ascending value in the order sort uses (NaN after every number),
56
+ # then gather both arrays through it.
57
+ order = values.sort_index
58
+ [values[order], counts[order]]
66
59
  else
67
60
  [values, counts]
68
61
  end
@@ -49,7 +49,7 @@ class CARecord
49
49
  # @return [CArray] the indices that partition about the `kth` record.
50
50
  [:sort_addr, :sort_index, :rank_index, :order,
51
51
  :min_index, :max_index, :partition_index].each do |op|
52
- define_method(op) { |*args, **kw| record_rank.public_send(op, *args, **kw) }
52
+ define_method(op, &Ractor.make_shareable(proc { |*args, **kw| record_rank.public_send(op, *args, **kw) }))
53
53
  end
54
54
 
55
55
  # @overload min(axis: nil, keep_axis: false)
@@ -93,10 +93,10 @@ class CARecord
93
93
  # @!method gt(other)
94
94
  # @!method ge(other)
95
95
  [:lt, :le, :gt, :ge].each do |op|
96
- define_method(op) do |other|
96
+ define_method(op, &Ractor.make_shareable(proc do |other|
97
97
  mine, theirs = record_rank_with(other)
98
98
  mine.public_send(op, theirs)
99
- end
99
+ end))
100
100
  end
101
101
 
102
102
  alias < lt
data/lib/carray/rng.rb CHANGED
@@ -29,6 +29,7 @@ class CArray
29
29
  :xoshiro256pp => File.expand_path("../../ext/ca_rng_xoshiro256pp.h",
30
30
  __dir__),
31
31
  }.freeze
32
+ Ractor.make_shareable(SOURCE_FILES)
32
33
 
33
34
  # The C each generator is, as text.
34
35
  #
@@ -50,6 +51,7 @@ class CArray
50
51
  SOURCE = SOURCE_FILES.transform_values { |path|
51
52
  File.read(path, :encoding => "UTF-8")
52
53
  }.freeze
54
+ Ractor.make_shareable(SOURCE)
53
55
 
54
56
  # What every generator's text needs beside it, and what none of them
55
57
  # owns: turning uniforms into a normal is the same arithmetic whichever
@@ -80,6 +82,7 @@ class CArray
80
82
  :bits => "ca_xoshiro256pp_next",
81
83
  }.freeze,
82
84
  }.freeze
85
+ Ractor.make_shareable(DRAW_FUNCTIONS)
83
86
 
84
87
  end
85
88