Classical Wave equation¶
The wave equation is an example of a second-order PDE (partial differential equation). This PDE governs the behavior of the displacement in time and space.

Figure 1:The classical wave equation describes any complicated wave in space and time, given the initial conditions.
Applications of the Classical Wave Equation: To illustrate the applications of the classical wave equation, we will solve it for a 1D guitar string. This provides a comprehensive mathematical description of the string’s behavior.
Predicting Evolution: By specifying arbitrary initial conditions, the wave equation allows us to precisely predict the evolution of the string over time and space.

Figure 2:A plucked guitar string is a classic system governed by the 1D wave equation.
Solving Wave Equation: The big picture¶
1. Boundary Conditions: To solve the wave equation for a specific physical situation, such as a guitar string fixed at both ends, we need to specify two boundary conditions. Mathematically, these are:
where represents the displacement of the string at position and time .
2. Separation of Variables: A common method to solve such equations is the technique of separation of variables. This technique assumes that and vary independently of each other, allowing us to express as a product of two functions, each depending on only one variable. This lets us decompose the PDE into ordinary differential equations (ODEs).
3. Principle of superposition: The wave equation is linear, which you can see from the terms appearing to first order on both sides. Linearity means that a linear combination of two solutions and is also a solution. If you have particular solutions, then you write the general solution as a linear combination of those terms.
Step 1: Plug the Product of Univariate Functions into the Wave Equation¶
Start with the 1D wave equation that can describe 1D guitar string:
Substitute into the wave equation:
After rearranging, we get:
Thus, we obtain two separate ordinary differential equations (ODEs) from the original partial differential equation (PDE):
where is a constant.
Step 2: Solving Each Ordinary Differential Equation¶
After decomposing the PDE into ODEs, we solve each ODE separately:
Solving the spatial part: when
If , we specify , where is a constant. The general solution for is represented as a linear combination of particular solutions (principle of superposition):
Applying the boundary conditions leads to . Since a non-trivial linear combination of two exponential functions cannot be zero everywhere, this results in the trivial solution:
This implies that the string does not move, so there is no music!
Solving the spatial part: when
Solving temporal part
Solution for spatial part¶
Because of the boundary conditions () imposed at the ends of the guitar string, we found an infinite number of solutions indexed by an integer . These terms are called normal modes and are visualized below.

Figure 3:The first seven solutions (normal modes) of the spatial part of the wave equation on a string of length , shown at positive and negative amplitude.
Source
import numpy as np
import matplotlib.pyplot as plt
# Parameters
L = 1.0
B = 1.0
ns = [1, 2, 3, 4]
x = np.linspace(0, L, 800)
# 2x2 subplot
fig, axes = plt.subplots(2, 2, figsize=(8, 6))
for ax, n in zip(axes.ravel(), ns):
Xn = B * np.sin(n * np.pi * x / L)
ax.plot(x, Xn, color="C0")
ax.axhline(0, color="black", linewidth=0.8)
ax.set_title(f"n = {n}")
ax.set_xlabel("x")
ax.set_ylabel("X(x)")
ax.set_xlim(0, L)
fig.suptitle("Normal modes of a fixed–fixed string")
fig.tight_layout(rect=[0, 0, 1, 0.96])
Solution for temporal part¶
For the temporal part, we do not have boundary conditions! Time is allowed to march forward into infinity. We find the solution in the form of a linear combination of sine and cosine functions with , where indexes the normal modes.
In the second line we use a trig identity to express the sum of cosine and sine in terms of a single cosine. We still have two constants to determine.
The constants and in the temporal part are determined by initial conditions, e.g. what the amplitude and phase of the wave should be at the starting time .
Step 3 Full solution: A linear combination of normal modes¶
After solving the ODEs for the spatial and temporal parts, we now combine them into a full solution.
The complete description of any vibrational motion of the guitar string is given by the sum of the normal modes, .
While the terms involving time, , depend on how and where the string is plucked, the normal modes remain the same for a given string.
About nodes¶
Places where the guitar string stays at the resting position of 0 are called nodes. We notice that the number of nodal points increases with .
For : There are 0 nodes (excluding the endpoints). This is called the fundamental frequency or first harmonic.
For : There is 1 node. This is known as the first overtone or second harmonic.
For : There are 2 nodes. This is called the second overtone or third harmonic.
Note that the general solution to wave equation is expressed as a linear combination of all normal modes
Source
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
# Parameters (reduced frames for faster export)
L = 1.0
v = 1.0
B = 1.0
A = {1: 1.0, 2: 1.0, 3: 1.0}
phi = {1: 0.0, 2: 0.0, 3: 0.0}
x = np.linspace(0, L, 600)
def X(n, x):
return np.sin(n * np.pi * x / L)
def T(n, t):
omega_n = n * np.pi * v / L
return np.cos(omega_n * t + phi[n])
def u_mode(n, x, t):
return B * X(n, x) * T(n, t)
def u_sum(modes, x, t):
s = np.zeros_like(x)
for n in modes:
s += A[n] * X(n, x) * T(n, t)
return s
omega1 = np.pi * v / L
T1 = 2 * np.pi / omega1
tmax = 2 * T1
frames = 120 # fewer frames
ts = np.linspace(0, tmax, frames)
fig, axes = plt.subplots(2, 2, figsize=(8, 6))
axes = axes.ravel()
titles = ["n = 1", "n = 3", "n = 1 + 2", "n = 1 + 2 + 3"]
lines = []
for ax, title in zip(axes, titles):
ax.set_xlim(0, L)
ax.set_ylim(-1.6, 1.6)
ax.set_xlabel("x")
ax.set_ylabel("u(x,t)")
ax.set_title(title)
ax.axhline(0, linewidth=0.8)
line, = ax.plot([], [])
lines.append(line)
fig.suptitle("Standing waves on a fixed–fixed string")
def init():
for line in lines:
line.set_data([], [])
return lines
def update(idx):
t = ts[idx]
lines[0].set_data(x, u_mode(1, x, t))
lines[1].set_data(x, u_mode(3, x, t))
lines[2].set_data(x, u_sum([1, 2], x, t))
lines[3].set_data(x, u_sum([1, 2, 3], x, t))
return lines
ani = FuncAnimation(fig, update, frames=frames, init_func=init, blit=True, interval=1000*(tmax/frames))
plt.close(fig) # Prevents static display of the last frame
HTML(ani.to_jshtml())2D Membrane Vibrations¶
The wave function of a 2D membrane with fixed edges depends on two independent spatial variables, and . By applying the method of separation of variables, we decompose the wave function into three ordinary differential equations.
For the spatial components and , there are boundary conditions that must be satisfied at the fixed edges: Boundary condition along the -axis: and Boundary condition along the -axis:
The 2D normal mode is the product of two 1D modes. Since the dimensions are independent, we get integers for the -direction and for the -direction:
The angular frequency depends on the geometry of the membrane (with dimensions and ) and the mode numbers and :
Where:
is the wave speed,
and are the mode numbers,
and are the dimensions of the membrane.

Figure 4:Vibrations of a 2D membrane.
Full derivation of 2D rectangular membrane problem
To extend the solution of the 1D guitar string problem to 2D, you can analyze a 2D vibrating membrane, such as a rectangular or circular drumhead. Here is a step-by-step solution for a 2D rectangular membrane:
Solution for a 2D Rectangular Membrane
Consider a rectangular membrane with dimensions and , fixed along its edges. The wave equation for the membrane is:
where is the wave speed.
Separation of Variables
Assume the solution can be written as a product of functions, each depending on only one variable:
Substitute this into the wave equation:
This simplifies to:
Divide through by :
Since the left side depends only on and the right side depends only on and , each side must equal a constant, which we denote as :
Solving for
The ODE for is:
This has the general solution:
where .
Solving for and
To solve the spatial part, we need to separate the problem into two parts:
Assume:
where .
So, we have:
Boundary Conditions:
For a rectangular membrane with boundaries , , , and , we apply:
The solutions for and are:
where and are positive integers, and:
So, the general solution for is:
where:
This solution describes the vibration modes of a 2D rectangular membrane, with each mode characterized by different integer values of and .
Source
import numpy as np
import matplotlib.pyplot as plt
import ipywidgets
from ipywidgets import interact, interactive, Dropdown
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
import plotly.graph_objects as go
from plotly.subplots import make_subplots
def membrane2d_mode(n=1, m=1, t=0):
"""
Calculates the 2D grid of points (X, Y) and the normal mode displacement (Z) of a vibrating
rectangular membrane at a specific time t.
Parameters:
n (int): Mode number along the x-direction. Default is 1.
m (int): Mode number along the y-direction. Default is 1.
t (float): Time at which to evaluate the normal mode. Default is 0.
Returns:
X, Y, Z (numpy arrays): Grid of points in the X-Y plane and the corresponding
membrane displacement Z(X, Y, t).
"""
# Constants
Lx, Ly = 1.0, 1.0 # Dimensions of the rectangular region
v = 0.1 # Wave speed
omega = v * np.pi / Lx * (n**2 + m**2) # Angular frequency for the normal mode
# Create a spatial grid
Nx, Ny = 100, 100 # Number of grid points in each dimension
x, y = np.linspace(0, Lx, Nx), np.linspace(0, Ly, Ny)
X, Y = np.meshgrid(x, y)
# Compute the spatial part of the normal mode
Z = np.sin(m * np.pi * X / Lx) * np.sin(n * np.pi * Y / Ly) * np.cos(omega * t)
return X, Y, Z
def viz_membrane2d_plotly(n=1, m=1, t=0):
"""
Visualizes the 2D normal modes of a vibrating membrane on a square geometry using Plotly.
This function displays both a 2D contour plot and a 3D surface plot of the membrane displacement.
Parameters:
n (int): Mode number along the x-direction. Default is 1.
m (int): Mode number along the y-direction. Default is 1.
t (float): Time at which to evaluate the normal mode. Default is 0.
"""
# Get the membrane displacement at time t
X, Y, Z = membrane2d_mode(n, m, t)
# Create a Plotly subplot with 2 views: 2D contour and 3D surface
fig = make_subplots(
rows=1,
cols=2,
subplot_titles=('2D Contour Plot', '3D Surface Plot'),
specs=[[{"type": "contour"}, {"type": "surface"}]]
)
# Add 2D contour plot to the left side
fig.add_trace(
go.Contour(x=X.flatten(), y=Y.flatten(), z=Z.flatten(), colorscale='RdBu'),
row=1, col=1
)
# Add 3D surface plot to the right side
fig.add_trace(
go.Surface(x=X, y=Y, z=Z, colorscale='RdBu'),
row=1, col=2
)
# Update the layout for better visualization
fig.update_layout(
title_text="2D Contour and 3D Surface Plots of Membrane Vibrational Normal Modes",
width=1000,
height=500
)
# Show the figure
fig.show()
interact(viz_membrane2d_plotly, n=(1,1), m=(2,3), t=(0,100))<function __main__.viz_membrane2d_plotly(n=1, m=1, t=0)>The sound of music.¶
Music produced by musical instruments is a combination of sound waves with frequencies corresponding to a superposition of the normal modes (called harmonics or overtones in music) of those instruments.
Learn more from this series.

Figure 5:The size of a musical instrument reflects the range of frequencies it is designed to produce: smaller instruments produce higher frequencies, larger instruments lower frequencies.
Example Problems¶
Problem 1: Simple Harmonic Oscillator¶
Solve the ODE:
where is a constant.
Solution:
Problem 2: Damped Oscillator¶
Solve the ODE:
where and are constants. Consider cases of , and
Solution:
Problem 3: ODE with Constant Coefficients¶
Solve the ODE:
Solution:
Problem 4: Simple 2nd order ODE¶
Solve the ODE:
Solution:
Problem 5: When ODE gives repeated roots¶
Solve the ODE: