In [1]:
using Plots,LaTeXStrings
default(markersize=3,linewidth=1.5)
using LinearAlgebra,DifferentialEquations
include("FNC.jl");
We solve the advection equation on a domain with periodic end conditions. Our approach is the method of lines.
In [2]:
x,Dx,Dxx = FNC.diffper(300,[-4,4])
f = (u,c,t) -> -c*(Dx*u);
The following initial condition isn't mathematically periodic, but the deviation is less than machine precision. We specify RK4 as the solver.
In [3]:
u_init = @. 1 + exp(-3*x^2)
IVP = ODEProblem(f,u_init,(0.,3.),2.)
sol = solve(IVP,RK4());
An animation shows the solution nicely. The bump moves with speed 2 to the right, reentering on the left as it exits to the right because of the periodic conditions.
In [4]:
an = @animate for t = LinRange(0,3,120)
plot(x,sol(t),
xaxis=(L"x"),yaxis=([1,2],L"u(x,t)"),
title="Advection equation, t=$(round(t,digits=2))",leg=:none )
end
gif(an,"advection.gif")
Out[4]:
We solve for traffic flow using periodic boundary conditions. The following are parameters and a function relevant to defining the problem.
In [5]:
rho_c = 1080; rho_m = 380; q_m = 10000;
Q0prime(rho) = q_m*4*rho_c^2*(rho_c-rho_m)*rho_m*(rho_m-rho)/(rho*(rho_c-2*rho_m) + rho_c*rho_m)^3;
Here we create a discretization on $m=800$ points.
In [6]:
x,Dx,Dxx = FNC.diffper(800,[0,4]);
Next we define the ODE resulting from the method of lines.
In [7]:
ode = (rho,ep,t) -> -Q0prime.(rho).*(Dx*rho) + ep*(Dxx*rho);
Our first initial condition has moderate density with a small bump.
In [8]:
rho_init = @. 400 + 10*exp(-20*(x-3)^2)
IVP = ODEProblem(ode,rho_init,(0.,1.),0.02)
sol = solve(IVP,alg_hints=[:stiff]);
plot(x,[sol(t) for t=0:.2:1],label=["t=$t" for t=0:.2:1],
xaxis=(L"x"),yaxis=("car density"),title="Traffic flow")
Out[8]:
The bump slowly moves backward on the roadway, spreading out and gradually fading away due to the presence of diffusion.
In [9]:
an = @animate for t = LinRange(0,0.9,91)
plot(x,sol(t),
xaxis=(L"x"),yaxis=([400,410],"density"),
title="Traffic flow, t=$(round(t,digits=2))",leg=:none )
end
gif(an,"traffic1.gif")
Out[9]:
Now we use an initial condition with a larger bump.
In [10]:
rho_init = @. 400 + 80*exp(-16*(x-3)^2)
IVP = ODEProblem(ode,rho_init,(0.,0.5),0.02)
sol = solve(IVP,alg_hints=[:stiff]);
In [11]:
an = @animate for t = LinRange(0,0.5,101)
plot(x,sol(t),
xaxis=(L"x"),yaxis=([400,480],"density"),
title="A traffic jam, t=$(round(t,digits=2))",leg=:none )
end
gif(an,"traffic2.gif")
Out[11]:
In this case the density bump travels backward along the road. It also steepens on the side facing the incoming traffic and decreases much more slowly on the other side. A motorist would experience this as an abrupt increase in density, followed by a much more gradual decrease in density and resulting gradual increase in speed. (You also see some transient, high-frequency oscillations. These are caused by instabilities, as we discuss in simpler situations later in this chapter.)
We set up a test problem with velocity $c=2$ and periodic end conditions.
In [12]:
x,Dx = FNC.diffper(400,[0 1])
uinit = @. exp(-80*(x-0.5)^2);
For this problem we use RK4
, an explicit method.
In [13]:
ode = (u,c,t) -> -c*(Dx*u);
IVP = ODEProblem(ode,uinit,(0.,2.),2.)
sol = solve(IVP,RK4());
In [14]:
t = 2*(0:80)/80
u = hcat([sol(t) for t in t]...)
contour(x,t,u',color=:viridis,
xaxis=(L"x"),yaxis=(L"t"),title="Linear advection",leg=:none)
Out[14]:
You can see the hump traveling rightward at constant speed, traversing the domain once for each integer multiple of $t=1/2$. We note the average time step that was chosen:
In [15]:
avgtau1 = sum(diff(sol.t))/(length(sol.t)-1)
Out[15]:
We cut $h$ by a factor of two and solve again.
In [16]:
x,Dx = FNC.diffper(800,[0 1])
uinit = @. exp(-80*(x-0.5)^2);
IVP = ODEProblem(ode,uinit,(0.,2.),2.)
sol = solve(IVP,RK4());
The CFL condition suggests that the time step should be cut by a factor of two also.
In [17]:
avgtau2 = sum(diff(sol.t))/(length(sol.t)-1)
@show ratio = avgtau1 / avgtau2
Out[17]:
We set up advection over $[0,1]$ with velocity $c=-1$. This puts the right-side boundary in the upwind direction.
In [18]:
n = 100
x,Dx = FNC.diffmat2(n,[0 1])
uinit = @. exp(-80*(x-0.5)^2);
First we try imposing $u=0$ at the right boundary, by appending that value to the end of the vector before multiplying by the differentiation matrix.
In [19]:
trunc = u -> u[1:n]; extend = v -> [v;0];
ode = (v,c,t) -> -c*trunc(Dx*extend(v));
IVP = ODEProblem(ode,uinit[1:n],(0.,1.),-1)
sol = solve(IVP,RK4());
In [20]:
plot(x[1:n],sol.t,sol[:,:]',
xlabel=L"x",ylabel=L"t",title="Inflow boundary condition",leg=:none)
Out[20]:
The data from the initial condition propagates out of the left edge. Because only zero is coming in from the upwind direction, the solution remains zero thereafter.
Now we try $u=0$ imposed at the left boundary.
In [21]:
trunc = u -> u[2:n+1]; extend = v -> [0;v];
sol = solve(IVP,RK4());
plot(x[1:n],sol.t,sol[:,:]',
xlabel=L"x",ylabel=L"t",title="Outflow boundary condition",leg=:none)
Out[21]:
Everything seems OK until the data begins to interact with the inappropriate boundary condition. The resulting "reflection" is entirely wrong for advection from right to left.
For $c=1$ we get the eigenvalues:
In [22]:
x,Dx = FNC.diffper(40,[0,1])
lambda = eigvals(Dx);
scatter(real(lambda),imag(lambda),xlim=[-40,40],ylim=[-40,40],
title="Eigenvalues for pure advection",leg=:none)
Out[22]:
Let's choose a time step of $\tau=0.1$ and compare to the stability regions of the Euler and backward Euler time steppers.
In [23]:
zc = @.exp(1im*2pi*(0:360)/360); # points on |z|=1
z = zc .- 1; # shift left by 1
plot(Shape(real(z),imag(z)),color=RGB(.8,.8,1),layout=(1,2),subplot=1)
scatter!(real(0.1*lambda),imag(0.1*lambda),subplot=1,
xlim=[-5,5],ylim=[-5,5],aspect_ratio=1,title="Euler",leg=:none)
z = zc .+ 1; # shift right by 1
plot!(Shape([-6,6,6,-6],[-6,-6,6,6]),color=RGB(.8,.8,1),subplot=2)
plot!(Shape(real(z),imag(z)),color=:white,subplot=2)
scatter!(real(0.1*lambda),imag(0.1*lambda),subplot=2,
xlim=[-5,5],ylim=[-5,5],aspect_ratio=1,title="Backward Euler",leg=:none)
Out[23]:
In the Euler case it's clear that no real value of $\tau>0$ is going to make all (or even any) of the $\tau\lambda_j$ fit within the stability region. Hence Euler will never produce bounded solutions to this discretization of the advection equation. The A-stable backward Euler time stepping tells the exact opposite story; it will be absolutely stable regardless of $\tau$.
The eigenvalues of advection--diffusion are near-imaginary for $\epsilon\approx 0$ and more negative-real for increasing values of $\epsilon$.
In [24]:
plot([],[],label="",leg=:left,aspect_ratio=1)
x,Dx,Dxx = FNC.diffper(40,[0,1]);
tau = 0.1
for ep = [0.001 0.01 0.05]
lambda = eigvals(-Dx + ep*Dxx)
scatter!(real(tau*lambda),imag(tau*lambda),label="\\epsilon=$ep")
end
title!("Eigenvalues for advection-diffusion")
Out[24]:
Deleting the first row and column places all the eigenvalues of the discretization into the left half of the complex plane.
In [25]:
x,Dx,Dxx = FNC.diffcheb(40,[0,1]);
A = -Dx[2:end,2:end]; # leave out first row and column
lambda = eigvals(A)
scatter(real(lambda),imag(lambda),
xlim=[-300,100],title="Eigenvalues of advection with zero inflow",leg=:none,aspect_ratio=1)
Out[25]:
Note that the rightmost eigenvalues have real part at most
In [26]:
maximum( real(lambda) )
Out[26]:
Consequently all solutions decay exponentially to zero as $t\to\infty$. This matches the intuition of a flow with nothing at the inlet: eventually everything flows out of the domain.
We solve the wave equation (in Maxwell form) for speed $c=2$, with homogeneous Dirichlet conditions on the first variable.
In [27]:
m = 200
x,Dx = FNC.diffmat2(m,[-1,1]);
The boundary values of $u$ are given to be zero, so they are not unknowns in the ODEs we solve. Instead they are added or removed as necessary.
In [28]:
trunc = u -> u[2:m];
extend = v -> [0;v;0];
The following function computes the time derivative of the system at interior points.
In [29]:
dwdt = function(w,c,t)
u = extend(w[1:m-1])
z = w[m:2*m]
dudt = Dx*z
dzdt = c^2*(Dx*u)
return [ trunc(dudt); dzdt ]
end;
Our initial condition is a single hump for $u$.
In [30]:
u_init = @.exp(-100*(x)^2)
z_init = -u_init
w_init = [ trunc(u_init); z_init ];
Because the wave equation is hyperbolic, we can use a nonstiff explicit solver.
In [31]:
IVP = ODEProblem(dwdt,w_init,(0.,2.),2)
sol = solve(IVP,RK4());
We extract the original $u$ and $z$ variables from the results, adding in the zeros at the boundaries for $u$.
In [32]:
n = length(sol.t)-1
U = [ zeros(1,n+1); sol[1:m-1,:]; zeros(1,n+1) ];
Z = sol[m:2*m,:];
We plot the results for the original $u$ variable.
In [33]:
an = @animate for (i,t) = enumerate(sol.t)
plot(x,U[:,i],layout=(1,2),subplot=1,
xaxis=(L"x"),yaxis=([-1,1],L"u(x,t)"),
title="Wave equation, t=$(round(t,digits=3))",leg=:none )
plot!(x,sol.t,U',subplot=2,xlabel=L"x",ylabel=L"t",title="Space-time view",leg=:none)
end
gif(an,"wave.gif")
Out[33]:
The original hump breaks into two pieces of different amplitudes. Each travels with speed $c=2$, and they pass through one another without interference. When a hump encounters a boundary, it is perfectly reflected, but with inverted shape. At time $t=2$ the exact solution looks just like the initial condition.
We now use a wave speed that is discontinuous at $x=0$.
In [34]:
m = 120
x,Dx = FNC.diffcheb(m,[-1,1]);
c = @. 1 + (sign(x)+1)/2
trunc = u -> u[2:m];
extend = v -> [0;v;0];
This function computes the time derivative of the method-of-lines system.
In [35]:
dwdt = function(w,c,t)
u = extend(w[1:m-1])
z = w[m:2*m]
dudt = Dx*z
dzdt = c.^2 .* (Dx*u)
return [ trunc(dudt); dzdt ]
end;
We set the initial conditions and solve using |ode45|.
In [36]:
u_init = @.exp(-100*(x+0.5)^2);
z_init = -u_init;
w_init = [ trunc(u_init); z_init ];
IVP = ODEProblem(dwdt,w_init,(0.,5.),c)
sol = solve(IVP,RK4());
At each time in the following animation, we evaluate the discrete solution for $u$ and then extend it to the boundaries using the boundary conditions.
In [37]:
an = @animate for t = LinRange(0,5,200)
plot(x,extend(sol(t,idxs=1:m-1)),
xaxis=(L"x"),yaxis=([-1,1],L"u(x,t)"),
title="Wave equation, variable speed",label="t=$(round(t,digits=2))" )
end
gif(an,"wavereflect.gif")
Out[37]:
Each pass through the interface at $x=0$ generates a reflected and transmitted wave. By conservation of energy, these are both smaller in amplitude than the incoming bump.