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,39 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload index(axis: 0)
|
|
4
|
+
# Returns a writable int32 CArray holding the coordinate ramp
|
|
5
|
+
# `[0, 1, ..., shape[axis] - 1]` along `axis`, in an open
|
|
6
|
+
# broadcast shape: size `shape[axis]` on that axis and 1 on
|
|
7
|
+
# every other axis (e.g. for `(d0, d1, d2)`, `index(axis: 1)`
|
|
8
|
+
# returns `(1, d1, 1)`).
|
|
9
|
+
#
|
|
10
|
+
# The result broadcasts against `self` in element-wise ops
|
|
11
|
+
# without materialising the full shape. For the dense
|
|
12
|
+
# full-shape grid use
|
|
13
|
+
# `index(axis: k).broadcast_to(*shape)` or `CArray.meshgrid`.
|
|
14
|
+
#
|
|
15
|
+
# @param axis [Integer] axis to vary (negative counts from the
|
|
16
|
+
# end).
|
|
17
|
+
# @return [CArray] writable int32 CArray; size 1 on every axis
|
|
18
|
+
# except `axis`, which has size `shape[axis]`.
|
|
19
|
+
def index (axis: 0)
|
|
20
|
+
k = normalize_axis(axis, "index")
|
|
21
|
+
oshape = Array.new(ndim, 1)
|
|
22
|
+
oshape[k] = shape[k]
|
|
23
|
+
CArray.int32(*oshape).seq!
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @overload indices
|
|
27
|
+
# Returns an Array of +ndim+ coordinate ramps, one per axis, each in
|
|
28
|
+
# the open broadcast shape of {#index}.
|
|
29
|
+
# @return [Array<CArray>] one open coordinate ramp per axis.
|
|
30
|
+
# @overload indices { |*ramps| ... }
|
|
31
|
+
# Yields the +ndim+ open coordinate ramps as splat arguments.
|
|
32
|
+
# @yield [*ramps] the per-axis open coordinate ramps.
|
|
33
|
+
# @return [Object] the block's return value.
|
|
34
|
+
def indices
|
|
35
|
+
list = (0...ndim).map { |k| index(axis: k) }
|
|
36
|
+
block_given? ? yield(*list) : list
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Block insert / delete pair (insert_block / delete_block): grow or shrink
|
|
2
|
+
# an array by inserting or removing a block region along each axis.
|
|
3
|
+
|
|
4
|
+
class CArray
|
|
5
|
+
|
|
6
|
+
# @overload insert_block(offset, bsize, &block)
|
|
7
|
+
# Returns a new CArray obtained by inserting a block of size
|
|
8
|
+
# `bsize` (per axis) at `offset`, growing the array.
|
|
9
|
+
#
|
|
10
|
+
# Per axis, `offset` accepts `0..shape[i]` (equal to `shape[i]`
|
|
11
|
+
# appends at the end) and a negative value counts from the end;
|
|
12
|
+
# `bsize[i]` must be non-negative. Inserted cells are filled by
|
|
13
|
+
# the block's return value, or left at the type's default when
|
|
14
|
+
# no block is given. Preserves fixlen bytes and Face identity
|
|
15
|
+
# (built on the storage layout, re-wrapped via `face_lift`).
|
|
16
|
+
# The `offset` array is not mutated.
|
|
17
|
+
#
|
|
18
|
+
# @param offset [Array<Integer>] insertion offset per axis.
|
|
19
|
+
# @param bsize [Array<Integer>] block size per axis.
|
|
20
|
+
# @yieldreturn [Object] fill value for the inserted cells.
|
|
21
|
+
# @return [CArray] grown copy.
|
|
22
|
+
# @raise [ArgumentError] on `ndim` mismatch or out-of-range
|
|
23
|
+
# offset / size.
|
|
24
|
+
def insert_block (offset, bsize, &block)
|
|
25
|
+
if offset.size != ndim or bsize.size != ndim
|
|
26
|
+
raise ArgumentError, "ndim mismatch"
|
|
27
|
+
end
|
|
28
|
+
offset = offset.dup # normalize without mutating the caller's array
|
|
29
|
+
newdim = shape
|
|
30
|
+
grids = shape.map{|d| CArray.int32(d) }
|
|
31
|
+
ndim.times do |i|
|
|
32
|
+
offset[i] += shape[i] if offset[i] < 0
|
|
33
|
+
if offset[i] < 0 or offset[i] > shape[i] or bsize[i] < 0
|
|
34
|
+
raise ArgumentError, "invalid offset or size at axis #{i}"
|
|
35
|
+
end
|
|
36
|
+
if bsize[i] > 0
|
|
37
|
+
newdim[i] += bsize[i]
|
|
38
|
+
end
|
|
39
|
+
grids[i][0...offset[i]].seq! if offset[i] > 0
|
|
40
|
+
# offset == dim (append) leaves nothing on the upper side to shift.
|
|
41
|
+
grids[i][offset[i]..-1].seq!(offset[i]+bsize[i]) if offset[i] < shape[i]
|
|
42
|
+
end
|
|
43
|
+
# Build at the storage layout (preserving bytes for fixlen / Face),
|
|
44
|
+
# then re-wrap as the same Face.
|
|
45
|
+
face_parent = self.face? ? self : nil
|
|
46
|
+
src = self
|
|
47
|
+
src = src.parent while src.face?
|
|
48
|
+
dt = src.data_type
|
|
49
|
+
bytes = (dt == :fixlen) ? src.bytes : nil
|
|
50
|
+
out = CArray.new(dt, newdim, bytes: bytes)
|
|
51
|
+
if block_given?
|
|
52
|
+
sel = out.true
|
|
53
|
+
sel[*grids] = 0
|
|
54
|
+
out[sel] = block.call
|
|
55
|
+
end
|
|
56
|
+
out[*grids] = src
|
|
57
|
+
out = out.face_lift(face_parent) if face_parent
|
|
58
|
+
return out
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @overload delete_block(offset, bsize)
|
|
62
|
+
# Returns a new CArray obtained by deleting a block of `bsize`
|
|
63
|
+
# cells (per axis) starting at `offset`, shrinking the array.
|
|
64
|
+
#
|
|
65
|
+
# Per axis, `offset` accepts `0..shape[i]-1` (negative counts
|
|
66
|
+
# from the end) and `bsize[i]` must be non-negative with
|
|
67
|
+
# `offset[i] + bsize[i] <= shape[i]`. Bytes and Face identity
|
|
68
|
+
# are preserved naturally because the result is built by
|
|
69
|
+
# fancy-index copy of `self`. The `offset` array is not mutated.
|
|
70
|
+
#
|
|
71
|
+
# @param offset [Array<Integer>] start of the block per axis.
|
|
72
|
+
# @param bsize [Array<Integer>] block size per axis.
|
|
73
|
+
# @return [CArray] shrunk copy.
|
|
74
|
+
# @raise [ArgumentError] on `ndim` mismatch or out-of-range
|
|
75
|
+
# offset / size.
|
|
76
|
+
def delete_block (offset, bsize)
|
|
77
|
+
if offset.size != ndim or bsize.size != ndim
|
|
78
|
+
raise ArgumentError, "ndim mismatch"
|
|
79
|
+
end
|
|
80
|
+
offset = offset.dup # normalize without mutating the caller's array
|
|
81
|
+
newdim = shape
|
|
82
|
+
grids = []
|
|
83
|
+
ndim.times do |i|
|
|
84
|
+
offset[i] += shape[i] if offset[i] < 0
|
|
85
|
+
if bsize[i] < 0 or offset[i] < 0 or offset[i] >= shape[i] or
|
|
86
|
+
offset[i] + bsize[i] > shape[i]
|
|
87
|
+
raise ArgumentError, "invalid offset or size at axis #{i}"
|
|
88
|
+
end
|
|
89
|
+
newdim[i] -= bsize[i]
|
|
90
|
+
grids[i] = CArray.int32(newdim[i])
|
|
91
|
+
grids[i][0...offset[i]].seq! if offset[i] > 0
|
|
92
|
+
if offset[i] + bsize[i] < shape[i]
|
|
93
|
+
grids[i][offset[i]..-1].seq!(offset[i]+bsize[i])
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
return self[*grids].copy
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload is_in(values)
|
|
4
|
+
# Returns a boolean CArray of the same shape as `self`, `true` at
|
|
5
|
+
# each cell whose value appears in the set `values`.
|
|
6
|
+
#
|
|
7
|
+
# `values` is treated as a set, not as an operand to broadcast: it
|
|
8
|
+
# may be any shape (or an Array / Range) and is flattened to a
|
|
9
|
+
# single seen-set, so its shape need not match `self`. Only one
|
|
10
|
+
# argument is accepted; to test a few immediate values pass an
|
|
11
|
+
# Array (`a.is_in([0, -1])`).
|
|
12
|
+
#
|
|
13
|
+
# When `self` and `values` have different numeric dtypes they are
|
|
14
|
+
# promoted to a common type first (the same promotion binops use,
|
|
15
|
+
# {CArray.result_type}), so membership is value-correct across
|
|
16
|
+
# dtypes (e.g. an int cell equals a float set element of the same
|
|
17
|
+
# value, and a fractional set element never truncates onto an int
|
|
18
|
+
# cell). Genuinely incompatible dtypes (e.g. numeric vs fixlen)
|
|
19
|
+
# raise.
|
|
20
|
+
#
|
|
21
|
+
# Membership is value-based and shares the distinctness of the
|
|
22
|
+
# value-hash discovery family ({#unique} / {#value_counts}):
|
|
23
|
+
# numeric follows `==` with all NaN collapsed to one value and
|
|
24
|
+
# -0.0 == +0.0; `CA_OBJECT` follows Ruby `hash` / `eql?` with Float
|
|
25
|
+
# NaN collapsed; `CA_FIXLEN` follows byte equality.
|
|
26
|
+
#
|
|
27
|
+
# Masked cells of `values` do not enter the set. Masked cells of
|
|
28
|
+
# `self` stay masked in the result (membership is unknown), so
|
|
29
|
+
# `is_in` propagates `self`'s mask like an element-wise comparison.
|
|
30
|
+
#
|
|
31
|
+
# For a per-fiber "does this fiber contain any of these values"
|
|
32
|
+
# reduction, compose with {#any}: `a.is_in(values).any(axis: k)`.
|
|
33
|
+
#
|
|
34
|
+
# @param values [CArray, Array, Range] the set to test membership
|
|
35
|
+
# against. Promoted with `self` to a common dtype.
|
|
36
|
+
# Between two time arrays the question is about instants, not ticks:
|
|
37
|
+
# `values` is reconciled into `self`'s unit first, so a `:D` array and an
|
|
38
|
+
# `:h` array match on the instants they share. The same holds for the set
|
|
39
|
+
# operations below, whose results come back as `self`'s own type.
|
|
40
|
+
#
|
|
41
|
+
# @return [CArray] boolean CArray of the same shape as `self`.
|
|
42
|
+
def is_in (values)
|
|
43
|
+
a, b = promote_value_set(values)
|
|
44
|
+
a.__send__(:__is_in__, b)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @overload intersection(other, sort: false)
|
|
48
|
+
# Returns a 1-D CArray of the distinct values appearing in both
|
|
49
|
+
# `self` and `other`, in `self`'s first-appearance order.
|
|
50
|
+
#
|
|
51
|
+
# Value-based, sharing the distinctness of the discovery family
|
|
52
|
+
# (see {#is_in}); `self` and `other` are promoted to a common dtype.
|
|
53
|
+
# Masked cells of either array do not participate. The result is
|
|
54
|
+
# always flat, like {#unique}, because the distinct values of a
|
|
55
|
+
# fiber vary in number.
|
|
56
|
+
#
|
|
57
|
+
# @param other [CArray, Array, Range] promoted with `self`.
|
|
58
|
+
# @param sort [Boolean] when true, return the values sorted
|
|
59
|
+
# ascending instead of in first-appearance order.
|
|
60
|
+
# @return [CArray] 1-D CArray of the common distinct values.
|
|
61
|
+
def intersection (other, sort: false)
|
|
62
|
+
a, b = promote_value_set(other)
|
|
63
|
+
r = a.__send__(:__intersection__, b)
|
|
64
|
+
sort ? r.sort : r
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @overload difference(other, sort: false)
|
|
68
|
+
# Returns a 1-D CArray of the distinct values in `self` that are
|
|
69
|
+
# absent from `other`, in `self`'s first-appearance order.
|
|
70
|
+
# See {#intersection} for the shared semantics and options.
|
|
71
|
+
#
|
|
72
|
+
# @param other [CArray, Array, Range] promoted with `self`.
|
|
73
|
+
# @param sort [Boolean] when true, return the values sorted ascending.
|
|
74
|
+
# @return [CArray] 1-D CArray of the self-only distinct values.
|
|
75
|
+
def difference (other, sort: false)
|
|
76
|
+
a, b = promote_value_set(other)
|
|
77
|
+
r = a.__send__(:__difference__, b)
|
|
78
|
+
sort ? r.sort : r
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @overload union(other, sort: false)
|
|
82
|
+
# Returns a 1-D CArray of the distinct values appearing in either
|
|
83
|
+
# `self` or `other`, in self-then-other first-appearance order.
|
|
84
|
+
# See {#intersection} for the shared semantics and options.
|
|
85
|
+
#
|
|
86
|
+
# @param other [CArray, Array, Range] promoted with `self`.
|
|
87
|
+
# @param sort [Boolean] when true, return the values sorted ascending
|
|
88
|
+
# (a merged, ordered set — e.g. a common time axis).
|
|
89
|
+
# @return [CArray] 1-D CArray of the combined distinct values.
|
|
90
|
+
def union (other, sort: false)
|
|
91
|
+
a, b = promote_value_set(other)
|
|
92
|
+
r = a.__send__(:__union__, b)
|
|
93
|
+
sort ? r.sort : r
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
# Reconcile a set-valued argument with self to a common data_type via
|
|
99
|
+
# CArray.result_type (the single-source promotion rule the eager binop and
|
|
100
|
+
# lazy CABinOp share), returning [self', set']. Only dtypes are reconciled,
|
|
101
|
+
# never shapes: unlike the binop coercion (cast_self_or_other) the set's
|
|
102
|
+
# shape never broadcasts against self's, so a size-1 self keeps its shape.
|
|
103
|
+
# result_type raises for genuinely incompatible dtypes (numeric vs fixlen).
|
|
104
|
+
#
|
|
105
|
+
# A bare Array / Range has no intrinsic dtype, so the common type is inferred
|
|
106
|
+
# from self and the individual elements (result_type classifies each scalar).
|
|
107
|
+
# This keeps a fractional literal from truncating onto an int self, and an
|
|
108
|
+
# int literal from boxing a float self into the object lane (where Float 2.0
|
|
109
|
+
# is not eql? Integer 2). A CArray argument uses its own dtype; any other
|
|
110
|
+
# operand (Numo, a MemoryView producer, ...) comes in through wrap_readonly,
|
|
111
|
+
# the canonical type-coercion entry, so its format's dtype drives the promote.
|
|
112
|
+
def promote_value_set (values)
|
|
113
|
+
case values
|
|
114
|
+
when Array then return promote_elements(values)
|
|
115
|
+
when Range then return promote_elements(values.to_a)
|
|
116
|
+
end
|
|
117
|
+
set = values.is_a?(CArray) ? values : CArray.wrap_readonly(values)
|
|
118
|
+
return [self, set] if set.data_type == data_type # common fast path
|
|
119
|
+
t = CArray.result_type(self, set)
|
|
120
|
+
[coerce_self(t), set.to_type(t)]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Bare Array / Range against numeric self: infer the common numeric type
|
|
124
|
+
# from self and the elements (each classified by result_type), so a
|
|
125
|
+
# fractional literal promotes self to float instead of truncating. Against
|
|
126
|
+
# object / fixlen self the elements are values, not dtype specifiers (a
|
|
127
|
+
# String is a value, not a type name), so build the set in self's dtype.
|
|
128
|
+
def promote_elements (elems)
|
|
129
|
+
if data_type == CA_OBJECT || data_type == CA_FIXLEN
|
|
130
|
+
[self, elems.to_ca.to_type(data_type)]
|
|
131
|
+
else
|
|
132
|
+
t = CArray.result_type(self, *elems)
|
|
133
|
+
[coerce_self(t), elems.to_ca.to_type(t)]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def coerce_self (t)
|
|
138
|
+
CArray.result_type(self) == t ? self : to_type(t)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload join(sep = nil)
|
|
4
|
+
# Flat form. Stringifies and concatenates every element of `self`
|
|
5
|
+
# (as if `to_a.flatten.join(sep)`). Returns a String.
|
|
6
|
+
#
|
|
7
|
+
# @overload join(sep = "", axis:, keep_axis: false)
|
|
8
|
+
# Per-axis form. Reduces `axis` into strings, one per fiber along
|
|
9
|
+
# that axis, and returns the result as a CArray with `axis` removed
|
|
10
|
+
# (or set to 1 when `keep_axis: true`). Composable — call `join`
|
|
11
|
+
# again on the result to collapse another axis or fold to a String.
|
|
12
|
+
#
|
|
13
|
+
# For a 1-D `self`, the axis form fully reduces and returns the
|
|
14
|
+
# String directly (matching the flat form and the reduction
|
|
15
|
+
# convention).
|
|
16
|
+
#
|
|
17
|
+
# @param sep [String] separator between elements along the axis.
|
|
18
|
+
# @param axis [Integer] axis to reduce (negative allowed).
|
|
19
|
+
# @param keep_axis [Boolean] keep the reduced axis as length 1.
|
|
20
|
+
# @return [CArray, String] a CArray of strings, or a String when
|
|
21
|
+
# `self` is 1-D.
|
|
22
|
+
#
|
|
23
|
+
# @example Flat form
|
|
24
|
+
# a = CArray.object(3, 3).seq("a", :succ)
|
|
25
|
+
# a.join # => "abcdefghi"
|
|
26
|
+
# a.join(",") # => "a,b,c,d,e,f,g,h,i"
|
|
27
|
+
#
|
|
28
|
+
# @example Per-axis form
|
|
29
|
+
# a = CArray.int32(3, 3).seq
|
|
30
|
+
# a.join(" ", axis: 1) # → CArray["0 1 2", "3 4 5", "6 7 8"]
|
|
31
|
+
# a.join(" ", axis: 1).join("\n") # => "0 1 2\n3 4 5\n6 7 8"
|
|
32
|
+
#
|
|
33
|
+
# @note The 2.x multi-separator form `a.join("\n", ",")` was removed
|
|
34
|
+
# in 3.0; use the axis form and chain, e.g.
|
|
35
|
+
# `a.join(",", axis: 1).join("\n")`.
|
|
36
|
+
def join (*argv, axis: nil, keep_axis: false)
|
|
37
|
+
if argv.size > 1
|
|
38
|
+
raise ArgumentError,
|
|
39
|
+
"join accepts at most one positional separator " \
|
|
40
|
+
"(the 2.x multi-separator form was removed in 3.0; " \
|
|
41
|
+
"use axis: for per-axis join and chain)"
|
|
42
|
+
end
|
|
43
|
+
sep = argv.first # nil or String
|
|
44
|
+
|
|
45
|
+
if axis.nil?
|
|
46
|
+
return sep.nil? ? to_a.join : to_a.join(sep)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
ax = Integer(axis)
|
|
50
|
+
ax += ndim if ax < 0
|
|
51
|
+
if ax < 0 || ax >= ndim
|
|
52
|
+
raise ArgumentError,
|
|
53
|
+
"axis #{axis.inspect} out of range for ndim=#{ndim}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
sep_str = sep || ""
|
|
57
|
+
|
|
58
|
+
# Bring `ax` to the innermost position so we can iterate fibers as
|
|
59
|
+
# rows of a 2-D reshape. transpose returns a view; reshape may
|
|
60
|
+
# materialize on non-contig, which is fine for this formatting op.
|
|
61
|
+
if ax == ndim - 1
|
|
62
|
+
t = self
|
|
63
|
+
else
|
|
64
|
+
order = (0...ndim).to_a
|
|
65
|
+
order << order.delete_at(ax)
|
|
66
|
+
t = transpose(*order)
|
|
67
|
+
end
|
|
68
|
+
inner = t.shape[-1]
|
|
69
|
+
outer_n = t.elements / inner # 1 when ndim == 1
|
|
70
|
+
flat = t.reshape(outer_n, inner)
|
|
71
|
+
|
|
72
|
+
strings = Array.new(outer_n) { |i| flat[i, nil].to_a.join(sep_str) }
|
|
73
|
+
|
|
74
|
+
if ndim == 1
|
|
75
|
+
# Full reduction: return the String directly, or a length-1
|
|
76
|
+
# CArray when keep_axis was requested.
|
|
77
|
+
return keep_axis ? CA_OBJECT([strings.first]) : strings.first
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
result_shape = shape.dup
|
|
81
|
+
if keep_axis
|
|
82
|
+
result_shape[ax] = 1
|
|
83
|
+
else
|
|
84
|
+
result_shape.delete_at(ax)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
CA_OBJECT(strings).reshape(*result_shape)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# User-facing YARD docs for #locate_addr and #locate_nearest_addr live
|
|
4
|
+
# in yard-stubs/carray_order.rb (grouped with the search family).
|
|
5
|
+
|
|
6
|
+
def locate_addr (ref)
|
|
7
|
+
ref = ref.to_ca unless ref.is_a?(CArray)
|
|
8
|
+
# Put self and ref in a common lane via the single-source promotion rule
|
|
9
|
+
# (CArray.result_type), so a fractional query against an int ref is compared
|
|
10
|
+
# at the promoted type instead of truncating (1.5 no longer matches 1).
|
|
11
|
+
# to_type is elementwise and order-preserving, so the addresses stay valid
|
|
12
|
+
# indices into ref. result_type raises for cross-family input.
|
|
13
|
+
t = CArray.result_type(self, ref)
|
|
14
|
+
q = (data_type == t) ? self : to_type(t)
|
|
15
|
+
r = (ref.data_type == t) ? ref : ref.to_type(t)
|
|
16
|
+
q.send(:__locate_addr__, r)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def locate_nearest_addr (ref, direction: :round, tolerance: nil)
|
|
20
|
+
unless [:round, :floor, :ceil].include?(direction)
|
|
21
|
+
raise ArgumentError,
|
|
22
|
+
"locate_nearest_addr: direction must be :round / :floor / " \
|
|
23
|
+
":ceil (got #{direction.inspect})"
|
|
24
|
+
end
|
|
25
|
+
ri = ref.sort_addr
|
|
26
|
+
rs = ref[ri]
|
|
27
|
+
sec = rs.linear_section(self)
|
|
28
|
+
unless sec.is_a?(CArray)
|
|
29
|
+
# A single-element (scalar-like) self makes linear_section collapse to
|
|
30
|
+
# its scalar-query path, which returns a bare Float (or nil when out of
|
|
31
|
+
# range) instead of a CArray. Rebuild a self-shaped float64 CArray so
|
|
32
|
+
# the mask_invalid -> direction -> project pipeline stays array-valued
|
|
33
|
+
# and the returned addr array matches self's shape.
|
|
34
|
+
fill = CArray.float64(*shape)
|
|
35
|
+
fill[] = sec.nil? ? UNDEF : sec
|
|
36
|
+
sec = fill
|
|
37
|
+
end
|
|
38
|
+
si = sec.mask_invalid.send(direction).int64
|
|
39
|
+
idx = ri.project(si)
|
|
40
|
+
if tolerance
|
|
41
|
+
dist = (ref.project(idx) - self).abs
|
|
42
|
+
idx[dist > tolerance] = UNDEF
|
|
43
|
+
end
|
|
44
|
+
idx
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload mask_duplicates(axis: nil)
|
|
4
|
+
# Returns a shape-preserving copy of `self` with the mask set at
|
|
5
|
+
# every cell whose value duplicates an earlier-seen one; the
|
|
6
|
+
# first occurrence is kept.
|
|
7
|
+
#
|
|
8
|
+
# With `axis: nil` duplicates are detected in flatten (row-major)
|
|
9
|
+
# order across the whole array. With `axis: k` duplicates are
|
|
10
|
+
# detected per-fiber along axis `k`, independently for each
|
|
11
|
+
# fiber. Marking duplicates (not compressing) is what makes the
|
|
12
|
+
# per-axis form expressible: fibers may hold different numbers of
|
|
13
|
+
# distinct values, so a compressed result would be ragged.
|
|
14
|
+
#
|
|
15
|
+
# Distinctness matches the value-hash discovery family. Numeric:
|
|
16
|
+
# `==` with all NaN collapsed to one value (so the second and
|
|
17
|
+
# later NaN are duplicates) and -0.0 == +0.0. `CA_OBJECT` /
|
|
18
|
+
# `CA_FIXLEN`: Ruby `eql?` / `hash` (distinct NaN objects stay
|
|
19
|
+
# distinct). Masked input cells stay masked and do not participate
|
|
20
|
+
# in duplicate judging. Both `axis: nil` and `axis: k` work.
|
|
21
|
+
#
|
|
22
|
+
# @param axis [Integer, nil] axis to detect duplicates along;
|
|
23
|
+
# `nil` uses flatten order.
|
|
24
|
+
# @return [CArray] shape-preserving copy of `self` with
|
|
25
|
+
# duplicates masked.
|
|
26
|
+
def mask_duplicates (axis: nil)
|
|
27
|
+
dup =
|
|
28
|
+
if axis.nil?
|
|
29
|
+
# One seen-set over the flattened array, then restore shape.
|
|
30
|
+
flatten.send(:__mask_duplicates__, 0).reshape(*shape)
|
|
31
|
+
else
|
|
32
|
+
# Per-fiber single-pass seen-set hash (C __mask_duplicates__): one lane
|
|
33
|
+
# per dtype family (integer widen, float bitwise key with NaN collapse,
|
|
34
|
+
# object rb_hash + rb_eql, fixlen byte-hash + memcmp, boolean via the
|
|
35
|
+
# uint8 lane). O(distinct) memory, no sort/gather/scatter buffers.
|
|
36
|
+
__mask_duplicates__(normalize_axis(axis, "mask_duplicates"))
|
|
37
|
+
end
|
|
38
|
+
mask_where(dup)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload meshgrid(*axes, indexing: "xy", copy: true, sparse: false)
|
|
4
|
+
# Returns coordinate matrices built from 1-D coordinate vectors.
|
|
5
|
+
#
|
|
6
|
+
# Given N 1-D vectors, produces N arrays each broadcasting one
|
|
7
|
+
# input axis across the others. Useful for evaluating a function
|
|
8
|
+
# on a grid.
|
|
9
|
+
#
|
|
10
|
+
# With `indexing: "xy"` (default) the first two axes are swapped
|
|
11
|
+
# in the output shape (matrix-style convention): `meshgrid(x, y)`
|
|
12
|
+
# gives outputs of shape `[y.elements, x.elements]`. With
|
|
13
|
+
# `indexing: "ij"` input order is preserved:
|
|
14
|
+
# `meshgrid(x, y, indexing: "ij")` gives outputs of shape
|
|
15
|
+
# `[x.elements, y.elements]`. For more than two axes only the
|
|
16
|
+
# first two are swapped under `"xy"`; the remaining axes follow
|
|
17
|
+
# input order in both modes.
|
|
18
|
+
#
|
|
19
|
+
# When `copy` is true (default) each output is a materialised
|
|
20
|
+
# CArray; when false, view chains (CARepeat / CAUnboundRepeat)
|
|
21
|
+
# are returned. When `sparse` is true the outputs are
|
|
22
|
+
# CAUnboundRepeat views that broadcast on demand, saving memory
|
|
23
|
+
# for large grids.
|
|
24
|
+
#
|
|
25
|
+
# If a block is given, yields the resulting arrays as splat
|
|
26
|
+
# arguments and returns the block's value.
|
|
27
|
+
#
|
|
28
|
+
# Each axis goes through {CArray.wrap_readonly}, so a coordinate
|
|
29
|
+
# vector may be given as anything that entry point accepts (a
|
|
30
|
+
# CArray, an Array, a Range, a Numeric, a MemoryView producer, an
|
|
31
|
+
# object answering `ca` / `to_ca`); its own data type is kept.
|
|
32
|
+
#
|
|
33
|
+
# @param axes [Array<CArray, Array, Object>] 1-D coordinate vectors.
|
|
34
|
+
# @param indexing [String] `"xy"` or `"ij"`.
|
|
35
|
+
# @param copy [Boolean] materialise each output when true.
|
|
36
|
+
# @param sparse [Boolean] return broadcast-on-demand views when true.
|
|
37
|
+
# @yieldparam grids [Array<CArray>] the resulting coordinate arrays.
|
|
38
|
+
# @return [Array<CArray>] the coordinate arrays, or the block's
|
|
39
|
+
# return value.
|
|
40
|
+
# @raise [ArgumentError] when `indexing` is neither `"xy"` nor `"ij"`,
|
|
41
|
+
# or when a coordinate vector is not 1-D.
|
|
42
|
+
# @example
|
|
43
|
+
# x = CA_FLOAT64([1.0, 2.0, 3.0])
|
|
44
|
+
# y = CA_FLOAT64([10.0, 20.0])
|
|
45
|
+
# xx, yy = CArray.meshgrid(x, y)
|
|
46
|
+
# xx.shape # => [2, 3]
|
|
47
|
+
# yy.to_a # => [[10.0, 10.0, 10.0], [20.0, 20.0, 20.0]]
|
|
48
|
+
def self.meshgrid (*axes, indexing: "xy", copy: true, sparse: false, &block)
|
|
49
|
+
unless %w[xy ij].include?(indexing)
|
|
50
|
+
raise ArgumentError, %{indexing option should be one of "xy" and "ij"}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Each axis is negotiable, so no target type is imposed here; a
|
|
54
|
+
# CArray comes back as itself and anything else is brought in with
|
|
55
|
+
# its own data type.
|
|
56
|
+
axes = axes.map.with_index do |axis, k|
|
|
57
|
+
a = CArray.wrap_readonly(axis)
|
|
58
|
+
unless a.ndim == 1
|
|
59
|
+
raise ArgumentError,
|
|
60
|
+
"coordinate vector #{k} should be 1-D (got #{a.ndim}-D)"
|
|
61
|
+
end
|
|
62
|
+
a
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
ndim = axes.size
|
|
66
|
+
|
|
67
|
+
# dest[k] = output axis position that input axis k populates.
|
|
68
|
+
# "xy" swaps the first two; everything else is in input order.
|
|
69
|
+
dest = (0...ndim).to_a
|
|
70
|
+
dest[0], dest[1] = 1, 0 if indexing == "xy" && ndim >= 2
|
|
71
|
+
|
|
72
|
+
# Output shape: each output axis i takes its size from the input
|
|
73
|
+
# axis that maps there.
|
|
74
|
+
out_shape = Array.new(ndim)
|
|
75
|
+
axes.each_with_index { |a, k| out_shape[dest[k]] = a.size }
|
|
76
|
+
|
|
77
|
+
list = axes.map.with_index do |axis, k|
|
|
78
|
+
d = dest[k]
|
|
79
|
+
idx = if sparse
|
|
80
|
+
Array.new(ndim) { |i| i == d ? nil : :* }
|
|
81
|
+
else
|
|
82
|
+
out_shape.dup.tap { |s| s[d] = :% }
|
|
83
|
+
end
|
|
84
|
+
view = axis[*idx]
|
|
85
|
+
copy ? view.copy : view
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
block ? block.call(*list) : list
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload is_mode(axis: nil)
|
|
4
|
+
# Returns a shape-preserving boolean CArray, true at every cell
|
|
5
|
+
# that holds a modal value — a value whose occurrence count equals
|
|
6
|
+
# the maximum count. This is the first-class primitive of the mode
|
|
7
|
+
# family: rather than returning the mode *value* (whose count is
|
|
8
|
+
# data-dependent when there are ties), it marks every occurrence of
|
|
9
|
+
# every most-frequent value, so ties are never silently broken.
|
|
10
|
+
#
|
|
11
|
+
# ```
|
|
12
|
+
# CA_INT32([1, 1, 2, 2, 3]).is_mode # => [1, 1, 1, 1, 0] (1 and 2 tie)
|
|
13
|
+
# ```
|
|
14
|
+
#
|
|
15
|
+
# With `axis: nil` the frequency is over the whole array; with
|
|
16
|
+
# `axis: k` it is per fiber along axis `k`, independently. The
|
|
17
|
+
# result is always the input shape, so — unlike returning the mode
|
|
18
|
+
# value — the per-axis form has no ragged-length problem. Select the
|
|
19
|
+
# modal cells with `a[a.is_mode]` / `a[a.is_mode(axis: k)]` (the
|
|
20
|
+
# {#mask_duplicates} idiom); reduce further with `.min` / `.unique`.
|
|
21
|
+
#
|
|
22
|
+
# Mode is only meaningful for discrete or binned data: raw float is
|
|
23
|
+
# almost all unique, so `is_mode` would mark just the single lowest
|
|
24
|
+
# cell (count 1). Bin first (`bin` / `histogram` / `categorize`).
|
|
25
|
+
#
|
|
26
|
+
# Masked cells do not participate and are marked false. An empty or
|
|
27
|
+
# all-masked fiber marks every cell false (mode has no identity; it
|
|
28
|
+
# never raises). Numeric distinctness follows the discovery family
|
|
29
|
+
# (all NaN collapse to one value, -0.0 == +0.0); `CA_OBJECT` /
|
|
30
|
+
# `CA_FIXLEN` follow Ruby `eql?` / `hash`.
|
|
31
|
+
#
|
|
32
|
+
# @param axis [Integer, nil] axis to take the mode along; `nil` uses
|
|
33
|
+
# the whole array.
|
|
34
|
+
# @return [CArray] boolean CArray of `self.shape`.
|
|
35
|
+
def is_mode (axis: nil)
|
|
36
|
+
# Per-fiber two-pass frequency table (C __is_mode__), one lane per dtype
|
|
37
|
+
# family (numeric widen / NaN collapse, object rb_hash + rb_eql, fixlen
|
|
38
|
+
# byte-hash + memcmp). Ties are all marked; masked cells stay false.
|
|
39
|
+
if axis.nil?
|
|
40
|
+
flatten.send(:__is_mode__, 0).reshape(*shape)
|
|
41
|
+
else
|
|
42
|
+
__is_mode__(normalize_axis(axis, "is_mode"))
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @overload mode(axis: nil)
|
|
47
|
+
# Returns the distinct modal values — the most frequent value(s),
|
|
48
|
+
# ascending. All values that tie for the highest count are returned
|
|
49
|
+
# (matching pandas `Series.mode`), because {#is_mode} does not break
|
|
50
|
+
# ties; `mode` is the value-form consumer of that primitive, read
|
|
51
|
+
# straight from the frequency table.
|
|
52
|
+
#
|
|
53
|
+
# With `axis: nil` the result is a 1-D CArray of the distinct modal
|
|
54
|
+
# values over the whole array (empty when all-masked).
|
|
55
|
+
#
|
|
56
|
+
# With `axis: k` the per-fiber mode counts are ragged, so — like
|
|
57
|
+
# per-axis `quantile` — the result is an `Array` of reduced CArrays.
|
|
58
|
+
# Element `j` holds each fiber's `j`-th smallest modal value, masked
|
|
59
|
+
# where a fiber has fewer than `j + 1` modes; the Array length is the
|
|
60
|
+
# widest fiber's mode count. So `mode(axis: k)[0]` is the smallest
|
|
61
|
+
# mode of each fiber (a plain reduced CArray). To get the rectangular
|
|
62
|
+
# mask-padded form, stack them: `CArray.stack(a.mode(axis: k), axis: k)`.
|
|
63
|
+
# An all-masked array yields an empty Array.
|
|
64
|
+
#
|
|
65
|
+
# Only meaningful for discrete / binned data: raw float is almost all
|
|
66
|
+
# unique, so every value is modal and the per-axis Array grows to the
|
|
67
|
+
# fiber length. Bin first. Same NaN / mask semantics as {#is_mode}.
|
|
68
|
+
#
|
|
69
|
+
# @param axis [Integer, nil] axis to take the mode along; `nil` uses
|
|
70
|
+
# the whole array.
|
|
71
|
+
# @return [CArray, Array<CArray>] 1-D CArray (flat) or an Array of
|
|
72
|
+
# reduced CArrays, one per mode rank (per-axis).
|
|
73
|
+
def mode (axis: nil)
|
|
74
|
+
return __mode_flat if axis.nil?
|
|
75
|
+
k = normalize_axis(axis, "mode")
|
|
76
|
+
|
|
77
|
+
# Numeric: the C frequency-table kernel emits the ragged Array<CArray>
|
|
78
|
+
# directly (reduced CArrays, self.shape with axis k dropped). A 1-D input
|
|
79
|
+
# reduces to length-1 CArrays, unwrapped to scalars like flat quantile.
|
|
80
|
+
unless data_type == CA_OBJECT || data_type == CA_FIXLEN
|
|
81
|
+
cols = __mode_axis__(k)
|
|
82
|
+
return ndim == 1 ? cols.map { |col| col[0] } : cols
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Object / fixlen (rare): per-fiber Ruby path, reusing the flat mode as the
|
|
86
|
+
# single source of what counts as a mode. Move axis k to the innermost
|
|
87
|
+
# position and fold the rest to one outer axis, so each row is a fiber.
|
|
88
|
+
perm = (0...ndim).to_a
|
|
89
|
+
perm.delete(k)
|
|
90
|
+
perm << k
|
|
91
|
+
a2 = (ndim == 1) ? self : transpose(*perm).copy # (outer..., L)
|
|
92
|
+
outer = a2.shape[0...-1]
|
|
93
|
+
m = outer.empty? ? 1 : outer.inject(:*)
|
|
94
|
+
flat2 = a2.reshape(m, a2.shape[-1])
|
|
95
|
+
lists = Array.new(m) { |r| flat2[r, nil].__send__(:__mode_flat).to_a }
|
|
96
|
+
|
|
97
|
+
# K = widest fiber's mode count. Emit K reduced CArrays (like quantile's
|
|
98
|
+
# per-axis Array<CArray>): slot j holds each fiber's j-th smallest mode,
|
|
99
|
+
# masked where a fiber has fewer than j+1 modes. Stack them to get the
|
|
100
|
+
# rectangular mask-padded form: CArray.stack(result, axis: k).
|
|
101
|
+
kk = lists.map(&:size).max || 0
|
|
102
|
+
(0...kk).map do |j|
|
|
103
|
+
# Take the column shape from self rather than building it from data_type:
|
|
104
|
+
# it carries the element width a fixlen array needs, and it keeps a Face
|
|
105
|
+
# (a time array), whose cells then accept the surface values in `lists`.
|
|
106
|
+
col = flat2[nil, 0].copy
|
|
107
|
+
col[] = UNDEF
|
|
108
|
+
m.times { |r| col[r] = lists[r][j] if j < lists[r].size }
|
|
109
|
+
outer.empty? ? col[0] : col.reshape(*outer)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
# Flat mode: the distinct modal values ascending, 1-D CArray of self's dtype.
|
|
116
|
+
# The single source of what counts as a mode (per-axis reuses it per fiber).
|
|
117
|
+
# The distinct values with the maximum count, read from the frequency table
|
|
118
|
+
# (value_counts, which covers numeric / object / fixlen), then sorted
|
|
119
|
+
# ascending. value_counts already skips masked cells.
|
|
120
|
+
def __mode_flat
|
|
121
|
+
values, counts = value_counts
|
|
122
|
+
return values if values.elements == 0
|
|
123
|
+
values[counts.eq(counts.max)].sort
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end
|