Ordinary differential equation solvers in Julia
This post shows the use of the ordinary differential equation (abbreviated ODE) solvers
implemented by the DifferentialEquations.jl native package of Julia ecosystem.
The resolution techniques shown here are numerical and not analytical techniques, as this site deals with computation.
Moreover this post is published under the category of neural networks: although not all the techniques shown here
use technologies of deep learning, its purpose is to be proactive to the topic on the relationship between neural networks and differential equations.
The post extends the Rosetta Stele started in the post Ordinary differential equation solvers in Python
as it presents the same three problems there (and there solved in Python) and shows how the same problems are solved here in Julia.
The three problems are an equation of the first order, a system of two equations of the first order and an equation of the second order each with its own initial conditions
(or Cauchy's conditions, abbreviated IVP for Initial Value Problem) and below their solutions.
Of each problem the analytical solution is also known and this allows to compare the quality of the numerical solutions obtained.
All the various code fragments described in this post require Julia version 1.5.3 and the following packages: DifferentialEquations, StaticArrays e Plots.
To get the code see paragraph Download the complete code at the end of this post.
Conventions
In this post the conventions used are as follows:
- $t$ is the independent variable
- $x$ is the unknown function
- $y$ is the second unknown function in the case of systems of two equations
- $x$ and $y$ are intended to be functions of $t$, so $x=x(t)$ and $y=y(t)$, but the use of this compact notation, in addition to having a greater readability at a mathematical level makes it easier to "translate" the equation into code
- $x'$ is the first derivative of $x$ with respect to $t$ and of course $y'$ is the first derivative of $y$ with respect to $t$
- $x''$ is the second derivative of $x$ with respect to $t$ and of course $y''$ is the second derivative of $y$ with respect to $t$
First order ODE with IVP
Let the following Cauchy problem be given:
$$ \begin{equation}
\begin{cases}
x'+x=\sin t + 3 \cos 2t
\\
x(0)=0
\end{cases}
\end{equation} $$
whose analytical solution is:
$$ x(t) = \frac{1}{2} \sin t − \frac{1}{2} \cos t + \frac{3}{5} \cos 2t + \frac{6}{5} \sin 2t − \frac{1}{10}e^{-t} $$
verifiable online via Wolfram Alpha.
Implementations of the following resolutions require the differential equation to be written explicitly in the form $x'=F(x,t)$
and then it becomes:
$$ x'=\sin t + 3 \cos 2t - x$$
Solution with DifferentialEquations.jl package
DifferentialEquations.jl uses the ODEProblem
class
and the solve
function
to numerically solve an ordinary first order differential equation with initial value.
The explicit form of the above equation in Julia with DifferentialEquations is implemented as follows:
ode_fn(x,p,t) = sin(t) + 3.0 * cos(2.0 * t) - x
Below is an example of Julia code that compares the analytical solution with the numerical one obtained by
ODEProblem
and solve
:
using DifferentialEquations
using Plots
ode_fn(x,p,t) = sin(t) + 3.0 * cos(2.0 * t) - x
an_sol(t) = (1.0/2.0) * sin(t) - (1.0/2.0) * cos(t) +
(3.0/5.0) * cos(2.0*t) + (6.0/5.0) * sin(2.0*t) -
(1.0/10.0) * exp(-t)
t_begin=0.0
t_end=10.0
tspan = (t_begin,t_end)
x_init=0.0
prob = ODEProblem(ode_fn, x_init, tspan)
num_sol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8)
plot(num_sol.t, an_sol.(num_sol.t),
linewidth=2, ls=:dash,
title="ODE 1st order IVP solved by D.E. package",
xaxis="t", yaxis="x",
label="analytical",
legend=true)
plot!(num_sol,
linewidth=1,
label="numerical")
Here the link to the code on GitHub.
System of two ODEs of first order with IVP
Let the following system of two ordinary differential equations with initial values be given:
$$ \begin{equation}
\begin{cases}
x' + x − y = 0
\\
y' - 4x + y = 0
\\
x(0)=2
\\
y(0)=0
\end{cases}
\end{equation} $$
whose analytical solution is:
$$ \begin{equation}
\begin{cases}
x(t) = e^t + e^{-3 t}
\\
y(t) = 2 e^t - 2 e^{-3 t}
\end{cases}
\end{equation} $$
verifiable online via Wolfram Alpha.
Implementations of the following resolutions require the differential equations to be written explicitly in the forms $x'=F_1(x,y,t)$ and $y'=F_2(x,y,t)$
and then the two equations become:
$$ \begin{equation}
\begin{cases}
x' = y - x
\\
y' = 4x - y
\end{cases}
\end{equation} $$
and in matrix form:
$$\left[\begin{matrix} x' \\ y' \end{matrix} \right] = \left[\begin{matrix} -1 & 1 \\ 4 & -1 \end{matrix} \right] \left[\begin{matrix} x \\ y \end{matrix} \right] $$
Solution with DifferentialEquations.jl package
DifferentialEquations.jl uses the ODEProblem
clas
and the solve
function
to numerically solve a system of ordinary first order differential equations of first order with initial values.
The explicit form of the above pair of equations in Julia with DifferentialEquations is implemented as follows:
function ode_fn(du,u,p,t)
x, y = u
du[1] = y - x
du[2] = 4.0 * x - y
end
Alternatively, the system represented in matrix form in Julia with DifferentialEquations is implemented as follows:
A = @SMatrix [-1.0 1.0
4.0 -1.0]
function ode_fn(du,u,p,t)
du[[true, true]] = A * u
end
Note that the second argument is an array of size two, which is as much as the number of unknown functions.
Below is an example of Julia code that compares the analytical solution of the system with the numerical one obtained by
ODEProblem
and solve
:
using StaticArrays
using DifferentialEquations
using Plots
A = @SMatrix [-1.0 1.0
4.0 -1.0]
function ode_fn(du,u,p,t)
du[[true, true]] = A * u
end
an_sol_x(t) = exp(t) + exp(-3.0 * t)
an_sol_y(t) = 2.0 * exp(t) - 2.0 * exp(-3.0 * t)
t_begin=0.0
t_end=5
tspan = (t_begin,t_end)
x_init=2.0
y_init=0.0
prob = ODEProblem(ode_fn, [x_init, y_init], tspan)
num_sol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8)
x_num_sol = [u[1] for u in num_sol.u]
y_num_sol = [u[2] for u in num_sol.u]
plot(num_sol.t, an_sol_x.(num_sol.t),
linewidth=2, ls=:dash,
title="System of 2 ODEs 1st order IVP solved by D.E. package",
xaxis="t",
label="analytical x",
legend=true)
plot!(num_sol.t, an_sol_y.(num_sol.t),
linewidth=2, ls=:dash,
label="analytical y",
legend=true)
plot!(num_sol.t, x_num_sol,
linewidth=1,
label="numerical x")
plot!(num_sol.t, y_num_sol,
linewidth=1,
label="numerical y")
Here the link to the code on GitHub.Here the link for the matrix form variation on GitHub.
Second order ODE with IVP
Let the following Cauchy problem be given:
$$ \begin{equation}
\begin{cases}
x'' + x' + 2x = 0
\\
x(0)=1
\\
x'(0)=0
\end{cases}
\end{equation} $$
whose analytical solution is:
$$ x(t) = e^{\frac{-t}{2}} (\cos {\sqrt{7} \frac{t}{2}} + \frac{\sin {\sqrt{7} \frac{t}{2}}}{\sqrt{7}}) $$
verifiable online via Wolfram Alpha.
Implementations of the following resolutions require the differential equation of second order
to be written explicitly in the form as a system of two equations of first order as follows:
$$ \begin{equation}
\begin{cases}
y=x'
\\
y'=F(x, y, t)
\end{cases}
\end{equation} $$
and then the initial Cauchy problem is written equivalently in the following way:
$$ \begin{equation}
\begin{cases}
y = x'
\\
y'= -y - 2x = 0
\\
x(0)=1
\\
y(0)=0
\end{cases}
\end{equation} $$
Solution with DifferentialEquations.jl package
DifferentialEquations.jl uses the SecondOrderODEProblem
class
and the solve
function
to numerically solve a second order differential equation with initial values.
The explicit form of the above equation in Julia with DifferentialEquations is implemented as follows:
function ode_fn(dx,x,p,t)
-dx -2.0 * x
end
Below is an example of Julia code that compares the analytical solution with the numerical one obtained by
SecondOrderODEProblem
and solve
:
using DifferentialEquations
using Plots
function ode_fn(dx,x,p,t)
-dx -2.0 * x
end
an_sol(t) = exp(-t/2.0) *
(cos(sqrt(7.0) * t / 2.0) + sin(sqrt(7.0) * t / 2.0)/sqrt(7.0))
t_begin=0.0
t_end=12.0
tspan = (t_begin,t_end)
x_init=1.0
dxdt_init=0.0
prob = SecondOrderODEProblem(ode_fn, dxdt_init, x_init, tspan)
num_sol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8)
x_num_sol = [u[2] for u in num_sol.u]
plot(num_sol.t, an_sol.(num_sol.t),
linewidth=2, ls=:dash,
title="ODE 2nd order IVP solved by D.E. package",
xaxis="t", yaxis="x",
label="analytical",
legend=true)
plot!(num_sol.t, x_num_sol,
linewidth=1,
label="numerical")
Here the link to the code on GitHub.
Download of the complete code
The complete code is available at GitHub.
These materials are distributed under MIT license; feel free to use, share, fork and adapt these materials as you see fit.
Also please feel free to submit pull-requests and bug-reports to this GitHub repository or contact me on my social media channels available on the top right corner of this page.