Skip to content

Periodic Poisson (FFT) ​

Solving the periodic Poisson equation with FFTs ​

We solve the Poisson equation on the unit square with periodic boundaries:

∇2u(x,y)=f(x,y),f(x,y)=−8π2sin⁡(2πx)sin⁡(2πy).

In Fourier space, the Laplacian becomes multiplication by λkx,ky=−4π2(kx2+ky2). We recover the solution with a forward FFT, division by these eigenvalues, and an inverse FFT:

u=F−1[F[f]λ].

The zero-frequency eigenvalue is zero because the solution is determined only up to an additive constant (∇2(u+C)=∇2u). We set that Fourier coefficient to zero to choose the zero-mean solution (C = 0).

julia
using cuNumeric

n = 64
ω = 2f0 * Float32(π)
xs = Float32.(0:(n - 1)) ./ Float32(n)

# Sample the prescribed forcing on the periodic grid.
f = NDArray([-2f0 * ω^2 * sin(ω * x) * sin(ω * y) for x in xs, y in xs])

# Laplacian eigenvalues.
k = NDArray(Float32.(vcat(0:(n ÷ 2 - 1), -(n ÷ 2):-1)))  # integer FFT frequencies
kx = cuNumeric.reshape(k, n, 1)
ky = cuNumeric.reshape(k, 1, n)
λ = (-ω^2) .* (kx.^2 .+ ky.^2)
λ[1:1, 1:1] .= 1f0 # insert placeholder, to avoid dividng by zero
λ = cuNumeric.as_type(λ, ComplexF32) # match `fft(f)` so the division doesn't promote

# Solve using cuNumeric.
u_hat = fft(f) ./ λ
u_hat[1:1, 1:1] .= 0f0  # Select the zero-mean solution (C = 0)
u = real(ifft(u_hat))

# Construct the analytical answer to check the result.
u_exact = [sin(ω * x) * sin(ω * y) for x in xs, y in xs]
error = fetch(maximum(abs.(u .- NDArray(u_exact))))
println("Maximum absolute error: ", error)

For this forcing, the exact zero-mean solution is

uexact(x,y)=sin⁡(2πx)sin⁡(2πy).

These sine modes are resolved by the grid, so the FFT solve recovers the analytical solution up to floating-point error.