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,512 @@
|
|
|
1
|
+
# ----------------------------------------------------------------------------
|
|
2
|
+
#
|
|
3
|
+
# `CArray::Histogram` + `CArray#histogram1d` / `#histogram2d` / `#histogram`.
|
|
4
|
+
#
|
|
5
|
+
# A single class handles M=1, 2, ..., general M-D. The 1-D entry point
|
|
6
|
+
# (`data.histogram1d`) reshapes the input to add a trailing channel axis of
|
|
7
|
+
# length 1, then dispatches into the same Histogram path; the returned
|
|
8
|
+
# object is a Histogram instance with M=1.
|
|
9
|
+
#
|
|
10
|
+
# ## Concepts / vocabulary
|
|
11
|
+
#
|
|
12
|
+
# ### Histogram dimensionality
|
|
13
|
+
#
|
|
14
|
+
# * **M** — the histogram's dimensionality (= number of edges arrays = length
|
|
15
|
+
# of the channel axis in input data). M=1 for `histogram1d`, M=2 for
|
|
16
|
+
# `histogram2d`, M>=1 for general `histogram`.
|
|
17
|
+
#
|
|
18
|
+
# ### Input layout
|
|
19
|
+
#
|
|
20
|
+
# Data passed to the entry methods has shape:
|
|
21
|
+
#
|
|
22
|
+
# fiber_shape + (A,) + (M,)
|
|
23
|
+
# ^ ^ ^
|
|
24
|
+
# | | channel axis (= M coordinate dims per sample)
|
|
25
|
+
# | sample axis (= A independent samples per fiber)
|
|
26
|
+
# leading fiber axes (= per-fiber histogram is computed)
|
|
27
|
+
#
|
|
28
|
+
# For `histogram1d` (M=1), the trailing `(M,)` axis may be omitted (= raw
|
|
29
|
+
# `fiber_shape + (A,)` is accepted directly; the entry method reshapes).
|
|
30
|
+
#
|
|
31
|
+
# * **fiber_shape** — the leading axes of the input. Each fiber position
|
|
32
|
+
# gets its own independent histogram. shape `()` (= no fiber axes) means
|
|
33
|
+
# "one global histogram over all samples".
|
|
34
|
+
# * **sample axis** — the axis along which independent samples are drawn.
|
|
35
|
+
# Locked at construction (= the entry method's `axis:` kwarg fixes it).
|
|
36
|
+
# * **channel axis** — the trailing axis of length M carrying the M coordinate
|
|
37
|
+
# dimensions of each sample. For M=1 it is length 1 (= degenerate).
|
|
38
|
+
#
|
|
39
|
+
# ### Bin structure
|
|
40
|
+
#
|
|
41
|
+
# * **edges** — N+1 boundary values defining N bins per dimension. Edges
|
|
42
|
+
# must be sorted ascending. bin k spans `[edges[k], edges[k+1])` (left-
|
|
43
|
+
# closed, right-open). See `include_max:` for the upper-edge variant.
|
|
44
|
+
# * **bin** — half-open interval between two consecutive edges. Bin index
|
|
45
|
+
# k in 0..N-1.
|
|
46
|
+
# * **N** (= n_list[k]) — number of bins along dimension k (= edges.size - 1).
|
|
47
|
+
# * **midpoints** — bin center points: `(edges[0..-2] + edges[1..-1]) / 2`,
|
|
48
|
+
# shape (N,). Returned per-dim (single CArray for M=1, Array of M for M>=2).
|
|
49
|
+
#
|
|
50
|
+
# ### Counts storage (= extended counts model)
|
|
51
|
+
#
|
|
52
|
+
# Internal storage is a single CArray of shape:
|
|
53
|
+
#
|
|
54
|
+
# fiber_shape + (n_0 + 2, n_1 + 2, ..., n_{M-1} + 2)
|
|
55
|
+
#
|
|
56
|
+
# Each bin dimension is "extended" by 2 (= +1 cell on each side for under /
|
|
57
|
+
# over). All accumulation lands in this one buffer; outlier counters are
|
|
58
|
+
# views (= no separate allocation).
|
|
59
|
+
#
|
|
60
|
+
# * **full_counts** — the extended storage, shape includes outlier cells.
|
|
61
|
+
# * **counts** — CABlock inner view: shape fiber + (n_0, ..., n_{M-1}), the
|
|
62
|
+
# in-range bins only. Zero-copy view of `full_counts`.
|
|
63
|
+
# * **under(axis: k)** — count of samples whose dim-k coordinate fell below
|
|
64
|
+
# `edges[k][0]`. Other bin axes are marginalised (= summed over all
|
|
65
|
+
# positions including their outliers). shape = fiber_shape.
|
|
66
|
+
# * **over(axis: k)** — symmetric upper outlier marginal.
|
|
67
|
+
# * **outlier_total** — count of samples that fell outside on *any* axis
|
|
68
|
+
# (= `total - counts.sum`). shape = fiber_shape.
|
|
69
|
+
# * **total** — count of all samples seen (including outliers).
|
|
70
|
+
# shape = fiber_shape.
|
|
71
|
+
#
|
|
72
|
+
# Per-dim marginals may "double-count" samples that fall out on multiple
|
|
73
|
+
# axes (= a sample that's under on dim 0 AND over on dim 1 appears in both
|
|
74
|
+
# `under(axis: 0)` and `over(axis: 1)`). This is the intended marginal
|
|
75
|
+
# reading; for exact non-overlapping breakdown, slice `full_counts`
|
|
76
|
+
# directly.
|
|
77
|
+
#
|
|
78
|
+
# ### Bin closure (`include_max:`)
|
|
79
|
+
#
|
|
80
|
+
# By default, all bins are left-closed right-open: `[edges[k], edges[k+1])`.
|
|
81
|
+
# A sample exactly at `edges[-1]` (= the upper boundary) falls into the over
|
|
82
|
+
# counter, not bin N-1. For bounded physical ranges (humidity 0-100%,
|
|
83
|
+
# probability, angles mod 360°), opt in with `include_max:`:
|
|
84
|
+
#
|
|
85
|
+
# h = data.histogram1d(edges: linspace(0, 100, 21), include_max: true)
|
|
86
|
+
# # v == 100.0 now lands in bin 19 (= [95, 100]), not over.
|
|
87
|
+
#
|
|
88
|
+
# Per-dim Array of booleans is accepted for joint histograms (= mark each
|
|
89
|
+
# dim independently as bounded vs unbounded).
|
|
90
|
+
#
|
|
91
|
+
# ### Weighted accumulation
|
|
92
|
+
#
|
|
93
|
+
# Pass `weights: w` to `histogram1d` / etc., or to `add(chunk, weights: w)`.
|
|
94
|
+
# Each sample contributes `w[i]` instead of 1 to its target cell.
|
|
95
|
+
#
|
|
96
|
+
# * **weights.shape** = chunk.shape minus the channel axis (= fiber + (A,)).
|
|
97
|
+
# * **dtype** is locked at construction (= the entry method's `weights:` kwarg
|
|
98
|
+
# fixes weighted vs unweighted; subsequent adds must match). Counts dtype:
|
|
99
|
+
# int64 unweighted, float64 weighted. Weighted counts are always float64:
|
|
100
|
+
# the fused scatter kernel requires float64 weights, so integer weights are
|
|
101
|
+
# taken as float64 (= integer weighted counts are not supported).
|
|
102
|
+
#
|
|
103
|
+
# ### Streaming via `add`
|
|
104
|
+
#
|
|
105
|
+
# The instance returned by the entry methods is a live accumulator; further
|
|
106
|
+
# data can be added via `acc.add(chunk, weights: ...)`. Shape contract:
|
|
107
|
+
# the chunk's fiber_shape must match, sample axis length is free. Empty
|
|
108
|
+
# accumulator bootstrap: pass zero-length sample axis to the entry method
|
|
109
|
+
# (e.g. `CArray.float64(K, L, 0).histogram1d(edges: e)`).
|
|
110
|
+
#
|
|
111
|
+
# ### Mask handling
|
|
112
|
+
#
|
|
113
|
+
# Input data with a CArray mask: masked samples are skipped (= not counted
|
|
114
|
+
# in any cell, including outliers). NaN inputs are treated the same way
|
|
115
|
+
# (= masked via `mask_invalid`). Per-sample mask is the union over all
|
|
116
|
+
# channels (= a sample with even one masked channel is dropped entirely).
|
|
117
|
+
#
|
|
118
|
+
# ### Composition
|
|
119
|
+
#
|
|
120
|
+
# `h1 + h2` returns a new Histogram with cells summed elementwise. Both
|
|
121
|
+
# operands must agree on edges / fiber_shape / include_max / weighted dtype
|
|
122
|
+
# (= the structure-level semantic guard); cells themselves are just
|
|
123
|
+
# integer / float tallies. See the `+` method.
|
|
124
|
+
#
|
|
125
|
+
# ----------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
class CArray
|
|
128
|
+
|
|
129
|
+
# Binned counts over one or more continuous variables, of any
|
|
130
|
+
# dimensionality M. Built by `CArray#histogram1d` / `#histogram2d` /
|
|
131
|
+
# `#histogram` rather than constructed directly; the 1-D entry point is the
|
|
132
|
+
# same class with M = 1.
|
|
133
|
+
#
|
|
134
|
+
# For discrete integer labels (value == bin index, no edges) use
|
|
135
|
+
# {BincountND} instead.
|
|
136
|
+
class Histogram
|
|
137
|
+
|
|
138
|
+
# @overload initialize(edges:, fiber_shape: [], include_max: false, weights_dtype: nil)
|
|
139
|
+
# Allocates a new histogram accumulator.
|
|
140
|
+
# @param edges [Array<CArray, Array<Numeric>>] one edges array
|
|
141
|
+
# per histogram dimension; each must be 1-D sorted ascending
|
|
142
|
+
# with at least 2 values.
|
|
143
|
+
# @param fiber_shape [Array<Integer>] shape of the leading
|
|
144
|
+
# (non-sample, non-channel) axes.
|
|
145
|
+
# @param include_max [Boolean, Array<Boolean>] whether values
|
|
146
|
+
# equal to the last edge fold into the last bin; a scalar
|
|
147
|
+
# broadcasts across dimensions.
|
|
148
|
+
# @param weights_dtype [Symbol, nil] `data_type` of the
|
|
149
|
+
# accumulator when weighted; `nil` for a count-only
|
|
150
|
+
# accumulator (int64 counts).
|
|
151
|
+
# @return [Histogram]
|
|
152
|
+
def initialize (edges:, fiber_shape: [], include_max: false, weights_dtype: nil)
|
|
153
|
+
@edges_list = edges.map { |e| CArray.wrap_readonly(e, :float64) }
|
|
154
|
+
raise ArgumentError, "edges must be a non-empty list" if @edges_list.empty?
|
|
155
|
+
@edges_list.each_with_index do |e, k|
|
|
156
|
+
raise ArgumentError, "edges[#{k}] must be 1-D" unless e.ndim == 1
|
|
157
|
+
raise ArgumentError, "edges[#{k}] needs at least 2 values" if e.elements < 2
|
|
158
|
+
end
|
|
159
|
+
@m = @edges_list.size # histogram dimensionality (= channel axis length)
|
|
160
|
+
@n_list = @edges_list.map { |e| e.elements - 1 } # per-dim bin count
|
|
161
|
+
@fiber_shape = fiber_shape.map(&:to_i).freeze
|
|
162
|
+
@include_max = case include_max
|
|
163
|
+
when Array
|
|
164
|
+
raise ArgumentError, "include_max length mismatch" unless include_max.size == @m
|
|
165
|
+
include_max.map { |v| !!v }
|
|
166
|
+
else
|
|
167
|
+
[!!include_max] * @m
|
|
168
|
+
end
|
|
169
|
+
@weighted = !weights_dtype.nil?
|
|
170
|
+
@counts_dtype = @weighted ? weights_dtype : :int64
|
|
171
|
+
ext_dims = @n_list.map { |n| n + 2 }
|
|
172
|
+
ext_shape = @fiber_shape + ext_dims
|
|
173
|
+
@full_counts = CArray.public_send(@counts_dtype, *ext_shape).fill(0)
|
|
174
|
+
@sample_axis = nil
|
|
175
|
+
@channel_axis = nil
|
|
176
|
+
end
|
|
177
|
+
private_class_method :new
|
|
178
|
+
|
|
179
|
+
attr_reader :edges_list, :fiber_shape, :include_max, :n_list, :full_counts, :m
|
|
180
|
+
|
|
181
|
+
# @overload edges
|
|
182
|
+
# Returns the bin edges: a single CArray when the accumulator
|
|
183
|
+
# is 1-D (M == 1), an Array of CArrays otherwise.
|
|
184
|
+
# @return [CArray, Array<CArray>]
|
|
185
|
+
def edges
|
|
186
|
+
@m == 1 ? @edges_list[0] : @edges_list
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @overload counts
|
|
190
|
+
# Returns the in-range counts view with shape
|
|
191
|
+
# `fiber_shape + (n1, n2, ..., nM)`, excluding under- and
|
|
192
|
+
# over-flow bins.
|
|
193
|
+
# @return [CArray]
|
|
194
|
+
def counts
|
|
195
|
+
idx = [nil] * @fiber_shape.size + [1..-2] * @m
|
|
196
|
+
@full_counts[*idx]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# @overload under(axis: nil)
|
|
200
|
+
# Returns the underflow marginal along the given bin `axis`,
|
|
201
|
+
# shape `fiber_shape`. For 1-D accumulators `axis` may be
|
|
202
|
+
# omitted; for joint histograms it must be specified.
|
|
203
|
+
# @param axis [Integer, nil] bin dimension to marginalise.
|
|
204
|
+
# @return [CArray]
|
|
205
|
+
# @raise [ArgumentError] when `axis` is required but omitted.
|
|
206
|
+
def under (axis: nil)
|
|
207
|
+
raise ArgumentError, "axis: keyword required (M=#{@m})" if axis.nil? && @m > 1
|
|
208
|
+
ax = axis.nil? ? 0 : CArray.normalize_axis(axis, @m, "under")
|
|
209
|
+
outlier_marginal(ax, 0)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# @overload over(axis: nil)
|
|
213
|
+
# Returns the overflow marginal along the given bin `axis`,
|
|
214
|
+
# shape `fiber_shape`. Same `axis` convention as {#under}.
|
|
215
|
+
# @param axis [Integer, nil] bin dimension to marginalise.
|
|
216
|
+
# @return [CArray]
|
|
217
|
+
# @raise [ArgumentError] when `axis` is required but omitted.
|
|
218
|
+
def over (axis: nil)
|
|
219
|
+
raise ArgumentError, "axis: keyword required (M=#{@m})" if axis.nil? && @m > 1
|
|
220
|
+
ax = axis.nil? ? 0 : CArray.normalize_axis(axis, @m, "over")
|
|
221
|
+
outlier_marginal(ax, -1)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# @overload midpoints
|
|
225
|
+
# Returns the midpoint of each in-range bin. Polymorphic like
|
|
226
|
+
# {#edges}: a single CArray for M == 1, an Array of CArrays
|
|
227
|
+
# otherwise.
|
|
228
|
+
# @return [CArray, Array<CArray>]
|
|
229
|
+
def midpoints
|
|
230
|
+
arr = @edges_list.map { |e| (e[0..-2] + e[1..-1]) / 2.0 }
|
|
231
|
+
@m == 1 ? arr[0] : arr
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# @overload total
|
|
235
|
+
# Returns the per-fiber sample total, including outliers, with
|
|
236
|
+
# shape `fiber_shape` (or a scalar when `fiber_shape` is
|
|
237
|
+
# empty).
|
|
238
|
+
# @return [CArray]
|
|
239
|
+
def total
|
|
240
|
+
sum_along_bin_axes(@full_counts)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# @overload outlier_total
|
|
244
|
+
# Returns the per-fiber count of samples that fell outside
|
|
245
|
+
# every in-range bin.
|
|
246
|
+
# @return [CArray]
|
|
247
|
+
def outlier_total
|
|
248
|
+
sum_along_bin_axes(@full_counts) - sum_along_bin_axes(counts)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# @overload add(chunk, axis: nil, weights: nil)
|
|
252
|
+
# Accumulates `chunk` into `self`. On the first call the
|
|
253
|
+
# sample and channel axes are locked; subsequent calls must
|
|
254
|
+
# supply the same axis pair. When the accumulator is
|
|
255
|
+
# weighted, `weights` are required with a shape equal to
|
|
256
|
+
# `chunk.shape` minus the channel axis.
|
|
257
|
+
# @param chunk [CArray] sample values with shape
|
|
258
|
+
# `fiber_shape + (A, M)` (channel axis size must equal `m`).
|
|
259
|
+
# @param axis [Array(Integer, Integer), Integer, nil]
|
|
260
|
+
# `[sample, channel]` axis pair; a bare Integer is treated
|
|
261
|
+
# as the sample axis for 1-D accumulators.
|
|
262
|
+
# @param weights [CArray, nil] optional per-sample weights.
|
|
263
|
+
# @return [self]
|
|
264
|
+
# @raise [ArgumentError] on shape / axis / weighted-state
|
|
265
|
+
# mismatch.
|
|
266
|
+
def add (chunk, axis: nil, weights: nil)
|
|
267
|
+
chunk = CArray.wrap_readonly(chunk, :float64)
|
|
268
|
+
|
|
269
|
+
# For M=1, accept chunks without the trailing channel axis (= the 1-D
|
|
270
|
+
# user convention from `histogram1d`). Auto-reshape adds a length-1
|
|
271
|
+
# axis at the end; scalar `axis:` is interpreted as the sample axis in
|
|
272
|
+
# the unwrapped layout.
|
|
273
|
+
if @m == 1 && chunk.ndim == @fiber_shape.size + 1
|
|
274
|
+
chunk = chunk.reshape(*(chunk.shape + [1]))
|
|
275
|
+
if axis.is_a?(Integer)
|
|
276
|
+
# axis was given in the unwrapped (pre-reshape) layout: normalize
|
|
277
|
+
# against ndim-1 (= the unwrapped ndim) then pair with the new
|
|
278
|
+
# trailing channel position.
|
|
279
|
+
ax = CArray.normalize_axis(axis, chunk.ndim - 1, "add axis")
|
|
280
|
+
axis = [ax, chunk.ndim - 1]
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# --- normalize axis: into [sample, channel] pair -----------------
|
|
285
|
+
ax = axis || [-2, -1]
|
|
286
|
+
ax = [ax] if ax.is_a?(Integer)
|
|
287
|
+
raise ArgumentError, "axis must be [sample, channel]" unless ax.is_a?(Array) && ax.size == 2
|
|
288
|
+
sample_ax = CArray.normalize_axis(ax[0], chunk.ndim, "sample axis")
|
|
289
|
+
channel_ax = CArray.normalize_axis(ax[1], chunk.ndim, "channel axis")
|
|
290
|
+
raise ArgumentError, "same axis used twice" if sample_ax == channel_ax
|
|
291
|
+
|
|
292
|
+
# --- lock axes on first add, otherwise verify against locked -----
|
|
293
|
+
if @sample_axis.nil?
|
|
294
|
+
@sample_axis = sample_ax
|
|
295
|
+
@channel_axis = channel_ax
|
|
296
|
+
elsif @sample_axis != sample_ax || @channel_axis != channel_ax
|
|
297
|
+
raise ArgumentError,
|
|
298
|
+
"axis mismatch (locked at [#{@sample_axis}, #{@channel_axis}], got [#{sample_ax}, #{channel_ax}])"
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# --- validate chunk shape against (fiber_shape, M) ---------------
|
|
302
|
+
expected_ndim = @fiber_shape.size + 2
|
|
303
|
+
unless chunk.ndim == expected_ndim
|
|
304
|
+
raise ArgumentError,
|
|
305
|
+
"chunk.ndim=#{chunk.ndim} expected #{expected_ndim} " \
|
|
306
|
+
"(fiber #{@fiber_shape.inspect} + sample + channel)"
|
|
307
|
+
end
|
|
308
|
+
unless chunk.shape[channel_ax] == @m
|
|
309
|
+
raise ArgumentError,
|
|
310
|
+
"channel axis length #{chunk.shape[channel_ax]} != M=#{@m}"
|
|
311
|
+
end
|
|
312
|
+
chunk_fiber = chunk.shape.dup
|
|
313
|
+
[sample_ax, channel_ax].sort.reverse.each { |p| chunk_fiber.delete_at(p) }
|
|
314
|
+
unless chunk_fiber == @fiber_shape
|
|
315
|
+
raise ArgumentError,
|
|
316
|
+
"fiber shape mismatch: chunk yields #{chunk_fiber.inspect}, expected #{@fiber_shape.inspect}"
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
sample_count = chunk.shape[sample_ax]
|
|
320
|
+
return self if sample_count == 0
|
|
321
|
+
|
|
322
|
+
if weights
|
|
323
|
+
raise ArgumentError, "weights given but accumulator is unweighted" unless @weighted
|
|
324
|
+
weights = CArray.wrap_readonly(weights, @counts_dtype)
|
|
325
|
+
expected_w_shape = chunk.shape.dup
|
|
326
|
+
expected_w_shape.delete_at(channel_ax)
|
|
327
|
+
unless weights.shape == expected_w_shape
|
|
328
|
+
raise ArgumentError,
|
|
329
|
+
"weights shape #{weights.shape.inspect} expected #{expected_w_shape.inspect} " \
|
|
330
|
+
"(chunk minus channel axis at #{channel_ax})"
|
|
331
|
+
end
|
|
332
|
+
elsif @weighted
|
|
333
|
+
raise ArgumentError, "weights required (accumulator is weighted)"
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# --- fused scatter kernel (stage 2) ------------------------------
|
|
337
|
+
# Bin all M channels per sample and scatter directly into @full_counts
|
|
338
|
+
# with NO intermediate index arrays (peak memory O(1), not O(M * A) for
|
|
339
|
+
# A samples).
|
|
340
|
+
# self is transposed to [fiber..., sample, channel] (a view; the kernel
|
|
341
|
+
# iterator delivers it strided, no materialise). Weights, if present,
|
|
342
|
+
# are transposed to [fiber..., sample] and delivered by a second
|
|
343
|
+
# iterator in lockstep (both views, no materialise).
|
|
344
|
+
fiber_axes = (0...chunk.ndim).to_a - [sample_ax, channel_ax]
|
|
345
|
+
tchunk = chunk.transpose(*(fiber_axes + [sample_ax, channel_ax]))
|
|
346
|
+
|
|
347
|
+
tweights = nil
|
|
348
|
+
if weights
|
|
349
|
+
# weights axes = chunk axes with channel removed: an index above
|
|
350
|
+
# channel_ax shifts down by 1.
|
|
351
|
+
shift = ->(p) { p < channel_ax ? p : p - 1 }
|
|
352
|
+
tweights = weights.transpose(*(fiber_axes.map(&shift) + [shift.call(sample_ax)]))
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
tchunk.send(:histogram_scatter_ki, @full_counts, @edges_list, @include_max, tweights)
|
|
356
|
+
|
|
357
|
+
self
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# @overload +(other)
|
|
361
|
+
# Returns a new Histogram whose counts are the element-wise
|
|
362
|
+
# sum of `self` and `other`. Both operands must share edges,
|
|
363
|
+
# `fiber_shape`, `include_max`, and weighted/unweighted state.
|
|
364
|
+
# @param other [Histogram] compatible accumulator.
|
|
365
|
+
# @return [Histogram]
|
|
366
|
+
# @raise [ArgumentError] when the structure does not match.
|
|
367
|
+
def + (other)
|
|
368
|
+
# --- semantic guards: structure must match exactly ---------------
|
|
369
|
+
raise ArgumentError, "type mismatch" unless other.is_a?(Histogram)
|
|
370
|
+
raise ArgumentError, "M mismatch" unless @m == other.m
|
|
371
|
+
@edges_list.each_with_index do |e, k|
|
|
372
|
+
raise ArgumentError, "edges[#{k}] mismatch" unless e == other.edges_list[k]
|
|
373
|
+
end
|
|
374
|
+
raise ArgumentError, "fiber_shape mismatch" unless @fiber_shape == other.fiber_shape
|
|
375
|
+
raise ArgumentError, "include_max mismatch (semantic guard)" unless @include_max == other.include_max
|
|
376
|
+
raise ArgumentError, "weighted/unweighted mismatch" unless @weighted == other.weighted?
|
|
377
|
+
|
|
378
|
+
result = self.class.send(:new,
|
|
379
|
+
edges: @edges_list,
|
|
380
|
+
fiber_shape: @fiber_shape,
|
|
381
|
+
include_max: @include_max,
|
|
382
|
+
weights_dtype: @weighted ? @counts_dtype : nil)
|
|
383
|
+
rf = result.instance_variable_get(:@full_counts)
|
|
384
|
+
rf[] = @full_counts + other.full_counts
|
|
385
|
+
result.instance_variable_set(:@sample_axis, @sample_axis)
|
|
386
|
+
result.instance_variable_set(:@channel_axis, @channel_axis)
|
|
387
|
+
result
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
protected
|
|
391
|
+
|
|
392
|
+
# Exposed to sibling instances only (= `+` reads the other operand's
|
|
393
|
+
# weighted state for the semantic guard). protected, not public: this is
|
|
394
|
+
# internal accumulator state, not part of the user-facing surface.
|
|
395
|
+
def weighted?
|
|
396
|
+
@weighted
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
private
|
|
400
|
+
|
|
401
|
+
# arr.shape = fiber_shape + (last M bin axes).
|
|
402
|
+
# Reduce along the last M axes, returns shape fiber_shape (or scalar).
|
|
403
|
+
#
|
|
404
|
+
# `accumulate` preserves dtype (= int64 stays int64, float64 stays float64),
|
|
405
|
+
# unlike `sum` which always lifts to float64. Caveat: int64 overflows at
|
|
406
|
+
# ~9.2e18 (silent wrap); weighted float64 loses precision past 2^53 but
|
|
407
|
+
# does not overflow. Realistic histograms do not hit these limits.
|
|
408
|
+
def sum_along_bin_axes (arr)
|
|
409
|
+
out = arr
|
|
410
|
+
@m.times { out = out.accumulate(axis: out.ndim - 1) }
|
|
411
|
+
out
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# axis k of the bin dims (= which channel's outlier to look at).
|
|
415
|
+
# offset = 0 (under) or -1 (over). Other bin axes are marginalised away
|
|
416
|
+
# (= including their outlier positions).
|
|
417
|
+
def outlier_marginal (axis, offset)
|
|
418
|
+
base = [nil] * @fiber_shape.size
|
|
419
|
+
bin_idx = (0...@m).map { |k|
|
|
420
|
+
if k == axis
|
|
421
|
+
offset == 0 ? 0 : @n_list[k] + 1
|
|
422
|
+
else
|
|
423
|
+
nil
|
|
424
|
+
end
|
|
425
|
+
}
|
|
426
|
+
slice = @full_counts[*(base + bin_idx)]
|
|
427
|
+
remaining = @m - 1
|
|
428
|
+
remaining.times { slice = slice.accumulate(axis: slice.ndim - 1) }
|
|
429
|
+
slice
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class CArray
|
|
438
|
+
|
|
439
|
+
# @overload histogram1d(edges:, axis: -1, include_max: false, weights: nil)
|
|
440
|
+
# Returns a 1-D {Histogram} built from `self` with shape
|
|
441
|
+
# `fiber_shape + (A,)`, where `A` is the sample axis of length
|
|
442
|
+
# picked by `axis`.
|
|
443
|
+
# @param edges [CArray, Array<Numeric>] 1-D ascending bin edges.
|
|
444
|
+
# @param axis [Integer] sample axis.
|
|
445
|
+
# @param include_max [Boolean] fold last-edge equality into the
|
|
446
|
+
# last bin.
|
|
447
|
+
# @param weights [CArray, nil] optional per-sample weights.
|
|
448
|
+
# @return [Histogram] 1-D accumulator (`m == 1`).
|
|
449
|
+
def histogram1d (edges:, axis: -1, include_max: false, weights: nil)
|
|
450
|
+
ax = normalize_axis(axis, "histogram1d")
|
|
451
|
+
|
|
452
|
+
new_shape = shape + [1]
|
|
453
|
+
arr_with_channel = reshape(*new_shape)
|
|
454
|
+
# `include_max` passes straight through: the Histogram constructor
|
|
455
|
+
# normalizes a scalar bool to per-dim, and raises on a wrong-length Array
|
|
456
|
+
# (= same path as histogram2d, no M=1 special-casing here).
|
|
457
|
+
arr_with_channel.histogram(edges: [edges],
|
|
458
|
+
axis: [ax, new_shape.size - 1],
|
|
459
|
+
include_max: include_max,
|
|
460
|
+
weights: weights)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# @overload histogram2d(edges:, axis: [-2, -1], include_max: false, weights: nil)
|
|
464
|
+
# Returns a 2-D joint {Histogram} built from `self` with shape
|
|
465
|
+
# `fiber_shape + (A, 2)`.
|
|
466
|
+
# @param edges [Array<CArray, Array<Numeric>>] two edges arrays.
|
|
467
|
+
# @param axis [Array(Integer, Integer)] `[sample, channel]`
|
|
468
|
+
# axis pair.
|
|
469
|
+
# @param include_max [Boolean, Array<Boolean>] fold-max flag,
|
|
470
|
+
# per dimension.
|
|
471
|
+
# @param weights [CArray, nil] optional per-sample weights.
|
|
472
|
+
# @return [Histogram] 2-D accumulator (`m == 2`).
|
|
473
|
+
# @raise [ArgumentError] when `edges` is not a length-2 Array.
|
|
474
|
+
def histogram2d (edges:, axis: [-2, -1], include_max: false, weights: nil)
|
|
475
|
+
raise ArgumentError, "edges must be a list of 2" unless edges.is_a?(Array) && edges.size == 2
|
|
476
|
+
histogram(edges: edges, axis: axis, include_max: include_max, weights: weights)
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# @overload histogram(edges:, axis: [-2, -1], include_max: false, weights: nil)
|
|
480
|
+
# Returns an M-D joint {Histogram} built from `self` with shape
|
|
481
|
+
# `fiber_shape + (A, M)`, where `M == edges.size`.
|
|
482
|
+
# @param edges [Array<CArray, Array<Numeric>>] one edges array
|
|
483
|
+
# per dimension.
|
|
484
|
+
# @param axis [Array(Integer, Integer)] `[sample, channel]`
|
|
485
|
+
# axis pair.
|
|
486
|
+
# @param include_max [Boolean, Array<Boolean>] fold-max flag,
|
|
487
|
+
# per dimension.
|
|
488
|
+
# @param weights [CArray, nil] optional per-sample weights.
|
|
489
|
+
# @return [Histogram]
|
|
490
|
+
# @raise [ArgumentError] when `edges` is not an Array.
|
|
491
|
+
def histogram (edges:, axis: [-2, -1], include_max: false, weights: nil)
|
|
492
|
+
raise ArgumentError, "edges must be an Array of edges arrays" unless edges.is_a?(Array)
|
|
493
|
+
arr = self
|
|
494
|
+
sample_ax = normalize_axis(axis[0], "histogram sample axis")
|
|
495
|
+
channel_ax = normalize_axis(axis[1], "histogram channel axis")
|
|
496
|
+
fiber_shape = arr.shape.dup
|
|
497
|
+
[sample_ax, channel_ax].sort.reverse.each { |p| fiber_shape.delete_at(p) }
|
|
498
|
+
|
|
499
|
+
# Weighted counts are float64-only (the fused scatter kernel requires
|
|
500
|
+
# float64 weights and float64 counts), so the dtype is fixed here rather
|
|
501
|
+
# than derived from the weights' own dtype.
|
|
502
|
+
weights_dtype = (:float64 if weights)
|
|
503
|
+
|
|
504
|
+
h = Histogram.send(:new,
|
|
505
|
+
edges: edges,
|
|
506
|
+
fiber_shape: fiber_shape,
|
|
507
|
+
include_max: include_max,
|
|
508
|
+
weights_dtype: weights_dtype)
|
|
509
|
+
h.add(arr, axis: axis, weights: weights)
|
|
510
|
+
h
|
|
511
|
+
end
|
|
512
|
+
end
|
data/lib/carray/inspect.rb
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
# carray/inspect.rb
|
|
4
|
-
#
|
|
5
|
-
# This file is part of Ruby/CArray extension library.
|
|
6
|
-
#
|
|
7
|
-
# Copyright (C) 2005-2020 Hiroki Motoyoshi
|
|
8
|
-
#
|
|
9
|
-
# ----------------------------------------------------------------------------
|
|
1
|
+
require "pp" # CArray#source_code uses Array#pretty_inspect
|
|
10
2
|
|
|
11
3
|
class CArray::Inspector # :nodoc:
|
|
12
4
|
|
|
@@ -14,6 +6,7 @@ class CArray::Inspector # :nodoc:
|
|
|
14
6
|
@carray = carray
|
|
15
7
|
end
|
|
16
8
|
|
|
9
|
+
# @!visibility private
|
|
17
10
|
def inspect_string
|
|
18
11
|
if @carray.ndim == 0
|
|
19
12
|
raise "can't inspect CArray of ndim == 0"
|
|
@@ -46,7 +39,9 @@ class CArray::Inspector # :nodoc:
|
|
|
46
39
|
@carray.instance_exec {
|
|
47
40
|
case data_type
|
|
48
41
|
when CA_FIXLEN
|
|
49
|
-
|
|
42
|
+
# Kernel.format explicitly: inside instance_exec self is the CArray,
|
|
43
|
+
# where a bare format() would resolve to the public CArray#format.
|
|
44
|
+
return Kernel.format("%s[%i]", type_name, bytes)
|
|
50
45
|
else
|
|
51
46
|
return type_name
|
|
52
47
|
end
|
|
@@ -58,7 +53,7 @@ class CArray::Inspector # :nodoc:
|
|
|
58
53
|
when CA_OBJ_UNBOUND_REPEAT
|
|
59
54
|
dim = @carray.spec
|
|
60
55
|
else
|
|
61
|
-
dim = @carray.
|
|
56
|
+
dim = @carray.shape
|
|
62
57
|
end
|
|
63
58
|
return dim
|
|
64
59
|
end
|
|
@@ -108,22 +103,34 @@ class CArray::Inspector # :nodoc:
|
|
|
108
103
|
list << "attached"
|
|
109
104
|
end
|
|
110
105
|
# ---
|
|
111
|
-
if
|
|
112
|
-
list << "attrs={" +
|
|
106
|
+
if has_attr?
|
|
107
|
+
list << "attrs={" + attrs.keys.join(",") + "}"
|
|
113
108
|
end
|
|
114
109
|
}
|
|
115
110
|
return list
|
|
116
111
|
end
|
|
117
112
|
|
|
118
113
|
def get_formatter
|
|
114
|
+
# A Face that defines storage_to_scalar decodes each cell into a surface
|
|
115
|
+
# value (CATime::Element, a String, a category label, ...) that has nothing
|
|
116
|
+
# to do with the storage data_type, so the formatter must follow the decoded
|
|
117
|
+
# value, not the storage. Faces without the hook (CAString) hand back the
|
|
118
|
+
# stored value itself and fall through to the storage formatters below.
|
|
119
|
+
if @carray.face? and @carray.respond_to?(:storage_to_scalar)
|
|
120
|
+
return lambda { |x| x.inspect }
|
|
121
|
+
end
|
|
119
122
|
case @carray.data_type
|
|
120
|
-
when CA_BOOLEAN
|
|
123
|
+
when CA_BOOLEAN
|
|
124
|
+
# Boolean cells fetch as true/false; show compact 1/0 (masked = _).
|
|
125
|
+
# The type name in the inspect header distinguishes this from an int array.
|
|
126
|
+
lambda{|x| x ? "1" : "0" }
|
|
127
|
+
when CA_INT8, CA_INT16, CA_INT32, CA_INT64
|
|
121
128
|
lambda{|x| "%i" % x }
|
|
122
129
|
when CA_UINT8, CA_UINT16, CA_UINT32, CA_UINT64
|
|
123
130
|
lambda{|x| "%u" % x }
|
|
124
|
-
when CA_FLOAT32, CA_FLOAT64
|
|
131
|
+
when CA_FLOAT32, CA_FLOAT64
|
|
125
132
|
lambda{|x| x.inspect }
|
|
126
|
-
when CA_CMPLX64, CA_CMPLX128
|
|
133
|
+
when CA_CMPLX64, CA_CMPLX128
|
|
127
134
|
lambda{|x| format("%s%s%si",
|
|
128
135
|
x.real.inspect, (x.imag >= 0) ? "+" : "-", x.imag.abs.inspect) }
|
|
129
136
|
when CA_FIXLEN
|
|
@@ -146,9 +153,9 @@ class CArray::Inspector # :nodoc:
|
|
|
146
153
|
end
|
|
147
154
|
|
|
148
155
|
def get_data_spec (level, idx, formatter)
|
|
149
|
-
io = "[ "
|
|
156
|
+
io = +"[ " # mutable buffer; `<<` below appends into it
|
|
150
157
|
ndim = @carray.ndim
|
|
151
|
-
dim = @carray.
|
|
158
|
+
dim = @carray.shape
|
|
152
159
|
if level == ndim - 1
|
|
153
160
|
over = false
|
|
154
161
|
dim[level].times do |i|
|
|
@@ -209,6 +216,11 @@ end
|
|
|
209
216
|
|
|
210
217
|
class CArray
|
|
211
218
|
|
|
219
|
+
# @overload inspect
|
|
220
|
+
# Returns a human-readable description of `self` including
|
|
221
|
+
# class, `data_type`, shape, element and memory summaries, mask
|
|
222
|
+
# count, and a truncated data preview.
|
|
223
|
+
# @return [String]
|
|
212
224
|
def inspect
|
|
213
225
|
return CArray::Inspector.new(self).inspect_string
|
|
214
226
|
end
|
|
@@ -216,7 +228,7 @@ class CArray
|
|
|
216
228
|
private
|
|
217
229
|
|
|
218
230
|
def desc
|
|
219
|
-
output = ""
|
|
231
|
+
output = +"" # mutable buffer; `<<` below appends into it
|
|
220
232
|
case data_type
|
|
221
233
|
when CA_FIXLEN
|
|
222
234
|
output << sprintf("CArray.%s(%s, :bytes=>%i)",
|
|
@@ -230,7 +242,12 @@ class CArray
|
|
|
230
242
|
|
|
231
243
|
public
|
|
232
244
|
|
|
233
|
-
|
|
245
|
+
# @overload source_code
|
|
246
|
+
# Returns a Ruby source-like string that would reconstruct
|
|
247
|
+
# `self`, combining the type/shape descriptor with a pretty
|
|
248
|
+
# printed value block. Useful for embedding fixtures in scripts.
|
|
249
|
+
# @return [String]
|
|
250
|
+
def source_code
|
|
234
251
|
text = [
|
|
235
252
|
desc,
|
|
236
253
|
" { ",
|