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
@@ -0,0 +1,123 @@
1
+ # ---------------------------------------------------------------------------
2
+ # Reading `CArray.fuse { a + b * c }`.
3
+ #
4
+ # The block is not called. Its `a` is the array itself, so calling it would
5
+ # evaluate the expression eagerly -- which is the thing fuse exists to avoid.
6
+ # The source is read instead, every name that holds a CArray is given `.lazy`,
7
+ # and the result is evaluated back in the block's own binding, so `self`,
8
+ # instance variables, methods and constants are what they were.
9
+ #
10
+ # Ruby has no macro, so the alternative was to pass the arrays in and take
11
+ # shadows back -- `fuse(a, b) { |x, y| ... }` -- which names each of them
12
+ # twice. Julia writes `@.` for the same reason and does the same thing to
13
+ # the expression underneath.
14
+ # ---------------------------------------------------------------------------
15
+
16
+ require "prism"
17
+
18
+ class CArray
19
+
20
+ module FuseSource
21
+
22
+ # Runtime coercion, so the rewrite never has to work out what a name
23
+ # holds: anything that is not an array passes through untouched.
24
+ def self.shadow (value)
25
+ value.is_a?(CArray) ? value.lazy : value
26
+ end
27
+
28
+ def self.evaluate (block)
29
+ result = eval(rewrite(body_source(block)), block.binding,
30
+ *block.source_location)
31
+ # An expression that is just an array is that array; the shadow put
32
+ # around it has nothing to fuse.
33
+ result.is_a?(CALazyMarker) ? result.parent : result
34
+ end
35
+
36
+ # -- the block's own text ---------------------------------------------
37
+
38
+ def self.body_source (block)
39
+ text = extract(block)
40
+ wrapped = "proc " + text
41
+ node = Prism.parse(wrapped).value
42
+ .breadth_first_search { |n| n.is_a?(Prism::BlockNode) }
43
+ inner = node && node.body
44
+ unless inner
45
+ raise ArgumentError,
46
+ "CArray.fuse could not read an expression out of this block"
47
+ end
48
+ wrapped.byteslice(inner.location.start_offset...inner.location.end_offset)
49
+ end
50
+
51
+ def self.extract (block)
52
+ sequence = RubyVM::InstructionSequence.of(block) rescue nil
53
+ location = sequence && sequence.to_a[4][:code_location]
54
+ path = sequence && (sequence.absolute_path || sequence.path)
55
+ unless location && path && File.readable?(path)
56
+ raise ArgumentError,
57
+ "CArray.fuse cannot read this block's source (defined in irb, " \
58
+ "eval, or a file that is no longer there). Write `.lazy` on " \
59
+ "the operands instead: `a.lazy + b.lazy`."
60
+ end
61
+ lines = File.readlines(path)
62
+ first_line, first_column, last_line, last_column = location
63
+ # The columns count bytes, not characters, so a line with anything
64
+ # multi-byte on it slices in the wrong place unless this does too.
65
+ if first_line == last_line
66
+ lines[first_line - 1].byteslice(first_column...last_column)
67
+ else
68
+ [lines[first_line - 1].byteslice(first_column..),
69
+ *lines[first_line...(last_line - 1)],
70
+ lines[last_line - 1].byteslice(0...last_column)].join
71
+ end
72
+ end
73
+
74
+ # -- the rewrite -------------------------------------------------------
75
+
76
+ # The leaves are the names being read. Everything else keeps its shape:
77
+ # calls are inserted around leaves and the expression they sit in is
78
+ # left alone.
79
+ class Leaves < Prism::Visitor
80
+ attr_reader :spots
81
+
82
+ def initialize
83
+ @spots = []
84
+ end
85
+
86
+ def visit_local_variable_read_node (node) = mark(node)
87
+ def visit_instance_variable_read_node (node) = mark(node)
88
+ def visit_constant_read_node (node) = mark(node)
89
+
90
+ # `Math::PI` is one name, not `Math` with something after it.
91
+ def visit_constant_path_node (node)
92
+ mark(node)
93
+ end
94
+
95
+ def visit_call_node (node)
96
+ if node.name == :[] || node.name == :[]=
97
+ # An index is a position, not a value to fuse: `a[i]` shadows `a`
98
+ # and leaves `i` alone.
99
+ visit(node.receiver)
100
+ return
101
+ end
102
+ mark(node) if node.receiver.nil? && node.arguments.nil? && node.block.nil?
103
+ super
104
+ end
105
+
106
+ private
107
+
108
+ def mark (node)
109
+ @spots << [node.location.start_offset, node.location.end_offset]
110
+ end
111
+ end
112
+
113
+ def self.rewrite (source)
114
+ visitor = Leaves.new
115
+ Prism.parse(source).value.accept(visitor)
116
+ out = source.dup
117
+ visitor.spots.sort_by { |start, _| -start }.each do |start, stop|
118
+ out[start...stop] = "::CArray::FuseSource.shadow(#{source[start...stop]})"
119
+ end
120
+ out
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,218 @@
1
+ # ---------------------------------------------------------------------------
2
+ # Turning a lazy expression into a plan a compiler can read.
3
+ #
4
+ # A lazy view is already a typed, closed expression graph, and the kernels
5
+ # already carry the C that computes each operation (CArray.__kernel_body__).
6
+ # What is missing between them is the reading: which operation each node is,
7
+ # what its mask does, and where the leaves are. That is what a plan holds.
8
+ #
9
+ # Nothing here compiles anything. A plan is plain data, and CArray itself
10
+ # never needs one -- it can always walk the view. What a plan is for is a
11
+ # second evaluator: hand it to one, and the answer must be the same.
12
+ # ---------------------------------------------------------------------------
13
+
14
+ class CArray
15
+
16
+ module Fusion
17
+
18
+ # One value per node, in evaluation order; the last is the result.
19
+ #
20
+ # Op an operation, reading the nodes named in `args`
21
+ # Leaf an array, the `index`-th of plan.leaves
22
+ # Const a scalar written into the expression
23
+ Op = Struct.new(:kind, :name, :data_type, :args, :body, :mask, :trapping)
24
+ Leaf = Struct.new(:index, :data_type, :masked)
25
+ Const = Struct.new(:value, :data_type)
26
+
27
+ Plan = Struct.new(:nodes, :leaves, :data_type, :dim, :masked, :signature)
28
+
29
+ LAZY_CLASSES = [CAMonOp, CABinOp, CATriOp, CAMonCmp, CABinCmp, CALazyMarker]
30
+
31
+ # A lazy node names its operation by an id and the kernels name it by a
32
+ # symbol. These are the same operations, spelled the way each side
33
+ # spells them; the rest are spelled alike.
34
+ BINOP_NAMES = {
35
+ :+ => :add, :- => :sub, :* => :mul,
36
+ :/ => :div, :** => :power, :% => :mod,
37
+ :& => :bit_and_i, :| => :bit_or_i, :^ => :bit_xor_i,
38
+ :<< => :bit_lshift, :>> => :bit_rshift,
39
+ }.freeze
40
+ TRIOP_NAMES = { :__clip_ki__ => :clip }.freeze
41
+
42
+ MONOP_BY_ID = CArray::LAZY_MONOP_OP_IDS.invert.freeze
43
+ BINOP_BY_ID = CArray::LAZY_BINOP_OP_IDS.invert.freeze
44
+ TRIOP_BY_ID = CArray::LAZY_TRIOP_OP_IDS.invert.freeze
45
+
46
+ # Integer division and its relatives raise on a zero divisor, so a cell
47
+ # the mask excludes must not be computed at all -- the divisor there is
48
+ # nobody's business (ca_binop_dispatch.c).
49
+ TRAPPING = %i[div mod quo_i fmod].freeze
50
+ INTEGERS = %i[int8 int16 int32 int64 uint8 uint16 uint32 uint64].freeze
51
+
52
+ class Refused < StandardError; end
53
+
54
+ # ---- who computes a plan --------------------------------------------
55
+ #
56
+ # CArray can always walk the expression, so nothing has to be registered
57
+ # and nothing changes when nothing is. What a registered evaluator adds
58
+ # is a second way to arrive at the same answer; it is asked, and it may
59
+ # decline. The dispatch point stays on CArray's side, which is what
60
+ # keeps the threshold below a decision about CArray's own walk rather
61
+ # than one that moves with whatever is installed.
62
+ #
63
+ # The evaluator itself is held by CArray (see carray/lazy.rb), so that
64
+ # materialising an expression need not reach for this file at all until
65
+ # something has been registered.
66
+
67
+ # Reaching a compiled kernel costs about the same whatever the array's
68
+ # size, and what it buys is the passes the walk would make. Below this
69
+ # the walk is the faster answer. The crossing moves with how wide the
70
+ # expression is -- measured, thirty thousand cells at one operation, six
71
+ # thousand at six -- and this brackets those: a one-operation expression
72
+ # loses a couple of microseconds here, a six-operation one wins ten.
73
+ THRESHOLD = 10_000
74
+
75
+ # Returns the array, or nil where nothing computed it.
76
+ def self.evaluate (view)
77
+ return nil unless askable?(view)
78
+ out = CArray.__alloc_uninit__(view.data_type, view.dim)
79
+ evaluate_into(view, out) ? out : nil
80
+ end
81
+
82
+ # Fills an array the caller already has. Called from the store as well,
83
+ # where making one and copying it over would be most of the work.
84
+ # Returns true when something computed it.
85
+ def self.evaluate_into (view, out)
86
+ evaluator = CArray.expression_evaluator or return false
87
+ return false unless askable?(view)
88
+ plan = plan(view) or return false
89
+ # A marker over an array, or anything else with nothing to compute,
90
+ # is not worth handing over.
91
+ return false unless plan.nodes.any? { |n| n.is_a?(Op) }
92
+ out.mask = 0 if plan.masked && ! out.has_mask?
93
+ evaluator.call(plan, out) ? true : false
94
+ rescue StandardError => error
95
+ CArray.expression_evaluator = nil
96
+ warn "CArray: the registered expression evaluator raised " \
97
+ "(#{error.class}: #{error.message}); expressions will be walked " \
98
+ "from here on"
99
+ false
100
+ end
101
+
102
+ def self.askable? (view)
103
+ ! CArray.expression_evaluator.nil? && view.elements >= THRESHOLD
104
+ end
105
+
106
+ # Returns a Plan, or nil where the expression holds something a plan
107
+ # cannot describe. Refusing is ordinary: the caller walks instead.
108
+ def self.plan (view)
109
+ build(view)
110
+ rescue Refused
111
+ nil
112
+ end
113
+
114
+ def self.build (view)
115
+ raise Refused, "not a lazy expression" unless lazy?(view)
116
+ w = Walk.new
117
+ w.visit(view)
118
+ Plan.new(w.nodes, w.leaves, view.data_type, view.dim,
119
+ w.leaves.any? { |a| a.has_mask? }, w.signature)
120
+ end
121
+
122
+ def self.lazy? (x)
123
+ LAZY_CLASSES.any? { |k| x.is_a?(k) }
124
+ end
125
+
126
+ # ---- the walk -------------------------------------------------------
127
+
128
+ class Walk
129
+ attr_reader :nodes, :leaves, :signature
130
+
131
+ def initialize
132
+ @nodes = []
133
+ @leaves = []
134
+ @seen = {}
135
+ @signature = +""
136
+ end
137
+
138
+ def visit (n)
139
+ @seen[n.object_id] ||= build(n)
140
+ end
141
+
142
+ private
143
+
144
+ def build (n)
145
+ case n
146
+ when CALazyMarker then visit(n.parent)
147
+ when CAMonOp then unary(n)
148
+ when CABinOp then binary(n)
149
+ when CATriOp then ternary(n)
150
+ when CScalar then constant(n)
151
+ when CArray then leaf(n)
152
+ else raise Refused, "#{n.class} in an expression"
153
+ end
154
+ end
155
+
156
+ def unary (n)
157
+ name = spell(MONOP_BY_ID, n.__op_id__, {})
158
+ args = [visit(n.parent)]
159
+ # A view over one array is masked exactly where that array is
160
+ # (ca_obj_monop.c).
161
+ op(:monop, name, n.data_type, args, :pass)
162
+ end
163
+
164
+ def binary (n)
165
+ name = spell(BINOP_BY_ID, n.__op_id__, BINOP_NAMES)
166
+ args = [visit(n.parent), visit(n.__binop_right__)]
167
+ # Boolean `&` and `|` are three-valued: a masked cell whose known
168
+ # side settles the answer comes back unmasked (ca_obj_binop.c).
169
+ rule = if n.data_type == :boolean && name == :bit_or_i then :kleene_or
170
+ elsif n.data_type == :boolean && name == :bit_and_i then :kleene_and
171
+ else :union
172
+ end
173
+ op(:binop, name, n.data_type, args, rule)
174
+ end
175
+
176
+ def ternary (n)
177
+ name = spell(TRIOP_BY_ID, n.__op_id__, TRIOP_NAMES)
178
+ args = [visit(n.parent), visit(n.__triop_op2__), visit(n.__triop_op3__)]
179
+ op(:triop, name, n.data_type, args, :union)
180
+ end
181
+
182
+ def op (kind, name, type, args, mask)
183
+ body = CArray.__kernel_body__(kind, name, type) or
184
+ raise Refused, "#{kind} #{name} has no body at #{type}"
185
+ note(kind.to_s[0], name, type)
186
+ push Op.new(kind, name, type, args, body, mask,
187
+ TRAPPING.include?(name) && INTEGERS.include?(type))
188
+ end
189
+
190
+ def leaf (n)
191
+ note("a", n.data_type, n.has_mask? ? 1 : 0)
192
+ @leaves << n
193
+ push Leaf.new(@leaves.size - 1, n.data_type, n.has_mask?)
194
+ end
195
+
196
+ def constant (n)
197
+ note("k", n[0], n.data_type)
198
+ push Const.new(n[0], n.data_type)
199
+ end
200
+
201
+ def push (node)
202
+ @nodes << node
203
+ @nodes.size - 1
204
+ end
205
+
206
+ def spell (table, id, renames)
207
+ ruby = table[id] or raise Refused, "operation id #{id}"
208
+ renames.fetch(ruby, ruby)
209
+ end
210
+
211
+ # Two expressions of the same shape compute alike, whatever arrays
212
+ # they are over, so a consumer can keep one compiled kernel for both.
213
+ def note (*parts)
214
+ @signature << parts.join(":") << ";"
215
+ end
216
+ end
217
+ end
218
+ end
@@ -94,8 +94,8 @@
94
94
  # Each sample contributes `w[i]` instead of 1 to its target cell.
95
95
  #
96
96
  # * **weights.shape** = chunk.shape minus the channel axis (= fiber + (A,)).
97
- # * **dtype** is locked at construction (= the entry method's `weights:` kwarg
98
- # fixes weighted vs unweighted; subsequent adds must match). Counts dtype:
97
+ # * **The data type** is locked at construction (= the entry method's `weights:` kwarg
98
+ # fixes weighted vs unweighted; subsequent adds must match). Counts type:
99
99
  # int64 unweighted, float64 weighted. Weighted counts are always float64:
100
100
  # the fused scatter kernel requires float64 weights, so integer weights are
101
101
  # taken as float64 (= integer weighted counts are not supported).
@@ -118,7 +118,7 @@
118
118
  # ### Composition
119
119
  #
120
120
  # `h1 + h2` returns a new Histogram with cells summed elementwise. Both
121
- # operands must agree on edges / fiber_shape / include_max / weighted dtype
121
+ # operands must agree on edges / fiber_shape / include_max / weighted data type
122
122
  # (= the structure-level semantic guard); cells themselves are just
123
123
  # integer / float tallies. See the `+` method.
124
124
  #
@@ -135,7 +135,7 @@ class CArray
135
135
  # {BincountND} instead.
136
136
  class Histogram
137
137
 
138
- # @overload initialize(edges:, fiber_shape: [], include_max: false, weights_dtype: nil)
138
+ # @overload initialize(edges:, fiber_shape: [], include_max: false, weights_data_type: nil)
139
139
  # Allocates a new histogram accumulator.
140
140
  # @param edges [Array<CArray, Array<Numeric>>] one edges array
141
141
  # per histogram dimension; each must be 1-D sorted ascending
@@ -145,11 +145,11 @@ class CArray
145
145
  # @param include_max [Boolean, Array<Boolean>] whether values
146
146
  # equal to the last edge fold into the last bin; a scalar
147
147
  # broadcasts across dimensions.
148
- # @param weights_dtype [Symbol, nil] `data_type` of the
148
+ # @param weights_data_type [Symbol, nil] `data_type` of the
149
149
  # accumulator when weighted; `nil` for a count-only
150
150
  # accumulator (int64 counts).
151
151
  # @return [Histogram]
152
- def initialize (edges:, fiber_shape: [], include_max: false, weights_dtype: nil)
152
+ def initialize (edges:, fiber_shape: [], include_max: false, weights_data_type: nil)
153
153
  @edges_list = edges.map { |e| CArray.wrap_readonly(e, :float64) }
154
154
  raise ArgumentError, "edges must be a non-empty list" if @edges_list.empty?
155
155
  @edges_list.each_with_index do |e, k|
@@ -166,11 +166,11 @@ class CArray
166
166
  else
167
167
  [!!include_max] * @m
168
168
  end
169
- @weighted = !weights_dtype.nil?
170
- @counts_dtype = @weighted ? weights_dtype : :int64
169
+ @weighted = !weights_data_type.nil?
170
+ @counts_data_type = @weighted ? weights_data_type : :int64
171
171
  ext_dims = @n_list.map { |n| n + 2 }
172
172
  ext_shape = @fiber_shape + ext_dims
173
- @full_counts = CArray.public_send(@counts_dtype, *ext_shape).fill(0)
173
+ @full_counts = CArray.public_send(@counts_data_type, *ext_shape).fill(0)
174
174
  @sample_axis = nil
175
175
  @channel_axis = nil
176
176
  end
@@ -321,7 +321,7 @@ class CArray
321
321
 
322
322
  if weights
323
323
  raise ArgumentError, "weights given but accumulator is unweighted" unless @weighted
324
- weights = CArray.wrap_readonly(weights, @counts_dtype)
324
+ weights = CArray.wrap_readonly(weights, @counts_data_type)
325
325
  expected_w_shape = chunk.shape.dup
326
326
  expected_w_shape.delete_at(channel_ax)
327
327
  unless weights.shape == expected_w_shape
@@ -379,7 +379,7 @@ class CArray
379
379
  edges: @edges_list,
380
380
  fiber_shape: @fiber_shape,
381
381
  include_max: @include_max,
382
- weights_dtype: @weighted ? @counts_dtype : nil)
382
+ weights_data_type: @weighted ? @counts_data_type : nil)
383
383
  rf = result.instance_variable_get(:@full_counts)
384
384
  rf[] = @full_counts + other.full_counts
385
385
  result.instance_variable_set(:@sample_axis, @sample_axis)
@@ -401,7 +401,7 @@ class CArray
401
401
  # arr.shape = fiber_shape + (last M bin axes).
402
402
  # Reduce along the last M axes, returns shape fiber_shape (or scalar).
403
403
  #
404
- # `accumulate` preserves dtype (= int64 stays int64, float64 stays float64),
404
+ # `accumulate` preserves the data type (= int64 stays int64, float64 stays float64),
405
405
  # unlike `sum` which always lifts to float64. Caveat: int64 overflows at
406
406
  # ~9.2e18 (silent wrap); weighted float64 loses precision past 2^53 but
407
407
  # does not overflow. Realistic histograms do not hit these limits.
@@ -497,15 +497,15 @@ class CArray
497
497
  [sample_ax, channel_ax].sort.reverse.each { |p| fiber_shape.delete_at(p) }
498
498
 
499
499
  # Weighted counts are float64-only (the fused scatter kernel requires
500
- # float64 weights and float64 counts), so the dtype is fixed here rather
501
- # than derived from the weights' own dtype.
502
- weights_dtype = (:float64 if weights)
500
+ # float64 weights and float64 counts), so the type is fixed here rather
501
+ # than derived from the weights' own type.
502
+ weights_data_type = (:float64 if weights)
503
503
 
504
504
  h = Histogram.send(:new,
505
505
  edges: edges,
506
506
  fiber_shape: fiber_shape,
507
507
  include_max: include_max,
508
- weights_dtype: weights_dtype)
508
+ weights_data_type: weights_data_type)
509
509
  h.add(arr, axis: axis, weights: weights)
510
510
  h
511
511
  end
@@ -49,13 +49,7 @@ class CArray::Inspector # :nodoc:
49
49
  end
50
50
 
51
51
  def get_shape
52
- case @carray.obj_type
53
- when CA_OBJ_UNBOUND_REPEAT
54
- dim = @carray.spec
55
- else
56
- dim = @carray.shape
57
- end
58
- return dim
52
+ return @carray.shape
59
53
  end
60
54
 
61
55
  def get_info_list
@@ -14,8 +14,8 @@
14
14
  # those are NOT part of the common contract because some members legitimately
15
15
  # omit them (a window has no map, a group has no within-piece min_index).
16
16
  #
17
- # The retired 2.0 generic dispatch (calculate / filter / evaluate over a
18
- # kernel_at_addr slot) is preserved at `samples/caiterator/iterator.{rb,c}`.
17
+ # The 2.0 generic dispatch (calculate / filter / evaluate over a
18
+ # kernel_at_addr slot) was retired in 3.0.
19
19
  #
20
20
  # ----------------------------------------------------------------------------
21
21
 
@@ -37,7 +37,8 @@ class CAIterator
37
37
  # A member that genuinely cannot provide one overrides it to raise with its
38
38
  # own reason.
39
39
  [
40
- :sum, :prod, :mean, :min, :max, :variance, :stddev, :all, :any, # tier 1
40
+ :sum, :accumulate, :prod, :mean, :min, :max, # tier 1
41
+ :variance, :stddev, :all, :any,
41
42
  :variancep, :stddevp, :minmax, # tier 2
42
43
  :min_index, :max_index, :min_addr, :max_addr, # position
43
44
  :wsum, :wmean, # weighted