Runge-Kutta-Fehlberg法による数値解析

### Runge-Kutta-Fehlberg法とは
前回にRunge-Kutta法による数値解析を投稿しました。また、前々回においては、Euler法による数値解析を投稿しました。これらの方法のでは、シミュレーション時間を固定幅の刻み幅で分割し、分割した単位時間をそれぞれの方式の演算区間としていました。対して、Runge-Kutta-Fehlberg法では、シミュレーション時間の刻み幅を可変にすることが出来ます。これにより、数値の変化が基準より少ない場合には、刻み幅を大きくし、数値の変化が基準値より大きい場合には、刻み幅を小さくする制御として使用することが出来ます。従って、Runge-Kutta法よりも精度が高くなります。今回においても対象とする回路は、直流電源のRC回路とし、入力を電源電圧、出力をコンデンサ電圧とします。次にC言語によるソースコードを示します。


```
#include<stdio.h>
#include<math.h>

double func(double t, double Vc) {

	return 100000 - 1000 * Vc;

}

int main(void) {

	//変数設定//

	long double Vc, t, step, R, C, end, r;
	long double k1, k2, k3, k4, k5, k6;
	int FLAG = 0;
	long double step_MIN, step_MAX, tol, e, Vc_o5, Vc_o4, diff;
	int error;
	FILE *fi;

	//変数初期化//
	R = 10;
	C = 0.0001;
	r = 0.0;
	step = ((R*C) / 100.0) * 100;
	//step = 0.00001; //step数は関数に応じて任意に設定する
	diff = 0.0;
	Vc = 0.0;
	Vc_o4 = 0.0;
	Vc_o5 = 0.0;
	step_MIN = 0.00001;
	step_MAX = 0.005;
	//tol = 2.059999; //偏差の基準は関数に応じて任意に決定する
	tol = 2.06;
	e = 0;
	t = 0.0;
	end = 0.01;

	//textファイルの作成//
	if ((error = fopen_s(&fi, "Runge_Kutta_Fehlberg_data.txt", "w")) != 0) {

		printf("\nThis file can't opened \n\n");

		return 0;

	}

	//初期値の書き込み//
	fprintf(fi, "%f %f\n", t, Vc);

	//Runge-Kutta-Fehlberg法の演算//
	while (FLAG != 1) {
		
	/*	k1からk6までを求める演算では、コメントアウトしている式が正しい式なのですが、
		分数のまま演算すると後で0になってしまうので、わざわざ小数にしています*/

		k1 = step*func(t, Vc); printf("k1 = %lf\n", k1);
		k2 = step*func(t + step*0.25, Vc + (k1*0.25)); printf("k2 = %lf\n", k2);
		//k3 = step*func(t + step*(3/8), Vc + ( (k1*(3/32)) + (k2*(9/32)))); 
		k3 = step*func(t + step*(0.375), Vc + ((k1*(0.09375)) + (k2*(0.28125))));
		//k4 = step*func(t + step*(12/13), Vc +  (((1932/2197)*k1) - ((7200/2197)*k2) + ((7296/2197) * k3) )); 
		k4 = step*func(t + step*(0.92307), Vc + (((0.87938)*k1) - ((3.27719)*k2) + ((3.320892) * k3))); printf("k4 = %lf\n", k4);
		//k5 = step*func(t + step, Vc +  (((439 / 216)*k1) - (8 * k2) + ((3680 / 513)*k3) - ((845 / 4104)*k4)));
		k5 = step*func(t + step, Vc + (((2.032407)*k1) - (8 * k2) + ((7.173489)*k3) - ((0.20589)*k4))); printf("k5 = %lf\n", k5);
		//k6 = step*func(t + step*0.5, Vc + (-((8 / 27)*k1) + (2 * k2) - ((3544 / 2565)*k3) + ((1859 / 4104)*k4) - ((11/40)*k5)));
		k6 = step*func(t + step*0.5, Vc + (-((0.29629)*k1) + (2 * k2) - ((1.38167)*k3) + ((0.45330)*k4) - ((0.275)*k5))); printf("k6 = %lf\n", k6);

		Vc_o5 = ((0.1185 * k1) + (0.51898 * k3) + (0.50613 * k4) - (0.18 * k5) + (0.03636 * k6));
		//Vc_o5 = (((16 / 135) * k1) + ((6656 / 12825) * k3) + ((28561 / 56430) * k4) - ((9 / 50) * k5) + ((2 / 55) * k6));
		//Vc_o4 = Vc + (((25 / 216)*k1) + ((1408 / 2565)*k3) + ((2197 / 4104)*k4) - (1 / 5)*k5);
		Vc_o4 = ((0.11574 * k1) + (0.54892 * k3) + (0.53533 * k4) - (0.2)*k5);

		diff = Vc_o5 - Vc_o4;

		if (diff < 0) { 

			diff = diff*(-1);

		}

		r = (diff / step); printf("r = %lf\n", r);

		if (r <= tol) {

			t = t + step;
			//Vc = Vc + (25/216)*k1 + (1408/2565)*k3 + (2197/4104)*k4 - (1/5)*k5;
			Vc = Vc + (0.11574) * k1 + (0.54892)*k3 + (0.535722)*k4 - (0.2)*k5;
			fprintf(fi, "%f %f\n", t, Vc);

		}

		e = pow((tol / (2 * r)), 0.25);

		//次のif文の基準は、関数に応じて任意に設定する
		if (e <= 0.1) {

			step = step * 0.1;  //step幅を小さくする

		}
		else if (e >= 1.5) {

			//step = step * 100000;
			step = step * 2; //step幅を大きくする

		}
		else {

			step = e*step;

		}

		if (step > step_MAX) {

			step = step_MAX;

		}

		printf("t = %lf\n", t);

		if (t >= end) {

			FLAG = 1;

		}
		else if ((t + step) > end) {

			step = 0.01 - t;

			if (step < step_MIN) {

				FLAG = 1;
				printf("\nMINIMUM step exceeded !!\n");

			}
		}
	}

	//textファイルの保存//
	fclose(fi);

	return 0;
}

```


上記のソースコードを実行すると、時間領域におけるRC回路のコンデンサ電圧の値がtextファイルとして出てきます。
この出てきたファイルをgunuplotで実行させるとRC回路のコンデンサ電圧におけるEuler法を用いた数値解をプロットする事が出来ます。

以下に、簡単なGunuplotのソースコードを示します。


```

se grid
se xrange [0:0.01]
se yrange [0:110]
se xlabel "s[sec]"
se ylabel "Vc[V]"
plot "Euler_data.txt" using 1:2 with lines



```