carray 3.0.0 → 3.0.2

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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +845 -3
  4. data/{NEWS.md → CHANGELOG.v1.md} +1 -1
  5. data/README.md +33 -33
  6. data/carray.gemspec +12 -8
  7. data/ext/ca_axis_dispatch.c +33 -4
  8. data/ext/ca_axis_group.c +256 -114
  9. data/ext/ca_binop_dispatch.c +5 -6
  10. data/ext/ca_binop_dispatch.h +0 -7
  11. data/ext/ca_categorical_iterator.c +121 -67
  12. data/ext/ca_for_each_element.h +6 -8
  13. data/ext/ca_group_iter.c +1 -1
  14. data/ext/ca_kernel_iterator.c +386 -74
  15. data/ext/ca_kernel_iterator.h +145 -45
  16. data/ext/ca_obj_array.c +62 -20
  17. data/ext/ca_obj_bincmp.c +57 -27
  18. data/ext/ca_obj_binop.c +86 -30
  19. data/ext/ca_obj_block.c +4 -4
  20. data/ext/ca_obj_const_string.c +88 -27
  21. data/ext/ca_obj_face.c +104 -0
  22. data/ext/ca_obj_face.h +48 -1
  23. data/ext/ca_obj_fake.c +11 -0
  24. data/ext/ca_obj_fixlen_string.c +21 -6
  25. data/ext/ca_obj_grid.c +11 -3
  26. data/ext/ca_obj_meld.c +125 -22
  27. data/ext/ca_obj_moncmp.c +33 -14
  28. data/ext/ca_obj_monop.c +19 -7
  29. data/ext/ca_obj_object.c +107 -4
  30. data/ext/ca_obj_record.c +3 -1
  31. data/ext/ca_obj_refer.c +30 -6
  32. data/ext/ca_obj_roll.c +13 -5
  33. data/ext/ca_obj_select.c +49 -34
  34. data/ext/ca_obj_select_axis.c +16 -6
  35. data/ext/ca_obj_shift.c +3 -3
  36. data/ext/ca_obj_stack.c +3 -8
  37. data/ext/ca_obj_stride.c +178 -24
  38. data/ext/ca_obj_string.c +11 -5
  39. data/ext/ca_obj_tile.c +13 -5
  40. data/ext/ca_obj_time.c +3 -1
  41. data/ext/ca_obj_timedelta.c +3 -1
  42. data/ext/ca_obj_transpose.c +1 -1
  43. data/ext/ca_obj_triop.c +62 -23
  44. data/ext/ca_obj_window.c +94 -19
  45. data/ext/ca_op_cmplx64.h +123 -0
  46. data/ext/ca_op_ipower.c +1 -5
  47. data/ext/ca_rng_normal.h +42 -0
  48. data/ext/ca_rng_xoshiro256pp.h +105 -0
  49. data/ext/ca_sort_kernels.h +5 -5
  50. data/ext/ca_sweep_engine.c +362 -155
  51. data/ext/ca_sweep_engine.h +39 -9
  52. data/ext/ca_transform_common.c +7 -0
  53. data/ext/carray.h +148 -132
  54. data/ext/carray_access.c +83 -49
  55. data/ext/carray_address_basis.c +590 -0
  56. data/ext/carray_bincount.c +8 -8
  57. data/ext/carray_broadcast.c +102 -9
  58. data/ext/carray_build_flags.h +3 -0
  59. data/ext/carray_call_cfunc.c +2010 -335
  60. data/ext/carray_call_cfunc.h +153 -0
  61. data/ext/carray_cast.c +184 -72
  62. data/ext/carray_conversion.c +22 -22
  63. data/ext/carray_copy.c +67 -32
  64. data/ext/carray_core.c +140 -13
  65. data/ext/carray_count.c +10 -11
  66. data/ext/carray_element.c +7 -7
  67. data/ext/carray_factorize.c +72 -51
  68. data/ext/carray_hold.c +1 -1
  69. data/ext/carray_index_classifier.c +7 -21
  70. data/ext/carray_internal.h +36 -3
  71. data/ext/carray_kernels_bincmp.c +1 -0
  72. data/ext/carray_kernels_binop.c +320 -298
  73. data/ext/carray_kernels_init.c +1095 -0
  74. data/ext/carray_kernels_map.c +1 -0
  75. data/ext/carray_kernels_moncmp.c +1 -0
  76. data/ext/carray_kernels_monop.c +412 -411
  77. data/ext/carray_kernels_reduce_aggregate.c +256 -87
  78. data/ext/carray_kernels_reduce_boolean.c +1 -0
  79. data/ext/carray_kernels_reduce_cumulative.c +324 -54
  80. data/ext/carray_kernels_reduce_extreme.c +605 -58
  81. data/ext/carray_kernels_reduce_variance.c +1 -0
  82. data/ext/carray_kernels_scan.c +5 -4
  83. data/ext/carray_kernels_search.c +187 -106
  84. data/ext/carray_kernels_sort.c +1 -0
  85. data/ext/carray_kernels_triop.c +1 -0
  86. data/ext/carray_lazy.c +175 -5
  87. data/ext/carray_loop.c +7 -1
  88. data/ext/carray_mask.c +29 -13
  89. data/ext/carray_median_percentile.c +56 -1
  90. data/ext/carray_memory_view.c +46 -21
  91. data/ext/carray_operator.c +50 -53
  92. data/ext/carray_order.c +1 -1
  93. data/ext/carray_partition.c +4 -4
  94. data/ext/carray_random.c +391 -47
  95. data/ext/carray_scatter.c +1 -1
  96. data/ext/carray_slab.c +13 -0
  97. data/ext/carray_sort.c +22 -24
  98. data/ext/carray_sort_kernel.c +10 -10
  99. data/ext/carray_test.c +1 -1
  100. data/ext/extconf.rb +18 -0
  101. data/ext/mk_call_cfunc.rb +323 -145
  102. data/ext/mkkernel.rb +651 -129
  103. data/ext/ruby_carray.c +28 -17
  104. data/ext/version.h +4 -4
  105. data/lib/carray/autoload_carray.rb +24 -3
  106. data/lib/carray/autoload_method_extension.rb +14 -1
  107. data/lib/carray/axis_group.rb +84 -7
  108. data/lib/carray/basics.rb +63 -59
  109. data/lib/carray/bincount_nd.rb +12 -12
  110. data/lib/carray/block_iterator.rb +111 -25
  111. data/lib/carray/categorical.rb +154 -37
  112. data/lib/carray/categorical_iterator.rb +300 -151
  113. data/lib/carray/conditional.rb +14 -14
  114. data/lib/carray/const_string.rb +131 -27
  115. data/lib/carray/construct.rb +61 -0
  116. data/lib/carray/core_extensions.rb +62 -5
  117. data/lib/carray/data_type_extension.rb +21 -1
  118. data/lib/carray/data_type_limits.rb +91 -0
  119. data/lib/carray/fixlen_string.rb +1 -1
  120. data/lib/carray/frame/concat.rb +3 -3
  121. data/lib/carray/frame/convert.rb +1 -1
  122. data/lib/carray/frame/csv_parser.rb +11 -4
  123. data/lib/carray/frame/frame.rb +85 -21
  124. data/lib/carray/frame/group.rb +36 -3
  125. data/lib/carray/frame/io.rb +224 -15
  126. data/lib/carray/frame/records.rb +18 -4
  127. data/lib/carray/frame/sort.rb +1 -1
  128. data/lib/carray/frame/verbs.rb +32 -12
  129. data/lib/carray/fuse_source.rb +123 -0
  130. data/lib/carray/fusion.rb +218 -0
  131. data/lib/carray/histogram.rb +16 -16
  132. data/lib/carray/inspect.rb +43 -16
  133. data/lib/carray/iterator.rb +147 -3
  134. data/lib/carray/lazy.rb +88 -73
  135. data/lib/carray/mask_gap_fill.rb +3 -1
  136. data/lib/carray/meld_reduce.rb +2 -2
  137. data/lib/carray/methods/align_addr.rb +1 -1
  138. data/lib/carray/methods/composition.rb +1 -1
  139. data/lib/carray/methods/discovery_along.rb +74 -0
  140. data/lib/carray/methods/factorize.rb +50 -0
  141. data/lib/carray/methods/is_in.rb +25 -14
  142. data/lib/carray/methods/locate_addr.rb +81 -3
  143. data/lib/carray/methods/mask_duplicates.rb +36 -2
  144. data/lib/carray/methods/meshgrid.rb +4 -5
  145. data/lib/carray/methods/mode.rb +2 -2
  146. data/lib/carray/methods/nunique.rb +23 -2
  147. data/lib/carray/methods/repeat.rb +110 -0
  148. data/lib/carray/methods/snap.rb +7 -2
  149. data/lib/carray/methods/unique.rb +44 -4
  150. data/lib/carray/methods/value_counts.rb +2 -2
  151. data/lib/carray/rng.rb +86 -0
  152. data/lib/carray/runtime.rb +0 -19
  153. data/lib/carray/slab_iterator.rb +77 -19
  154. data/lib/carray/string_operation_extension.rb +10 -6
  155. data/lib/carray/time.rb +906 -491
  156. data/lib/carray/window_iterator.rb +421 -27
  157. data/lib/carray.rb +7 -5
  158. data/yard-stubs/ca_obj_array.rb +385 -0
  159. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  160. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  161. data/yard-stubs/ca_obj_block.rb +68 -0
  162. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  163. data/yard-stubs/ca_obj_fake.rb +31 -0
  164. data/yard-stubs/ca_obj_farray.rb +32 -0
  165. data/yard-stubs/ca_obj_field.rb +45 -0
  166. data/yard-stubs/ca_obj_grid.rb +35 -0
  167. data/yard-stubs/ca_obj_refer.rb +72 -0
  168. data/yard-stubs/ca_obj_roll.rb +45 -0
  169. data/yard-stubs/ca_obj_shift.rb +43 -0
  170. data/yard-stubs/ca_obj_stride.rb +181 -0
  171. data/yard-stubs/ca_obj_tile.rb +29 -0
  172. data/yard-stubs/ca_obj_transpose.rb +40 -0
  173. data/yard-stubs/ca_obj_window.rb +57 -0
  174. data/yard-stubs/carray_access.rb +131 -0
  175. data/yard-stubs/carray_attribute.rb +246 -0
  176. data/yard-stubs/carray_broadcast.rb +37 -0
  177. data/yard-stubs/carray_cast.rb +489 -0
  178. data/yard-stubs/carray_class.rb +65 -0
  179. data/yard-stubs/carray_conversion.rb +76 -0
  180. data/yard-stubs/carray_copy.rb +79 -0
  181. data/yard-stubs/carray_core.rb +34 -0
  182. data/yard-stubs/carray_count.rb +84 -0
  183. data/yard-stubs/carray_element.rb +108 -0
  184. data/yard-stubs/carray_generate.rb +66 -0
  185. data/yard-stubs/carray_lazy.rb +228 -0
  186. data/yard-stubs/carray_loop.rb +140 -0
  187. data/yard-stubs/carray_mask.rb +259 -0
  188. data/yard-stubs/carray_math.rb +1615 -0
  189. data/yard-stubs/carray_mathfunc.rb +45 -0
  190. data/yard-stubs/carray_median_percentile.rb +103 -0
  191. data/yard-stubs/carray_memory_view.rb +163 -0
  192. data/yard-stubs/carray_order.rb +252 -0
  193. data/yard-stubs/carray_random.rb +89 -0
  194. data/yard-stubs/carray_scatter.rb +106 -0
  195. data/yard-stubs/carray_slab.rb +59 -0
  196. data/yard-stubs/carray_sort.rb +165 -0
  197. data/yard-stubs/carray_test.rb +85 -0
  198. data/yard-stubs/carray_undef.rb +64 -0
  199. data/yard-stubs/carray_utils.rb +97 -0
  200. data/yard-stubs/ruby_carray.rb +193 -0
  201. metadata +67 -10
  202. data/ext/ca_obj_unbound_repeat.c +0 -496
@@ -1,3 +1,5 @@
1
+ require "carray/methods/discovery_along"
2
+
1
3
  class CArray
2
4
 
3
5
  # @overload mask_duplicates(axis: nil)
@@ -23,14 +25,46 @@ class CArray
23
25
  # `nil` uses flatten order.
24
26
  # @return [CArray] shape-preserving copy of `self` with
25
27
  # duplicates masked.
26
- def mask_duplicates (axis: nil)
28
+ # @overload mask_duplicates(along: k)
29
+ # Returns a shape-preserving copy of `self` with every cell of a
30
+ # duplicated **sub-array** masked, comparing whole sub-arrays rather
31
+ # than cells: `along: k` names the axis whose index enumerates them,
32
+ # so `z.mask_duplicates(along: 0)` masks each repeated row of a 2-D
33
+ # array and keeps the first occurrence.
34
+ #
35
+ # Note the contrast with `axis:`, which names the axis a *fiber runs
36
+ # along* and marks repeated values inside each fiber. The two cannot
37
+ # be given together.
38
+ #
39
+ # A sub-array holding a masked cell does not participate: it is
40
+ # neither judged a duplicate nor able to make a later one, and its
41
+ # cells keep the mask they came with. `object` arrays are refused,
42
+ # because their cells hold Ruby references.
43
+ #
44
+ # @param along [Integer] axis whose index enumerates the sub-arrays.
45
+ # @return [CArray] shape-preserving copy of `self` with duplicated
46
+ # sub-arrays masked.
47
+ def mask_duplicates (axis: nil, along: nil)
48
+ reject_axis_with_along(axis, along, "mask_duplicates")
49
+ if along
50
+ keys = fibers_as_cells(along, "mask_duplicates")
51
+ # A sub-array that did not participate (it held a masked cell) is
52
+ # masked in `keys` already; only the ones that did and repeated
53
+ # are duplicates here.
54
+ repeated = keys.mask_duplicates.is_masked & keys.is_not_masked
55
+ args = [:_] * ndim
56
+ args[normalize_axis(along, "mask_duplicates")] = nil
57
+ spread = CArray.boolean(*shape)
58
+ spread[] = repeated[*args]
59
+ return mask_where(spread)
60
+ end
27
61
  dup =
28
62
  if axis.nil?
29
63
  # One seen-set over the flattened array, then restore shape.
30
64
  flatten.send(:__mask_duplicates__, 0).reshape(*shape)
31
65
  else
32
66
  # Per-fiber single-pass seen-set hash (C __mask_duplicates__): one lane
33
- # per dtype family (integer widen, float bitwise key with NaN collapse,
67
+ # per data type family (integer widen, float bitwise key with NaN collapse,
34
68
  # object rb_hash + rb_eql, fixlen byte-hash + memcmp, boolean via the
35
69
  # uint8 lane). O(distinct) memory, no sort/gather/scatter buffers.
36
70
  __mask_duplicates__(normalize_axis(axis, "mask_duplicates"))
@@ -17,10 +17,9 @@ class CArray
17
17
  # input order in both modes.
18
18
  #
19
19
  # When `copy` is true (default) each output is a materialised
20
- # CArray; when false, view chains (CARepeat / CAUnboundRepeat)
21
- # are returned. When `sparse` is true the outputs are
22
- # CAUnboundRepeat views that broadcast on demand, saving memory
23
- # for large grids.
20
+ # CArray; when false, a view is returned. When `sparse` is true
21
+ # each output keeps a size-1 axis wherever the full grid would
22
+ # repeat, and broadcasts on demand, saving memory for large grids.
24
23
  #
25
24
  # If a block is given, yields the resulting arrays as splat
26
25
  # arguments and returns the block's value.
@@ -77,7 +76,7 @@ class CArray
77
76
  list = axes.map.with_index do |axis, k|
78
77
  d = dest[k]
79
78
  idx = if sparse
80
- Array.new(ndim) { |i| i == d ? nil : :* }
79
+ Array.new(ndim) { |i| i == d ? nil : :_ }
81
80
  else
82
81
  out_shape.dup.tap { |s| s[d] = :% }
83
82
  end
@@ -33,7 +33,7 @@ class CArray
33
33
  # the whole array.
34
34
  # @return [CArray] boolean CArray of `self.shape`.
35
35
  def is_mode (axis: nil)
36
- # Per-fiber two-pass frequency table (C __is_mode__), one lane per dtype
36
+ # Per-fiber two-pass frequency table (C __is_mode__), one lane per data type
37
37
  # family (numeric widen / NaN collapse, object rb_hash + rb_eql, fixlen
38
38
  # byte-hash + memcmp). Ties are all marked; masked cells stay false.
39
39
  if axis.nil?
@@ -112,7 +112,7 @@ class CArray
112
112
 
113
113
  private
114
114
 
115
- # Flat mode: the distinct modal values ascending, 1-D CArray of self's dtype.
115
+ # Flat mode: the distinct modal values ascending, 1-D CArray of self's data type.
116
116
  # The single source of what counts as a mode (per-axis reuses it per fiber).
117
117
  # The distinct values with the maximum count, read from the frequency table
118
118
  # (value_counts, which covers numeric / object / fixlen), then sorted
@@ -1,3 +1,5 @@
1
+ require "carray/methods/discovery_along"
2
+
1
3
  class CArray
2
4
 
3
5
  # @overload nunique(axis: nil, keep_axis: false)
@@ -29,8 +31,27 @@ class CArray
29
31
  # axis as a length-1 axis instead of dropping it.
30
32
  # @return [Integer, CArray] Integer for `axis: nil`, otherwise a
31
33
  # reduced `CA_INT64` CArray.
32
- def nunique (axis: nil, keep_axis: false)
33
- # Per-fiber single-pass seen-set hash (C __nunique__), one lane per dtype
34
+ # @overload nunique(along: k)
35
+ # Returns how many distinct **sub-arrays** `self` holds, comparing
36
+ # whole sub-arrays rather than cells: `along: k` names the axis
37
+ # whose index enumerates them, so `z.nunique(along: 0)` counts the
38
+ # distinct rows of a 2-D array. This is `len(np.unique(z, axis=k))`.
39
+ #
40
+ # Note the contrast with `axis:`, which names the axis a *fiber runs
41
+ # along* and counts the distinct values inside each fiber -- one
42
+ # count per fiber, not one count for the array. The two cannot be
43
+ # given together.
44
+ #
45
+ # Sub-arrays holding a masked cell do not participate and are not
46
+ # counted. `object` arrays are refused, because their cells hold
47
+ # Ruby references.
48
+ #
49
+ # @param along [Integer] axis whose index enumerates the sub-arrays.
50
+ # @return [Integer] the number of distinct sub-arrays.
51
+ def nunique (axis: nil, keep_axis: false, along: nil)
52
+ reject_axis_with_along(axis, along, "nunique")
53
+ return fibers_as_cells(along, "nunique").nunique if along
54
+ # Per-fiber single-pass seen-set hash (C __nunique__), one lane per data type
34
55
  # family (numeric widen / NaN collapse, object rb_hash + rb_eql, fixlen
35
56
  # byte-hash + memcmp). Masked cells are skipped; the accumulator is a no-op
36
57
  # (the distinct count is the interned-key count).
@@ -0,0 +1,110 @@
1
+ class CArray
2
+
3
+ # @overload repeat(count, axis: nil)
4
+ # @overload repeat(counts, axis: nil)
5
+ # Returns each element (or each sub-array) of `self` laid down
6
+ # `count` times in place, one after another.
7
+ #
8
+ # With an Integer, every element is repeated the same number of
9
+ # times. With an array of counts -- one per element, or one per
10
+ # sub-array when `axis:` is given -- each is repeated its own number
11
+ # of times, and a count of 0 drops that one:
12
+ #
13
+ # v = CA_INT([10, 20, 30])
14
+ # v.repeat(2) # => [ 10, 10, 20, 20, 30, 30 ]
15
+ # v.repeat([3, 1, 2]) # => [ 10, 10, 10, 20, 30, 30 ]
16
+ # v.repeat([2, 0, 1]) # => [ 10, 10, 30 ]
17
+ #
18
+ # This is `np.repeat`, and it is not {#tile}: `repeat` puts the
19
+ # copies of one element next to each other, `tile` lays the whole
20
+ # array down again -- `v.tile(2)` is `[10, 20, 30, 10, 20, 30]`.
21
+ #
22
+ # `axis: k` repeats the sub-arrays enumerated by axis `k` rather
23
+ # than the cells, so a 2-D array repeats whole rows and the result
24
+ # keeps its shape apart from that axis:
25
+ #
26
+ # t = CA_INT([[0, 1], [2, 3], [4, 5]])
27
+ # t.repeat([2, 1, 1], axis: 0)
28
+ # # => [ [ 0, 1 ],
29
+ # # [ 0, 1 ],
30
+ # # [ 2, 3 ],
31
+ # # [ 4, 5 ] ]
32
+ #
33
+ # Without `axis:` a multi-dimensional receiver is taken in flatten
34
+ # (row-major) order and the result is 1-D, as `np.repeat` does.
35
+ #
36
+ # The result is a **view** of `self` -- the same element named as
37
+ # many times as it was repeated -- so nothing is copied and writing
38
+ # through it reaches `self`, at every position that names the cell
39
+ # written. Take a `copy` when that is not wanted.
40
+ #
41
+ # Counts must be non-negative, and there must be exactly as many as
42
+ # there are elements (or sub-arrays along `axis:`). A masked count
43
+ # is refused: it names no number of repetitions.
44
+ #
45
+ # @param count [Integer, CArray, Array] how many times to repeat --
46
+ # one number for all, or one per element / sub-array.
47
+ # @param axis [Integer, nil] axis whose index enumerates the
48
+ # sub-arrays to repeat; `nil` repeats cells in flatten order.
49
+ # @return [CArray] the repeated elements, as a view of `self`.
50
+ def repeat (count, axis: nil)
51
+ along = axis.nil? ? nil : normalize_axis(axis, "repeat")
52
+ width = along ? shape[along] : elements
53
+
54
+ counts =
55
+ case count
56
+ when Integer
57
+ if count < 0
58
+ raise ArgumentError, "repeat: count must not be negative (got #{count})"
59
+ end
60
+ CArray.int64(width).fill(count)
61
+ else
62
+ given = count.to_ca.int64
63
+ unless given.elements == width
64
+ raise ArgumentError,
65
+ "repeat: #{given.elements} counts for #{width} " \
66
+ "#{along ? "sub-arrays along axis #{along}" : 'elements'}"
67
+ end
68
+ if given.has_mask? and given.count_masked > 0
69
+ raise ArgumentError,
70
+ "repeat: a masked count names no number of repetitions"
71
+ end
72
+ if given.lt(0).any
73
+ raise ArgumentError, "repeat: counts must not be negative"
74
+ end
75
+ given.flatten
76
+ end
77
+
78
+ source = along ? self : (ndim > 1 ? flatten : self)
79
+ source[*gather_args(repeat_index(counts), along)]
80
+ end
81
+
82
+ # The source index of every position in the result: element i appears
83
+ # at the `counts[i]` positions starting at the exclusive prefix sum.
84
+ #
85
+ # Marking those starts with +1 and running a cumulative sum turns the
86
+ # marks back into indices, and does the right thing with a zero
87
+ # count for free -- two elements then share a start, that position
88
+ # carries 2, and the sum steps over the one that was asked for zero
89
+ # times.
90
+ private def repeat_index (counts)
91
+ total = counts.sum.to_i
92
+ return CArray.int64(0) if total.zero?
93
+ starts = counts.cumsum.int64 - counts
94
+ marks = CArray.int64(total)
95
+ # A trailing zero count starts one past the end; it marks nothing,
96
+ # which is what a zero count means.
97
+ marks.scatter_add!(starts[starts.lt(total)], 1)
98
+ marks.cumsum.int64 - 1
99
+ end
100
+
101
+ # One index argument per axis: the gathered index on the axis being
102
+ # repeated, whole axes everywhere else.
103
+ private def gather_args (index, along)
104
+ return [index] unless along
105
+ args = [nil] * ndim
106
+ args[along] = index
107
+ args
108
+ end
109
+
110
+ end
@@ -7,7 +7,7 @@ class CArray
7
7
  # matching `CArray#round`; `:floor` snaps toward `-inf`, `:ceil`
8
8
  # toward `+inf`).
9
9
  #
10
- # The output has the same dtype as `self` (integer input is coerced
10
+ # The output has the same data type as `self` (integer input is coerced
11
11
  # to float internally when `step` / `offset` are floats, following
12
12
  # normal arithmetic promotion). NaN / Inf are preserved as a mask
13
13
  # on the output; the rounding kernels map NaN to 0.0, which would
@@ -45,7 +45,12 @@ class CArray
45
45
  # Detect NaN / Inf before rounding (which maps NaN -> 0.0 silently).
46
46
  invalid_mask = scaled.float? ? scaled.is_invalid : nil
47
47
 
48
- out = scaled.send(direction) * step + offset
48
+ snapped = case direction
49
+ when :round then scaled.round
50
+ when :floor then scaled.floor
51
+ when :ceil then scaled.ceil
52
+ end
53
+ out = snapped * step + offset
49
54
 
50
55
  if invalid_mask && invalid_mask.count(true) > 0
51
56
  out.mask = out.has_mask? ? (out.mask | invalid_mask) : invalid_mask
@@ -1,3 +1,5 @@
1
+ require "carray/methods/discovery_along"
2
+
1
3
  class CArray
2
4
 
3
5
  # @overload unique(sort: false)
@@ -16,7 +18,7 @@ class CArray
16
18
  # Masked cells do not participate and never appear in the result;
17
19
  # an all-masked array yields an empty CArray.
18
20
  #
19
- # Distinctness follows `==` for numeric dtypes, with two float
21
+ # Distinctness follows `==` for numeric data types, with two float
20
22
  # special cases so the result matches value-based expectations:
21
23
  # all NaN collapse to a single distinct value (rather than one per
22
24
  # cell) and -0.0 / +0.0 are the same value. The value kept for
@@ -32,10 +34,48 @@ class CArray
32
34
  # its own unit: the distinct values are values, so the array comes back
33
35
  # as itself rather than as raw storage ticks.
34
36
  #
35
- # @return [CArray] 1-D CArray of the distinct values, same dtype
37
+ # @return [CArray] 1-D CArray of the distinct values, same data type
36
38
  # as `self`.
37
- def unique (sort: false)
38
- # Single-pass seen-set hash (C __unique_flat__), one lane per dtype family:
39
+ #
40
+ # @overload unique(along: k)
41
+ # Returns the distinct **sub-arrays** of `self`, comparing whole
42
+ # sub-arrays rather than cells: `along: k` names the axis whose
43
+ # index enumerates them, so `z.unique(along: 0)` gives the distinct
44
+ # rows of a 2-D array, in first-appearance order. This is
45
+ # `np.unique(z, axis=k)`.
46
+ #
47
+ # Note the contrast with `axis:` on {#nunique} and
48
+ # {#mask_duplicates}, which names the axis a *fiber runs along* and
49
+ # asks about the values inside each fiber. The two cannot be given
50
+ # together.
51
+ #
52
+ # Distinctness is the family's, widened from a cell to a sub-array:
53
+ # two sub-arrays are the same when every cell is, with all NaN one
54
+ # value and -0.0 == +0.0. A sub-array holding a masked cell does not
55
+ # participate and never appears in the result.
56
+ #
57
+ # The result is a **view** of `self` -- the surviving sub-arrays,
58
+ # not copies of them -- so writing through it reaches `self`. Take a
59
+ # `copy` when that is not wanted. `sort:` is not available here:
60
+ # sub-arrays have no order to sort by. `object` arrays are refused,
61
+ # because their cells hold Ruby references.
62
+ #
63
+ # @param along [Integer] axis whose index enumerates the sub-arrays.
64
+ # @return [CArray] the distinct sub-arrays, same shape as `self`
65
+ # except along `along`.
66
+ def unique (sort: false, along: nil)
67
+ if along
68
+ if sort
69
+ raise ArgumentError,
70
+ "unique: sort: is not available with along: -- sub-arrays have " \
71
+ "no order to sort by; the result is in first-appearance order"
72
+ end
73
+ keys = fibers_as_cells(along, "unique")
74
+ args = [nil] * ndim
75
+ args[normalize_axis(along, "unique")] = keys.mask_duplicates.is_not_masked
76
+ return self[*args]
77
+ end
78
+ # Single-pass seen-set hash (C __unique_flat__), one lane per data type family:
39
79
  # integer widens to a 64-bit key; float uses the bitwise key with all-NaN
40
80
  # collapsed and -0.0 / +0.0 normalized; object keys on rb_hash + rb_eql and
41
81
  # fixlen on a byte-hash + memcmp, both reproducing Ruby Hash distinctness.
@@ -3,7 +3,7 @@ class CArray
3
3
  # @overload value_counts(sort: false)
4
4
  # Returns `[values, counts]`, the distinct values of `self` paired
5
5
  # with the number of times each occurs. `values` is a 1-D CArray of
6
- # `self`'s dtype; `counts` is a 1-D `CA_INT64` where `counts[i]` is
6
+ # `self`'s data type; `counts` is a 1-D `CA_INT64` where `counts[i]` is
7
7
  # the number of occurrences of `values[i]`. This is the frequency-
8
8
  # table member of the value-hash discovery family ({#unique},
9
9
  # {#mask_duplicates}, {#nunique}); like {#unique} it always
@@ -39,7 +39,7 @@ class CArray
39
39
  raise ArgumentError, "value_counts: sort must be false, :count, or :value"
40
40
  end
41
41
  # Single-pass frequency-table hash (C __value_counts_flat__), one lane per
42
- # dtype family: integer widens to a 64-bit key; float uses the bitwise key
42
+ # data type family: integer widens to a 64-bit key; float uses the bitwise key
43
43
  # with all NaN collapsed and -0.0 / +0.0 normalized; object keys on rb_hash +
44
44
  # rb_eql and fixlen on a byte-hash + memcmp, both reproducing Ruby Hash
45
45
  # distinctness. Masked cells are skipped in the kernel.
data/lib/carray/rng.rb ADDED
@@ -0,0 +1,86 @@
1
+ class CArray
2
+
3
+ # A random number generator with its own state.
4
+ #
5
+ # `Rng` rather than `Random`: a `CArray::Random` would shadow `::Random`
6
+ # for every bare `Random` written inside `class CArray`, and the two mean
7
+ # different generators -- `random!(rng: Random.new(4))` draws through
8
+ # Ruby's MT19937 and `random!(rng: CArray::Rng.new(seed: 4))` through the
9
+ # one below. Two names that different should not look alike.
10
+ #
11
+ # The class itself is defined in ext/carray_random.c, which is where the
12
+ # state is advanced. What is added here is the generator's source text,
13
+ # so that another gem can run the same code rather than a second
14
+ # implementation of it.
15
+ class Rng
16
+
17
+ # The generators this carray knows, and how many int64 cells each one
18
+ # keeps its state in.
19
+ STATE_CELLS = {
20
+ :xoshiro256pp => 4,
21
+ }.freeze
22
+
23
+ GENERATORS = STATE_CELLS.keys.freeze
24
+
25
+ # Where a generator's C lives. Shipped: the gemspec takes ext/*.h,
26
+ # and `ext` is a require path, so this resolves in a checkout and in
27
+ # an installed gem alike -- lib/ and ext/ are siblings in both.
28
+ SOURCE_FILES = {
29
+ :xoshiro256pp => File.expand_path("../../ext/ca_rng_xoshiro256pp.h",
30
+ __dir__),
31
+ }.freeze
32
+
33
+ # The C each generator is, as text.
34
+ #
35
+ # CArray::Rng::SOURCE[:xoshiro256pp] #=> "/* ---- ... */\n..."
36
+ #
37
+ # This is the same text the extension compiled -- the file below is
38
+ # `#include`d by ext/carray_random.c -- which is the point of handing
39
+ # it out. A caller that pastes it into a translation unit of its own
40
+ # gets a generator that continues a sequence this one started, because
41
+ # it is running the code this one ran and not a copy of it.
42
+ #
43
+ # carray-jit is the caller this exists for: a kernel's `random(rng:)`
44
+ # pastes the text beside the helpers its own compiler emits, so a
45
+ # kernel draws from the CArray::Rng it was handed. The text needs
46
+ # nothing but <stdint.h> and defines only `static inline` functions
47
+ # under a `ca_` prefix.
48
+ #
49
+ # It is read once, when this file is first required.
50
+ SOURCE = SOURCE_FILES.transform_values { |path|
51
+ File.read(path, :encoding => "UTF-8")
52
+ }.freeze
53
+
54
+ # What every generator's text needs beside it, and what none of them
55
+ # owns: turning uniforms into a normal is the same arithmetic whichever
56
+ # generator the uniforms came from.
57
+ #
58
+ # Handed out apart from SOURCE because whoever pastes it pastes it once,
59
+ # however many generators are drawing. Folded into each generator's own
60
+ # text instead, two generators in one translation unit would define it
61
+ # twice.
62
+ COMMON_SOURCE = File.read(
63
+ File.expand_path("../../ext/ca_rng_normal.h", __dir__), :encoding => "UTF-8"
64
+ ).freeze
65
+
66
+ # The functions in SOURCE a caller may reach, by generator and by what
67
+ # the draw answers: `:random` a double in [0.0, 1.0), `:randomn` a
68
+ # standard normal, `:bits` the raw word a draw came from. Each takes the
69
+ # state and advances it -- `:randomn` by two draws, the other two by one.
70
+ #
71
+ # Named here rather than worked out by whoever pastes the text: which
72
+ # symbols are the entry points is a fact about the generator, and the
73
+ # generator is CArray's. These are the three `#random`, `#randomn`
74
+ # and `#bits` call, so a caller pasting the text draws the sequence
75
+ # this one draws.
76
+ DRAW_FUNCTIONS = {
77
+ :xoshiro256pp => {
78
+ :random => "ca_xoshiro256pp_next_real",
79
+ :randomn => "ca_xoshiro256pp_next_normal",
80
+ :bits => "ca_xoshiro256pp_next",
81
+ }.freeze,
82
+ }.freeze
83
+
84
+ end
85
+
86
+ end
@@ -87,22 +87,3 @@ class CArray
87
87
 
88
88
  end
89
89
 
90
- class CAUnboundRepeat
91
- # Overrides {CArray#template} for unbound-repeat views.
92
- #
93
- # The C-level `CArray#template` allocates an entity sized to `self.shape`,
94
- # which for a CAUnboundRepeat would include the inflated unbound (`:*`)
95
- # axes — semantically wrong (unbound axes are not yet bound to a size)
96
- # and a memory bomb on broadcast patterns. Required for correctness.
97
- #
98
- # This override allocates at the **parent's compact shape** instead, then
99
- # rewraps the result with the same `spec` so the returned view preserves
100
- # the unbound broadcast pattern.
101
- #
102
- # @param argv [Array] forwarded to {CArray#template} (data_type, bytes:, ...)
103
- # @yield forwarded to {CArray#template}
104
- # @return [CAUnboundRepeat] new entity wrapped with the original unbound spec
105
- def template (*argv, &block)
106
- return parent.template(*argv,&block)[*spec.map{|x| x != :* ? nil : x}]
107
- end
108
- end
@@ -90,22 +90,76 @@ class CASlabIterator < CAIterator
90
90
  # A per-slab reduction that folds each slab to one value is exactly the core
91
91
  # per-axis reduction over the slab axes, so every reduction is a direct
92
92
  # delegation to `reference.<op>(axis: slab_axes)`. This inherits the core
93
- # dtype, mask, empty/all-masked (identity vs UNDEF) and epsilon-close
93
+ # data type, mask, empty/all-masked (identity vs UNDEF) and epsilon-close
94
94
  # contracts unchanged -- there is no separate slab reduction kernel. (Unlike
95
95
  # the map / reduce block surface, these are mask-aware: they route through the
96
96
  # core reduction, which handles masked sources.)
97
97
 
98
- # @overload sum
99
- # Per-slab sum, delegating to `reference.sum(axis: slab_axes)`.
100
- # @return [CArray] one value per slab (shape = self.dim minus the slab axes)
101
- # The rest are analogous: prod / mean / min / max, sample and population
102
- # variance / stddev, all / any, fused minmax ([min, max] pair), the axis-local
103
- # position min_index / max_index (index within the slab axes), and the flat
104
- # source address min_addr / max_addr (which source cell holds the extremum).
105
- [:sum, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
98
+ # Each reduction below delegates to `reference.<op>(axis: slab_axes)`, so it
99
+ # inherits the core data type, mask, empty / all-masked and epsilon-close
100
+ # contracts unchanged. See {CAIterator} for what each one computes.
101
+ #
102
+ # @!method sum
103
+ # Per-slab sum.
104
+ # @return [CArray] one value per slab (shape = {#shape}).
105
+ # @!method accumulate
106
+ # Per-slab sum kept in the source's own data type, wrapping at its width,
107
+ # as the core `accumulate` does -- `sum` answers in the type the core
108
+ # promotes to (float64 for integers).
109
+ # @return [CArray] one value per slab
110
+ # @!method prod
111
+ # Per-slab product.
112
+ # @return [CArray] one value per slab
113
+ # @!method mean
114
+ # Per-slab arithmetic mean.
115
+ # @return [CArray] one value per slab
116
+ # @!method min
117
+ # Per-slab minimum.
118
+ # @return [CArray] one value per slab
119
+ # @!method max
120
+ # Per-slab maximum.
121
+ # @return [CArray] one value per slab
122
+ # @!method variance
123
+ # Per-slab sample variance (divisor `n - 1`).
124
+ # @return [CArray] one value per slab
125
+ # @!method stddev
126
+ # Per-slab sample standard deviation (divisor `n - 1`).
127
+ # @return [CArray] one value per slab
128
+ # @!method variancep
129
+ # Per-slab population variance (divisor `n`).
130
+ # @return [CArray] one value per slab
131
+ # @!method stddevp
132
+ # Per-slab population standard deviation (divisor `n`).
133
+ # @return [CArray] one value per slab
134
+ # @!method all
135
+ # Whether every cell of each slab is true.
136
+ # @return [CArray] `:boolean`, one value per slab
137
+ # @!method any
138
+ # Whether any cell of each slab is true.
139
+ # @return [CArray] `:boolean`, one value per slab
140
+ # @!method minmax
141
+ # Per-slab minimum and maximum, found in one pass.
142
+ # @return [Array<CArray>] the pair `[min, max]`
143
+ # @!method min_index
144
+ # Per-slab position of the minimum, local to the slab axes.
145
+ # @return [CArray] one index per slab
146
+ # @!method max_index
147
+ # Per-slab position of the maximum, local to the slab axes.
148
+ # @return [CArray] one index per slab
149
+ # @!method min_addr
150
+ # Per-slab flat source address of the minimum -- which source cell holds it.
151
+ # @return [CArray] one flat address per slab
152
+ # @!method max_addr
153
+ # Per-slab flat source address of the maximum.
154
+ # @return [CArray] one flat address per slab
155
+ [:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
106
156
  :variancep, :stddevp, :minmax, :min_index, :max_index,
107
157
  :min_addr, :max_addr].each do |op|
108
- define_method(op) { @reference.send(op, axis: @slab_axes) }
158
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
159
+ def #{op}
160
+ @reference.#{op}(axis: @slab_axes)
161
+ end
162
+ RUBY
109
163
  end
110
164
 
111
165
  # @overload count(v = <none>)
@@ -243,7 +297,7 @@ class CASlabIterator < CAIterator
243
297
  #
244
298
  # A per-slab running accumulation along the slab axis is exactly the core
245
299
  # per-axis cumulative over that axis, so every scan delegates to
246
- # `reference.<op>(axis: slab_axis)`, inheriting the core dtype / mask (masked
300
+ # `reference.<op>(axis: slab_axis)`, inheriting the core data type / mask (masked
247
301
  # cells hold the running total, output unmasked) contracts unchanged. Each
248
302
  # cell is in exactly one slab (a partition), so the running value is
249
303
  # well-defined; the surface is uniform with the family even though it
@@ -251,23 +305,27 @@ class CASlabIterator < CAIterator
251
305
  # multi-axis running accumulation is ambiguous), so a multi-axis slab raises --
252
306
  # scan one axis at a time, like the order surface.
253
307
 
254
- # @overload cumsum
308
+ # @!method cumsum
255
309
  # Per-slab inclusive running sum (float64), reference-shaped.
256
310
  # @return [CArray]
257
- # @overload cumprod
311
+ # @!method cumprod
258
312
  # Per-slab inclusive running product (float64), reference-shaped.
259
313
  # @return [CArray]
260
- # @overload cummax
261
- # Per-slab inclusive running maximum (reference dtype), reference-shaped.
314
+ # @!method cummax
315
+ # Per-slab inclusive running maximum (reference data type), reference-shaped.
262
316
  # @return [CArray]
263
- # @overload cummin
264
- # Per-slab inclusive running minimum (reference dtype), reference-shaped.
317
+ # @!method cummin
318
+ # Per-slab inclusive running minimum (reference data type), reference-shaped.
265
319
  # @return [CArray]
266
- # @overload cumcount
320
+ # @!method cumcount
267
321
  # Per-slab running count of present cells (int64), reference-shaped.
268
322
  # @return [CArray]
269
323
  [:cumsum, :cumprod, :cummax, :cummin, :cumcount].each do |op|
270
- define_method(op) { @reference.send(op, axis: single_scan_axis(op)) }
324
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
325
+ def #{op}
326
+ @reference.#{op}(axis: single_scan_axis(:#{op}))
327
+ end
328
+ RUBY
271
329
  end
272
330
 
273
331
  private
@@ -26,18 +26,18 @@ class CArray
26
26
  # Boolean result -> :boolean CArray
27
27
  module StringOperationMixin
28
28
 
29
- # Per-cell map through the Face's scalar fetch. `dtype` nil returns a
30
- # CAString (string results); an explicit dtype returns a plain typed array
29
+ # Per-cell map through the Face's scalar fetch. A nil `data_type` returns a
30
+ # CAString (string results); an explicit one returns a plain typed array
31
31
  # (integer / boolean results). Shape and mask are carried.
32
- def string_map (dtype = nil) # :nodoc:
33
- out = dtype ? template(dtype) : CArray.object(*shape)
32
+ def string_map (data_type = nil) # :nodoc:
33
+ out = data_type ? template(data_type) : CArray.object(*shape)
34
34
  if has_mask?
35
35
  m = is_masked
36
36
  elements.times { |i| out[i] = (m[i] ? UNDEF : yield(self[i])) }
37
37
  else
38
38
  elements.times { |i| out[i] = yield(self[i]) }
39
39
  end
40
- dtype ? out : CAString.wrap(out)
40
+ data_type ? out : CAString.wrap(out)
41
41
  end
42
42
  private :string_map
43
43
 
@@ -229,7 +229,11 @@ class CArray
229
229
  else
230
230
  values = Array.new(elements) { |i| self[i] }
231
231
  end
232
- CArray.const_string(values, encoding: encoding)
232
+ out = CArray.const_string(values, encoding: encoding)
233
+ # The builder takes a flat list, so give the shape back -- as
234
+ # to_fixlen_string above already does. Without this an N-D column
235
+ # came back 1-D.
236
+ ndim > 1 ? out.reshape(*shape) : out
233
237
  end
234
238
 
235
239
  # In-place transforms, mixed into the mutable Faces only (CAString /