% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % fourierSC % ____________________________________________________________________________ % 18.385 (Nonlinear Dynamical Systems). MIT, Fall 1999. R. R. Rosales. % % Demo: To illustrate the convergence of the Fourier Series of a 2*pi periodic % function F = F(x). The Fourier Series here is defined by % % F(x) = cF_0 + Sum_{n=1:infinity} [ cF_n * cos(n*x) + sF_n * sin(n*x) ]. (1) % % The demo computes the Fourier Coefficients cF_n and sF_n up to some n = Np, % that is specified by the user via screen input. The demo has several built % in examples of functions F(x) --- which the user can enter as an OPTION, via % screen input. Arbitrary periodic functions F = F(x) are allowed, provided a % file FSFun.m (with a function script) is present in the directory from which % the user is running MatLab. The MatLab statement y = FSFun(x) should return % the values of F(x) in the array y [i.e. y(n)=F(x(n))], for any array x with % x(n) in [0, 2*pi]. The function in FSFun will be used if OPTION = 0 is % picked when running the demo (a default is provided). % ^^^^^^^^^^^^^^^^^^^^^ % A simple example of a FSFun.m file is: % function y = FSFun(x) % y = exp(-10*(x-pi).^2); % Note the use of the vector operand .^, needed to get a vector valued answer. % % >>>> Check help FSFun for important details about how to define the function % when dealing with discontinuities! % % >>>> NOTE: help FSFun will NOT work if the current directory for MatLab has % a user constructed FSFun.m file already in it. % ____________________________________________________________________________ % % The demo does the following: % a) Computes the Fourier Coefficients and stores them in the 1 by Np arrays % sF and cF. The mean value cF_0 is stored in the variable cF_0. % b) Plots the sine and cosine Fourier coefficients as functions of n. % c) Shows how F is approximated as terms are added to the series. Plots are % shown of the approximations to (1) above given by: % % cF_0 + Sum_{n=1:N} [ cF_n * cos(n*x) + sF_n * sin(n*x) ], (2) % % for N=0:Nskip:Np (including Np, even if Np is not a multiple of Nskip) % and compared with the exact function. For N=0 the plot is just the mean % value of the function F (i.e. cF_0). The values of Nskip and Np must be % picked by the user (via requested screen input). % d) Plots the error in the approximation above as a function of N. Error is % in the array goof, where goof(n) = error after N = n-1 coefficients in % the sum, with N=0:Np. The error is measured pointwise in the numerical % grid x and then normalized by the range of F(x) = max(F) - min(F). This % except near discontinuities in F(x). Near the discontinuities of F(x) a % refined computational grid is introduced, and a calculation of how well % the Fourier series approximation does the jump is done. The error here % is defined simply as the difference between the two limits of F, at the % discontinuity, and the max and min achieved by the approximation there, % again: normalized by the range of F. % e) Plots the power spectrum as a function of n, using semilog and loglog % scales. Here the power spectrum is defined by % % power(n+1) = sqrt((cF_n)^2 + (sF_n)^2) for n=1:Np (3) % power(1) = abs(cF_0) % % The decay rate of the power spectrum as n grows is directly related to, % for example, how well the Fourier Series converges. % ____________________________________________________________________________ % % The following variables are left in memory -- corresponding to the last F(x) % done -- at the end of a run. NOTE: if there are no discontinuities in F(x), % then some of the variables that have to do with discontinuities will not be % defined. Specifically: xRef, recRef and nwidth; while Idis and Jump will be % null arrays. % % Np number of Fourier coefficients computed. % Nskip approximations to the function F are plotted with N terms in the sums % --- with N = 0, Nskip, 2*Nskip, .... k*Nskip, Np. Because of the way % the code works, it is a good idea to avoid taking Nskip too large --- % say, not much bigger than 50. Memory usage goes up with Nskip and the % code performance deteriorates. % dx grid spacing (defined in the code). % x row array with space mesh. Namely: x=dx:dx:2*pi, for some dx. % fct row array with the values F(x). % rec row array with the Fourier Series approximation to F(x) --- as in (2) % above --- with N = Np. % xRef refined grid with points near discontinuities in F(x). This grid has % a spacing of dx/nRef --- as opposed to dx for x --- and extends (for % each discontinuity point x_dc) a distance pl*dx to the left of x_dc % and a distance pr*dx to the right of x_dc. Thus each discontinuity % has nwidth = nRef*(pl+pr)+1 associated with it in xRef. % recRef row array with values of the Fourier Series approximation at xRef. % NOTE: the plots done by the script DO NOT include the data in recRef, % which is used only for the error calculation. % pl parameter defining xRef (defined in the code). % pr """""""""""""""""""""""""""""""""""""""""""""" % nwidth """""""""""""""""""""""""""""""""""""""""""""" % nRef """""""""""""""""""""""""""""""""""""""""""""" % Jump array with the indexes j in x=x(j) that correspond to points in xRef. % Idis indexes in grid x where discontinuities (approximately) occur. That % is: the discontinuities are at or near x(Idis(n)). % DtR trigger value for discontinuities. A discontinuity at x(j) is assumed % when abs(fct(j) - fct(j-1)) > DtR*dx a % power row array (size Np+1), with the power spectrum. See (3) above. % goof row array (size Np+1), with the errors in the Fourier Sums --- in (2) % above --- as function of N. The errors are relative to the range of % F(x), defined by range = max(F) - min(F). % cF_0 mean value of F. % cF row array with cosine Fourier coefficients for n=1:Np. % sF row array with sine Fourier coefficients for n=1:Np. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % VERSION = 2; fprintf('\n There are two versions: ') fprintf('\n fancy (with buttons);VERSION = 1,') fprintf('\n and pedestrian; VERSION = 2. \n') VERSION = input(' ENTER VERSION = '); if VERSION == 1 VERSION = 1; else VERSION = 2; end %========== Vector sizes and useful text variables: ========================== M = 2048; N = 2*M; % Size of vectors in space. In the code dx = 2*pi/N. DtR = 75; % Trigger: define discontinuities where dF/dx > DtR pl = 31; % Buffer size to left of a discontinuity is pl*dx pr = 30; % Buffer size to right of a discontinuity is pr*dx nRef = 3; % Refined grid near discontinuity has size dx/nRef %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Notes here on the choice of pl, pr and nRef: % % A) Near a discontinuity we want to exclude a region of (at least) 1/8 of the % shortest wave-length in the sum, so we do not end up computing as error % the zone where the Fourier Series connects: the mean value of the jump at % at the discontinuity with the two limit values of the function. % % When we sum Np terms, the shortest wave-length is 2*pi/Np, thus we want % pl*dx to be at least 1/8 of this. That is: pl = N/8*Np. Thus, for pl = 30 % and N = 4096, things will work fine for Np > 17. A similar argument works % for pr. % % Thus, with the numbers N=4096 and pl~pr~30, error calculations should be % fine for sums with more than (say) 20 terms. Below this it's not even % worth worrying, as the errors at discontinuities will be fairly large % anyway. % % B) The second point is how much do we need to refine. If we want the refined % grid to have at least 25 points per period at the maximum number of terms % to be added (Np = 512), we need 25*dx/nRef = 2*pi/512, which yields % nRef = 25*512/N = 3.125 for N = 4096. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% FN = 'FontName'; HNB = 'HelveticaNarrowBold'; FS = 'FontSize'; LW = 'LineWidth'; MS = 'MarkerSize'; % %========== Enter inputs needed and print on-screen info: ==================== % fprintf('\n The exact function F(x) and the Fourier approximations: \n') fprintf('\n cF_0 + Sum_{n=1:N}[cF_n*cos(n*x) + sF_n*sin(n*x)], \n') fprintf('\n with N = 0:Nskip:Np will be plotted (N=0 is the mean of F).\n') % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% myFLAG = 1; % To allow continuous execution. ////////////////////////# while myFLAG > 0.5 % % Np = input(' Enter Np, with 1 < Np < 512 .......... Np = '); Nskip = input(' Enter Nskip (with Nskip < 60) ..... Nskip = '); Np = floor(Np); Nskip = floor(Nskip); Np = min(max([Np, 1]), 512); Nskip = min(max([Nskip, 1]), 60); % %========== Construct array of x's and calculate function ==================== % dx = 2*pi/N; x = 0:dx:2*pi-dx; fprintf('\n NOW CHOOSE THE FUNCTION F.\n') if VERSION == 1 FSoption elseif VERSION == 2 FSoptionP end % %========== Now detect the indexes where discontinuities occur, etc. ========= % % We need to do this to have a decent error evaluation at the points where F % is discontinuous, because the Fourier series oscillates there and point-wise % error evaluation leads to misleading results. % % We ASSUME that discontinuities occupy at most 3 points in the grid (that is: % "large" jumps in fct can occur in no more than two consecutive intervals in % the index j. In fact, we ASSUME that the function whose Fourier series is % being computed is piecewise continuous and that the evaluation near a point % of discontinuity (say, at x = x_d) is done by: % % (A) F(x) defined by continuous expressions for both x > x_d and x < x_d; % with some value assigned at x = x_d. % (B) The value assigned at x_d satisfies: % F(x_d) = Fl or F(x_d) = Fr or F(x_d) = 0.5*(Fl + Fr), % where: Fl and Fr are the limits of F(x) from the left and right at x_d. % When F(x_d) = Fl or F(x_d) = Fr, the "large" jump in fct will occur in % just one grid interval (from some x_j to x_(j+1)). Otherwise the jump % may end up spread over two consecutive grid intervals --- note that the % periodicity condition identifies N+1 with 1. % % We detect discontinuities by checking for large "jumps" in fct, where large % means that the numerical derivative is larger than some threshold DtR. Each % discontinuity is labeled by one index: the right one in the jump if two grid % points are involved and the middle one in the three grid points case. These % indexes are in the array Idis. % Temp = [abs(fct(1)-fct(N)), abs(fct(2:N)-fct(1:N-1))]/dx; Idis = find((Temp > DtR) & (Temp([N, 1:N-1]) <= DtR)); Ndis = length(Idis); % % At each discontinuity: a region of width pl*dx to the left and pr*dx to the % right will be done carefully. A refined grid will be constructed there for % this, in the array xRef. The indexes for the points in x affected are in the % array Jump. The arrays Maxdisf and Mindisf contain the approximate values of % the limits for the function on each discontinuity. The refining factor in % xRef is nRef, i.e. dx ---> dx/nRef. % Jump = []; if Ndis >= 1 xRef = []; % ---------------------- Column j in indx is [(j+pp):(-1):(j-qq)] mod N pp = 1; qq = 1; indx = toeplitz([pp+1:-1:1, N:-1:N-qq+1], [pp+1:N, 1:pp]); for nn=1:Ndis Maxdisf(nn) = max(fct(indx(:, Idis(nn)))); Mindisf(nn) = min(fct(indx(:, Idis(nn)))); end % ---------------------- Column j in indx is [(j+pp):(-1):(j-qq)] mod N pp = pr; qq = pl; indx = toeplitz([pp+1:-1:1, N:-1:N-qq+1], [pp+1:N, 1:pp]); for nn=1:Ndis Jump = [Jump, indx(:, Idis(nn))']; xRef = [xRef, (x(Idis(nn))-pl*dx):(dx/nRef):(x(Idis(nn))+pr*dx)]; end nwidth = nRef*(pl+pr)+1; % Number of points in xRef at each discontinuity. Jump = sort(Jump); imove = find(xRef < 0); xRef(imove) = xRef(imove) + 2*pi; imove = find(xRef >= 2*pi); xRef(imove) = xRef(imove) - 2*pi; clear imove pp qq indx end % %++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ %==== Construct Sine coeff. for function expansion (size M-1). ======== % sF(n) is n_th Sine Fourier Coeff. of fct. % Temp = fft(fct); sF = -imag(Temp([2:Np+1]))/M; flag = max(abs(sF)); if flag <= 1e-7 sF = 0*sF; end % %==== Construct Cosine coeff. for function expansion (size M-1). ============= % cF(n) is n_th Cosine Fourier Coeff. of fct. % cF = real(Temp([2:Np+1]))/M; cF_0 = real(Temp(1))/N; flag = max(abs([cF, cF_0])); if flag <= 1e-7 cF = 0*cF; cF_0 = 0; end % %==== Plot exact function. =================================================== % Maxf = max(fct); Minf = min(fct); range = Maxf - Minf + 1e-10; Maxf = Maxf + 0.2*range; Minf = Minf - 0.2*range; Nrange = num2str(range); fprintf(['\n Errors are relative to [max(F(x)) - min(F(x))] = ', ... Nrange, ' \n']) figure whitebg('k') plot(x, fct, '-m', LW, 3) axis([0 2*pi Minf Maxf]) ylabel('F(x)', FN, HNB, FS, 18) xlabel('x', FN, HNB, FS, 18) title('Exact function.', FN, HNB, FS, 20) grid on fprintf('\n Press any key to continue.\n') pause %==== Plot Fourier Coefficients. ============================================= figure whitebg('k') plot(sF, 'oc', MS, 6) grid on hold on plot(sF, '-c') ylabel('sF_n', FN, HNB, FS, 18) xlabel('Fourier index n.', FN, HNB, FS, 18) title('Sine Fourier Coefficients.', FN, HNB, FS, 20) pause nnn=0:1:Np; % figure whitebg('k') plot(nnn, [cF_0, cF], 'oc', MS, 6) grid on hold on plot(nnn, [cF_0, cF], '-c') ylabel('cF_n', FN, HNB, FS, 18) xlabel('Fourier index n.', FN, HNB, FS, 18) title('Cosine Fourier Coefficients.', FN, HNB, FS, 20) pause % figure whitebg('k') power = sqrt([cF_0, cF].^2 + [0, sF].^2); power = power + 1e-20; % This is so 0's points in power are not dropped % from the log plots. Error is bigger than this, % anyway! semilogy(nnn, power, '-r', LW, 3) grid on title('Semilog plot of Power Spectrum.', FN, HNB, FS, 20) ylabel('Power Spectrum.', FN, HNB, FS, 18) xlabel('Fourier index n.', FN, HNB, FS, 18) pause % figure whitebg('k') loglog(nnn, power, '-r', LW, 3) grid on title('Loglog plot of Power Spectrum.', FN, HNB, FS, 20) ylabel('Power Spectrum.', FN, HNB, FS, 18) xlabel('Fourier index n.', FN, HNB, FS, 18) pause % %==== Reconstruction and plot. =============================================== % rec = cF_0 * ones(1,N); if Ndis >= 1 recRef = cF_0 * ones(size(xRef)); end Temp = abs(rec - fct); goof = (max(Temp))/range; % figure whitebg('k') plot(x, rec, '-r', LW, 3) axis([0 2*pi Minf Maxf]) hold on grid on plot(x, fct) xlabel('x', FN, HNB, FS, 18) ylabel('y = F', FN, HNB, FS, 18) title('Function and Mean Value.', FN, HNB, FS, 20) pause hold off n = 1; while n < Np+1 Ndiff = min(Np-n+1, Nskip); nT=n:(Ndiff+n-1); nT = nT'; recTemp = diag(sF(nT))*sin(nT*x) + diag(cF(nT))*cos(nT*x); if Ndis >= 1 recTempRef = diag(sF(nT))*sin(nT*xRef) + diag(cF(nT))*cos(nT*xRef); end % This here is a little trick to avoid putting sine and cosine evaluations % inside a for loop. Reason is sine and cosines are very slow in MatLab, % so it is best to "vectorize" their evaluation to get it all done at once % --- rather than inside a loop. Else the first statement inside the loop % should be replaced by: % rec = rec + sF(n)*sin(n*x) + cF(n)*cos(n*x); n=n+1; % A real killer if Nskip and N are large!! for nn=1:Ndiff rec = rec + recTemp(nn, :); if Ndis >= 1 recRef = recRef + recTempRef(nn, :); end n=n+1; Temp = abs(rec - fct); % DO NOT CALCULATE ERROR THIS WAY AT Temp(Jump) = zeros(size(Jump)); % DISCONTINUITIES, because there are goofn = (max(Temp))/range; % oscillations. Jump skips them. % if Ndis >= 1 for jj=1:Ndis Maxdisrec(jj) = max(recRef((nwidth*(jj-1)+1):nwidth*jj)); Mindisrec(jj) = min(recRef((nwidth*(jj-1)+1):nwidth*jj)); end goofn = max(max(abs(Maxdisrec - Maxdisf))/range, goofn); goofn = max(max(abs(Mindisrec - Mindisf))/range, goofn); end % goof = [goof, goofn]; end goofT = num2str(goofn); plot(x, rec, '-r', LW, 3) axis([0 2*pi Minf Maxf]) hold on grid on plot(x, fct) TEXT = num2str(n-1); title(['Fourier series added up to N = ', TEXT, '.'], FN, HNB, FS, 20) xlabel(['Relative error in approximation = ', goofT], FN, HNB, FS, 18) ylabel('y = F', FN, HNB, FS, 18) pause hold off end % %==== Plot error now. ================================================= % [n nn] = size(goof); nnn = 0:nn-1; figure whitebg('k') plot(nnn, goof, '-r', 'LineWidth', 3) grid on title('Plot of relative error in approximation.', FN, HNB, FS, 20) ylabel('Error.', FN, HNB, FS, 18) xlabel('Number of terms in Fourier sum.', FN, HNB, FS, 18) pause % figure whitebg('k') semilogy(nnn, goof, '-r', LW, 3) grid on title('Semilog plot of relative error in approximation.', FN, HNB, FS, 20) ylabel('Error.', FN, HNB, FS, 18) xlabel('Number of terms in Fourier sum.', FN, HNB, FS, 18) pause % figure whitebg('k') loglog(nnn, goof, '-r', LW, 3) grid on title('Loglog plot of relative error in approximation.', FN, HNB, FS, 20) ylabel('Error.', FN, HNB, FS, 18) xlabel('Number of terms in Fourier sum.', FN, HNB, FS, 18) pause % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% fprintf('\n To clear all figures and continue, enter FLAG = 1.') fprintf('\n To STOP NOW ....................., enter FLAG = 0.\n') myFLAG = input(' FLAG = '); if myFLAG > 0.5 close all end end clear Maxf TEXT nT Temp flag nn CLR clear yb FN Minf VERSION nnn yt FS clear N goofT HNB NN h range jj clear LW h_fig M Ndiff OPTION dyt goofn clear jn recTemp MM OPT dyb myFLAG MS clear n smooth DOTS TEXTT YT dl dy clear nop x_m y_m yl BGC Nrange recTempRef % % CLEAN UP xRef so it is ordered: % if Ndis >= 1 [xRef, II] = sort(xRef); recRef = recRef(II); end clear II Ndis Maxdisf Mindisf Maxdisrec Mindisrec % % Ndis number of discontinuities. % Maxdisf maximum values at discontinuities of function. % Mindisf minimum values at discontinuities of function. % Maxdisrec maximum values near discontinuities of approximation. % Maxdisrec minimum values near discontinuities of approximation. % % EOF