【波動論4】
任意の周期関数をフーリエ変換+逆フーリエ変換する
【波動論1】フーリエ級数展開1:f=x、
【波動論2】フーリエ級数展開2:f=x^2では、解析的にフーリエ級数を計算し元の関数を再現できることを確かめました。
本節では、任意の周期関数を数値的にフーリエ級数展開します。
また、逆フーリエ変換を行うことで、元の関数になっていることを確かめます。
周期 L の場合のフーリエ級数展開
f(x) を任意の周期が L の関数とします。 f(x) は関数形が分かっている必要はなく、数値的なデータで問題ありません。 f(x) は次のように展開することができます。
係数の c_n は、両辺に e^{-ik_mx}を掛けて区間で積分することで得られます(最後に m→n とする)。
但し、積分を計算するときに、e^{ik_nx}と e^{ik_mx}が直交することを利用しています。
つまり、式(1)の展開する関数は区間で直交する関数であれば、どんなものでも利用できます。
計算結果
具体例として、次の関数のフーリエ級数の係数 c_n を数値的に計算します。
(※下の具体例は解析的に計算可能ですが。。。)
フーリエ級数の係数
c_0 を除いて、c_n の虚数部のみ値を持ちます。 これは、f(x) が奇関数であることを意味し、sin 成分のみで記述できることと同義です。
フーリエ級数を用いて、元の関数を再現
N は式(1)における和の数です。 N が大きくなるに従って、元の関数を忠実に再現していることが分かります。 x = 0 付近を拡大してみたものが下の図です。
任意の周期関数をフーリエ変換+逆フーリエ変換する C++プログラム
下のサンプルプログラムは、OpenMP を利用した並列計算も考慮しています。
「#pragma」利用して、OpenMP が利用できない環境でもコンパイルできるようになっています。
【参考】VisualC++ でOpenMPを利用した並列計算
式(2)の積分を計算するのに、シンプソン法を利用しています。
【参考】シンプソン法を利用して積分を数値計算する
///////////////////////////////////////////////// //フーリエ級数展開_シンプソン法 //フーリエ級数展開+フーリエ級数の和 ///////////////////////////////////////////////// #include <math.h> #include <stdlib.h> #include <time.h> #include <fstream> #include <sstream> #include <iostream> #include <string> #include <cstdio> #include <iomanip> #include <stdio.h> #include <complex> #if defined(_OPENMP) #include <omp.h> #endif #if defined(_MSC_VER) #include <direct.h> // Windowsフォルダ作成用 #elif defined(__GNUC__) #include <sys/stat.h> // UNIX系ディレクトリ作成用 #endif using namespace std; double PI = acos(-1.0); double e = 2.7182818284590452354; complex<double> I = complex<double>(0.0,1.0); const int N = 10000; const int Nx = 1000; // x方向の分割の数 const int Ns = 20000; // 積分区間の分割の数(NとNsは同じにしてはいけない) double L = 2.0; string folder = "4", ff="", fg=""; //保存フォルダ名 ostringstream fname; complex<double> FT_Simpson(double a ,double b ,int N, double k); complex<double> K[N+1], F[Ns+1]; double f(double x){ if(x<0) return 1.0; else return 0.0; } int main(){ #if defined(_MSC_VER) _mkdir(folder.c_str()); // Windowsフォルダ作成 #elif defined(__GNUC__) mkdir(folder.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); // UNIX系のディレクトリ作成 #endif #if defined(_OPENMP) omp_set_num_threads(8); cout << "OpenMPを利用します"<< endl; cout << "最大スレッド数:"<< omp_get_max_threads() << endl; #endif clock_t time_start, time_end; time_start = clock(); ff = folder + "/fk.data"; ofstream fout_fk( ff.c_str()); ff = folder + "/fx.data"; ofstream fout_fx( ff.c_str()); #pragma omp parallel { #pragma omp for //schedule(dynamic, 100) for(int i=0; i<=N; i++){ double k = (2.0*PI/L) * double(i-N/2); K[i] = FT_Simpson(-L/2.0, L/2.0, Ns, k)/L; } } for(int i=0; i<=N; i++){ double k = (2.0*PI/L) * double(i-N/2); fout_fk << i-N/2 << " " << K[i].real() << " " << K[i].imag() << endl; } #pragma omp parallel { #pragma omp for //schedule(dynamic, 100) for(int i=0; i<=Nx; i++){ double x = L * double(i-Nx/2)/double(Nx) * 4.0; F[i] = complex<double>(0.0,0.0); for(int j=0; j<=N; j++){ double k = (2.0*PI/L) * double(j-N/2); F[i] += K[j] * exp(I*k*x); } } } for(int i=0; i<=Nx; i++){ double x = L * double(i-Nx/2)/double(Nx) * 4.0; fout_fx << x << " " << F[i].real() << " " << F[i].imag() << endl; } time_end = clock(); cout <<"計算時間は " << double(time_end - time_start)/double(CLOCKS_PER_SEC) << "でした" <<endl; #if defined(_MSC_VER) cin.get();//入力待ち #endif return 0; } complex<double> FT_Simpson(double a ,double b ,int N, double k){ complex<double> ss1 = complex<double>(0.0,0.0); for(int i=1; i<=N/2-1; i++){ double x = a + (b-a)/double(N) * double(2*i); ss1 += exp(-I*k*x) * f(x); } complex<double> ss2 = complex<double>(0.0,0.0); for(int i=1; i<=N/2; i++){ double x = a + (b-a)/double(N) * double(2*i-1); ss2 += exp(-I*k*x) * f(x); } return (b-a)/(3.0*double(N)) * ( exp(-I*k*a) * f(a) + exp(-I*k*b) * f(b) + 2.0 * ss1 + 4.0 * ss2 ); }
プログラムをちょっと解説
double f(double x){ if(x<0) return 1.0; else return 0.0; }
任意の関数を上記部分で指定します。
どんなに些細なものでも構いません。
コメント、ご指摘がございましたらこちらからお願い申し上げます。