Differential Equations.jl
Contents
Differential Equations.jl¶
DifferentialEquations.jl is likely the most sophisticated differential equation solver suite available in any language. There are more than 200 methods implemented that are capable of solving all types of differential equations including
Discrete equations (function maps, discrete stochastic (Gillespie/Markov) simulations)
Ordinary differential equations (ODEs)
Split and Partitioned ODEs (Symplectic integrators, IMEX Methods)
Stochastic ordinary differential equations (SODEs or SDEs)
Stochastic differential-algebraic equations (SDAEs)
Random differential equations (RODEs or RDEs)
Differential algebraic equations (DAEs)
Delay differential equations (DDEs)
Neutral, retarded, and algebraic delay differential equations (NDDEs, RDDEs, and DDAEs)
Stochastic delay differential equations (SDDEs)
Experimental support for stochastic neutral, retarded, and algebraic delay differential equations (SNDDEs, SRDDEs, and SDDAEs)
Mixed discrete and continuous equations (Hybrid Equations, Jump Diffusions)
(Stochastic) partial differential equations ((S)PDEs) (with both finite difference and finite element methods)
See documentation for more.
Install¶
DifferentialEquations.jl is not installed by default when Julia is installed. To ensure it’s installed, either run
using Pkg; Pkg.add("DifferentialEquations")
from the Julia REPL or enter the package manager with ]
and then run
add DifferentialEquations
Example - Robertson Equations¶
Define the system of equations.
function rober!(du,u,p,t)
y₁,y₂,y₃ = u
k₁,k₂,k₃ = p
du[1] = -k₁*y₁+k₃*y₂*y₃
du[2] = k₁*y₁-k₂*y₂^2-k₃*y₂*y₃
du[3] = k₂*y₂^2
nothing
end
rober! (generic function with 1 method)
Define the ODE problem.
using DifferentialEquations
u₀ = [1.0,0.0,0.0]
tspan = (0.0,1e5)
params = [0.04,3e7,1e4]
ode_prob = ODEProblem(rober!,u₀,tspan,params);
Solve and plot
using Plots
ode_sol = solve(ode_prob)
plot(ode_sol, tspan=(1e-6,1e5), xscale=:log10, layout=(3,1))
InterruptException:
Stacktrace:
[1] update_child_bboxes!
@ ~/.julia/packages/Plots/cPJQu/src/layouts.jl:328 [inlined]
[2] prepare_output(plt::Plots.Plot{Plots.GRBackend})
@ Plots ~/.julia/packages/Plots/cPJQu/src/plot.jl:241
[3] show(io::IOBuffer, m::MIME{Symbol("image/svg+xml")}, plt::Plots.Plot{Plots.GRBackend})
@ Plots ~/.julia/packages/Plots/cPJQu/src/output.jl:204
[4] sprint(::Function, ::MIME{Symbol("image/svg+xml")}, ::Vararg{Any}; context::Nothing, sizehint::Int64)
@ Base ./strings/io.jl:114
[5] sprint
@ ./strings/io.jl:108 [inlined]
[6] _ijulia_display_dict(plt::Plots.Plot{Plots.GRBackend})
@ Plots ~/.julia/packages/Plots/cPJQu/src/ijulia.jl:47
[7] display_dict(plt::Plots.Plot{Plots.GRBackend})
@ Plots ~/.julia/packages/Plots/cPJQu/src/init.jl:92
[8] #invokelatest#2
@ ./essentials.jl:716 [inlined]
[9] invokelatest
@ ./essentials.jl:714 [inlined]
[10] execute_request(socket::ZMQ.Socket, msg::IJulia.Msg)
@ IJulia ~/.julia/packages/IJulia/e8kqU/src/execute_request.jl:112
[11] #invokelatest#2
@ ./essentials.jl:716 [inlined]
[12] invokelatest
@ ./essentials.jl:714 [inlined]
[13] eventloop(socket::ZMQ.Socket)
@ IJulia ~/.julia/packages/IJulia/e8kqU/src/eventloop.jl:8
[14] (::IJulia.var"#15#18")()
@ IJulia ./task.jl:423
User defined Jacobian¶
The Jacobian \(J\) of a function \(f(\mathbf{x})\) is
function rober_jac!(J,u,p,t)
y₁,y₂,y₃ = u
k₁,k₂,k₃ = p
J[1,1] = k₁ * -1
J[2,1] = k₁
J[3,1] = 0
J[1,2] = y₃ * k₃
J[2,2] = y₂ * k₂ * -2 + y₃ * k₃ * -1
J[3,2] = y₂ * 2 * k₂
J[1,3] = k₃ * y₂
J[2,3] = k₃ * y₂ * -1
J[3,3] = 0
nothing
end
Define the function and problem
ode_fun_jac = ODEFunction(rober!, jac=rober_jac!)
ode_prob_jac = ODEProblem(ode_fun_jac, u₀, tspan, params);
Solve and plot, this time specifying the solver.
ode_sol_jac = solve(ode_prob_jac, Rosenbrock23())
plot(ode_sol_jac, tspan=(1e-6,1e5), xscale=:log10, layout=(3,1))
Benchmark the two solves.
using BenchmarkTools;
@btime solve(ode_prob, Rosenbrock23());
@btime solve(ode_prob_jac, Rosenbrock23());
Automatic differentiation Jacobian¶
using ForwardDiff
function rober_jac_auto!(J,u,p,t)
out = zero(u)
ForwardDiff.jacobian!(J, (y, x) -> rober!(y, x, p, t), out, u)
nothing
end
ode_fun_ad_jac = ODEFunction(rober!, jac=rober_jac_auto!)
ode_prob_ad_jac = ODEProblem(ode_fun_ad_jac, u₀, tspan, params);
ode_sol_ad_jac = solve(ode_prob_ad_jac, Rosenbrock23())
plot(ode_sol_ad_jac, tspan=(1e-6,1e5), xscale=:log10, layout=(3,1))
Benchmark all three solves
@btime solve(ode_prob, Rosenbrock23());
@btime solve(ode_prob_jac, Rosenbrock23());
@btime solve(ode_prob_ad_jac, Rosenbrock23());
Solve with StaticArrays¶
This approach can work well for small systems (< 20 ODEs)
using StaticArrays
function rober_sa(u, p, t)
y₁,y₂,y₃ = u
k₁,k₂,k₃ = p
dy₁ = -k₁*y₁+k₃*y₂*y₃
dy₂ = k₁*y₁-k₂*y₂^2-k₃*y₂*y₃
dy₃ = k₂*y₂^2
SA[dy₁, dy₂, dy₃]
end
function rober_jac_auto_sa(u,p,t)
ForwardDiff.jacobian(x -> rober_sa(x, p, t), u)
end
ode_fun_sa = ODEFunction(rober_sa, jac=rober_jac_auto_sa, jac_prototype=StaticArray)
u₀ = SA[1.0,0.0,0.0]
ode_prob_sa = ODEProblem(ode_fun_sa, u₀,tspan,params);
@btime solve(ode_prob, Rosenbrock23());
@btime solve(ode_prob_jac, Rosenbrock23());
@btime solve(ode_prob_ad_jac, Rosenbrock23());
@btime solve(ode_prob_sa, Rosenbrock23());
Mass matrix ODEs¶
Normally we seek to write our ODEs in the form
However, sometimes it’s common to have an equation in the form
where \(M\) is known as the mass matrix. We can rewrite the Robertson equations as
function rober_dae!(du,u,p,t)
y₁,y₂,y₃ = u
k₁,k₂,k₃ = p
du[1] = -k₁*y₁ + k₃*y₂*y₃
du[2] = k₁*y₁ - k₃*y₂*y₃ - k₂*y₂^2
du[3] = y₁ + y₂ + y₃ - 1
nothing
end
M = [1. 0 0
0 1. 0
0 0 0]
u₀ = [1.0,0.0,0.0]
ode_fun_mm = ODEFunction(rober_dae!, mass_matrix=M)
ode_prob_mm = ODEProblem(ode_fun_mm, u₀, tspan, params)
ode_sol_mm = solve(ode_prob_mm,Rodas5(),reltol=1e-8,abstol=1e-8)
plot(ode_sol_mm, xscale=:log10, tspan=(1e-6, 1e5), layout=(3,1))
Implicitly defined ODEs/DAEs¶
In this example we’ll write the Robertson equation in an implicit (also known as residual) form
Rewriting the Robertson equations in this form we have
function rober_implicit_dae!(out, du, u, p, t)
dy₁, dy₂, dy₃ = du
y₁, y₂, y₃ = u
k₁, k₂, k₃ = p
out[1] = -k₁*y₁ + k₃*y₂*y₃ - dy₁
out[2] = k₁*y₁ - k₃*y₂*y₃ - k₂*y₂^2 - dy₂
out[3] = y₁ + y₂ + y₃ - 1
nothing
end
du₀ = [-0.04, 0.04, 0.0]
implicit_dae_prob = DAEProblem(rober_implicit_dae!, du₀, u₀,tspan, params,
differential_vars=[true,true,false]);
implicit_dae_sol = solve(implicit_dae_prob)
plot(implicit_dae_sol, xscale=:log10, tspan=(1e-6, 1e5), layout=(3,1))
Implicit ODE/DAE with Jacobian¶
The Jacobian of an implicit ODE/DAE written in the form
is
where \(\gamma\) is supplied by the solver.
function rober_implicit_dae_jacobian!(J,du,u,p,gamma,t)
#J = gamma*df/d(du) + df/du
dy₁, dy₂, dy₃ = du
y₁, y₂, y₃ = u
k₁, k₂, k₃ = p
J[1,1] = -gamma - k₁
J[1,2] = k₃*y₃
J[1,3] = k₃*y₂
J[2,1] = k₁
J[2,2] = -gamma - k₃*y₃ - 2*k₂*y₂
J[2,3] = -k₃*y₂
J[3,1] = 1
J[3,2] = 1
J[3,3] = 1
end
dae_implicit_fun_jac = DAEFunction(rober_implicit_dae!, jac=rober_implicit_dae_jacobian!)
implicit_dae_prob_jac = DAEProblem(dae_implicit_fun_jac, du₀, u₀, tspan, params,
differential_vars=[true,true,false]);
implicit_dae_sol_jac = solve(implicit_dae_prob_jac)
plot(implicit_dae_sol_jac, xscale=:log10, tspan=(1e-6, 1e5), layout=(3,1))
Automatic differentiation Jacobian (StaticArrays)¶
function rober_implicit_dae(du, u, p, t)
dy₁, dy₂, dy₃ = du
y₁, y₂, y₃ = u
k₁, k₂, k₃ = p
SA[-k₁*y₁ + k₃*y₂*y₃ - dy₁,
k₁*y₁ - k₃*y₂*y₃ - k₂*y₂^2 - dy₂,
y₁ + y₂ + y₃ - 1]
end
function rober_implicit_dae_ad_jacobian!(J,du,u,p,gamma,t)
#J = gamma*df/d(du) + df/du
ForwardDiff.jacobian!(J, x -> rober_implicit_dae(x, u, p, t), du)
J .*= gamma
J .+= ForwardDiff.jacobian(x -> rober_implicit_dae(du, x, p, t), u)
nothing
end
u₀ = SA[1.0, 0.0, 0.0]
du₀ = SA[-0.04, 0.04, 0.0]
dae_implicit_fun_ad_jac_sa = DAEFunction(rober_implicit_dae,
jac=rober_implicit_dae_ad_jacobian!,
jac_prototype=StaticArray)
implicit_dae_prob_ad_jac_sa = DAEProblem(dae_implicit_fun_ad_jac_sa, du₀, u₀, tspan, params,
differential_vars=[true,true,false]);
implicit_dae_sol_ad_jac_sa = solve(implicit_dae_prob_ad_jac_sa)
plot(implicit_dae_sol_ad_jac_sa, xscale=:log10, tspan=(1e-6, 1e5), layout=(3,1))
Benchmarks
@btime solve(implicit_dae_prob)
@btime solve(implicit_dae_prob_jac);
@btime solve(implicit_dae_prob_ad_jac_sa);