/*
* rkf3.c --- RKF45 (多次元, 何次元でも動くように)
*/
#include <stdio.h>
#include <math.h> /* fabs() */
#include "rkf3.h"
static double rkf45_alpha[RKF45_STAGE] = {
0.0, 1.0/4, 3.0/8, 12.0/13, 1.0, 1.0/2};
static double rkf45_beta[RKF45_STAGE][RKF45_STAGE-1] = {
{ 0.0, 0.0, 0.0, 0.0},
{ 1.0/4, 0.0, 0.0, 0.0},
{ 3.0/32, 9.0/32, 0.0, 0.0},
{1932.0/2197, -7200.0/2197, 7296.0/2197, 0.0, 0.0},
{ 439.0/216, -8.0, 3680.0/513, -845.0/4104, 0.0},
{ -8.0/27, 2.0, -3544.0/2565, 1859.0/4104, -11.0/40}
};
static double rkf45_mu5[RKF45_STAGE] = {
16.0/135, 0.0, 6656.0/12825, 28561.0/56430, -9.0/50, 2.0/55
};
static double rkf45_mu4[RKF45_STAGE] = {
25.0/216, 0.0, 1408.0/2565, 2197.0/4104, -1.0/5, 0.0
};
static double rkf45_mu_diff[RKF45_STAGE] = {
-1.0/360, 0.0, 128.0/4275, 2197.0/75240, -1.0/50, -2.0/55
};
static double sqr(double x) { return x * x; }
#include "rkf_utilities3.c" /* dotprod(), etc. */
/*
* d次元の ODE x'(t)=f(t,x) を解く。
* 時刻 t で値が x だとして、h だけ時間を進めたときの値 nextx を計算する。
*
* 注意:
* x と next x は同じメモリー領域を指さないようにしておくこと。つまり
* bf_rkf45(d, f, t, x, h, x, err, k, w);
* のようにして呼び出すと結果は壊れてしまう。
* --- こういう仕様にした方が全体に無駄が少なくなると考えた。
*
* work は問題の次元 d だけの長さの double 型の作業用領域
*/
int bf_rkf45(int d, function f,
double t, double *x, double h,
double *nextx, double *error,
double k[][RKF45_STAGE], double *work)
{
int i, j, s = RKF45_STAGE;
/* x と nextx が重ならないことをチェック */
if (x == nextx) {
fprintf(stderr, "bf_rkf45(): x と nextx は同じメモリー領域ではいけない!");
*error = 1e+10;
return -1;
}
/* k1,k2,k3,k4,k5,k6 を計算する */
for (i = 0; i < s; i++) {
double *xx = nextx; /* ちょっとの間使わせてもらう (お行儀が悪いけど) */
double *kk = work;
for (j = 0; j < d; j++)
xx[j] = x[j] + dotprod(i, rkf45_beta[i], k[j]);
f(t + rkf45_alpha[i] * h, xx, kk);
for (j = 0; j < d; j++)
k[j][i] = kk[j] * h;
}
/* 次のステップの値を5次公式で計算する&4次公式の誤差を推測する */
for (j = 0; j < s; j++) {
nextx[j] = x[j] + dotprod(s, rkf45_mu5, k[j]);
work[j] = dotprod(s, rkf45_mu_diff, k[j]);
}
/* 推定誤差のノルム */
*error = 0;
for (j = 0; j < d; j++) *error += sqr(work[j]);
*error = sqrt(*error);
/* もし4次の値が欲しければ */
for (j = 0; j < d; j++)
work[j] -= nextx[j];
return 0;
}