using Plots
Runge-Kutta methods
We will implement in Julia different numerical methods to solve
\[ y'(t) = 1 - y(t) \]
\[ t \in [0,5] \qquad \mbox{ and } \qquad y(0) = 0 \]
Explicit Euler
"""
euler(f, t, y, h)
explicit euler method function that returns
``y^{n+1} = y^n + h \\cdot f(t^n, y^n)``
"""
function euler(f::Function, t::Float64, y::Float64, h::Float64)
+ h, y + h * f(t,y)
t end
euler
?euler
search: euler schedule NamedTuple promote_rule baremodule @NamedTuple
euler(f, t, y, h)
explicit euler method function that returns
\(y^{n+1} = y^n + h \cdot f(t^n, y^n)\)
Runge-Kutta 2nd order
"""
rk2(f, t, y, dt)
Runge-Kutta second order method function
"""
function rk2(f::Function, t::Float64, y::Float64, h::Float64)
= y + h/2 * f(t,y)
ỹ + h, y + h * f(t+h/2,ỹ)
t end
rk2
Runge-Kutta 4th order
"""
rk4(f::Function, t::Float64, y::Float64, dt::Float64)
Runge-Kutta fourth order method function
[Runge–Kutta methods on Wikipedia](https://en.wikipedia.org/wiki/Runge–Kutta_methods)
"""
function rk4(f::Function, t::Float64, y::Float64, dt::Float64)
= dt * f(t,y)
y₁ = dt * f(t+dt/2,y+y₁/2)
y₂ = dt * f(t+dt/2,y+y₂/2)
y₃ = dt * f(t+dt,y+y₃)
y₄
+dt, y+(y₁+2*y₂+2*y₃+y₄)/6
tend
rk4
Solver function
"""
solver(f::Function, df::Function, t₀::Float64,
y₀::Float64, dt::Float64, nsteps::Int64)
Solve numerically the equation ``y' = f(t, y)``
with `y(t₀)= y₀` and `nsteps` steps `h`
## Arguments
- `f::Function`: the function `f` of equation ``y' = f(t,y)``.
- `df::Function`: numerical method from (tⁿ,yⁿ) returns ``(t^{n+1},y^{n+1})``
"""
function solver(f::Function,
::Function,
df::Float64,
t₀::Float64, h::Float64, nsteps::Int64)
y₀
= zeros(Float64,nsteps)
t = similar(t)
y
1] = t₀
t[1] = y₀
y[
for i in 2:nsteps
= df(f,t[i-1],y[i-1], h)
t[i], y[i] end
t, y
end
solver
?solver
search: solver
solver(f::Function, df::Function, t₀::Float64,
y₀::Float64, dt::Float64, nsteps::Int64)
Solve numerically the equation \(y' = f(t, y)\)
with y(t₀)= y₀
and nsteps
steps h
Arguments
f::Function
: the functionf
of equation \(y' = f(t,y)\).df::Function
: numerical method from (tⁿ,yⁿ) returns \((t^{n+1},y^{n+1})\)
= 7, 5.0
nsteps, tfinal = 0., 0. t₀, x₀
(0.0, 0.0)
= tfinal / (nsteps-1)
dt f(t, x) = 1 - x
f (generic function with 1 method)
plot( solver(f, euler, t₀, x₀, dt, nsteps); marker = :o, label="euler")
plot!(solver(f, rk2, t₀, x₀, dt, nsteps); marker = :d, label="rk2")
plot!(solver(f, rk4, t₀, x₀, dt, nsteps); marker = :p, label="rk4")
= 0:0.1:5
t plot!(t, 1 .- exp.(-t); line = 3, label = "exact")
Callable object
We want to build a numerical method to solve the problem and then use it as a function and still call the solver function.
Runge-Kutta scheme can be built using Butcher tableau :
\[\begin{array}{c|cccc} c₁ & & & & \\ c₂ & a_{21} & & & \\ c₃ & a_{31} & a_{32} & & \\ c₄ & a_{41} & a_{42} & a_{43} & \\ \hline & b_1 & b_2 & b_3 & b_4 \\ \end{array}\]\[ \forall i = 1, \dotsc, q, \begin{cases}t_{n,i} &= t_n + c_i h_n, \\ y_{n,i} &= y_n + h_n \sum_{k = 1}^{i-1} a_{ik} p_{n,k}\\ p_{n,i} &= f(t_{n,i}, y_{n,i}) \end{cases} \]
\[ y_{n+1} = y_n + h_n \sum_{k = 1}^q b_k p_{n,k}. \]
mutable struct RungeKutta
:: Int64
q :: Array{Float64, 2}
a :: Array{Float64, 1}
b :: Array{Float64, 1}
c
:: Vector{Float64}
tn :: Vector{Float64}
yn :: Vector{Float64}
pn
function RungeKutta( a::Array{Float64,2}, b::Vector{Float64}, c::Vector{Float64})
= length(c)
q @assert ( length(c) == size(a,1))
@assert ( length(b) == size(a,2))
= zeros(Float64, q)
tn = zeros(Float64, q)
yn = zeros(Float64, q)
pn new( q, a, b, c, tn, yn, pn)
end
end
function (rk::RungeKutta)(f::Function, t::Float64, y::Float64, h::Float64)
for i = 1:rk.q
= t + rk.c[i] * h
rk.tn[i] = y + h * sum([rk.a[i,k]*rk.pn[k] for k = 1:i-1])
rk.yn[i] = f(rk.tn[i],rk.yn[i])
rk.pn[i] end
+ h, y + h * sum([rk.b[k]*rk.pn[k] for k in 1:rk.q ])
t
end
function solver(f::Function,
::RungeKutta,
df::Float64,
t₀::Float64, h::Float64, nsteps::Int64)
y₀
= zeros(Float64,nsteps)
t = similar(t)
y
1] = t₀
t[1] = y₀
y[
for i in 2:nsteps
= df(f,t[i-1],y[i-1], h)
t[i], y[i] end
t, y
end
solver (generic function with 2 methods)
= [ 0 0 0 0;
a 1/2 0 0 0;
0 1/2 0 0;
0 0 1 0]
= [1/6 ,1/3, 1/3, 1/6]
b = [0 ,1/2, 1/2, 1 ]
c
= RungeKutta(a, b, c) rk4_new
RungeKutta(4, [0.0 0.0 0.0 0.0; 0.5 0.0 0.0 0.0; 0.0 0.5 0.0 0.0; 0.0 0.0 1.0 0.0], [0.16666666666666666, 0.3333333333333333, 0.3333333333333333, 0.16666666666666666], [0.0, 0.5, 0.5, 1.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0])
= 0:0.1:5
t plot(t, 1 .- exp.(-t),label = "exact")
plot!(solver(f, rk4_new, t₀, x₀, dt, nsteps), marker = :o, label="rk4_new")
= [ 0 0 0 0;
a 1/3 0 0 0;
-1/3 1 0 0;
1 -1 1 0]
4×4 Matrix{Float64}:
0.0 0.0 0.0 0.0
0.333333 0.0 0.0 0.0
-0.333333 1.0 0.0 0.0
1.0 -1.0 1.0 0.0
= [1/8, 3/8, 3/8, 1/8]
b = [0, 1/3, 2/3, 1]
c = RungeKutta(a, b, c)
rk4_38 plot!(solver(f, rk4_38, t₀, x₀, dt, nsteps), marker = :r, label="rk4_38")
Reference:Ordinary Differential Equation Solvers: Runge-Kutta Methods by Christina Lee
Creating expressions via interpolation
Create a solver function with the method choosen at initialization.
macro add(x, y)
return :($x + $y)
end
@add (macro with 1 method)
@add 2 3 ## or @add(2, 3)
5
macro abs(x)
return :( $x > 0 ? $x : -$x)
end
@abs(-2), @abs(2)
(2, 2)
macro make_method( meth)
return quote
function (f::Function, t₀::Float64,
::Float64, dt::Float64, nsteps::Int64)
x₀
= zeros(Float64,nsteps)
t = zeros(Float64,nsteps)
x
1] = t₀
t[1] = x₀
x[
for i in 2:nsteps
= $meth(f,t[i-1],x[i-1], dt)
t[i], x[i] end
return t, x
end
end
end
@make_method (macro with 1 method)
= @make_method rk4 rk4_solver
#17 (generic function with 1 method)
plot(rk4_solver(f, t₀, x₀, dt, nsteps))
using DifferentialEquations
using Plots
f(y,p,t) = 1.0 - y
= 0.0
y₀ = (0.0,5.0)
t = ODEProblem(f,y₀,t)
prob = solve(prob,Euler(), dt=1.0)
euler_solution = solve(prob, RK4(), dt=1.0)
rk4_solution plot(euler_solution,label="Euler")
plot!(rk4_solution,label="RK4")
plot!(1:0.1:5, t->1. - exp(-t),lw=3,ls=:dash,label="True Solution!")