Skip to content

Commit fc9c29a

Browse files
Merge pull request SciML#547 from ChrisRackauckas-Claude/abstractarray-breaking-update
BREAKING: Make AbstractVectorOfArray <: AbstractArray
2 parents a217961 + f0dfd27 commit fc9c29a

37 files changed

Lines changed: 2857 additions & 1147 deletions

Project.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "RecursiveArrayTools"
22
uuid = "731186ca-8d62-57ce-b412-fbd966d074cd"
3+
version = "4.0.0"
34
authors = ["Chris Rackauckas <accounts@chrisrackauckas.com>"]
4-
version = "3.51.0"
55

66
[deps]
77
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
@@ -15,6 +15,7 @@ StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
1515
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
1616

1717
[weakdeps]
18+
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
1819
FastBroadcast = "7034ab61-46d4-4ed7-9d0f-46aef9175898"
1920
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
2021
KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
@@ -29,6 +30,7 @@ Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c"
2930
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
3031

3132
[extensions]
33+
RecursiveArrayToolsCUDAExt = "CUDA"
3234
RecursiveArrayToolsFastBroadcastExt = "FastBroadcast"
3335
RecursiveArrayToolsForwardDiffExt = "ForwardDiff"
3436
RecursiveArrayToolsKernelAbstractionsExt = "KernelAbstractions"
@@ -46,6 +48,7 @@ RecursiveArrayToolsZygoteExt = "Zygote"
4648
Adapt = "4"
4749
Aqua = "0.8"
4850
ArrayInterface = "7.16"
51+
CUDA = "5"
4952
DocStringExtensions = "0.9.3"
5053
FastBroadcast = "1.1"
5154
ForwardDiff = "0.10.38, 1"
@@ -61,7 +64,6 @@ Random = "1"
6164
RecipesBase = "1.3.4"
6265
ReverseDiff = "1.15"
6366
SafeTestsets = "0.1"
64-
SciMLBase = "2.103"
6567
SparseArrays = "1.10"
6668
StaticArrays = "1.6"
6769
StaticArraysCore = "1.4.2"
@@ -86,7 +88,6 @@ NLsolve = "2774e3e8-f4cf-5e23-947b-6d7e65073b56"
8688
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
8789
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
8890
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
89-
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
9091
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
9192
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
9293
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
@@ -97,4 +98,4 @@ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
9798
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
9899

99100
[targets]
100-
test = ["Aqua", "FastBroadcast", "ForwardDiff", "KernelAbstractions", "Measurements", "NLsolve", "Pkg", "Random", "SafeTestsets", "SciMLBase", "SparseArrays", "StaticArrays", "Statistics", "StructArrays", "Tables", "Test", "Unitful", "Zygote"]
101+
test = ["Aqua", "FastBroadcast", "ForwardDiff", "KernelAbstractions", "Measurements", "NLsolve", "Pkg", "Random", "SafeTestsets", "SparseArrays", "StaticArrays", "Statistics", "StructArrays", "Tables", "Test", "Unitful", "Zygote"]

docs/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
44

55
[compat]
66
Documenter = "1.3"
7-
RecursiveArrayTools = "3"
7+
RecursiveArrayTools = "4"

docs/pages.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
pages = [
44
"Home" => "index.md",
5+
"breaking_changes_v4.md",
56
"AbstractVectorOfArrayInterface.md",
67
"array_types.md",
8+
"plotting.md",
79
"recursive_array_functions.md",
810
]

docs/src/assets/Project.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[deps]
2+
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
3+
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
4+
5+
[compat]
6+
Documenter = "1.3"
7+
RecursiveArrayTools = "3"

docs/src/breaking_changes_v4.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Breaking Changes in v4.0: AbstractArray Interface
2+
3+
## Summary
4+
5+
`AbstractVectorOfArray{T, N, A}` now subtypes `AbstractArray{T, N}`. This means
6+
all `VectorOfArray` and `DiffEqArray` objects are proper Julia `AbstractArray`s,
7+
and all standard `AbstractArray` operations work out of the box, including linear
8+
algebra, broadcasting with plain arrays, and generic algorithms.
9+
10+
## Key Changes
11+
12+
### Linear Indexing
13+
14+
Previously, `A[i]` returned the `i`th inner array (`A.u[i]`). Now, `A[i]` returns
15+
the `i`th element in column-major linear order, matching standard Julia `AbstractArray`
16+
behavior.
17+
18+
```julia
19+
A = VectorOfArray([[1, 2], [3, 4]])
20+
# Old: A[1] == [1, 2] (first inner array)
21+
# New: A[1] == 1 (first element, column-major)
22+
# To access inner arrays: A.u[1] or A[:, 1]
23+
```
24+
25+
### Size and Ragged Arrays
26+
27+
For ragged arrays (inner arrays of different sizes), `size(A)` now reports the
28+
**maximum** size in each dimension. Out-of-bounds elements are treated as zero
29+
(sparse representation):
30+
31+
```julia
32+
A = VectorOfArray([[1, 2], [3, 4, 5]])
33+
size(A) # (3, 2) — max inner length is 3
34+
A[3, 1] # 0 — implicit zero (inner array 1 has only 2 elements)
35+
A[3, 2] # 5 — actual stored value
36+
Array(A) # [1 3; 2 4; 0 5] — zero-padded dense array
37+
```
38+
39+
This means ragged `VectorOfArray`s can be used directly with linear algebra
40+
operations, treating the data as a rectangular matrix with zero padding.
41+
42+
### Iteration
43+
44+
Iteration now goes over scalar elements in column-major order, matching
45+
`AbstractArray` behavior:
46+
47+
```julia
48+
A = VectorOfArray([[1, 2], [3, 4]])
49+
collect(A) # [1 3; 2 4] — 2x2 matrix
50+
# To iterate over inner arrays: for u in A.u ... end
51+
```
52+
53+
### `length`
54+
55+
`length(A)` now returns `prod(size(A))` (total number of elements including
56+
ragged zeros), not the number of inner arrays. Use `length(A.u)` for the number
57+
of inner arrays.
58+
59+
### `map`
60+
61+
`map(f, A)` now maps over individual elements, not inner arrays. Use
62+
`map(f, A.u)` to map over inner arrays.
63+
64+
### `first` / `last`
65+
66+
`first(A)` and `last(A)` return the first/last scalar element, not the first/last
67+
inner array. Use `first(A.u)` / `last(A.u)` for inner arrays.
68+
69+
### `eachindex`
70+
71+
`eachindex(A)` returns `CartesianIndices(size(A))` for the full rectangular shape,
72+
not indices into `A.u`.
73+
74+
## Migration Guide
75+
76+
| Old Code | New Code |
77+
|----------|----------|
78+
| `A[i]` (get inner array) | `A.u[i]` or `A[:, i]` |
79+
| `length(A)` (number of arrays) | `length(A.u)` |
80+
| `for elem in A` (iterate columns) | `for elem in A.u` |
81+
| `first(A)` (first inner array) | `first(A.u)` |
82+
| `map(f, A)` (map over columns) | `map(f, A.u)` |
83+
| `A == vec_of_vecs` | `A.u == vec_of_vecs` |
84+
85+
## Interpolation Interface on DiffEqArray
86+
87+
`DiffEqArray` now has `interp` and `dense` fields for interpolation support:
88+
89+
```julia
90+
# Create with interpolation
91+
da = DiffEqArray(u, t, p, sys; interp = my_interp, dense = true)
92+
93+
# Callable syntax
94+
da(0.5) # interpolate at t=0.5
95+
da(0.5, Val{1}) # first derivative at t=0.5
96+
da([0.1, 0.5, 0.9]) # interpolate at multiple times
97+
da(0.5; idxs = 1) # interpolate single component
98+
da(0.5; idxs = [1, 2]) # interpolate subset of components
99+
```
100+
101+
The interpolation object must be callable as `interp(t, idxs, deriv, p, continuity)`,
102+
matching the protocol used by SciMLBase's `LinearInterpolation`, `HermiteInterpolation`,
103+
and `ConstantInterpolation`.
104+
105+
When `dense = true` and `interp` is provided, `plot(da)` automatically generates
106+
dense interpolated output instead of plotting only the saved time points.
107+
108+
## Ragged Arrays Sublibrary
109+
110+
`RaggedVectorOfArray` and `RaggedDiffEqArray` are available via:
111+
112+
```julia
113+
using RecursiveArrayToolsRaggedArrays
114+
```
115+
116+
These types preserve the true ragged structure without zero-padding, and do **not**
117+
subtype `AbstractArray`. See the `RecursiveArrayToolsRaggedArrays` subpackage for details.
118+
119+
## Zygote Compatibility
120+
121+
Some Zygote adjoint rules need updating for the new `AbstractArray` subtyping.
122+
ForwardDiff continues to work correctly. Zygote support will be updated in a
123+
follow-up release.

docs/src/plotting.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Plotting
2+
3+
`AbstractVectorOfArray` and `AbstractDiffEqArray` types include
4+
[Plots.jl](https://github.com/JuliaPlots/Plots.jl) recipes so they can be
5+
visualised directly with `plot(A)`.
6+
7+
## VectorOfArray
8+
9+
A `VectorOfArray` plots as a matrix where each inner array is a column:
10+
11+
```julia
12+
using RecursiveArrayTools, Plots
13+
A = VectorOfArray([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
14+
plot(A) # 3 series (components) vs column index
15+
```
16+
17+
## DiffEqArray
18+
19+
A `DiffEqArray` plots as component time series against `A.t`:
20+
21+
```julia
22+
u = [[sin(t), cos(t)] for t in 0:0.1:2pi]
23+
t = collect(0:0.1:2pi)
24+
A = DiffEqArray(u, t)
25+
plot(A) # plots sin and cos vs t
26+
```
27+
28+
If the `DiffEqArray` carries a symbolic system (via `variables` and
29+
`independent_variables` keyword arguments), the axis labels and series names
30+
are set automatically from the symbol names.
31+
32+
## Dense (Interpolated) Plotting
33+
34+
When a `DiffEqArray` has an interpolation object in its `interp` field and
35+
`dense = true`, calling `plot(A)` generates a smooth curve by evaluating the
36+
interpolation at many points rather than connecting only the saved time steps.
37+
38+
```julia
39+
using SciMLBase: LinearInterpolation
40+
41+
u = [[1.0, 0.0], [0.0, 1.0], [-1.0, 0.0], [0.0, -1.0]]
42+
t = [0.0, 1.0, 2.0, 3.0]
43+
interp = LinearInterpolation(t, u)
44+
A = DiffEqArray(u, t; interp = interp, dense = true)
45+
plot(A) # smooth interpolated curve with 1000+ points
46+
```
47+
48+
### Plot Recipe Keyword Arguments
49+
50+
The `AbstractDiffEqArray` recipe accepts the following keyword arguments,
51+
which can be passed directly to `plot`:
52+
53+
| Keyword | Type | Default | Description |
54+
|---------|------|---------|-------------|
55+
| `denseplot` | `Bool` | `A.dense && A.interp !== nothing` | Use dense interpolation for smooth curves. Set `false` to show only saved points. |
56+
| `plotdensity` | `Int` | `max(1000, 10 * length(A.u))` | Number of evenly-spaced points to evaluate when `denseplot = true`. |
57+
| `tspan` | `Tuple` or `nothing` | `nothing` | Restrict the time window. E.g. `tspan = (0.0, 5.0)`. |
58+
| `idxs` | varies | `nothing` | Select which components to plot (see below). |
59+
60+
Example:
61+
62+
```julia
63+
plot(A; denseplot = true, plotdensity = 5000, tspan = (0.0, 2.0))
64+
```
65+
66+
## Selecting Variables with `idxs`
67+
68+
The `idxs` keyword controls which variables appear in the plot. It supports
69+
several formats:
70+
71+
### Single component
72+
73+
```julia
74+
plot(A; idxs = 1) # plot component 1 vs time
75+
plot(A; idxs = 2) # plot component 2 vs time
76+
```
77+
78+
### Multiple components
79+
80+
```julia
81+
plot(A; idxs = [1, 3, 5]) # plot components 1, 3, 5 vs time
82+
```
83+
84+
### Phase-space plots (component vs component)
85+
86+
Use a tuple where index `0` represents the independent variable (time):
87+
88+
```julia
89+
plot(A; idxs = (1, 2)) # component 1 vs component 2
90+
plot(A; idxs = (0, 1)) # time vs component 1 (same as default)
91+
plot(A; idxs = (1, 2, 3)) # 3D plot of components 1, 2, 3
92+
```
93+
94+
### Symbolic indexing
95+
96+
When the `DiffEqArray` carries a symbolic system, variables can be referenced
97+
by symbol:
98+
99+
```julia
100+
A = DiffEqArray(u, t; variables = [:x, :y], independent_variables = [:t])
101+
plot(A; idxs = :x) # plot x vs time
102+
plot(A; idxs = [:x, :y]) # plot both
103+
plot(A; idxs = (:x, :y)) # phase plot of x vs y
104+
```
105+
106+
### Custom transformations
107+
108+
A function can be applied to the plotted values:
109+
110+
```julia
111+
plot(A; idxs = (norm, 0, 1, 2)) # plot norm(u1, u2) vs time
112+
```
113+
114+
The tuple format is `(f, xvar, yvar)` or `(f, xvar, yvar, zvar)` where `f`
115+
is applied element-wise.
116+
117+
## Callable Interface
118+
119+
Any `AbstractDiffEqArray` with an `interp` field supports callable syntax for
120+
interpolation, independent of plotting:
121+
122+
```julia
123+
A(0.5) # interpolate all components at t=0.5
124+
A(0.5; idxs = 1) # interpolate component 1 at t=0.5
125+
A([0.1, 0.5, 0.9]) # interpolate at multiple times (returns DiffEqArray)
126+
A(0.5, Val{1}) # first derivative at t=0.5
127+
A(0.5; continuity = :right) # right-continuity at discontinuities
128+
```
129+
130+
The interpolation object must be callable as
131+
`interp(t, idxs, deriv, p, continuity)`, matching the protocol used by
132+
SciMLBase's `LinearInterpolation`, `HermiteInterpolation`, and
133+
`ConstantInterpolation`.
134+
135+
When no interpolation is available (`interp === nothing`), calling `A(t)`
136+
throws an error.
137+
138+
## ODE Solution Plotting
139+
140+
ODE solutions from DifferentialEquations.jl are subtypes of
141+
`AbstractDiffEqArray` and inherit all of the above functionality, plus
142+
additional features:
143+
144+
- **Automatic dense plotting**: `denseplot` defaults to `true` when the solver
145+
provides dense output.
146+
- **Analytic solution overlay**: `plot(sol; plot_analytic = true)` overlays the
147+
exact solution if the problem defines one.
148+
- **Discrete variables**: Time-varying parameters are plotted as step functions
149+
with dashed lines and markers.
150+
- **Symbolic indexing**: Full symbolic indexing through ModelingToolkit is
151+
supported, including observed (derived) variables.
152+
153+
These advanced features are defined in SciMLBase and activate automatically
154+
when plotting solution objects.

ext/RecursiveArrayToolsCUDAExt.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module RecursiveArrayToolsCUDAExt
2+
3+
using RecursiveArrayTools: AbstractVectorOfArray
4+
import CUDA: CuArray
5+
6+
# Disambiguate CuArray(::AbstractVectorOfArray) vs CuArray(::AbstractArray{T,N}) from CUDA.jl.
7+
# This is the exact signature Julia's ambiguity error requests.
8+
# Uses stack to stay on GPU (avoids GPU→CPU→GPU round-trip).
9+
function CuArray(VA::AbstractVectorOfArray{T, N}) where {T, N}
10+
return CuArray{T, N}(stack(VA.u))
11+
end
12+
13+
end

0 commit comments

Comments
 (0)