Changelog
Releases from 3.0.0 onward are recorded here. There is no separate NEWS file: this is where to look for what changed between the version you have and a newer one. The 1.x history, up to the 2.0.0 release, is in CHANGELOG.v1.md; 2.0.1 went unrecorded.
3.0.2
-
New:
CArray::AddressBasis, for a C extension whose code addresses cells itself rather than being handed them — a kernel generated from an expression, which writes its own loop.openlends a pointer and one byte stride per axis for the length of a block, and closes what it opened even when the block raises;classifyreports how an array would be opened without opening it. It is a runtime facility at theca_attachlayer, not a user API: what it lends is a raw machine address, so it is described in the developer's guide rather than in the user documentation. -
New: a
CAStringcolumn can be searched, not only sorted:bsearch,bsearch_addr,searchandcount(v)answer where they used to raiseArgumentError. A cell of one is the Ruby String it shows, so a String query compares against it directly, with nothing to reconcile. Sorting, which already worked, is unchanged, and so isCAConstString, which answerssearch/count(v)natively and still has nobsearch. -
New:
count(v)counts an object or fixlen array, which used to raiseCArray::DataTypeError. An object array compares by Ruby==, socount(1)andcount(1.0)agree, andtrue/false/nilare values to count rather than the boolean array'strue/false. A fixlen array compares the whole cell bymemcmp, with a short String query padded out to the cell width -- so a 4-byte cell holding"a\0\0\0"is counted bycount("a"). -
New:
CArray.empty(data_type, dim, bytes: nil)allocates without the zero fill, for an array whose every cell is written before anything reads it. One existing call changes:CArray.empty(3, [4])raisedTypeErrorin 3.0.1 and now matchesCArray.new(3, [4]). -
New:
CAFrame.from_csvreads an open IO as well as a path, so CSV already in memory need not go through a temporary file first. A String argument is still always a path, never CSV text. -
New:
inspect_fullrenders an array the wayinspectdoes but without the...abbreviation. -
New:
repeatlays each element of an array down several times --v.repeat(2), orv.repeat([3, 1, 2])for a count each. It is nottile, which lays the whole array down again. The result is a view. -
New:
unique,nuniqueandmask_duplicatestakealong: k, comparing whole sub-arrays instead of cells --z.unique(along: 0)gives the distinct rows of a 2-D array. Giving bothalong:andaxis:raises. -
New: each numeric data type names its own limits on its class --
CArray::Int32::MIN/MAX, andTINY/EPSILONfor float and complex types.MINis the bottom of the range, where Ruby'sFloat::MINis what is calledTINYhere. -
New:
CArray::Rngis a random number generator with its own state, whichrandom!,randomn!andshuffle!accept asrng:alongside a RubyRandom. Withoutrng:, or with a RubyRandom, nothing changes. -
New:
CArray#factorizeanswers[codes, levels]in one pass, for a caller who wants the codes as storage rather than theCACategoricalthatcategorizebuilds from the same two. -
New: C extensions only.
CA_FOR_EACH_FIBER_PAIRandCA_FOR_EACH_FIBER_PAIR_MASKEDyield one contiguous fiber from each of two sources at the same position. -
Change:
CArray.meld(andCAMeld.new, and soCAFrame.meld) now treats a homogeneous list of Faces the wayCArray.stackdoes: a Face whose state is per-parent is refused, and one that can be carried is kept on the result.CAConstStringandCAStringnow raiseArgumentError— weld the storage instead, with.parent, or useCArray.concatenate.CAFixlenString,CATimeandCATimedeltacome back as themselves rather than as the raw storage; melding pieces whose Face state differs, such as twoCATimecolumns in different units, now raises rather than welding the ticks. Lists of plain arrays, and a list of one, are unaffected. -
Change:
CAConstString.wrapnow checks the(start, end)pairs it is given against the buffer, and takes ownership of the offsets entity by marking it read-only. A pair outside the buffer raisesArgumentErrornaming the element; masked cells are exempt, since their bytes may be anything. Code that built a column with well-formed offsets is unaffected, except that the entity it passed can no longer be written afterwards — pass.copyto keep a mutable one. This also means the storage behind an existing column (column.parent[i] = ...) now raises rather than silently rewriting a column that reports itself read-only. -
Change:
allandanyon anaxis_groupreduction now require a boolean payload, asCArray#all/#anyand the other iterators do. They folded any numeric payload, counting a non-zero cell as true, sodata.allrefused anddata[g].all(axis: :group)answered for the same float array. Convert first if you meant the old reading:data.ne(0)[g].all(axis: :group). -
Change: the
axis:reductions ongroup_by_categorynow read the source array when asked, rather than some of them answering from a result kept from an earlier call.sum,mean,min,maxand the counts shared a kept result per axis whileprod, the variance family andwsum/wmeandid not, so after a write through the source one iterator could report a mean and a variance that no data can produce together. Reading several members off one iterator now costs one kernel run each instead of one shared run; keep the result if you want the old sharing. The no-axis reductions are unchanged: they still work from the copy taken when the iterator was built. -
Change:
CAFrame#at(UNDEF)now raisesArgumentErrorinstead of returning a row. An index can hold a masked cell -- an:outer/:rightjoin andalignboth produce one -- but a row with no label cannot be identified by one, and two undefined labels are not the same label; the key matching behindjoinandalignalready treats a masked key as matching nothing. Usedf.filter { |f| f.index.is_masked }for the rows with no label, which also handles more than one of them. Asking for a real label whose cell is masked still raisesKeyError, unchanged. -
Change: functions built on the C-extension bridge (
ca_call_cfunc_*,ca_call_cslab_*, andCAMath.spherical_to_xyz/xyz_to_spherical) pair two array operands only when their shapes agree, and otherwise raiseArgumentErrornaming both shapes. Arrays of the same size but different shape, such as (2,3) and (3,2), used to be accepted and read in flat order; reshape one of them first. Arrays of different sizes raisedRuntimeErrorbefore, so arescueof that class needs updating. A scalar still pairs with any array. -
Change:
min,max,minmax,cumminandcummaxanswerNaN, andmin_index,max_index,min_addrandmax_addranswerUNDEF, when every cell a float array contributes isNaN. They used to answerInfinity,-Infinity, the interval[Infinity, -Infinity]and position0. ANaNstill loses to any number, so an array holding at least one number answers as before, as does one holding only real infinities. Empty and all-masked still answerUNDEF, and integer, boolean, fixlen and object arrays are unchanged -- an object array already answeredNaN. To haveNaNcounted as missing rather than skipped, callmask_invalidfirst;min_count:andfill_value:act on masked cells and do not reachNaNones. -
Change: the
CAConstStringordering family takesaxis:-- andkind:/masked_position:/keep_axis:where CArray does -- acrossmin,max,minmax,min_index,max_index,sort,sort_copy,sort_addr,sort_index,rank_index,order,partition_copyandpartition_index. Three answers move to CArray's:sort_indexgives per-fiber indices where it gave view-flat addresses (asksort_addrfor those);sortwith noaxis:flattens first, where it kept the shape (a 1-D column is unaffected); andmin/maxon an empty or wholly masked column give UNDEF, not nil. -
Change:
CABlock#countandCAWindow#countare gone. They gave back the per-axis number of cells the view exposes -- which is whatshapeanswers -- and in doing so hidCArray#counton the two classes an indexing expression lands on most:a[2...8].count(true)raisedArgumentError, anda[2...8].countgave a shape rather than a population. Read the geometry withshape.size0/start/step/offset, which say where the view sits in its parent, are unchanged. -
Change:
CArray.jit_for,CArray.jit_eachandCArray.jit_mapare no longer defined here; they arrive withrequire "carray/jit". Without it a call raisesNoMethodErrorwhere 3.0.1 raisedNotImplementedError, so code that rescued that to fall back asksCArray.respond_to?(:jit_each). -
Change: the Ruby attach surface is gone from released builds:
CArray.attach/.attach!,CArray#attach/#attach!, and#__attach__/#__sync__/#__detach__. Write through the array directly instead.CArray#attached?and the C lifecycle are unchanged. -
Change:
a[1, :_]returns a view of the axes:_asked for instead of raisingIndexError. To keep an axis rather than drop it, index it with something that is not a scalar --a[[1], :_]. -
Change: C extensions only. A kernel iterator init the engine refuses now raises instead of returning a code the block macros discarded. To handle a refusal rather than propagate it, call
ca_iter_state_init_l1/_l2directly and read the code. -
Change:
CACategorical.from_codesnow materialisescodeswhen it is a view rather than an array of its own, so writing through the array the view was taken from no longer changes the categorical underneath it. A wrapped memory view is an array of its own and is still adopted without a copy, so a zero-copy import stays zero-copy. The array you pass is never marked read-only beyond what you handed over. -
Change:
CACategorical.from_codesnow checks what it is handed and normalises it. It raisesArgumentErrorfor duplicate labels, for more labels than the codes data type can carry once its top value is reserved as the exclusion sentinel, and for an unmasked code outside0...labels.sizethat is not the sentinel. A cell that arrives masked also gets the sentinel written into its code byte, so the mask and the byte now agree for every reader, a byte-reinterpret export included. Codes built bycategorizealready satisfy all of this, so nothing changes for a categorical made that way. -
Change:
search_nearestandsearch_nearest_addrwork on an object array of numbers, and say why when they cannot. They measured only with#distance, and sinceNumeric#distancebecame an opt-in refinement -- which a C-level call does not see -- that raisedNoMethodErrorfor an Integer as readily as for a String. A number is now measured as(query - cell).abs, exactly for Rational and BigDecimal; an object defining a real#distancestill uses it; anything else raisesCArray::DataTypeErrornaming the query's class, and points atsearch/bsearchfor an exact match. Numeric arrays are unaffected. -
Change:
CAFrame.from_csvreads a missing field as UNDEF in every column, not only in one named bytypes:. An unquoted empty field, and a cell a short row never reached, used to arrive as a Rubynilsitting in an uncast column, so a mask written byto_csvdid not survive the trip back. A quoted empty field ("") is still the empty string, which is a value. Code that worked around this withcol[:eq, nil] = UNDEFcan drop the line. -
Change:
CArray.timereads a string array about eight times faster with an explicitformat:, and about three times faster letting it auto-detect -- soCAFrame#parse_to_time, which calls it, speeds up by the same amount. Parsed values are unchanged. -
Change:
windowacceptsbounds:as a Symbol as well as a String, which is the spellingwindowsalready took. Strings keep working. -
Change: C extensions only. A partial fill of an array backed by a CAObject or CASource subclass takes the
fill_block/fill_addrsslots where the subclass defines them, instead of onestore_addrper cell. Which cells are written is unchanged, and a subclass defining no fill slot keeps the per-cell path. -
Fix:
is_in,count(v), the set operations,locate_addr,search,bsearchandlinear_sectionno longer compare a Face operand by its storage when that storage is not the value it shows. Passing aCAConstString(whose cells are byte ranges) to one of these on another Face used to answer from the byte ranges: where the two cell widths coincided — aCAConstStringcell is 16 bytes, and so is aCAFixlenStringcell whose column is 16 bytes wide — you got a wrong answer with no error, and a set operation could return raw offset bytes as its values. Such an operand now raisesArgumentError; convert it first, with#to_stringfor a string Face, or pass.parenton both sides to work in storage space. Plain operands, and Faces whose cells are their values (CAString,CAFixlenString), are unaffected, as is the cross-unit reconciliationCATimedoes throughto_comparable. -
Fix: the reductions without
axis:ongroup_by_categorynow agree with one another about which values they are reducing.cumsumand the other scans read the array when called while every other member worked from the copy taken when the iterator was built, so a write through the source between two calls was visible to one and not the other. All of them now answer about the values as they were when the iterator was built; build a new iterator to pick up a write. Theaxis:reductions read the array when called, unchanged. -
Fix:
CArray.load_from_fileno longer exhausts the stack. It was registered for autoload but defined nowhere, so calling it recursed until Ruby gave up; it now raisesNoMethodErrorlike any other method that does not exist. UseCArray.load, which is unchanged. An autoload registration whose library defines no such method now says which method and which library, rather than recursing. -
Fix: a
group_by_categoryreduction over a read-only Face — aCAConstStringcolumn — no longer fails with anIndexErrorabout a buffer range. Such a Face cannot be built by writing into it, somin,maxand the other value members hand back the surface values (the strings) rather than the Face. A writable Face such asCATimestill answers in its Face. -
Fix: a group iterator from
axis_groupnow answersshape,ndimanddim, which every other iterator answers and which it returnednilfor, and itscounttakes the two forms the family declares:count(UNDEF)for masked cells andcount(v)for cells equal tov. Both previously raisedArgumentErrorabout the number of arguments. -
Fix: a
group_by_categoryreduction over values that carry a Face (aCATimecolumn, say) now answers in that Face, asCArray's own reduction does:min,maxandmediancome back as aCATimeof elements rather than failing with an internal message about a zero width. A member the core does not define for that Face still refuses, in the core's own words. -
Fix: the band-only classifier shape for a
group_by_category(axis:)reduction is now reachable on a two-dimensional source, where it was refused and the refusal listed it among the accepted forms. Where both the case A shape and the band-only shape fit, which a square source allows, case A is taken, as before. The refusal no longer namessumwhen another reduction was the one called. -
Fix:
count(axis:)andcount_not_masked(axis:)ongroup_by_categorynow work for a complex, boolean or object payload. They counted cells through a numeric-only kernel, which refused those payloads for an answer that never depended on the payload. The no-axis form already worked. -
Fix: a
group_by_categoryiterator whose classifier does not line up cell-for-cell with the value now says so. A no-axis reduction on one raisesArgumentErrornaming the mismatch and pointing at theaxis:form, rather than aNoMethodErroraboutnil;elementsraises the same instead of answeringnil; andinspectsays "per-fiber only" instead of printing an empty grouping.accumulate(axis:), which failed outright on such an iterator, now works. -
Fix: a reduction from
group_by_categorynow hands back an array of the caller's own.min,max,minmax,count,count_not_masked,elements,min_indexandmax_indexreturned the iterator's memo itself, so writing into a result changed what that iterator answered from then on, and changed it for the other members reading the same memo.sumalready copied. Nothing to change in calling code unless you relied on writing through a result. -
Fix: an
axis_groupreduction or scan that raises part-way through no longer leaks the working memory it had taken. An object-valued scan (cumsum,cummaxand the rest) calls back into Ruby for every cell, so a value that will not coerce or a<=>that answersnilraises from an ordinary call and used to leave roughly 36 bytes per source element behind each time. Nothing to change in calling code. -
Fix:
CAFrame#set_indexon a frame that already has an index no longer discards it. The index being replaced now goes back to being a column, the same demotionreset_indexperforms and in the same position, so re-indexing keeps every column andset_index("b")on a frame indexed by"a"is the same asreset_indexfollowed byset_index("b"). Previously the column the old index had been made from was gone, with nothing said. -
Fix:
CAFrame#reset_indexnow restores the row axis name the frame had beforeset_indexpromoted a column over it, so the two are each other's inverse as documented. It used to leave"row", which is user-visible: the row axis name is the header of the index's column into_csvand its key in a rowHash. A frame built with an index, or derived from one, never had an earlier name, soreset_indexstill leaves the default there. -
Fix:
minandmaxon anaxis_groupreduction now answer in the source array's data type, asCArray#min/#maxdo, instead of float64 -- an int64 beyond the float mantissa came back rounded. A boolean array answers as its 0/1 numeric storage (all/anyare the boolean-returning twins). A group holding nothing butNaNnow answersNaNrather than the accumulator's infinity, andmin_addr/max_addranswer UNDEF for it, since no cell won. -
Fix: an
axis_groupreduction over a grouping whose group axis has length zero now answers each output cell the way a group with no member is answered --sum0,prod1,count0,alltrue,anyfalse, and UNDEF formean,min,max,min_addr,max_addrand the variance family. It previously returned an unmasked 0 for all of them, so a mean and a variance both read as 0.0. A zero-length band axis, which reduces to no cells at all, is unchanged. -
Fix: a per-category
min,max,min_index,max_index,median,percentileorquantilefromgroup_by_categorynow treatsNaNthe wayCArray's own reduction does: aNaNloses every contest, a category holding nothing butNaNanswersNaNfor an extremum and UNDEF for a position, and an order statistic sortsNaNlast. Before, the answer depended on where in the category theNaNsat, so the same values in a different row order gave different results. Nothing to change in calling code. -
Fix:
p/inspecton aCAFramewhose only data is its index now shows the table. It printed the summary line alone, because it gated on the column set while the table itself counts the index as a column. -
Fix:
CAFrameno longer reports a row count that nothing in the frame backs. Splicing a frame that has no columns into another that has neither columns nor an index left the target claiming the spliced frame's row count, while its owncopy,headandfilterall answered 0 and it would then accept only columns of that length. The count is now read off a column, or off the index when there are no columns. Nothing to change in calling code. -
Fix:
CAFrame'sdf[rows] = UNDEFnow refuses a row outside the frame on a frame with no columns, as the read and delete forms already did. It used to return quietly, because the bound check came from the column indexer the selector was handed to and there was no column to hand it to. Masking a row that does exist on such a frame is still a no-op -- there are no data cells, and the index is left alone by design. -
Fix:
CAFrame'sto_table(and sop/puts/to_s) now prints a masked element inside an N-D cell as_, the marker it already used for a masked scalar cell, instead of the literalUNDEF. Nothing to change in calling code. -
Fix:
CAFrame#group_bywith a composite key no longer makes a group of its own for rows whose key has a masked component. Such a row now forms no group, which is what a single masked key cell already did. Nothing to change in calling code unless you relied on the UNDEF-labelled group. -
Fix: two
CAFrameverbs that change every column now decide before changing any, so a column that refuses no longer leaves the frame half-changed in an order that depends on how the columns were inserted.df[sel] = UNDEFon a frame holding a read-only column (a categorical) raises without masking anything, andpromote(type)raises without casting anything when some column would narrow. Nothing to change in calling code. -
Fix:
CAFrame.from_recordsnow reads anilcell back as UNDEF in every column, not only in one a numeric cast happens to convert. A string, boolean, object or N-D column used to keep thenilas a value, so a mask written byto_recordsdid not survive the trip and a row with no index label came back labellednil. Note that the data type is still rebuilt from the values, so an integer column returns asint64and a boolean column as an object column;castafterwards if the exact type matters. -
Fix: a CSV written from a frame with a single column -- or with only an index -- now reads back with all of its rows. A masked cell is written as an empty field, which for a one-column row is a line with nothing on it, and the reader skipped it as a blank line. Blank lines in a file with more than one column are still skipped, as a row there always carries a separator. Nothing to change in calling code.
-
Fix: linear gap-fill on an integer array no longer fills the cells outside the interpolable span with
0and drops their mask. It now leaves them masked, as it already did for a float array and as the documentation says. This coversunmask(method: :linear)andstrip_mask(method: :linear)as well asCAFrame#fill(name, :linear), with or without a frame index. Nothing to change in calling code. -
Fix: on a frame grouped by a numeric column,
CAFrame'smean,sum,minandmaxshortcuts now work. They raisedaxis_name "..." collides with a column of the same name, because the key column was reduced into the result while also being its index; a key only stayed out of the way when its data type was one a reduction skips anyway -- a string, boolean, categorical or time column. A composite numeric key no longer returns its key columns as reduced columns either, so it gives the same column set a composite string key gives.aggregateandtablewere never affected. Nothing to change in calling code. -
Fix:
CAFrame#filter(keep_masked: true)no longer hands back a frame whose index writes through to the original. Its columns were already independent, so writing the result's index changed the original while writing its columns did not. The result is now materialized throughout -- columns and index -- whether or not the selector actually carries a masked cell, so the same call site no longer switches between sharing and copying depending on the data. Code that wants a frame sharing storage with the original should use plainfilter, which is still a view-frame. -
Fix: reductions, scans and order statistics no longer leak memory when reading their source raises part way through -- for example
cumsum,sumormedianover a float64 view of an object array holding a cell that is not a number. Each call used to leave the slab the walk was gathering into behind. Nothing to change in calling code. -
Fix:
a[sel]no longer leaks memory when reading the boolean selector raises -- for examplefake(CA_BOOLEAN)over an int32 array holding a 2. Nothing to change in calling code. -
Fix: a view that converts on read -- for example
fake(CA_BOOLEAN)over an int32 array holding a 2 -- now raises every time it is read, where the second read used to succeed silently and return values from a half-converted buffer. A view stacked or melded over such an array no longer leaves its other parents attached when the read raises, and a reshape of a lazy view no longer leaks its buffer. Nothing to change in calling code. -
Fix: for C extensions, a callback passed to
ca_call_cfunc_*orca_call_cslab_*may nowrb_raiseto refuse a value: the bridge detaches and frees what it holds before the exception propagates, where it used to leave an output view attached and its scratch memory behind. The outputs are left partly written. Nothing to change in calling code. -
Fix: when
to_typeon a view raises part way through the cast -- for example an int32 value other than 0 or 1 cast to boolean -- the view is no longer left holding a stale copy of its parent, which made later reads through it return the old values. Nothing to change in calling code. -
Fix:
copyandstrip_mask(fill)no longer leak the result's memory when reading the source raises part way through -- for example a float64 view of an object array holding a cell that is not a number. Nothing to change in calling code. -
Fix: functions built on the C-extension bridge (
ca_call_cfunc_*,ca_call_cslab_*, theCA_FOR_EACH_ELEMENTmacros, andCAMath.lgammaand its siblings) no longer leak memory, or leave an output view attached, when reading an operand raises part way through -- for example a float64 view of an object array holding a cell that is not a number. Nothing to change in calling code. -
Fix:
is_in,intersection,differenceanduniontake an Array or Range of Strings against a fixlen array, where every such call raisedCArray::DataTypeError--CAFixlenStringincluded. The set is built at the array's cell width, so a short String matches a padded cell the way it does everywhere else. A set given as a CArray must still be of that width; when it is not, the refusal now says which width was wanted instead of reporting a data type mismatch between two fixlen arrays. -
Fix: comparing a fixlen array against a String compares it as a value of that array's cell width. The String became an object operand, so
eq/ne/lt/gt/ge/leand the[:eq, v]indexer ranString#==per cell against the cell's NUL-padded text -- and an array pads a short String on write, soa[i] = "be"thena.eq("be")was false,a.gt("be")was true, and the scan took 30x longer than the same one insearch. Two fixlen arrays of different widths compare as before, as does a Regexp formatch.CAFixlenStringwas never affected. -
Fix:
percentileandmedianno longer interpolate between objects that have no arithmetic. On a column of Stringspercentile(30)quietly answered""and even-lengthmedianraisedNoMethodErrorfrom inside a funcall; both now raiseCArray::DataTypeErrornamingmethod: :lower/:higher/:nearest, which pick an element and work. Apthat lands exactly on an element (percentile(50)of five) still answers, as does an odd-lengthmedian. Numbers stored as objects -- Integer, Rational, BigDecimal -- are unaffected. -
Fix:
sort_copytakes whateversorttakes. It refused everything its own fast path could not handle, so an object or boolean array sorted throughsortand raisedCArray::DataTypeErrorthroughsort_copy; complex, which neither can order, refused differently depending on which one was asked, and now refuses alike. Numeric arrays keep the fast path and are unchanged. -
Fix:
CAConstString#sort_addr,#sort_index,#rank_index,#order,#min_index,#max_index,#partition_copyand#partition_indexread the strings. They read the(start, end)offsets that hold them, which order by how the column was packed, so they gave well-formed wrong answers rather than raising:sort_addron an unsorted column gave the identity, andpartition_copygave NUL bytes.#minmaxanswers instead of raising, and sorting a column with masked cells no longer raises. -
Fix:
to_const_stringgives an N-D source back with its shape instead of flattened, andCAConstString#unique/#mode/#mask_duplicates/#intersection/#difference/#unionkeep the column's encoding -- on a column that was not UTF-8 they raised out of the builder's check. -
Fix:
each_with_indexandmap_with_index!no longer raiseSystemStackErroron a long array, and neither doCArray#format/CArray.format, which are built on them. The ceiling was the C stack, so where it fell depended on where the code ran: around a million cells on the main thread, under a hundred thousand inside aThread. -
Fix:
CArray.concatenateandCArray.mosaictake a zero-length piece -- an empty slice such asa[0...0], orCArray.int32(0)-- instead of raisingIndexError. The piece contributes nothing and the remaining ones concatenate as before.CArray#pastelikewise accepts a source covering no cell, and writes nothing. -
Fix: C extensions only. A kernel writing into a view the caller supplied now reaches the array; writes were lost, or crashed, for several view kinds iterated along an axis whose fiber is not contiguous. Kernels writing into an array they allocated themselves were never affected.
-
Fix:
CArray#each_slabyields a read-only slab, and writing through it raises rather than reaching the array on one axis and being dropped on another. Return values from the block instead, or assign through the array.
3.0.1
-
New:
CArray.jit_for,CArray.jit_eachandCArray.jit_mapname a block that is compiled rather than run. Compiling needs the carray-jit gem; without it they raiseNotImplementedError. An expression over whole arrays wantsCArray.fuse, which needs no compiler. -
New: every iterator answers
accumulatebesidesum; some of them did not. It is the same fold kept in the source's own data type, wheresumanswers in the type the core promotes to -- a window or tile count overuint8cells stays one byte wide. -
New:
CArray::CoreExtensionsadds postfix math onComplex, soa[0].tanhreads the waya.tanhdoes. It covers the seventeen functions a complex array supports and agrees with the array form exactly, branch cuts and the sign of a zero included. Opt in withusing CArray::CoreExtensions. -
New:
ca_is_stride_family(ca)incarray.h, for a C extension that folds a view intoroot->ptr + base + sum(idx[k] * strides[k])itself. True for CAStride, CARefer, CABlock, CARepeat, CATranspose, CAFarray and CAField and the mask array of each, and for an externally installed view that shares their operation table. -
New:
divmodreturns[quotient, remainder]element-wise with the quotient floored, the pair Ruby'sInteger#divmodandFloat#divmodreturn. The quotient keeps the receiver's data type. -
New: two optional CAObject callbacks take a partial fill as a region instead of one
store_addrper cell:fill_block(starts, counts, steps, val)for a forward per-axis sub-region,fill_addrs(addrs, val)otherwise. Defining neither keeps the old behaviour. Filling a 1000x1000 region of a 2000x2000 CAObject: 116 ms to 0.6 ms. -
New:
CAFrame#to_tablerenders the frame as an aligned text table, andinspect/to_ssit on it:p dfsummarises the first 8 and last 2 rows,puts dfprints the whole frame.rows:caps the printed rows,precision:rounds float cells for display (default 6), masked cells show as_. -
New:
CAFrame#to_timetakes aCATime::Grid, positionally or asunit:, so a netCDFunitsattribute goes in whole. The grid also carries an epoch phase theunit:/epoch:pair cannot -- that pair reads the epoch on the coarse grid and loses the time of day. The keyword form is unchanged. -
New:
CATime::Gridpackages the(unit:, origin:)pair that#timesteps,#snapand.from_timestepstake, so it is built once and passed as one value:t.snap(g, direction: :floor),t.timesteps(g),g.at(k). It parses and prints the udunits"<unit> since <instant>"form, holding a phased origin that the keyword pair cannot ("12 hours since 2017-11-30 09:00").CATime#gridis the grid an array is stored on. Storage is unchanged. -
New:
CATime#snap(grid, direction:)rounds to a tick grid, the shapeCArray#snaphas for numbers.#floor/#ceil/#roundare its fixed-direction forms; their results and keyword forms are unchanged. -
New: a time element is taken directly as a start or origin literal, so a
floor/ceil/to_unitanswer feeds back intoCArray.time,time_range,time_series,CArray#timeand anorigin:; it used to have to go out throughDateTimeand be re-parsed. A:Mor:Yelement names the first midnight of its granule. What an origin must satisfy is unchanged. -
New:
CArray.timeparses a year-month ("2019-09") and a bare year ("2019"), so the form a:M/:Yelement prints reads back in. A missing finer field names the head of that period. -
Change:
abs,abs2andargon acmplx64array now returnfloat32instead offloat64-- the width that type carries its real values in, the same onerealandimagalready returned.argon afloat32array likewise returnsfloat32rather than widening.cmplx128andfloat64are unchanged, andargon an integer array still givesfloat64. To keep the old width, add.to_type(:float64). -
Change:
CArray.fusetakes the expression rather than the arrays it is over --CArray.fuse { (a + b) * c }in place ofCArray.fuse(a, b, c) { |x, y, z| (x + y) * z }. What comes back is the expression, sox = CArray.fuse { ... }now wants.to_cafor an array. Where the block's source cannot be read -- anirbprompt, insideeval-- writea.lazy + b.lazy.CArray.lazy(*args) { ... }is gone. -
Change: a lazy expression (
a.lazy + b,CArray.fuse) builds its mask when something reads it, rather than when the expression is built. A mask set on either operand after the expression was built is now seen; before, only the left one was.root_arrayandancestorsnow stop at a lazy operation instead of walking into its left operand. Building a long masked expression is also no longer quadratic in the length of the chain. -
Change: the top-level constant
CA_NILis nowCArray::UNSPECIFIED(CA_UNSPECIFIEDin C). It is an internal sentinel for "the caller gave no argument", not a value to pass in;unmask,shift(fill_value:)andwindow(fill_value:)behave as before. -
Change:
group_by_categoryreductions answer in the data type the core reduction promotes the value to, instead of one chosen per reduction.sumon an integer value now answers in float64 (accumulateis the spelling that stays in the value's type), andmean,varianceandmedianon an object value stay exact.sumon a boolean value andprodormeanon a complex one, which raised, now work. -
Change: the
:*unbound repeat is retired.a[:*, nil]raisesIndexError;CArray#unbound_repeat,CAUnboundRepeatandinsert_axis(repeat: :*)are gone. Use:_, which gives the same shape and now stretches on a store as well as in an operation.CArray#broadcast_toandCArray.meshgrid(sparse: true)are unaffected. -
Change: a binary operation requires shapes to agree, or to differ only in size-1 axes at equal ndim.
(3,2) + (2,3)and(3,2) + (6)raiseArgumentErrorwhere they used to answer in one operand's shape; flatten both sides to combine the values in the order they lie. Comparisons,fmaand the lazy forms follow the same rule. Scalars are unaffected, but a one-element 1-D array such asCArray.int32(1)counts as a shape. -
Change: an assignment requires the shapes to match.
t[] = srcwith a differently shaped source raisesRuntimeError; usesrc.flatten. In exchange a smaller source is repeated to fit, sot[] = row[:_, nil, nil]andt[] = col(shape(n,1)) work where they used to raise. A 1-D side on either end still passes, as do shapes differing only in size-1 axes; Ruby Arrays and scalars are unchanged. -
Change:
to_caon a view derived from a lazy marker returns a new entity rather than the view itself --a.lazy.shift(1, 0).to_ca, inside afuseblock or not.copybehaves as before. -
Change:
/and%follow Ruby instead of C. Integer division floors and the remainder carries the sign of the divisor; float%floors too, float/is unchanged. For the old behaviour usefmod, which now takes integers as well as floats. -
Change:
reminderis removed -- it was IEEE 754 remainder on floats but C%on integers. Usefmodfor the truncated remainder. The IEEE form has no replacement. -
Change: the result-type override of
CArray#conditionalandCArray.selectis nowdata_type:, spelled the way the rest of the library spells it. There is no alias:dtype:raisesunknown keyword. -
Change: a calendar-grid
origin:given to the bucket-grid methods of aCATimearray --timesteps,snap,floor,ceil,round,is_righttime-- now has to be a month head (the 1st at 00:00); it used to drop the day and time silently. A:Ytick likewise has to start in January.from_timestepsalready refused an off-grid origin. -
Change: the MemoryView producer emits the format vocabulary
ruby/memory_view.hspecifies rather than PEP 3118's, so below 32 bits it writesc/C/s/Sin place ofb/B/h/H. From 32 bits up the two already agreed, and?,Zf/Zd,T{...}andNsstay PEP 3118, which Ruby has no spelling for. The consumer side already accepted both, so views produced by 3.0.0 still import. -
Change: multiplying and dividing a
cmplx64array (*,/,rcp,rcp_mul) is computed in double and rounded once, so both are correctly rounded; the old route could be off by about 1800 units in the last place. A product that overflows a float but not a double no longer comes back NaN. Division is 4.2-5.3x faster and multiplication 1.3x slower.+,-and everycmplx128operation are unchanged. -
Change: element-wise math on a
float32orcmplx64array is computed at that width rather than widened and rounded back, sosqrt,exp,log, the trigonometric and hyperbolic families,atan2,hypot,absandargare 1.1-3.5x faster there and can move by a bit or two in the last place. Complexlog,power,exp2andexp10are unchanged, as are the rounding, min / max, comparison and variance families and the wider types. -
Change: a rolling
sum,mean,prod,min,max,allorany--a.windows(-1..1).sumand the like -- over a window up to five cells wide on every axis is 2-5x faster, and 1.3-4x over a masked source;min_count:andfill_value:come along.min,maxandprodanswer exactly as before;sumandmeanmay differ in the last bits. A wider window, or any other reduction, is unchanged. -
Change:
CATime#to_unitfloors to a coarser grid instead of raising, and crosses the calendar / fixed-length boundary (:M<->:D) through civil-date algebra.:Y/:M->:Wstill raises. -
Change:
CATimedelta#to_unittruncates toward zero instead of raising on a coarser target. Crossing the calendar boundary still raises. -
Fix:
nextafteron afloat32array returned its own input. The step was taken in double, and the next double above a float rounds back to that same float. It now steps by one float32 ulp. -
Fix: a lazy expression over an object array (
CArray.object,CA_OBJECT) returned wrong values, and crashed when materialised repeatedly. Affectsto_caandcopyon a lazy view, an eager operation with a lazy operand, and a reduction over one. Other data types were never affected. -
Fix: reading a concatenated view (
CArray.concat,CAFrame.concat) backwards along the concatenated axis --m.reverseand any other negative step -- raised IndexError instead of answering. -
Fix: reading a lazy expression (
a.lazy + b,CArray.fuse) with a step other than one --expr[[0, 3, 2]],expr.reverseand the like -- returned values the expression cannot produce, because its operands were read from the wrong cells. Comparisons carried the same fault,a.lazy > band one-operand ones such asa.lazy.is_nanalike. Contiguous reads were never affected. -
Fix:
a.value + braisedcan not create mask array for the value arraywhenbcarried a mask. It now propagatesb's mask. -
Fix: a rolling window that does not cover the cell it is centred on --
windows(1..2), the two cells after this one -- was read as if it started at the array's edge, so every answer came out shifted by the range's start, andbounds: :truncateproduced anchors whose window did not fit.windows(1..2)on[1..8]now sums to[5, 7, 9, 11, 13, 15, 8, 0]. A window that covers its anchor, which is every centred one, is unaffected. -
Fix: storing a
Complexinto acmplx64orcmplx128array kept the sign of a negative zero real part only when the imaginary part was also negative, soComplex(-0.0, 0.0)came back as0.0+0.0i. The sign of a zero picks the side of a branch cut, so a value stored this way could be carried to the wrong branch. All four sign combinations now round-trip, through element assignment and throughto_type. -
Fix:
sinh,cosh,tanh,asinh,acoshandatanhon a complex array gave the hyperbolic function of the real part alone:cmplx128andcmplx64arrays came back withtanh(Re z)wherectanh(z)was meant, wrong in both parts, andacoshandatanhalso returned 0 or infinity where the true value is finite. They now agree with C99complex.h. Real and object arrays were never affected. -
Fix: a C extension reading a view in column-major order -- axes and steps reversed against the view's own -- got wrong values from a view with a length-1 axis, such as
v[nil, :_],v.reshape(n, 1)or a one-column slice: the first cell repeated, a read out of bounds, or a hang.carray-linalg-accelerate'ssolve(a, b)returnedb[0]repeated for a single-column right-hand side. -
Fix: an operation between two views of an array that computes its values -- a lazy expression such as
(a.lazy + b), or aCAObjectover a file -- no longer reads that source one cell at a time.+,fmaand comparisons on such a pair now run at the speed of copying each operand first, so the.copythat worked around it is no longer needed. Single-operand operations, copies, region transfers and reductions were already unaffected. -
Fix: views built inside a
CArray.fuseblock stay part of the expression instead of dropping out of it, so a stencil written the natural way is fused.[],shift,roll,flip/reverse,transpose/T,reshape,flatten,window,diagonal,tileandreferkeep the chain;unbound_repeatand[:*, ...]deliberately do not. -
Fix: a window or shift over a view parent no longer copies that parent in full on every transfer.
a[nil, nil].shift(1, 0)and friends now read at parity with an entity parent and write several times faster. -
Fix: reductions over a bare
.lazymarker no longer raise. Per-axis forms and anything over a masked array failed, soa.lazy.sum(axis: 0)raised whilea.lazy.sumworked. -
Fix:
ca_test_flag/ca_set_flag/ca_unset_flagincarray.h, which only a C extension calls, did not parenthesise their flag argument, so testing two flags at once was true for every array. -
Fix: a Face no longer hands back its storage bytes through the type casts.
as_type,fakeandCArray.wrap_writableraise;CArray.wrap_readonlyconverts asto_typedoes, which also makest.eq(o)ando.eq(t)agree. Reach the storage explicitly witht.parent.fake(...). Numeric Faces are unaffected. -
Fix:
arangeraisedNoMethodErrorin every form; it builds the array now. Integer arguments count exactly, so a step dividing the span evenly no longer picks up an extra element. A zero step or a wrong argument count raisesArgumentError. -
Fix:
from_timestepson a week grid answered Thursdays. The week grid counts from the epoch, which is one, so it cannot hold an ISO Monday head; a week bucket now answers on the day grid and round-trips againstfloorcell for cell. A day-or-finer array keeps the Monday default, a week-stored array its own epoch-anchored ticks. -
Fix: a masked cell decided whether a
CATimeconversion fit. The range guards took their extremes with the mask stripped, soto_unitraisedRangeErrorover a wide value that was masked out. They now answer UNDEF when there is nothing to bound -- an empty array, or every cell masked. -
Fix:
CATimedelta::Element#/floored a negative duration, so-30h / 4answered-8hwhere the array form answered-7h. A duration is a magnitude, so it shrinks toward zero, matching the array form in all four sign combinations. -
Fix:
CArray.timeno longer rolls a field that is out of range over into another date."2019-02-31"parsed to 2019-03-03, and"201909"-- a valid YYMMDD to Ruby, 2020-19-09 -- to 2021-07; both now raise. -
Fix:
CATime#to_unitandCATimedelta#to_unitwere wrong between two resolutions where neither tick is a whole multiple of the other: converting 3 hours to a"90 minutes"grid gave 1 unit rather than 2. -
Fix: importing a MemoryView whose mask is published as
Corcno longer fails. The check knew only PEP 3118'sB,band?, so a producer that spells a byte in Ruby's format vocabulary was refused.
3.0.0
First public release. Earlier versions existed on RubyGems, but the library was developed for the author's own use; 3.0 is where it is packaged, documented and tested as something other people can pick up. It is not source-compatible with 2.0.1.
See README.md for what the library does, and docs/ and guides/ for the reference and the guides.
Requires Ruby 3.0 or later (3.1 for the MemoryView-backed paths).