Engee documentation

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.6625650.2036070.4354840.800465
20.06250.7043890.2164590.4629740.850994
30.1250.7488540.2301230.4921990.904713
40.18750.7961250.244650.523270.961823
50.250.846380.2600940.5563011.02254
60.31250.8998080.2765120.5914171.08709
70.3750.9566090.2939670.6287511.15571
80.43751.016990.3125230.668441.22866
90.51.081190.3322510.7106361.30622
100.56251.149440.3532250.7554951.38868
110.6251.2220.3755220.8031851.47634
120.68751.299140.3992270.8538861.56953
130.751.381150.4244280.9077881.66861
140.81251.468330.451220.9650921.77394
150.8751.561020.4797041.026011.88592
160.93751.659560.5099851.090782.00497
171.01.764320.5421781.159642.13153

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.2888180.4112990.5491920.356189
20.06250.307050.4372620.5838590.378673
30.1250.3264320.4648650.6207150.402577
40.18750.3470380.4942090.6598980.42799
50.250.3689450.5254060.7015540.455006
60.31250.3922350.5585720.745840.483729
70.3750.4169950.5938320.7929210.514264
80.43750.4433170.6313180.8429740.546727
90.50.4713020.671170.8961870.581239
100.56250.5010530.7135370.9527590.61793
110.6250.5326820.7585791.01290.656937
120.68750.5663070.8064651.076840.698406
130.750.6020550.8573731.144820.742493
140.81250.640060.9114951.217080.789362
150.8750.6804640.9690331.293910.839191
160.93750.7234181.03021.375590.892165
171.00.7690841.095231.462420.948483

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)