carray 2.0.0 → 3.0.0
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/.yardopts +5 -25
- data/CHANGELOG.md +16 -0
- data/LICENSE +1 -1
- data/NEWS.md +3 -0
- data/README.md +128 -44
- data/carray.gemspec +22 -24
- data/ext/ca_array_pool.c +91 -0
- data/ext/ca_axis_descriptor.h +186 -0
- data/ext/ca_axis_dispatch.c +924 -0
- data/ext/ca_axis_group.c +1208 -0
- data/ext/ca_bincmp_dispatch.c +76 -0
- data/ext/ca_bincmp_dispatch.h +85 -0
- data/ext/ca_binop_dispatch.c +125 -0
- data/ext/ca_binop_dispatch.h +159 -0
- data/ext/ca_categorical_iterator.c +1375 -0
- data/ext/ca_compare.c +94 -0
- data/ext/ca_compare.h +26 -0
- data/ext/ca_composite_dispatch.c +414 -0
- data/ext/ca_composite_dispatch.h +116 -0
- data/ext/ca_for_buffer.h +96 -0
- data/ext/ca_for_each_element.h +241 -0
- data/ext/ca_group_iter.c +304 -0
- data/ext/ca_iter_substrate.h +325 -0
- data/ext/ca_kernel_iterator.c +4321 -0
- data/ext/ca_kernel_iterator.h +2603 -0
- data/ext/ca_moncmp_dispatch.c +37 -0
- data/ext/ca_moncmp_dispatch.h +62 -0
- data/ext/ca_monop_dispatch.c +200 -0
- data/ext/ca_monop_dispatch.h +235 -0
- data/ext/ca_obj_array.c +355 -359
- data/ext/ca_obj_bincmp.c +809 -0
- data/ext/ca_obj_binop.c +892 -0
- data/ext/ca_obj_bitarray.c +369 -164
- data/ext/ca_obj_bitfield.c +294 -234
- data/ext/ca_obj_block.c +189 -711
- data/ext/ca_obj_byte_swap.c +766 -0
- data/ext/ca_obj_const_string.c +965 -0
- data/ext/ca_obj_face.c +670 -0
- data/ext/ca_obj_face.h +247 -0
- data/ext/ca_obj_fake.c +228 -100
- data/ext/ca_obj_farray.c +54 -441
- data/ext/ca_obj_field.c +82 -529
- data/ext/ca_obj_fixlen_string.c +306 -0
- data/ext/ca_obj_grid.c +858 -440
- data/ext/ca_obj_meld.c +1034 -0
- data/ext/ca_obj_moncmp.c +569 -0
- data/ext/ca_obj_monop.c +1111 -0
- data/ext/ca_obj_object.c +774 -298
- data/ext/ca_obj_record.c +468 -0
- data/ext/ca_obj_reduce.c +97 -82
- data/ext/ca_obj_refer.c +569 -459
- data/ext/ca_obj_remap.c +475 -0
- data/ext/ca_obj_repeat.c +92 -477
- data/ext/ca_obj_roll.c +616 -0
- data/ext/ca_obj_select.c +344 -296
- data/ext/ca_obj_select_axis.c +1296 -0
- data/ext/ca_obj_shift.c +230 -792
- data/ext/ca_obj_source.c +78 -0
- data/ext/ca_obj_stack.c +1173 -0
- data/ext/ca_obj_stride.c +2501 -0
- data/ext/ca_obj_string.c +268 -0
- data/ext/ca_obj_tile.c +614 -0
- data/ext/ca_obj_time.c +546 -0
- data/ext/ca_obj_timedelta.c +435 -0
- data/ext/ca_obj_transpose.c +62 -516
- data/ext/ca_obj_triop.c +746 -0
- data/ext/ca_obj_unbound_repeat.c +208 -241
- data/ext/ca_obj_window.c +1131 -563
- data/ext/ca_op_byte_swap.c +175 -0
- data/ext/ca_op_ipower.c +319 -0
- data/ext/ca_op_powi.h +88 -0
- data/ext/ca_sort_kernels.h +132 -0
- data/ext/ca_sweep_engine.c +430 -0
- data/ext/ca_sweep_engine.h +157 -0
- data/ext/ca_transform_common.c +228 -0
- data/ext/ca_triop_dispatch.c +55 -0
- data/ext/ca_triop_dispatch.h +62 -0
- data/ext/carray.h +795 -402
- data/ext/carray_access.c +831 -711
- data/ext/carray_attribute.c +98 -330
- data/ext/carray_bincount.c +255 -0
- data/ext/carray_broadcast.c +283 -0
- data/ext/carray_call_cfunc.c +1360 -828
- data/ext/carray_call_cfunc.h +160 -0
- data/ext/carray_cast.c +1212 -301
- data/ext/carray_cast_func.rb +81 -40
- data/ext/carray_class.c +53 -63
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +350 -346
- data/ext/carray_copy.c +156 -268
- data/ext/carray_core.c +1342 -199
- data/ext/carray_count.c +312 -0
- data/ext/carray_data_type.c +43 -19
- data/ext/carray_element.c +585 -213
- data/ext/carray_factorize.c +2542 -0
- data/ext/carray_generate.c +230 -559
- data/ext/carray_histogram.c +490 -0
- data/ext/carray_hold.c +228 -0
- data/ext/carray_index_classifier.c +1035 -0
- data/ext/carray_index_classifier.h +27 -0
- data/ext/carray_internal.h +120 -0
- data/ext/carray_kernels_bincmp.c +4445 -0
- data/ext/carray_kernels_binop.c +10979 -0
- data/ext/carray_kernels_init.c +36 -0
- data/ext/carray_kernels_map.c +3466 -0
- data/ext/carray_kernels_moncmp.c +2096 -0
- data/ext/carray_kernels_monop.c +18312 -0
- data/ext/carray_kernels_reduce_aggregate.c +25836 -0
- data/ext/carray_kernels_reduce_boolean.c +329 -0
- data/ext/carray_kernels_reduce_cumulative.c +14592 -0
- data/ext/carray_kernels_reduce_extreme.c +16947 -0
- data/ext/carray_kernels_reduce_variance.c +3909 -0
- data/ext/carray_kernels_scan.c +3692 -0
- data/ext/carray_kernels_search.c +32137 -0
- data/ext/carray_kernels_sort.c +10625 -0
- data/ext/carray_kernels_triop.c +1391 -0
- data/ext/carray_lazy.c +567 -0
- data/ext/carray_loop.c +88 -200
- data/ext/carray_mask.c +848 -154
- data/ext/carray_math_kernel.h +120 -0
- data/ext/carray_mathfunc.c +10 -241
- data/ext/carray_median_percentile.c +1257 -0
- data/ext/carray_memory_view.c +1625 -0
- data/ext/carray_operator.c +1526 -318
- data/ext/carray_order.c +664 -1394
- data/ext/carray_partition.c +416 -0
- data/ext/carray_random.c +518 -0
- data/ext/carray_scatter.c +357 -0
- data/ext/carray_slab.c +1219 -0
- data/ext/carray_slab.h +84 -0
- data/ext/carray_sort.c +829 -0
- data/ext/carray_sort_kernel.c +620 -0
- data/ext/carray_struct.c +695 -0
- data/ext/carray_test.c +343 -229
- data/ext/carray_undef.c +34 -17
- data/ext/carray_utils.c +175 -74
- data/ext/extconf.rb +216 -55
- data/ext/mk_call_cfunc.rb +480 -0
- data/ext/mkkernel.rb +8842 -0
- data/ext/ruby_carray.c +202 -101
- data/ext/version.h +4 -14
- data/ext/version.rb +5 -13
- data/lib/carray/arrow_tensor.rb +401 -0
- data/lib/carray/attribute.rb +166 -0
- data/lib/carray/autoload_carray.rb +220 -0
- data/lib/carray/autoload_method_extension.rb +44 -0
- data/lib/carray/axis_group.rb +711 -0
- data/lib/carray/basics.rb +481 -0
- data/lib/carray/bincount_nd.rb +358 -0
- data/lib/carray/block_iterator.rb +604 -0
- data/lib/carray/boolean_reduce.rb +109 -0
- data/lib/carray/categorical.rb +561 -0
- data/lib/carray/categorical_iterator.rb +1062 -0
- data/lib/carray/complex.rb +150 -0
- data/lib/carray/conditional.rb +216 -0
- data/lib/carray/const_string.rb +228 -0
- data/lib/carray/construct.rb +139 -328
- data/lib/carray/core_extensions.rb +240 -0
- data/lib/carray/data_type_extension.rb +233 -0
- data/lib/carray/fixlen_string.rb +95 -0
- data/lib/carray/frame/concat.rb +132 -0
- data/lib/carray/frame/convert.rb +95 -0
- data/lib/carray/frame/csv_parser.rb +211 -0
- data/lib/carray/frame/frame.rb +649 -0
- data/lib/carray/frame/group.rb +186 -0
- data/lib/carray/frame/io.rb +164 -0
- data/lib/carray/frame/join.rb +248 -0
- data/lib/carray/frame/records.rb +99 -0
- data/lib/carray/frame/sort.rb +113 -0
- data/lib/carray/frame/verbs.rb +299 -0
- data/lib/carray/frame.rb +16 -0
- data/lib/carray/histogram.rb +512 -0
- data/lib/carray/inspect.rb +37 -20
- data/lib/carray/iterator.rb +57 -349
- data/lib/carray/lazy.rb +889 -0
- data/lib/carray/mask_gap_fill.rb +200 -0
- data/lib/carray/math.rb +78 -342
- data/lib/carray/meld_reduce.rb +289 -0
- data/lib/carray/methods/align_addr.rb +116 -0
- data/lib/carray/methods/bin.rb +128 -0
- data/lib/carray/methods/bincount.rb +87 -0
- data/lib/carray/methods/bit_string.rb +92 -0
- data/lib/carray/methods/broadcast.rb +63 -0
- data/lib/carray/methods/choose.rb +39 -0
- data/lib/carray/methods/composition.rb +280 -0
- data/lib/carray/methods/gather_nd.rb +206 -0
- data/lib/carray/methods/index.rb +39 -0
- data/lib/carray/methods/insert_block.rb +99 -0
- data/lib/carray/methods/is_in.rb +141 -0
- data/lib/carray/methods/join.rb +90 -0
- data/lib/carray/methods/locate_addr.rb +47 -0
- data/lib/carray/methods/mask_duplicates.rb +41 -0
- data/lib/carray/methods/meshgrid.rb +91 -0
- data/lib/carray/methods/mode.rb +126 -0
- data/lib/carray/methods/nunique.rb +46 -0
- data/lib/carray/methods/resize.rb +56 -0
- data/lib/carray/methods/snap.rb +156 -0
- data/lib/carray/methods/string_format.rb +57 -0
- data/lib/carray/methods/unique.rb +47 -0
- data/lib/carray/methods/value_counts.rb +71 -0
- data/lib/carray/mkmf.rb +124 -101
- data/lib/carray/runtime.rb +108 -0
- data/lib/carray/serialize.rb +478 -167
- data/lib/carray/slab_iterator.rb +292 -0
- data/lib/carray/stack.rb +291 -0
- data/lib/carray/string.rb +56 -180
- data/lib/carray/string_operation_extension.rb +289 -0
- data/lib/carray/struct.rb +335 -323
- data/lib/carray/struct_builder.rb +697 -0
- data/lib/carray/table.rb +41 -2
- data/lib/carray/time.rb +2255 -38
- data/lib/carray/window_iterator.rb +655 -0
- data/lib/carray.rb +55 -57
- metadata +163 -130
- data/Rakefile +0 -51
- data/TODO.md +0 -18
- data/ext/ca_iter_block.c +0 -257
- data/ext/ca_iter_dimension.c +0 -299
- data/ext/ca_iter_window.c +0 -214
- data/ext/ca_obj_mapping.c +0 -644
- data/ext/carray_iterator.c +0 -641
- data/ext/carray_math.rb +0 -850
- data/ext/carray_numeric.c +0 -259
- data/ext/carray_sort_addr.c +0 -254
- data/ext/carray_stat.c +0 -2100
- data/ext/carray_stat_proc.rb +0 -1999
- data/ext/mkmath.rb +0 -741
- data/ext/ruby_ccomplex.c +0 -509
- data/ext/ruby_float_func.c +0 -86
- data/lib/carray/array.rb +0 -8
- data/lib/carray/autoload/autoload_base.rb +0 -19
- data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
- data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
- data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
- data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
- data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
- data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
- data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
- data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
- data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
- data/lib/carray/autoload/autoload_gem_random.rb +0 -8
- data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
- data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
- data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
- data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
- data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
- data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
- data/lib/carray/autoload/autoload_object_link.rb +0 -1
- data/lib/carray/autoload/autoload_object_pack.rb +0 -2
- data/lib/carray/autoload.rb +0 -141
- data/lib/carray/basic.rb +0 -191
- data/lib/carray/broadcast.rb +0 -101
- data/lib/carray/compose.rb +0 -315
- data/lib/carray/convert.rb +0 -115
- data/lib/carray/info.rb +0 -110
- data/lib/carray/io/imagemagick.rb +0 -235
- data/lib/carray/mask.rb +0 -102
- data/lib/carray/math/histogram.rb +0 -177
- data/lib/carray/math/recurrence.rb +0 -93
- data/lib/carray/object/ca_obj_iterator.rb +0 -50
- data/lib/carray/object/ca_obj_link.rb +0 -50
- data/lib/carray/object/ca_obj_pack.rb +0 -99
- data/lib/carray/obsolete.rb +0 -256
- data/lib/carray/ordering.rb +0 -181
- data/lib/carray/testing.rb +0 -51
- data/lib/carray/transform.rb +0 -109
- data/misc/Methods.ja.md +0 -182
- data/misc/NOTE +0 -51
- data/spec/Classes/CABitfield_spec.rb +0 -58
- data/spec/Classes/CABlockIterator_spec.rb +0 -114
- data/spec/Classes/CABlock_spec.rb +0 -205
- data/spec/Classes/CAField_spec.rb +0 -39
- data/spec/Classes/CAGrid_spec.rb +0 -75
- data/spec/Classes/CAMap_spec.rb +0 -0
- data/spec/Classes/CAMapping_spec.rb +0 -105
- data/spec/Classes/CAObject_attribute_spec.rb +0 -33
- data/spec/Classes/CAObject_spec.rb +0 -33
- data/spec/Classes/CARefer_spec.rb +0 -93
- data/spec/Classes/CARepeat_spec.rb +0 -65
- data/spec/Classes/CASelect_spec.rb +0 -22
- data/spec/Classes/CAShift_spec.rb +0 -16
- data/spec/Classes/CAStruct_spec.rb +0 -71
- data/spec/Classes/CATranspose_spec.rb +0 -60
- data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
- data/spec/Classes/CAWindow_spec.rb +0 -54
- data/spec/Classes/CAWrap_spec.rb +0 -8
- data/spec/Classes/CArray_spec.rb +0 -184
- data/spec/Classes/CScalar_spec.rb +0 -55
- data/spec/Classes/ex1.rb +0 -46
- data/spec/Features/feature_130_spec.rb +0 -19
- data/spec/Features/feature_attributes_spec.rb +0 -280
- data/spec/Features/feature_boolean_spec.rb +0 -98
- data/spec/Features/feature_broadcast.rb +0 -116
- data/spec/Features/feature_cast_function.rb +0 -19
- data/spec/Features/feature_cast_spec.rb +0 -33
- data/spec/Features/feature_class_spec.rb +0 -84
- data/spec/Features/feature_complex_spec.rb +0 -42
- data/spec/Features/feature_composite_spec.rb +0 -124
- data/spec/Features/feature_convert_spec.rb +0 -46
- data/spec/Features/feature_copy_spec.rb +0 -123
- data/spec/Features/feature_creation_spec.rb +0 -84
- data/spec/Features/feature_element_spec.rb +0 -144
- data/spec/Features/feature_extream_spec.rb +0 -54
- data/spec/Features/feature_generate_spec.rb +0 -74
- data/spec/Features/feature_index_spec.rb +0 -69
- data/spec/Features/feature_mask_spec.rb +0 -580
- data/spec/Features/feature_math_spec.rb +0 -97
- data/spec/Features/feature_order_spec.rb +0 -146
- data/spec/Features/feature_ref_store_spec.rb +0 -209
- data/spec/Features/feature_serialization_spec.rb +0 -125
- data/spec/Features/feature_stat_spec.rb +0 -397
- data/spec/Features/feature_virtual_spec.rb +0 -48
- data/spec/Features/method_eq_spec.rb +0 -81
- data/spec/Features/method_is_nan_spec.rb +0 -12
- data/spec/Features/method_map_spec.rb +0 -54
- data/spec/Features/method_max_with.rb +0 -20
- data/spec/Features/method_min_with.rb +0 -19
- data/spec/Features/method_ne_spec.rb +0 -18
- data/spec/Features/method_project_spec.rb +0 -188
- data/spec/Features/method_ref_spec.rb +0 -27
- data/spec/Features/method_round_spec.rb +0 -11
- data/spec/Features/method_s_linspace_spec.rb +0 -48
- data/spec/Features/method_s_span_spec.rb +0 -14
- data/spec/Features/method_seq_spec.rb +0 -47
- data/spec/Features/method_sort_with.rb +0 -43
- data/spec/Features/method_sorted_with.rb +0 -29
- data/spec/Features/method_span_spec.rb +0 -42
- data/spec/Features/method_wrap_readonly_spec.rb +0 -43
- data/spec/UnitTest/test_CAVirtual.rb +0 -214
- data/spec/spec_all.rb +0 -10
- data/utils/ca_ase.rb +0 -21
- data/utils/ca_methods.rb +0 -15
- data/utils/cast_checker.rb +0 -30
- data/utils/convert_test.rb +0 -73
- data/utils/extract_yard.rb +0 -22
- data/utils/guess_shape.rb +0 -76
- data/utils/monkey_patch_methods.rb +0 -62
- data/utils/remove_resource_fork.sh +0 -5
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Pack / unpack an integer CArray to and from a packed-bit byte string,
|
|
2
|
+
# +nb+ bits per element.
|
|
3
|
+
|
|
4
|
+
class CArray
|
|
5
|
+
|
|
6
|
+
# @overload to_bit_string(nb)
|
|
7
|
+
# Returns a packed-bit byte string built from `self`, using `nb`
|
|
8
|
+
# bits per element.
|
|
9
|
+
# @param nb [Integer] bits per element.
|
|
10
|
+
# @return [String] byte string of length `ceil(nb * elements / 8)`.
|
|
11
|
+
def to_bit_string (nb)
|
|
12
|
+
hex = CArray.uint8(((nb*elements)/8.0).ceil)
|
|
13
|
+
hex.bits[nil].paste([0], self.bits[false,[(nb-1)..0]].flatten)
|
|
14
|
+
hex.bits[] = hex.bits[nil,[-1..0]]
|
|
15
|
+
return hex.to_s
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @overload from_bit_string(bstr, nb)
|
|
19
|
+
# Sets `self` by unpacking `bstr` as a packed-bit byte string
|
|
20
|
+
# with `nb` bits per element.
|
|
21
|
+
# @param bstr [String] packed byte string.
|
|
22
|
+
# @param nb [Integer] bits per element.
|
|
23
|
+
# @return [self]
|
|
24
|
+
def from_bit_string (bstr, nb)
|
|
25
|
+
hex = CArray.uint8(bstr.length).load_binary(bstr)
|
|
26
|
+
hex.bits[] = hex.bits[nil,[-1..0]]
|
|
27
|
+
bits = hex.bits.flatten
|
|
28
|
+
self.bits[false,[(nb-1)..0]][nil].paste([0], bits)
|
|
29
|
+
return self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @overload from_bit_string(bstr, nb, data_type = CA_INT32, dim = nil)
|
|
33
|
+
# Returns a new CArray built by unpacking `bstr` as a packed-bit
|
|
34
|
+
# byte string with `nb` bits per element.
|
|
35
|
+
# @param bstr [String] packed byte string.
|
|
36
|
+
# @param nb [Integer] bits per element.
|
|
37
|
+
# @param data_type [Symbol, Integer] result `data_type`.
|
|
38
|
+
# @param dim [Array<Integer>, nil] result shape; when `nil` the
|
|
39
|
+
# length is `floor(bstr.length * 8 / nb)`.
|
|
40
|
+
# @return [CArray]
|
|
41
|
+
def self.from_bit_string (bstr, nb, data_type=CA_INT32, dim=nil)
|
|
42
|
+
if dim
|
|
43
|
+
obj = CArray.new(data_type, dim)
|
|
44
|
+
else
|
|
45
|
+
dim0 = ((bstr.length*8)/nb.to_f).floor
|
|
46
|
+
obj = CArray.new(data_type, [dim0])
|
|
47
|
+
end
|
|
48
|
+
obj.from_bit_string(bstr, nb)
|
|
49
|
+
return obj
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @overload pack_bits
|
|
53
|
+
# Packs a 1-D boolean / 0-1 uint8 CArray of length `n` into a uint8
|
|
54
|
+
# CArray of `ceil(n / 8)` bytes, LSB-first within each byte. Exact
|
|
55
|
+
# inverse of the `.bitarray` view's unpack direction: for any packed
|
|
56
|
+
# uint8 array `p`, `p.bitarray.reshape(-1)[0...p.elements * 8].pack_bits`
|
|
57
|
+
# round-trips to `p`. Tail bits of the last byte (when `n` is not a
|
|
58
|
+
# multiple of 8) are zero-filled. The byte order matches the packed-bit
|
|
59
|
+
# convention used by Apache Arrow validity bitmaps and PEP 3118 `?` /
|
|
60
|
+
# `_Bool` at the bit level.
|
|
61
|
+
# @return [CArray] uint8 CArray of shape `[ceil(n / 8)]`.
|
|
62
|
+
# @raise [ArgumentError] when the receiver is not 1-D or its
|
|
63
|
+
# `data_type` is not one of `CA_BOOLEAN` / `CA_UINT8` / `CA_INT8`.
|
|
64
|
+
def pack_bits
|
|
65
|
+
unless data_type == CA_BOOLEAN || data_type == CA_UINT8 || data_type == CA_INT8
|
|
66
|
+
raise ArgumentError,
|
|
67
|
+
"pack_bits: expected CA_BOOLEAN / CA_UINT8 / CA_INT8 (got #{data_type_name})"
|
|
68
|
+
end
|
|
69
|
+
raise ArgumentError, "pack_bits: 1-D CArray expected (got rank #{rank})" unless rank == 1
|
|
70
|
+
n = elements
|
|
71
|
+
n_bytes = (n + 7) / 8
|
|
72
|
+
packed = CArray.uint8(n_bytes) { 0 }
|
|
73
|
+
return packed if n == 0
|
|
74
|
+
packed.bitarray.reshape(-1)[0..n-1] = self
|
|
75
|
+
packed
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @overload validity_bits
|
|
79
|
+
# Returns a packed uint8 CArray where bit `i` is 1 iff cell `i` of the
|
|
80
|
+
# receiver is *not* masked (LSB-first, length `ceil(elements / 8)`).
|
|
81
|
+
# Returns `nil` when the receiver has no mask; consumers such as Arrow
|
|
82
|
+
# treat a missing bitmap as "all valid", so `nil` is the correct
|
|
83
|
+
# omission-signalling value.
|
|
84
|
+
# Equivalent to `is_not_masked.pack_bits` when a mask is present.
|
|
85
|
+
# @return [CArray, nil] uint8 CArray of shape `[ceil(elements / 8)]`,
|
|
86
|
+
# or `nil` when no mask is set.
|
|
87
|
+
def validity_bits
|
|
88
|
+
return nil unless has_mask?
|
|
89
|
+
is_not_masked.reshape(-1).pack_bits
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload broadcast(*argv, expand_scalar: false)
|
|
4
|
+
# Returns each CArray argument broadcast to the common shape, or
|
|
5
|
+
# yields the broadcast tuple to a block.
|
|
6
|
+
#
|
|
7
|
+
# Non-CArray arguments (Float / Integer) pass through unchanged
|
|
8
|
+
# unless `expand_scalar` is true. CScalar instances are treated
|
|
9
|
+
# as ndim-0 scalars and excluded from the ndim check.
|
|
10
|
+
#
|
|
11
|
+
# All real CArray arguments must share the same `ndim`; implicit
|
|
12
|
+
# cross-ndim trailing-align is rejected. Insert size-1 axes with
|
|
13
|
+
# `:_` to align explicitly, e.g. `arg[:_, nil]` turns `(N,)` into
|
|
14
|
+
# `(1, N)` (row vector) and `arg[nil, :_]` turns it into `(N, 1)`
|
|
15
|
+
# (column vector).
|
|
16
|
+
#
|
|
17
|
+
# @param argv [Array<CArray, Numeric>] arguments to broadcast.
|
|
18
|
+
# @param expand_scalar [Boolean] when true, also expand CScalar
|
|
19
|
+
# and pass-through scalar arguments to the common shape.
|
|
20
|
+
# @yieldparam broadcast [Array<CArray, Numeric>] the broadcast
|
|
21
|
+
# arguments in input order.
|
|
22
|
+
# @return [Array<CArray, Numeric>] broadcast arguments, or the
|
|
23
|
+
# block's return value when a block is given.
|
|
24
|
+
# @raise [ArgumentError] when CArray arguments differ in `ndim`.
|
|
25
|
+
def CArray.broadcast (*argv, expand_scalar: false, &block)
|
|
26
|
+
|
|
27
|
+
sel = argv.select { |arg| arg.is_a?(CArray) && !arg.is_a?(CScalar) }
|
|
28
|
+
return argv if sel.empty?
|
|
29
|
+
|
|
30
|
+
ndims = sel.map(&:ndim).uniq
|
|
31
|
+
if ndims.size > 1
|
|
32
|
+
shapes = argv.each_with_index
|
|
33
|
+
.select { |a, _| a.is_a?(CArray) && !a.is_a?(CScalar) }
|
|
34
|
+
.map { |a, i| " arg[#{i}]: shape=(#{a.shape.join(', ')})" }
|
|
35
|
+
raise ArgumentError, <<~MSG.strip
|
|
36
|
+
CArray.broadcast: ndim mismatch (got #{ndims.sort.join(' and ')})
|
|
37
|
+
#{shapes.join("\n")}
|
|
38
|
+
CArray does not implicit-broadcast across ndim. Insert a size-1
|
|
39
|
+
axis with :_ to align explicitly, e.g.
|
|
40
|
+
arg[:_, nil] # (N,) -> (1, N) row-vector
|
|
41
|
+
arg[nil, :_] # (N,) -> (N, 1) column-vector
|
|
42
|
+
MSG
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
ndim = sel.first.ndim
|
|
46
|
+
dim = (0...ndim).map { |k| sel.map { |a| a.shape[k] }.max }
|
|
47
|
+
|
|
48
|
+
list = argv.map do |arg|
|
|
49
|
+
case arg
|
|
50
|
+
when CScalar
|
|
51
|
+
expand_scalar ? arg.broadcast_to(*dim) : arg
|
|
52
|
+
when CArray
|
|
53
|
+
arg.broadcast_to(*dim)
|
|
54
|
+
else
|
|
55
|
+
expand_scalar ? arg : arg
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
return block.call(*list) if block
|
|
60
|
+
return list
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload choose(choices, data_type: nil)
|
|
4
|
+
# Returns label-based per-cell selection: `self` is an integer
|
|
5
|
+
# label array, and `choices` is a list indexed by those labels.
|
|
6
|
+
#
|
|
7
|
+
# Where `self == i`, the result takes `choices[i]` -- a scalar
|
|
8
|
+
# fills those cells, a CArray contributes its corresponding
|
|
9
|
+
# cells. The result has the same shape as `self`.
|
|
10
|
+
#
|
|
11
|
+
# @param choices [Array<CArray, Object>] values indexed by the
|
|
12
|
+
# labels in `self`; each entry is either a same-shape CArray
|
|
13
|
+
# or a scalar fill.
|
|
14
|
+
# @param data_type [Symbol, Integer, nil] result `data_type`.
|
|
15
|
+
# When `nil` it is inferred: `CArray.result_type` of the CArray
|
|
16
|
+
# choices, or `CA_OBJECT` when every choice is a scalar.
|
|
17
|
+
# @return [CArray] new CArray with the shape of `self` holding
|
|
18
|
+
# the chosen values.
|
|
19
|
+
# @example
|
|
20
|
+
# ref = CA_INT([[0, 1, 2], [1, 2, 0], [2, 0, 1]])
|
|
21
|
+
# a = CArray.int(3, 3).seq(1)
|
|
22
|
+
# b = CArray.int(3, 3).seq(11)
|
|
23
|
+
# c = CArray.int(3, 3).seq(21)
|
|
24
|
+
# ref.choose([a, b, c]) # per-cell pick from a / b / c
|
|
25
|
+
# ref.choose(["a", "b", "c"]) # recode labels to values
|
|
26
|
+
def choose (choices, data_type: nil)
|
|
27
|
+
unless data_type
|
|
28
|
+
ca = choices.select { |v| v.is_a?(CArray) }
|
|
29
|
+
data_type = ca.empty? ? CA_OBJECT : CArray.result_type(*ca)
|
|
30
|
+
end
|
|
31
|
+
out = template(data_type)
|
|
32
|
+
choices.each_with_index do |v, i|
|
|
33
|
+
s = self.eq(i)
|
|
34
|
+
out[s] = v.is_a?(CArray) ? v[s] : v
|
|
35
|
+
end
|
|
36
|
+
out
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Composition methods cheat sheet (CArray 3.0 surface)
|
|
2
|
+
#
|
|
3
|
+
# The three class methods `meld` / `stack` / `montage` form a uniform-input
|
|
4
|
+
# view-default surface; their ragged eager counterparts `concatenate` / --
|
|
5
|
+
# / `mosaic` accept non-uniform pieces.
|
|
6
|
+
#
|
|
7
|
+
# This file holds the eager ragged family (`concatenate` / `mosaic`) and
|
|
8
|
+
# the 1-D column bundler (`tabulate`). `split` (the inverse of stack) lives
|
|
9
|
+
# in carray/stack.rb; the sub-region copy pair (`paste` / `crop`) lives in
|
|
10
|
+
# carray/basics.rb; the shape-edit helpers (`resize`, `insert_block` /
|
|
11
|
+
# `delete_block`) live in carray/methods/. The CAStack view-default trio
|
|
12
|
+
# (`stack` / `meld` / `montage`, plus `CArray#stack` and `CAStack#append`)
|
|
13
|
+
# lives in carray/stack.rb. The cheat sheet below covers the whole family.
|
|
14
|
+
#
|
|
15
|
+
# | view (same dtype) | eager (auto-cast)
|
|
16
|
+
# -------------------+------------------------+-----------------------
|
|
17
|
+
# concat existing | meld (CAMeld view) | concatenate (materialised)
|
|
18
|
+
# axis (ndim same) | |
|
|
19
|
+
# -------------------+------------------------+-----------------------
|
|
20
|
+
# add new axis | stack (CAStack view) | (= ragged impossible)
|
|
21
|
+
# (ndim grows) | |
|
|
22
|
+
# -------------------+------------------------+-----------------------
|
|
23
|
+
# tile grid | montage | mosaic (materialised)
|
|
24
|
+
# (ndim same, axes | |
|
|
25
|
+
# extend by tdim) | |
|
|
26
|
+
#
|
|
27
|
+
# `meld` (CAMeld view) is the unified surface for concatenation along an
|
|
28
|
+
# existing axis: it handles both uniform and ragged pieces and returns a
|
|
29
|
+
# CAMeld view. Parents must agree on data_type — cast beforehand or use
|
|
30
|
+
# `concatenate` (eager, auto-casts).
|
|
31
|
+
#
|
|
32
|
+
# Vocabulary:
|
|
33
|
+
# meld = melt + weld; pieces dissolve their boundaries along the
|
|
34
|
+
# named axis and are regarded as one; returns a CAMeld view
|
|
35
|
+
# stack = K parents pushed onto a new K axis (CAStack view)
|
|
36
|
+
# montage = uniform tiles arranged in a regular grid (ImageMagick)
|
|
37
|
+
# concatenate = 1-axis concat, eager materialised copy with auto-cast
|
|
38
|
+
# mosaic = irregular pieces fitted into a grid (art-form analog)
|
|
39
|
+
#
|
|
40
|
+
# Examples (a, b, c are each shape [3, 4]):
|
|
41
|
+
#
|
|
42
|
+
# CArray.meld([a, b, c]) #=> view, shape (9, 4)
|
|
43
|
+
# CArray.meld([a, b, c], axis: 1) #=> view, shape (3, 12)
|
|
44
|
+
# CArray.meld([a, b, c]).to_ca #=> eager CArray, shape (9, 4)
|
|
45
|
+
# CArray.stack([a, b, c]) #=> view, shape (3, 3, 4) (K outermost)
|
|
46
|
+
# CArray.stack([a, b, c], axis: -1) #=> view, shape (3, 4, 3) (RGB pattern)
|
|
47
|
+
# CArray.montage([a, b, c, d], [2, 2], axis: 0) #=> view, shape (6, 8) (2x2 image tile)
|
|
48
|
+
#
|
|
49
|
+
# # data_type is a kwarg (= 3.0 breaking); omitted = result_type auto-infer
|
|
50
|
+
# CArray.meld([a, b, c], axis: 0, data_type: :float64)
|
|
51
|
+
# CArray.meld([a, b, c]) # data_type inferred
|
|
52
|
+
#
|
|
53
|
+
# `tabulate` bundles a list of 1-D arrays into a 2-D table (one column
|
|
54
|
+
# each). Block-matrix assembly is `mosaic`; vertical stacking of 2-D
|
|
55
|
+
# tables is `concatenate(axis: 0)`. (The old `join` / block-style
|
|
56
|
+
# helper was retired in 3.0 -- its column-major nesting was confusing and
|
|
57
|
+
# `mosaic` covers block assembly correctly.)
|
|
58
|
+
#
|
|
59
|
+
# Known limitation: scalar indexing on a CAStack-rooted view drops ndim
|
|
60
|
+
# and triggers a full materialise fallback. Use range indexing (e.g.
|
|
61
|
+
# `meld(...)[k..k, nil]` instead of `meld(...)[k, nil]`) for partial-use
|
|
62
|
+
# perf, or `.to_ca` upfront to materialise eagerly.
|
|
63
|
+
|
|
64
|
+
class CArray
|
|
65
|
+
|
|
66
|
+
# CArray#paste / #crop (sub-region copy) live in carray/basics.rb.
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------- eager ragged 2
|
|
69
|
+
|
|
70
|
+
# @overload concatenate(list, axis: 0, data_type: nil)
|
|
71
|
+
# Returns `list` concatenated along a single existing axis. Eager
|
|
72
|
+
# (returns a fresh CArray) and accepts non-uniform pieces (varying
|
|
73
|
+
# sizes along the `axis`); non-tile axes must agree across pieces.
|
|
74
|
+
#
|
|
75
|
+
# Use `CArray.meld` for the uniform-shape view-default counterpart.
|
|
76
|
+
#
|
|
77
|
+
# @param list [Array<CArray>] pieces to concatenate.
|
|
78
|
+
# @param axis [Integer] axis to concatenate along.
|
|
79
|
+
# @param data_type [Symbol, Integer, nil] result `data_type`;
|
|
80
|
+
# inferred via `result_type` when `nil`.
|
|
81
|
+
# @return [CArray] fresh CArray with per-piece `axis` sizes summed.
|
|
82
|
+
# @raise [ArgumentError] when `list` is empty or piece shapes are
|
|
83
|
+
# inconsistent.
|
|
84
|
+
def self.concatenate (list, axis: 0, data_type: nil)
|
|
85
|
+
raise ArgumentError, "concatenate: list must not be empty" if list.empty?
|
|
86
|
+
__ragged_paste(list, [list.size], axis, data_type)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @overload concatenate(*others, axis: 0, data_type: nil)
|
|
90
|
+
# Instance form of {CArray.concatenate}: returns `[self, *others]`
|
|
91
|
+
# concatenated along `axis` as a fresh CArray. Eager auto-cast
|
|
92
|
+
# counterpart of `#meld` (view).
|
|
93
|
+
#
|
|
94
|
+
# @param others [Array<CArray>] additional pieces.
|
|
95
|
+
# @param axis [Integer] axis to concatenate along.
|
|
96
|
+
# @param data_type [Symbol, Integer, nil] result `data_type`;
|
|
97
|
+
# inferred via `result_type` when `nil`.
|
|
98
|
+
# @return [CArray] fresh CArray.
|
|
99
|
+
# @raise [ArgumentError] when no `others` are given.
|
|
100
|
+
def concatenate (*others, axis: 0, data_type: nil)
|
|
101
|
+
raise ArgumentError, "concatenate: at least one other array required" if others.empty?
|
|
102
|
+
CArray.concatenate([self, *others], axis: axis, data_type: data_type)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @overload mosaic(list, tdim, axis: 0, data_type: nil)
|
|
106
|
+
# Returns `list` tiled into an N-D grid layout described by
|
|
107
|
+
# `tdim`. Eager (returns a fresh CArray), accepts non-uniform
|
|
108
|
+
# sizes along the tile axes with block-matrix consistency
|
|
109
|
+
# (row-by-row / column-by-column agreement).
|
|
110
|
+
#
|
|
111
|
+
# @param list [Array<CArray>] pieces to tile; length must equal
|
|
112
|
+
# the product of `tdim`.
|
|
113
|
+
# @param tdim [Array<Integer>] tile grid shape.
|
|
114
|
+
# @param axis [Integer] first tile axis in the result.
|
|
115
|
+
# @param data_type [Symbol, Integer, nil] result `data_type`;
|
|
116
|
+
# inferred when `nil`.
|
|
117
|
+
# @return [CArray] fresh CArray with tile axes extended by summed
|
|
118
|
+
# per-tile sizes.
|
|
119
|
+
# @raise [ArgumentError] when `list` is empty, `tdim` is
|
|
120
|
+
# ill-formed, or piece shapes violate block-matrix consistency.
|
|
121
|
+
def self.mosaic (list, tdim, axis: 0, data_type: nil)
|
|
122
|
+
raise ArgumentError, "mosaic: list must not be empty" if list.empty?
|
|
123
|
+
unless tdim.is_a?(Array) && tdim.size > 0
|
|
124
|
+
raise ArgumentError, "mosaic: tdim must be a non-empty Array of Integer"
|
|
125
|
+
end
|
|
126
|
+
expected = tdim.inject(1, :*)
|
|
127
|
+
unless expected == list.size
|
|
128
|
+
raise ArgumentError,
|
|
129
|
+
"mosaic: tdim product (#{expected}) must equal list size (#{list.size})"
|
|
130
|
+
end
|
|
131
|
+
__ragged_paste(list, tdim, axis, data_type)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Shared eager paste-loop helper (= ex-`combine` paste implementation,
|
|
135
|
+
# preserved verbatim for concatenate / mosaic). Returns a fresh CArray of
|
|
136
|
+
# shape obtained by tiling `list` over `tdim` starting at `axis`, with
|
|
137
|
+
# per-piece dim sizes summed along each tile axis (= block-matrix
|
|
138
|
+
# consistency required across rows / columns).
|
|
139
|
+
def self.__ragged_paste (list, tdim, axis, data_type)
|
|
140
|
+
list = CArray.promote_list(list, data_type: data_type)
|
|
141
|
+
# promote_list has already enforced homogeneity (= common data_type, and
|
|
142
|
+
# for Face elements: same Face class + portable + state-compatible). So,
|
|
143
|
+
# mirroring CArray.stack, paste at the storage level and re-wrap the
|
|
144
|
+
# result with face_lift, rather than special-casing data_class / DATA_SIZE
|
|
145
|
+
# here (= which only ever handled CARecord and silently corrupted plain
|
|
146
|
+
# fixlen / numeric Faces by leaving bytes nil).
|
|
147
|
+
face_parent = list[0].face? ? list[0] : nil
|
|
148
|
+
if face_parent
|
|
149
|
+
list = list.map { |x| s = x; s = s.parent while s.face?; s }
|
|
150
|
+
end
|
|
151
|
+
data_type = list[0].data_type
|
|
152
|
+
bytes = (data_type == :fixlen) ? list[0].bytes : nil
|
|
153
|
+
# promote_list guarantees a non-empty list of CArrays, so the reference
|
|
154
|
+
# shape is simply the first element (a CScalar carries shape [1], so a
|
|
155
|
+
# leading scalar is fine; the scalar-expansion pass below broadcasts it).
|
|
156
|
+
ref = list[0]
|
|
157
|
+
dim = ref.shape
|
|
158
|
+
ndim = ref.ndim
|
|
159
|
+
tndim = tdim.size
|
|
160
|
+
axis = CArray.normalize_axis(axis, ndim - tndim + 1, "concatenate/mosaic")
|
|
161
|
+
|
|
162
|
+
list = list.map do |x|
|
|
163
|
+
if x.scalar?
|
|
164
|
+
rdim = dim.clone
|
|
165
|
+
rdim[axis] = :%
|
|
166
|
+
x = x[*rdim]
|
|
167
|
+
end
|
|
168
|
+
x
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
block = CArray.object(*tdim).tap { |a| a[] = list }
|
|
172
|
+
# Measure the per-tile-axis sizes from the representative line (= all
|
|
173
|
+
# other tile coords 0). Tile axes may be ragged, but every piece must
|
|
174
|
+
# then be block-matrix consistent (= same size along a tile axis as its
|
|
175
|
+
# line) and must agree with the reference on each non-tile axis.
|
|
176
|
+
edim = tdim.clone
|
|
177
|
+
tile_sizes = Array.new(tndim) { [] }
|
|
178
|
+
offset = Array.new(tndim) { [] }
|
|
179
|
+
probe = Array.new(tndim, 0)
|
|
180
|
+
tndim.times do |i|
|
|
181
|
+
edim[i] = 0
|
|
182
|
+
probe.map! { 0 }
|
|
183
|
+
probe[i] = nil
|
|
184
|
+
block[*probe].each do |e|
|
|
185
|
+
offset[i] << edim[i]
|
|
186
|
+
tile_sizes[i] << e.shape[axis + i]
|
|
187
|
+
edim[i] += e.shape[axis + i]
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
block.each_with_index do |item, *tidx|
|
|
191
|
+
unless item.ndim == ndim
|
|
192
|
+
raise ArgumentError,
|
|
193
|
+
"concatenate/mosaic: piece at tile #{tidx.inspect} has ndim " \
|
|
194
|
+
"#{item.ndim} (expected #{ndim})"
|
|
195
|
+
end
|
|
196
|
+
ndim.times do |d|
|
|
197
|
+
i = d - axis
|
|
198
|
+
on_tile = (i >= 0 && i < tndim)
|
|
199
|
+
expected = on_tile ? tile_sizes[i][tidx[i]] : dim[d]
|
|
200
|
+
unless item.shape[d] == expected
|
|
201
|
+
raise ArgumentError,
|
|
202
|
+
"concatenate/mosaic: piece at tile #{tidx.inspect} has " \
|
|
203
|
+
"#{on_tile ? 'tile' : 'non-tile'} axis #{d} size " \
|
|
204
|
+
"#{item.shape[d]} (expected #{expected}); pieces must agree " \
|
|
205
|
+
"on non-tile axes and be block-matrix consistent along tile axes"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
newdim = dim.clone
|
|
210
|
+
newdim[axis, tndim] = edim
|
|
211
|
+
obj = CArray.new(data_type, newdim, bytes: bytes)
|
|
212
|
+
idx = newdim.map { 0 }
|
|
213
|
+
block.each_with_index do |item, *tidx|
|
|
214
|
+
(axis...axis + tndim).each_with_index do |d, i|
|
|
215
|
+
idx[d] = offset[i][tidx[i]]
|
|
216
|
+
end
|
|
217
|
+
obj.paste(idx, item)
|
|
218
|
+
end
|
|
219
|
+
obj = obj.face_lift(face_parent) if face_parent
|
|
220
|
+
obj
|
|
221
|
+
end
|
|
222
|
+
private_class_method :__ragged_paste
|
|
223
|
+
|
|
224
|
+
# ---------------------------------------------------------------- tabulate
|
|
225
|
+
|
|
226
|
+
# @overload tabulate(columns, data_type: nil)
|
|
227
|
+
# Returns a 2-D table assembled from a list of column blocks,
|
|
228
|
+
# coerced to a common `data_type`. Eager (returns a fresh, owned
|
|
229
|
+
# CArray) -- the point is to materialise a typed table, not a view.
|
|
230
|
+
#
|
|
231
|
+
# Each entry is a 1-D array (one column, length L) or a 2-D array
|
|
232
|
+
# (a block of `L x k` columns). All entries must share the same
|
|
233
|
+
# length L; `tabulate` does not pad ragged lengths. Column counts
|
|
234
|
+
# may differ: entries are concatenated along the column axis, so
|
|
235
|
+
# a 1-column, a 3-column and a 2-column block produce a 6-column
|
|
236
|
+
# table. The result `data_type` is inferred (`result_type` of the
|
|
237
|
+
# entries) unless `data_type` is given.
|
|
238
|
+
#
|
|
239
|
+
# For block-matrix assembly use `mosaic`; to stack 2-D tables
|
|
240
|
+
# vertically use `concatenate(axis: 0)`.
|
|
241
|
+
#
|
|
242
|
+
# @param columns [Array<CArray>] 1-D columns and/or 2-D column
|
|
243
|
+
# blocks, all of equal length `L`.
|
|
244
|
+
# @param data_type [Symbol, Integer, nil] result `data_type`;
|
|
245
|
+
# inferred when `nil`.
|
|
246
|
+
# @return [CArray] 2-D CArray of shape `(L, total column count)`.
|
|
247
|
+
# @raise [ArgumentError] when `columns` is empty, entries are
|
|
248
|
+
# not 1-D or 2-D CArrays, or row counts disagree.
|
|
249
|
+
# @example
|
|
250
|
+
# c1 = CA_INT([1, 2, 3])
|
|
251
|
+
# c2 = CA_DOUBLE([4.5, 5.5, 6.5])
|
|
252
|
+
# CArray.tabulate([c1, c2]) # float64 (3, 2)
|
|
253
|
+
# CArray.tabulate([c1, c2], data_type: :int32) # int32 (3, 2)
|
|
254
|
+
def self.tabulate (columns, data_type: nil)
|
|
255
|
+
raise ArgumentError, "tabulate: columns must not be empty" if columns.empty?
|
|
256
|
+
blocks = columns.map do |c|
|
|
257
|
+
unless c.is_a?(CArray) && (c.ndim == 1 || c.ndim == 2)
|
|
258
|
+
raise ArgumentError, "tabulate: each column must be a 1-D or 2-D CArray"
|
|
259
|
+
end
|
|
260
|
+
c.ndim == 1 ? c[nil, :_] : c # promote a bare column to (L, 1)
|
|
261
|
+
end
|
|
262
|
+
len = blocks[0].shape[0]
|
|
263
|
+
blocks.each_with_index do |b, i|
|
|
264
|
+
unless b.shape[0] == len
|
|
265
|
+
raise ArgumentError,
|
|
266
|
+
"tabulate: all columns must have equal length (row count) " \
|
|
267
|
+
"(column 0 has length #{len}, column #{i} has length " \
|
|
268
|
+
"#{b.shape[0]}); tabulate does not pad ragged lengths"
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
# Equal-length blocks, ragged column counts -> concatenate along the
|
|
272
|
+
# column axis with a common (coerced or inferred) data_type.
|
|
273
|
+
concatenate(blocks, axis: 1, data_type: data_type)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# CArray#split (the inverse of CArray.stack) lives in carray/stack.rb,
|
|
277
|
+
# next to stack. CArray#resize / #insert_block / #delete_block live in
|
|
278
|
+
# carray/methods/ (resize.rb, insert_block.rb), autoloaded.
|
|
279
|
+
|
|
280
|
+
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# N-D arbitrary-position gather / scatter (CArray implementation of
|
|
2
|
+
# TensorFlow `gather_nd` / `scatter_nd_update`).
|
|
3
|
+
#
|
|
4
|
+
# Natural N-D extension of `take_along_axis` (= fiber-aligned gather along axis).
|
|
5
|
+
# Treats the trailing K axes of `indices.shape = (..., K)` as coordinate tuples
|
|
6
|
+
# and consumes the first K axes of params while carrying the remaining axes (= rest).
|
|
7
|
+
#
|
|
8
|
+
# Implementation goes through a CAMapping view of the flattened array:
|
|
9
|
+
# `params.flatten[flat_addr]`. Pure Ruby, no additional framework, composed
|
|
10
|
+
# entirely from existing indexers as in `take_along_axis`. Since CAMapping is
|
|
11
|
+
# writable, write-through via `put_nd` uses last-write-wins semantics (= last
|
|
12
|
+
# value wins on duplicate indices, same contract as `put_along_axis`). For
|
|
13
|
+
# accumulate semantics, route to the `scatter_*!` family instead.
|
|
14
|
+
|
|
15
|
+
class CArray
|
|
16
|
+
|
|
17
|
+
# @overload gather_nd(indices)
|
|
18
|
+
# Returns elements (or sub-arrays) gathered from `self` at the
|
|
19
|
+
# N-D coordinates given by `indices`.
|
|
20
|
+
#
|
|
21
|
+
# `indices` accepts two equivalent forms:
|
|
22
|
+
#
|
|
23
|
+
# * **stacked** — a single CArray whose last axis enumerates a
|
|
24
|
+
# K-dimensional coordinate tuple into the first K axes of `self`.
|
|
25
|
+
# * **per-axis** — an `Array` of K coordinate CArrays (one per
|
|
26
|
+
# consumed axis). The per-axis arrays are broadcast together
|
|
27
|
+
# (via {CArray.broadcast}) and stacked along a new trailing axis,
|
|
28
|
+
# so `gather_nd([i, j])` == `gather_nd(CArray.stack([i, j], axis: -1))`.
|
|
29
|
+
# An Integer scalar is accepted for a constant axis and broadcast
|
|
30
|
+
# to the common shape. Entries must be CArray or Integer: Ruby
|
|
31
|
+
# Array literals are rejected (wrap them with `CA_INT64(...)`
|
|
32
|
+
# yourself), keeping this a copy-free gather path.
|
|
33
|
+
#
|
|
34
|
+
# The remaining `self.shape[K..-1]` axes (called `rest`) are carried
|
|
35
|
+
# through:
|
|
36
|
+
#
|
|
37
|
+
# self.shape = (D0, ..., D_{K-1}, *rest)
|
|
38
|
+
# indices.shape = (*outer, K) # stacked form
|
|
39
|
+
# result.shape = (*outer, *rest)
|
|
40
|
+
#
|
|
41
|
+
# In the fully-degenerate case where both `outer` and `rest` are
|
|
42
|
+
# empty (a single full coordinate via 1-D `indices` of shape `(K,)`
|
|
43
|
+
# with `K == ndim`), the result is a 1-element `(1,)` CArray,
|
|
44
|
+
# following CArray's scalar model (CScalar carries shape `[1]`),
|
|
45
|
+
# not a 0-dim array.
|
|
46
|
+
#
|
|
47
|
+
# The result is a fresh materialised CArray. Negative indices on
|
|
48
|
+
# each coordinate axis follow CArray's standard wrap rule
|
|
49
|
+
# (`-1` == last); out-of-range indices raise. Duplicate
|
|
50
|
+
# coordinates in `indices` are fine on gather: the same value is
|
|
51
|
+
# picked multiple times. See {#put_nd} for the duplicate-write
|
|
52
|
+
# story.
|
|
53
|
+
#
|
|
54
|
+
# @param indices [CArray, Array<CArray, Integer>] stacked integer
|
|
55
|
+
# CArray with `ndim >= 1` and last axis size `K` in `[1, ndim]`,
|
|
56
|
+
# or an Array of K per-axis coordinate CArrays (Integer scalars
|
|
57
|
+
# allowed per axis).
|
|
58
|
+
# @return [CArray] materialised result with shape `outer + rest`.
|
|
59
|
+
# @raise [ArgumentError] when `indices` is neither a CArray nor an
|
|
60
|
+
# Array, is 0-dim, is non-integer, or has a last-axis size
|
|
61
|
+
# outside `[1, ndim]`.
|
|
62
|
+
# @raise [IndexError] when a coordinate is out of range on any axis.
|
|
63
|
+
def gather_nd (indices)
|
|
64
|
+
flat_addr, outer, rest = gather_nd_flat_addr(indices, "gather_nd")
|
|
65
|
+
out_shape = outer + rest
|
|
66
|
+
# flatten + 1-D fancy indexing -> CAMapping view -> materialise via .copy.
|
|
67
|
+
result = self.flatten[flat_addr].copy
|
|
68
|
+
out_shape.empty? ? result : result.reshape(*out_shape)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @overload put_nd(indices, values)
|
|
72
|
+
# Sets `self` at the N-D coordinates given by `indices` to
|
|
73
|
+
# `values`. Inverse of {#gather_nd}.
|
|
74
|
+
#
|
|
75
|
+
# self.shape = (D0, ..., D_{K-1}, *rest)
|
|
76
|
+
# indices.shape = (*outer, K)
|
|
77
|
+
# values broadcast to (*outer, *rest)
|
|
78
|
+
#
|
|
79
|
+
# Duplicate coordinates in `indices` use **last-write-wins**
|
|
80
|
+
# semantics, matching `put_along_axis`. Accumulate semantics
|
|
81
|
+
# (`+=`) are not provided here; route to the `scatter_*!`
|
|
82
|
+
# family instead (e.g. `self.flatten.scatter_add!(flat_addr, vals)`).
|
|
83
|
+
#
|
|
84
|
+
# @param indices [CArray, Array<CArray, Integer>] stacked integer
|
|
85
|
+
# CArray shaped `(*outer, K)`, or an Array of K per-axis
|
|
86
|
+
# coordinate arrays (same forms as {#gather_nd}).
|
|
87
|
+
# @param values [CArray, Numeric] values broadcastable to
|
|
88
|
+
# `(*outer, *rest)`.
|
|
89
|
+
# @return [self]
|
|
90
|
+
# @raise [ArgumentError] on the same conditions as {#gather_nd}.
|
|
91
|
+
# @raise [IndexError] when a coordinate is out of range on any axis.
|
|
92
|
+
def put_nd (indices, values)
|
|
93
|
+
flat_addr, _outer, _rest = gather_nd_flat_addr(indices, "put_nd")
|
|
94
|
+
self.flatten[flat_addr] = values
|
|
95
|
+
self
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
# Shared helper: compute the flat-address CArray (shape = outer + rest)
|
|
101
|
+
# for both `gather_nd` and `put_nd`. Returns `[flat_addr, outer, rest]`.
|
|
102
|
+
def gather_nd_flat_addr (indices, name)
|
|
103
|
+
# Per-axis form: an Array of K coordinate arrays. Broadcast them to a
|
|
104
|
+
# common outer shape and stack along a new trailing axis, yielding the
|
|
105
|
+
# (*outer, K) stacked form the rest of this helper already handles.
|
|
106
|
+
indices = gather_nd_stack_axes(indices, name) if indices.is_a?(Array)
|
|
107
|
+
|
|
108
|
+
unless indices.is_a?(CArray)
|
|
109
|
+
raise ArgumentError, "#{name}: indices must be a CArray or an Array of per-axis CArrays"
|
|
110
|
+
end
|
|
111
|
+
if indices.ndim == 0
|
|
112
|
+
raise ArgumentError, "#{name}: indices must have at least 1 dimension"
|
|
113
|
+
end
|
|
114
|
+
unless indices.integer?
|
|
115
|
+
raise ArgumentError,
|
|
116
|
+
"#{name}: indices must be an integer CArray (got #{indices.data_type})"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
k = indices.shape[-1]
|
|
120
|
+
unless k.is_a?(Integer) && k >= 1 && k <= ndim
|
|
121
|
+
raise ArgumentError,
|
|
122
|
+
"#{name}: indices last-axis size #{k} out of [1, #{ndim}]"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
outer = indices.shape[0..-2] # may be []
|
|
126
|
+
rest = (k < ndim) ? shape[k..-1] : [] # may be []
|
|
127
|
+
m = outer.empty? ? 1 : outer.inject(:*)
|
|
128
|
+
rest_size = rest.empty? ? 1 : rest.inject(:*)
|
|
129
|
+
|
|
130
|
+
# Stride per consumed axis i = product of params.shape[(i+1)..-1]
|
|
131
|
+
# (covers remaining consumed axes + all rest axes).
|
|
132
|
+
strides_a = (0...k).map { |i| shape[(i+1)..-1].inject(1, :*) }
|
|
133
|
+
strides = CA_INT64(strides_a).reshape(1, k)
|
|
134
|
+
|
|
135
|
+
# Per-axis dimension for negative-index normalize + OOB check.
|
|
136
|
+
dims = CA_INT64(shape[0...k]).reshape(1, k)
|
|
137
|
+
|
|
138
|
+
flat_idx_raw = indices.reshape(m, k)
|
|
139
|
+
|
|
140
|
+
# OOB check (per-axis): require -dim <= idx < dim on every coordinate.
|
|
141
|
+
if (flat_idx_raw >= dims).any
|
|
142
|
+
raise IndexError, "#{name}: coordinate >= dim on some axis"
|
|
143
|
+
end
|
|
144
|
+
if (flat_idx_raw < -dims).any
|
|
145
|
+
raise IndexError, "#{name}: coordinate < -dim on some axis"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Wrap negatives: (-dim..-1) -> (0..dim-1) via (idx + dim) % dim.
|
|
149
|
+
# Safe under -dim <= idx < dim (just checked).
|
|
150
|
+
flat_idx = (flat_idx_raw + dims) % dims
|
|
151
|
+
|
|
152
|
+
base = (flat_idx * strides).sum(axis: 1).int64.reshape(m) # (M,)
|
|
153
|
+
|
|
154
|
+
flat_addr =
|
|
155
|
+
if rest.empty?
|
|
156
|
+
base # (M,)
|
|
157
|
+
else
|
|
158
|
+
offsets = CArray.int64(rest_size).seq
|
|
159
|
+
base.reshape(m, 1) + offsets.reshape(1, rest_size) # (M, rest_size)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
[flat_addr, outer, rest]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Convert the per-axis form (an Array of K coordinate arrays) into the
|
|
166
|
+
# stacked `(*outer, K)` CArray. Coordinate arrays are broadcast to a
|
|
167
|
+
# common `outer` shape (via CArray.broadcast, which rejects cross-ndim
|
|
168
|
+
# implicit align) and stacked along a new trailing axis. Integer
|
|
169
|
+
# scalars are accepted per axis and expand to the common shape; an
|
|
170
|
+
# all-scalar list yields the 1-D `(K,)` single-coordinate form.
|
|
171
|
+
def gather_nd_stack_axes (list, name)
|
|
172
|
+
if list.empty?
|
|
173
|
+
raise ArgumentError, "#{name}: coordinate list must not be empty"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# This is a high-performance gather path: per-axis entries must be
|
|
177
|
+
# CArray coordinate arrays (or a bare Integer scalar for a constant
|
|
178
|
+
# axis). Ruby Arrays are rejected on purpose -- coercing them via
|
|
179
|
+
# CA_INT64 on every call would defeat the point of the API; wrap them
|
|
180
|
+
# yourself once (CA_INT64(...)) if you have array literals.
|
|
181
|
+
list.each do |c|
|
|
182
|
+
unless c.is_a?(CArray) || c.is_a?(Integer)
|
|
183
|
+
raise ArgumentError,
|
|
184
|
+
"#{name}: per-axis coordinate must be a CArray or Integer (got #{c.class})"
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# All scalars -> a single K-coordinate tuple (degenerate stacked form).
|
|
189
|
+
if list.none? { |c| c.is_a?(CArray) }
|
|
190
|
+
return CA_INT64(list)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
coords = CArray.broadcast(*list, expand_scalar: true)
|
|
194
|
+
ref = coords.find { |c| c.is_a?(CArray) && !c.is_a?(CScalar) }
|
|
195
|
+
coords = coords.map do |c|
|
|
196
|
+
case c
|
|
197
|
+
when CArray
|
|
198
|
+
c
|
|
199
|
+
else # Integer scalar on this axis
|
|
200
|
+
CArray.int64(*ref.shape) { c }
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
CArray.stack(coords, axis: -1)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
end
|