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
@@ -1,7 +1,7 @@
1
1
  # Complex-component accessors (real / real= / imag / imag= / real? /
2
- # is_real). Loaded eagerly BEFORE carray/lazy.rb, because lazy.rb does
3
- # `alias_method :__real_eager__, :real` (and the imag equivalent) at load
4
- # time -- these defs must exist by then.
2
+ # is_real). Loaded eagerly BEFORE carray/lazy.rb, because lazy.rb takes
3
+ # `instance_method(:real)` (and the imag equivalent) at load time -- these
4
+ # defs must exist by then.
5
5
 
6
6
  class CArray
7
7
 
@@ -40,9 +40,9 @@ class CArray
40
40
  # Architecturally:
41
41
  # - lib/carray/lazy.rb's `real`/`imag` overrides dispatch to CAMonOp
42
42
  # when self is a lazy view, and fall through to the defs here for eager
43
- # parents (via `alias_method :__real_eager__, :real` at load time).
43
+ # parents (via `instance_method(:real)` taken at load time).
44
44
  # This file MUST be required before carray/lazy.rb (see carray.rb), and
45
- # removing these defs would break that alias and the lazy.rb load.
45
+ # removing these defs would break the lazy.rb load.
46
46
  #
47
47
  # - ext/ca_obj_stride.c recognises `CAField .real/.imag over complex`
48
48
  # as a first-class byte-mismatched reinterpret pattern. Architectural
@@ -52,6 +52,14 @@ class CArray
52
52
  # framework's read-only chain does not replace the CAField mutable view).
53
53
  # ---------------------------------------------------------------------------
54
54
 
55
+ # Whether a view kept by {#real} / {#imag} was made for this array.
56
+ # `dup` and `clone` copy instance variables, so a copy arrives holding the
57
+ # original's view, whose parent is the original; the copy makes its own.
58
+ def part_of_self? (view)
59
+ view && view.parent.equal?(self)
60
+ end
61
+ private :part_of_self?
62
+
55
63
  # @overload real
56
64
  # Returns the real part of `self` as a zero-copy view. For a
57
65
  # complex array the view is a mutable {CAField} into the
@@ -59,19 +67,16 @@ class CArray
59
67
  # over `self`. Writing to the view updates `self` in place.
60
68
  # @return [CArray]
61
69
  def real
62
- if not @__real__
63
- if complex?
64
- @__real__ = case data_type
65
- when CA_CMPLX64
66
- field(0, CA_FLOAT32)
67
- when CA_CMPLX128
68
- field(0, CA_FLOAT64)
69
- end
70
- else
71
- @__real__ = self[]
72
- end
73
- end
74
- @__real__
70
+ return @__real__ if part_of_self?(@__real__)
71
+ view = if complex?
72
+ case data_type
73
+ when CA_CMPLX64 then field(0, CA_FLOAT32)
74
+ when CA_CMPLX128 then field(0, CA_FLOAT64)
75
+ end
76
+ else
77
+ self[]
78
+ end
79
+ frozen? ? view : (@__real__ = view)
75
80
  end
76
81
 
77
82
  # @overload real=(val)
@@ -85,35 +90,37 @@ class CArray
85
90
  # @overload imag
86
91
  # Returns the imaginary part of `self`. For a complex array the
87
92
  # result is a mutable {CAField} view of the imaginary slot;
88
- # writing to it updates `self` in place. For a real array the
89
- # result is a fresh independent CArray filled with 0.
93
+ # writing to it updates `self` in place. For an object array each
94
+ # cell's `imaginary`; for a real array a fresh independent CArray
95
+ # filled with 0. Masked cells stay masked.
90
96
  # @return [CArray]
91
97
  def imag
92
- if not @__imag__
93
- if complex?
94
- @__imag__ = case data_type
95
- when CA_CMPLX64
96
- field(4, CA_FLOAT32)
97
- when CA_CMPLX128
98
- field(8, CA_FLOAT64)
99
- end
100
- else
101
- @__imag__ = self.template { 0 }
102
- end
98
+ if object?
99
+ return imag_i
100
+ end
101
+ if not complex?
102
+ zero = template { 0 }
103
+ zero[is_masked] = UNDEF if has_mask?
104
+ return zero
103
105
  end
104
- return @__imag__
106
+ return @__imag__ if part_of_self?(@__imag__)
107
+ view = case data_type
108
+ when CA_CMPLX64 then field(4, CA_FLOAT32)
109
+ when CA_CMPLX128 then field(8, CA_FLOAT64)
110
+ end
111
+ frozen? ? view : (@__imag__ = view)
105
112
  end
106
113
 
107
114
  # @overload imag=(val)
108
115
  # Sets the imaginary-part slot to `val` (complex arrays only).
109
116
  # @param val [CArray, Numeric] value to broadcast.
110
117
  # @return [Object] `val`.
111
- # @raise [RuntimeError] when `self` is not a complex array.
118
+ # @raise [CArray::DataTypeError] when `self` is not a complex array.
112
119
  def imag= (val)
113
120
  if complex?
114
121
  imag[] = val
115
122
  else
116
- raise "not a complex array"
123
+ raise CArray::DataTypeError, "not a complex array"
117
124
  end
118
125
  end
119
126
 
@@ -6,7 +6,7 @@
6
6
  # same "pick a value per cell based on a boolean condition" contract but
7
7
  # differ in where the condition lives and how the branches are supplied:
8
8
  #
9
- # cond.then_else(x, y) # boolean receiver, 2 values (eager)
9
+ # cond.then_else(x, y) # boolean receiver, 2 values (lazy if an operand is)
10
10
  # self.replace_where(cond, b) # value receiver, 1 replacement (eager)
11
11
  # self.conditional(cond, f_then, f_else) # value receiver, 2 callables (per-region)
12
12
  #
@@ -25,10 +25,25 @@ class CArray
25
25
  # reading as "if `self` then `x` else `y`". The boolean 2-way
26
26
  # case of {#choose} that additionally propagates `self`'s mask
27
27
  # (`UNDEF` in `self` produces `UNDEF` in the result).
28
+ # The result's data type is the common type of the two branches; a
29
+ # branch given as `UNDEF` masks its cells and leaves the type to the
30
+ # other branch (`cond.then_else(a, UNDEF)` has `a`'s data type).
31
+ # String branches make the result a {CAString}
32
+ # (`cond.then_else("yes", "no")`, also with `UNDEF` for one of them).
33
+ # A Symbol branch, or a String beside a value of another kind, makes
34
+ # the result an object array.
35
+ # When `self` or a branch is a lazy view, the result is a lazy view
36
+ # (read-only) that joins the expression around it; `copy` makes an
37
+ # array of it. An object result is always computed at once.
28
38
  # @param x [CArray, Numeric, Object] true-branch value(s).
29
39
  # @param y [CArray, Numeric, Object] false-branch value(s).
30
- # @return [CArray] new array with the same shape as `self`.
31
- # @raise [ArgumentError] when `self` is not a boolean CArray.
40
+ # @return [CArray] new array with the same shape as `self` (a
41
+ # {CAString} for String branches), or a lazy view (CATriOp) when an
42
+ # operand is lazy.
43
+ # @raise [ArgumentError] when `self` is not a boolean CArray, or when
44
+ # an array branch's shape does not pair with `self`'s as an
45
+ # operator's operands would (equal, or size-1 axes of the same ndim;
46
+ # an object or fixlen result needs `self`'s shape exactly).
32
47
  def then_else (x, y)
33
48
  # Guard: self must be boolean. Integer / float receiver would be
34
49
  # silently reinterpreted by the indexer setter (`result[self] = ...`)
@@ -38,10 +53,22 @@ class CArray
38
53
  raise ArgumentError,
39
54
  "then_else: receiver must be a boolean CArray (data_type == CA_BOOLEAN), got #{self.data_type}"
40
55
  end
41
- # Promote data_type from both branches via CArray.result_type
42
- # (a CScalar contributes its own data_type, so CA_INT32(0) keeps int32
43
- # where a bare Ruby Integer would widen to int64).
44
- dt = CArray.result_type(x, y)
56
+ reshaped = [x, y].any? { |v| v.is_a?(CArray) && !v.scalar? && v.shape != shape }
57
+ face = [x, y].find { |v| v.is_a?(CArray) && !v.scalar? && v.face? }
58
+ if face
59
+ refuse_branch_shape(x, y) if reshaped
60
+ refuse_branch_mix(x, y, face)
61
+ return face_then_else(x, y, face)
62
+ end
63
+ dt = CArray.send(:selection_type, [x, y])
64
+ return lazy_then_else(x, y, dt) if lazy_then_else?(x, y, dt)
65
+ # A branch shaped unlike the condition goes through the lazy select,
66
+ # which pairs the shapes by the operator rule (equal, or size-1 axes
67
+ # with the same ndim) and refuses the rest.
68
+ if reshaped
69
+ refuse_branch_shape(x, y) if scalar? || dt == CA_OBJECT || dt == CA_FIXLEN
70
+ return lazy_then_else(x, y, dt).copy
71
+ end
45
72
  # A CScalar (scalar? CArray) is treated as a scalar value, not as a
46
73
  # self-shaped operand: full CArray -> gather/copy, scalar -> broadcast.
47
74
  y_full = y.is_a?(CArray) && !y.scalar?
@@ -57,6 +84,82 @@ class CArray
57
84
  if self.has_mask?
58
85
  result[self.is_masked] = UNDEF
59
86
  end
87
+ CArray.send(:selection_text?, [x, y]) ? CAString.wrap(result) : result
88
+ end
89
+
90
+ # A lazy receiver or branch makes the selection a lazy node (CATriOp
91
+ # select), so it joins the expression around it rather than computing
92
+ # it here. The branches carry the result type: a scalar branch becomes
93
+ # a CScalar of it, UNDEF a masked one.
94
+ private def lazy_then_else? (x, y, dt)
95
+ return false if scalar? || dt == CA_OBJECT || dt == CA_FIXLEN
96
+ [self, x, y].any? { |v| v.is_a?(CArray) && v.lazy_view? }
97
+ end
98
+
99
+ # The types the lazy select cannot carry keep the strict form: a full
100
+ # branch must have the condition's shape.
101
+ private def refuse_branch_shape (x, y)
102
+ bad = [x, y].find { |v| v.is_a?(CArray) && !v.scalar? && v.shape != shape }
103
+ raise ArgumentError,
104
+ "then_else: branch shape #{bad.shape.inspect} does not match the condition's #{shape.inspect}"
105
+ end
106
+
107
+ # An array branch beside a Face branch must be a Face of the same kind
108
+ # (the same obj_type, so a CARecord subclass and a CARecord of one data
109
+ # class pair, as their store takes): the Face decides what a cell means,
110
+ # so a plain array would be read as its storage (an integer as a time's
111
+ # ticks) and the result would take the class of whichever branch came
112
+ # second. A scalar branch is a value and goes through the Face's store.
113
+ private def refuse_branch_mix (x, y, face)
114
+ arrays = [x, y].select { |v| v.is_a?(CArray) && !v.scalar? }
115
+ return if arrays.all? { |v| v.face? && v.obj_type == face.obj_type }
116
+ hint = face.is_a?(CAString) ?
117
+ " (CArray.string(a) makes a CAString of an object array of Strings)" : ""
118
+ raise ArgumentError,
119
+ "then_else: cannot mix Face and non-Face arrays, or arrays of " \
120
+ "different Face classes; convert them first to one Face class or to no Face" + hint
121
+ end
122
+
123
+ private def lazy_then_else (x, y, dt)
124
+ branch = lambda do |v|
125
+ next v if v.is_a?(CArray) && !v.scalar?
126
+ cell = CScalar.new(dt)
127
+ cell[0] = v.is_a?(CArray) ? v[0] : v
128
+ cell
129
+ end
130
+ CATriOp.__build__(self, branch.(x), branch.(y), CATriOp::OP_SELECT)
131
+ end
132
+
133
+ private def face_then_else (x, y, face)
134
+ # A Face branch decides the result: the other branch is written into a
135
+ # copy of it through the Face's own store, so an instant stays an instant.
136
+ # Promoting the two branches to a common data type would compare a Face's
137
+ # surface type with its storage's. A CAConstString cannot be written, so
138
+ # it selects among its strings and packs them again.
139
+ if face.is_a?(CAConstString)
140
+ sx = x.is_a?(CAConstString) ? x.to_string : x
141
+ sy = y.is_a?(CAConstString) ? y.to_string : y
142
+ return CArray.const_string(then_else(sx, sy), encoding: face.encoding)
143
+ end
144
+ x_full = x.is_a?(CArray) && !x.scalar?
145
+ y_full = y.is_a?(CArray) && !y.scalar?
146
+ base = y_full ? y : x
147
+ if base.read_only?
148
+ # No store to write through (a CACategorical's codes): select among
149
+ # the values themselves.
150
+ result = CArray.object(*shape)
151
+ result[] = y_full ? y.to_type(CA_OBJECT) : y
152
+ result[self] = x_full ? x.to_type(CA_OBJECT)[self] : x
153
+ result[self.is_masked] = UNDEF if self.has_mask?
154
+ return result
155
+ end
156
+ result = base.copy
157
+ if y_full
158
+ result[self] = x_full ? x[self] : x
159
+ else
160
+ result[self.not] = y.is_a?(CArray) ? y[0] : y
161
+ end
162
+ result[self.is_masked] = UNDEF if self.has_mask?
60
163
  result
61
164
  end
62
165
 
@@ -91,7 +194,9 @@ class CArray
91
194
  # Scalar returns from a callable (e.g. `->(v) { 0 }`) broadcast
92
195
  # to the subset shape. The result `data_type` is the promotion
93
196
  # of the two subset results via `CArray.result_type`, or
94
- # `data_type` when given. Masked cells in `cond` propagate to
197
+ # `data_type` when given. A String result is a value (not the name
198
+ # of a data type); when both callables give Strings or {CAString}s
199
+ # the result is a {CAString}. Masked cells in `cond` propagate to
95
200
  # `UNDEF` in the result.
96
201
  #
97
202
  # @param cond [CArray] boolean selector; same shape as `self`.
@@ -118,18 +223,19 @@ class CArray
118
223
  x_else = self[cond.not]
119
224
  y_then = then_fn.call(x_then)
120
225
  y_else = else_fn.call(x_else)
226
+ text = !data_type && CArray.send(:selection_text?, [y_then, y_else])
121
227
 
122
228
  # A callable that returns a scalar (e.g. `->(v) { 0 }`) broadcasts
123
229
  # to the subset shape; wrap it here so the scatter step below sees a
124
230
  # same-length CArray.
125
231
  unless y_then.is_a?(CArray)
126
- y_then = CArray.new(data_type || CArray.result_type(y_then), x_then.shape).fill(y_then)
232
+ y_then = CArray.new(data_type || CArray.send(:selection_type, [y_then]), x_then.shape).fill(y_then)
127
233
  end
128
234
  unless y_else.is_a?(CArray)
129
- y_else = CArray.new(data_type || CArray.result_type(y_else), x_else.shape).fill(y_else)
235
+ y_else = CArray.new(data_type || CArray.send(:selection_type, [y_else]), x_else.shape).fill(y_else)
130
236
  end
131
237
 
132
- dt = data_type || CArray.result_type(y_then, y_else)
238
+ dt = data_type || CArray.send(:selection_type, [y_then, y_else])
133
239
  out = CArray.new(dt, self.shape)
134
240
  out[cond] = y_then
135
241
  out[cond.not] = y_else
@@ -138,7 +244,7 @@ class CArray
138
244
  # positions, so both scatters leave the cell untouched — an explicit
139
245
  # fix-up is required.
140
246
  out[cond.is_masked] = UNDEF if cond.has_mask?
141
- out
247
+ text ? CAString.wrap(out) : out
142
248
  end
143
249
 
144
250
  # @overload select(condlist, choicelist, default: 0, data_type: nil)
@@ -151,7 +257,9 @@ class CArray
151
257
  # Each `choicelist[k]` is either a same-shape CArray or a scalar
152
258
  # broadcast to every cell. The result `data_type` is the promotion
153
259
  # of every choice plus `default` via `CArray.result_type`, or
154
- # `data_type` when given.
260
+ # `data_type` when given. A String choice is a value (not the name of
261
+ # a data type); when every choice and `default` is a String or a
262
+ # {CAString}, the result is a {CAString}.
155
263
  #
156
264
  # @param condlist [Array<CArray>] boolean selectors.
157
265
  # @param choicelist [Array<CArray, Numeric, Object>] values, one
@@ -187,7 +295,7 @@ class CArray
187
295
  end
188
296
  shape = first.shape
189
297
 
190
- dt = data_type || CArray.result_type(*choicelist, default)
298
+ dt = data_type || selection_type(choicelist + [default])
191
299
  # `default` can be either a same-shape CArray (per-cell fallback) or a
192
300
  # scalar (broadcast to every cell).
193
301
  default_full = default.is_a?(CArray) && !default.scalar?
@@ -210,7 +318,35 @@ class CArray
210
318
  v = choicelist[k]
211
319
  out[c] = v.is_a?(CArray) ? v[c] : v
212
320
  end
321
+ if !data_type && selection_text?(choicelist + [default])
322
+ out = CAString.wrap(out)
323
+ end
213
324
  out
214
325
  end
215
326
 
327
+ # The data type a selection among `values` writes into: their common type,
328
+ # by the rule an operator promotes its operands by (a scalar takes the
329
+ # type of an array beside it). UNDEF is a missing value, not a value of
330
+ # some type, so it leaves the type to the others. A String or Symbol is a
331
+ # value, which CArray.result_type would read as the name of a data type
332
+ # ("int32"), so it makes the result object.
333
+ def self.selection_type (values)
334
+ typed = values.reject { |v| v.equal?(UNDEF) }
335
+ if typed.empty? || typed.any? { |v| v.is_a?(String) || v.is_a?(Symbol) }
336
+ CA_OBJECT
337
+ else
338
+ CArray.result_type(*typed)
339
+ end
340
+ end
341
+ private_class_method :selection_type
342
+
343
+ # Whether a selection among `values` gives text: every value that is not
344
+ # UNDEF is a String or a CAString, and there is at least one. The result
345
+ # is then a CAString rather than an object array of Strings.
346
+ def self.selection_text? (values)
347
+ present = values.reject { |v| v.equal?(UNDEF) }
348
+ !present.empty? && present.all? { |v| v.is_a?(String) || v.is_a?(CAString) }
349
+ end
350
+ private_class_method :selection_text?
351
+
216
352
  end
@@ -55,7 +55,7 @@ class CArray
55
55
  # @return [CAConstString]
56
56
  def self.const_string (arg, encoding: Encoding::UTF_8, &block)
57
57
  if arg.is_a?(CArray)
58
- return string_face_of(arg).to_const_string(encoding: encoding)
58
+ return string_face_of(arg, "CArray.const_string").to_const_string(encoding: encoding)
59
59
  end
60
60
  if block
61
61
  n = Integer(arg)
@@ -135,7 +135,7 @@ class CAConstString
135
135
  def sort_addr (*args, **kw)
136
136
  # The native scan has no notion of an incomparable sentinel, so a masked
137
137
  # column goes the same way a per-axis one does.
138
- return __sort_addr_bytes__ if args.empty? && kw.empty? && ! has_mask?
138
+ return sort_addr_by_bytes if args.empty? && kw.empty? && ! has_mask?
139
139
  to_string.sort_addr(*args, **kw)
140
140
  end
141
141
 
@@ -143,6 +143,12 @@ class CAConstString
143
143
  # Returns the per-fiber indices that index a sort by string order.
144
144
  # @return [CArray] `:int64` indices.
145
145
  def sort_index (*args, **kw)
146
+ if args.empty? && kw.empty? && ! has_mask?
147
+ # With no axis the indices are those of the flattened array, which are
148
+ # its addresses.
149
+ addr = sort_addr_by_bytes
150
+ return ndim == 1 ? addr : addr.flatten.copy
151
+ end
146
152
  to_string.sort_index(*args, **kw)
147
153
  end
148
154
 
@@ -163,7 +169,7 @@ class CAConstString
163
169
  # every cell is masked or the array is empty.
164
170
  # @return [String, CAConstString]
165
171
  def min (*args, **kw)
166
- return __min_bytes__ || UNDEF if args.empty? && kw.empty?
172
+ return min_string_by_bytes || UNDEF if args.empty? && kw.empty?
167
173
  const_string_lift(to_string.min(*args, **kw))
168
174
  end
169
175
 
@@ -171,7 +177,7 @@ class CAConstString
171
177
  # Byte-largest counterpart of {#min}.
172
178
  # @return [String, CAConstString]
173
179
  def max (*args, **kw)
174
- return __max_bytes__ || UNDEF if args.empty? && kw.empty?
180
+ return max_string_by_bytes || UNDEF if args.empty? && kw.empty?
175
181
  const_string_lift(to_string.max(*args, **kw))
176
182
  end
177
183
 
@@ -182,6 +188,13 @@ class CAConstString
182
188
  to_string.minmax(*args, **kw).map { |r| const_string_lift(r) }
183
189
  end
184
190
 
191
+ # @overload clip(min, max = nil, fill_value = nil, lfill: nil, ufill: nil)
192
+ # Clips the strings in byte order (see {CArray#clip}).
193
+ # @return [CAConstString]
194
+ def clip (*args, **kw)
195
+ const_string_lift(to_string.clip(*args, **kw))
196
+ end
197
+
185
198
  # @overload min_index(axis: nil)
186
199
  # @return [Integer, CArray] where the byte-smallest string sits.
187
200
  def min_index (*args, **kw)
@@ -312,9 +325,38 @@ class CAConstString
312
325
  # @overload categorize(labels: nil, sort_labels: false)
313
326
  # @return [CACategorical] categories keyed on the strings.
314
327
  def categorize (labels: nil, sort_labels: false)
328
+ # Discovery reads the bytes (a String per distinct value only); a given
329
+ # vocabulary is matched through the strings.
330
+ return super if labels.nil?
315
331
  to_string.categorize(labels: labels, sort_labels: sort_labels)
316
332
  end
317
333
 
334
+ # ---- comparison operators ---------------------------------------------
335
+ #
336
+ # The core operators compare storage, and a storage cell is a byte range:
337
+ # `x < "dd"` compared where each string sits in the buffer and answered
338
+ # true everywhere. They read the strings here instead. `eq` has a native
339
+ # byte scan and stays; `<=>` composes from `>` and `<`, so it follows.
340
+
341
+ # @!method ne(other)
342
+ # @return [CArray] boolean, true where the strings differ.
343
+ # @!method lt(other)
344
+ # @return [CArray] boolean, true where self sorts before `other`.
345
+ # @!method le(other)
346
+ # @return [CArray] boolean, true where self sorts before or equals `other`.
347
+ # @!method gt(other)
348
+ # @return [CArray] boolean, true where self sorts after `other`.
349
+ # @!method ge(other)
350
+ # @return [CArray] boolean, true where self sorts after or equals `other`.
351
+ [:ne, :lt, :le, :gt, :ge].each do |op|
352
+ define_method(op, &Ractor.make_shareable(proc { |other| to_string.public_send(op, string_operand(other)) }))
353
+ end
354
+
355
+ alias < lt
356
+ alias <= le
357
+ alias > gt
358
+ alias >= ge
359
+
318
360
  # A CAConstString operand has to be decoded too; anything else (a CAString,
319
361
  # an object array, an Array) already compares as strings.
320
362
  private def string_operand (other)
@@ -330,3 +372,16 @@ class CAConstString
330
372
  end
331
373
 
332
374
  end
375
+
376
+ class CAConstString
377
+ # The decimal readers of CAFrame#infer_types / #cast, read from the buffer
378
+ # without a String per cell.
379
+ def __infer_number_type_of_text__
380
+ CArray.__const_string_infer_number_type_of_text__(self)
381
+ end
382
+
383
+ def __read_text_as_number__(type, report = nil)
384
+ out = CArray.__const_string_read_text_as_number__(self, type, report)
385
+ out && ndim > 1 ? out.reshape(*shape) : out
386
+ end
387
+ end
@@ -287,7 +287,7 @@ class CArray
287
287
  "(its contents are left undefined); use CArray.new to fill."
288
288
  end
289
289
  if args.size == 2 and args[1].is_a?(Array)
290
- return __empty__(*args, **options)
290
+ return empty_of_data_type(*args, **options)
291
291
  end
292
292
  unless options.empty?
293
293
  raise ArgumentError,
@@ -314,32 +314,36 @@ class CArray
314
314
  CArray.new(self::DataType, shape)
315
315
  end
316
316
 
317
- # @overload from_memory_view(obj)
317
+ # @overload from_memory_view(obj, mask: nil)
318
318
  # Returns a fresh CArray interpreted as this typed subclass's
319
319
  # `data_type`, imported by copy from a MemoryView producer.
320
320
  # Forwards to
321
- # `CArray.from_memory_view(obj, data_type: self::DataType)`; the
322
- # result is always 1-D (chain `.reshape` for higher rank).
321
+ # `CArray.from_memory_view(obj, data_type: self::DataType)`. A
322
+ # typed producer keeps its shape and must agree with the type; a
323
+ # typeless one is read as 1-D (chain `.reshape` for higher rank).
323
324
  # @param obj [Object] MemoryView producer.
325
+ # @param mask [Object, nil] MemoryView producer of the mask.
324
326
  # @return [CArray]
325
- def from_memory_view (obj)
326
- CArray.from_memory_view(obj, data_type: self::DataType)
327
+ def from_memory_view (obj, mask: nil)
328
+ CArray.from_memory_view(obj, data_type: self::DataType, mask: mask)
327
329
  end
328
330
 
329
- # @overload wrap_memory_view(obj)
331
+ # @overload wrap_memory_view(obj, mask: nil)
330
332
  # Returns a zero-copy CAWrap over `obj`'s MemoryView interpreted
331
333
  # as this typed subclass's `data_type`. Forwards to
332
- # `CArray.wrap_memory_view(obj, data_type: self::DataType)`; the
333
- # result is always 1-D (chain `.reshape` for higher rank).
334
+ # `CArray.wrap_memory_view(obj, data_type: self::DataType)`. A
335
+ # typed producer keeps its shape and must agree with the type; a
336
+ # typeless one is read as 1-D (chain `.reshape` for higher rank).
334
337
  #
335
338
  # Here the receiver names the element type, not the class of the
336
339
  # result: the call forwards with `CArray` as the receiver, so a
337
340
  # `CAWrap` comes back. Choosing the class is the other reading of
338
341
  # the receiver, and it lives on `CArray.wrap_memory_view` itself.
339
342
  # @param obj [Object] MemoryView producer.
343
+ # @param mask [Object, nil] MemoryView producer of the mask.
340
344
  # @return [CAWrap]
341
- def wrap_memory_view (obj)
342
- CArray.wrap_memory_view(obj, data_type: self::DataType)
345
+ def wrap_memory_view (obj, mask: nil)
346
+ CArray.wrap_memory_view(obj, data_type: self::DataType, mask: mask)
343
347
  end
344
348
 
345
349
  end
@@ -164,6 +164,7 @@ class CArray
164
164
  # midpoints go the same way as numpy's integer linspace).
165
165
  float_out = CArray.new(CA_FLOAT64, [n]).span(x1.to_f..x2.to_f)
166
166
  return float_out if data_type == CA_FLOAT64
167
+ # coerce: the type the caller asked for
167
168
  float_out.floor.to_type(data_type)
168
169
  end
169
170
 
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CArray
4
+
5
+ # The length a match by distance is limited by -- the +tolerance:+ of
6
+ # locate_nearest_addr (and of align_nearest_addr and CAFrame#join_asof
7
+ # through it), the +before:+, +after:+ and +interval:+ of a key window --
8
+ # read the same way wherever it is given: a length of the key's kind,
9
+ # not below 0.
10
+ #
11
+ # - For a number key it is a real number; NaN, which no distance is
12
+ # within or beyond, and a negative length are refused, as is a String.
13
+ # - For a time or duration key it is a duration: a String such as
14
+ # "10 minutes" (and "0 minutes") or a CATimedelta value, not negative.
15
+ # A bare number is refused, since its length would depend on the key's
16
+ # unit.
17
+ #
18
+ # Every refusal is ArgumentError naming +label+, the keyword as the
19
+ # caller says it ("tolerance:", "windows: before:").
20
+ module DistanceLimit
21
+
22
+ ZERO_DURATION = /\A\s*0+\s+([A-Za-z]+)\s*\z/
23
+
24
+ # +width+ as a Numeric (number key) or a CATimedelta::Element (time or
25
+ # duration key); +nil+ passes through.
26
+ def self.read (width, key, label)
27
+ return nil if width.nil?
28
+ if key.is_a?(CATime) || key.is_a?(CATimedelta)
29
+ duration(width, label)
30
+ else
31
+ number(width, key, label)
32
+ end
33
+ end
34
+
35
+ def self.number (width, key, label)
36
+ if width.is_a?(String) || width.is_a?(CATimedelta::Element)
37
+ raise ArgumentError,
38
+ "#{label} #{width.inspect} is a duration, but the key is " \
39
+ "#{CArray::Inspector.type_label(key)}; give a number"
40
+ end
41
+ unless width.is_a?(Numeric) && width.real? && !(width.is_a?(Float) && width.nan?) && width >= 0
42
+ raise ArgumentError, "#{label} is a number not below 0 (got #{width.inspect})"
43
+ end
44
+ width
45
+ end
46
+
47
+ def self.duration (width, label)
48
+ element =
49
+ case width
50
+ when CATimedelta::Element
51
+ width
52
+ when String
53
+ if (zero = width.match(ZERO_DURATION))
54
+ CATimedelta::Element.new(0, CATime::Resolution.parse(zero[1]).base)
55
+ else
56
+ res = begin
57
+ CATime::Resolution.parse(width)
58
+ rescue ArgumentError
59
+ raise ArgumentError,
60
+ "#{label} #{width.inspect} is not a duration " \
61
+ "(use e.g. \"10 minutes\" / \"1 hour\")"
62
+ end
63
+ CATimedelta::Element.new(res.count, res.base)
64
+ end
65
+ end
66
+ unless element
67
+ raise ArgumentError,
68
+ "#{label} a time key needs a duration such as \"10 minutes\" " \
69
+ "or a CATimedelta value (got #{width.inspect})"
70
+ end
71
+ if element.value < 0
72
+ raise ArgumentError, "#{label} is a duration not below 0 (got #{width.inspect})"
73
+ end
74
+ element
75
+ end
76
+ end
77
+ end
@@ -46,7 +46,7 @@ class CArray
46
46
  # @return [CAFixlenString]
47
47
  def self.fixlen_string (arg, bytes: nil, truncate: :error, &block)
48
48
  if arg.is_a?(CArray)
49
- return string_face_of(arg).to_fixlen_string(bytes: bytes, truncate: truncate)
49
+ return string_face_of(arg, "CArray.fixlen_string").to_fixlen_string(bytes: bytes, truncate: truncate)
50
50
  end
51
51
  unless [:error, :silent].include?(truncate)
52
52
  raise ArgumentError, "truncate: must be :error or :silent (got #{truncate.inspect})"
@@ -90,6 +90,12 @@ end
90
90
  # Mutable fixed-width String Face: L1 ops + in-place variants. In-place
91
91
  # transforms re-store through the native fixlen path (silent truncate).
92
92
  class CAFixlenString
93
+ # inspect's name for the array: the class and the cell width, which is the
94
+ # longest string a cell holds.
95
+ private def inspect_label
96
+ "#{self.class}[#{bytes}]"
97
+ end
98
+
93
99
  include CArray::StringOperationMixin
94
100
  include CArray::StringOperationMixin::Mutable
95
101
  end