WARNING: using LinearAlgebra.rotate! in module Main conflicts with an existing identifier.
Lecture 16: Basic methods for IVPs
Initial-value problems
Basics of IVPs
Linear IVPs can be solved in terms of integrals using an integrating factor. However, often the necessary integrals cannot be computed in closed form.
Nonlinear IVPs are typically even more challenging.
Most often, there is no analytic formula available for the solution of an IVP.
Numerical solutions
Numerical techniques for IVPs do not use techniques you may have learned in Differential Equations to produce analytic formulas for the solution. Instead, they approximate power series, integrals, and derivatives locally and use iterative progression to advance the independent variable and approximate a solution.
While numerical techniques are approximative, they are far more robust than analytic techniques in the scope of problems they can solve.
Code
= (u,p,t) -> sin((t+u)^2) # defines du/dt, must include p -- not used here
f = -1.0 # initial value
u₀ = (0.0,4.0) # t interval tspan
(0.0, 4.0)
Code
= ODEProblem(f,u₀,tspan)
ivp = solve(ivp,Tsit5()); sol
Code
plot(sol,label="solution",legend=:bottom,xlabel="t",ylabel="u(t)",title="u' = sin((t+u)^2)")
Code
@show sol(1.0); # sol acts like a function
# under the hood, sol is defined by u values
[sol.t sol.u] # at t nodes and is interpolated
sol(1.0) = -0.7903205813665345
15×2 Matrix{Float64}:
0.0 -1.0
0.0867807 -0.93483
0.241035 -0.856617
0.464665 -0.805668
0.696832 -0.793614
1.00862 -0.789925
1.37461 -0.718601
1.70407 -0.476837
1.93572 -0.29033
2.17184 -0.294994
2.4843 -0.483948
2.69425 -0.654121
3.27049 -1.1783
3.62534 -1.51729
4.0 -1.88086
Code
scatter!(sol.t,sol.u,label="discrete values")
Existence
There are some simple IVPs that do not have solutions at all possible times.
Code
= (u,p,t) -> (t+u)^2
f = ODEProblem(f,1.0,(0.,1.))
ivp = solve(ivp,Tsit5()); sol
┌ Warning: At t=0.7853839417697203, dt was forced below floating point epsilon 1.1102230246251565e-16, and step error estimate = 42.16290621054672. Aborting. There is either an error in your model specification or the true solution is unstable (or the true solution can not be represented in the precision of Float64).
└ @ SciMLBase ~/.julia/packages/SciMLBase/XzPx0/src/integrator_interface.jl:623
Code
plot(sol,label="",xlabel="t",yaxis=(:log10,"u(t)"),title="Finite-time blowup")
Uniqueness
IVPs may also have more than one solution.
However, there is a condition we can check that guarantees that a unique solution exists.
Conditioning of first-order IVPs
We’ll focus on the effect of perturbations to \(u_0\) and not to \(f(t,u)\).
This theorem gives \(e^{L(b-a)}\) as an upper bound on the infinity norm absolute condition number of the solution with respect to perturbations to the IVP initial value.
However, like we’ve seen before, these bounds can be very rough.
Code
= range(0,3,length=800)
t = @. exp(t)*1'
u = @. exp(t)*0.7, @. exp(t)*1.3
lower, upper plot(t,u,l=:black,ribbon=(lower,upper), leg=:none,xlabel="t",ylabel="u(t)",
="Exponential divergence of solutions") title
This IVP has condition number equal to the upper bound provided in the theorem above.
But with \(u' = -u\), solutions actually get closer over time, so the maximum deviation of the solution is the same as the perturbation to the initial value.
Code
= @. exp(-t)*1
u = @. exp(-t)*0.7, @. exp(-t)*1.3
lower, upper plot(t,u,l=:bloack,ribbon=(lower,upper),leg=:none,xlabel="t",ylabel="u(t)",
="Exponential convergence of solutions") title
┌ Warning: Skipped line arg bloack.
└ @ Plots ~/.julia/packages/Plots/FFuQi/src/args.jl:1105
In this case, the condition number is one. Here, the exponentially growing bound is a terrible overestimate!
Euler’s Method
One way to estimate a solution to an IVP is to estimate its values at a finite collection of nodes: \[t_i = a + ih, \quad\quad h = \frac{b-a}{n}, \quad\quad i=0, \cdots, n.\] The number \(h\) is called the step size.
We’ll let \(\hat{u}(t)\) denote the exact solution of the IVP and our approximations at \(t_i\) be \(u_i \approx \hat{u}(t_i)\).
The model that defines Euler’s method is asking that the piecewise linear interpolant of the values \(u_0, \cdots, u_n\) have slopes equal to the derivatives of the exact solution at the point \((t_i,u_i)\), \[\frac{u_{i+1}-u_i}{h} = \frac{u_{i+1}-u_i}{t_{i+1}-t_i} = f(t_i,u_i).\]
Algorithm: Euler’s method for an IVP
Given the IVP \(u' = f(t,u), u(a) = u_0\) and the nodes \(t_i = a + ih, h = (b-a)/n\), iteratively compute the sequence \[u_{i+1} = u_i + h f(t_i, u_i), \quad\quad i = 0, \cdots, n-1.\]
Code
"""
euler(ivp,n)
Apply Euler's method to solve the given IVP using 'n' time steps.
Returns a vector of times and a vector of solution values.
"""
function euler(ivp,n)
# Time discretization
= ivp.tspan
a,b = (b-a)/n
h = [ a + i*h for i in 0:n ]
t
# Initial condition and output setup.
= fill(float(ivp.u0),n+1)
u
# The time stepping criterion
for i in 1:n
+1] = u[i] + h*ivp.f(u[i],ivp.p,t[i])
u[iend
return t,u
end
euler
This is an example of a one-step method.
Error
Suppose we have access to the exact value at \(t_i\), \(\hat{u}(t_i) = u_i\). How good is our \(u_{i+1}\) approximation to \(\hat{u}(t_{i+1})\)?
Answer:
We have
\[\begin{align*} \hat{u}(t_{i+1}) - [u_i + hf(t_i,u_i)] &= \hat{u}(t_{i+1}) - [\hat{u}(t_i) + hf(t_i,\hat{u}(t_i))] \\ &= [\hat{u}(t_i) + h\hat{u}'(t_i) + \frac{h^2}{2}\hat{u}''(t_i) + O(h^3)] - [\hat{u}(t_i) + h\hat{u}'(t_i)] \\ &= \frac{h^2}{2}\hat{u}''(t_i) + O(h^3) \end{align*}\]Local truncation error
Note that the local error in stepping from \(t_i\) to \(t_{i+1}\) is \(h\tau_{i+1}(h)\). We can make these local errors smaller by chopping \(h\) in half, but we would have to take twice as many steps. The important thing to measure is local error per unit step length, which is how \(\tau\) is defined.
Convergence
One might hope that the global error simply adds up all of the local errors over the steps taken, however each step causes a perturbation of the solution which then grows.
Code
= (u,p,t) -> sin((t+u)^2);
f = (0.0,4.0);
tspan = -1.0;
u0
= ODEProblem(f,u0,tspan) ivp
ODEProblem with uType Float64 and tType Float64. In-place: false timespan: (0.0, 4.0) u0: -1.0
Code
= euler(ivp,20)
t,u
plot(t,u,m=2,label="n=20",xlabel="t",ylabel="u(t)",
="Solution by Euler's method") title
Code
= euler(ivp,50)
t,u plot!(t,u,m=2,label="n=50")
Code
= solve(ivp,Tsit5(),reltol=1e-14,abstol=1e-14)
u_exact
plot!(u_exact,l=(2,:black),label="reference")
Code
= [ round(Int,5*10^k) for k in 0:0.5:3 ]
n = []
errs for n in n
= euler(ivp,n)
t,u push!(errs, norm(u_exact.(t)-u, Inf) )
end
Code
plot(n,errs,m=:o,label="results",xaxis=(:log10,"n"),
=(:log10,"Inf-norm global error"),
yaxis="Convergence of Euler's method")
titleplot!(n,0.05*(n/n[1]).^(-1),l=:dash,label="O(n^{-1})")