The Wave Equation on the Unit Square
Let us consider the wave equation
on the unit square with homogeneous Dirichlet boundary conditions, .
We would like to solve this partial differential equation numerically. First, we discretize in space using a finite difference discretization as described in a previous post. More specifically, we use interior points in each direction and a grid spacing of . We introduce the matrix with entries for . Finally, we set , where the operator stacks the columns of on top of each other. Thus, .
We now have
where and is the finite difference discretization of the Laplace operator:
(see the previous post for the definition of and other details).
So now we have a system of ordinary differential equations. It is of second order, however, but it is easily transformed into a system of first order ODEs:
Now, given initial values for and at time , we have a system that can be solved using a numerical ODE solver.
Let us now describe how to solve this system numerically using Python's numpy and scipy libraries.
First, the grid:
n = 199
h = 1 / (n + 1)
x = y = np.linspace(0, 1, n + 2)
Next, the initial conditions:
X, Y = np.meshgrid(x, y)
R = np.sqrt((X - 0.45)**2 + (Y - 0.4)**2)
U = 20 * np.exp(-R**2) * np.cos(4 * R) * X * (1 - X) * Y * (1 - Y)
This expression for is chosen to have a nice, non-trivial shape and to satisfy the boundary conditions:
We now set up the Laplace matrix and its 2D version :
L = (scipy.sparse.eye(n, k=-1) - 2 * scipy.sparse.eye(n)
+ scipy.sparse.eye(n, k=1)) / h**2
D = scipy.sparse.kronsum(L, L)
We set the initial values for (a vector version of the inner of ), use and set up the time range for the solver:
u0 = U[1:-1, 1:-1].reshape(-1)
ut0 = np.zeros_like(u0)
timespan = [0, 5]
sample_times = np.linspace(0, 5, 200)
The variable sample_times is used to instruct the solver to record the solution at these times.
Finally, we solve the system using scipy's
initial value ODE solver,
which uses the explicit Runge-Kutta method
of order 5(4) by default:
R = scipy.integrate.solve_ivp(
lambda _, u: np.concatenate((u[n*n:], D @ u[:n*n])),
timespan,
np.concatenate((u0, ut0)),
t_eval=sample_times
)
By plotting each of the sampled solutions, we can view an animation of the solution:
(The plotting was done using
matplotlib
and the movie was created using ffmpeg.)