Fourier Series
Key Points
- •A Fourier series rewrites any reasonable periodic function as a weighted sum of sines and cosines (or complex exponentials).
- •The weights (Fourier coefficients) capture amplitudes and phases of the function’s harmonics and can be computed by integrals or numerically from samples.
- •Orthogonality of sine and cosine waves makes the decomposition unique and efficient for analysis and reconstruction.
- •In practice, we approximate integrals by sums; with equally spaced samples this becomes the Discrete Fourier Transform (DFT), often computed via the FFT.
- •Fourier series converge pointwise for piecewise smooth periodic functions and in mean-square for all square-integrable periodic functions.
- •Discontinuities cause the Gibbs phenomenon: persistent overshoots near jumps that do not vanish but localize as more terms are added.
- •C++ implementations can compute coefficients directly in O(NM) or use FFT for O(N N) to accelerate from many samples.
- •Correct scaling, sampling rate (Nyquist), and domain normalization (period 2 vs. 2L) are essential to avoid errors.
Prerequisites
- →Trigonometry (sine, cosine, radians) — Fourier series combine and project onto sine and cosine waves with specific frequencies.
- →Calculus: definite integrals — Fourier coefficients are defined by integrals over one period.
- →Complex numbers and Euler’s formula — Complex exponential form c_n e^{inx} simplifies derivations and FFT connections.
- →DFT and FFT basics — Efficient numerical computation of coefficients relies on understanding the DFT/FFT.
- →Numerical integration (Riemann/trapezoid) — Approximating integrals from samples is the practical route when f is not integrable in closed form.
- →Sampling theory and Nyquist rate — Prevents aliasing and ensures correct reconstruction from discrete samples.
- →Inner products and orthogonality — Coefficient formulas come from projecting onto an orthogonal basis.
Detailed Explanation
Tap terms for definitions01Overview
Hook → Think about music: a complex chord sounds like one thing, yet it’s really a blend of pure notes (pitches). Concept → A Fourier series says every periodic signal can be expressed as a sum of simple waves—sines and cosines—at integer multiples of a base frequency. Each term’s weight tells you “how much” of that harmonic is present. Mathematically, this is possible because these waves are mutually orthogonal, so they don’t interfere when we measure their contributions. Example → A square wave that flips between +1 and −1 doesn’t look sinusoidal, but its Fourier series is a sum of odd harmonics with decreasing amplitudes. Truncating the series gives a close approximation, and adding more terms refines it. In computing, we rarely compute exact integrals; instead, we sample the function over one period and use weighted sums. With uniform sampling, this is exactly what the Discrete Fourier Transform (DFT) does, and the Fast Fourier Transform (FFT) computes it quickly. Engineers and scientists use Fourier series to analyze signals, filter noise, compress data, and solve differential equations (like the heat equation), because moving to the “frequency domain” often simplifies problems dramatically.
02Intuition & Analogies
Imagine building any complex shape from simple LEGO bricks. For periodic functions, those bricks are pure sine and cosine waves. Each sine/cosine has a frequency (how fast it oscillates), amplitude (how tall it is), and phase (where its peaks land). Just as different combinations of LEGO bricks produce different models, different combinations of sine/cosine waves produce different periodic signals. A tuning app listens to a guitar string and identifies a base pitch (fundamental frequency) and its overtones (harmonics). That’s the Fourier series in action: decomposing a time signal into a stack of pure tones. Another picture is spinning arrows (phasors): imagine several arrows rotating at constant speeds (frequencies). At each time, add their tip positions: the sum traces your signal. Changing an arrow’s length or initial angle changes the contribution’s amplitude and phase. Orthogonality is like grading exams with non-overlapping questions: a score in one section doesn’t affect another. Sines and cosines at different frequencies don’t “leak” into each other when integrated over a full period, so we can measure each harmonic cleanly. Finally, discontinuities are like trying to draw a sharp corner with smooth curves—you can get very close but near the corner you will always see a small wiggle or overshoot (Gibbs phenomenon). As you add more harmonics, the wiggle narrows but doesn’t vanish in height; away from the corner, the approximation becomes excellent.
03Formal Definition
04When to Use
- Signal analysis and filtering: To identify or modify frequency content (e.g., remove 60 Hz hum), compute Fourier coefficients and zero-out undesired bands.
- Solving PDEs on bounded intervals with periodic or homogeneous boundary conditions: Fourier series diagonalize linear operators like derivatives, turning differential equations into algebraic ones.
- Compression and synthesis: Periodic textures, audio synthesis, and procedural animation benefit from storing just a few significant harmonics.
- Numerical approximation of periodic functions: Spectral methods use truncated Fourier series to achieve very high accuracy for smooth functions.
- Fast convolution/correlation on periodic domains: Work in frequency space (coefficients) where convolution becomes multiplication. Use the integral formulas when you have analytic expressions and can integrate. Use uniform sampling plus DFT/FFT when you have data or a black-box function. Prefer FFT when you have many samples (N large) or need repeated transforms; direct O(NM) quadrature is fine for small N and M. Always ensure the sampling rate satisfies Nyquist and the period is normalized consistently (2\pi vs. 2L).
⚠️Common Mistakes
- Wrong scaling and domain: Mixing 2\pi-periodic formulas with [-L,L] formulas leads to missing factors of \pi or L. Always normalize x and double-check the 1/L, 1/\pi factors in coefficients.
- Insufficient sampling (aliasing): Sampling too slowly folds high frequencies into low ones, corrupting coefficients. Ensure sampling frequency f_s satisfies f_s \ge 2 f_{\max}.
- Ignoring periodicity alignment: Sampling over a non-integer number of periods causes spectral leakage. Sample exactly one (or an integer number of) full periods or apply a window if not possible.
- Expecting Gibbs to vanish: The overshoot near discontinuities persists (~9%) though it localizes. Increasing terms narrows the region but doesn’t remove the peak height.
- Endpoint duplication: In discrete sums on [0,2\pi], avoid double-counting the endpoint 2\pi which equals 0 for periodic functions.
- Numerical instability or drift: Forgetting normalization by N in DFT/FFT, or mixing forward/inverse sign conventions, yields wrong amplitudes/phases.
- Overfitting noise: Keeping too many high-frequency coefficients can amplify noise; use smoothing or low-pass filtering when appropriate.
Key Formulas
Real Fourier Series (period 2L)
Explanation: Any sufficiently nice 2L-periodic function can be written as a sum of cosines and sines. The coefficients and determine how much of each harmonic is present.
Coefficient Formulas (real)
Explanation: These integrals measure the projection of f onto each cosine/sine basis function. They come from orthogonality over one full period.
Complex Fourier Series
Explanation: The complex form packs amplitude and phase into a single complex number per harmonic. It is algebraically convenient and maps directly to the DFT.
Real–Complex Relation
Explanation: You can convert between real (, ) and complex () coefficients. For real-valued functions, is the complex conjugate of .
Orthogonality (cosines)
Explanation: Different harmonics are orthogonal over a full period. This property makes coefficient extraction by inner products straightforward.
Parseval’s Identity
Explanation: Average energy in the time domain equals the sum of squared Fourier coefficients. This justifies energy preservation under Fourier decomposition.
Mean-Square Truncation Error
Explanation: The error of truncating to N harmonics equals the leftover energy in the tail of coefficients. As coefficients decay, the error decreases.
Discrete Approximation (Riemann sum)
Explanation: Approximating integrals by uniform sums over one period yields simple, practical formulas for coefficients. This is equivalent to using the DFT with proper scaling.
Discrete Complex Coefficients
Explanation: Sampling a 2-periodic function at N points gives coefficients via the DFT normalized by 1/N. This maps directly to FFT-based computation.
Nyquist–Shannon Criterion
Explanation: To avoid aliasing when sampling, the sampling frequency must be at least twice the highest frequency present in the signal.
Complexity Analysis
Code Examples
1 #include <iostream> 2 #include <vector> 3 #include <cmath> 4 #include <iomanip> 5 6 // This example computes a0, an, bn for a 2π-periodic function using uniform samples. 7 // We use f(x) = square wave: +1 for sin(x) >= 0, else -1. 8 // Coefficient formulas for 2π-periodic f: 9 // a0 = (1/π) ∫_{-π}^{π} f(x) dx ≈ (2/N) Σ f(x_k) 10 // an = (1/π) ∫ f(x) cos(nx) dx ≈ (2/N) Σ f(x_k) cos(n x_k) 11 // bn = (1/π) ∫ f(x) sin(nx) dx ≈ (2/N) Σ f(x_k) sin(n x_k) 12 13 static inline double PI() { return std::acos(-1.0); } 14 15 double f(double x) { 16 return (std::sin(x) >= 0.0) ? 1.0 : -1.0; // 2π-periodic square wave 17 } 18 19 int main() { 20 const int N = 4096; // number of uniform samples over [0, 2π) 21 const int M = 15; // number of harmonics to compute 22 23 const double twoPI = 2.0 * PI(); 24 const double h = twoPI / N; // step size 25 26 // Precompute samples 27 std::vector<double> samples(N); 28 for (int k = 0; k < N; ++k) { 29 double xk = (twoPI * k) / N; // x in [0, 2π) 30 samples[k] = f(xk); 31 } 32 33 // Compute coefficients 34 double a0 = 0.0; 35 for (int k = 0; k < N; ++k) a0 += samples[k]; 36 a0 = (2.0 / N) * a0; // (1/π) * h * Σ = (1/π) * (2π/N) * Σ = 2/N * Σ 37 38 std::vector<double> an(M + 1, 0.0), bn(M + 1, 0.0); 39 for (int n = 1; n <= M; ++n) { 40 double sumC = 0.0, sumS = 0.0; 41 for (int k = 0; k < N; ++k) { 42 double xk = (twoPI * k) / N; 43 sumC += samples[k] * std::cos(n * xk); 44 sumS += samples[k] * std::sin(n * xk); 45 } 46 an[n] = (2.0 / N) * sumC; 47 bn[n] = (2.0 / N) * sumS; 48 } 49 50 // Print a0/2, then first few an, bn (expect only odd bn nonzero for a square wave) 51 std::cout << std::fixed << std::setprecision(6); 52 std::cout << "a0/2 = " << (a0 / 2.0) << "\n"; 53 for (int n = 1; n <= M; ++n) { 54 std::cout << "n=" << n 55 << ": an=" << an[n] 56 << ", bn=" << bn[n] << "\n"; 57 } 58 return 0; 59 } 60
We uniformly sample a 2π-periodic function and approximate the defining integrals by Riemann sums. For the square wave, theory predicts a0=0, an≈0, and bn≈4/(π n) for odd n and 0 for even n; the output numerically confirms this. Uniform sampling over exactly one period avoids endpoint duplication and leakage.
1 #include <iostream> 2 #include <vector> 3 #include <cmath> 4 #include <iomanip> 5 6 static inline double PI() { return std::acos(-1.0); } 7 8 double f(double x) { 9 // 2π-periodic square wave 10 return (std::sin(x) >= 0.0) ? 1.0 : -1.0; 11 } 12 13 // Evaluate S_M(x) = a0/2 + sum_{n=1}^M (an cos(nx) + bn sin(nx)) 14 double partial_sum(double x, double a0, 15 const std::vector<double>& an, 16 const std::vector<double>& bn) { 17 double s = 0.5 * a0; 18 int M = (int)an.size() - 1; 19 for (int n = 1; n <= M; ++n) { 20 s += an[n] * std::cos(n * x) + bn[n] * std::sin(n * x); 21 } 22 return s; 23 } 24 25 int main() { 26 const int N = 4096; // samples for coefficient estimation 27 const int M = 25; // number of harmonics in reconstruction 28 29 const double twoPI = 2.0 * PI(); 30 31 // 1) Estimate coefficients from uniform samples 32 std::vector<double> samples(N); 33 for (int k = 0; k < N; ++k) { 34 double xk = (twoPI * k) / N; 35 samples[k] = f(xk); 36 } 37 38 double a0 = 0.0; 39 for (double v : samples) a0 += v; 40 a0 = (2.0 / N) * a0; 41 42 std::vector<double> an(M + 1, 0.0), bn(M + 1, 0.0); 43 for (int n = 1; n <= M; ++n) { 44 double sumC = 0.0, sumS = 0.0; 45 for (int k = 0; k < N; ++k) { 46 double xk = (twoPI * k) / N; 47 sumC += samples[k] * std::cos(n * xk); 48 sumS += samples[k] * std::sin(n * xk); 49 } 50 an[n] = (2.0 / N) * sumC; 51 bn[n] = (2.0 / N) * sumS; 52 } 53 54 // 2) Evaluate the partial sum at grid points and print CSV: x, S_M(x), f(x) 55 std::cout << std::fixed << std::setprecision(6); 56 const int G = 200; // visualization grid 57 for (int i = 0; i < G; ++i) { 58 double x = (twoPI * i) / G; // [0, 2π) 59 double s = partial_sum(x, a0, an, bn); 60 std::cout << x << "," << s << "," << f(x) << "\n"; 61 } 62 return 0; 63 } 64
This program estimates coefficients for the square wave and then reconstructs the M-term partial sum on a grid, printing CSV lines (x, approximation, true value). Plotting the output (e.g., in Python or a spreadsheet) reveals oscillations near the jump that narrow but persist in height as M increases—the Gibbs phenomenon.
1 #include <iostream> 2 #include <vector> 3 #include <complex> 4 #include <cmath> 5 #include <iomanip> 6 #include <algorithm> 7 8 // Radix-2 iterative FFT computing DFT with negative sign convention: 9 // Forward transform (invert=false): F[k] = sum_{n=0}^{N-1} a[n] * exp(-i 2π k n / N) 10 // Inverse (invert=true): a[n] = (1/N) sum_{k=0}^{N-1} F[k] * exp(+i 2π k n / N) 11 12 void fft(std::vector<std::complex<double>>& a, bool invert) { 13 int n = (int)a.size(); 14 15 // bit-reversal permutation 16 for (int i = 1, j = 0; i < n; ++i) { 17 int bit = n >> 1; 18 for (; j & bit; bit >>= 1) j ^= bit; 19 j ^= bit; 20 if (i < j) std::swap(a[i], a[j]); 21 } 22 23 for (int len = 2; len <= n; len <<= 1) { 24 double ang = 2 * M_PI / len * (invert ? 1 : -1); 25 std::complex<double> wlen(std::cos(ang), std::sin(ang)); 26 for (int i = 0; i < n; i += len) { 27 std::complex<double> w(1.0, 0.0); 28 for (int j = 0; j < len / 2; ++j) { 29 std::complex<double> u = a[i + j]; 30 std::complex<double> v = a[i + j + len / 2] * w; 31 a[i + j] = u + v; 32 a[i + j + len / 2] = u - v; 33 w *= wlen; 34 } 35 } 36 } 37 38 if (invert) { 39 for (int i = 0; i < n; ++i) a[i] /= n; 40 } 41 } 42 43 static inline double PI() { return std::acos(-1.0); } 44 45 double f(double x) { // example: 2π-periodic square wave 46 return (std::sin(x) >= 0.0) ? 1.0 : -1.0; 47 } 48 49 int main() { 50 int N = 4096; // must be a power of two for this FFT 51 int M = 15; // number of harmonics to extract 52 53 // 1) Sample f at x_k = 2π k / N 54 std::vector<std::complex<double>> a(N); 55 for (int k = 0; k < N; ++k) { 56 double xk = (2.0 * PI() * k) / N; 57 a[k] = std::complex<double>(f(xk), 0.0); 58 } 59 60 // 2) Compute DFT via FFT (forward transform with negative sign) 61 fft(a, false); 62 63 // Now a[n] holds F[n] = sum f(x_k) e^{-i 2π n k / N} 64 // For a 2π-periodic function, complex Fourier coefficients are c_n ≈ F[n] / N. 65 // Convert to real form: a_n = 2 Re(c_n), b_n = -2 Im(c_n) for n ≥ 1; a_0 = 2 Re(c_0). 66 67 std::vector<double> an(M + 1, 0.0), bn(M + 1, 0.0); 68 69 std::complex<double> c0 = a[0] / (double)N; // n = 0 70 double a0 = 2.0 * c0.real(); 71 72 for (int n = 1; n <= M; ++n) { 73 std::complex<double> cn = a[n] / (double)N; // uses forward DFT definition 74 an[n] = 2.0 * cn.real(); // cosine coefficient 75 bn[n] = -2.0 * cn.imag(); // sine coefficient 76 } 77 78 // Print coefficients 79 std::cout << std::fixed << std::setprecision(6); 80 std::cout << "a0/2 = " << (a0 / 2.0) << "\n"; 81 for (int n = 1; n <= M; ++n) { 82 std::cout << "n=" << n << ": an=" << an[n] << ", bn=" << bn[n] << "\n"; 83 } 84 85 return 0; 86 } 87
We sample one period, perform a forward FFT using the negative-exponent convention, and then normalize by 1/N to obtain complex Fourier coefficients c_n. For a real-valued, 2π-periodic f, convert to real coefficients with a_n = 2 Re(c_n) and b_n = −2 Im(c_n). This reduces the cost from O(NM) to O(N log N) when many coefficients are needed.