---
# System prepended metadata

title: 8-0. Fourier approximations
tags: [Spectral method]

---

---
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.

