常微分方程式の数値解を求める手法のひとつです。
オイラー法が1次の精度であるのに対して、ルンゲ・クッタ法では4次の精度まで得られます。
y が x の未知関数 \( y=y\left( x\right) \) で、\( \dfrac{dy}{dx}=f\left( x,y\right) \) が与えられているとき、
\(x=x_{i} \) で \( y=y\left( x_{i}\right) \) のとき、\( x_{i+1}=x_{i}+h \) での y の値 \( y\left( x_{i+1}\right) \) を次の式で近似したものです。
\(k_{1}=hf\left( x_{i},y_{i}\right)\)
\(k_{2}=hf\left( x_{i}+\dfrac{h}{2},y_{i}+\dfrac{k_{1}}{2}\right)\)
\(k_{3}=hf\left( x_{i}+\dfrac{h}{2},y_{i}+\dfrac{k_{2}}{2}\right)\)
\(k_{4}=hf\left( x_{i}+h,y_{i}+k_{3}\right)\)
\(x_{i+1}=x_{i}+h\)
\(y_{i+1}=y_{i}+\dfrac{1}{6} \left( k_{1}+2k_{2}+2k_{3}+k_{4}\right)\)
\(k_{1}, k_{2}, k_{3}, k_{4}\) を図で表すと下のようになります。
\(x_{i}\leq x\leq x_{i+1}\) で ①~④ の4点で傾きを求め、点① \(\left( x_{i},y_{i}\right)\) からそれぞれの傾きを使って \(x_{i+1}( =x_{i}+h)\) での y の変化量を求めていきます。
点① \(\left( x_{i},y_{i}\right)\) で傾きを求めます(赤い矢印)。
点① からこの傾きを使って求めた \(x_{i+1}\) での y の変化量が \(k_{1}\) です。
点② \(\left( x_{i}+\dfrac{h}{2},y_{i}+\dfrac{k_{1}}{2}\right)\) で傾きを求めます(青い矢印)。
点①からこの傾きを使って求めた \(x_{i+1}\) での y の変化量が \(k_{2}\) です。
点③ \(\left( x_{i}+\dfrac{h}{2},y_{i}+\dfrac{k_{2}}{2}\right)\) で傾きを求めます(えんじ色の矢印)。
点①からこの傾きを使って求めた \(x_{i+1}\) での y の変化量が \(k_{3}\) です。
点④ \(\left( x_{i}+h,y_{i}+k_{3}\right)\) で傾きを求めます(緑色の矢印)。
点① からこの傾きを使って求めた \(x_{i+1}\) での y の変化量が \(k_{4}\) です。
例として微分方程式 \(\dfrac{dy}{dx}=5x^{2}y\) の解をルンゲ・クッタ法で求めていきます。
ここで \(f\left( x,y\right) =5x^{2}y\) は点 \(( x, y)\) での \( y\left( x\right) \) の傾きで、主な点での傾きを図で 表すと下のようになります。矢印が傾きで、これらが接線になるように結んだ曲線がこの微分方程式の解です。解は無数にあります。
この微分方程式の一般解は \(y=A\exp \left( \dfrac{5}{3}x^{3}\right)\) です。
\(x=0\) のとき、それぞれ \(y=0.1, 0.2, 0.3\) を通る3つの特殊解
\(y=0.1 \exp \left( \dfrac{5}{3}x^{3}\right), y=0.2 \exp \left( \dfrac{5}{3}x^{3}\right), y=0.3 \exp \left( \dfrac{5}{3}x^{3}\right)\)
を図に書き込むと以下のようになります。いずれも接線が矢印方向となっています。
ここで、\(x=0\) のとき、\(y=0.1\) を通る解をルンゲ・クッタ法で求めます。
プログラムのソース(C++)です。\(h=0.05\) としています。
#include <stdio.h>
#include <math.h>
//--- dy/dx=5*x*x*y, y=C*exp(5/3*x*x*x)
// 特別解 x=0.0,y=0.1 y=0.1*exp(5/3*x*x*x)
double f(double x,double y);
double y_true(double x);
int main(){
int i,N;
double x,y,h;
double dy;
double k1,k2,k3,k4;
h = 0.05; //刻み幅
N = (1.0-0.0)/h;
printf(" x y_RK y_true dy\n");
//--- 初期値 ---
x = 0.0;
y = 0.1;
dy = y-y_true(x);
printf("%7.5lf %10.7lf %10.7lf %10.7lf\n",x,y,y_true(x),dy);
//--------------
for (i=0; i<N; i++){
k1 = h*f(x,y);
k2 = h*f(x+h/2.0,y+k1/2.0);
k3 = h*f(x+h/2.0,y+k2/2.0);
k4 = h*f(x+h,y+k3);
x = x+h;
y = y+(k1+2.0*k2+2.0*k3+k4)/6.0;
dy = y-y_true(x); // 解析解との差
printf("%7.5lf %10.7lf %10.7lf %10.7lf\n",x,y,y_true(x),dy);
}
}
double f(double x,double y){
return 5.0*x*x*y;
}
//--- 解析解 ---
double y_true(double x){
return 0.1*exp(5.0/3.0*x*x*x);
}
結果です。比較的簡単な式ですので実際の値とよく一致しています。
x y_RK y_true dy
0.00000 0.1000000 0.1000000 0.0000000
0.05000 0.1000208 0.1000208 -0.0000000
0.10000 0.1001668 0.1001668 -0.0000000
0.15000 0.1005641 0.1005641 -0.0000000
0.20000 0.1013423 0.1013423 -0.0000000
0.25000 0.1026384 0.1026384 -0.0000000
0.30000 0.1046028 0.1046028 -0.0000000
0.35000 0.1074073 0.1074073 -0.0000000
0.40000 0.1112563 0.1112563 -0.0000000
0.45000 0.1164015 0.1164015 -0.0000000
0.50000 0.1231624 0.1231624 -0.0000000
0.55000 0.1319551 0.1319551 -0.0000000
0.60000 0.1433329 0.1433329 -0.0000000
0.65000 0.1580448 0.1580448 -0.0000000
0.70000 0.1771216 0.1771217 -0.0000001
0.75000 0.2020054 0.2020056 -0.0000001
0.80000 0.2347456 0.2347459 -0.0000003
0.85000 0.2783027 0.2783034 -0.0000007
0.90000 0.3370279 0.3370294 -0.0000015
0.95000 0.4174317 0.4174349 -0.0000032
1.00000 0.5294421 0.5294490 -0.0000069
図で表すと下のようになります。