Description
In lectures we saw that we can write a periodic function ๐ in the form
๐
๐(๐ฅ) = โ ๐๐ฬ ๐๐๐๐ฅ
๐=โ๐
where ๐๐ฬ โถ= 21๐ โซ02๐ ๐(๐ฅ)๐โ๐๐๐ฅd๐ฅ if the period is 2๐.
The FFTW library uses a different convention, taking the period to be 1. Suppose we sample a function at ๐ + 1 uniformly-spaced points between [0, 1], obtaining values ๐0, ๐1, โฆ , ๐๐, where ๐๐ โถ= ๐(๐โ) with โ โถ= ๐1 . By using the Fourier series we are implicitly assuming that ๐ is periodic, i.e. ๐0 = ๐๐. Then
๐2 โ1
๐(๐ฅ) = โ ๐๐ฬ ๐2๐๐๐๐ฅ
๐=โ๐2
where the Fourier coefficients are
1
๐๐ฬ โถ= โซ ๐(๐ฅ) ๐โ2๐๐๐๐ฅd๐ฅ
0
This is the convention we will use in this problem.
1. Discretize the integral for the Fourier series coefficients using the trapezoidal rule using ๐ + 1 points. Using the assumption that ๐0 = ๐๐ reduce this to a sum over ๐๐ for ๐ = 0, โฆ , ๐ โ 1.
Implement this as a function fourier_coefficients_trapezoidal(f::Vector) where f is the vector of samples of ๐๐ for ๐ = 0, โฆ , ๐ โ 1. Your function should return the coefficients for ๐๐ฬ for ๐ = โ๐2 , โฆ , ๐2 โ 1 as a vector.
2. Consider the following three functions:
1. The sawtooth function
๐(๐ฅ) = mod(๐ฅ, 1)
(The function mod(๐ฅ, 1) returns just the fractional part of a number.)
2. The โWโ wave function
โง{๐ฅ 0 โค ๐ฅ < 0.5
๐(๐ฅ) = 1 โ ๐ฅ 0.5 โค ๐ฅ < 1
โจ{โฉ๐(mod(๐ฅ, 1)) otherwise
3. The function โ(๐ฅ) = exp(cos(2๐๐ฅ))
Plot them.
Given the properties of these functions, how would you expect their Fourier coefficients to decay?
3. Take ๐ = 100. Calculate ๐,ฬ ๐ฬand โฬ using your fourier_ceofficients_trapezoidal function. Plot the magnitude of the coefficients as a function of ๐ only for ๐ โฅ 0. Do you see the expected behavior?
4. How accurately does our function fourier_coefficients_trapezoidal(f::Vector) calculate the Fourier coefficients? Use the following analytic solutions to calculate and plot the respective errors:
๐0ฬ = 12 and ๐๐ฬ = 2 ๐๐๐ for ๐ โ 0
1 1
๐ฬ0 = 4 ; ๐ฬ๐ = โ๐ 2๐2 for ๐ odd; and ๐ฬ๐ = 0 for ๐ even
โฬ๐ = ๐ผ1(๐)
where ๐ผ1(๐) is the modified Bessel function of the first kind, calculated in Julia as besseli(n, 1) using the SpecialFunctions.jl package.
Calculate the error (using norm) as the number of points used changes between ๐ = 10 and ๐ = 1000.
5. Write a function reconstruct_fourier_series(fฬs::Vector, xs::Vector) which reconstructs f(x) from the Fourier series coefficients.
Make a plot of ||f(xt) – reconstruct_fourier_series(fฬs, xt)||/N as the number of coefficients used ๐ is varied from 4 โ 200 for each of the functions.
Sample the reconstructed Fourier series at the points xt = 0:0.001:1. What do you see? Can you explain why?
6. Make an interactive visualiztion that plots the following on the same axes:
(i) the points used to calculate the Fourier coefficients in fourier_coefficients_rectangle;
(ii) the reconstructed function from the Fourier coefficients, found using reconstruct_fourier_series; and
(iii) the true function as the number of points is varied from ๐ = 10 โถ 2 โถ 250.
Does this help explain the results in [1.6]? In particular, what do you see for the sawtooth function? This is known as the Gibbs phenomenon, which occurs when a function is discontinuous.
7. What is the operation count for your naive fourier_coefficients_trapezoidal function? In general this will not behave well as ๐ grows very large. The FFT, however, can calculate this in ๐(log(๐)) steps. The FFT implemented in FFTW.jl calculates
๐โ1
๐๐ฬ = โ ๐๐๐โ2๐๐๐๐/๐
๐=0
for ๐ = 0 โถ ๐ โ 1, which should be related to your discretization in [1.1].
Note that the ๐s are different here. But since ๐โ2๐๐(๐+๐๐)๐/๐ =
๐โ2๐๐๐๐๐โ2๐๐๐๐/๐ = ๐โ2๐๐๐๐/๐ we have the relationship ๐๐/2+๐ฬ =
๐โฬ ๐/2+๐.
The FFT algorithm therefore outputs
fฬ= [๐0ฬ , ๐1ฬ , โฆ , ๐๐/2โ1ฬ , ๐โฬ ๐/2, ๐โฬ ๐/2+1, โฆ , ๐โ1ฬ ]
The FFTWpackage defines a function fftshift that shifts this vector to the form
fฬ= [๐โฬ ๐/2, ๐โฬ ๐/2+1, โฆ , ๐โ1ฬ , ๐0ฬ , ๐1ฬ , โฆ , ๐๐/2โ1ฬ ]
8. Implement a function fast_fourier_coefficients that outputs the same results as fourier_coefficients_trapezoidal but using the FFT from FFTW.jl.
Check that the output is the similar to before.
Time your two functions for ๐ = 210. Is one faster than the other?
How large can you take N such that fast_fourier_coefficients runs for under 1 second? What about for fourier_coefficients_trapezoidal?
Exercise 2: Solving an ODE with a spectral method
In this problem we will solve the boundary-value problem
๐โณ = ๐
with boundary conditions ๐(0) = ๐(1) = 0 by using a spectral method, i.e. by expanding in suitable basis functions.
๐(๐ฅ) = (๐๐๐ฅ),
which is the same as the Fourier series for an odd function.
In practice we have to truncate the summation as
๐
๐(๐ฅ) = โ ๐๐ sin(๐๐๐ฅ)
๐=1
The coefficients are given by
1
๐๐ = 2 โซ ๐(๐ฅ) sin(๐๐๐ฅ)d๐ฅ
0
Similarly to the DFT we discretize this using the rectangle rule to get the Discrete Sine Transform (DST): ๐โ1
2 ๐๐๐
๐๐ = ๐(๐ฅ) sin ( ๐ )
This sum is implemented in julia using the FFTW.jl library (which you will need to install) as follows:
dst(x) = FFTW.r2r(x, FFTW.RODFT00) / length(x) Its inverse is given by idst(x) = FFTW.r2r(x, FFTW.RODFT00)
[r2r stands for โreal to realโ, meaning that the transform maps a real vector to a real vector. RODFT00 is a symbol that selects one particular type of transform.]
1. Assume that there is a solution of the form ๐(๐ฅ) = โฬ ๐๐=1 ๐๐ sin(๐๐๐ฅ).
Substitute this into the ODE ๐โณ(๐ฅ) = ๐(๐ฅ) to show that ๐๐ฬฬ ๐ ๐ .
2. We can therefore solve the ODE for ๐ by first calculating ๐ฬusing the DST, then calculate ๐๐ฬ , and finally invert using the iDST.
Write a function spectral_solver(b) that does this to solve the ODE, where b is the discretized version of g(x). Solve the ODE with ๐(๐ฅ) = sin(2๐๐ฅ). Plot the result. The right-hand side is given by
h = 1 / N
b = sin.(2ฯ * (h:h:1-h))
3. Calculate the error as a function of ๐ and plot it. What rate of convergence do you see?
4. Generate the error plots again, now for the right-hand side ๐(๐ฅ) = exp(sin(2๐๐ฅ)) โ 1. Use the solution from your spectral solver with
๐ = 213 as the true solution. Calculate the error for ๐ = 2๐ for ๐ = 1 โ 12. Some care is needed to make sure you use the correct points from the โtrueโ solution for comparison.
Exercise 3: Finding roots in a different way
In class we defined the Chebyshev expansion of a function as
๐
๐๐(๐ฅ) = โ ๐๐๐๐(๐ฅ)
๐=0
which is an ๐th-degree polynomial. The Chebyshev polynomials are defined as ๐๐(๐ฅ) = cos(๐ arccos(๐ฅ)).
In general for a smooth function the Chebyshev series converges rapidly. We therefore expect that the roots of ๐๐(๐ฅ) should be close to the roots of ๐(๐ฅ), provided that ๐๐ is indeed a good approximation to ๐. We have already seen that we can find all the roots of a polynomial with various methods.
The Chebyshev polynomials satisfy the following recurrence relation:
๐๐+1(๐ฅ) = 2๐ฅ ๐๐(๐ฅ) โ ๐๐โ1(๐ฅ)
We will use this to find the companion matrix for ๐๐(๐ฅ), from which we can find the roots of ๐๐(๐ฅ).
1. Consider the polynomial ๐ฅ๐๐(๐ฅ). This is a degree ๐+1 polynomial and hence can be reexpanded in Chebyshev polynomials.
Consider the vector of Chebyshev polynomials
๐0(๐ฅ)
T(๐ฅ) = โข 1(โฎ๐ฅ) โคโฅ โก ๐
โฃ๐๐โ1(๐ฅ)โฆ
Now we can write ๐ฅT(๐ฅ) = ๐T(๐ฅ) + ๐๐๐ฬ๐๐๐(๐ฅ), where ๐ is an (๐ โ1)ร(๐ โ1) matrix. We need the ๐๐ term to account for the fact that ๐ฅ๐๐โ1 is a degree-๐ polynomial. Here, ๐ฬ๐ is the standard basis vector with zeros everywhere except in the ๐th component.
Use the recurrence relation to find the form of ๐ and ๐๐. 2. Verify what you found in [3.1] numerically when ๐ = 10. Build the vector T(๐ฅ) for ๐ฅ a random number in [โ1, 1]. Compute ๐ and check that it gives ๐ฅT(๐ฅ).
3. This looks almost like an eigenvalue problem, except for the ๐๐ term. To remove this we can add and subtract ๐ถ๐๐(๐ฅ)๐ฬ๐ from the right-hand
side. Writing ๐๐(๐ฅ) = ๐๐๐๐(๐ฅ) + โ๐โ1๐=0 ๐๐๐๐(๐ฅ), what value of ๐ถ should you choose so that
๐โ1
๐ฅT(๐ฅ) = ๐T(๐ฅ) + ๐ถ๐๐(๐ฅ)๐ฬ๐ โ ๐ถ๐ฬ๐ โ ๐๐๐๐(๐ฅ) (1)
๐=0
= ๐ฬ T(๐ฅ) + ๐ถ๐๐(๐ฅ)๐ฬ๐ (2)
What is the new matrix ๐ฬ ?
4. This becomes an eigenvalue problem when ๐ฅ is a root of ๐๐(๐ฅ). Therefore, the eigenvalues of ๐ฬ are the roots of ๐๐.
Write a function buildM(c::Vector) that constructs the matrix ๐ฬ from the coefficients in the Chebyshev expansion. Use this to write a chebyshev_roots(c) function that finds the roots of the polynomial defined using the Chebyshev coefficients c. Finally write a function fN(x, c) that calculates the series expansion to find ๐๐(๐ฅ) defined by the vector c. 5. We can calculate Chebyshev coefficients using the dct functions in FFTW.
We will use the Chebyshev points ๐ฅ๐ โถ= cos(๐๐/๐) for ๐ = 0 โถ ๐. You can then calculate the Chebyshev coefficients using the following code:
chebyshev_points(N) = cos.(ฯ*(0:1:N)/N)
function chebyshev_coefficients(x) N = length(x)
c = FFTW.r2r(x, FFTW.REDFT00)/(N-1)
c[1] /= 2 c[N] /=2
return c
end 6. Consider the polynomial ๐(๐ฅ) = ๐ฅ (๐ฅ โ 1/2)2 (๐ฅ2 โ 1/9) (๐ฅ + 1/4).
Using 10 Chebyshev points calculate the Chebyshev coefficients and then calculate the roots using chebyshev_roots. What do you see. What about multiplicities?
7. Plot ๐(๐ฅ) and scatter plot the roots you find on top.
8. Now consider solving the problem exp(cos(4๐๐ฅ)) = 1. Using ๐ = 100 points, alculate the Chebyshev coefficients for ๐(๐ฅ) = exp(cos(4๐๐ฅ))โ 1. Do they decay quickly? Use these to calculate the roots of ๐(๐ฅ). Plot ๐(๐ฅ) and scatter the roots you find on top. Do you find all the roots?
(Hint: you will find 100 eigenvalues. Only plot those that are real and lie between -1 and 1.)
9. Make an interactive visualization as ๐ is varied between ๐ = 4 โถ 150. Plot ๐(๐ฅ), the Cheyshev approximation to ๐(๐ฅ) using ๐ coefficients and the roots you find on the same axes. Comment on what you see.
At what value of ๐ do you find all the roots? Remember to plot only those roots that are real and between -1 and 1.
Exercise 4: GramโSchmidt for polynomials In lectures we discussed treating the set of polynomials {1, ๐ฅ, ๐ฅ2, ๐ฅ3, โฆ} as the basis of a vector space with the inner product
๐
(๐, ๐)๐ค = โซ ๐(๐ฅ)๐(๐ฅ)๐ค(๐ฅ)d๐ฅ
๐
We can therefore carry out GramโSchmidt orthogonalization on these polynomials to generate a family of orthogonal polynomials.
We will implemnent this using the Polynomials.jl package. Integrals can be performed using the polyint(f, a, b) function to integrate the polynomial ๐ from ๐ to ๐. Here, ๐, ๐ and ๐ค should all be Polynomials.
1. Write a function gram_schmidt(vs::Vector, ip) which accepts a vector of โvectorsโ in the vector space and the inner product on the vector spacce. For standard vectors this would be dot(v1, v2). The function should implement the GramโSchmidt algorithm and return a vector of the resulting orthonormal basis elements.
2. Test your function for standard vectors vs = [rand(10) for i = 1:10] using ip = dot. To check everything went according to plan, form the matrix ๐ท๐๐๐ = ๐๐ โ
๐๐. If everything worked this should be the identity matrix.
3. For polynomials we define the inner product
๐
(๐, ๐)๐ค โถ= โซ ๐(๐ฅ)๐(๐ฅ)๐ค(๐ฅ)d๐ฅ
๐
For Legendre polynomials we have ๐ = โ1 , ๐ = 1, and ๐ค(๐ฅ) =
1. Using the Polynomials.jl package, implement a function legendre_inner_product(f,g). Use this to orthogonalize the vector of monomials up to order 7.
4. Use the functions you found in [4.3] and your inner-product function, find the Legendre polynomial expansion coefficients for the function ๐(๐ฅ) = ๐ฅ (๐ฅ โ 1/2)2 (๐ฅ2 โ 1/9) (๐ฅ + 1/4).
Plot the reconstructed polynomial using the first ๐ coefficients for ๐ = 1 โถ 7 and the true function. What do you see? How good are the Legendre polynomials at approximating the function?
Reviews
There are no reviews yet.