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,2542 @@
|
|
|
1
|
+
/* ---------------------------------------------------------------------------
|
|
2
|
+
|
|
3
|
+
carray_factorize.c — single-pass value seen-set kernels.
|
|
4
|
+
|
|
5
|
+
The value-hash discovery family shares one open-addressing hash that maps a
|
|
6
|
+
widened 64-bit key to first-appearance state in a single linear pass, read
|
|
7
|
+
through the kernel_iterator fiber surface (no entry ca_attach). Each kernel
|
|
8
|
+
reads a different answer out of the same intern pass:
|
|
9
|
+
|
|
10
|
+
- __factorize_appearance__ (categorize): map each value to a dense code in
|
|
11
|
+
first-appearance order, emitting both the code storage and the level
|
|
12
|
+
vocabulary in one pass. Replaces categorize's discovery path, which reads
|
|
13
|
+
the levels then assigns codes with one full eq scan per distinct value
|
|
14
|
+
(O(distinct * N)).
|
|
15
|
+
|
|
16
|
+
- __mask_duplicates__ (mask_duplicates): mark every cell whose value
|
|
17
|
+
duplicates an earlier-seen one. The hash's first-seen flag IS the answer
|
|
18
|
+
(dup = not first-seen), so the sort-based mask_dup path collapses to the
|
|
19
|
+
same one pass.
|
|
20
|
+
|
|
21
|
+
- __unique_flat__ (unique): emit the distinct values in appearance order
|
|
22
|
+
(the levels alone, codes discarded).
|
|
23
|
+
|
|
24
|
+
- __value_counts_flat__ (value_counts): the levels plus a per-code count
|
|
25
|
+
(a count lane beside the level buffer).
|
|
26
|
+
|
|
27
|
+
- __nunique__ (nunique): the distinct count per fiber (the intern count is
|
|
28
|
+
the answer; the reduction accumulator is a no-op).
|
|
29
|
+
|
|
30
|
+
- __is_mode__ (is_mode): mark every cell holding a modal value (count equal
|
|
31
|
+
to the fiber max) with a per-fiber two-pass frequency table; ties are all
|
|
32
|
+
marked, never broken.
|
|
33
|
+
|
|
34
|
+
- __mode_axis__ (mode per-axis): read the distinct modal values out of the
|
|
35
|
+
per-fiber frequency table, ascending, as a ragged Array of reduced
|
|
36
|
+
CArrays (the value-form consumer of the is_mode primitive).
|
|
37
|
+
|
|
38
|
+
Peak memory is O(distinct values) for the hash, plus each kernel's own output.
|
|
39
|
+
No sort, no gathered copy.
|
|
40
|
+
|
|
41
|
+
The hash carries three key lanes. Numeric (integer / float) widens the
|
|
42
|
+
element to a lossless 64-bit key; boolean rides the uint8 numeric lane (its
|
|
43
|
+
storage is uint8 0/1, so at most two distinct keys ever intern). Float
|
|
44
|
+
reproduces `==` with two value-based
|
|
45
|
+
exceptions: all NaN collapse to one distinct value (so the second and later
|
|
46
|
+
NaN are duplicates) and +0.0 / -0.0 compare equal (so -0.0 is normalized to
|
|
47
|
+
+0.0 before the bitwise key). Object keys on rb_hash with an rb_eql re-check,
|
|
48
|
+
and fixlen on a byte-hash with a memcmp re-check; both reproduce Ruby Hash
|
|
49
|
+
distinctness (`hash` + `eql?`) -- which already folds -0.0 / +0.0 together
|
|
50
|
+
(Float#eql? treats them equal). The object lane adds one deviation from Ruby
|
|
51
|
+
Hash, aligning it with the numeric lane: every Float NaN collapses to one
|
|
52
|
+
distinct value (a NaN takes a fixed canonical key and matches any stored Float
|
|
53
|
+
NaN), where Ruby Hash would keep distinct NaN objects apart.
|
|
54
|
+
__factorize_appearance__ and the discovery members (unique, value_counts,
|
|
55
|
+
mask_duplicates) all cover the numeric / object / fixlen lanes.
|
|
56
|
+
|
|
57
|
+
Private surfaces:
|
|
58
|
+
self.__factorize_appearance__ -> [codes, levels]
|
|
59
|
+
codes : narrow unsigned CArray (uint8 / uint16 / uint32), the code
|
|
60
|
+
storage; masked source cells store the type-max sentinel
|
|
61
|
+
(from_codes derives the mask from it, matching categorize).
|
|
62
|
+
levels : integer CArray (source dtype) of the k distinct values in
|
|
63
|
+
first-appearance order.
|
|
64
|
+
self.__mask_duplicates__(axis) -> boolean CArray of self.shape, true at
|
|
65
|
+
each cell that duplicates an earlier-seen one along axis (per-fiber
|
|
66
|
+
independent seen-set). Masked cells do not participate and stay false.
|
|
67
|
+
self.__unique_flat__ -> 1-D CArray of the distinct values (appearance order).
|
|
68
|
+
self.__value_counts_flat__ -> [levels, counts] (counts is 1-D CA_INT64).
|
|
69
|
+
self.__nunique__(axis, keep_axis) -> reduced CA_INT64 distinct-count CArray.
|
|
70
|
+
self.__is_mode__(axis) -> boolean CArray of self.shape, true at every modal
|
|
71
|
+
cell (per-fiber max count) along axis; ties all marked.
|
|
72
|
+
self.__mode_axis__(axis) -> Array of K reduced CArrays (self.shape with axis
|
|
73
|
+
dropped), slot j = each fiber's j-th smallest modal value ascending, UNDEF
|
|
74
|
+
where a fiber has fewer than j+1 modes; K = widest fiber's modal count.
|
|
75
|
+
|
|
76
|
+
--------------------------------------------------------------------------- */
|
|
77
|
+
|
|
78
|
+
#include "carray.h"
|
|
79
|
+
#include "ca_obj_face.h"
|
|
80
|
+
#include <string.h>
|
|
81
|
+
#include <math.h>
|
|
82
|
+
|
|
83
|
+
/* ---- Face gate (same three moves as the search / sort / count families) ----
|
|
84
|
+
|
|
85
|
+
The value hash keys on raw cells, so a Face has to be brought into its
|
|
86
|
+
storage space first and put back afterwards:
|
|
87
|
+
|
|
88
|
+
- descend an ORDERABLE Face to its storage. ORDERABLE claims that
|
|
89
|
+
storage order == surface order, which for equality is the
|
|
90
|
+
part that matters: equal storage <=> equal surface.
|
|
91
|
+
- reconcile an operand through the reference Face (to_comparable), so a
|
|
92
|
+
set / membership question compares instants rather than the
|
|
93
|
+
ticks of whichever unit each side happens to carry.
|
|
94
|
+
- re-lift an output that carries *values* (unique / value_counts'
|
|
95
|
+
values / mode / the set operations), so the caller gets its
|
|
96
|
+
own type back. Counts, booleans and indices stay plain.
|
|
97
|
+
|
|
98
|
+
A Face without ORDERABLE is left exactly as it was: its equality is not its
|
|
99
|
+
storage's, so descending would answer the wrong question. CAConstString is
|
|
100
|
+
the live example -- a cell is a (start, end) byte range, so two equal strings
|
|
101
|
+
at different offsets have different storage -- and it needs its own
|
|
102
|
+
discovery surface rather than this gate (see
|
|
103
|
+
devel/PROPOSAL_DISCOVERY_FAMILY_FACE_GATE.md §4). */
|
|
104
|
+
|
|
105
|
+
/* Descend `self` when it is an ORDERABLE Face; *pface keeps the pre-strip Face
|
|
106
|
+
for the re-lift (Qnil when there is nothing to put back). */
|
|
107
|
+
static VALUE
|
|
108
|
+
fz_face_descend (VALUE self, volatile VALUE *pface)
|
|
109
|
+
{
|
|
110
|
+
CArray *ca;
|
|
111
|
+
GetCArray(self, ca);
|
|
112
|
+
if ( ca_is_face(ca) && ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {
|
|
113
|
+
*pface = self;
|
|
114
|
+
return rb_ca_strip_face_value(self);
|
|
115
|
+
}
|
|
116
|
+
*pface = Qnil;
|
|
117
|
+
return self;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Bring `operand` into the space of `reference` (the *pre-strip* Face) and
|
|
121
|
+
descend it, so both sides hash in one storage space.
|
|
122
|
+
|
|
123
|
+
`to_comparable` exists because a Face can carry a *unit* -- an alternative
|
|
124
|
+
space for the same value -- so a reference that defines it reconciles any
|
|
125
|
+
operand type through it (and refuses what it cannot convert, which is how a
|
|
126
|
+
bare storage number stays out). A reference with one space only (COMPARABLE,
|
|
127
|
+
or a Face with no unit algebra at all, e.g. CAString) has nothing to
|
|
128
|
+
reconcile between: descending both sides is the whole job. */
|
|
129
|
+
static VALUE
|
|
130
|
+
fz_face_reconcile (VALUE reference, VALUE operand, const char *name)
|
|
131
|
+
{
|
|
132
|
+
CArray *ca;
|
|
133
|
+
if ( NIL_P(reference) ) {
|
|
134
|
+
return operand;
|
|
135
|
+
}
|
|
136
|
+
GetCArray(reference, ca);
|
|
137
|
+
if ( ! ca_test_flag(ca, CA_FLAG_FACE_COMPARABLE_STORAGE)
|
|
138
|
+
&& rb_respond_to(reference, rb_intern("to_comparable")) ) {
|
|
139
|
+
return rb_ca_strip_face_value(rb_funcall(reference,
|
|
140
|
+
rb_intern("to_comparable"),
|
|
141
|
+
1, operand));
|
|
142
|
+
}
|
|
143
|
+
if ( rb_obj_is_carray(operand) ) {
|
|
144
|
+
CArray *op;
|
|
145
|
+
GetCArray(operand, op);
|
|
146
|
+
if ( ca_is_face(op) ) {
|
|
147
|
+
return rb_ca_strip_face_value(operand);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
(void) name;
|
|
151
|
+
return operand;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Put the Face back on a value-carrying output. */
|
|
155
|
+
static VALUE
|
|
156
|
+
fz_face_relift (VALUE out, VALUE face)
|
|
157
|
+
{
|
|
158
|
+
if ( NIL_P(face) ) {
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
return ca_face_lift(out, face);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* ---- open-addressing hash: widened 64-bit key -> first-appearance code ---- */
|
|
165
|
+
|
|
166
|
+
typedef struct {
|
|
167
|
+
uint64_t *key;
|
|
168
|
+
int32_t *code;
|
|
169
|
+
uint8_t *used;
|
|
170
|
+
VALUE *val; /* object lane only: the interned VALUE, kept for an eql? re-check
|
|
171
|
+
on a hash collision (NULL in the numeric lane, where the widened
|
|
172
|
+
key is lossless so a key match already proves value equality).
|
|
173
|
+
Its VALUEs are always elements of the live receiver, so the GC
|
|
174
|
+
reaches them through `self`; this array is not separately marked. */
|
|
175
|
+
char *raw; /* fixlen lane only: cap*esz bytes, the interned element bytes kept
|
|
176
|
+
for a memcmp re-check on a hash collision (NULL otherwise). */
|
|
177
|
+
int esz; /* fixlen lane: element width in bytes (0 otherwise) */
|
|
178
|
+
ca_size_t cap; /* power of two */
|
|
179
|
+
ca_size_t n; /* distinct keys interned so far */
|
|
180
|
+
int shift; /* 64 - log2(cap); the home slot is the top log2(cap) bits */
|
|
181
|
+
} fz_hash;
|
|
182
|
+
|
|
183
|
+
/* Multiplicative hashing (Knuth, TAOCP vol. 3 sec. 6.4): multiply by
|
|
184
|
+
2^64 / golden-ratio and take the TOP log2(cap) bits of the product -- the
|
|
185
|
+
high bits are where a multiply mixes best (the low bits carry little
|
|
186
|
+
entropy, which sparse keys such as small-integer float bit patterns expose).
|
|
187
|
+
The constant is a plain mathematical value = floor(2^64 / phi),
|
|
188
|
+
phi = (1 + sqrt 5) / 2. */
|
|
189
|
+
#define FZ_GOLDEN 0x9E3779B97F4A7C15ULL
|
|
190
|
+
#define FZ_HOME(h, key) ((ca_size_t) (((uint64_t)(key) * FZ_GOLDEN) >> (h)->shift))
|
|
191
|
+
|
|
192
|
+
static void
|
|
193
|
+
fz_hash_init (fz_hash *h)
|
|
194
|
+
{
|
|
195
|
+
h->cap = 1024;
|
|
196
|
+
h->shift = 64 - 10; /* log2(1024) = 10 */
|
|
197
|
+
h->n = 0;
|
|
198
|
+
h->key = ALLOC_N(uint64_t, h->cap);
|
|
199
|
+
h->code = ALLOC_N(int32_t, h->cap);
|
|
200
|
+
h->used = ALLOC_N(uint8_t, h->cap);
|
|
201
|
+
h->val = NULL; /* numeric lane: no VALUE re-check needed */
|
|
202
|
+
h->raw = NULL;
|
|
203
|
+
h->esz = 0;
|
|
204
|
+
MEMZERO(h->used, uint8_t, h->cap);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* Object lane: the widened key is `rb_hash` (lossy), so a slot also stores the
|
|
208
|
+
interned VALUE for an `eql?` re-check on collision. Equality then matches
|
|
209
|
+
Ruby Hash exactly (`hash` + `eql?`), reproducing the object seen-set path. */
|
|
210
|
+
static void
|
|
211
|
+
fz_hash_init_obj (fz_hash *h)
|
|
212
|
+
{
|
|
213
|
+
fz_hash_init(h);
|
|
214
|
+
h->val = ALLOC_N(VALUE, h->cap);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Fixlen lane: the key is a byte-hash of the esz-wide element (lossy), so a slot
|
|
218
|
+
stores the element bytes for a `memcmp` re-check on collision. For fixlen
|
|
219
|
+
cells (uniform width, binary encoding) byte equality matches Ruby String
|
|
220
|
+
`eql?`, reproducing the Ruby Hash seen-set the fixlen path used. */
|
|
221
|
+
static void
|
|
222
|
+
fz_hash_init_mem (fz_hash *h, int esz)
|
|
223
|
+
{
|
|
224
|
+
fz_hash_init(h);
|
|
225
|
+
h->esz = esz;
|
|
226
|
+
h->raw = ALLOC_N(char, h->cap * esz);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
static void
|
|
230
|
+
fz_hash_free (fz_hash *h)
|
|
231
|
+
{
|
|
232
|
+
if ( h->key ) { xfree(h->key); h->key = NULL; }
|
|
233
|
+
if ( h->code ) { xfree(h->code); h->code = NULL; }
|
|
234
|
+
if ( h->used ) { xfree(h->used); h->used = NULL; }
|
|
235
|
+
if ( h->val ) { xfree(h->val); h->val = NULL; }
|
|
236
|
+
if ( h->raw ) { xfree(h->raw); h->raw = NULL; }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* FNV-1a over esz bytes: a lossy 64-bit key for the fixlen lane. */
|
|
240
|
+
static uint64_t
|
|
241
|
+
fz_bytehash (const char *b, int esz)
|
|
242
|
+
{
|
|
243
|
+
uint64_t x = 14695981039346656037ULL;
|
|
244
|
+
for ( int i = 0; i < esz; i++ ) {
|
|
245
|
+
x = (x ^ (unsigned char) b[i]) * 1099511628211ULL;
|
|
246
|
+
}
|
|
247
|
+
return x;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* Clear all interned keys while keeping the allocated capacity, so a per-fiber
|
|
251
|
+
seen-set can be reused across fibers without reallocating each time. */
|
|
252
|
+
static void
|
|
253
|
+
fz_hash_reset (fz_hash *h)
|
|
254
|
+
{
|
|
255
|
+
h->n = 0;
|
|
256
|
+
MEMZERO(h->used, uint8_t, h->cap);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
static void
|
|
260
|
+
fz_hash_grow (fz_hash *h)
|
|
261
|
+
{
|
|
262
|
+
ca_size_t oldcap = h->cap, newcap = oldcap << 1;
|
|
263
|
+
uint64_t *ok = h->key;
|
|
264
|
+
int32_t *oc = h->code;
|
|
265
|
+
uint8_t *ou = h->used;
|
|
266
|
+
VALUE *ov = h->val;
|
|
267
|
+
char *orw = h->raw;
|
|
268
|
+
int esz = h->esz;
|
|
269
|
+
h->cap = newcap;
|
|
270
|
+
h->shift -= 1; /* log2(cap) grew by one */
|
|
271
|
+
h->key = ALLOC_N(uint64_t, newcap);
|
|
272
|
+
h->code = ALLOC_N(int32_t, newcap);
|
|
273
|
+
h->used = ALLOC_N(uint8_t, newcap);
|
|
274
|
+
if ( ov ) { h->val = ALLOC_N(VALUE, newcap); }
|
|
275
|
+
if ( orw ) { h->raw = ALLOC_N(char, newcap * esz); }
|
|
276
|
+
MEMZERO(h->used, uint8_t, newcap);
|
|
277
|
+
ca_size_t mask = newcap - 1;
|
|
278
|
+
for ( ca_size_t s = 0; s < oldcap; s++ ) {
|
|
279
|
+
if ( ! ou[s] ) { continue; }
|
|
280
|
+
ca_size_t slot = FZ_HOME(h, ok[s]);
|
|
281
|
+
while ( h->used[slot] ) { slot = (slot + 1) & mask; }
|
|
282
|
+
h->used[slot] = 1;
|
|
283
|
+
h->key[slot] = ok[s];
|
|
284
|
+
h->code[slot] = oc[s];
|
|
285
|
+
if ( ov ) { h->val[slot] = ov[s]; }
|
|
286
|
+
if ( orw ) { memcpy(h->raw + slot * esz, orw + s * esz, esz); }
|
|
287
|
+
}
|
|
288
|
+
xfree(ok); xfree(oc); xfree(ou);
|
|
289
|
+
if ( ov ) { xfree(ov); }
|
|
290
|
+
if ( orw ) { xfree(orw); }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* True when v is a Float holding NaN (any bit pattern). */
|
|
294
|
+
static inline int
|
|
295
|
+
fz_is_float_nan (VALUE v)
|
|
296
|
+
{
|
|
297
|
+
return RB_FLOAT_TYPE_P(v) && isnan(RFLOAT_VALUE(v));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* Object-lane intern: key = rb_hash(v) (lossy), collision re-check via rb_eql.
|
|
301
|
+
Matches Ruby Hash distinctness (`hash` + `eql?`) element for element, with one
|
|
302
|
+
value-based exception aligning with the numeric lane: every Float NaN collapses
|
|
303
|
+
to a single distinct value (Ruby Hash keeps distinct NaN objects apart via
|
|
304
|
+
eql?, but the discovery family unifies them). A NaN takes a fixed canonical
|
|
305
|
+
key and matches any stored Float NaN, so its second and later occurrences fold
|
|
306
|
+
into the first. */
|
|
307
|
+
static int32_t
|
|
308
|
+
fz_hash_intern_obj (fz_hash *h, VALUE v, int *is_new)
|
|
309
|
+
{
|
|
310
|
+
if ( (h->n + 1) * 10 >= h->cap * 7 ) { fz_hash_grow(h); } /* load factor 0.7 */
|
|
311
|
+
int v_nan = fz_is_float_nan(v);
|
|
312
|
+
uint64_t key = v_nan ? 0x7ff8000000000000ULL
|
|
313
|
+
: (uint64_t) NUM2LL(rb_hash(v));
|
|
314
|
+
ca_size_t mask = h->cap - 1;
|
|
315
|
+
ca_size_t slot = FZ_HOME(h, key);
|
|
316
|
+
while ( h->used[slot] ) {
|
|
317
|
+
if ( h->key[slot] == key &&
|
|
318
|
+
( v_nan ? fz_is_float_nan(h->val[slot]) : rb_eql(h->val[slot], v) ) ) {
|
|
319
|
+
*is_new = 0; return h->code[slot];
|
|
320
|
+
}
|
|
321
|
+
slot = (slot + 1) & mask;
|
|
322
|
+
}
|
|
323
|
+
int32_t code = (int32_t) h->n;
|
|
324
|
+
h->used[slot] = 1;
|
|
325
|
+
h->key[slot] = key;
|
|
326
|
+
h->val[slot] = v;
|
|
327
|
+
h->code[slot] = code;
|
|
328
|
+
h->n++;
|
|
329
|
+
*is_new = 1;
|
|
330
|
+
return code;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* Fixlen-lane intern: key = byte-hash of the esz-wide element (lossy), collision
|
|
334
|
+
re-check via memcmp. Matches Ruby String eql? for uniform-width binary cells. */
|
|
335
|
+
static int32_t
|
|
336
|
+
fz_hash_intern_mem (fz_hash *h, const char *b, int *is_new)
|
|
337
|
+
{
|
|
338
|
+
if ( (h->n + 1) * 10 >= h->cap * 7 ) { fz_hash_grow(h); } /* load factor 0.7 */
|
|
339
|
+
int esz = h->esz;
|
|
340
|
+
uint64_t key = fz_bytehash(b, esz);
|
|
341
|
+
ca_size_t mask = h->cap - 1;
|
|
342
|
+
ca_size_t slot = FZ_HOME(h, key);
|
|
343
|
+
while ( h->used[slot] ) {
|
|
344
|
+
if ( h->key[slot] == key && memcmp(h->raw + slot * esz, b, esz) == 0 ) {
|
|
345
|
+
*is_new = 0; return h->code[slot];
|
|
346
|
+
}
|
|
347
|
+
slot = (slot + 1) & mask;
|
|
348
|
+
}
|
|
349
|
+
int32_t code = (int32_t) h->n;
|
|
350
|
+
h->used[slot] = 1;
|
|
351
|
+
h->key[slot] = key;
|
|
352
|
+
memcpy(h->raw + slot * esz, b, esz);
|
|
353
|
+
h->code[slot] = code;
|
|
354
|
+
h->n++;
|
|
355
|
+
*is_new = 1;
|
|
356
|
+
return code;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* Intern `key`; return its code, set *is_new when first seen. */
|
|
360
|
+
static int32_t
|
|
361
|
+
fz_hash_intern (fz_hash *h, uint64_t key, int *is_new)
|
|
362
|
+
{
|
|
363
|
+
if ( (h->n + 1) * 10 >= h->cap * 7 ) { fz_hash_grow(h); } /* load factor 0.7 */
|
|
364
|
+
ca_size_t mask = h->cap - 1;
|
|
365
|
+
ca_size_t slot = FZ_HOME(h, key);
|
|
366
|
+
while ( h->used[slot] ) {
|
|
367
|
+
if ( h->key[slot] == key ) { *is_new = 0; return h->code[slot]; }
|
|
368
|
+
slot = (slot + 1) & mask;
|
|
369
|
+
}
|
|
370
|
+
int32_t code = (int32_t) h->n;
|
|
371
|
+
h->used[slot] = 1;
|
|
372
|
+
h->key[slot] = key;
|
|
373
|
+
h->code[slot] = code;
|
|
374
|
+
h->n++;
|
|
375
|
+
*is_new = 1;
|
|
376
|
+
return code;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* ---- lookup-only probes: does the value exist in the seen-set? ------------
|
|
380
|
+
Each mirrors its intern sibling's search loop but never inserts, so a set
|
|
381
|
+
built once (by interning one array) can be probed by the other array's
|
|
382
|
+
elements. Return 1 when present, 0 when absent. The three lanes reuse the
|
|
383
|
+
same key derivation and collision re-check as intern, so membership matches
|
|
384
|
+
the discovery family's distinctness exactly.
|
|
385
|
+
|
|
386
|
+
code_out (nullable): when non-NULL and the value is present, the interned
|
|
387
|
+
dense code (appearance-order index intern wrote at that slot) is returned
|
|
388
|
+
through it. is_in passes NULL (membership only); the 3.1 locate / set-op
|
|
389
|
+
members read the code to map a hit to a flat address / distinct push. */
|
|
390
|
+
|
|
391
|
+
static int
|
|
392
|
+
fz_hash_lookup (fz_hash *h, uint64_t key, int32_t *code_out)
|
|
393
|
+
{
|
|
394
|
+
ca_size_t mask = h->cap - 1;
|
|
395
|
+
ca_size_t slot = FZ_HOME(h, key);
|
|
396
|
+
while ( h->used[slot] ) {
|
|
397
|
+
if ( h->key[slot] == key ) {
|
|
398
|
+
if ( code_out ) { *code_out = h->code[slot]; }
|
|
399
|
+
return 1;
|
|
400
|
+
}
|
|
401
|
+
slot = (slot + 1) & mask;
|
|
402
|
+
}
|
|
403
|
+
return 0;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
static int
|
|
407
|
+
fz_hash_lookup_obj (fz_hash *h, VALUE v, int32_t *code_out)
|
|
408
|
+
{
|
|
409
|
+
int v_nan = fz_is_float_nan(v);
|
|
410
|
+
uint64_t key = v_nan ? 0x7ff8000000000000ULL
|
|
411
|
+
: (uint64_t) NUM2LL(rb_hash(v));
|
|
412
|
+
ca_size_t mask = h->cap - 1;
|
|
413
|
+
ca_size_t slot = FZ_HOME(h, key);
|
|
414
|
+
while ( h->used[slot] ) {
|
|
415
|
+
if ( h->key[slot] == key &&
|
|
416
|
+
( v_nan ? fz_is_float_nan(h->val[slot]) : rb_eql(h->val[slot], v) ) ) {
|
|
417
|
+
if ( code_out ) { *code_out = h->code[slot]; }
|
|
418
|
+
return 1;
|
|
419
|
+
}
|
|
420
|
+
slot = (slot + 1) & mask;
|
|
421
|
+
}
|
|
422
|
+
return 0;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
static int
|
|
426
|
+
fz_hash_lookup_mem (fz_hash *h, const char *b, int32_t *code_out)
|
|
427
|
+
{
|
|
428
|
+
int esz = h->esz;
|
|
429
|
+
uint64_t key = fz_bytehash(b, esz);
|
|
430
|
+
ca_size_t mask = h->cap - 1;
|
|
431
|
+
ca_size_t slot = FZ_HOME(h, key);
|
|
432
|
+
while ( h->used[slot] ) {
|
|
433
|
+
if ( h->key[slot] == key && memcmp(h->raw + slot * esz, b, esz) == 0 ) {
|
|
434
|
+
if ( code_out ) { *code_out = h->code[slot]; }
|
|
435
|
+
return 1;
|
|
436
|
+
}
|
|
437
|
+
slot = (slot + 1) & mask;
|
|
438
|
+
}
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/* ---- growable raw-element buffer: the levels in appearance order ---------- */
|
|
443
|
+
|
|
444
|
+
typedef struct {
|
|
445
|
+
char *p;
|
|
446
|
+
ca_size_t cap; /* capacity in elements */
|
|
447
|
+
ca_size_t n; /* elements stored */
|
|
448
|
+
int esz; /* element bytes */
|
|
449
|
+
} fz_levels;
|
|
450
|
+
|
|
451
|
+
static void
|
|
452
|
+
fz_levels_init (fz_levels *l, int esz)
|
|
453
|
+
{
|
|
454
|
+
l->esz = esz;
|
|
455
|
+
l->cap = 16;
|
|
456
|
+
l->n = 0;
|
|
457
|
+
l->p = ALLOC_N(char, l->cap * esz);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
static void
|
|
461
|
+
fz_levels_push (fz_levels *l, const void *e)
|
|
462
|
+
{
|
|
463
|
+
if ( l->n == l->cap ) {
|
|
464
|
+
l->cap <<= 1;
|
|
465
|
+
REALLOC_N(l->p, char, l->cap * l->esz);
|
|
466
|
+
}
|
|
467
|
+
memcpy(l->p + l->n * l->esz, e, l->esz);
|
|
468
|
+
l->n++;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
static void
|
|
472
|
+
fz_levels_free (fz_levels *l)
|
|
473
|
+
{
|
|
474
|
+
if ( l->p ) { xfree(l->p); l->p = NULL; }
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* @overload __factorize_appearance__
|
|
478
|
+
|
|
479
|
+
INTERNAL (categorize's discovery pass). Factorize self into dense codes in
|
|
480
|
+
first-appearance order, one linear pass, no sort. Accepts the integer, float,
|
|
481
|
+
object, and fixlen lanes (the numeric lane keys on the widened integer, the
|
|
482
|
+
float lane on the bitwise key with NaN collapsed to one value and -0.0 == +0.0,
|
|
483
|
+
the object lane on rb_hash + rb_eql, the fixlen lane on a byte-hash + memcmp),
|
|
484
|
+
matching the discovery family's lane coverage.
|
|
485
|
+
|
|
486
|
+
Returns [codes, levels]:
|
|
487
|
+
codes = narrow unsigned CArray; masked source cells store the type-max
|
|
488
|
+
sentinel (0xFF / 0xFFFF / 0xFFFFFFFF), so from_codes reconstructs
|
|
489
|
+
the mask exactly as the mask_duplicates path does.
|
|
490
|
+
levels = CArray (source dtype) of the k distinct values, in first-appearance
|
|
491
|
+
(row-major flatten) order.
|
|
492
|
+
*/
|
|
493
|
+
static VALUE
|
|
494
|
+
rb_ca_factorize_appearance (VALUE self)
|
|
495
|
+
{
|
|
496
|
+
CArray *ca;
|
|
497
|
+
volatile VALUE face;
|
|
498
|
+
self = fz_face_descend(self, &face);
|
|
499
|
+
TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
|
|
500
|
+
|
|
501
|
+
int8_t dt = ca->data_type;
|
|
502
|
+
switch ( dt ) {
|
|
503
|
+
case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
|
|
504
|
+
case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
|
|
505
|
+
case CA_FLOAT32: case CA_FLOAT64:
|
|
506
|
+
case CA_OBJECT: case CA_FIXLEN:
|
|
507
|
+
case CA_BOOLEAN:
|
|
508
|
+
break;
|
|
509
|
+
default:
|
|
510
|
+
rb_raise(rb_eCADataTypeError,
|
|
511
|
+
"__factorize_appearance__: integer, float, object, or fixlen dtype required (got %d)", dt);
|
|
512
|
+
}
|
|
513
|
+
if ( ca->ndim < 1 ) {
|
|
514
|
+
rb_raise(rb_eRuntimeError, "__factorize_appearance__: need ndim >= 1");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
ca_size_t N = ca->elements;
|
|
518
|
+
|
|
519
|
+
/* uint32 code scratch, same shape as self; narrowed once k is known. */
|
|
520
|
+
VALUE vu32 = rb_carray_new(CA_UINT32, ca->ndim, ca->dim, 0, NULL);
|
|
521
|
+
CArray *cu32;
|
|
522
|
+
TypedData_Get_Struct(vu32, CArray, &carray_data_type, cu32);
|
|
523
|
+
|
|
524
|
+
fz_hash h;
|
|
525
|
+
fz_levels lv;
|
|
526
|
+
if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
|
|
527
|
+
else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
|
|
528
|
+
else { fz_hash_init(&h); }
|
|
529
|
+
fz_levels_init(&lv, (int) ca->bytes);
|
|
530
|
+
|
|
531
|
+
int8_t axis = (int8_t) (ca->ndim - 1); /* innermost fiber = row-major flatten */
|
|
532
|
+
|
|
533
|
+
ca_iter_state st_in, st_out;
|
|
534
|
+
char *p_in, *p_out;
|
|
535
|
+
boolean8_t *m;
|
|
536
|
+
ca_size_t n;
|
|
537
|
+
|
|
538
|
+
/* Fiber inner loop, monomorphised per dtype. WIDEN sign- or zero-extends the
|
|
539
|
+
element to a 64-bit key; equality within one dtype is preserved. */
|
|
540
|
+
#define FZ_LOOP(T, WIDEN) \
|
|
541
|
+
do { \
|
|
542
|
+
const T *ip = (const T *) p_in; \
|
|
543
|
+
uint32_t *op = (uint32_t *) p_out; \
|
|
544
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
545
|
+
if ( m && m[i] ) { op[i] = 0xFFFFFFFFu; continue; } \
|
|
546
|
+
uint64_t key = (uint64_t) (WIDEN ip[i]); \
|
|
547
|
+
int is_new; \
|
|
548
|
+
int32_t code = fz_hash_intern(&h, key, &is_new); \
|
|
549
|
+
if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
|
|
550
|
+
op[i] = (uint32_t) code; \
|
|
551
|
+
} \
|
|
552
|
+
} while (0)
|
|
553
|
+
|
|
554
|
+
/* Float lane: reproduce `==` with two value-based exceptions matching the
|
|
555
|
+
discovery family -- all NaN collapse to one canonical key (v != v -> NANKEY),
|
|
556
|
+
and -0.0 == +0.0 (a zero normalizes to +0.0 before the bitwise key). The
|
|
557
|
+
level pushed is the raw first-seen element (an appearance-first NaN / -0.0 is
|
|
558
|
+
kept as-is), so masked source cells store the sentinel. */
|
|
559
|
+
#define FZ_LOOP_FLOAT(T, UINT, NANKEY) \
|
|
560
|
+
do { \
|
|
561
|
+
const T *ip = (const T *) p_in; \
|
|
562
|
+
uint32_t *op = (uint32_t *) p_out; \
|
|
563
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
564
|
+
if ( m && m[i] ) { op[i] = 0xFFFFFFFFu; continue; } \
|
|
565
|
+
T v = ip[i]; \
|
|
566
|
+
uint64_t key; \
|
|
567
|
+
if ( v != v ) { key = (NANKEY); } \
|
|
568
|
+
else { \
|
|
569
|
+
if ( v == (T) 0 ) { v = (T) 0; } \
|
|
570
|
+
UINT bits; \
|
|
571
|
+
memcpy(&bits, &v, sizeof(bits)); \
|
|
572
|
+
key = (uint64_t) bits; \
|
|
573
|
+
} \
|
|
574
|
+
int is_new; \
|
|
575
|
+
int32_t code = fz_hash_intern(&h, key, &is_new); \
|
|
576
|
+
if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
|
|
577
|
+
op[i] = (uint32_t) code; \
|
|
578
|
+
} \
|
|
579
|
+
} while (0)
|
|
580
|
+
|
|
581
|
+
/* Object lane: intern the raw VALUE (rb_hash + rb_eql); the level pushed is the
|
|
582
|
+
first-seen VALUE, kept alive through self. Masked cells store the sentinel. */
|
|
583
|
+
#define FZ_LOOP_OBJ \
|
|
584
|
+
do { \
|
|
585
|
+
const VALUE *ip = (const VALUE *) p_in; \
|
|
586
|
+
uint32_t *op = (uint32_t *) p_out; \
|
|
587
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
588
|
+
if ( m && m[i] ) { op[i] = 0xFFFFFFFFu; continue; } \
|
|
589
|
+
int is_new; \
|
|
590
|
+
int32_t code = fz_hash_intern_obj(&h, ip[i], &is_new); \
|
|
591
|
+
if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
|
|
592
|
+
op[i] = (uint32_t) code; \
|
|
593
|
+
} \
|
|
594
|
+
} while (0)
|
|
595
|
+
|
|
596
|
+
/* Fixlen lane: intern the esz-wide element bytes (byte-hash + memcmp); the
|
|
597
|
+
level pushed is a copy of the first-seen bytes. */
|
|
598
|
+
#define FZ_LOOP_MEM \
|
|
599
|
+
do { \
|
|
600
|
+
int esz = (int) ca->bytes; \
|
|
601
|
+
uint32_t *op = (uint32_t *) p_out; \
|
|
602
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
603
|
+
if ( m && m[i] ) { op[i] = 0xFFFFFFFFu; continue; } \
|
|
604
|
+
const char *e = p_in + i * esz; \
|
|
605
|
+
int is_new; \
|
|
606
|
+
int32_t code = fz_hash_intern_mem(&h, e, &is_new); \
|
|
607
|
+
if ( is_new ) { fz_levels_push(&lv, e); } \
|
|
608
|
+
op[i] = (uint32_t) code; \
|
|
609
|
+
} \
|
|
610
|
+
} while (0)
|
|
611
|
+
|
|
612
|
+
CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cu32, axis,
|
|
613
|
+
CA_KERNEL_READ, p_in, p_out, n, m) {
|
|
614
|
+
switch ( dt ) {
|
|
615
|
+
case CA_INT8: FZ_LOOP(int8_t, (int64_t)); break;
|
|
616
|
+
case CA_INT16: FZ_LOOP(int16_t, (int64_t)); break;
|
|
617
|
+
case CA_INT32: FZ_LOOP(int32_t, (int64_t)); break;
|
|
618
|
+
case CA_INT64: FZ_LOOP(int64_t, (int64_t)); break;
|
|
619
|
+
case CA_BOOLEAN: case CA_UINT8: FZ_LOOP(uint8_t, (uint64_t)); break;
|
|
620
|
+
case CA_UINT16: FZ_LOOP(uint16_t, (uint64_t)); break;
|
|
621
|
+
case CA_UINT32: FZ_LOOP(uint32_t, (uint64_t)); break;
|
|
622
|
+
case CA_UINT64: FZ_LOOP(uint64_t, (uint64_t)); break;
|
|
623
|
+
case CA_FLOAT32: FZ_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
|
|
624
|
+
case CA_FLOAT64: FZ_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
|
|
625
|
+
case CA_OBJECT: FZ_LOOP_OBJ; break;
|
|
626
|
+
case CA_FIXLEN: FZ_LOOP_MEM; break;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
#undef FZ_LOOP
|
|
630
|
+
#undef FZ_LOOP_FLOAT
|
|
631
|
+
#undef FZ_LOOP_OBJ
|
|
632
|
+
#undef FZ_LOOP_MEM
|
|
633
|
+
|
|
634
|
+
ca_size_t k = h.n;
|
|
635
|
+
fz_hash_free(&h);
|
|
636
|
+
|
|
637
|
+
/* Narrow the uint32 scratch to the smallest code storage that holds k codes
|
|
638
|
+
plus the type-max sentinel, mirroring categorize's width rule. */
|
|
639
|
+
int8_t code_dt;
|
|
640
|
+
if ( k <= 0xFF ) { code_dt = CA_UINT8; }
|
|
641
|
+
else if ( k <= 0xFFFF ) { code_dt = CA_UINT16; }
|
|
642
|
+
else { code_dt = CA_UINT32; }
|
|
643
|
+
|
|
644
|
+
VALUE vcodes;
|
|
645
|
+
if ( code_dt == CA_UINT32 ) {
|
|
646
|
+
vcodes = vu32; /* sentinel already 0xFFFFFFFF */
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
vcodes = rb_carray_new(code_dt, ca->ndim, ca->dim, 0, NULL);
|
|
650
|
+
CArray *cco;
|
|
651
|
+
TypedData_Get_Struct(vcodes, CArray, &carray_data_type, cco);
|
|
652
|
+
const uint32_t *up = (const uint32_t *) cu32->ptr;
|
|
653
|
+
if ( code_dt == CA_UINT8 ) {
|
|
654
|
+
uint8_t *cp = (uint8_t *) cco->ptr;
|
|
655
|
+
for ( ca_size_t i = 0; i < N; i++ ) {
|
|
656
|
+
cp[i] = (up[i] == 0xFFFFFFFFu) ? (uint8_t) 0xFF : (uint8_t) up[i];
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
uint16_t *cp = (uint16_t *) cco->ptr;
|
|
661
|
+
for ( ca_size_t i = 0; i < N; i++ ) {
|
|
662
|
+
cp[i] = (up[i] == 0xFFFFFFFFu) ? (uint16_t) 0xFFFF : (uint16_t) up[i];
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/* Levels: the interned raw values, in source dtype. CA_FIXLEN carries its
|
|
668
|
+
element width; numeric / object use bytes = 0. */
|
|
669
|
+
ca_size_t ldim[1];
|
|
670
|
+
ldim[0] = k;
|
|
671
|
+
VALUE vlev = rb_carray_new(dt, 1, ldim, (dt == CA_FIXLEN) ? ca->bytes : 0, NULL);
|
|
672
|
+
CArray *clev;
|
|
673
|
+
TypedData_Get_Struct(vlev, CArray, &carray_data_type, clev);
|
|
674
|
+
if ( k > 0 ) {
|
|
675
|
+
memcpy(clev->ptr, lv.p, (size_t) k * (size_t) ca->bytes);
|
|
676
|
+
}
|
|
677
|
+
fz_levels_free(&lv);
|
|
678
|
+
|
|
679
|
+
/* codes stay plain; levels are *values* and become categorize's labels */
|
|
680
|
+
return rb_ary_new3(2, vcodes, fz_face_relift(vlev, face));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/* @overload __mask_duplicates__(axis)
|
|
684
|
+
|
|
685
|
+
INTERNAL (mask_duplicates). Mark each cell whose value duplicates an
|
|
686
|
+
earlier-seen one along axis, one linear pass per fiber, no sort.
|
|
687
|
+
|
|
688
|
+
Returns a boolean CArray of self.shape: true at every duplicate position
|
|
689
|
+
(the first occurrence of each distinct value stays false). The seen-set is
|
|
690
|
+
per-fiber independent along axis. Masked source cells do not participate in
|
|
691
|
+
duplicate judging and stay false (they remain masked via mask_where
|
|
692
|
+
downstream). Covers numeric / object / fixlen; boolean routes through the
|
|
693
|
+
uint8 numeric lane (storage is uint8 0/1, at most two distinct keys).
|
|
694
|
+
|
|
695
|
+
Numeric distinctness follows `==` with two value-based exceptions matching the
|
|
696
|
+
discovery family: all NaN collapse to one distinct value (so the second and
|
|
697
|
+
later NaN along the fiber are duplicates) and -0.0 == +0.0. Object keys on
|
|
698
|
+
rb_hash + rb_eql and fixlen on a byte-hash + memcmp, reproducing Ruby Hash
|
|
699
|
+
distinctness; the object lane additionally collapses every Float NaN to one
|
|
700
|
+
value (so the second and later NaN are duplicates, as in the numeric lane).
|
|
701
|
+
*/
|
|
702
|
+
static VALUE
|
|
703
|
+
rb_ca_mask_duplicates (VALUE self, VALUE vaxis)
|
|
704
|
+
{
|
|
705
|
+
CArray *ca;
|
|
706
|
+
TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
|
|
707
|
+
|
|
708
|
+
int8_t dt = ca->data_type;
|
|
709
|
+
switch ( dt ) {
|
|
710
|
+
case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
|
|
711
|
+
case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
|
|
712
|
+
case CA_FLOAT32: case CA_FLOAT64:
|
|
713
|
+
case CA_OBJECT: case CA_FIXLEN:
|
|
714
|
+
case CA_BOOLEAN:
|
|
715
|
+
break;
|
|
716
|
+
default:
|
|
717
|
+
rb_raise(rb_eCADataTypeError,
|
|
718
|
+
"__mask_duplicates__: numeric, object, or fixlen dtype required (got %d)", dt);
|
|
719
|
+
}
|
|
720
|
+
if ( ca->ndim < 1 ) {
|
|
721
|
+
rb_raise(rb_eRuntimeError, "__mask_duplicates__: need ndim >= 1");
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
int axis = NUM2INT(vaxis);
|
|
725
|
+
if ( axis < 0 || axis >= ca->ndim ) {
|
|
726
|
+
rb_raise(rb_eArgError, "__mask_duplicates__: axis %d out of range", axis);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
VALUE vout = rb_carray_new(CA_BOOLEAN, ca->ndim, ca->dim, 0, NULL);
|
|
730
|
+
CArray *cout;
|
|
731
|
+
TypedData_Get_Struct(vout, CArray, &carray_data_type, cout);
|
|
732
|
+
|
|
733
|
+
fz_hash h;
|
|
734
|
+
if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
|
|
735
|
+
else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
|
|
736
|
+
else { fz_hash_init(&h); }
|
|
737
|
+
|
|
738
|
+
ca_iter_state st_in, st_out;
|
|
739
|
+
char *p_in, *p_out;
|
|
740
|
+
boolean8_t *m;
|
|
741
|
+
ca_size_t n;
|
|
742
|
+
|
|
743
|
+
/* Fiber inner loop, monomorphised per dtype. WIDEN sign- or zero-extends the
|
|
744
|
+
element to a 64-bit key; equality within one dtype is preserved. The hash
|
|
745
|
+
interns first appearances, so is_new == 0 flags a duplicate. */
|
|
746
|
+
#define MD_LOOP(T, WIDEN) \
|
|
747
|
+
do { \
|
|
748
|
+
const T *ip = (const T *) p_in; \
|
|
749
|
+
boolean8_t *op = (boolean8_t *) p_out; \
|
|
750
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
751
|
+
if ( m && m[i] ) { op[i] = 0; continue; } \
|
|
752
|
+
uint64_t key = (uint64_t) (WIDEN ip[i]); \
|
|
753
|
+
int is_new; \
|
|
754
|
+
fz_hash_intern(&h, key, &is_new); \
|
|
755
|
+
op[i] = is_new ? 0 : 1; \
|
|
756
|
+
} \
|
|
757
|
+
} while (0)
|
|
758
|
+
|
|
759
|
+
/* Float variant: reproduce `==` with two value-based exceptions so the
|
|
760
|
+
distinct-value judgement matches uniq / the discovery family. All NaN
|
|
761
|
+
collapse to one canonical key (v != v -> NANKEY), so the second and later
|
|
762
|
+
NaN in a fiber are duplicates; -0.0 == +0.0, so a zero normalizes to +0.0
|
|
763
|
+
before the bitwise key. */
|
|
764
|
+
#define MD_LOOP_FLOAT(T, UINT, NANKEY) \
|
|
765
|
+
do { \
|
|
766
|
+
const T *ip = (const T *) p_in; \
|
|
767
|
+
boolean8_t *op = (boolean8_t *) p_out; \
|
|
768
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
769
|
+
if ( m && m[i] ) { op[i] = 0; continue; } \
|
|
770
|
+
T v = ip[i]; \
|
|
771
|
+
uint64_t key; \
|
|
772
|
+
if ( v != v ) { key = (NANKEY); } \
|
|
773
|
+
else { \
|
|
774
|
+
if ( v == (T) 0 ) { v = (T) 0; } \
|
|
775
|
+
UINT bits; \
|
|
776
|
+
memcpy(&bits, &v, sizeof(bits)); \
|
|
777
|
+
key = (uint64_t) bits; \
|
|
778
|
+
} \
|
|
779
|
+
int is_new; \
|
|
780
|
+
fz_hash_intern(&h, key, &is_new); \
|
|
781
|
+
op[i] = is_new ? 0 : 1; \
|
|
782
|
+
} \
|
|
783
|
+
} while (0)
|
|
784
|
+
|
|
785
|
+
/* Object: rb_hash + rb_eql lane, per-fiber seen-set (is_new == 0 -> dup). */
|
|
786
|
+
#define MD_LOOP_OBJ \
|
|
787
|
+
do { \
|
|
788
|
+
const VALUE *ip = (const VALUE *) p_in; \
|
|
789
|
+
boolean8_t *op = (boolean8_t *) p_out; \
|
|
790
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
791
|
+
if ( m && m[i] ) { op[i] = 0; continue; } \
|
|
792
|
+
int is_new; \
|
|
793
|
+
fz_hash_intern_obj(&h, ip[i], &is_new); \
|
|
794
|
+
op[i] = is_new ? 0 : 1; \
|
|
795
|
+
} \
|
|
796
|
+
} while (0)
|
|
797
|
+
|
|
798
|
+
/* Fixlen: byte-hash + memcmp lane, per-fiber seen-set. */
|
|
799
|
+
#define MD_LOOP_MEM \
|
|
800
|
+
do { \
|
|
801
|
+
boolean8_t *op = (boolean8_t *) p_out; \
|
|
802
|
+
int esz = (int) ca->bytes; \
|
|
803
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
804
|
+
if ( m && m[i] ) { op[i] = 0; continue; } \
|
|
805
|
+
int is_new; \
|
|
806
|
+
fz_hash_intern_mem(&h, p_in + i * esz, &is_new); \
|
|
807
|
+
op[i] = is_new ? 0 : 1; \
|
|
808
|
+
} \
|
|
809
|
+
} while (0)
|
|
810
|
+
|
|
811
|
+
CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cout, (int8_t) axis,
|
|
812
|
+
CA_KERNEL_READ, p_in, p_out, n, m) {
|
|
813
|
+
fz_hash_reset(&h); /* independent seen-set per fiber */
|
|
814
|
+
switch ( dt ) {
|
|
815
|
+
case CA_INT8: MD_LOOP(int8_t, (int64_t)); break;
|
|
816
|
+
case CA_INT16: MD_LOOP(int16_t, (int64_t)); break;
|
|
817
|
+
case CA_INT32: MD_LOOP(int32_t, (int64_t)); break;
|
|
818
|
+
case CA_INT64: MD_LOOP(int64_t, (int64_t)); break;
|
|
819
|
+
case CA_BOOLEAN: case CA_UINT8: MD_LOOP(uint8_t, (uint64_t)); break;
|
|
820
|
+
case CA_UINT16: MD_LOOP(uint16_t, (uint64_t)); break;
|
|
821
|
+
case CA_UINT32: MD_LOOP(uint32_t, (uint64_t)); break;
|
|
822
|
+
case CA_UINT64: MD_LOOP(uint64_t, (uint64_t)); break;
|
|
823
|
+
case CA_FLOAT32: MD_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
|
|
824
|
+
case CA_FLOAT64: MD_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
|
|
825
|
+
case CA_OBJECT: MD_LOOP_OBJ; break;
|
|
826
|
+
case CA_FIXLEN: MD_LOOP_MEM; break;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
#undef MD_LOOP
|
|
830
|
+
#undef MD_LOOP_FLOAT
|
|
831
|
+
#undef MD_LOOP_OBJ
|
|
832
|
+
#undef MD_LOOP_MEM
|
|
833
|
+
|
|
834
|
+
fz_hash_free(&h);
|
|
835
|
+
return vout;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/* @overload __unique_flat__
|
|
839
|
+
|
|
840
|
+
INTERNAL (CArray#unique). Collect the distinct values of self in
|
|
841
|
+
first-appearance (row-major flatten) order, one linear pass, no sort. Returns
|
|
842
|
+
a 1-D CArray of source dtype. Masked cells do not participate.
|
|
843
|
+
|
|
844
|
+
Numeric (integer / float): distinctness follows `==` except NaN collapses to a
|
|
845
|
+
single distinct value (all NaN patterns map to one canonical hash key) and
|
|
846
|
+
-0.0 == +0.0 (a zero normalizes to +0.0 for the key). The emitted level value
|
|
847
|
+
is the first element seen for each key, so an appearance-first NaN or -0.0 is
|
|
848
|
+
preserved.
|
|
849
|
+
|
|
850
|
+
Object (CA_OBJECT): distinctness follows Ruby `hash` + `eql?` (the object lane
|
|
851
|
+
keys on rb_hash and re-checks with rb_eql), which already folds -0.0 / +0.0
|
|
852
|
+
together. It deviates from Ruby Hash in one way, aligning with the numeric
|
|
853
|
+
lane: every Float NaN collapses to one distinct value (a NaN takes a fixed
|
|
854
|
+
canonical key and matches any stored Float NaN), where Ruby Hash would keep
|
|
855
|
+
distinct NaN objects apart.
|
|
856
|
+
|
|
857
|
+
Fixlen (CA_FIXLEN): distinctness is byte equality (the fixlen lane keys on a
|
|
858
|
+
byte-hash and re-checks with memcmp), matching Ruby String eql? for the
|
|
859
|
+
uniform-width binary cells the fixlen seen-set used.
|
|
860
|
+
*/
|
|
861
|
+
static VALUE
|
|
862
|
+
rb_ca_unique_flat (VALUE self)
|
|
863
|
+
{
|
|
864
|
+
CArray *ca;
|
|
865
|
+
volatile VALUE face;
|
|
866
|
+
self = fz_face_descend(self, &face);
|
|
867
|
+
TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
|
|
868
|
+
|
|
869
|
+
int8_t dt = ca->data_type;
|
|
870
|
+
switch ( dt ) {
|
|
871
|
+
case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
|
|
872
|
+
case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
|
|
873
|
+
case CA_FLOAT32: case CA_FLOAT64:
|
|
874
|
+
case CA_OBJECT: case CA_FIXLEN:
|
|
875
|
+
case CA_BOOLEAN:
|
|
876
|
+
break;
|
|
877
|
+
default:
|
|
878
|
+
rb_raise(rb_eCADataTypeError,
|
|
879
|
+
"__unique_flat__: numeric, object, or fixlen dtype required (got %d)", dt);
|
|
880
|
+
}
|
|
881
|
+
if ( ca->ndim < 1 ) {
|
|
882
|
+
rb_raise(rb_eRuntimeError, "__unique_flat__: need ndim >= 1");
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
fz_hash h;
|
|
886
|
+
fz_levels lv;
|
|
887
|
+
if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
|
|
888
|
+
else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
|
|
889
|
+
else { fz_hash_init(&h); }
|
|
890
|
+
fz_levels_init(&lv, (int) ca->bytes);
|
|
891
|
+
|
|
892
|
+
int8_t axis = (int8_t) (ca->ndim - 1); /* innermost fiber; hash not reset =
|
|
893
|
+
one seen-set over the whole array */
|
|
894
|
+
ca_iter_state st_in;
|
|
895
|
+
char *p_in;
|
|
896
|
+
boolean8_t *m;
|
|
897
|
+
ca_size_t n;
|
|
898
|
+
|
|
899
|
+
#define UQ_LOOP(T, WIDEN) \
|
|
900
|
+
do { \
|
|
901
|
+
const T *ip = (const T *) p_in; \
|
|
902
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
903
|
+
if ( m && m[i] ) { continue; } \
|
|
904
|
+
uint64_t key = (uint64_t) (WIDEN ip[i]); \
|
|
905
|
+
int is_new; \
|
|
906
|
+
fz_hash_intern(&h, key, &is_new); \
|
|
907
|
+
if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
|
|
908
|
+
} \
|
|
909
|
+
} while (0)
|
|
910
|
+
|
|
911
|
+
/* Float: NaN collapses to one canonical key; -0.0 / +0.0 share a key. The
|
|
912
|
+
pushed level is the raw first-seen element. */
|
|
913
|
+
#define UQ_LOOP_FLOAT(T, UINT, NANKEY) \
|
|
914
|
+
do { \
|
|
915
|
+
const T *ip = (const T *) p_in; \
|
|
916
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
917
|
+
if ( m && m[i] ) { continue; } \
|
|
918
|
+
T v = ip[i]; \
|
|
919
|
+
uint64_t key; \
|
|
920
|
+
if ( v != v ) { key = (NANKEY); } \
|
|
921
|
+
else { \
|
|
922
|
+
if ( v == (T) 0 ) { v = (T) 0; } \
|
|
923
|
+
UINT bits; \
|
|
924
|
+
memcpy(&bits, &v, sizeof(bits)); \
|
|
925
|
+
key = (uint64_t) bits; \
|
|
926
|
+
} \
|
|
927
|
+
int is_new; \
|
|
928
|
+
fz_hash_intern(&h, key, &is_new); \
|
|
929
|
+
if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
|
|
930
|
+
} \
|
|
931
|
+
} while (0)
|
|
932
|
+
|
|
933
|
+
/* Object: intern the raw VALUE via the object lane (rb_hash + rb_eql); the
|
|
934
|
+
pushed level is the first-seen VALUE. The interned VALUEs stay alive through
|
|
935
|
+
`self` (they are its elements), so no separate GC registration is needed. */
|
|
936
|
+
#define UQ_LOOP_OBJ \
|
|
937
|
+
do { \
|
|
938
|
+
const VALUE *ip = (const VALUE *) p_in; \
|
|
939
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
940
|
+
if ( m && m[i] ) { continue; } \
|
|
941
|
+
int is_new; \
|
|
942
|
+
fz_hash_intern_obj(&h, ip[i], &is_new); \
|
|
943
|
+
if ( is_new ) { fz_levels_push(&lv, &ip[i]); } \
|
|
944
|
+
} \
|
|
945
|
+
} while (0)
|
|
946
|
+
|
|
947
|
+
/* Fixlen: intern the esz-wide element bytes via the fixlen lane; the pushed
|
|
948
|
+
level is a copy of the first-seen element bytes. */
|
|
949
|
+
#define UQ_LOOP_MEM \
|
|
950
|
+
do { \
|
|
951
|
+
int esz = (int) ca->bytes; \
|
|
952
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
953
|
+
if ( m && m[i] ) { continue; } \
|
|
954
|
+
const char *e = p_in + i * esz; \
|
|
955
|
+
int is_new; \
|
|
956
|
+
fz_hash_intern_mem(&h, e, &is_new); \
|
|
957
|
+
if ( is_new ) { fz_levels_push(&lv, e); } \
|
|
958
|
+
} \
|
|
959
|
+
} while (0)
|
|
960
|
+
|
|
961
|
+
CA_FOR_EACH_FIBER_MASKED(st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
|
|
962
|
+
switch ( dt ) {
|
|
963
|
+
case CA_INT8: UQ_LOOP(int8_t, (int64_t)); break;
|
|
964
|
+
case CA_INT16: UQ_LOOP(int16_t, (int64_t)); break;
|
|
965
|
+
case CA_INT32: UQ_LOOP(int32_t, (int64_t)); break;
|
|
966
|
+
case CA_INT64: UQ_LOOP(int64_t, (int64_t)); break;
|
|
967
|
+
case CA_BOOLEAN: case CA_UINT8: UQ_LOOP(uint8_t, (uint64_t)); break;
|
|
968
|
+
case CA_UINT16: UQ_LOOP(uint16_t, (uint64_t)); break;
|
|
969
|
+
case CA_UINT32: UQ_LOOP(uint32_t, (uint64_t)); break;
|
|
970
|
+
case CA_UINT64: UQ_LOOP(uint64_t, (uint64_t)); break;
|
|
971
|
+
case CA_FLOAT32: UQ_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
|
|
972
|
+
case CA_FLOAT64: UQ_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
|
|
973
|
+
case CA_OBJECT: UQ_LOOP_OBJ; break;
|
|
974
|
+
case CA_FIXLEN: UQ_LOOP_MEM; break;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
#undef UQ_LOOP
|
|
978
|
+
#undef UQ_LOOP_FLOAT
|
|
979
|
+
#undef UQ_LOOP_OBJ
|
|
980
|
+
#undef UQ_LOOP_MEM
|
|
981
|
+
|
|
982
|
+
ca_size_t k = h.n;
|
|
983
|
+
fz_hash_free(&h);
|
|
984
|
+
|
|
985
|
+
ca_size_t ldim[1];
|
|
986
|
+
ldim[0] = k;
|
|
987
|
+
/* CA_FIXLEN carries its element width; numeric / object use bytes = 0. */
|
|
988
|
+
VALUE vlev = rb_carray_new(dt, 1, ldim, (dt == CA_FIXLEN) ? ca->bytes : 0, NULL);
|
|
989
|
+
CArray *clev;
|
|
990
|
+
TypedData_Get_Struct(vlev, CArray, &carray_data_type, clev);
|
|
991
|
+
if ( k > 0 ) {
|
|
992
|
+
memcpy(clev->ptr, lv.p, (size_t) k * (size_t) ca->bytes);
|
|
993
|
+
}
|
|
994
|
+
fz_levels_free(&lv);
|
|
995
|
+
|
|
996
|
+
return fz_face_relift(vlev, face); /* distinct *values*: give the Face back */
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/* Intern every non-masked cell of cv into h, one seen-set over the whole array
|
|
1000
|
+
(the hash is not reset between fibers). When lv is non-NULL, the first-seen
|
|
1001
|
+
raw element of each distinct value is pushed to it (appearance order), so the
|
|
1002
|
+
same pass builds both a probe set and the distinct-value list. Dispatches the
|
|
1003
|
+
three key lanes exactly as the discovery family: numeric widen with all NaN
|
|
1004
|
+
collapsed and -0.0 == +0.0, object rb_hash + rb_eql with Float NaN collapsed,
|
|
1005
|
+
fixlen byte-hash + memcmp. Used to build the probe set (is_in, set relations)
|
|
1006
|
+
and to accumulate distinct values (union). */
|
|
1007
|
+
static void
|
|
1008
|
+
fz_intern_all (fz_hash *h, CArray *cv, fz_levels *lv)
|
|
1009
|
+
{
|
|
1010
|
+
int8_t dt = cv->data_type;
|
|
1011
|
+
int8_t vaxis = (int8_t) (cv->ndim - 1);
|
|
1012
|
+
ca_iter_state st_v;
|
|
1013
|
+
char *p_v;
|
|
1014
|
+
boolean8_t *mv;
|
|
1015
|
+
ca_size_t nv;
|
|
1016
|
+
|
|
1017
|
+
#define FZ_IA(T, WIDEN) \
|
|
1018
|
+
do { \
|
|
1019
|
+
const T *ip = (const T *) p_v; \
|
|
1020
|
+
for ( ca_size_t i = 0; i < nv; i++ ) { \
|
|
1021
|
+
if ( mv && mv[i] ) { continue; } \
|
|
1022
|
+
uint64_t key = (uint64_t) (WIDEN ip[i]); \
|
|
1023
|
+
int is_new; \
|
|
1024
|
+
fz_hash_intern(h, key, &is_new); \
|
|
1025
|
+
if ( is_new && lv ) { fz_levels_push(lv, &ip[i]); } \
|
|
1026
|
+
} \
|
|
1027
|
+
} while (0)
|
|
1028
|
+
|
|
1029
|
+
#define FZ_IA_FLOAT(T, UINT, NANKEY) \
|
|
1030
|
+
do { \
|
|
1031
|
+
const T *ip = (const T *) p_v; \
|
|
1032
|
+
for ( ca_size_t i = 0; i < nv; i++ ) { \
|
|
1033
|
+
if ( mv && mv[i] ) { continue; } \
|
|
1034
|
+
T v = ip[i]; \
|
|
1035
|
+
uint64_t key; \
|
|
1036
|
+
if ( v != v ) { key = (NANKEY); } \
|
|
1037
|
+
else { \
|
|
1038
|
+
if ( v == (T) 0 ) { v = (T) 0; } \
|
|
1039
|
+
UINT bits; \
|
|
1040
|
+
memcpy(&bits, &v, sizeof(bits)); \
|
|
1041
|
+
key = (uint64_t) bits; \
|
|
1042
|
+
} \
|
|
1043
|
+
int is_new; \
|
|
1044
|
+
fz_hash_intern(h, key, &is_new); \
|
|
1045
|
+
if ( is_new && lv ) { fz_levels_push(lv, &ip[i]); } \
|
|
1046
|
+
} \
|
|
1047
|
+
} while (0)
|
|
1048
|
+
|
|
1049
|
+
#define FZ_IA_OBJ \
|
|
1050
|
+
do { \
|
|
1051
|
+
const VALUE *ip = (const VALUE *) p_v; \
|
|
1052
|
+
for ( ca_size_t i = 0; i < nv; i++ ) { \
|
|
1053
|
+
if ( mv && mv[i] ) { continue; } \
|
|
1054
|
+
int is_new; \
|
|
1055
|
+
fz_hash_intern_obj(h, ip[i], &is_new); \
|
|
1056
|
+
if ( is_new && lv ) { fz_levels_push(lv, &ip[i]); } \
|
|
1057
|
+
} \
|
|
1058
|
+
} while (0)
|
|
1059
|
+
|
|
1060
|
+
#define FZ_IA_MEM \
|
|
1061
|
+
do { \
|
|
1062
|
+
int esz = (int) cv->bytes; \
|
|
1063
|
+
for ( ca_size_t i = 0; i < nv; i++ ) { \
|
|
1064
|
+
if ( mv && mv[i] ) { continue; } \
|
|
1065
|
+
const char *e = p_v + i * esz; \
|
|
1066
|
+
int is_new; \
|
|
1067
|
+
fz_hash_intern_mem(h, e, &is_new); \
|
|
1068
|
+
if ( is_new && lv ) { fz_levels_push(lv, e); } \
|
|
1069
|
+
} \
|
|
1070
|
+
} while (0)
|
|
1071
|
+
|
|
1072
|
+
CA_FOR_EACH_FIBER_MASKED(st_v, cv, vaxis, CA_KERNEL_READ, p_v, nv, mv) {
|
|
1073
|
+
switch ( dt ) {
|
|
1074
|
+
case CA_INT8: FZ_IA(int8_t, (int64_t)); break;
|
|
1075
|
+
case CA_INT16: FZ_IA(int16_t, (int64_t)); break;
|
|
1076
|
+
case CA_INT32: FZ_IA(int32_t, (int64_t)); break;
|
|
1077
|
+
case CA_INT64: FZ_IA(int64_t, (int64_t)); break;
|
|
1078
|
+
case CA_BOOLEAN: case CA_UINT8: FZ_IA(uint8_t, (uint64_t)); break;
|
|
1079
|
+
case CA_UINT16: FZ_IA(uint16_t, (uint64_t)); break;
|
|
1080
|
+
case CA_UINT32: FZ_IA(uint32_t, (uint64_t)); break;
|
|
1081
|
+
case CA_UINT64: FZ_IA(uint64_t, (uint64_t)); break;
|
|
1082
|
+
case CA_FLOAT32: FZ_IA_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
|
|
1083
|
+
case CA_FLOAT64: FZ_IA_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
|
|
1084
|
+
case CA_OBJECT: FZ_IA_OBJ; break;
|
|
1085
|
+
case CA_FIXLEN: FZ_IA_MEM; break;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
#undef FZ_IA
|
|
1089
|
+
#undef FZ_IA_FLOAT
|
|
1090
|
+
#undef FZ_IA_OBJ
|
|
1091
|
+
#undef FZ_IA_MEM
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/* @overload __is_in__(values)
|
|
1095
|
+
|
|
1096
|
+
INTERNAL (CArray#is_in). Return a boolean CArray of self.shape, true at each
|
|
1097
|
+
cell whose value appears in the set `values` (any shape; flattened to one
|
|
1098
|
+
seen-set). One pass to build the set from `values`, one pass to probe self;
|
|
1099
|
+
no sort, peak memory O(distinct values).
|
|
1100
|
+
|
|
1101
|
+
`values` must be a CArray of the same dtype as self (the Ruby surface coerces
|
|
1102
|
+
Array / Range / other-dtype input first). Masked cells of `values` do not
|
|
1103
|
+
enter the set. Masked cells of self stay masked in the output (membership is
|
|
1104
|
+
unknown), reproducing the mask propagation of the retired `contains`
|
|
1105
|
+
(self.eq(v)); their boolean payload is false.
|
|
1106
|
+
|
|
1107
|
+
Distinctness follows the discovery family per lane: numeric `==` with all NaN
|
|
1108
|
+
collapsed and -0.0 == +0.0; object rb_hash + rb_eql with Float NaN collapsed;
|
|
1109
|
+
fixlen byte equality (byte-hash + memcmp).
|
|
1110
|
+
*/
|
|
1111
|
+
static VALUE
|
|
1112
|
+
rb_ca_is_in (VALUE self, VALUE rvalues)
|
|
1113
|
+
{
|
|
1114
|
+
CArray *ca, *cv;
|
|
1115
|
+
volatile VALUE face;
|
|
1116
|
+
self = fz_face_descend(self, &face);
|
|
1117
|
+
rvalues = fz_face_reconcile(face, rvalues, "is_in");
|
|
1118
|
+
GetCArray(self, ca);
|
|
1119
|
+
|
|
1120
|
+
int8_t dt = ca->data_type;
|
|
1121
|
+
switch ( dt ) {
|
|
1122
|
+
case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
|
|
1123
|
+
case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
|
|
1124
|
+
case CA_FLOAT32: case CA_FLOAT64:
|
|
1125
|
+
case CA_OBJECT: case CA_FIXLEN:
|
|
1126
|
+
case CA_BOOLEAN:
|
|
1127
|
+
break;
|
|
1128
|
+
default:
|
|
1129
|
+
rb_raise(rb_eCADataTypeError,
|
|
1130
|
+
"__is_in__: numeric, object, or fixlen dtype required (got %d)", dt);
|
|
1131
|
+
}
|
|
1132
|
+
if ( ca->ndim < 1 ) {
|
|
1133
|
+
rb_raise(rb_eRuntimeError, "__is_in__: need ndim >= 1");
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if ( ! RTEST(rb_obj_is_kind_of(rvalues, rb_cCArray)) ) {
|
|
1137
|
+
rb_raise(rb_eArgError, "__is_in__: values must be a CArray");
|
|
1138
|
+
}
|
|
1139
|
+
GetCArray(rvalues, cv);
|
|
1140
|
+
if ( cv->data_type != dt || (dt == CA_FIXLEN && cv->bytes != ca->bytes) ) {
|
|
1141
|
+
rb_raise(rb_eCADataTypeError,
|
|
1142
|
+
"__is_in__: values dtype must match self (%d)", dt);
|
|
1143
|
+
}
|
|
1144
|
+
if ( cv->ndim < 1 ) {
|
|
1145
|
+
rb_raise(rb_eRuntimeError, "__is_in__: values need ndim >= 1");
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
fz_hash h;
|
|
1149
|
+
if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
|
|
1150
|
+
else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
|
|
1151
|
+
else { fz_hash_init(&h); }
|
|
1152
|
+
|
|
1153
|
+
/* ---- Phase 1: build the seen-set from every non-masked cell of `values`. */
|
|
1154
|
+
fz_intern_all(&h, cv, NULL);
|
|
1155
|
+
|
|
1156
|
+
/* ---- Phase 2: probe every cell of self, writing the boolean membership. */
|
|
1157
|
+
VALUE vout = rb_carray_new(CA_BOOLEAN, ca->ndim, ca->dim, 0, NULL);
|
|
1158
|
+
CArray *cout;
|
|
1159
|
+
GetCArray(vout, cout);
|
|
1160
|
+
{
|
|
1161
|
+
ca_iter_state st_in, st_out;
|
|
1162
|
+
char *p_in, *p_out;
|
|
1163
|
+
boolean8_t *m;
|
|
1164
|
+
ca_size_t n;
|
|
1165
|
+
int8_t axis = (int8_t) (ca->ndim - 1);
|
|
1166
|
+
|
|
1167
|
+
#define IN_PROBE(T, WIDEN) \
|
|
1168
|
+
do { \
|
|
1169
|
+
const T *ip = (const T *) p_in; \
|
|
1170
|
+
boolean8_t *op = (boolean8_t *) p_out; \
|
|
1171
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1172
|
+
if ( m && m[i] ) { op[i] = 0; continue; } \
|
|
1173
|
+
uint64_t key = (uint64_t) (WIDEN ip[i]); \
|
|
1174
|
+
op[i] = (boolean8_t) fz_hash_lookup(&h, key, NULL); \
|
|
1175
|
+
} \
|
|
1176
|
+
} while (0)
|
|
1177
|
+
|
|
1178
|
+
#define IN_PROBE_FLOAT(T, UINT, NANKEY) \
|
|
1179
|
+
do { \
|
|
1180
|
+
const T *ip = (const T *) p_in; \
|
|
1181
|
+
boolean8_t *op = (boolean8_t *) p_out; \
|
|
1182
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1183
|
+
if ( m && m[i] ) { op[i] = 0; continue; } \
|
|
1184
|
+
T v = ip[i]; \
|
|
1185
|
+
uint64_t key; \
|
|
1186
|
+
if ( v != v ) { key = (NANKEY); } \
|
|
1187
|
+
else { \
|
|
1188
|
+
if ( v == (T) 0 ) { v = (T) 0; } \
|
|
1189
|
+
UINT bits; \
|
|
1190
|
+
memcpy(&bits, &v, sizeof(bits)); \
|
|
1191
|
+
key = (uint64_t) bits; \
|
|
1192
|
+
} \
|
|
1193
|
+
op[i] = (boolean8_t) fz_hash_lookup(&h, key, NULL); \
|
|
1194
|
+
} \
|
|
1195
|
+
} while (0)
|
|
1196
|
+
|
|
1197
|
+
#define IN_PROBE_OBJ \
|
|
1198
|
+
do { \
|
|
1199
|
+
const VALUE *ip = (const VALUE *) p_in; \
|
|
1200
|
+
boolean8_t *op = (boolean8_t *) p_out; \
|
|
1201
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1202
|
+
if ( m && m[i] ) { op[i] = 0; continue; } \
|
|
1203
|
+
op[i] = (boolean8_t) fz_hash_lookup_obj(&h, ip[i], NULL); \
|
|
1204
|
+
} \
|
|
1205
|
+
} while (0)
|
|
1206
|
+
|
|
1207
|
+
#define IN_PROBE_MEM \
|
|
1208
|
+
do { \
|
|
1209
|
+
boolean8_t *op = (boolean8_t *) p_out; \
|
|
1210
|
+
int esz = (int) ca->bytes; \
|
|
1211
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1212
|
+
if ( m && m[i] ) { op[i] = 0; continue; } \
|
|
1213
|
+
op[i] = (boolean8_t) fz_hash_lookup_mem(&h, p_in + i * esz, NULL); \
|
|
1214
|
+
} \
|
|
1215
|
+
} while (0)
|
|
1216
|
+
|
|
1217
|
+
CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cout, axis,
|
|
1218
|
+
CA_KERNEL_READ, p_in, p_out, n, m) {
|
|
1219
|
+
switch ( dt ) {
|
|
1220
|
+
case CA_INT8: IN_PROBE(int8_t, (int64_t)); break;
|
|
1221
|
+
case CA_INT16: IN_PROBE(int16_t, (int64_t)); break;
|
|
1222
|
+
case CA_INT32: IN_PROBE(int32_t, (int64_t)); break;
|
|
1223
|
+
case CA_INT64: IN_PROBE(int64_t, (int64_t)); break;
|
|
1224
|
+
case CA_BOOLEAN: case CA_UINT8: IN_PROBE(uint8_t, (uint64_t)); break;
|
|
1225
|
+
case CA_UINT16: IN_PROBE(uint16_t, (uint64_t)); break;
|
|
1226
|
+
case CA_UINT32: IN_PROBE(uint32_t, (uint64_t)); break;
|
|
1227
|
+
case CA_UINT64: IN_PROBE(uint64_t, (uint64_t)); break;
|
|
1228
|
+
case CA_FLOAT32: IN_PROBE_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
|
|
1229
|
+
case CA_FLOAT64: IN_PROBE_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
|
|
1230
|
+
case CA_OBJECT: IN_PROBE_OBJ; break;
|
|
1231
|
+
case CA_FIXLEN: IN_PROBE_MEM; break;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
#undef IN_PROBE
|
|
1235
|
+
#undef IN_PROBE_FLOAT
|
|
1236
|
+
#undef IN_PROBE_OBJ
|
|
1237
|
+
#undef IN_PROBE_MEM
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
fz_hash_free(&h);
|
|
1241
|
+
|
|
1242
|
+
/* Masked self cells stay masked in the output (membership unknown), matching
|
|
1243
|
+
the retired contains (self.eq(v)) mask propagation. */
|
|
1244
|
+
if ( ca_has_mask(ca) ) { ca_copy_mask(cout, ca); }
|
|
1245
|
+
|
|
1246
|
+
return vout;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/* @overload __locate_addr__(ref)
|
|
1250
|
+
|
|
1251
|
+
INTERNAL (CArray#locate_addr, exact hash lane). For each cell of self, the
|
|
1252
|
+
row-major flat address into `ref` where that value first occurs, or UNDEF
|
|
1253
|
+
where the value is absent from `ref`. Output is CA_INT64 of self.shape. One
|
|
1254
|
+
pass builds a value -> first-address map from `ref`, one pass probes self; no
|
|
1255
|
+
sort, peak memory O(distinct ref values). Numeric / object / fixlen all work
|
|
1256
|
+
through the discovery-family lanes (NaN collapse, rb_hash + rb_eql, byte
|
|
1257
|
+
equality).
|
|
1258
|
+
|
|
1259
|
+
`ref` must be a CArray of the same dtype as self (the Ruby surface coerces
|
|
1260
|
+
first). Masked cells of `ref` do not enter the map but still occupy their
|
|
1261
|
+
flat address (position counts). Masked cells of self, and cells whose value
|
|
1262
|
+
is absent from `ref`, are UNDEF in the output. "First" occurrence is
|
|
1263
|
+
appearance order (matching the discovery family); on a `ref` with duplicate
|
|
1264
|
+
values this is the earliest address, which can differ from the bsearch lane. */
|
|
1265
|
+
static VALUE
|
|
1266
|
+
rb_ca_locate_addr (VALUE self, VALUE rref)
|
|
1267
|
+
{
|
|
1268
|
+
CArray *ca, *cr;
|
|
1269
|
+
/* Here the *reference* is the argument (self is the query being placed on
|
|
1270
|
+
rref), so the gate runs the other way round: rref reconciles self. The
|
|
1271
|
+
output is an address, so nothing is lifted back. */
|
|
1272
|
+
{
|
|
1273
|
+
volatile VALUE ref_face;
|
|
1274
|
+
rref = fz_face_descend(rref, &ref_face);
|
|
1275
|
+
self = fz_face_reconcile(ref_face, self, "locate_addr");
|
|
1276
|
+
}
|
|
1277
|
+
GetCArray(self, ca);
|
|
1278
|
+
|
|
1279
|
+
int8_t dt = ca->data_type;
|
|
1280
|
+
switch ( dt ) {
|
|
1281
|
+
case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
|
|
1282
|
+
case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
|
|
1283
|
+
case CA_FLOAT32: case CA_FLOAT64:
|
|
1284
|
+
case CA_OBJECT: case CA_FIXLEN:
|
|
1285
|
+
case CA_BOOLEAN:
|
|
1286
|
+
break;
|
|
1287
|
+
default:
|
|
1288
|
+
rb_raise(rb_eCADataTypeError,
|
|
1289
|
+
"__locate_addr__: numeric, object, or fixlen dtype required (got %d)", dt);
|
|
1290
|
+
}
|
|
1291
|
+
if ( ca->ndim < 1 ) {
|
|
1292
|
+
rb_raise(rb_eRuntimeError, "__locate_addr__: need ndim >= 1");
|
|
1293
|
+
}
|
|
1294
|
+
if ( ! RTEST(rb_obj_is_kind_of(rref, rb_cCArray)) ) {
|
|
1295
|
+
rb_raise(rb_eArgError, "__locate_addr__: ref must be a CArray");
|
|
1296
|
+
}
|
|
1297
|
+
GetCArray(rref, cr);
|
|
1298
|
+
if ( cr->data_type != dt || (dt == CA_FIXLEN && cr->bytes != ca->bytes) ) {
|
|
1299
|
+
rb_raise(rb_eCADataTypeError,
|
|
1300
|
+
"__locate_addr__: ref dtype must match self (%d)", dt);
|
|
1301
|
+
}
|
|
1302
|
+
if ( cr->ndim < 1 ) {
|
|
1303
|
+
rb_raise(rb_eRuntimeError, "__locate_addr__: ref need ndim >= 1");
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
fz_hash h;
|
|
1307
|
+
if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
|
|
1308
|
+
else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
|
|
1309
|
+
else { fz_hash_init(&h); }
|
|
1310
|
+
|
|
1311
|
+
/* addr.p[code] = flat address of the first (appearance-order) occurrence of
|
|
1312
|
+
the ref value carrying dense code `code`. Codes are assigned 0,1,2,... in
|
|
1313
|
+
insertion order, so pushing on is_new fills addr.p in code order. */
|
|
1314
|
+
fz_levels addr;
|
|
1315
|
+
fz_levels_init(&addr, (int) sizeof(int64_t));
|
|
1316
|
+
|
|
1317
|
+
/* ---- Phase 1: build value -> first-address map from ref (row-major). ----- */
|
|
1318
|
+
{
|
|
1319
|
+
ca_iter_state st_r;
|
|
1320
|
+
char *p_r;
|
|
1321
|
+
boolean8_t *mr;
|
|
1322
|
+
ca_size_t nr;
|
|
1323
|
+
int8_t raxis = (int8_t) (cr->ndim - 1);
|
|
1324
|
+
ca_size_t base = 0; /* row-major flat offset of the current fiber */
|
|
1325
|
+
|
|
1326
|
+
#define LOC_BUILD(T, WIDEN) \
|
|
1327
|
+
do { \
|
|
1328
|
+
const T *ip = (const T *) p_r; \
|
|
1329
|
+
for ( ca_size_t i = 0; i < nr; i++ ) { \
|
|
1330
|
+
if ( mr && mr[i] ) { continue; } \
|
|
1331
|
+
uint64_t key = (uint64_t) (WIDEN ip[i]); \
|
|
1332
|
+
int is_new; \
|
|
1333
|
+
fz_hash_intern(&h, key, &is_new); \
|
|
1334
|
+
if ( is_new ) { int64_t a = (int64_t) (base + i); \
|
|
1335
|
+
fz_levels_push(&addr, &a); } \
|
|
1336
|
+
} \
|
|
1337
|
+
} while (0)
|
|
1338
|
+
|
|
1339
|
+
#define LOC_BUILD_FLOAT(T, UINT, NANKEY) \
|
|
1340
|
+
do { \
|
|
1341
|
+
const T *ip = (const T *) p_r; \
|
|
1342
|
+
for ( ca_size_t i = 0; i < nr; i++ ) { \
|
|
1343
|
+
if ( mr && mr[i] ) { continue; } \
|
|
1344
|
+
T v = ip[i]; \
|
|
1345
|
+
uint64_t key; \
|
|
1346
|
+
if ( v != v ) { key = (NANKEY); } \
|
|
1347
|
+
else { \
|
|
1348
|
+
if ( v == (T) 0 ) { v = (T) 0; } \
|
|
1349
|
+
UINT bits; \
|
|
1350
|
+
memcpy(&bits, &v, sizeof(bits)); \
|
|
1351
|
+
key = (uint64_t) bits; \
|
|
1352
|
+
} \
|
|
1353
|
+
int is_new; \
|
|
1354
|
+
fz_hash_intern(&h, key, &is_new); \
|
|
1355
|
+
if ( is_new ) { int64_t a = (int64_t) (base + i); \
|
|
1356
|
+
fz_levels_push(&addr, &a); } \
|
|
1357
|
+
} \
|
|
1358
|
+
} while (0)
|
|
1359
|
+
|
|
1360
|
+
#define LOC_BUILD_OBJ \
|
|
1361
|
+
do { \
|
|
1362
|
+
const VALUE *ip = (const VALUE *) p_r; \
|
|
1363
|
+
for ( ca_size_t i = 0; i < nr; i++ ) { \
|
|
1364
|
+
if ( mr && mr[i] ) { continue; } \
|
|
1365
|
+
int is_new; \
|
|
1366
|
+
fz_hash_intern_obj(&h, ip[i], &is_new); \
|
|
1367
|
+
if ( is_new ) { int64_t a = (int64_t) (base + i); \
|
|
1368
|
+
fz_levels_push(&addr, &a); } \
|
|
1369
|
+
} \
|
|
1370
|
+
} while (0)
|
|
1371
|
+
|
|
1372
|
+
#define LOC_BUILD_MEM \
|
|
1373
|
+
do { \
|
|
1374
|
+
int esz = (int) cr->bytes; \
|
|
1375
|
+
for ( ca_size_t i = 0; i < nr; i++ ) { \
|
|
1376
|
+
if ( mr && mr[i] ) { continue; } \
|
|
1377
|
+
int is_new; \
|
|
1378
|
+
fz_hash_intern_mem(&h, p_r + i * esz, &is_new); \
|
|
1379
|
+
if ( is_new ) { int64_t a = (int64_t) (base + i); \
|
|
1380
|
+
fz_levels_push(&addr, &a); } \
|
|
1381
|
+
} \
|
|
1382
|
+
} while (0)
|
|
1383
|
+
|
|
1384
|
+
CA_FOR_EACH_FIBER_MASKED(st_r, cr, raxis, CA_KERNEL_READ, p_r, nr, mr) {
|
|
1385
|
+
switch ( dt ) {
|
|
1386
|
+
case CA_INT8: LOC_BUILD(int8_t, (int64_t)); break;
|
|
1387
|
+
case CA_INT16: LOC_BUILD(int16_t, (int64_t)); break;
|
|
1388
|
+
case CA_INT32: LOC_BUILD(int32_t, (int64_t)); break;
|
|
1389
|
+
case CA_INT64: LOC_BUILD(int64_t, (int64_t)); break;
|
|
1390
|
+
case CA_BOOLEAN: case CA_UINT8: LOC_BUILD(uint8_t, (uint64_t)); break;
|
|
1391
|
+
case CA_UINT16: LOC_BUILD(uint16_t, (uint64_t)); break;
|
|
1392
|
+
case CA_UINT32: LOC_BUILD(uint32_t, (uint64_t)); break;
|
|
1393
|
+
case CA_UINT64: LOC_BUILD(uint64_t, (uint64_t)); break;
|
|
1394
|
+
case CA_FLOAT32: LOC_BUILD_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
|
|
1395
|
+
case CA_FLOAT64: LOC_BUILD_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
|
|
1396
|
+
case CA_OBJECT: LOC_BUILD_OBJ; break;
|
|
1397
|
+
case CA_FIXLEN: LOC_BUILD_MEM; break;
|
|
1398
|
+
}
|
|
1399
|
+
base += nr;
|
|
1400
|
+
}
|
|
1401
|
+
#undef LOC_BUILD
|
|
1402
|
+
#undef LOC_BUILD_FLOAT
|
|
1403
|
+
#undef LOC_BUILD_OBJ
|
|
1404
|
+
#undef LOC_BUILD_MEM
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/* ---- Phase 2: probe every cell of self, writing the int64 address + UNDEF. */
|
|
1408
|
+
VALUE vout = rb_carray_new(CA_INT64, ca->ndim, ca->dim, 0, NULL);
|
|
1409
|
+
CArray *cout;
|
|
1410
|
+
GetCArray(vout, cout);
|
|
1411
|
+
int64_t *out = (int64_t *) cout->ptr;
|
|
1412
|
+
boolean8_t *um = ALLOC_N(boolean8_t, ca->elements); /* undef flags scratch */
|
|
1413
|
+
ca_size_t n_undef = 0;
|
|
1414
|
+
{
|
|
1415
|
+
ca_iter_state st_in;
|
|
1416
|
+
char *p_in;
|
|
1417
|
+
boolean8_t *m;
|
|
1418
|
+
ca_size_t n;
|
|
1419
|
+
int8_t axis = (int8_t) (ca->ndim - 1);
|
|
1420
|
+
ca_size_t base = 0;
|
|
1421
|
+
|
|
1422
|
+
#define LOC_PROBE(T, WIDEN) \
|
|
1423
|
+
do { \
|
|
1424
|
+
const T *ip = (const T *) p_in; \
|
|
1425
|
+
const int64_t *ab = (const int64_t *) addr.p; \
|
|
1426
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1427
|
+
ca_size_t o = base + i; \
|
|
1428
|
+
if ( m && m[i] ) { out[o] = 0; um[o] = 1; n_undef++; continue; } \
|
|
1429
|
+
uint64_t key = (uint64_t) (WIDEN ip[i]); \
|
|
1430
|
+
int32_t code; \
|
|
1431
|
+
if ( fz_hash_lookup(&h, key, &code) ) { out[o] = ab[code]; um[o] = 0; } \
|
|
1432
|
+
else { out[o] = 0; um[o] = 1; n_undef++; } \
|
|
1433
|
+
} \
|
|
1434
|
+
} while (0)
|
|
1435
|
+
|
|
1436
|
+
#define LOC_PROBE_FLOAT(T, UINT, NANKEY) \
|
|
1437
|
+
do { \
|
|
1438
|
+
const T *ip = (const T *) p_in; \
|
|
1439
|
+
const int64_t *ab = (const int64_t *) addr.p; \
|
|
1440
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1441
|
+
ca_size_t o = base + i; \
|
|
1442
|
+
if ( m && m[i] ) { out[o] = 0; um[o] = 1; n_undef++; continue; } \
|
|
1443
|
+
T v = ip[i]; \
|
|
1444
|
+
uint64_t key; \
|
|
1445
|
+
if ( v != v ) { key = (NANKEY); } \
|
|
1446
|
+
else { \
|
|
1447
|
+
if ( v == (T) 0 ) { v = (T) 0; } \
|
|
1448
|
+
UINT bits; \
|
|
1449
|
+
memcpy(&bits, &v, sizeof(bits)); \
|
|
1450
|
+
key = (uint64_t) bits; \
|
|
1451
|
+
} \
|
|
1452
|
+
int32_t code; \
|
|
1453
|
+
if ( fz_hash_lookup(&h, key, &code) ) { out[o] = ab[code]; um[o] = 0; } \
|
|
1454
|
+
else { out[o] = 0; um[o] = 1; n_undef++; } \
|
|
1455
|
+
} \
|
|
1456
|
+
} while (0)
|
|
1457
|
+
|
|
1458
|
+
#define LOC_PROBE_OBJ \
|
|
1459
|
+
do { \
|
|
1460
|
+
const VALUE *ip = (const VALUE *) p_in; \
|
|
1461
|
+
const int64_t *ab = (const int64_t *) addr.p; \
|
|
1462
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1463
|
+
ca_size_t o = base + i; \
|
|
1464
|
+
if ( m && m[i] ) { out[o] = 0; um[o] = 1; n_undef++; continue; } \
|
|
1465
|
+
int32_t code; \
|
|
1466
|
+
if ( fz_hash_lookup_obj(&h, ip[i], &code) ) { out[o] = ab[code]; um[o] = 0; } \
|
|
1467
|
+
else { out[o] = 0; um[o] = 1; n_undef++; } \
|
|
1468
|
+
} \
|
|
1469
|
+
} while (0)
|
|
1470
|
+
|
|
1471
|
+
#define LOC_PROBE_MEM \
|
|
1472
|
+
do { \
|
|
1473
|
+
const int64_t *ab = (const int64_t *) addr.p; \
|
|
1474
|
+
int esz = (int) ca->bytes; \
|
|
1475
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1476
|
+
ca_size_t o = base + i; \
|
|
1477
|
+
if ( m && m[i] ) { out[o] = 0; um[o] = 1; n_undef++; continue; } \
|
|
1478
|
+
int32_t code; \
|
|
1479
|
+
if ( fz_hash_lookup_mem(&h, p_in + i * esz, &code) ) { out[o] = ab[code]; um[o] = 0; } \
|
|
1480
|
+
else { out[o] = 0; um[o] = 1; n_undef++; } \
|
|
1481
|
+
} \
|
|
1482
|
+
} while (0)
|
|
1483
|
+
|
|
1484
|
+
CA_FOR_EACH_FIBER_MASKED(st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
|
|
1485
|
+
switch ( dt ) {
|
|
1486
|
+
case CA_INT8: LOC_PROBE(int8_t, (int64_t)); break;
|
|
1487
|
+
case CA_INT16: LOC_PROBE(int16_t, (int64_t)); break;
|
|
1488
|
+
case CA_INT32: LOC_PROBE(int32_t, (int64_t)); break;
|
|
1489
|
+
case CA_INT64: LOC_PROBE(int64_t, (int64_t)); break;
|
|
1490
|
+
case CA_BOOLEAN: case CA_UINT8: LOC_PROBE(uint8_t, (uint64_t)); break;
|
|
1491
|
+
case CA_UINT16: LOC_PROBE(uint16_t, (uint64_t)); break;
|
|
1492
|
+
case CA_UINT32: LOC_PROBE(uint32_t, (uint64_t)); break;
|
|
1493
|
+
case CA_UINT64: LOC_PROBE(uint64_t, (uint64_t)); break;
|
|
1494
|
+
case CA_FLOAT32: LOC_PROBE_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
|
|
1495
|
+
case CA_FLOAT64: LOC_PROBE_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
|
|
1496
|
+
case CA_OBJECT: LOC_PROBE_OBJ; break;
|
|
1497
|
+
case CA_FIXLEN: LOC_PROBE_MEM; break;
|
|
1498
|
+
}
|
|
1499
|
+
base += n;
|
|
1500
|
+
}
|
|
1501
|
+
#undef LOC_PROBE
|
|
1502
|
+
#undef LOC_PROBE_FLOAT
|
|
1503
|
+
#undef LOC_PROBE_OBJ
|
|
1504
|
+
#undef LOC_PROBE_MEM
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
fz_hash_free(&h);
|
|
1508
|
+
fz_levels_free(&addr);
|
|
1509
|
+
|
|
1510
|
+
/* Attach an output mask only when some cell is UNDEF (miss or masked self), so
|
|
1511
|
+
an all-hit locate stays mask-free. */
|
|
1512
|
+
if ( n_undef > 0 ) {
|
|
1513
|
+
ca_create_mask(cout);
|
|
1514
|
+
memcpy(cout->mask->ptr, um, (size_t) ca->elements);
|
|
1515
|
+
}
|
|
1516
|
+
xfree(um);
|
|
1517
|
+
|
|
1518
|
+
return vout;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/* Shared body of __intersection__ (keep_when_hit = 1) and __difference__
|
|
1522
|
+
(keep_when_hit = 0): the distinct values of self that are (resp. are not)
|
|
1523
|
+
present in `other`, in self's first-appearance order, as a 1-D CArray of
|
|
1524
|
+
self's dtype. Two seen-sets: `hoth` built from `other` is the probe set;
|
|
1525
|
+
`hself` dedups self so each distinct value is decided once. Masked cells of
|
|
1526
|
+
either array do not participate. Distinctness is the discovery family's per
|
|
1527
|
+
lane (numeric `==` + NaN collapse + -0.0 == +0.0, object hash/eql? + NaN
|
|
1528
|
+
collapse, fixlen byte equality). */
|
|
1529
|
+
static VALUE
|
|
1530
|
+
fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
|
|
1531
|
+
{
|
|
1532
|
+
CArray *ca, *co;
|
|
1533
|
+
volatile VALUE face;
|
|
1534
|
+
self = fz_face_descend(self, &face);
|
|
1535
|
+
rother = fz_face_reconcile(face, rother, "set relation");
|
|
1536
|
+
GetCArray(self, ca);
|
|
1537
|
+
|
|
1538
|
+
int8_t dt = ca->data_type;
|
|
1539
|
+
switch ( dt ) {
|
|
1540
|
+
case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
|
|
1541
|
+
case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
|
|
1542
|
+
case CA_FLOAT32: case CA_FLOAT64:
|
|
1543
|
+
case CA_OBJECT: case CA_FIXLEN:
|
|
1544
|
+
case CA_BOOLEAN:
|
|
1545
|
+
break;
|
|
1546
|
+
default:
|
|
1547
|
+
rb_raise(rb_eCADataTypeError,
|
|
1548
|
+
"set relation: numeric, object, or fixlen dtype required (got %d)", dt);
|
|
1549
|
+
}
|
|
1550
|
+
if ( ca->ndim < 1 ) {
|
|
1551
|
+
rb_raise(rb_eRuntimeError, "set relation: need ndim >= 1");
|
|
1552
|
+
}
|
|
1553
|
+
if ( ! RTEST(rb_obj_is_kind_of(rother, rb_cCArray)) ) {
|
|
1554
|
+
rb_raise(rb_eArgError, "set relation: other must be a CArray");
|
|
1555
|
+
}
|
|
1556
|
+
GetCArray(rother, co);
|
|
1557
|
+
if ( co->data_type != dt || (dt == CA_FIXLEN && co->bytes != ca->bytes) ) {
|
|
1558
|
+
rb_raise(rb_eCADataTypeError, "set relation: other dtype must match self (%d)", dt);
|
|
1559
|
+
}
|
|
1560
|
+
if ( co->ndim < 1 ) {
|
|
1561
|
+
rb_raise(rb_eRuntimeError, "set relation: other need ndim >= 1");
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
fz_hash hoth, hself;
|
|
1565
|
+
if ( dt == CA_OBJECT ) {
|
|
1566
|
+
fz_hash_init_obj(&hoth); fz_hash_init_obj(&hself);
|
|
1567
|
+
}
|
|
1568
|
+
else if ( dt == CA_FIXLEN ) {
|
|
1569
|
+
fz_hash_init_mem(&hoth, (int) ca->bytes); fz_hash_init_mem(&hself, (int) ca->bytes);
|
|
1570
|
+
}
|
|
1571
|
+
else {
|
|
1572
|
+
fz_hash_init(&hoth); fz_hash_init(&hself);
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
fz_levels lv;
|
|
1576
|
+
fz_levels_init(&lv, (int) ca->bytes);
|
|
1577
|
+
|
|
1578
|
+
/* Build the probe set from every non-masked cell of other. */
|
|
1579
|
+
fz_intern_all(&hoth, co, NULL);
|
|
1580
|
+
|
|
1581
|
+
/* Walk self: hself dedups, so each distinct self value is decided once; keep
|
|
1582
|
+
it when its membership in other equals keep_when_hit. */
|
|
1583
|
+
int8_t axis = (int8_t) (ca->ndim - 1);
|
|
1584
|
+
ca_iter_state st;
|
|
1585
|
+
char *p;
|
|
1586
|
+
boolean8_t *m;
|
|
1587
|
+
ca_size_t n;
|
|
1588
|
+
|
|
1589
|
+
#define SR(T, WIDEN) \
|
|
1590
|
+
do { \
|
|
1591
|
+
const T *ip = (const T *) p; \
|
|
1592
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1593
|
+
if ( m && m[i] ) { continue; } \
|
|
1594
|
+
uint64_t key = (uint64_t) (WIDEN ip[i]); \
|
|
1595
|
+
int is_new; \
|
|
1596
|
+
fz_hash_intern(&hself, key, &is_new); \
|
|
1597
|
+
if ( is_new && fz_hash_lookup(&hoth, key, NULL) == keep_when_hit ) { \
|
|
1598
|
+
fz_levels_push(&lv, &ip[i]); \
|
|
1599
|
+
} \
|
|
1600
|
+
} \
|
|
1601
|
+
} while (0)
|
|
1602
|
+
|
|
1603
|
+
#define SR_FLOAT(T, UINT, NANKEY) \
|
|
1604
|
+
do { \
|
|
1605
|
+
const T *ip = (const T *) p; \
|
|
1606
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1607
|
+
if ( m && m[i] ) { continue; } \
|
|
1608
|
+
T v = ip[i]; \
|
|
1609
|
+
uint64_t key; \
|
|
1610
|
+
if ( v != v ) { key = (NANKEY); } \
|
|
1611
|
+
else { \
|
|
1612
|
+
if ( v == (T) 0 ) { v = (T) 0; } \
|
|
1613
|
+
UINT bits; \
|
|
1614
|
+
memcpy(&bits, &v, sizeof(bits)); \
|
|
1615
|
+
key = (uint64_t) bits; \
|
|
1616
|
+
} \
|
|
1617
|
+
int is_new; \
|
|
1618
|
+
fz_hash_intern(&hself, key, &is_new); \
|
|
1619
|
+
if ( is_new && fz_hash_lookup(&hoth, key, NULL) == keep_when_hit ) { \
|
|
1620
|
+
fz_levels_push(&lv, &ip[i]); \
|
|
1621
|
+
} \
|
|
1622
|
+
} \
|
|
1623
|
+
} while (0)
|
|
1624
|
+
|
|
1625
|
+
#define SR_OBJ \
|
|
1626
|
+
do { \
|
|
1627
|
+
const VALUE *ip = (const VALUE *) p; \
|
|
1628
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1629
|
+
if ( m && m[i] ) { continue; } \
|
|
1630
|
+
int is_new; \
|
|
1631
|
+
fz_hash_intern_obj(&hself, ip[i], &is_new); \
|
|
1632
|
+
if ( is_new && fz_hash_lookup_obj(&hoth, ip[i], NULL) == keep_when_hit ) { \
|
|
1633
|
+
fz_levels_push(&lv, &ip[i]); \
|
|
1634
|
+
} \
|
|
1635
|
+
} \
|
|
1636
|
+
} while (0)
|
|
1637
|
+
|
|
1638
|
+
#define SR_MEM \
|
|
1639
|
+
do { \
|
|
1640
|
+
int esz = (int) ca->bytes; \
|
|
1641
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1642
|
+
if ( m && m[i] ) { continue; } \
|
|
1643
|
+
const char *e = p + i * esz; \
|
|
1644
|
+
int is_new; \
|
|
1645
|
+
fz_hash_intern_mem(&hself, e, &is_new); \
|
|
1646
|
+
if ( is_new && fz_hash_lookup_mem(&hoth, e, NULL) == keep_when_hit ) { \
|
|
1647
|
+
fz_levels_push(&lv, e); \
|
|
1648
|
+
} \
|
|
1649
|
+
} \
|
|
1650
|
+
} while (0)
|
|
1651
|
+
|
|
1652
|
+
CA_FOR_EACH_FIBER_MASKED(st, ca, axis, CA_KERNEL_READ, p, n, m) {
|
|
1653
|
+
switch ( dt ) {
|
|
1654
|
+
case CA_INT8: SR(int8_t, (int64_t)); break;
|
|
1655
|
+
case CA_INT16: SR(int16_t, (int64_t)); break;
|
|
1656
|
+
case CA_INT32: SR(int32_t, (int64_t)); break;
|
|
1657
|
+
case CA_INT64: SR(int64_t, (int64_t)); break;
|
|
1658
|
+
case CA_BOOLEAN: case CA_UINT8: SR(uint8_t, (uint64_t)); break;
|
|
1659
|
+
case CA_UINT16: SR(uint16_t, (uint64_t)); break;
|
|
1660
|
+
case CA_UINT32: SR(uint32_t, (uint64_t)); break;
|
|
1661
|
+
case CA_UINT64: SR(uint64_t, (uint64_t)); break;
|
|
1662
|
+
case CA_FLOAT32: SR_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
|
|
1663
|
+
case CA_FLOAT64: SR_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
|
|
1664
|
+
case CA_OBJECT: SR_OBJ; break;
|
|
1665
|
+
case CA_FIXLEN: SR_MEM; break;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
#undef SR
|
|
1669
|
+
#undef SR_FLOAT
|
|
1670
|
+
#undef SR_OBJ
|
|
1671
|
+
#undef SR_MEM
|
|
1672
|
+
|
|
1673
|
+
ca_size_t k = lv.n;
|
|
1674
|
+
fz_hash_free(&hoth);
|
|
1675
|
+
fz_hash_free(&hself);
|
|
1676
|
+
|
|
1677
|
+
ca_size_t ldim[1];
|
|
1678
|
+
ldim[0] = k;
|
|
1679
|
+
VALUE vlev = rb_carray_new(dt, 1, ldim, (dt == CA_FIXLEN) ? ca->bytes : 0, NULL);
|
|
1680
|
+
CArray *clev;
|
|
1681
|
+
GetCArray(vlev, clev);
|
|
1682
|
+
if ( k > 0 ) {
|
|
1683
|
+
memcpy(clev->ptr, lv.p, (size_t) k * (size_t) ca->bytes);
|
|
1684
|
+
}
|
|
1685
|
+
fz_levels_free(&lv);
|
|
1686
|
+
|
|
1687
|
+
return fz_face_relift(vlev, face); /* set *values*: give the Face back */
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
/* @overload __intersection__(other)
|
|
1691
|
+
INTERNAL (CArray#intersection). 1-D CArray of the distinct values present in
|
|
1692
|
+
both self and other, in self's first-appearance order. */
|
|
1693
|
+
static VALUE
|
|
1694
|
+
rb_ca_intersection (VALUE self, VALUE rother)
|
|
1695
|
+
{
|
|
1696
|
+
return fz_set_relation(self, rother, 1);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
/* @overload __difference__(other)
|
|
1700
|
+
INTERNAL (CArray#difference). 1-D CArray of the distinct values in self that
|
|
1701
|
+
are absent from other, in self's first-appearance order. */
|
|
1702
|
+
static VALUE
|
|
1703
|
+
rb_ca_difference (VALUE self, VALUE rother)
|
|
1704
|
+
{
|
|
1705
|
+
return fz_set_relation(self, rother, 0);
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/* @overload __union__(other)
|
|
1709
|
+
INTERNAL (CArray#union). 1-D CArray of the distinct values appearing in either
|
|
1710
|
+
self or other, in self-then-other first-appearance order. One seen-set spans
|
|
1711
|
+
both arrays; each distinct value is pushed on its first appearance (self's
|
|
1712
|
+
distinct values first, then other's not-yet-seen ones). Masked cells of
|
|
1713
|
+
either array do not participate. */
|
|
1714
|
+
static VALUE
|
|
1715
|
+
rb_ca_set_union (VALUE self, VALUE rother)
|
|
1716
|
+
{
|
|
1717
|
+
CArray *ca, *co;
|
|
1718
|
+
volatile VALUE face;
|
|
1719
|
+
self = fz_face_descend(self, &face);
|
|
1720
|
+
rother = fz_face_reconcile(face, rother, "union");
|
|
1721
|
+
GetCArray(self, ca);
|
|
1722
|
+
|
|
1723
|
+
int8_t dt = ca->data_type;
|
|
1724
|
+
switch ( dt ) {
|
|
1725
|
+
case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
|
|
1726
|
+
case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
|
|
1727
|
+
case CA_FLOAT32: case CA_FLOAT64:
|
|
1728
|
+
case CA_OBJECT: case CA_FIXLEN:
|
|
1729
|
+
case CA_BOOLEAN:
|
|
1730
|
+
break;
|
|
1731
|
+
default:
|
|
1732
|
+
rb_raise(rb_eCADataTypeError,
|
|
1733
|
+
"__union__: numeric, object, or fixlen dtype required (got %d)", dt);
|
|
1734
|
+
}
|
|
1735
|
+
if ( ca->ndim < 1 ) {
|
|
1736
|
+
rb_raise(rb_eRuntimeError, "__union__: need ndim >= 1");
|
|
1737
|
+
}
|
|
1738
|
+
if ( ! RTEST(rb_obj_is_kind_of(rother, rb_cCArray)) ) {
|
|
1739
|
+
rb_raise(rb_eArgError, "__union__: other must be a CArray");
|
|
1740
|
+
}
|
|
1741
|
+
GetCArray(rother, co);
|
|
1742
|
+
if ( co->data_type != dt || (dt == CA_FIXLEN && co->bytes != ca->bytes) ) {
|
|
1743
|
+
rb_raise(rb_eCADataTypeError, "__union__: other dtype must match self (%d)", dt);
|
|
1744
|
+
}
|
|
1745
|
+
if ( co->ndim < 1 ) {
|
|
1746
|
+
rb_raise(rb_eRuntimeError, "__union__: other need ndim >= 1");
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
fz_hash h;
|
|
1750
|
+
if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
|
|
1751
|
+
else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
|
|
1752
|
+
else { fz_hash_init(&h); }
|
|
1753
|
+
|
|
1754
|
+
fz_levels lv;
|
|
1755
|
+
fz_levels_init(&lv, (int) ca->bytes);
|
|
1756
|
+
|
|
1757
|
+
fz_intern_all(&h, ca, &lv); /* self's distinct values, appearance order */
|
|
1758
|
+
fz_intern_all(&h, co, &lv); /* + other's not-yet-seen distinct values */
|
|
1759
|
+
|
|
1760
|
+
ca_size_t k = lv.n;
|
|
1761
|
+
fz_hash_free(&h);
|
|
1762
|
+
|
|
1763
|
+
ca_size_t ldim[1];
|
|
1764
|
+
ldim[0] = k;
|
|
1765
|
+
VALUE vlev = rb_carray_new(dt, 1, ldim, (dt == CA_FIXLEN) ? ca->bytes : 0, NULL);
|
|
1766
|
+
CArray *clev;
|
|
1767
|
+
GetCArray(vlev, clev);
|
|
1768
|
+
if ( k > 0 ) {
|
|
1769
|
+
memcpy(clev->ptr, lv.p, (size_t) k * (size_t) ca->bytes);
|
|
1770
|
+
}
|
|
1771
|
+
fz_levels_free(&lv);
|
|
1772
|
+
|
|
1773
|
+
return fz_face_relift(vlev, face); /* set *values*: give the Face back */
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
/* @overload __value_counts_flat__
|
|
1777
|
+
|
|
1778
|
+
INTERNAL (CArray#value_counts). Collect the distinct values of self in
|
|
1779
|
+
first-appearance (row-major flatten) order together with the number of times
|
|
1780
|
+
each occurs, one linear pass, no sort.
|
|
1781
|
+
Returns [levels, counts]:
|
|
1782
|
+
levels = 1-D CArray of source dtype, the k distinct values in appearance
|
|
1783
|
+
order (identical to __unique_flat__).
|
|
1784
|
+
counts = 1-D CA_INT64 of length k, counts[i] = occurrences of levels[i].
|
|
1785
|
+
Masked cells do not participate (skipped, not counted). Numeric distinctness
|
|
1786
|
+
follows the discovery family: all NaN collapse to one distinct value (their
|
|
1787
|
+
counts add up) and -0.0 == +0.0. Object keys on rb_hash + rb_eql and fixlen on
|
|
1788
|
+
a byte-hash + memcmp, reproducing Ruby Hash distinctness; the object lane also
|
|
1789
|
+
collapses every Float NaN to one value (their counts add up, as numeric does).
|
|
1790
|
+
*/
|
|
1791
|
+
static VALUE
|
|
1792
|
+
rb_ca_value_counts_flat (VALUE self)
|
|
1793
|
+
{
|
|
1794
|
+
CArray *ca;
|
|
1795
|
+
volatile VALUE face;
|
|
1796
|
+
self = fz_face_descend(self, &face);
|
|
1797
|
+
TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
|
|
1798
|
+
|
|
1799
|
+
int8_t dt = ca->data_type;
|
|
1800
|
+
switch ( dt ) {
|
|
1801
|
+
case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
|
|
1802
|
+
case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
|
|
1803
|
+
case CA_FLOAT32: case CA_FLOAT64:
|
|
1804
|
+
case CA_OBJECT: case CA_FIXLEN:
|
|
1805
|
+
case CA_BOOLEAN:
|
|
1806
|
+
break;
|
|
1807
|
+
default:
|
|
1808
|
+
rb_raise(rb_eCADataTypeError,
|
|
1809
|
+
"__value_counts_flat__: numeric, object, or fixlen dtype required (got %d)", dt);
|
|
1810
|
+
}
|
|
1811
|
+
if ( ca->ndim < 1 ) {
|
|
1812
|
+
rb_raise(rb_eRuntimeError, "__value_counts_flat__: need ndim >= 1");
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
fz_hash h;
|
|
1816
|
+
fz_levels lv; /* distinct values, appearance order */
|
|
1817
|
+
fz_levels ct; /* int64 count per code (code == push index) */
|
|
1818
|
+
if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
|
|
1819
|
+
else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
|
|
1820
|
+
else { fz_hash_init(&h); }
|
|
1821
|
+
fz_levels_init(&lv, (int) ca->bytes);
|
|
1822
|
+
fz_levels_init(&ct, (int) sizeof(int64_t));
|
|
1823
|
+
|
|
1824
|
+
int8_t axis = (int8_t) (ca->ndim - 1); /* one seen-set over the whole array */
|
|
1825
|
+
ca_iter_state st_in;
|
|
1826
|
+
char *p_in;
|
|
1827
|
+
boolean8_t *m;
|
|
1828
|
+
ca_size_t n;
|
|
1829
|
+
|
|
1830
|
+
#define VC_LOOP(T, WIDEN) \
|
|
1831
|
+
do { \
|
|
1832
|
+
const T *ip = (const T *) p_in; \
|
|
1833
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1834
|
+
if ( m && m[i] ) { continue; } \
|
|
1835
|
+
uint64_t key = (uint64_t) (WIDEN ip[i]); \
|
|
1836
|
+
int is_new; \
|
|
1837
|
+
int32_t code = fz_hash_intern(&h, key, &is_new); \
|
|
1838
|
+
if ( is_new ) { \
|
|
1839
|
+
int64_t one = 1; \
|
|
1840
|
+
fz_levels_push(&lv, &ip[i]); \
|
|
1841
|
+
fz_levels_push(&ct, &one); \
|
|
1842
|
+
} else { \
|
|
1843
|
+
((int64_t *) ct.p)[code]++; \
|
|
1844
|
+
} \
|
|
1845
|
+
} \
|
|
1846
|
+
} while (0)
|
|
1847
|
+
|
|
1848
|
+
#define VC_LOOP_FLOAT(T, UINT, NANKEY) \
|
|
1849
|
+
do { \
|
|
1850
|
+
const T *ip = (const T *) p_in; \
|
|
1851
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1852
|
+
if ( m && m[i] ) { continue; } \
|
|
1853
|
+
T v = ip[i]; \
|
|
1854
|
+
uint64_t key; \
|
|
1855
|
+
if ( v != v ) { key = (NANKEY); } \
|
|
1856
|
+
else { \
|
|
1857
|
+
if ( v == (T) 0 ) { v = (T) 0; } \
|
|
1858
|
+
UINT bits; \
|
|
1859
|
+
memcpy(&bits, &v, sizeof(bits)); \
|
|
1860
|
+
key = (uint64_t) bits; \
|
|
1861
|
+
} \
|
|
1862
|
+
int is_new; \
|
|
1863
|
+
int32_t code = fz_hash_intern(&h, key, &is_new); \
|
|
1864
|
+
if ( is_new ) { \
|
|
1865
|
+
int64_t one = 1; \
|
|
1866
|
+
fz_levels_push(&lv, &ip[i]); \
|
|
1867
|
+
fz_levels_push(&ct, &one); \
|
|
1868
|
+
} else { \
|
|
1869
|
+
((int64_t *) ct.p)[code]++; \
|
|
1870
|
+
} \
|
|
1871
|
+
} \
|
|
1872
|
+
} while (0)
|
|
1873
|
+
|
|
1874
|
+
/* Object: rb_hash + rb_eql lane; the level is the first-seen VALUE. */
|
|
1875
|
+
#define VC_LOOP_OBJ \
|
|
1876
|
+
do { \
|
|
1877
|
+
const VALUE *ip = (const VALUE *) p_in; \
|
|
1878
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1879
|
+
if ( m && m[i] ) { continue; } \
|
|
1880
|
+
int is_new; \
|
|
1881
|
+
int32_t code = fz_hash_intern_obj(&h, ip[i], &is_new); \
|
|
1882
|
+
if ( is_new ) { \
|
|
1883
|
+
int64_t one = 1; \
|
|
1884
|
+
fz_levels_push(&lv, &ip[i]); \
|
|
1885
|
+
fz_levels_push(&ct, &one); \
|
|
1886
|
+
} else { \
|
|
1887
|
+
((int64_t *) ct.p)[code]++; \
|
|
1888
|
+
} \
|
|
1889
|
+
} \
|
|
1890
|
+
} while (0)
|
|
1891
|
+
|
|
1892
|
+
/* Fixlen: byte-hash + memcmp lane; the level is the first-seen element bytes. */
|
|
1893
|
+
#define VC_LOOP_MEM \
|
|
1894
|
+
do { \
|
|
1895
|
+
int esz = (int) ca->bytes; \
|
|
1896
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
1897
|
+
if ( m && m[i] ) { continue; } \
|
|
1898
|
+
const char *e = p_in + i * esz; \
|
|
1899
|
+
int is_new; \
|
|
1900
|
+
int32_t code = fz_hash_intern_mem(&h, e, &is_new); \
|
|
1901
|
+
if ( is_new ) { \
|
|
1902
|
+
int64_t one = 1; \
|
|
1903
|
+
fz_levels_push(&lv, e); \
|
|
1904
|
+
fz_levels_push(&ct, &one); \
|
|
1905
|
+
} else { \
|
|
1906
|
+
((int64_t *) ct.p)[code]++; \
|
|
1907
|
+
} \
|
|
1908
|
+
} \
|
|
1909
|
+
} while (0)
|
|
1910
|
+
|
|
1911
|
+
CA_FOR_EACH_FIBER_MASKED(st_in, ca, axis, CA_KERNEL_READ, p_in, n, m) {
|
|
1912
|
+
switch ( dt ) {
|
|
1913
|
+
case CA_INT8: VC_LOOP(int8_t, (int64_t)); break;
|
|
1914
|
+
case CA_INT16: VC_LOOP(int16_t, (int64_t)); break;
|
|
1915
|
+
case CA_INT32: VC_LOOP(int32_t, (int64_t)); break;
|
|
1916
|
+
case CA_INT64: VC_LOOP(int64_t, (int64_t)); break;
|
|
1917
|
+
case CA_BOOLEAN: case CA_UINT8: VC_LOOP(uint8_t, (uint64_t)); break;
|
|
1918
|
+
case CA_UINT16: VC_LOOP(uint16_t, (uint64_t)); break;
|
|
1919
|
+
case CA_UINT32: VC_LOOP(uint32_t, (uint64_t)); break;
|
|
1920
|
+
case CA_UINT64: VC_LOOP(uint64_t, (uint64_t)); break;
|
|
1921
|
+
case CA_FLOAT32: VC_LOOP_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
|
|
1922
|
+
case CA_FLOAT64: VC_LOOP_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
|
|
1923
|
+
case CA_OBJECT: VC_LOOP_OBJ; break;
|
|
1924
|
+
case CA_FIXLEN: VC_LOOP_MEM; break;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
#undef VC_LOOP
|
|
1928
|
+
#undef VC_LOOP_FLOAT
|
|
1929
|
+
#undef VC_LOOP_OBJ
|
|
1930
|
+
#undef VC_LOOP_MEM
|
|
1931
|
+
|
|
1932
|
+
ca_size_t k = h.n;
|
|
1933
|
+
fz_hash_free(&h);
|
|
1934
|
+
|
|
1935
|
+
ca_size_t ldim[1];
|
|
1936
|
+
ldim[0] = k;
|
|
1937
|
+
/* CA_FIXLEN carries its element width; numeric / object use bytes = 0. */
|
|
1938
|
+
VALUE vlev = rb_carray_new(dt, 1, ldim, (dt == CA_FIXLEN) ? ca->bytes : 0, NULL);
|
|
1939
|
+
CArray *clev;
|
|
1940
|
+
TypedData_Get_Struct(vlev, CArray, &carray_data_type, clev);
|
|
1941
|
+
VALUE vcnt = rb_carray_new(CA_INT64, 1, ldim, 0, NULL);
|
|
1942
|
+
CArray *ccnt;
|
|
1943
|
+
TypedData_Get_Struct(vcnt, CArray, &carray_data_type, ccnt);
|
|
1944
|
+
if ( k > 0 ) {
|
|
1945
|
+
memcpy(clev->ptr, lv.p, (size_t) k * (size_t) ca->bytes);
|
|
1946
|
+
memcpy(ccnt->ptr, ct.p, (size_t) k * sizeof(int64_t));
|
|
1947
|
+
}
|
|
1948
|
+
fz_levels_free(&lv);
|
|
1949
|
+
fz_levels_free(&ct);
|
|
1950
|
+
|
|
1951
|
+
/* values carry the Face, counts stay plain int64 */
|
|
1952
|
+
return rb_ary_new3(2, fz_face_relift(vlev, face), vcnt);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
/* @overload __nunique__(axis, keep_axis)
|
|
1956
|
+
|
|
1957
|
+
INTERNAL (CArray#nunique). Count the distinct values along axis, one linear
|
|
1958
|
+
pass per fiber with an independent seen-set. Returns a reduced
|
|
1959
|
+
CA_INT64 CArray of self.shape with axis removed (or kept as length-1 when
|
|
1960
|
+
keep_axis). Masked cells do not participate; an all-masked fiber counts 0
|
|
1961
|
+
(an empty set has zero distinct values, not UNDEF -- nunique has identity 0).
|
|
1962
|
+
Numeric distinctness collapses all NaN to one value and treats -0.0 == +0.0;
|
|
1963
|
+
object keys on rb_hash + rb_eql and fixlen on a byte-hash + memcmp, reproducing
|
|
1964
|
+
Ruby Hash distinctness; the object lane also collapses every Float NaN to one.
|
|
1965
|
+
*/
|
|
1966
|
+
static VALUE
|
|
1967
|
+
rb_ca_nunique (VALUE self, VALUE vaxis, VALUE vkeep)
|
|
1968
|
+
{
|
|
1969
|
+
CArray *ca;
|
|
1970
|
+
TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
|
|
1971
|
+
|
|
1972
|
+
int8_t dt = ca->data_type;
|
|
1973
|
+
switch ( dt ) {
|
|
1974
|
+
case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
|
|
1975
|
+
case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
|
|
1976
|
+
case CA_FLOAT32: case CA_FLOAT64:
|
|
1977
|
+
case CA_OBJECT: case CA_FIXLEN:
|
|
1978
|
+
case CA_BOOLEAN:
|
|
1979
|
+
break;
|
|
1980
|
+
default:
|
|
1981
|
+
rb_raise(rb_eCADataTypeError,
|
|
1982
|
+
"__nunique__: numeric, object, or fixlen dtype required (got %d)", dt);
|
|
1983
|
+
}
|
|
1984
|
+
if ( ca->ndim < 1 ) {
|
|
1985
|
+
rb_raise(rb_eRuntimeError, "__nunique__: need ndim >= 1");
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
int axis = NUM2INT(vaxis);
|
|
1989
|
+
if ( axis < 0 || axis >= ca->ndim ) {
|
|
1990
|
+
rb_raise(rb_eArgError, "__nunique__: axis %d out of range", axis);
|
|
1991
|
+
}
|
|
1992
|
+
int keep_axis = RTEST(vkeep);
|
|
1993
|
+
|
|
1994
|
+
int8_t ax = (int8_t) axis;
|
|
1995
|
+
VALUE vout = rb_ca_new_reduced(self, &ax, 1, CA_INT64, keep_axis);
|
|
1996
|
+
CArray *cout;
|
|
1997
|
+
TypedData_Get_Struct(vout, CArray, &carray_data_type, cout);
|
|
1998
|
+
int64_t *op = (int64_t *) cout->ptr;
|
|
1999
|
+
|
|
2000
|
+
fz_hash h;
|
|
2001
|
+
if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
|
|
2002
|
+
else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
|
|
2003
|
+
else { fz_hash_init(&h); }
|
|
2004
|
+
|
|
2005
|
+
ca_iter_state st;
|
|
2006
|
+
int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES, &ax, 1, 0);
|
|
2007
|
+
if ( rc != CA_ITER_OK ) {
|
|
2008
|
+
fz_hash_free(&h);
|
|
2009
|
+
rb_raise(rb_eRuntimeError, "__nunique__: kernel_iterator init failed rc=%d", rc);
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
char *p;
|
|
2013
|
+
boolean8_t *m;
|
|
2014
|
+
ca_size_t out_i = 0;
|
|
2015
|
+
ca_size_t dummy; /* CA_SLAB_REDUCE_T needs an acc lvalue; unused here */
|
|
2016
|
+
|
|
2017
|
+
/* Intern each element into the per-fiber hash; the accumulator is a no-op.
|
|
2018
|
+
WIDEN sign- or zero-extends the integer element; float normalizes NaN and
|
|
2019
|
+
-0.0 before the bitwise key. */
|
|
2020
|
+
#define NU_INT(WIDEN) \
|
|
2021
|
+
do { int _n; fz_hash_intern(&h, (uint64_t) ((WIDEN) v), &_n); } while (0)
|
|
2022
|
+
#define NU_FLOAT(T, UINT, NANKEY) \
|
|
2023
|
+
do { \
|
|
2024
|
+
T _v = v; \
|
|
2025
|
+
uint64_t _key; \
|
|
2026
|
+
if ( _v != _v ) { _key = (NANKEY); } \
|
|
2027
|
+
else { \
|
|
2028
|
+
if ( _v == (T) 0 ) { _v = (T) 0; } \
|
|
2029
|
+
UINT _b; \
|
|
2030
|
+
memcpy(&_b, &_v, sizeof(_b)); \
|
|
2031
|
+
_key = (uint64_t) _b; \
|
|
2032
|
+
} \
|
|
2033
|
+
int _n; fz_hash_intern(&h, _key, &_n); \
|
|
2034
|
+
} while (0)
|
|
2035
|
+
/* Object: intern the VALUE via the object lane (v is bound by the macro). */
|
|
2036
|
+
#define NU_OBJ \
|
|
2037
|
+
do { int _n; fz_hash_intern_obj(&h, v, &_n); } while (0)
|
|
2038
|
+
|
|
2039
|
+
/* Fixlen has no scalar element type for CA_SLAB_REDUCE_T, so walk the slab by
|
|
2040
|
+
multi-index (order-independent -- nunique only counts distinct) and intern
|
|
2041
|
+
each esz-wide element via the fixlen lane. */
|
|
2042
|
+
#define NU_MEM_WALK \
|
|
2043
|
+
do { \
|
|
2044
|
+
int8_t K = st.slab_ndim; \
|
|
2045
|
+
ca_size_t idx[CA_RANK_MAX] = { 0 }; \
|
|
2046
|
+
ca_size_t total = st.slab_elements; \
|
|
2047
|
+
for ( ca_size_t e = 0; e < total; e++ ) { \
|
|
2048
|
+
ca_size_t doff = 0, moff = 0; \
|
|
2049
|
+
for ( int8_t kk = 0; kk < K; kk++ ) { \
|
|
2050
|
+
doff += idx[kk] * st.slab_strides[kk]; \
|
|
2051
|
+
moff += idx[kk] * st.slab_mask_strides[kk]; \
|
|
2052
|
+
} \
|
|
2053
|
+
if ( ! (m && m[moff]) ) { \
|
|
2054
|
+
int _n; fz_hash_intern_mem(&h, p + doff, &_n); \
|
|
2055
|
+
} \
|
|
2056
|
+
for ( int8_t kk = (int8_t)(K - 1); kk >= 0; kk-- ) { \
|
|
2057
|
+
if ( ++idx[kk] < st.slab_dims[kk] ) break; \
|
|
2058
|
+
idx[kk] = 0; \
|
|
2059
|
+
} \
|
|
2060
|
+
} \
|
|
2061
|
+
} while (0)
|
|
2062
|
+
|
|
2063
|
+
while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
|
|
2064
|
+
fz_hash_reset(&h); /* independent seen-set per fiber */
|
|
2065
|
+
switch ( dt ) {
|
|
2066
|
+
case CA_INT8: CA_SLAB_REDUCE_T(int8_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
|
|
2067
|
+
case CA_INT16: CA_SLAB_REDUCE_T(int16_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
|
|
2068
|
+
case CA_INT32: CA_SLAB_REDUCE_T(int32_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
|
|
2069
|
+
case CA_INT64: CA_SLAB_REDUCE_T(int64_t, st, p, m, dummy, 0, NU_INT(int64_t)); break;
|
|
2070
|
+
case CA_BOOLEAN: case CA_UINT8: CA_SLAB_REDUCE_T(uint8_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
|
|
2071
|
+
case CA_UINT16: CA_SLAB_REDUCE_T(uint16_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
|
|
2072
|
+
case CA_UINT32: CA_SLAB_REDUCE_T(uint32_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
|
|
2073
|
+
case CA_UINT64: CA_SLAB_REDUCE_T(uint64_t, st, p, m, dummy, 0, NU_INT(uint64_t)); break;
|
|
2074
|
+
case CA_FLOAT32:
|
|
2075
|
+
CA_SLAB_REDUCE_T(float, st, p, m, dummy, 0, NU_FLOAT(float, uint32_t, 0x7fc00000ULL));
|
|
2076
|
+
break;
|
|
2077
|
+
case CA_FLOAT64:
|
|
2078
|
+
CA_SLAB_REDUCE_T(double, st, p, m, dummy, 0, NU_FLOAT(double, uint64_t, 0x7ff8000000000000ULL));
|
|
2079
|
+
break;
|
|
2080
|
+
case CA_OBJECT: CA_SLAB_REDUCE_T(VALUE, st, p, m, dummy, 0, NU_OBJ); break;
|
|
2081
|
+
case CA_FIXLEN: NU_MEM_WALK; break;
|
|
2082
|
+
}
|
|
2083
|
+
op[out_i++] = (int64_t) h.n;
|
|
2084
|
+
}
|
|
2085
|
+
#undef NU_INT
|
|
2086
|
+
#undef NU_FLOAT
|
|
2087
|
+
#undef NU_OBJ
|
|
2088
|
+
#undef NU_MEM_WALK
|
|
2089
|
+
(void) dummy;
|
|
2090
|
+
|
|
2091
|
+
ca_iter_state_finish(&st);
|
|
2092
|
+
fz_hash_free(&h);
|
|
2093
|
+
return vout;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
/* @overload __is_mode__(axis)
|
|
2097
|
+
|
|
2098
|
+
INTERNAL (CArray#is_mode). Mark each cell that holds a modal value along axis
|
|
2099
|
+
-- a value whose per-fiber occurrence count equals the fiber's maximum count.
|
|
2100
|
+
Two passes per fiber: pass one builds the frequency table (the fz_hash count
|
|
2101
|
+
lane) and finds the max count; pass two marks every cell whose value's count
|
|
2102
|
+
equals it, so ties are all marked (no tie-break). Returns a boolean CArray of
|
|
2103
|
+
self.shape.
|
|
2104
|
+
|
|
2105
|
+
Masked cells do not participate (excluded from the counts) and are marked
|
|
2106
|
+
false. An empty / all-masked fiber has max count 0 and marks every cell false
|
|
2107
|
+
(is_mode has no identity; it never raises). Numeric distinctness collapses all
|
|
2108
|
+
NaN to one value and treats -0.0 == +0.0, matching the discovery family;
|
|
2109
|
+
object keys on rb_hash + rb_eql and fixlen on a byte-hash + memcmp, reproducing
|
|
2110
|
+
Ruby Hash distinctness; the object lane also collapses every Float NaN to one.
|
|
2111
|
+
Callers pass flat input by flattening first (a single fiber over axis 0).
|
|
2112
|
+
*/
|
|
2113
|
+
static VALUE
|
|
2114
|
+
rb_ca_is_mode (VALUE self, VALUE vaxis)
|
|
2115
|
+
{
|
|
2116
|
+
CArray *ca;
|
|
2117
|
+
TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
|
|
2118
|
+
|
|
2119
|
+
int8_t dt = ca->data_type;
|
|
2120
|
+
switch ( dt ) {
|
|
2121
|
+
case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
|
|
2122
|
+
case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
|
|
2123
|
+
case CA_FLOAT32: case CA_FLOAT64:
|
|
2124
|
+
case CA_OBJECT: case CA_FIXLEN:
|
|
2125
|
+
case CA_BOOLEAN:
|
|
2126
|
+
break;
|
|
2127
|
+
default:
|
|
2128
|
+
rb_raise(rb_eCADataTypeError,
|
|
2129
|
+
"__is_mode__: numeric, object, or fixlen dtype required (got %d)", dt);
|
|
2130
|
+
}
|
|
2131
|
+
if ( ca->ndim < 1 ) {
|
|
2132
|
+
rb_raise(rb_eRuntimeError, "__is_mode__: need ndim >= 1");
|
|
2133
|
+
}
|
|
2134
|
+
int axis = NUM2INT(vaxis);
|
|
2135
|
+
if ( axis < 0 || axis >= ca->ndim ) {
|
|
2136
|
+
rb_raise(rb_eArgError, "__is_mode__: axis %d out of range", axis);
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
VALUE vout = rb_carray_new(CA_BOOLEAN, ca->ndim, ca->dim, 0, NULL);
|
|
2140
|
+
CArray *cout;
|
|
2141
|
+
TypedData_Get_Struct(vout, CArray, &carray_data_type, cout);
|
|
2142
|
+
|
|
2143
|
+
fz_hash h;
|
|
2144
|
+
fz_levels ct; /* int64 count per code (code == push index), reset per fiber */
|
|
2145
|
+
if ( dt == CA_OBJECT ) { fz_hash_init_obj(&h); }
|
|
2146
|
+
else if ( dt == CA_FIXLEN ) { fz_hash_init_mem(&h, (int) ca->bytes); }
|
|
2147
|
+
else { fz_hash_init(&h); }
|
|
2148
|
+
fz_levels_init(&ct, (int) sizeof(int64_t));
|
|
2149
|
+
|
|
2150
|
+
ca_iter_state st_in, st_out;
|
|
2151
|
+
char *p_in, *p_out;
|
|
2152
|
+
boolean8_t *m;
|
|
2153
|
+
ca_size_t n;
|
|
2154
|
+
|
|
2155
|
+
/* Integer: WIDEN gives the key directly. Two passes over the fiber. */
|
|
2156
|
+
#define IM_BODY(T, WIDEN) \
|
|
2157
|
+
do { \
|
|
2158
|
+
const T *ip = (const T *) p_in; \
|
|
2159
|
+
boolean8_t *op = (boolean8_t *) p_out; \
|
|
2160
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
2161
|
+
if ( m && m[i] ) { continue; } \
|
|
2162
|
+
uint64_t key = (uint64_t) (WIDEN ip[i]); \
|
|
2163
|
+
int is_new; \
|
|
2164
|
+
int32_t code = fz_hash_intern(&h, key, &is_new); \
|
|
2165
|
+
if ( is_new ) { int64_t one = 1; fz_levels_push(&ct, &one); } \
|
|
2166
|
+
else { ((int64_t *) ct.p)[code]++; } \
|
|
2167
|
+
} \
|
|
2168
|
+
int64_t mx = 0; \
|
|
2169
|
+
for ( ca_size_t c = 0; c < h.n; c++ ) { \
|
|
2170
|
+
int64_t cc = ((int64_t *) ct.p)[c]; \
|
|
2171
|
+
if ( cc > mx ) { mx = cc; } \
|
|
2172
|
+
} \
|
|
2173
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
2174
|
+
if ( m && m[i] ) { op[i] = 0; continue; } \
|
|
2175
|
+
uint64_t key = (uint64_t) (WIDEN ip[i]); \
|
|
2176
|
+
int is_new; \
|
|
2177
|
+
int32_t code = fz_hash_intern(&h, key, &is_new); \
|
|
2178
|
+
op[i] = (((int64_t *) ct.p)[code] == mx) ? 1 : 0; \
|
|
2179
|
+
} \
|
|
2180
|
+
} while (0)
|
|
2181
|
+
|
|
2182
|
+
/* Float: NaN collapses to one canonical key; -0.0 / +0.0 share a key. */
|
|
2183
|
+
#define IM_BODY_FLOAT(T, UINT, NANKEY) \
|
|
2184
|
+
do { \
|
|
2185
|
+
const T *ip = (const T *) p_in; \
|
|
2186
|
+
boolean8_t *op = (boolean8_t *) p_out; \
|
|
2187
|
+
for ( int pass = 0; pass < 2; pass++ ) { \
|
|
2188
|
+
int64_t mx = 0; \
|
|
2189
|
+
if ( pass == 1 ) { \
|
|
2190
|
+
for ( ca_size_t c = 0; c < h.n; c++ ) { \
|
|
2191
|
+
int64_t cc = ((int64_t *) ct.p)[c]; \
|
|
2192
|
+
if ( cc > mx ) { mx = cc; } \
|
|
2193
|
+
} \
|
|
2194
|
+
} \
|
|
2195
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
2196
|
+
if ( m && m[i] ) { if ( pass == 1 ) { op[i] = 0; } continue; } \
|
|
2197
|
+
T v = ip[i]; \
|
|
2198
|
+
uint64_t key; \
|
|
2199
|
+
if ( v != v ) { key = (NANKEY); } \
|
|
2200
|
+
else { \
|
|
2201
|
+
if ( v == (T) 0 ) { v = (T) 0; } \
|
|
2202
|
+
UINT bits; \
|
|
2203
|
+
memcpy(&bits, &v, sizeof(bits)); \
|
|
2204
|
+
key = (uint64_t) bits; \
|
|
2205
|
+
} \
|
|
2206
|
+
int is_new; \
|
|
2207
|
+
int32_t code = fz_hash_intern(&h, key, &is_new); \
|
|
2208
|
+
if ( pass == 0 ) { \
|
|
2209
|
+
if ( is_new ) { int64_t one = 1; fz_levels_push(&ct, &one); } \
|
|
2210
|
+
else { ((int64_t *) ct.p)[code]++; } \
|
|
2211
|
+
} \
|
|
2212
|
+
else { op[i] = (((int64_t *) ct.p)[code] == mx) ? 1 : 0; } \
|
|
2213
|
+
} \
|
|
2214
|
+
} \
|
|
2215
|
+
} while (0)
|
|
2216
|
+
|
|
2217
|
+
/* Object: rb_hash + rb_eql lane. Pass one builds the count lane, pass two
|
|
2218
|
+
marks cells whose value's count ties the fiber max (re-intern returns the
|
|
2219
|
+
existing code without inserting). */
|
|
2220
|
+
#define IM_BODY_OBJ \
|
|
2221
|
+
do { \
|
|
2222
|
+
const VALUE *ip = (const VALUE *) p_in; \
|
|
2223
|
+
boolean8_t *op = (boolean8_t *) p_out; \
|
|
2224
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
2225
|
+
if ( m && m[i] ) { continue; } \
|
|
2226
|
+
int is_new; \
|
|
2227
|
+
int32_t code = fz_hash_intern_obj(&h, ip[i], &is_new); \
|
|
2228
|
+
if ( is_new ) { int64_t one = 1; fz_levels_push(&ct, &one); } \
|
|
2229
|
+
else { ((int64_t *) ct.p)[code]++; } \
|
|
2230
|
+
} \
|
|
2231
|
+
int64_t mx = 0; \
|
|
2232
|
+
for ( ca_size_t c = 0; c < h.n; c++ ) { \
|
|
2233
|
+
int64_t cc = ((int64_t *) ct.p)[c]; \
|
|
2234
|
+
if ( cc > mx ) { mx = cc; } \
|
|
2235
|
+
} \
|
|
2236
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
2237
|
+
if ( m && m[i] ) { op[i] = 0; continue; } \
|
|
2238
|
+
int is_new; \
|
|
2239
|
+
int32_t code = fz_hash_intern_obj(&h, ip[i], &is_new); \
|
|
2240
|
+
op[i] = (((int64_t *) ct.p)[code] == mx) ? 1 : 0; \
|
|
2241
|
+
} \
|
|
2242
|
+
} while (0)
|
|
2243
|
+
|
|
2244
|
+
/* Fixlen: byte-hash + memcmp lane, same two-pass structure. */
|
|
2245
|
+
#define IM_BODY_MEM \
|
|
2246
|
+
do { \
|
|
2247
|
+
boolean8_t *op = (boolean8_t *) p_out; \
|
|
2248
|
+
int esz = (int) ca->bytes; \
|
|
2249
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
2250
|
+
if ( m && m[i] ) { continue; } \
|
|
2251
|
+
int is_new; \
|
|
2252
|
+
int32_t code = fz_hash_intern_mem(&h, p_in + i * esz, &is_new); \
|
|
2253
|
+
if ( is_new ) { int64_t one = 1; fz_levels_push(&ct, &one); } \
|
|
2254
|
+
else { ((int64_t *) ct.p)[code]++; } \
|
|
2255
|
+
} \
|
|
2256
|
+
int64_t mx = 0; \
|
|
2257
|
+
for ( ca_size_t c = 0; c < h.n; c++ ) { \
|
|
2258
|
+
int64_t cc = ((int64_t *) ct.p)[c]; \
|
|
2259
|
+
if ( cc > mx ) { mx = cc; } \
|
|
2260
|
+
} \
|
|
2261
|
+
for ( ca_size_t i = 0; i < n; i++ ) { \
|
|
2262
|
+
if ( m && m[i] ) { op[i] = 0; continue; } \
|
|
2263
|
+
int is_new; \
|
|
2264
|
+
int32_t code = fz_hash_intern_mem(&h, p_in + i * esz, &is_new); \
|
|
2265
|
+
op[i] = (((int64_t *) ct.p)[code] == mx) ? 1 : 0; \
|
|
2266
|
+
} \
|
|
2267
|
+
} while (0)
|
|
2268
|
+
|
|
2269
|
+
CA_FOR_EACH_FIBER_INOUT_MASKED(st_in, st_out, ca, cout, (int8_t) axis,
|
|
2270
|
+
CA_KERNEL_READ, p_in, p_out, n, m) {
|
|
2271
|
+
fz_hash_reset(&h); /* independent frequency table per fiber */
|
|
2272
|
+
ct.n = 0;
|
|
2273
|
+
switch ( dt ) {
|
|
2274
|
+
case CA_INT8: IM_BODY(int8_t, (int64_t)); break;
|
|
2275
|
+
case CA_INT16: IM_BODY(int16_t, (int64_t)); break;
|
|
2276
|
+
case CA_INT32: IM_BODY(int32_t, (int64_t)); break;
|
|
2277
|
+
case CA_INT64: IM_BODY(int64_t, (int64_t)); break;
|
|
2278
|
+
case CA_BOOLEAN: case CA_UINT8: IM_BODY(uint8_t, (uint64_t)); break;
|
|
2279
|
+
case CA_UINT16: IM_BODY(uint16_t, (uint64_t)); break;
|
|
2280
|
+
case CA_UINT32: IM_BODY(uint32_t, (uint64_t)); break;
|
|
2281
|
+
case CA_UINT64: IM_BODY(uint64_t, (uint64_t)); break;
|
|
2282
|
+
case CA_FLOAT32: IM_BODY_FLOAT(float, uint32_t, 0x7fc00000ULL); break;
|
|
2283
|
+
case CA_FLOAT64: IM_BODY_FLOAT(double, uint64_t, 0x7ff8000000000000ULL); break;
|
|
2284
|
+
case CA_OBJECT: IM_BODY_OBJ; break;
|
|
2285
|
+
case CA_FIXLEN: IM_BODY_MEM; break;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
#undef IM_BODY
|
|
2289
|
+
#undef IM_BODY_FLOAT
|
|
2290
|
+
#undef IM_BODY_OBJ
|
|
2291
|
+
#undef IM_BODY_MEM
|
|
2292
|
+
|
|
2293
|
+
fz_hash_free(&h);
|
|
2294
|
+
fz_levels_free(&ct);
|
|
2295
|
+
return vout;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
/* Ascending comparison for the modal-value sort, NaN ordered last so a
|
|
2299
|
+
collapsed NaN key (at most one per fiber) trails the real values, matching
|
|
2300
|
+
CArray#sort. */
|
|
2301
|
+
static int mode_gt_f32 (float a, float b) { if ( a != a ) return b == b; if ( b != b ) return 0; return a > b; }
|
|
2302
|
+
static int mode_gt_f64 (double a, double b) { if ( a != a ) return b == b; if ( b != b ) return 0; return a > b; }
|
|
2303
|
+
#define MODE_GTI(a, b) ((a) > (b))
|
|
2304
|
+
|
|
2305
|
+
/* @overload __mode_axis__(axis)
|
|
2306
|
+
|
|
2307
|
+
INTERNAL (CArray#mode's numeric per-axis path). Emit the distinct modal
|
|
2308
|
+
values along axis, per fiber, ascending, as an Array of reduced CArrays --
|
|
2309
|
+
the ragged value-form consumer of the per-fiber frequency table (see
|
|
2310
|
+
__is_mode__, which marks the modal cells; this reads out the values).
|
|
2311
|
+
|
|
2312
|
+
Two passes over the family substrate per fiber: pass one builds the
|
|
2313
|
+
frequency table (fz_hash + count lane + first-seen level buffer) and finds
|
|
2314
|
+
the fiber's max count; the modal values are the distinct values whose count
|
|
2315
|
+
equals it, sorted ascending. The ragged per-fiber lists are collected into
|
|
2316
|
+
one flat buffer, then redistributed into K reduced CArrays where K is the
|
|
2317
|
+
widest fiber's modal count: element j holds each fiber's j-th smallest modal
|
|
2318
|
+
value, masked (UNDEF) where a fiber has fewer than j+1 modes -- the same
|
|
2319
|
+
Array<CArray> shape as per-axis quantile. Stack them along axis to get the
|
|
2320
|
+
rectangular mask-padded form.
|
|
2321
|
+
|
|
2322
|
+
The result Array's row-major cell order per reduced CArray matches the fiber
|
|
2323
|
+
walk (self.shape with axis removed). An all-masked array (no modes anywhere,
|
|
2324
|
+
K == 0) yields an empty Array. Masked cells are excluded from the counts. A
|
|
2325
|
+
1-D input reduces to length-1 reduced CArrays; the Ruby surface unwraps them
|
|
2326
|
+
to scalars. Float distinctness collapses all NaN to one value (sorted last)
|
|
2327
|
+
and treats -0.0 == +0.0 (the first-seen raw value is emitted, so -0.0 keeps
|
|
2328
|
+
its sign), matching the discovery family.
|
|
2329
|
+
*/
|
|
2330
|
+
static VALUE
|
|
2331
|
+
rb_ca_mode_axis (VALUE self, VALUE vaxis)
|
|
2332
|
+
{
|
|
2333
|
+
CArray *ca;
|
|
2334
|
+
volatile VALUE face;
|
|
2335
|
+
self = fz_face_descend(self, &face);
|
|
2336
|
+
TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
|
|
2337
|
+
|
|
2338
|
+
int8_t dt = ca->data_type;
|
|
2339
|
+
switch ( dt ) {
|
|
2340
|
+
case CA_INT8: case CA_INT16: case CA_INT32: case CA_INT64:
|
|
2341
|
+
case CA_UINT8: case CA_UINT16: case CA_UINT32: case CA_UINT64:
|
|
2342
|
+
case CA_FLOAT32: case CA_FLOAT64:
|
|
2343
|
+
case CA_BOOLEAN:
|
|
2344
|
+
break;
|
|
2345
|
+
default:
|
|
2346
|
+
rb_raise(rb_eCADataTypeError,
|
|
2347
|
+
"__mode_axis__: numeric dtype required (got %d)", dt);
|
|
2348
|
+
}
|
|
2349
|
+
if ( ca->ndim < 1 ) {
|
|
2350
|
+
rb_raise(rb_eRuntimeError, "__mode_axis__: need ndim >= 1");
|
|
2351
|
+
}
|
|
2352
|
+
int axis = NUM2INT(vaxis);
|
|
2353
|
+
if ( axis < 0 || axis >= ca->ndim ) {
|
|
2354
|
+
rb_raise(rb_eArgError, "__mode_axis__: axis %d out of range", axis);
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
int esz = (int) ca->bytes;
|
|
2358
|
+
|
|
2359
|
+
/* Number of fibers = self.elements with axis dropped = reduced cell count. */
|
|
2360
|
+
ca_size_t M = 1;
|
|
2361
|
+
for ( int d = 0; d < ca->ndim; d++ ) { if ( d != axis ) M *= ca->dim[d]; }
|
|
2362
|
+
|
|
2363
|
+
fz_hash h;
|
|
2364
|
+
fz_levels lv; /* first-seen raw values, appearance order (reset per fiber) */
|
|
2365
|
+
fz_levels ct; /* int64 count per code (code == push index, reset per fiber) */
|
|
2366
|
+
fz_levels mod; /* per-fiber modal values, collected then sorted (reset) */
|
|
2367
|
+
fz_levels flat; /* all fibers' modal values concatenated, fiber order */
|
|
2368
|
+
fz_hash_init(&h);
|
|
2369
|
+
fz_levels_init(&lv, esz);
|
|
2370
|
+
fz_levels_init(&ct, (int) sizeof(int64_t));
|
|
2371
|
+
fz_levels_init(&mod, esz);
|
|
2372
|
+
fz_levels_init(&flat, esz);
|
|
2373
|
+
|
|
2374
|
+
ca_size_t *foff = ALLOC_N(ca_size_t, M + 1); /* prefix offsets into flat */
|
|
2375
|
+
foff[0] = 0;
|
|
2376
|
+
|
|
2377
|
+
int8_t ax = (int8_t) axis;
|
|
2378
|
+
ca_iter_state st;
|
|
2379
|
+
int rc = ca_iter_state_init_l2(&st, ca, CA_SLAB_AXES, &ax, 1, 0);
|
|
2380
|
+
if ( rc != CA_ITER_OK ) {
|
|
2381
|
+
fz_hash_free(&h); fz_levels_free(&lv); fz_levels_free(&ct);
|
|
2382
|
+
fz_levels_free(&mod); fz_levels_free(&flat); xfree(foff);
|
|
2383
|
+
rb_raise(rb_eRuntimeError, "__mode_axis__: kernel_iterator init failed rc=%d", rc);
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
char *p;
|
|
2387
|
+
boolean8_t *m;
|
|
2388
|
+
ca_size_t out_i = 0;
|
|
2389
|
+
ca_size_t Kmax = 0;
|
|
2390
|
+
ca_size_t dummy; /* CA_SLAB_REDUCE_T needs an acc lvalue; unused here */
|
|
2391
|
+
|
|
2392
|
+
/* Pass-one body: intern each value, growing the count lane and first-seen
|
|
2393
|
+
level buffer (code == push index). WIDEN keys the integer element; float
|
|
2394
|
+
normalizes NaN and -0.0 before the bitwise key but pushes the raw value. */
|
|
2395
|
+
#define MB_INT(WIDEN) \
|
|
2396
|
+
do { \
|
|
2397
|
+
uint64_t key = (uint64_t) ((WIDEN) v); \
|
|
2398
|
+
int is_new; int32_t code = fz_hash_intern(&h, key, &is_new); \
|
|
2399
|
+
if ( is_new ) { int64_t one = 1; fz_levels_push(&lv, &v); \
|
|
2400
|
+
fz_levels_push(&ct, &one); } \
|
|
2401
|
+
else { ((int64_t *) ct.p)[code]++; } \
|
|
2402
|
+
} while (0)
|
|
2403
|
+
#define MB_FLOAT(T, UINT, NANKEY) \
|
|
2404
|
+
do { \
|
|
2405
|
+
T vn = v; uint64_t key; \
|
|
2406
|
+
if ( vn != vn ) { key = (NANKEY); } \
|
|
2407
|
+
else { if ( vn == (T) 0 ) { vn = (T) 0; } \
|
|
2408
|
+
UINT bits; memcpy(&bits, &vn, sizeof(bits)); key = (uint64_t) bits; } \
|
|
2409
|
+
int is_new; int32_t code = fz_hash_intern(&h, key, &is_new); \
|
|
2410
|
+
if ( is_new ) { int64_t one = 1; fz_levels_push(&lv, &v); \
|
|
2411
|
+
fz_levels_push(&ct, &one); } \
|
|
2412
|
+
else { ((int64_t *) ct.p)[code]++; } \
|
|
2413
|
+
} while (0)
|
|
2414
|
+
|
|
2415
|
+
/* Collect the distinct values whose count ties the fiber max, insertion-sort
|
|
2416
|
+
ascending (NaN last), and append them to the flat buffer. */
|
|
2417
|
+
#define MODE_EMIT(T, GT) \
|
|
2418
|
+
do { \
|
|
2419
|
+
T *lvp = (T *) lv.p; int64_t *ctp = (int64_t *) ct.p; \
|
|
2420
|
+
for ( ca_size_t c = 0; c < h.n; c++ ) { \
|
|
2421
|
+
if ( mx > 0 && ctp[c] == mx ) { fz_levels_push(&mod, &lvp[c]); } \
|
|
2422
|
+
} \
|
|
2423
|
+
T *dp = (T *) mod.p; ca_size_t cm = mod.n; \
|
|
2424
|
+
for ( ca_size_t x = 1; x < cm; x++ ) { \
|
|
2425
|
+
T kv = dp[x]; ca_size_t y = x; \
|
|
2426
|
+
while ( y > 0 && GT(dp[y - 1], kv) ) { dp[y] = dp[y - 1]; y--; } \
|
|
2427
|
+
dp[y] = kv; \
|
|
2428
|
+
} \
|
|
2429
|
+
for ( ca_size_t x = 0; x < cm; x++ ) { fz_levels_push(&flat, &dp[x]); } \
|
|
2430
|
+
} while (0)
|
|
2431
|
+
|
|
2432
|
+
while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {
|
|
2433
|
+
fz_hash_reset(&h);
|
|
2434
|
+
lv.n = 0; ct.n = 0; mod.n = 0;
|
|
2435
|
+
|
|
2436
|
+
switch ( dt ) {
|
|
2437
|
+
case CA_INT8: CA_SLAB_REDUCE_T(int8_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
|
|
2438
|
+
case CA_INT16: CA_SLAB_REDUCE_T(int16_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
|
|
2439
|
+
case CA_INT32: CA_SLAB_REDUCE_T(int32_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
|
|
2440
|
+
case CA_INT64: CA_SLAB_REDUCE_T(int64_t, st, p, m, dummy, 0, MB_INT(int64_t)); break;
|
|
2441
|
+
case CA_BOOLEAN: case CA_UINT8: CA_SLAB_REDUCE_T(uint8_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
|
|
2442
|
+
case CA_UINT16: CA_SLAB_REDUCE_T(uint16_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
|
|
2443
|
+
case CA_UINT32: CA_SLAB_REDUCE_T(uint32_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
|
|
2444
|
+
case CA_UINT64: CA_SLAB_REDUCE_T(uint64_t, st, p, m, dummy, 0, MB_INT(uint64_t)); break;
|
|
2445
|
+
case CA_FLOAT32:
|
|
2446
|
+
CA_SLAB_REDUCE_T(float, st, p, m, dummy, 0, MB_FLOAT(float, uint32_t, 0x7fc00000ULL));
|
|
2447
|
+
break;
|
|
2448
|
+
case CA_FLOAT64:
|
|
2449
|
+
CA_SLAB_REDUCE_T(double, st, p, m, dummy, 0, MB_FLOAT(double, uint64_t, 0x7ff8000000000000ULL));
|
|
2450
|
+
break;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
int64_t mx = 0;
|
|
2454
|
+
for ( ca_size_t c = 0; c < h.n; c++ ) {
|
|
2455
|
+
int64_t cc = ((int64_t *) ct.p)[c];
|
|
2456
|
+
if ( cc > mx ) { mx = cc; }
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
switch ( dt ) {
|
|
2460
|
+
case CA_INT8: MODE_EMIT(int8_t, MODE_GTI); break;
|
|
2461
|
+
case CA_INT16: MODE_EMIT(int16_t, MODE_GTI); break;
|
|
2462
|
+
case CA_INT32: MODE_EMIT(int32_t, MODE_GTI); break;
|
|
2463
|
+
case CA_INT64: MODE_EMIT(int64_t, MODE_GTI); break;
|
|
2464
|
+
case CA_BOOLEAN: case CA_UINT8: MODE_EMIT(uint8_t, MODE_GTI); break;
|
|
2465
|
+
case CA_UINT16: MODE_EMIT(uint16_t, MODE_GTI); break;
|
|
2466
|
+
case CA_UINT32: MODE_EMIT(uint32_t, MODE_GTI); break;
|
|
2467
|
+
case CA_UINT64: MODE_EMIT(uint64_t, MODE_GTI); break;
|
|
2468
|
+
case CA_FLOAT32: MODE_EMIT(float, mode_gt_f32); break;
|
|
2469
|
+
case CA_FLOAT64: MODE_EMIT(double, mode_gt_f64); break;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
if ( out_i < M ) { foff[out_i + 1] = foff[out_i] + mod.n; }
|
|
2473
|
+
if ( mod.n > Kmax ) { Kmax = mod.n; }
|
|
2474
|
+
out_i++;
|
|
2475
|
+
}
|
|
2476
|
+
#undef MB_INT
|
|
2477
|
+
#undef MB_FLOAT
|
|
2478
|
+
#undef MODE_EMIT
|
|
2479
|
+
(void) dummy;
|
|
2480
|
+
|
|
2481
|
+
ca_iter_state_finish(&st);
|
|
2482
|
+
fz_hash_free(&h);
|
|
2483
|
+
fz_levels_free(&lv);
|
|
2484
|
+
fz_levels_free(&ct);
|
|
2485
|
+
fz_levels_free(&mod);
|
|
2486
|
+
|
|
2487
|
+
/* K columns, each a reduced CArray; slot j is fiber r's j-th modal value or
|
|
2488
|
+
UNDEF when r has fewer than j+1 modes. K == 0 -> empty Array. */
|
|
2489
|
+
VALUE result = rb_ary_new_capa((long) Kmax);
|
|
2490
|
+
for ( ca_size_t j = 0; j < Kmax; j++ ) {
|
|
2491
|
+
VALUE col = rb_ca_new_reduced(self, &ax, 1, dt, 0);
|
|
2492
|
+
CArray *cc;
|
|
2493
|
+
TypedData_Get_Struct(col, CArray, &carray_data_type, cc);
|
|
2494
|
+
ca_create_mask(cc);
|
|
2495
|
+
boolean8_t *cm = (boolean8_t *) cc->mask->ptr;
|
|
2496
|
+
char *dst = cc->ptr;
|
|
2497
|
+
for ( ca_size_t r = 0; r < M; r++ ) {
|
|
2498
|
+
ca_size_t cnt_r = foff[r + 1] - foff[r];
|
|
2499
|
+
if ( j < cnt_r ) {
|
|
2500
|
+
memcpy(dst + (size_t) r * esz, flat.p + (size_t) (foff[r] + j) * esz, esz);
|
|
2501
|
+
cm[r] = 0;
|
|
2502
|
+
}
|
|
2503
|
+
else {
|
|
2504
|
+
cm[r] = 1;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
rb_ary_push(result, fz_face_relift(col, face)); /* modal *values* */
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
fz_levels_free(&flat);
|
|
2511
|
+
xfree(foff);
|
|
2512
|
+
return result;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
void
|
|
2516
|
+
Init_carray_factorize (void)
|
|
2517
|
+
{
|
|
2518
|
+
rb_define_private_method(rb_cCArray, "__factorize_appearance__",
|
|
2519
|
+
rb_ca_factorize_appearance, 0);
|
|
2520
|
+
rb_define_private_method(rb_cCArray, "__mask_duplicates__",
|
|
2521
|
+
rb_ca_mask_duplicates, 1);
|
|
2522
|
+
rb_define_private_method(rb_cCArray, "__unique_flat__",
|
|
2523
|
+
rb_ca_unique_flat, 0);
|
|
2524
|
+
rb_define_private_method(rb_cCArray, "__is_in__",
|
|
2525
|
+
rb_ca_is_in, 1);
|
|
2526
|
+
rb_define_private_method(rb_cCArray, "__locate_addr__",
|
|
2527
|
+
rb_ca_locate_addr, 1);
|
|
2528
|
+
rb_define_private_method(rb_cCArray, "__intersection__",
|
|
2529
|
+
rb_ca_intersection, 1);
|
|
2530
|
+
rb_define_private_method(rb_cCArray, "__difference__",
|
|
2531
|
+
rb_ca_difference, 1);
|
|
2532
|
+
rb_define_private_method(rb_cCArray, "__union__",
|
|
2533
|
+
rb_ca_set_union, 1);
|
|
2534
|
+
rb_define_private_method(rb_cCArray, "__value_counts_flat__",
|
|
2535
|
+
rb_ca_value_counts_flat, 0);
|
|
2536
|
+
rb_define_private_method(rb_cCArray, "__nunique__",
|
|
2537
|
+
rb_ca_nunique, 2);
|
|
2538
|
+
rb_define_private_method(rb_cCArray, "__is_mode__",
|
|
2539
|
+
rb_ca_is_mode, 1);
|
|
2540
|
+
rb_define_private_method(rb_cCArray, "__mode_axis__",
|
|
2541
|
+
rb_ca_mode_axis, 1);
|
|
2542
|
+
}
|