Building a flow
A flow is a callable that integrates a system from an initial condition to a final time. Building one involves three steps:
Data → build_system → SciML → build_flow → FlowThe shortcut constructor Flows.Flow(data; opts...) collapses all three steps into one call. This page explains both paths.
Shortcut: Flows.Flow
The simplest way to build a flow is to pass data directly to Flows.Flow:
# From a VectorField → StateFlow
vf = Data.VectorField(x -> -x)
flow = Flows.Flow(vf; reltol=1e-8, abstol=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 [user]
├─ save_start = auto [default]
└─ dense = auto [default]
Tip: use describe(SciML{CPU}) to see all available options.# From a HamiltonianVectorField → HamiltonianFlow
hvf = Data.HamiltonianVectorField((x, p) -> (p, -x))
hflow = Flows.Flow(hvf; reltol=1e-10)Flow
├─ system: HamiltonianVectorFieldSystem
│ ├─ time_dependence: Autonomous
│ ├─ variable_dependence: Fixed
│ └─ HamiltonianVectorField: autonomous, fixed (no variable), out-of-place
│ natural call: f(x, p)
│ uniform call: f(t, x, p, v)
└─ integrator: SciML{CPU} (instance, id=:sciml)
├─ internalnorm = real_norm [default]
├─ alg = Tsit5 [default]
├─ reltol = 1.0e-10 [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.# From a scalar Hamiltonian (AD computes the derivatives) → HamiltonianFlow
using DifferentiationInterface: DifferentiationInterface
using ForwardDiff: ForwardDiff
using LinearAlgebra
h = Data.Hamiltonian((x, p) -> 0.5 * (dot(x, x) + dot(p, p)))
hflow_ad = Flows.Flow(h; reltol=1e-10)Flow
├─ system: HamiltonianSystem
│ ├─ time_dependence: Autonomous
│ ├─ variable_dependence: Fixed
│ ├─ Hamiltonian: autonomous, fixed (no variable)
│ │ natural call: h(x, p)
│ │ uniform call: h(t, x, p, v)
│ └─ backend: DifferentiationInterface{CPU}(ad_backend=AutoForwardDiff)
└─ integrator: SciML{CPU} (instance, id=:sciml)
├─ internalnorm = real_norm [default]
├─ alg = Tsit5 [default]
├─ reltol = 1.0e-10 [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.Options passed as keyword arguments are forwarded to the integrator strategy; see Integrating for the full list.
Explicit pipeline
The explicit form gives full control over each step.
Step 1 — Build the system
build_system wraps data into an AbstractSystem that exposes the ODE right-hand side:
# VectorField → VectorFieldSystem
sys = Systems.build_system(vf)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)# HamiltonianVectorField → HamiltonianVectorFieldSystem
hsys = Systems.build_system(hvf)HamiltonianVectorFieldSystem
├─ time_dependence: Autonomous
├─ variable_dependence: Fixed
└─ HamiltonianVectorField: autonomous, fixed (no variable), out-of-place
natural call: f(x, p)
uniform call: f(t, x, p, v)A system exposes:
julia> Traits.time_dependence(sys)
CTBase.Traits.Autonomous
julia> Traits.variable_dependence(sys)
CTBase.Traits.Fixedconfig = Configs.StateEndPointConfig(0.0, [1.0, 0.0], 1.0)StateEndPointConfig
├─ t0: 0.0
├─ x0: [1.0, 0.0]
└─ tf: 1.0Systems.get_ip_rhs(sys, config)IPVFOoPRHS
├─ wraps: VectorField: autonomous, fixed (no variable), out-of-place
└─ converts: out-of-place VF → in-place interfaceSystems.get_oop_rhs(sys, config)OoPVFOoPRHS
├─ wraps: VectorField: autonomous, fixed (no variable), out-of-place
└─ converts: out-of-place VF → out-of-place interfaceStep 2 — Build the integrator
The integrator is a strategy object; construct it directly with its options.
integ = Integrators.SciML(; reltol=1e-8, abstol=1e-8)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 [user]
├─ save_start = auto [default]
└─ dense = auto [default]
Tip: use describe(SciML{CPU}) to see all available options.SciML is the integrator strategy provided by CTSolvers (backed by OrdinaryDiffEqTsit5 when loaded). See Integrating for how to choose a different algorithm.
Step 3 — Combine into a flow
flow_explicit = Flows.build_flow(sys, integ)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 [user]
├─ save_start = auto [default]
└─ dense = auto [default]
Tip: use describe(SciML{CPU}) to see all available options.This produces the same StateFlow as the shortcut:
typeof(flow_explicit) == typeof(flow)trueFlow types
| Input data | Flow type |
|---|---|
VectorField | StateFlow |
HamiltonianVectorField | HamiltonianFlow |
Hamiltonian (with AD) | HamiltonianFlow |
PseudoHamiltonian + DynClosedLoop law | HamiltonianFlow |
PseudoHamiltonianVectorField + DynClosedLoop law | HamiltonianFlow |
ControlledVectorField + OpenLoop/ClosedLoop law | ControlledFlow |
| OCP (control-free) | OptimalControlFlow |
OCP (with control) + DynClosedLoop law | OptimalControlFlow |
OCP (with control) + OpenLoop/ClosedLoop law | ControlledFlow |
OCP (with control) + u::Function | OptimalControlFlow (auto DynClosedLoop) |
Both StateFlow and HamiltonianFlow are concrete subtypes of AbstractFlow. ControlledFlow is a state-flow wrapper carrying a control law (see Control laws). OptimalControlFlow wraps a HamiltonianFlow with the OCP reference (see Optimal control). Their trait parameters mirror the underlying data:
julia> Traits.time_dependence(flow) # Autonomous (inherited from vf)
CTBase.Traits.Autonomous
julia> Traits.variable_dependence(flow) # Fixed
CTBase.Traits.FixedAbstractSystem contract
Any concrete system must implement:
get_ip_rhs(system, config)— returns the in-place ODE function(du, u, p, t) -> nothingget_oop_rhs(system, config)— returns the out-of-place ODE function(u, p, t) -> du
Hamiltonian systems supporting variable-costate integration additionally implement get_ip_rhs_augmented(system, config). Eager systems (e.g. VectorFieldSystem) ignore config and return pre-computed closures; lazy systems (e.g. HamiltonianSystem) read the initial conditions from config to build type-specific closures.
Traits are propagated automatically from the data layer:
Traits.time_dependence(system)Traits.variable_dependence(system)
AbstractFlow contract
Any concrete flow must implement:
system(flow)— the associatedAbstractSystemintegrator(flow)— the associatedAbstractIntegrator
Flows.system(flow) # the VectorFieldSystem wrapped by this StateFlowVectorFieldSystem
├─ time_dependence: Autonomous
├─ variable_dependence: Fixed
└─ VectorField: autonomous, fixed (no variable), out-of-place
natural call: f(x)
uniform call: f(t, x, v)Flows.integrator(flow) # the SciML integrator strategySciML{CPU} (instance, id=:sciml)
├─ internalnorm = real_norm [default]
├─ alg = Tsit5 [default]
├─ reltol = 1.0e-8 [user]
├─ save_everystep = auto [default]
├─ abstol = 1.0e-8 [user]
├─ save_start = auto [default]
└─ dense = auto [default]
Tip: use describe(SciML{CPU}) to see all available options.Calling a flow delegates through _invoke_flow which builds the ODE problem, solves it, and wraps the result — see Integrating.
Flow getters
Every flow answers a small, uniform set of getters. Which ones are meaningful depends on how the flow was built; calling an inapplicable getter raises a clear IncorrectArgument.
system, integrator, and vector_field are available on all flows (the first two are shown in the AbstractFlow contract above). For a StateFlow, vector_field returns the underlying AbstractVectorField directly:
julia> Flows.vector_field(flow)
VectorField: autonomous, fixed (no variable), out-of-place
natural call: f(x)
uniform call: f(t, x, v)For a HamiltonianFlow, vector_field is an alias of hamiltonian_vector_field and returns
julia> Flows.vector_field(hflow)
HamiltonianVectorField: autonomous, fixed (no variable), out-of-place
natural call: f(x, p)
uniform call: f(t, x, p, v)
julia> Flows.hamiltonian_vector_field(hflow)
HamiltonianVectorField: autonomous, fixed (no variable), out-of-place
natural call: f(x, p)
uniform call: f(t, x, p, v)
julia> Flows.vector_field(hflow) === Flows.hamiltonian_vector_field(hflow)
trueFor an AD-backed HamiltonianFlow (built from a scalar Hamiltonian), the Hamiltonian itself is also available:
julia> Flows.hamiltonian(hflow_ad)
Hamiltonian: autonomous, fixed (no variable)
natural call: h(x, p)
uniform call: h(t, x, p, v)
julia> Flows.hamiltonian_vector_field(hflow_ad)
HamiltonianVectorField: autonomous, fixed (no variable), out-of-place
natural call: f(x, p)
uniform call: f(t, x, p, v)hamiltonian_vector_field (and therefore vector_field) also covers flows built from a pseudo-Hamiltonian or an OCP together with a control law — the Hamiltonian is then a CTBase.Data.ComposedHamiltonian (:total mode) or reconstructed from a PseudoHamiltonianSystem (:partial mode). The pseudo-Hamiltonian getters (pseudo_hamiltonian, control_law, get_pseudo_hamiltonian_gradient, get_pseudo_variable_gradient) and get_variable_gradient are shown on Control laws and Integrating respectively.
See also
CTFlows.Flows.Flow,CTFlows.Flows.StateFlow,CTFlows.Flows.HamiltonianFlow— concrete flow types.CTFlows.Flows.build_flow,CTFlows.Systems.build_system,CTSolvers.Integrators.SciML— pipeline builders.CTFlows.Flows.AbstractFlow,CTFlows.Flows.AbstractStateFlow,CTFlows.Flows.AbstractHamiltonianFlow— abstract supertypes.CTFlows.Flows.system,CTFlows.Flows.integrator— flow accessors.CTFlows.Systems.AbstractSystem,CTFlows.Systems.get_ip_rhs,CTFlows.Systems.get_oop_rhs— system contract.CTFlows.Systems.vector_field,CTFlows.Flows.hamiltonian_vector_field— vector field getters (state and Hamiltonian flows).