every part builds on the one before. New to the topic? Start at part 01.
How they efficiently process global information through Fourier transform frequency coefficients.
The previous post built the Fourier transform: a signal is a sum of pure oscillations, and the transform recovers the amplitude and phase of each one. This post uses that machinery to build an actual architecture. The Fourier Neural Operator (FNO) is a neural operator in the sense of the neural operators post — it approximates the solution operator of a PDE, and it attaches its weights to resolution-independent objects rather than to grid points. In FNO those objects are frequency modes.
The post starts from the problem FNO exists to solve, then walks through every layer of the architecture in the order data flows through it, tracking the tensor shape at each step.
A convolution takes a short list of numbers, called the kernel, and slides it along a longer list, computing one weighted sum per position.
Where
Take the kernel
Where the three products pair the kernel weights
That kernel is not arbitrary. It is Post 2's three-point curvature stencil
A kernel of half-width
Layer 0 — the raw input. Point
Layer 1 — one convolution applied. Point
Layer 2 — a second convolution applied to layer 1's output. Point
Generalizing this growth pattern across
Where
Now put a number on it. For a domain discretized into 1000 grid points, connecting one end to the other requires
This matters directly for the wave equation. Post 6 established that information propagates at speed
The Fourier transform post gave the discrete Fourier transform.
Where
Every sample
The Fourier transform post established the mechanism; this section only fixes the reading of the two numbers stored per mode, because the architecture manipulates them directly.
Expanding the reference wave with Euler's formula splits the sum into two real sums.
Where
The two numbers can be read either as a pair or in polar form. The magnitude
Check this on a signal that is exactly one cosine. Take
Where
The result is the pair
The concrete problem for the rest of this post is one step of the wave equation's solution operator in two spatial dimensions. Post 4 established that the wave equation needs a two-field state: the displacement field
Discretize the domain on a 100 by 100 grid. The input to the network is then a tensor of shape (2, 100, 100): two channels, one per field, each holding a 100 by 100 array of grid values. A channel is a full spatial field; a grid cell holds one number per channel. At the very start, the two numbers at a given cell are that cell's displacement and velocity.
Every operation below is stated twice: what it does to the tensor shape, and what it does to the numbers sitting at one individual grid cell.
The first layer is a lifting layer: a linear map applied independently at every grid cell, which increases the channel count.
Where (4, 2),
Take a cell whose values are
Where each row of
Where the input column vector holds the cell's displacement and velocity, and the output column vector holds the four lifted values at that cell.
The same matrix (2, 100, 100) to (4, 100, 100).
Two channels are too narrow to carry the intermediate representation. The layers that follow mix channels together, and with only two channels there are just four mixing coefficients available per frequency mode — far too few to represent a useful learned transformation. Widening also breaks the interpretation: after lifting, the four numbers at a cell are learned features, not displacement and velocity. Nothing forces channel 1 to remain
Because the lifting map is the same at every position and involves no neighbours, it is resolution independent in the sense of the neural operators post: the same weights apply on a grid of any size.
The lifted tensor now enters the core of the architecture. An FNO block splits into two parallel paths. The spectral branch is the global one, and it runs in four steps.
Each of the 4 channels is transformed independently with a two-dimensional discrete Fourier transform. The Fourier transform post gave the two-dimensional reference wave; the transform is the same dot-product-against-a-reference-wave construction, now with a frequency index per axis.
Where
A naive implementation would produce a 100 by 100 array of complex coefficients per channel. Half of that is redundant. The input field is real-valued, and for real input the coefficients obey a conjugate symmetry.
Where the overline denotes complex conjugation, meaning the real part is unchanged and the imaginary part flips sign, and
The reason is that a real signal has no imaginary content to encode, so the negative-frequency half carries no new information: the sine similarity at mode
The Real-Values FFT exploits this by returning only the non-redundant half along the last axis. For a 100 by 100 field that is 51 columns, since (4, 100, 51), holding one complex number per channel per mode. This can honestly trip you up a bit, so I encourage you to make a deep dive into this topic, even though it is not necessary. Just remember, that our input value has no imaginary part. Throwing the signal into the DFT produces complex coefficients. However, the imaginary part does not contain any new information. Therefore, for the output signal to be real again, the imaginary parts of the coefficients must be conjugate symmetric for the imaginary parts to cancel out.
The next step discards most of those modes. Fix a mode count (4, 20, 10).
The asymmetry between 20 and 10 comes from the folding in Step 1. The second axis was folded, so its negative frequencies are already gone and keeping the 10 lowest means 10 columns. The first axis was not folded, so it still stores both signs: its 100 rows run from mode 0 upward through the Nyquist mode and then continue as the negative modes counting back toward
Low modes are kept because they carry almost all of a field's shape: the coarse structure holds most of the amplitude, and coefficient magnitudes decay as frequency rises.
The more important property is what a fixed mode count buys. Mode 7 names a shape — a wave that repeats 7 times across the whole domain — not a grid location. Sample that shape with 100 points or with 400 and it is still the same 7 repetitions, just measured more finely. So a weight attached to mode 7 stays attached to the same physical wave regardless of resolution, unlike a weight attached to "grid point 7," which points somewhere different at every resolution. That is why keeping the lowest
The cost is just as direct. Keep only 10 modes, and mode 11 upward is gone, not approximated. A sharp wavefront is made of exactly those discarded modes reinforcing each other at one spot and cancelling everywhere else; remove them and the edge slumps into a slope. Post 2's heat equation was going to smooth itself anyway, so this costs it nothing. The wave equation exists to keep its edges sharp, so the same cut fights the physics directly — FNO's known weak point on hyperbolic problems.
Each kept coefficient is multiplied by a learnable complex weight.
Where
Multiplying by a complex number rescales the coefficient's magnitude and rotates its phase, the two quantities defined in Section 2: the oscillation's amplitude changes and its peak shifts, while its frequency stays fixed.
So essentially such complex weight lets the network turn one frequency up or down, and slide that whole oscillation sideways — all of its repeating peaks move together, since a phase shift is just the same wave pattern translated along the grid — independently of every other frequency. Give it one such weight per mode, and it can learn to keep the frequencies that matter for reproducing the dynamics and suppress the ones that don't and by that, essentially move the fields forward in time.
Channels mix here the same way as in a normal convolutional layer: each output channel is a weighted combination of all input channels. The only difference is that every kept mode gets its own separate weights.
where
This makes the weight tensor's shape (4, 4, 20, 10). The coefficient tensor stays (4, 20, 10).
Post 7's widget runs this weight step in miniature: one channel instead of four, sine shapes on a string with fixed ends instead of complex waves, and every weight set to the exact solution operator for one time step
The multiplied block has shape (4, 20, 10), but the real-valued inverse Fast Fourier Transform (RFFT) expects a full coefficient array of shape (4, 100, 51). As we know, we intentionally discarded the high modes, so the missing entries are simply filled back up with zeros as coefficients.
This padding happens in frequency space, before the inverse transform. It does not restore the discarded content and therefore states that the model's output has no content at those frequencies. The inverse transform then returns to the grid, producing shape (4, 100, 100). The output of the spectral branch is a bandlimited field: it has no content above the cutoff frequency, and therefore cannot reproduce sharp edges. Note how this might become a problem for hyperbolic PDEs, which are known to maintain sharp wavefronts rather than smoothing them out. The local branch is introduced to address this problem.
Running alongside the spectral branch is a second path applied to the same lifted input. It is a linear map applied at each grid cell independently, with no neighbours involved — a 1 by 1 convolution.
Illustrate with two channels for readable arithmetic. Take the weight matrix and a cell holding
Where
The same (4, 100, 100).
This branch reads the untruncated input. The spectral branch saw only 200 modes, whereas the local branch sees the field as it actually is at each cell, including whatever high-frequency content the truncation in the spectral branch removed. It is therefore the one route by which fine detail can reach the block's output. There is a major limitation to this though: being pointwise, it can only pass a cell's own information forward. Independently of it's neighbours. Because of this, the local branch is literally blind to shapes, slopes and curvatures.
The obvious question is why this branch is not a 3 by 3 convolution, which would at least see neighbours. The answer is resolution invariance. A 3 by 3 kernel spans a fixed number of grid cells, not a fixed physical distance. On a unit-length domain with 100 points,
Hybrids that attach a U-Net to the FNO block do recover local detail and measurably improve sharp-feature accuracy, but they lose resolution invariance at their downsampling and upsampling steps. The direction a fix would take is to define the local operation as a continuous kernel function evaluated at the actual grid spacings
The two branches produce tensors of identical shape (4, 100, 100), and are combined by elementwise addition followed by a pointwise nonlinearity. That is the complete FNO block.
Where
Numerically, at one grid cell: suppose the spectral branch outputs (4, 100, 100) throughout.
A single block applies one global mixing step and one nonlinearity. A full FNO stacks (4, 100, 100) from the first block's output to the last.
Depth here buys something different from depth in a CNN. A CNN needs depth to grow its receptive field; an FNO already reaches globally in one block. Depth instead buys composition — repeated alternation between global linear mixing and pointwise nonlinearity — which is what allows the model to represent nonlinear solution operators rather than a single linear reweighting of modes.
The last block's output has 4 channels of learned features. The projection layer maps those back to the 2 physical fields.
Where (2, 4),
The shape returns to (2, 100, 100): displacement and velocity on the original grid, one time step ahead.
The full path through the network, in shapes:
(2, 100, 100) → lifting → (4, 100, 100) → block × (4, 100, 100) → projection → (2, 100, 100)
Each block splits into a spectral branch — transform to (4, 100, 51), truncate to (4, 20, 10), multiply by mode-indexed complex weights, zero-pad, inverse transform to (4, 100, 100) — and a pointwise local branch at the same shape, with the two summed and passed through a nonlinearity.
Real configurations are of the same order. Grids run from 64 to a few hundred points per side. The mode count does not scale with the grid: a model trained at 64 points per side with 12 modes is evaluated at 256 points per side with the same 12 modes and the same weights. That is what discretisation invariance means in practice.
The weakness is however, that truncation to