Class: CArray
- Inherits:
-
Object
- Object
- CArray
- Extended by:
- AutoloadMethodExtension, DataTypeExtension
- Defined in:
- lib/carray.rb,
lib/carray.rb,
lib/carray/lazy.rb,
lib/carray/time.rb,
lib/carray/stack.rb,
lib/carray/stack.rb,
lib/carray/table.rb,
lib/carray/basics.rb,
lib/carray/string.rb,
lib/carray/struct.rb,
lib/carray/struct.rb,
lib/carray/complex.rb,
lib/carray/inspect.rb,
lib/carray/runtime.rb,
lib/carray/attribute.rb,
lib/carray/construct.rb,
lib/carray/construct.rb,
lib/carray/construct.rb,
lib/carray/histogram.rb,
lib/carray/histogram.rb,
lib/carray/serialize.rb,
lib/carray/axis_group.rb,
lib/carray/bincount_nd.rb,
lib/carray/bincount_nd.rb,
lib/carray/categorical.rb,
lib/carray/conditional.rb,
lib/carray/methods/bin.rb,
lib/carray/arrow_tensor.rb,
lib/carray/const_string.rb,
lib/carray/methods/join.rb,
lib/carray/methods/mode.rb,
lib/carray/methods/snap.rb,
lib/carray/fixlen_string.rb,
lib/carray/mask_gap_fill.rb,
lib/carray/methods/index.rb,
lib/carray/methods/is_in.rb,
lib/carray/block_iterator.rb,
lib/carray/boolean_reduce.rb,
lib/carray/methods/choose.rb,
lib/carray/methods/resize.rb,
lib/carray/methods/unique.rb,
lib/carray/autoload_carray.rb,
lib/carray/autoload_carray.rb,
lib/carray/autoload_carray.rb,
lib/carray/autoload_carray.rb,
lib/carray/autoload_carray.rb,
lib/carray/autoload_carray.rb,
lib/carray/autoload_carray.rb,
lib/carray/autoload_carray.rb,
lib/carray/autoload_carray.rb,
lib/carray/autoload_carray.rb,
lib/carray/core_extensions.rb,
lib/carray/methods/nunique.rb,
lib/carray/window_iterator.rb,
lib/carray/methods/bincount.rb,
lib/carray/methods/meshgrid.rb,
lib/carray/methods/broadcast.rb,
lib/carray/methods/gather_nd.rb,
lib/carray/methods/align_addr.rb,
lib/carray/methods/bit_string.rb,
lib/carray/data_type_extension.rb,
lib/carray/methods/composition.rb,
lib/carray/methods/locate_addr.rb,
lib/carray/categorical_iterator.rb,
lib/carray/methods/insert_block.rb,
lib/carray/methods/value_counts.rb,
lib/carray/methods/string_format.rb,
lib/carray/methods/mask_duplicates.rb,
lib/carray/string_operation_extension.rb
Overview
Shared String-operation surface for the String Faces: the CArray::StringOperationMixin (included by CAString / CAFixlenString / CAConstString).
Defined Under Namespace
Modules: ArrowTensor, CoreExtensions, DataTypeExtension, DataTypeNewConstructor, StringOperationMixin, TableMethods Classes: BincountND, Boolean, Complex128, Complex64, Fixlen, Float32, Float64, Histogram, Inspector, Int16, Int32, Int64, Int8, Object, Serializer, UInt16, UInt32, UInt64, UInt8
Constant Summary collapse
- LAZY_MONOP_OP_IDS =
op_name => CAMonOp::OP_
{ # Preserve-data_type monop (8) zero: CAMonOp::OP_ZERO, one: CAMonOp::OP_ONE, frac: CAMonOp::OP_FRAC, neg: CAMonOp::OP_NEG, bit_neg: CAMonOp::OP_BIT_NEG, abs_i: CAMonOp::OP_ABS_I, conj: CAMonOp::OP_CONJ, not: CAMonOp::OP_NOT, # Preserve-data_type monfunc (4) ceil: CAMonOp::OP_CEIL, floor: CAMonOp::OP_FLOOR, round: CAMonOp::OP_ROUND, rcp: CAMonOp::OP_RCP, # Widening monfunc (22) rad: CAMonOp::OP_RAD, deg: CAMonOp::OP_DEG, sqrt: CAMonOp::OP_SQRT, exp: CAMonOp::OP_EXP, exp2: CAMonOp::OP_EXP2, exp10: CAMonOp::OP_EXP10, log: CAMonOp::OP_LOG, log10: CAMonOp::OP_LOG10, log2: CAMonOp::OP_LOG2, logb: CAMonOp::OP_LOGB, sin: CAMonOp::OP_SIN, cos: CAMonOp::OP_COS, tan: CAMonOp::OP_TAN, asin: CAMonOp::OP_ASIN, acos: CAMonOp::OP_ACOS, atan: CAMonOp::OP_ATAN, sinh: CAMonOp::OP_SINH, cosh: CAMonOp::OP_COSH, tanh: CAMonOp::OP_TANH, asinh: CAMonOp::OP_ASINH, acosh: CAMonOp::OP_ACOSH, atanh: CAMonOp::OP_ATANH, # Additional monfunc expm1: CAMonOp::OP_EXPM1, log1p: CAMonOp::OP_LOG1P, rsqrt: CAMonOp::OP_RSQRT, trunc: CAMonOp::OP_TRUNC, square: CAMonOp::OP_SQUARE, # Angle normalisation deg_360: CAMonOp::OP_DEG_360, deg_180: CAMonOp::OP_DEG_180, rad_2pi: CAMonOp::OP_RAD_2PI, rad_pi: CAMonOp::OP_RAD_PI, # Sign function (preserve dtype). bool/uint → 0/1, sint → -1/0/1, # float → -1/0/1 NaN-preserving, complex → unit vector or 0. sign: CAMonOp::OP_SIGN, # imag_i: preserve-dtype primitive (0 for numeric, cimag for complex # in the real slot). Primarily consumed by the `imag` special case # below but also directly callable via `a.lazy.imag_i`; entry here # so the direct call fuses instead of falling to eager. imag_i: CAMonOp::OP_IMAG_I, }.freeze
- LAZY_BINOP_OP_IDS =
Binop dispatch. Operator entries are redefined so a lazy operand on either side routes into CABinOp.build. Op scope:
- 5 arithmetic: + - * / **
- 3 bitwise: & | ^
- 2 shifts: << >>
- 2 misc: %, rcp_mul
{ :+ => CABinOp::OP_ADD, :- => CABinOp::OP_SUB, :* => CABinOp::OP_MUL, :/ => CABinOp::OP_DIV, :** => CABinOp::OP_POW, :& => CABinOp::OP_BIT_AND, :| => CABinOp::OP_BIT_OR, :^ => CABinOp::OP_BIT_XOR, :<< => CABinOp::OP_BIT_LSHIFT, :>> => CABinOp::OP_BIT_RSHIFT, :% => CABinOp::OP_MOD, :rcp_mul => CABinOp::OP_RCP_MUL, # Float-only binops registered eagerly by mkkernel; the lazy entries # here pick them up so `a.lazy.hypot(b)` etc. ride the substrate. :copysign => CABinOp::OP_COPYSIGN, :logaddexp => CABinOp::OP_LOGADDEXP, :nextafter => CABinOp::OP_NEXTAFTER, :fmod => CABinOp::OP_FMOD, :atan2 => CABinOp::OP_ATAN2, :hypot => CABinOp::OP_HYPOT, # Pair-wise max / min (NaN-skip via C99 fmax/fmin on float branch). :pmax => CABinOp::OP_PMAX, :pmin => CABinOp::OP_PMIN, # Pair-wise max / min, NaN-propagate variant. :maximum => CABinOp::OP_MAXIMUM, :minimum => CABinOp::OP_MINIMUM, # Boolean word forms (bool + object; plain mask propagation, no # Kleene fixup — see the CA_BINOP_AND note in ca_binop_dispatch.h). :and => CABinOp::OP_AND, :or => CABinOp::OP_OR, :xor => CABinOp::OP_XOR, # IEEE 754 remainder (distinct semantics from `%` / `mod`: float # branch uses C99 `remainder`, round-half-to-even). :reminder => CABinOp::OP_REMINDER, }.freeze
- LAZY_TRIOP_OP_IDS =
Triop dispatch (fma / fms / clip). CATriOp is the CABinOp analog for three-operand element-wise ops. Each Ruby method redefined below dispatches to CATriOp.build when any of self / op2 / op3 is a lazy view, and falls to the eager C method otherwise.
clipis the strict-clamp entry (__clip_ki__, called by the lib/carray/basics.rbclipwrapper's both-bounds-present path). The nil-bound one-sided cases route through the wrapper'spmax/pmincalls, which themselves lazy-fuse via LAZY_BINOP_OP_IDS above — soa.lazy.clip(nil, hi)anda.lazy.clip(lo, nil)fuse without adedicated triop entry.
{ fma: CATriOp::OP_FMA, fms: CATriOp::OP_FMS, __clip_ki__: CATriOp::OP_CLIP, }.freeze
- LAZY_BINCMP_OP_IDS =
coerce: when self is a lazy view and the scalar appears on the LEFT (e.g.
2 * a.lazy), Ruby's Numeric#* calls a.lazy.coerce(2). The default coerce would unwrap the lazy-ness via eager scalar promotion; here we keep it lazy by returning [scalar_as_cscalar, self], so the subsequent operator call ends up with a lazy receiver and triggersthe CABinOp builder.
bincmp / moncmp dispatch.
Comparison output is always boolean8_t, so it cannot reuse the CABinOp in-place trick. CABinCmp pulls both operands into operand-data_type scratches and writes boolean to the output buffer. Integer is_nan / is_inf / is_finite use existing per-data_type kernels (which handle the integer const-false/true result and mask skip).
Scope: 7 bincmp + 3 moncmp + operator aliases (
</>/<=/>=) for canonical Ruby comparison syntax. Note:==/eql?are NOT comparison ops — eagerCArray#==(rb_ca_equal) is array-level equality returning bool, not element-wise. Element-wise equality iseq/feq.feqis arity 1 in eager (compile-time FLT_EPSILON / DBL_EPSILON); we mirror that here. Runtime eps is a future extension (struct fieldalready reserved).
{ # Canonical method names + operator aliases (rb_define_alias in C # creates separate dispatch entries, so we override both). :lt => CABinCmp::OP_LT, :< => CABinCmp::OP_LT, :gt => CABinCmp::OP_GT, :> => CABinCmp::OP_GT, :le => CABinCmp::OP_LE, :<= => CABinCmp::OP_LE, :ge => CABinCmp::OP_GE, :>= => CABinCmp::OP_GE, :eq => CABinCmp::OP_EQ, :ne => CABinCmp::OP_NE, :feq => CABinCmp::OP_FEQ, }.freeze
- LAZY_BINCMP_TOL_OP_IDS =
tolerance-bearing bincmp ops (= is_close / is_equiv) use the same CABinCmp dispatch but with a 2nd
tolpositional arg. Kept in a separate dict because the LAZY_BINCMP_OP_IDS define_method block above uses arity 1 (|other|); these need arity 2 (|other, tol|). { :is_close => CABinCmp::OP_IS_CLOSE, :is_equiv => CABinCmp::OP_IS_EQUIV, }.freeze
- SFloat =
Numo-compatible alias of Float32.
Float32- DFloat =
Numo-compatible alias of Float64.
Float64- SComplex =
Numo-compatible alias of Complex64.
Complex64- DComplex =
Numo-compatible alias of Complex128.
Complex128- RObject =
Numo-compatible alias of Object.
Object
Attributes collapse
-
#attr(key) ⇒ Object?
Returns the value of the attribute
key, ornilwhen the key is absent. -
#attrs ⇒ Hash{String => Object}
Returns a frozen shallow Hash of all attributes visible on
self, merged along the parent chain (deeper writes shadow shallower ones on a per-key basis). - #has_attr?(key = nil) ⇒ Object
-
#set_attr(key, value) ⇒ Object
Sets attribute
keytovalueonself(not on any parent).
Class Method Summary collapse
-
._epoch_seconds_exact(spec, format = nil) ⇒ Object
Exact Rational seconds since the Unix epoch for a start literal (Time / DateTime / Integer unix-seconds / String).
-
._epoch_tick_index(spec, res, format = nil) ⇒ Object
Tick index of
spec's instant on theresgrid (floor toward the past). -
._epoch_year_month(spec, format = nil) ⇒ Object
year, month of a start literal, for a calendar-resolution grid.
-
._time_cell(literal, res, format, on_error) ⇒ Object
Single-literal build for CArray.time: a 1-element CATime, honouring the on_error policy (raise, or a masked cell).
-
.align_addr(*arrays, join: :outer) ⇒ Array<CArray>
Aligns several arrays onto one common set of coordinate values and returns, for each array, the flat addresses that gather it onto that common set.
-
.align_nearest_addr(*arrays, grid: nil, direction: :round, tolerance: nil) ⇒ Array<CArray>
Aligns several arrays onto one common coordinate grid by nearest match, the ordered-lane (continuous) sibling of CArray.align_addr.
-
.concatenate(list, axis: 0, data_type: nil) ⇒ CArray
Returns
listconcatenated along a single existing axis. -
.const_string(arg, encoding: Encoding::UTF_8, &block) ⇒ Object
Build a CAConstString (read-only variable-length string column) from Ruby data.
-
.dump(ca, **opt) ⇒ String
Returns
caserialized to a String in the _CARRAY3 format. -
.fixlen_string(arg, bytes: nil, truncate: :error, &block) ⇒ Object
Build a CAFixlenString (fixed-width String array over CA_FIXLEN storage).
-
.format(fmt, *argv) ⇒ CAString
Returns a CAString of formatted strings.
-
.from_bit_string(bstr, nb, data_type = CA_INT32, dim = nil) ⇒ CArray
Returns a new CArray built by unpacking
bstras a packed-bit byte string withnbbits per element. -
.fuse(*args) {|shadows| ... } ⇒ Object
Runs a transient lazy-fusion scope: wraps each CArray argument with
.lazy, yields the wrappers (and any non-CArray args) to the block, then auto-materialises a bare lazy return value into an entity. -
.lazy(*args) {|shadows| ... } ⇒ Object
CArray.lazy(*args) { |lazies| ... } — dual of fuse.
-
.load(input, **opt) ⇒ CArray
Reads a _CARRAY3 payload from
input. -
.load_arrow_tensor(filename) ⇒ CArray
Reads an Arrow tensor IPC file and returns it as a new CArray.
-
.meld(*arrays, axis: 0) ⇒ Object
Returns a CAMeld view of the arrays welded along an existing axis.
-
.meshgrid(*axes, indexing: "xy", copy: true, sparse: false) {|grids| ... } ⇒ Array<CArray>
Returns coordinate matrices built from 1-D coordinate vectors.
-
.montage(list, tdim, axis: 0, data_type: nil) ⇒ CArray
Arrange
listof uniform-shape pieces in atdim-shape grid that extends parent axesaxis..axis+tdim.size-1by the correspondingtdim[i]factor (= ImageMagickmontageanalog). -
.mosaic(list, tdim, axis: 0, data_type: nil) ⇒ CArray
Returns
listtiled into an N-D grid layout described bytdim. -
.save(ca, output, **opt) ⇒ CArray
Writes
catooutputin the _CARRAY3 portable format. -
.select(condlist, choicelist, default: 0, dtype: nil) ⇒ CArray
Multi-way ternary select: for each cell, picks the value from the first
choicelist[k]whose matchingcondlist[k]is true, falling back todefaultwhen no condition holds. -
.stack(list, axis: 0, data_type: nil) ⇒ CArray
Stack
listof CArrays along a new axis inserted at positionaxis:(default 0 = outermost). -
.string(arg, &block) ⇒ Object
Build a CAString (mutable String array over object storage) from Ruby data.
-
.struct(opt = {}) { ... } ⇒ Class
Returns a new CAStruct subclass defined by the block via CAStruct::Builder.
-
.tabulate(columns, data_type: nil) ⇒ CArray
Returns a 2-D table assembled from a list of column blocks, coerced to a common
data_type. -
.time(x, unit: :s, format: nil, on_error: :raise) ⇒ CATime
Builds a CATime on the
unitgrid from time value(s). -
.time_range(start, last, unit:, step: nil, format: nil) ⇒ CATime
Returns a CATime from
starttolastinclusive on theunitgrid, spacedstepapart. -
.time_series(start, count:, unit:, step: nil, format: nil) ⇒ CATime
Returns a CATime of
countinstants starting atstarton theunitgrid, spacedstepapart. -
.union(opt = {}) { ... } ⇒ Class
Returns a new CAUnion subclass defined by the block.
Instance Method Summary collapse
-
#<=>(other) ⇒ CArray
(also: #cmp)
Returns an element-wise 3-way comparison:
+1whereself > other,-1whereself < other,0where equal. -
#__real_eager__ ⇒ Object
--------------------------------------------------------------------------- real / imag lazy fuse (post-IC follow-up):.
-
#address ⇒ CArray
Returns an int32 CArray of the same shape as
selfwhere each cell holds its row-major flat address. -
#all(skip_masked: true, **opts) ⇒ Boolean, CArray
Whether every cell is true.
-
#any(skip_masked: true, **opts) ⇒ Boolean, CArray
Whether any cell is true.
-
#axis_group(*slots) ⇒ Object
------------------------------------------------------------------------ CArray#axis_group(cat_or_nil, ...) -- build an AxisGroup spec.
-
#bin(vmin, vmax, step = nil, bins: nil, lfill: nil, ufill: nil, include_max: true) ⇒ CArray
Returns each element's bin index for equal-width, half-open bins over
[vmin, vmax]. -
#bin_to(edges, lfill: nil, ufill: nil, include_max: false) ⇒ CArray
Returns each element's bin index against an explicit ascending
edgesarray (non-uniform binning). -
#bincount(weights: nil, length: 0) ⇒ CArray
Returns occurrence counts per non-negative integer label in
self, or a per-label sum ofweights. -
#bincount_nd(lengths:, axis: [-2, -1], weights: nil) ⇒ BincountND
Returns a discrete N-D joint BincountND count of
selfwith shapefiber_shape + (A, M). -
#blocks(*blocks) ⇒ CABlockIterator
Returns a CABlockIterator tiling
selfwith non-overlapping tiles of a per-axis size. -
#categorize(labels: nil, sort_labels: false) ⇒ CACategorical
Build a CACategorical from self read as category keys (= the values whose distinct levels become the categories).
-
#choose(choices, data_type: nil) ⇒ CArray
Returns label-based per-cell selection:
selfis an integer label array, andchoicesis a list indexed by those labels. -
#clip(min, max = nil, fill_value = nil, lfill: nil, ufill: nil) ⇒ CArray
Returns
selfwith every element clamped to[min, max]. - #coerce ⇒ Object (also: #__coerce_eager__)
-
#concatenate(*others, axis: 0, data_type: nil) ⇒ CArray
Instance form of CArray.concatenate: returns
[self, *others]concatenated alongaxisas a fresh CArray. -
#conditional(cond, then_fn, else_fn, dtype: nil) ⇒ CArray
Returns per-cell
then_fn.call(self[cond])wherecondis true andelse_fn.call(self[cond.not])where it is false. -
#crop(offset, dst) ⇒ CArray
Reads a
dst.shape-sized region fromselfstarting atoffsetintodst. -
#delete_block(offset, bsize) ⇒ CArray
Returns a new CArray obtained by deleting a block of
bsizecells (per axis) starting atoffset, shrinking the array. -
#difference(other, sort: false) ⇒ CArray
Returns a 1-D CArray of the distinct values in
selfthat are absent fromother, inself's first-appearance order. -
#drop_axis ⇒ CArray
Returns a view of
selfwith every size-1 axis dropped. -
#false ⇒ CArray
Returns a boolean CArray of the same shape as
selffilled withfalse. -
#format(fmt, *argv) ⇒ CAString
Returns a CAString formatting each cell of
selfwithfmt;selfis the firstKernel.formatargument, soarr.format("%03d")renders the cells andarr.format("%s=%d", other)interleaves a second array. -
#from_bit_string(bstr, nb) ⇒ self
Sets
selfby unpackingbstras a packed-bit byte string withnbbits per element. -
#gather_nd(indices) ⇒ CArray
Returns elements (or sub-arrays) gathered from
selfat the N-D coordinates given byindices. -
#group_by_category(cat) ⇒ CACategoricalIterator
Returns a CACategoricalIterator that reduces
self(the payload) per category ofcat. -
#group_by_run ⇒ CACategoricalIterator
Segments
selfinto maximal runs of consecutive non-masked cells and returns a CACategoricalIterator that reduces each run as one category, ordered by position. -
#histogram(edges:, axis: [-2, -1], include_max: false, weights: nil) ⇒ Histogram
Returns an M-D joint Histogram built from
selfwith shapefiber_shape + (A, M), whereM == edges.size. -
#histogram1d(edges:, axis: -1, include_max: false, weights: nil) ⇒ Histogram
Returns a 1-D Histogram built from
selfwith shapefiber_shape + (A,), whereAis the sample axis of length picked byaxis. -
#histogram2d(edges:, axis: [-2, -1], include_max: false, weights: nil) ⇒ Histogram
Returns a 2-D joint Histogram built from
selfwith shapefiber_shape + (A, 2). -
#imag ⇒ CArray
Returns the imaginary part of
self. -
#imag=(val) ⇒ Object
Sets the imaginary-part slot to
val(complex arrays only). -
#index(axis: 0) ⇒ CArray
Returns a writable int32 CArray holding the coordinate ramp
[0, 1, ..., shape[axis] - 1]alongaxis, in an open broadcast shape: sizeshape[axis]on that axis and 1 on every other axis (e.g. for(d0, d1, d2),index(axis: 1)returns(1, d1, 1)). - #indices ⇒ Object
-
#insert_axis(*positions, repeat: nil) ⇒ CArray
Returns a view of
selfwith one or more new axes inserted, optionally repeating along them. -
#insert_block(offset, bsize, &block) ⇒ CArray
Returns a new CArray obtained by inserting a block of size
bsize(per axis) atoffset, growing the array. -
#inspect ⇒ String
Returns a human-readable description of
selfincluding class,data_type, shape, element and memory summaries, mask count, and a truncated data preview. -
#intersection(other, sort: false) ⇒ CArray
Returns a 1-D CArray of the distinct values appearing in both
selfandother, inself's first-appearance order. -
#is_in(values) ⇒ CArray
Returns a boolean CArray of the same shape as
self,trueat each cell whose value appears in the setvalues. -
#is_mode(axis: nil) ⇒ CArray
Returns a shape-preserving boolean CArray, true at every cell that holds a modal value — a value whose occurrence count equals the maximum count.
-
#is_real ⇒ CArray?
Returns an element-wise boolean CArray marking cells whose imaginary part is zero (all-true for real numeric arrays,
nilfor non-numeric arrays). - #join(*argv, axis: nil, keep_axis: false) ⇒ Object
-
#locate_addr(ref) ⇒ Object
User-facing YARD docs for #locate_addr and #locate_nearest_addr live in yard-stubs/carray_order.rb (grouped with the search family).
- #locate_nearest_addr(ref, direction: :round, tolerance: nil) ⇒ Object
-
#lookup(table, fill_value = nil, lfill: nil, ufill: nil) ⇒ CArray
Returns values gathered from
tableat the indices given byself. -
#marshal_dump ⇒ Array
Returns the Marshal payload for
self. -
#marshal_load(data) ⇒ void
Reconstitutes
selffrom a Marshal payload produced by #marshal_dump. -
#mask_duplicates(axis: nil) ⇒ CArray
Returns a shape-preserving copy of
selfwith the mask set at every cell whose value duplicates an earlier-seen one; the first occurrence is kept. -
#meld(*others, axis: 0) ⇒ CAMeld
Returns a CAMeld view of
[self, *others]welded alongaxis. -
#mode(axis: nil) ⇒ CArray+
Returns the distinct modal values — the most frequent value(s), ascending.
-
#none(skip_masked: true, **opts) ⇒ Boolean, CArray
Whether no cell is true.
-
#nunique(axis: nil, keep_axis: false) ⇒ Integer, CArray
Counts the distinct values of
self. -
#pack_bits ⇒ CArray
Packs a 1-D boolean / 0-1 uint8 CArray of length
ninto a uint8 CArray ofceil(n / 8)bytes, LSB-first within each byte. -
#paste(offset, src) ⇒ self
Sets
selfatoffsetby copyingsrc. -
#put_nd(indices, values) ⇒ self
Sets
selfat the N-D coordinates given byindicestovalues. -
#real ⇒ CArray
Returns the real part of
selfas a zero-copy view. -
#real=(val) ⇒ Object
Sets the real-part slot to
valvia #real. -
#real? ⇒ Boolean?
Returns whether every element of
selfis real (imaginary part is zero for complex arrays; alwaystruefor real numeric arrays;nilfor non-numeric arrays). -
#replace_where(cond, b) ⇒ CArray
Returns a copy of
selfwith cells wherecondis true replaced byb. -
#resize(*newdim, fill_value: 0) ⇒ CArray
Returns
selfresized tonewdim. -
#save_arrow_tensor(filename) ⇒ self
Writes
selftofilenameas an Arrow tensor IPC message. -
#scale(xa, xb) ⇒ CArray
Returns a fresh CArray shaped like
selfholdingelementsevenly spaced values fromxatoxbinclusive. -
#scale!(xa, xb) ⇒ self
Sets
selftoelementsevenly spaced float64 values fromxatoxbinclusive. -
#snap(step, offset: 0.0, direction: :round) ⇒ CArray
Returns each element snapped to a point on the uniform grid
..., -step + offset, offset, step + offset, 2*step + offset, .... -
#snap_to(list, lfill: :clamp, ufill: :clamp, direction: :round) ⇒ CArray
Returns each element snapped to a value in
list(non-uniform grid). -
#source_code ⇒ String
Returns a Ruby source-like string that would reconstruct
self, combining the type/shape descriptor with a pretty printed value block. -
#span(range) ⇒ CArray
Returns a fresh CArray shaped like
selffilled with the linear sequence produced by #span!. -
#span!(range) ⇒ self
Sets
selfto a linear sequence overrange, with the step chosen so thatrange.end(orrange.endwhen the range is exclusive-end, treated as the limit not reached) determines the endpoint. -
#split(axis:) ⇒ Array<CArray>
Split self along a single axis into an Array of (ndim-1)-D slices, each a writable CABlock view.
-
#st ⇒ Struct
Returns a Ruby
Structview exposing every CAStruct member ofselfas aStructattribute holding the corresponding member column. -
#stack(*others, axis: 0, data_type: nil) ⇒ CArray
Instance-side stack: build a new K-stack from
[self] + othersalong the new K axis at positionaxis:. - #strip_mask(fill = MASK_FILL_UNSET, method: nil, axis: nil) ⇒ Object
-
#then_else(x, y) ⇒ CArray
Returns a ternary selection on
self(a boolean CArray), reading as "ifselfthenxelsey". -
#time(unit: :ns, origin: nil) ⇒ CATime
Returns
selfas a CATime on theunitgrid. -
#timedelta(unit: :ns) ⇒ CATimedelta
Returns
selfre-wrapped as a zero-copy CATimedelta view with the givenunit. -
#to_bit_string(nb) ⇒ String
Returns a packed-bit byte string built from
self, usingnbbits per element. -
#true ⇒ CArray
Returns a boolean CArray of the same shape as
selffilled withtrue. -
#union(other, sort: false) ⇒ CArray
Returns a 1-D CArray of the distinct values appearing in either
selforother, in self-then-other first-appearance order. -
#unique(sort: false) ⇒ CArray
Returns a 1-D CArray of the distinct values of
self, in first-appearance (row-major flatten) order. - #unmask(fill = MASK_FILL_UNSET, method: nil, axis: nil) ⇒ Object
-
#validity_bits ⇒ CArray?
Returns a packed uint8 CArray where bit
iis 1 iff celliof the receiver is not masked (LSB-first, lengthceil(elements / 8)). -
#value_counts(sort: false) ⇒ Array(CArray, CArray)
Returns
[values, counts], the distinct values ofselfpaired with the number of times each occurs. -
#windows(*ranges, bounds: :skip, fill_value: nil) ⇒ CAWindowIterator
Returns a CAWindowIterator rolling a per-axis offset window over
self.
Methods included from DataTypeExtension
arange, empty, eye, full, identity, linspace, ones, zeros
Class Method Details
._epoch_seconds_exact(spec, format = nil) ⇒ Object
Exact Rational seconds since the Unix epoch for a start literal (Time / DateTime / Integer unix-seconds / String). UTC default.
1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 |
# File 'lib/carray/time.rb', line 1500 def self._epoch_seconds_exact(spec, format = nil) require 'date' require 'time' case spec when Time then spec.to_r when Integer then Rational(spec) when String h = format ? Date._strptime(spec, format) : Date._parse(spec) unless h && h[:year] && h[:mon] && h[:mday] raise ArgumentError, "cannot parse time #{spec.inspect}" end days = CATime.send(:_days_from_civil, CA_INT64([h[:year]]), CA_INT64([h[:mon]]), CA_INT64([h[:mday]]))[0] sec = Rational(days * 86400 + (h[:hour] || 0) * 3600 + (h[:min] || 0) * 60 + (h[:sec] || 0)) sec += h[:sec_fraction] if h[:sec_fraction] sec -= h[:offset] if h[:offset] # east-of-UTC offset -> UTC sec else if defined?(DateTime) && spec.is_a?(DateTime) spec.to_time.to_r else raise ArgumentError, "cannot parse time #{spec.class}" end end end |
._epoch_tick_index(spec, res, format = nil) ⇒ Object
Tick index of spec's instant on the res grid (floor toward the past).
1551 1552 1553 1554 1555 1556 1557 1558 1559 |
# File 'lib/carray/time.rb', line 1551 def self._epoch_tick_index(spec, res, format = nil) if CATimeUnitAlgebra::FIXED.key?(res.base) (_epoch_seconds_exact(spec, format) / res.tick_ratio).floor else y, m = _epoch_year_month(spec, format) months = (y - 1970) * 12 + (m - 1) (Rational(months) / res.tick_ratio).floor end end |
._epoch_year_month(spec, format = nil) ⇒ Object
year, month of a start literal, for a calendar-resolution grid.
1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 |
# File 'lib/carray/time.rb', line 1529 def self._epoch_year_month(spec, format = nil) require 'date' require 'time' case spec when Time then t = spec.utc; [t.year, t.month] when Integer then t = Time.at(spec, in: 'UTC'); [t.year, t.month] when String h = format ? Date._strptime(spec, format) : Date._parse(spec) unless h && h[:year] raise ArgumentError, "cannot parse time #{spec.inspect}" end [h[:year], h[:mon] || 1] else if defined?(DateTime) && spec.is_a?(DateTime) t = spec.to_time.utc; [t.year, t.month] else raise ArgumentError, "cannot parse time #{spec.class}" end end end |
._time_cell(literal, res, format, on_error) ⇒ Object
Single-literal build for time: a 1-element CATime, honouring the on_error policy (raise, or a masked cell).
1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 |
# File 'lib/carray/time.rb', line 1665 def self._time_cell(literal, res, format, on_error) raw = CArray.int64(1) begin raw[0] = _epoch_tick_index(literal, res, format) rescue ArgumentError, TypeError raise if on_error == :raise raw[0] = UNDEF end raw.time(unit: res) end |
.align_addr(*arrays, join: :outer) ⇒ Array<CArray>
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/carray/methods/align_addr.rb', line 46 def self.align_addr (*arrays, join: :outer) raise ArgumentError, "align_addr: need at least one array" if arrays.empty? arrays = arrays.map { |a| a.is_a?(CArray) ? a : a.to_ca } # Seed the fold with the first array's distinct values so N == 1 and the # union/intersection folds all agree (a bare reduce over one element would # return it with duplicates intact). seed = arrays.first.unique common = case join when :outer then arrays[1..-1].reduce(seed) { |acc, a| acc.union(a) } when :inner then arrays[1..-1].reduce(seed) { |acc, a| acc.intersection(a) } when :left then seed when :right then arrays.last.unique else raise ArgumentError, "align_addr: join must be :outer / :inner / :left / :right " \ "(got #{join.inspect})" end idxs = arrays.map { |a| common.locate_addr(a) } [common, *idxs] end |
.align_nearest_addr(*arrays, grid: nil, direction: :round, tolerance: nil) ⇒ Array<CArray>
102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/carray/methods/align_addr.rb', line 102 def self.align_nearest_addr (*arrays, grid: nil, direction: :round, tolerance: nil) raise ArgumentError, "align_nearest_addr: need at least one array" if arrays.empty? arrays = arrays.map { |a| a.is_a?(CArray) ? a : a.to_ca } common = if grid.nil? arrays.first else grid.is_a?(CArray) ? grid : grid.to_ca end idxs = arrays.map { |a| common.locate_nearest_addr(a, direction: direction, tolerance: tolerance) } [common, *idxs] end |
.concatenate(list, axis: 0, data_type: nil) ⇒ CArray
84 85 86 87 |
# File 'lib/carray/methods/composition.rb', line 84 def self.concatenate (list, axis: 0, data_type: nil) raise ArgumentError, "concatenate: list must not be empty" if list.empty? __ragged_paste(list, [list.size], axis, data_type) end |
.const_string(values, encoding: Encoding::UTF_8) ⇒ CAConstString .const_string(ca, encoding: Encoding::UTF_8) ⇒ CAConstString .const_string(n, encoding: Encoding::UTF_8) {|i| ... } ⇒ CAConstString
Build a CAConstString (read-only variable-length string column) from Ruby data.
CArray.const_string(["alpha", "", "gamma"]) # 1-D from Array
CArray.const_string(3) { |i| "item#{i}" } # block form
CArray.const_string([a, nil, b]) # nil → masked element
B1: "" (length 0) is a valid empty string, distinct from a masked element (nil → masked). B2: element encoding must match :encoding (strict), pure-ASCII strings pass regardless (ASCII-compatible relaxation).
Storage is one (start, end) byte-range pair per element over a
pure-concatenation buffer (Arrow string layout). For a high-duplication
column (categorical labels), use CACategorical (= Arrow DictionaryArray)
instead — CAConstString stores every element's bytes, without dedup.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/carray/const_string.rb', line 53 def self.const_string (arg, encoding: Encoding::UTF_8, &block) if arg.is_a?(CArray) return string_face_of(arg).to_const_string(encoding: encoding) end if block n = Integer(arg) # B5: follow CArray.<type>(n){ ... } arity-0 broadcast quirk for # consistency — arity-0 block is evaluated once and broadcast. if block.arity == 0 v = block.call values = Array.new(n) { v } else values = Array.new(n) { |i| block.call(i) } end else values = arg.to_a end # Arrow-style layout, built in one C pass: pure-concatenation buffer + # one (start,end) int64 pair per element, mask for nil. CAConstString.__build__(values, encoding) end |
.dump(ca, **opt) ⇒ String
498 499 500 501 502 |
# File 'lib/carray/serialize.rb', line 498 def self.dump (ca, **opt) io = StringIO.new("".b) Serializer.new(io).save(ca, **opt) return io.string end |
.fixlen_string(values, bytes: nil, truncate: :error) ⇒ CAFixlenString .fixlen_string(ca, bytes: nil, truncate: :error) ⇒ CAFixlenString .fixlen_string(n, bytes: nil, truncate: :error) ⇒ CAFixlenString
Build a CAFixlenString (fixed-width String array over CA_FIXLEN storage).
CArray.fixlen_string(["ab", "cde"], bytes: 4) # explicit slot width CArray.fixlen_string(["ab", "cde"]) # width = max bytesize CArray.fixlen_string([a, nil, b], bytes: 8) # nil → masked element
The bounded slot width is the storage seam CAFixlenString exposes.
truncate: controls what happens when
a value exceeds bytes (only reachable when bytes is given explicitly;
the auto width can never overflow):
:error (default) raise ArgumentError on overflow — loud data loss
:silent let the native fixlen store keep the leading bytes bytes
The overflow policy lives at this construction surface, not at per-cell
fix[i] = v (which always truncates silently via the native fixlen store).
A CArray source is normalised through a String Face
(string_face_of): a raw CA_FIXLEN of matching width wraps
zero-copy, other string-bearing arrays materialise, numeric is rejected.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/carray/fixlen_string.rb', line 47 def self.fixlen_string (arg, bytes: nil, truncate: :error, &block) if arg.is_a?(CArray) return string_face_of(arg).to_fixlen_string(bytes: bytes, truncate: truncate) end unless [:error, :silent].include?(truncate) raise ArgumentError, "truncate: must be :error or :silent (got #{truncate.inspect})" end if block n = Integer(arg) if block.arity == 0 v = block.call values = Array.new(n) { v } else values = Array.new(n) { |i| block.call(i) } end else values = arg.to_a end width = bytes || values.compact.map { |s| s.to_s.bytesize }.max || 1 width = 1 if width < 1 if truncate == :error values.each_with_index do |s, i| next if s.nil? b = s.to_s.bytesize if b > width raise ArgumentError, "CArray.fixlen_string: value at #{i} is #{b} bytes, exceeds slot width #{width} " \ "(use truncate: :silent to keep the leading bytes)" end end end entity = CArray.new(CA_FIXLEN, [values.size], :bytes => width) values.each_with_index do |s, i| entity[i] = s.nil? ? UNDEF : s.to_s end CAFixlenString.wrap(entity) end |
.format(fmt, *argv) ⇒ CAString
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/carray/methods/string_format.rb', line 18 def self.format (fmt, *argv) cas = argv.select { |a| a.is_a?(CArray) } raise ArgumentError, "CArray.format: at least one CArray argument is required" if cas.empty? shape = cas.first.shape cas.each do |a| next if a.shape == shape raise ArgumentError, "CArray.format: shape mismatch (#{a.shape.inspect} vs #{shape.inspect})" end out = CArray.object(*shape) out.map_with_index! do |_, *idx| args = argv.map { |a| a.is_a?(CArray) ? a[*idx] : a } # a masked cell in any source array masks the output (UNDEF), rather # than feeding UNDEF into Kernel.format. args.any? { |v| v.equal?(UNDEF) } ? UNDEF : Kernel.format(fmt, *args) end CAString.wrap(out) end |
.from_bit_string(bstr, nb, data_type = CA_INT32, dim = nil) ⇒ CArray
41 42 43 44 45 46 47 48 49 50 |
# File 'lib/carray/methods/bit_string.rb', line 41 def self.from_bit_string (bstr, nb, data_type=CA_INT32, dim=nil) if dim obj = CArray.new(data_type, dim) else dim0 = ((bstr.length*8)/nb.to_f).floor obj = CArray.new(data_type, [dim0]) end obj.from_bit_string(bstr, nb) return obj end |
.fuse(*args) {|shadows| ... } ⇒ Object
839 840 841 842 843 844 845 846 847 848 849 |
# File 'lib/carray/lazy.rb', line 839 def fuse(*args) raise LocalJumpError, "CArray.fuse requires a block" unless block_given? shadows = args.map { |a| a.is_a?(CArray) ? a.lazy : a } result = yield(*shadows) case result when CAMonOp, CABinOp, CAMonCmp, CABinCmp, CALazyMarker result.to_ca else result end end |
.lazy(*args) {|shadows| ... } ⇒ Object
CArray.lazy(*args) { |lazies| ... } — dual of fuse
Like fuse, wraps each CArray argument with .lazy and yields it to
the block, but does not auto-materialise at block exit (= returns
the lazy structure as-is). If the block return is non-lazy (= Numeric
/ entity CArray / Array etc.) it's pass-through (= same polymorphic
semantics as fuse).
Use cases:
- Passing a chain between functions: build the lazy expression
inside the function and materialise at the caller
(=
.to_ca/.sum/.mean(axis:)etc.) - Reusable expressions: apply the same expr to multiple datasets
- debug / dump_tree: observe the lazy structure as-is
- Pick the materialise form later: full materialise or reduction
Example: expr = CArray.lazy(a, b) { |s, o| (s + o) * 2 } expr.class #=> CABinOp (lazy view) expr.to_ca # full materialise expr.sum # reduction (= chain + reduce in 1 pass)
Polymorphic semantics (= symmetric with fuse): CArray.lazy(25.0, b) { |s, o| s + o } # s=25.0 Float pass-through CArray.lazy(arr, b) { |s, o| s + o } # s=arr.lazy
884 885 886 887 888 |
# File 'lib/carray/lazy.rb', line 884 def lazy(*args) raise LocalJumpError, "CArray.lazy requires a block" unless block_given? shadows = args.map { |a| a.is_a?(CArray) ? a.lazy : a } yield(*shadows) end |
.load(input, **opt) ⇒ CArray
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 |
# File 'lib/carray/serialize.rb', line 476 def self.load (input, **opt) case input when String if input.bytesize >= Serializer::HEADER_BYTES and input.byteslice(0, 8) == Serializer::MAGIC io = StringIO.new(input) return Serializer.new(io).load(**opt) else open(input, "rb:ASCII-8BIT") { |io| return Serializer.new(io).load(**opt) } end else return Serializer.new(input).load(**opt) end end |
.load_arrow_tensor(filename) ⇒ CArray
Reads an Arrow tensor IPC file and returns it as a new CArray.
Experimental, and the name is provisional.
385 386 387 |
# File 'lib/carray/arrow_tensor.rb', line 385 def self.load_arrow_tensor (filename) File.open(filename, "rb") { |io| ArrowTensor.read(io) } end |
.meld(*arrays, axis: 0) ⇒ Object .meld(list, axis: 0) ⇒ CAMeld
Returns a CAMeld view of the arrays welded along an existing axis. No data is copied; reads gather from parents on demand and writes flow back to them (chain composability preserved).
Pieces must agree on ndim, data_type, byte width, and every axis
length except axis (the "meld axis"). Mismatched data_type raises:
cast the pieces yourself (.to_type(:float64)) or use
concatenate (eager, auto-casts).
"meld" = melt + weld — pieces dissolve their boundaries along the named axis and are regarded as one.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/carray/stack.rb', line 89 def self.meld (*arrays, axis: 0) if arrays.length == 1 && arrays[0].is_a?(Array) arrays = arrays[0] end raise ArgumentError, "meld: list must not be empty" if arrays.empty? first = arrays[0] unless first.is_a?(CArray) raise ArgumentError, "meld: entries must be CArray (got #{first.class})" end axis_norm = CArray.normalize_axis(axis, first.ndim, "meld") # Flatten nested CAMeld inputs that share our meld axis: they already # describe a segment sequence, so absorbing their parents keeps chain # depth at 1 (avoids 2-level xfer_all / reduce chains through the # intermediate CAMeld). A CAMeld with a different meld_axis is left # intact — its segment structure is orthogonal. if arrays.any? { |a| a.is_a?(CAMeld) && a.meld_axis == axis_norm } arrays = arrays.flat_map { |a| a.is_a?(CAMeld) && a.meld_axis == axis_norm ? a.parents : [a] } end CAMeld.new(arrays, axis: axis_norm) end |
.meshgrid(*axes, indexing: "xy", copy: true, sparse: false) {|grids| ... } ⇒ Array<CArray>
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/carray/methods/meshgrid.rb', line 48 def self.meshgrid (*axes, indexing: "xy", copy: true, sparse: false, &block) unless %w[xy ij].include?(indexing) raise ArgumentError, %{indexing option should be one of "xy" and "ij"} end # Each axis is negotiable, so no target type is imposed here; a # CArray comes back as itself and anything else is brought in with # its own data type. axes = axes.map.with_index do |axis, k| a = CArray.wrap_readonly(axis) unless a.ndim == 1 raise ArgumentError, "coordinate vector #{k} should be 1-D (got #{a.ndim}-D)" end a end ndim = axes.size # dest[k] = output axis position that input axis k populates. # "xy" swaps the first two; everything else is in input order. dest = (0...ndim).to_a dest[0], dest[1] = 1, 0 if indexing == "xy" && ndim >= 2 # Output shape: each output axis i takes its size from the input # axis that maps there. out_shape = Array.new(ndim) axes.each_with_index { |a, k| out_shape[dest[k]] = a.size } list = axes.map.with_index do |axis, k| d = dest[k] idx = if sparse Array.new(ndim) { |i| i == d ? nil : :* } else out_shape.dup.tap { |s| s[d] = :% } end view = axis[*idx] copy ? view.copy : view end block ? block.call(*list) : list end |
.montage(list, tdim, axis: 0, data_type: nil) ⇒ CArray
Arrange list of uniform-shape pieces in a tdim-shape grid that
extends parent axes axis..axis+tdim.size-1 by the corresponding
tdim[i] factor (= ImageMagick montage analog). Output ndim equals
each piece's ndim; the tile axes occupy positions
axis..axis+tdim.size-1. Returns a view; call .to_ca to materialise.
tdim.product must equal list.size. For non-uniform pieces along
tile axes, use CArray.mosaic.
Example (parent shape (3, 4), 6-element list, tdim=[2, 3], axis: 0):
CArray.montage([a, b, c, d, e, f], [2, 3], axis: 0) #=> shape (6, 12) -- 2 rows x 3 cols grid of (3, 4) blocks # +-----+-----+-----+ # | a | b | c | rows 0..2 # +-----+-----+-----+ # | d | e | f | rows 3..5 # +-----+-----+-----+
3.0 (post K_AXIS / promote_list / stack rename): renamed from combine
(= 20-year vocabulary that didn't describe the action). Positional
at replaced with axis: kwarg for consistency with bind / stack.
Parameter order changed from (tdim, list, at) to (list, tdim, axis:)
to align with bind / stack (list first).
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/carray/stack.rb', line 165 def self.montage (list, tdim, axis: 0, data_type: nil) raise ArgumentError, "montage: list must not be empty" if list.empty? unless tdim.is_a?(Array) && tdim.size > 0 raise ArgumentError, "montage: tdim must be a non-empty Array of Integer" end expected = tdim.inject(1) { |acc, n| acc * n } unless expected == list.size raise ArgumentError, "montage: tdim product (#{expected}) must equal list size (#{list.size})" end list = CArray.promote_list(list, data_type: data_type) parent_shape = list[0].shape ntile = tdim.size nparent = parent_shape.size axis = CArray.normalize_axis(axis, nparent - ntile + 1, "montage") s = CArray.stack(list).reshape(*tdim, *parent_shape) # (K, *) → (*tdim, *) # Interleave: tile axis i (= s axis i, i ∈ [0, ntile)) is moved to # just before parent axis (axis + i) in s coordinates (= s axis # ntile + axis + i). perm = [] nparent.times do |j| if j.between?(axis, axis + ntile - 1) perm << (j - axis) # tile axis end perm << ntile + j # parent axis end s = s.transpose(*perm) # Merge each (tile[i], parent[axis+i]) pair via reshape. new_shape = parent_shape.dup ntile.times { |i| new_shape[axis + i] *= tdim[i] } s.reshape(*new_shape) end |
.mosaic(list, tdim, axis: 0, data_type: nil) ⇒ CArray
121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/carray/methods/composition.rb', line 121 def self.mosaic (list, tdim, axis: 0, data_type: nil) raise ArgumentError, "mosaic: list must not be empty" if list.empty? unless tdim.is_a?(Array) && tdim.size > 0 raise ArgumentError, "mosaic: tdim must be a non-empty Array of Integer" end expected = tdim.inject(1, :*) unless expected == list.size raise ArgumentError, "mosaic: tdim product (#{expected}) must equal list size (#{list.size})" end __ragged_paste(list, tdim, axis, data_type) end |
.save(ca, output, **opt) ⇒ CArray
458 459 460 461 462 463 464 465 466 467 |
# File 'lib/carray/serialize.rb', line 458 def self.save (ca, output, **opt) case output when String open(output, "wb:ASCII-8BIT") { |io| return Serializer.new(io).save(ca, **opt) } else return Serializer.new(output).save(ca, **opt) end end |
.select(condlist, choicelist, default: 0, dtype: nil) ⇒ CArray
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/carray/conditional.rb', line 172 def self.select (condlist, choicelist, default: 0, dtype: nil) unless condlist.is_a?(Array) && choicelist.is_a?(Array) raise ArgumentError, "select: condlist and choicelist must be Arrays" end if condlist.size != choicelist.size raise ArgumentError, "select: condlist (#{condlist.size}) and choicelist (#{choicelist.size}) size mismatch" end if condlist.empty? raise ArgumentError, "select: at least one condition required" end first = condlist.first unless first.is_a?(CArray) && first.boolean? raise ArgumentError, "select: condlist[0] must be a boolean CArray" end shape = first.shape dt = dtype || CArray.result_type(*choicelist, default) # `default` can be either a same-shape CArray (per-cell fallback) or a # scalar (broadcast to every cell). default_full = default.is_a?(CArray) && !default.scalar? out = if default_full default.data_type == dt ? default.copy : default.to_type(dt) else CArray.new(dt, shape).fill(default.is_a?(CArray) ? default[0] : default) end # Iterate from lowest priority to highest (reverse) so the earliest # entry in `condlist` ends up on top — matches `np.select`'s # first-match semantics. (condlist.size - 1).downto(0) do |k| c = condlist[k] unless c.is_a?(CArray) && c.boolean? && c.shape == shape raise ArgumentError, "select: condlist[#{k}] must be a same-shape boolean CArray" end v = choicelist[k] out[c] = v.is_a?(CArray) ? v[c] : v end out end |
.stack(list, axis: 0, data_type: nil) ⇒ CArray
Stack list of CArrays along a new axis inserted at position
axis: (default 0 = outermost). Returns a view
(CAStack with k_axis = axis) when inputs are storage-uniform, or a
Face-lifted view (= CATime, CATimedelta, ...) when inputs are
homogeneous Face instances. Call .to_ca to materialise eagerly.
data_type: kwarg (optional, primitive Symbol only) forces primitive
promotion; cannot be used when the list contains Face elements. Class
/ Module targets are rejected (= data_type: CATime is invalid;
use auto-detect for Face round-trip).
3.0 (post-K_AXIS, F.S1-stack landed): replaces CArray.merge. The
low-level raw constructor is CAStack.new(list, axis:); this method
is the high-level surface that performs promote_list + CAStack.new +
(face_lift when homogeneous Face).
59 60 61 62 63 64 |
# File 'lib/carray/stack.rb', line 59 def self.stack (list, axis: 0, data_type: nil) raise ArgumentError, "stack: list must not be empty" if list.empty? list = CArray.promote_list(list, data_type: data_type) axis = CArray.normalize_axis(axis, list[0].ndim + 1, "stack") CAStack.new(list, axis: axis) # CAStack.new does Face lift internally end |
.string(values) ⇒ CAString .string(ca) ⇒ CAString .string(n) {|i| ... } ⇒ CAString
Build a CAString (mutable String array over object storage) from Ruby data.
CArray.string(["alpha", "", "gamma"]) # 1-D from Array
CArray.string(3) { |i| "item#{i}" } # block form
CArray.string([a, nil, b]) # nil → masked element
CArray.string(other_ca) # from a String Face / object / raw fixlen
nil entries become masked cells; "" (empty) is a valid distinct value.
A CArray source is normalised through a String Face (string_face_of):
a String Face converts, CA_OBJECT storage wraps, a raw CA_FIXLEN reads as
NUL-stripped strings; a numeric / boolean array is rejected (stringify with
#format / format).
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/carray/string.rb', line 41 def self.string (arg, &block) return string_face_of(arg).to_string if arg.is_a?(CArray) if block n = Integer(arg) if block.arity == 0 v = block.call values = Array.new(n) { v } else values = Array.new(n) { |i| block.call(i) } end else values = arg.to_a end entity = CArray.object(values.size) values.each_with_index do |s, i| entity[i] = s.nil? ? UNDEF : s end CAString.wrap(entity) end |
.struct(opt = {}) { ... } ⇒ Class
533 534 535 |
# File 'lib/carray/struct.rb', line 533 def self.struct (opt={}, &block) return CAStruct::Builder.new(:struct, opt).define(&block) end |
.tabulate(columns, data_type: nil) ⇒ CArray
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/carray/methods/composition.rb', line 254 def self.tabulate (columns, data_type: nil) raise ArgumentError, "tabulate: columns must not be empty" if columns.empty? blocks = columns.map do |c| unless c.is_a?(CArray) && (c.ndim == 1 || c.ndim == 2) raise ArgumentError, "tabulate: each column must be a 1-D or 2-D CArray" end c.ndim == 1 ? c[nil, :_] : c # promote a bare column to (L, 1) end len = blocks[0].shape[0] blocks.each_with_index do |b, i| unless b.shape[0] == len raise ArgumentError, "tabulate: all columns must have equal length (row count) " \ "(column 0 has length #{len}, column #{i} has length " \ "#{b.shape[0]}); tabulate does not pad ragged lengths" end end # Equal-length blocks, ragged column counts -> concatenate along the # column axis with a common (coerced or inferred) data_type. concatenate(blocks, axis: 1, data_type: data_type) end |
.time(x, unit: :s, format: nil, on_error: :raise) ⇒ CATime
1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 |
# File 'lib/carray/time.rb', line 1637 def self.time(x, unit: :s, format: nil, on_error: :raise) res = CATime::Resolution.parse(unit) unless %i[raise mask].include?(on_error) raise ArgumentError, "on_error: must be :raise or :mask (got #{on_error.inspect})" end x = CA_OBJECT(x) if x.is_a?(Array) # Ruby Array of literals -> object CArray unless x.is_a?(CArray) return _time_cell(x, res, format, on_error) end raw = CArray.int64(*x.shape) x.each_index do |*idx| s = x[*idx] if s == UNDEF || s.nil? raw[*idx] = UNDEF # missing input -> missing output (no phantom epoch) next end begin raw[*idx] = _epoch_tick_index(s, res, format) rescue ArgumentError, TypeError raise if on_error == :raise raw[*idx] = UNDEF # opt-in parse-mask end end raw.time(unit: res) end |
.time_range(start, last, unit:, step: nil, format: nil) ⇒ CATime
1580 1581 1582 1583 1584 1585 1586 1587 1588 |
# File 'lib/carray/time.rb', line 1580 def self.time_range(start, last, unit:, step: nil, format: nil) res = CATime::Resolution.parse(unit) stride = step.nil? ? 1 : CATimeUnitAlgebra.multiple_factor(CATime::Resolution.parse(step), res) s = _epoch_tick_index(start, res, format) e = _epoch_tick_index(last, res, format) n = e < s ? 0 : (e - s) / stride + 1 CArray.int64(n) {|i| s + i * stride }.time(unit: res) end |
.time_series(start, count:, unit:, step: nil, format: nil) ⇒ CATime
1606 1607 1608 1609 1610 1611 1612 |
# File 'lib/carray/time.rb', line 1606 def self.time_series(start, count:, unit:, step: nil, format: nil) res = CATime::Resolution.parse(unit) stride = step.nil? ? 1 : CATimeUnitAlgebra.multiple_factor(CATime::Resolution.parse(step), res) s = _epoch_tick_index(start, res, format) CArray.int64(count) {|i| s + i * stride }.time(unit: res) end |
Instance Method Details
#<=>(other) ⇒ CArray Also known as: cmp
243 244 245 |
# File 'lib/carray/basics.rb', line 243 def <=> (other) (self > other).as_int8 - (self < other).as_int8 end |
#__real_eager__ ⇒ Object
real / imag lazy fuse (post-IC follow-up):
Eager real/imag in lib/carray/math.rb return CAField views
(zero-copy, mutable byte-offset access into complex storage) or
fresh template entities for non-complex. Both break a lazy chain
when applied to a lazy parent (CAField materialises the parent into
an entity to byte-offset into). For lazy parents we re-express via
chain composition so the chain stays fused:
complex parent .real → CAMonOp(cast_
complex parent .imag → CAMonOp(cast_
non-complex parent .real → self (= pass-through, chain unchanged)
non-complex parent .imag → CAMonOp(imag_i) (= same-shape lazy zero)
Eager parents fall through to the existing math.rb implementation
(= CAField for complex, CARefer/template for non-complex) so the
mutable-setter use cases (.real = val / .imag = val) remain
supported. Lazy views are inherently read-only so the loss of
mutability on the lazy path is not a regression.
177 |
# File 'lib/carray/lazy.rb', line 177 alias_method :__real_eager__, :real |
#address ⇒ CArray
152 153 154 |
# File 'lib/carray/basics.rb', line 152 def address return CArray.int32(*shape).seq! end |
#all(skip_masked: true, **opts) ⇒ Boolean, CArray
Whether every cell is true.
With skip_masked: true (the default) masked cells are simply ignored and
the result is always true / false. With skip_masked: false the fold
is three-valued: the result is UNDEF when a masked cell could change it,
matching the element-wise Kleene semantics of | / &.
54 55 56 57 |
# File 'lib/carray/boolean_reduce.rb', line 54 def all (skip_masked: true, **opts) return __all_skipna__(**opts) if skip_masked __kleene_fold(:all, opts) end |
#any(skip_masked: true, **opts) ⇒ Boolean, CArray
Whether any cell is true.
With skip_masked: true (the default) masked cells are simply ignored and
the result is always true / false. With skip_masked: false the fold
is three-valued: the result is UNDEF when a masked cell could change it,
matching the element-wise Kleene semantics of | / &.
38 39 40 41 |
# File 'lib/carray/boolean_reduce.rb', line 38 def any (skip_masked: true, **opts) return __any_skipna__(**opts) if skip_masked __kleene_fold(:any, opts) end |
#attr(key) ⇒ Object?
41 42 43 44 45 46 47 |
# File 'lib/carray/attribute.rb', line 41 def attr (key) k = attr_normalize_key(key) attr_each_chain do |h| return h[k] if h.key?(k) end nil end |
#attrs ⇒ Hash{String => Object}
69 70 71 72 73 74 75 76 |
# File 'lib/carray/attribute.rb', line 69 def attrs merged = nil attr_each_chain do |h| merged ||= {} h.each { |k, v| merged[k] = v unless merged.key?(k) } end (merged || {}).freeze end |
#axis_group(*slots) ⇒ Object
CArray#axis_group(cat_or_nil, ...) -- build an AxisGroup spec.
Slot position = source axis. A CACategorical slot consumes cat.ndim source axes (rank-1 = one axis, rank-N = several axes collapsed into one group axis); a nil slot is a band (held) axis. ALL axes must be given explicitly -- the rank-sum must equal self.ndim, trailing omission / nil fill is forbidden (explicit > implicit). The value is used as a shape TEMPLATE only (its data is never read).
33 34 35 |
# File 'lib/carray/axis_group.rb', line 33 def axis_group (*slots) AxisGroup.new(self, slots) end |
#bin(vmin, vmax, step = nil, bins: nil, lfill: nil, ufill: nil, include_max: true) ⇒ CArray
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/carray/methods/bin.rb', line 43 def bin(vmin, vmax, step = nil, bins: nil, lfill: nil, ufill: nil, include_max: true) if step.nil? == bins.nil? raise ArgumentError, "bin: give exactly one of `step` or `bins:`" end raise ArgumentError, "bin: vmin > vmax" if vmin > vmax n = bins || ((vmax - vmin).to_f / step).round raise ArgumentError, "bin: n must be >= 1" if n < 1 if vmin == vmax # Degenerate: zero interval → all cells fall on the single edge; # with include_max: true they land in bin 0. out = CArray.int64(*shape) { 0 } out.mask = self.mask.to_ca if self.has_mask? if self.float? inv = self.is_invalid if inv.count(true) > 0 out.mask = out.has_mask? ? (out.mask | inv) : inv end end return out end # Delegate to `bin_to` with generated uniform edges — same kernel # (`histbin_ki`) as `histogram`, so semantics are identical. edges = CArray.float64(n + 1).span(vmin..vmax) bin_to(edges, lfill: lfill, ufill: ufill, include_max: include_max) end |
#bin_to(edges, lfill: nil, ufill: nil, include_max: false) ⇒ CArray
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/carray/methods/bin.rb', line 110 def bin_to(edges, lfill: nil, ufill: nil, include_max: false) e = CArray.wrap_readonly(edges, :float64) raise ArgumentError, "bin_to: edges must be 1-D" unless e.ndim == 1 raise ArgumentError, "bin_to: edges needs at least 2 values" if e.elements < 2 n = e.elements - 1 # number of bins src = data_type == CA_FLOAT64 ? self : CArray.wrap_readonly(self, :float64) # histbin_ki returns the extended index (0 = under, 1..N = in-range bins, # N+1 = over; NaN / masked -> masked). Shift to the in-range convention: # under -> -1, in-range -> 0..N-1, over -> N. out = src.send(:histbin_ki, e, include_max) - 1 out[:eq, -1] = lfill.nil? ? UNDEF : lfill # under out[:eq, n] = ufill.nil? ? UNDEF : ufill # over out end |
#bincount(weights: nil, length: 0) ⇒ CArray
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/carray/methods/bincount.rb', line 32 def bincount(weights: nil, length: 0) unless [CA_INT8, CA_INT16, CA_INT32, CA_INT64, CA_UINT8, CA_UINT16, CA_UINT32, CA_UINT64].include?(data_type) raise CArray::DataTypeError, "bincount requires an integer label array (got #{data_type_name})" end if elements.zero? if weights out = CArray.new(weights.data_type, [length]) else out_type = (length > 0xFFFFFFFF) ? CA_UINT64 : CA_UINT32 out = CArray.new(out_type, [length]) end out.fill(0) unless length.zero? return out end # Single-pass fused min+max so the prereq scan over labels costs # one walk instead of two. label_min, label_max = minmax if label_min.equal?(UNDEF) # Every cell is masked: no labels to count, same result as an empty # input (all-zero output of the requested minimum length). if weights out = CArray.new(weights.data_type, [length]) else out_type = (length > 0xFFFFFFFF) ? CA_UINT64 : CA_UINT32 out = CArray.new(out_type, [length]) end out.fill(0) unless length.zero? return out end if label_min < 0 raise ArgumentError, "bincount: negative label not allowed (got #{label_min})" end n = [length, label_max + 1].max if weights unless weights.is_a?(CArray) raise ArgumentError, "bincount: weights must be a CArray" end if weights.elements != elements raise ArgumentError, "bincount: weights length (#{weights.elements}) doesn't " \ "match labels length (#{elements})" end __bincount_weighted__(weights, n) else __bincount_count__(n) end end |
#bincount_nd(lengths:, axis: [-2, -1], weights: nil) ⇒ BincountND
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
# File 'lib/carray/bincount_nd.rb', line 339 def bincount_nd (lengths:, axis: [-2, -1], weights: nil) raise ArgumentError, "lengths must be an Array of per-dim extents" unless lengths.is_a?(Array) sample_ax = normalize_axis(axis[0], "bincount_nd sample axis") channel_ax = normalize_axis(axis[1], "bincount_nd channel axis") fiber_shape = shape.dup [sample_ax, channel_ax].sort.reverse.each { |p| fiber_shape.delete_at(p) } # Weighted counts are float64-only (the FLAT bincount coerces weights to the # counts dtype and the FIBER kernel requires float64 weights/counts), so the # dtype is fixed here rather than derived from the weights' own dtype. weights_dtype = (:float64 if weights) h = BincountND.send(:new, lengths: lengths, fiber_shape: fiber_shape, weights_dtype: weights_dtype) h.add(self, axis: axis, weights: weights) h end |
#blocks(*blocks) ⇒ CABlockIterator
601 602 603 |
# File 'lib/carray/block_iterator.rb', line 601 def blocks (*blocks) CABlockIterator.new(self, *blocks) end |
#categorize(labels: nil, sort_labels: false) ⇒ CACategorical
Build a CACategorical from self read as category keys (= the values whose
distinct levels become the categories). Codes are dense 0-based in the
order labels appear (first-appearance by default, or ascending sorted
when sort_labels: true); masked keys become masked (excluded) codes.
labels: nil -> discover, first-appearance order labels: nil, sort_labels: true -> discover, then sort ascending labels: set -> fixed vocabulary (must be unique); keys outside it are excluded (masked)
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 |
# File 'lib/carray/categorical.rb', line 515 def categorize(labels: nil, sort_labels: false) # Automatic appearance-order vocabulary: one linear pass (C # __factorize_appearance__) returns both codes and levels directly, over the # integer / float / object / fixlen / boolean lanes (boolean rides the uint8 # lane). Distinctness is the hash-key judgement shared with the discovery # family: Float NaN collapses to one category and -0.0 == +0.0, while mixed # Integer / Float keys stay distinct (eql?, so 1 and 1.0 are separate # categories). The discovery path below is reserved for sort_labels (which # reorders the vocabulary, desyncing the appearance-order codes), an explicit # labels list, and the dtypes the factorize kernel does not take (complex). if labels.nil? && !sort_labels && (integer? || float? || object? || fixlen? || boolean?) codes, levels = __factorize_appearance__ return CACategorical.from_codes(codes, levels.to_a) end if labels.nil? # Discover the levels in first-appearance order: mask_duplicates keeps the # first occurrence of each distinct value and masks the rest (already- # masked keys stay excluded), so the non-masked cells are the levels. # Only the final list is Ruby, since labels are Ruby objects. labels_arr = mask_duplicates[:is_not_masked].to_a labels_arr.sort! if sort_labels else labels_arr = labels.respond_to?(:to_a) ? labels.to_a : Array(labels) if labels_arr.uniq.size != labels_arr.size raise ArgumentError, "categorize: labels: must be unique (got duplicates)" end end # Choose a narrow unsigned code dtype, reserving its top value as the # exclusion sentinel so it never collides with a real code 0..k-1. k = labels_arr.size code_type, sentinel = if k <= 0xFF then [CA_UINT8, 0xFF] elsif k <= 0xFFFF then [CA_UINT16, 0xFFFF] else [CA_UINT32, 0xFFFFFFFF] end # One vectorized masked write per category. Cells matching no category # (out-of-vocabulary) and masked cells (eq yields UNDEF, skipped) keep the # sentinel; from_codes then derives the mask from it. codes = CArray.new(code_type, shape).fill(sentinel) labels_arr.each_with_index { |label, c| codes[eq(label)] = c } CACategorical.from_codes(codes, labels_arr) end |
#choose(choices, data_type: nil) ⇒ CArray
26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/carray/methods/choose.rb', line 26 def choose (choices, data_type: nil) unless data_type ca = choices.select { |v| v.is_a?(CArray) } data_type = ca.empty? ? CA_OBJECT : CArray.result_type(*ca) end out = template(data_type) choices.each_with_index do |v, i| s = self.eq(i) out[s] = v.is_a?(CArray) ? v[s] : v end out end |
#clip(min, max = nil, fill_value = nil, lfill: nil, ufill: nil) ⇒ CArray
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/carray/basics.rb', line 276 def clip(min, max=nil, fill_value=nil, lfill: nil, ufill: nil) if min.nil? && max.nil? raise ArgumentError, "clip: at least one of (min, max) must be given" end # `fill_value` as a single argument is sugar applied to both ends; kwargs override. lfill = fill_value if lfill.nil? ufill = fill_value if ufill.nil? if lfill.nil? && ufill.nil? return __clip_ki__(min, max) if !min.nil? && !max.nil? return pmax(min) if max.nil? return pmin(max) end out = self.copy out[:lt, min] = lfill unless min.nil? || lfill.nil? out[:gt, max] = ufill unless max.nil? || ufill.nil? out end |
#coerce ⇒ Object Also known as: __coerce_eager__
1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 |
# File 'ext/carray_operator.c', line 1800
static VALUE
rb_ca_coerce (VALUE self, VALUE other)
{
if ( rb_obj_is_carray(other) ) {
return Qnil;
}
else if ( rb_respond_to(other, rb_intern("to_ca")) ) {
return rb_ca_coerce(self, rb_funcall(other,rb_intern("to_ca"),0));
}
else {
/* do implicit casting and resolving unbound repeat array */
rb_ca_cast_self_or_other(&self, &other);
return rb_assoc_new(other, self);
}
}
|
#concatenate(*others, axis: 0, data_type: nil) ⇒ CArray
100 101 102 103 |
# File 'lib/carray/methods/composition.rb', line 100 def concatenate (*others, axis: 0, data_type: nil) raise ArgumentError, "concatenate: at least one other array required" if others.empty? CArray.concatenate([self, *others], axis: axis, data_type: data_type) end |
#conditional(cond, then_fn, else_fn, dtype: nil) ⇒ CArray
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/carray/conditional.rb', line 111 def conditional (cond, then_fn, else_fn, dtype: nil) unless cond.is_a?(CArray) && cond.boolean? && cond.shape == self.shape raise ArgumentError, "conditional: cond must be a boolean CArray with same shape as self" end x_then = self[cond] x_else = self[cond.not] y_then = then_fn.call(x_then) y_else = else_fn.call(x_else) # A callable that returns a scalar (e.g. `->(v) { 0 }`) broadcasts # to the subset shape; wrap it here so the scatter step below sees a # same-length CArray. unless y_then.is_a?(CArray) y_then = CArray.new(dtype || CArray.result_type(y_then), x_then.shape).fill(y_then) end unless y_else.is_a?(CArray) y_else = CArray.new(dtype || CArray.result_type(y_else), x_else.shape).fill(y_else) end dt = dtype || CArray.result_type(y_then, y_else) out = CArray.new(dt, self.shape) out[cond] = y_then out[cond.not] = y_else # Propagate cond's mask (mirrors then_else's rule): UNDEF in cond -> # UNDEF in out. Kleene `cond.not` also carries UNDEF at the same # positions, so both scatters leave the cell untouched — an explicit # fix-up is required. out[cond.is_masked] = UNDEF if cond.has_mask? out end |
#crop(offset, dst) ⇒ CArray
203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/carray/basics.rb', line 203 def crop (offset, dst) raise ArgumentError, "offset length must equal ndim" if offset.length != ndim src_ranges = [] dst_ranges = [] ndim.times do |i| s_lo = [offset[i], 0].max s_hi = [offset[i] + dst.shape[i], shape[i]].min return dst if s_lo >= s_hi src_ranges << (s_lo...s_hi) dst_ranges << ((s_lo - offset[i])...(s_hi - offset[i])) end dst[*dst_ranges] = self[*src_ranges] dst end |
#delete_block(offset, bsize) ⇒ CArray
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/carray/methods/insert_block.rb', line 76 def delete_block (offset, bsize) if offset.size != ndim or bsize.size != ndim raise ArgumentError, "ndim mismatch" end offset = offset.dup # normalize without mutating the caller's array newdim = shape grids = [] ndim.times do |i| offset[i] += shape[i] if offset[i] < 0 if bsize[i] < 0 or offset[i] < 0 or offset[i] >= shape[i] or offset[i] + bsize[i] > shape[i] raise ArgumentError, "invalid offset or size at axis #{i}" end newdim[i] -= bsize[i] grids[i] = CArray.int32(newdim[i]) grids[i][0...offset[i]].seq! if offset[i] > 0 if offset[i] + bsize[i] < shape[i] grids[i][offset[i]..-1].seq!(offset[i]+bsize[i]) end end return self[*grids].copy end |
#difference(other, sort: false) ⇒ CArray
75 76 77 78 79 |
# File 'lib/carray/methods/is_in.rb', line 75 def difference (other, sort: false) a, b = promote_value_set(other) r = a.__send__(:__difference__, b) sort ? r.sort : r end |
#drop_axis ⇒ CArray
139 140 141 142 143 144 145 146 |
# File 'lib/carray/basics.rb', line 139 def drop_axis if ndim == 1 return self[] else newdim = shape.reject{|x| x == 1 } return ( ndim != newdim.size ) ? reshape(*newdim) : self[] end end |
#false ⇒ CArray
160 161 162 |
# File 'lib/carray/basics.rb', line 160 def false () return template(:boolean) end |
#format(fmt, *argv) ⇒ CAString
53 54 55 |
# File 'lib/carray/methods/string_format.rb', line 53 def format (fmt, *argv) CArray.format(fmt, self, *argv) end |
#from_bit_string(bstr, nb) ⇒ self
24 25 26 27 28 29 30 |
# File 'lib/carray/methods/bit_string.rb', line 24 def from_bit_string (bstr, nb) hex = CArray.uint8(bstr.length).load_binary(bstr) hex.bits[] = hex.bits[nil,[-1..0]] bits = hex.bits.flatten self.bits[false,[(nb-1)..0]][nil].paste([0], bits) return self end |
#gather_nd(indices) ⇒ CArray
63 64 65 66 67 68 69 |
# File 'lib/carray/methods/gather_nd.rb', line 63 def gather_nd (indices) flat_addr, outer, rest = gather_nd_flat_addr(indices, "gather_nd") out_shape = outer + rest # flatten + 1-D fancy indexing -> CAMapping view -> materialise via .copy. result = self.flatten[flat_addr].copy out_shape.empty? ? result : result.reshape(*out_shape) end |
#group_by_category(cat) ⇒ CACategoricalIterator
1016 1017 1018 |
# File 'lib/carray/categorical_iterator.rb', line 1016 def group_by_category (cat) CACategoricalIterator.new(self, cat) end |
#group_by_run ⇒ CACategoricalIterator
1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 |
# File 'lib/carray/categorical_iterator.rb', line 1042 def group_by_run raise "group_by_run: 1-D only (got #{ndim}-D)" unless ndim == 1 if elements == 0 code = CArray.int64(0) else present = is_not_masked edge = present & present.shift(1).not # rising edge = run start # feed cumsum via a zero-copy int8 reinterpret of the 1-byte booleans # rather than widening to int64; cumsum promotes to float64, so the # running count never overflows int8. code = edge.refer(:int8).cumsum.int64 - 1 # 0-based run index per cell code[present.not] = UNDEF # masked cells join no run end # categorize turns the dense run indices into the run categories: it derives # the label vocabulary and folds an all-masked (dry) series to zero groups # on its own, so no explicit run count is needed here. code is monotonic (a # cumsum), so categorize's first-appearance order is already run order and # sort_labels would be a no-op. group_by_category(code.categorize) end |
#has_attr? ⇒ Boolean #has_attr?(key) ⇒ Boolean
87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/carray/attribute.rb', line 87 def has_attr? (key = nil) if key.nil? attr_each_chain do |h| return true unless h.empty? end false else k = attr_normalize_key(key) attr_each_chain do |h| return true if h.key?(k) end false end end |
#histogram(edges:, axis: [-2, -1], include_max: false, weights: nil) ⇒ Histogram
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 |
# File 'lib/carray/histogram.rb', line 491 def histogram (edges:, axis: [-2, -1], include_max: false, weights: nil) raise ArgumentError, "edges must be an Array of edges arrays" unless edges.is_a?(Array) arr = self sample_ax = normalize_axis(axis[0], "histogram sample axis") channel_ax = normalize_axis(axis[1], "histogram channel axis") fiber_shape = arr.shape.dup [sample_ax, channel_ax].sort.reverse.each { |p| fiber_shape.delete_at(p) } # Weighted counts are float64-only (the fused scatter kernel requires # float64 weights and float64 counts), so the dtype is fixed here rather # than derived from the weights' own dtype. weights_dtype = (:float64 if weights) h = Histogram.send(:new, edges: edges, fiber_shape: fiber_shape, include_max: include_max, weights_dtype: weights_dtype) h.add(arr, axis: axis, weights: weights) h end |
#histogram1d(edges:, axis: -1, include_max: false, weights: nil) ⇒ Histogram
449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'lib/carray/histogram.rb', line 449 def histogram1d (edges:, axis: -1, include_max: false, weights: nil) ax = normalize_axis(axis, "histogram1d") new_shape = shape + [1] arr_with_channel = reshape(*new_shape) # `include_max` passes straight through: the Histogram constructor # normalizes a scalar bool to per-dim, and raises on a wrong-length Array # (= same path as histogram2d, no M=1 special-casing here). arr_with_channel.histogram(edges: [edges], axis: [ax, new_shape.size - 1], include_max: include_max, weights: weights) end |
#histogram2d(edges:, axis: [-2, -1], include_max: false, weights: nil) ⇒ Histogram
474 475 476 477 |
# File 'lib/carray/histogram.rb', line 474 def histogram2d (edges:, axis: [-2, -1], include_max: false, weights: nil) raise ArgumentError, "edges must be a list of 2" unless edges.is_a?(Array) && edges.size == 2 histogram(edges: edges, axis: axis, include_max: include_max, weights: weights) end |
#imag ⇒ CArray
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/carray/complex.rb', line 91 def imag if not @__imag__ if complex? @__imag__ = case data_type when CA_CMPLX64 field(4, CA_FLOAT32) when CA_CMPLX128 field(8, CA_FLOAT64) end else @__imag__ = self.template { 0 } end end return @__imag__ end |
#imag=(val) ⇒ Object
112 113 114 115 116 117 118 |
# File 'lib/carray/complex.rb', line 112 def imag= (val) if complex? imag[] = val else raise "not a complex array" end end |
#index(axis: 0) ⇒ CArray
19 20 21 22 23 24 |
# File 'lib/carray/methods/index.rb', line 19 def index (axis: 0) k = normalize_axis(axis, "index") oshape = Array.new(ndim, 1) oshape[k] = shape[k] CArray.int32(*oshape).seq! end |
#indices ⇒ Array<CArray> #indices({ |*ramps| ... }) {|*ramps| ... } ⇒ Object
34 35 36 37 |
# File 'lib/carray/methods/index.rb', line 34 def indices list = (0...ndim).map { |k| index(axis: k) } block_given? ? yield(*list) : list end |
#insert_axis(*positions, repeat: nil) ⇒ CArray
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/carray/basics.rb', line 54 def insert_axis (*positions, repeat: nil) flat = positions.flatten if flat.empty? raise ArgumentError, "insert_axis: at least one position is required" end # No repeat: -> plain size-1 insertion. The source-frame C primitive # handles normalization, range check and multiplicity directly. return __insert_axis_size1__(*flat) if repeat.nil? # Source frame: each position names the source axis the new axis goes # before. Gaps live in [0, ndim] (ndim = append at end); negatives count # from the end gap. Duplicates are allowed (several axes before one # source axis), kept in argument order. gaps = flat.map { |p| CArray.normalize_axis(p, ndim + 1, "insert_axis") } # One repeat value per position, in argument order. reps = case repeat when Array unless repeat.length == flat.length raise ArgumentError, "insert_axis: repeat array length (#{repeat.length}) " \ "must match number of positions (#{flat.length})" end repeat else Array.new(flat.length, repeat) end # Validate each value. A positive Integer or :* only; nil is not a # valid per-axis repeat. reps.each do |r| case r when Integer raise ArgumentError, "insert_axis: repeat count must be >= 1" if r < 1 when :* # ok else raise ArgumentError, "insert_axis: repeat must be a positive Integer or :*, got #{r.inspect}" end end # Final output layout: stable order by (gap, argument index) keeps # same-gap axes in argument order; the k-th inserted axis lands at output # position gap + k. This output position is only used to drive the # output-shaped view constructors (broadcast_to / unbound_repeat); the # actual insertion always goes through the source-frame primitive below. order = (0...flat.length).sort_by { |i| [gaps[i], i] } final = {} order.each_with_index { |i, k| final[i] = gaps[i] + k } unbound_args = order.select { |i| reps[i] == :* } concrete_args = order.reject { |i| reps[i] == :* } # in output order # Stage 1: insert the concrete (size-1 / bound) axes by their source # gaps, then grow the bound ones with broadcast_to. inter = self unless concrete_args.empty? inter = __insert_axis_size1__(*concrete_args.map { |i| gaps[i] }) if concrete_args.any? { |i| reps[i].is_a?(Integer) && reps[i] > 1 } shp = inter.shape concrete_args.each do |i| r = reps[i] next unless r.is_a?(Integer) && r > 1 # intermediate position = final position minus unbound axes before it shp[final[i] - unbound_args.count { |u| final[u] < final[i] }] = r end inter = inter.broadcast_to(*shp) end end return inter if unbound_args.empty? # Stage 2: add the unbound axes over the final ndim (`:*` at unbound # positions, nil consumes one stage-1 axis in order). pattern = Array.new(ndim + flat.length, nil) unbound_args.each { |i| pattern[final[i]] = :* } inter.unbound_repeat(*pattern) end |
#insert_block(offset, bsize, &block) ⇒ CArray
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/carray/methods/insert_block.rb', line 24 def insert_block (offset, bsize, &block) if offset.size != ndim or bsize.size != ndim raise ArgumentError, "ndim mismatch" end offset = offset.dup # normalize without mutating the caller's array newdim = shape grids = shape.map{|d| CArray.int32(d) } ndim.times do |i| offset[i] += shape[i] if offset[i] < 0 if offset[i] < 0 or offset[i] > shape[i] or bsize[i] < 0 raise ArgumentError, "invalid offset or size at axis #{i}" end if bsize[i] > 0 newdim[i] += bsize[i] end grids[i][0...offset[i]].seq! if offset[i] > 0 # offset == dim (append) leaves nothing on the upper side to shift. grids[i][offset[i]..-1].seq!(offset[i]+bsize[i]) if offset[i] < shape[i] end # Build at the storage layout (preserving bytes for fixlen / Face), # then re-wrap as the same Face. face_parent = self.face? ? self : nil src = self src = src.parent while src.face? dt = src.data_type bytes = (dt == :fixlen) ? src.bytes : nil out = CArray.new(dt, newdim, bytes: bytes) if block_given? sel = out.true sel[*grids] = 0 out[sel] = block.call end out[*grids] = src out = out.face_lift(face_parent) if face_parent return out end |
#inspect ⇒ String
224 225 226 |
# File 'lib/carray/inspect.rb', line 224 def inspect return CArray::Inspector.new(self).inspect_string end |
#intersection(other, sort: false) ⇒ CArray
61 62 63 64 65 |
# File 'lib/carray/methods/is_in.rb', line 61 def intersection (other, sort: false) a, b = promote_value_set(other) r = a.__send__(:__intersection__, b) sort ? r.sort : r end |
#is_in(values) ⇒ CArray
42 43 44 45 |
# File 'lib/carray/methods/is_in.rb', line 42 def is_in (values) a, b = promote_value_set(values) a.__send__(:__is_in__, b) end |
#is_mode(axis: nil) ⇒ CArray
35 36 37 38 39 40 41 42 43 44 |
# File 'lib/carray/methods/mode.rb', line 35 def is_mode (axis: nil) # Per-fiber two-pass frequency table (C __is_mode__), one lane per dtype # family (numeric widen / NaN collapse, object rb_hash + rb_eql, fixlen # byte-hash + memcmp). Ties are all marked; masked cells stay false. if axis.nil? flatten.send(:__is_mode__, 0).reshape(*shape) else __is_mode__(normalize_axis(axis, "is_mode")) end end |
#is_real ⇒ CArray?
140 141 142 143 144 145 146 147 148 |
# File 'lib/carray/complex.rb', line 140 def is_real if complex? imag.eq(0) elsif numeric? self.true else nil end end |
#join(sep = nil) ⇒ Object #join(sep = "", axis:, keep_axis: false) ⇒ CArray, String
The 2.x multi-separator form a.join("\n", ",") was removed
in 3.0; use the axis form and chain, e.g.
a.join(",", axis: 1).join("\n").
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/carray/methods/join.rb', line 36 def join (*argv, axis: nil, keep_axis: false) if argv.size > 1 raise ArgumentError, "join accepts at most one positional separator " \ "(the 2.x multi-separator form was removed in 3.0; " \ "use axis: for per-axis join and chain)" end sep = argv.first # nil or String if axis.nil? return sep.nil? ? to_a.join : to_a.join(sep) end ax = Integer(axis) ax += ndim if ax < 0 if ax < 0 || ax >= ndim raise ArgumentError, "axis #{axis.inspect} out of range for ndim=#{ndim}" end sep_str = sep || "" # Bring `ax` to the innermost position so we can iterate fibers as # rows of a 2-D reshape. transpose returns a view; reshape may # materialize on non-contig, which is fine for this formatting op. if ax == ndim - 1 t = self else order = (0...ndim).to_a order << order.delete_at(ax) t = transpose(*order) end inner = t.shape[-1] outer_n = t.elements / inner # 1 when ndim == 1 flat = t.reshape(outer_n, inner) strings = Array.new(outer_n) { |i| flat[i, nil].to_a.join(sep_str) } if ndim == 1 # Full reduction: return the String directly, or a length-1 # CArray when keep_axis was requested. return keep_axis ? CA_OBJECT([strings.first]) : strings.first end result_shape = shape.dup if keep_axis result_shape[ax] = 1 else result_shape.delete_at(ax) end CA_OBJECT(strings).reshape(*result_shape) end |
#locate_addr(ref) ⇒ Object
User-facing YARD docs for #locate_addr and #locate_nearest_addr live in yard-stubs/carray_order.rb (grouped with the search family).
6 7 8 9 10 11 12 13 14 15 16 17 |
# File 'lib/carray/methods/locate_addr.rb', line 6 def locate_addr (ref) ref = ref.to_ca unless ref.is_a?(CArray) # Put self and ref in a common lane via the single-source promotion rule # (CArray.result_type), so a fractional query against an int ref is compared # at the promoted type instead of truncating (1.5 no longer matches 1). # to_type is elementwise and order-preserving, so the addresses stay valid # indices into ref. result_type raises for cross-family input. t = CArray.result_type(self, ref) q = (data_type == t) ? self : to_type(t) r = (ref.data_type == t) ? ref : ref.to_type(t) q.send(:__locate_addr__, r) end |
#locate_nearest_addr(ref, direction: :round, tolerance: nil) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/carray/methods/locate_addr.rb', line 19 def locate_nearest_addr (ref, direction: :round, tolerance: nil) unless [:round, :floor, :ceil].include?(direction) raise ArgumentError, "locate_nearest_addr: direction must be :round / :floor / " \ ":ceil (got #{direction.inspect})" end ri = ref.sort_addr rs = ref[ri] sec = rs.linear_section(self) unless sec.is_a?(CArray) # A single-element (scalar-like) self makes linear_section collapse to # its scalar-query path, which returns a bare Float (or nil when out of # range) instead of a CArray. Rebuild a self-shaped float64 CArray so # the mask_invalid -> direction -> project pipeline stays array-valued # and the returned addr array matches self's shape. fill = CArray.float64(*shape) fill[] = sec.nil? ? UNDEF : sec sec = fill end si = sec.mask_invalid.send(direction).int64 idx = ri.project(si) if tolerance dist = (ref.project(idx) - self).abs idx[dist > tolerance] = UNDEF end idx end |
#lookup(table, fill_value = nil, lfill: nil, ufill: nil) ⇒ CArray
231 232 233 234 235 |
# File 'lib/carray/basics.rb', line 231 def lookup(table, fill_value=nil, lfill: nil, ufill: nil) lfill = fill_value if lfill.nil? ufill = fill_value if ufill.nil? table.project(self, lfill, ufill) end |
#marshal_dump ⇒ Array
518 519 520 521 522 523 524 525 526 527 528 |
# File 'lib/carray/serialize.rb', line 518 def marshal_dump () target = (self.class != CArray and self.class != CScalar) ? self.copy : self if target.data_type == :object ["object", target.shape, target.value.to_a, (target.has_mask? ? target.mask.to_a : nil)] else ["portable", CArray.dump(target)] end end |
#marshal_load(data) ⇒ void
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 |
# File 'lib/carray/serialize.rb', line 535 def marshal_load (data) tag, *rest = data case tag when "object" shape, values, mask = rest ca = CArray.object(*shape) ca[] = values if mask ca.mask = 0 ca.mask[] = mask end initialize_copy(ca) when "portable" ca = CArray.load(StringIO.new(rest[0])) initialize_copy(ca) else raise TypeError, "unrecognised CArray Marshal payload" end end |
#mask_duplicates(axis: nil) ⇒ CArray
26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/carray/methods/mask_duplicates.rb', line 26 def mask_duplicates (axis: nil) dup = if axis.nil? # One seen-set over the flattened array, then restore shape. flatten.send(:__mask_duplicates__, 0).reshape(*shape) else # Per-fiber single-pass seen-set hash (C __mask_duplicates__): one lane # per dtype family (integer widen, float bitwise key with NaN collapse, # object rb_hash + rb_eql, fixlen byte-hash + memcmp, boolean via the # uint8 lane). O(distinct) memory, no sort/gather/scatter buffers. __mask_duplicates__(normalize_axis(axis, "mask_duplicates")) end mask_where(dup) end |
#meld(*others, axis: 0) ⇒ CAMeld
123 124 125 126 |
# File 'lib/carray/stack.rb', line 123 def meld (*others, axis: 0) raise ArgumentError, "meld: at least one other array required" if others.empty? CArray.meld(self, *others, axis: axis) end |
#mode(axis: nil) ⇒ CArray+
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/carray/methods/mode.rb', line 73 def mode (axis: nil) return __mode_flat if axis.nil? k = normalize_axis(axis, "mode") # Numeric: the C frequency-table kernel emits the ragged Array<CArray> # directly (reduced CArrays, self.shape with axis k dropped). A 1-D input # reduces to length-1 CArrays, unwrapped to scalars like flat quantile. unless data_type == CA_OBJECT || data_type == CA_FIXLEN cols = __mode_axis__(k) return ndim == 1 ? cols.map { |col| col[0] } : cols end # Object / fixlen (rare): per-fiber Ruby path, reusing the flat mode as the # single source of what counts as a mode. Move axis k to the innermost # position and fold the rest to one outer axis, so each row is a fiber. perm = (0...ndim).to_a perm.delete(k) perm << k a2 = (ndim == 1) ? self : transpose(*perm).copy # (outer..., L) outer = a2.shape[0...-1] m = outer.empty? ? 1 : outer.inject(:*) flat2 = a2.reshape(m, a2.shape[-1]) lists = Array.new(m) { |r| flat2[r, nil].__send__(:__mode_flat).to_a } # K = widest fiber's mode count. Emit K reduced CArrays (like quantile's # per-axis Array<CArray>): slot j holds each fiber's j-th smallest mode, # masked where a fiber has fewer than j+1 modes. Stack them to get the # rectangular mask-padded form: CArray.stack(result, axis: k). kk = lists.map(&:size).max || 0 (0...kk).map do |j| # Take the column shape from self rather than building it from data_type: # it carries the element width a fixlen array needs, and it keeps a Face # (a time array), whose cells then accept the surface values in `lists`. col = flat2[nil, 0].copy col[] = UNDEF m.times { |r| col[r] = lists[r][j] if j < lists[r].size } outer.empty? ? col[0] : col.reshape(*outer) end end |
#none(skip_masked: true, **opts) ⇒ Boolean, CArray
Whether no cell is true.
With skip_masked: true (the default) masked cells are simply ignored and
the result is always true / false. With skip_masked: false the fold
is three-valued: the result is UNDEF when a masked cell could change it,
matching the element-wise Kleene semantics of | / &.
70 71 72 73 74 75 |
# File 'lib/carray/boolean_reduce.rb', line 70 def none (skip_masked: true, **opts) return __none_skipna__(**opts) if skip_masked # none = not any (Kleene): not(true)=false, not(false)=true, not(UNDEF)=UNDEF r = __kleene_fold(:any, opts) r.is_a?(CArray) ? r.not : (r.equal?(UNDEF) ? UNDEF : !r) end |
#nunique(axis: nil, keep_axis: false) ⇒ Integer, CArray
32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/carray/methods/nunique.rb', line 32 def nunique (axis: nil, keep_axis: false) # Per-fiber single-pass seen-set hash (C __nunique__), one lane per dtype # family (numeric widen / NaN collapse, object rb_hash + rb_eql, fixlen # byte-hash + memcmp). Masked cells are skipped; the accumulator is a no-op # (the distinct count is the interned-key count). if axis.nil? # Whole-array distinct count: flatten to 1-D and reduce its only axis, # then read the single reduced cell as an Integer. flatten.send(:__nunique__, 0, false)[0] else __nunique__(normalize_axis(axis, "nunique"), keep_axis) end end |
#pack_bits ⇒ CArray
64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/carray/methods/bit_string.rb', line 64 def pack_bits unless data_type == CA_BOOLEAN || data_type == CA_UINT8 || data_type == CA_INT8 raise ArgumentError, "pack_bits: expected CA_BOOLEAN / CA_UINT8 / CA_INT8 (got #{data_type_name})" end raise ArgumentError, "pack_bits: 1-D CArray expected (got rank #{rank})" unless rank == 1 n = elements n_bytes = (n + 7) / 8 packed = CArray.uint8(n_bytes) { 0 } return packed if n == 0 packed.bitarray.reshape(-1)[0..n-1] = self packed end |
#paste(offset, src) ⇒ self
187 188 189 190 191 192 |
# File 'lib/carray/basics.rb', line 187 def paste (offset, src) raise ArgumentError, "offset length must equal ndim" if offset.length != ndim ranges = offset.each_with_index.map { |o, i| o...(o + src.shape[i]) } self.window(*ranges)[] = src self end |
#put_nd(indices, values) ⇒ self
92 93 94 95 96 |
# File 'lib/carray/methods/gather_nd.rb', line 92 def put_nd (indices, values) flat_addr, _outer, _rest = gather_nd_flat_addr(indices, "put_nd") self.flatten[flat_addr] = values self end |
#real ⇒ CArray
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/carray/complex.rb', line 61 def real if not @__real__ if complex? @__real__ = case data_type when CA_CMPLX64 field(0, CA_FLOAT32) when CA_CMPLX128 field(0, CA_FLOAT64) end else @__real__ = self[] end end @__real__ end |
#real=(val) ⇒ Object
81 82 83 |
# File 'lib/carray/complex.rb', line 81 def real= (val) real[] = val end |
#real? ⇒ Boolean?
125 126 127 128 129 130 131 132 133 |
# File 'lib/carray/complex.rb', line 125 def real? if complex? imag.eq(0).all elsif numeric? true else nil end end |
#replace_where(cond, b) ⇒ CArray
74 75 76 77 78 79 80 81 82 |
# File 'lib/carray/conditional.rb', line 74 def replace_where (cond, b) unless cond.is_a?(CArray) && cond.boolean? raise ArgumentError, "replace_where: cond must be a boolean CArray (data_type == CA_BOOLEAN)" end result = self.copy result[cond] = b.is_a?(CArray) ? b[cond] : b result end |
#resize(*newdim, fill_value: 0) ⇒ CArray
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/carray/methods/resize.rb', line 23 def resize (*newdim, fill_value: 0) raise "ndim mismatch" if newdim.size != ndim offset = Array.new(ndim, 0) newdim = newdim.each_with_index.map do |d, i| case d when nil shape[i] when Integer size = d.abs offset[i] = size - shape[i] if d < 0 size else raise "invalid dimension size" end end face_parent = self.face? ? self : nil src = self src = src.parent while src.face? dt = src.data_type bytes = (dt == :fixlen) ? src.bytes : nil out = CArray.new(dt, newdim, bytes: bytes) # Fill the new area: numeric storage takes fill_value as-is; fixlen # storage cannot hold a numeric 0, so leave zero bytes and honor only # UNDEF (mask) or an explicit String fill. if dt != :fixlen || fill_value.equal?(UNDEF) || fill_value.is_a?(String) out[] = fill_value end out.mask.paste(offset, src.false) if out.has_mask? out.paste(offset, src) out = out.face_lift(face_parent) if face_parent out end |
#save_arrow_tensor(filename) ⇒ self
Writes self to filename as an Arrow tensor IPC message.
Experimental, and the name is provisional. Rejects a masked or non-numeric array; see ArrowTensor for the type policy.
397 398 399 400 |
# File 'lib/carray/arrow_tensor.rb', line 397 def save_arrow_tensor (filename) File.open(filename, "wb") { |io| ArrowTensor.write(self, io) } self end |
#scale(xa, xb) ⇒ CArray
385 386 387 |
# File 'lib/carray/basics.rb', line 385 def scale (xa, xb) template.scale!(xa, xb) end |
#scale!(xa, xb) ⇒ self
373 374 375 376 377 |
# File 'lib/carray/basics.rb', line 373 def scale! (xa, xb) xa = xa.to_f xb = xb.to_f seq!(xa, (xb-xa)/(elements-1)) end |
#set_attr(key, value) ⇒ Object
59 60 61 62 |
# File 'lib/carray/attribute.rb', line 59 def set_attr (key, value) attr_validate_value(value) (@attr ||= {})[attr_normalize_key(key)] = attr_coerce_value(value) end |
#snap(step, offset: 0.0, direction: :round) ⇒ CArray
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/carray/methods/snap.rb', line 35 def snap(step, offset: 0.0, direction: :round) raise ArgumentError, "snap: step must be > 0" unless step > 0 unless [:round, :floor, :ceil].include?(direction) raise ArgumentError, "snap: direction must be :round / :floor / :ceil " \ "(got #{direction.inspect})" end scaled = (self - offset) / step # Detect NaN / Inf before rounding (which maps NaN -> 0.0 silently). invalid_mask = scaled.float? ? scaled.is_invalid : nil out = scaled.send(direction) * step + offset if invalid_mask && invalid_mask.count(true) > 0 out.mask = out.has_mask? ? (out.mask | invalid_mask) : invalid_mask end out end |
#snap_to(list, lfill: :clamp, ufill: :clamp, direction: :round) ⇒ CArray
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/carray/methods/snap.rb', line 97 def snap_to(list, lfill: :clamp, ufill: :clamp, direction: :round) ref = list.is_a?(CArray) ? list : CArray.wrap_readonly(list, self.data_type) raise ArgumentError, "snap_to: list must be 1-D" unless ref.ndim == 1 n = ref.elements raise ArgumentError, "snap_to: list must have at least one value" if n < 1 if n == 1 # Degenerate: every finite cell snaps to the only value. out = CArray.new(ref.data_type, shape).fill(ref[0]) out.mask = self.mask.to_ca if self.has_mask? if self.float? inv = self.is_invalid if inv.count(true) > 0 out.mask = out.has_mask? ? (out.mask | inv) : inv end end return out end # `locate_nearest_addr` (via `linear_section`) accepts a 1-D `val` only; # flatten multi-D input and reshape the result back to preserve the # element-wise semantic on any shape. if ndim > 1 return reshape(-1).snap_to(ref, lfill: lfill, ufill: ufill, direction: direction).reshape(*shape) end # locate_nearest_addr returns int64 indices; OOB (below / above / NaN) # cells come back masked. We split OOB into below / above with # explicit comparisons so the two sides can be filled independently. idx = self.locate_nearest_addr(ref, direction: direction) out = ref.project(idx) below = self.lt(ref[0]) above = self.gt(ref[-1]) case lfill when :clamp then out[below] = ref[0] when nil then # leave masked (locate_nearest_addr already masked OOB) else out[below] = lfill end case ufill when :clamp then out[above] = ref[-1] when nil then # leave masked else out[above] = ufill end # Propagate input mask (locate_nearest_addr / project do not forward # `self`'s mask on their own; a masked input cell must produce a # masked output cell regardless of the fill options above). if self.has_mask? m = self.mask.to_ca out.mask = out.has_mask? ? (out.mask | m) : m end out end |
#source_code ⇒ String
250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/carray/inspect.rb', line 250 def source_code text = [ desc, " { ", self.to_a.pretty_inspect.split("\n").map{|s| " " * (desc.length+3) + s }.join("\n").lstrip, " }" ].join return text end |
#span(range) ⇒ CArray
363 364 365 |
# File 'lib/carray/basics.rb', line 363 def span (range) return template.span!(range) end |
#span!(range) ⇒ self
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'lib/carray/basics.rb', line 339 def span! (range) unless float? raise ArgumentError, "span!: integer arrays are ambiguous — 'N evenly-spaced " \ "integers' has two distinct meanings. Pick the one you want:\n" \ " (A) N points hitting both endpoints exactly (linspace-like):\n" \ " CArray.int32(N).seq * (b - a) / (N - 1) + a\n" \ " or CArray.float64(N).span(a.to_f..b.to_f).int32\n" \ " (B) N labels distributed uniformly over range values:\n" \ " CArray.int32(N).seq * (b - a + 1) / N + a" end first = range.begin.to_r last = range.end.to_r step = range.exclude_end? ? (last-first)/elements : (last-first)/(elements-1) seq!(first, step) return self end |
#split(axis:) ⇒ Array<CArray>
Split self along a single axis into an Array of (ndim-1)-D slices, each a writable CABlock view. The exact inverse of CArray.stack -- split's slices are all the same shape, so they round-trip back through stack:
CArray.stack(a.split(axis: k), axis: k) == a
a = CA_INT([[1,2,3], [4,5,6]]) a.split(axis: 0) #=> <[1,2,3]>, <[4,5,6]> a.split(axis: 1) #=> <[1,4]>, <[2,5]>, <[3,6]>
3.0 breaking:
- returns a Ruby Array of views (was an object CArray), so it round-trips with CArray.stack (which takes an Array)
- +axis:+ takes a single Integer (the multi-axis Array form, which returned an N-D object grid, is no longer accepted)
- pieces are CABlock views, NOT copies; writing through a piece mutates +self+. Chain +.copy+ / +.to_ca+ for independent entities.
251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/carray/stack.rb', line 251 def split (axis:) if axis.is_a?(Array) raise ArgumentError, "split: axis must be a single Integer" end k = normalize_axis(axis, "split") (0...shape[k]).map do |i| idx = [nil] * ndim idx[k] = i self[*idx] end end |
#st ⇒ Struct
166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/carray/struct.rb', line 166 def st unless has_data_class? raise CAStruct::Error, "carray does not have a data_class" end unless @struct struct_class = Struct.new(nil, *data_class::MEMBERS) members = data_class::MEMBERS.map{|name| self[name]} @struct = struct_class.new(*members) end return @struct end |
#stack(*others, axis: 0, data_type: nil) ⇒ CArray
Instance-side stack: build a new K-stack from [self] + others
along the new K axis at position axis:. Always treats self as a
parent (= even when self is a CAStack, the resulting stack has self
as one of its parents, NOT flat-appended into self's parents).
For flat-appending into an existing CAStack (= same k_axis, parents
extended), use CAStack#append.
3.0: high-level Face-aware surface, mirrors CArray.stack(list, axis:).
221 222 223 224 |
# File 'lib/carray/stack.rb', line 221 def stack (*others, axis: 0, data_type: nil) raise ArgumentError, "stack: at least one other parent required" if others.empty? CArray.stack([self] + others, axis: axis, data_type: data_type) end |
#strip_mask(fill = MASK_FILL_UNSET, method: nil, axis: nil) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/carray/mask_gap_fill.rb', line 68 def strip_mask (fill = MASK_FILL_UNSET, method: nil, axis: nil) if method unless fill.equal?(MASK_FILL_UNSET) raise ArgumentError, "strip_mask: pass either a constant fill value or method:, not both" end return __gap_fill__(method, axis) end if fill.equal?(MASK_FILL_UNSET) raise ArgumentError, "strip_mask: a fill value is required (or method:)" end __strip_mask_const__(fill) end |
#then_else(x, y) ⇒ CArray
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/carray/conditional.rb', line 32 def then_else (x, y) # Guard: self must be boolean. Integer / float receiver would be # silently reinterpreted by the indexer setter (`result[self] = ...`) # as an index array, producing surprising scatter rather than the # intended ternary select. Fail fast. unless self.boolean? raise ArgumentError, "then_else: receiver must be a boolean CArray (data_type == CA_BOOLEAN), got #{self.data_type}" end # Promote data_type from both branches via CArray.result_type # (a CScalar contributes its own data_type, so CA_INT32(0) keeps int32 # where a bare Ruby Integer would widen to int64). dt = CArray.result_type(x, y) # A CScalar (scalar? CArray) is treated as a scalar value, not as a # self-shaped operand: full CArray -> gather/copy, scalar -> broadcast. y_full = y.is_a?(CArray) && !y.scalar? result = if y_full y.data_type == dt ? y.copy : y.to_type(dt) else CArray.new(dt, self.shape).fill(y.is_a?(CArray) ? y[0] : y) end x_full = x.is_a?(CArray) && !x.scalar? result[self] = x_full ? x[self] : x # Propagate cond's mask: UNDEF in self -> UNDEF in result. if self.has_mask? result[self.is_masked] = UNDEF end result end |
#time(unit: :ns, origin: nil) ⇒ CATime
1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 |
# File 'lib/carray/time.rb', line 1689 def time(unit: :ns, origin: nil) res = CATime::Resolution.parse(unit) src = _time_int64_storage if origin.nil? CATime.wrap(src, unit: res) else o = CArray._epoch_tick_index(origin, res) (src + o).time(unit: res) end end |
#timedelta(unit: :ns) ⇒ CATimedelta
1707 1708 1709 |
# File 'lib/carray/time.rb', line 1707 def timedelta(unit: :ns) CATimedelta.wrap(_time_int64_storage, unit: unit) end |
#to_bit_string(nb) ⇒ String
11 12 13 14 15 16 |
# File 'lib/carray/methods/bit_string.rb', line 11 def to_bit_string (nb) hex = CArray.uint8(((nb*elements)/8.0).ceil) hex.bits[nil].paste([0], self.bits[false,[(nb-1)..0]].flatten) hex.bits[] = hex.bits[nil,[-1..0]] return hex.to_s end |
#true ⇒ CArray
168 169 170 |
# File 'lib/carray/basics.rb', line 168 def true () return template(:boolean) { 1 } end |
#union(other, sort: false) ⇒ CArray
90 91 92 93 94 |
# File 'lib/carray/methods/is_in.rb', line 90 def union (other, sort: false) a, b = promote_value_set(other) r = a.__send__(:__union__, b) sort ? r.sort : r end |
#unique(sort: false) ⇒ CArray
37 38 39 40 41 42 43 44 45 |
# File 'lib/carray/methods/unique.rb', line 37 def unique (sort: false) # Single-pass seen-set hash (C __unique_flat__), one lane per dtype family: # integer widens to a 64-bit key; float uses the bitwise key with all-NaN # collapsed and -0.0 / +0.0 normalized; object keys on rb_hash + rb_eql and # fixlen on a byte-hash + memcmp, both reproducing Ruby Hash distinctness. # Masked cells are skipped in the kernel. levels = __unique_flat__ sort ? levels.sort : levels end |
#unmask(fill = MASK_FILL_UNSET, method: nil, axis: nil) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/carray/mask_gap_fill.rb', line 43 def unmask (fill = MASK_FILL_UNSET, method: nil, axis: nil) if method unless fill.equal?(MASK_FILL_UNSET) raise ArgumentError, "unmask: pass either a constant fill value or method:, not both" end held = __gap_fill__(method, axis) # Copy the filled values in place. A Face writes through its storage: a # bulk store into its surface would try to cast the storage values to the # surface type (int64 ticks to fixlen, for a time array). if face? parent.value[] = held.parent.value else value[] = held.value end if held.has_mask? self.mask = held.mask # residual leading/trailing mask else __unmask_const__ # fully filled: drop the mask end return self end fill.equal?(MASK_FILL_UNSET) ? __unmask_const__ : __unmask_const__(fill) end |
#validity_bits ⇒ CArray?
87 88 89 90 |
# File 'lib/carray/methods/bit_string.rb', line 87 def validity_bits return nil unless has_mask? is_not_masked.reshape(-1).pack_bits end |
#value_counts(sort: false) ⇒ Array(CArray, CArray)
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/carray/methods/value_counts.rb', line 37 def value_counts (sort: false) unless [false, :count, :value].include?(sort) raise ArgumentError, "value_counts: sort must be false, :count, or :value" end # Single-pass frequency-table hash (C __value_counts_flat__), one lane per # dtype family: integer widens to a 64-bit key; float uses the bitwise key # with all NaN collapsed and -0.0 / +0.0 normalized; object keys on rb_hash + # rb_eql and fixlen on a byte-hash + memcmp, both reproducing Ruby Hash # distinctness. Masked cells are skipped in the kernel. values, counts = __value_counts_flat__ case sort when :count # Descending count, ties broken by first-appearance index (stable). c = counts.to_a order = (0...c.size).sort_by { |i| [-c[i], i] } [ values[CArray.int64(order.size) { |i| order[i] }], counts[CArray.int64(order.size) { |i| order[i] }] ] when :value # Ascending value; NaN (numeric) or non-comparable last. Build the # permutation with an explicit NaN-last key so float NaN doesn't blow up # the Ruby sort, then gather both arrays through it. v = values.to_a order = (0...v.size).sort_by do |i| x = v[i] nan = x.is_a?(Float) && x.nan? [nan ? 1 : 0, nan ? 0 : x, i] end [ values[CArray.int64(order.size) { |i| order[i] }], counts[CArray.int64(order.size) { |i| order[i] }] ] else [values, counts] end end |
#windows(*ranges, bounds: :skip, fill_value: nil) ⇒ CAWindowIterator
649 650 651 652 653 654 |
# File 'lib/carray/window_iterator.rb', line 649 def windows (*ranges, bounds: :skip, fill_value: nil) if ranges.size == 1 && ranges[0].is_a?(CArray) && ranges[0].obj_type == CA_OBJ_WINDOW return CAWindowIterator.new(ranges[0]) end CAWindowIterator.new(self, *ranges, bounds: bounds, fill_value: fill_value) end |