Skip to content

Save & load

Persist a solution to disk and read it back — for expensive solves you don't want to redo, warm starts across sessions, or sharing a result with someone who doesn't 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 cover the whole API — export_ocp_solution and import_ocp_solution — each with a format= keyword picking the backend:

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

import_ocp_solution takes the model, not a filename, as its positional argument — the solution is reconstructed against it, so the model has to be given explicitly rather than inferred from the file.

JLD2

Exact round-trip of the underlying Julia values — the format to reach for by default.

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

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

JSON3

Portable, human-readable, at the cost of not being Julia-specific:

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

Which format

JLD2 round-trips Julia values exactly — the same types come back out. JSON3 is text, portable across languages and tools, but floating-point round-tripping through it isn't guaranteed bit-exact in general the way JLD2's is — use JLD2 unless portability specifically matters.

Reloading as an initial guess

A reloaded solution works as a warm start exactly like a freshly-computed one — see Initial guess for everything else init= accepts:

julia
sol_warm = solve(ocp; init=sol_reloaded, display=false)

Without the extension

Both functions are extensions — nothing works until the matching package is loaded:

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

Same pattern for format=:JSON, naming JSON3 instead. An invalid format is caught before either extension is even 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
└─

Filenames have no extension of their own

filename is a base name — the extension is added automatically (.jld2 or .json), which is why the same filename= works unchanged for both formats above. Including the extension yourself doesn't get stripped — it gets doubled:

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

See also