Skip to content

Commit 8b6a168

Browse files
Merge pull request #562 from ChrisRackauckas-Claude/add-ragged-array-docs
Add ragged arrays documentation and README section
2 parents 0d6265a + 1e84dc2 commit 8b6a168

File tree

4 files changed

+282
-0
lines changed

4 files changed

+282
-0
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,26 @@ d = VA[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
4040
c .* d
4141
```
4242

43+
### Ragged Arrays
44+
45+
```julia
46+
using RecursiveArrayTools
47+
48+
# VectorOfArray with ragged data uses zero-padded rectangular interpretation
49+
ragged = VectorOfArray([[1, 2], [3, 4, 5]])
50+
size(ragged) # (3, 2) — max inner length is 3
51+
ragged[3, 1] # 0 — implicit zero
52+
Array(ragged) # [1 3; 2 4; 0 5]
53+
54+
# For true ragged structure without zero-padding:
55+
using RecursiveArrayToolsRaggedArrays
56+
57+
A = RaggedVectorOfArray([[1, 2, 3], [4, 5, 6, 7], [8, 9]])
58+
A[end, 1] # 3 — last of first array (length 3)
59+
A[end, 2] # 7 — last of second array (length 4)
60+
A[end, 3] # 9 — last of third array (length 2)
61+
```
62+
4363
### ArrayPartition
4464

4565
```julia

docs/pages.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pages = [
55
"breaking_changes_v4.md",
66
"AbstractVectorOfArrayInterface.md",
77
"array_types.md",
8+
"ragged_arrays.md",
89
"plotting.md",
910
"recursive_array_functions.md",
1011
]

docs/src/ragged_arrays.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Ragged Arrays
2+
3+
RecursiveArrayTools provides two approaches for working with ragged (non-rectangular)
4+
arrays, i.e., collections of arrays where the inner arrays have different sizes.
5+
6+
## Zero-Padded Ragged Arrays (`VectorOfArray`)
7+
8+
A `VectorOfArray` accepts inner arrays of different sizes. When this happens, the
9+
array presents a rectangular view where `size(A)` reports the **maximum** size in
10+
each dimension and out-of-bounds elements are treated as zero:
11+
12+
```julia
13+
using RecursiveArrayTools
14+
15+
A = VectorOfArray([[1, 2], [3, 4, 5]])
16+
size(A) # (3, 2) — max inner length is 3
17+
A[3, 1] # 0 — implicit zero (inner array 1 has only 2 elements)
18+
A[3, 2] # 5 — actual stored value
19+
Array(A) # [1 3; 2 4; 0 5] — zero-padded dense array
20+
```
21+
22+
Because `VectorOfArray` subtypes `AbstractArray`, this zero-padded representation
23+
integrates directly with linear algebra operations, broadcasting, and the rest of
24+
the Julia array ecosystem.
25+
26+
### `end` Indexing
27+
28+
`end` indexing on the ragged dimension resolves to the maximum size, consistent
29+
with the rectangular interpretation:
30+
31+
```julia
32+
A = VectorOfArray([[1, 2], [3, 4, 5]])
33+
A[end, 1] # 0 — row 3 of column 1, which is zero-padded
34+
A[end, 2] # 5 — row 3 of column 2, which exists
35+
```
36+
37+
### Setting Values
38+
39+
You can set values within the stored bounds of each inner array. Attempting to set a
40+
non-zero value outside the stored bounds of an inner array will throw an error:
41+
42+
```julia
43+
A = VectorOfArray([[1, 2], [3, 4, 5]])
44+
A[1, 1] = 10 # works — within bounds
45+
A[3, 1] = 0 # works — setting to zero is fine (it's already implicitly zero)
46+
# A[3, 1] = 1 # error — cannot store non-zero outside ragged bounds
47+
```
48+
49+
## True Ragged Arrays (`RaggedVectorOfArray`)
50+
51+
For use cases where zero-padding is undesirable and you want to preserve the true
52+
ragged structure, the `RecursiveArrayToolsRaggedArrays` subpackage provides
53+
`RaggedVectorOfArray` and `RaggedDiffEqArray`.
54+
55+
```julia
56+
using RecursiveArrayToolsRaggedArrays
57+
```
58+
59+
!!! note
60+
`RaggedVectorOfArray` does **not** subtype `AbstractArray`. This is by design:
61+
a true ragged structure has no well-defined rectangular `size`, so the
62+
`AbstractArray` interface does not apply. Indexing returns actual stored data
63+
without zero-padding.
64+
65+
### Construction
66+
67+
```julia
68+
using RecursiveArrayToolsRaggedArrays
69+
70+
# From a vector of arrays with different sizes
71+
A = RaggedVectorOfArray([[1, 2, 3], [4, 5, 6, 7], [8, 9]])
72+
```
73+
74+
### Indexing
75+
76+
Indexing follows a column-major convention where the last index selects the inner
77+
array and preceding indices select elements within it:
78+
79+
```julia
80+
A = RaggedVectorOfArray([[1, 2, 3], [4, 5, 6, 7], [8, 9]])
81+
82+
A.u[1] # [1, 2, 3] — first inner array
83+
A.u[2] # [4, 5, 6, 7] — second inner array
84+
A[:, 1] # [1, 2, 3] — equivalent to A.u[1]
85+
A[2, 2] # 5 — second element of second array
86+
A[:, 2] # [4, 5, 6, 7] — full second inner array
87+
```
88+
89+
### `end` Indexing with `RaggedEnd`
90+
91+
One of the key features of `RaggedVectorOfArray` is type-stable `end` indexing on
92+
ragged dimensions. When indexing a ragged dimension, `end` returns a `RaggedEnd`
93+
object that is resolved per-column at access time:
94+
95+
```julia
96+
A = RaggedVectorOfArray([[1, 2, 3], [4, 5, 6, 7], [8, 9]])
97+
98+
A[end, 1] # 3 — last element of first array (length 3)
99+
A[end, 2] # 7 — last element of second array (length 4)
100+
A[end, 3] # 9 — last element of third array (length 2)
101+
A[end - 1, 2] # 6 — second-to-last element of second array
102+
```
103+
104+
Range indexing with `end` also works:
105+
106+
```julia
107+
A[1:end, 1] # [1, 2, 3] — all elements of first array
108+
A[1:end, 2] # [4, 5, 6, 7] — all elements of second array
109+
A[end-1:end, 2] # [6, 7] — last two elements of second array
110+
```
111+
112+
The `RaggedEnd` and `RaggedRange` types broadcast as scalars, so they integrate
113+
correctly with SymbolicIndexingInterface and other broadcasting contexts.
114+
115+
### Conversion to Dense Arrays
116+
117+
`RaggedVectorOfArray` can be converted to a standard dense `Array` when all inner
118+
arrays have the same size:
119+
120+
```julia
121+
A = RaggedVectorOfArray([[1, 2, 3], [4, 5, 6]])
122+
Array(A) # [1 4; 2 5; 3 6]
123+
Matrix(A) # [1 4; 2 5; 3 6]
124+
```
125+
126+
### Multi-Dimensional Inner Arrays
127+
128+
`RaggedVectorOfArray` supports inner arrays of any dimension, not just vectors:
129+
130+
```julia
131+
A = RaggedVectorOfArray([rand(2, 3), rand(2, 4)]) # 2D inner arrays, ragged in second dim
132+
A[1, 2, 1] # element (1,2) of first inner array
133+
```
134+
135+
### `push!` and Growing Ragged
136+
137+
An initially rectangular `RaggedVectorOfArray` can become ragged by pushing arrays
138+
of different sizes:
139+
140+
```julia
141+
A = RaggedVectorOfArray([[1, 2], [3, 4]])
142+
push!(A, [5, 6, 7]) # now ragged — third array has 3 elements
143+
```
144+
145+
## `RaggedDiffEqArray`
146+
147+
`RaggedDiffEqArray` extends `RaggedVectorOfArray` with time, parameter, and symbolic
148+
system information, mirroring the relationship between `DiffEqArray` and
149+
`VectorOfArray`:
150+
151+
```julia
152+
using RecursiveArrayToolsRaggedArrays
153+
154+
t = 0.0:0.1:1.0
155+
vals = [[sin(ti), cos(ti)] for ti in t]
156+
A = RaggedDiffEqArray(vals, collect(t))
157+
158+
A.t # time vector
159+
A.u # vector of solution arrays
160+
A[1, :] # first component across all times
161+
```
162+
163+
`RaggedDiffEqArray` is useful for differential equation solutions where the state
164+
dimension can change over time (e.g., particle systems with birth/death, adaptive
165+
mesh methods).
166+
167+
## Choosing Between the Two Approaches
168+
169+
| Feature | `VectorOfArray` (zero-padded) | `RaggedVectorOfArray` (true ragged) |
170+
|---------|-------------------------------|-------------------------------------|
171+
| Subtypes `AbstractArray` | Yes | No |
172+
| Linear algebra support | Yes (zero-padded) | No |
173+
| Broadcasting with plain arrays | Yes | No |
174+
| Preserves true ragged structure | No (pads with zeros) | Yes |
175+
| `end` on ragged dimension | Resolves to max size | Resolves per-column (`RaggedEnd`) |
176+
| Package | `RecursiveArrayTools` | `RecursiveArrayToolsRaggedArrays` |
177+
178+
Use `VectorOfArray` when you need standard `AbstractArray` interop and are fine with
179+
zero-padding. Use `RaggedVectorOfArray` when you need to preserve the exact ragged
180+
structure and access elements without implicit zeros.
181+
182+
## API Reference
183+
184+
```@docs
185+
RecursiveArrayTools.AbstractRaggedVectorOfArray
186+
RecursiveArrayTools.AbstractRaggedDiffEqArray
187+
```
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# RecursiveArrayToolsRaggedArrays.jl
2+
3+
True ragged (non-rectangular) array types for
4+
[RecursiveArrayTools.jl](https://github.com/SciML/RecursiveArrayTools.jl).
5+
6+
This subpackage provides `RaggedVectorOfArray` and `RaggedDiffEqArray`, which
7+
preserve the exact ragged structure of collections of differently-sized arrays
8+
without zero-padding. Unlike the main package's `VectorOfArray` (which presents
9+
ragged data as a zero-padded rectangular `AbstractArray`), these types do **not**
10+
subtype `AbstractArray` — indexing returns only actual stored data.
11+
12+
It is separated from the main package because the ragged indexing methods
13+
(including `end` support via `RaggedEnd`/`RaggedRange`) would cause method
14+
invalidations on the hot path for rectangular `VectorOfArray` operations.
15+
16+
## Installation
17+
18+
```julia
19+
using Pkg
20+
Pkg.add(url = "https://github.com/SciML/RecursiveArrayTools.jl",
21+
subdir = "lib/RecursiveArrayToolsRaggedArrays")
22+
```
23+
24+
## Usage
25+
26+
```julia
27+
using RecursiveArrayToolsRaggedArrays
28+
29+
# Inner arrays can have different sizes
30+
A = RaggedVectorOfArray([[1, 2, 3], [4, 5, 6, 7], [8, 9]])
31+
32+
A.u[1] # [1, 2, 3] — first inner array
33+
A.u[2] # [4, 5, 6, 7] — second inner array
34+
A[:, 2] # [4, 5, 6, 7] — same as A.u[2]
35+
A[2, 2] # 5 — second element of second array
36+
37+
# end resolves per-column via RaggedEnd
38+
A[end, 1] # 3 — last of first array (length 3)
39+
A[end, 2] # 7 — last of second array (length 4)
40+
A[end, 3] # 9 — last of third array (length 2)
41+
A[end-1:end, 2] # [6, 7] — last two elements of second array
42+
43+
# Convert to dense (when inner arrays have the same size)
44+
B = RaggedVectorOfArray([[1, 2], [3, 4]])
45+
Array(B) # [1 3; 2 4]
46+
```
47+
48+
### `RaggedDiffEqArray`
49+
50+
`RaggedDiffEqArray` adds time, parameter, and symbolic system fields for
51+
differential equation solutions where the state dimension varies over time:
52+
53+
```julia
54+
t = 0.0:0.1:1.0
55+
vals = [[sin(ti), cos(ti)] for ti in t]
56+
A = RaggedDiffEqArray(vals, collect(t))
57+
58+
A.t # time vector
59+
A.u # vector of solution arrays
60+
A[1, :] # first component across all times
61+
```
62+
63+
## Comparison with `VectorOfArray`
64+
65+
| | `VectorOfArray` (main package) | `RaggedVectorOfArray` (this package) |
66+
|-|-------------------------------|--------------------------------------|
67+
| Subtypes `AbstractArray` | Yes | No |
68+
| Ragged handling | Zero-padded rectangular view | True ragged structure |
69+
| `end` on ragged dim | Resolves to max size | Resolves per-column (`RaggedEnd`) |
70+
| Linear algebra | Yes | No |
71+
| Broadcasting with plain arrays | Yes | No |
72+
73+
Use `VectorOfArray` when you need `AbstractArray` interop. Use
74+
`RaggedVectorOfArray` when you need exact ragged structure without implicit zeros.

0 commit comments

Comments
 (0)