Skip to content

Trajectories

The primal part of a solution is read through accessors that return callables of time (interpolated from the stored samples) for the trajectories, and plain values for the variable and the objective.

AccessorReturnsShape
statex(t)callable →
controlu(t)callable →
costatep(t)callable →
variablevvalue
objectiveoptimal costscalar
julia
using CTModels

pre = CTModels.PreModel()
CTModels.variable!(pre, 1, "v")
CTModels.time!(pre; t0=0.0, tf=1.0)
CTModels.state!(pre, 2)
CTModels.control!(pre, 1)
CTModels.dynamics!(pre, (r, t, x, u, v) -> (r[1] = x[2]; r[2] = u[1]; nothing))
CTModels.objective!(pre, :min; lagrange=(t, x, u, v) -> 0.5u[1]^2)
CTModels.time_dependence!(pre; autonomous=true)
ocp = CTModels.build(pre)

N = 101
T = collect(range(0.0, 1.0; length=N))
X = hcat(cos.(T), -sin.(T))
U = reshape(-cos.(T), N, 1)
P = zeros(N, 2)
v = [2.0]                         # the optimisation variable value

sol = CTModels.build_solution(ocp, T, X, U, v, P;
    objective=0.5, iterations=10, constraints_violation=1e-9,
    message="ok", status=:optimal, successful=true)

Reading trajectories

The trajectories are callables, so they can be evaluated at any time in the interval, not only at grid points — the samples are interpolated:

julia
x = CTModels.state(sol)
u = CTModels.control(sol)
p = CTModels.costate(sol)
julia
julia> x(0.0)
2-element Vector{Float64}:
  1.0
 -0.0

julia> x(0.123)
2-element Vector{Float64}:
  0.9924346132121428
 -0.12268878788815202

julia> u(0.5)
-0.8775825618903728

julia> p(1.0)
2-element Vector{Float64}:
 0.0
 0.0
julia
julia> CTModels.variable(sol)
2.0

julia> CTModels.objective(sol)
0.5

Interpolation of the control

State and costate are interpolated linearly. The control may instead be piecewise constant — typical of direct collocation — selected by the control_interpolation keyword of build_solution (:linear or :constant). The choice is recorded and read back with control_interpolation:

julia
sol_const = CTModels.build_solution(ocp, T, X, U, v, P;
    objective=0.5, iterations=10, constraints_violation=1e-9,
    message="ok", status=:optimal, successful=true,
    control_interpolation=:constant)
julia
julia> CTModels.control_interpolation(sol)
:constant

julia> CTModels.control_interpolation(sol_const)
:constant

Because the interpolation kind is a stored field — not baked into an opaque closure — downstream code (plotting, serialization) can branch on it explicitly. The Duals & diagnostics page covers the remaining accessors.