Skip to content

Save & load ​

A solution can be written to disk and read back later: to keep the result of an expensive solve, to restart from it in another session, or to share it with someone who does not have the code that produced it.

julia
using OptimalControl
using NLPModelsIpopt

t0 = 0
tf = 1
x0 = [-1, 0]

ocp = @def begin
    t ∈ [t0, tf], time
    x = (q, v) ∈ R², state
    u ∈ R, control
    x(t0) == x0
    x(tf) == [0, 0]
    ẋ(t) == [v(t), u(t)]
    0.5∫(u(t)^2) → min
end

sol = solve(ocp; display=false)

Two functions do the work, export_ocp_solution and import_ocp_solution. The format keyword chooses the file format:

julia
export_ocp_solution(sol; format=:JLD, filename="solution")
import_ocp_solution(ocp; format=:JLD, filename="solution")

import_ocp_solution takes the model as its positional argument: the file holds the values of the solution (grid, trajectories, multipliers, solver information), and the solution is rebuilt against the model. Pass the model the solution was computed from. With another model, the import fails with an error that does not say so yet (CTModels#435).

JLD2 ​

JLD2 writes a binary file, solution.jld2:

julia
using JLD2
export_ocp_solution(sol; format=:JLD, filename="solution")

sol_jld = import_ocp_solution(ocp; format=:JLD, filename="solution")
objective(sol_jld) == objective(sol)
true

JSON ​

JSON3 writes a text file, solution.json, which other languages and tools can read:

julia
using JSON3
export_ocp_solution(sol; format=:JSON, filename="solution")

sol_json = import_ocp_solution(ocp; format=:JSON, filename="solution")
objective(sol_json) == objective(sol)
true

What comes back ​

Both formats give back the same solution, value for value: the time grid, the state, control and costate on that grid, the variable, the multipliers, and the solver's status, message and iteration count. The model is the one you pass:

julia
tg = time_grid(sol)
time_grid(sol_json) == tg,
all(state(sol_json)(t) == state(sol)(t) for t in tg),
status(sol_json), iterations(sol_json) == iterations(sol),
model(sol_json) === ocp
(true, true, :first_order, true, true)

So the choice is about the file, not the content. JLD2 is compact (here about 35 kB) but readable from Julia only. JSON is about 3 times larger, and readable anywhere.

julia
filesize("solution.jld2"), filesize("solution.json")
(35121, 107934)

Reloading as an initial guess ​

A reloaded solution is a warm start, like a freshly computed one (see Initial guess for everything init accepts). Started from the solution, the solver stops at once, with 0 iterations. From scratch it needs 1 here, since this problem is a quadratic program; on a nonlinear problem the saving is larger.

julia
sol_warm = solve(ocp; init=sol_jld, display=false)
iterations(sol), iterations(sol_warm)
(1, 0)

File names ​

filename is the name of the file without its extension: .jld2 or .json is added, which is why the same filename="solution" works for both formats above. A name that already ends with the extension is used as it is:

julia
export_ocp_solution(sol; format=:JLD, filename="sol.jld2")
isfile("sol.jld2")
true

Without JLD2 or JSON3 ​

Both formats need their package. Without it, the call raises an error that names the package to load:

julia
julia> export_ocp_solution(sol; format=:JLD)
ERROR: ExtensionError
│
│  missing dependencies to export solutions to JLD2 format
│
│  Missing  JLD2
│
│  Hint     Run: using JLD2
└─

and the same with JSON3 for format=:JSON. A format other than :JLD and :JSON is rejected before any package is needed:

julia
julia> export_ocp_solution(sol; format=:XML)
IncorrectArgument → top-level scope, REPL[1]:2
│
│  Invalid export format specified
│
│  Got       format=XML
│  Expected  :JLD or :JSON
│
│  Context   export_ocp_solution - validating export format
│  Hint      Use format=:JLD for binary files or format=:JSON for text files
└─

See also ​