I/O: Saving and Loading Solution Data

The ability to save and load solutions is important for handling large datasets and analyzing the results over multiple Julia sessions. This page explains the existing functionality for doing so.

Tabular Data: IterableTables

An interface to IterableTables.jl is provided. This IterableTables link allows you to use a solution type as the data source to convert to other tabular data formats. For example, let's solve a 4x2 system of ODEs and get the DataFrame:

using OrdinaryDiffEq, DataFrames
f_2dlinear = (du, u, p, t) -> du .= 1.01u;
tspan = (0.0, 1.0)
prob = ODEProblem(f_2dlinear, rand(2, 2), tspan);
sol = solve(prob, Euler(); dt = 1 // 2^(4));
df = DataFrame(sol)
17×5 DataFrame
Rowtimestampvalue1value2value3value4
Float64Float64Float64Float64Float64
10.00.1634050.1317240.9676180.886117
20.06250.173720.1400391.02870.942054
30.1250.1846860.1488791.093641.00152
40.18750.1963440.1582771.162671.06474
50.250.2087380.1682681.236061.13195
60.31250.2219150.178891.314091.20341
70.3750.2359230.1901821.397041.27937
80.43750.2508160.2021881.485231.36013
90.50.2666480.2149511.578991.44599
100.56250.2834810.228521.678661.53727
110.6250.3013750.2429451.784631.63431
120.68750.32040.2582811.897281.73748
130.750.3406250.2745852.017051.84715
140.81250.3621270.2919182.144371.96376
150.8750.3849860.3103452.279742.08772
160.93750.4092880.3299362.423642.21951
171.00.4351250.3507632.576642.35961

If we set syms in the DiffEqFunction, then those names will be used:

f = ODEFunction(f_2dlinear, syms = [:a, :b, :c, :d])
prob = ODEProblem(f, rand(2, 2), (0.0, 1.0));
sol = solve(prob, Euler(); dt = 1 // 2^(4));
df = DataFrame(sol)
17×5 DataFrame
Rowtimestampabcd
Float64Float64Float64Float64Float64
10.00.6209230.05126880.9621710.553494
20.06250.6601190.05450521.022910.588433
30.1250.7017890.05794581.087480.625578
40.18750.746090.06160361.156130.665068
50.250.7931870.06549231.229110.70705
60.31250.8432570.06962661.306690.751683
70.3750.8964870.07402171.389180.799133
80.43750.9530780.07869441.476870.849578
90.51.013240.08366191.57010.903208
100.56251.07720.08894311.669210.960222
110.6251.14520.09455761.774581.02084
120.68751.217490.1005271.88661.08528
130.751.294340.1068722.005691.15378
140.81251.376050.1136192.13231.22662
150.8751.462910.1207912.26691.30405
160.93751.555260.1284162.411.38637
171.01.653440.1365222.562131.47388

Many modeling frameworks will automatically set syms for this feature. Additionally, this data can be saved to a CSV:

using CSV
CSV.write("out.csv", df)
"out.csv"

JLD2 and BSON.jl

JLD2.jl and BSON.jl will work with the full solution type if you bring the required functions back into scope before loading. For example, if we save the solution:

sol = solve(prob, Euler(); dt = 1 // 2^(4))
using JLD2
@save "out.jld2" sol

then we can get the full solution type back, interpolations and all, if we load the dependent functions first:

# New session
using JLD2
using OrdinaryDiffEq
JLD2.@load "out.jld2" sol
1-element Vector{Symbol}:
 :sol

The example with BSON.jl is:

sol = solve(prob, Euler(); dt = 1 // 2^(4))
using BSON
bson("test.bson", Dict(:sol => sol))
# New session
using OrdinaryDiffEq
using BSON
# BSON.load("test.bson") # currently broken: https://github.com/JuliaIO/BSON.jl/issues/109

If you load it without the DE function then for some algorithms the interpolation may not work, and for all algorithms you'll need at least a solver package or SciMLBase.jl in scope in order for the solution interface (plot recipes, array indexing, etc.) to work. If none of these are put into scope, the solution type will still load and hold all of the values (so sol.u and sol.t will work), but none of the interface will be available.

JLD

Don't use JLD. It's dead. Julia types can be saved via JLD.jl. However, they cannot save types which have functions, which means that the solution type is currently not compatible with JLD.

using JLD
JLD.save("out.jld", "sol", sol)