carray 3.0.2 → 3.0.3

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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +304 -0
  3. data/README.md +3 -4
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +225 -0
  6. data/ext/ca_axis_group.c +177 -99
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +66 -71
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +663 -214
  14. data/ext/ca_kernel_iterator.h +231 -167
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +132 -22
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +1 -0
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +96 -25
  25. data/ext/ca_obj_face.c +406 -64
  26. data/ext/ca_obj_face.h +92 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +21 -21
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +189 -107
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +543 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +47 -49
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -118
  46. data/ext/ca_obj_stride.c +195 -34
  47. data/ext/ca_obj_string.c +17 -18
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +149 -27
  54. data/ext/ca_op_ipower.c +24 -11
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_sort_kernels.h +70 -0
  58. data/ext/ca_sweep_engine.c +102 -79
  59. data/ext/ca_sweep_engine.h +7 -20
  60. data/ext/ca_triop_dispatch.c +4 -1
  61. data/ext/ca_triop_dispatch.h +5 -2
  62. data/ext/caframe_csv_reader.c +463 -0
  63. data/ext/carray.h +70 -27
  64. data/ext/carray_access.c +455 -211
  65. data/ext/carray_address_basis.c +177 -36
  66. data/ext/carray_attribute.c +45 -3
  67. data/ext/carray_bincount.c +32 -2
  68. data/ext/carray_broadcast.c +46 -14
  69. data/ext/carray_call_cfunc.c +0 -28
  70. data/ext/carray_cast.c +1444 -209
  71. data/ext/carray_cast_func.rb +34 -21
  72. data/ext/carray_conversion.c +197 -73
  73. data/ext/carray_copy.c +32 -11
  74. data/ext/carray_core.c +514 -122
  75. data/ext/carray_count.c +140 -87
  76. data/ext/carray_element.c +18 -27
  77. data/ext/carray_factorize.c +569 -277
  78. data/ext/carray_generate.c +76 -40
  79. data/ext/carray_histogram.c +152 -94
  80. data/ext/carray_hold.c +2 -1
  81. data/ext/carray_index_classifier.c +63 -96
  82. data/ext/carray_internal.h +385 -3
  83. data/ext/carray_lazy.c +834 -79
  84. data/ext/carray_loop.c +81 -65
  85. data/ext/carray_mask.c +682 -316
  86. data/ext/carray_math_kernel.h +1 -0
  87. data/ext/carray_median_percentile.c +221 -76
  88. data/ext/carray_memory_view.c +254 -248
  89. data/ext/carray_operator.c +1089 -359
  90. data/ext/carray_order.c +133 -69
  91. data/ext/carray_partition.c +102 -56
  92. data/ext/carray_random.c +273 -239
  93. data/ext/carray_scatter.c +308 -65
  94. data/ext/carray_segment.c +257 -0
  95. data/ext/carray_slab.c +170 -70
  96. data/ext/carray_sort.c +215 -218
  97. data/ext/carray_sort_kernel.c +8 -1
  98. data/ext/carray_struct.c +99 -21
  99. data/ext/carray_test.c +167 -64
  100. data/ext/carray_time_text.c +1182 -0
  101. data/ext/carray_utils.c +363 -98
  102. data/ext/extconf.rb +27 -2
  103. data/ext/mk_call_cfunc.rb +0 -2
  104. data/ext/mk_pow5_table.rb +35 -0
  105. data/ext/mkkernel.rb +1076 -846
  106. data/ext/ruby_carray.c +7 -1
  107. data/ext/version.h +4 -4
  108. data/ext/version.rb +4 -2
  109. data/lib/carray/attribute.rb +39 -0
  110. data/lib/carray/autoload_carray.rb +19 -0
  111. data/lib/carray/autoload_method_extension.rb +6 -0
  112. data/lib/carray/axis_group.rb +174 -26
  113. data/lib/carray/basics.rb +91 -25
  114. data/lib/carray/bincount_nd.rb +6 -0
  115. data/lib/carray/block_iterator.rb +118 -48
  116. data/lib/carray/boolean_reduce.rb +21 -29
  117. data/lib/carray/categorical.rb +94 -26
  118. data/lib/carray/categorical_iterator.rb +333 -826
  119. data/lib/carray/complex.rb +36 -29
  120. data/lib/carray/conditional.rb +94 -7
  121. data/lib/carray/const_string.rb +46 -0
  122. data/lib/carray/frame/concat.rb +157 -6
  123. data/lib/carray/frame/convert.rb +13 -7
  124. data/lib/carray/frame/csv_parser.rb +589 -82
  125. data/lib/carray/frame/describe.rb +79 -0
  126. data/lib/carray/frame/frame.rb +124 -25
  127. data/lib/carray/frame/group.rb +189 -63
  128. data/lib/carray/frame/io.rb +412 -72
  129. data/lib/carray/frame/join.rb +134 -46
  130. data/lib/carray/frame/records.rb +37 -16
  131. data/lib/carray/frame/reshape.rb +448 -0
  132. data/lib/carray/frame/sort.rb +26 -2
  133. data/lib/carray/frame/verbs.rb +715 -48
  134. data/lib/carray/frame.rb +2 -0
  135. data/lib/carray/fuse_source.rb +111 -2
  136. data/lib/carray/fusion.rb +229 -38
  137. data/lib/carray/inspect.rb +29 -11
  138. data/lib/carray/iterator.rb +14 -2
  139. data/lib/carray/lazy.rb +54 -30
  140. data/lib/carray/mask_gap_fill.rb +66 -52
  141. data/lib/carray/math.rb +32 -19
  142. data/lib/carray/meld_reduce.rb +40 -7
  143. data/lib/carray/methods/align_addr.rb +8 -4
  144. data/lib/carray/methods/bincount.rb +2 -2
  145. data/lib/carray/methods/bit_string.rb +14 -3
  146. data/lib/carray/methods/composition.rb +16 -10
  147. data/lib/carray/methods/factorize.rb +2 -2
  148. data/lib/carray/methods/gather_nd.rb +13 -2
  149. data/lib/carray/methods/insert_block.rb +9 -14
  150. data/lib/carray/methods/is_in.rb +117 -4
  151. data/lib/carray/methods/join.rb +1 -1
  152. data/lib/carray/methods/locate_addr.rb +177 -36
  153. data/lib/carray/methods/mode.rb +18 -1
  154. data/lib/carray/methods/repeat.rb +1 -20
  155. data/lib/carray/methods/resize.rb +21 -23
  156. data/lib/carray/methods/snap.rb +34 -12
  157. data/lib/carray/methods/unique.rb +11 -2
  158. data/lib/carray/record_order.rb +208 -0
  159. data/lib/carray/runtime.rb +14 -11
  160. data/lib/carray/segment_iterator.rb +990 -0
  161. data/lib/carray/serialize.rb +197 -27
  162. data/lib/carray/slab_iterator.rb +51 -27
  163. data/lib/carray/stack.rb +10 -4
  164. data/lib/carray/string_operation_extension.rb +4 -3
  165. data/lib/carray/struct.rb +142 -51
  166. data/lib/carray/struct_builder.rb +110 -14
  167. data/lib/carray/table.rb +2 -2
  168. data/lib/carray/time.rb +517 -103
  169. data/lib/carray/window_iterator.rb +127 -72
  170. data/lib/carray.rb +2 -2
  171. data/yard-stubs/ca_obj_array.rb +4 -4
  172. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  173. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  174. data/yard-stubs/ca_obj_fake.rb +1 -1
  175. data/yard-stubs/ca_obj_field.rb +1 -1
  176. data/yard-stubs/ca_obj_refer.rb +3 -3
  177. data/yard-stubs/ca_obj_roll.rb +0 -1
  178. data/yard-stubs/ca_obj_stride.rb +10 -7
  179. data/yard-stubs/ca_obj_transpose.rb +1 -1
  180. data/yard-stubs/ca_obj_window.rb +1 -2
  181. data/yard-stubs/carray_access.rb +5 -4
  182. data/yard-stubs/carray_broadcast.rb +1 -1
  183. data/yard-stubs/carray_cast.rb +78 -43
  184. data/yard-stubs/carray_conversion.rb +13 -1
  185. data/yard-stubs/carray_copy.rb +8 -3
  186. data/yard-stubs/carray_core.rb +3 -3
  187. data/yard-stubs/carray_count.rb +9 -0
  188. data/yard-stubs/carray_lazy.rb +5 -1
  189. data/yard-stubs/carray_loop.rb +4 -1
  190. data/yard-stubs/carray_mask.rb +34 -10
  191. data/yard-stubs/carray_math.rb +25 -13
  192. data/yard-stubs/carray_median_percentile.rb +10 -6
  193. data/yard-stubs/carray_order.rb +34 -13
  194. data/yard-stubs/carray_scatter.rb +10 -3
  195. data/yard-stubs/carray_segment.rb +72 -0
  196. data/yard-stubs/carray_slab.rb +2 -1
  197. data/yard-stubs/carray_sort.rb +43 -3
  198. data/yard-stubs/carray_test.rb +10 -0
  199. data/yard-stubs/carray_utils.rb +0 -26
  200. metadata +11 -19
  201. data/ext/ca_for_each_element.h +0 -239
  202. data/ext/carray_build_flags.h +0 -3
  203. data/ext/carray_config.h +0 -28
  204. data/ext/carray_kernels_bincmp.c +0 -4446
  205. data/ext/carray_kernels_binop.c +0 -11001
  206. data/ext/carray_kernels_init.c +0 -1131
  207. data/ext/carray_kernels_map.c +0 -3467
  208. data/ext/carray_kernels_moncmp.c +0 -2097
  209. data/ext/carray_kernels_monop.c +0 -18313
  210. data/ext/carray_kernels_reduce_aggregate.c +0 -26005
  211. data/ext/carray_kernels_reduce_boolean.c +0 -330
  212. data/ext/carray_kernels_reduce_cumulative.c +0 -14862
  213. data/ext/carray_kernels_reduce_extreme.c +0 -17494
  214. data/ext/carray_kernels_reduce_variance.c +0 -3910
  215. data/ext/carray_kernels_scan.c +0 -3693
  216. data/ext/carray_kernels_search.c +0 -32218
  217. data/ext/carray_kernels_sort.c +0 -10626
  218. data/ext/carray_kernels_triop.c +0 -1392
data/lib/carray/lazy.rb CHANGED
@@ -133,6 +133,10 @@ class CArray
133
133
  __abs_eager_um__ = instance_method(:__abs_eager__)
134
134
  define_method(:abs) do
135
135
  if __lazy_view__?
136
+ # A boolean has no absolute value to change type for, as eager says.
137
+ raise CArray::DataTypeError,
138
+ "data_type-changing monop not implemented for input data_type " \
139
+ "#{CArray.data_type_code(CA_BOOLEAN)}" if boolean?
136
140
  abs_i_node = CAMonOp.__build__(self, CAMonOp::OP_ABS_I)
137
141
  if complex?
138
142
  # float_dt is a Symbol; convert via data_type_code so the cast
@@ -206,7 +210,11 @@ class CArray
206
210
  define_method(:imag) do
207
211
  if __lazy_view__?
208
212
  imag_i_node = CAMonOp.__build__(self, CAMonOp::OP_IMAG_I)
209
- if complex?
213
+ if boolean?
214
+ # imag_i reads a boolean as int64; the eager imag stays boolean.
215
+ CAMonOp.__build__(imag_i_node,
216
+ CAMonOp::CAST_BASE + CArray.data_type_code(CA_BOOLEAN))
217
+ elsif complex?
210
218
  float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
211
219
  # float_dt is a Symbol; convert via data_type_code for kernel
212
220
  # op_id arithmetic.
@@ -235,8 +243,7 @@ class CArray
235
243
  #
236
244
  # Both paths keep the operand's float / complex width (f32 → f32,
237
245
  # cmplx64 → f32), and both send integers to f64 since pi does not fit
238
- # an integer slot. That agrees with the general lazy-substrate rule
239
- # "scalar keeps operand's precision" (see ca_lazy_wrap_scalar header).
246
+ # an integer slot.
240
247
  # ---------------------------------------------------------------------------
241
248
 
242
249
  # @private
@@ -244,8 +251,11 @@ class CArray
244
251
  __arg_eager_um__ = instance_method(:__arg_eager__)
245
252
  define_method(:arg) do
246
253
  if __lazy_view__?
254
+ raise CArray::DataTypeError,
255
+ "data_type-changing monop not implemented for input data_type " \
256
+ "#{CArray.data_type_code(CA_BOOLEAN)}" if boolean?
247
257
  parent = self
248
- if integer? || boolean?
258
+ if integer?
249
259
  parent = CAMonOp.__build__(parent,
250
260
  CAMonOp::CAST_BASE +
251
261
  CArray.data_type_code(CA_FLOAT64))
@@ -326,6 +336,10 @@ class CArray
326
336
  end
327
337
  end
328
338
 
339
+ # The shift count of an Integer is brought into the array's type before
340
+ # either form sees it (see CArray::IntegerShiftCount).
341
+ prepend IntegerShiftCount
342
+
329
343
  # Binop word aliases (`add` -> `+`, `mul` -> `*`, `bit_and` -> `&`, …) were
330
344
  # installed by rb_define_alias at Init time (see MkKernel.alias_binop in
331
345
  # ext/carray_kernels_*.c) BEFORE the redefinitions above. The C alias
@@ -529,23 +543,48 @@ class CArray
529
543
  end
530
544
  end
531
545
 
546
+ # A lazy receiver takes the scalar the eager coerce makes, and stays
547
+ # lazy itself: the builder promotes the pair as the eager operator does.
532
548
  # @!visibility private
533
549
  alias_method :__coerce_eager__, :coerce
534
550
  coerce_um = instance_method(:__coerce_eager__)
535
551
  define_method(:coerce) do |other|
536
- if __lazy_view__?
537
- dt_name = data_type_name
538
- if CScalar.respond_to?(dt_name)
539
- s = CScalar.public_send(dt_name)
540
- s[0] = other
541
- [s, self]
542
- else
543
- coerce_um.bind_call(self, other)
544
- end
545
- else
546
- coerce_um.bind_call(self, other)
552
+ begin
553
+ scalar, receiver = coerce_um.bind_call(self, other)
554
+ rescue RangeError => e
555
+ raise unless other.is_a?(Integer)
556
+ return [OutOfRangeInteger.new(other, self, e), self]
547
557
  end
558
+ __lazy_view__? ? [scalar, self] : [scalar, receiver]
548
559
  end
560
+
561
+ # An Integer the array's type cannot hold, on the left of an operator.
562
+ # A comparison is answered from the array's side, where it compares by
563
+ # value; any other operator refuses the Integer as the array would.
564
+ class OutOfRangeInteger
565
+ def initialize (value, array, error)
566
+ @value, @array, @error = value, array, error
567
+ end
568
+
569
+ def < (other) = flip(other, :>)
570
+ def <= (other) = flip(other, :>=)
571
+ def > (other) = flip(other, :<)
572
+ def >= (other) = flip(other, :<=)
573
+
574
+ def method_missing (*)
575
+ raise @error
576
+ end
577
+
578
+ def respond_to_missing? (*) = false
579
+
580
+ private
581
+
582
+ def flip (other, op)
583
+ raise @error unless other.equal?(@array)
584
+ @array.send(op, @value)
585
+ end
586
+ end
587
+ private_constant :OutOfRangeInteger
549
588
  end
550
589
 
551
590
  # ---------------------------------------------------------------------------
@@ -618,14 +657,6 @@ class CAMonOp
618
657
  to_ca.to_a
619
658
  end
620
659
 
621
- # @overload sort(*args, **kwargs)
622
- # Materialises `self` into a copy and returns
623
- # `entity.sort(*args, **kwargs)`.
624
- # @return [CArray]
625
- def sort(*args, **kwargs)
626
- copy.sort(*args, **kwargs)
627
- end
628
-
629
660
  # --- MemoryView export → TypeError + .copy recommendation -----------
630
661
 
631
662
  # bulk-memory-view / Numo / Apache Arrow consumers go through the MV
@@ -714,13 +745,6 @@ class CABinOp
714
745
  to_ca.to_a
715
746
  end
716
747
 
717
- # @overload sort(*args, **kwargs)
718
- # Materialises `self` and sorts the resulting entity.
719
- # @return [CArray]
720
- def sort(*args, **kwargs)
721
- to_ca.sort(*args, **kwargs)
722
- end
723
-
724
748
  # @overload to_memory_view
725
749
  # Not supported for lazy binops.
726
750
  # @raise [TypeError] always.
@@ -46,15 +46,10 @@ class CArray
46
46
  raise ArgumentError,
47
47
  "unmask: pass either a constant fill value or method:, not both"
48
48
  end
49
- held = __gap_fill__(method, axis)
50
- # Copy the filled values in place. A Face writes through its storage: a
51
- # bulk store into its surface would try to cast the storage values to the
52
- # surface type (int64 ticks to fixlen, for a time array).
53
- if face?
54
- parent.value[] = held.parent.value
55
- else
56
- value[] = held.value
57
- end
49
+ held = __gap_fill__(method, axis, "unmask")
50
+ # Copy the filled values in place (a Face takes a Face of its own kind
51
+ # as it is).
52
+ value[] = held.value
58
53
  if held.has_mask?
59
54
  self.mask = held.mask # residual leading/trailing mask
60
55
  else
@@ -71,7 +66,7 @@ class CArray
71
66
  raise ArgumentError,
72
67
  "strip_mask: pass either a constant fill value or method:, not both"
73
68
  end
74
- return __gap_fill__(method, axis)
69
+ return __gap_fill__(method, axis, "strip_mask")
75
70
  end
76
71
  if fill.equal?(MASK_FILL_UNSET)
77
72
  raise ArgumentError, "strip_mask: a fill value is required (or method:)"
@@ -85,7 +80,8 @@ class CArray
85
80
  # unfillable cells stay masked; a fully filled result carries no mask
86
81
  # (hold allocates the output mask only when a leading run goes UNDEF;
87
82
  # :linear masks only the exterior via mask_invalid).
88
- def __gap_fill__ (method, axis)
83
+ def __gap_fill__ (method, axis, name)
84
+ axis = normalize_axis(axis, name) unless axis.nil?
89
85
  case method
90
86
  when :forward, :ffill
91
87
  __hold_axis__(axis, false)
@@ -100,8 +96,8 @@ class CArray
100
96
  end
101
97
  end
102
98
 
103
- # Forward (backward = false) or backward (true) hold along `axis`
104
- # (axis nil = flatten). Backward reuses the forward primitive on the
99
+ # Forward (backward = false) or backward (true) hold along `axis`, already
100
+ # normalised by __gap_fill__ (axis nil = flatten). Backward reuses the forward primitive on the
105
101
  # reversed view; flatten flattens, holds axis 0, reshapes back.
106
102
  def __hold_axis__ (axis, backward)
107
103
  if axis.nil?
@@ -111,11 +107,10 @@ class CArray
111
107
  held = held.reverse if backward
112
108
  held.reshape(*shape)
113
109
  else
114
- ax = axis < 0 ? axis + ndim : axis
115
110
  if backward
116
- flip(ax).send(:__hold__, ax).flip(ax)
111
+ flip(axis).send(:__hold__, axis).flip(axis)
117
112
  else
118
- send(:__hold__, ax)
113
+ send(:__hold__, axis)
119
114
  end
120
115
  end
121
116
  end
@@ -123,11 +118,18 @@ class CArray
123
118
  # Linear-by-index gap fill: interpolate each masked cell from the two
124
119
  # bracketing valid cells, x = cell index along the axis. Cells outside the
125
120
  # valid range (leading/trailing) stay masked. A Face goes through its own
126
- # linear_fetch, which keeps its unit and rounds to its grid; whether that
127
- # means anything is the Face's call (an ORDERABLE numeric one answers, the
128
- # rest raise from there), so this gate only asks that it be one.
121
+ # linear_fetch, which keeps its unit and rounds to its grid. Defining one
122
+ # is how a Face says it interpolates: without it, the core linear_fetch
123
+ # reads the Face's storage and fails from inside.
129
124
  def __gap_fill_linear__ (axis)
130
- unless numeric? || face?
125
+ if face?
126
+ unless __interpolable__
127
+ raise ArgumentError,
128
+ "unmask/strip_mask(method: :linear): #{self.class} does not " \
129
+ "interpolate (it defines no linear_fetch of its own); " \
130
+ "use method: :forward or :backward"
131
+ end
132
+ elsif ! numeric?
131
133
  raise ArgumentError,
132
134
  "unmask/strip_mask(method: :linear): numeric or time data_type " \
133
135
  "required (got #{data_type_name})"
@@ -135,46 +137,58 @@ class CArray
135
137
  if axis.nil?
136
138
  return __linear_fiber__(flatten).reshape(*shape)
137
139
  end
138
- ax = axis < 0 ? axis + ndim : axis
139
- if ax < 0 || ax >= ndim
140
- raise ArgumentError, "axis #{axis} out of range for ndim #{ndim}"
141
- end
142
- # A Face assembles in storage space -- its linear_fetch has already rounded
143
- # to the grid, so there is nothing left to hold in float64. A numeric
144
- # array assembles in float64 and casts back once at the end.
145
- out = face? ? template : float64.copy
146
- sink = face? ? out.parent : out
140
+ ax = axis
141
+ # A fiber comes back in self's own type (a Face's linear_fetch has already
142
+ # rounded to its grid), so the result assembles in that type and the
143
+ # present cells are never carried through float64.
144
+ out = face? ? template : value.copy
147
145
  __each_fiber_key__(ax) do |key|
148
- fiber = __linear_fiber__(self[*key])
149
- sink[*key] = face? ? fiber.parent : fiber
146
+ out[*key] = __linear_fiber__(self[*key])
150
147
  end
151
- face? ? out : out.to_type(data_type)
148
+ out
152
149
  end
153
150
 
154
- # 1-D linear-by-index fill of a single (possibly masked) fiber. Present
155
- # cells reproduce exactly; interior masked cells interpolate; exterior
156
- # masked cells become UNDEF (linear_section returns NaN out of range,
157
- # mask_invalid marks it). Returns the same kind as `vec`: a Face fiber
158
- # comes back as that Face, on its own unit.
151
+ # Whether linear interpolation can fill this array: a numeric array, or a
152
+ # Face that defines a linear_fetch of its own -- which is how a Face says it
153
+ # interpolates (the core linear_fetch would read its storage).
154
+ private def __interpolable__
155
+ face? ? self.class.instance_method(:linear_fetch).owner != CArray : numeric?
156
+ end
157
+
158
+ # 1-D linear-by-index fill of a single (possibly masked) fiber.
159
159
  def __linear_fiber__ (vec)
160
- n = vec.elements
161
- present = vec.is_not_masked
160
+ vec.send(:__linear_fill_along__, CArray.float64(vec.elements) { |i| i.to_f })
161
+ end
162
+
163
+ # Fill the masked cells of this 1-D array by linear interpolation against
164
+ # the coordinate `x` (same length, ascending over the present cells).
165
+ # The one rule both the core (x = cell position) and CAFrame (x = index)
166
+ # use: present cells come back exactly as they are, in this array's own
167
+ # type; a masked cell inside the span of the present ones takes the
168
+ # interpolated value; one outside it stays masked. Outside is where
169
+ # linear_section answers NaN -- a property of x, not of the values, so a
170
+ # NaN or Inf stored in a present cell is left alone. A Face goes through
171
+ # its own linear_fetch, which keeps its unit and rounds to its grid.
172
+ def __linear_fill_along__ (x)
173
+ present = is_not_masked
162
174
  # Fewer than two valid points -> nothing to interpolate between; leave
163
- # every masked cell masked (the copy carries vec's mask).
164
- if present.count(true) < 2
165
- return vec.face? ? vec.copy : vec.float64.copy
166
- end
167
- pos = CArray.float64(n) { |i| i.to_f }
168
- addr = pos[present].linear_section(pos) # valid positions -> monotonic grid
169
- if vec.face?
175
+ # every masked cell masked.
176
+ return copy if present.count(true) < 2
177
+ addr = x[present].linear_section(x) # valid positions -> monotonic grid
178
+ if face?
170
179
  # The Face's linear_fetch already masks the exterior (out of range) and
171
- # lands on its own grid, so it needs no cast and no mask_invalid.
172
- return vec[present].linear_fetch(addr)
180
+ # lands on its own grid.
181
+ return self[present].linear_fetch(addr)
173
182
  end
174
- vval = vec.value.float64[present] # valid values
175
- # Mark the out-of-range NaN before casting back: for an integer data_type
176
- # the cast turns NaN into 0, and mask_invalid then has nothing left to find.
177
- vval.linear_fetch(addr).mask_invalid.to_type(vec.data_type)
183
+ interp = value.float64[present].linear_fetch(addr)
184
+ # Built from the values alone, so a fully filled result carries no mask.
185
+ out = value.copy
186
+ masked = is_masked
187
+ fill = masked & addr.is_finite
188
+ exterior = masked & fill.not
189
+ out[fill] = interp[fill] if fill.any
190
+ out[exterior] = UNDEF if exterior.any
191
+ out
178
192
  end
179
193
 
180
194
  # Yield an index key (Array with `nil` at `axis`, integers elsewhere)
data/lib/carray/math.rb CHANGED
@@ -3,23 +3,30 @@ module CAMath
3
3
  module_function
4
4
 
5
5
  # Module-function front-ends for binop math methods registered on
6
- # CArray by mkkernel. The first argument is auto-wrapped to CArray
7
- # via `CArray.wrap_readonly`, so `CAMath.hypot(3, arr)` continues to
8
- # work. Integer input is unsupported at the kernel layer -- the
9
- # caller must cast to float64 explicitly (e.g.
10
- # `arr.as_float64.hypot(...)`).
6
+ # CArray by mkkernel. A CArray first argument answers through its own
7
+ # method, in its own data_type; any other first argument, and an
8
+ # integer CArray for a function defined only on floats, is taken as
9
+ # float64, so `CAMath.hypot(3, arr)` works.
11
10
 
12
11
  # @overload expm1(x)
13
- # Returns `exp(x) - 1` element-wise as float64.
12
+ # Returns `exp(x) - 1` element-wise. A CArray answers in its own
13
+ # `data_type` (integer input widens to float64, as {CArray#expm1}
14
+ # does); any other value is taken as float64.
14
15
  # @param x [CArray, Numeric] input value.
15
16
  # @return [CArray]
16
- def expm1(x); CArray.wrap_readonly(x, :float64).expm1; end
17
+ def expm1(x)
18
+ x.is_a?(CArray) ? x.expm1 : CArray.wrap_readonly(x, :float64).expm1
19
+ end
17
20
 
18
21
  # @overload log1p(x)
19
- # Returns `log(1 + x)` element-wise as float64.
22
+ # Returns `log(1 + x)` element-wise. A CArray answers in its own
23
+ # `data_type` (integer input widens to float64, as {CArray#log1p}
24
+ # does); any other value is taken as float64.
20
25
  # @param x [CArray, Numeric] input value.
21
26
  # @return [CArray]
22
- def log1p(x); CArray.wrap_readonly(x, :float64).log1p; end
27
+ def log1p(x)
28
+ x.is_a?(CArray) ? x.log1p : CArray.wrap_readonly(x, :float64).log1p
29
+ end
23
30
 
24
31
  # @overload atan2(y, x)
25
32
  # Returns the element-wise arc tangent of `y / x` with quadrant
@@ -27,21 +34,21 @@ module CAMath
27
34
  # @param y [CArray, Numeric] numerator.
28
35
  # @param x [CArray, Numeric] denominator.
29
36
  # @return [CArray]
30
- def atan2(y, x); CArray.wrap_readonly(y, :float64).atan2(x); end
37
+ def atan2(y, x); float_operand(y).atan2(x); end
31
38
 
32
39
  # @overload hypot(x, y)
33
40
  # Returns the element-wise Euclidean distance `sqrt(x^2 + y^2)`.
34
41
  # @param x [CArray, Numeric] first leg.
35
42
  # @param y [CArray, Numeric] second leg.
36
43
  # @return [CArray]
37
- def hypot(x, y); CArray.wrap_readonly(x, :float64).hypot(y); end
44
+ def hypot(x, y); float_operand(x).hypot(y); end
38
45
 
39
46
  # @overload copysign(x, y)
40
47
  # Returns `|x|` with the sign of `y`, element-wise.
41
48
  # @param x [CArray, Numeric] magnitude source.
42
49
  # @param y [CArray, Numeric] sign source.
43
50
  # @return [CArray]
44
- def copysign(x, y); CArray.wrap_readonly(x, :float64).copysign(y); end
51
+ def copysign(x, y); float_operand(x).copysign(y); end
45
52
 
46
53
  # @overload logaddexp(x, y)
47
54
  # Returns `log(exp(x) + exp(y))` computed to avoid overflow,
@@ -49,7 +56,7 @@ module CAMath
49
56
  # @param x [CArray, Numeric] first log-space value.
50
57
  # @param y [CArray, Numeric] second log-space value.
51
58
  # @return [CArray]
52
- def logaddexp(x, y); CArray.wrap_readonly(x, :float64).logaddexp(y); end
59
+ def logaddexp(x, y); float_operand(x).logaddexp(y); end
53
60
 
54
61
  # @overload nextafter(x, y)
55
62
  # Returns the next representable float from `x` toward `y`,
@@ -57,21 +64,27 @@ module CAMath
57
64
  # @param x [CArray, Numeric] starting value.
58
65
  # @param y [CArray, Numeric] direction target.
59
66
  # @return [CArray]
60
- def nextafter(x, y); CArray.wrap_readonly(x, :float64).nextafter(y); end
67
+ def nextafter(x, y); float_operand(x).nextafter(y); end
61
68
 
62
69
  # @overload fmod(x, y)
63
70
  # Returns the C-style `fmod(x, y)` element-wise (sign follows `x`).
64
71
  # @param x [CArray, Numeric] dividend.
65
72
  # @param y [CArray, Numeric] divisor.
66
73
  # @return [CArray]
67
- def fmod(x, y); CArray.wrap_readonly(x, :float64).fmod(y); end
74
+ def fmod(x, y); (x.is_a?(CArray) ? x : CArray.wrap_readonly(x, :float64)).fmod(y); end
75
+
76
+ # A float or complex CArray as it is; anything else as float64.
77
+ def float_operand(x)
78
+ x.is_a?(CArray) && (x.float? || x.complex?) ? x : CArray.wrap_readonly(x, :float64)
79
+ end
80
+ private_class_method :float_operand
68
81
 
69
82
  # @overload min(*argv)
70
83
  # Returns the element-wise minimum of the given CArray and other
71
84
  # arguments. At least one argument must be a CArray.
72
85
  # @param argv [Array<CArray, Numeric>] operands.
73
86
  # @return [CArray] fresh CArray holding the running min.
74
- # @raise [RuntimeError] when no CArray argument is present.
87
+ # @raise [ArgumentError] when no CArray argument is present.
75
88
  def min (*argv)
76
89
  if ary = argv.find{|x| x.is_a?(CArray) }
77
90
  out = ary.copy
@@ -80,7 +93,7 @@ module CAMath
80
93
  out.pmin!(x)
81
94
  end
82
95
  else
83
- raise "args should contain more than one CArray object"
96
+ raise ArgumentError, "args should contain more than one CArray object"
84
97
  end
85
98
  return out
86
99
  end
@@ -90,7 +103,7 @@ module CAMath
90
103
  # arguments. At least one argument must be a CArray.
91
104
  # @param argv [Array<CArray, Numeric>] operands.
92
105
  # @return [CArray] fresh CArray holding the running max.
93
- # @raise [RuntimeError] when no CArray argument is present.
106
+ # @raise [ArgumentError] when no CArray argument is present.
94
107
  def max (*argv)
95
108
  if ary = argv.find{|x| x.is_a?(CArray) }
96
109
  out = ary.copy
@@ -99,7 +112,7 @@ module CAMath
99
112
  out.pmax!(x)
100
113
  end
101
114
  else
102
- raise "args should contain more than one CArray object"
115
+ raise ArgumentError, "args should contain more than one CArray object"
103
116
  end
104
117
  return out
105
118
  end
@@ -36,7 +36,7 @@ class CAMeld
36
36
 
37
37
  def sum(*args, **kw)
38
38
  return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
39
- axis = kw[:axis]
39
+ axis = meld_fast_path_axis(kw)
40
40
  if axis.nil?
41
41
  parents.map(&:sum).inject(:+)
42
42
  elsif meld_axis_normalized?(axis)
@@ -52,7 +52,10 @@ class CAMeld
52
52
  # @return [CArray, Numeric]
53
53
  def mean(*args, **kw)
54
54
  return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
55
- axis = kw[:axis]
55
+ # The combine divides in Float; an object array's mean stays exact
56
+ # (Rational, Integer division) on the core path.
57
+ return super if object?
58
+ axis = meld_fast_path_axis(kw)
56
59
  # `mean` has no identity: an empty set of contributors has no defined
57
60
  # mean (0/0 = NaN). Punt to super, which returns UNDEF: a reduction
58
61
  # with no contributors yields the identity where one exists and UNDEF
@@ -78,12 +81,12 @@ class CAMeld
78
81
  # @return [CArray, Numeric]
79
82
  def min(*args, **kw)
80
83
  return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
81
- axis = kw[:axis]
84
+ axis = meld_fast_path_axis(kw)
82
85
  # `min` has no identity — empty parent list / all-empty parents punt to
83
86
  # super for UNDEF.
84
87
  if axis.nil?
85
88
  return super if parents.all? { |p| p.elements == 0 }
86
- parents.reject { |p| p.elements == 0 }.map(&:min).min
89
+ meld_combine_flat(:min)
87
90
  elsif meld_axis_normalized?(axis)
88
91
  return super if parents.all? { |p| p.dim[meld_axis] == 0 }
89
92
  nonempty = parents.reject { |p| p.dim[meld_axis] == 0 }
@@ -99,10 +102,10 @@ class CAMeld
99
102
  # @return [CArray, Numeric]
100
103
  def max(*args, **kw)
101
104
  return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
102
- axis = kw[:axis]
105
+ axis = meld_fast_path_axis(kw)
103
106
  if axis.nil?
104
107
  return super if parents.all? { |p| p.elements == 0 }
105
- parents.reject { |p| p.elements == 0 }.map(&:max).max
108
+ meld_combine_flat(:max)
106
109
  elsif meld_axis_normalized?(axis)
107
110
  return super if parents.all? { |p| p.dim[meld_axis] == 0 }
108
111
  nonempty = parents.reject { |p| p.dim[meld_axis] == 0 }
@@ -168,6 +171,9 @@ class CAMeld
168
171
  # more elaborate (min_count / fill_value / keep_axis / multi-axis /
169
172
  # mask propagation) punts to super.
170
173
  def meld_reduce_fast_path_ok?(kw)
174
+ return false unless CArray.__fast_paths_enabled__
175
+ # The mask of a meld is a meld built in C, with no parents list.
176
+ return false unless parents
171
177
  return false unless (kw.keys - [:axis]).empty?
172
178
  axis = kw[:axis]
173
179
  unless axis.nil?
@@ -177,9 +183,30 @@ class CAMeld
177
183
  end
178
184
  return false if has_mask?
179
185
  parents.each { |p| return false if p.has_mask? }
186
+ # A meld of Face arrays keeps its parents in their Face; the per-parent
187
+ # calls would reach the Face's own reductions, which answer in the Face's
188
+ # terms (or refuse), not in storage.
189
+ parents.each { |p| return false if p.face? }
180
190
  true
181
191
  end
182
192
 
193
+ # The axis a fast path reduces along. A 1-D meld has only the meld axis,
194
+ # and reducing it is reducing the whole array: the answer is a scalar, as
195
+ # for the flat call.
196
+ def meld_fast_path_axis(kw)
197
+ ndim == 1 ? nil : kw[:axis]
198
+ end
199
+
200
+ # The per-parent answers of a whole-array min / max, combined by core's
201
+ # own min / max so NaN loses the way it loses inside one parent (Ruby's
202
+ # Array#min would raise on it).
203
+ def meld_combine_flat(op)
204
+ vals = parents.reject { |p| p.elements == 0 }.map(&op)
205
+ part = CArray.new(data_type, [vals.size])
206
+ vals.each_with_index { |v, i| part[i] = v }
207
+ part.public_send(op)
208
+ end
209
+
183
210
  # Non-meld-axis decompose: each parent reduces the same axis independently,
184
211
  # then the K per-parent results are concatenated back along the meld axis.
185
212
  # Materialised to entity (`.copy`) to match CArray#reduce entity semantics.
@@ -198,7 +225,13 @@ class CAMeld
198
225
 
199
226
  def variance_family(args, kw, sample:, sqrt:)
200
227
  return yield unless args.empty? && meld_reduce_fast_path_ok?(kw)
201
- axis = kw[:axis]
228
+ # The merge works in Float; an object array's variance stays exact
229
+ # (Rational, BigDecimal) on the core path.
230
+ return yield if object?
231
+ # The merge squares the difference of means, which for complex values is
232
+ # not |δ|²; the core path computes the variance of a complex array.
233
+ return yield if complex?
234
+ axis = meld_fast_path_axis(kw)
202
235
  # Empty-parent / short-parent handling — Welford needs n >= 2 per parent
203
236
  # to recover m2 from p.variance for sample, n >= 1 for variancep (n=1
204
237
  # gives m2=0, fine). Any parent below its threshold punts to super,
@@ -85,8 +85,10 @@ class CArray
85
85
  # b_on_grid = b_data.project(ib)
86
86
  #
87
87
  # `direction:` (`:round` / `:floor` / `:ceil`) and `tolerance:` are
88
- # forwarded to {#locate_nearest_addr}: out-of-range grid points, and points
89
- # whose nearest value is farther than `tolerance`, come back masked.
88
+ # forwarded to {#locate_nearest_addr}: grid points with no match in the
89
+ # direction (before an array's first value under `:floor`, after its last
90
+ # under `:ceil`), and points whose match is farther than `tolerance`, come
91
+ # back masked.
90
92
  #
91
93
  # @param arrays [Array<CArray>] one or more arrays (Array / Range coerced
92
94
  # via `to_ca`).
@@ -94,8 +96,10 @@ class CArray
94
96
  # `nil` uses the first array verbatim.
95
97
  # @param direction [:round, :floor, :ceil] rounding rule for the nearest
96
98
  # match.
97
- # @param tolerance [Numeric, nil] maximum accepted distance; farther grid
98
- # points are masked. `nil` disables the check.
99
+ # @param tolerance [Numeric, String, CATimedelta::Element, nil] maximum
100
+ # accepted distance; farther grid points are masked. For time keys it
101
+ # is a duration (`"10 minutes"` or a CATimedelta value), as in
102
+ # {#locate_nearest_addr}. `nil` disables the check.
99
103
  # @return [Array<CArray>] `[common, idx_0, idx_1, ...]`.
100
104
  # @raise [ArgumentError] when no array is given (or `direction` is invalid,
101
105
  # raised by {#locate_nearest_addr}).
@@ -38,7 +38,7 @@ class CArray
38
38
 
39
39
  if elements.zero?
40
40
  if weights
41
- out = CArray.new(weights.data_type, [length])
41
+ out = CArray.new(weights.data_type, [length], bytes: weights.bytes)
42
42
  else
43
43
  out_type = (length > 0xFFFFFFFF) ? CA_UINT64 : CA_UINT32
44
44
  out = CArray.new(out_type, [length])
@@ -54,7 +54,7 @@ class CArray
54
54
  # Every cell is masked: no labels to count, same result as an empty
55
55
  # input (all-zero output of the requested minimum length).
56
56
  if weights
57
- out = CArray.new(weights.data_type, [length])
57
+ out = CArray.new(weights.data_type, [length], bytes: weights.bytes)
58
58
  else
59
59
  out_type = (length > 0xFFFFFFFF) ? CA_UINT64 : CA_UINT32
60
60
  out = CArray.new(out_type, [length])
@@ -58,9 +58,12 @@ class CArray
58
58
  # multiple of 8) are zero-filled. The byte order matches the packed-bit
59
59
  # convention used by Apache Arrow validity bitmaps and PEP 3118 `?` /
60
60
  # `_Bool` at the bit level.
61
+ # A masked cell has no bit to pack, so a receiver with masked cells is
62
+ # refused; pack the mask separately with {#validity_bits}.
61
63
  # @return [CArray] uint8 CArray of shape `[ceil(n / 8)]`.
62
- # @raise [ArgumentError] when the receiver is not 1-D or its
63
- # `data_type` is not one of `CA_BOOLEAN` / `CA_UINT8` / `CA_INT8`.
64
+ # @raise [ArgumentError] when the receiver is not 1-D, its `data_type`
65
+ # is not one of `CA_BOOLEAN` / `CA_UINT8` / `CA_INT8`, or it has
66
+ # masked cells.
64
67
  def pack_bits
65
68
  unless data_type == CA_BOOLEAN || data_type == CA_UINT8 || data_type == CA_INT8
66
69
  raise ArgumentError,
@@ -71,7 +74,15 @@ class CArray
71
74
  n_bytes = (n + 7) / 8
72
75
  packed = CArray.uint8(n_bytes) { 0 }
73
76
  return packed if n == 0
74
- packed.bitarray.reshape(-1)[0..n-1] = self
77
+ source = self
78
+ if has_mask?
79
+ if count_masked > 0
80
+ raise ArgumentError,
81
+ "pack_bits: masked cells have no bit to pack; pack the mask with validity_bits"
82
+ end
83
+ source = value
84
+ end
85
+ packed.bitarray.reshape(-1)[0..n-1] = source
75
86
  packed
76
87
  end
77
88