---
title: 8-0. Fourier approximations
tags: Spectral method
---
**Textbook**
1. [Spectral method in Matlab](https://people.maths.ox.ac.uk/trefethen/spectral.html) by *Lloyd N. Trefethen*
2. [Finite difference and Spectral methods](https://people.maths.ox.ac.uk/trefethen/pdetext.html) by *Lloyd N. Trefethen*
3. A practical guide to pseudospectral method by *Bengt Fornberg*
**Suggested reading**
1. [Notes on FFT-based differentiation](https://math.mit.edu/~stevenj/fft-deriv.pdf)
---
## Example of FFT in `Matlab`
### Example 1: FFT and trigonometric functions
Consider $f(x) = \sin(x)$ and its corresponding Fourier transform:
```Matlab
N = 10;
x = linspace(0, 2*pi, N+1);
x = x(1:N);
f = sin(x);
fft(f)
```
The result shows
0 -5i 0 0 0 0 0 0 0 5i
**Remark:** One can see easily that the Fourier modes are scaled by the number of points, so if one double the points, the result of the mode should be doubled.
#### Exercise 1:
* Try the following grid and see what you found in the Fourier coefficients of $\sin(x)$
```Matlab
N = 20;
h = 2*pi/N;
x = (1:N)*h;
```
---
### Example 2: Differentiation with FFT
**Remark** The treatments for the 1st derivaative and the 2nd derivative are different at the highest Fourier mode $(k=M/2)$.
```Matlab
% grid points
N = 24; h = 2*pi/N; x = h*(0:N-1)';
% v: the function
% vp1: dv/dx
% vp2: d^2v/dx^2
v = exp(sin(x));
vp1 = cos(x).*v;
vp2 = cos(x).*vp1 -sin(x).*v;
v_hat = fft(v);
% weights: Fourier modes for odd deriv.
kk1 = [0:N/2-1 0 -N/2+1:-1]';
% w1: spectral approximation of dv/dx
w1_hat = 1i*kk1.*v_hat;
w1 = real(ifft(w1_hat));
error = norm(w1-vp1,inf);
disp(['max error for 1st deriv. = ' num2str(error)])
% weights: Fourier modes for even deriv.
kk2 = [0:N/2 -N/2+1:-1]';
% w2: spectral approximation of d^2v/dx^2
w2_hat = -(kk2.^2).* v_hat;
w2 = real(ifft(w2_hat));
error = norm(w2-vp2,inf);
disp(['max error for 2nd deriv. = ' num2str(error)])
```
The result shows
max error for 1st deriv. = 9.5757e-13
max error for 2nd deriv. = 2.0521e-12
#### Exercise 2:
* Try to use `kk1` as Fourier modes to compute the 2nd derivative and see how the result changes
* Use Matlab command `semilogy(abs(v_hat))` to determine how many grid points are required
* Change number fo grid points `N` to see how the result changes
---
### Example 3: Spatial shift
Given
$$
u(x) = \sum_k \hat{u}_k e^{ikx},
$$
it is easy to see that a spatial shift to the function can be written as
$$
u(x-c) = \sum_k \hat{u}_k e^{ik(x-c)} = \sum_k e^{-ikc}\hat{u}_k e^{ikx},
$$
where $c$ is a constant.
```Matlab
% grid points
N = 24; h = 2*pi/N; x = h*(0:N-1)';
% v: the function
v = exp(sin(x));
v_hat = fft(v);
% weights: Fourier modes for odd deriv.
kk = [0:N/2 -N/2+1:-1]';
% c: spatial shift
c = 1;
vc_hat = exp(-1i*kk*c).*v_hat;
vc = real(ifft(vc_hat));
% Compare the original one and the shifted one
figure
plot(x, v, x, vc)
```
---
### Example 4: Adding points
Given $N$ sample points of a function $u(x)$, we use Fourier transform to obtain the Fourier coefficients and interpolate the function as
$$
u(x) \approx u_N(x) = \sum^{N/2}_{-N/2+1} \hat{u}_k e^{ikx}.
$$
Suppose we want to evaluate $2N$ sample points from $u_N(x)$, we can rewrite the equation as
$$
u_{2N}(x) = \sum^{N}_{-N+1} \tilde{u}_k e^{ikx},
$$
where
$$
\tilde{u}_k = \hat{u}_k, \quad k=-N/2+1,\cdots,N/2-1,
$$
and
$$
\tilde{u}_k = \frac{1}{2}\hat{u}_k, \quad k=-N/2,N/2,
$$
and $\tilde{u}_k = 0$, for all the rest of the modes.
**Remark** The implementation in `Matlab` requires a bit careful as the multiplication scalar in FFT and iFFT are defined differently.
```Matlab
% grid points
N = 24; h = 2*pi/N; x = h*(0:N-1)';
% v: the function
v = exp(sin(x));
v_hat = fft(v);
% new points
M = 2*N;
h2 = 2*pi/M; x2 = h2*(0:M-1)';
v_tilde = zeros(M,1);
v_tilde(1:N/2) = v_hat(1:N/2);
v_tilde(N/2+1) = 0.5*v_hat(N/2+1);
v_tilde(M-N/2+1) = 0.5*v_hat(N/2+1);
v_tilde(M-N/2+2:M) = v_hat(N/2+2:N);
%
v_tilde = v_tilde*M/N;
plot(x, v, '*-', x2, real(ifft(v_tilde)), 'ro-')
```
---
### Example 5: Aliasing error visualization
We use fine grid to evaluate the "exact" Fourier modes and see how well the approximation on the modes is for coarse grid.
```Matlab
% f: the function
f = @(x) exp(sin(x)) + sin(15*x);
% coarse grid points
N = 20; h = 2*pi/N; x = h*(0:N-1)';
% vc: sampling of the function at coarse grids
vc = f(x);
vc_hat = fft(vc);
% fine grid points
M = 60; h2 = 2*pi/M; y = h2*(0:M-1)';
% vf: sampling of the function at fine grids
vf = f(y);
vf_hat = fft(vf);
% Comparing the two
clf
semilogy(abs(vf_hat(1:M/2+1)/M), 'o')
hold on
semilogy(abs(vc_hat(1:N/2+1)/N), '*')
semilogy(abs(vc_hat(1:N/2+1)/N-vf_hat(1:N/2+1)/M), '+')
legend('exact', 'approximate', 'difference')
```
---
### Example 6: Aliasing error
Consider taking derivative of the square of a function
$$
\frac{d}{dx} u^2(x) = 2u(x)u'(x)
$$
Let's see the difference in the following two approaches
```Matlab
% grid points
N = 24; h = 2*pi/N; x = h*(0:N-1)';
% v: the function
v = exp(sin(x));
v_hat = fft(v);
% ve: the exact solution of (v^2)'
ve = exp(2*sin(x)).*(2*cos(x));
% v2: v(x)^2
v2 = v.^2;
v2_hat = fft(v2);
% weights: Fourier modes for odd deriv.
kk1 = [0:N/2-1 0 -N/2+1:-1]';
% w2: spectral approximation of (v^2)_x
w2_hat = 1i*kk1.*v2_hat;
w2 = real(ifft(w2_hat));
error = norm(w2-ve,inf);
disp(['max error for (v^2)_x = ' num2str(error)])
% w1: spectral approximation of v_x
w1_hat = 1i*kk1.*v_hat;
w1 = real(ifft(w1_hat));
error = norm(2*v.*w1-ve,inf);
disp(['max error for 2*v*v_x = ' num2str(error)])
```
The result shows
max error for (v^2)_x = 8.2602e-09
max error for 2*v*v_x = 2.883e-12
#### Exercise 6:
* Consider $u(x)=\sin(3x)$, using $10$ sample points to approximate this function. Do we have enough sample points to approximate the function accurately?
* Now consider $v(x)=u(x)^2$, using $10$ sample points to approximate this function. Do we have enough sample points to approximate the function accurately? What's the magnitude of the error?
---
### Example 7: De-Aliasing by zero-padding
```Matlab
% grid points
N = 24; h = 2*pi/N; x = h*(0:N-1)';
% v: the function
v = exp(sin(x));
v_hat = fft(v);
% 3/2-rule
M = ceil((3/2)*N);
h2 = 2*pi/M; x2 = h2*(0:M-1)';
v_tilde = zeros(M,1);
v_tilde(1:N/2) = v_hat(1:N/2);
v_tilde(N/2+1) = 0.5*v_hat(N/2+1);
v_tilde(M-N/2+1) = 0.5*v_hat(N/2+1);
v_tilde(M-N/2+2:M) = v_hat(N/2+2:N);
%
v_tilde = v_tilde*M/N;
vm = real(ifft(v_tilde));
vm2_tilde = fft(vm.^2);
% v2_hat: Fourier modes of v^2
v2_hat = zeros(N,1);
v2_hat(1:N/2) = vm2_tilde(1:N/2);
v2_hat(N/2+2:N) = vm2_tilde(M-N/2+2:M);
v2_hat = v2_hat*N/M;
% ve: the exact solution of (v^2)'
ve = exp(2*sin(x)).*(2*cos(x));
% weights: Fourier modes for odd deriv.
kk1 = [0:N/2-1 0 -N/2+1:-1]';
% w2: spectral approximation of (v^2)_x
w2_hat = 1i*kk1.*v2_hat;
w2 = real(ifft(w2_hat));
error = norm(w2-ve,inf);
disp(['max error for (v^2)_x = ' num2str(error)])
% w1: spectral approximation of v_x
w1_hat = 1i*kk1.*v_hat;
w1 = real(ifft(w1_hat));
error = norm(2*v.*w1-ve,inf);
disp(['max error for 2*v*v_x = ' num2str(error)])
```
The result shows
max error for (v^2)_x = 4.4851e-09
max error for 2*v*v_x = 2.883e-12
It looks like the process improves a bit but not much, and it also looks like expand the whole computation as $2u(x)u'(x)$ is much metter.
**But**, let's take another look at the error by reading the Fourier coefficients (continue from the above Matlab code):
```Matlab
% grid points
M = 60; h2 = 2*pi/M; y = h2*(0:M-1)';
% v2p: (v^2)_x
v2p = exp(2*sin(y)).*cos(y)*2;
v2p_hat = fft(v2p);
semilogy(abs(v2p_hat(1:M/2+1)/M), 'o'); hold on
%
semilogy(abs(w2_hat(1:N/2+1)/N), '*')
semilogy(abs(w2_hat(1:N/2+1)/N-v2p_hat(1:N/2+1)/M), '*')
%
u = 2*v.*w1;
u_hat = fft(u);
semilogy(abs(u_hat(1:N/2+1)/N), '+')
semilogy(abs(u_hat(1:N/2+1)/N-v2p_hat(1:N/2+1)/M), '+')
%
legend('exact', 'de-alias(DA)', 'exact-DA', 'full expand(FP)', 'exact-FP')
```
One easily find that the Fourier coefficients for the full-expanded one is much larger comparing to the de-aliased one.