このページでは、C言語を用いて数値的に微分を評価する数値微分について学びます。
他のプログラミング言語を利用の方は以下のページも参考にしてみてください:
Pythonで数値微分
Juliaで数値微分
Fortranで数値微分
数値的に関数の微分を評価する前に、数学的な微分の定義を復習してみましょう。関数 \(f(x)\)の微分は次の式によって定義されています: \[f'(x)=\lim_{h\rightarrow 0} \frac{f(x+h)-f(x)}{h}\] この式に基づき、十分小さな\(h\)を用いることで関数の微分\(f'(x) \)を次のように近似することが出来ます: \[f'(x) \approx \frac{f(x+h) - f(x)}{h}. \tag{1} \] このような有限の大きさの(ただし十分小さい)幅\(h\)を使って微分を近似する方法は有限差分法と呼ばれます。有限差分法による近似の誤差は、Taylor展開を用いることで次のように評価することが出来ます。例えば、関数\(f(x\))のTaylor展開は次のようになります。 \[f(x+h) = f(x) + f'(x)h +O(h^2). \tag{2}\] ここで、\(O(h^2)\)は、\(h\)の2次以上の項からなり、Taylor展開を有限の次数で打ち切った場合の誤差を表しています。式(\(2\))を変形することで次のような式を得ます。 \[f'(x) = \frac{f(x+h)-f(x)}{h}+O(h). \] したがって、有限差分近似公式, 式(\(1\)), の誤差は\(h\)に比例する事が分かります。これを\(O(h)\)[読み:オーターh]の誤差と呼びます。このことから、十分小さな\(h\)を用いることで関数の微分値\(f'(x)\)を正確に近似できることが分かります。
Talyor展開を用いることで、さらに高精度な近似公式を作ることも可能です。ここでは、関数\(f(x+h)\)と\(f(x-h)\)の2次までのTaylor展開を次のように考えてみます: \[f(x+h) = f(x) + f'(x)h + \frac{1}{2}f''(x)h^2 + O(h^3), \tag{3}\] \[f(x-h) = f(x) - f'(x)h + \frac{1}{2}f''(x)h^2 + O(h^3). \tag{4}\] ここで式(\(3\))と式(\(4\))の差を取ることによって次のような近似公式を導くことが出来ます: \[f'(x) = \frac{f(x+h)-f(x-h)}{2h} +O(h^2). \tag{5}\] この有限差分近似公式,式(\(5\)),の誤差は\(h^2\)に比例しており、\(h\)が小さい極限で式(\(2\))の差分公式よりも速く厳密な微分値に収束することが分かります。このことから、十分小さな\(h\)に対して、式(\(5\))の方が式(\(2\))よりも高精度な近似公式となっていることが分かります。式(\(2\))のような差分公式は前進差分公式と呼ばれ、式(\(5\))のような差分公式は中心差分公式と呼ばれています。
この節では、前進差分公式と中心差分公式を用いた数値微分のC言語の書き方について学びます。具体的な例題として、指数関数,\(f(x)=e^x\) の\( x=0 \)における微分を考えます。次のコードでは、関数の微分値, \( f'(0) \),を差分幅, \(h\),を変えながら計算し、厳密な微分値からの誤差を評価します。数値微分の誤差を厳密な微分値からのずれとして定義しています: \( Error = \left|f'_{numerical}(0)-f'_{exact}(0) \right| \).
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
int main() {
double x = 0.0;
double dfdx_e = exp(x);
double h = 1.0;
int n = 16;
FILE *fp;
// Open file for output
fp = fopen("fd_error.out", "w");
for (int i = 1; i <= n; i++) {
double ff_0 = exp(x);
double ff_p = exp(x + h);
double ff_m = exp(x - h);
double dfdx_f = (ff_p - ff_0) / h;
double dfdx_m = (ff_p - ff_m) / (2 * h);
fprintf(fp, "%16.6e %16.6e %16.6e %16.6e %16.6e\n", h, dfdx_f, fabs(dfdx_f - dfdx_e), dfdx_m, fabs(dfdx_m - dfdx_e));
h = h / 2.0;
}
// Close file
fclose(fp);
return 0;
}
上のコードでは、\(x=0\)における微分の値を計算しています。このため、コードの7行目で変数x
にはx=0
を代入して初期化しています。また、8行目では、関数の厳密な微分値をexact = exp(x)
によって計算しています。
コードの13行目では、結果を出力するためのファイルを開いています。printf
文を使って結果をフォーマット化して表示しています。
上記のコードではfor
ループが始まり、刻み幅\(h\)を変えながら繰り返し数値微分の評価が行われています。h
の初期値は1.0で、各反復で半分にされています。
差分計算はforward_diff
関数とcentral_diff
関数で行われています。前者は前進差分公式を、後者は中心差分公式を使用しています。
上記のLesson 1-1で取り上げた1階微分を数値的に評価する数値微分の拡張として、2階の微分を数値的に取り扱う方法について学びます。式(\(3\))と式(\(4\))と同様に次のようなTaylor展開を考えます。 \[f(x+h) = f(x) + f'(x)h + \frac{1}{2}f''(x)h^2 + \frac{1}{6}f^{(3)}(x)h^3 + O(h^4), \tag{6}\] \[f(x-h) = f(x) - f'(x)h + \frac{1}{2}f''(x)h^2 - \frac{1}{6}f^{(3)}(x)h^3 + O(h^4), \tag{7}\] さらに、式(6)と式(7)を用いることで、次のような関係式を導くことが出来ます。 \[f(x+h) -2 f(x) +f(x-h) = f''(x)h^2 +O(h^4). \tag{8} \] さらに式変形を行うことで、次のような式を得ることができます。 \[f''(x) = \frac{f(x+h) -2 f(x) +f(x-h)}{h^2} +O(h^2). \tag{9} \] この式は、2階微分の差分公式で、2階微分を数値的に評価する際に用います。また、右辺最後の\(O(h^2\))より、差分公式の誤差が\(h^2\)に比例することが分かります。
ここでは例題として、三角関数\(f(x)=\sin(x)\)の二階微分を数値的に計算するプログラムを見てみます。三角関数は解析的に微分でき、関数\(f(x)=\sin(x)\)の2階微分は\(f''(x)=-\sin(x)\)となります。この微分を数値的に評価するために、差分公式、式(9)を用いたプログラムを考えます。ここでは、三角関数の1周期である(\( 0\le x \le 2 \pi \))に渡って二階微分を評価します。
#include <stdio.h>
#include <math.h>
int main() {
int i, n;
double ff_0, ff_p, ff_m, d2fdx2;
double x, h, x_i, x_f;
FILE *file;
x_i = 0.0;
x_f = 2.0 * 3.14159;
n = 100;
h = (x_f - x_i) / n;
file = fopen("second_order_derivative_sin.out", "w");
// Second order derivative of sin(x)
for (i = 0; i <= n; i++) {
x = x_i + i * h;
ff_0 = sin(x);
ff_p = sin(x + h);
ff_m = sin(x - h);
d2fdx2 = (ff_p - 2.0 * ff_0 + ff_m) / (h * h);
fprintf(file, "%16.6e%16.6e%16.6e\n", x, d2fdx2, -sin(x));
}
fclose(file);
return 0;
}
上のコードでは、三角関数\(f(x)=\sin (x)\)の2階微分が範囲\(0 \le x \le 2\pi\)に渡って評価しています。ここでは、評価範囲の始点をx_ini = 0.0
で設定し、終点をx_fin = 2 * M_PI
によって設定しています。M_PI
は標準ライブラリに定義されている円周率の値です。次に、評価区間であるx_ini
とx_fin
の間の区間をn
の小区間に分割します。この際、小区間の幅\(h\)は次式で表されます:h = (x_fin - x_ini) / n
. 上のコードのfor
ループ内では、二階微分の値が差分公式, 式(\(9\)),により評価され、標準出力に書き出されます。
結果を比較することで、数値微分によって、厳密な微分の値を再現できていることが確認できます。