carray 3.0.6 → 3.0.7
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 +110 -0
- data/README.md +24 -3
- data/carray.gemspec +1 -1
- data/ext/ca_axis_group.c +6 -6
- data/ext/ca_binop_dispatch.c +8 -0
- data/ext/ca_binop_dispatch.h +1 -0
- data/ext/ca_categorical_iterator.c +7 -7
- data/ext/ca_kernel_iterator.h +6 -4
- data/ext/ca_obj_array.c +4 -4
- data/ext/ca_obj_bincmp.c +19 -3
- data/ext/ca_obj_binop.c +36 -11
- data/ext/ca_obj_bitarray.c +2 -4
- data/ext/ca_obj_bitfield.c +86 -4
- data/ext/ca_obj_byte_swap.c +2 -4
- data/ext/ca_obj_const_string.c +422 -257
- data/ext/ca_obj_face.c +71 -33
- data/ext/ca_obj_fake.c +112 -13
- data/ext/ca_obj_farray.c +7 -1
- data/ext/ca_obj_fixlen_string.c +51 -17
- data/ext/ca_obj_grid.c +12 -7
- data/ext/ca_obj_meld.c +5 -5
- data/ext/ca_obj_monop.c +65 -39
- data/ext/ca_obj_object.c +9 -9
- data/ext/ca_obj_reduce.c +2 -4
- data/ext/ca_obj_refer.c +104 -10
- data/ext/ca_obj_repeat.c +1 -1
- data/ext/ca_obj_roll.c +2 -4
- data/ext/ca_obj_select.c +8 -6
- data/ext/ca_obj_select_axis.c +8 -6
- data/ext/ca_obj_stack.c +5 -5
- data/ext/ca_obj_stride.c +46 -8
- data/ext/ca_obj_string.c +2 -2
- data/ext/ca_obj_tile.c +2 -4
- data/ext/ca_obj_triop.c +15 -12
- data/ext/ca_obj_window.c +22 -15
- data/ext/ca_op_ipower.c +63 -15
- data/ext/ca_op_powi.h +6 -29
- data/ext/ca_sort_kernels.h +3 -0
- data/ext/ca_sweep_engine.c +90 -23
- data/ext/ca_sweep_engine.h +5 -6
- data/ext/caframe_csv_reader.c +1 -1
- data/ext/carray.h +3 -1
- data/ext/carray_access.c +98 -39
- data/ext/carray_address_basis.c +98 -24
- data/ext/carray_broadcast.c +2 -2
- data/ext/carray_call_cfunc.c +5 -8
- data/ext/carray_cast.c +311 -37
- data/ext/carray_cast_func.rb +37 -5
- data/ext/carray_conversion.c +56 -2
- data/ext/carray_copy.c +24 -0
- data/ext/carray_core.c +69 -6
- data/ext/carray_count.c +28 -9
- data/ext/carray_element.c +9 -3
- data/ext/carray_factorize.c +11 -11
- data/ext/carray_histogram.c +2 -2
- data/ext/carray_index_classifier.c +7 -5
- data/ext/carray_internal.h +94 -0
- data/ext/carray_kernels_binop.c +55 -55
- data/ext/carray_kernels_init.c +14 -14
- data/ext/carray_kernels_monop.c +90 -140
- data/ext/carray_lazy.c +16 -1
- data/ext/carray_loop.c +9 -2
- data/ext/carray_mask.c +118 -11
- data/ext/carray_median_percentile.c +13 -8
- data/ext/carray_memory_view.c +41 -22
- data/ext/carray_operator.c +72 -11
- data/ext/carray_order.c +52 -9
- data/ext/carray_partition.c +1 -1
- data/ext/carray_random.c +1 -1
- data/ext/carray_round.c +24 -1
- data/ext/carray_scatter.c +4 -0
- data/ext/carray_segment.c +1 -1
- data/ext/carray_slab.c +13 -4
- data/ext/carray_sort.c +6 -3
- data/ext/carray_sort_kernel.c +24 -0
- data/ext/carray_struct.c +21 -6
- data/ext/carray_test.c +85 -39
- data/ext/carray_time_text.c +6 -0
- data/ext/carray_utils.c +124 -54
- data/ext/mk_call_cfunc.rb +6 -11
- data/ext/mkkernel.rb +37 -27
- data/ext/version.h +4 -4
- data/lib/carray/axis_group.rb +9 -6
- data/lib/carray/basics.rb +26 -8
- data/lib/carray/bincount_nd.rb +8 -2
- data/lib/carray/block_iterator.rb +48 -21
- data/lib/carray/categorical.rb +21 -8
- data/lib/carray/categorical_iterator.rb +2 -1
- data/lib/carray/conditional.rb +52 -4
- data/lib/carray/const_string.rb +74 -0
- data/lib/carray/construct.rb +2 -2
- data/lib/carray/core_extensions.rb +19 -68
- data/lib/carray/data_type_extension.rb +1 -0
- data/lib/carray/fixlen_string.rb +83 -1
- data/lib/carray/frame/column_verbs.rb +61 -6
- data/lib/carray/frame/concat.rb +50 -6
- data/lib/carray/frame/csv_parser.rb +13 -4
- data/lib/carray/frame/frame.rb +38 -6
- data/lib/carray/frame/group.rb +67 -40
- data/lib/carray/frame/io.rb +10 -5
- data/lib/carray/frame/join.rb +20 -6
- data/lib/carray/frame/records.rb +6 -4
- data/lib/carray/frame/reshape.rb +25 -5
- data/lib/carray/frame/verbs.rb +78 -25
- data/lib/carray/frame/windows.rb +30 -6
- data/lib/carray/fuse_source.rb +4 -2
- data/lib/carray/iterator.rb +8 -0
- data/lib/carray/key_windows.rb +10 -4
- data/lib/carray/lazy.rb +12 -17
- data/lib/carray/methods/bin.rb +3 -0
- data/lib/carray/methods/composition.rb +1 -1
- data/lib/carray/methods/factorize.rb +2 -0
- data/lib/carray/methods/gather_nd.rb +4 -1
- data/lib/carray/methods/is_in.rb +23 -3
- data/lib/carray/methods/locate_addr.rb +63 -12
- data/lib/carray/methods/repeat.rb +11 -1
- data/lib/carray/record_order.rb +35 -6
- data/lib/carray/runtime.rb +10 -0
- data/lib/carray/segment_iterator.rb +19 -16
- data/lib/carray/serialize.rb +22 -0
- data/lib/carray/slab_iterator.rb +1 -1
- data/lib/carray/stack.rb +3 -3
- data/lib/carray/string.rb +6 -6
- data/lib/carray/string_operation_extension.rb +25 -4
- data/lib/carray/struct_builder.rb +4 -1
- data/lib/carray/time.rb +190 -46
- data/lib/carray/window_iterator.rb +48 -12
- data/yard-stubs/ca_obj_bitfield.rb +4 -1
- data/yard-stubs/ca_obj_refer.rb +3 -0
- data/yard-stubs/ca_obj_window.rb +2 -1
- data/yard-stubs/carray_access.rb +4 -2
- data/yard-stubs/carray_cast.rb +6 -0
- data/yard-stubs/carray_count.rb +6 -3
- data/yard-stubs/carray_math.rb +5 -0
- data/yard-stubs/carray_order.rb +6 -0
- data/yard-stubs/carray_scatter.rb +2 -1
- data/yard-stubs/carray_test.rb +18 -5
- metadata +2 -2
data/lib/carray/basics.rb
CHANGED
|
@@ -162,6 +162,10 @@ class CArray
|
|
|
162
162
|
lfill = fill_value if lfill.nil?
|
|
163
163
|
ufill = fill_value if ufill.nil?
|
|
164
164
|
out = copy
|
|
165
|
+
# Whatever is written must fit: a finer time, a wider fixed string.
|
|
166
|
+
[min, max, lfill, ufill].each { |v|
|
|
167
|
+
out, = branches_in_common(out, v) unless v.nil? || UNDEF.equal?(v)
|
|
168
|
+
}
|
|
165
169
|
out[self < min] = lfill.nil? ? min : lfill unless min.nil?
|
|
166
170
|
out[self > max] = ufill.nil? ? max : ufill unless max.nil?
|
|
167
171
|
out
|
|
@@ -170,6 +174,12 @@ class CArray
|
|
|
170
174
|
# An array of +shape+ of the same kind as self: its data type and cell
|
|
171
175
|
# width, or its Face over a zero storage. Nothing is written into it, so
|
|
172
176
|
# it can be built for a read-only Face too.
|
|
177
|
+
# Whether a cell can be given a value it has not seen. Not a CAConstString,
|
|
178
|
+
# whose strings share a buffer, or a CACategorical, whose codes index a
|
|
179
|
+
# vocabulary. A view that is read-only can still hold them: it is only
|
|
180
|
+
# that view that cannot be written.
|
|
181
|
+
private def holds_new_values? = true
|
|
182
|
+
|
|
173
183
|
private def blank_like (shape)
|
|
174
184
|
storage = face_storage
|
|
175
185
|
out = CArray.new(storage.data_type, shape, bytes: storage.bytes)
|
|
@@ -334,7 +344,8 @@ class CArray
|
|
|
334
344
|
# masked: a missing bound is not read as either, so fill it first.
|
|
335
345
|
#
|
|
336
346
|
# Boundary is strict `[min, max]`: values equal to a bound
|
|
337
|
-
# remain unchanged in both the clamped and filled variants.
|
|
347
|
+
# remain unchanged in both the clamped and filled variants. A NaN
|
|
348
|
+
# is in neither range and stays NaN.
|
|
338
349
|
#
|
|
339
350
|
# @param min [Numeric, nil] lower bound; `nil` for one-sided
|
|
340
351
|
# clip above.
|
|
@@ -597,14 +608,14 @@ class CArray
|
|
|
597
608
|
# reductions do: each block value is written into a blank of the Face
|
|
598
609
|
# (which reads a CATime::Element, a string, a Face array ...) and handed
|
|
599
610
|
# to the slab engine as the storage it became; the result is lifted back
|
|
600
|
-
# at the end. A
|
|
601
|
-
# are collected as the surface objects they are. A
|
|
602
|
-
# caller is the caller's, and passes through.
|
|
611
|
+
# at the end. A Face that holds no new value has no blank to write into,
|
|
612
|
+
# so its values are collected as the surface objects they are. A
|
|
613
|
+
# `data_type:` from the caller is the caller's, and passes through.
|
|
603
614
|
module FaceSlabResult
|
|
604
615
|
def map_slab (**kw, &block)
|
|
605
616
|
return super unless face? && kw[:data_type].nil? && block
|
|
606
|
-
return super(**kw, data_type: :object, &block)
|
|
607
|
-
out = super(**kw,
|
|
617
|
+
return super(**kw, data_type: :object, &block) unless holds_new_values?
|
|
618
|
+
out = super(**kw, **face_slab_type) { |slab|
|
|
608
619
|
face_slab_storage(block.call(slab), slab.shape)
|
|
609
620
|
}
|
|
610
621
|
out.face_lift(self)
|
|
@@ -612,10 +623,10 @@ class CArray
|
|
|
612
623
|
|
|
613
624
|
def reduce_slab (**kw, &block)
|
|
614
625
|
return super unless face? && kw[:data_type].nil? && block
|
|
615
|
-
return super(**kw, data_type: :object, &block)
|
|
626
|
+
return super(**kw, data_type: :object, &block) unless holds_new_values?
|
|
616
627
|
fiber = kw.key?(:init)
|
|
617
628
|
init = kw.delete(:init)
|
|
618
|
-
out = super(**kw,
|
|
629
|
+
out = super(**kw, **face_slab_type) { |slab|
|
|
619
630
|
value = if fiber
|
|
620
631
|
slab.to_a.flatten.inject(init) { |acc, e| block.call(acc, e) }
|
|
621
632
|
else
|
|
@@ -628,6 +639,13 @@ class CArray
|
|
|
628
639
|
|
|
629
640
|
private
|
|
630
641
|
|
|
642
|
+
# The engine's output type. Fixlen storage is named by leaving it out: the
|
|
643
|
+
# name alone has no width, and the source's is the one wanted.
|
|
644
|
+
def face_slab_type
|
|
645
|
+
storage = face_storage
|
|
646
|
+
storage.data_type == CA_FIXLEN ? {} : { data_type: storage.data_type }
|
|
647
|
+
end
|
|
648
|
+
|
|
631
649
|
def face_slab_storage (value, shape)
|
|
632
650
|
cell = blank_like(shape)
|
|
633
651
|
cell[] = value
|
data/lib/carray/bincount_nd.rb
CHANGED
|
@@ -238,7 +238,10 @@ class CArray
|
|
|
238
238
|
ext_sizes = @lengths.map { |l| l + 1 } # +1: upper overflow cell
|
|
239
239
|
strides_ext = ext_sizes.each_with_index.map { |_, k| ext_sizes[(k + 1)..].inject(1, :*) }
|
|
240
240
|
total_ext = ext_sizes.inject(:*)
|
|
241
|
-
|
|
241
|
+
# The flat index is worked out in the labels' own type, so it must reach
|
|
242
|
+
# the last cell of the table in that type.
|
|
243
|
+
label_bits = 8 * chunk.bytes - (chunk.data_type_name.start_with?("u") ? 0 : 1)
|
|
244
|
+
widen = total_ext - 1 >= 2 ** label_bits
|
|
242
245
|
|
|
243
246
|
# canonical [fiber..., sample, channel] view (channel last); weights to
|
|
244
247
|
# [fiber..., sample]. Skip the transpose when the layout is already
|
|
@@ -270,7 +273,10 @@ class CArray
|
|
|
270
273
|
ravel = nil
|
|
271
274
|
(0...@m).each do |k|
|
|
272
275
|
b = tchunk[nil, k]
|
|
273
|
-
|
|
276
|
+
top = b.max
|
|
277
|
+
# No present cell in a channel: every sample is dropped.
|
|
278
|
+
return self if top == UNDEF
|
|
279
|
+
b = b.clip(0, @lengths[k]) if top > @lengths[k] - 1
|
|
274
280
|
b = b.int64 if widen
|
|
275
281
|
term = strides_ext[k] == 1 ? b : b * strides_ext[k]
|
|
276
282
|
ravel = ravel.nil? ? term : ravel + term
|
|
@@ -88,10 +88,15 @@ class CABlockIterator < CAIterator
|
|
|
88
88
|
@sizes = Array.new(@sndim)
|
|
89
89
|
blocks.each_with_index do |b, i|
|
|
90
90
|
if b.is_a?(Range)
|
|
91
|
+
unless b.begin.is_a?(Integer) && b.end.is_a?(Integer)
|
|
92
|
+
raise TypeError, "blocks: axis #{i} takes a Range of Integer positions, got #{b.inspect}"
|
|
93
|
+
end
|
|
91
94
|
offsets[i] = b.begin
|
|
92
95
|
@sizes[i] = b.end - b.begin + (b.exclude_end? ? 0 : 1)
|
|
96
|
+
elsif b.is_a?(Integer)
|
|
97
|
+
@sizes[i] = b
|
|
93
98
|
else
|
|
94
|
-
|
|
99
|
+
raise TypeError, "blocks: a tile size must be an Integer (got #{b.class})"
|
|
95
100
|
end
|
|
96
101
|
if @sizes[i] < 1
|
|
97
102
|
raise ArgumentError, "blocks: tile size on axis #{i} must be >= 1"
|
|
@@ -365,16 +370,9 @@ class CABlockIterator < CAIterator
|
|
|
365
370
|
def count (*args, **kw)
|
|
366
371
|
return count_not_masked(**kw) if args.empty?
|
|
367
372
|
kw = CAIterator.reduce_keywords(:count, kw)
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
# block_view is a CAStride, so #count is not shadowed; dispatch
|
|
372
|
-
# CArray#count explicitly anyway, matching the family regularity.
|
|
373
|
-
red = CArray.instance_method(:count).bind_call(view, *args, axis: @tile_axes, **kw)
|
|
374
|
-
out ||= CArray.new(red.data_type, @shape, bytes: red.bytes)
|
|
375
|
-
out[*out_ranges] = red
|
|
376
|
-
end
|
|
377
|
-
out
|
|
373
|
+
# block_view is a CAStride, so #count is not shadowed; dispatch
|
|
374
|
+
# CArray#count explicitly anyway, matching the family regularity.
|
|
375
|
+
assemble { |view, _| count_cells(view, *args, axis: @tile_axes, **kw) }
|
|
378
376
|
end
|
|
379
377
|
|
|
380
378
|
# @overload count_not_masked
|
|
@@ -477,6 +475,12 @@ class CABlockIterator < CAIterator
|
|
|
477
475
|
return src[addr.strip_mask(hole[0])].copy
|
|
478
476
|
end
|
|
479
477
|
out = src[addr.strip_mask(0)].copy
|
|
478
|
+
unless out.__send__(:holds_new_values?)
|
|
479
|
+
# A const string's cells are written as Strings and packed again.
|
|
480
|
+
text = out.to_string
|
|
481
|
+
text[empty] = fill_value.nil? ? UNDEF : fill_value
|
|
482
|
+
return text.to_const_string
|
|
483
|
+
end
|
|
480
484
|
out[empty] = fill_value.nil? ? UNDEF : fill_value
|
|
481
485
|
out
|
|
482
486
|
end
|
|
@@ -681,15 +685,24 @@ class CABlockIterator < CAIterator
|
|
|
681
685
|
# into a source-shaped CArray (the out-of-bounds cells of a partial edge
|
|
682
686
|
# tile are dropped). Well-defined because tiles do not overlap; the source
|
|
683
687
|
# is not modified (a new array is returned). Without a block, an Enumerator.
|
|
688
|
+
# The result has the source's data type; a Face source gives a result of
|
|
689
|
+
# the same Face, and a read-only Face (a categorical, a const string) an
|
|
690
|
+
# object array of its surface values.
|
|
684
691
|
# @yieldparam tile [CArray]
|
|
685
692
|
# @return [CArray, Enumerator] source-shaped
|
|
686
693
|
def map
|
|
687
694
|
return to_enum(:map) unless block_given?
|
|
688
|
-
|
|
695
|
+
src = padded_source
|
|
696
|
+
surface = src.face? && !src.__send__(:holds_new_values?)
|
|
697
|
+
pout = surface ? CArray.new(CA_OBJECT, src.shape) : src.__send__(:blank_like, src.shape)
|
|
689
698
|
pgv = pout.block_view(*@sizes)
|
|
690
699
|
tgv = tile_grid_view
|
|
691
700
|
nils = Array.new(@sndim, nil)
|
|
692
|
-
CArray.each_index(*@shape) { |*g|
|
|
701
|
+
CArray.each_index(*@shape) { |*g|
|
|
702
|
+
tile = yield(tgv[*g, *nils])
|
|
703
|
+
tile = tile.to_a if surface && tile.is_a?(CArray) && tile.face?
|
|
704
|
+
pgv[*g, *nils] = tile
|
|
705
|
+
}
|
|
693
706
|
pout[*@sndim.times.map { |i| 0...@source.shape[i] }].copy
|
|
694
707
|
end
|
|
695
708
|
|
|
@@ -797,20 +810,23 @@ class CABlockIterator < CAIterator
|
|
|
797
810
|
tile = tgv[*g, *nils]
|
|
798
811
|
scanned = tile.copy.reshape(tile.elements).public_send(op, *args).reshape(*@sizes)
|
|
799
812
|
unless pout
|
|
800
|
-
pout =
|
|
813
|
+
pout = scanned.face? ? scanned.__send__(:blank_like, padded_source.shape) :
|
|
814
|
+
CArray.new(scanned.data_type, padded_source.shape, bytes: scanned.bytes)
|
|
801
815
|
pout[] = UNDEF
|
|
802
816
|
pgv = pout.block_view(*@sizes)
|
|
803
817
|
end
|
|
804
818
|
pgv[*g, *nils] = scanned
|
|
805
819
|
end
|
|
820
|
+
# a source with no cells has no tile; its own scan gives the type
|
|
821
|
+
return @source.copy.reshape(@source.elements).public_send(op, *args).reshape(*@source.shape) unless pout
|
|
806
822
|
pout[*@sndim.times.map { |i| 0...@source.shape[i] }].copy
|
|
807
823
|
end
|
|
808
824
|
|
|
809
825
|
# A source-shaped array that can hold what #tile_wise gives: of its kind,
|
|
810
|
-
# or an object array for a
|
|
811
|
-
# cell.
|
|
826
|
+
# or an object array for a Face that holds no new value, which cannot be
|
|
827
|
+
# written cell by cell.
|
|
812
828
|
def tile_output (lagged)
|
|
813
|
-
if lagged.face? && lagged.
|
|
829
|
+
if lagged.face? && !lagged.__send__(:holds_new_values?)
|
|
814
830
|
CArray.object(*@source.shape)
|
|
815
831
|
else
|
|
816
832
|
lagged.__send__(:blank_like, @source.shape)
|
|
@@ -834,10 +850,21 @@ class CABlockIterator < CAIterator
|
|
|
834
850
|
if pshape == @source.shape.to_a
|
|
835
851
|
@source
|
|
836
852
|
else
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
853
|
+
interior = @sndim.times.map { |i| 0...@source.shape[i] }
|
|
854
|
+
if @source.face? && !@source.__send__(:holds_new_values?)
|
|
855
|
+
# A Face that holds no new value cannot be written: its storage is
|
|
856
|
+
# padded and the Face put back on.
|
|
857
|
+
storage = @source.__send__(:face_storage)
|
|
858
|
+
pad = CArray.new(storage.data_type, pshape, bytes: storage.bytes)
|
|
859
|
+
pad[] = UNDEF
|
|
860
|
+
pad[*interior] = storage
|
|
861
|
+
pad.face_lift(@source)
|
|
862
|
+
else
|
|
863
|
+
pad = @source.__send__(:blank_like, pshape) # a Face stays one
|
|
864
|
+
pad[] = UNDEF
|
|
865
|
+
pad[*interior] = @source
|
|
866
|
+
pad
|
|
867
|
+
end
|
|
841
868
|
end
|
|
842
869
|
end
|
|
843
870
|
end
|
data/lib/carray/categorical.rb
CHANGED
|
@@ -59,6 +59,8 @@ require "carray/methods/is_in" # refuse_range_set, for the set-valued verbs
|
|
|
59
59
|
# kernels are gated off (`cat + 1` raises; arithmetic on category codes is
|
|
60
60
|
# not meaningful).
|
|
61
61
|
class CACategorical < CAObject
|
|
62
|
+
private def holds_new_values? = false
|
|
63
|
+
|
|
62
64
|
|
|
63
65
|
# raw-byte unpack format per storage (codes) data_type, native endian.
|
|
64
66
|
# The FIXLEN surface delivers a per-cell fetch as an N-byte String; this
|
|
@@ -373,12 +375,13 @@ class CACategorical < CAObject
|
|
|
373
375
|
# Returns a boolean CArray marking cells whose category equals
|
|
374
376
|
# `label`. A CArray is compared cell by cell, by label: another
|
|
375
377
|
# categorical is matched whatever order its labels are in, and a
|
|
376
|
-
# masked cell on either side gives UNDEF.
|
|
377
|
-
#
|
|
378
|
-
# nothing.
|
|
378
|
+
# masked cell on either side gives UNDEF. `UNDEF` asks the mask, as for
|
|
379
|
+
# {CArray#eq}: true where a cell is in no category. Anything else, a Ruby
|
|
380
|
+
# Array included, is one label. An unknown label matches nothing.
|
|
379
381
|
# @param label [Object, CArray] category to match, or one per cell.
|
|
380
382
|
# @return [CArray]
|
|
381
383
|
def eq(label)
|
|
384
|
+
return is_masked if label.equal?(UNDEF)
|
|
382
385
|
codes.eq(codes_of(label))
|
|
383
386
|
end
|
|
384
387
|
|
|
@@ -388,24 +391,31 @@ class CACategorical < CAObject
|
|
|
388
391
|
# cell.
|
|
389
392
|
# @return [CArray]
|
|
390
393
|
def ne(label)
|
|
394
|
+
return is_not_masked if label.equal?(UNDEF)
|
|
391
395
|
codes.ne(codes_of(label))
|
|
392
396
|
end
|
|
393
397
|
|
|
394
398
|
# @overload count(label)
|
|
395
399
|
# Returns the number of cells whose category equals `label`
|
|
396
|
-
# (0 for an unknown label).
|
|
400
|
+
# (0 for an unknown label). `UNDEF` counts the cells in no category,
|
|
401
|
+
# as {#count_masked}. The keywords of {CArray#count} (`axis:`,
|
|
402
|
+
# `keep_axis:`, `min_count:`, `fill_value:`) are taken in both forms.
|
|
397
403
|
# @param label [Object] category to count.
|
|
398
404
|
# @return [Integer]
|
|
399
405
|
# @overload count
|
|
400
406
|
# Returns the number of cells in some category (not masked), as
|
|
401
407
|
# {CArray#count} with no value does.
|
|
402
408
|
# @return [Integer]
|
|
403
|
-
def count(*args)
|
|
404
|
-
return count_not_masked if args.empty?
|
|
409
|
+
def count(*args, **kw)
|
|
410
|
+
return count_not_masked(**kw) if args.empty?
|
|
405
411
|
raise ArgumentError, "count: wrong number of arguments (given #{args.size}, expected 0..1)" if args.size > 1
|
|
406
412
|
label = args[0]
|
|
413
|
+
return count_masked(**kw) if label.equal?(UNDEF)
|
|
407
414
|
code = @labels.index(label)
|
|
408
|
-
|
|
415
|
+
return codes.count(code, **kw) if code
|
|
416
|
+
# an unknown label matches no cell; counted over the same mask so the
|
|
417
|
+
# keywords (axis:, min_count:, fill_value:) answer as for a known label
|
|
418
|
+
kw.empty? ? 0 : codes.ne(codes).count(true, **kw)
|
|
409
419
|
end
|
|
410
420
|
|
|
411
421
|
# Per-category counts as a length-k array aligned to #labels. Trailing empty
|
|
@@ -494,7 +504,10 @@ class CACategorical < CAObject
|
|
|
494
504
|
# @return [CArray]
|
|
495
505
|
def is_in (values)
|
|
496
506
|
refuse_range_set(values, "is_in")
|
|
497
|
-
wanted = label_list(values).filter_map
|
|
507
|
+
wanted = label_list(values).filter_map do |l|
|
|
508
|
+
# nan-order: looks a label up and orders nothing; == does not find a NaN.
|
|
509
|
+
l.is_a?(Float) && l.nan? ? @labels.index { |x| x.is_a?(Float) && x.nan? } : @labels.index(l)
|
|
510
|
+
end
|
|
498
511
|
codes.is_in(CArray.int64(wanted.size) { |i| wanted[i] })
|
|
499
512
|
end
|
|
500
513
|
|
|
@@ -492,6 +492,7 @@ class CACategoricalIterator < CASegmentIterator
|
|
|
492
492
|
# each c) — bounded by K, typically small. A fused per-fiber variance
|
|
493
493
|
# kernel is a natural follow-on if bench demands it.
|
|
494
494
|
def axis_by_masked_copy (axis, op, out_data_type = core_reduce_type(op))
|
|
495
|
+
return axis_by_core(axis, op) if @value.face?
|
|
495
496
|
h = @value
|
|
496
497
|
axis = checked_axis(axis, op)
|
|
497
498
|
full_c = resolve_axis_codes(@cat.codes, h.shape, axis)
|
|
@@ -534,7 +535,7 @@ class CACategoricalIterator < CASegmentIterator
|
|
|
534
535
|
flat ? g.__send__(op, *args, axis: axis, keep_axis: true)
|
|
535
536
|
: g.__send__(op, *args, axis: axis)
|
|
536
537
|
end
|
|
537
|
-
if answers.first.face? && answers.first.
|
|
538
|
+
if answers.first.face? && !answers.first.__send__(:holds_new_values?)
|
|
538
539
|
answers = answers.map { |a| CArray.object(*a.shape) { a.to_a } }
|
|
539
540
|
end
|
|
540
541
|
out = flat ? CArray.concatenate(answers) : CArray.stack(answers, axis: 0).copy
|
data/lib/carray/conditional.rb
CHANGED
|
@@ -130,6 +130,13 @@ class CArray
|
|
|
130
130
|
CATriOp.__build__(self, branch.(x), branch.(y), CATriOp::OP_SELECT)
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
+
# The two branches of a selection, in a form that holds the cells of both.
|
|
134
|
+
# A Face whose cells differ by more than their values (a time's unit, a
|
|
135
|
+
# fixed string's width) brings them to one here, whichever branch is which.
|
|
136
|
+
private def branches_in_common (x, y)
|
|
137
|
+
[x, y]
|
|
138
|
+
end
|
|
139
|
+
|
|
133
140
|
private def face_then_else (x, y, face)
|
|
134
141
|
# A Face branch decides the result: the other branch is written into a
|
|
135
142
|
# copy of it through the Face's own store, so an instant stays an instant.
|
|
@@ -141,10 +148,11 @@ class CArray
|
|
|
141
148
|
sy = y.is_a?(CAConstString) ? y.to_string : y
|
|
142
149
|
return CArray.const_string(then_else(sx, sy), encoding: face.encoding)
|
|
143
150
|
end
|
|
151
|
+
x, y = face.__send__(:branches_in_common, x, y)
|
|
144
152
|
x_full = x.is_a?(CArray) && !x.scalar?
|
|
145
153
|
y_full = y.is_a?(CArray) && !y.scalar?
|
|
146
154
|
base = y_full ? y : x
|
|
147
|
-
|
|
155
|
+
unless base.__send__(:holds_new_values?)
|
|
148
156
|
# No store to write through (a CACategorical's codes): select among
|
|
149
157
|
# the values themselves.
|
|
150
158
|
result = CArray.object(*shape)
|
|
@@ -180,7 +188,8 @@ class CArray
|
|
|
180
188
|
"replace_where: cond must be a boolean CArray (data_type == CA_BOOLEAN)"
|
|
181
189
|
end
|
|
182
190
|
result = self.copy
|
|
183
|
-
|
|
191
|
+
# A CScalar takes no index: like a number, it is the value for every cell.
|
|
192
|
+
result[cond] = b.is_a?(CArray) && !b.scalar? ? b[cond] : b
|
|
184
193
|
result
|
|
185
194
|
end
|
|
186
195
|
|
|
@@ -265,6 +274,8 @@ class CArray
|
|
|
265
274
|
# @param condlist [Array<CArray>] boolean selectors.
|
|
266
275
|
# @param choicelist [Array<CArray, Numeric, Object>] values, one
|
|
267
276
|
# per condition (same length as `condlist`).
|
|
277
|
+
# When a choice is a Face the result is that Face (the choices meet as
|
|
278
|
+
# in {#then_else}), and `default` is UNDEF unless it is given.
|
|
268
279
|
# @param default [CArray, Numeric, Object] value written where no
|
|
269
280
|
# condition holds.
|
|
270
281
|
# @param data_type [Symbol, Integer, nil] override for the result
|
|
@@ -278,7 +289,7 @@ class CArray
|
|
|
278
289
|
# [-x, x * 10],
|
|
279
290
|
# default: 999)
|
|
280
291
|
# # => [5.0, 3.0, -10.0, 0.0, 10.0, 999.0]
|
|
281
|
-
def self.select (condlist, choicelist, default: 0, data_type: nil)
|
|
292
|
+
def self.select (condlist, choicelist, default: (no_default = true; 0), data_type: nil)
|
|
282
293
|
unless condlist.is_a?(Array) && choicelist.is_a?(Array)
|
|
283
294
|
raise ArgumentError, "select: condlist and choicelist must be Arrays"
|
|
284
295
|
end
|
|
@@ -296,6 +307,14 @@ class CArray
|
|
|
296
307
|
end
|
|
297
308
|
shape = first.shape
|
|
298
309
|
|
|
310
|
+
if data_type.nil?
|
|
311
|
+
face = choicelist.find { |v| v.is_a?(CArray) && !v.scalar? && v.face? }
|
|
312
|
+
if face
|
|
313
|
+
fallback = no_default ? UNDEF : default
|
|
314
|
+
return select_among_faces(condlist, choicelist, fallback, face, shape)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
299
318
|
dt = data_type || selection_type(choicelist + [default])
|
|
300
319
|
# `default` can be either a same-shape CArray (per-cell fallback) or a
|
|
301
320
|
# scalar (broadcast to every cell).
|
|
@@ -317,7 +336,7 @@ class CArray
|
|
|
317
336
|
"select: condlist[#{k}] must be a same-shape boolean CArray"
|
|
318
337
|
end
|
|
319
338
|
v = choicelist[k]
|
|
320
|
-
out[c] = v.is_a?(CArray) ? v[c] : v
|
|
339
|
+
out[c] = v.is_a?(CArray) && !v.scalar? ? v[c] : v
|
|
321
340
|
end
|
|
322
341
|
if !data_type && selection_text?(choicelist + [default])
|
|
323
342
|
out = CAString.wrap(out)
|
|
@@ -331,6 +350,35 @@ class CArray
|
|
|
331
350
|
# some type, so it leaves the type to the others. A String or Symbol is a
|
|
332
351
|
# value, which CArray.result_type would read as the name of a data type
|
|
333
352
|
# ("int32"), so it makes the result object.
|
|
353
|
+
# select over Face choices. Each condition is a then_else on the choices
|
|
354
|
+
# below it, so the Faces meet as they do there; a Face that cannot be
|
|
355
|
+
# written selects among its values.
|
|
356
|
+
def self.select_among_faces (condlist, choicelist, fallback, face, shape)
|
|
357
|
+
if face.is_a?(CAConstString)
|
|
358
|
+
choices = choicelist.map { |v| v.is_a?(CAConstString) ? v.to_string : v }
|
|
359
|
+
text = select(condlist, choices, default: fallback)
|
|
360
|
+
return CArray.const_string(text, encoding: face.encoding)
|
|
361
|
+
end
|
|
362
|
+
unless face.__send__(:holds_new_values?)
|
|
363
|
+
choices = choicelist.map { |v| v.is_a?(CArray) && v.face? ? v.to_type(CA_OBJECT) : v }
|
|
364
|
+
return select(condlist, choices, default: fallback)
|
|
365
|
+
end
|
|
366
|
+
unless face.shape == shape
|
|
367
|
+
raise ArgumentError, "select: choicelist shape #{face.shape} differs from condlist shape #{shape}"
|
|
368
|
+
end
|
|
369
|
+
out = CArray.boolean(*shape).zero.then_else(face, fallback)
|
|
370
|
+
(condlist.size - 1).downto(0) do |k|
|
|
371
|
+
c = condlist[k]
|
|
372
|
+
unless c.is_a?(CArray) && c.boolean? && c.shape == shape
|
|
373
|
+
raise ArgumentError,
|
|
374
|
+
"select: condlist[#{k}] must be a same-shape boolean CArray"
|
|
375
|
+
end
|
|
376
|
+
out = c.then_else(choicelist[k], out)
|
|
377
|
+
end
|
|
378
|
+
out
|
|
379
|
+
end
|
|
380
|
+
private_class_method :select_among_faces
|
|
381
|
+
|
|
334
382
|
def self.selection_type (values)
|
|
335
383
|
typed = values.reject { |v| v.equal?(UNDEF) }
|
|
336
384
|
if typed.empty? || typed.any? { |v| v.is_a?(String) || v.is_a?(Symbol) }
|
data/lib/carray/const_string.rb
CHANGED
|
@@ -86,6 +86,7 @@ end
|
|
|
86
86
|
# (categorical labels) prefer {CACategorical}, which stores each distinct
|
|
87
87
|
# value once.
|
|
88
88
|
class CAConstString
|
|
89
|
+
private def holds_new_values? = false
|
|
89
90
|
|
|
90
91
|
# Read-only String Face: L1 string operations only (no in-place Mutable).
|
|
91
92
|
# Its C-native byte predicates / search / comparison (ext/ca_obj_const_string.c)
|
|
@@ -205,6 +206,17 @@ class CAConstString
|
|
|
205
206
|
const_string_lift(to_string.clip(*args, **kw))
|
|
206
207
|
end
|
|
207
208
|
|
|
209
|
+
# A String has no distance, so there is no nearest one; refused in the
|
|
210
|
+
# words CAString uses.
|
|
211
|
+
def search_nearest (*args, **kw)
|
|
212
|
+
to_string.search_nearest(*args, **kw)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# @see #search_nearest
|
|
216
|
+
def search_nearest_addr (*args, **kw)
|
|
217
|
+
to_string.search_nearest_addr(*args, **kw)
|
|
218
|
+
end
|
|
219
|
+
|
|
208
220
|
# @overload min_index(axis: nil)
|
|
209
221
|
# @return [Integer, CArray] where the byte-smallest string sits.
|
|
210
222
|
def min_index (*args, **kw)
|
|
@@ -217,6 +229,68 @@ class CAConstString
|
|
|
217
229
|
to_string.max_index(*args, **kw)
|
|
218
230
|
end
|
|
219
231
|
|
|
232
|
+
# @overload min_addr(axis: nil)
|
|
233
|
+
# @return [Integer, CArray] the flat address of the byte-smallest string.
|
|
234
|
+
def min_addr (*args, **kw)
|
|
235
|
+
to_string.min_addr(*args, **kw)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# @overload max_addr(axis: nil)
|
|
239
|
+
# @return [Integer, CArray] the flat address of the byte-largest string.
|
|
240
|
+
def max_addr (*args, **kw)
|
|
241
|
+
to_string.max_addr(*args, **kw)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# @overload bsearch(value)
|
|
245
|
+
# @return [Integer, nil] where a sorted column holds `value`.
|
|
246
|
+
def bsearch (*args, **kw)
|
|
247
|
+
to_string.bsearch(*args, **kw)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# @overload bsearch_addr(value)
|
|
251
|
+
# @return [Integer, nil] the flat address of `value` in a sorted column.
|
|
252
|
+
def bsearch_addr (*args, **kw)
|
|
253
|
+
to_string.bsearch_addr(*args, **kw)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# @overload search_addr(value)
|
|
257
|
+
# @return [Integer, nil] the flat address of the first cell equal to `value`.
|
|
258
|
+
def search_addr (*args, **kw)
|
|
259
|
+
to_string.search_addr(*args, **kw)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# @overload match(pattern)
|
|
263
|
+
# @return [CArray] boolean, as {CAString} answers.
|
|
264
|
+
def match (*args)
|
|
265
|
+
to_string.match(*args)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# @!method pmax(other)
|
|
269
|
+
# The later String in byte order of each cell and `other`.
|
|
270
|
+
# @return [CAConstString]
|
|
271
|
+
# @!method pmin(other)
|
|
272
|
+
# The earlier String in byte order of each cell and `other`.
|
|
273
|
+
# @return [CAConstString]
|
|
274
|
+
# @!method maximum(other)
|
|
275
|
+
# Another name of {#pmax}.
|
|
276
|
+
# @return [CAConstString]
|
|
277
|
+
# @!method minimum(other)
|
|
278
|
+
# Another name of {#pmin}.
|
|
279
|
+
# @return [CAConstString]
|
|
280
|
+
# @!method fmax(other)
|
|
281
|
+
# Another name of {#pmax}.
|
|
282
|
+
# @return [CAConstString]
|
|
283
|
+
# @!method fmin(other)
|
|
284
|
+
# Another name of {#pmin}.
|
|
285
|
+
# @return [CAConstString]
|
|
286
|
+
CArray.send(:define_named_methods, self,
|
|
287
|
+
[:pmax, :pmin, :maximum, :minimum, :fmax, :fmin], :pick_among_strings)
|
|
288
|
+
|
|
289
|
+
private def pick_among_strings (op, other)
|
|
290
|
+
refuse_unordered_operands(op, other)
|
|
291
|
+
const_string_lift(to_string.public_send(op, string_operand(other)))
|
|
292
|
+
end
|
|
293
|
+
|
|
220
294
|
# @overload partition_copy(kth, axis: nil)
|
|
221
295
|
# @return [CAConstString] partitioned about the `kth` string in order.
|
|
222
296
|
def partition_copy (*args, **kw)
|
data/lib/carray/construct.rb
CHANGED
|
@@ -191,7 +191,7 @@ class CArray
|
|
|
191
191
|
extend DataTypeNewConstructor
|
|
192
192
|
extend DataTypeExtension
|
|
193
193
|
# @!visibility private
|
|
194
|
-
TypeSymbol = :
|
|
194
|
+
TypeSymbol = :cmplx64
|
|
195
195
|
# @!visibility private
|
|
196
196
|
DataType = CA_CMPLX64
|
|
197
197
|
end
|
|
@@ -208,7 +208,7 @@ class CArray
|
|
|
208
208
|
extend DataTypeNewConstructor
|
|
209
209
|
extend DataTypeExtension
|
|
210
210
|
# @!visibility private
|
|
211
|
-
TypeSymbol = :
|
|
211
|
+
TypeSymbol = :cmplx128
|
|
212
212
|
# @!visibility private
|
|
213
213
|
DataType = CA_CMPLX128
|
|
214
214
|
end
|