In fluid dynamics, spectral-Galerkin methods are commonly used to solve the incompressible Navier-Stokes equations. By transforming the equations into spectral space, the nonlinear terms can be computed efficiently while maintaining conservation properties; and the viscous term can be easily solve implicitly. For incompressible flows, the vorticity-streamfunction formulation is particularly usable, as it eliminates the pressure term and reduces the computational complexity.
In this lecture, we will focus on applying spectral methods to solve the 2D incompressible hydrodynamics equations. Specifically, we will:
Derive the governing equations in spectral space.
Discuss the vorticity-streamfunction formulation and its advantages.
Implement a spectral solver to simulate the evolution of vorticity in a 2D periodic domain.
This introduction sets the stage for understanding how spectral methods leverage mathematical elegance and computational efficiency to solve complex PDEs with remarkable accuracy.
2D Incompressible Hydrodynamics¶
The hydrodynamic equations govern the conservation of mass and momentum in a fluid.
In their compressible form (that we derived in previous lectures), they are written as:
where:
is the density,
is the velocity field,
is the pressure,
is the dynamic viscosity, and
is an external force.
In the incompressible limit, the sound speed approaches infinite . For simplicity, the density can be assumed constant, and the continuity equation reduces to the incompressibility condition:
Substituting this condition into the momentum equation simplifies the Navier-Stokes equations to:
where is the kinematic viscosity and is usually still called external “force”. These equations describe the flow of incompressible fluids. They are widely used in modeling low Mach number fluid from small-scale laboratory experiments to large-scale geophysical flows.
While the incompressible Navier-Stokes equations describes three-dimensional flows, many physical systems can be effectively approximated as two-dimensional. For example:
Atmospheric flows and ocean currents are largely horizontal due to their vast spatial extent compared to their depth.
Thin liquid films and confined flows are geometrically restricted to two dimensions.
To simplify the mathematical and computational treatment of 2D incompressible hydrodynamics, the governing equations are often reformulated in terms of the vorticity and the streamfunction .
This formulation has several advantages: it eliminates the pressure term from the equations, reduces the number of variables, and ensures incompressibility is automatically satisfied.
Vorticity-Streamfunction Formulation¶
For 2D incompressible flows, the velocity field can be expressed in terms of a scalar function, the streamfunction , as:
where is the unit vector perpendicular to the 2D plane. In component form:
This representation automatically satisfies the incompressibility condition:
# Let's visualize the streamfunction
from jax import numpy as np
Lx, Ly = 2*np.pi, 2*np.pi # domain size
Nx, Ny = 256, 256 # number of grid points
x, dx = np.linspace(-Lx/2, Lx/2, Nx, endpoint=False, retstep=True)
y, dy = np.linspace(-Ly/2, Ly/2, Ny, endpoint=False, retstep=True)
x, y = np.meshgrid(x, y)psi = np.exp(-(x*x + y*y)/2)
ux = -y * psi
uy = +x * psifrom matplotlib import pyplot as plt
fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(8,4))
ax0.quiver(x[::8,::8], y[::8,::8], ux[::8,::8], uy[::8,::8])
ax0.set_aspect('equal')
ax1.contour(x, y, psi)
ax1.set_aspect('equal')The vorticity is a scalar quantity in 2D, defined as the curl of the velocity field:
Using the velocity components in terms of the streamfunction, the vorticity can be written as:
Thus, the vorticity and streamfunction are related by the Poisson equation:
This relationship allows the velocity field to be computed from the vorticity by solving the Poisson equation for , and then taking derivatives of to find and .
omega = -(x*x + y*y - 2) * psifig = plt.figure(figsize=(8,4))
ax0 = fig.add_subplot(1, 2, 1, projection='3d')
ax1 = fig.add_subplot(1, 2, 2, projection='3d')
ax0.plot_surface(x, y, omega)
ax0.set_title(r"Vorticity $\omega$")
ax1.plot_surface(x, y, psi)
ax1.set_title(r"Streamfunction $\psi$")The vorticity transport equation is derived from the incompressible Navier-Stokes equations. Taking the curl of the momentum equation eliminates the pressure gradient term,
where:
represents the nonlinear advection of vorticity,
accounts for viscous diffusion,
is the vorticity-specific forcing term.
The term can be expanded using the velocity components as:
By substituting and in terms of , the nonlinear advection term is rewritten as the Jacobian determinant:
Thus, the vorticity transport equation becomes:
Ekman Damping¶
Ekman damping models frictional effects caused by the interaction of the fluid with a boundary layer. It acts as a large-scale energy sink and is proportional to the vorticity , where is the Ekman coefficient.
Including this term in the vorticity transport equation gives:
Ekman damping is particularly relevant in geophysical systems, where it represents energy dissipation due to the Earth’s surface or ocean floors.
-Plane Model¶
The -plane approximation models the variation of the Coriolis parameter with latitude. In the vorticity equation, this introduces a term proportional to the northward velocity component, i.e., , where is the linear expansion coefficient of the Coriolis parameter.
Including this term in the vorticity transport equation gives:
The beta-plane term is crucial for studying large-scale atmospheric and oceanic dynamics, as it leads to phenomena such as Rossby waves and geostrophic turbulence.
In addition to damping and planetary rotation effects, many fluid systems are subject to external forcing that injects energy into the flow. External forcing terms are introduced into the vorticity equation through , and their form strongly influences the resulting turbulence and flow structures.
Kolmogorov Forcing¶
Kolmogorov forcing is one of the most widely used deterministic forcing schemes in studies of two-dimensional turbulence. It drives the flow through a sinusoidal body force that acts at a specific wavenumber . In vorticity form, the forcing can be written as
where is the forcing amplitude and is the forcing wavenumber.
This forcing continuously injects energy at large scales and maintains a statistically steady turbulent state by balancing viscous dissipation. Kolmogorov forcing is particularly useful for studying cascades of energy and enstrophy in spectral simulations, since it creates a controlled, spatially periodic source of turbulence. However, note that Kolmogorov forcing does not provide constant energy input to the turbulence system.
Random Forcing¶
An alternative is to model forcing as a stochastic process. Random forcing mimics the unpredictable nature of real-world turbulence drivers, such as atmospheric convection, wind stress on the ocean surface, or small-scale instabilities.
In practice, random forcing is often applied in Fourier space, where modes in a narrow band of wavenumbers around are driven with random amplitudes and phases:
where is a stochastic process, commonly chosen as Gaussian white noise with prescribed variance.
Random forcing produces flows that are less structured than Kolmogorov forcing, and is often employed to study statistical steady states of turbulence. It provides a more realistic representation of natural geophysical systems, where energy input is irregular and multi-scale. Note that, however, random forcing provides a statistically constant energy input to the turbulence system.
Inverse Energy Cascade¶
2D turbulence shows unique features that distinguish them from 3D flows: Conservation of Enstrophy.
In 2D, the vorticity is a scalar field. Its evolution is governed by the vorticity transport equation that we derive, which conserves both energy and enstrophy in the absence of dissipation:
Energy conservation governs the total kinetic energy of the system, while enstrophy conservation introduces a second constraint that strongly influences the flow dynamics.
A striking feature of 2D turbulence is the inverse energy cascade. In 3D turbulence, energy flows from large scales (low wavenumbers) to small scales (high wavenumbers) and is dissipated by viscosity. In 2D, however, energy flows in the opposite direction, from small scales to large scales, leading to the formation of large, coherent structures like cyclones and anticyclones. This behavior is directly tied to the dual conservation of energy and enstrophy.
Spectral Equation¶
The vorticity-streamfunction formulation simplifies the governing equations of 2D incompressible hydrodynamics, making them naturally suited to spectral methods. By representing the vorticity and streamfunction in terms of Fourier series, we can efficiently compute derivatives and nonlinear terms in spectral space.
Fourier Representation of Vorticity and Streamfunction¶
For a periodic domain of size , the vorticity and streamfunction can be expanded as Fourier series:
where:
and are the Fourier coefficients of vorticity and streamfunction, respectively.
and are the wavenumbers in the - and -directions, given by:
Poisson Equation in Spectral Space¶
The Poisson equation relates the vorticity and the streamfunction :
Recalling in spectral space, derivatives with respect to and transform into simple multiplications by and , respectively. For example:
This property makes spectral methods computationally efficient, as derivatives reduce to element-wise multiplications.
Hence, in Fourier space, the Laplacian becomes multiplication by , where . Thus, the Poisson equation transforms into:
Note that the mode corresponds to the mean value of , which can be set to zero for flows with no net circulation.
Vorticity Transport Equation in Spectral Space¶
Recalling the vorticity transport equation in real space is:
In spectral space:
The Laplacian term transforms into .
The Ekman damping term becomes .
The Coriolis term becomes .
The nonlinear term is evaluated in real space and transformed back to spectral space using the Fast Fourier Transform (FFT).
The equation in spectral space becomes:
# Create spectral grid
kx = 2*np.pi * np.fft.fftfreq(Nx, d=Lx/Nx)
ky = 2*np.pi * np.fft.fftfreq(Ny, d=Ly/Ny)
kx, ky = np.meshgrid(kx, ky)
kk = kx*kx + ky*ky
ikk = 1.0 / (kk + 1.2e-38)
ikk = ikk.at[0,0].set(0) # avoid multiplied by infinity for mean mode
print(ikk[0:4,0:4])from jax.numpy.fft import fft2
# Initialize vorticity and streamfunction in spectral space
omega = np.exp(-0.5*(x*x + y*y)*16) # example initial vorticity
# Transform to Fourier (spectral) space
Omega = fft2(omega)
Psi = ikk * Omegafrom jax.numpy.fft import ifft2
# Obtain velocity field
def vel(Psi):
psi_x = ifft2(1j * kx * Psi).real
psi_y = ifft2(1j * ky * Psi).real
return psi_y, -psi_x
def plot(Omega, skip=Nx//32, cmap='bwr'):
Psi = ikk * Omega
ux, uy = vel(Psi)
plt.imshow(ifft2(Omega).real, origin='lower', extent=[-Lx/2,Lx/2,-Ly/2,Ly/2], cmap=cmap)
plt.quiver(x[::skip,::skip], y[::skip,::skip], ux[::skip,::skip], uy[::skip,::skip])
plot(Omega, cmap='Reds')# Compute Jacobian determinant in real space
def Jdet(Psi, Omega):
psi_x = ifft2(1j * kx * Psi ).real
psi_y = ifft2(1j * ky * Psi ).real
omega_x = ifft2(1j * kx * Omega).real
omega_y = ifft2(1j * ky * Omega).real
return fft2(psi_x * omega_y - psi_y * omega_x)
J = Jdet(Psi, Omega)
print(np.max(abs(ifft2(J).imag)))Handling Aliasing Errors¶
Nonlinear terms, such as , involve products in real space that translate into convolutions in spectral space. Due to the finite resolution of the grid, these convolutions can introduce spurious interactions between modes known as aliasing errors.
The 2/3 rule is a widely used dealiasing technique that truncates Fourier modes beyond of the maximum wavenumber. For a grid with points in each dimension:
Retain modes for .
Set all other modes to zero.
The 2/3 rule ensures that spurious contributions from nonlinear interactions fall outside the resolved spectral range.
# Apply the 2/3 rule
def twothird(F):
lx = Nx // 3
ly = Ny // 3
F = F.at[:,lx:-lx+1].set(0)
F = F.at[ly:-ly+1,:].set(0)
return F
# Apply de-aliasing to the Jacobian
J = twothird(J)
plt.imshow(np.log(abs(J)))
print(np.max(abs(ifft2(J).imag)))Time Integration of the Spectral Equations¶
After reformulating the vorticity-streamfunction equations in spectral space, the next step is to integrate the vorticity transport equation in time. Time integration involves advancing the Fourier coefficients of vorticity while accurately handling the nonlinear and linear terms. This section discusses suitable time-stepping schemes, their implementation, and how they are applied to the spectral representation of the equations.
Time-Stepping Schemes¶
We need time integration methods for the spectral vorticity transport equation must balance stability, accuracy, and computational efficiency. The commonly schemes to consider for spectral methods are:
Explicit Schemes¶
Explicit schemes, such as the Runge-Kutta (RK) family, compute the solution at the next time step based on known quantities at the current time step. They are easy to implement and efficient for problems dominated by advection or nonlinear dynamics.
Implicit Schemes¶
Implicit schemes, such as Crank-Nicolson, are unconditionally stable for linear terms but require solving a system of equations at each time step. It is uncommon to use a purely implicit scheme to integrate even the nonlinear term in spectral methods.
Semi-Implicit Methods¶
However, it makes sense to use implicit schemes for the linear terms (e.g., viscous diffusion) to allow larger time steps, while keep explicit schemes for nonlinear terms.
A common approach splits the equation into linear and nonlinear parts:
where:
represents nonlinear terms (e.g., Jacobian).
represents linear terms (e.g., viscous diffusion, Ekman damping, Coriolis effects),
The linear terms are treated implicitly, while the nonlinear terms are advanced explicitly. For example, the time-discretized form is:
This approach combines the stability of implicit methods with the simplicity of explicit methods.
Time Integration of the Vorticity Transport Equation¶
The spectral vorticity transport equation is:
Breaking this into linear and nonlinear parts:
Nonlinear Terms: Jacobian determinant and external forcing .
Linear Terms: Viscous diffusion , Ekman damping , and Coriolis effect .
Using a semi-implicit scheme:
Advance the nonlinear terms explicitly to by, e.g., Runge-Kutta methods.
Solve the linear terms implicitly using the formula:
# Initialize vorticity and streamfunction in spectral space
from jax.random import key, normal
ux = np.where(y >= 0, 1, -1) # Example initial vorticity
uy = 0.5 * normal(key(0), shape=x.shape)Omega = twothird(1j * kx * fft2(uy) - 1j * ky * fft2(ux))
plot(Omega)# Define simulation parameters
N = Nx* 10 # number of time steps
S = Nx//10 # number of skip between outputs
dt = 1.0/Nx # time step
nu = 0.001 # viscosity
mu = 0.0 # Ekman damping
beta = 0.0 # Coriolis parameterfrom jax import jit
@jit
def nonlinear(Omega):
# Obtain stream function
Psi = ikk * Omega
# Compute nonlinear term (Jacobian determinant)
J = Jdet(Psi, Omega)
J = twothird(J) # Apply 2/3 rule
return J
@jit
def semiimplicit(Omega):
# Explicit RK4
k1 = nonlinear(Omega )
k2 = nonlinear(Omega + 0.5 * dt * k1)
k3 = nonlinear(Omega + 0.5 * dt * k2)
k4 = nonlinear(Omega + dt * k3)
Omega = Omega + dt * (k1/6 + k2/3 + k3/3 + k4/6)
# TODO: External forcing
# Implicit backward Euler
Omega = Omega / (1 + dt * (nu * kk + mu - (1j * beta) * (kx * ikk)))
return Omegafrom tqdm import tqdm
! rm -rf imgs && mkdir imgs
# Time-stepping loop
for n in tqdm(range(N//S)):
plot(Omega)
plt.savefig(f'imgs/{n:04d}.png', dpi=144, bbox_inches='tight')
plt.close()
for j in range(S):
Omega = semiimplicit(Omega)
# Plot and save the last frame
plot(Omega)
plt.savefig(f'imgs/{n+1:04d}.png', dpi=144, bbox_inches='tight')# Let's combine the png files into a movie:
! rm -f KH.mpg && ffmpeg -i imgs/%04d.png -loglevel panic -qmax 2 KH.mpgTo solve the full vorticity transport equation numerically, we adopted a semi-implicit time integration scheme:
The nonlinear advection term is integrated explicitly with a high-order Runge–Kutta (RK4) method, ensuring accuracy in capturing nonlinear interactions.
The linear dissipative terms (viscosity and Ekman damping) are treated implicitly using backward Euler, which provides unconditional stability against stiff linear decay.
This combination gives us a robust, efficient method capable of evolving turbulent flows for long times while maintaining both stability and accuracy.
Closing Thought¶
With this, we conclude the first half of ASTR 513 on computational methods. We have built up the foundations of numerical analysis and applied them to some of the most beautiful and challenging problems in physics: nonlinear dynamics and turbulence.
The real power of computational physics is not just in solving equations, but in enabling us to explore phenomena beyond the reach of experiment and pure theory. May the tools you now hold inspire you to look deeper, simulate boldly, and discover what lies hidden in the flow of nature.