carray 3.0.0 → 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/.yardopts +2 -1
- data/CHANGELOG.md +845 -3
- data/{NEWS.md → CHANGELOG.v1.md} +1 -1
- data/README.md +33 -33
- data/carray.gemspec +12 -8
- data/ext/ca_axis_dispatch.c +33 -4
- data/ext/ca_axis_group.c +256 -114
- data/ext/ca_binop_dispatch.c +5 -6
- data/ext/ca_binop_dispatch.h +0 -7
- data/ext/ca_categorical_iterator.c +121 -67
- data/ext/ca_for_each_element.h +6 -8
- data/ext/ca_group_iter.c +1 -1
- data/ext/ca_kernel_iterator.c +386 -74
- data/ext/ca_kernel_iterator.h +145 -45
- data/ext/ca_obj_array.c +62 -20
- data/ext/ca_obj_bincmp.c +57 -27
- data/ext/ca_obj_binop.c +86 -30
- data/ext/ca_obj_block.c +4 -4
- data/ext/ca_obj_const_string.c +88 -27
- data/ext/ca_obj_face.c +104 -0
- data/ext/ca_obj_face.h +48 -1
- data/ext/ca_obj_fake.c +11 -0
- data/ext/ca_obj_fixlen_string.c +21 -6
- data/ext/ca_obj_grid.c +11 -3
- data/ext/ca_obj_meld.c +125 -22
- data/ext/ca_obj_moncmp.c +33 -14
- data/ext/ca_obj_monop.c +19 -7
- data/ext/ca_obj_object.c +107 -4
- data/ext/ca_obj_record.c +3 -1
- data/ext/ca_obj_refer.c +30 -6
- data/ext/ca_obj_roll.c +13 -5
- data/ext/ca_obj_select.c +49 -34
- data/ext/ca_obj_select_axis.c +16 -6
- data/ext/ca_obj_shift.c +3 -3
- data/ext/ca_obj_stack.c +3 -8
- data/ext/ca_obj_stride.c +178 -24
- data/ext/ca_obj_string.c +11 -5
- data/ext/ca_obj_tile.c +13 -5
- data/ext/ca_obj_time.c +3 -1
- data/ext/ca_obj_timedelta.c +3 -1
- data/ext/ca_obj_transpose.c +1 -1
- data/ext/ca_obj_triop.c +62 -23
- data/ext/ca_obj_window.c +94 -19
- data/ext/ca_op_cmplx64.h +123 -0
- data/ext/ca_op_ipower.c +1 -5
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sort_kernels.h +5 -5
- data/ext/ca_sweep_engine.c +362 -155
- data/ext/ca_sweep_engine.h +39 -9
- data/ext/ca_transform_common.c +7 -0
- data/ext/carray.h +148 -132
- data/ext/carray_access.c +83 -49
- data/ext/carray_address_basis.c +590 -0
- data/ext/carray_bincount.c +8 -8
- data/ext/carray_broadcast.c +102 -9
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +2010 -335
- data/ext/carray_call_cfunc.h +153 -0
- data/ext/carray_cast.c +184 -72
- data/ext/carray_conversion.c +22 -22
- data/ext/carray_copy.c +67 -32
- data/ext/carray_core.c +140 -13
- data/ext/carray_count.c +10 -11
- data/ext/carray_element.c +7 -7
- data/ext/carray_factorize.c +72 -51
- data/ext/carray_hold.c +1 -1
- data/ext/carray_index_classifier.c +7 -21
- data/ext/carray_internal.h +36 -3
- data/ext/carray_kernels_bincmp.c +1 -0
- data/ext/carray_kernels_binop.c +320 -298
- data/ext/carray_kernels_init.c +1095 -0
- data/ext/carray_kernels_map.c +1 -0
- data/ext/carray_kernels_moncmp.c +1 -0
- data/ext/carray_kernels_monop.c +412 -411
- data/ext/carray_kernels_reduce_aggregate.c +256 -87
- data/ext/carray_kernels_reduce_boolean.c +1 -0
- data/ext/carray_kernels_reduce_cumulative.c +324 -54
- data/ext/carray_kernels_reduce_extreme.c +605 -58
- data/ext/carray_kernels_reduce_variance.c +1 -0
- data/ext/carray_kernels_scan.c +5 -4
- data/ext/carray_kernels_search.c +187 -106
- data/ext/carray_kernels_sort.c +1 -0
- data/ext/carray_kernels_triop.c +1 -0
- data/ext/carray_lazy.c +175 -5
- data/ext/carray_loop.c +7 -1
- data/ext/carray_mask.c +29 -13
- data/ext/carray_median_percentile.c +56 -1
- data/ext/carray_memory_view.c +46 -21
- data/ext/carray_operator.c +50 -53
- data/ext/carray_order.c +1 -1
- data/ext/carray_partition.c +4 -4
- data/ext/carray_random.c +391 -47
- data/ext/carray_scatter.c +1 -1
- data/ext/carray_slab.c +13 -0
- data/ext/carray_sort.c +22 -24
- data/ext/carray_sort_kernel.c +10 -10
- data/ext/carray_test.c +1 -1
- data/ext/extconf.rb +18 -0
- data/ext/mk_call_cfunc.rb +323 -145
- data/ext/mkkernel.rb +651 -129
- data/ext/ruby_carray.c +28 -17
- data/ext/version.h +4 -4
- data/lib/carray/autoload_carray.rb +24 -3
- data/lib/carray/autoload_method_extension.rb +14 -1
- data/lib/carray/axis_group.rb +84 -7
- data/lib/carray/basics.rb +63 -59
- data/lib/carray/bincount_nd.rb +12 -12
- data/lib/carray/block_iterator.rb +111 -25
- data/lib/carray/categorical.rb +154 -37
- data/lib/carray/categorical_iterator.rb +300 -151
- data/lib/carray/conditional.rb +14 -14
- data/lib/carray/const_string.rb +131 -27
- data/lib/carray/construct.rb +61 -0
- data/lib/carray/core_extensions.rb +62 -5
- data/lib/carray/data_type_extension.rb +21 -1
- data/lib/carray/data_type_limits.rb +91 -0
- data/lib/carray/fixlen_string.rb +1 -1
- data/lib/carray/frame/concat.rb +3 -3
- data/lib/carray/frame/convert.rb +1 -1
- data/lib/carray/frame/csv_parser.rb +11 -4
- data/lib/carray/frame/frame.rb +85 -21
- data/lib/carray/frame/group.rb +36 -3
- data/lib/carray/frame/io.rb +224 -15
- data/lib/carray/frame/records.rb +18 -4
- data/lib/carray/frame/sort.rb +1 -1
- data/lib/carray/frame/verbs.rb +32 -12
- data/lib/carray/fuse_source.rb +123 -0
- data/lib/carray/fusion.rb +218 -0
- data/lib/carray/histogram.rb +16 -16
- data/lib/carray/inspect.rb +43 -16
- data/lib/carray/iterator.rb +147 -3
- data/lib/carray/lazy.rb +88 -73
- data/lib/carray/mask_gap_fill.rb +3 -1
- data/lib/carray/meld_reduce.rb +2 -2
- data/lib/carray/methods/align_addr.rb +1 -1
- data/lib/carray/methods/composition.rb +1 -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 +25 -14
- data/lib/carray/methods/locate_addr.rb +81 -3
- data/lib/carray/methods/mask_duplicates.rb +36 -2
- data/lib/carray/methods/meshgrid.rb +4 -5
- data/lib/carray/methods/mode.rb +2 -2
- data/lib/carray/methods/nunique.rb +23 -2
- data/lib/carray/methods/repeat.rb +110 -0
- data/lib/carray/methods/snap.rb +7 -2
- data/lib/carray/methods/unique.rb +44 -4
- data/lib/carray/methods/value_counts.rb +2 -2
- data/lib/carray/rng.rb +86 -0
- data/lib/carray/runtime.rb +0 -19
- data/lib/carray/slab_iterator.rb +77 -19
- data/lib/carray/string_operation_extension.rb +10 -6
- data/lib/carray/time.rb +906 -491
- data/lib/carray/window_iterator.rb +421 -27
- data/lib/carray.rb +7 -5
- data/yard-stubs/ca_obj_array.rb +385 -0
- data/yard-stubs/ca_obj_bitarray.rb +38 -0
- data/yard-stubs/ca_obj_bitfield.rb +43 -0
- data/yard-stubs/ca_obj_block.rb +68 -0
- data/yard-stubs/ca_obj_byte_swap.rb +56 -0
- data/yard-stubs/ca_obj_fake.rb +31 -0
- data/yard-stubs/ca_obj_farray.rb +32 -0
- data/yard-stubs/ca_obj_field.rb +45 -0
- data/yard-stubs/ca_obj_grid.rb +35 -0
- data/yard-stubs/ca_obj_refer.rb +72 -0
- data/yard-stubs/ca_obj_roll.rb +45 -0
- data/yard-stubs/ca_obj_shift.rb +43 -0
- data/yard-stubs/ca_obj_stride.rb +181 -0
- data/yard-stubs/ca_obj_tile.rb +29 -0
- data/yard-stubs/ca_obj_transpose.rb +40 -0
- data/yard-stubs/ca_obj_window.rb +57 -0
- data/yard-stubs/carray_access.rb +131 -0
- data/yard-stubs/carray_attribute.rb +246 -0
- data/yard-stubs/carray_broadcast.rb +37 -0
- data/yard-stubs/carray_cast.rb +489 -0
- data/yard-stubs/carray_class.rb +65 -0
- data/yard-stubs/carray_conversion.rb +76 -0
- data/yard-stubs/carray_copy.rb +79 -0
- data/yard-stubs/carray_core.rb +34 -0
- data/yard-stubs/carray_count.rb +84 -0
- data/yard-stubs/carray_element.rb +108 -0
- data/yard-stubs/carray_generate.rb +66 -0
- data/yard-stubs/carray_lazy.rb +228 -0
- data/yard-stubs/carray_loop.rb +140 -0
- data/yard-stubs/carray_mask.rb +259 -0
- data/yard-stubs/carray_math.rb +1615 -0
- data/yard-stubs/carray_mathfunc.rb +45 -0
- data/yard-stubs/carray_median_percentile.rb +103 -0
- data/yard-stubs/carray_memory_view.rb +163 -0
- data/yard-stubs/carray_order.rb +252 -0
- data/yard-stubs/carray_random.rb +89 -0
- data/yard-stubs/carray_scatter.rb +106 -0
- data/yard-stubs/carray_slab.rb +59 -0
- data/yard-stubs/carray_sort.rb +165 -0
- data/yard-stubs/carray_test.rb +85 -0
- data/yard-stubs/carray_undef.rb +64 -0
- data/yard-stubs/carray_utils.rb +97 -0
- data/yard-stubs/ruby_carray.rb +193 -0
- metadata +67 -10
- data/ext/ca_obj_unbound_repeat.c +0 -496
data/lib/carray/lazy.rb
CHANGED
|
@@ -73,11 +73,11 @@ class CArray
|
|
|
73
73
|
rad_2pi: CAMonOp::OP_RAD_2PI,
|
|
74
74
|
rad_pi: CAMonOp::OP_RAD_PI,
|
|
75
75
|
|
|
76
|
-
# Sign function (
|
|
76
|
+
# Sign function (preserves the data type). bool/uint → 0/1, sint → -1/0/1,
|
|
77
77
|
# float → -1/0/1 NaN-preserving, complex → unit vector or 0.
|
|
78
78
|
sign: CAMonOp::OP_SIGN,
|
|
79
79
|
|
|
80
|
-
# imag_i:
|
|
80
|
+
# imag_i: type-preserving primitive (0 for numeric, cimag for complex
|
|
81
81
|
# in the real slot). Primarily consumed by the `imag` special case
|
|
82
82
|
# below but also directly callable via `a.lazy.imag_i`; entry here
|
|
83
83
|
# so the direct call fuses instead of falling to eager.
|
|
@@ -118,24 +118,28 @@ class CArray
|
|
|
118
118
|
# complex-specific dispatch (= magnitude as float). Re-express that
|
|
119
119
|
# dispatch via chain composition over existing lazy ops so abs rides
|
|
120
120
|
# the lazy substrate fully:
|
|
121
|
-
# numeric parent: CAMonOp(OP_ABS_I)
|
|
122
|
-
# complex parent: CAMonOp(
|
|
123
|
-
# The complex chain works because ca_monop_abs_i
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
121
|
+
# numeric parent: CAMonOp(OP_ABS_I) (1 node)
|
|
122
|
+
# complex parent: CAMonOp(cast_<float>) ∘ CAMonOp(OP_ABS_I) (2 nodes)
|
|
123
|
+
# The complex chain works because ca_monop_abs_i stores |z| in the
|
|
124
|
+
# real slot with imag=0, then the cast picks up the real part —
|
|
125
|
+
# semantically identical to the eager abs. The cast target is the
|
|
126
|
+
# real component width of the operand (cmplx64 -> f32, cmplx128 ->
|
|
127
|
+
# f64), matching the eager kernel's `complex: :real_of_source`.
|
|
128
|
+
# No new C kernel, no new op_id required.
|
|
127
129
|
# ---------------------------------------------------------------------------
|
|
128
130
|
|
|
131
|
+
# @private
|
|
129
132
|
alias_method :__abs_eager__, :abs
|
|
130
133
|
__abs_eager_um__ = instance_method(:__abs_eager__)
|
|
131
134
|
define_method(:abs) do
|
|
132
135
|
if __lazy_view__?
|
|
133
136
|
abs_i_node = CAMonOp.__build__(self, CAMonOp::OP_ABS_I)
|
|
134
137
|
if complex?
|
|
135
|
-
#
|
|
138
|
+
# float_dt is a Symbol; convert via data_type_code so the cast
|
|
136
139
|
# op_id can be computed by Integer arithmetic.
|
|
140
|
+
float_dt = (data_type == CA_CMPLX64) ? CA_FLOAT32 : CA_FLOAT64
|
|
137
141
|
CAMonOp.__build__(abs_i_node,
|
|
138
|
-
CAMonOp::CAST_BASE + CArray.data_type_code(
|
|
142
|
+
CAMonOp::CAST_BASE + CArray.data_type_code(float_dt))
|
|
139
143
|
else
|
|
140
144
|
abs_i_node
|
|
141
145
|
end
|
|
@@ -174,6 +178,7 @@ class CArray
|
|
|
174
178
|
# mutability on the lazy path is not a regression.
|
|
175
179
|
# ---------------------------------------------------------------------------
|
|
176
180
|
|
|
181
|
+
# @private
|
|
177
182
|
alias_method :__real_eager__, :real
|
|
178
183
|
__real_eager_um__ = instance_method(:__real_eager__)
|
|
179
184
|
define_method(:real) do
|
|
@@ -216,11 +221,11 @@ class CArray
|
|
|
216
221
|
end
|
|
217
222
|
|
|
218
223
|
# ---------------------------------------------------------------------------
|
|
219
|
-
# arg lazy fuse: eager `arg`
|
|
220
|
-
#
|
|
221
|
-
# CAMonOp's cast-before invariant requires input
|
|
224
|
+
# arg lazy fuse: eager `arg` is a data_type-changing monop (see
|
|
225
|
+
# MkKernel.monop :arg output rule).
|
|
226
|
+
# CAMonOp's cast-before invariant requires input data type == output data type
|
|
222
227
|
# at each in-place step, so `arg` can't sit directly in the substrate.
|
|
223
|
-
# Chain compose via the
|
|
228
|
+
# Chain compose via the type-preserving `arg_i` primitive:
|
|
224
229
|
#
|
|
225
230
|
# integer / bool parent → cast_f64 ∘ arg_i (result f64)
|
|
226
231
|
# float parent → arg_i (result same float)
|
|
@@ -228,14 +233,13 @@ class CArray
|
|
|
228
233
|
# component of complex slot; cast extracts
|
|
229
234
|
# the real part, same trick as abs / imag)
|
|
230
235
|
#
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
# header). A user needing exact eager-parity can wrap with
|
|
236
|
-
# `.to_type(:float64)` before / after.
|
|
236
|
+
# Both paths keep the operand's float / complex width (f32 → f32,
|
|
237
|
+
# cmplx64 → f32), and both send integers to f64 since pi does not fit
|
|
238
|
+
# an integer slot. That agrees with the general lazy-substrate rule
|
|
239
|
+
# "scalar keeps operand's precision" (see ca_lazy_wrap_scalar header).
|
|
237
240
|
# ---------------------------------------------------------------------------
|
|
238
241
|
|
|
242
|
+
# @private
|
|
239
243
|
alias_method :__arg_eager__, :arg
|
|
240
244
|
__arg_eager_um__ = instance_method(:__arg_eager__)
|
|
241
245
|
define_method(:arg) do
|
|
@@ -307,7 +311,6 @@ class CArray
|
|
|
307
311
|
|
|
308
312
|
# IEEE 754 remainder (distinct semantics from `%` / `mod`: float
|
|
309
313
|
# branch uses C99 `remainder`, round-half-to-even).
|
|
310
|
-
:reminder => CABinOp::OP_REMINDER,
|
|
311
314
|
}.freeze
|
|
312
315
|
|
|
313
316
|
LAZY_BINOP_OP_IDS.each do |method_name, op_id|
|
|
@@ -533,7 +536,7 @@ class CArray
|
|
|
533
536
|
if __lazy_view__?
|
|
534
537
|
dt_name = data_type_name
|
|
535
538
|
if CScalar.respond_to?(dt_name)
|
|
536
|
-
s = CScalar.
|
|
539
|
+
s = CScalar.public_send(dt_name)
|
|
537
540
|
s[0] = other
|
|
538
541
|
[s, self]
|
|
539
542
|
else
|
|
@@ -801,6 +804,15 @@ end
|
|
|
801
804
|
end
|
|
802
805
|
copy
|
|
803
806
|
end
|
|
807
|
+
|
|
808
|
+
# Where something is registered to compute an expression -- a compiler,
|
|
809
|
+
# say -- it is asked here, and the walk below is what happens when it
|
|
810
|
+
# declines, when nothing is registered, or when the array is small
|
|
811
|
+
# enough that walking is the faster answer.
|
|
812
|
+
def copy
|
|
813
|
+
return super unless CArray.expression_evaluator
|
|
814
|
+
CArray::Fusion.evaluate(self) || super
|
|
815
|
+
end
|
|
804
816
|
end
|
|
805
817
|
end
|
|
806
818
|
|
|
@@ -825,65 +837,68 @@ end
|
|
|
825
837
|
# - A bare lazy return auto-materialises; escaping the lazy view (e.g.
|
|
826
838
|
# stashing it in an Array) is the user's responsibility.
|
|
827
839
|
# ---------------------------------------------------------------------------
|
|
840
|
+
# CArray.fuse
|
|
841
|
+
#
|
|
842
|
+
# Builds an expression instead of evaluating it, so that it is computed in
|
|
843
|
+
# one pass with no array standing for a step along the way. The block is
|
|
844
|
+
# read rather than called; see CArray::FuseSource for why, and for what
|
|
845
|
+
# happens where its source cannot be read.
|
|
846
|
+
# ---------------------------------------------------------------------------
|
|
828
847
|
class << CArray
|
|
829
|
-
#
|
|
830
|
-
#
|
|
831
|
-
#
|
|
832
|
-
#
|
|
833
|
-
#
|
|
834
|
-
#
|
|
835
|
-
#
|
|
836
|
-
#
|
|
837
|
-
#
|
|
838
|
-
#
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
+
# The object asked to compute an expression, or nil. CArray can always
|
|
849
|
+
# walk one, so nothing has to be registered and nothing changes when
|
|
850
|
+
# nothing is; what a registered evaluator adds is a second way to arrive
|
|
851
|
+
# at the same answer, and it may decline.
|
|
852
|
+
#
|
|
853
|
+
# It is called as `call(plan, out)` -- see {CArray::Fusion} for what a
|
|
854
|
+
# plan holds -- and answers by filling `out` and returning something
|
|
855
|
+
# true, or by returning something false having written nothing. Raising
|
|
856
|
+
# is not an answer: one that raises is dropped, with a warning, and
|
|
857
|
+
# expressions are walked from there on.
|
|
858
|
+
#
|
|
859
|
+
# @return [#call, nil]
|
|
860
|
+
attr_reader :expression_evaluator
|
|
861
|
+
|
|
862
|
+
# @param object [#call, nil] see {#expression_evaluator}
|
|
863
|
+
# @raise [ArgumentError] when it cannot be called.
|
|
864
|
+
def expression_evaluator= (object)
|
|
865
|
+
if object && ! object.respond_to?(:call)
|
|
866
|
+
raise ArgumentError,
|
|
867
|
+
"an expression evaluator must respond to #call(plan, out)"
|
|
848
868
|
end
|
|
869
|
+
@expression_evaluator = object
|
|
849
870
|
end
|
|
850
871
|
|
|
851
|
-
#
|
|
872
|
+
# @overload fuse { <expression> }
|
|
873
|
+
# Builds the expression rather than evaluating it, so that it is
|
|
874
|
+
# computed in one pass with no array standing for a step along the way.
|
|
852
875
|
#
|
|
853
|
-
#
|
|
854
|
-
#
|
|
855
|
-
#
|
|
856
|
-
#
|
|
857
|
-
# semantics as fuse).
|
|
876
|
+
# The block is not called. Its source is read, every name in it that
|
|
877
|
+
# holds a CArray is given `.lazy`, and the result is evaluated back in
|
|
878
|
+
# the block's own binding -- so `self`, instance variables, methods and
|
|
879
|
+
# constants are what they were where it was written.
|
|
858
880
|
#
|
|
859
|
-
#
|
|
860
|
-
#
|
|
861
|
-
# inside the function and materialise at the caller
|
|
862
|
-
# (= `.to_ca` / `.sum` / `.mean(axis:)` etc.)
|
|
863
|
-
# - Reusable expressions: apply the same expr to multiple datasets
|
|
864
|
-
# - debug / dump_tree: observe the lazy structure as-is
|
|
865
|
-
# - Pick the materialise form later: full materialise or reduction
|
|
881
|
+
# out[] = CArray.fuse { (a + b) * (c - a) }
|
|
882
|
+
# total = CArray.fuse { a * weight }.sum
|
|
866
883
|
#
|
|
867
|
-
#
|
|
868
|
-
#
|
|
869
|
-
#
|
|
870
|
-
#
|
|
871
|
-
# expr.sum # reduction (= chain + reduce in 1 pass)
|
|
884
|
+
# What comes back is the expression, not an array: it is computed where
|
|
885
|
+
# it is used -- stored into an array, reduced, or asked for one with
|
|
886
|
+
# `to_ca`. A block holding anything but an expression over arrays comes
|
|
887
|
+
# back as whatever it evaluated to.
|
|
872
888
|
#
|
|
873
|
-
#
|
|
874
|
-
# CArray.lazy(25.0, b) { |s, o| s + o } # s=25.0 Float pass-through
|
|
875
|
-
# CArray.lazy(arr, b) { |s, o| s + o } # s=arr.lazy
|
|
876
|
-
# @overload lazy(*args) { |*shadows| ... }
|
|
877
|
-
# Like {.fuse} but does not auto-materialise: returns whatever
|
|
878
|
-
# the block yields (typically a lazy view) so the expression
|
|
879
|
-
# can be materialised later via `.to_ca`, `.sum`, `.mean`, etc.
|
|
880
|
-
# @param args [Array<CArray, Object>] operands.
|
|
881
|
-
# @yieldparam shadows [Array<CArray, Object>] lazy wrappers.
|
|
882
|
-
# @return [Object]
|
|
889
|
+
# @return [Object] the expression, as a lazy view
|
|
883
890
|
# @raise [LocalJumpError] when no block is given.
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
891
|
+
# @raise [ArgumentError] when the block's source cannot be read -- in
|
|
892
|
+
# irb, in `eval`, or from a file that is gone. Write `.lazy` on the
|
|
893
|
+
# operands there: `a.lazy + b.lazy`.
|
|
894
|
+
def fuse (*args, &block)
|
|
895
|
+
raise LocalJumpError, "CArray.fuse requires a block" unless block
|
|
896
|
+
unless args.empty?
|
|
897
|
+
raise ArgumentError,
|
|
898
|
+
"CArray.fuse takes no arguments: write the expression itself, " \
|
|
899
|
+
"`CArray.fuse { a + b }`, and the names in it are read from " \
|
|
900
|
+
"where the block was written"
|
|
901
|
+
end
|
|
902
|
+
CArray::FuseSource.evaluate(block)
|
|
888
903
|
end
|
|
889
904
|
end
|
data/lib/carray/mask_gap_fill.rb
CHANGED
|
@@ -172,7 +172,9 @@ class CArray
|
|
|
172
172
|
return vec[present].linear_fetch(addr)
|
|
173
173
|
end
|
|
174
174
|
vval = vec.value.float64[present] # valid values
|
|
175
|
-
|
|
175
|
+
# Mark the out-of-range NaN before casting back: for an integer data_type
|
|
176
|
+
# the cast turns NaN into 0, and mask_invalid then has nothing left to find.
|
|
177
|
+
vval.linear_fetch(addr).mask_invalid.to_type(vec.data_type)
|
|
176
178
|
end
|
|
177
179
|
|
|
178
180
|
# Yield an index key (Array with `nil` at `axis`, integers elsewhere)
|
data/lib/carray/meld_reduce.rb
CHANGED
|
@@ -190,7 +190,7 @@ class CAMeld
|
|
|
190
190
|
# is unchanged. (Reduced axis == meld_axis is handled by the callers'
|
|
191
191
|
# meld_axis branch and never reaches here.)
|
|
192
192
|
def non_meld_axis_decompose(op, axis)
|
|
193
|
-
parts = parents.map { |p| p.
|
|
193
|
+
parts = parents.map { |p| p.public_send(op, axis: axis) }
|
|
194
194
|
axis_norm = axis < 0 ? axis + ndim : axis
|
|
195
195
|
new_meld_axis = axis_norm < meld_axis ? meld_axis - 1 : meld_axis
|
|
196
196
|
CArray.meld(parts, axis: new_meld_axis).copy
|
|
@@ -202,7 +202,7 @@ class CAMeld
|
|
|
202
202
|
# Empty-parent / short-parent handling — Welford needs n >= 2 per parent
|
|
203
203
|
# to recover m2 from p.variance for sample, n >= 1 for variancep (n=1
|
|
204
204
|
# gives m2=0, fine). Any parent below its threshold punts to super,
|
|
205
|
-
# which handles UNDEF /
|
|
205
|
+
# which handles the UNDEF / zero-count cases.
|
|
206
206
|
min_n = sample ? 2 : 1
|
|
207
207
|
if axis.nil?
|
|
208
208
|
return yield if parents.any? { |p| p.elements < min_n }
|
|
@@ -33,7 +33,7 @@ class CArray
|
|
|
33
33
|
#
|
|
34
34
|
# Value equality follows the value-hash discovery family (numeric `==` with
|
|
35
35
|
# NaN collapsed and `-0.0 == +0.0`; object `hash` / `eql?`; fixlen byte
|
|
36
|
-
# equality). Arrays are coerced to the first array's
|
|
36
|
+
# equality). Arrays are coerced to the first array's data type within the same
|
|
37
37
|
# family (cross-family raises). Masked cells do not enter `common`.
|
|
38
38
|
#
|
|
39
39
|
# @param arrays [Array<CArray>] two or more arrays (Array / Range coerced
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# (`stack` / `meld` / `montage`, plus `CArray#stack` and `CAStack#append`)
|
|
13
13
|
# lives in carray/stack.rb. The cheat sheet below covers the whole family.
|
|
14
14
|
#
|
|
15
|
-
# | view (same
|
|
15
|
+
# | view (same data type) | eager (auto-cast)
|
|
16
16
|
# -------------------+------------------------+-----------------------
|
|
17
17
|
# concat existing | meld (CAMeld view) | concatenate (materialised)
|
|
18
18
|
# axis (ndim same) | |
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# The value-hash discovery family (`unique`, `nunique`,
|
|
4
|
+
# `mask_duplicates`, ...) interns one **cell** at a time. `along:`
|
|
5
|
+
# widens the unit to a whole sub-array: the sub-arrays enumerated
|
|
6
|
+
# along one axis, each compared as a single value.
|
|
7
|
+
#
|
|
8
|
+
# Nothing new has to hash. `fz_hash`'s third key lane already interns
|
|
9
|
+
# a fixed-width block of bytes (FNV-1a, with a memcmp re-check), which
|
|
10
|
+
# is what a sub-array is once it sits contiguously; this method is the
|
|
11
|
+
# bridge to it, and the three things the bridge has to get right:
|
|
12
|
+
#
|
|
13
|
+
# - the sub-arrays have to be contiguous before their bytes mean
|
|
14
|
+
# anything, which a transpose or a strided view does not give;
|
|
15
|
+
# - byte equality is not the family's contract for floats, where
|
|
16
|
+
# every NaN is one value and -0.0 is +0.0;
|
|
17
|
+
# - masks fold for free -- a CARefer that covers several parent
|
|
18
|
+
# cells with one view cell OR-reduces their mask bits, so a
|
|
19
|
+
# sub-array holding a masked cell is a masked sub-array and the
|
|
20
|
+
# kernels skip it.
|
|
21
|
+
#
|
|
22
|
+
# Returns a 1-D fixlen array, one cell per sub-array, for the caller
|
|
23
|
+
# to run an ordinary cell-level discovery method over.
|
|
24
|
+
private def fibers_as_cells (along, caller_name)
|
|
25
|
+
if ndim < 2
|
|
26
|
+
raise ArgumentError,
|
|
27
|
+
"#{caller_name}: along: compares sub-arrays, and a #{ndim}-D array " \
|
|
28
|
+
"has none -- drop along: for the whole-array form"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if data_type == CA_OBJECT
|
|
32
|
+
raise ArgumentError,
|
|
33
|
+
"#{caller_name}: along: is not available for an object array -- " \
|
|
34
|
+
"its cells hold Ruby references, which would compare by identity " \
|
|
35
|
+
"rather than by value"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
axis = normalize_axis(along, caller_name)
|
|
39
|
+
|
|
40
|
+
# Bring the named axis to the front so each sub-array is one
|
|
41
|
+
# unbroken run of bytes, and take a copy: `refer` reinterprets a
|
|
42
|
+
# byte buffer, and a view whose element stride is not its element
|
|
43
|
+
# width is not one. The copy is also what makes the normalisation
|
|
44
|
+
# below safe to write.
|
|
45
|
+
order = [axis] + (0...ndim).to_a.reject { |i| i == axis }
|
|
46
|
+
block = (axis.zero? ? self : transpose(*order)).copy
|
|
47
|
+
|
|
48
|
+
# Two values the family calls equal are not equal byte for byte.
|
|
49
|
+
# Normalise them so the bytes say what the values mean.
|
|
50
|
+
if block.float?
|
|
51
|
+
block[:is_nan] = Float::NAN # every NaN is one value
|
|
52
|
+
block[:eq, 0.0] = 0.0 # -0.0 is +0.0
|
|
53
|
+
elsif block.complex?
|
|
54
|
+
[block.real, block.imag].each do |part|
|
|
55
|
+
part[:is_nan] = Float::NAN
|
|
56
|
+
part[:eq, 0.0] = 0.0
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
count = shape[axis]
|
|
61
|
+
block.refer(:fixlen, [count], bytes: elements / count * bytes)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# `axis:` and `along:` ask different questions of the same array, so
|
|
65
|
+
# answering both at once has no meaning.
|
|
66
|
+
private def reject_axis_with_along (axis, along, caller_name)
|
|
67
|
+
return unless axis and along
|
|
68
|
+
raise ArgumentError,
|
|
69
|
+
"#{caller_name}: axis: and along: cannot be given together -- " \
|
|
70
|
+
"axis: is about the values inside each sub-array, along: is about " \
|
|
71
|
+
"the sub-arrays themselves"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload factorize
|
|
4
|
+
# Returns `[codes, levels]` in one pass: `levels` is a 1-D CArray of
|
|
5
|
+
# the distinct values of `self` in first-appearance order — what
|
|
6
|
+
# {#unique} answers — and `codes` is an integer CArray of `self`'s
|
|
7
|
+
# shape where `levels[codes[i]]` is `self[i]`.
|
|
8
|
+
#
|
|
9
|
+
# This is the member of the value-hash discovery family ({#unique},
|
|
10
|
+
# {#value_counts}, {#mask_duplicates}, {#nunique}) that hands back
|
|
11
|
+
# the codes as storage. {#unique} answers with the vocabulary alone
|
|
12
|
+
# and {#categorize} wraps both in a {CACategorical} Face; a caller
|
|
13
|
+
# who wants the codes themselves — a position to scatter into, a key
|
|
14
|
+
# to group by, a dense renumbering of sparse keys — would otherwise
|
|
15
|
+
# pay a second pass or take the Face and its Ruby label list.
|
|
16
|
+
#
|
|
17
|
+
# `codes` takes the narrowest unsigned data type the vocabulary
|
|
18
|
+
# fits, reserving that type's top value as the exclusion sentinel.
|
|
19
|
+
# A cell that joins no category — a masked cell — is both masked and
|
|
20
|
+
# holds the sentinel, exactly as {CACategorical}'s storage is, so a
|
|
21
|
+
# consumer may read either.
|
|
22
|
+
#
|
|
23
|
+
# Distinctness is the family's hash-key judgement (see {#unique}):
|
|
24
|
+
# `==` for numeric with all NaN collapsed to one value and
|
|
25
|
+
# -0.0 / +0.0 the same value; `eql?` / `hash` for `CA_OBJECT` and
|
|
26
|
+
# `CA_FIXLEN`. Complex is not a lane the factorizer takes and raises
|
|
27
|
+
# {CArray::DataTypeError}.
|
|
28
|
+
#
|
|
29
|
+
# There is no `sort:` here, unlike {#unique} and {#value_counts}:
|
|
30
|
+
# the codes index the levels, so reordering the vocabulary would
|
|
31
|
+
# desync them. Take {#unique}`(sort: true)` where the codes are not
|
|
32
|
+
# wanted, or sort afterwards and carry the codes through the same
|
|
33
|
+
# permutation.
|
|
34
|
+
#
|
|
35
|
+
# @return [Array(CArray, CArray)] `[codes, levels]`.
|
|
36
|
+
def factorize
|
|
37
|
+
# One linear pass through the shared value hash (C
|
|
38
|
+
# __factorize_appearance__). It writes the sentinel into the
|
|
39
|
+
# excluded cells but leaves the codes unmasked; masking them is what
|
|
40
|
+
# CACategorical.from_codes does on its way to the Face, and this
|
|
41
|
+
# surface owes the same, since it hands the storage out bare.
|
|
42
|
+
codes, levels = __factorize_appearance__
|
|
43
|
+
excluded = codes.eq(CACategorical::SENTINEL[codes.data_type])
|
|
44
|
+
if excluded.count(true) > 0
|
|
45
|
+
codes.mask = codes.has_mask? ? (codes.mask | excluded) : excluded
|
|
46
|
+
end
|
|
47
|
+
[codes, levels]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
data/lib/carray/methods/is_in.rb
CHANGED
|
@@ -10,19 +10,22 @@ class CArray
|
|
|
10
10
|
# argument is accepted; to test a few immediate values pass an
|
|
11
11
|
# Array (`a.is_in([0, -1])`).
|
|
12
12
|
#
|
|
13
|
-
# When `self` and `values` have different numeric
|
|
13
|
+
# When `self` and `values` have different numeric data types they are
|
|
14
14
|
# promoted to a common type first (the same promotion binops use,
|
|
15
15
|
# {CArray.result_type}), so membership is value-correct across
|
|
16
|
-
#
|
|
16
|
+
# data types (e.g. an int cell equals a float set element of the same
|
|
17
17
|
# value, and a fractional set element never truncates onto an int
|
|
18
|
-
# cell). Genuinely incompatible
|
|
18
|
+
# cell). Genuinely incompatible data types (e.g. numeric vs fixlen)
|
|
19
19
|
# raise.
|
|
20
20
|
#
|
|
21
21
|
# Membership is value-based and shares the distinctness of the
|
|
22
22
|
# value-hash discovery family ({#unique} / {#value_counts}):
|
|
23
23
|
# numeric follows `==` with all NaN collapsed to one value and
|
|
24
24
|
# -0.0 == +0.0; `CA_OBJECT` follows Ruby `hash` / `eql?` with Float
|
|
25
|
-
# NaN collapsed; `CA_FIXLEN` follows byte equality
|
|
25
|
+
# NaN collapsed; `CA_FIXLEN` follows byte equality over the whole
|
|
26
|
+
# cell, with a short String in the set padded out to the cell width
|
|
27
|
+
# (so a 5-byte array written from `"be"` is `is_in(["be"])`). A
|
|
28
|
+
# `CA_FIXLEN` set given as a CArray must already be of that width.
|
|
26
29
|
#
|
|
27
30
|
# Masked cells of `values` do not enter the set. Masked cells of
|
|
28
31
|
# `self` stay masked in the result (membership is unknown), so
|
|
@@ -32,7 +35,7 @@ class CArray
|
|
|
32
35
|
# reduction, compose with {#any}: `a.is_in(values).any(axis: k)`.
|
|
33
36
|
#
|
|
34
37
|
# @param values [CArray, Array, Range] the set to test membership
|
|
35
|
-
# against. Promoted with `self` to a common
|
|
38
|
+
# against. Promoted with `self` to a common data type.
|
|
36
39
|
# Between two time arrays the question is about instants, not ticks:
|
|
37
40
|
# `values` is reconciled into `self`'s unit first, so a `:D` array and an
|
|
38
41
|
# `:h` array match on the instants they share. The same holds for the set
|
|
@@ -49,7 +52,7 @@ class CArray
|
|
|
49
52
|
# `self` and `other`, in `self`'s first-appearance order.
|
|
50
53
|
#
|
|
51
54
|
# Value-based, sharing the distinctness of the discovery family
|
|
52
|
-
# (see {#is_in}); `self` and `other` are promoted to a common
|
|
55
|
+
# (see {#is_in}); `self` and `other` are promoted to a common data type.
|
|
53
56
|
# Masked cells of either array do not participate. The result is
|
|
54
57
|
# always flat, like {#unique}, because the distinct values of a
|
|
55
58
|
# fiber vary in number.
|
|
@@ -97,18 +100,18 @@ class CArray
|
|
|
97
100
|
|
|
98
101
|
# Reconcile a set-valued argument with self to a common data_type via
|
|
99
102
|
# CArray.result_type (the single-source promotion rule the eager binop and
|
|
100
|
-
# lazy CABinOp share), returning [self', set']. Only
|
|
103
|
+
# lazy CABinOp share), returning [self', set']. Only data types are reconciled,
|
|
101
104
|
# never shapes: unlike the binop coercion (cast_self_or_other) the set's
|
|
102
105
|
# shape never broadcasts against self's, so a size-1 self keeps its shape.
|
|
103
|
-
# result_type raises for genuinely incompatible
|
|
106
|
+
# result_type raises for genuinely incompatible data types (numeric vs fixlen).
|
|
104
107
|
#
|
|
105
|
-
# A bare Array / Range has no intrinsic
|
|
108
|
+
# A bare Array / Range has no intrinsic data type, so the common type is inferred
|
|
106
109
|
# from self and the individual elements (result_type classifies each scalar).
|
|
107
110
|
# This keeps a fractional literal from truncating onto an int self, and an
|
|
108
111
|
# int literal from boxing a float self into the object lane (where Float 2.0
|
|
109
|
-
# is not eql? Integer 2). A CArray argument uses its own
|
|
112
|
+
# is not eql? Integer 2). A CArray argument uses its own data type; any other
|
|
110
113
|
# operand (Numo, a MemoryView producer, ...) comes in through wrap_readonly,
|
|
111
|
-
# the canonical type-coercion entry, so its format's
|
|
114
|
+
# the canonical type-coercion entry, so its format's data type drives the promote.
|
|
112
115
|
def promote_value_set (values)
|
|
113
116
|
case values
|
|
114
117
|
when Array then return promote_elements(values)
|
|
@@ -123,10 +126,18 @@ class CArray
|
|
|
123
126
|
# Bare Array / Range against numeric self: infer the common numeric type
|
|
124
127
|
# from self and the elements (each classified by result_type), so a
|
|
125
128
|
# fractional literal promotes self to float instead of truncating. Against
|
|
126
|
-
# object / fixlen self the elements are values, not
|
|
127
|
-
# String is a value, not a type name), so build the set in self's
|
|
129
|
+
# object / fixlen self the elements are values, not data type specifiers (a
|
|
130
|
+
# String is a value, not a type name), so build the set in self's data type.
|
|
128
131
|
def promote_elements (elems)
|
|
129
|
-
if data_type ==
|
|
132
|
+
if data_type == CA_FIXLEN
|
|
133
|
+
# A fixlen value is a cell-width blob, so the set has to be built at
|
|
134
|
+
# this array's width -- to_type(:fixlen) with no bytes: builds it at
|
|
135
|
+
# width 0, where every element is "" and the C guard then refuses the
|
|
136
|
+
# set for not matching self. Same rule the scalar operand of a
|
|
137
|
+
# comparison follows: a String standing in for a cell is padded to the
|
|
138
|
+
# cell's width.
|
|
139
|
+
[self, elems.to_ca.to_type(data_type, bytes: bytes)]
|
|
140
|
+
elsif data_type == CA_OBJECT
|
|
130
141
|
[self, elems.to_ca.to_type(data_type)]
|
|
131
142
|
else
|
|
132
143
|
t = CArray.result_type(self, *elems)
|
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
class CArray
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
# in yard-stubs/carray_order.rb (grouped with the search family).
|
|
3
|
+
# @!group Sorting and searching
|
|
5
4
|
|
|
5
|
+
# @overload locate_addr(ref)
|
|
6
|
+
# Returns, for each element of `self`, the flat address into `ref`
|
|
7
|
+
# where the value first occurs, or `UNDEF` where it is not present.
|
|
8
|
+
# Builds a value-to-first-address map from `ref` in one pass (an
|
|
9
|
+
# open-addressing hash, the same substrate as {#unique} /
|
|
10
|
+
# {#value_counts}), then probes each element of `self`; no sort,
|
|
11
|
+
# peak memory `O(distinct ref values)`.
|
|
12
|
+
#
|
|
13
|
+
# The return is `ref`'s flat address (0 to `ref.elements - 1`), so a
|
|
14
|
+
# multi-dimensional `ref` still yields a `self`-shaped result of flat
|
|
15
|
+
# addresses; downstream reads (`ref[addr]`, `model_var[addr]`, ...)
|
|
16
|
+
# apply it as a flat gather.
|
|
17
|
+
#
|
|
18
|
+
# Works on numeric, `CA_OBJECT`, and `CA_FIXLEN` values, matching the
|
|
19
|
+
# value-hash discovery family: numeric follows `==` with all NaN
|
|
20
|
+
# collapsed to one value and `-0.0 == +0.0`; object follows Ruby
|
|
21
|
+
# `hash` / `eql?` with Float NaN collapsed; fixlen follows byte
|
|
22
|
+
# equality. When `ref` holds duplicate values the returned address is
|
|
23
|
+
# the earliest (appearance-order) occurrence. Masked cells of `ref` do
|
|
24
|
+
# not enter the map but still occupy their flat address; masked cells
|
|
25
|
+
# of `self` are `UNDEF` in the result.
|
|
26
|
+
#
|
|
27
|
+
# `self` and `ref` are compared at their common type, negotiated by
|
|
28
|
+
# `CArray.result_type` — a fractional query against an integer `ref`
|
|
29
|
+
# is compared at the promoted type rather than truncated, so `1.5` no
|
|
30
|
+
# longer matches `1`. Cross-family input (numeric against fixlen)
|
|
31
|
+
# raises.
|
|
32
|
+
#
|
|
33
|
+
# Typical use is time-axis lookup: compute the address once against a
|
|
34
|
+
# reference axis, then reuse it to gather from many `ref`-shaped
|
|
35
|
+
# variables without repeating the lookup.
|
|
36
|
+
#
|
|
37
|
+
# @param ref [CArray, Array, Range] reference values to match against;
|
|
38
|
+
# any shape (used as flat). An Array or Range is coerced with `to_ca`
|
|
39
|
+
# and so lands in `:object`, where numeric matching follows Ruby
|
|
40
|
+
# `eql?` (`2.0` does not match `2`); pass a CArray to match in a
|
|
41
|
+
# numeric lane.
|
|
42
|
+
# @return [CArray] `:int64` flat addresses into `ref`, same shape as
|
|
43
|
+
# `self`; unmatched cells are masked.
|
|
44
|
+
# @raise [RuntimeError] when `self` and `ref` have no common data type.
|
|
6
45
|
def locate_addr (ref)
|
|
7
46
|
ref = ref.to_ca unless ref.is_a?(CArray)
|
|
8
47
|
# Put self and ref in a common lane via the single-source promotion rule
|
|
@@ -16,6 +55,38 @@ class CArray
|
|
|
16
55
|
q.send(:__locate_addr__, r)
|
|
17
56
|
end
|
|
18
57
|
|
|
58
|
+
# @overload locate_nearest_addr(ref, direction: :round, tolerance: nil)
|
|
59
|
+
# Returns, for each element of `self`, the flat address into `ref` of
|
|
60
|
+
# the nearest reference value. Continuous sibling of {#locate_addr};
|
|
61
|
+
# uses `linear_section` + rounding for non-exact matching against a
|
|
62
|
+
# sorted `ref`.
|
|
63
|
+
#
|
|
64
|
+
# Out-of-range cells of `self` (outside `ref`'s span) mask through the
|
|
65
|
+
# pipeline: `linear_section` returns NaN, `mask_invalid` propagates
|
|
66
|
+
# that as `UNDEF`, rounding and the int64 cast carry the mask, and
|
|
67
|
+
# `project` scatters it into the final positions. `mask_invalid` runs
|
|
68
|
+
# before rounding because `CArray#round` maps NaN to 0 and would
|
|
69
|
+
# otherwise silently match `ref[0]`.
|
|
70
|
+
#
|
|
71
|
+
# `tolerance:` (default `nil`) sets a maximum accepted absolute
|
|
72
|
+
# distance between `self[i]` and its matched `ref` value. When
|
|
73
|
+
# `|ref[addr] - self[i]| > tolerance`, the result cell is masked. Use
|
|
74
|
+
# for accuracy-controlled matching (e.g. "an observation snaps to a
|
|
75
|
+
# time step only if within N seconds").
|
|
76
|
+
#
|
|
77
|
+
# `ref` need not be given in ascending order: it is sorted internally
|
|
78
|
+
# and the returned addresses are mapped back to positions in `ref` as
|
|
79
|
+
# passed.
|
|
80
|
+
#
|
|
81
|
+
# @param ref [CArray] 1-D reference grid to match against.
|
|
82
|
+
# @param direction [Symbol] `:round`, `:floor`, or `:ceil` — rounding
|
|
83
|
+
# applied to the fractional position.
|
|
84
|
+
# @param tolerance [Numeric, nil] maximum accepted `|self - ref|`
|
|
85
|
+
# distance; cells beyond this are masked. `nil` disables the check.
|
|
86
|
+
# @return [CArray] `:int64` flat addresses into `ref`, same shape as
|
|
87
|
+
# `self`; out-of-range and beyond-tolerance cells are masked.
|
|
88
|
+
# @raise [ArgumentError] when `direction` is not one of the accepted
|
|
89
|
+
# symbols.
|
|
19
90
|
def locate_nearest_addr (ref, direction: :round, tolerance: nil)
|
|
20
91
|
unless [:round, :floor, :ceil].include?(direction)
|
|
21
92
|
raise ArgumentError,
|
|
@@ -35,7 +106,12 @@ class CArray
|
|
|
35
106
|
fill[] = sec.nil? ? UNDEF : sec
|
|
36
107
|
sec = fill
|
|
37
108
|
end
|
|
38
|
-
|
|
109
|
+
masked = sec.mask_invalid
|
|
110
|
+
si = case direction
|
|
111
|
+
when :round then masked.round
|
|
112
|
+
when :floor then masked.floor
|
|
113
|
+
when :ceil then masked.ceil
|
|
114
|
+
end.int64
|
|
39
115
|
idx = ri.project(si)
|
|
40
116
|
if tolerance
|
|
41
117
|
dist = (ref.project(idx) - self).abs
|
|
@@ -44,4 +120,6 @@ class CArray
|
|
|
44
120
|
idx
|
|
45
121
|
end
|
|
46
122
|
|
|
123
|
+
# @!endgroup
|
|
124
|
+
|
|
47
125
|
end
|