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
@@ -12,7 +12,9 @@ class CArray
12
12
  # whole array and returns an Integer. With `axis: k` it counts per
13
13
  # fiber along axis `k`, returning a reduced `CA_INT64` CArray
14
14
  # (shape = self.shape with axis `k` removed; `keep_axis: true` keeps
15
- # it as a length-1 axis). The shape rule matches other per-axis
15
+ # it as a length-1 axis). An Array of axes counts over all of them
16
+ # together, one count per remaining position; naming every axis
17
+ # returns an Integer. The shape rule matches other per-axis
16
18
  # reductions such as `sum(axis:)`.
17
19
  #
18
20
  # The distinct count has identity 0: an empty array, an all-masked
@@ -25,12 +27,12 @@ class CArray
25
27
  # distinctness follows Ruby `eql?` / `hash` (which, unlike numeric,
26
28
  # does not collapse distinct NaN objects).
27
29
  #
28
- # @param axis [Integer, nil] axis to count along; `nil` counts over
29
- # the whole array.
30
+ # @param axis [Integer, Array<Integer>, nil] axis or axes to count
31
+ # over; `nil` counts over the whole array.
30
32
  # @param keep_axis [Boolean] when `axis` is given, keep the reduced
31
33
  # axis as a length-1 axis instead of dropping it.
32
- # @return [Integer, CArray] Integer for `axis: nil`, otherwise a
33
- # reduced `CA_INT64` CArray.
34
+ # @return [Integer, CArray] Integer for `axis: nil` or every axis,
35
+ # otherwise a reduced `CA_INT64` CArray.
34
36
  # @overload nunique(along: k)
35
37
  # Returns how many distinct **sub-arrays** `self` holds, comparing
36
38
  # whole sub-arrays rather than cells: `along: k` names the axis
@@ -60,7 +62,10 @@ class CArray
60
62
  # then read the single reduced cell as an Integer.
61
63
  flatten.send(:__nunique__, 0, false)[0]
62
64
  else
63
- __nunique__(normalize_axis(axis, "nunique"), keep_axis)
65
+ axes = Array(axis).map { |a| normalize_axis(a, "nunique") }
66
+ counts = __nunique__(axes, keep_axis)
67
+ # naming every axis reduces to one count, an Integer as sum(axis:) gives
68
+ (axes.size == ndim && ! keep_axis) ? counts[0] : counts
64
69
  end
65
70
  end
66
71
 
@@ -76,26 +76,7 @@ class CArray
76
76
  end
77
77
 
78
78
  source = along ? self : (ndim > 1 ? flatten : self)
79
- source[*gather_args(repeat_index(counts), along)]
80
- end
81
-
82
- # The source index of every position in the result: element i appears
83
- # at the `counts[i]` positions starting at the exclusive prefix sum.
84
- #
85
- # Marking those starts with +1 and running a cumulative sum turns the
86
- # marks back into indices, and does the right thing with a zero
87
- # count for free -- two elements then share a start, that position
88
- # carries 2, and the sum steps over the one that was asked for zero
89
- # times.
90
- private def repeat_index (counts)
91
- total = counts.sum.to_i
92
- return CArray.int64(0) if total.zero?
93
- starts = counts.cumsum.int64 - counts
94
- marks = CArray.int64(total)
95
- # A trailing zero count starts one past the end; it marks nothing,
96
- # which is what a zero count means.
97
- marks.scatter_add!(starts[starts.lt(total)], 1)
98
- marks.cumsum.int64 - 1
79
+ source[*gather_args(CArray.segment_index(lengths: counts), along)]
99
80
  end
100
81
 
101
82
  # One index argument per axis: the gathered index on the axis being
@@ -4,8 +4,8 @@ class CArray
4
4
  # Returns `self` resized to `newdim`. The original data is
5
5
  # placed at offset 0 (positive size) or right-aligned (negative
6
6
  # size); the new area outside the original region is filled
7
- # with `fill_value`, or -- for fixlen storage -- left as zero
8
- # bytes. Pass `UNDEF` to mask the new area.
7
+ # with `fill_value`, or left as zero storage when it is not given
8
+ # (0 for a numeric array). Pass `UNDEF` to mask the new area.
9
9
  #
10
10
  # Each entry of `newdim` is: `nil` to keep the current
11
11
  # `shape[i]`, a positive Integer for a new size with the
@@ -13,15 +13,19 @@ class CArray
13
13
  # `d` for a new size `|d|` with the original right-aligned.
14
14
  #
15
15
  # Works on Face arrays (CARecord / CATime / ...) and
16
- # plain fixlen: the resize is done on the storage layout
17
- # (preserving bytes) and re-wrapped as the same Face.
16
+ # plain fixlen: the result is the same Face, and `fill_value` is a
17
+ # value of it (a Time for a time array).
18
18
  #
19
19
  # @param newdim [Array<Integer, nil>] new shape spec, one entry
20
20
  # per axis.
21
- # @param fill_value [Object] value for the extended area.
21
+ # @param fill_value [Object] value for the extended area (zero
22
+ # storage when not given).
22
23
  # @return [CArray] resized copy.
23
- def resize (*newdim, fill_value: 0)
24
- raise "ndim mismatch" if newdim.size != ndim
24
+ NO_FILL_VALUE = ::Object.new.freeze
25
+ private_constant :NO_FILL_VALUE
26
+
27
+ def resize (*newdim, fill_value: NO_FILL_VALUE)
28
+ raise ArgumentError, "ndim mismatch" if newdim.size != ndim
25
29
  offset = Array.new(ndim, 0)
26
30
  newdim = newdim.each_with_index.map do |d, i|
27
31
  case d
@@ -32,24 +36,18 @@ class CArray
32
36
  offset[i] = size - shape[i] if d < 0
33
37
  size
34
38
  else
35
- raise "invalid dimension size"
39
+ raise ArgumentError, "invalid dimension size"
36
40
  end
37
41
  end
38
- face_parent = self.face? ? self : nil
39
- src = self
40
- src = src.parent while src.face?
41
- dt = src.data_type
42
- bytes = (dt == :fixlen) ? src.bytes : nil
43
- out = CArray.new(dt, newdim, bytes: bytes)
44
- # Fill the new area: numeric storage takes fill_value as-is; fixlen
45
- # storage cannot hold a numeric 0, so leave zero bytes and honor only
46
- # UNDEF (mask) or an explicit String fill.
47
- if dt != :fixlen || fill_value.equal?(UNDEF) || fill_value.is_a?(String)
48
- out[] = fill_value
49
- end
50
- out.mask.paste(offset, src.false) if out.has_mask?
51
- out.paste(offset, src)
52
- out = out.face_lift(face_parent) if face_parent
42
+ # The original cells are copied as storage, so a read-only Face (a
43
+ # const string, a categorical) resizes too. The new area starts as zero
44
+ # storage; a given fill_value is written as a value of the array, which a
45
+ # read-only Face refuses.
46
+ out = blank_like(newdim)
47
+ out_storage = out.__send__(:face_storage)
48
+ out[] = fill_value unless fill_value.equal?(NO_FILL_VALUE)
49
+ out_storage.mask.paste(offset, self.false) if out_storage.has_mask?
50
+ out_storage.paste(offset, face_storage)
53
51
  out
54
52
  end
55
53
 
@@ -66,8 +66,11 @@ class CArray
66
66
  # the sample, `:ceil` at or above), and delegates to
67
67
  # `locate_nearest_addr(direction:)`.
68
68
  #
69
- # `list` must be a 1-D ascending numeric CArray (or convertible via
70
- # `CArray.wrap_readonly`). Out-of-range and NaN handling follows the
69
+ # `list` must be a 1-D ascending numeric CArray, or a Ruby Array /
70
+ # Range. An Array grid takes `self`'s data_type, except that a Float
71
+ # in it makes an integer array's grid float64 (so `0.5` is not
72
+ # truncated); an Integer that does not fit `self`'s type raises
73
+ # `RangeError`. Out-of-range and NaN handling follows the
71
74
  # same pattern as `bin_to`, but with an additional `:clamp`
72
75
  # sentinel that snaps out-of-range cells to the nearest list end.
73
76
  #
@@ -95,19 +98,21 @@ class CArray
95
98
  # shape as `self`.
96
99
  # @raise [ArgumentError] when `list` is not 1-D or is empty, or
97
100
  # `direction` is not one of the accepted symbols.
101
+ # @raise [RangeError] when an Integer in an Array `list` does not
102
+ # fit `self`'s data_type.
98
103
  # @example
99
104
  # temp.snap_to([270.0, 280.0, 290.0, 300.0]) # clamp OOB
100
105
  # temp.snap_to(grid, lfill: nil, ufill: nil) # mask OOB
101
106
  # rain.snap_to([0.0, 1.0, 5.0, 20.0], direction: :floor) # list value at or below
102
107
  def snap_to(list, lfill: :clamp, ufill: :clamp, direction: :round)
103
- ref = list.is_a?(CArray) ? list : CArray.wrap_readonly(list, self.data_type)
108
+ ref = list.is_a?(CArray) ? list : snap_list(list)
104
109
  raise ArgumentError, "snap_to: list must be 1-D" unless ref.ndim == 1
105
110
  n = ref.elements
106
111
  raise ArgumentError, "snap_to: list must have at least one value" if n < 1
107
112
 
108
113
  if n == 1
109
114
  # Degenerate: every finite cell snaps to the only value.
110
- out = CArray.new(ref.data_type, shape).fill(ref[0])
115
+ out = CArray.new(ref.data_type, shape, bytes: ref.bytes).fill(ref[0])
111
116
  out.mask = self.mask.to_ca if self.has_mask?
112
117
  if self.float?
113
118
  inv = self.is_invalid
@@ -118,17 +123,16 @@ class CArray
118
123
  return out
119
124
  end
120
125
 
121
- # `locate_nearest_addr` (via `linear_section`) accepts a 1-D `val` only;
122
- # flatten multi-D input and reshape the result back to preserve the
123
- # element-wise semantic on any shape.
126
+ # Multi-D input is snapped as its flattened cells and given its shape
127
+ # back.
124
128
  if ndim > 1
125
129
  return reshape(-1).snap_to(ref, lfill: lfill, ufill: ufill,
126
130
  direction: direction).reshape(*shape)
127
131
  end
128
132
 
129
- # locate_nearest_addr returns int64 indices; OOB (below / above / NaN)
130
- # cells come back masked. We split OOB into below / above with
131
- # explicit comparisons so the two sides can be filled independently.
133
+ # locate_nearest_addr returns int64 indices; whether a cell outside the
134
+ # list gets one depends on the direction, so the two sides are split with
135
+ # explicit comparisons and filled here independently of it.
132
136
  idx = self.locate_nearest_addr(ref, direction: direction)
133
137
  out = ref.project(idx)
134
138
 
@@ -137,13 +141,13 @@ class CArray
137
141
 
138
142
  case lfill
139
143
  when :clamp then out[below] = ref[0]
140
- when nil then # leave masked (locate_nearest_addr already masked OOB)
144
+ when nil then out[below] = UNDEF
141
145
  else out[below] = lfill
142
146
  end
143
147
 
144
148
  case ufill
145
149
  when :clamp then out[above] = ref[-1]
146
- when nil then # leave masked
150
+ when nil then out[above] = UNDEF
147
151
  else out[above] = ufill
148
152
  end
149
153
 
@@ -158,4 +162,22 @@ class CArray
158
162
  out
159
163
  end
160
164
 
165
+ private
166
+
167
+ # A Ruby Array grid in the type a search query would get: a Float in it
168
+ # makes an integer array's grid float64 (so 0.5 is not truncated to 0),
169
+ # otherwise the grid takes self's type, and an Integer that does not fit
170
+ # it is refused rather than wrapped.
171
+ def snap_list(list)
172
+ vals = list.to_a
173
+ type = data_type
174
+ type = CA_FLOAT64 if (integer? || data_type == CA_BOOLEAN) && vals.any? { |v| v.is_a?(Float) }
175
+ ref = CArray.wrap_readonly(vals, type)
176
+ if ref.integer?
177
+ bad = vals.zip(ref.to_a).find { |v, w| v != w }
178
+ raise RangeError, "snap_to: #{bad[0].inspect} is out of range for #{ref.data_type_name}" if bad
179
+ end
180
+ ref
181
+ end
182
+
161
183
  end
@@ -26,10 +26,13 @@ class CArray
26
26
  # For `CA_OBJECT` / `CA_FIXLEN` distinctness follows Ruby
27
27
  # `eql?` / `hash`; note that Ruby does not collapse distinct NaN
28
28
  # objects, so a `CA_OBJECT` array of Float NaN is not collapsed
29
- # (unlike the numeric path).
29
+ # (unlike the numeric path). Complex values follow the float rules
30
+ # in each component; the values come back with -0.0 written as 0.0
31
+ # and every NaN as one NaN.
30
32
  #
31
33
  # @param sort [Boolean] when true, return the distinct values
32
- # sorted ascending instead of in first-appearance order.
34
+ # sorted ascending instead of in first-appearance order. A complex
35
+ # array has no order and raises {CArray::DataTypeError}.
33
36
  # A time array (`CATime` / `CATimedelta`) answers with its own type on
34
37
  # its own unit: the distinct values are values, so the array comes back
35
38
  # as itself rather than as raw storage ticks.
@@ -80,6 +83,12 @@ class CArray
80
83
  # collapsed and -0.0 / +0.0 normalized; object keys on rb_hash + rb_eql and
81
84
  # fixlen on a byte-hash + memcmp, both reproducing Ruby Hash distinctness.
82
85
  # Masked cells are skipped in the kernel.
86
+ if sort && complex?
87
+ raise CArray::DataTypeError,
88
+ "unique(sort: true): a #{data_type_name} value has no order " \
89
+ "(complex numbers are not ordered); unique without sort: gives " \
90
+ "the values in first-appearance order"
91
+ end
83
92
  levels = __unique_flat__
84
93
  sort ? levels.sort : levels
85
94
  end
@@ -52,17 +52,10 @@ class CArray
52
52
  [ values[CArray.int64(order.size) { |i| order[i] }],
53
53
  counts[CArray.int64(order.size) { |i| order[i] }] ]
54
54
  when :value
55
- # Ascending value; NaN (numeric) or non-comparable last. Build the
56
- # permutation with an explicit NaN-last key so float NaN doesn't blow up
57
- # the Ruby sort, then gather both arrays through it.
58
- v = values.to_a
59
- order = (0...v.size).sort_by do |i|
60
- x = v[i]
61
- nan = x.is_a?(Float) && x.nan?
62
- [nan ? 1 : 0, nan ? 0 : x, i]
63
- end
64
- [ values[CArray.int64(order.size) { |i| order[i] }],
65
- counts[CArray.int64(order.size) { |i| order[i] }] ]
55
+ # Ascending value in the order sort uses (NaN after every number),
56
+ # then gather both arrays through it.
57
+ order = values.sort_index
58
+ [values[order], counts[order]]
66
59
  else
67
60
  [values, counts]
68
61
  end
@@ -0,0 +1,208 @@
1
+ # ----------------------------------------------------------------------------
2
+ #
3
+ # carray/record_order.rb
4
+ #
5
+ # The order of a CARecord: the members its struct names in `order_by:`,
6
+ # compared in that order.
7
+ #
8
+ # A record's storage is its bytes, and comparing bytes is no order of the
9
+ # values in them (a little-endian float, a signed integer). So the
10
+ # ordering members do not run on the storage: they run on one int64 array,
11
+ # each record's rank among the others by the declared members, and the
12
+ # values they return are gathered from the record array by the positions
13
+ # that array gives. A struct that declares no order has none, and these
14
+ # raise.
15
+ #
16
+ # ----------------------------------------------------------------------------
17
+
18
+ class CARecord
19
+
20
+ # @overload sort(axis: nil, kind: :quick, masked_position: :last)
21
+ # Returns a sorted {CARecord} view, ordered by the struct's
22
+ # `order_by:` members. With no `axis:` the array is flattened first.
23
+ # @return [CARecord]
24
+ # @raise [ArgumentError] when the struct declares no `order_by:`.
25
+ def sort (*args, **kw)
26
+ addr = record_rank.sort_addr(*args, **kw)
27
+ kw[:axis].nil? ? self[addr.flatten] : self[addr]
28
+ end
29
+
30
+ # @overload sort_copy(axis: nil, kind: :quick, masked_position: :last)
31
+ # @return [CARecord] an owned sorted copy.
32
+ def sort_copy (*args, **kw)
33
+ sort(*args, **kw).copy
34
+ end
35
+
36
+ # @!method sort_addr(axis: nil, kind: :quick, masked_position: :last)
37
+ # @return [CArray] the view-flat addresses of a sort by the declared order.
38
+ # @!method sort_index(axis: nil, kind: :quick, masked_position: :last)
39
+ # @return [CArray] the per-fiber indices of a sort by the declared order.
40
+ # @!method rank_index(axis: nil)
41
+ # @return [CArray] each record's rank by the declared order.
42
+ # @!method order(axis: nil, descending: false, method: :ordinal, kind: :quick)
43
+ # @return [CArray] each record's rank by the declared order.
44
+ # @!method min_index(axis: nil)
45
+ # @return [Integer, CArray] where the smallest record sits.
46
+ # @!method max_index(axis: nil)
47
+ # @return [Integer, CArray] where the largest record sits.
48
+ # @!method partition_index(kth, axis: nil)
49
+ # @return [CArray] the indices that partition about the `kth` record.
50
+ [:sort_addr, :sort_index, :rank_index, :order,
51
+ :min_index, :max_index, :partition_index].each do |op|
52
+ define_method(op, &Ractor.make_shareable(proc { |*args, **kw| record_rank.public_send(op, *args, **kw) }))
53
+ end
54
+
55
+ # @overload min(axis: nil, keep_axis: false)
56
+ # Returns the smallest record by the declared order, skipping masked
57
+ # records; UNDEF when every record is masked. With `axis:` a
58
+ # {CARecord} of the smallest along that axis.
59
+ # @return [CAStruct, CARecord]
60
+ def min (axis: nil, keep_axis: false)
61
+ record_extreme(:min_index, axis, keep_axis)
62
+ end
63
+
64
+ # @overload max(axis: nil, keep_axis: false)
65
+ # The largest record; see {#min}.
66
+ # @return [CAStruct, CARecord]
67
+ def max (axis: nil, keep_axis: false)
68
+ record_extreme(:max_index, axis, keep_axis)
69
+ end
70
+
71
+ # @overload minmax(axis: nil, keep_axis: false)
72
+ # @return [Array] `[min, max]`.
73
+ def minmax (axis: nil, keep_axis: false)
74
+ [min(axis: axis, keep_axis: keep_axis), max(axis: axis, keep_axis: keep_axis)]
75
+ end
76
+
77
+ # @overload partition_copy(kth, axis: nil)
78
+ # @return [CARecord] partitioned about the `kth` record in the declared
79
+ # order. With no `axis:` the array is flattened first.
80
+ def partition_copy (kth, axis: nil)
81
+ if axis.nil?
82
+ flat = flatten
83
+ return flat[flat.send(:record_rank).partition_index(kth)].copy
84
+ end
85
+ take_along_axis(record_rank.partition_index(kth, axis: axis), axis: axis).copy
86
+ end
87
+
88
+ # @!method lt(other)
89
+ # Compares by the declared order. `other` is a record of the same
90
+ # struct, or a {CARecord} of them.
91
+ # @return [CArray] boolean.
92
+ # @!method le(other)
93
+ # @!method gt(other)
94
+ # @!method ge(other)
95
+ [:lt, :le, :gt, :ge].each do |op|
96
+ define_method(op, &Ractor.make_shareable(proc do |other|
97
+ mine, theirs = record_rank_with(other)
98
+ mine.public_send(op, theirs)
99
+ end))
100
+ end
101
+
102
+ alias < lt
103
+ alias <= le
104
+ alias > gt
105
+ alias >= ge
106
+
107
+ private
108
+
109
+ # The struct's order_by: members, or a refusal naming where to declare it.
110
+ def record_order_by
111
+ data_class.order_by or
112
+ raise ArgumentError,
113
+ "#{data_class.inspect} declares no order; pass order_by: to " \
114
+ "CArray.struct to name the members records are ordered by"
115
+ end
116
+
117
+ # Each record's dense rank by the declared members, shaped like self.
118
+ # Each member is ranked on its own (a numeric member by value, NaN last;
119
+ # a fixlen member by its bytes), and the ranks are folded in member order.
120
+ # Ranking again after each fold keeps every value below the record count.
121
+ def record_rank
122
+ record_rank_of(self).reshape(*shape)
123
+ end
124
+
125
+ def record_rank_of (records)
126
+ rank = nil
127
+ record_order_by.each do |key|
128
+ r = records[key].flatten.order(method: :dense)
129
+ if rank.nil?
130
+ rank = r
131
+ else
132
+ top = r.max
133
+ span = top.equal?(UNDEF) ? 1 : top + 1
134
+ rank = (rank * span + r).order(method: :dense)
135
+ end
136
+ end
137
+ rank
138
+ end
139
+
140
+ # Ranks of self and `other` in one ordering, so they can be compared.
141
+ def record_rank_with (other)
142
+ case other
143
+ when CARecord
144
+ unless other.data_class.equal?(data_class)
145
+ raise ArgumentError,
146
+ "can not compare a record of #{data_class.inspect} " \
147
+ "with one of #{other.data_class.inspect}"
148
+ end
149
+ theirs = other.flatten
150
+ when data_class
151
+ theirs = CARecord.new(data_class, 1)
152
+ theirs[0] = other
153
+ else
154
+ raise ArgumentError,
155
+ "can not compare a record of #{data_class.inspect} with #{other.class}"
156
+ end
157
+ n = elements
158
+ both = record_rank_of(CArray.concatenate([flatten, theirs]))
159
+ mine = both[0...n].reshape(*shape)
160
+ rest = both[n..-1]
161
+ [mine, other.is_a?(CARecord) ? rest.reshape(*other.shape) : rest[0]]
162
+ end
163
+
164
+ # The record at each min_index / max_index, gathered from self.
165
+ def record_extreme (index_op, axis, keep_axis)
166
+ rank = record_rank
167
+ if axis.nil?
168
+ i = rank.flatten.public_send(index_op)
169
+ return i.equal?(UNDEF) ? UNDEF : flatten[i]
170
+ end
171
+ name = index_op.to_s.sub("_index", "")
172
+ if axis.is_a?(Array)
173
+ axes = axis.map { |k| normalize_axis(k, name) }.uniq
174
+ return record_extreme(index_op, axes[0], keep_axis) if axes.size == 1
175
+ return record_extreme_over(index_op, axes, keep_axis)
176
+ end
177
+ axis = normalize_axis(axis, name)
178
+ idx = rank.public_send(index_op, axis: axis)
179
+ empty = idx.is_masked
180
+ picked_shape = shape.dup
181
+ picked_shape[axis] = 1
182
+ out = take_along_axis(idx.strip_mask(0).reshape(*picked_shape), axis: axis).copy
183
+ if empty.any
184
+ out[empty.reshape(*picked_shape)] = UNDEF
185
+ end
186
+ return out if keep_axis
187
+ out.reshape(*(shape[0...axis] + shape[axis + 1..-1]))
188
+ end
189
+
190
+ # Over several axes: the reduced axes are moved last and merged into one.
191
+ def record_extreme_over (index_op, axes, keep_axis)
192
+ keep = (0...ndim).to_a - axes
193
+ kept_shape = shape.values_at(*keep)
194
+ if keep.empty?
195
+ r = record_extreme(index_op, nil, false)
196
+ return r unless keep_axis
197
+ out = flatten[0..0].copy
198
+ out[0] = r
199
+ return out.reshape(*([1] * ndim))
200
+ end
201
+ merged = transpose(*(keep + axes))
202
+ .reshape(*kept_shape, shape.values_at(*axes).inject(:*))
203
+ out = merged.send(:record_extreme, index_op, keep.size, false)
204
+ return out unless keep_axis
205
+ out.reshape(*shape.each_with_index.map { |n, k| axes.include?(k) ? 1 : n })
206
+ end
207
+
208
+ end
data/lib/carray/rng.rb CHANGED
@@ -29,6 +29,7 @@ class CArray
29
29
  :xoshiro256pp => File.expand_path("../../ext/ca_rng_xoshiro256pp.h",
30
30
  __dir__),
31
31
  }.freeze
32
+ Ractor.make_shareable(SOURCE_FILES)
32
33
 
33
34
  # The C each generator is, as text.
34
35
  #
@@ -50,6 +51,7 @@ class CArray
50
51
  SOURCE = SOURCE_FILES.transform_values { |path|
51
52
  File.read(path, :encoding => "UTF-8")
52
53
  }.freeze
54
+ Ractor.make_shareable(SOURCE)
53
55
 
54
56
  # What every generator's text needs beside it, and what none of them
55
57
  # owns: turning uniforms into a normal is the same arithmetic whichever
@@ -80,6 +82,7 @@ class CArray
80
82
  :bits => "ca_xoshiro256pp_next",
81
83
  }.freeze,
82
84
  }.freeze
85
+ Ractor.make_shareable(DRAW_FUNCTIONS)
83
86
 
84
87
  end
85
88
 
@@ -5,13 +5,32 @@
5
5
 
6
6
  class CArray
7
7
 
8
+ # A fast path answers by a different algorithm what a general path also
9
+ # answers. A development build defines this in C, false when run with
10
+ # CARRAY_GENERAL_PATH=1 so the test suite takes the general paths (see
11
+ # ext/carray_internal.h); a release build has no switch.
12
+ unless respond_to?(:__fast_paths_enabled__)
13
+ def self.__fast_paths_enabled__ # :nodoc:
14
+ true
15
+ end
16
+ end
17
+
18
+ # True when a development build runs with CARRAY_GENERAL_PATH=2: the
19
+ # kernel iterator then delivers every source through the view's own
20
+ # transfer. A release build has no switch.
21
+ unless respond_to?(:__iter_by_transfer_only__)
22
+ def self.__iter_by_transfer_only__ # :nodoc:
23
+ false
24
+ end
25
+ end
26
+
8
27
  # String fallback for the global cast functions CA_INT32(data) etc. The C
9
28
  # dispatcher (ext/carray_cast.c) handles every other input class in C and
10
29
  # delegates only String here, because the whitespace / `,` / `;` /
11
30
  # `_`->UNDEF parser is far more natural (and maintainable) in Ruby.
12
31
  # +type+ arrives as the data_type Symbol. Required: without it
13
32
  # +CA_INT32("1 2 3")+ and friends raise.
14
- def self.__cast_string__ (type, v) # :nodoc:
33
+ def self.cast_string (type, v)
15
34
  if type == CA_OBJECT
16
35
  return CScalar.new(CA_OBJECT) { v }
17
36
  elsif type == CA_BOOLEAN
@@ -30,7 +49,7 @@ class CArray
30
49
  return CArray.new(type, CArray.guess_array_shape(v)).tap { |a| a[] = v }
31
50
  end
32
51
  end
33
- private_class_method :__cast_string__
52
+ private_class_method :cast_string
34
53
 
35
54
  # CA_OBJECT escape for the weighted-sum kernel (mkkernel object_escape:).
36
55
  # The C dispatcher (carray_kernels.c rb_ca_wsum_ki) intercepts CA_OBJECT
@@ -43,18 +62,18 @@ class CArray
43
62
  # scalar or a full-shape weight), matching CArray's explicit-broadcast rule.
44
63
  # +opts+ is the trailing options Hash (axis:/keep_axis:/min_count:/
45
64
  # fill_value:) forwarded verbatim by the C escape; it goes straight to +sum+.
46
- def __wsum_object__ (weights, opts = {}) # :nodoc:
65
+ def wsum_object (weights, opts = {}) # :nodoc:
47
66
  (self * weights).sum(**opts)
48
67
  end
49
- private :__wsum_object__
68
+ private :wsum_object
50
69
 
51
70
  # CA_OBJECT escape for the weighted-mean kernel. Weighted mean is
52
71
  # +Σ(w*v) / Σw+ over the cells valid after mask overlay (a masked value OR a
53
72
  # masked weight drops the cell from BOTH sums). The denominator is guarded:
54
73
  # an empty / all-masked slab or weights summing to zero yields UNDEF (the
55
74
  # object analogue of the numeric kernel's 0/0 -> NaN, since objects have no
56
- # NaN). Weight forms and +opts+ as in {#__wsum_object__}.
57
- def __wmean_object__ (weights, opts = {}) # :nodoc:
75
+ # NaN). Weight forms and +opts+ as in {#wsum_object}.
76
+ def wmean_object (weights, opts = {}) # :nodoc:
58
77
  axis = opts[:axis]
59
78
  keep_axis = opts[:keep_axis]
60
79
  prod = self * weights
@@ -65,25 +84,54 @@ class CArray
65
84
  num = prod.sum(**nopt)
66
85
  den = wv.sum(axis: axis, keep_axis: keep_axis)
67
86
  result =
68
- if axis
87
+ if den.is_a?(CArray) # axis given, or keep_axis
69
88
  den = den.copy
70
89
  den[den.eq(0)] = UNDEF # zero / empty denominator -> undetermined
71
90
  num / den # UNDEF propagates cell-wise
72
- elsif den.equal?(UNDEF) || den == 0
73
- UNDEF
91
+ elsif num.equal?(UNDEF) || den.equal?(UNDEF) || den == 0
92
+ UNDEF # min_count unmet leaves num UNDEF
74
93
  else
75
94
  num / den
76
95
  end
77
- fill_value = opts[:fill_value]
78
- return result if fill_value.nil?
79
- if result.is_a?(CArray)
80
- result[result.is_masked] = fill_value if result.has_mask?
81
- result
82
- else
83
- result.equal?(UNDEF) ? fill_value : result
96
+ CArray.__fill_undefined_result_cells__(result, opts[:fill_value])
97
+ end
98
+ private :wmean_object
99
+
100
+ # Define on +mod+ one method per name in +names+, each a `def` that passes
101
+ # its own name and its arguments to the private method +to+:
102
+ #
103
+ # CArray.send(:define_named_methods, self, [:sum, :min], :fold_reduction)
104
+ # # def sum (...) = fold_reduction(:sum, ...)
105
+ #
106
+ # A method made by define_method carries no name in its frame, so an error
107
+ # raised inside it cannot name the method the user called; a `def` does.
108
+ # With +super_block+ the method passes its arguments as an Array and a Hash,
109
+ # and a block that calls the method it overrides with the arguments the
110
+ # block is given (`yield(*args, **kw)`).
111
+ #
112
+ # With +template+ (and no +to+) each method is written out from it, for a
113
+ # method on a path where a helper call would cost: __NAME__ is the name and
114
+ # __VALUE__ the value +names+ (a Hash) gives it, inspected.
115
+ NAMED_METHOD = /\A(?:[a-z_]\w*[?!]?|\+|-|\*\*?|\/|%|<=>|==|<=?|>=?|<<|>>|&|\||\^|~|[+-]@)\z/
116
+ def self.define_named_methods (mod, names, to = nil, super_block: false, template: nil)
117
+ loc = caller_locations(1, 1).first
118
+ names.each do |name, value|
119
+ unless name.to_s.match?(NAMED_METHOD)
120
+ raise ArgumentError, "define_named_methods: #{name.inspect} is not a method name"
121
+ end
122
+ src = if template
123
+ template.gsub("__NAME__", name.to_s).gsub("__VALUE__", value.inspect)
124
+ elsif super_block
125
+ "def #{name}(*args, **kw, &blk)\n" \
126
+ " #{to}(:#{name}, args, kw) { |*a, **k| super(*a, **k, &blk) }\n" \
127
+ "end\n"
128
+ else
129
+ "def #{name}(...)\n #{to}(:#{name}, ...)\nend\n"
130
+ end
131
+ mod.class_eval(src, loc.path, loc.lineno)
84
132
  end
85
133
  end
86
- private :__wmean_object__
134
+ private_class_method :define_named_methods
87
135
 
88
136
  end
89
137