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
@@ -18,6 +18,11 @@ class CArray
18
18
  # that side. NaN / masked input cells are always masked in the
19
19
  # output, independently of `lfill` / `ufill`.
20
20
  #
21
+ # When `vmin == vmax` the range is one point: a value equal to it is
22
+ # in bin 0 whatever `bins:` or `step` says, and a value below or above
23
+ # it is out of range. An array whose values are all the same is
24
+ # binned this way by `bin(a.min, a.max, ...)`.
25
+ #
21
26
  # Use `snap(step, offset:)` when the desired output is the snapped
22
27
  # value; use `bin_to(edges)` when the edges are non-uniform.
23
28
  #
@@ -46,23 +51,21 @@ class CArray
46
51
  end
47
52
  raise ArgumentError, "bin: vmin > vmax" if vmin > vmax
48
53
 
49
- n = bins || ((vmax - vmin).to_f / step).round
50
- raise ArgumentError, "bin: n must be >= 1" if n < 1
51
-
54
+ unless bins.nil? || bins.is_a?(Integer)
55
+ raise TypeError, "bin: bins: must be an Integer (got #{bins.inspect})"
56
+ end
52
57
  if vmin == vmax
53
- # Degenerate: zero interval → all cells fall on the single edge;
54
- # with include_max: true they land in bin 0.
55
- out = CArray.int64(*shape) { 0 }
56
- out.mask = self.mask.to_ca if self.has_mask?
57
- if self.float?
58
- inv = self.is_invalid
59
- if inv.count(true) > 0
60
- out.mask = out.has_mask? ? (out.mask | inv) : inv
61
- end
62
- end
63
- return out
58
+ # A range of one point: a value equal to it is in bin 0 whatever the
59
+ # count of bins (or the step), a value below or above it is out of
60
+ # range.
61
+ raise ArgumentError, "bin: n must be >= 1" if bins && bins < 1
62
+ return bin_to(CArray.float64(2) { vmin.to_f },
63
+ lfill: lfill, ufill: ufill, include_max: true)
64
64
  end
65
65
 
66
+ n = bins || ((vmax - vmin).to_f / step).round
67
+ raise ArgumentError, "bin: n must be >= 1" if n < 1
68
+
66
69
  # Delegate to `bin_to` with generated uniform edges — same kernel
67
70
  # (`histbin_ki`) as `histogram`, so semantics are identical.
68
71
  edges = CArray.float64(n + 1).span(vmin..vmax)
@@ -99,8 +102,9 @@ class CArray
99
102
  # @return [CArray] `CA_INT64` array with the same shape as `self`
100
103
  # holding bin indices in `[0, N-1]` (or the fill values / mask
101
104
  # for out-of-range and masked cells).
102
- # @raise [ArgumentError] when `edges` is not 1-D or has fewer
103
- # than 2 values.
105
+ # @raise [ArgumentError] when `edges` is not 1-D, has fewer than 2
106
+ # values, has a masked value or NaN, or decreases anywhere. Equal
107
+ # edges (a bin of zero width) and infinite ones are accepted.
104
108
  # @example
105
109
  # e = CA_FLOAT64([0, 1, 10, 100])
106
110
  # v = CA_FLOAT64([0.5, 5.0, 50.0, -1.0, 200.0])
@@ -120,8 +124,12 @@ class CArray
120
124
  # under -> -1, in-range -> 0..N-1, over -> N.
121
125
  out = src.send(:histbin_ki, e, include_max) - 1
122
126
 
123
- out[:eq, -1] = lfill.nil? ? UNDEF : lfill # under
124
- out[:eq, n] = ufill.nil? ? UNDEF : ufill # over
127
+ # Both sides are found before either is written, so a fill that equals
128
+ # the other side's index (lfill: n) is not taken for it.
129
+ under = out.eq(-1)
130
+ over = out.eq(n)
131
+ out[under] = lfill.nil? ? UNDEF : lfill
132
+ out[over] = ufill.nil? ? UNDEF : ufill
125
133
  out
126
134
  end
127
135
 
@@ -35,10 +35,26 @@ class CArray
35
35
  raise CArray::DataTypeError,
36
36
  "bincount requires an integer label array (got #{data_type_name})"
37
37
  end
38
+ unless length.is_a?(Integer)
39
+ raise TypeError, "bincount: length: must be an Integer (got #{length.inspect})"
40
+ end
41
+ if length < 0
42
+ raise ArgumentError, "bincount: length: must not be negative (got #{length})"
43
+ end
44
+ unless weights.nil?
45
+ unless weights.is_a?(CArray)
46
+ raise ArgumentError, "bincount: weights must be a CArray"
47
+ end
48
+ unless weights.integer? || weights.float?
49
+ raise CArray::DataTypeError,
50
+ "bincount: weights must be numeric (got " \
51
+ "#{CArray::Inspector.type_label(weights)})"
52
+ end
53
+ end
38
54
 
39
55
  if elements.zero?
40
56
  if weights
41
- out = CArray.new(weights.data_type, [length])
57
+ out = CArray.new(weights.data_type, [length], bytes: weights.bytes)
42
58
  else
43
59
  out_type = (length > 0xFFFFFFFF) ? CA_UINT64 : CA_UINT32
44
60
  out = CArray.new(out_type, [length])
@@ -54,7 +70,7 @@ class CArray
54
70
  # Every cell is masked: no labels to count, same result as an empty
55
71
  # input (all-zero output of the requested minimum length).
56
72
  if weights
57
- out = CArray.new(weights.data_type, [length])
73
+ out = CArray.new(weights.data_type, [length], bytes: weights.bytes)
58
74
  else
59
75
  out_type = (length > 0xFFFFFFFF) ? CA_UINT64 : CA_UINT32
60
76
  out = CArray.new(out_type, [length])
@@ -58,20 +58,31 @@ class CArray
58
58
  # multiple of 8) are zero-filled. The byte order matches the packed-bit
59
59
  # convention used by Apache Arrow validity bitmaps and PEP 3118 `?` /
60
60
  # `_Bool` at the bit level.
61
+ # A masked cell has no bit to pack, so a receiver with masked cells is
62
+ # refused; pack the mask separately with {#validity_bits}.
61
63
  # @return [CArray] uint8 CArray of shape `[ceil(n / 8)]`.
62
- # @raise [ArgumentError] when the receiver is not 1-D or its
63
- # `data_type` is not one of `CA_BOOLEAN` / `CA_UINT8` / `CA_INT8`.
64
+ # @raise [ArgumentError] when the receiver is not 1-D, its `data_type`
65
+ # is not one of `CA_BOOLEAN` / `CA_UINT8` / `CA_INT8`, or it has
66
+ # masked cells.
64
67
  def pack_bits
65
68
  unless data_type == CA_BOOLEAN || data_type == CA_UINT8 || data_type == CA_INT8
66
69
  raise ArgumentError,
67
70
  "pack_bits: expected CA_BOOLEAN / CA_UINT8 / CA_INT8 (got #{data_type_name})"
68
71
  end
69
- raise ArgumentError, "pack_bits: 1-D CArray expected (got rank #{rank})" unless rank == 1
72
+ raise ArgumentError, "pack_bits: 1-D CArray expected (got ndim #{ndim})" unless ndim == 1
70
73
  n = elements
71
74
  n_bytes = (n + 7) / 8
72
75
  packed = CArray.uint8(n_bytes) { 0 }
73
76
  return packed if n == 0
74
- packed.bitarray.reshape(-1)[0..n-1] = self
77
+ source = self
78
+ if has_mask?
79
+ if count_masked > 0
80
+ raise ArgumentError,
81
+ "pack_bits: masked cells have no bit to pack; pack the mask with validity_bits"
82
+ end
83
+ source = value
84
+ end
85
+ packed.bitarray.reshape(-1)[0..n-1] = source
75
86
  packed
76
87
  end
77
88
 
@@ -6,7 +6,8 @@ class CArray
6
6
  #
7
7
  # Where `self == i`, the result takes `choices[i]` -- a scalar
8
8
  # fills those cells, a CArray contributes its corresponding
9
- # cells. The result has the same shape as `self`.
9
+ # cells. The result has the same shape as `self`. Where a label
10
+ # is masked, the result is masked.
10
11
  #
11
12
  # @param choices [Array<CArray, Object>] values indexed by the
12
13
  # labels in `self`; each entry is either a same-shape CArray
@@ -15,7 +16,10 @@ class CArray
15
16
  # When `nil` it is inferred: `CArray.result_type` of the CArray
16
17
  # choices, or `CA_OBJECT` when every choice is a scalar.
17
18
  # @return [CArray] new CArray with the shape of `self` holding
18
- # the chosen values.
19
+ # the chosen values; a {CAString} when every choice is a String
20
+ # or a {CAString} and `data_type` is not given.
21
+ # @raise [IndexError] if a label that is not masked is negative or
22
+ # not less than the number of choices.
19
23
  # @example
20
24
  # ref = CA_INT([[0, 1, 2], [1, 2, 0], [2, 0, 1]])
21
25
  # a = CArray.int(3, 3).seq(1)
@@ -24,6 +28,14 @@ class CArray
24
28
  # ref.choose([a, b, c]) # per-cell pick from a / b / c
25
29
  # ref.choose(["a", "b", "c"]) # recode labels to values
26
30
  def choose (choices, data_type: nil)
31
+ outside = (self.lt(0) | self.ge(choices.size)).where
32
+ if outside.elements > 0
33
+ addr = outside[0]
34
+ raise IndexError,
35
+ "choose: label #{self[addr]} at address #{addr} is out of range " \
36
+ "for #{choices.size} choices"
37
+ end
38
+ text = !data_type && CArray.send(:selection_text?, choices)
27
39
  unless data_type
28
40
  ca = choices.select { |v| v.is_a?(CArray) }
29
41
  data_type = ca.empty? ? CA_OBJECT : CArray.result_type(*ca)
@@ -33,7 +45,8 @@ class CArray
33
45
  s = self.eq(i)
34
46
  out[s] = v.is_a?(CArray) ? v[s] : v
35
47
  end
36
- out
48
+ out[self.is_masked] = UNDEF if has_mask?
49
+ text ? CAString.wrap(out) : out
37
50
  end
38
51
 
39
52
  end
@@ -75,7 +75,8 @@ class CArray
75
75
  # Use `CArray.meld` for the uniform-shape view-default counterpart.
76
76
  #
77
77
  # @param list [Array<CArray>] pieces to concatenate.
78
- # @param axis [Integer] axis to concatenate along.
78
+ # @param axis [Integer, nil] axis to concatenate along; `nil` is the
79
+ # same as `0`.
79
80
  # @param data_type [Symbol, Integer, nil] result `data_type`;
80
81
  # inferred via `result_type` when `nil`.
81
82
  # @return [CArray] fresh CArray with per-piece `axis` sizes summed.
@@ -83,7 +84,8 @@ class CArray
83
84
  # inconsistent.
84
85
  def self.concatenate (list, axis: 0, data_type: nil)
85
86
  raise ArgumentError, "concatenate: list must not be empty" if list.empty?
86
- __ragged_paste(list, [list.size], axis, data_type)
87
+ axis = 0 if axis.nil?
88
+ __ragged_paste(list, [list.size], axis, data_type, "concatenate")
87
89
  end
88
90
 
89
91
  # @overload concatenate(*others, axis: 0, data_type: nil)
@@ -128,7 +130,7 @@ class CArray
128
130
  raise ArgumentError,
129
131
  "mosaic: tdim product (#{expected}) must equal list size (#{list.size})"
130
132
  end
131
- __ragged_paste(list, tdim, axis, data_type)
133
+ __ragged_paste(list, tdim, axis, data_type, "mosaic")
132
134
  end
133
135
 
134
136
  # Shared eager paste-loop helper (= ex-`combine` paste implementation,
@@ -136,7 +138,8 @@ class CArray
136
138
  # shape obtained by tiling `list` over `tdim` starting at `axis`, with
137
139
  # per-piece dim sizes summed along each tile axis (= block-matrix
138
140
  # consistency required across rows / columns).
139
- def self.__ragged_paste (list, tdim, axis, data_type)
141
+ def self.__ragged_paste (list, tdim, axis, data_type, name)
142
+ shared_attrs = attrs_shared_by(list)
140
143
  list = CArray.promote_list(list, data_type: data_type)
141
144
  # promote_list has already enforced homogeneity (= common data_type, and
142
145
  # for Face elements: same Face class + portable + state-compatible). So,
@@ -144,12 +147,16 @@ class CArray
144
147
  # result with face_lift, rather than special-casing data_class / DATA_SIZE
145
148
  # here (= which only ever handled CARecord and silently corrupted plain
146
149
  # fixlen / numeric Faces by leaving bytes nil).
147
- face_parent = list[0].face? ? list[0] : nil
150
+ # The Face is re-wrapped from the widest piece: a fixlen Face carries
151
+ # its cell width, and the result is as wide as its widest piece.
152
+ face_parent = list[0].face? ? list.max_by(&:bytes) : nil
148
153
  if face_parent
149
- list = list.map { |x| s = x; s = s.parent while s.face?; s }
154
+ list = list.map { |x| x.__send__(:face_storage) }
150
155
  end
151
156
  data_type = list[0].data_type
152
- bytes = (data_type == :fixlen) ? list[0].bytes : nil
157
+ # Fixlen pieces of different widths join at the widest, so no cell is
158
+ # cut; each narrower piece is padded with NUL as it is pasted.
159
+ bytes = (data_type == :fixlen) ? list.map(&:bytes).max : nil
153
160
  # promote_list guarantees a non-empty list of CArrays, so the reference
154
161
  # shape is simply the first element (a CScalar carries shape [1], so a
155
162
  # leading scalar is fine; the scalar-expansion pass below broadcasts it).
@@ -157,7 +164,7 @@ class CArray
157
164
  dim = ref.shape
158
165
  ndim = ref.ndim
159
166
  tndim = tdim.size
160
- axis = CArray.normalize_axis(axis, ndim - tndim + 1, "concatenate/mosaic")
167
+ axis = CArray.normalize_axis(axis, ndim - tndim + 1, name)
161
168
 
162
169
  list = list.map do |x|
163
170
  if x.scalar?
@@ -190,7 +197,7 @@ class CArray
190
197
  block.each_with_index do |item, *tidx|
191
198
  unless item.ndim == ndim
192
199
  raise ArgumentError,
193
- "concatenate/mosaic: piece at tile #{tidx.inspect} has ndim " \
200
+ "#{name}: piece at tile #{tidx.inspect} has ndim " \
194
201
  "#{item.ndim} (expected #{ndim})"
195
202
  end
196
203
  ndim.times do |d|
@@ -199,7 +206,7 @@ class CArray
199
206
  expected = on_tile ? tile_sizes[i][tidx[i]] : dim[d]
200
207
  unless item.shape[d] == expected
201
208
  raise ArgumentError,
202
- "concatenate/mosaic: piece at tile #{tidx.inspect} has " \
209
+ "#{name}: piece at tile #{tidx.inspect} has " \
203
210
  "#{on_tile ? 'tile' : 'non-tile'} axis #{d} size " \
204
211
  "#{item.shape[d]} (expected #{expected}); pieces must agree " \
205
212
  "on non-tile axes and be block-matrix consistent along tile axes"
@@ -217,6 +224,7 @@ class CArray
217
224
  obj.paste(idx, item)
218
225
  end
219
226
  obj = obj.face_lift(face_parent) if face_parent
227
+ obj.set_attrs(shared_attrs) unless shared_attrs.empty?
220
228
  obj
221
229
  end
222
230
  private_class_method :__ragged_paste
@@ -23,8 +23,8 @@ class CArray
23
23
  # Distinctness is the family's hash-key judgement (see {#unique}):
24
24
  # `==` for numeric with all NaN collapsed to one value and
25
25
  # -0.0 / +0.0 the same value; `eql?` / `hash` for `CA_OBJECT` and
26
- # `CA_FIXLEN`. Complex is not a lane the factorizer takes and raises
27
- # {CArray::DataTypeError}.
26
+ # `CA_FIXLEN`. Complex values compare by value, with NaN and -0.0
27
+ # treated the same way in each component.
28
28
  #
29
29
  # There is no `sort:` here, unlike {#unique} and {#value_counts}:
30
30
  # the codes index the levels, so reordering the vocabulary would
@@ -5,9 +5,9 @@
5
5
  # Treats the trailing K axes of `indices.shape = (..., K)` as coordinate tuples
6
6
  # and consumes the first K axes of params while carrying the remaining axes (= rest).
7
7
  #
8
- # Implementation goes through a CAMapping view of the flattened array:
8
+ # Implementation goes through an index view (CAGrid) of the flattened array:
9
9
  # `params.flatten[flat_addr]`. Pure Ruby, no additional framework, composed
10
- # entirely from existing indexers as in `take_along_axis`. Since CAMapping is
10
+ # entirely from existing indexers as in `take_along_axis`. Since the view is
11
11
  # writable, write-through via `put_nd` uses last-write-wins semantics (= last
12
12
  # value wins on duplicate indices, same contract as `put_along_axis`). For
13
13
  # accumulate semantics, route to the `scatter_*!` family instead.
@@ -63,8 +63,9 @@ class CArray
63
63
  def gather_nd (indices)
64
64
  flat_addr, outer, rest = gather_nd_flat_addr(indices, "gather_nd")
65
65
  out_shape = outer + rest
66
- # flatten + 1-D fancy indexing -> CAMapping view -> materialise via .copy.
67
- result = self.flatten[flat_addr].copy
66
+ # flatten + 1-D fancy indexing -> CAGrid view -> materialise via .copy.
67
+ # A masked coordinate reads as UNDEF: project carries the address mask.
68
+ result = flat_addr.has_mask? ? flatten.project(flat_addr) : self.flatten[flat_addr].copy
68
69
  out_shape.empty? ? result : result.reshape(*out_shape)
69
70
  end
70
71
 
@@ -90,8 +91,22 @@ class CArray
90
91
  # @raise [ArgumentError] on the same conditions as {#gather_nd}.
91
92
  # @raise [IndexError] when a coordinate is out of range on any axis.
92
93
  def put_nd (indices, values)
94
+ # The write goes through a flattened view, which carries a frozen
95
+ # array's read-only flag but not its frozenness; say what the array is.
96
+ raise FrozenError.new("can't modify frozen #{self.class}", receiver: self) if frozen?
93
97
  flat_addr, _outer, _rest = gather_nd_flat_addr(indices, "put_nd")
94
- self.flatten[flat_addr] = values
98
+ if flat_addr.has_mask?
99
+ # A masked coordinate writes nothing. The values are laid out over
100
+ # the coordinates by the store itself, so they mean what they mean
101
+ # unmasked, and only the cells at unmasked coordinates are written.
102
+ flat = flatten
103
+ vals = flat.project(flat_addr)
104
+ vals[] = values
105
+ sel = flat_addr.is_not_masked
106
+ flat[flat_addr[sel]] = vals[sel]
107
+ else
108
+ self.flatten[flat_addr] = values
109
+ end
95
110
  self
96
111
  end
97
112
 
@@ -150,6 +165,11 @@ class CArray
150
165
  flat_idx = (flat_idx_raw + dims) % dims
151
166
 
152
167
  base = (flat_idx * strides).sum(axis: 1).int64.reshape(m) # (M,)
168
+ # sum skips a masked coordinate, which would leave its row pointing at
169
+ # the wrong cell: a row with any masked coordinate has no address.
170
+ if flat_idx_raw.has_mask?
171
+ base[flat_idx_raw.is_masked.any(axis: 1)] = UNDEF
172
+ end
153
173
 
154
174
  flat_addr =
155
175
  if rest.empty?
@@ -11,8 +11,8 @@ class CArray
11
11
  # appends at the end) and a negative value counts from the end;
12
12
  # `bsize[i]` must be non-negative. Inserted cells are filled by
13
13
  # the block's return value, or left at the type's default when
14
- # no block is given. Preserves fixlen bytes and Face identity
15
- # (built on the storage layout, re-wrapped via `face_lift`).
14
+ # no block is given. Preserves fixlen bytes and Face identity; the
15
+ # block returns a value of the array (a Time for a time array).
16
16
  # The `offset` array is not mutated.
17
17
  #
18
18
  # @param offset [Array<Integer>] insertion offset per axis.
@@ -40,21 +40,16 @@ class CArray
40
40
  # offset == dim (append) leaves nothing on the upper side to shift.
41
41
  grids[i][offset[i]..-1].seq!(offset[i]+bsize[i]) if offset[i] < shape[i]
42
42
  end
43
- # Build at the storage layout (preserving bytes for fixlen / Face),
44
- # then re-wrap as the same Face.
45
- face_parent = self.face? ? self : nil
46
- src = self
47
- src = src.parent while src.face?
48
- dt = src.data_type
49
- bytes = (dt == :fixlen) ? src.bytes : nil
50
- out = CArray.new(dt, newdim, bytes: bytes)
43
+ # The original cells are copied as storage, so a read-only Face inserts
44
+ # too; the block's value is written as a value of the array, which a
45
+ # read-only Face refuses.
46
+ out = blank_like(newdim)
51
47
  if block_given?
52
- sel = out.true
53
- sel[*grids] = 0
48
+ sel = CArray.boolean(*newdim) { true }
49
+ sel[*grids] = false
54
50
  out[sel] = block.call
55
51
  end
56
- out[*grids] = src
57
- out = out.face_lift(face_parent) if face_parent
52
+ out.__send__(:face_storage)[*grids] = face_storage
58
53
  return out
59
54
  end
60
55
 
@@ -0,0 +1,59 @@
1
+ class CArray
2
+
3
+ # @!group Elementwise math
4
+
5
+ # @overload is_between(lo, hi, exclude: nil)
6
+ # @overload is_between(range)
7
+ # Returns a boolean CArray of the same shape as `self`, `true` at each
8
+ # cell whose value lies between `lo` and `hi`. Both bounds are included
9
+ # unless `exclude:` names the one to leave out:
10
+ #
11
+ # a.is_between(2, 4) # 2 <= a <= 4
12
+ # a.is_between(2, 4, exclude: :upper) # 2 <= a < 4
13
+ # a.is_between(2, 4, exclude: :lower) # 2 < a <= 4
14
+ # a.is_between(2, 4, exclude: :both) # 2 < a < 4
15
+ #
16
+ # A Range says the same as the two bounds, its excluded end as
17
+ # `exclude: :upper`: `a.is_between(2...4)`. A bound given as `nil` (or
18
+ # the open end of `..4` / `2..`) leaves that side unbounded.
19
+ #
20
+ # It is the two comparisons joined by `&`, so it takes what they take --
21
+ # a time array is bounded by Strings, `Time` or elements -- and answers as
22
+ # they do: a masked cell is masked, and NaN is outside every interval.
23
+ # Bounds the wrong way round (`lo > hi`) contain nothing.
24
+ # @param lo [Object, nil] the lower bound.
25
+ # @param hi [Object, nil] the upper bound.
26
+ # @param exclude [nil, :lower, :upper, :both] the bounds left out.
27
+ # @return [CArray] boolean, the shape of `self`.
28
+ # @raise [ArgumentError] when neither bound is given, `exclude:` is not one
29
+ # of the three, or a Range comes with a second bound or `exclude:`.
30
+ def is_between (*bounds, exclude: nil)
31
+ if bounds.size == 1 && bounds[0].is_a?(Range)
32
+ unless exclude.nil?
33
+ raise ArgumentError,
34
+ "is_between: a Range says which ends it includes; give two bounds to use exclude:"
35
+ end
36
+ range = bounds[0]
37
+ lo, hi = range.begin, range.end
38
+ exclude = :upper if range.exclude_end? && !hi.nil?
39
+ elsif bounds.size == 2
40
+ lo, hi = bounds
41
+ else
42
+ raise ArgumentError,
43
+ "is_between: give a lower and an upper bound, or a Range (got #{bounds.size} arguments)"
44
+ end
45
+ unless [nil, :lower, :upper, :both].include?(exclude)
46
+ raise ArgumentError,
47
+ "is_between: exclude: must be :lower, :upper or :both (got #{exclude.inspect})"
48
+ end
49
+ if lo.nil? && hi.nil?
50
+ raise ArgumentError, "is_between: give at least one bound"
51
+ end
52
+ lower = lo.nil? ? nil : (%i[lower both].include?(exclude) ? gt(lo) : ge(lo))
53
+ upper = hi.nil? ? nil : (%i[upper both].include?(exclude) ? lt(hi) : le(hi))
54
+ lower && upper ? lower & upper : (lower || upper)
55
+ end
56
+
57
+ # @!endgroup
58
+
59
+ end