carray 3.0.1 → 3.0.2
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/CHANGELOG.md +520 -0
- data/README.md +2 -2
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +33 -4
- data/ext/ca_axis_group.c +202 -96
- data/ext/ca_categorical_iterator.c +108 -54
- data/ext/ca_kernel_iterator.c +317 -51
- data/ext/ca_kernel_iterator.h +142 -35
- data/ext/ca_obj_array.c +62 -20
- data/ext/ca_obj_block.c +4 -4
- data/ext/ca_obj_const_string.c +85 -26
- data/ext/ca_obj_face.c +24 -0
- data/ext/ca_obj_face.h +15 -0
- data/ext/ca_obj_fixlen_string.c +18 -5
- data/ext/ca_obj_meld.c +123 -25
- data/ext/ca_obj_object.c +8 -0
- data/ext/ca_obj_select.c +49 -34
- data/ext/ca_obj_stack.c +3 -8
- data/ext/ca_obj_stride.c +72 -1
- data/ext/ca_obj_string.c +8 -4
- data/ext/ca_obj_window.c +8 -2
- data/ext/ca_op_ipower.c +1 -2
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sweep_engine.c +307 -143
- data/ext/ca_sweep_engine.h +26 -5
- data/ext/carray.h +21 -2
- data/ext/carray_access.c +32 -20
- data/ext/carray_address_basis.c +590 -0
- data/ext/carray_broadcast.c +3 -3
- data/ext/carray_call_cfunc.c +667 -483
- data/ext/carray_cast.c +115 -41
- data/ext/carray_copy.c +55 -30
- data/ext/carray_core.c +83 -3
- data/ext/carray_count.c +9 -10
- data/ext/carray_factorize.c +46 -25
- data/ext/carray_internal.h +17 -0
- data/ext/carray_kernels_reduce_aggregate.c +168 -0
- data/ext/carray_kernels_reduce_cumulative.c +270 -1
- data/ext/carray_kernels_reduce_extreme.c +554 -8
- data/ext/carray_kernels_scan.c +4 -4
- data/ext/carray_kernels_search.c +94 -14
- data/ext/carray_loop.c +7 -1
- data/ext/carray_mask.c +23 -8
- data/ext/carray_median_percentile.c +55 -0
- data/ext/carray_operator.c +4 -4
- data/ext/carray_order.c +1 -1
- data/ext/carray_random.c +384 -40
- data/ext/carray_slab.c +13 -0
- data/ext/carray_sort.c +20 -22
- data/ext/mk_call_cfunc.rb +103 -116
- data/ext/mkkernel.rb +297 -29
- data/ext/ruby_carray.c +10 -1
- data/ext/version.h +4 -4
- data/lib/carray/autoload_carray.rb +5 -3
- data/lib/carray/autoload_method_extension.rb +12 -0
- data/lib/carray/axis_group.rb +77 -0
- data/lib/carray/basics.rb +4 -0
- data/lib/carray/block_iterator.rb +92 -16
- data/lib/carray/categorical.rb +150 -33
- data/lib/carray/categorical_iterator.rb +207 -80
- data/lib/carray/const_string.rb +131 -27
- data/lib/carray/construct.rb +40 -0
- data/lib/carray/data_type_extension.rb +3 -0
- data/lib/carray/data_type_limits.rb +91 -0
- data/lib/carray/fixlen_string.rb +1 -1
- data/lib/carray/frame/csv_parser.rb +11 -4
- data/lib/carray/frame/frame.rb +81 -10
- data/lib/carray/frame/group.rb +36 -3
- data/lib/carray/frame/io.rb +67 -15
- data/lib/carray/frame/records.rb +18 -4
- data/lib/carray/frame/verbs.rb +14 -11
- data/lib/carray/inspect.rb +42 -9
- data/lib/carray/iterator.rb +143 -0
- data/lib/carray/lazy.rb +0 -37
- data/lib/carray/mask_gap_fill.rb +3 -1
- data/lib/carray/methods/discovery_along.rb +74 -0
- data/lib/carray/methods/factorize.rb +50 -0
- data/lib/carray/methods/is_in.rb +13 -2
- data/lib/carray/methods/locate_addr.rb +75 -2
- data/lib/carray/methods/mask_duplicates.rb +35 -1
- data/lib/carray/methods/nunique.rb +22 -1
- data/lib/carray/methods/repeat.rb +110 -0
- data/lib/carray/methods/unique.rb +41 -1
- data/lib/carray/rng.rb +86 -0
- data/lib/carray/slab_iterator.rb +58 -13
- data/lib/carray/string_operation_extension.rb +5 -1
- data/lib/carray/time.rb +18 -2
- data/lib/carray/window_iterator.rb +142 -20
- data/lib/carray.rb +2 -0
- data/yard-stubs/ca_obj_block.rb +2 -7
- data/yard-stubs/ca_obj_window.rb +10 -2
- data/yard-stubs/carray_access.rb +1 -1
- data/yard-stubs/carray_broadcast.rb +1 -1
- data/yard-stubs/carray_core.rb +0 -80
- data/yard-stubs/carray_count.rb +7 -2
- data/yard-stubs/carray_lazy.rb +205 -0
- data/yard-stubs/carray_math.rb +1486 -3
- data/yard-stubs/carray_median_percentile.rb +16 -2
- data/yard-stubs/carray_order.rb +9 -69
- data/yard-stubs/carray_slab.rb +9 -7
- data/yard-stubs/carray_sort.rb +7 -5
- metadata +9 -1
data/ext/mkkernel.rb
CHANGED
|
@@ -360,6 +360,7 @@ module MkKernel
|
|
|
360
360
|
face_gate: nil,
|
|
361
361
|
object_escape: nil,
|
|
362
362
|
identity_on_empty: false,
|
|
363
|
+
all_nan_result: nil,
|
|
363
364
|
outputs: 1,
|
|
364
365
|
# Two-pass centred algorithm (variance / stddev family).
|
|
365
366
|
# When algorithm: :two_pass_centred is set, `state / init /
|
|
@@ -465,6 +466,30 @@ module MkKernel
|
|
|
465
466
|
end
|
|
466
467
|
end
|
|
467
468
|
end
|
|
469
|
+
# all_nan_result: what an extreme-value reduction answers when every
|
|
470
|
+
# contributing cell was NaN. The `(v < acc) ? v : acc` reduce body is
|
|
471
|
+
# false for NaN, so acc is never updated and the init (+/-INFINITY)
|
|
472
|
+
# leaks out as the answer -- a value that is neither the minimum of
|
|
473
|
+
# anything nor a missing marker, and indistinguishable from data that
|
|
474
|
+
# genuinely held only +INFINITY.
|
|
475
|
+
#
|
|
476
|
+
# :nan -- answer NaN (C99 fmin folded: a lone non-NaN wins, two
|
|
477
|
+
# NaNs give NaN). For kernels whose output can hold it.
|
|
478
|
+
# :undef -- answer UNDEF, by folding into the mask_policy trigger.
|
|
479
|
+
# For kernels whose output is a position (argmin family):
|
|
480
|
+
# an integer output cannot hold NaN, and index 0 would be
|
|
481
|
+
# a lie about where the minimum is.
|
|
482
|
+
#
|
|
483
|
+
# Only the float srcs act on it; integers and boolean have no NaN and
|
|
484
|
+
# their init is a legitimate value.
|
|
485
|
+
unless all_nan_result.nil? || %i[nan undef].include?(all_nan_result)
|
|
486
|
+
raise "#{name}: all_nan_result #{all_nan_result.inspect} invalid " \
|
|
487
|
+
"(expected :nan, :undef, or nil)"
|
|
488
|
+
end
|
|
489
|
+
if all_nan_result == :undef && mask_policy.nil?
|
|
490
|
+
raise "#{name}: all_nan_result: :undef needs a mask_policy " \
|
|
491
|
+
"(UNDEF is written through the mask_policy trigger)"
|
|
492
|
+
end
|
|
468
493
|
if value_arg
|
|
469
494
|
raise "#{name}: value_arg must be a Hash" unless value_arg.is_a?(Hash)
|
|
470
495
|
raise "#{name}: value_arg: target must be :T_IN" unless value_arg[:target] == :T_IN
|
|
@@ -478,16 +503,29 @@ module MkKernel
|
|
|
478
503
|
# selects direction (:min / :max) and output kind:
|
|
479
504
|
# :min / :max -> extremum blob, output data_type = CA_FIXLEN
|
|
480
505
|
# :argmin / :argmax -> position of the extremum, output i64
|
|
506
|
+
# :count_equal -> how many cells memcmp-equal value_arg, output i64
|
|
481
507
|
# Author must also list :fixlen in source: (parallel to how :object
|
|
482
508
|
# opts in via source: + an :object body).
|
|
509
|
+
#
|
|
510
|
+
# :count_equal is the one mode that takes a value_arg, and takes it as a
|
|
511
|
+
# byte blob rather than a scalar: the dispatcher packs the query with
|
|
512
|
+
# rb_ca_obj2ptr into a ca->bytes buffer, the same way the search family
|
|
513
|
+
# already does for a fixlen query (which is what makes a short query
|
|
514
|
+
# NUL-pad to the cell width instead of never matching).
|
|
483
515
|
if fixlen
|
|
484
|
-
unless %i[min max argmin argmax].include?(fixlen)
|
|
485
|
-
raise "#{name}: fixlen: must be :min / :max / :argmin / :argmax (got #{fixlen.inspect})"
|
|
516
|
+
unless %i[min max argmin argmax count_equal].include?(fixlen)
|
|
517
|
+
raise "#{name}: fixlen: must be :min / :max / :argmin / :argmax / :count_equal (got #{fixlen.inspect})"
|
|
486
518
|
end
|
|
487
519
|
raise "#{name}: fixlen: requires :fixlen in source:" unless source.include?(:fixlen)
|
|
488
520
|
raise "#{name}: fixlen: requires mask_policy: :min_count" unless mask_policy == :min_count
|
|
489
521
|
raise "#{name}: fixlen: requires outputs: 1" unless outputs == 1
|
|
490
|
-
raise "#{name}: fixlen: does not support
|
|
522
|
+
raise "#{name}: fixlen: does not support array_arg" if array_arg
|
|
523
|
+
if value_arg && fixlen != :count_equal
|
|
524
|
+
raise "#{name}: fixlen: only :count_equal takes a value_arg"
|
|
525
|
+
end
|
|
526
|
+
if fixlen == :count_equal && !value_arg
|
|
527
|
+
raise "#{name}: fixlen: :count_equal requires value_arg"
|
|
528
|
+
end
|
|
491
529
|
elsif source.include?(:fixlen)
|
|
492
530
|
raise "#{name}: source includes :fixlen but no fixlen: spec given"
|
|
493
531
|
end
|
|
@@ -612,6 +650,7 @@ module MkKernel
|
|
|
612
650
|
face_gate: face_gate,
|
|
613
651
|
object_escape: object_escape,
|
|
614
652
|
identity_on_empty: identity_on_empty,
|
|
653
|
+
all_nan_result: all_nan_result,
|
|
615
654
|
outputs: outputs,
|
|
616
655
|
algorithm: algorithm,
|
|
617
656
|
divisor: divisor,
|
|
@@ -1351,6 +1390,62 @@ module MkKernel
|
|
|
1351
1390
|
io.puts "#undef #{expr_macro}"
|
|
1352
1391
|
end
|
|
1353
1392
|
|
|
1393
|
+
# State vars that start at the type's limit (T_LIMIT_HI / T_LIMIT_LO)
|
|
1394
|
+
# and so can leak their init as an answer. A position state (argmin's
|
|
1395
|
+
# best_i, init "0") is deliberately not one of them: index 0 is a value
|
|
1396
|
+
# a real minimum can legitimately produce.
|
|
1397
|
+
def self.limit_init_state_vars(k, si, oi, src)
|
|
1398
|
+
return [] unless k[:state]
|
|
1399
|
+
k[:state].keys.filter_map do |var|
|
|
1400
|
+
token = pick_family_string(k[:init][var], src, "init")
|
|
1401
|
+
next unless %w[T_LIMIT_HI T_LIMIT_LO].include?(token)
|
|
1402
|
+
[var, resolve_init_expr(k[:init][var], oi, si, src)]
|
|
1403
|
+
end
|
|
1404
|
+
end
|
|
1405
|
+
|
|
1406
|
+
# Emit the all-NaN fix-up for an extreme-value reduction (see the
|
|
1407
|
+
# all_nan_result: validation in MkKernel.reduce for why it exists).
|
|
1408
|
+
#
|
|
1409
|
+
# The per-cell reduce body is left alone: it stays the SIMD-licensed
|
|
1410
|
+
# ternary, so the 8-way horizontal split (reduce_8way_eligible?) and
|
|
1411
|
+
# the `reduction(min:acc)` clause are untouched, and ordinary data pays
|
|
1412
|
+
# one comparison per slab. Carrying a "saw a number" flag as a second
|
|
1413
|
+
# DSL state var was the alternative and costs more than it looks: it
|
|
1414
|
+
# takes the kernel over reduce_8way_eligible?'s single-state gate and
|
|
1415
|
+
# drops min / max back onto the legacy single-accumulator macro.
|
|
1416
|
+
#
|
|
1417
|
+
# acc still sitting at its init means either nothing updated it or the
|
|
1418
|
+
# data genuinely held only +/-INFINITY. Those are told apart by
|
|
1419
|
+
# walking the slab once more for a non-NaN cell -- O(n), and only for
|
|
1420
|
+
# a slab whose answer came out equal to the init.
|
|
1421
|
+
#
|
|
1422
|
+
# Returns true when it emitted anything.
|
|
1423
|
+
def self.emit_all_nan_fixup(io, k, src, si, pairs, valid_guard,
|
|
1424
|
+
indent: " ", flag_var: nil)
|
|
1425
|
+
mode = k[:all_nan_result]
|
|
1426
|
+
return false unless mode
|
|
1427
|
+
return false unless FLOAT_DTYPES.include?(src)
|
|
1428
|
+
cond = pairs.map { |var, init| "#{var} == (#{init})" }.join(" && ")
|
|
1429
|
+
cond = "(#{cond}) && #{valid_guard}" if valid_guard
|
|
1430
|
+
io.puts "#{indent}/* all-NaN fix-up (all_nan_result: #{mode.inspect}). */"
|
|
1431
|
+
io.puts "#{indent}if ( #{cond} ) {"
|
|
1432
|
+
io.puts "#{indent} int64_t __anf_seen = 0;"
|
|
1433
|
+
io.puts "#{indent} ca_size_t __anf_mc = 0;"
|
|
1434
|
+
io.puts "#{indent} CA_SLAB_REDUCE_T_EX(#{si[:c]}, st, p, m, __anf_seen, 0, " \
|
|
1435
|
+
"__anf_seen |= (v == v), __anf_mc);"
|
|
1436
|
+
io.puts "#{indent} (void) __anf_mc;"
|
|
1437
|
+
io.puts "#{indent} if ( ! __anf_seen ) {"
|
|
1438
|
+
if mode == :nan
|
|
1439
|
+
pairs.each { |var, _| io.puts "#{indent} #{var} = (#{si[:c]}) NAN;" }
|
|
1440
|
+
else
|
|
1441
|
+
raise "#{k[:name]}: all_nan_result: :undef needs flag_var" unless flag_var
|
|
1442
|
+
io.puts "#{indent} #{flag_var} = 1;"
|
|
1443
|
+
end
|
|
1444
|
+
io.puts "#{indent} }"
|
|
1445
|
+
io.puts "#{indent}}"
|
|
1446
|
+
true
|
|
1447
|
+
end
|
|
1448
|
+
|
|
1354
1449
|
def self.reduce_macro_suffix(k, src = nil)
|
|
1355
1450
|
# CA_OBJECT cannot ride the SIMD-licensed macros (= _PLUS / _MIN / _MAX
|
|
1356
1451
|
# / _STAR), which assume C operators (= acc is a VALUE, so
|
|
@@ -1774,7 +1869,7 @@ module MkKernel
|
|
|
1774
1869
|
|
|
1775
1870
|
if streamable
|
|
1776
1871
|
emit_reduce_streaming(io, k, si, oi, ruby_wrap, acc_var, acc_init,
|
|
1777
|
-
decls, reduce_stmt, finish_expr, extra_args)
|
|
1872
|
+
decls, reduce_stmt, finish_expr, extra_args, src)
|
|
1778
1873
|
end
|
|
1779
1874
|
|
|
1780
1875
|
# L.1 / L.3 / L.4 (PROPOSAL_REDUCTION_LOOP_INTERCHANGE):
|
|
@@ -1934,6 +2029,17 @@ module MkKernel
|
|
|
1934
2029
|
emit_reduce_slab_call(io, k, src, si, oi, suffix, acc_var, acc_init,
|
|
1935
2030
|
reduce_stmt, "masked_cnt", indent: " ")
|
|
1936
2031
|
end
|
|
2032
|
+
all_nan_flag = nil
|
|
2033
|
+
if k[:all_nan_result]
|
|
2034
|
+
pairs = limit_init_state_vars(k, si, oi, src)
|
|
2035
|
+
if k[:all_nan_result] == :undef && FLOAT_DTYPES.include?(src)
|
|
2036
|
+
all_nan_flag = "__anf_all_nan"
|
|
2037
|
+
io.puts " int #{all_nan_flag} = 0;"
|
|
2038
|
+
end
|
|
2039
|
+
emit_all_nan_fixup(io, k, src, si, pairs,
|
|
2040
|
+
"masked_cnt < st.slab_elements",
|
|
2041
|
+
indent: " ", flag_var: all_nan_flag)
|
|
2042
|
+
end
|
|
1937
2043
|
trigger = case k[:mask_policy]
|
|
1938
2044
|
when :strict then "masked_cnt > 0"
|
|
1939
2045
|
when :all_masked then "masked_cnt == st.slab_elements"
|
|
@@ -1955,6 +2061,7 @@ module MkKernel
|
|
|
1955
2061
|
": st.slab_elements - masked_cnt < min_count)"
|
|
1956
2062
|
end
|
|
1957
2063
|
end
|
|
2064
|
+
trigger = "(#{trigger}) || #{all_nan_flag}" if all_nan_flag
|
|
1958
2065
|
finish_emit = view_flat \
|
|
1959
2066
|
? "(transform_active ? (outer_off + ((ca_size_t)(#{finish_expr})) * axis_vstride) : ((ca_size_t)(#{finish_expr})))" \
|
|
1960
2067
|
: "(#{finish_expr})"
|
|
@@ -2037,13 +2144,15 @@ module MkKernel
|
|
|
2037
2144
|
# the numeric argmin's best_i. mask_policy is always :min_count here.
|
|
2038
2145
|
def self.emit_reduce_native_fixlen(io, k, src)
|
|
2039
2146
|
name = k[:name]
|
|
2147
|
+
counting = k[:fixlen] == :count_equal
|
|
2040
2148
|
want_max = %i[max argmax].include?(k[:fixlen])
|
|
2041
|
-
index_out = %i[argmin argmax].include?(k[:fixlen])
|
|
2149
|
+
index_out = %i[argmin argmax].include?(k[:fixlen]) || counting
|
|
2042
2150
|
cmp = want_max ? ">" : "<"
|
|
2151
|
+
varg = counting ? ", const char *value_arg" : ""
|
|
2043
2152
|
|
|
2044
2153
|
io.puts
|
|
2045
2154
|
io.puts "static VALUE"
|
|
2046
|
-
io.puts "#{name}_ki_native_fixlen (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis, ca_size_t min_count)"
|
|
2155
|
+
io.puts "#{name}_ki_native_fixlen (VALUE self, CArray *ca, int8_t *slab_axes, int8_t naxes, int keep_axis#{varg}, ca_size_t min_count)"
|
|
2047
2156
|
io.puts "{"
|
|
2048
2157
|
io.puts " ca_size_t K = ca->bytes; /* uniform fixlen byte width */"
|
|
2049
2158
|
if index_out
|
|
@@ -2064,9 +2173,13 @@ module MkKernel
|
|
|
2064
2173
|
io.puts " ca_size_t out_i = 0;"
|
|
2065
2174
|
io.puts " boolean8_t *op_mask = NULL; /* lazily allocated on first UNDEF */"
|
|
2066
2175
|
io.puts " while ( ca_iter_state_next_slab_axes(&st, &p, &m) ) {"
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2176
|
+
if counting
|
|
2177
|
+
io.puts " int64_t cnt = 0;"
|
|
2178
|
+
else
|
|
2179
|
+
io.puts " const char *best = NULL;"
|
|
2180
|
+
io.puts " ca_size_t best_i = 0;"
|
|
2181
|
+
io.puts " (void) best_i;" unless index_out # value output ignores the index
|
|
2182
|
+
end
|
|
2070
2183
|
io.puts " ca_size_t masked_cnt = 0;"
|
|
2071
2184
|
io.puts " int8_t sndim = st.slab_ndim;"
|
|
2072
2185
|
io.puts " ca_size_t sidx[CA_RANK_MAX] = { 0 };"
|
|
@@ -2082,12 +2195,18 @@ module MkKernel
|
|
|
2082
2195
|
io.puts " }"
|
|
2083
2196
|
io.puts " else {"
|
|
2084
2197
|
io.puts " const char *q = (const char *) p + doff;"
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2198
|
+
if counting
|
|
2199
|
+
io.puts " if ( memcmp(q, value_arg, (size_t) K) == 0 ) {"
|
|
2200
|
+
io.puts " cnt++;"
|
|
2201
|
+
io.puts " }"
|
|
2202
|
+
else
|
|
2203
|
+
io.puts " if ( best == NULL ) {"
|
|
2204
|
+
io.puts " best = q; best_i = idx;"
|
|
2205
|
+
io.puts " }"
|
|
2206
|
+
io.puts " else if ( memcmp(q, best, (size_t) K) #{cmp} 0 ) {"
|
|
2207
|
+
io.puts " best = q; best_i = idx;"
|
|
2208
|
+
io.puts " }"
|
|
2209
|
+
end
|
|
2091
2210
|
io.puts " }"
|
|
2092
2211
|
io.puts " /* row-major odometer (innermost slab axis fastest) so idx"
|
|
2093
2212
|
io.puts " matches CA_SLAB_REDUCE_T's flat slab index. */"
|
|
@@ -2096,8 +2215,16 @@ module MkKernel
|
|
|
2096
2215
|
io.puts " sidx[sk] = 0;"
|
|
2097
2216
|
io.puts " }"
|
|
2098
2217
|
io.puts " }"
|
|
2099
|
-
|
|
2100
|
-
|
|
2218
|
+
# ERI.0: a count over nothing is 0, not UNDEF -- so the default
|
|
2219
|
+
# (min_count < 0) never fires the mask for :count_equal. An explicit
|
|
2220
|
+
# min_count: still does.
|
|
2221
|
+
if counting
|
|
2222
|
+
io.puts " if ( min_count < 0 ? 0"
|
|
2223
|
+
io.puts " : st.slab_elements - masked_cnt < min_count ) {"
|
|
2224
|
+
else
|
|
2225
|
+
io.puts " if ( min_count < 0 ? masked_cnt == st.slab_elements"
|
|
2226
|
+
io.puts " : st.slab_elements - masked_cnt < min_count ) {"
|
|
2227
|
+
end
|
|
2101
2228
|
io.puts " if ( ! op_mask ) {"
|
|
2102
2229
|
io.puts " ca_create_mask(co);"
|
|
2103
2230
|
io.puts " op_mask = (boolean8_t *) co->mask->ptr;"
|
|
@@ -2111,7 +2238,9 @@ module MkKernel
|
|
|
2111
2238
|
io.puts " out_i++;"
|
|
2112
2239
|
io.puts " }"
|
|
2113
2240
|
io.puts " else {"
|
|
2114
|
-
if
|
|
2241
|
+
if counting
|
|
2242
|
+
io.puts " ((int64_t *) op)[out_i] = cnt;"
|
|
2243
|
+
elsif index_out
|
|
2115
2244
|
io.puts " ((int64_t *) op)[out_i] = (int64_t) best_i;"
|
|
2116
2245
|
else
|
|
2117
2246
|
io.puts " memcpy(op + out_i * K, best, (size_t) K);"
|
|
@@ -2439,6 +2568,49 @@ module MkKernel
|
|
|
2439
2568
|
buf_decls = plus_info.map do |pi|
|
|
2440
2569
|
" #{pi[:c_type]} #{pi[:buf]}[512];\n"
|
|
2441
2570
|
end.join
|
|
2571
|
+
|
|
2572
|
+
# all-NaN fix-up for the tiled core (see emit_all_nan_fixup for the
|
|
2573
|
+
# reduction counterpart). Each output cell of a tile has its own
|
|
2574
|
+
# accumulator, so the check is per column: a column whose accumulator
|
|
2575
|
+
# is still at the init either saw only NaN or only +/-INFINITY.
|
|
2576
|
+
#
|
|
2577
|
+
# The `_Pragma("omp simd")` j-loop is left alone -- carrying a
|
|
2578
|
+
# "saw a number" flag beside the accumulator would double its loads
|
|
2579
|
+
# and stores on every cell of every column. Instead the tile is
|
|
2580
|
+
# tested once after the M loop (tile_len comparisons against M *
|
|
2581
|
+
# tile_len already done), and only a tile that holds a suspicious
|
|
2582
|
+
# column re-reads its rows.
|
|
2583
|
+
li_nan_fixup =
|
|
2584
|
+
if k[:all_nan_result] == :nan && FLOAT_DTYPES.include?(src) &&
|
|
2585
|
+
plus_info.size == 1
|
|
2586
|
+
pi = plus_info.first
|
|
2587
|
+
seen_buf = "__li_buf_seen"
|
|
2588
|
+
lambda do |m_step|
|
|
2589
|
+
<<~C.rstrip
|
|
2590
|
+
{
|
|
2591
|
+
int __li_susp = 0;
|
|
2592
|
+
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2593
|
+
if ( #{pi[:buf]}[__j] == (#{pi[:c_type]}) (#{pi[:init]}) ) { __li_susp = 1; break; }
|
|
2594
|
+
}
|
|
2595
|
+
if ( __li_susp ) {
|
|
2596
|
+
int8_t #{seen_buf}[512];
|
|
2597
|
+
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) #{seen_buf}[__j] = 0;
|
|
2598
|
+
for ( ca_size_t __li_i = 0; __li_i < __li_M; __li_i++ ) {
|
|
2599
|
+
const #{si[:c]} *__li_row = __li_plane + __li_i * #{m_step} + __li_tile;
|
|
2600
|
+
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2601
|
+
#{seen_buf}[__j] |= (__li_row[__j] == __li_row[__j]);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2605
|
+
if ( ! #{seen_buf}[__j] ) #{pi[:buf]}[__j] = (#{pi[:c_type]}) NAN;
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
C
|
|
2610
|
+
end
|
|
2611
|
+
else
|
|
2612
|
+
lambda { |_m_step| "" }
|
|
2613
|
+
end
|
|
2442
2614
|
init_loops = plus_info.map do |pi|
|
|
2443
2615
|
" for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {\n" \
|
|
2444
2616
|
" #{pi[:buf]}[__j] = (#{pi[:c_type]}) (#{pi[:init]});\n" \
|
|
@@ -2468,6 +2640,7 @@ module MkKernel
|
|
|
2468
2640
|
(void) v;
|
|
2469
2641
|
}
|
|
2470
2642
|
}
|
|
2643
|
+
#{li_nan_fixup.call("__li_INNER")}
|
|
2471
2644
|
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2472
2645
|
#{opv}[__li_o * __li_INNER + __li_tile + __j] = (#{oi[:c]}) (#{finish_li});
|
|
2473
2646
|
}
|
|
@@ -2688,6 +2861,7 @@ module MkKernel
|
|
|
2688
2861
|
(void) v;
|
|
2689
2862
|
}
|
|
2690
2863
|
}
|
|
2864
|
+
#{li_nan_fixup.call("__li_INNER")}
|
|
2691
2865
|
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2692
2866
|
__li_op_k[__li_tile + __j] = (#{oi[:c]}) (#{finish_li});
|
|
2693
2867
|
}
|
|
@@ -2846,6 +3020,7 @@ module MkKernel
|
|
|
2846
3020
|
(void) v;
|
|
2847
3021
|
}
|
|
2848
3022
|
}
|
|
3023
|
+
#{li_nan_fixup.call("__li_M_stride")}
|
|
2849
3024
|
for ( ca_size_t __j = 0; __j < __li_tile_len; __j++ ) {
|
|
2850
3025
|
__li_op_k[__li_tile + __j] = (#{oi[:c]}) (#{finish_li});
|
|
2851
3026
|
}
|
|
@@ -2937,6 +3112,10 @@ module MkKernel
|
|
|
2937
3112
|
if min_count
|
|
2938
3113
|
io.puts " ca_size_t masked_cnt = 0;"
|
|
2939
3114
|
io.puts " CA_SLAB_REDUCE_T_EX(#{si[:c]}, st, p, m, #{acc_var}, #{acc_init}, #{reduce_stmt}, masked_cnt);"
|
|
3115
|
+
if k[:all_nan_result]
|
|
3116
|
+
emit_all_nan_fixup(io, k, src, si, limit_init_state_vars(k, si, oi, src),
|
|
3117
|
+
"masked_cnt < st.slab_elements", indent: " ")
|
|
3118
|
+
end
|
|
2940
3119
|
# Same trigger as single-output :min_count: legacy default (all_masked)
|
|
2941
3120
|
# when min_count < 0, otherwise need at least min_count valid cells.
|
|
2942
3121
|
trigger = "(min_count < 0 ? masked_cnt == st.slab_elements " \
|
|
@@ -3012,7 +3191,7 @@ module MkKernel
|
|
|
3012
3191
|
# - mask present: ca_has_mask(ca)
|
|
3013
3192
|
def self.emit_reduce_streaming(io, k, si, oi, ruby_wrap, acc_var,
|
|
3014
3193
|
acc_init, decls, reduce_stmt,
|
|
3015
|
-
finish_expr, extra_args)
|
|
3194
|
+
finish_expr, extra_args, src)
|
|
3016
3195
|
name = k[:name]
|
|
3017
3196
|
min_count = (k[:mask_policy] == :min_count)
|
|
3018
3197
|
has_mp = !k[:mask_policy].nil?
|
|
@@ -3043,6 +3222,14 @@ module MkKernel
|
|
|
3043
3222
|
# acc_var requires explicit init (= the macro normally does this).
|
|
3044
3223
|
# Other state vars in decls already include `= init` per line.
|
|
3045
3224
|
io.puts " #{acc_var} = (#{acc_init});"
|
|
3225
|
+
# all-NaN fix-up, streaming variant. The other two paths confirm a
|
|
3226
|
+
# suspicious answer by re-reading the data; here re-reading means
|
|
3227
|
+
# evaluating the lazy chain a second time, so the flag rides along in
|
|
3228
|
+
# the chunk loop instead. The loop already pays for producing each
|
|
3229
|
+
# cell, which is what makes one more compare affordable here and not
|
|
3230
|
+
# in the tiled core.
|
|
3231
|
+
all_nan_stream = k[:all_nan_result] == :nan && FLOAT_DTYPES.include?(src)
|
|
3232
|
+
io.puts " int64_t __anf_seen = 0;" if all_nan_stream
|
|
3046
3233
|
if has_mp
|
|
3047
3234
|
# Mask-policy reductions need masked_cnt to satisfy the macro/
|
|
3048
3235
|
# finish_expr signature. On streaming we have no mask, so it's
|
|
@@ -3070,11 +3257,15 @@ module MkKernel
|
|
|
3070
3257
|
io.puts " for ( __i = 0; __i < __n; __i++ ) {"
|
|
3071
3258
|
io.puts " #{si[:c]} v = __chunk[__i];"
|
|
3072
3259
|
io.puts " #{reduce_stmt};"
|
|
3260
|
+
io.puts " __anf_seen |= (v == v);" if all_nan_stream
|
|
3073
3261
|
io.puts " }"
|
|
3074
3262
|
io.puts " __outer_off += __r;"
|
|
3075
3263
|
io.puts " }"
|
|
3076
3264
|
io.puts " ca_lazy_arena_release(__chunk);"
|
|
3077
3265
|
io.puts " ca_lazy_arena_exit();"
|
|
3266
|
+
if all_nan_stream
|
|
3267
|
+
io.puts " if ( ! __anf_seen && ca->elements > 0 ) #{acc_var} = (#{si[:c]}) NAN;"
|
|
3268
|
+
end
|
|
3078
3269
|
if has_mp
|
|
3079
3270
|
# Streaming path has no mask source, so masked_cnt is 0; min_count
|
|
3080
3271
|
# / strict / all_masked triggers all evaluate to false except
|
|
@@ -3318,7 +3509,17 @@ module MkKernel
|
|
|
3318
3509
|
k[:source].each do |s|
|
|
3319
3510
|
si = DTYPES[s]
|
|
3320
3511
|
# Per-src value_arg cast: NUM2LL / NUM2ULL / NUM2DBL -> (T_IN).
|
|
3321
|
-
varg_decl = has_varg
|
|
3512
|
+
varg_decl = if !has_varg
|
|
3513
|
+
""
|
|
3514
|
+
elsif s == :fixlen
|
|
3515
|
+
# A fixlen query is a runtime-width byte blob with no
|
|
3516
|
+
# scalar cast; pack it the way the search family does,
|
|
3517
|
+
# which NUL-pads a short String to the cell width.
|
|
3518
|
+
" char *value_arg = ALLOCA_N(char, src->bytes);\n" \
|
|
3519
|
+
" rb_ca_obj2ptr(self, rval, value_arg);\n"
|
|
3520
|
+
else
|
|
3521
|
+
" #{si[:c]} value_arg = (#{si[:c]}) #{si[:num2c]}(rval);\n"
|
|
3522
|
+
end
|
|
3322
3523
|
if use_result_var || has_varg
|
|
3323
3524
|
io.puts " case #{si[:ca]}: {"
|
|
3324
3525
|
io.print varg_decl unless varg_decl.empty?
|
|
@@ -5333,7 +5534,7 @@ module MkKernel
|
|
|
5333
5534
|
io.puts " }"
|
|
5334
5535
|
io.puts " if ( self_face_comparable ) {"
|
|
5335
5536
|
io.puts " if ( rval_is_face ) {"
|
|
5336
|
-
io.puts
|
|
5537
|
+
io.puts %Q[ rval = ca_face_operand_descend(rval, "#{name}_ki");]
|
|
5337
5538
|
io.puts " }"
|
|
5338
5539
|
io.puts " } else if ( self_was_face ) {"
|
|
5339
5540
|
io.puts " if ( rb_respond_to(self_ref, rb_intern(\"to_comparable\")) ) {"
|
|
@@ -6443,6 +6644,7 @@ MkKernel.reduce :min,
|
|
|
6443
6644
|
bool: "acc = ((uint64_t) v < acc) ? (uint64_t) v : acc",
|
|
6444
6645
|
object: 'if (acc == Qundef) acc = v; else if (RTEST(rb_funcall(v, rb_intern("<"), 1, acc))) acc = v;' },
|
|
6445
6646
|
reduction_kind: :min, # SL.1.2
|
|
6647
|
+
all_nan_result: :nan,
|
|
6446
6648
|
# CA_FIXLEN: memcmp lexicographic min (byte order == the fixlen sort
|
|
6447
6649
|
# order); the numeric reduce/init above are unused for fixlen (bespoke
|
|
6448
6650
|
# slab walk, see the fixlen: option in MkKernel.reduce).
|
|
@@ -6467,6 +6669,7 @@ MkKernel.reduce :max,
|
|
|
6467
6669
|
bool: "acc = ((uint64_t) v > acc) ? (uint64_t) v : acc",
|
|
6468
6670
|
object: 'if (acc == Qundef) acc = v; else if (RTEST(rb_funcall(v, rb_intern(">"), 1, acc))) acc = v;' },
|
|
6469
6671
|
reduction_kind: :max, # SL.1.2
|
|
6672
|
+
all_nan_result: :nan,
|
|
6470
6673
|
# CA_FIXLEN: memcmp lexicographic max (byte order == the fixlen sort order).
|
|
6471
6674
|
fixlen: :max,
|
|
6472
6675
|
source: MkKernel::ALL_NUMERIC + [:bool, :object, :fixlen],
|
|
@@ -6692,6 +6895,7 @@ MkKernel.reduce :minmax,
|
|
|
6692
6895
|
# together on init, get set together on first reduce).
|
|
6693
6896
|
object: 'if (lo == Qundef) { lo = v; hi = v; } else { if (RTEST(rb_funcall(v, rb_intern("<"), 1, lo))) lo = v; if (RTEST(rb_funcall(v, rb_intern(">"), 1, hi))) hi = v; }' },
|
|
6694
6897
|
outputs: 2,
|
|
6898
|
+
all_nan_result: :nan,
|
|
6695
6899
|
finish: { min: "lo", max: "hi" },
|
|
6696
6900
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
6697
6901
|
# bool: u64 (Integer 0/1) so minmax returns [0/1, 0/1], not
|
|
@@ -6740,6 +6944,7 @@ MkKernel.reduce :argmin,
|
|
|
6740
6944
|
# An ORDERABLE Face descends to its numeric storage (position output needs
|
|
6741
6945
|
# no re-lift; the axis-local index is identical for Face and storage).
|
|
6742
6946
|
face_gate: :strip,
|
|
6947
|
+
all_nan_result: :undef,
|
|
6743
6948
|
public_method: :min_index
|
|
6744
6949
|
|
|
6745
6950
|
MkKernel.reduce :argmax,
|
|
@@ -6758,6 +6963,7 @@ MkKernel.reduce :argmax,
|
|
|
6758
6963
|
fallback: :raise,
|
|
6759
6964
|
mask_policy: :min_count,
|
|
6760
6965
|
face_gate: :strip,
|
|
6966
|
+
all_nan_result: :undef,
|
|
6761
6967
|
public_method: :max_index
|
|
6762
6968
|
|
|
6763
6969
|
# ---- argmin_addr / argmax_addr (view-flat address variants) ---------
|
|
@@ -6801,6 +7007,7 @@ MkKernel.reduce :argmin_addr,
|
|
|
6801
7007
|
mask_policy: :min_count,
|
|
6802
7008
|
semantics: :view_flat,
|
|
6803
7009
|
face_gate: :strip,
|
|
7010
|
+
all_nan_result: :undef,
|
|
6804
7011
|
public_method: :min_addr
|
|
6805
7012
|
|
|
6806
7013
|
MkKernel.reduce :argmax_addr,
|
|
@@ -6818,6 +7025,7 @@ MkKernel.reduce :argmax_addr,
|
|
|
6818
7025
|
mask_policy: :min_count,
|
|
6819
7026
|
semantics: :view_flat,
|
|
6820
7027
|
face_gate: :strip,
|
|
7028
|
+
all_nan_result: :undef,
|
|
6821
7029
|
public_method: :max_addr
|
|
6822
7030
|
|
|
6823
7031
|
# ---- mask_policy demos ------------------------------------------------
|
|
@@ -6945,9 +7153,13 @@ MkKernel.reduce :count_false,
|
|
|
6945
7153
|
|
|
6946
7154
|
MkKernel.reduce :count_equal,
|
|
6947
7155
|
init: "0",
|
|
6948
|
-
|
|
7156
|
+
# The object lane compares with rb_equal (= Ruby ==), not the C ==, which
|
|
7157
|
+
# on two VALUEs would ask whether they are the same object.
|
|
7158
|
+
reduce: { numeric: "if (v == value_arg) acc += 1",
|
|
7159
|
+
object: "if (RTEST(rb_equal(v, value_arg))) acc += 1" },
|
|
6949
7160
|
reduction_kind: :plus, # SL.1.4 (conditional predication; clang predicates safely under reduction(+:acc))
|
|
6950
|
-
source: MkKernel::ALL_NUMERIC,
|
|
7161
|
+
source: MkKernel::ALL_NUMERIC + [:object, :fixlen],
|
|
7162
|
+
fixlen: :count_equal, # bespoke memcmp walk (a blob has no scalar C type)
|
|
6951
7163
|
output: :i64,
|
|
6952
7164
|
ruby_scalar: :LL2NUM,
|
|
6953
7165
|
fallback: :raise,
|
|
@@ -7140,22 +7352,38 @@ MkKernel.scan :cumprod,
|
|
|
7140
7352
|
# init Qnil is the "no running extremum yet" sentinel, also never leaked
|
|
7141
7353
|
# (unseen cells are masked). First unmasked cell adopts v as acc;
|
|
7142
7354
|
# subsequent unmasked cells compare via rb_funcall(:>) / rb_funcall(:<).
|
|
7355
|
+
# The float lane starts at NaN and folds with C99 fmin / fmax, whose
|
|
7356
|
+
# rule is exactly the one wanted: a lone number beats NaN, two NaNs give
|
|
7357
|
+
# NaN. So a prefix that has seen only NaN answers NaN instead of leaking
|
|
7358
|
+
# +/-INFINITY -- the running form of the rule min / max follow
|
|
7359
|
+
# (all_nan_result:). fmin / fmax are exact on a float at either width,
|
|
7360
|
+
# so the f32 lane needs no narrowing. Integer and boolean have no NaN
|
|
7361
|
+
# and keep the limit init and the plain compare.
|
|
7143
7362
|
MkKernel.scan :cummax,
|
|
7144
7363
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
7145
7364
|
output: { bool: :u64, default: :preserve },
|
|
7146
|
-
init: { numeric: "T_LIMIT_LO", bool: "T_LIMIT_LO", object: "Qnil" },
|
|
7147
|
-
step: {
|
|
7365
|
+
init: { float: "NAN", numeric: "T_LIMIT_LO", bool: "T_LIMIT_LO", object: "Qnil" },
|
|
7366
|
+
step: { float: "acc = fmax(acc, v); r = acc",
|
|
7367
|
+
numeric: "if (v > acc) acc = v; r = acc",
|
|
7148
7368
|
bool: "if ((uint64_t) v > acc) acc = v; r = acc",
|
|
7149
7369
|
object: 'if (acc == Qnil) acc = v; else if (RTEST(rb_funcall(v, rb_intern(">"), 1, acc))) acc = v; r = acc' },
|
|
7150
7370
|
fallback: :raise,
|
|
7151
7371
|
axis_default: :flatten,
|
|
7152
7372
|
empty: :undef
|
|
7153
7373
|
|
|
7374
|
+
# The float lane starts at NaN and folds with C99 fmin / fmax, whose
|
|
7375
|
+
# rule is exactly the one wanted: a lone number beats NaN, two NaNs give
|
|
7376
|
+
# NaN. So a prefix that has seen only NaN answers NaN instead of leaking
|
|
7377
|
+
# +/-INFINITY -- the running form of the rule min / max follow
|
|
7378
|
+
# (all_nan_result:). fmin / fmax are exact on a float at either width,
|
|
7379
|
+
# so the f32 lane needs no narrowing. Integer and boolean have no NaN
|
|
7380
|
+
# and keep the limit init and the plain compare.
|
|
7154
7381
|
MkKernel.scan :cummin,
|
|
7155
7382
|
source: MkKernel::ALL_NUMERIC + [:bool, :object],
|
|
7156
7383
|
output: { bool: :u64, default: :preserve },
|
|
7157
|
-
init: { numeric: "T_LIMIT_HI", bool: "T_LIMIT_HI", object: "Qnil" },
|
|
7158
|
-
step: {
|
|
7384
|
+
init: { float: "NAN", numeric: "T_LIMIT_HI", bool: "T_LIMIT_HI", object: "Qnil" },
|
|
7385
|
+
step: { float: "acc = fmin(acc, v); r = acc",
|
|
7386
|
+
numeric: "if (v < acc) acc = v; r = acc",
|
|
7159
7387
|
bool: "if ((uint64_t) v < acc) acc = v; r = acc",
|
|
7160
7388
|
object: 'if (acc == Qnil) acc = v; else if (RTEST(rb_funcall(v, rb_intern("<"), 1, acc))) acc = v; r = acc' },
|
|
7161
7389
|
fallback: :raise,
|
|
@@ -7543,12 +7771,32 @@ MkKernel.search :search_nearest,
|
|
|
7543
7771
|
object: <<~C,
|
|
7544
7772
|
/* CA_OBJECT nearest: minimum of query_val.distance(cell), compared
|
|
7545
7773
|
with `<` (matches the legacy flat proc_nearest_addr_VALUE). */
|
|
7774
|
+
/* Nearest needs a metric. #distance is the protocol the 2.0 flat
|
|
7775
|
+
kernel used, back when Numeric#distance was a monkey patch; it
|
|
7776
|
+
is an opt-in refinement now, and a refinement does not reach an
|
|
7777
|
+
rb_funcall from C -- so a number reaching here answers no more
|
|
7778
|
+
than a String does. Measure a number the way #distance itself
|
|
7779
|
+
does, keep #distance for anything that defines a real one, and
|
|
7780
|
+
refuse the rest by name rather than let a bare NoMethodError out
|
|
7781
|
+
of the loop below. */
|
|
7782
|
+
ID nearest_id = rb_intern("distance");
|
|
7783
|
+
int nearest_by_distance = rb_respond_to(query_val, nearest_id);
|
|
7784
|
+
if ( ! nearest_by_distance && ! rb_obj_is_kind_of(query_val, rb_cNumeric) ) {
|
|
7785
|
+
rb_raise(rb_eCADataTypeError,
|
|
7786
|
+
"search_nearest: nearest needs a distance, and %s is neither a "
|
|
7787
|
+
"number nor answers #distance (define one on the stored "
|
|
7788
|
+
"objects, or use search / bsearch for an exact match)",
|
|
7789
|
+
rb_obj_classname(query_val));
|
|
7790
|
+
}
|
|
7546
7791
|
result = (ca_size_t) -1;
|
|
7547
7792
|
VALUE best = Qnil;
|
|
7548
7793
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7549
7794
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7550
7795
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7551
|
-
VALUE dist =
|
|
7796
|
+
VALUE dist = nearest_by_distance
|
|
7797
|
+
? rb_funcall(query_val, nearest_id, 1, v)
|
|
7798
|
+
: rb_funcall(rb_funcall(query_val, '-', 1, v),
|
|
7799
|
+
rb_intern("abs"), 0);
|
|
7552
7800
|
if ( NIL_P(best) || RTEST(rb_funcall(dist, rb_intern("<"), 1, best)) ) {
|
|
7553
7801
|
best = dist; result = i;
|
|
7554
7802
|
}
|
|
@@ -7724,12 +7972,32 @@ MkKernel.search :search_nearest_addr,
|
|
|
7724
7972
|
object: <<~C,
|
|
7725
7973
|
/* CA_OBJECT nearest (view_flat addr): minimum of
|
|
7726
7974
|
query_val.distance(cell), compared with `<`. */
|
|
7975
|
+
/* Nearest needs a metric. #distance is the protocol the 2.0 flat
|
|
7976
|
+
kernel used, back when Numeric#distance was a monkey patch; it
|
|
7977
|
+
is an opt-in refinement now, and a refinement does not reach an
|
|
7978
|
+
rb_funcall from C -- so a number reaching here answers no more
|
|
7979
|
+
than a String does. Measure a number the way #distance itself
|
|
7980
|
+
does, keep #distance for anything that defines a real one, and
|
|
7981
|
+
refuse the rest by name rather than let a bare NoMethodError out
|
|
7982
|
+
of the loop below. */
|
|
7983
|
+
ID nearest_id = rb_intern("distance");
|
|
7984
|
+
int nearest_by_distance = rb_respond_to(query_val, nearest_id);
|
|
7985
|
+
if ( ! nearest_by_distance && ! rb_obj_is_kind_of(query_val, rb_cNumeric) ) {
|
|
7986
|
+
rb_raise(rb_eCADataTypeError,
|
|
7987
|
+
"search_nearest_addr: nearest needs a distance, and %s is neither a "
|
|
7988
|
+
"number nor answers #distance (define one on the stored "
|
|
7989
|
+
"objects, or use search / bsearch for an exact match)",
|
|
7990
|
+
rb_obj_classname(query_val));
|
|
7991
|
+
}
|
|
7727
7992
|
result = (ca_size_t) -1;
|
|
7728
7993
|
VALUE best = Qnil;
|
|
7729
7994
|
for ( ca_size_t i = 0; i < slab_n; i++ ) {
|
|
7730
7995
|
if ( mask_in && mask_in[i * slab_mask_stride] ) continue;
|
|
7731
7996
|
T_LOAD v = *(T_LOAD *)(slab_ptr + i * slab_stride);
|
|
7732
|
-
VALUE dist =
|
|
7997
|
+
VALUE dist = nearest_by_distance
|
|
7998
|
+
? rb_funcall(query_val, nearest_id, 1, v)
|
|
7999
|
+
: rb_funcall(rb_funcall(query_val, '-', 1, v),
|
|
8000
|
+
rb_intern("abs"), 0);
|
|
7733
8001
|
if ( NIL_P(best) || RTEST(rb_funcall(dist, rb_intern("<"), 1, best)) ) {
|
|
7734
8002
|
best = dist; result = i;
|
|
7735
8003
|
}
|
data/ext/ruby_carray.c
CHANGED
|
@@ -121,6 +121,13 @@ void Init_ca_kernel_iterator ();
|
|
|
121
121
|
|
|
122
122
|
void Init_carray_slab ();
|
|
123
123
|
|
|
124
|
+
void Init_carray_address_basis (); /* CArray::AddressBasis: pointer + byte
|
|
125
|
+
strides lent for the length of a block,
|
|
126
|
+
for code that addresses cells itself.
|
|
127
|
+
A runtime facility at the ca_attach
|
|
128
|
+
layer, not a user surface; see
|
|
129
|
+
guides/devel/21_address_basis.md */
|
|
130
|
+
|
|
124
131
|
void
|
|
125
132
|
Init_carray_ext (void)
|
|
126
133
|
{
|
|
@@ -310,7 +317,7 @@ Init_carray_ext (void)
|
|
|
310
317
|
Init_ca_obj_refer();
|
|
311
318
|
Init_ca_obj_stride(); /* CAREFUL: must precede CAStride subclasses
|
|
312
319
|
(farray / block / transpose / repeat /
|
|
313
|
-
|
|
320
|
+
field). */
|
|
314
321
|
Init_ca_obj_farray();
|
|
315
322
|
Init_ca_obj_block();
|
|
316
323
|
Init_ca_obj_select();
|
|
@@ -373,5 +380,7 @@ Init_carray_ext (void)
|
|
|
373
380
|
Init_ca_kernel_iterator();
|
|
374
381
|
|
|
375
382
|
Init_carray_slab();
|
|
383
|
+
|
|
384
|
+
Init_carray_address_basis();
|
|
376
385
|
}
|
|
377
386
|
|
data/ext/version.h
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#define CA_VERSION "3.0.
|
|
2
|
-
#define CA_VERSION_CODE
|
|
1
|
+
#define CA_VERSION "3.0.2"
|
|
2
|
+
#define CA_VERSION_CODE 302
|
|
3
3
|
#define CA_VERSION_MAJOR 3
|
|
4
4
|
#define CA_VERSION_MINOR 0
|
|
5
|
-
#define CA_VERSION_TEENY
|
|
6
|
-
#define CA_VERSION_DATE "2026/09/
|
|
5
|
+
#define CA_VERSION_TEENY 2
|
|
6
|
+
#define CA_VERSION_DATE "2026/09/24"
|
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
|
|
31
31
|
class CArray
|
|
32
32
|
inspect_rb = "carray/inspect"
|
|
33
|
-
autoload_method "inspect",
|
|
34
|
-
autoload_method "
|
|
33
|
+
autoload_method "inspect", inspect_rb
|
|
34
|
+
autoload_method "inspect_full", inspect_rb
|
|
35
|
+
autoload_method "source_code", inspect_rb
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
# ---- Serializer / Marshal / dump / load ------------------------------------
|
|
@@ -42,7 +43,6 @@ class CArray
|
|
|
42
43
|
autoload_method "self.save", serialize_rb
|
|
43
44
|
autoload_method "self.load", serialize_rb
|
|
44
45
|
autoload_method "self.dump", serialize_rb
|
|
45
|
-
autoload_method "self.load_from_file", serialize_rb
|
|
46
46
|
autoload_method "marshal_dump", serialize_rb
|
|
47
47
|
autoload_method "marshal_load", serialize_rb
|
|
48
48
|
end
|
|
@@ -201,11 +201,13 @@ class CArray
|
|
|
201
201
|
autoload_method "self.meshgrid", "carray/methods/meshgrid"
|
|
202
202
|
|
|
203
203
|
autoload_method "bincount", "carray/methods/bincount"
|
|
204
|
+
autoload_method "repeat", "carray/methods/repeat"
|
|
204
205
|
autoload_method "self.broadcast", "carray/methods/broadcast"
|
|
205
206
|
autoload_method "gather_nd", "carray/methods/gather_nd"
|
|
206
207
|
autoload_method "put_nd", "carray/methods/gather_nd"
|
|
207
208
|
autoload_method "mask_duplicates", "carray/methods/mask_duplicates"
|
|
208
209
|
autoload_method "unique", "carray/methods/unique"
|
|
210
|
+
autoload_method "factorize", "carray/methods/factorize"
|
|
209
211
|
autoload_method "is_in", "carray/methods/is_in"
|
|
210
212
|
autoload_method "intersection", "carray/methods/is_in"
|
|
211
213
|
autoload_method "difference", "carray/methods/is_in"
|