We next describe how to apply the method of lines to PDE s of the form
u t = ϕ ( u , u x , u y , u x x , u x y , u y y ) , ( x , y ) ∈ [ a , b ] × [ c , d ] . u_t = \phi(u,u_x,u_y,u_{xx},u_{xy},u_{yy}), \quad (x,y)\in [a,b]\times [c,d]. u t = ϕ ( u , u x , u y , u xx , u x y , u yy ) , ( x , y ) ∈ [ a , b ] × [ c , d ] . The PDE may be of either parabolic or hyperbolic type, with the primary difference being potential restrictions on the time step size. To keep descriptions and implementations relatively simple, we will consider only periodic conditions, or Dirichlet boundary conditions imposed on all the edges of the rectangular domain.
As described in Tensor-product discretizations , the rectangular domain is discretized by a grid ( x i , y j ) (x_i,y_j) ( x i , y j ) for i = 0 , … , m i=0,\ldots,m i = 0 , … , m and j = 0 , … , n j=0,\ldots,n j = 0 , … , n . The solution is semidiscretized as a matrix U ( t ) \mathbf{U}(t) U ( t ) such that U i j ≈ u ( x i , y j , t ) U_{ij}\approx u(x_i,y_j,t) U ij ≈ u ( x i , y j , t ) . Terms involving the spatial derivatives of u u u are readily replaced by discrete counterparts: D x U \mathbf{D}_x\mathbf{U} D x U for u x u_x u x , U D y T \mathbf{U}\mathbf{D}_{y}^T U D y T for u y u_y u y , and so on.
13.2.1 Matrix and vector shapes ¶ Our destination is an IVP that can be solved by a Runge–Kutta or multistep solver. These solvers are intended for vector problems, but our unknowns naturally have a matrix shape, which is the most convenient for the differentiation formulas (13.1.8) and (13.1.9) . Fortunately, it’s easy to translate back and forth between a matrix and an equivalent vector.
Definition 13.2.1 (vec and unvec operations)
Let A \mathbf{A} A be an m × n m\times n m × n matrix. Define the vec function as stacking the columns of A \mathbf{A} A into a vector, i.e.,
vec ( A ) = [ A 11 ⋮ A m 1 ⋮ A 1 n ⋮ A m n ] . \operatorname{vec}(\mathbf{A}) =
\begin{bmatrix}
A_{11} \\ \vdots \\ A_{m1} \\ \vdots \\ A_{1n} \\ \vdots \\ A_{m n}
\end{bmatrix}. vec ( A ) = ⎣ ⎡ A 11 ⋮ A m 1 ⋮ A 1 n ⋮ A mn ⎦ ⎤ . Let z \mathbf{z} z be a vector of length m n m n mn . Define the unvec function as the inverse of vec:
unvec ( z ) = [ z 1 z m + 1 ⋯ z m ( n − 1 ) + 1 z 2 z m + 2 ⋯ z m ( n − 1 ) + 2 ⋮ ⋮ ⋮ z m z 2 m ⋯ z m n ] . \operatorname{unvec}(\mathbf{z}) = \begin{bmatrix}
z_1 & z_{m+1} & \cdots & z_{m(n-1)+1} \\
z_2 & z_{m+2} & \cdots & z_{m(n-1)+2} \\
\vdots & \vdots & & \vdots \\
z_m & z_{2m} & \cdots & z_{m n} \\
\end{bmatrix}. unvec ( z ) = ⎣ ⎡ z 1 z 2 ⋮ z m z m + 1 z m + 2 ⋮ z 2 m ⋯ ⋯ ⋯ z m ( n − 1 ) + 1 z m ( n − 1 ) + 2 ⋮ z mn ⎦ ⎤ . The function vec
is built-in to Julia, whereas unvec is a particular use case of the reshape
function.
Example 13.2.1 (Reshaping for grid functions)
Example 13.2.1 m = 2;
n = 3;
V = rand(1:9, m, n);
v = vec(V)
The unvec
operation is the inverse of vec.
unvec = z -> reshape(z, m, n)
unvec(v)
Example 13.2.1 m = 4; n = 3;
x = linspace(0, 2, m+1);
y = linspace(-3, 0, n+1);
f = @(x, y) cos(0.75*pi * x .* y - 0.5*pi * y);
[mtx, X, Y, vec, unvec] = tensorgrid(x, y);
F = mtx(f);
disp("function on a 4x3 grid:")
disp(F)
disp("vec(F):")
disp(vec(F))
The unvec
operation is the inverse of vec.
disp("unvec(vec(F)):")
disp(unvec(vec(F)))
Example 13.2.1 m, n = 4, 3
x = linspace(0, 2, m+1)
y = linspace(-3, 0, n+1)
f = lambda x, y: cos(0.75 * pi * x * y - 0.5 * pi * y)
mtx, X, Y, vec, unvec, _ = FNC.tensorgrid(x, y)
F = mtx(f)
print(f"function on a {m}x{n} grid:")
with printoptions(precision=4, suppress=True):
print(F)
print("vec(F):")
with printoptions(precision=4, suppress=True):
print(vec(F))
The unvec
operation is the inverse of vec.
print("unvec(vec(F)):")
with printoptions(precision=4, suppress=True):
print(unvec(vec(F)))
13.2.2 Periodic end conditions ¶ If the boundary conditions are periodic, then the unknowns in the method of lines are the elements of the matrix U ( t ) \mathbf{U}(t) U ( t ) representing grid values of the numerical solution. For the purposes of an IVP solution, this matrix is equivalent to the vector u ( t ) \mathbf{u}(t) u ( t ) defined as u = vec ( U ) \mathbf{u}=\operatorname{vec}(\mathbf{U}) u = vec ( U ) .
Example 13.2.2 (Heat equation in 2D)
We will solve a 2D heat equation, u t = 0.1 ( u x x + u y y ) u_t = 0.1(u_{xx} + u_{yy}) u t = 0.1 ( u xx + u yy ) , on the square [ − 1 , 1 ] × [ − 1 , 1 ] [-1,1]\times[-1,1] [ − 1 , 1 ] × [ − 1 , 1 ] , with periodic behavior in both directions.
Example 13.2.2 m, n = (60, 25)
x, Dx, Dxx = FNC.diffper(m, [-1, 1])
y, Dy, Dyy = FNC.diffper(n, [-1, 1])
mtx = f -> [f(x, y) for x in x, y in y]
unvec = z -> reshape(z, m, n);
Note that the initial condition should also be periodic on the domain.
using Plots
u_init = (x, y) -> sin(4 * π * x) * exp(cos(π * y))
U₀ = mtx(u_init)
M = maximum(abs, U₀)
contour(x, y, U₀';
color=:redsblues, clims=(-M, M),
aspect_ratio=1,
xaxis=("x", (-1, 1)), yaxis=("y", (-1, 1)),
title="Initial condition" )
This function computes the time derivative for the unknowns. The actual calculations take place using the matrix shape.
function du_dt(u, α, t)
U = unvec(u)
Uxx = Dxx * U
Uyy = U * Dyy' # 2nd partials
du_dt = α * (Uxx + Uyy) # PDE
return vec(du_dt)
end;
Since this problem is parabolic, a stiff integrator is appropriate.
using OrdinaryDiffEq
IVP = ODEProblem(du_dt, vec(U₀), (0, 0.2), 0.1)
sol = solve(IVP, Rodas4P());
Here is an animation of the solution.
Tip
Here clims
are set so that colors remain at fixed values throughout the animation.
anim = @animate for t in range(0, 0.2, 81)
surface(x, y, unvec(sol(t))';
color=:redsblues, clims=(-M, M),
xaxis=(L"x", (-1, 1)),
yaxis=(L"y", (-1, 1)),
zlims=(-M, M),
title=@sprintf("Heat equation, t=%.3f", t),
dpi=150, colorbar=:none)
end
closeall();
mp4(anim, "figures/diffadv-heat.mp4");
Example 13.2.2 m = 60; n = 40;
[x, Dx, Dxx] = diffper(m, [-1, 1]);
[y, Dy, Dyy] = diffper(n, [-1, 1]);
[mtx, X, Y, vec, unvec] = tensorgrid(x, y);
Note that the initial condition should also be periodic on the domain.
U0 = sin(4*pi*X) .* exp( cos(pi*Y) );
clf, surf(X', Y', U0')
mx = max(abs(vec(U0)));
clim([-mx, mx]), shading interp
colormap(redsblues)
xlabel('x'), ylabel('y')
title('Initial condition')
This function computes the time derivative for the unknowns. The actual calculations take place using the matrix shape.
function du_dt = timederiv(t, u, p)
[alpha, Dxx, Dyy, vec, unvec] = p{:};
U = unvec(u);
Uxx = Dxx * U; Uyy = U * Dyy'; % 2nd partials
dU_dt = alpha * (Uxx + Uyy); % PDE
du_dt = vec(dU_dt);
end
Since this problem is parabolic, a stiff integrator is appropriate.
ivp = ode(ODEFcn=@f13_2_heat);
ivp.InitialTime = 0;
ivp.InitialValue = vec(U0);
ivp.Parameters = {0.1, Dxx, Dyy, vec, unvec};
ivp.Solver = "stiff";
sol = solutionFcn(ivp, 0, 0.2);
U = @(t) unvec(sol(t));
surf(X', Y', U0')
mx = max(abs(vec(U0)));
clim([-mx, mx]), shading interp
colormap(redsblues)
xlabel('x'), ylabel('y')
title('Initial condition')
Here is an animation of the solution.
Tip
Here clims
are set so that colors remain at fixed values throughout the animation.
title('Heat equation on a periodic domain')
vid = VideoWriter("figures/2d-heat.mp4","MPEG-4");
vid.Quality = 85;
open(vid);
for t = linspace(0, 0.2, 61)
cla, surf(X', Y', U(t)')
shading interp
str = sprintf("t = %.2f", t);
text(-0.9, 0.75, 2, str, fontsize=14);
writeVideo(vid, frame2im(getframe(gcf)));
end
close(vid)
Example 13.2.2 m, n = 60, 40
x, Dx, Dxx = FNC.diffper(m, [-1, 1])
y, Dy, Dyy = FNC.diffper(n, [-1, 1])
mtx, X, Y, vec, unvec, _ = FNC.tensorgrid(x, y)
Note that the initial condition should also be periodic on the domain.
u_init = lambda x, y: sin(4 * pi * x) * exp(cos(pi * y))
U0 = mtx(u_init)
mx = max(abs(U0))
pcolormesh(X, Y, U0, vmin=-mx, vmax=mx, cmap="RdBu", shading="gouraud")
axis("equal"), colorbar()
xlabel("$x$"), ylabel("$y$")
title("Initial condition");
This function computes the time derivative for the unknowns. The actual calculations take place using the matrix shape.
alpha = 0.1
def du_dt(t, u):
U = unvec(u)
Uyy = Dxx @ U
Uxx = U @ Dyy.T
dU_dt = alpha * (Uxx + Uyy) # PDE
return vec(dU_dt)
Since this problem is parabolic, a stiff integrator is appropriate.
from scipy.integrate import solve_ivp
sol = solve_ivp(du_dt, (0, 0.2), vec(U0), method="BDF", dense_output=True)
U = lambda t: unvec(sol.sol(t))
pcolormesh(X.T, Y.T, U(0.02).T,
vmin=-mx, vmax=mx, cmap="RdBu", shading="gouraud")
axis("equal"), colorbar()
xlabel("$x$"), ylabel("$y$")
title("Heat equation, t=0.02")
Here is an animation of the solution.
Tip
Here clims
are set so that colors remain at fixed values throughout the animation.
from matplotlib import animation
fig, ax = subplots()
obj = ax.pcolormesh(X, Y, U(0), vmin=-mx, vmax=mx, cmap="RdBu", shading="gouraud")
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
ax.set_xlabel("$x$"), ax.set_ylabel("$y$")
ax.set_aspect("equal")
ax.set_title("Heat equation on a periodic domain")
def snapshot(t):
global obj
obj.remove()
obj = ax.pcolormesh(X, Y, U(t), vmin=-mx, vmax=mx, cmap="RdBu", shading="gouraud")
time_text.set_text(f"t = {t:.2f}")
anim = animation.FuncAnimation(fig, snapshot, frames=linspace(0, 0.2, 41))
anim.save("figures/heat-2d.mp4", fps=30)
close()
13.2.3 Dirichlet conditions ¶ In Boundaries we coped with boundary conditions by removing the boundary values from the vector of unknowns being solved in the semidiscretized ODE . Each evaluation of the time derivative required us to extend the values to include the boundaries before applying differentiation matrices in space. We proceed similarly here, except that we have changes in the shape as well as boundary conditions to consider.
Suppose we are given a matrix U \mathbf{U} U that represents the solution on an ( m + 1 ) × ( n + 1 ) (m+1)\times (n+1) ( m + 1 ) × ( n + 1 ) grid, including boundary values. Then we define
pack ( U ) = vec ( E x U E y T ) , \operatorname{pack}(\mathbf{U}) = \operatorname{vec}(\mathbf{E}_x \mathbf{U} \mathbf{E}_y^T), pack ( U ) = vec ( E x U E y T ) , where
E x = [ 0 1 0 ⋯ 0 0 0 0 1 ⋯ 0 0 ⋱ 0 0 0 ⋯ 1 0 ] \mathbf{E}_x = \begin{bmatrix}
0 & 1 & 0 & \cdots & 0 & 0 \\
0 & 0 & 1 & \cdots & 0 & 0 \\
& & & \ddots & & \\
0 & 0 & 0 & \cdots & 1 & 0
\end{bmatrix} E x = ⎣ ⎡ 0 0 0 1 0 0 0 1 0 ⋯ ⋯ ⋱ ⋯ 0 0 1 0 0 0 ⎦ ⎤ is ( m − 1 ) × ( m + 1 ) (m-1)\times (m+1) ( m − 1 ) × ( m + 1 ) , and E y \mathbf{E}_y E y is analogous but of size ( n − 1 ) × ( n + 1 ) (n-1)\times (n+1) ( n − 1 ) × ( n + 1 ) . The left multiplication in (13.2.4) deletes the first and last row of U \mathbf{U} U and the right multiplication deletes its first and last column. All that remains, then, are the interior values, which are converted into a vector by the vec operator.
For the inverse transformation, let us be given a vector w \mathbf{w} w of interior solution values. Then we define
unpack ( w ) = E x T ⋅ unvec ( w ) ⋅ E y . \operatorname{unpack}(\mathbf{w}) = \mathbf{E}_x^T \cdot \operatorname{unvec}(\mathbf{w}) \cdot \mathbf{E}_y. unpack ( w ) = E x T ⋅ unvec ( w ) ⋅ E y . This operator reshapes the vector to a grid of interior values, then appends one extra zero row and column on each side of the grid.[1]
Now suppose the ODE unknowns for the interior solution values are in the vector w ( t ) \mathbf{w}(t) w ( t ) . When we form unpack ( w ) \operatorname{unpack}(\mathbf{w}) unpack ( w ) , we reinterpret the values on the tensor-product grid and then extend these values to zero around the boundary. If the boundary values are given as g ( x , y ) g(x,y) g ( x , y ) , then g g g has to be evaluated at the boundary nodes of the grid and inserted into the grid function matrix. Then the grid values are used to compute partial derivatives in x x x and y y y , the discrete form of the PDE is evaluated, and we pack the result as the computation of w ′ \mathbf{w}' w ′ .
Example 13.2.3 (Advection-diffusion equation in 2D)
We will solve an advection-diffusion problem, u t + u x = 1 + ϵ ( u x x + u y y ) u_t + u_x = 1 + \epsilon(u_{xx} + u_{yy}) u t + u x = 1 + ϵ ( u xx + u yy ) , where u = 0 u=0 u = 0 on the boundary of the square [ − 1 , 1 ] 2 [-1,1]^2 [ − 1 , 1 ] 2 . The outline of our approach is based on Function 11.5.2 for parabolic PDE s in one space dimension.
Example 13.2.3 The first step is to define a discretization of the domain.
m, n = 50, 36
x, Dx, Dxx = FNC.diffcheb(m, [-1, 1])
y, Dy, Dyy = FNC.diffcheb(n, [-1, 1])
mtx, X, Y, _ = FNC.tensorgrid(x, y)
U₀ = mtx( (x, y) -> (1 + y) * (1 - x)^4 * (1 + x)^2 * (1 - y^4) );
There are really two grids now: the full grid and the subset grid of interior points. Since the IVP unknowns are on the interior grid, that is the one we need to change shapes on. We also need the functions extend
and chop
to add and remove boundary values.
chop = U -> U[2:m, 2:n]
extend = U -> [zeros(m+1) [zeros(1, n-1); U; zeros(1, n-1)] zeros(m+1)]
unvec = u -> reshape(u, m-1, n-1)
pack = U -> vec(chop(U))
unpack = w -> extend(unvec(w))
Now we can define and solve the IVP using a stiff solver.
function dw_dt(w, ϵ, t)
U = unpack(w)
Ux, Uxx = Dx * U, Dxx * U
Uyy = U * Dyy'
du_dt = @. 1 - Ux + ϵ * (Uxx + Uyy)
return pack(du_dt)
end
IVP = ODEProblem(dw_dt, pack(U₀), (0.0, 2), 0.05)
w = solve(IVP, Rodas4P());
When we evaluate the solution at a particular value of t t t , we get a vector of the interior grid values. The same unpack
function above converts this to a complete matrix of grid values.
U = t -> unpack(w(t))
contour(x, y, U(0.5)';
fill=true, color=:blues, levels=20, l=0,
aspect_ratio=1, xlabel=L"x", ylabel=L"y",
title="Solution at t = 0.5")
anim = @animate for t in 0:0.02:2
U = unpack(w(t))
surface(x, y, U';
layout=(1, 2), size=(640, 320),
xlabel=L"x", ylabel=L"y", zaxis=((0, 2), L"u(x,y)"),
color=:blues, alpha=0.66, clims=(0, 2), colorbar=:none,
title="Advection-diffusion", dpi=150)
contour!(x, y, U';
levels=24,
aspect_ratio=1, subplot=2,
xlabel=L"x", ylabel=L"y",
color=:blues, clims=(0, 2), colorbar=:none,
title=@sprintf("t = %.2f", t))
end
closeall();
mp4(anim, "figures/diffadv-advdiff.mp4");
Example 13.2.3 The first step is to define a discretization of the domain and the initial state.
m = 50; n = 40;
[x, Dx, Dxx] = diffcheb(m, [-1, 1]);
[y, Dy, Dyy] = diffcheb(n, [-1, 1]);
[mtx, X, Y] = tensorgrid(x, y);
u_init = @(x, y) (1+y) .* (1-x).^4 .* (1+x).^2 .* (1-y.^4);
There are really two grids now: the full grid and the subset grid of interior points. Since the IVP unknowns are on the interior grid, that is the one we need to change shapes on. We also need the functions extend
and chop
to add and remove boundary values.
[~, ~, ~, vec, unvec] = tensorgrid(x(2:m), y(2:n));
chop = @(U) U(2:m, 2:n);
z = zeros(1, n-1);
extend = @(U) [ zeros(m+1, 1) [z; U; z] zeros(m+1, 1)];
pack = @(U) vec(chop(U));
unpack = @(u) extend(unvec(u));
Now we can define and solve the IVP using a stiff solver.
function du_dt = timederiv(t, u, p)
[ep, Dx, Dxx, Dy, Dyy, pack, unpack] = p{:};
U = unpack(u);
Uxx = Dxx * U; Uyy = U * Dyy';
dU_dt = 1 - Dx * U + ep * (Uxx + Uyy); % PDE
du_dt = pack(dU_dt);
end
ivp = ode(ODEFcn=@f13_2_advdiff);
ivp.InitialTime = 0;
ivp.InitialValue = pack(mtx(u_init));
ivp.Parameters = {0.05, Dx, Dxx, Dy, Dyy, pack, unpack};
ivp.Solver = "stiff";
sol = solutionFcn(ivp, 0, 2);
When we evaluate the solution at a particular value of t t t , we get a vector of the interior grid values. The same unpack
function above converts this to a complete matrix of grid values.
U = @(t) unpack(sol(t));
clf, pcolor(X', Y', U(0.5)')
clim([0, 2]), shading interp
axis equal, colormap(sky), colorbar
title('Advection-diffusion at t = 0.5')
xlabel('x'), ylabel('y')
hold on
vid = VideoWriter("figures/2d-advdiff.mp4","MPEG-4");
vid.Quality = 85;
open(vid);
for t = linspace(0, 2, 81)
cla, pcolor(X', Y', U(t)')
shading interp
str = sprintf("t = %.2f", t);
text(-1.5, 0.75, str, fontsize=14);
writeVideo(vid, frame2im(getframe(gcf)));
end
close(vid)
Example 13.2.3 The first step is to define a discretization of the domain.
m, n = 50, 36
x, Dx, Dxx = FNC.diffcheb(m, [-1, 1])
y, Dy, Dyy = FNC.diffcheb(n, [-1, 1])
mtx, X, Y, _, _, _ = FNC.tensorgrid(x, y)
u_init = lambda x, y: (1 + y) * (1 - x)**4 * (1 + x)**2 * (1 - y**4)
There are really two grids now: the full grid and the subset grid of interior points. Since the IVP unknowns are on the interior grid, that is the one we need to change shapes on. We also need the functions extend
and chop
to add and remove boundary values.
_, _, _, vec, unvec, _ = FNC.tensorgrid(x[1:-1], y[1:-1])
def chop(U):
return U[1:-1, 1:-1]
def extend(U):
UU = zeros((m+1, n+1))
UU[1:-1, 1:-1] = U
return UU
pack = lambda U: vec(chop(U)) # restrict to interior, then vectorize
unpack = lambda u: extend(unvec(u)) # unvectorize, then extend to boundary
Now we can define and solve the IVP using a stiff solver.
ep = 0.05
def dw_dt(t, w):
U = unpack(w)
Uyy = Dxx @ U
Uxx = U @ Dyy.T
dU_dt = 1 - Dx @ U + ep * (Uxx + Uyy)
return pack(dU_dt)
U0 = mtx(u_init)
sol = solve_ivp(dw_dt, (0, 2), pack(U0), method="BDF", dense_output=True)
When we evaluate the solution at a particular value of t t t , we get a vector of the interior grid values. The same unpack
function above converts this to a complete matrix of grid values.
U = lambda t: unpack(sol.sol(t)) # function of time on the grid
pcolormesh(X.T, Y.T, U(0.5).T, cmap="Blues", shading="gouraud")
colorbar()
xlabel("$x$"), ylabel("$y$")
axis("equal"), title("Solution at t=0.5");
fig, ax = subplots()
obj = ax.pcolormesh(X.T, Y.T, U(0).T, vmin=0, vmax=2, cmap="Blues", shading="gouraud")
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
ax.set_xlabel("$x$"), ax.set_ylabel("$y$")
ax.set_aspect("equal")
ax.set_title("Advection-diffusion in 2d")
def snapshot(t):
global obj
obj.remove()
obj = ax.pcolormesh(X.T, Y.T, U(t).T, vmin=0, vmax=2, cmap="Blues", shading="gouraud")
time_text.set_text(f"t = {t:.2f}")
anim = animation.FuncAnimation(fig, snapshot, frames=linspace(0, 2, 81))
anim.save("figures/advdiff-2d.mp4", fps=30)
close()
The wave equation introduces a little additional complexity. First, we write the 2D wave equation u t t = c 2 ( u x x + u y y ) u_{tt}=c^2(u_{xx}+u_{yy}) u tt = c 2 ( u xx + u yy ) in first-order form as
u t = v , v t = c 2 ( u x x + u y y ) . \begin{split}
u_t &= v, \\
v_t &= c^2(u_{xx}+u_{yy}).
\end{split} u t v t = v , = c 2 ( u xx + u yy ) . Now the grid unknowns are a pair of matrices U ( t ) \mathbf{U}(t) U ( t ) and V ( t ) \mathbf{V}(t) V ( t ) . Typical boundary conditions would prescribe u u u on all of the boundary and let v v v be unspecified. Since the boundary values of U \mathbf{U} U are prescribed, those values are omitted from the semidiscretization IVP , while all of V \mathbf{V} V is included. All of these unknowns need to be packed into and unpacked from a single vector w ( t ) \mathbf{w}(t) w ( t ) for the IVP solver.
Example 13.2.4 (Wave equation in 2D)
We solve the wave equation with c = 1 c=1 c = 1 on the square [ − 2 , 2 ] × [ − 2 , 2 ] [-2,2]\times[-2,2] [ − 2 , 2 ] × [ − 2 , 2 ] , where u = 0 u=0 u = 0 on the boundary.
Example 13.2.4 We start with the discretization and initial condition.
m, n = 40, 40
x, Dx, Dxx = FNC.diffcheb(m, [-2, 2])
y, Dy, Dyy = FNC.diffcheb(n, [-2, 2])
mtx, X, Y, _ = FNC.tensorgrid(x, y)
U₀ = mtx( (x, y) -> (x + 0.2) * exp(-12 * (x^2 + y^2)) )
V₀ = zeros(size(U₀));
Note that because u u u is known on the boundary, while v v v is unknown over the full grid, there are two different sizes of vec/unvec operations. We also need to define functions to pack grid unknowns into a vector and to unpack them. When the unknowns for u u u are packed, the boundary values are chopped off, and these are restored when unpacking.
_, _, _, unvec_v, _ = FNC.tensorgrid(x, y)
_, _, _, unvec_u, _ = FNC.tensorgrid(x[2:m], y[2:n])
chop = U -> U[2:m, 2:n]
extend = U -> [zeros(m+1) [zeros(1, n-1); U; zeros(1, n-1)] zeros(m+1)]
pack = (U, V) -> [vec(chop(U)); vec(V)]
N = (m-1) * (n-1) # number of interior unknowns
unpack = w -> ( extend(unvec_u(w[1:N])), unvec_v(w[N+1:end]) )
We can now define and solve the IVP . Since this problem is hyperbolic, not parabolic, a nonstiff integrator is faster than a stiff one.
function dw_dt(w, c, t)
U, V = unpack(w)
du_dt = V
dv_dt = c^2 * (Dxx * U + U * Dyy')
return pack(du_dt, dv_dt)
end
IVP = ODEProblem(dw_dt, pack(U₀, V₀), (0, 4.0), 1)
sol = solve(IVP, Tsit5())
U = t -> unpack(sol(t))[1]
anim = @animate for t in 0:4/100:4
Ut = U(t)
surface(x, y, Ut';
layout=(1, 2), size=(640, 320),
xlabel=L"x", ylabel=L"y", zaxis=((-0.1, 0.1), L"u(x,y)"),
color=:redsblues, alpha=0.66, clims=(-0.1, 0.1), colorbar=:none,
title="Wave equation", dpi=150)
contour!(x, y, Ut';
levels=24, subplot=2,
aspect_ratio=1,
xlabel=L"x", ylabel=L"y",
color=:redsblues, clims=(-0.1, 0.1),
colorbar=:none, title=@sprintf("t = %.2f", t))
end
closeall();
mp4(anim, "figures/diffadv-wave.mp4");
Example 13.2.4 We start with the discretization and initial condition.
m = 40; n = 42;
[x, Dx, Dxx] = diffcheb(m, [-2, 2]);
[y, Dy, Dyy] = diffcheb(n, [-2, 2]);
[mtx, X, Y] = tensorgrid(x, y);
u_init = @(x, y) (x+0.2) .* exp(-12*(x.^2 + y.^2));
U0 = mtx(u_init);
V0 = zeros(size(U0));
Note that because u u u is known on the boundary, while v v v is unknown over the full grid, there are two different sizes of vec/unvec operations. We also need to define functions to pack grid unknowns into a vector and to unpack them. When the unknowns for u u u are packed, the boundary values are chopped off, and these are restored when unpacking.
[~, ~, ~, vec_v, unvec_v] = tensorgrid(x, y);
[~, ~, ~, vec_u, unvec_u] = tensorgrid(x(2:m), y(2:n));
chop = @(U) U(2:m, 2:n);
z = zeros(1, n-1);
extend = @(U) [ zeros(m+1, 1) [z; U; z] zeros(m+1, 1)];
pack = @(U, V) [vec_u(chop(U)); vec_v(V)];
N = (m-1) * (n-1);
unpack = @(u) f13_2_wave_unpack(u, N, unvec_u, unvec_v, extend);
function [U, V] = unpack(w, N, unvec_u, unvec_v, extend)
U = extend( unvec_u(w(1:N)) );
V = unvec_v(w(N+1:end));
end
We can now define and solve the IVP . Since this problem is hyperbolic, not parabolic, a nonstiff integrator is faster than a stiff one.
function dw_dt = timederiv(t, w, p)
[Dxx, Dyy, pack, unpack] = p{:};
[U, V] = unpack(w);
dU_dt = V;
dV_dt = Dxx * U + U * Dyy';
dw_dt = pack(dU_dt, dV_dt);
end
ivp = ode(ODEFcn=@f13_2_wave);
ivp.InitialTime = 0;
ivp.InitialValue = pack(U0, V0);
ivp.Parameters = {Dxx, Dyy, pack, unpack};
ivp.Solver = "nonstiff";
sol = solutionFcn(ivp, 0, 4);
clf
[U, V] = unpack(sol(0.5));
pcolor(X', Y', U')
axis equal, clim([-0.1, 0.1])
colormap(redsblues), shading interp
xlabel("x"), ylabel("y")
title("Wave equation at t = 0.5")
hold on
vid = VideoWriter("figures/2d-wave.mp4","MPEG-4");
vid.Quality = 85;
open(vid);
for t = linspace(0, 4, 121)
[U, V] = unpack(sol(t));
cla, pcolor(X, Y, U)
shading interp
str = sprintf("t = %.2f", t);
text(-3, 1.75, str, fontsize=14);
writeVideo(vid, frame2im(getframe(gcf)));
end
close(vid)
Example 13.2.4 We start with the discretization and initial condition.
m, n = 40, 42
x, Dx, Dxx = FNC.diffcheb(m, [-2, 2])
y, Dy, Dyy = FNC.diffcheb(n, [-2, 2])
mtx, X, Y, _, _, _ = FNC.tensorgrid(x, y)
U0 = mtx(lambda x, y: (x + 0.2) * exp(-12 * (x**2 + y**2)))
V0 = zeros(U0.shape)
Note that because u u u is known on the boundary, while v v v is unknown over the full grid, there are two different sizes of vec/unvec operations. We also need to define functions to pack grid unknowns into a vector and to unpack them. When the unknowns for u u u are packed, the boundary values are chopped off, and these are restored when unpacking.
_, _, _, vec_v, unvec_v, _ = FNC.tensorgrid(x, y)
_, _, _, vec_u, unvec_u, _ = FNC.tensorgrid(x[1:-1], y[1:-1])
def extend(U):
UU = zeros((m+1, n+1))
UU[1:-1, 1:-1] = U
return UU
def chop(U):
return U[1:-1, 1:-1]
def pack(U, V):
return hstack([vec_u(chop(U)), vec_v(V)])
N = (m-1) * (n-1)
def unpack(w):
U = extend(unvec_u(w[:N]))
V = unvec_v(w[N:])
return U, V
We can now define and solve the IVP . Since this problem is hyperbolic, not parabolic, a nonstiff integrator is faster than a stiff one.
def dw_dt(t, w):
U, V = unpack(w)
dU_dt = V
dV_dt = Dxx @ U + U @ Dyy.T
return pack(dU_dt, dV_dt)
from scipy.integrate import solve_ivp
sol = solve_ivp(dw_dt, (0, 4), pack(U0, V0), method="RK45", dense_output=True)
U = lambda t: unpack(sol.sol(t))[0]
fig, ax = subplots()
obj = ax.pcolormesh(X, Y, U(0), vmin=-0.1, vmax=0.1, cmap="RdBu", shading="gouraud")
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
ax.set_xlabel("$x$"), ax.set_ylabel("$y$")
ax.set_aspect("equal")
ax.set_title("Wave equation in 2d")
def snapshot(t):
global obj
obj.remove()
obj = ax.pcolormesh(X, Y, U(t), vmin=-0.1, vmax=0.1, cmap="RdBu", shading="gouraud")
time_text.set_text(f"t = {t:.2f}")
anim = animation.FuncAnimation(fig, snapshot, frames=linspace(0, 4, 91))
anim.save("figures/wave-2d.mp4", fps=30);
close()
13.2.4 Exercises ¶ ⌨ For the given u ( x , y ) u(x,y) u ( x , y ) , make a plot of the given quantity on the square [ − 2 , 2 ] 2 [-2,2]^2 [ − 2 , 2 ] 2 using appropriate differentiation matrices.
(a) u ( x , y ) = exp ( x − y 2 ) u(x,y) = \exp(x-y^2) u ( x , y ) = exp ( x − y 2 ) ; plot u x x + u y y u_{xx}+u_{yy} u xx + u yy
(b) u ( x , y ) = cos ( π x ) + sin ( π y ) u(x,y) =\cos (\pi x)+\sin (\pi y) u ( x , y ) = cos ( π x ) + sin ( π y ) ; plot u x + u y u_x+u_y u x + u y
(c) u ( x , y ) = exp ( − x 2 − 4 y 2 ) u(x,y) =\exp(-x^2-4y^2) u ( x , y ) = exp ( − x 2 − 4 y 2 ) ; plot x u y x u_y x u y
⌨ Following Demo 13.2.2 as a model, solve the Allen–Cahn equation u t = u ( 1 − u 2 ) + 0.001 ( u x x + u y y ) u_t=u(1-u^2)+0.001(u_{xx}+u_{yy}) u t = u ( 1 − u 2 ) + 0.001 ( u xx + u yy ) on the square [ − 1 , 1 ] 2 [-1,1]^2 [ − 1 , 1 ] 2 with periodic conditions, taking u ( x , y , 0 ) = sin ( π x ) cos ( 2 π y ) u(x,y,0)=\sin(\pi x)\cos(2\pi y) u ( x , y , 0 ) = sin ( π x ) cos ( 2 π y ) . Use m = n = 60 m=n=60 m = n = 60 to solve up to t = 4 t=4 t = 4 , and make an animation of the result.
⌨ Following Demo 13.2.3 as a model, solve u t = y u x − u y + 0.03 ( u x x + u y y ) u_t=y u_x-u_y+0.03(u_{xx}+u_{yy}) u t = y u x − u y + 0.03 ( u xx + u yy ) on the square [ − 1 , 1 ] 2 [-1,1]^2 [ − 1 , 1 ] 2 , with u ( x , y , 0 ) = ( 1 − x 2 ) ( 1 − y 2 ) u(x,y,0)=(1-x^2)(1-y^2) u ( x , y , 0 ) = ( 1 − x 2 ) ( 1 − y 2 ) and homogeneous Dirichlet boundary conditions. Use m = n = 40 m=n=40 m = n = 40 to solve up to t = 2 t=2 t = 2 , and make an animation of the result.
⌨ Following Demo 13.2.4 as a model, solve u t t = u x x + u y y + cos ( 7 t ) u_{tt}=u_{xx}+u_{yy}+\cos(7t) u tt = u xx + u yy + cos ( 7 t ) on the square [ − 1 , 1 ] 2 [-1,1]^2 [ − 1 , 1 ] 2 , with u ( x , y , 0 ) = x ( 1 − x 6 ) ( 1 − y 2 ) u(x,y,0)=x(1-x^6)(1-y^2) u ( x , y , 0 ) = x ( 1 − x 6 ) ( 1 − y 2 ) , u t ( x , y , 0 ) = 0 u_t(x,y,0)=0 u t ( x , y , 0 ) = 0 , subject to homogeneous Dirichlet boundary conditions. Take m = n = 60 m=n=60 m = n = 60 to solve between t = 0 t=0 t = 0 and t = 12 t=12 t = 12 , and make an animation of the result.
From Maxwell’s equations we can find a way to convert the wave equation to a first-order form that, unlike (13.2.7) , uses only first-order derivatives in space:
u t = c 2 ( v y − w x ) , v t = u y , w t = − u x , \begin{split}
u_t &= c^2(v_y - w_x),\\
v_t &= u_y, \\
w_t &= -u_x,
\end{split} u t v t w t = c 2 ( v y − w x ) , = u y , = − u x , subject to u = 0 u=0 u = 0 on the boundary.
(a) ✍ Show that a solution of (13.2.8) satisfies u t = c 2 ( u x x + u y y ) u_t=c^2(u_{xx}+u_{yy}) u t = c 2 ( u xx + u yy ) .
(b) ⌨ Solve (13.2.8) with c = 2 c=2 c = 2 in the rectangle x ∈ [ − 3 , 3 ] x\in[-3,3] x ∈ [ − 3 , 3 ] , y ∈ [ − 1 , 1 ] y\in[-1,1] y ∈ [ − 1 , 1 ] , u ( x , y , 0 ) = exp ( x − x 2 ) ( 9 − x 2 ) ( 1 − y 2 ) u(x,y,0) = \exp(x-x^2)(9-x^2)(1-y^2) u ( x , y , 0 ) = exp ( x − x 2 ) ( 9 − x 2 ) ( 1 − y 2 ) , and v = w = 0 v=w=0 v = w = 0 at t = 0 t=0 t = 0 . Use m = 50 m=50 m = 50 for x x x and n = 25 n=25 n = 25 for y y y , solve for 0 ≤ t ≤ 6 0\le t \le 6 0 ≤ t ≤ 6 , and make an animation of the solution.