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 → $\mathbb{R}^n$
controlu(t)callable → $\mathbb{R}^m$
costatep(t)callable → $\mathbb{R}^n$
variablevvalue
objectiveoptimal costscalar
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:

x = CTModels.state(sol)
u = CTModels.control(sol)
p = CTModels.costate(sol)

(x(0.0), x(0.123), u(0.5), p(1.0))
([1.0, -0.0], [0.9924346132121428, -0.12268878788815202], -0.8775825618903728, [0.0, 0.0])
(CTModels.variable(sol), CTModels.objective(sol))
(2.0, 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:

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)

(CTModels.control_interpolation(sol), CTModels.control_interpolation(sol_const))
(:constant, :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.