carray 3.0.1 → 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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +824 -0
  3. data/README.md +5 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +258 -4
  6. data/ext/ca_axis_group.c +368 -184
  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 +90 -41
  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 +960 -245
  14. data/ext/ca_kernel_iterator.h +352 -181
  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 +194 -42
  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 +5 -4
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +180 -50
  25. data/ext/ca_obj_face.c +428 -62
  26. data/ext/ca_obj_face.h +107 -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 +39 -26
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +235 -55
  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 +551 -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 +88 -75
  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 -123
  46. data/ext/ca_obj_stride.c +267 -35
  47. data/ext/ca_obj_string.c +25 -22
  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 +157 -29
  54. data/ext/ca_op_ipower.c +25 -13
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_rng_normal.h +42 -0
  58. data/ext/ca_rng_xoshiro256pp.h +105 -0
  59. data/ext/ca_sort_kernels.h +70 -0
  60. data/ext/ca_sweep_engine.c +385 -198
  61. data/ext/ca_sweep_engine.h +31 -23
  62. data/ext/ca_triop_dispatch.c +4 -1
  63. data/ext/ca_triop_dispatch.h +5 -2
  64. data/ext/caframe_csv_reader.c +463 -0
  65. data/ext/carray.h +90 -28
  66. data/ext/carray_access.c +487 -231
  67. data/ext/carray_address_basis.c +731 -0
  68. data/ext/carray_attribute.c +45 -3
  69. data/ext/carray_bincount.c +32 -2
  70. data/ext/carray_broadcast.c +49 -17
  71. data/ext/carray_call_cfunc.c +667 -511
  72. data/ext/carray_cast.c +1556 -247
  73. data/ext/carray_cast_func.rb +34 -21
  74. data/ext/carray_conversion.c +197 -73
  75. data/ext/carray_copy.c +84 -38
  76. data/ext/carray_core.c +591 -119
  77. data/ext/carray_count.c +148 -96
  78. data/ext/carray_element.c +18 -27
  79. data/ext/carray_factorize.c +604 -291
  80. data/ext/carray_generate.c +76 -40
  81. data/ext/carray_histogram.c +152 -94
  82. data/ext/carray_hold.c +2 -1
  83. data/ext/carray_index_classifier.c +63 -96
  84. data/ext/carray_internal.h +400 -1
  85. data/ext/carray_lazy.c +834 -79
  86. data/ext/carray_loop.c +88 -66
  87. data/ext/carray_mask.c +705 -324
  88. data/ext/carray_math_kernel.h +1 -0
  89. data/ext/carray_median_percentile.c +263 -63
  90. data/ext/carray_memory_view.c +254 -248
  91. data/ext/carray_operator.c +1093 -363
  92. data/ext/carray_order.c +133 -69
  93. data/ext/carray_partition.c +102 -56
  94. data/ext/carray_random.c +582 -204
  95. data/ext/carray_scatter.c +308 -65
  96. data/ext/carray_segment.c +257 -0
  97. data/ext/carray_slab.c +183 -70
  98. data/ext/carray_sort.c +235 -240
  99. data/ext/carray_sort_kernel.c +8 -1
  100. data/ext/carray_struct.c +99 -21
  101. data/ext/carray_test.c +167 -64
  102. data/ext/carray_time_text.c +1182 -0
  103. data/ext/carray_utils.c +363 -98
  104. data/ext/extconf.rb +27 -2
  105. data/ext/mk_call_cfunc.rb +103 -118
  106. data/ext/mk_pow5_table.rb +35 -0
  107. data/ext/mkkernel.rb +1349 -851
  108. data/ext/ruby_carray.c +17 -2
  109. data/ext/version.h +4 -4
  110. data/ext/version.rb +4 -2
  111. data/lib/carray/attribute.rb +39 -0
  112. data/lib/carray/autoload_carray.rb +24 -3
  113. data/lib/carray/autoload_method_extension.rb +18 -0
  114. data/lib/carray/axis_group.rb +245 -20
  115. data/lib/carray/basics.rb +95 -25
  116. data/lib/carray/bincount_nd.rb +6 -0
  117. data/lib/carray/block_iterator.rb +200 -54
  118. data/lib/carray/boolean_reduce.rb +21 -29
  119. data/lib/carray/categorical.rb +244 -59
  120. data/lib/carray/categorical_iterator.rb +391 -757
  121. data/lib/carray/complex.rb +36 -29
  122. data/lib/carray/conditional.rb +94 -7
  123. data/lib/carray/const_string.rb +177 -27
  124. data/lib/carray/construct.rb +40 -0
  125. data/lib/carray/data_type_extension.rb +3 -0
  126. data/lib/carray/data_type_limits.rb +91 -0
  127. data/lib/carray/fixlen_string.rb +1 -1
  128. data/lib/carray/frame/concat.rb +157 -6
  129. data/lib/carray/frame/convert.rb +13 -7
  130. data/lib/carray/frame/csv_parser.rb +590 -76
  131. data/lib/carray/frame/describe.rb +79 -0
  132. data/lib/carray/frame/frame.rb +205 -35
  133. data/lib/carray/frame/group.rb +199 -40
  134. data/lib/carray/frame/io.rb +452 -60
  135. data/lib/carray/frame/join.rb +134 -46
  136. data/lib/carray/frame/records.rb +54 -19
  137. data/lib/carray/frame/reshape.rb +448 -0
  138. data/lib/carray/frame/sort.rb +26 -2
  139. data/lib/carray/frame/verbs.rb +726 -56
  140. data/lib/carray/frame.rb +2 -0
  141. data/lib/carray/fuse_source.rb +111 -2
  142. data/lib/carray/fusion.rb +229 -38
  143. data/lib/carray/inspect.rb +71 -20
  144. data/lib/carray/iterator.rb +157 -2
  145. data/lib/carray/lazy.rb +54 -67
  146. data/lib/carray/mask_gap_fill.rb +66 -50
  147. data/lib/carray/math.rb +32 -19
  148. data/lib/carray/meld_reduce.rb +40 -7
  149. data/lib/carray/methods/align_addr.rb +8 -4
  150. data/lib/carray/methods/bincount.rb +2 -2
  151. data/lib/carray/methods/bit_string.rb +14 -3
  152. data/lib/carray/methods/composition.rb +16 -10
  153. data/lib/carray/methods/discovery_along.rb +74 -0
  154. data/lib/carray/methods/factorize.rb +50 -0
  155. data/lib/carray/methods/gather_nd.rb +13 -2
  156. data/lib/carray/methods/insert_block.rb +9 -14
  157. data/lib/carray/methods/is_in.rb +130 -6
  158. data/lib/carray/methods/join.rb +1 -1
  159. data/lib/carray/methods/locate_addr.rb +235 -21
  160. data/lib/carray/methods/mask_duplicates.rb +35 -1
  161. data/lib/carray/methods/mode.rb +18 -1
  162. data/lib/carray/methods/nunique.rb +22 -1
  163. data/lib/carray/methods/repeat.rb +91 -0
  164. data/lib/carray/methods/resize.rb +21 -23
  165. data/lib/carray/methods/snap.rb +34 -12
  166. data/lib/carray/methods/unique.rb +52 -3
  167. data/lib/carray/record_order.rb +208 -0
  168. data/lib/carray/rng.rb +86 -0
  169. data/lib/carray/runtime.rb +14 -11
  170. data/lib/carray/segment_iterator.rb +990 -0
  171. data/lib/carray/serialize.rb +197 -27
  172. data/lib/carray/slab_iterator.rb +109 -40
  173. data/lib/carray/stack.rb +10 -4
  174. data/lib/carray/string_operation_extension.rb +9 -4
  175. data/lib/carray/struct.rb +142 -51
  176. data/lib/carray/struct_builder.rb +110 -14
  177. data/lib/carray/table.rb +2 -2
  178. data/lib/carray/time.rb +535 -105
  179. data/lib/carray/window_iterator.rb +259 -82
  180. data/lib/carray.rb +4 -2
  181. data/yard-stubs/ca_obj_array.rb +4 -4
  182. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  183. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  184. data/yard-stubs/ca_obj_block.rb +2 -7
  185. data/yard-stubs/ca_obj_fake.rb +1 -1
  186. data/yard-stubs/ca_obj_field.rb +1 -1
  187. data/yard-stubs/ca_obj_refer.rb +3 -3
  188. data/yard-stubs/ca_obj_roll.rb +0 -1
  189. data/yard-stubs/ca_obj_stride.rb +10 -7
  190. data/yard-stubs/ca_obj_transpose.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +11 -4
  192. data/yard-stubs/carray_access.rb +6 -5
  193. data/yard-stubs/carray_broadcast.rb +2 -2
  194. data/yard-stubs/carray_cast.rb +78 -43
  195. data/yard-stubs/carray_conversion.rb +13 -1
  196. data/yard-stubs/carray_copy.rb +8 -3
  197. data/yard-stubs/carray_core.rb +3 -83
  198. data/yard-stubs/carray_count.rb +16 -2
  199. data/yard-stubs/carray_lazy.rb +210 -1
  200. data/yard-stubs/carray_loop.rb +4 -1
  201. data/yard-stubs/carray_mask.rb +34 -10
  202. data/yard-stubs/carray_math.rb +1499 -4
  203. data/yard-stubs/carray_median_percentile.rb +26 -8
  204. data/yard-stubs/carray_order.rb +43 -82
  205. data/yard-stubs/carray_scatter.rb +10 -3
  206. data/yard-stubs/carray_segment.rb +72 -0
  207. data/yard-stubs/carray_slab.rb +11 -8
  208. data/yard-stubs/carray_sort.rb +50 -8
  209. data/yard-stubs/carray_test.rb +10 -0
  210. data/yard-stubs/carray_utils.rb +0 -26
  211. metadata +19 -19
  212. data/ext/ca_for_each_element.h +0 -239
  213. data/ext/carray_build_flags.h +0 -3
  214. data/ext/carray_config.h +0 -28
  215. data/ext/carray_kernels_bincmp.c +0 -4446
  216. data/ext/carray_kernels_binop.c +0 -11001
  217. data/ext/carray_kernels_init.c +0 -1131
  218. data/ext/carray_kernels_map.c +0 -3467
  219. data/ext/carray_kernels_moncmp.c +0 -2097
  220. data/ext/carray_kernels_monop.c +0 -18313
  221. data/ext/carray_kernels_reduce_aggregate.c +0 -25837
  222. data/ext/carray_kernels_reduce_boolean.c +0 -330
  223. data/ext/carray_kernels_reduce_cumulative.c +0 -14593
  224. data/ext/carray_kernels_reduce_extreme.c +0 -16948
  225. data/ext/carray_kernels_reduce_variance.c +0 -3910
  226. data/ext/carray_kernels_scan.c +0 -3693
  227. data/ext/carray_kernels_search.c +0 -32138
  228. data/ext/carray_kernels_sort.c +0 -10626
  229. data/ext/carray_kernels_triop.c +0 -1392
@@ -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
@@ -1,3 +1,5 @@
1
+ require "carray/methods/discovery_along"
2
+
1
3
  class CArray
2
4
 
3
5
  # @overload unique(sort: false)
@@ -24,22 +26,69 @@ class CArray
24
26
  # For `CA_OBJECT` / `CA_FIXLEN` distinctness follows Ruby
25
27
  # `eql?` / `hash`; note that Ruby does not collapse distinct NaN
26
28
  # objects, so a `CA_OBJECT` array of Float NaN is not collapsed
27
- # (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.
28
32
  #
29
33
  # @param sort [Boolean] when true, return the distinct values
30
- # 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}.
31
36
  # A time array (`CATime` / `CATimedelta`) answers with its own type on
32
37
  # its own unit: the distinct values are values, so the array comes back
33
38
  # as itself rather than as raw storage ticks.
34
39
  #
35
40
  # @return [CArray] 1-D CArray of the distinct values, same data type
36
41
  # as `self`.
37
- def unique (sort: false)
42
+ #
43
+ # @overload unique(along: k)
44
+ # Returns the distinct **sub-arrays** of `self`, comparing whole
45
+ # sub-arrays rather than cells: `along: k` names the axis whose
46
+ # index enumerates them, so `z.unique(along: 0)` gives the distinct
47
+ # rows of a 2-D array, in first-appearance order. This is
48
+ # `np.unique(z, axis=k)`.
49
+ #
50
+ # Note the contrast with `axis:` on {#nunique} and
51
+ # {#mask_duplicates}, which names the axis a *fiber runs along* and
52
+ # asks about the values inside each fiber. The two cannot be given
53
+ # together.
54
+ #
55
+ # Distinctness is the family's, widened from a cell to a sub-array:
56
+ # two sub-arrays are the same when every cell is, with all NaN one
57
+ # value and -0.0 == +0.0. A sub-array holding a masked cell does not
58
+ # participate and never appears in the result.
59
+ #
60
+ # The result is a **view** of `self` -- the surviving sub-arrays,
61
+ # not copies of them -- so writing through it reaches `self`. Take a
62
+ # `copy` when that is not wanted. `sort:` is not available here:
63
+ # sub-arrays have no order to sort by. `object` arrays are refused,
64
+ # because their cells hold Ruby references.
65
+ #
66
+ # @param along [Integer] axis whose index enumerates the sub-arrays.
67
+ # @return [CArray] the distinct sub-arrays, same shape as `self`
68
+ # except along `along`.
69
+ def unique (sort: false, along: nil)
70
+ if along
71
+ if sort
72
+ raise ArgumentError,
73
+ "unique: sort: is not available with along: -- sub-arrays have " \
74
+ "no order to sort by; the result is in first-appearance order"
75
+ end
76
+ keys = fibers_as_cells(along, "unique")
77
+ args = [nil] * ndim
78
+ args[normalize_axis(along, "unique")] = keys.mask_duplicates.is_not_masked
79
+ return self[*args]
80
+ end
38
81
  # Single-pass seen-set hash (C __unique_flat__), one lane per data type family:
39
82
  # integer widens to a 64-bit key; float uses the bitwise key with all-NaN
40
83
  # collapsed and -0.0 / +0.0 normalized; object keys on rb_hash + rb_eql and
41
84
  # fixlen on a byte-hash + memcmp, both reproducing Ruby Hash distinctness.
42
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
43
92
  levels = __unique_flat__
44
93
  sort ? levels.sort : levels
45
94
  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) { |*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) 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 ADDED
@@ -0,0 +1,86 @@
1
+ class CArray
2
+
3
+ # A random number generator with its own state.
4
+ #
5
+ # `Rng` rather than `Random`: a `CArray::Random` would shadow `::Random`
6
+ # for every bare `Random` written inside `class CArray`, and the two mean
7
+ # different generators -- `random!(rng: Random.new(4))` draws through
8
+ # Ruby's MT19937 and `random!(rng: CArray::Rng.new(seed: 4))` through the
9
+ # one below. Two names that different should not look alike.
10
+ #
11
+ # The class itself is defined in ext/carray_random.c, which is where the
12
+ # state is advanced. What is added here is the generator's source text,
13
+ # so that another gem can run the same code rather than a second
14
+ # implementation of it.
15
+ class Rng
16
+
17
+ # The generators this carray knows, and how many int64 cells each one
18
+ # keeps its state in.
19
+ STATE_CELLS = {
20
+ :xoshiro256pp => 4,
21
+ }.freeze
22
+
23
+ GENERATORS = STATE_CELLS.keys.freeze
24
+
25
+ # Where a generator's C lives. Shipped: the gemspec takes ext/*.h,
26
+ # and `ext` is a require path, so this resolves in a checkout and in
27
+ # an installed gem alike -- lib/ and ext/ are siblings in both.
28
+ SOURCE_FILES = {
29
+ :xoshiro256pp => File.expand_path("../../ext/ca_rng_xoshiro256pp.h",
30
+ __dir__),
31
+ }.freeze
32
+
33
+ # The C each generator is, as text.
34
+ #
35
+ # CArray::Rng::SOURCE[:xoshiro256pp] #=> "/* ---- ... */\n..."
36
+ #
37
+ # This is the same text the extension compiled -- the file below is
38
+ # `#include`d by ext/carray_random.c -- which is the point of handing
39
+ # it out. A caller that pastes it into a translation unit of its own
40
+ # gets a generator that continues a sequence this one started, because
41
+ # it is running the code this one ran and not a copy of it.
42
+ #
43
+ # carray-jit is the caller this exists for: a kernel's `random(rng:)`
44
+ # pastes the text beside the helpers its own compiler emits, so a
45
+ # kernel draws from the CArray::Rng it was handed. The text needs
46
+ # nothing but <stdint.h> and defines only `static inline` functions
47
+ # under a `ca_` prefix.
48
+ #
49
+ # It is read once, when this file is first required.
50
+ SOURCE = SOURCE_FILES.transform_values { |path|
51
+ File.read(path, :encoding => "UTF-8")
52
+ }.freeze
53
+
54
+ # What every generator's text needs beside it, and what none of them
55
+ # owns: turning uniforms into a normal is the same arithmetic whichever
56
+ # generator the uniforms came from.
57
+ #
58
+ # Handed out apart from SOURCE because whoever pastes it pastes it once,
59
+ # however many generators are drawing. Folded into each generator's own
60
+ # text instead, two generators in one translation unit would define it
61
+ # twice.
62
+ COMMON_SOURCE = File.read(
63
+ File.expand_path("../../ext/ca_rng_normal.h", __dir__), :encoding => "UTF-8"
64
+ ).freeze
65
+
66
+ # The functions in SOURCE a caller may reach, by generator and by what
67
+ # the draw answers: `:random` a double in [0.0, 1.0), `:randomn` a
68
+ # standard normal, `:bits` the raw word a draw came from. Each takes the
69
+ # state and advances it -- `:randomn` by two draws, the other two by one.
70
+ #
71
+ # Named here rather than worked out by whoever pastes the text: which
72
+ # symbols are the entry points is a fact about the generator, and the
73
+ # generator is CArray's. These are the three `#random`, `#randomn`
74
+ # and `#bits` call, so a caller pasting the text draws the sequence
75
+ # this one draws.
76
+ DRAW_FUNCTIONS = {
77
+ :xoshiro256pp => {
78
+ :random => "ca_xoshiro256pp_next_real",
79
+ :randomn => "ca_xoshiro256pp_next_normal",
80
+ :bits => "ca_xoshiro256pp_next",
81
+ }.freeze,
82
+ }.freeze
83
+
84
+ end
85
+
86
+ end
@@ -5,6 +5,16 @@
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
+
8
18
  # String fallback for the global cast functions CA_INT32(data) etc. The C
9
19
  # dispatcher (ext/carray_cast.c) handles every other input class in C and
10
20
  # delegates only String here, because the whitespace / `,` / `;` /
@@ -65,23 +75,16 @@ class CArray
65
75
  num = prod.sum(**nopt)
66
76
  den = wv.sum(axis: axis, keep_axis: keep_axis)
67
77
  result =
68
- if axis
78
+ if den.is_a?(CArray) # axis given, or keep_axis
69
79
  den = den.copy
70
80
  den[den.eq(0)] = UNDEF # zero / empty denominator -> undetermined
71
81
  num / den # UNDEF propagates cell-wise
72
- elsif den.equal?(UNDEF) || den == 0
73
- UNDEF
82
+ elsif num.equal?(UNDEF) || den.equal?(UNDEF) || den == 0
83
+ UNDEF # min_count unmet leaves num UNDEF
74
84
  else
75
85
  num / den
76
86
  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
84
- end
87
+ CArray.__reduce_fill__(result, opts[:fill_value])
85
88
  end
86
89
  private :__wmean_object__
87
90