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  →  build_integrator  →  build_flow  →  Flow

The 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
                wraps: VectorField: autonomous, fixed (no variable), out-of-place
                rhs:   IPVFOoPRHS (out-of-place VF → in-place interface)
  integrator: SciML (abstol = 1.0e-8, reltol = 1.0e-8)
# From a HamiltonianVectorField → HamiltonianFlow
hvf  = Data.HamiltonianVectorField((x, p) -> (p, -x))
hflow = Flows.Flow(hvf; reltol=1e-10)
Flow
  system:     HamiltonianVectorFieldSystem
                wraps: HamiltonianVectorField: autonomous, fixed (no variable), out-of-place
  integrator: SciML (reltol = 1.0e-10)
using LinearAlgebra

# From a scalar Hamiltonian (AD computes the derivatives) → HamiltonianFlow
import DifferentiationInterface, ForwardDiff
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: CTBase.Traits.Autonomous
                variable_dependence: CTBase.Traits.Fixed
                hamiltonian: Hamiltonian: autonomous, fixed (no variable)
                natural call: h(x, p)
                uniform call: h(t, x, p, v)
                backend: DifferentiationInterface(ad_backend=AutoForwardDiff())
              
  integrator: SciML (reltol = 1.0e-10)

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
  wraps: VectorField: autonomous, fixed (no variable), out-of-place
  rhs:   IPVFOoPRHS (out-of-place VF → in-place interface)
# HamiltonianVectorField → HamiltonianVectorFieldSystem
hsys = Systems.build_system(hvf)
HamiltonianVectorFieldSystem
  wraps: HamiltonianVectorField: autonomous, fixed (no variable), out-of-place

A system exposes:

  • Systems.rhs(sys) — the ODE right-hand side function (du, u, p, t) -> …
  • Traits.time_dependence(sys), Traits.variable_dependence(sys) — delegated from the data

Step 2 — Build the integrator

integ = Integrators.build_integrator(; reltol=1e-8, abstol=1e-8)
SciML (instance, id=:sciml)
├─ internalnorm = real_norm  [default]
├─ alg = Tsit5{typeof(OrdinaryDiffEqCore.trivial_limiter!), typeof(OrdinaryDiffEqCore.trivial_limiter!), FastBroadcast.Serial}(OrdinaryDiffEqCore.trivial_limiter!, OrdinaryDiffEqCore.trivial_limiter!, FastBroadcast.Serial())  [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) to see all available options.

The default integrator is SciML (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
                wraps: VectorField: autonomous, fixed (no variable), out-of-place
                rhs:   IPVFOoPRHS (out-of-place VF → in-place interface)
  integrator: SciML (abstol = 1.0e-8, reltol = 1.0e-8)

This produces the same StateFlow as the shortcut:

typeof(flow_explicit) == typeof(flow)
true

Flow types

Input dataFlow type
VectorFieldStateFlow
HamiltonianVectorFieldHamiltonianFlow
Hamiltonian (with AD)HamiltonianFlow

Both StateFlow and HamiltonianFlow are concrete subtypes of AbstractFlow. Their trait parameters mirror the underlying data:

Traits.time_dependence(flow)      # Autonomous (inherited from vf)
Traits.variable_dependence(flow)  # Fixed
CTBase.Traits.Fixed

AbstractSystem contract

Any concrete system must implement:

  • rhs(system) — returns an ODE function (du, u, p, t) -> nothing

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 associated AbstractSystem
  • integrator(flow) — the associated AbstractIntegrator

Calling a flow delegates through _invoke_flow which builds the ODE problem, solves it, and wraps the result — see Integrating.


Hamiltonian vector field getter

For a HamiltonianFlow, you can retrieve the underlying HamiltonianVectorField at any time:

# HamiltonianVectorField-backed flow
hvf_back = 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)

For an AD-backed flow (built from Hamiltonian), the getter materialises the vector field on demand:

hvf_ad = 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)

See also