Export & import
export_ocp_solution writes a Solution to disk; import_ocp_solution reads it back, given the Model it belongs to. Two formats are available, each behind its extension:
format | Extension | File |
|---|---|---|
:JLD (default) | CTModelsJLD (JLD2) | binary .jld2 |
:JSON | CTModelsJSON (JSON) | text .json |
using CTModels
using JSON # CTModelsJSON
using JLD2 # CTModelsJLD
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; nothing))
CTModels.objective!(pre, :min; lagrange=(t, x, u, v) -> u^2)
CTModels.time_dependence!(pre; autonomous=true)
ocp = CTModels.build(pre)
N = 101
T = collect(range(0.0, 1.0; length=N))
sol = CTModels.build_solution(ocp, T, hcat(cos.(T), -sin.(T)),
reshape(-cos.(T), N, 1), Float64[], zeros(N, 2);
objective=0.5, iterations=10, constraints_violation=1e-9,
message="ok", status=:optimal, successful=true)Both formats round-trip
The filename is a base path; the extension is appended automatically.
base = joinpath(tempdir(), "ctmodels_demo")
CTModels.export_ocp_solution(sol; filename=base, format=:JSON)
CTModels.export_ocp_solution(sol; filename=base, format=:JLD)
from_json = CTModels.import_ocp_solution(ocp; filename=base, format=:JSON)
from_jld = CTModels.import_ocp_solution(ocp; filename=base, format=:JLD)julia> CTModels.objective(from_json)
0.5
julia> CTModels.objective(from_jld)
0.5The reloaded solution behaves exactly like the original — its trajectories are again callables of time:
x = CTModels.state(from_json)julia> x(0.5)
2-element Vector{Float64}:
0.8775825618903728
-0.479425538604203Extension appending is idempotent
If filename already ends with the target extension (case-insensitively), it is left alone instead of being suffixed a second time:
base_json = base * ".json"
CTModels.export_ocp_solution(sol; filename=base_json, format=:JSON)julia> isfile(base_json)
true
julia> isfile(base_json * ".json")
falseImporting with the wrong model
The file does not contain the model, only a signature of it (dimensions, names, labels of the constraints, fixed or free times, criterion). import_ocp_solution compares it with the model you pass, before rebuilding the solution:
if the solution cannot belong to this model (dimension of the state, control or variable, number or labels of the constraints, fixed versus free initial or final time), an
IncorrectArgumentlists all the differences between the file and the model, and asks for the model the solution was computed from;if the differences cannot make the solution unusable (names of the components, criterion, type of cost, values of fixed times), a warning is logged and the solution is returned.
import_ocp_solution(other_model; filename="solution")
# IncorrectArgument: The solution in the file does not match the model
# Got state dimension = 2 (file)
# Expected state dimension = 1 (model)
# Hint Pass the model the solution was computed from.Files exported by older versions of CTModels carry no signature: only the dimensions and the time grids can then be checked, and an informational message says so.
How trajectories survive serialization
A trajectory is a function t -> x(t), which neither JSON nor JLD2 can store directly. CTModels works around this by resampling: before writing, each trajectory is evaluated on its time grid (the helpers of src/Solutions/discretization_utils.jl), and only the resulting arrays are persisted. On import, the arrays are wrapped back into interpolated callables — the same mechanism used when building a solution.
Low-level tags
The format keyword dispatches internally on the tags JLD2Tag and JSONTag, both subtypes of AbstractTag. The extension methods export_ocp_solution(CTModels.JLD2Tag(), sol; …) etc. are what each ext/ file implements; the format= wrapper is the public, dependency-free entry point.
Switching structure
The switching structure of a solution (switching times, phase labels, left and right limits, see Switching structure) is saved with it, in both formats. When the file is read back, the trajectories are rebuilt phase by phase, so they keep their jumps. A file without switching structure, including one written by an older version, is read with an EmptySwitchingModel.