carray 3.0.0 → 3.0.1
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 +325 -3
- data/{NEWS.md → CHANGELOG.v1.md} +1 -1
- data/README.md +32 -32
- data/carray.gemspec +12 -8
- data/ext/ca_axis_group.c +55 -19
- data/ext/ca_binop_dispatch.c +5 -6
- data/ext/ca_binop_dispatch.h +0 -7
- data/ext/ca_categorical_iterator.c +13 -13
- data/ext/ca_for_each_element.h +6 -8
- data/ext/ca_group_iter.c +1 -1
- data/ext/ca_kernel_iterator.c +69 -23
- data/ext/ca_kernel_iterator.h +3 -10
- data/ext/ca_obj_bincmp.c +57 -27
- data/ext/ca_obj_binop.c +86 -30
- data/ext/ca_obj_const_string.c +3 -1
- data/ext/ca_obj_face.c +80 -0
- data/ext/ca_obj_face.h +33 -1
- data/ext/ca_obj_fake.c +11 -0
- data/ext/ca_obj_fixlen_string.c +3 -1
- data/ext/ca_obj_grid.c +11 -3
- data/ext/ca_obj_meld.c +13 -8
- data/ext/ca_obj_moncmp.c +33 -14
- data/ext/ca_obj_monop.c +19 -7
- data/ext/ca_obj_object.c +99 -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_axis.c +16 -6
- data/ext/ca_obj_shift.c +3 -3
- data/ext/ca_obj_stride.c +106 -23
- data/ext/ca_obj_string.c +3 -1
- 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 +86 -17
- data/ext/ca_op_cmplx64.h +123 -0
- data/ext/ca_op_ipower.c +0 -3
- data/ext/ca_sort_kernels.h +5 -5
- data/ext/ca_sweep_engine.c +78 -35
- data/ext/ca_sweep_engine.h +13 -4
- data/ext/ca_transform_common.c +7 -0
- data/ext/carray.h +127 -130
- data/ext/carray_access.c +56 -34
- data/ext/carray_bincount.c +8 -8
- data/ext/carray_broadcast.c +99 -6
- data/ext/carray_build_flags.h +3 -0
- data/ext/carray_call_cfunc.c +1491 -0
- data/ext/carray_call_cfunc.h +153 -0
- data/ext/carray_cast.c +69 -31
- data/ext/carray_conversion.c +22 -22
- data/ext/carray_copy.c +12 -2
- data/ext/carray_core.c +57 -10
- data/ext/carray_count.c +1 -1
- data/ext/carray_element.c +7 -7
- data/ext/carray_factorize.c +28 -28
- data/ext/carray_hold.c +1 -1
- data/ext/carray_index_classifier.c +7 -21
- data/ext/carray_internal.h +19 -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 +88 -87
- data/ext/carray_kernels_reduce_boolean.c +1 -0
- data/ext/carray_kernels_reduce_cumulative.c +54 -53
- data/ext/carray_kernels_reduce_extreme.c +51 -50
- data/ext/carray_kernels_reduce_variance.c +1 -0
- data/ext/carray_kernels_scan.c +1 -0
- data/ext/carray_kernels_search.c +93 -92
- 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_mask.c +6 -5
- data/ext/carray_median_percentile.c +1 -1
- data/ext/carray_memory_view.c +46 -21
- data/ext/carray_operator.c +46 -49
- data/ext/carray_partition.c +4 -4
- data/ext/carray_random.c +7 -7
- data/ext/carray_scatter.c +1 -1
- data/ext/carray_sort.c +3 -3
- 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 +243 -52
- data/ext/mkkernel.rb +354 -100
- data/ext/ruby_carray.c +18 -16
- data/ext/version.h +4 -4
- data/lib/carray/autoload_carray.rb +19 -0
- data/lib/carray/autoload_method_extension.rb +2 -1
- data/lib/carray/axis_group.rb +7 -7
- data/lib/carray/basics.rb +59 -59
- data/lib/carray/bincount_nd.rb +12 -12
- data/lib/carray/block_iterator.rb +24 -14
- data/lib/carray/categorical.rb +4 -4
- data/lib/carray/categorical_iterator.rb +98 -76
- data/lib/carray/conditional.rb +14 -14
- data/lib/carray/construct.rb +21 -0
- data/lib/carray/core_extensions.rb +62 -5
- data/lib/carray/data_type_extension.rb +18 -1
- data/lib/carray/frame/concat.rb +3 -3
- data/lib/carray/frame/convert.rb +1 -1
- data/lib/carray/frame/frame.rb +4 -11
- data/lib/carray/frame/io.rb +159 -2
- data/lib/carray/frame/sort.rb +1 -1
- data/lib/carray/frame/verbs.rb +18 -1
- 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 +1 -7
- data/lib/carray/iterator.rb +4 -3
- data/lib/carray/lazy.rb +125 -73
- 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/is_in.rb +12 -12
- data/lib/carray/methods/locate_addr.rb +6 -1
- data/lib/carray/methods/mask_duplicates.rb +1 -1
- data/lib/carray/methods/meshgrid.rb +4 -5
- data/lib/carray/methods/mode.rb +2 -2
- data/lib/carray/methods/nunique.rb +1 -1
- data/lib/carray/methods/snap.rb +7 -2
- data/lib/carray/methods/unique.rb +3 -3
- data/lib/carray/methods/value_counts.rb +2 -2
- data/lib/carray/runtime.rb +0 -19
- data/lib/carray/slab_iterator.rb +20 -7
- data/lib/carray/string_operation_extension.rb +5 -5
- data/lib/carray/time.rb +890 -491
- data/lib/carray/window_iterator.rb +285 -13
- data/lib/carray.rb +5 -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 +73 -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 +49 -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 +114 -0
- data/yard-stubs/carray_count.rb +79 -0
- data/yard-stubs/carray_element.rb +108 -0
- data/yard-stubs/carray_generate.rb +66 -0
- data/yard-stubs/carray_lazy.rb +23 -0
- data/yard-stubs/carray_loop.rb +140 -0
- data/yard-stubs/carray_mask.rb +259 -0
- data/yard-stubs/carray_math.rb +132 -0
- data/yard-stubs/carray_mathfunc.rb +45 -0
- data/yard-stubs/carray_median_percentile.rb +89 -0
- data/yard-stubs/carray_memory_view.rb +163 -0
- data/yard-stubs/carray_order.rb +312 -0
- data/yard-stubs/carray_random.rb +89 -0
- data/yard-stubs/carray_scatter.rb +106 -0
- data/yard-stubs/carray_slab.rb +57 -0
- data/yard-stubs/carray_sort.rb +163 -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 +59 -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,105 @@ 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)
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
# @overload jit_for (*extents) { |i, j, ...| ... }
|
|
906
|
+
# Runs a block over an index space, so that a cell may reach the ones
|
|
907
|
+
# around it -- a recurrence, a stencil. The block is compiled, and the
|
|
908
|
+
# compiler is the carray-jit gem.
|
|
909
|
+
# @raise [NotImplementedError] when that gem is not installed.
|
|
910
|
+
def jit_for (*extents, **options)
|
|
911
|
+
raise NotImplementedError, no_compiler("jit_for")
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
# @overload jit_each { ... }
|
|
915
|
+
# Runs a block over the cells of arrays at once, naming each cell by the
|
|
916
|
+
# array it came from, and writing what it computes into arrays of yours:
|
|
917
|
+
# `CArray.jit_each { out = a + b * c }`. The block is compiled, and the
|
|
918
|
+
# compiler is the carray-jit gem.
|
|
919
|
+
# @raise [NotImplementedError] when that gem is not installed.
|
|
920
|
+
def jit_each (&block)
|
|
921
|
+
raise NotImplementedError, no_compiler("jit_each")
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
# @overload jit_map { ... }
|
|
925
|
+
# The same block with its value asked for: the last statement is what
|
|
926
|
+
# every cell of the result gets, and the result comes back. The block is
|
|
927
|
+
# compiled, and the compiler is the carray-jit gem.
|
|
928
|
+
# @raise [NotImplementedError] when that gem is not installed.
|
|
929
|
+
def jit_map (&block)
|
|
930
|
+
raise NotImplementedError, no_compiler("jit_map")
|
|
931
|
+
end
|
|
932
|
+
|
|
933
|
+
private
|
|
934
|
+
|
|
935
|
+
def no_compiler (name)
|
|
936
|
+
"CArray.#{name} compiles its block, and the compiler is the carray-jit " \
|
|
937
|
+
"gem, which is not installed. An expression over whole arrays can be " \
|
|
938
|
+
"written as `CArray.fuse { ... }` instead, which needs no compiler; " \
|
|
939
|
+
"reaching a neighbouring cell, or writing the loop itself, does."
|
|
888
940
|
end
|
|
889
941
|
end
|
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) | |
|
data/lib/carray/methods/is_in.rb
CHANGED
|
@@ -10,12 +10,12 @@ 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
|
|
@@ -32,7 +32,7 @@ class CArray
|
|
|
32
32
|
# reduction, compose with {#any}: `a.is_in(values).any(axis: k)`.
|
|
33
33
|
#
|
|
34
34
|
# @param values [CArray, Array, Range] the set to test membership
|
|
35
|
-
# against. Promoted with `self` to a common
|
|
35
|
+
# against. Promoted with `self` to a common data type.
|
|
36
36
|
# Between two time arrays the question is about instants, not ticks:
|
|
37
37
|
# `values` is reconciled into `self`'s unit first, so a `:D` array and an
|
|
38
38
|
# `:h` array match on the instants they share. The same holds for the set
|
|
@@ -49,7 +49,7 @@ class CArray
|
|
|
49
49
|
# `self` and `other`, in `self`'s first-appearance order.
|
|
50
50
|
#
|
|
51
51
|
# Value-based, sharing the distinctness of the discovery family
|
|
52
|
-
# (see {#is_in}); `self` and `other` are promoted to a common
|
|
52
|
+
# (see {#is_in}); `self` and `other` are promoted to a common data type.
|
|
53
53
|
# Masked cells of either array do not participate. The result is
|
|
54
54
|
# always flat, like {#unique}, because the distinct values of a
|
|
55
55
|
# fiber vary in number.
|
|
@@ -97,18 +97,18 @@ class CArray
|
|
|
97
97
|
|
|
98
98
|
# Reconcile a set-valued argument with self to a common data_type via
|
|
99
99
|
# CArray.result_type (the single-source promotion rule the eager binop and
|
|
100
|
-
# lazy CABinOp share), returning [self', set']. Only
|
|
100
|
+
# lazy CABinOp share), returning [self', set']. Only data types are reconciled,
|
|
101
101
|
# never shapes: unlike the binop coercion (cast_self_or_other) the set's
|
|
102
102
|
# shape never broadcasts against self's, so a size-1 self keeps its shape.
|
|
103
|
-
# result_type raises for genuinely incompatible
|
|
103
|
+
# result_type raises for genuinely incompatible data types (numeric vs fixlen).
|
|
104
104
|
#
|
|
105
|
-
# A bare Array / Range has no intrinsic
|
|
105
|
+
# A bare Array / Range has no intrinsic data type, so the common type is inferred
|
|
106
106
|
# from self and the individual elements (result_type classifies each scalar).
|
|
107
107
|
# This keeps a fractional literal from truncating onto an int self, and an
|
|
108
108
|
# 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
|
|
109
|
+
# is not eql? Integer 2). A CArray argument uses its own data type; any other
|
|
110
110
|
# operand (Numo, a MemoryView producer, ...) comes in through wrap_readonly,
|
|
111
|
-
# the canonical type-coercion entry, so its format's
|
|
111
|
+
# the canonical type-coercion entry, so its format's data type drives the promote.
|
|
112
112
|
def promote_value_set (values)
|
|
113
113
|
case values
|
|
114
114
|
when Array then return promote_elements(values)
|
|
@@ -123,8 +123,8 @@ class CArray
|
|
|
123
123
|
# Bare Array / Range against numeric self: infer the common numeric type
|
|
124
124
|
# from self and the elements (each classified by result_type), so a
|
|
125
125
|
# 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
|
|
126
|
+
# object / fixlen self the elements are values, not data type specifiers (a
|
|
127
|
+
# String is a value, not a type name), so build the set in self's data type.
|
|
128
128
|
def promote_elements (elems)
|
|
129
129
|
if data_type == CA_OBJECT || data_type == CA_FIXLEN
|
|
130
130
|
[self, elems.to_ca.to_type(data_type)]
|
|
@@ -35,7 +35,12 @@ class CArray
|
|
|
35
35
|
fill[] = sec.nil? ? UNDEF : sec
|
|
36
36
|
sec = fill
|
|
37
37
|
end
|
|
38
|
-
|
|
38
|
+
masked = sec.mask_invalid
|
|
39
|
+
si = case direction
|
|
40
|
+
when :round then masked.round
|
|
41
|
+
when :floor then masked.floor
|
|
42
|
+
when :ceil then masked.ceil
|
|
43
|
+
end.int64
|
|
39
44
|
idx = ri.project(si)
|
|
40
45
|
if tolerance
|
|
41
46
|
dist = (ref.project(idx) - self).abs
|
|
@@ -30,7 +30,7 @@ class CArray
|
|
|
30
30
|
flatten.send(:__mask_duplicates__, 0).reshape(*shape)
|
|
31
31
|
else
|
|
32
32
|
# Per-fiber single-pass seen-set hash (C __mask_duplicates__): one lane
|
|
33
|
-
# per
|
|
33
|
+
# per data type family (integer widen, float bitwise key with NaN collapse,
|
|
34
34
|
# object rb_hash + rb_eql, fixlen byte-hash + memcmp, boolean via the
|
|
35
35
|
# uint8 lane). O(distinct) memory, no sort/gather/scatter buffers.
|
|
36
36
|
__mask_duplicates__(normalize_axis(axis, "mask_duplicates"))
|
|
@@ -17,10 +17,9 @@ class CArray
|
|
|
17
17
|
# input order in both modes.
|
|
18
18
|
#
|
|
19
19
|
# When `copy` is true (default) each output is a materialised
|
|
20
|
-
# CArray; when false, view
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# for large grids.
|
|
20
|
+
# CArray; when false, a view is returned. When `sparse` is true
|
|
21
|
+
# each output keeps a size-1 axis wherever the full grid would
|
|
22
|
+
# repeat, and broadcasts on demand, saving memory for large grids.
|
|
24
23
|
#
|
|
25
24
|
# If a block is given, yields the resulting arrays as splat
|
|
26
25
|
# arguments and returns the block's value.
|
|
@@ -77,7 +76,7 @@ class CArray
|
|
|
77
76
|
list = axes.map.with_index do |axis, k|
|
|
78
77
|
d = dest[k]
|
|
79
78
|
idx = if sparse
|
|
80
|
-
Array.new(ndim) { |i| i == d ? nil :
|
|
79
|
+
Array.new(ndim) { |i| i == d ? nil : :_ }
|
|
81
80
|
else
|
|
82
81
|
out_shape.dup.tap { |s| s[d] = :% }
|
|
83
82
|
end
|
data/lib/carray/methods/mode.rb
CHANGED
|
@@ -33,7 +33,7 @@ class CArray
|
|
|
33
33
|
# the whole array.
|
|
34
34
|
# @return [CArray] boolean CArray of `self.shape`.
|
|
35
35
|
def is_mode (axis: nil)
|
|
36
|
-
# Per-fiber two-pass frequency table (C __is_mode__), one lane per
|
|
36
|
+
# Per-fiber two-pass frequency table (C __is_mode__), one lane per data type
|
|
37
37
|
# family (numeric widen / NaN collapse, object rb_hash + rb_eql, fixlen
|
|
38
38
|
# byte-hash + memcmp). Ties are all marked; masked cells stay false.
|
|
39
39
|
if axis.nil?
|
|
@@ -112,7 +112,7 @@ class CArray
|
|
|
112
112
|
|
|
113
113
|
private
|
|
114
114
|
|
|
115
|
-
# Flat mode: the distinct modal values ascending, 1-D CArray of self's
|
|
115
|
+
# Flat mode: the distinct modal values ascending, 1-D CArray of self's data type.
|
|
116
116
|
# The single source of what counts as a mode (per-axis reuses it per fiber).
|
|
117
117
|
# The distinct values with the maximum count, read from the frequency table
|
|
118
118
|
# (value_counts, which covers numeric / object / fixlen), then sorted
|
|
@@ -30,7 +30,7 @@ class CArray
|
|
|
30
30
|
# @return [Integer, CArray] Integer for `axis: nil`, otherwise a
|
|
31
31
|
# reduced `CA_INT64` CArray.
|
|
32
32
|
def nunique (axis: nil, keep_axis: false)
|
|
33
|
-
# Per-fiber single-pass seen-set hash (C __nunique__), one lane per
|
|
33
|
+
# Per-fiber single-pass seen-set hash (C __nunique__), one lane per data type
|
|
34
34
|
# family (numeric widen / NaN collapse, object rb_hash + rb_eql, fixlen
|
|
35
35
|
# byte-hash + memcmp). Masked cells are skipped; the accumulator is a no-op
|
|
36
36
|
# (the distinct count is the interned-key count).
|
data/lib/carray/methods/snap.rb
CHANGED
|
@@ -7,7 +7,7 @@ class CArray
|
|
|
7
7
|
# matching `CArray#round`; `:floor` snaps toward `-inf`, `:ceil`
|
|
8
8
|
# toward `+inf`).
|
|
9
9
|
#
|
|
10
|
-
# The output has the same
|
|
10
|
+
# The output has the same data type as `self` (integer input is coerced
|
|
11
11
|
# to float internally when `step` / `offset` are floats, following
|
|
12
12
|
# normal arithmetic promotion). NaN / Inf are preserved as a mask
|
|
13
13
|
# on the output; the rounding kernels map NaN to 0.0, which would
|
|
@@ -45,7 +45,12 @@ class CArray
|
|
|
45
45
|
# Detect NaN / Inf before rounding (which maps NaN -> 0.0 silently).
|
|
46
46
|
invalid_mask = scaled.float? ? scaled.is_invalid : nil
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
snapped = case direction
|
|
49
|
+
when :round then scaled.round
|
|
50
|
+
when :floor then scaled.floor
|
|
51
|
+
when :ceil then scaled.ceil
|
|
52
|
+
end
|
|
53
|
+
out = snapped * step + offset
|
|
49
54
|
|
|
50
55
|
if invalid_mask && invalid_mask.count(true) > 0
|
|
51
56
|
out.mask = out.has_mask? ? (out.mask | invalid_mask) : invalid_mask
|
|
@@ -16,7 +16,7 @@ class CArray
|
|
|
16
16
|
# Masked cells do not participate and never appear in the result;
|
|
17
17
|
# an all-masked array yields an empty CArray.
|
|
18
18
|
#
|
|
19
|
-
# Distinctness follows `==` for numeric
|
|
19
|
+
# Distinctness follows `==` for numeric data types, with two float
|
|
20
20
|
# special cases so the result matches value-based expectations:
|
|
21
21
|
# all NaN collapse to a single distinct value (rather than one per
|
|
22
22
|
# cell) and -0.0 / +0.0 are the same value. The value kept for
|
|
@@ -32,10 +32,10 @@ class CArray
|
|
|
32
32
|
# its own unit: the distinct values are values, so the array comes back
|
|
33
33
|
# as itself rather than as raw storage ticks.
|
|
34
34
|
#
|
|
35
|
-
# @return [CArray] 1-D CArray of the distinct values, same
|
|
35
|
+
# @return [CArray] 1-D CArray of the distinct values, same data type
|
|
36
36
|
# as `self`.
|
|
37
37
|
def unique (sort: false)
|
|
38
|
-
# Single-pass seen-set hash (C __unique_flat__), one lane per
|
|
38
|
+
# Single-pass seen-set hash (C __unique_flat__), one lane per data type family:
|
|
39
39
|
# integer widens to a 64-bit key; float uses the bitwise key with all-NaN
|
|
40
40
|
# collapsed and -0.0 / +0.0 normalized; object keys on rb_hash + rb_eql and
|
|
41
41
|
# fixlen on a byte-hash + memcmp, both reproducing Ruby Hash distinctness.
|
|
@@ -3,7 +3,7 @@ class CArray
|
|
|
3
3
|
# @overload value_counts(sort: false)
|
|
4
4
|
# Returns `[values, counts]`, the distinct values of `self` paired
|
|
5
5
|
# with the number of times each occurs. `values` is a 1-D CArray of
|
|
6
|
-
# `self`'s
|
|
6
|
+
# `self`'s data type; `counts` is a 1-D `CA_INT64` where `counts[i]` is
|
|
7
7
|
# the number of occurrences of `values[i]`. This is the frequency-
|
|
8
8
|
# table member of the value-hash discovery family ({#unique},
|
|
9
9
|
# {#mask_duplicates}, {#nunique}); like {#unique} it always
|
|
@@ -39,7 +39,7 @@ class CArray
|
|
|
39
39
|
raise ArgumentError, "value_counts: sort must be false, :count, or :value"
|
|
40
40
|
end
|
|
41
41
|
# Single-pass frequency-table hash (C __value_counts_flat__), one lane per
|
|
42
|
-
#
|
|
42
|
+
# data type family: integer widens to a 64-bit key; float uses the bitwise key
|
|
43
43
|
# with all NaN collapsed and -0.0 / +0.0 normalized; object keys on rb_hash +
|
|
44
44
|
# rb_eql and fixlen on a byte-hash + memcmp, both reproducing Ruby Hash
|
|
45
45
|
# distinctness. Masked cells are skipped in the kernel.
|
data/lib/carray/runtime.rb
CHANGED
|
@@ -87,22 +87,3 @@ class CArray
|
|
|
87
87
|
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
class CAUnboundRepeat
|
|
91
|
-
# Overrides {CArray#template} for unbound-repeat views.
|
|
92
|
-
#
|
|
93
|
-
# The C-level `CArray#template` allocates an entity sized to `self.shape`,
|
|
94
|
-
# which for a CAUnboundRepeat would include the inflated unbound (`:*`)
|
|
95
|
-
# axes — semantically wrong (unbound axes are not yet bound to a size)
|
|
96
|
-
# and a memory bomb on broadcast patterns. Required for correctness.
|
|
97
|
-
#
|
|
98
|
-
# This override allocates at the **parent's compact shape** instead, then
|
|
99
|
-
# rewraps the result with the same `spec` so the returned view preserves
|
|
100
|
-
# the unbound broadcast pattern.
|
|
101
|
-
#
|
|
102
|
-
# @param argv [Array] forwarded to {CArray#template} (data_type, bytes:, ...)
|
|
103
|
-
# @yield forwarded to {CArray#template}
|
|
104
|
-
# @return [CAUnboundRepeat] new entity wrapped with the original unbound spec
|
|
105
|
-
def template (*argv, &block)
|
|
106
|
-
return parent.template(*argv,&block)[*spec.map{|x| x != :* ? nil : x}]
|
|
107
|
-
end
|
|
108
|
-
end
|
data/lib/carray/slab_iterator.rb
CHANGED
|
@@ -90,7 +90,7 @@ class CASlabIterator < CAIterator
|
|
|
90
90
|
# A per-slab reduction that folds each slab to one value is exactly the core
|
|
91
91
|
# per-axis reduction over the slab axes, so every reduction is a direct
|
|
92
92
|
# delegation to `reference.<op>(axis: slab_axes)`. This inherits the core
|
|
93
|
-
#
|
|
93
|
+
# data type, mask, empty/all-masked (identity vs UNDEF) and epsilon-close
|
|
94
94
|
# contracts unchanged -- there is no separate slab reduction kernel. (Unlike
|
|
95
95
|
# the map / reduce block surface, these are mask-aware: they route through the
|
|
96
96
|
# core reduction, which handles masked sources.)
|
|
@@ -98,14 +98,23 @@ class CASlabIterator < CAIterator
|
|
|
98
98
|
# @overload sum
|
|
99
99
|
# Per-slab sum, delegating to `reference.sum(axis: slab_axes)`.
|
|
100
100
|
# @return [CArray] one value per slab (shape = self.dim minus the slab axes)
|
|
101
|
+
# @overload accumulate
|
|
102
|
+
# Per-slab sum kept in the source's own data type, wrapping at its width,
|
|
103
|
+
# as the core `accumulate` does -- `sum` answers in the type the core
|
|
104
|
+
# promotes to (float64 for integers).
|
|
105
|
+
# @return [CArray] one value per slab
|
|
101
106
|
# The rest are analogous: prod / mean / min / max, sample and population
|
|
102
107
|
# variance / stddev, all / any, fused minmax ([min, max] pair), the axis-local
|
|
103
108
|
# position min_index / max_index (index within the slab axes), and the flat
|
|
104
109
|
# source address min_addr / max_addr (which source cell holds the extremum).
|
|
105
|
-
[:sum, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
|
|
110
|
+
[:sum, :accumulate, :prod, :mean, :min, :max, :variance, :stddev, :all, :any,
|
|
106
111
|
:variancep, :stddevp, :minmax, :min_index, :max_index,
|
|
107
112
|
:min_addr, :max_addr].each do |op|
|
|
108
|
-
|
|
113
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
114
|
+
def #{op}
|
|
115
|
+
@reference.#{op}(axis: @slab_axes)
|
|
116
|
+
end
|
|
117
|
+
RUBY
|
|
109
118
|
end
|
|
110
119
|
|
|
111
120
|
# @overload count(v = <none>)
|
|
@@ -243,7 +252,7 @@ class CASlabIterator < CAIterator
|
|
|
243
252
|
#
|
|
244
253
|
# A per-slab running accumulation along the slab axis is exactly the core
|
|
245
254
|
# per-axis cumulative over that axis, so every scan delegates to
|
|
246
|
-
# `reference.<op>(axis: slab_axis)`, inheriting the core
|
|
255
|
+
# `reference.<op>(axis: slab_axis)`, inheriting the core data type / mask (masked
|
|
247
256
|
# cells hold the running total, output unmasked) contracts unchanged. Each
|
|
248
257
|
# cell is in exactly one slab (a partition), so the running value is
|
|
249
258
|
# well-defined; the surface is uniform with the family even though it
|
|
@@ -258,16 +267,20 @@ class CASlabIterator < CAIterator
|
|
|
258
267
|
# Per-slab inclusive running product (float64), reference-shaped.
|
|
259
268
|
# @return [CArray]
|
|
260
269
|
# @overload cummax
|
|
261
|
-
# Per-slab inclusive running maximum (reference
|
|
270
|
+
# Per-slab inclusive running maximum (reference data type), reference-shaped.
|
|
262
271
|
# @return [CArray]
|
|
263
272
|
# @overload cummin
|
|
264
|
-
# Per-slab inclusive running minimum (reference
|
|
273
|
+
# Per-slab inclusive running minimum (reference data type), reference-shaped.
|
|
265
274
|
# @return [CArray]
|
|
266
275
|
# @overload cumcount
|
|
267
276
|
# Per-slab running count of present cells (int64), reference-shaped.
|
|
268
277
|
# @return [CArray]
|
|
269
278
|
[:cumsum, :cumprod, :cummax, :cummin, :cumcount].each do |op|
|
|
270
|
-
|
|
279
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
280
|
+
def #{op}
|
|
281
|
+
@reference.#{op}(axis: single_scan_axis(:#{op}))
|
|
282
|
+
end
|
|
283
|
+
RUBY
|
|
271
284
|
end
|
|
272
285
|
|
|
273
286
|
private
|