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 array

Because 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 exists

Setting 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 bounds

True 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 RecursiveArrayToolsRaggedArrays
Note

RaggedVectorOfArray 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 array

end 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 array

Range 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 array

The 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 array

push! 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 elements

RaggedDiffEqArray

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 times

RaggedDiffEqArray 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

FeatureVectorOfArray (zero-padded)RaggedVectorOfArray (true ragged)
Subtypes AbstractArrayYesNo
Linear algebra supportYes (zero-padded)No
Broadcasting with plain arraysYesNo
Preserves true ragged structureNo (pads with zeros)Yes
end on ragged dimensionResolves to max sizeResolves per-column (RaggedEnd)
PackageRecursiveArrayToolsRecursiveArrayToolsRaggedArrays

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.

source
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.

source
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] == 5
source
RecursiveArrayToolsRaggedArrays.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 of u.

Keyword Arguments

  • discretes: discrete parameter timeseries, or nothing.
  • 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, or nothing.
  • 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 of u.
  • p: parameter values associated with the solution.
  • sys: symbolic indexing metadata.
  • discretes: discrete parameter timeseries, or nothing.
  • interp: interpolation object for dense output, or nothing.
  • 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]
source