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,289 @@
|
|
|
1
|
+
# CAMeld reduce fast paths (per-parent decompose family).
|
|
2
|
+
#
|
|
3
|
+
# All decomposable reductions along the meld axis land at eager-entity
|
|
4
|
+
# parity via the identity
|
|
5
|
+
# op(concat_k p_k) = combine_k op(p_k)
|
|
6
|
+
# where op is sum/min/max/mean/etc. and combine_k is +, min, max, or the
|
|
7
|
+
# Welford update (variance/stddev). Each per-parent op runs on an entity,
|
|
8
|
+
# hitting the fastest kernel_iterator path, and dodges the whole-view
|
|
9
|
+
# SRC_ATTACH materialise (K per-parent xfer_all GET into scratch).
|
|
10
|
+
#
|
|
11
|
+
# Reductions along a non-meld axis (memo §7.4) also decompose: each
|
|
12
|
+
# parent independently reduces the non-meld axis, and the K results are
|
|
13
|
+
# concatenated back along the meld axis via `CArray.meld`. Result is
|
|
14
|
+
# materialised (`.copy`) to preserve the entity-returning semantic of
|
|
15
|
+
# `CArray#sum`/`#mean`/etc.
|
|
16
|
+
#
|
|
17
|
+
# Fast path activates when:
|
|
18
|
+
# - axis kwarg is a single Integer (or absent for flat reduce)
|
|
19
|
+
# - no mask on CAMeld or any parent (skipna needs per-cell dispatch;
|
|
20
|
+
# the decompose would not see correct mask propagation across parents)
|
|
21
|
+
# - no non-:axis kwargs (min_count / fill_value / keep_axis / etc. punt
|
|
22
|
+
# to super; those change finalisation semantics)
|
|
23
|
+
# When any condition fails the method falls through to super, landing on
|
|
24
|
+
# the kernel_iterator SRC_ATTACH path (correct, just slower).
|
|
25
|
+
#
|
|
26
|
+
# Order statistics (median / percentile / quantile) do NOT decompose —
|
|
27
|
+
# per-parent medians are not a function of the overall median — so those
|
|
28
|
+
# stay on the SRC_ATTACH path, which is already essentially parity because
|
|
29
|
+
# the sort cost dominates.
|
|
30
|
+
#
|
|
31
|
+
# See docs/objects/CAMeld.md and devel/bench_cameld_*.rb for numbers.
|
|
32
|
+
|
|
33
|
+
class CAMeld
|
|
34
|
+
|
|
35
|
+
# ---------- monoid reductions (sum / mean / min / max) ----------
|
|
36
|
+
|
|
37
|
+
def sum(*args, **kw)
|
|
38
|
+
return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
|
|
39
|
+
axis = kw[:axis]
|
|
40
|
+
if axis.nil?
|
|
41
|
+
parents.map(&:sum).inject(:+)
|
|
42
|
+
elsif meld_axis_normalized?(axis)
|
|
43
|
+
parents.map { |p| p.sum(axis: axis) }.inject(:+)
|
|
44
|
+
else
|
|
45
|
+
non_meld_axis_decompose(:sum, axis)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Same result as `CArray#mean`, obtained by reducing each parent and
|
|
50
|
+
# combining, so the melded array is never materialised. Falls back to
|
|
51
|
+
# the generic path when the decomposition does not apply.
|
|
52
|
+
# @return [CArray, Numeric]
|
|
53
|
+
def mean(*args, **kw)
|
|
54
|
+
return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
|
|
55
|
+
axis = kw[:axis]
|
|
56
|
+
# `mean` has no identity: an empty set of contributors has no defined
|
|
57
|
+
# mean (0/0 = NaN). Punt to super, which returns UNDEF: a reduction
|
|
58
|
+
# with no contributors yields the identity where one exists and UNDEF
|
|
59
|
+
# where none does, and never raises.
|
|
60
|
+
if axis.nil?
|
|
61
|
+
return super if parents.all? { |p| p.elements == 0 }
|
|
62
|
+
total_sum = parents.map(&:sum).inject(:+)
|
|
63
|
+
total_count = parents.map(&:elements).inject(:+)
|
|
64
|
+
total_sum / total_count.to_f
|
|
65
|
+
elsif meld_axis_normalized?(axis)
|
|
66
|
+
return super if parents.all? { |p| p.dim[meld_axis] == 0 }
|
|
67
|
+
total_sum = parents.map { |p| p.sum(axis: axis) }.inject(:+)
|
|
68
|
+
total_count = parents.map { |p| p.dim[meld_axis] }.inject(:+)
|
|
69
|
+
total_sum / total_count.to_f
|
|
70
|
+
else
|
|
71
|
+
non_meld_axis_decompose(:mean, axis)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Same result as `CArray#min`, obtained by reducing each parent and
|
|
76
|
+
# combining, so the melded array is never materialised. Falls back to
|
|
77
|
+
# the generic path when the decomposition does not apply.
|
|
78
|
+
# @return [CArray, Numeric]
|
|
79
|
+
def min(*args, **kw)
|
|
80
|
+
return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
|
|
81
|
+
axis = kw[:axis]
|
|
82
|
+
# `min` has no identity — empty parent list / all-empty parents punt to
|
|
83
|
+
# super for UNDEF.
|
|
84
|
+
if axis.nil?
|
|
85
|
+
return super if parents.all? { |p| p.elements == 0 }
|
|
86
|
+
parents.reject { |p| p.elements == 0 }.map(&:min).min
|
|
87
|
+
elsif meld_axis_normalized?(axis)
|
|
88
|
+
return super if parents.all? { |p| p.dim[meld_axis] == 0 }
|
|
89
|
+
nonempty = parents.reject { |p| p.dim[meld_axis] == 0 }
|
|
90
|
+
CArray.stack(nonempty.map { |p| p.min(axis: axis) }).min(axis: 0)
|
|
91
|
+
else
|
|
92
|
+
non_meld_axis_decompose(:min, axis)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Same result as `CArray#max`, obtained by reducing each parent and
|
|
97
|
+
# combining, so the melded array is never materialised. Falls back to
|
|
98
|
+
# the generic path when the decomposition does not apply.
|
|
99
|
+
# @return [CArray, Numeric]
|
|
100
|
+
def max(*args, **kw)
|
|
101
|
+
return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
|
|
102
|
+
axis = kw[:axis]
|
|
103
|
+
if axis.nil?
|
|
104
|
+
return super if parents.all? { |p| p.elements == 0 }
|
|
105
|
+
parents.reject { |p| p.elements == 0 }.map(&:max).max
|
|
106
|
+
elsif meld_axis_normalized?(axis)
|
|
107
|
+
return super if parents.all? { |p| p.dim[meld_axis] == 0 }
|
|
108
|
+
nonempty = parents.reject { |p| p.dim[meld_axis] == 0 }
|
|
109
|
+
CArray.stack(nonempty.map { |p| p.max(axis: axis) }).max(axis: 0)
|
|
110
|
+
else
|
|
111
|
+
non_meld_axis_decompose(:max, axis)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# ---------- variance family (Welford combine along meld axis) ----------
|
|
116
|
+
#
|
|
117
|
+
# Chan / Welford parallel merge: each parent contributes (n_k, mean_k,
|
|
118
|
+
# M2_k = Σ (x - mean_k)^2). Combine two chunks (n1, m1, M1) + (n2, m2, M2):
|
|
119
|
+
# n = n1 + n2
|
|
120
|
+
# δ = m2 - m1
|
|
121
|
+
# mean = m1 + δ * n2 / n
|
|
122
|
+
# M2 = M1 + M2 + δ² * (n1 * n2 / n)
|
|
123
|
+
# sample variance = M2 / (n - 1)
|
|
124
|
+
# population variance = M2 / n
|
|
125
|
+
#
|
|
126
|
+
# M2 per parent is recovered from CArray's variance:
|
|
127
|
+
# M2_k = variance_k * (n_k - 1) [sample-variance CArray path]
|
|
128
|
+
# This inherits CArray's ε-close SIMD reduce contract (memo: reduction is
|
|
129
|
+
# ε-close, not bit-exact). Falls through to super when any parent has
|
|
130
|
+
# n <= 1 along the axis (sample variance undefined).
|
|
131
|
+
|
|
132
|
+
def variance(*args, **kw)
|
|
133
|
+
variance_family(args, kw, sample: true, sqrt: false) { super }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Same result as `CArray#variancep`, obtained by reducing each parent and
|
|
137
|
+
# combining, so the melded array is never materialised. Falls back to
|
|
138
|
+
# the generic path when the decomposition does not apply.
|
|
139
|
+
# @return [CArray, Numeric]
|
|
140
|
+
def variancep(*args, **kw)
|
|
141
|
+
variance_family(args, kw, sample: false, sqrt: false) { super }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Same result as `CArray#stddev`, obtained by reducing each parent and
|
|
145
|
+
# combining, so the melded array is never materialised. Falls back to
|
|
146
|
+
# the generic path when the decomposition does not apply.
|
|
147
|
+
# @return [CArray, Numeric]
|
|
148
|
+
def stddev(*args, **kw)
|
|
149
|
+
variance_family(args, kw, sample: true, sqrt: true) { super }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Same result as `CArray#stddevp`, obtained by reducing each parent and
|
|
153
|
+
# combining, so the melded array is never materialised. Falls back to
|
|
154
|
+
# the generic path when the decomposition does not apply.
|
|
155
|
+
# @return [CArray, Numeric]
|
|
156
|
+
def stddevp(*args, **kw)
|
|
157
|
+
variance_family(args, kw, sample: false, sqrt: true) { super }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def meld_axis_normalized?(axis)
|
|
163
|
+
(axis < 0 ? axis + ndim : axis) == meld_axis
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Fast path fires when: no mask anywhere, no non-:axis kwargs, and axis
|
|
167
|
+
# (if given) is a valid single Integer within [-ndim, ndim). Anything
|
|
168
|
+
# more elaborate (min_count / fill_value / keep_axis / multi-axis /
|
|
169
|
+
# mask propagation) punts to super.
|
|
170
|
+
def meld_reduce_fast_path_ok?(kw)
|
|
171
|
+
return false unless (kw.keys - [:axis]).empty?
|
|
172
|
+
axis = kw[:axis]
|
|
173
|
+
unless axis.nil?
|
|
174
|
+
return false unless axis.is_a?(Integer)
|
|
175
|
+
norm = axis < 0 ? axis + ndim : axis
|
|
176
|
+
return false unless norm >= 0 && norm < ndim
|
|
177
|
+
end
|
|
178
|
+
return false if has_mask?
|
|
179
|
+
parents.each { |p| return false if p.has_mask? }
|
|
180
|
+
true
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Non-meld-axis decompose: each parent reduces the same axis independently,
|
|
184
|
+
# then the K per-parent results are concatenated back along the meld axis.
|
|
185
|
+
# Materialised to entity (`.copy`) to match CArray#reduce entity semantics.
|
|
186
|
+
#
|
|
187
|
+
# Axis index in the reduced result: if the reduced axis is < meld_axis in
|
|
188
|
+
# the CAMeld frame, meld_axis shifts down by 1 in the per-parent result
|
|
189
|
+
# (post-reduce ndim = ndim - 1). If reduced axis > meld_axis, meld_axis
|
|
190
|
+
# is unchanged. (Reduced axis == meld_axis is handled by the callers'
|
|
191
|
+
# meld_axis branch and never reaches here.)
|
|
192
|
+
def non_meld_axis_decompose(op, axis)
|
|
193
|
+
parts = parents.map { |p| p.send(op, axis: axis) }
|
|
194
|
+
axis_norm = axis < 0 ? axis + ndim : axis
|
|
195
|
+
new_meld_axis = axis_norm < meld_axis ? meld_axis - 1 : meld_axis
|
|
196
|
+
CArray.meld(parts, axis: new_meld_axis).copy
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def variance_family(args, kw, sample:, sqrt:)
|
|
200
|
+
return yield unless args.empty? && meld_reduce_fast_path_ok?(kw)
|
|
201
|
+
axis = kw[:axis]
|
|
202
|
+
# Empty-parent / short-parent handling — Welford needs n >= 2 per parent
|
|
203
|
+
# to recover m2 from p.variance for sample, n >= 1 for variancep (n=1
|
|
204
|
+
# gives m2=0, fine). Any parent below its threshold punts to super,
|
|
205
|
+
# which handles UNDEF / 0-count per CLAUDE.md contract.
|
|
206
|
+
min_n = sample ? 2 : 1
|
|
207
|
+
if axis.nil?
|
|
208
|
+
return yield if parents.any? { |p| p.elements < min_n }
|
|
209
|
+
finalize_scalar(welford_flat, sample, sqrt)
|
|
210
|
+
elsif meld_axis_normalized?(axis)
|
|
211
|
+
return yield if parents.any? { |p| p.dim[meld_axis] < min_n }
|
|
212
|
+
finalize_vector(welford_axis(axis), sample, sqrt)
|
|
213
|
+
else
|
|
214
|
+
# Non-meld-axis: each parent's variance/stddev is independent along
|
|
215
|
+
# its own local axis; concatenate the K results along the meld axis.
|
|
216
|
+
op_super = sqrt ? (sample ? :stddev : :stddevp) : (sample ? :variance : :variancep)
|
|
217
|
+
non_meld_axis_decompose(op_super, axis)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Flat Welford — reduces every parent fully; returns scalar (n_tot, mean, M2)
|
|
222
|
+
# trio in a shape suitable for finalize_scalar. Returns [n_tot, M2].
|
|
223
|
+
# Preconditions (checked in variance_family): every parent has enough
|
|
224
|
+
# elements (>= 2 for sample, >= 1 for population).
|
|
225
|
+
def welford_flat
|
|
226
|
+
n_tot = 0
|
|
227
|
+
mean = nil
|
|
228
|
+
m2 = nil
|
|
229
|
+
parents.each do |p|
|
|
230
|
+
np = p.elements
|
|
231
|
+
mp = p.mean
|
|
232
|
+
# variance is undefined at n=1 (returns UNDEF / raises). For
|
|
233
|
+
# variancep (min_n=1) with n=1, m2_p = 0 by definition; skip the
|
|
234
|
+
# p.variance call which would blow up.
|
|
235
|
+
m2p = np < 2 ? 0.0 : p.variance * (np - 1)
|
|
236
|
+
if mean.nil?
|
|
237
|
+
n_tot = np; mean = mp; m2 = m2p
|
|
238
|
+
else
|
|
239
|
+
delta = mp - mean
|
|
240
|
+
nn = n_tot + np
|
|
241
|
+
mean = mean + delta * np / nn.to_f
|
|
242
|
+
m2 = m2 + m2p + delta * delta * (n_tot * np / nn.to_f)
|
|
243
|
+
n_tot = nn
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
[n_tot, m2]
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Per-axis Welford — reduces every parent along the given (meld) axis and
|
|
250
|
+
# combines the vector-shaped (n_tot, mean, M2) trio. Returns [n_tot, M2]
|
|
251
|
+
# (n_tot is scalar since the meld axis is what we're reducing; M2 is a
|
|
252
|
+
# CArray of the collapsed shape).
|
|
253
|
+
def welford_axis(axis)
|
|
254
|
+
n_tot = 0
|
|
255
|
+
mean = nil
|
|
256
|
+
m2 = nil
|
|
257
|
+
parents.each do |p|
|
|
258
|
+
np = p.dim[meld_axis]
|
|
259
|
+
mp = p.mean(axis: axis)
|
|
260
|
+
# variance is undefined at n=1; for variancep with n=1, m2_p = 0 by
|
|
261
|
+
# definition (variance_family already gated n >= min_n).
|
|
262
|
+
m2p = np < 2 ? mp * 0 : p.variance(axis: axis) * (np - 1)
|
|
263
|
+
if mean.nil?
|
|
264
|
+
n_tot = np; mean = mp; m2 = m2p
|
|
265
|
+
else
|
|
266
|
+
delta = mp - mean
|
|
267
|
+
nn = n_tot + np
|
|
268
|
+
mean = mean + delta * (np.to_f / nn)
|
|
269
|
+
m2 = m2 + m2p + delta * delta * (n_tot * np.to_f / nn)
|
|
270
|
+
n_tot = nn
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
[n_tot, m2]
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def finalize_scalar(pair, sample, sqrt)
|
|
277
|
+
n_tot, m2 = pair
|
|
278
|
+
div = sample ? (n_tot - 1) : n_tot
|
|
279
|
+
v = m2 / div.to_f
|
|
280
|
+
sqrt ? Math.sqrt(v) : v
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def finalize_vector(pair, sample, sqrt)
|
|
284
|
+
n_tot, m2 = pair
|
|
285
|
+
div = sample ? (n_tot - 1) : n_tot
|
|
286
|
+
v = m2 / div.to_f
|
|
287
|
+
sqrt ? v.sqrt : v
|
|
288
|
+
end
|
|
289
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload align_addr(*arrays, join: :outer)
|
|
4
|
+
# Aligns several arrays onto one common set of coordinate values and
|
|
5
|
+
# returns, for each array, the flat addresses that gather it onto that
|
|
6
|
+
# common set. Symmetric N-ary counterpart of the instance method
|
|
7
|
+
# {#locate_addr} (which is the asymmetric self-against-ref lookup).
|
|
8
|
+
#
|
|
9
|
+
# Returns `[common, idx_0, idx_1, ...]`:
|
|
10
|
+
# - `common` is a 1-D CArray of the common coordinate values, chosen by
|
|
11
|
+
# `join:` (see below), in first-appearance order.
|
|
12
|
+
# - `idx_k` is a `common`-shaped `:int64` CArray of flat addresses into
|
|
13
|
+
# `arrays[k]`: `idx_k[j]` is where `common[j]` lives in `arrays[k]`, or
|
|
14
|
+
# `UNDEF` when that array lacks the value. Each `idx_k` is exactly
|
|
15
|
+
# `common.locate_addr(arrays[k])`.
|
|
16
|
+
#
|
|
17
|
+
# Reindex each array onto the common grid with `project`, then compare
|
|
18
|
+
# element-wise (missing coordinates come through masked):
|
|
19
|
+
#
|
|
20
|
+
# common, idx_a, idx_b = CArray.align_addr(a_coord, b_coord, join: :outer)
|
|
21
|
+
# a_on_grid = a_data.project(idx_a) # common-shaped, UNDEF where a lacks it
|
|
22
|
+
# b_on_grid = b_data.project(idx_b)
|
|
23
|
+
#
|
|
24
|
+
# Because the addresses are returned (not the reindexed values), one
|
|
25
|
+
# alignment serves any number of `arrays[k]`-shaped variables — compute the
|
|
26
|
+
# idx once, project many.
|
|
27
|
+
#
|
|
28
|
+
# `join:` selects the common coordinate set:
|
|
29
|
+
# - `:outer` — union of the distinct values of every array.
|
|
30
|
+
# - `:inner` — distinct values present in every array.
|
|
31
|
+
# - `:left` — the first array's distinct values.
|
|
32
|
+
# - `:right` — the last array's distinct values.
|
|
33
|
+
#
|
|
34
|
+
# Value equality follows the value-hash discovery family (numeric `==` with
|
|
35
|
+
# NaN collapsed and `-0.0 == +0.0`; object `hash` / `eql?`; fixlen byte
|
|
36
|
+
# equality). Arrays are coerced to the first array's dtype within the same
|
|
37
|
+
# family (cross-family raises). Masked cells do not enter `common`.
|
|
38
|
+
#
|
|
39
|
+
# @param arrays [Array<CArray>] two or more arrays (Array / Range coerced
|
|
40
|
+
# via `to_ca`). One array is allowed (degenerate: `common` is its distinct
|
|
41
|
+
# values).
|
|
42
|
+
# @param join [:outer, :inner, :left, :right] how to build `common`.
|
|
43
|
+
# @return [Array<CArray>] `[common, idx_0, idx_1, ...]`.
|
|
44
|
+
# @raise [ArgumentError] when no array is given or `join` is not one of the
|
|
45
|
+
# accepted symbols.
|
|
46
|
+
def self.align_addr (*arrays, join: :outer)
|
|
47
|
+
raise ArgumentError, "align_addr: need at least one array" if arrays.empty?
|
|
48
|
+
arrays = arrays.map { |a| a.is_a?(CArray) ? a : a.to_ca }
|
|
49
|
+
# Seed the fold with the first array's distinct values so N == 1 and the
|
|
50
|
+
# union/intersection folds all agree (a bare reduce over one element would
|
|
51
|
+
# return it with duplicates intact).
|
|
52
|
+
seed = arrays.first.unique
|
|
53
|
+
common = case join
|
|
54
|
+
when :outer then arrays[1..-1].reduce(seed) { |acc, a| acc.union(a) }
|
|
55
|
+
when :inner then arrays[1..-1].reduce(seed) { |acc, a| acc.intersection(a) }
|
|
56
|
+
when :left then seed
|
|
57
|
+
when :right then arrays.last.unique
|
|
58
|
+
else
|
|
59
|
+
raise ArgumentError,
|
|
60
|
+
"align_addr: join must be :outer / :inner / :left / :right " \
|
|
61
|
+
"(got #{join.inspect})"
|
|
62
|
+
end
|
|
63
|
+
idxs = arrays.map { |a| common.locate_addr(a) }
|
|
64
|
+
[common, *idxs]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @overload align_nearest_addr(*arrays, grid: nil, direction: :round, tolerance: nil)
|
|
68
|
+
# Aligns several arrays onto one common coordinate grid by nearest match,
|
|
69
|
+
# the ordered-lane (continuous) sibling of {align_addr}. Returns
|
|
70
|
+
# `[common, idx_0, idx_1, ...]` with the same reindex contract: `idx_k` is a
|
|
71
|
+
# `common`-shaped `:int64` array of flat addresses into `arrays[k]` giving,
|
|
72
|
+
# for each grid point, the nearest value in that array — exactly
|
|
73
|
+
# `common.locate_nearest_addr(arrays[k], direction:, tolerance:)`.
|
|
74
|
+
#
|
|
75
|
+
# Unlike {align_addr}, the common grid is **not** built by a set union:
|
|
76
|
+
# continuous coordinates rarely coincide exactly, so a union would merely
|
|
77
|
+
# pile up near-duplicate points. Instead the grid is a reference axis —
|
|
78
|
+
# `grid:` when given, otherwise the first array verbatim (kept as-is, not
|
|
79
|
+
# deduplicated). Pass `grid: arrays.last` to align onto the last array's
|
|
80
|
+
# axis. (`join:` has no meaning here and is not accepted; clustering nearby
|
|
81
|
+
# coordinates into a synthesised grid is out of scope.)
|
|
82
|
+
#
|
|
83
|
+
# common, ia, ib = CArray.align_nearest_addr(a_coord, b_coord, grid: ref)
|
|
84
|
+
# a_on_grid = a_data.project(ia) # each grid point <- nearest a value
|
|
85
|
+
# b_on_grid = b_data.project(ib)
|
|
86
|
+
#
|
|
87
|
+
# `direction:` (`:round` / `:floor` / `:ceil`) and `tolerance:` are
|
|
88
|
+
# forwarded to {#locate_nearest_addr}: out-of-range grid points, and points
|
|
89
|
+
# whose nearest value is farther than `tolerance`, come back masked.
|
|
90
|
+
#
|
|
91
|
+
# @param arrays [Array<CArray>] one or more arrays (Array / Range coerced
|
|
92
|
+
# via `to_ca`).
|
|
93
|
+
# @param grid [CArray, Array, Range, nil] the reference coordinate grid;
|
|
94
|
+
# `nil` uses the first array verbatim.
|
|
95
|
+
# @param direction [:round, :floor, :ceil] rounding rule for the nearest
|
|
96
|
+
# match.
|
|
97
|
+
# @param tolerance [Numeric, nil] maximum accepted distance; farther grid
|
|
98
|
+
# points are masked. `nil` disables the check.
|
|
99
|
+
# @return [Array<CArray>] `[common, idx_0, idx_1, ...]`.
|
|
100
|
+
# @raise [ArgumentError] when no array is given (or `direction` is invalid,
|
|
101
|
+
# raised by {#locate_nearest_addr}).
|
|
102
|
+
def self.align_nearest_addr (*arrays, grid: nil, direction: :round, tolerance: nil)
|
|
103
|
+
raise ArgumentError, "align_nearest_addr: need at least one array" if arrays.empty?
|
|
104
|
+
arrays = arrays.map { |a| a.is_a?(CArray) ? a : a.to_ca }
|
|
105
|
+
common = if grid.nil?
|
|
106
|
+
arrays.first
|
|
107
|
+
else
|
|
108
|
+
grid.is_a?(CArray) ? grid : grid.to_ca
|
|
109
|
+
end
|
|
110
|
+
idxs = arrays.map { |a|
|
|
111
|
+
common.locate_nearest_addr(a, direction: direction, tolerance: tolerance)
|
|
112
|
+
}
|
|
113
|
+
[common, *idxs]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload bin(vmin, vmax, step = nil, bins: nil, lfill: nil, ufill: nil, include_max: true)
|
|
4
|
+
# Returns each element's bin index for equal-width, half-open bins
|
|
5
|
+
# over `[vmin, vmax]`. Bin `k` covers `[vmin + k*w, vmin + (k+1)*w)`
|
|
6
|
+
# where `w = (vmax - vmin) / n`. The number of bins is set either by
|
|
7
|
+
# `step` (positional; bin width, matching `snap`'s step) or by
|
|
8
|
+
# `bins:` (kwarg; count of bins, matching histogram convention);
|
|
9
|
+
# exactly one must be given.
|
|
10
|
+
#
|
|
11
|
+
# Because `[vmin, vmax]` is a user-declared inclusive range, values
|
|
12
|
+
# exactly equal to `vmax` land in the last bin by default
|
|
13
|
+
# (`include_max: true`); this differs from `bin_to`, where the
|
|
14
|
+
# user-supplied edges are treated as-is (half-open, default `false`).
|
|
15
|
+
#
|
|
16
|
+
# Out-of-range convention follows `bin_to` / `project`: `lfill` for
|
|
17
|
+
# below-range, `ufill` for above-range; `nil` on either side masks
|
|
18
|
+
# that side. NaN / masked input cells are always masked in the
|
|
19
|
+
# output, independently of `lfill` / `ufill`.
|
|
20
|
+
#
|
|
21
|
+
# Use `snap(step, offset:)` when the desired output is the snapped
|
|
22
|
+
# value; use `bin_to(edges)` when the edges are non-uniform.
|
|
23
|
+
#
|
|
24
|
+
# @param vmin [Numeric] range lower bound.
|
|
25
|
+
# @param vmax [Numeric] range upper bound; must be `>= vmin`.
|
|
26
|
+
# @param step [Numeric, nil] bin width. The number of bins is
|
|
27
|
+
# `((vmax - vmin) / step).round` (silent FP rounding, standard
|
|
28
|
+
# numeric convention).
|
|
29
|
+
# @param bins [Integer, nil] number of bins (alternative to `step`);
|
|
30
|
+
# must be `>= 1`.
|
|
31
|
+
# @param lfill [Integer, nil] fill for below-range cells.
|
|
32
|
+
# @param ufill [Integer, nil] fill for above-range cells.
|
|
33
|
+
# @param include_max [Boolean] fold values equal to `vmax` into the
|
|
34
|
+
# last bin instead of treating them as above-range.
|
|
35
|
+
# @return [CArray] `CA_INT64` bin indices in `[0, n-1]`, same
|
|
36
|
+
# shape as `self`.
|
|
37
|
+
# @raise [ArgumentError] when neither / both of `step` and `bins:`
|
|
38
|
+
# are given, `bins < 1`, or `vmin > vmax`.
|
|
39
|
+
# @example
|
|
40
|
+
# temp.bin(270, 300, 0.5) # 60 uniform bins every 0.5 K
|
|
41
|
+
# temp.bin(0, 1, bins: 100) # 100 equal-width bins over [0, 1]
|
|
42
|
+
# temp.bin(0, 9, 1, lfill: 0, ufill: 8) # clamp OOB to end bins
|
|
43
|
+
def bin(vmin, vmax, step = nil, bins: nil, lfill: nil, ufill: nil, include_max: true)
|
|
44
|
+
if step.nil? == bins.nil?
|
|
45
|
+
raise ArgumentError, "bin: give exactly one of `step` or `bins:`"
|
|
46
|
+
end
|
|
47
|
+
raise ArgumentError, "bin: vmin > vmax" if vmin > vmax
|
|
48
|
+
|
|
49
|
+
n = bins || ((vmax - vmin).to_f / step).round
|
|
50
|
+
raise ArgumentError, "bin: n must be >= 1" if n < 1
|
|
51
|
+
|
|
52
|
+
if vmin == vmax
|
|
53
|
+
# Degenerate: zero interval → all cells fall on the single edge;
|
|
54
|
+
# with include_max: true they land in bin 0.
|
|
55
|
+
out = CArray.int64(*shape) { 0 }
|
|
56
|
+
out.mask = self.mask.to_ca if self.has_mask?
|
|
57
|
+
if self.float?
|
|
58
|
+
inv = self.is_invalid
|
|
59
|
+
if inv.count(true) > 0
|
|
60
|
+
out.mask = out.has_mask? ? (out.mask | inv) : inv
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
return out
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Delegate to `bin_to` with generated uniform edges — same kernel
|
|
67
|
+
# (`histbin_ki`) as `histogram`, so semantics are identical.
|
|
68
|
+
edges = CArray.float64(n + 1).span(vmin..vmax)
|
|
69
|
+
bin_to(edges, lfill: lfill, ufill: ufill, include_max: include_max)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @overload bin_to(edges, lfill: nil, ufill: nil, include_max: false)
|
|
73
|
+
# Returns each element's bin index against an explicit ascending
|
|
74
|
+
# `edges` array (non-uniform binning). Sibling of `bin` (uniform,
|
|
75
|
+
# range + step) and `snap_to` (nearest-value snap to the same shape
|
|
76
|
+
# of grid).
|
|
77
|
+
#
|
|
78
|
+
# `edges` are `N+1` ascending boundaries defining `N` bins; bin
|
|
79
|
+
# `k` covers the half-open interval `[edges[k], edges[k+1])`.
|
|
80
|
+
# Out-of-range values follow the `bin` / `project` convention:
|
|
81
|
+
# a value below `edges[0]` becomes `lfill`, a value at or above
|
|
82
|
+
# `edges[-1]` becomes `ufill`; `nil` on either side masks that
|
|
83
|
+
# side. When `include_max` is true, a value exactly equal to
|
|
84
|
+
# `edges[-1]` lands in the last bin `N-1` instead of being
|
|
85
|
+
# treated as above-range. NaN or masked input cells are masked
|
|
86
|
+
# in the output, independently of `lfill` / `ufill`.
|
|
87
|
+
#
|
|
88
|
+
# The inner binning kernel is shared with `histogram`, which counts
|
|
89
|
+
# how many values land in each bin.
|
|
90
|
+
#
|
|
91
|
+
# @param edges [CArray, Array<Numeric>] 1-D ascending boundaries
|
|
92
|
+
# with at least 2 values.
|
|
93
|
+
# @param lfill [Integer, nil] fill for below-range cells; `nil`
|
|
94
|
+
# masks them.
|
|
95
|
+
# @param ufill [Integer, nil] fill for above-range cells; `nil`
|
|
96
|
+
# masks them.
|
|
97
|
+
# @param include_max [Boolean] fold values equal to `edges[-1]`
|
|
98
|
+
# into the last bin instead of treating them as above-range.
|
|
99
|
+
# @return [CArray] `CA_INT64` array with the same shape as `self`
|
|
100
|
+
# holding bin indices in `[0, N-1]` (or the fill values / mask
|
|
101
|
+
# for out-of-range and masked cells).
|
|
102
|
+
# @raise [ArgumentError] when `edges` is not 1-D or has fewer
|
|
103
|
+
# than 2 values.
|
|
104
|
+
# @example
|
|
105
|
+
# e = CA_FLOAT64([0, 1, 10, 100])
|
|
106
|
+
# v = CA_FLOAT64([0.5, 5.0, 50.0, -1.0, 200.0])
|
|
107
|
+
# v.bin_to(e) # => [0, 1, 2, UNDEF, UNDEF]
|
|
108
|
+
# v.bin_to(e, lfill: 0, ufill: 2)
|
|
109
|
+
# # => [0, 1, 2, 0, 2]
|
|
110
|
+
def bin_to(edges, lfill: nil, ufill: nil, include_max: false)
|
|
111
|
+
e = CArray.wrap_readonly(edges, :float64)
|
|
112
|
+
raise ArgumentError, "bin_to: edges must be 1-D" unless e.ndim == 1
|
|
113
|
+
raise ArgumentError, "bin_to: edges needs at least 2 values" if e.elements < 2
|
|
114
|
+
n = e.elements - 1 # number of bins
|
|
115
|
+
|
|
116
|
+
src = data_type == CA_FLOAT64 ? self : CArray.wrap_readonly(self, :float64)
|
|
117
|
+
|
|
118
|
+
# histbin_ki returns the extended index (0 = under, 1..N = in-range bins,
|
|
119
|
+
# N+1 = over; NaN / masked -> masked). Shift to the in-range convention:
|
|
120
|
+
# under -> -1, in-range -> 0..N-1, over -> N.
|
|
121
|
+
out = src.send(:histbin_ki, e, include_max) - 1
|
|
122
|
+
|
|
123
|
+
out[:eq, -1] = lfill.nil? ? UNDEF : lfill # under
|
|
124
|
+
out[:eq, n] = ufill.nil? ? UNDEF : ufill # over
|
|
125
|
+
out
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Ruby front-end for CArray#bincount. Validates inputs, picks the
|
|
2
|
+
# output length, and dispatches to the dedicated C kernels in
|
|
3
|
+
# ext/carray_bincount.c (`__bincount_count__` / `__bincount_weighted__`).
|
|
4
|
+
|
|
5
|
+
class CArray
|
|
6
|
+
|
|
7
|
+
# @overload bincount(weights: nil, length: 0)
|
|
8
|
+
# Returns occurrence counts per non-negative integer label in
|
|
9
|
+
# `self`, or a per-label sum of `weights`.
|
|
10
|
+
#
|
|
11
|
+
# Masked labels are skipped (not counted). Masked weights are
|
|
12
|
+
# skipped too (their label contributes 0).
|
|
13
|
+
#
|
|
14
|
+
# @param weights [CArray, nil] when given, sums weights per
|
|
15
|
+
# label instead of counting; length must equal `self.elements`
|
|
16
|
+
# and the output `data_type` is inherited from `weights`.
|
|
17
|
+
# When `nil` (default), counts occurrences and returns
|
|
18
|
+
# `CA_UINT32` (or `CA_UINT64` if `length >= 2^32`).
|
|
19
|
+
# @param length [Integer] minimum output length; the actual
|
|
20
|
+
# length is `max(length, self.max + 1)`.
|
|
21
|
+
# @return [CArray] 1-D output of length `max(length, self.max + 1)`.
|
|
22
|
+
# @raise [CArray::DataTypeError] when `self` is not an integer
|
|
23
|
+
# `data_type`.
|
|
24
|
+
# @raise [ArgumentError] when a label is negative or `weights`
|
|
25
|
+
# length disagrees with `self.elements`.
|
|
26
|
+
# @example
|
|
27
|
+
# labels = CA_INT32([0, 1, 1, 2, 0, 1])
|
|
28
|
+
# labels.bincount # => CA_UINT32([2, 3, 1])
|
|
29
|
+
# labels.bincount(length: 5) # => CA_UINT32([2, 3, 1, 0, 0])
|
|
30
|
+
# weights = CA_DOUBLE([1, 2, 3, 4, 5, 6])
|
|
31
|
+
# labels.bincount(weights: weights) # => CA_DOUBLE([6, 11, 4])
|
|
32
|
+
def bincount(weights: nil, length: 0)
|
|
33
|
+
unless [CA_INT8, CA_INT16, CA_INT32, CA_INT64,
|
|
34
|
+
CA_UINT8, CA_UINT16, CA_UINT32, CA_UINT64].include?(data_type)
|
|
35
|
+
raise CArray::DataTypeError,
|
|
36
|
+
"bincount requires an integer label array (got #{data_type_name})"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if elements.zero?
|
|
40
|
+
if weights
|
|
41
|
+
out = CArray.new(weights.data_type, [length])
|
|
42
|
+
else
|
|
43
|
+
out_type = (length > 0xFFFFFFFF) ? CA_UINT64 : CA_UINT32
|
|
44
|
+
out = CArray.new(out_type, [length])
|
|
45
|
+
end
|
|
46
|
+
out.fill(0) unless length.zero?
|
|
47
|
+
return out
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Single-pass fused min+max so the prereq scan over labels costs
|
|
51
|
+
# one walk instead of two.
|
|
52
|
+
label_min, label_max = minmax
|
|
53
|
+
if label_min.equal?(UNDEF)
|
|
54
|
+
# Every cell is masked: no labels to count, same result as an empty
|
|
55
|
+
# input (all-zero output of the requested minimum length).
|
|
56
|
+
if weights
|
|
57
|
+
out = CArray.new(weights.data_type, [length])
|
|
58
|
+
else
|
|
59
|
+
out_type = (length > 0xFFFFFFFF) ? CA_UINT64 : CA_UINT32
|
|
60
|
+
out = CArray.new(out_type, [length])
|
|
61
|
+
end
|
|
62
|
+
out.fill(0) unless length.zero?
|
|
63
|
+
return out
|
|
64
|
+
end
|
|
65
|
+
if label_min < 0
|
|
66
|
+
raise ArgumentError,
|
|
67
|
+
"bincount: negative label not allowed (got #{label_min})"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
n = [length, label_max + 1].max
|
|
71
|
+
|
|
72
|
+
if weights
|
|
73
|
+
unless weights.is_a?(CArray)
|
|
74
|
+
raise ArgumentError, "bincount: weights must be a CArray"
|
|
75
|
+
end
|
|
76
|
+
if weights.elements != elements
|
|
77
|
+
raise ArgumentError,
|
|
78
|
+
"bincount: weights length (#{weights.elements}) doesn't " \
|
|
79
|
+
"match labels length (#{elements})"
|
|
80
|
+
end
|
|
81
|
+
__bincount_weighted__(weights, n)
|
|
82
|
+
else
|
|
83
|
+
__bincount_count__(n)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|