Flow(VectorField) compatibility
This page is a living compatibility reference for the state flow built from a Data.VectorField: which state types and call styles it accepts, each shown with a minimal, executable example. Every ✓ / ⚠ in the table below is demonstrated by a code block on this page and is re-run on every documentation build, so the page cannot drift from the code.
Scope: CPU only. The table is generated from probe/cpu/probe_cpu.jl (run it locally with julia --project=probe/cpu probe/cpu/probe_cpu.jl). GPU compatibility is a separate effort — see probe/gpu.
All examples integrate the scalar exponential decay OrdinaryDiffEqTsit5). The analytic solution is
Last probed: 2026-07-24
This page's table shape — which containers and axes are tested — reflects probe/cpu/probe_cpu.jl as of this date. Every ✓/⚠ cell below is still re-executed on every documentation build regardless (see Compatibility overview) — only the scope of what's tested can go stale, not the results shown.
Two flows are built once — one from an out-of-place vector field, one from an in-place one — and reused by every example below, so each example isolates the one thing that varies: the initial condition x0.
vf = Data.VectorField(x -> -x) # out-of-place: x -> -x
flow = Flows.Flow(vf; reltol=1e-8)Flow
├─ system: VectorFieldSystem
│ ├─ time_dependence: Autonomous
│ ├─ variable_dependence: Fixed
│ └─ VectorField: autonomous, fixed (no variable), out-of-place
│ natural call: f(x)
│ uniform call: f(t, x, v)
└─ integrator: SciML{CPU} (instance, id=:sciml)
├─ internalnorm = real_norm [default]
├─ alg = Tsit5 [default]
├─ reltol = 1.0e-8 [user]
├─ save_everystep = auto [default]
├─ abstol = 1.0e-8 [default]
├─ save_start = auto [default]
└─ dense = auto [default]
Tip: use describe(SciML{CPU}) to see all available options.vf_ip = Data.VectorField((du, x) -> (du .= -x); is_autonomous=true, is_variable=false) # in-place
flow_ip = Flows.Flow(vf_ip; reltol=1e-8)Flow
├─ system: VectorFieldSystem
│ ├─ time_dependence: Autonomous
│ ├─ variable_dependence: Fixed
│ └─ VectorField: autonomous, fixed (no variable), in-place
│ natural call: f(dx, x)
│ uniform call: f(dx, t, x, v)
└─ integrator: SciML{CPU} (instance, id=:sciml)
├─ internalnorm = real_norm [default]
├─ alg = Tsit5 [default]
├─ reltol = 1.0e-8 [user]
├─ save_everystep = auto [default]
├─ abstol = 1.0e-8 [default]
├─ save_start = auto [default]
└─ dense = auto [default]
Tip: use describe(SciML{CPU}) to see all available options.Compatibility table
The two call styles are the point call flow(t0, x0, tf) → final state xf, and the trajectory call flow((t0, tf), x0) → a Trajectories.VectorFieldTrajectory. The OOP / IP columns are the vector-field kind — an out-of-place x -> -x versus an in-place (du, x) -> (du .= -x) — independent of the state container. (Which integration path is taken is chosen automatically from the mutability of x0; see In-place vector fields.)
| State type | OOP point | OOP traj | IP point | IP traj |
|---|---|---|---|---|
Scalar Real | ✓ | ✓ | ✓ | ✓ |
Vector Real | ✓ | ✓ | ✓ | ✓ |
MVector Real | ✓ | ✓ | ✓ | ✓ |
SVector Real | ✓ | ✓ | ⚠ | ⚠ |
Matrix Real (batch) | ✓ | ✓ | ✓ | ✓ |
MMatrix Real | ✓ | ✓ | ✓ | ✓ |
SMatrix Real | ✓ | ✓ | ⚠ | ⚠ |
Scalar Complex | ✓ | ✓ | ✓ | ✓ |
Vector Complex | ✓ | ✓ | ✓ | ✓ |
MVector Complex | ✓ | ✓ | ✓ | ✓ |
SVector Complex | ✓ | ✓ | ⚠ | ⚠ |
Matrix Complex | ✓ | ✓ | ✓ | ✓ |
SMatrix Complex | ✓ | ✓ | ⚠ | ⚠ |
ForwardDiff.Dual scalar | ✓ | ✓ | ✓ | ✓ |
ForwardDiff.Dual Vector | ✓ | ✓ | ✓ | ✓ |
ForwardDiff.Dual MVector | ✓ | ✓ | ✓ | ✓ |
ForwardDiff.Dual SVector | ✓ | ✓ | ⚠ | ⚠ |
Legend
✓ — works; the result matches
and is executed on this page.⚠ — works, but emits a performance warning; see note (a).
Every state type is supported on CPU — there is no unsupported (✗) combination. Two behaviours are worth calling out:
(a) In-place vector field + immutable initial condition
An in-place vector field with an immutable x0 (SVector, SMatrix) emits a performance @warn and falls back to an out-of-place finalize RHS. The result is correct but the path is slower. Prefer an out-of-place vector field, or a mutable MVector / MMatrix, for static states.
1-D = scalar, end to end
A scalar x0 stays a scalar in both call styles, and a length-1 vector or SVector x0 collapses to a scalar too — the point call returns a scalar xf, and the trajectory accessor state(sol)(t) also returns a scalar, not a length-1 vector. This is the ecosystem's "1-D = scalar" convention, matching what Flow(HamiltonianVectorField) already did — see the Shape contract page for the full cross-constructor picture, and #357 for the fix that brought this constructor in line with it.
Real states
Scalar
The point call returns a scalar:
julia> flow(0.0, 1.0, 1.0) # ≈ exp(-1)
0.36787944125650673The trajectory call returns a VectorFieldTrajectory; its state accessor is a scalar too, at each time:
julia> sol = flow((0.0, 1.0), 1.0);
julia> Trajectories.state(sol)(1.0) # ≈ exp(-1)
0.3678794412565068Length-1 Vector/SVector (collapses to scalar)
A length-1 container is not a "real" vector — it collapses to a scalar in both call styles, the same way a bare scalar x0 does:
julia> flow(0.0, [1.0], 1.0) # ≈ exp(-1), a Real — not a length-1 Vector
0.36787944125650673
julia> flow(0.0, SA[1.0], 1.0) # ≈ exp(-1), a Real — not a length-1 SVector
0.36787944125650673
julia> sol = flow((0.0, 1.0), [1.0]);
julia> Trajectories.state(sol)(1.0) # ≈ exp(-1), a Real
0.3678794412565068Vector
A length ≥ 2 vector is unaffected by the collapse above and keeps its container shape:
julia> flow(0.0, [1.0, 2.0], 1.0)
2-element Vector{Float64}:
0.3678794412375269
0.7357588824750538The trajectory's state accessor interpolates at any time in the span:
julia> sol = flow((0.0, 1.0), [1.0, 2.0]);
julia> Trajectories.state(sol)(0.5)
2-element Vector{Float64}:
0.6065306597377546
1.213061319475509MVector and SVector
Mutable and immutable static vectors both integrate out-of-place:
julia> flow(0.0, MVector{2}(1.0, 2.0), 1.0)
2-element MVector{2, Float64} with indices SOneTo(2):
0.3678794412375269
0.7357588824750538
julia> flow(0.0, SA[1.0, 2.0], 1.0)
2-element SVector{2, Float64} with indices SOneTo(2):
0.3678794412375269
0.7357588824750538Matrix (batch)
A matrix state integrates every column as an independent trajectory of
julia> flow(0.0, [1.0 2.0; 3.0 4.0], 1.0)
2×2 Matrix{Float64}:
0.367879 0.735759
1.10364 1.47152
julia> flow(0.0, MMatrix{2,2}(1.0, 3.0, 2.0, 4.0), 1.0) # mutable
2×2 MMatrix{2, 2, Float64, 4} with indices SOneTo(2)×SOneTo(2):
0.367879 0.735759
1.10364 1.47152
julia> flow(0.0, SMatrix{2,2}(1.0, 3.0, 2.0, 4.0), 1.0) # immutable
2×2 SMatrix{2, 2, Float64, 4} with indices SOneTo(2)×SOneTo(2):
0.367879 0.735759
1.10364 1.47152Complex states
Complex initial conditions work with the same real vector field — the scalar case:
julia> flow(0.0, 1.0 + 2.0im, 1.0) # ≈ (1 + 2im) * exp(-1)
0.36787944123358063 + 0.7357588824671613imvectors:
julia> flow(0.0, [1.0 + 2.0im, 3.0 + 4.0im], 1.0)
2-element Vector{ComplexF64}:
0.36787944122283733 + 0.7357588824456747im
1.1036383236685123 + 1.4715177648913493imand matrices:
julia> flow(0.0, [1.0+2.0im 5.0+6.0im; 3.0+4.0im 7.0+8.0im], 1.0)
2×2 Matrix{ComplexF64}:
0.367879+0.735759im 1.8394+2.20728im
1.10364+1.47152im 2.57516+2.94304imAutomatic differentiation (ForwardDiff.Dual)
A ForwardDiff.Dual initial condition propagates a sensitivity through the integration — the basis for differentiating a flow with respect to its initial state:
julia> x0 = ForwardDiff.Dual(1.0, 1.0) # value 1.0, seed 1.0
Dual{Nothing}(1.0,1.0)
julia> xf = flow(0.0, x0, 1.0)
Dual{Nothing}(0.36787944125650673,0.36787944125650673)
julia> (ForwardDiff.value(xf), ForwardDiff.partials(xf, 1)) # ≈ (exp(-1), exp(-1))
(0.36787944125650673, 0.36787944125650673)The value tracks
julia> x0 = [ForwardDiff.Dual(1.0, 1.0), ForwardDiff.Dual(2.0, 0.0)]
2-element Vector{ForwardDiff.Dual{Nothing, Float64, 1}}:
Dual{Nothing}(1.0,1.0)
Dual{Nothing}(2.0,0.0)
julia> flow(0.0, x0, 1.0)
2-element Vector{ForwardDiff.Dual{Nothing, Float64, 1}}:
Dual{Nothing}(0.3678794412375269,0.3678794412375269)
Dual{Nothing}(0.7357588824750538,0.0)Grid invariance
The SciML integrator's internalnorm uses a real_norm that ignores dual parts, so the integration grid is identical whether the initial condition is a Real or a Dual. See test/suite/extensions/test_forwarddiff_extension.jl.
Recommended: differentiate the flow, not one hand-seeded Dual
Seeding a single Dual per component (as above) only gives one partial derivative at a time. In practice, wrap the flow call in an outer ForwardDiff.jacobian / ForwardDiff.gradient instead — one call returns every partial at once. Flow(VectorField) has no internal AD backend, so there is no nesting concern here (unlike Flow(Hamiltonian), whose own gradient computation is itself AutoForwardDiff):
julia> ForwardDiff.jacobian(x0 -> flow(0.0, x0, 1.0), [1.0, 2.0]) # ≈ e⁻¹·I
2×2 Matrix{Float64}:
0.367879 0.0
0.0 0.367879In-place vector fields
An in-place vector field (du, x) -> (du .= -x) produces the same results. The flow picks the integration path from the mutability of x0: a mutable container (Vector, MVector, Matrix, MMatrix) is integrated truly in place, with no allocation per step:
julia> flow_ip(0.0, [1.0, 2.0], 1.0)
2-element Vector{Float64}:
0.3678794412375269
0.7357588824750538
julia> flow_ip(0.0, MVector{2}(1.0, 2.0), 1.0)
2-element MVector{2, Float64} with indices SOneTo(2):
0.3678794412375269
0.7357588824750538A scalar initial condition is also accepted: it is promoted to a length-1 vector before the mutability dispatch, so the in-place path applies.
julia> flow_ip(0.0, 1.0, 1.0) # scalar promoted internally
0.36787944125650673Immutable initial conditions (SVector / SMatrix)
An immutable static array cannot be written in place, so an in-place vector field falls back to an out-of-place finalize RHS and emits a performance warning (note (a)). The result is still correct:
julia> flow_ip(0.0, SA[1.0, 2.0], 1.0) # warns, then returns the correct value
┌ Warning: InPlace VectorField with immutable u0 (e.g. SVector): consider using an out-of-place function for better performance.
└ @ CTFlows.Systems ~/work/CTFlows.jl/CTFlows.jl/src/Systems/vector_field_system.jl:182
2-element SVector{2, Float64} with indices SOneTo(2):
0.3678794412375269
0.7357588824750538For static states, prefer the out-of-place vector field (flow) shown above, or use a mutable MVector / MMatrix.
See also
Building a flow — the shortcut constructor and explicit pipeline.
Integrating — call styles, variable parameters, and integrator options.
Compatibility overview — the per-constructor feature matrix and the other flow types.
Shape contract — the cross-constructor "1-D = scalar" reference.
CTFlows.Flows.Flow,CTFlows.Flows.StateFlow— the flow types.CTBase.Data.VectorField— the data wrapper.