carray 3.0.2 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +304 -0
  3. data/README.md +3 -4
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +225 -0
  6. data/ext/ca_axis_group.c +177 -99
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +66 -71
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +663 -214
  14. data/ext/ca_kernel_iterator.h +231 -167
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +132 -22
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +1 -0
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +96 -25
  25. data/ext/ca_obj_face.c +406 -64
  26. data/ext/ca_obj_face.h +92 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +21 -21
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +189 -107
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +543 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +47 -49
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -118
  46. data/ext/ca_obj_stride.c +195 -34
  47. data/ext/ca_obj_string.c +17 -18
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +149 -27
  54. data/ext/ca_op_ipower.c +24 -11
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_sort_kernels.h +70 -0
  58. data/ext/ca_sweep_engine.c +102 -79
  59. data/ext/ca_sweep_engine.h +7 -20
  60. data/ext/ca_triop_dispatch.c +4 -1
  61. data/ext/ca_triop_dispatch.h +5 -2
  62. data/ext/caframe_csv_reader.c +463 -0
  63. data/ext/carray.h +70 -27
  64. data/ext/carray_access.c +455 -211
  65. data/ext/carray_address_basis.c +177 -36
  66. data/ext/carray_attribute.c +45 -3
  67. data/ext/carray_bincount.c +32 -2
  68. data/ext/carray_broadcast.c +46 -14
  69. data/ext/carray_call_cfunc.c +0 -28
  70. data/ext/carray_cast.c +1444 -209
  71. data/ext/carray_cast_func.rb +34 -21
  72. data/ext/carray_conversion.c +197 -73
  73. data/ext/carray_copy.c +32 -11
  74. data/ext/carray_core.c +514 -122
  75. data/ext/carray_count.c +140 -87
  76. data/ext/carray_element.c +18 -27
  77. data/ext/carray_factorize.c +569 -277
  78. data/ext/carray_generate.c +76 -40
  79. data/ext/carray_histogram.c +152 -94
  80. data/ext/carray_hold.c +2 -1
  81. data/ext/carray_index_classifier.c +63 -96
  82. data/ext/carray_internal.h +385 -3
  83. data/ext/carray_lazy.c +834 -79
  84. data/ext/carray_loop.c +81 -65
  85. data/ext/carray_mask.c +682 -316
  86. data/ext/carray_math_kernel.h +1 -0
  87. data/ext/carray_median_percentile.c +221 -76
  88. data/ext/carray_memory_view.c +254 -248
  89. data/ext/carray_operator.c +1089 -359
  90. data/ext/carray_order.c +133 -69
  91. data/ext/carray_partition.c +102 -56
  92. data/ext/carray_random.c +273 -239
  93. data/ext/carray_scatter.c +308 -65
  94. data/ext/carray_segment.c +257 -0
  95. data/ext/carray_slab.c +170 -70
  96. data/ext/carray_sort.c +215 -218
  97. data/ext/carray_sort_kernel.c +8 -1
  98. data/ext/carray_struct.c +99 -21
  99. data/ext/carray_test.c +167 -64
  100. data/ext/carray_time_text.c +1182 -0
  101. data/ext/carray_utils.c +363 -98
  102. data/ext/extconf.rb +27 -2
  103. data/ext/mk_call_cfunc.rb +0 -2
  104. data/ext/mk_pow5_table.rb +35 -0
  105. data/ext/mkkernel.rb +1076 -846
  106. data/ext/ruby_carray.c +7 -1
  107. data/ext/version.h +4 -4
  108. data/ext/version.rb +4 -2
  109. data/lib/carray/attribute.rb +39 -0
  110. data/lib/carray/autoload_carray.rb +19 -0
  111. data/lib/carray/autoload_method_extension.rb +6 -0
  112. data/lib/carray/axis_group.rb +174 -26
  113. data/lib/carray/basics.rb +91 -25
  114. data/lib/carray/bincount_nd.rb +6 -0
  115. data/lib/carray/block_iterator.rb +118 -48
  116. data/lib/carray/boolean_reduce.rb +21 -29
  117. data/lib/carray/categorical.rb +94 -26
  118. data/lib/carray/categorical_iterator.rb +333 -826
  119. data/lib/carray/complex.rb +36 -29
  120. data/lib/carray/conditional.rb +94 -7
  121. data/lib/carray/const_string.rb +46 -0
  122. data/lib/carray/frame/concat.rb +157 -6
  123. data/lib/carray/frame/convert.rb +13 -7
  124. data/lib/carray/frame/csv_parser.rb +589 -82
  125. data/lib/carray/frame/describe.rb +79 -0
  126. data/lib/carray/frame/frame.rb +124 -25
  127. data/lib/carray/frame/group.rb +189 -63
  128. data/lib/carray/frame/io.rb +412 -72
  129. data/lib/carray/frame/join.rb +134 -46
  130. data/lib/carray/frame/records.rb +37 -16
  131. data/lib/carray/frame/reshape.rb +448 -0
  132. data/lib/carray/frame/sort.rb +26 -2
  133. data/lib/carray/frame/verbs.rb +715 -48
  134. data/lib/carray/frame.rb +2 -0
  135. data/lib/carray/fuse_source.rb +111 -2
  136. data/lib/carray/fusion.rb +229 -38
  137. data/lib/carray/inspect.rb +29 -11
  138. data/lib/carray/iterator.rb +14 -2
  139. data/lib/carray/lazy.rb +54 -30
  140. data/lib/carray/mask_gap_fill.rb +66 -52
  141. data/lib/carray/math.rb +32 -19
  142. data/lib/carray/meld_reduce.rb +40 -7
  143. data/lib/carray/methods/align_addr.rb +8 -4
  144. data/lib/carray/methods/bincount.rb +2 -2
  145. data/lib/carray/methods/bit_string.rb +14 -3
  146. data/lib/carray/methods/composition.rb +16 -10
  147. data/lib/carray/methods/factorize.rb +2 -2
  148. data/lib/carray/methods/gather_nd.rb +13 -2
  149. data/lib/carray/methods/insert_block.rb +9 -14
  150. data/lib/carray/methods/is_in.rb +117 -4
  151. data/lib/carray/methods/join.rb +1 -1
  152. data/lib/carray/methods/locate_addr.rb +177 -36
  153. data/lib/carray/methods/mode.rb +18 -1
  154. data/lib/carray/methods/repeat.rb +1 -20
  155. data/lib/carray/methods/resize.rb +21 -23
  156. data/lib/carray/methods/snap.rb +34 -12
  157. data/lib/carray/methods/unique.rb +11 -2
  158. data/lib/carray/record_order.rb +208 -0
  159. data/lib/carray/runtime.rb +14 -11
  160. data/lib/carray/segment_iterator.rb +990 -0
  161. data/lib/carray/serialize.rb +197 -27
  162. data/lib/carray/slab_iterator.rb +51 -27
  163. data/lib/carray/stack.rb +10 -4
  164. data/lib/carray/string_operation_extension.rb +4 -3
  165. data/lib/carray/struct.rb +142 -51
  166. data/lib/carray/struct_builder.rb +110 -14
  167. data/lib/carray/table.rb +2 -2
  168. data/lib/carray/time.rb +517 -103
  169. data/lib/carray/window_iterator.rb +127 -72
  170. data/lib/carray.rb +2 -2
  171. data/yard-stubs/ca_obj_array.rb +4 -4
  172. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  173. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  174. data/yard-stubs/ca_obj_fake.rb +1 -1
  175. data/yard-stubs/ca_obj_field.rb +1 -1
  176. data/yard-stubs/ca_obj_refer.rb +3 -3
  177. data/yard-stubs/ca_obj_roll.rb +0 -1
  178. data/yard-stubs/ca_obj_stride.rb +10 -7
  179. data/yard-stubs/ca_obj_transpose.rb +1 -1
  180. data/yard-stubs/ca_obj_window.rb +1 -2
  181. data/yard-stubs/carray_access.rb +5 -4
  182. data/yard-stubs/carray_broadcast.rb +1 -1
  183. data/yard-stubs/carray_cast.rb +78 -43
  184. data/yard-stubs/carray_conversion.rb +13 -1
  185. data/yard-stubs/carray_copy.rb +8 -3
  186. data/yard-stubs/carray_core.rb +3 -3
  187. data/yard-stubs/carray_count.rb +9 -0
  188. data/yard-stubs/carray_lazy.rb +5 -1
  189. data/yard-stubs/carray_loop.rb +4 -1
  190. data/yard-stubs/carray_mask.rb +34 -10
  191. data/yard-stubs/carray_math.rb +25 -13
  192. data/yard-stubs/carray_median_percentile.rb +10 -6
  193. data/yard-stubs/carray_order.rb +34 -13
  194. data/yard-stubs/carray_scatter.rb +10 -3
  195. data/yard-stubs/carray_segment.rb +72 -0
  196. data/yard-stubs/carray_slab.rb +2 -1
  197. data/yard-stubs/carray_sort.rb +43 -3
  198. data/yard-stubs/carray_test.rb +10 -0
  199. data/yard-stubs/carray_utils.rb +0 -26
  200. metadata +11 -19
  201. data/ext/ca_for_each_element.h +0 -239
  202. data/ext/carray_build_flags.h +0 -3
  203. data/ext/carray_config.h +0 -28
  204. data/ext/carray_kernels_bincmp.c +0 -4446
  205. data/ext/carray_kernels_binop.c +0 -11001
  206. data/ext/carray_kernels_init.c +0 -1131
  207. data/ext/carray_kernels_map.c +0 -3467
  208. data/ext/carray_kernels_moncmp.c +0 -2097
  209. data/ext/carray_kernels_monop.c +0 -18313
  210. data/ext/carray_kernels_reduce_aggregate.c +0 -26005
  211. data/ext/carray_kernels_reduce_boolean.c +0 -330
  212. data/ext/carray_kernels_reduce_cumulative.c +0 -14862
  213. data/ext/carray_kernels_reduce_extreme.c +0 -17494
  214. data/ext/carray_kernels_reduce_variance.c +0 -3910
  215. data/ext/carray_kernels_scan.c +0 -3693
  216. data/ext/carray_kernels_search.c +0 -32218
  217. data/ext/carray_kernels_sort.c +0 -10626
  218. data/ext/carray_kernels_triop.c +0 -1392
@@ -65,12 +65,25 @@ class CArray
65
65
  # Only meaningful for discrete / binned data: raw float is almost all
66
66
  # unique, so every value is modal and the per-axis Array grows to the
67
67
  # fiber length. Bin first. Same NaN / mask semantics as {#is_mode}.
68
+ # A complex array raises {CArray::DataTypeError}, since the modes
69
+ # are listed in ascending order; {#is_mode} marks its modal cells.
68
70
  #
69
71
  # @param axis [Integer, nil] axis to take the mode along; `nil` uses
70
72
  # the whole array.
71
73
  # @return [CArray, Array<CArray>] 1-D CArray (flat) or an Array of
72
74
  # reduced CArrays, one per mode rank (per-axis).
73
75
  def mode (axis: nil)
76
+ # An error from the methods mode is made of (sort, ...) names mode.
77
+ CArray.__answer_as__(:mode) { mode_answered(axis) }
78
+ end
79
+
80
+ private def mode_answered (axis)
81
+ if complex?
82
+ raise CArray::DataTypeError,
83
+ "mode: the modes are listed in ascending order, and a " \
84
+ "#{data_type_name} value has no order (complex numbers are not " \
85
+ "ordered); is_mode marks the cells that hold a mode"
86
+ end
74
87
  return __mode_flat if axis.nil?
75
88
  k = normalize_axis(axis, "mode")
76
89
 
@@ -120,7 +133,11 @@ class CArray
120
133
  def __mode_flat
121
134
  values, counts = value_counts
122
135
  return values if values.elements == 0
123
- values[counts.eq(counts.max)].sort
136
+ modes = values[counts.eq(counts.max)]
137
+ # Records are sorted only by the members their struct declares in
138
+ # order_by:; without one the modes stay in first-appearance order.
139
+ return modes if is_a?(CARecord) && data_class.order_by.nil?
140
+ modes.sort
124
141
  end
125
142
 
126
143
  end
@@ -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
@@ -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
@@ -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