carray 3.0.2 → 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 (247) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +568 -0
  3. data/README.md +6 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +220 -0
  7. data/ext/ca_axis_group.c +201 -123
  8. data/ext/ca_binop_dispatch.c +46 -52
  9. data/ext/ca_binop_dispatch.h +1 -1
  10. data/ext/ca_categorical_iterator.c +249 -183
  11. data/ext/ca_compare.c +7 -12
  12. data/ext/ca_for_buffer.h +21 -68
  13. data/ext/ca_group_iter.c +38 -23
  14. data/ext/ca_kernel_iterator.c +1765 -2076
  15. data/ext/ca_kernel_iterator.h +584 -566
  16. data/ext/ca_monop_dispatch.c +86 -63
  17. data/ext/ca_monop_dispatch.h +5 -0
  18. data/ext/ca_obj_array.c +223 -56
  19. data/ext/ca_obj_bincmp.c +172 -138
  20. data/ext/ca_obj_binop.c +116 -230
  21. data/ext/ca_obj_bitarray.c +40 -26
  22. data/ext/ca_obj_bitfield.c +100 -68
  23. data/ext/ca_obj_block.c +19 -9
  24. data/ext/ca_obj_byte_swap.c +45 -46
  25. data/ext/ca_obj_const_string.c +269 -44
  26. data/ext/ca_obj_face.c +612 -89
  27. data/ext/ca_obj_face.h +111 -47
  28. data/ext/ca_obj_fake.c +36 -79
  29. data/ext/ca_obj_farray.c +7 -3
  30. data/ext/ca_obj_field.c +33 -8
  31. data/ext/ca_obj_fixlen_string.c +23 -24
  32. data/ext/ca_obj_grid.c +541 -210
  33. data/ext/ca_obj_meld.c +331 -111
  34. data/ext/ca_obj_moncmp.c +80 -65
  35. data/ext/ca_obj_monop.c +177 -149
  36. data/ext/ca_obj_object.c +664 -248
  37. data/ext/ca_obj_record.c +63 -23
  38. data/ext/ca_obj_reduce.c +18 -16
  39. data/ext/ca_obj_refer.c +140 -58
  40. data/ext/ca_obj_remap.c +85 -24
  41. data/ext/ca_obj_repeat.c +24 -9
  42. data/ext/ca_obj_roll.c +121 -16
  43. data/ext/ca_obj_select.c +61 -58
  44. data/ext/ca_obj_select_axis.c +73 -107
  45. data/ext/ca_obj_shift.c +32 -102
  46. data/ext/ca_obj_stack.c +372 -122
  47. data/ext/ca_obj_stride.c +348 -140
  48. data/ext/ca_obj_string.c +88 -22
  49. data/ext/ca_obj_tile.c +38 -21
  50. data/ext/ca_obj_time.c +45 -31
  51. data/ext/ca_obj_timedelta.c +43 -29
  52. data/ext/ca_obj_transpose.c +17 -6
  53. data/ext/ca_obj_triop.c +125 -141
  54. data/ext/ca_obj_window.c +330 -44
  55. data/ext/ca_op_ipower.c +24 -11
  56. data/ext/ca_op_powi.h +57 -30
  57. data/ext/ca_pow5_table.h +656 -0
  58. data/ext/ca_sort_kernels.h +70 -0
  59. data/ext/ca_sweep_engine.c +104 -81
  60. data/ext/ca_sweep_engine.h +7 -20
  61. data/ext/ca_triop_dispatch.c +4 -1
  62. data/ext/ca_triop_dispatch.h +5 -2
  63. data/ext/caframe_csv_reader.c +542 -0
  64. data/ext/carray.h +241 -92
  65. data/ext/carray_access.c +608 -276
  66. data/ext/carray_address_basis.c +494 -97
  67. data/ext/carray_attribute.c +129 -4
  68. data/ext/carray_bincount.c +39 -7
  69. data/ext/carray_broadcast.c +56 -19
  70. data/ext/carray_build_flags.h +1 -1
  71. data/ext/carray_call_cfunc.c +108 -28
  72. data/ext/carray_cast.c +1576 -223
  73. data/ext/carray_cast_func.rb +77 -24
  74. data/ext/carray_class.c +4 -3
  75. data/ext/carray_conversion.c +208 -75
  76. data/ext/carray_copy.c +82 -24
  77. data/ext/carray_core.c +646 -162
  78. data/ext/carray_count.c +171 -88
  79. data/ext/carray_element.c +94 -60
  80. data/ext/carray_factorize.c +670 -324
  81. data/ext/carray_generate.c +260 -83
  82. data/ext/carray_histogram.c +209 -106
  83. data/ext/carray_hold.c +14 -16
  84. data/ext/carray_index_classifier.c +422 -243
  85. data/ext/carray_internal.h +692 -3
  86. data/ext/carray_kernels_bincmp.c +7 -0
  87. data/ext/carray_kernels_binop.c +487 -120
  88. data/ext/carray_kernels_init.c +653 -50
  89. data/ext/carray_kernels_moncmp.c +15 -8
  90. data/ext/carray_kernels_monop.c +99 -43
  91. data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
  92. data/ext/carray_kernels_reduce_boolean.c +112 -36
  93. data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
  94. data/ext/carray_kernels_reduce_extreme.c +3032 -2892
  95. data/ext/carray_kernels_reduce_variance.c +439 -500
  96. data/ext/carray_kernels_scan.c +1097 -488
  97. data/ext/carray_kernels_search.c +3787 -2176
  98. data/ext/carray_kernels_sort.c +1200 -920
  99. data/ext/carray_kernels_triop.c +479 -7
  100. data/ext/carray_key_windows.c +216 -0
  101. data/ext/carray_lazy.c +967 -153
  102. data/ext/carray_loop.c +81 -65
  103. data/ext/carray_mask.c +910 -324
  104. data/ext/carray_math_kernel.h +1 -0
  105. data/ext/carray_median_percentile.c +299 -100
  106. data/ext/carray_memory_view.c +872 -758
  107. data/ext/carray_operator.c +1171 -364
  108. data/ext/carray_order.c +505 -133
  109. data/ext/carray_partition.c +112 -65
  110. data/ext/carray_random.c +327 -239
  111. data/ext/carray_round.c +273 -0
  112. data/ext/carray_scatter.c +346 -78
  113. data/ext/carray_segment.c +257 -0
  114. data/ext/carray_slab.c +249 -86
  115. data/ext/carray_sort.c +224 -227
  116. data/ext/carray_sort_kernel.c +8 -1
  117. data/ext/carray_struct.c +118 -39
  118. data/ext/carray_test.c +193 -69
  119. data/ext/carray_time_text.c +1182 -0
  120. data/ext/carray_undef.c +4 -0
  121. data/ext/carray_utils.c +579 -113
  122. data/ext/extconf.rb +27 -2
  123. data/ext/mk_call_cfunc.rb +19 -4
  124. data/ext/mk_pow5_table.rb +35 -0
  125. data/ext/mkkernel.rb +1202 -1014
  126. data/ext/ruby_carray.c +17 -5
  127. data/ext/version.h +4 -4
  128. data/ext/version.rb +4 -2
  129. data/lib/carray/attribute.rb +160 -17
  130. data/lib/carray/autoload_carray.rb +23 -27
  131. data/lib/carray/autoload_method_extension.rb +20 -5
  132. data/lib/carray/axis_group.rb +360 -63
  133. data/lib/carray/basics.rb +162 -28
  134. data/lib/carray/bincount_nd.rb +18 -2
  135. data/lib/carray/block_iterator.rb +231 -58
  136. data/lib/carray/boolean_reduce.rb +34 -42
  137. data/lib/carray/categorical.rb +242 -52
  138. data/lib/carray/categorical_iterator.rb +437 -827
  139. data/lib/carray/complex.rb +41 -34
  140. data/lib/carray/conditional.rb +150 -14
  141. data/lib/carray/const_string.rb +59 -4
  142. data/lib/carray/construct.rb +15 -11
  143. data/lib/carray/data_type_extension.rb +1 -0
  144. data/lib/carray/distance_limit.rb +77 -0
  145. data/lib/carray/fixlen_string.rb +7 -1
  146. data/lib/carray/frame/column_verbs.rb +70 -0
  147. data/lib/carray/frame/concat.rb +157 -6
  148. data/lib/carray/frame/convert.rb +82 -7
  149. data/lib/carray/frame/csv_parser.rb +688 -87
  150. data/lib/carray/frame/describe.rb +80 -0
  151. data/lib/carray/frame/frame.rb +369 -45
  152. data/lib/carray/frame/group.rb +631 -90
  153. data/lib/carray/frame/io.rb +506 -79
  154. data/lib/carray/frame/join.rb +411 -62
  155. data/lib/carray/frame/records.rb +116 -19
  156. data/lib/carray/frame/reshape.rb +455 -0
  157. data/lib/carray/frame/sort.rb +30 -18
  158. data/lib/carray/frame/verbs.rb +868 -61
  159. data/lib/carray/frame/windows.rb +117 -0
  160. data/lib/carray/frame.rb +4 -0
  161. data/lib/carray/fuse_source.rb +118 -2
  162. data/lib/carray/fusion.rb +257 -42
  163. data/lib/carray/histogram.rb +6 -1
  164. data/lib/carray/inspect.rb +54 -13
  165. data/lib/carray/iterator.rb +99 -9
  166. data/lib/carray/key_windows.rb +620 -0
  167. data/lib/carray/lazy.rb +216 -193
  168. data/lib/carray/mask_gap_fill.rb +112 -92
  169. data/lib/carray/math.rb +32 -19
  170. data/lib/carray/meld_reduce.rb +52 -21
  171. data/lib/carray/methods/align_addr.rb +8 -4
  172. data/lib/carray/methods/bin.rb +26 -18
  173. data/lib/carray/methods/bincount.rb +18 -2
  174. data/lib/carray/methods/bit_string.rb +15 -4
  175. data/lib/carray/methods/choose.rb +16 -3
  176. data/lib/carray/methods/composition.rb +18 -10
  177. data/lib/carray/methods/factorize.rb +2 -2
  178. data/lib/carray/methods/gather_nd.rb +25 -5
  179. data/lib/carray/methods/insert_block.rb +9 -14
  180. data/lib/carray/methods/is_between.rb +59 -0
  181. data/lib/carray/methods/is_in.rb +140 -10
  182. data/lib/carray/methods/join.rb +2 -7
  183. data/lib/carray/methods/lag.rb +49 -0
  184. data/lib/carray/methods/locate_addr.rb +182 -45
  185. data/lib/carray/methods/mode.rb +18 -1
  186. data/lib/carray/methods/nunique.rb +11 -6
  187. data/lib/carray/methods/repeat.rb +1 -20
  188. data/lib/carray/methods/resize.rb +21 -23
  189. data/lib/carray/methods/snap.rb +34 -12
  190. data/lib/carray/methods/unique.rb +11 -2
  191. data/lib/carray/methods/value_counts.rb +4 -11
  192. data/lib/carray/record_order.rb +208 -0
  193. data/lib/carray/rng.rb +3 -0
  194. data/lib/carray/runtime.rb +65 -17
  195. data/lib/carray/segment_iterator.rb +1221 -0
  196. data/lib/carray/serialize.rb +214 -30
  197. data/lib/carray/slab_iterator.rb +101 -27
  198. data/lib/carray/stack.rb +10 -4
  199. data/lib/carray/string.rb +105 -5
  200. data/lib/carray/string_operation_extension.rb +12 -4
  201. data/lib/carray/struct.rb +137 -58
  202. data/lib/carray/struct_builder.rb +124 -17
  203. data/lib/carray/time.rb +1222 -186
  204. data/lib/carray/window_iterator.rb +242 -81
  205. data/lib/carray.rb +9 -2
  206. data/yard-stubs/ca_obj_array.rb +4 -4
  207. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  208. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  209. data/yard-stubs/ca_obj_fake.rb +1 -1
  210. data/yard-stubs/ca_obj_field.rb +5 -1
  211. data/yard-stubs/ca_obj_grid.rb +21 -10
  212. data/yard-stubs/ca_obj_refer.rb +5 -4
  213. data/yard-stubs/ca_obj_roll.rb +2 -3
  214. data/yard-stubs/ca_obj_shift.rb +5 -1
  215. data/yard-stubs/ca_obj_stride.rb +10 -7
  216. data/yard-stubs/ca_obj_tile.rb +1 -1
  217. data/yard-stubs/ca_obj_transpose.rb +1 -1
  218. data/yard-stubs/ca_obj_window.rb +9 -5
  219. data/yard-stubs/carray_access.rb +11 -5
  220. data/yard-stubs/carray_attribute.rb +0 -5
  221. data/yard-stubs/carray_broadcast.rb +1 -1
  222. data/yard-stubs/carray_cast.rb +88 -43
  223. data/yard-stubs/carray_conversion.rb +15 -2
  224. data/yard-stubs/carray_copy.rb +12 -5
  225. data/yard-stubs/carray_core.rb +3 -3
  226. data/yard-stubs/carray_count.rb +9 -0
  227. data/yard-stubs/carray_element.rb +10 -4
  228. data/yard-stubs/carray_generate.rb +21 -1
  229. data/yard-stubs/carray_lazy.rb +5 -1
  230. data/yard-stubs/carray_loop.rb +4 -1
  231. data/yard-stubs/carray_mask.rb +60 -12
  232. data/yard-stubs/carray_math.rb +52 -14
  233. data/yard-stubs/carray_median_percentile.rb +15 -8
  234. data/yard-stubs/carray_memory_view.rb +40 -23
  235. data/yard-stubs/carray_order.rb +61 -29
  236. data/yard-stubs/carray_random.rb +13 -4
  237. data/yard-stubs/carray_scatter.rb +21 -9
  238. data/yard-stubs/carray_segment.rb +72 -0
  239. data/yard-stubs/carray_slab.rb +10 -2
  240. data/yard-stubs/carray_sort.rb +49 -8
  241. data/yard-stubs/carray_test.rb +15 -3
  242. data/yard-stubs/carray_utils.rb +0 -26
  243. metadata +19 -5
  244. data/ext/ca_for_each_element.h +0 -239
  245. data/ext/carray_kernels_map.c +0 -3467
  246. data/lib/carray/arrow_tensor.rb +0 -401
  247. data/lib/carray/table.rb +0 -108
@@ -15,7 +15,7 @@
15
15
  # `strip_mask(:na)`). Passing both raises ArgumentError.
16
16
  #
17
17
  # Design / rationale: devel/PROPOSAL_UNMASK_FILL_METHOD.md. The forward
18
- # hold is the C primitive #__hold__; backward = flip -> forward -> flip,
18
+ # hold is the C primitive #hold_forward; backward = flip -> forward -> flip,
19
19
  # flatten = flatten -> hold -> reshape, and :linear composes
20
20
  # linear_section / linear_fetch. User-facing YARD docs live in
21
21
  # yard-stubs/carray_mask.rb.
@@ -29,55 +29,50 @@
29
29
 
30
30
  class CArray
31
31
 
32
- # Preserve the C constant-fill primitives under private names so the
33
- # Ruby wrappers can delegate the non-method path unchanged.
34
- alias __unmask_const__ unmask
35
- alias __strip_mask_const__ strip_mask
36
- private :__unmask_const__, :__strip_mask_const__
37
-
38
32
  # Sentinel for "no positional fill value given" (distinct from any real
39
33
  # fill value, including nil / false / a Symbol).
40
34
  MASK_FILL_UNSET = ::Object.new
35
+ Ractor.make_shareable(MASK_FILL_UNSET)
41
36
  private_constant :MASK_FILL_UNSET
42
37
 
43
- def unmask (fill = MASK_FILL_UNSET, method: nil, axis: nil)
44
- if method
45
- unless fill.equal?(MASK_FILL_UNSET)
46
- raise ArgumentError,
47
- "unmask: pass either a constant fill value or method:, not both"
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
38
+ # unmask / strip_mask with method:, in front of the C constant-fill
39
+ # primitives, which they reach with super.
40
+ module GapFill
41
+ def unmask (fill = MASK_FILL_UNSET, method: nil, axis: nil)
42
+ if method
43
+ unless fill.equal?(MASK_FILL_UNSET)
44
+ raise ArgumentError,
45
+ "unmask: pass either a constant fill value or method:, not both"
46
+ end
47
+ held = gap_fill(method, axis, "unmask")
48
+ # Copy the filled values in place (a Face takes a Face of its own kind
49
+ # as it is).
56
50
  value[] = held.value
51
+ if held.has_mask?
52
+ self.mask = held.mask # residual leading/trailing mask
53
+ else
54
+ super() # fully filled: drop the mask
55
+ end
56
+ return self
57
57
  end
58
- if held.has_mask?
59
- self.mask = held.mask # residual leading/trailing mask
60
- else
61
- __unmask_const__ # fully filled: drop the mask
62
- end
63
- return self
58
+ fill.equal?(MASK_FILL_UNSET) ? super() : super(fill)
64
59
  end
65
- fill.equal?(MASK_FILL_UNSET) ? __unmask_const__ : __unmask_const__(fill)
66
- end
67
60
 
68
- def strip_mask (fill = MASK_FILL_UNSET, method: nil, axis: nil)
69
- if method
70
- unless fill.equal?(MASK_FILL_UNSET)
71
- raise ArgumentError,
72
- "strip_mask: pass either a constant fill value or method:, not both"
61
+ def strip_mask (fill = MASK_FILL_UNSET, method: nil, axis: nil)
62
+ if method
63
+ unless fill.equal?(MASK_FILL_UNSET)
64
+ raise ArgumentError,
65
+ "strip_mask: pass either a constant fill value or method:, not both"
66
+ end
67
+ return carry_attrs(gap_fill(method, axis, "strip_mask"))
73
68
  end
74
- return __gap_fill__(method, axis)
75
- end
76
- if fill.equal?(MASK_FILL_UNSET)
77
- raise ArgumentError, "strip_mask: a fill value is required (or method:)"
69
+ if fill.equal?(MASK_FILL_UNSET)
70
+ raise ArgumentError, "strip_mask: a fill value is required (or method:)"
71
+ end
72
+ carry_attrs(super(fill))
78
73
  end
79
- __strip_mask_const__(fill)
80
74
  end
75
+ prepend GapFill
81
76
 
82
77
  private
83
78
 
@@ -85,14 +80,15 @@ 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
- __hold_axis__(axis, false)
87
+ hold_along_axis(axis, false)
92
88
  when :backward, :bfill
93
- __hold_axis__(axis, true)
89
+ hold_along_axis(axis, true)
94
90
  when :linear
95
- __gap_fill_linear__(axis)
91
+ gap_fill_linear(axis)
96
92
  else
97
93
  raise ArgumentError,
98
94
  "unmask/strip_mask: unknown method: #{method.inspect} " \
@@ -100,22 +96,21 @@ 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
- def __hold_axis__ (axis, backward)
102
+ def hold_along_axis (axis, backward)
107
103
  if axis.nil?
108
104
  flat = flatten
109
105
  flat = flat.reverse if backward
110
- held = flat.send(:__hold__, 0)
106
+ held = flat.send(:hold_forward, 0)
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_forward, axis).flip(axis)
117
112
  else
118
- send(:__hold__, ax)
113
+ send(:hold_forward, axis)
119
114
  end
120
115
  end
121
116
  end
@@ -123,64 +118,89 @@ 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.
129
- def __gap_fill_linear__ (axis)
130
- unless numeric? || face?
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.
124
+ def gap_fill_linear (axis)
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})"
134
136
  end
135
137
  if axis.nil?
136
- return __linear_fiber__(flatten).reshape(*shape)
138
+ return linear_fill_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}"
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
145
+ each_fiber_key(ax) do |key|
146
+ out[*key] = linear_fill_fiber(self[*key])
141
147
  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
147
- __each_fiber_key__(ax) do |key|
148
- fiber = __linear_fiber__(self[*key])
149
- sink[*key] = face? ? fiber.parent : fiber
150
- 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.
159
- def __linear_fiber__ (vec)
160
- n = vec.elements
161
- present = vec.is_not_masked
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? # type-label: asks what the Face can do, names nothing
156
+ end
157
+
158
+ # 1-D linear-by-index fill of a single (possibly masked) fiber.
159
+ def linear_fill_fiber (vec)
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
+ v = value[present]
184
+ # A complex value interpolates its real and imaginary parts.
185
+ interp = v.complex? ? v.linear_fetch(addr) : v.float64.linear_fetch(addr)
186
+ # An integer array takes the nearest integer, half away from zero (as a
187
+ # time Face lands on its own grid), rounded before the store would cut
188
+ # the fraction off.
189
+ interp = interp.round if integer?
190
+ # Built from the values alone, so a fully filled result carries no mask.
191
+ out = value.copy
192
+ masked = is_masked
193
+ fill = masked & addr.is_finite
194
+ exterior = masked & fill.not
195
+ out[fill] = interp[fill] if fill.any
196
+ out[exterior] = UNDEF if exterior.any
197
+ out
178
198
  end
179
199
 
180
200
  # Yield an index key (Array with `nil` at `axis`, integers elsewhere)
181
201
  # for every fiber along `axis`. `self[*key]` is that fiber as a masked
182
202
  # 1-D view.
183
- def __each_fiber_key__ (axis)
203
+ def each_fiber_key (axis)
184
204
  outer = shape
185
205
  outer_dims = outer.each_index.reject { |k| k == axis }.map { |k| outer[k] }
186
206
  idx = Array.new(outer_dims.size, 0)
@@ -196,7 +216,7 @@ class CArray
196
216
  end
197
217
  end
198
218
  end
199
- private :__hold_axis__, :__gap_fill_linear__, :__linear_fiber__,
200
- :__each_fiber_key__
219
+ private :hold_along_axis, :gap_fill_linear, :linear_fill_fiber,
220
+ :each_fiber_key
201
221
 
202
222
  end
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
@@ -16,7 +16,7 @@
16
16
  #
17
17
  # Fast path activates when:
18
18
  # - axis kwarg is a single Integer (or absent for flat reduce)
19
- # - no mask on CAMeld or any parent (skipna needs per-cell dispatch;
19
+ # - no mask on CAMeld or any parent (skipping masked cells needs per-cell dispatch;
20
20
  # the decompose would not see correct mask propagation across parents)
21
21
  # - no non-:axis kwargs (min_count / fill_value / keep_axis / etc. punt
22
22
  # to super; those change finalisation semantics)
@@ -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
@@ -63,9 +66,9 @@ class CAMeld
63
66
  total_count = parents.map(&:elements).inject(:+)
64
67
  total_sum / total_count.to_f
65
68
  elsif meld_axis_normalized?(axis)
66
- return super if parents.all? { |p| p.dim[meld_axis] == 0 }
69
+ return super if parents.all? { |p| p.shape[meld_axis] == 0 }
67
70
  total_sum = parents.map { |p| p.sum(axis: axis) }.inject(:+)
68
- total_count = parents.map { |p| p.dim[meld_axis] }.inject(:+)
71
+ total_count = parents.map { |p| p.shape[meld_axis] }.inject(:+)
69
72
  total_sum / total_count.to_f
70
73
  else
71
74
  non_meld_axis_decompose(:mean, axis)
@@ -78,15 +81,15 @@ 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
- return super if parents.all? { |p| p.dim[meld_axis] == 0 }
89
- nonempty = parents.reject { |p| p.dim[meld_axis] == 0 }
91
+ return super if parents.all? { |p| p.shape[meld_axis] == 0 }
92
+ nonempty = parents.reject { |p| p.shape[meld_axis] == 0 }
90
93
  CArray.stack(nonempty.map { |p| p.min(axis: axis) }).min(axis: 0)
91
94
  else
92
95
  non_meld_axis_decompose(:min, axis)
@@ -99,13 +102,13 @@ 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
- return super if parents.all? { |p| p.dim[meld_axis] == 0 }
108
- nonempty = parents.reject { |p| p.dim[meld_axis] == 0 }
110
+ return super if parents.all? { |p| p.shape[meld_axis] == 0 }
111
+ nonempty = parents.reject { |p| p.shape[meld_axis] == 0 }
109
112
  CArray.stack(nonempty.map { |p| p.max(axis: axis) }).max(axis: 0)
110
113
  else
111
114
  non_meld_axis_decompose(:max, axis)
@@ -160,7 +163,7 @@ class CAMeld
160
163
  private
161
164
 
162
165
  def meld_axis_normalized?(axis)
163
- (axis < 0 ? axis + ndim : axis) == meld_axis
166
+ normalize_axis(axis) == meld_axis
164
167
  end
165
168
 
166
169
  # Fast path fires when: no mask anywhere, no non-:axis kwargs, and axis
@@ -168,18 +171,40 @@ 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?
174
- return false unless axis.is_a?(Integer)
175
- norm = axis < 0 ? axis + ndim : axis
176
- return false unless norm >= 0 && norm < ndim
180
+ return false unless axis.is_a?(Integer) && axis >= -ndim && axis < ndim # axis-check: a gate only; super normalizes
177
181
  end
178
182
  return false if has_mask?
179
183
  parents.each { |p| return false if p.has_mask? }
184
+ # A meld of Face arrays keeps its parents in their Face; the per-parent
185
+ # calls would reach the Face's own reductions, which answer in the Face's
186
+ # terms (or refuse), not in storage.
187
+ parents.each { |p| return false if p.face? }
180
188
  true
181
189
  end
182
190
 
191
+ # The axis a fast path reduces along. A 1-D meld has only the meld axis,
192
+ # and reducing it is reducing the whole array: the answer is a scalar, as
193
+ # for the flat call.
194
+ def meld_fast_path_axis(kw)
195
+ ndim == 1 ? nil : kw[:axis]
196
+ end
197
+
198
+ # The per-parent answers of a whole-array min / max, combined by core's
199
+ # own min / max so NaN loses the way it loses inside one parent (Ruby's
200
+ # Array#min would raise on it).
201
+ def meld_combine_flat(op)
202
+ vals = parents.reject { |p| p.elements == 0 }.map(&op)
203
+ part = CArray.new(data_type, [vals.size])
204
+ vals.each_with_index { |v, i| part[i] = v }
205
+ part.public_send(op)
206
+ end
207
+
183
208
  # Non-meld-axis decompose: each parent reduces the same axis independently,
184
209
  # then the K per-parent results are concatenated back along the meld axis.
185
210
  # Materialised to entity (`.copy`) to match CArray#reduce entity semantics.
@@ -191,14 +216,20 @@ class CAMeld
191
216
  # meld_axis branch and never reaches here.)
192
217
  def non_meld_axis_decompose(op, axis)
193
218
  parts = parents.map { |p| p.public_send(op, axis: axis) }
194
- axis_norm = axis < 0 ? axis + ndim : axis
219
+ axis_norm = normalize_axis(axis)
195
220
  new_meld_axis = axis_norm < meld_axis ? meld_axis - 1 : meld_axis
196
221
  CArray.meld(parts, axis: new_meld_axis).copy
197
222
  end
198
223
 
199
224
  def variance_family(args, kw, sample:, sqrt:)
200
225
  return yield unless args.empty? && meld_reduce_fast_path_ok?(kw)
201
- axis = kw[:axis]
226
+ # The merge works in Float; an object array's variance stays exact
227
+ # (Rational, BigDecimal) on the core path.
228
+ return yield if object?
229
+ # The merge squares the difference of means, which for complex values is
230
+ # not |δ|²; the core path computes the variance of a complex array.
231
+ return yield if complex?
232
+ axis = meld_fast_path_axis(kw)
202
233
  # Empty-parent / short-parent handling — Welford needs n >= 2 per parent
203
234
  # to recover m2 from p.variance for sample, n >= 1 for variancep (n=1
204
235
  # gives m2=0, fine). Any parent below its threshold punts to super,
@@ -208,7 +239,7 @@ class CAMeld
208
239
  return yield if parents.any? { |p| p.elements < min_n }
209
240
  finalize_scalar(welford_flat, sample, sqrt)
210
241
  elsif meld_axis_normalized?(axis)
211
- return yield if parents.any? { |p| p.dim[meld_axis] < min_n }
242
+ return yield if parents.any? { |p| p.shape[meld_axis] < min_n }
212
243
  finalize_vector(welford_axis(axis), sample, sqrt)
213
244
  else
214
245
  # Non-meld-axis: each parent's variance/stddev is independent along
@@ -255,7 +286,7 @@ class CAMeld
255
286
  mean = nil
256
287
  m2 = nil
257
288
  parents.each do |p|
258
- np = p.dim[meld_axis]
289
+ np = p.shape[meld_axis]
259
290
  mp = p.mean(axis: axis)
260
291
  # variance is undefined at n=1; for variancep with n=1, m2_p = 0 by
261
292
  # definition (variance_family already gated n >= min_n).
@@ -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}).