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
data/lib/carray/fusion.rb CHANGED
@@ -66,6 +66,7 @@ class CArray
66
66
  Plan = Struct.new(:nodes, :leaves, :data_type, :dim, :masked, :signature)
67
67
 
68
68
  LAZY_CLASSES = [CAMonOp, CABinOp, CATriOp, CAMonCmp, CABinCmp, CALazyMarker]
69
+ Ractor.make_shareable(LAZY_CLASSES)
69
70
 
70
71
  # A lazy node names its operation by an id and the kernels name it by a
71
72
  # symbol. These are the same operations, spelled the way each side
@@ -76,7 +77,7 @@ class CArray
76
77
  :& => :bit_and_i, :| => :bit_or_i, :^ => :bit_xor_i,
77
78
  :<< => :bit_lshift, :>> => :bit_rshift,
78
79
  }.freeze
79
- TRIOP_NAMES = { :__clip_ki__ => :clip }.freeze
80
+ TRIOP_NAMES = { :clamp_between => :clip }.freeze
80
81
 
81
82
  MONOP_BY_ID = CArray::LAZY_MONOP_OP_IDS.invert.freeze
82
83
  # A Float or Complex array to an Integer power is its own node, ipow,
@@ -118,7 +119,7 @@ class CArray
118
119
  # Returns the array, or nil where nothing computed it.
119
120
  def self.evaluate (view)
120
121
  plan = worth_asking(view) or return nil
121
- out = CArray.__alloc_uninit__(view.data_type, view.dim)
122
+ out = CArray.__alloc_uninit__(view.data_type, view.shape)
122
123
  ask(plan, out) ? out : nil
123
124
  end
124
125
 
@@ -131,30 +132,46 @@ class CArray
131
132
  end
132
133
 
133
134
  # The plan to hand over, or nil. A marker over an array, or anything
134
- # else with nothing to compute, is not worth handing over.
135
+ # else with nothing to compute, is not worth handing over. Nothing here
136
+ # is the evaluator's doing, so nothing here retires it: an array that
137
+ # raises when it is looked at (a CAObject whose backing is gone) raises
138
+ # again in the walk, where the caller sees it.
135
139
  def self.worth_asking (view)
136
140
  return nil unless askable?(view)
137
141
  plan = plan(view) or return nil
138
142
  plan.nodes.any? { |n| n.is_a?(Op) } ? plan : nil
139
- rescue StandardError => error
140
- retire(error)
143
+ rescue StandardError
141
144
  nil
142
145
  end
143
146
 
144
147
  def self.ask (plan, out)
145
148
  evaluator = CArray.expression_evaluator or return false
146
149
  return false if overlaps?(plan, out)
147
- # The walk leaves the destination masked where the expression is, so
148
- # a destination that arrives masked loses its mask to an expression
149
- # that has none.
150
+ return false unless ready_mask(plan, out)
151
+ begin
152
+ evaluator.call(plan, out) ? true : false
153
+ rescue AddressBasis::Failure
154
+ # Raised by CArray lending the arrays or taking them back: a refusal,
155
+ # or an array that cannot be read. The walk meets the same array.
156
+ false
157
+ rescue StandardError => error
158
+ retire(error)
159
+ false
160
+ end
161
+ end
162
+
163
+ # The walk leaves the destination masked where the expression is, so a
164
+ # destination that arrives masked loses its mask to an expression that
165
+ # has none. A destination that cannot hold a mask (a value array) is
166
+ # left to the walk, which says so.
167
+ def self.ready_mask (plan, out)
150
168
  if plan.masked
151
169
  out.mask = 0 unless out.has_mask?
152
170
  elsif out.has_mask?
153
171
  out.mask = 0
154
172
  end
155
- evaluator.call(plan, out) ? true : false
156
- rescue StandardError => error
157
- retire(error)
173
+ true
174
+ rescue StandardError
158
175
  false
159
176
  end
160
177
 
@@ -171,13 +188,15 @@ class CArray
171
188
  if leaf.equal?(out)
172
189
  shifted.include?(index)
173
190
  else
174
- storages(leaf).any? { |r| mine.any? { |m| m.equal?(r) } }
191
+ storages(leaf).any? { |r| mine.any? { |m| m.send(:storage_overlaps?, r) } }
175
192
  end
176
193
  end
177
194
  end
178
195
 
179
196
  # The arrays that hold an array's cells: its root, or each parent's
180
- # where a view stands over several.
197
+ # where a view stands over several. Two of them share storage when
198
+ # they are one array, or entities whose memory overlaps (an array and a
199
+ # wrap_memory_view of it).
181
200
  def self.storages (array, found = [])
182
201
  root = array.root_array
183
202
  if ! root.entity? && root.respond_to?(:parents)
@@ -213,7 +232,7 @@ class CArray
213
232
  w.visit(view)
214
233
  masked = w.leaves.any? { |a| a.has_mask? } ||
215
234
  w.nodes.any? { |n| n.is_a?(Shifted) && n.bounds.include?(:mask) }
216
- Plan.new(w.nodes, w.leaves, view.data_type, view.dim, masked, w.signature)
235
+ Plan.new(w.nodes, w.leaves, view.data_type, view.shape, masked, w.signature)
217
236
  end
218
237
 
219
238
  def self.lazy? (x)
@@ -350,8 +369,8 @@ class CArray
350
369
  # expression, say -- has no array to read and is taken as a whole.
351
370
  def shifted (n)
352
371
  array = n.parent
353
- bounds = n.__axis_bounds__
354
- if Fusion.lazy?(array) || array.dim != n.dim ||
372
+ bounds = n.__boundary_rule_of_each_axis__
373
+ if Fusion.lazy?(array) || array.shape != n.shape ||
355
374
  ! bounds.all? { |b| b == :fill || b == :mask }
356
375
  return leaf(n)
357
376
  end
@@ -366,8 +385,13 @@ class CArray
366
385
  offset, bounds, fill)
367
386
  end
368
387
 
388
+ # A leaf the address bases would refuse to lend cannot be read by an
389
+ # evaluator, so the expression is not described.
369
390
  def leaf_index (array)
370
391
  @leaf_index.fetch(array.object_id) do
392
+ if (reason = AddressBasis.classify(array)[:refusal])
393
+ raise Refused, "a leaf: #{reason}"
394
+ end
371
395
  @leaves << array
372
396
  @leaf_index[array.object_id] = @leaves.size - 1
373
397
  end
@@ -321,6 +321,7 @@ class CArray
321
321
 
322
322
  if weights
323
323
  raise ArgumentError, "weights given but accumulator is unweighted" unless @weighted
324
+ # coerce: the accumulator's type, declared by weights_data_type:
324
325
  weights = CArray.wrap_readonly(weights, @counts_data_type)
325
326
  expected_w_shape = chunk.shape.dup
326
327
  expected_w_shape.delete_at(channel_ax)
@@ -447,7 +448,7 @@ class CArray
447
448
  # @param weights [CArray, nil] optional per-sample weights.
448
449
  # @return [Histogram] 1-D accumulator (`m == 1`).
449
450
  def histogram1d (edges:, axis: -1, include_max: false, weights: nil)
450
- ax = normalize_axis(axis, "histogram1d")
451
+ ax = normalize_axis(axis.nil? ? -1 : axis, "histogram1d")
451
452
 
452
453
  new_shape = shape + [1]
453
454
  arr_with_channel = reshape(*new_shape)
@@ -491,6 +492,10 @@ class CArray
491
492
  def histogram (edges:, axis: [-2, -1], include_max: false, weights: nil)
492
493
  raise ArgumentError, "edges must be an Array of edges arrays" unless edges.is_a?(Array)
493
494
  arr = self
495
+ axis = [-2, -1] if axis.nil?
496
+ unless axis.is_a?(Array) && axis.size == 2
497
+ raise ArgumentError, "histogram: axis: is a [sample, channel] pair (got #{axis.inspect})"
498
+ end
494
499
  sample_ax = normalize_axis(axis[0], "histogram sample axis")
495
500
  channel_ax = normalize_axis(axis[1], "histogram channel axis")
496
501
  fiber_shape = arr.shape.dup
@@ -34,16 +34,39 @@ class CArray::Inspector # :nodoc:
34
34
 
35
35
  private
36
36
 
37
- # The name before the shape: the class and the storage type, or what a
38
- # Face says instead through a private inspect_label (CATime gives its
39
- # tick, which the storage type does not tell).
37
+ # The name before the shape: the class and the storage type, or for a
38
+ # Face its own name (see .face_label).
40
39
  def get_label
41
- if @carray.respond_to?(:inspect_label, true)
42
- return @carray.send(:inspect_label)
43
- end
40
+ return CArray::Inspector.face_label(@carray) if @carray.face?
44
41
  return format("%s.%s", get_class_name, get_type_name)
45
42
  end
46
43
 
44
+ public
45
+
46
+ # The name a Face goes by where a person reads it -- the head of inspect,
47
+ # a frame's column list. A Face's data_type is the surface the kernels
48
+ # dispatch on (fixlen for a time, so arithmetic on it raises), which says
49
+ # nothing to a reader, so it is named by its class: what a private
50
+ # inspect_label gives (CATime adds its tick), or the class, with the
51
+ # surface type only for a Face whose surface is numeric.
52
+ # The type of an array as a person reads it in a message or a table: a
53
+ # Face by its name (face_label), any other array by its data type name.
54
+ # Every message and listing that names a type goes through here, so a
55
+ # Face is never named by its storage (:fixlen for a time).
56
+ def self.type_label (ca)
57
+ ca.face? ? face_label(ca) : CArray.data_type_name(ca.data_type).to_s
58
+ end
59
+
60
+ def self.face_label (ca)
61
+ return ca.send(:inspect_label) if ca.respond_to?(:inspect_label, true)
62
+ klass = ca.class
63
+ klass = klass.superclass while klass.name.nil?
64
+ return klass.name if [CA_FIXLEN, CA_OBJECT].include?(ca.data_type)
65
+ "#{klass.name}.#{CArray.data_type_name(ca.data_type)}"
66
+ end
67
+
68
+ private
69
+
47
70
  def get_class_name
48
71
  return @carray.class.to_s
49
72
  end
@@ -277,10 +300,10 @@ class CArray
277
300
  case data_type
278
301
  when CA_FIXLEN
279
302
  output << sprintf("CArray.%s(%s, :bytes=>%i)",
280
- data_type_name, dim.inspect[1..-2], bytes)
303
+ data_type_name, shape.inspect[1..-2], bytes)
281
304
  else
282
305
  output << sprintf("CArray.%s(%s)",
283
- data_type_name, dim.inspect[1..-2])
306
+ data_type_name, shape.inspect[1..-2])
284
307
  end
285
308
  return output
286
309
  end
@@ -32,6 +32,45 @@ class CAIterator
32
32
  # The keywords a member passes on to a core reduction over its own axes.
33
33
  # The member sets `axis:`, so one from the caller is refused; the rest go
34
34
  # to the core, which checks them.
35
+ # Whether a fill method carries values backward: :forward / :ffill carry
36
+ # them forward, :backward / :bfill back. A member fills within its own
37
+ # pieces by holding the nearest present value; :linear, which needs a
38
+ # coordinate, is the core's alone.
39
+ def self.hold_backward? (method) # :nodoc:
40
+ case method
41
+ when :forward, :ffill then false
42
+ when :backward, :bfill then true
43
+ else
44
+ raise ArgumentError,
45
+ "strip_mask: method must be :forward, :ffill, :backward or :bfill " \
46
+ "within a piece (got #{method.inspect})"
47
+ end
48
+ end
49
+
50
+ # A one-cell (1, 1) array of the kind `value` holds, for asking the core
51
+ # what a reduction or scan of it answers in: its data type, cell width and,
52
+ # for a Face, its Face. A member that answers by a route of its own
53
+ # (a fused kernel, a fold by offset) takes its output type from here, so
54
+ # the route and the core cannot disagree. A Face is a view of the values
55
+ # themselves rather than a blank -- a Face cannot be allocated from its
56
+ # surface data type alone, and a zeroed record is not a valid cell for every
57
+ # one (a const string's record indexes a shared pool). A Face with no cell
58
+ # is asked with the blank cell its resize gives, so a reduction over no
59
+ # groups answers in the same kind as over some; this is nil only when the
60
+ # Face has no blank cell.
61
+ def self.core_probe (value) # :nodoc:
62
+ unless value.face?
63
+ return CArray.new(value.data_type, [1, 1], bytes: value.bytes)
64
+ end
65
+ flat = value.reshape(value.elements)
66
+ return flat[[0]].reshape(1, 1) if value.elements > 0
67
+ begin
68
+ flat.resize(1).reshape(1, 1)
69
+ rescue StandardError
70
+ nil
71
+ end
72
+ end
73
+
35
74
  def self.reduce_keywords (op, kw) # :nodoc:
36
75
  if kw.key?(:axis)
37
76
  raise ArgumentError, "#{op}: unknown keyword: :axis (the iterator sets the axes)"
@@ -127,6 +166,18 @@ class CAIterator
127
166
  # Returns the five-number summary of each piece,
128
167
  # `[min, Q1, median, Q3, max]`.
129
168
  # @return [Array<CArray>] five arrays, one per position.
169
+ # @!method first
170
+ # Returns the first present value of each piece, in the piece's own
171
+ # order; a piece with no present value is UNDEF.
172
+ # @return [CArray] one value per piece, in the source's data type.
173
+ # @!method last
174
+ # Returns the last present value of each piece; a piece with no present
175
+ # value is UNDEF.
176
+ # @return [CArray] one value per piece, in the source's data type.
177
+ # @!method nunique
178
+ # Returns the number of distinct present values in each piece, as
179
+ # `CArray#nunique` counts them; a piece with none counts 0.
180
+ # @return [CArray] `:int64`, one count per piece.
130
181
  # @!method count(value = nil)
131
182
  # Returns a count per piece: with no argument the cells that are not
132
183
  # masked, with `UNDEF` the masked cells, and with any other value the
@@ -161,14 +212,16 @@ class CAIterator
161
212
  :min_index, :max_index, :min_addr, :max_addr, # position
162
213
  :wsum, :wmean, # weighted
163
214
  :median, :percentile, :quantile, # tier 3
215
+ :first, :last, :nunique, # position / distinct
164
216
  :count, :count_not_masked, :count_masked, :elements, # count family
165
217
  :each, :reduce, # generic iterate
166
218
  ].freeze
167
219
 
168
220
  REQUIRED_SURFACE.each do |name|
169
- define_method(name) do |*, **, &_blk|
221
+ # define_method: a stub that names itself in its own message
222
+ define_method(name, &Ractor.make_shareable(proc do |*, **, &_blk|
170
223
  raise NotImplementedError, "#{self.class} must implement ##{name}"
171
- end
224
+ end))
172
225
  end
173
226
 
174
227
  # Recommended (should) surface -- template methods. These are well-defined
@@ -176,7 +229,8 @@ class CAIterator
176
229
  # required: `map` is a per-piece element-wise transform scattered back to the
177
230
  # source, `sort_addr` is a per-piece sort returning source flat addresses, and
178
231
  # the segment scans (cumsum / cumprod / cummax / cummin / cumcount) write a
179
- # per-cell running statistic of each piece. A member implements each when it
232
+ # per-cell running statistic of each piece, and `lag` the value some steps
233
+ # earlier in the same piece. A member implements each when it
180
234
  # is well-defined and overrides it to raise with a reason when it is not. The
181
235
  # scans belong here for the same reason as map: a running per-cell value is
182
236
  # single-valued only when each cell belongs to exactly one piece, so the
@@ -221,11 +275,35 @@ class CAIterator
221
275
  # masked within each piece.
222
276
  # @return [CArray] shaped like the source.
223
277
  # @raise [NotImplementedError] for a member whose pieces overlap.
224
- [:map, :sort_addr,
225
- :cumsum, :cumprod, :cummax, :cummin, :cumcount].each do |name|
226
- define_method(name) do |*, **, &_blk|
278
+ # @!method lag(n = 1)
279
+ # Returns a source-shaped array whose cell holds the value `n` steps
280
+ # earlier within the same piece, as `CArray#lag` does along an axis; a
281
+ # cell with no such value in its piece is UNDEF, so a lag never reaches
282
+ # across into another piece.
283
+ # @param n [Integer] steps back; a negative `n` looks ahead.
284
+ # @return [CArray] shaped like the source.
285
+ # @raise [NotImplementedError] for a member whose pieces overlap, where a
286
+ # cell has no single position within a piece.
287
+ # @!method strip_mask(method:)
288
+ # Returns a source-shaped array with each masked cell filled from its own
289
+ # piece, as `CArray#strip_mask(method:)` fills: `:forward` (`:ffill`)
290
+ # carries the last present value forward, `:backward` (`:bfill`) the next
291
+ # one back, never across into another piece.
292
+ # @param method [Symbol] `:forward`, `:ffill`, `:backward` or `:bfill`.
293
+ # @return [CArray] shaped like the source.
294
+ # @raise [NotImplementedError] for a member whose pieces overlap.
295
+ # @!method order(descending: false, method: :ordinal, kind: :quick)
296
+ # Returns a source-shaped int64 array of each cell's rank among the cells
297
+ # of its piece, as `CArray#order` ranks them (`0` the smallest).
298
+ # @return [CArray] shaped like the source.
299
+ # @raise [NotImplementedError] for a member whose pieces overlap, where a
300
+ # cell has no single rank.
301
+ [:map, :sort_addr, :order, :strip_mask,
302
+ :cumsum, :cumprod, :cummax, :cummin, :cumcount, :lag].each do |name|
303
+ # define_method: a stub that names itself in its own message
304
+ define_method(name, &Ractor.make_shareable(proc do |*, **, &_blk|
227
305
  raise NotImplementedError, "#{self.class} does not provide ##{name} (optional)"
228
- end
306
+ end))
229
307
  end
230
308
 
231
309
  end