carray 3.0.0 → 3.0.1

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 (180) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +325 -3
  4. data/{NEWS.md → CHANGELOG.v1.md} +1 -1
  5. data/README.md +32 -32
  6. data/carray.gemspec +12 -8
  7. data/ext/ca_axis_group.c +55 -19
  8. data/ext/ca_binop_dispatch.c +5 -6
  9. data/ext/ca_binop_dispatch.h +0 -7
  10. data/ext/ca_categorical_iterator.c +13 -13
  11. data/ext/ca_for_each_element.h +6 -8
  12. data/ext/ca_group_iter.c +1 -1
  13. data/ext/ca_kernel_iterator.c +69 -23
  14. data/ext/ca_kernel_iterator.h +3 -10
  15. data/ext/ca_obj_bincmp.c +57 -27
  16. data/ext/ca_obj_binop.c +86 -30
  17. data/ext/ca_obj_const_string.c +3 -1
  18. data/ext/ca_obj_face.c +80 -0
  19. data/ext/ca_obj_face.h +33 -1
  20. data/ext/ca_obj_fake.c +11 -0
  21. data/ext/ca_obj_fixlen_string.c +3 -1
  22. data/ext/ca_obj_grid.c +11 -3
  23. data/ext/ca_obj_meld.c +13 -8
  24. data/ext/ca_obj_moncmp.c +33 -14
  25. data/ext/ca_obj_monop.c +19 -7
  26. data/ext/ca_obj_object.c +99 -4
  27. data/ext/ca_obj_record.c +3 -1
  28. data/ext/ca_obj_refer.c +30 -6
  29. data/ext/ca_obj_roll.c +13 -5
  30. data/ext/ca_obj_select_axis.c +16 -6
  31. data/ext/ca_obj_shift.c +3 -3
  32. data/ext/ca_obj_stride.c +106 -23
  33. data/ext/ca_obj_string.c +3 -1
  34. data/ext/ca_obj_tile.c +13 -5
  35. data/ext/ca_obj_time.c +3 -1
  36. data/ext/ca_obj_timedelta.c +3 -1
  37. data/ext/ca_obj_transpose.c +1 -1
  38. data/ext/ca_obj_triop.c +62 -23
  39. data/ext/ca_obj_window.c +86 -17
  40. data/ext/ca_op_cmplx64.h +123 -0
  41. data/ext/ca_op_ipower.c +0 -3
  42. data/ext/ca_sort_kernels.h +5 -5
  43. data/ext/ca_sweep_engine.c +78 -35
  44. data/ext/ca_sweep_engine.h +13 -4
  45. data/ext/ca_transform_common.c +7 -0
  46. data/ext/carray.h +127 -130
  47. data/ext/carray_access.c +56 -34
  48. data/ext/carray_bincount.c +8 -8
  49. data/ext/carray_broadcast.c +99 -6
  50. data/ext/carray_build_flags.h +3 -0
  51. data/ext/carray_call_cfunc.c +1491 -0
  52. data/ext/carray_call_cfunc.h +153 -0
  53. data/ext/carray_cast.c +69 -31
  54. data/ext/carray_conversion.c +22 -22
  55. data/ext/carray_copy.c +12 -2
  56. data/ext/carray_core.c +57 -10
  57. data/ext/carray_count.c +1 -1
  58. data/ext/carray_element.c +7 -7
  59. data/ext/carray_factorize.c +28 -28
  60. data/ext/carray_hold.c +1 -1
  61. data/ext/carray_index_classifier.c +7 -21
  62. data/ext/carray_internal.h +19 -3
  63. data/ext/carray_kernels_bincmp.c +1 -0
  64. data/ext/carray_kernels_binop.c +320 -298
  65. data/ext/carray_kernels_init.c +1095 -0
  66. data/ext/carray_kernels_map.c +1 -0
  67. data/ext/carray_kernels_moncmp.c +1 -0
  68. data/ext/carray_kernels_monop.c +412 -411
  69. data/ext/carray_kernels_reduce_aggregate.c +88 -87
  70. data/ext/carray_kernels_reduce_boolean.c +1 -0
  71. data/ext/carray_kernels_reduce_cumulative.c +54 -53
  72. data/ext/carray_kernels_reduce_extreme.c +51 -50
  73. data/ext/carray_kernels_reduce_variance.c +1 -0
  74. data/ext/carray_kernels_scan.c +1 -0
  75. data/ext/carray_kernels_search.c +93 -92
  76. data/ext/carray_kernels_sort.c +1 -0
  77. data/ext/carray_kernels_triop.c +1 -0
  78. data/ext/carray_lazy.c +175 -5
  79. data/ext/carray_mask.c +6 -5
  80. data/ext/carray_median_percentile.c +1 -1
  81. data/ext/carray_memory_view.c +46 -21
  82. data/ext/carray_operator.c +46 -49
  83. data/ext/carray_partition.c +4 -4
  84. data/ext/carray_random.c +7 -7
  85. data/ext/carray_scatter.c +1 -1
  86. data/ext/carray_sort.c +3 -3
  87. data/ext/carray_sort_kernel.c +10 -10
  88. data/ext/carray_test.c +1 -1
  89. data/ext/extconf.rb +18 -0
  90. data/ext/mk_call_cfunc.rb +243 -52
  91. data/ext/mkkernel.rb +354 -100
  92. data/ext/ruby_carray.c +18 -16
  93. data/ext/version.h +4 -4
  94. data/lib/carray/autoload_carray.rb +19 -0
  95. data/lib/carray/autoload_method_extension.rb +2 -1
  96. data/lib/carray/axis_group.rb +7 -7
  97. data/lib/carray/basics.rb +59 -59
  98. data/lib/carray/bincount_nd.rb +12 -12
  99. data/lib/carray/block_iterator.rb +24 -14
  100. data/lib/carray/categorical.rb +4 -4
  101. data/lib/carray/categorical_iterator.rb +98 -76
  102. data/lib/carray/conditional.rb +14 -14
  103. data/lib/carray/construct.rb +21 -0
  104. data/lib/carray/core_extensions.rb +62 -5
  105. data/lib/carray/data_type_extension.rb +18 -1
  106. data/lib/carray/frame/concat.rb +3 -3
  107. data/lib/carray/frame/convert.rb +1 -1
  108. data/lib/carray/frame/frame.rb +4 -11
  109. data/lib/carray/frame/io.rb +159 -2
  110. data/lib/carray/frame/sort.rb +1 -1
  111. data/lib/carray/frame/verbs.rb +18 -1
  112. data/lib/carray/fuse_source.rb +123 -0
  113. data/lib/carray/fusion.rb +218 -0
  114. data/lib/carray/histogram.rb +16 -16
  115. data/lib/carray/inspect.rb +1 -7
  116. data/lib/carray/iterator.rb +4 -3
  117. data/lib/carray/lazy.rb +125 -73
  118. data/lib/carray/meld_reduce.rb +2 -2
  119. data/lib/carray/methods/align_addr.rb +1 -1
  120. data/lib/carray/methods/composition.rb +1 -1
  121. data/lib/carray/methods/is_in.rb +12 -12
  122. data/lib/carray/methods/locate_addr.rb +6 -1
  123. data/lib/carray/methods/mask_duplicates.rb +1 -1
  124. data/lib/carray/methods/meshgrid.rb +4 -5
  125. data/lib/carray/methods/mode.rb +2 -2
  126. data/lib/carray/methods/nunique.rb +1 -1
  127. data/lib/carray/methods/snap.rb +7 -2
  128. data/lib/carray/methods/unique.rb +3 -3
  129. data/lib/carray/methods/value_counts.rb +2 -2
  130. data/lib/carray/runtime.rb +0 -19
  131. data/lib/carray/slab_iterator.rb +20 -7
  132. data/lib/carray/string_operation_extension.rb +5 -5
  133. data/lib/carray/time.rb +890 -491
  134. data/lib/carray/window_iterator.rb +285 -13
  135. data/lib/carray.rb +5 -5
  136. data/yard-stubs/ca_obj_array.rb +385 -0
  137. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  138. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  139. data/yard-stubs/ca_obj_block.rb +73 -0
  140. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  141. data/yard-stubs/ca_obj_fake.rb +31 -0
  142. data/yard-stubs/ca_obj_farray.rb +32 -0
  143. data/yard-stubs/ca_obj_field.rb +45 -0
  144. data/yard-stubs/ca_obj_grid.rb +35 -0
  145. data/yard-stubs/ca_obj_refer.rb +72 -0
  146. data/yard-stubs/ca_obj_roll.rb +45 -0
  147. data/yard-stubs/ca_obj_shift.rb +43 -0
  148. data/yard-stubs/ca_obj_stride.rb +181 -0
  149. data/yard-stubs/ca_obj_tile.rb +29 -0
  150. data/yard-stubs/ca_obj_transpose.rb +40 -0
  151. data/yard-stubs/ca_obj_window.rb +49 -0
  152. data/yard-stubs/carray_access.rb +131 -0
  153. data/yard-stubs/carray_attribute.rb +246 -0
  154. data/yard-stubs/carray_broadcast.rb +37 -0
  155. data/yard-stubs/carray_cast.rb +489 -0
  156. data/yard-stubs/carray_class.rb +65 -0
  157. data/yard-stubs/carray_conversion.rb +76 -0
  158. data/yard-stubs/carray_copy.rb +79 -0
  159. data/yard-stubs/carray_core.rb +114 -0
  160. data/yard-stubs/carray_count.rb +79 -0
  161. data/yard-stubs/carray_element.rb +108 -0
  162. data/yard-stubs/carray_generate.rb +66 -0
  163. data/yard-stubs/carray_lazy.rb +23 -0
  164. data/yard-stubs/carray_loop.rb +140 -0
  165. data/yard-stubs/carray_mask.rb +259 -0
  166. data/yard-stubs/carray_math.rb +132 -0
  167. data/yard-stubs/carray_mathfunc.rb +45 -0
  168. data/yard-stubs/carray_median_percentile.rb +89 -0
  169. data/yard-stubs/carray_memory_view.rb +163 -0
  170. data/yard-stubs/carray_order.rb +312 -0
  171. data/yard-stubs/carray_random.rb +89 -0
  172. data/yard-stubs/carray_scatter.rb +106 -0
  173. data/yard-stubs/carray_slab.rb +57 -0
  174. data/yard-stubs/carray_sort.rb +163 -0
  175. data/yard-stubs/carray_test.rb +85 -0
  176. data/yard-stubs/carray_undef.rb +64 -0
  177. data/yard-stubs/carray_utils.rb +97 -0
  178. data/yard-stubs/ruby_carray.rb +193 -0
  179. metadata +59 -10
  180. data/ext/ca_obj_unbound_repeat.c +0 -496
@@ -35,6 +35,7 @@ class CArray
35
35
  # CArray whose elements are `boolean`. Naming the element type as a
36
36
  # class lets a constructor be spelled `CArray::Boolean.new(...)`; see
37
37
  # {DataTypeNewConstructor} for what that form adds.
38
+ # @private
38
39
  class Boolean
39
40
  extend DataTypeNewConstructor
40
41
  extend DataTypeExtension
@@ -47,6 +48,7 @@ class CArray
47
48
  # CArray whose elements are `uint8`. Naming the element type as a
48
49
  # class lets a constructor be spelled `CArray::UInt8.new(...)`; see
49
50
  # {DataTypeNewConstructor} for what that form adds.
51
+ # @private
50
52
  class UInt8
51
53
  extend DataTypeNewConstructor
52
54
  extend DataTypeExtension
@@ -59,6 +61,7 @@ class CArray
59
61
  # CArray whose elements are `uint16`. Naming the element type as a
60
62
  # class lets a constructor be spelled `CArray::UInt16.new(...)`; see
61
63
  # {DataTypeNewConstructor} for what that form adds.
64
+ # @private
62
65
  class UInt16
63
66
  extend DataTypeNewConstructor
64
67
  extend DataTypeExtension
@@ -71,6 +74,7 @@ class CArray
71
74
  # CArray whose elements are `uint32`. Naming the element type as a
72
75
  # class lets a constructor be spelled `CArray::UInt32.new(...)`; see
73
76
  # {DataTypeNewConstructor} for what that form adds.
77
+ # @private
74
78
  class UInt32
75
79
  extend DataTypeNewConstructor
76
80
  extend DataTypeExtension
@@ -83,6 +87,7 @@ class CArray
83
87
  # CArray whose elements are `uint64`. Naming the element type as a
84
88
  # class lets a constructor be spelled `CArray::UInt64.new(...)`; see
85
89
  # {DataTypeNewConstructor} for what that form adds.
90
+ # @private
86
91
  class UInt64
87
92
  extend DataTypeNewConstructor
88
93
  extend DataTypeExtension
@@ -95,6 +100,7 @@ class CArray
95
100
  # CArray whose elements are `int8`. Naming the element type as a
96
101
  # class lets a constructor be spelled `CArray::Int8.new(...)`; see
97
102
  # {DataTypeNewConstructor} for what that form adds.
103
+ # @private
98
104
  class Int8
99
105
  extend DataTypeNewConstructor
100
106
  extend DataTypeExtension
@@ -107,6 +113,7 @@ class CArray
107
113
  # CArray whose elements are `int16`. Naming the element type as a
108
114
  # class lets a constructor be spelled `CArray::Int16.new(...)`; see
109
115
  # {DataTypeNewConstructor} for what that form adds.
116
+ # @private
110
117
  class Int16
111
118
  extend DataTypeNewConstructor
112
119
  extend DataTypeExtension
@@ -119,6 +126,7 @@ class CArray
119
126
  # CArray whose elements are `int32`. Naming the element type as a
120
127
  # class lets a constructor be spelled `CArray::Int32.new(...)`; see
121
128
  # {DataTypeNewConstructor} for what that form adds.
129
+ # @private
122
130
  class Int32
123
131
  extend DataTypeNewConstructor
124
132
  extend DataTypeExtension
@@ -131,6 +139,7 @@ class CArray
131
139
  # CArray whose elements are `int64`. Naming the element type as a
132
140
  # class lets a constructor be spelled `CArray::Int64.new(...)`; see
133
141
  # {DataTypeNewConstructor} for what that form adds.
142
+ # @private
134
143
  class Int64
135
144
  extend DataTypeNewConstructor
136
145
  extend DataTypeExtension
@@ -143,6 +152,7 @@ class CArray
143
152
  # CArray whose elements are `float32`. Naming the element type as a
144
153
  # class lets a constructor be spelled `CArray::Float32.new(...)`; see
145
154
  # {DataTypeNewConstructor} for what that form adds.
155
+ # @private
146
156
  class Float32
147
157
  extend DataTypeNewConstructor
148
158
  extend DataTypeExtension
@@ -153,11 +163,13 @@ class CArray
153
163
  end
154
164
 
155
165
  # Numo-compatible alias of {Float32}.
166
+ # @private
156
167
  SFloat = Float32
157
168
 
158
169
  # CArray whose elements are `float64`. Naming the element type as a
159
170
  # class lets a constructor be spelled `CArray::Float64.new(...)`; see
160
171
  # {DataTypeNewConstructor} for what that form adds.
172
+ # @private
161
173
  class Float64
162
174
  extend DataTypeNewConstructor
163
175
  extend DataTypeExtension
@@ -168,11 +180,13 @@ class CArray
168
180
  end
169
181
 
170
182
  # Numo-compatible alias of {Float64}.
183
+ # @private
171
184
  DFloat = Float64
172
185
 
173
186
  # CArray whose elements are `complex64`. Naming the element type as a
174
187
  # class lets a constructor be spelled `CArray::Complex64.new(...)`; see
175
188
  # {DataTypeNewConstructor} for what that form adds.
189
+ # @private
176
190
  class Complex64
177
191
  extend DataTypeNewConstructor
178
192
  extend DataTypeExtension
@@ -183,11 +197,13 @@ class CArray
183
197
  end
184
198
 
185
199
  # Numo-compatible alias of {Complex64}.
200
+ # @private
186
201
  SComplex = Complex64
187
202
 
188
203
  # CArray whose elements are `complex128`. Naming the element type as a
189
204
  # class lets a constructor be spelled `CArray::Complex128.new(...)`; see
190
205
  # {DataTypeNewConstructor} for what that form adds.
206
+ # @private
191
207
  class Complex128
192
208
  extend DataTypeNewConstructor
193
209
  extend DataTypeExtension
@@ -198,11 +214,13 @@ class CArray
198
214
  end
199
215
 
200
216
  # Numo-compatible alias of {Complex128}.
217
+ # @private
201
218
  DComplex = Complex128
202
219
 
203
220
  # CArray whose elements are `object`. Naming the element type as a
204
221
  # class lets a constructor be spelled `CArray::Object.new(...)`; see
205
222
  # {DataTypeNewConstructor} for what that form adds.
223
+ # @private
206
224
  class Object
207
225
  extend DataTypeNewConstructor
208
226
  extend DataTypeExtension
@@ -213,11 +231,13 @@ class CArray
213
231
  end
214
232
 
215
233
  # Numo-compatible alias of {Object}.
234
+ # @private
216
235
  RObject = Object
217
236
 
218
237
  # CArray whose elements are `fixlen`. Naming the element type as a
219
238
  # class lets a constructor be spelled `CArray::Fixlen.new(...)`; see
220
239
  # {DataTypeNewConstructor} for what that form adds.
240
+ # @private
221
241
  class Fixlen
222
242
  extend DataTypeNewConstructor
223
243
  extend DataTypeExtension
@@ -232,6 +252,7 @@ end
232
252
  class CArray
233
253
  extend DataTypeExtension
234
254
  # @!visibility private
255
+ # @private
235
256
  TypeSymbol = nil
236
257
  # @!visibility private
237
258
  DataType = nil
@@ -10,6 +10,8 @@
10
10
  # - Postfix math on Float/Integer/Rational (sqrt, exp, log, sin, ...)
11
11
  # enabling scalar/CArray polymorphism: the same expression
12
12
  # `(0.0415*(t-218.8)).tanh` works whether t is a Float or a CArray.
13
+ # - The same postfix math on Complex, so an expression written for a
14
+ # complex CArray still reads for a single cell taken out of it.
13
15
  # - Angle normalisation on Numeric (deg_360 / deg_180 / rad_2pi / rad_pi).
14
16
  # - Comparison helpers on Numeric (#eq / #ne) for symmetric usage:
15
17
  # `5.eq(ca)` dispatches to `ca.eq(5)`.
@@ -36,9 +38,9 @@ class CArray
36
38
  # `using CArray::CoreExtensions`.
37
39
  #
38
40
  # It provides postfix math on scalars (so `(0.0415*(t-218.8)).tanh` reads
39
- # the same whether `t` is a Float or a CArray), angle normalisation, and
40
- # comparison helpers. Being a refinement, it is lexically scoped: nothing
41
- # changes for code that does not opt in.
41
+ # the same whether `t` is a Float, a Complex or a CArray), angle
42
+ # normalisation, and comparison helpers. Being a refinement, it is
43
+ # lexically scoped: nothing changes for code that does not opt in.
42
44
  module CoreExtensions
43
45
 
44
46
  # @!visibility private
@@ -51,7 +53,11 @@ class CArray
51
53
  [Float, Integer, Rational].each do |klass|
52
54
  refine(klass) do
53
55
  MATH_METHODS.each do |m|
54
- define_method(m) { Math.send(m, self) }
56
+ module_eval <<~RUBY, __FILE__, __LINE__ + 1
57
+ def #{m}
58
+ Math.#{m}(self)
59
+ end
60
+ RUBY
55
61
  end
56
62
  def rad
57
63
  self.to_f * Math::PI / 180.0
@@ -65,7 +71,7 @@ class CArray
65
71
  # M.1 (PyTorch alignment) scalar polymorphism additions.
66
72
  # `trunc` is left to Ruby's built-in semantics (Float#truncate
67
73
  # returns Integer when arg=0) — its CArray counterpart preserves
68
- # dtype, so scalar-side use should call .to_f.trunc explicitly.
74
+ # data type, so scalar-side use should call .to_f.trunc explicitly.
69
75
  # Math.expm1 / Math.log1p don't exist in stdlib; hand-roll.
70
76
  def expm1
71
77
  Math.exp(self) - 1.0
@@ -85,6 +91,57 @@ class CArray
85
91
  end
86
92
  end
87
93
 
94
+ # The subset of MATH_METHODS that CArray's complex kernels implement,
95
+ # plus the two elementwise helpers that also accept complex input.
96
+ #
97
+ # Absent, because a complex CArray raises CArray::DataTypeError for
98
+ # them: log10 (C99 has no clog10) and expm1 / log1p (no complex form
99
+ # in C99 either). Leaving them undefined on Complex keeps the scalar
100
+ # side failing wherever the array side fails. rad / deg / distance /
101
+ # signbit are absent for the same reason from the other direction:
102
+ # they go through #to_f, which Complex does not have.
103
+ #
104
+ # @!visibility private
105
+ COMPLEX_MATH_METHODS = %i[
106
+ sqrt exp log
107
+ sin cos tan sinh cosh tanh
108
+ asin acos atan asinh acosh atanh
109
+ square rsqrt
110
+ ].freeze
111
+
112
+ # Complex is not handled the way Float / Integer / Rational are.
113
+ # Math.tanh(Complex(1,2)) raises RangeError -- of the stdlib Math
114
+ # methods only Math.sqrt accepts a Complex -- so the complex forms
115
+ # need their own implementation.
116
+ #
117
+ # Each one runs the value through a one-element cmplx128 CScalar, so
118
+ # the answer comes from the very kernel the array form would have
119
+ # used. That is the point: the branch cuts of csqrt / clog / casin /
120
+ # cacosh / catanh, and the sign of a zero on either side of them, are
121
+ # whatever the platform's C99 complex.h says they are, and they are
122
+ # not the same everywhere. On this machine, for instance,
123
+ # catanh(-1+0i) yields -Infinity+(pi/4)i where C99 Annex G describes
124
+ # -Infinity+0i. A hand-written Complex implementation would have to
125
+ # reproduce each such quirk to agree with the array form, and would
126
+ # stop agreeing on the next platform; delegating agrees by
127
+ # construction on all of them. The cost is one CScalar per call
128
+ # (~0.4 microseconds), which is the right trade for a scalar
129
+ # convenience whose whole purpose is to read the same as the array.
130
+ #
131
+ # `square` is delegated rather than written as `self * self`: Ruby's
132
+ # Complex multiplication and C's `double _Complex` multiplication part
133
+ # ways on infinities (Complex(1, Inf) squared gives -Inf+Inf*i in Ruby
134
+ # and -Inf+NaN*i in C).
135
+ refine Complex do
136
+ COMPLEX_MATH_METHODS.each do |m|
137
+ module_eval <<~RUBY, __FILE__, __LINE__ + 1
138
+ def #{m}
139
+ CA_CMPLX128(self).#{m}[0]
140
+ end
141
+ RUBY
142
+ end
143
+ end
144
+
88
145
  refine Numeric do
89
146
  def deg_360
90
147
  a = self.to_f
@@ -194,10 +194,27 @@ class CArray
194
194
  start = 0
195
195
  stop, = *args
196
196
  step = 1
197
+ else
198
+ raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1..3)"
197
199
  end
200
+ raise ArgumentError, "step must not be 0" if step == 0
198
201
  data_type = self::DataType
199
202
  data_type ||= guess_data_type_from_values(start, stop, step)
200
- CArray.send(:__cast__, data_type, start..stop-step, step)
203
+ # Element count comes from the arguments as given, not from the
204
+ # target data type: CArray::Int32.arange(0, 1, 0.25) counts four
205
+ # elements from the float step and truncates them on store.
206
+ # Integer arguments count exactly (divmod) so that a step dividing
207
+ # the span evenly does not gain a spurious element through float
208
+ # rounding.
209
+ span = stop - start
210
+ if span.is_a?(Integer) && step.is_a?(Integer)
211
+ q, r = span.divmod(step)
212
+ n = r.zero? ? q : q + 1
213
+ else
214
+ n = (span.to_f / step).ceil
215
+ end
216
+ n = 0 if n < 0
217
+ CArray.new(data_type, [n]).seq(start, step)
201
218
  end
202
219
 
203
220
  # @overload full(shape, fill_value)
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Two entry points, mirroring the CArray-level taxonomy:
4
4
  #
5
- # +CAFrame.meld+ view frame, strict same-dtype per column,
5
+ # +CAFrame.meld+ view frame, strict same data type per column,
6
6
  # each column is a CAMeld view over the inputs
7
7
  # +CAFrame.concatenate+ eager frame, auto-casts per column,
8
8
  # each column is a materialised entity
@@ -48,10 +48,10 @@ class CAFrame
48
48
 
49
49
  # Concatenate frames along the row axis, eagerly. Each output column is
50
50
  # +CArray.concatenate+ of that column across the input frames, so per-column
51
- # dtypes auto-promote to a common type. The result is a fresh, independent
51
+ # data types auto-promote to a common type. The result is a fresh, independent
52
52
  # frame — writes to it do not propagate back to the input frames.
53
53
  #
54
- # For a view frame that shares storage with the inputs (strict same dtype
54
+ # For a view frame that shares storage with the inputs (strict same data type
55
55
  # per column, chain composability preserved) use {CAFrame.meld}.
56
56
  #
57
57
  # Column matching, index handling, and column-set / index-mix rules
@@ -31,7 +31,7 @@ class CAFrame
31
31
  #
32
32
  # Only same-shape scalar (1-D) columns qualify. An N-D column has no single
33
33
  # matrix form and raises — escape it per column with +df["name"]+. A mixed
34
- # dtype set is promoted to a common type (+result_type+, §12-F) through
34
+ # data type set is promoted to a common type (+result_type+, §12-F) through
35
35
  # lazy cast lanes, so the promotion costs no buffer either.
36
36
  #
37
37
  # +writable: true+ demands a result whose writes reach this frame's own
@@ -436,13 +436,6 @@ class CAFrame
436
436
  h
437
437
  end
438
438
 
439
- # @return [String]
440
- def inspect
441
- parts = @columns.map { |k, v| "#{k}:#{v.data_type}#{v.ndim > 1 ? v.shape[1..].inspect : ''}" }
442
- idx = @index ? " index=#{@axis_name.inspect}" : ""
443
- "#<CAFrame nrow=#{@nrow} vars=[#{parts.join(', ')}]#{idx}>"
444
- end
445
-
446
439
  private def rebuild(cols)
447
440
  CAFrame.new(cols, axis_name: @axis_name, index: @index)
448
441
  end
@@ -511,7 +504,7 @@ class CAFrame
511
504
  end
512
505
 
513
506
  # df[sel] = other : replace the selected contiguous span with other's rows
514
- # (any length). Columns are concatenated head + other + tail, so dtypes
507
+ # (any length). Columns are concatenated head + other + tail, so data types
515
508
  # promote and the row count shifts by other.nrow - span.
516
509
  private def splice_rows(selector, other)
517
510
  lo, hi = contiguous_span(selector)
@@ -536,9 +529,9 @@ class CAFrame
536
529
  case pieces.size
537
530
  when 0 then col[CArray.int32(0), *tail] # replaced every row with none
538
531
  when 1 then pieces.first
539
- else CArray.meld(pieces, axis: 0) # CAMeld view; dtype mismatch
532
+ else CArray.meld(pieces, axis: 0) # CAMeld view; data type mismatch
540
533
  end # across pieces raises.
541
- # For dtype conversion, cast
534
+ # For data type conversion, cast
542
535
  # the incoming +other+'s
543
536
  # column beforehand — silent
544
537
  # promotion in a splice would
@@ -566,7 +559,7 @@ class CAFrame
566
559
  case pieces.size
567
560
  when 0 then @index[CArray.int32(0)]
568
561
  when 1 then pieces.first
569
- else CArray.meld(pieces, axis: 0) # index dtype must match across frames
562
+ else CArray.meld(pieces, axis: 0) # index data type must match across frames
570
563
  end
571
564
  end
572
565
 
@@ -62,8 +62,8 @@ class CAFrame
62
62
  # is a view into it (§3.6).
63
63
  # Write the frame as CSV. CSV is a flat table of scalar cells, so this is the
64
64
  # text form of the same all-scalar subset +to_ca+ requires (§11.9): every
65
- # column must be 1-D. Unlike +to_ca+ it does not promote to a common dtype --
66
- # each column is formatted to text independently, so mixed dtypes (numbers,
65
+ # column must be 1-D. Unlike +to_ca+ it does not promote to a common data type --
66
+ # each column is formatted to text independently, so mixed data types (numbers,
67
67
  # strings, datetime / categorical Faces) sit side by side. An N-D column has
68
68
  # no flat CSV cell and raises; export it per column, or use +to_records+ +
69
69
  # JSON for the structured shape (memo §11.9, the N-D escape).
@@ -115,6 +115,163 @@ class CAFrame
115
115
  end
116
116
  end
117
117
 
118
+ # Render the frame as an aligned text table for reading:
119
+ #
120
+ # puts df.to_table
121
+ #
122
+ # time temp station
123
+ # ---------- ---- -------
124
+ # 2026-01-01 1.5 Tokyo
125
+ # 2026-01-02 _ Osaka
126
+ #
127
+ # This is the display counterpart of +to_csv+ and shares none of its
128
+ # constraints: it is text meant to be looked at, not read back. Numeric
129
+ # columns are right-aligned, everything else left-aligned; a masked cell
130
+ # shows as +_+, the same marker CArray's own inspect uses. An N-D column
131
+ # (which +to_csv+ rejects, having no flat cell) shows each row's slice as
132
+ # an Array literal.
133
+ #
134
+ # Float cells are rounded to +precision+ decimal places for display only
135
+ # (default 6); +precision: nil+ prints them at full precision, which is
136
+ # faithful but lets one long value set the column width.
137
+ #
138
+ # Long frames are truncated in the middle: +rows+ caps how many rows are
139
+ # printed (default 20, split evenly around an ellipsis row), and
140
+ # +rows: nil+ prints every row. +index: false+ drops the index column.
141
+ def to_table(rows: 20, index: true, precision: 6)
142
+ head = rows && (rows + 1) / 2
143
+ render_table(head: head, tail: rows && rows - head,
144
+ index: index, precision: precision, footer: true)
145
+ end
146
+
147
+ # +to_s+ is the whole frame, +inspect+ the middle-elided one -- so +puts df+
148
+ # dumps everything and +p df+ stays a screenful. +inspect+ leads with the
149
+ # same summary line it always had (nrow, variable data types, index), so the
150
+ # table under it needs no row-count footer.
151
+ def to_s
152
+ to_table(rows: nil)
153
+ end
154
+
155
+ # The summary line (nrow, variable data types, index) followed by the
156
+ # middle-elided table.
157
+ # @return [String]
158
+ def inspect
159
+ parts = @columns.map { |k, v| "#{k}:#{v.data_type}#{v.ndim > 1 ? v.shape[1..].inspect : ''}" }
160
+ idx = @index ? " index=#{@axis_name.inspect}" : ""
161
+ head = "#<CAFrame nrow=#{@nrow} vars=[#{parts.join(', ')}]#{idx}>"
162
+ return head if @columns.empty?
163
+ head + "\n" + render_table(head: 8, tail: 2, index: true, precision: 6,
164
+ footer: false)
165
+ end
166
+
167
+ private def render_table(head:, tail:, index:, precision:, footer:)
168
+ names = []
169
+ columns = []
170
+ aligns = []
171
+
172
+ if index && @index
173
+ names << @axis_name
174
+ columns << @index
175
+ aligns << (@index.numeric? ? :right : :left)
176
+ end
177
+ @columns.each do |name, col|
178
+ names << name
179
+ columns << col
180
+ aligns << (col.ndim == 1 && col.numeric? ? :right : :left)
181
+ end
182
+ return "" if names.empty?
183
+
184
+ positions = table_row_positions(head, tail)
185
+ body = positions.map do |i|
186
+ if i.nil?
187
+ Array.new(columns.size, ":") # vertical ellipsis for the elided middle
188
+ else
189
+ columns.map { |col| format_table_cell(col, i, precision) }
190
+ end
191
+ end
192
+
193
+ widths = names.each_with_index.map do |name, j|
194
+ [display_width(name), *body.map { |cells| display_width(cells[j]) }].max
195
+ end
196
+
197
+ out = +""
198
+ out << table_row(names, widths, aligns) << "\n"
199
+ out << table_row(widths.map { |w| "-" * w }, widths, aligns) << "\n"
200
+ body.each { |cells| out << table_row(cells, widths, aligns) << "\n" }
201
+ if footer && positions.size - positions.count(nil) < @nrow
202
+ out << "(#{plural(@nrow, 'row')}, #{plural(@columns.size, 'variable')})\n"
203
+ end
204
+ out
205
+ end
206
+
207
+ # Row indices to print, with nil marking the elided middle. A nil head means
208
+ # no cap; a frame that already fits in head + tail is listed whole.
209
+ private def table_row_positions(head, tail)
210
+ return (0...@nrow).to_a if head.nil? || @nrow <= head + tail
211
+ (0...head).to_a + [nil] + ((@nrow - tail)...@nrow).to_a
212
+ end
213
+
214
+ private def table_row(cells, widths, aligns)
215
+ line = cells.each_with_index.map do |text, j|
216
+ pad = " " * (widths[j] - display_width(text))
217
+ aligns[j] == :right ? pad + text : text + pad
218
+ end.join(" ")
219
+ line.rstrip
220
+ end
221
+
222
+ # Column widths are counted in terminal cells, not characters: a CJK
223
+ # ideograph, kana, or full-width form occupies two cells, so counting
224
+ # characters would leave every column holding such a name ragged. The
225
+ # ranges below are the East Asian Wide / Fullwidth blocks; a combining
226
+ # mark takes no cell of its own.
227
+ WIDE_CHAR_RANGES = [
228
+ 0x1100..0x115F, 0x2E80..0x303E, 0x3041..0x33FF, 0x3400..0x4DBF,
229
+ 0x4E00..0x9FFF, 0xA000..0xA4CF, 0xA960..0xA97F, 0xAC00..0xD7A3,
230
+ 0xF900..0xFAFF, 0xFE10..0xFE19, 0xFE30..0xFE6F, 0xFF00..0xFF60,
231
+ 0xFFE0..0xFFE6, 0x1F300..0x1F64F, 0x1F900..0x1F9FF, 0x20000..0x3FFFD,
232
+ ].freeze
233
+ private_constant :WIDE_CHAR_RANGES
234
+
235
+ COMBINING_RANGES = [0x0300..0x036F, 0x1AB0..0x1AFF, 0x20D0..0x20F0].freeze
236
+ private_constant :COMBINING_RANGES
237
+
238
+ private def display_width(text)
239
+ text.each_char.sum do |ch|
240
+ cp = ch.ord
241
+ if COMBINING_RANGES.any? { |r| r.cover?(cp) }
242
+ 0
243
+ elsif WIDE_CHAR_RANGES.any? { |r| r.cover?(cp) }
244
+ 2
245
+ else
246
+ 1
247
+ end
248
+ end
249
+ end
250
+
251
+ private def plural(n, noun)
252
+ "#{n} #{noun}#{n == 1 ? '' : 's'}"
253
+ end
254
+
255
+ private def format_table_cell(col, i, precision)
256
+ e = elem_at(col, i)
257
+ if UNDEF.equal?(e) || e.nil?
258
+ "_"
259
+ elsif e.is_a?(Float) && precision
260
+ # Display rounding only: a full-precision float (141.67833333333334)
261
+ # sets the column width for every other row and makes the table hard
262
+ # to read. precision: nil prints the value as Ruby renders it.
263
+ e.round(precision).to_s
264
+ elsif e.is_a?(CArray)
265
+ e.to_a.inspect
266
+ elsif e.is_a?(String)
267
+ e
268
+ elsif e.respond_to?(:iso8601)
269
+ e.iso8601
270
+ else
271
+ e.to_s
272
+ end
273
+ end
274
+
118
275
  private def format_csv_column(col)
119
276
  col.to_a.map do |e|
120
277
  if UNDEF.equal?(e) || e.nil?
@@ -13,7 +13,7 @@
13
13
  #
14
14
  # sort_addr is ascending, so a descending key is expressed by replacing it with
15
15
  # its dense descending rank (CArray#order(descending: true, method: :dense)):
16
- # this works for every dtype (unlike negation, which cannot reverse a string and
16
+ # this works for every data type (unlike negation, which cannot reverse a string and
17
17
  # silently wraps an unsigned integer), and the *dense* rank keeps equal values
18
18
  # on one rank so ties fall through to later keys in a multi-key sort.
19
19
  # masked_position (CArray.sort_addr's kwarg) places masked key rows first or
@@ -143,11 +143,28 @@ class CAFrame
143
143
  # carry, so it raises rather than silently truncate. Make it the index with
144
144
  # +set_index+ afterward.
145
145
  #
146
+ # A +CATime::Grid+ carries the same (unit, epoch) pair as one value, so a
147
+ # netCDF +units+ attribute goes straight in. It also carries a phase the
148
+ # keyword form cannot: the keyword +epoch+ is read on the +unit+ grid, so
149
+ # an epoch off that grid ("days since 1980-01-01 12:00") loses its
150
+ # time-of-day, while a grid resolves the finer storage that holds it.
151
+ #
146
152
  # df.to_time("time", unit: :h, epoch: "1990-01-01").set_index("time")
147
- def to_time(name, unit: :s, epoch: nil)
153
+ # df.to_time("time", CATime::Grid.parse("hours since 1990-01-01"))
154
+ # df.to_time("time", CATime::Grid.parse("days since 1980-01-01 12:00"))
155
+ def to_time(name, grid = nil, unit: :s, epoch: nil)
148
156
  key = name.to_s
149
157
  col = @columns.fetch(key) { raise KeyError, "no column #{key.inspect}" }
150
158
  raw = integer_serial_column(col, key)
159
+ grid = unit if unit.is_a?(CATime::Grid)
160
+ if grid.is_a?(CATime::Grid)
161
+ @columns[key] = grid.at(raw)
162
+ return self
163
+ end
164
+ unless grid.nil?
165
+ raise ArgumentError,
166
+ "the positional argument must be a CATime::Grid (got #{grid.class})"
167
+ end
151
168
  if epoch
152
169
  raw = raw + CArray.time(epoch, unit: unit).ticks[0]
153
170
  end