Here's the note:
Fourier Transform and Fast Multiplication
When to Reach for Fourier
Here we revise when Fourier is the right tool, before how it works. The thing you have to remember: the test is about the system, not the signal.
The LTI Trigger
Fourier diagonalises every operator that is linear and shift-invariant (LTI). Complex exponentials are its eigenfunctions.
- Linear: .
- Shift-invariant: commutes with the shift , i.e. .
- Consequence: . A sine goes in and the same sine comes out, only scaled and phase-shifted.
Why: the eigenvectors of the shift are exponentials, and an operator that commutes with the shift shares its eigenvectors (see Simultaneous Diagonalisation). Any LTI operator is a Convolution, and in the Fourier basis convolution becomes pointwise multiplication.
A one-off event (a clap, a drum strike) can still be analysed with Fourier if the medium it passes through is LTI. "Independent" means the system treats each frequency separately, not that the input's components are unrelated.
Blurring, smoothing, echo, diffusion, "each output is a weighted average of its neighbours", periodic forcing, and any sum whose indices add up ().
Calibration Drill
| Case | Fourier? | Why |
|---|---|---|
| Un-blurring a photo | Yes | Blur = convolution → divide spectra; regularise where the kernel's spectrum ≈ 0 (Wiener Filter) |
| Singing in the shower | Yes | LTI room; hard tiles boost resonant modes and add reverb |
| Tides | Yes | Periodic astronomical forcing + approximately linear ocean → sum of known-period constituents |
| Stock prices | No | Non-stationary, no stable periods |
| Big-integer multiplication | Yes | Long multiplication is a convolution of digits |
| Heat on a ring | Yes | Mode decays as ; sharp bumps die first |
| A single clap | Bad lens for the clap | …but an excellent probe: impulse ≈ all frequencies → gives the room's response |
| JPEG | Yes | DCT; high-frequency coefficients stored crudely |
| Guitar vs drum | Yes, both | String modes are integer multiples (pitch); drumhead modes are not (≈1, 1.59, 2.14…) |
| Counting a crowd | No | Localised objects are smeared over all frequencies (except regular grids) |
Anti-Triggers
- Localised in time or space (transients, edges) → Short-Time Fourier Transform, Wavelets. Uncertainty principle: narrow in time means broad in frequency.
- Nonlinear systems create new frequencies (harmonics), so no clean per-frequency description exists.
- Non-stationary rules (the system changes over time) break shift-invariance.
Convolution in Disguise
Multiplication Is Convolution
Write digits units-first, so position = power of 10: . The core fact is that exponents add when you multiply.
Example: , so and :
| (5) | (4) | |
|---|---|---|
| (3) | 15 → | 12 → |
| (2) | 10 → | 8 → |
| (1) | 5 → | 4 → |
Summing the anti-diagonals gives . Carrying then gives .
Schoolbook multiplication without carrying shows exactly the convolution:
1 2 3
× 4 5
5 10 15
4 8 12
4 13 22 15 ← c3 c2 c1 c0
- Carrying is separate: it uses floor and mod, so it is nonlinear. Do it once, after the convolution.
- Shifted copies: . This is a weighted sum of shifted copies of , structurally identical to a blur kernel.
The Flip and the ML Convolution
Sliding a kernel over "adjacent cells" (a CNN layer) is this operation. Adjacent cells = adjacent digit positions.
position: 0 3 2 1 0 (123 units-first, zero-padded)
k=0: [4 5] → 15
k=1: [4 5] → 22
k=2: [4 5] → 13
k=3: [4 5] → 4
| Math convolution | ML "convolution" | |
|---|---|---|
| Formula | ||
| Kernel | Flipped | Not flipped (cross-correlation) |
| Why it doesn't matter | — | Kernel is learned, so the network learns the flip |
: a step forward in one sequence is a step backward in the other. Reading order is irrelevant; only the relative flip matters.
Circular Convolution and Padding
The DFT indexes modulo , so it computes circular convolution: high positions wrap onto low ones. With , wraps into , giving ✗.
Pad to .
Same phenomenon as zero-padding vs circular padding in CNNs.
Other Disguised Convolutions
- Polynomial multiplication (coefficients).
- PMF of a sum of independent random variables → Characteristic Function.
- Moving averages, discretised diffusion.
- Wildcard string matching via FFT.
From Convolution to Pointwise Product
Evaluate → Multiply → Interpolate
Replace 10 by : , , and for every .
- : , , .
- : , , .
At a single point, the whole convolution collapses to one scalar multiplication.
How many points: . Two distinct polynomials of degree agreeing on points would have a nonzero difference of degree with roots, which is impossible.
Worked interpolation with points , -values :
| Logarithm tables | Fourier | |
|---|---|---|
| Transform | Evaluate at points | |
| Easy operation | Add | Multiply pointwise, |
| Inverse | Interpolate |
Switch to a representation where the hard operation becomes easy, provided switching is cheap. With arbitrary points, evaluation and interpolation cost , so nothing is gained.
Link to Karatsuba and Toom–Cook
Evaluating at small points is itself a real algorithm family. Karatsuba Algorithm: split into halves (degree-1 polynomials), evaluate at , so 3 products instead of 4, giving . Toom–3: points . The FFT is the limit of this idea, with points chosen so the transform itself is fast.
The FFT
Even/Odd Split
For : and . The is factored out of the odd terms, so is constant here.
Butterfly (from one pair of child values, get two parent values):
Going up = evaluating the nested expression from the inside out: . The brackets depend only on , so they are shared by .
Why Roots of Unity
The regrouping holds for any . Roots are needed so that the set of points halves at every level.
| Point set () | Distinct points per level | Saving |
|---|---|---|
| Random | 8 → 8 → 8 | None |
| 8 → 4 → 4 → 4 | One level only (squares are positive) | |
| 8th roots of unity | 8 → 4 → 2 → 1 | Every level |
Squaring doubles the angle, so the equally spaced points fold onto equally spaced points, which again form pairs: .
Levels and Twiddle Factors
Points are fixed by the level, not chosen by hand (, ):
| Level | Sub-polynomial size | Points |
|---|---|---|
| 0 | 1 | |
| 1 | 2 | |
| 2 | 4 | |
| 3 | 8 | every |
- At each level, the values are the points in the upper half of the circle, , and is the antipode.
- The butterfly at needs child values at , which were computed one level below.
- The multiplier is the twiddle factor.
Worked Example,
at :
Complexity
For : 3 levels × 4 butterflies = 12.
Inverse FFT
Evaluation at the roots is the Vandermonde Matrix . Orthogonality of sampled waves:
So interpolation is the same FFT with , divided by . With arbitrary points, inverting is expensive and ill-conditioned.
Where the Fourier Part Is
Evaluation at Roots = DFT
The powers of are a sampled wave with cycles. So = (sequence · probe wave), i.e. how much of frequency is in the sequence.
- : frequency 0 (DC, the mean).
- : the highest frequency (Nyquist).
Standard DFT convention uses : same thing, traversing the circle the other way.
| Algebra view | Signal view |
|---|---|
| at every point | Convolution theorem: |
| Evaluate at the roots of unity | Measure each frequency |
| Even/odd split + halving lemma | Cooley and Tukey's FFT (1965), anticipated by Gauss |
Eigenvector View
Convolution with is a Circulant Matrix , and the DFT diagonalises it:
The algebra explains why it's fast; the eigenvector view explains why it works for any LTI system.
Big-Integer Multiplication: Full Algorithm
Pipeline
- Digits units-first; pad to a power of 2 with .
- FFT both, multiply pointwise, inverse FFT.
- Round to nearest integer (removes floating-point noise).
- Carry, .
import cmath
def fft(a, inv=False):
n = len(a)
if n == 1:
return a
E, O = fft(a[0::2], inv), fft(a[1::2], inv) # even / odd split
s = -1 if inv else 1
out = [0] * n
for k in range(n // 2):
w = cmath.exp(s * 2j * cmath.pi * k / n) # twiddle factor
out[k] = E[k] + w * O[k] # A(x)
out[k + n // 2] = E[k] - w * O[k] # A(-x)
return out
def multiply(x, y):
a = [int(d) for d in str(x)[::-1]] # units first
b = [int(d) for d in str(y)[::-1]]
n = 1
while n < len(a) + len(b) - 1: # avoid wrap-around
n *= 2
A = fft(a + [0] * (n - len(a)))
B = fft(b + [0] * (n - len(b)))
c = fft([p * q for p, q in zip(A, B)], inv=True)
c = [round((v / n).real) for v in c] # 1/N, kill float noise
return sum(ck * 10**k for k, ck in enumerate(c)) # carry
assert multiply(123, 45) == 5535
State of the Art
- Number-Theoretic Transform: replace with (roots of unity exist mod suitable primes), so the arithmetic is exact.
- Schönhage and Strassen's algorithm (1971): rings , where 2 is a root of unity. .
- Harvey and van der Hoeven's algorithm (2019): , conjectured optimal.
- GMP: schoolbook → Karatsuba → Toom–Cook → FFT as the size grows.
Drawbacks
- Constant factors: the FFT wins only for large .
- Floating-point precision: rounding fails at large or large digit bases → NTT.
- Wrap-around if padding is forgotten.
CNN Layers Through the Fourier Lens
Conv Layer as Per-Frequency Matrices
Without the activation, a conv layer is LTI. In the Fourier domain:
So a conv layer is one matrix per frequency, with no mixing across frequencies.
- Fast conv via FFT for large kernels; 3×3 kernels use Winograd instead.
- Small kernels give blunt filters: compact support forces smooth frequency responses (uncertainty principle). Depth multiplies responses, which sharpens them.
- Exact singular values = singular values of the per-frequency matrices → Lipschitz control (Sedghi et al., ICLR 2019).
- Fourier Neural Operator (Anandkumar's group, ICLR 2021): learn the per-frequency weights directly; keep low modes.
Where the Picture Breaks
| Component | Breaks | Effect |
|---|---|---|
| ReLU | Linearity | Creates new frequencies; can't Fourier the whole net |
| Stride | Shift-invariance | Downsampling → Aliasing; fixed by blur-before-subsample (Richard Zhang, ICML 2019) |
| Zero padding | Circularity | Boundary effects; FFT assumes wrap-around |
Connections
Adjacent and Distant Ideas
- Logarithms: the original transform → cheap operation → inverse trick.
- Probability: characteristic functions turn sums of independent RVs into products.
- Heat equation: Fourier's original problem; each mode decays independently at a rate .
- Graph Fourier Transform: on a graph, Laplacian eigenvectors replace sines; this is the basis of spectral GNNs.
- Consensus dynamics: averaging on a ring or lattice is LTI, so disagreement decomposes into modes; long-wavelength disagreement decays slowest. This is relevant when thinking about how norms spread over a lattice of agents (norm-emergence).
Checklist
- State the LTI test and why exponentials are eigenfunctions of shift-commuting operators.
- Classify a new case as Fourier / not Fourier, asking about the system.
- Derive from multiplying powers of 10.
- Explain the kernel flip and why ML layers skip it.
- Padding rule and what goes wrong without it.
- Evaluate → multiply → interpolate; why points suffice.
- Even/odd split, the butterfly, and why real pairs fail at level 2.
- Which points each level uses, and the twiddle factor.
- Count: butterflies.
- Inverse FFT via .
- Interpret as frequency content; = DC, = Nyquist.
- Conv layer = per-frequency channel-mixing matrices; where ReLU, stride and padding break it.
- Why the FFT needs NTT at scale; Schönhage–Strassen vs Harvey–van der Hoeven.
I ran the Python and it matches Python's built-in multiplication on random 60-digit numbers. The note also includes the inverse FFT and the A(1) answer, which we hadn't reached in the walkthrough.
Fourier's heat problem — Step 1: Heat on a ring of cells
Fourier studied how temperature evolves in a solid, including a metal ring. Start with a ring cut into cells. At each time step, every cell moves a little toward the average of its two neighbours:
u_j ← u_j + α·(u_{j−1} − 2u_j + u_{j+1})
A cell hotter than its neighbours cools down, and a colder one warms up. Written as a kernel, the update is a sliding window with weights [α, 1−2α, α]: the same kernel applied at every cell, wrapping around the ring.
Example: 4 cells, α = 0.25, starting at [4, 0, 0, 0], so all the heat is in one spot.
- u₀ = 4 + 0.25·(0 − 8 + 0) = 2
- u₁ = 0 + 0.25·(4 − 0 + 0) = 1
- u₂ = 0 + 0.25·(0 − 0 + 0) = 0
- u₃ = 0 + 0.25·(0 − 0 + 4) = 1
After one step the heat is [2, 1, 0, 1]: the peak has spread to its neighbours, and the total is still 4.
Check (two parts):
- Is this update linear and shift-invariant? What does that tell you, given everything above?
- Compute the next step from [2, 1, 0, 1].
Old Note
The algorithm has been the same, some ideas are in Fourier Series, but architectures change, which means there are new ways to make this algorithm even faster.
Example of transforms
We have learned in Algebra lineare numerica, Cambio di Base that linear transforms are usually a change of basis. They are matrix vector multiplications (additions and multiplications by constants). The optimizations are based on what sorts of transforms we have (e.g. Sparse Matrix Vector Multiplication, or dense versions). The same idea applies also for Fourier transforms.
Discrete Fourier Transforms
Primitive roots
It uses primitive roots of as coefficients. Discrete Fourier transform computes:
Where is the primitive root of .
The matrix is a square matrix of size and the entries are given by:
Example of DFT matrices
At each index we will have the th unit of root. For example if we know that the root is , then the matrix would be:
This has two adds (one add and sub), it would be quite inefficient to write the multiplication part. This is usually called butterflies in signal processing, due to the nature of the circuit.
for , the primitive root is we have:
If we assume complex everything, it has 12 additions and 4 multiplications. This matrix can be factorized, then we can add it as a series of matrix vector products.
Decomposing into sparse MVM
If , we can write:
This makes sense if are sparse (see Sparse Matrix Vector Multiplication, and not too many steps).
You can use Group Theory to find this kind of transformations.
The above representation can be rewritten using matrix algebra:
You can see that it is also quite easy to implement it in hardware.
It can be similar for size and radix .

Other transforms
IMDCT and DCT are useful transforms for MPEG and JPEG format, while DFT and its inverse can be considered universal transformation tools.
FFT transformations
General Radix Recursive Cooley-Tukey FFT
This is the most general version of DFT, it has a similar structure compared to the previous ones.
The diagonal matrix has the roots of unity. This is called the decimation in time frequency. It is possible also to use the decimation in frequency, which is just the transpose. We can decompose everything until we have size two butterfly operations (bunch of adds).
Cost of DFT
Independently of recursion (the radix), we have a number of
- additions and multiplications, but to be real we would need to consider real additions, so we have to multiply everything by two, to get the adds. For multiplication, we have four multiplication and two additions (image it as a matrix) (in reality you use the structure of roots of unity, so in reality you have less adds). Usually best is around radix 8
- You will have about real additions and complex multiplications, so upper bound of 5 operations.
For a long time radix 2 iterative version was the best version, after superscalar processors like Skylake Microprocessor it has better versions.
There is a practical lower bound analysis, but that is like Strassen Fast Linear Algebra, which is not much useful.
Recursive vs iterative FFT
The recursive version is good for re-use, has better locality. Triple loop is like the blocked MMM in Fast Linear Algebra. If we have big stages computed, we can continue with the next stage immediately.
Iterative similar, the op count is the same, is slower because of the (log n passes through the data). The code change is quite difficult. If we have much data, then at each iteration we need to load the whole data which could be expensive.
Decimation in Time and Frequency
TODO
Other FFTs
Pease FFT
Highly parallelized with maps and shuffles.

Stockham FFT
It is good for vector computers of the 80s, where we have long vectors

Six-Step FFT

Multi-core FFT
Good for Sharing memory.

Fastest Fourier Transform of the West
Also known as FFTW, we say.
Ideas for FFTW
Merging Steps
- In the original version, the original permutation could be very expensive, to load everything. There is no need to keep it as a sperate step, we can fuse it in the next step.
- This fusion would need more data, because it needs to write to another place (no-inplace computation!).
- The scaling can also be fused, with the previous or next step (just change the kernel).

Strides will change as we recurse for the DFTrec, and we can also interchange input and output to save space. Because of the power of two strides, often it is very difficult to reach peak performance.
Constants
We precompute the constants of the sins and cosines to save time (just need what roots of unity you need in order to compute it). They just depend on the input size, so they precompute it at the beginning of the function.
Block un-rolling
We un-roll the basic blocks at the end to get longer codes, less recursion (size 32 is good basecase for example).
Program Generator
We have DAG generator (for many types of FFT), a simplifier for the DAG (specific for the FFT computation).
- An example of a simplification is when you have and , an optimization would be flipping one after you have computed one.
- Also constants come in pairs (minus and positive version).
- Reduces register pressure.