Solutions

A Solution is the immutable container returned to the user once a solver has run. It bundles the primal trajectories (state, control, costate), the optimisation variable, the objective value, the dual variables, and the solver diagnostics — all behind a uniform accessor surface.

CTModels does not solve OCPs; a Solution is assembled from raw numerical arrays by build_solution, the bridge an NLP backend calls.

model + numerical arrays (T, X, U, v, P, duals, infos)
                    │
              build_solution
                    ▼
                 Solution ──► state/control/costate/variable/objective/dual/…

Reading order

PageTopicKey symbols
Time gridsOne grid or severalUnifiedTimeGridModel, MultipleTimeGridModel
TrajectoriesReading primal datastate, control, costate
Duals & diagnosticsMultipliers and solver statusdual, DualModel, SolverInfos

Minimal end-to-end example

We reuse a minimal model and feed build_solution fabricated arrays (in practice these come from a solver). State, control and costate are sampled on one uniform grid:

using CTModels

pre = CTModels.PreModel()
CTModels.variable!(pre, 0)
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) -> u[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))      # N×2 : state samples (rows = time)
U = reshape(-cos.(T), N, 1)      # N×1 : control samples
P = zeros(N, 2)                  # N×2 : costate samples
v = Float64[]                    # no optimisation variable

sol = CTModels.build_solution(ocp, T, X, U, v, P;
    objective=0.5,
    iterations=10,
    constraints_violation=1e-9,
    message="Solve_Succeeded",
    status=:Solve_Succeeded,
    successful=true,
)
• Solver:
  ✓ Successful  : true
  │  Status     : Solve_Succeeded
  │  Message    : Solve_Succeeded
  │  Iterations : 10
  │  Objective  : 0.5
  └─ Constraints violation : 1.0e-9

The trajectories are returned as callables (interpolated from the samples), and the diagnostics as scalars:

x = CTModels.state(sol)          # x(t) → state at time t
(x(0.5),
 CTModels.objective(sol),
 CTModels.iterations(sol),
 CTModels.successful(sol))
([0.8775825618903728, -0.479425538604203], 0.5, 10, true)

Anatomy of a Solution

Field groupAccessor(s)Stored as
time gridtime_gridAbstractTimeGridModel
state / control / costatestate, control, costatecallables t → …
variable / objectivevariable, objectivevalue
dualsdual, DualModelcallables / vectors
diagnosticsiterations, status, successfulSolverInfos

Each accessor dispatches on a typed field, so reading a solution never inspects raw closures. The following pages take each group in turn.