Skip to content

Commit aed06b0

Browse files
BREAKING: Add ragged arrays, interpolation interface, and plotting delegation
## Ragged Arrays (RecursiveArrayToolsRaggedArrays sublibrary) - Add AbstractRaggedVectorOfArray and AbstractRaggedDiffEqArray abstract types - Add RaggedVectorOfArray and RaggedDiffEqArray concrete types that preserve true ragged structure without zero-padding - Column-major linear indexing, no-padding A[:, i], symbolic indexing, broadcasting, and callable interpolation interface - Isolated in lib/ sublibrary to avoid invalidations ## Interpolation Interface on DiffEqArray - Add `interp` and `dense` fields to DiffEqArray (new type parameter I) - Callable syntax: da(t), da(t; idxs=1), da(t, Val{1}) - All constructors accept `interp=nothing, dense=false` kwargs ## Plotting Delegation - Full-featured AbstractDiffEqArray plot recipe with: - idxs variable selection (integer, array, tuple for phase plots, symbolic) - Dense interpolation via callable interface - denseplot, plotdensity, tspan, plotat keyword arguments - Export plotting helpers for SciMLBase delegation: DEFAULT_PLOT_FUNC, plottable_indices, plot_indices, getindepsym_defaultt, interpret_vars, add_labels!, diffeq_to_arrays, solplot_vecs_and_labels ## Bug Fix - Fix ragged VectorOfArray .= zero broadcast (DimensionMismatch) by using dest.u[i] instead of dest[:, i] in copyto! ## Version - Bump to v4.0.0 Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5410ab4 commit aed06b0

File tree

9 files changed

+1799
-38
lines changed

9 files changed

+1799
-38
lines changed

Project.toml

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

66
[deps]
@@ -11,6 +11,8 @@ GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527"
1111
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1212
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
1313
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
14+
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
15+
SnoopCompileCore = "e2b509da-e806-4183-be48-004708413034"
1416
StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
1517
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
1618

@@ -62,6 +64,7 @@ RecipesBase = "1.3.4"
6264
ReverseDiff = "1.15"
6365
SafeTestsets = "0.1"
6466
SciMLBase = "2.103"
67+
SnoopCompileCore = "3.1.1"
6568
SparseArrays = "1.10"
6669
StaticArrays = "1.6"
6770
StaticArraysCore = "1.4.2"

docs/pages.jl

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

docs/src/breaking_changes_v4.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,40 @@ not indices into `A.u`.
8282
| `map(f, A)` (map over columns) | `map(f, A.u)` |
8383
| `A == vec_of_vecs` | `A.u == vec_of_vecs` |
8484

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+
85119
## Zygote Compatibility
86120

87121
Some Zygote adjoint rules need updating for the new `AbstractArray` subtyping.

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.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name = "RecursiveArrayToolsRaggedArrays"
2+
uuid = "c384ba91-639a-44ca-823a-e1d3691ab84a"
3+
version = "1.0.0"
4+
5+
[deps]
6+
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
7+
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
8+
9+
[compat]
10+
RecursiveArrayTools = "3.48"
11+
SymbolicIndexingInterface = "0.3.35"
12+
julia = "1.10"
13+
14+
[extras]
15+
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
16+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
17+
18+
[targets]
19+
test = ["SymbolicIndexingInterface", "Test"]

0 commit comments

Comments
 (0)