carray 3.0.4 → 3.0.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +205 -0
- data/carray.gemspec +1 -1
- data/ext/ca_array_pool.c +25 -1
- data/ext/ca_axis_dispatch.c +13 -20
- data/ext/ca_categorical_iterator.c +27 -20
- data/ext/ca_compare.c +4 -4
- data/ext/ca_composite_dispatch.c +9 -9
- data/ext/ca_kernel_iterator.c +6 -6
- data/ext/ca_kernel_iterator.h +112 -83
- data/ext/ca_obj_array.c +69 -19
- data/ext/ca_obj_bincmp.c +1 -0
- data/ext/ca_obj_binop.c +1 -0
- data/ext/ca_obj_bitarray.c +5 -1
- data/ext/ca_obj_bitfield.c +58 -43
- data/ext/ca_obj_byte_swap.c +2 -1
- data/ext/ca_obj_const_string.c +15 -3
- data/ext/ca_obj_face.c +42 -1
- data/ext/ca_obj_field.c +13 -3
- data/ext/ca_obj_grid.c +52 -5
- data/ext/ca_obj_monop.c +46 -10
- data/ext/ca_obj_object.c +64 -12
- data/ext/ca_obj_record.c +3 -0
- data/ext/ca_obj_reduce.c +4 -0
- data/ext/ca_obj_refer.c +18 -9
- data/ext/ca_obj_roll.c +26 -6
- data/ext/ca_obj_select.c +34 -5
- data/ext/ca_obj_shift.c +31 -7
- data/ext/ca_obj_stack.c +1 -0
- data/ext/ca_obj_stride.c +28 -19
- data/ext/ca_obj_string.c +11 -2
- data/ext/ca_obj_tile.c +27 -7
- data/ext/ca_obj_transpose.c +8 -4
- data/ext/ca_obj_triop.c +3 -0
- data/ext/ca_obj_window.c +79 -50
- data/ext/ca_op_ipower.c +4 -4
- data/ext/ca_sort_kernels.h +6 -3
- data/ext/carray.h +33 -0
- data/ext/carray_access.c +16 -3
- data/ext/carray_attribute.c +24 -0
- data/ext/carray_cast.c +243 -82
- data/ext/carray_cast_func.rb +56 -26
- data/ext/carray_conversion.c +2 -2
- data/ext/carray_core.c +3 -0
- data/ext/carray_count.c +6 -0
- data/ext/carray_element.c +9 -4
- data/ext/carray_factorize.c +20 -20
- data/ext/carray_generate.c +8 -2
- data/ext/carray_histogram.c +1 -1
- data/ext/carray_index_classifier.c +4 -4
- data/ext/carray_internal.h +12 -0
- data/ext/carray_kernels_bincmp.c +168 -168
- data/ext/carray_kernels_binop.c +900 -900
- data/ext/carray_kernels_init.c +4 -4
- data/ext/carray_kernels_moncmp.c +96 -96
- data/ext/carray_kernels_monop.c +1984 -1984
- data/ext/carray_kernels_reduce_aggregate.c +1733 -1733
- data/ext/carray_kernels_reduce_cumulative.c +642 -642
- data/ext/carray_kernels_reduce_extreme.c +1150 -1150
- data/ext/carray_kernels_reduce_variance.c +258 -258
- data/ext/carray_kernels_scan.c +128 -128
- data/ext/carray_kernels_search.c +340 -340
- data/ext/carray_kernels_sort.c +295 -304
- data/ext/carray_kernels_triop.c +114 -114
- data/ext/carray_key_windows.c +4 -0
- data/ext/carray_mask.c +45 -22
- data/ext/carray_memory_view.c +2 -2
- data/ext/carray_operator.c +5 -5
- data/ext/carray_order.c +227 -8
- data/ext/carray_random.c +18 -18
- data/ext/carray_scatter.c +24 -7
- data/ext/carray_slab.c +32 -9
- data/ext/carray_sort.c +224 -16
- data/ext/carray_sort_kernel.c +5 -7
- data/ext/carray_struct.c +35 -43
- data/ext/carray_time_text.c +4 -2
- data/ext/carray_utils.c +63 -21
- data/ext/mkkernel.rb +210 -115
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +17 -4
- data/lib/carray/basics.rb +27 -7
- data/lib/carray/categorical.rb +30 -16
- data/lib/carray/categorical_iterator.rb +6 -6
- data/lib/carray/const_string.rb +10 -0
- data/lib/carray/data_type_extension.rb +8 -1
- data/lib/carray/fixlen_string.rb +41 -2
- data/lib/carray/frame/column_verbs.rb +4 -3
- data/lib/carray/frame/concat.rb +17 -3
- data/lib/carray/frame/frame.rb +6 -7
- data/lib/carray/frame/group.rb +100 -13
- data/lib/carray/frame/io.rb +3 -1
- data/lib/carray/fuse_source.rb +31 -2
- data/lib/carray/lazy.rb +35 -0
- data/lib/carray/methods/is_in.rb +8 -1
- data/lib/carray/segment_iterator.rb +12 -8
- data/lib/carray/serialize.rb +19 -2
- data/lib/carray/stack.rb +15 -12
- data/lib/carray/string.rb +8 -0
- data/lib/carray/string_operation_extension.rb +13 -5
- data/lib/carray/struct_builder.rb +5 -3
- data/lib/carray/time.rb +107 -68
- data/yard-stubs/ca_obj_transpose.rb +5 -4
- data/yard-stubs/carray_attribute.rb +3 -1
- data/yard-stubs/carray_cast.rb +2 -0
- data/yard-stubs/carray_conversion.rb +5 -1
- data/yard-stubs/carray_element.rb +7 -6
- data/yard-stubs/carray_mask.rb +8 -3
- data/yard-stubs/carray_math.rb +14 -4
- data/yard-stubs/carray_order.rb +100 -4
- data/yard-stubs/carray_scatter.rb +7 -5
- metadata +1 -1
data/lib/carray/frame/group.rb
CHANGED
|
@@ -158,7 +158,7 @@ class CAFrame
|
|
|
158
158
|
g_of = nb > 0 ? occupied / nb : occupied
|
|
159
159
|
b_of = nb > 0 ? occupied % nb : occupied
|
|
160
160
|
# Each group's by values, from its first row.
|
|
161
|
-
order = gcat.
|
|
161
|
+
order = gcat.group_addr
|
|
162
162
|
first = order[CArray.segment_offsets(lengths: gcat.category_sizes)[0...ng]]
|
|
163
163
|
columns = {}
|
|
164
164
|
by_names.zip(by_cols).each do |n, col|
|
|
@@ -239,10 +239,11 @@ end
|
|
|
239
239
|
# GroupedFrame — the result of +CAFrame#group_by+ (memo §11.6, §16).
|
|
240
240
|
#
|
|
241
241
|
# Holds the grouping categorical once (grouping is per-key, computed once
|
|
242
|
-
# and shared across every column).
|
|
243
|
-
# grp["col"]
|
|
244
|
-
# aggregate
|
|
245
|
-
#
|
|
242
|
+
# and shared across every column). The surfaces:
|
|
243
|
+
# grp["col"], grp[array] -> the CArray group iterator of a column or array
|
|
244
|
+
# aggregate(spec) -> declarative per-column reductions into a new frame
|
|
245
|
+
# aggregate { |grp| } -> columns built from iterators, run once
|
|
246
|
+
# table { |g| } -> Ruby escape, g is a per-group view-frame
|
|
246
247
|
class GroupedFrame
|
|
247
248
|
include CAFrame::ColumnVerbs
|
|
248
249
|
# +key_names+ are the frame columns the grouping was keyed on. They become
|
|
@@ -285,10 +286,40 @@ class GroupedFrame
|
|
|
285
286
|
@labels.dup
|
|
286
287
|
end
|
|
287
288
|
|
|
288
|
-
#
|
|
289
|
-
|
|
289
|
+
# The CArray group iterator of a column, or of any CArray with one entry
|
|
290
|
+
# per row of the frame: a value computed from columns, or a column with
|
|
291
|
+
# some cells masked to leave them out. Its reductions answer one value
|
|
292
|
+
# per group, and take the arguments a CArray's reductions take.
|
|
293
|
+
#
|
|
294
|
+
# grp["temp"].percentile(90)
|
|
295
|
+
# grp[df["temp"] * df["w"]].sum / grp["w"].sum
|
|
296
|
+
# grp[df["temp"].mask_where(df["qc"].ne(0))].mean # without the flagged rows
|
|
297
|
+
#
|
|
298
|
+
# The array is matched to the rows by position, as a CArray key of
|
|
299
|
+
# +group_by+ is. An N-D one is grouped along its first axis; pass
|
|
300
|
+
# +axis: 0+ to its reductions to keep the trailing shape.
|
|
301
|
+
#
|
|
302
|
+
# @param source [String, CArray] a column name, or a CArray whose first
|
|
303
|
+
# axis has one entry per row.
|
|
304
|
+
# @return [CACategoricalIterator]
|
|
305
|
+
# @raise [KeyError] for a name that is no column.
|
|
306
|
+
# @raise [ArgumentError] when the CArray's first axis is not the rows.
|
|
307
|
+
def [](source)
|
|
290
308
|
refuse_changed_rows("[]")
|
|
291
|
-
|
|
309
|
+
values =
|
|
310
|
+
case source
|
|
311
|
+
when CArray
|
|
312
|
+
rows = source.ndim == 0 ? 0 : source.shape[0]
|
|
313
|
+
unless source.ndim >= 1 && rows == @cat.elements
|
|
314
|
+
raise ArgumentError,
|
|
315
|
+
"[]: the array has #{source.ndim == 0 ? "no axis" : "#{rows} rows"} " \
|
|
316
|
+
"for the frame's #{@cat.elements}"
|
|
317
|
+
end
|
|
318
|
+
source
|
|
319
|
+
else
|
|
320
|
+
@frame[@frame.__send__(:column_key, source, "[]")]
|
|
321
|
+
end
|
|
322
|
+
values.group_by_category(@cat)
|
|
292
323
|
end
|
|
293
324
|
|
|
294
325
|
# Declarative aggregation (memo §11.6). Spec maps an output column name to
|
|
@@ -304,8 +335,34 @@ class GroupedFrame
|
|
|
304
335
|
# keyword works for +table+ and the reductions below.
|
|
305
336
|
#
|
|
306
337
|
# df.group_by("station").aggregate("temp_mean" => ["temp", :mean], keep_rows: true)
|
|
307
|
-
|
|
338
|
+
#
|
|
339
|
+
# Given a block instead of a spec, aggregate yields the grouped frame once
|
|
340
|
+
# and the block returns a Hash of output name => a CArray with one entry
|
|
341
|
+
# per group. The block computes the columns with the grouped frame's
|
|
342
|
+
# reductions, +grp[...]+ iterators and CArray arithmetic, so nothing runs
|
|
343
|
+
# per group in Ruby:
|
|
344
|
+
#
|
|
345
|
+
# df.group_by("station").aggregate do |g|
|
|
346
|
+
# {
|
|
347
|
+
# "temp" => g["temp"].mean,
|
|
348
|
+
# "range" => g["temp"].max - g["temp"].min,
|
|
349
|
+
# "wmean" => g[df["temp"] * df["w"]].sum / g["w"].sum,
|
|
350
|
+
# "n" => g["temp"].group_sizes,
|
|
351
|
+
# }
|
|
352
|
+
# end
|
|
353
|
+
#
|
|
354
|
+
# +keep_rows: true+ lays each column over the rows as for a spec.
|
|
355
|
+
#
|
|
356
|
+
# @raise [ArgumentError] when both a spec and a block are given, or the
|
|
357
|
+
# block returns something other than a Hash of per-group CArrays.
|
|
358
|
+
def aggregate(spec = nil, keep_rows: false, **named, &block)
|
|
308
359
|
refuse_changed_rows("aggregate")
|
|
360
|
+
if block
|
|
361
|
+
unless spec.nil? && named.empty?
|
|
362
|
+
raise ArgumentError, "aggregate: give a spec or a block, not both"
|
|
363
|
+
end
|
|
364
|
+
return result_frame(block_columns(yield(self)), keep_rows)
|
|
365
|
+
end
|
|
309
366
|
spec = (spec || {}).merge(named)
|
|
310
367
|
cols = {}
|
|
311
368
|
spec.each do |out_name, (in_name, reduction, *args)|
|
|
@@ -364,7 +421,9 @@ class GroupedFrame
|
|
|
364
421
|
# its trailing shape: a (N, L) column gives (groups, L). Each
|
|
365
422
|
# takes +min_count:+ and +fill_value:+ as a core reduction does: a group
|
|
366
423
|
# with fewer than min_count present values is UNDEF, and fill_value fills
|
|
367
|
-
# the UNDEF cells.
|
|
424
|
+
# the UNDEF cells. One fill_value goes to every column and has to suit each
|
|
425
|
+
# (a number into a string column raises); a Hash of column name => value
|
|
426
|
+
# fills those columns only. +keep_rows: true+ gives each row its group's value
|
|
368
427
|
# instead of one row per group, as for +aggregate+.
|
|
369
428
|
#
|
|
370
429
|
# Given column names, they reduce those columns only and return them as
|
|
@@ -644,8 +703,18 @@ class GroupedFrame
|
|
|
644
703
|
# +keep_rows+.
|
|
645
704
|
private def reduce_columns(reduction, names, keep_rows, **keywords)
|
|
646
705
|
keywords = keywords.compact
|
|
647
|
-
|
|
648
|
-
|
|
706
|
+
fills = keywords.delete(:fill_value)
|
|
707
|
+
if fills.is_a?(Hash)
|
|
708
|
+
unknown = fills.keys - @frame.column_names
|
|
709
|
+
unless unknown.empty?
|
|
710
|
+
raise ArgumentError,
|
|
711
|
+
"#{reduction}: fill_value names no column #{unknown.map(&:inspect).join(', ')}"
|
|
712
|
+
end
|
|
713
|
+
end
|
|
714
|
+
out = over_named_columns(reduction, names, @key_names) do |col, name|
|
|
715
|
+
fill = fills.is_a?(Hash) ? fills[name] : fills
|
|
716
|
+
kw = fill.nil? ? keywords : keywords.merge(fill_value: fill)
|
|
717
|
+
col = group_reduce(col, reduction, **kw)
|
|
649
718
|
keep_rows && !names.empty? ? spread_to_rows(col) : col
|
|
650
719
|
end
|
|
651
720
|
return out unless names.empty?
|
|
@@ -690,6 +759,24 @@ class GroupedFrame
|
|
|
690
759
|
end
|
|
691
760
|
end
|
|
692
761
|
|
|
762
|
+
# The columns an aggregate block returned: a Hash of names to CArrays
|
|
763
|
+
# with one entry per group along their first axis.
|
|
764
|
+
private def block_columns(out)
|
|
765
|
+
unless out.is_a?(Hash)
|
|
766
|
+
raise ArgumentError, "aggregate block must return a Hash (got #{out.class})"
|
|
767
|
+
end
|
|
768
|
+
out.each_with_object({}) do |(name, col), cols|
|
|
769
|
+
name = @frame.__send__(:column_name_arg, name, "aggregate")
|
|
770
|
+
unless col.is_a?(CArray) && col.ndim >= 1 && col.shape[0] == ngroup
|
|
771
|
+
got = col.is_a?(CArray) ? "shape #{col.shape.inspect}" : col.class.to_s
|
|
772
|
+
raise ArgumentError,
|
|
773
|
+
"aggregate: #{name.inspect} must be a CArray with one value per group " \
|
|
774
|
+
"(#{ngroup}), got #{got}"
|
|
775
|
+
end
|
|
776
|
+
cols[name] = col
|
|
777
|
+
end
|
|
778
|
+
end
|
|
779
|
+
|
|
693
780
|
private def per_group_column(in_name, proc)
|
|
694
781
|
results = []
|
|
695
782
|
each_group_slice(in_name) { |slice| results << proc.call(slice) }
|
|
@@ -751,7 +838,7 @@ class GroupedFrame
|
|
|
751
838
|
end
|
|
752
839
|
|
|
753
840
|
private def group_perm
|
|
754
|
-
@group_perm ||= @cat.
|
|
841
|
+
@group_perm ||= @cat.group_addr
|
|
755
842
|
end
|
|
756
843
|
|
|
757
844
|
private def group_bounds
|
data/lib/carray/frame/io.rb
CHANGED
|
@@ -66,7 +66,9 @@ class CAFrame
|
|
|
66
66
|
# attrs: attribute keys for the lines right below the header, one
|
|
67
67
|
# key per line: field j of a line becomes that attribute of
|
|
68
68
|
# column j (an empty field gives none). The data then starts
|
|
69
|
-
# below them. What to_csv(attrs:) writes, it reads back
|
|
69
|
+
# below them. What to_csv(attrs:) writes, it reads back. The
|
|
70
|
+
# fields are read as the data fields are, so strip: trims them
|
|
71
|
+
# too
|
|
70
72
|
#
|
|
71
73
|
# CAFrame.from_csv("obs.csv", header: 2) # a title above
|
|
72
74
|
# CAFrame.from_csv("obs.csv", header: 0, data: 3) # units on lines 1-2
|
data/lib/carray/fuse_source.rb
CHANGED
|
@@ -160,9 +160,9 @@ class CArray
|
|
|
160
160
|
|
|
161
161
|
def self.extract (block)
|
|
162
162
|
sequence = RubyVM::InstructionSequence.of(block) rescue nil
|
|
163
|
-
location = sequence && sequence.to_a[4][:code_location]
|
|
164
163
|
path = sequence && (sequence.absolute_path || sequence.path)
|
|
165
|
-
|
|
164
|
+
location = path && File.readable?(path) && code_location(sequence, path)
|
|
165
|
+
unless location
|
|
166
166
|
raise ArgumentError,
|
|
167
167
|
"CArray.fuse cannot read this block's source (defined in irb, " \
|
|
168
168
|
"eval, or a file that is no longer there). Write `.lazy` on " \
|
|
@@ -181,6 +181,35 @@ class CArray
|
|
|
181
181
|
end
|
|
182
182
|
end
|
|
183
183
|
|
|
184
|
+
# [first_line, first_column, last_line, last_column] of the block. The
|
|
185
|
+
# block that starts on the sequence's first line is found in the file's
|
|
186
|
+
# syntax tree. Only when two start on that line is the sequence itself
|
|
187
|
+
# dumped: InstructionSequence#to_a reads the instructions while another
|
|
188
|
+
# Ractor may be running them, and that can crash the process.
|
|
189
|
+
def self.code_location (sequence, path)
|
|
190
|
+
line = sequence.first_lineno
|
|
191
|
+
found = []
|
|
192
|
+
pending = [Prism.parse_file(path).value]
|
|
193
|
+
until pending.empty?
|
|
194
|
+
node = pending.pop
|
|
195
|
+
next unless node
|
|
196
|
+
# A lambda's code starts after the arrow, at its parameters or brace.
|
|
197
|
+
start = case node
|
|
198
|
+
when Prism::BlockNode then node.location
|
|
199
|
+
when Prism::LambdaNode then (node.parameters || node).location
|
|
200
|
+
end
|
|
201
|
+
start = node.opening_loc if start && node.is_a?(Prism::LambdaNode) && !node.parameters
|
|
202
|
+
found << [start, node.location] if start && start.start_line == line
|
|
203
|
+
pending.concat(node.compact_child_nodes)
|
|
204
|
+
end
|
|
205
|
+
if found.size == 1
|
|
206
|
+
start, whole = found.first
|
|
207
|
+
[start.start_line, start.start_column, whole.end_line, whole.end_column]
|
|
208
|
+
else
|
|
209
|
+
sequence.to_a[4][:code_location]
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
184
213
|
# -- the rewrite -------------------------------------------------------
|
|
185
214
|
|
|
186
215
|
# The leaves are the names being read. Everything else keeps its shape:
|
data/lib/carray/lazy.rb
CHANGED
|
@@ -330,9 +330,28 @@ class CArray
|
|
|
330
330
|
# branch uses C99 `remainder`, round-half-to-even).
|
|
331
331
|
}.freeze
|
|
332
332
|
|
|
333
|
+
# A Face answers an operator by its own values (a label, an instant, a
|
|
334
|
+
# String) and refuses what it can't answer; the lazy kernels read the
|
|
335
|
+
# storage and see neither. With a Face among the operands the operator is
|
|
336
|
+
# the eager one, on the lazy operands computed.
|
|
337
|
+
module LazyDispatch
|
|
338
|
+
private
|
|
339
|
+
|
|
340
|
+
def face_among_operands? (*operands)
|
|
341
|
+
operands.any? { |o| o.is_a?(CArray) && o.face? }
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def computed_operand (operand)
|
|
345
|
+
( operand.is_a?(CArray) && operand.lazy_view? ) ? operand.copy : operand
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
333
349
|
CArray.send(:define_named_methods, LazyDispatch, LAZY_BINOP_OP_IDS, template: <<~'RUBY')
|
|
334
350
|
def __NAME__(other)
|
|
335
351
|
if lazy_view? || (other.is_a?(CArray) && other.lazy_view?)
|
|
352
|
+
if face_among_operands?(self, other)
|
|
353
|
+
return computed_operand(self).__send__(__method__, computed_operand(other))
|
|
354
|
+
end
|
|
336
355
|
CABinOp.__build__(self, other, __VALUE__)
|
|
337
356
|
else
|
|
338
357
|
super(other)
|
|
@@ -403,6 +422,10 @@ class CArray
|
|
|
403
422
|
(op2.is_a?(CArray) && op2.lazy_view?) ||
|
|
404
423
|
(op3.is_a?(CArray) && op3.lazy_view?)
|
|
405
424
|
if lazy_receiver
|
|
425
|
+
if face_among_operands?(self, op2, op3)
|
|
426
|
+
return computed_operand(self).__send__(__method__, computed_operand(op2),
|
|
427
|
+
computed_operand(op3))
|
|
428
|
+
end
|
|
406
429
|
CATriOp.__build__(self, op2, op3, __VALUE__)
|
|
407
430
|
else
|
|
408
431
|
super(op2, op3)
|
|
@@ -435,6 +458,10 @@ class CArray
|
|
|
435
458
|
# is already fast. The lazy-substrate gap was Float/Complex only.
|
|
436
459
|
module LazyDispatch
|
|
437
460
|
def **(other)
|
|
461
|
+
if ( lazy_view? || (other.is_a?(CArray) && other.lazy_view?) ) &&
|
|
462
|
+
face_among_operands?(self, other)
|
|
463
|
+
return computed_operand(self) ** computed_operand(other)
|
|
464
|
+
end
|
|
438
465
|
if other.is_a?(Integer) && (float? || complex?) &&
|
|
439
466
|
(lazy_view? || (other.is_a?(CArray) && other.lazy_view?))
|
|
440
467
|
CABinOp.__build__(self, CScalar.int64.tap { |s| s[0] = other },
|
|
@@ -492,6 +519,9 @@ class CArray
|
|
|
492
519
|
CArray.send(:define_named_methods, LazyDispatch, LAZY_BINCMP_OP_IDS, template: <<~'RUBY')
|
|
493
520
|
def __NAME__(other)
|
|
494
521
|
if lazy_view? || (other.is_a?(CArray) && other.lazy_view?)
|
|
522
|
+
if face_among_operands?(self, other)
|
|
523
|
+
return computed_operand(self).__send__(__method__, computed_operand(other))
|
|
524
|
+
end
|
|
495
525
|
CABinCmp.__build__(self, other, __VALUE__)
|
|
496
526
|
else
|
|
497
527
|
super(other)
|
|
@@ -511,6 +541,9 @@ class CArray
|
|
|
511
541
|
CArray.send(:define_named_methods, LazyDispatch, LAZY_BINCMP_TOL_OP_IDS, template: <<~'RUBY')
|
|
512
542
|
def __NAME__(other, tol)
|
|
513
543
|
if lazy_view? || (other.is_a?(CArray) && other.lazy_view?)
|
|
544
|
+
if face_among_operands?(self, other)
|
|
545
|
+
return computed_operand(self).__send__(__method__, computed_operand(other), tol)
|
|
546
|
+
end
|
|
514
547
|
CABinCmp.__build__(self, other, __VALUE__, tol.to_f.abs)
|
|
515
548
|
else
|
|
516
549
|
super(other, tol)
|
|
@@ -598,6 +631,8 @@ class CAMonOp
|
|
|
598
631
|
op_id = __op_id__
|
|
599
632
|
if op_id >= CAMonOp::CAST_BASE
|
|
600
633
|
"cast_#{CArray.data_type_name(op_id - CAMonOp::CAST_BASE)}"
|
|
634
|
+
elsif op_id == CAMonOp::OP_BYTE_SWAP
|
|
635
|
+
"swap_bytes" # a view of its own, not a lazy operation by name
|
|
601
636
|
else
|
|
602
637
|
(OP_NAMES[op_id] || "op_#{op_id}").to_s
|
|
603
638
|
end
|
data/lib/carray/methods/is_in.rb
CHANGED
|
@@ -226,8 +226,15 @@ class CArray
|
|
|
226
226
|
# set for not matching self. Same rule the scalar operand of a
|
|
227
227
|
# comparison follows: a String standing in for a cell is padded to the
|
|
228
228
|
# cell's width.
|
|
229
|
+
# A String longer than the cell keeps its length: self is read at its
|
|
230
|
+
# width instead (NUL-padded), so "abX" is in no two-byte cell.
|
|
231
|
+
w = elems.map { |v| v.is_a?(String) ? v.bytesize : 0 }.max.to_i
|
|
232
|
+
me = self
|
|
233
|
+
if w > bytes
|
|
234
|
+
me = is_a?(CAFixlenString) ? to_fixlen_string(bytes: w) : to_type(CA_FIXLEN, bytes: w)
|
|
235
|
+
end
|
|
229
236
|
# coerce: a fixlen or object set holds values of self's kind (see above)
|
|
230
|
-
[
|
|
237
|
+
[me, elems.to_ca.to_type(data_type, bytes: me.bytes)]
|
|
231
238
|
elsif data_type == CA_OBJECT
|
|
232
239
|
# coerce: an object set holds values of self's kind
|
|
233
240
|
[self, elems.to_ca.to_type(data_type)]
|
|
@@ -252,12 +252,20 @@ class CASegmentIterator < CAIterator
|
|
|
252
252
|
return axis_by_masked_copy(axis, :variance) if axis
|
|
253
253
|
m = moments
|
|
254
254
|
return per_segment(core_reduce_kind(:variance)) { |s| s.variance } unless m
|
|
255
|
+
centred_variance(m, 1)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# The kernel divides the centred sum of squares by count - ddof once, as
|
|
259
|
+
# the core reduction does; scaling the sample variance by (n-1)/n instead
|
|
260
|
+
# rounds twice and lands an ulp away.
|
|
261
|
+
def centred_variance (m, ddof)
|
|
255
262
|
cnt = m[:count]
|
|
256
|
-
means = m[:sum] / cnt.float64 # per-segment mean (garbage where count
|
|
257
|
-
out = CArray.float64(@k) #
|
|
258
|
-
grouped.send(:__reduceat_variance__, @offsets, means, cnt, out)
|
|
263
|
+
means = m[:sum] / cnt.float64 # per-segment mean (garbage where the count
|
|
264
|
+
out = CArray.float64(@k) # is too small; the kernel masks those)
|
|
265
|
+
grouped.send(:__reduceat_variance__, @offsets, means, cnt, out, ddof)
|
|
259
266
|
out
|
|
260
267
|
end
|
|
268
|
+
private :centred_variance
|
|
261
269
|
|
|
262
270
|
# @overload stddev
|
|
263
271
|
# Returns per-segment SAMPLE standard deviation (ddof=1) as float64.
|
|
@@ -375,11 +383,7 @@ class CASegmentIterator < CAIterator
|
|
|
375
383
|
return axis_by_masked_copy(axis, :variancep) if axis
|
|
376
384
|
m = moments
|
|
377
385
|
return per_segment(core_reduce_kind(:variancep)) { |s| s.variancep } unless m
|
|
378
|
-
|
|
379
|
-
vp = variance * (cnt - 1).float64 / cnt.float64
|
|
380
|
-
one = cnt.eq(1) # one value: defined, unlike variance --
|
|
381
|
-
vp[one] = (m[:sum] - m[:sum])[one] # 0.0, or NaN for a NaN or an infinity
|
|
382
|
-
vp
|
|
386
|
+
centred_variance(m, 0) # one value: 0.0, or NaN for a NaN or an infinity
|
|
383
387
|
end
|
|
384
388
|
|
|
385
389
|
# @overload stddevp
|
data/lib/carray/serialize.rb
CHANGED
|
@@ -141,7 +141,15 @@ class CArray::Serializer # :nodoc:
|
|
|
141
141
|
"keep its values with Marshal.dump(ca.to_type(:object))"
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
-
file_endian =
|
|
144
|
+
file_endian = case endian
|
|
145
|
+
when nil, :native then CArray.endian
|
|
146
|
+
when :big, CA_BIG_ENDIAN then CA_BIG_ENDIAN
|
|
147
|
+
when :little, CA_LITTLE_ENDIAN then CA_LITTLE_ENDIAN
|
|
148
|
+
else
|
|
149
|
+
raise ArgumentError,
|
|
150
|
+
"endian: must be :big, :little, :native, " \
|
|
151
|
+
"CA_BIG_ENDIAN or CA_LITTLE_ENDIAN (got #{endian.inspect})"
|
|
152
|
+
end
|
|
145
153
|
swap = (file_endian != CArray.endian)
|
|
146
154
|
|
|
147
155
|
elements = ca.elements
|
|
@@ -383,6 +391,7 @@ class CArray::Serializer # :nodoc:
|
|
|
383
391
|
"members" => members,
|
|
384
392
|
}
|
|
385
393
|
schema["name"] = klass.name if klass.name
|
|
394
|
+
schema["order_by"] = klass.order_by.map(&:to_s) if klass.order_by
|
|
386
395
|
schema
|
|
387
396
|
end
|
|
388
397
|
|
|
@@ -439,7 +448,15 @@ class CArray::Serializer # :nodoc:
|
|
|
439
448
|
members = schema["members"]
|
|
440
449
|
record_bytes = schema["record_bytes"]
|
|
441
450
|
builder = (schema["kind"] == "union") ? CArray.method(:union) : CArray.method(:struct)
|
|
442
|
-
|
|
451
|
+
order_by = schema["order_by"]
|
|
452
|
+
names = members.map { |m| m["name"].to_s }
|
|
453
|
+
if order_by && !(order_by.is_a?(Array) && order_by.all? { |n| names.include?(n.to_s) })
|
|
454
|
+
raise ArgumentError, "corrupt CArray binary data (order_by #{order_by.inspect} " \
|
|
455
|
+
"names a member the data_class does not have)"
|
|
456
|
+
end
|
|
457
|
+
opts = { :pack => 1, :size => record_bytes }
|
|
458
|
+
opts[:order_by] = order_by.map(&:to_sym) if order_by
|
|
459
|
+
builder.call(**opts) do
|
|
443
460
|
members.each do |m|
|
|
444
461
|
sym = PEP_TO_SYMBOL[m["type"]]
|
|
445
462
|
unless sym
|
data/lib/carray/stack.rb
CHANGED
|
@@ -69,27 +69,28 @@ class CArray
|
|
|
69
69
|
# No data is copied; reads gather from parents on demand and writes flow
|
|
70
70
|
# back to them (chain composability preserved).
|
|
71
71
|
#
|
|
72
|
-
# Pieces must agree on `ndim
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
72
|
+
# Pieces must agree on `ndim` and every axis length except `axis` (the
|
|
73
|
+
# "meld axis"). Pieces of different data types are brought to a common
|
|
74
|
+
# one as {CArray.stack} brings them (`promote_list`), through read-only
|
|
75
|
+
# views of the pieces that need it.
|
|
76
76
|
#
|
|
77
77
|
# "meld" = melt + weld — pieces dissolve their boundaries along the named
|
|
78
78
|
# axis and are regarded as one.
|
|
79
79
|
#
|
|
80
|
-
# @overload meld(*arrays, axis: 0)
|
|
81
|
-
# @overload meld(list, axis: 0)
|
|
80
|
+
# @overload meld(*arrays, axis: 0, data_type: nil)
|
|
81
|
+
# @overload meld(list, axis: 0, data_type: nil)
|
|
82
82
|
# Convenience form: a single Array argument is treated as the list.
|
|
83
83
|
# @param arrays [Array<CArray>] pieces to weld. A single Array
|
|
84
84
|
# argument is accepted for compatibility with older callers.
|
|
85
85
|
# @param axis [Integer, nil] existing axis to extend (normalises
|
|
86
86
|
# negative values against the reference ndim); `nil` is the same
|
|
87
87
|
# as `0`.
|
|
88
|
+
# @param data_type [Symbol, Integer, nil] result `data_type`;
|
|
89
|
+
# inferred when `nil`.
|
|
88
90
|
# @return [CAMeld] view over the welded pieces.
|
|
89
91
|
# @raise [ArgumentError] when the list is empty, ndim mismatch,
|
|
90
|
-
#
|
|
91
|
-
|
|
92
|
-
def self.meld (*arrays, axis: 0)
|
|
92
|
+
# or non-axis dim mismatch across pieces (surfaced by CAMeld.new).
|
|
93
|
+
def self.meld (*arrays, axis: 0, data_type: nil)
|
|
93
94
|
if arrays.length == 1 && arrays[0].is_a?(Array)
|
|
94
95
|
arrays = arrays[0]
|
|
95
96
|
end
|
|
@@ -100,6 +101,7 @@ class CArray
|
|
|
100
101
|
raise ArgumentError, "meld: entries must be CArray (got #{first.class})"
|
|
101
102
|
end
|
|
102
103
|
axis_norm = CArray.normalize_axis(axis, first.ndim, "meld")
|
|
104
|
+
arrays = CArray.promote_list(arrays, data_type: data_type)
|
|
103
105
|
# Flatten nested CAMeld inputs that share our meld axis: they already
|
|
104
106
|
# describe a segment sequence, so absorbing their parents keeps chain
|
|
105
107
|
# depth at 1 (avoids 2-level xfer_all / reduce chains through the
|
|
@@ -119,14 +121,15 @@ class CArray
|
|
|
119
121
|
# Instance form of {CArray.meld}; non-destructive, see the class method
|
|
120
122
|
# for full semantics.
|
|
121
123
|
#
|
|
122
|
-
# @overload meld(*others, axis: 0)
|
|
124
|
+
# @overload meld(*others, axis: 0, data_type: nil)
|
|
123
125
|
# @param others [Array<CArray>] additional pieces.
|
|
124
126
|
# @param axis [Integer] existing axis to extend.
|
|
127
|
+
# @param data_type [Symbol, Integer, nil] result `data_type`.
|
|
125
128
|
# @return [CAMeld]
|
|
126
129
|
# @raise [ArgumentError] when no `others` are given.
|
|
127
|
-
def meld (*others, axis: 0)
|
|
130
|
+
def meld (*others, axis: 0, data_type: nil)
|
|
128
131
|
raise ArgumentError, "meld: at least one other array required" if others.empty?
|
|
129
|
-
CArray.meld(self, *others, axis: axis)
|
|
132
|
+
CArray.meld(self, *others, axis: axis, data_type: data_type)
|
|
130
133
|
end
|
|
131
134
|
|
|
132
135
|
# Arrange `list` of uniform-shape pieces in a `tdim`-shape grid that
|
data/lib/carray/string.rb
CHANGED
|
@@ -57,6 +57,14 @@ class CArray
|
|
|
57
57
|
else
|
|
58
58
|
values = Array.new(n) { |i| block.call(i) }
|
|
59
59
|
end
|
|
60
|
+
elsif arg.is_a?(Integer)
|
|
61
|
+
raise ArgumentError,
|
|
62
|
+
"CArray.string: a count is given with a block that makes each " \
|
|
63
|
+
"String (CArray.string(n) { |i| ... })"
|
|
64
|
+
elsif arg.is_a?(String) || ! arg.respond_to?(:to_a)
|
|
65
|
+
raise TypeError,
|
|
66
|
+
"CArray.string: expected an Array of Strings, an object array or " \
|
|
67
|
+
"a String array (got #{arg.class})"
|
|
60
68
|
else
|
|
61
69
|
values = arg.to_a
|
|
62
70
|
end
|
|
@@ -31,11 +31,14 @@ class CArray
|
|
|
31
31
|
# (integer / boolean results). Shape and mask are carried.
|
|
32
32
|
def string_map (data_type = nil) # :nodoc:
|
|
33
33
|
out = data_type ? template(data_type) : CArray.object(*shape)
|
|
34
|
+
# A typed result has no nil: a cell the String method answers nil for
|
|
35
|
+
# (index finds nothing) is UNDEF.
|
|
36
|
+
cell = data_type ? ->(s) { v = yield(s); v.nil? ? UNDEF : v } : ->(s) { yield(s) }
|
|
34
37
|
if has_mask?
|
|
35
38
|
m = is_masked
|
|
36
|
-
elements.times { |i| out[i] = (m[i] ? UNDEF :
|
|
39
|
+
elements.times { |i| out[i] = (m[i] ? UNDEF : cell.(self[i])) }
|
|
37
40
|
else
|
|
38
|
-
elements.times { |i| out[i] =
|
|
41
|
+
elements.times { |i| out[i] = cell.(self[i]) }
|
|
39
42
|
end
|
|
40
43
|
data_type ? out : CAString.wrap(out)
|
|
41
44
|
end
|
|
@@ -124,10 +127,12 @@ class CArray
|
|
|
124
127
|
# @return [CArray] `:int`, shape and mask of `self` preserved.
|
|
125
128
|
def bytesize () ; string_map(:int) { |s| s.bytesize } ; end
|
|
126
129
|
# Per-cell `String#index`.
|
|
127
|
-
# @return [CArray] `:int`, shape and mask of `self` preserved
|
|
130
|
+
# @return [CArray] `:int`, shape and mask of `self` preserved; UNDEF
|
|
131
|
+
# where the cell does not hold the substring.
|
|
128
132
|
def str_index (*a) ; string_map(:int) { |s| s.index(*a) } ; end
|
|
129
133
|
# Per-cell `String#rindex`.
|
|
130
|
-
# @return [CArray] `:int`, shape and mask of `self` preserved
|
|
134
|
+
# @return [CArray] `:int`, shape and mask of `self` preserved; UNDEF
|
|
135
|
+
# where the cell does not hold the substring.
|
|
131
136
|
def rindex (*a) ; string_map(:int) { |s| s.rindex(*a) } ; end
|
|
132
137
|
|
|
133
138
|
# ---- numeric parse (Integer / Float result) --------------------------
|
|
@@ -191,6 +196,9 @@ class CArray
|
|
|
191
196
|
unless [:error, :silent].include?(truncate)
|
|
192
197
|
raise ArgumentError, "truncate: must be :error or :silent (got #{truncate.inspect})"
|
|
193
198
|
end
|
|
199
|
+
unless bytes.nil? || (bytes.is_a?(Integer) && bytes >= 1)
|
|
200
|
+
raise ArgumentError, "to_fixlen_string: invalid bytes: #{bytes.inspect} (a cell needs bytes: of 1 or more)"
|
|
201
|
+
end
|
|
194
202
|
return self if is_a?(CAFixlenString) && (bytes.nil? || bytes == self.bytes)
|
|
195
203
|
|
|
196
204
|
if has_mask?
|
|
@@ -200,7 +208,7 @@ class CArray
|
|
|
200
208
|
values = Array.new(elements) { |i| self[i].to_s }
|
|
201
209
|
end
|
|
202
210
|
width = bytes || values.compact.map(&:bytesize).max || 1
|
|
203
|
-
width = 1 if width < 1
|
|
211
|
+
width = 1 if width < 1 # every string empty: the narrowest cell
|
|
204
212
|
if truncate == :error
|
|
205
213
|
values.each_with_index do |s, i|
|
|
206
214
|
next if s.nil?
|
|
@@ -224,11 +224,13 @@ class CAStruct::Builder # :nodoc:
|
|
|
224
224
|
end
|
|
225
225
|
vtype = {1 => :uint8, 2 => :uint16, 4 => :uint32, 8 => :uint64}[view_bytes]
|
|
226
226
|
range = bit_in_byte..(bit_in_byte + bits - 1)
|
|
227
|
+
# The word is read little-endian on every host: bits are numbered
|
|
228
|
+
# byte by byte from the record's start, as the layout places them.
|
|
227
229
|
reader = ->(data) {
|
|
228
|
-
data.field(start_byte, vtype).bitfield(range)[0]
|
|
230
|
+
data.field(start_byte, vtype).endian(:little).bitfield(range)[0]
|
|
229
231
|
}
|
|
230
232
|
writer = ->(data, val) {
|
|
231
|
-
data.field(start_byte, vtype).bitfield(range)[0] = val
|
|
233
|
+
data.field(start_byte, vtype).endian(:little).bitfield(range)[0] = val
|
|
232
234
|
}
|
|
233
235
|
[reader, writer].freeze
|
|
234
236
|
end
|
|
@@ -241,7 +243,7 @@ class CAStruct::Builder # :nodoc:
|
|
|
241
243
|
def self.build_nested_struct_dispatcher (offset, nested_class)
|
|
242
244
|
reader = ->(data) {
|
|
243
245
|
element = nested_class.allocate
|
|
244
|
-
element.instance_variable_set(:@data, data.field(offset, nested_class).
|
|
246
|
+
element.instance_variable_set(:@data, data.field(offset, nested_class).send(:face_storage))
|
|
245
247
|
element.freeze if data.frozen?
|
|
246
248
|
element
|
|
247
249
|
}
|