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