Ragged Arrays
RecursiveArrayTools provides two approaches for working with ragged (non-rectangular) arrays, i.e., collections of arrays where the inner arrays have different sizes.
Zero-Padded Ragged Arrays (VectorOfArray)
A VectorOfArray accepts inner arrays of different sizes. When this happens, the array presents a rectangular view where size(A) reports the maximum size in each dimension and out-of-bounds elements are treated as zero:
using RecursiveArrayTools
A = VectorOfArray([[1, 2], [3, 4, 5]])
size(A) # (3, 2) — max inner length is 3
A[3, 1] # 0 — implicit zero (inner array 1 has only 2 elements)
A[3, 2] # 5 — actual stored value
Array(A) # [1 3; 2 4; 0 5] — zero-padded dense arrayBecause VectorOfArray subtypes AbstractArray, this zero-padded representation integrates directly with linear algebra operations, broadcasting, and the rest of the Julia array ecosystem.
end Indexing
end indexing on the ragged dimension resolves to the maximum size, consistent with the rectangular interpretation:
A = VectorOfArray([[1, 2], [3, 4, 5]])
A[end, 1] # 0 — row 3 of column 1, which is zero-padded
A[end, 2] # 5 — row 3 of column 2, which existsSetting Values
You can set values within the stored bounds of each inner array. Attempting to set a non-zero value outside the stored bounds of an inner array will throw an error:
A = VectorOfArray([[1, 2], [3, 4, 5]])
A[1, 1] = 10 # works — within bounds
A[3, 1] = 0 # works — setting to zero is fine (it's already implicitly zero)
# A[3, 1] = 1 # error — cannot store non-zero outside ragged boundsTrue Ragged Arrays (RaggedVectorOfArray)
For use cases where zero-padding is undesirable and you want to preserve the true ragged structure, the RecursiveArrayToolsRaggedArrays subpackage provides RaggedVectorOfArray and RaggedDiffEqArray.
using RecursiveArrayToolsRaggedArraysRaggedVectorOfArray does not subtype AbstractArray. This is by design: a true ragged structure has no well-defined rectangular size, so the AbstractArray interface does not apply. Indexing returns actual stored data without zero-padding.
Construction
using RecursiveArrayToolsRaggedArrays
# From a vector of arrays with different sizes
A = RaggedVectorOfArray([[1, 2, 3], [4, 5, 6, 7], [8, 9]])Indexing
Indexing follows a column-major convention where the last index selects the inner array and preceding indices select elements within it:
A = RaggedVectorOfArray([[1, 2, 3], [4, 5, 6, 7], [8, 9]])
A.u[1] # [1, 2, 3] — first inner array
A.u[2] # [4, 5, 6, 7] — second inner array
A[:, 1] # [1, 2, 3] — equivalent to A.u[1]
A[2, 2] # 5 — second element of second array
A[:, 2] # [4, 5, 6, 7] — full second inner arrayend Indexing with RaggedEnd
One of the key features of RaggedVectorOfArray is type-stable end indexing on ragged dimensions. When indexing a ragged dimension, end returns a RaggedEnd object that is resolved per-column at access time:
A = RaggedVectorOfArray([[1, 2, 3], [4, 5, 6, 7], [8, 9]])
A[end, 1] # 3 — last element of first array (length 3)
A[end, 2] # 7 — last element of second array (length 4)
A[end, 3] # 9 — last element of third array (length 2)
A[end - 1, 2] # 6 — second-to-last element of second arrayRange indexing with end also works:
A[1:end, 1] # [1, 2, 3] — all elements of first array
A[1:end, 2] # [4, 5, 6, 7] — all elements of second array
A[end-1:end, 2] # [6, 7] — last two elements of second arrayThe RaggedEnd and RaggedRange types broadcast as scalars, so they integrate correctly with SymbolicIndexingInterface and other broadcasting contexts.
Conversion to Dense Arrays
RaggedVectorOfArray can be converted to a standard dense Array when all inner arrays have the same size:
A = RaggedVectorOfArray([[1, 2, 3], [4, 5, 6]])
Array(A) # [1 4; 2 5; 3 6]
Matrix(A) # [1 4; 2 5; 3 6]Multi-Dimensional Inner Arrays
RaggedVectorOfArray supports inner arrays of any dimension, not just vectors:
A = RaggedVectorOfArray([rand(2, 3), rand(2, 4)]) # 2D inner arrays, ragged in second dim
A[1, 2, 1] # element (1,2) of first inner arraypush! and Growing Ragged
An initially rectangular RaggedVectorOfArray can become ragged by pushing arrays of different sizes:
A = RaggedVectorOfArray([[1, 2], [3, 4]])
push!(A, [5, 6, 7]) # now ragged — third array has 3 elementsRaggedDiffEqArray
RaggedDiffEqArray extends RaggedVectorOfArray with time, parameter, and symbolic system information, mirroring the relationship between DiffEqArray and VectorOfArray:
using RecursiveArrayToolsRaggedArrays
t = 0.0:0.1:1.0
vals = [[sin(ti), cos(ti)] for ti in t]
A = RaggedDiffEqArray(vals, collect(t))
A.t # time vector
A.u # vector of solution arrays
A[1, :] # first component across all timesRaggedDiffEqArray is useful for differential equation solutions where the state dimension can change over time (e.g., particle systems with birth/death, adaptive mesh methods).
Choosing Between the Two Approaches
| Feature | VectorOfArray (zero-padded) | RaggedVectorOfArray (true ragged) |
|---|---|---|
Subtypes AbstractArray | Yes | No |
| Linear algebra support | Yes (zero-padded) | No |
| Broadcasting with plain arrays | Yes | No |
| Preserves true ragged structure | No (pads with zeros) | Yes |
end on ragged dimension | Resolves to max size | Resolves per-column (RaggedEnd) |
| Package | RecursiveArrayTools | RecursiveArrayToolsRaggedArrays |
Use VectorOfArray when you need standard AbstractArray interop and are fine with zero-padding. Use RaggedVectorOfArray when you need to preserve the exact ragged structure and access elements without implicit zeros.
API Reference
The abstract types below are developer interfaces for packages implementing ragged containers. Application code should construct RaggedVectorOfArray or RaggedDiffEqArray.
A concrete AbstractRaggedVectorOfArray must expose an indexable u field and preserve the true shape of every inner array. It must support the ragged indexing contract, including A[:, j] == A.u[j], and may use the generic conversion, copying, filling, iteration, and broadcasting methods supplied by the subpackage. It must not claim the rectangular AbstractArray interface or add implicit zero padding. A concrete AbstractRaggedDiffEqArray follows the same rules and additionally keeps t, p, and sys metadata aligned with u.
RecursiveArrayTools.AbstractRaggedVectorOfArray — Type
AbstractRaggedVectorOfArray{T, N, A}Abstract supertype for ragged (non-rectangular) vector-of-array types that preserve the true ragged structure without zero-padding. Unlike AbstractVectorOfArray, this does not subtype AbstractArray — indexing returns actual stored data and A[:, i] gives the i-th inner array with its original size.
Developer Interface
Concrete subtypes must expose an indexable u collection containing the stored inner arrays. They preserve each inner array's true shape and should implement the ragged indexing, iteration, copying, and broadcasting behavior documented by RecursiveArrayToolsRaggedArrays. They must not introduce zero-padding or claim the rectangular AbstractArray interface.
This is a developer API for packages implementing ragged array containers. Concrete implementations belong in RecursiveArrayToolsRaggedArrays to avoid method invalidations on the root package's hot path. Application code should construct RaggedVectorOfArray rather than subtype this interface.
RecursiveArrayTools.AbstractRaggedDiffEqArray — Type
AbstractRaggedDiffEqArray{T, N, A} <: AbstractRaggedVectorOfArray{T, N, A}Abstract supertype for ragged diff-eq arrays that carry a time vector t, parameters p, and symbolic system sys alongside ragged solution data.
Developer Interface
Subtypes must satisfy the AbstractRaggedVectorOfArray contract and expose t, p, and sys metadata compatible with DiffEqArray. They may also expose discretes, interp, and dense fields for discrete parameters and interpolation. The metadata must remain aligned with the entries of u whenever the container is mutated. Application code should use the generic symbolic-indexing methods on the subtype rather than inspecting these fields directly. Application code should construct RaggedDiffEqArray instead of subtyping this interface.
RecursiveArrayToolsRaggedArrays.RaggedVectorOfArray — Type
RaggedVectorOfArray(u::AbstractVector)Wrap a collection of arrays while preserving each array's shape. Unlike VectorOfArray, this type does not present zero-padded ragged data as a rectangular AbstractArray.
Arguments
u: an indexable collection of inner arrays. Each inner array keeps its own shape.
Returns
A mutable RaggedVectorOfArray implementing the ragged indexing and broadcasting interface. It does not subtype AbstractArray because its columns may have different sizes.
Fields
u: the collection of stored arrays.
Examples
A = RaggedVectorOfArray([[1, 2], [3, 4, 5]])
A[:, 1] == [1, 2]
A[end, 2] == 5RecursiveArrayToolsRaggedArrays.RaggedDiffEqArray — Type
RaggedDiffEqArray(u::AbstractVector, t::AbstractVector; kwargs...)Wrap ragged saved states u and matching time points t with differential-equation and symbolic-indexing metadata.
Arguments
u: an indexable collection of saved ragged state arrays.t: the time values corresponding to the entries ofu.
Keyword Arguments
discretes: discrete parameter timeseries, ornothing.variables: variable symbols for symbolic-indexing metadata.parameters: parameter symbols for symbolic-indexing metadata.independent_variables: independent-variable symbols for symbolic-indexing metadata.interp: an interpolation object, ornothing.dense: whether dense interpolation is available.
Returns
A mutable RaggedDiffEqArray with time, parameter, and symbolic-indexing metadata.
Fields
u: the saved ragged state arrays.t: the time corresponding to each entry ofu.p: parameter values associated with the solution.sys: symbolic indexing metadata.discretes: discrete parameter timeseries, ornothing.interp: interpolation object for dense output, ornothing.dense: whether dense interpolation is available.
Examples
t = [0.0, 1.0]
u = [[1.0, 2.0], [3.0, 4.0, 5.0]]
A = RaggedDiffEqArray(u, t)
A[:, 2] == [3.0, 4.0, 5.0]