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.1787530.3817760.6129620.130768
20.06250.1900370.4058760.6516550.139023
30.1250.2020330.4314970.6927910.147798
40.18750.2147860.4587350.7365240.157128
50.250.2283440.4876930.7830170.167047
60.31250.2427580.5184790.8324450.177592
70.3750.2580830.5512080.8849930.188802
80.43750.2743740.5860030.9408580.20072
90.50.2916940.6229941.000250.213391
100.56250.3101070.662321.063390.226861
110.6250.3296830.7041291.130520.241182
120.68750.3504940.7485781.201880.256406
130.750.3726190.7958321.277750.272592
140.81250.396140.8460681.358410.2898
150.8750.4211470.8994761.444160.308093
160.93750.4477320.9562561.535320.327541
171.00.4759951.016621.632240.348218

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.6643080.05813390.5197820.524613
20.06250.7062430.06180360.5525940.557729
30.1250.7508250.0657050.5874760.592936
40.18750.798220.06985260.624560.630365
50.250.8486080.0742620.6639860.670156
60.31250.9021760.07894980.70590.71246
70.3750.9591260.08393350.750460.757434
80.43751.019670.08923180.7978330.805247
90.51.084040.09486460.8481960.856078
100.56251.152470.1008530.9017380.910118
110.6251.225220.1072190.958660.967569
120.68751.302560.1139871.019181.02865
130.751.384780.1211831.083511.09358
140.81251.47220.1288331.151911.16261
150.8751.565130.1369651.224621.236
160.93751.663930.1456111.301931.31403
171.01.768960.1548031.384111.39697

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)