Trajectories

A trajectory integration returns a solution object that wraps the raw ODE result and exposes semantic accessors. The CTFlows.Trajectories submodule provides these wrappers.


Solution types

TypeProduced byContent
VectorFieldTrajectoryStateFlow trajectory callstate trajectory
HamiltonianVectorFieldTrajectoryHamiltonianFlow trajectory callstate + costate trajectories

VectorFieldTrajectory

sol   # produced by flow((t0, tf), x0)
VectorFieldTrajectory
  result: SciMLIntegrationResult

Accessors

ts = Trajectories.time_grid(sol)      # vector of time points
ts[1], ts[end]
(0.0, 1.0)
x = Trajectories.state(sol)           # callable: x(t) → state at time t
x(0.0)                             # initial state (exact)
2-element Vector{Float64}:
 1.0
 0.0
x(0.5)                             # interpolated at t = 0.5
2-element Vector{Float64}:
 0.6065306592843308
 0.0
x.(ts)                             # broadcast over the time grid
16-element Vector{Vector{Float64}}:
 [1.0, 0.0]
 [0.9877640316387758, 0.0]
 [0.9586392120306756, 0.0]
 [0.9219104181991034, 0.0]
 [0.8795807933369617, 0.0]
 [0.8322170230625516, 0.0]
 [0.7816374670004691, 0.0]
 [0.7290594161179201, 0.0]
 [0.6758982310332805, 0.0]
 [0.6231970367115284, 0.0]
 [0.571858602091371, 0.0]
 [0.5225200133759598, 0.0]
 [0.47564070023807775, 0.0]
 [0.431502170190499, 0.0]
 [0.39025901687118947, 0.0]
 [0.36787944127643135, 0.0]

state(sol) returns sol itself, which is callable. The two forms state(sol)(t) and sol(t) are equivalent. time_grid is an alias for times.

Point integration vs trajectory

Point integration (flow(t0, x0, tf)) returns the final state directly as a Vector, not a solution object:

xf = flow(0.0, x0, 1.0)   # Vector, not VectorFieldTrajectory
typeof(xf)
Vector{Float64} (alias for Array{Float64, 1})

Use trajectory integration (flow((t0, tf), x0)) when you need the full history.


HamiltonianVectorFieldTrajectory

hsol   # produced by hflow((t0, tf), x0, p0)
HamiltonianVectorFieldTrajectory
  result: SciMLIntegrationResult

Accessors

ts_h = Trajectories.time_grid(hsol)
18-element Vector{Float64}:
 0.0
 0.010717734625362933
 0.03678924587914857
 0.07082975288098367
 0.11171621300174836
 0.15977761725931772
 0.214066312625275
 0.27414099662100727
 0.339163563060794
 0.40850604198933105
 0.48147597812035586
 0.557505923126691
 0.6360680321223098
 0.716722082543547
 0.7990884851440951
 0.8828514711671517
 0.9677468775678137
 1.0
x_h = Trajectories.state(hsol)      # state trajectory: x(t)
p_h = Trajectories.costate(hsol)    # costate trajectory: p(t)
x_h(0.0), p_h(0.0)
([1.0, 0.0], [0.0, 1.0])
x_h(0.5), p_h(0.5)
([0.8775825617654902, 0.4794255388798979], [-0.4794255388798979, 0.8775825617654902])

Plotting

Load Plots (or any Plots-compatible backend) to unlock plot on solution objects:

using Plots
plot(sol)    # plots each component of the state trajectory
plot(hsol)   # plots state and costate components

The plot recipe is provided by the CTFlowsPlots extension (activated automatically when Plots is loaded).


Low-level: integration result

Under the hood, solution objects wrap an AbstractIntegrationResult which exposes:

  • Integrators.times(result) — the time grid
  • Integrators.evaluate_at(result, t) — interpolated value at t
  • Integrators.final_state(result) — the final value

These are used internally by the solution wrappers and normally not called directly.


See also