ここで、プログラム開発の標準的なルールを説明します。
以下、汎用の微分方程式数値計算関数を別ファイルにする例を示します。
ヘッダーファイルはdiffEqMethod.h、本体の記述部分をdiffEqMethods.cとします。(名前の付け方は自由ですが、中身がわかるように名付けることが重要です。)
これらを使うprac4.cをコンパイルするには、コマンドラインにソースファイル名を並べます。ヘッダーファイルのインクルードはそれぞれのソースの最初の方に書き込みます。
cc -o prac4 prac4.c diffEqMethods.c -lm -lcpgplot -lpgplot -lX11
/* 微分方程式の数値計算関数の定義 * 通常は関数ごとに引数のコメントをきちんと書く */ void euler(int n, double *xin, double *xout, double h, void (*f)(double *, double *, double *), double *params); void rk4(int size_of_x, double *x0, double *x, double h, void (*f)(), double *params);
/* 微分方程式の数値解法関数 */ #include <stdlib.h> #include "diffEqMethods.h" /* オイラー法 */ void euler(int size_of_x, double *x0, double *x, double h, void (*f)(double *, double *, double *), double *params) { int i; double *funcVal; funcVal = (double *) malloc(size_of_x * sizeof(double)); f(x0, funcVal, params); // 関数値の計算 (入力x0に対する微分方程式の右辺の値をfuncValに受け取る for(i=0; i<size_of_x; i++) { x[i] = x0[i] + h * funcVal[i]; //オイラー法 } } /* ルンゲ・クッタ法 */ void rk4(int size_of_x, double *x0, double *x, double h, void (*f)(), double *params) { int i; double *funcVal[4]; for(i=0;i<4;i++) { funcVal[i] = (double *) malloc(size_of_x * sizeof(double)); } f(x0, funcVal[0], params); for ( i = 0 ; i < size_of_x ; i++) x[i] = x0[i] + h/2.0*funcVal[0][i]; f(x, funcVal[1], params); for ( i = 0 ; i < size_of_x ; i++) x[i] = x0[i] + h/2.0* funcVal[1][i]; f(x, funcVal[2],params); for ( i = 0 ; i < size_of_x ; i++) x[i] = x0[i] + h* funcVal[2][i]; f(x, funcVal[3], params); for ( i = 0 ; i < size_of_x ; i++) x[i] = x0[i] + h/6.0 * (funcVal[0][i]+2.0*funcVal[1][i]+2.0*funcVal[2][i]+funcVal[3][i]); }
上記のdiffEqMethods.cはそのまま使う.func()を別のファイルにしてしまうこともあり得る.あるいは,func()をdiffEqMethods.cに入れる構成でもよい.
/* 交通流のOVモデル(Optimal Velocity Model) */ #include "cpgplot.h" #include <stdio.h> #include <math.h> #include <stdlib.h> #include <string.h> #include "diffEqMethods.h" #define N 80 /* 車の数 */ #define DT 0.002 #define UpdateMax 200000.0 void rk4(int size_of_x, double *x0, double *x, double h, void (*f)(), double *params); void func(double *x, double *funcVal, double *params); /* xは現在の値(入力)、funcValは返値(微分方程式の右辺)*/ double L=1000.0; double c=2.0, tanhc; /* 最適速度に含まれる係数*/ int main(int ARGC, char *ARGV[]) { double x[2*N+2], xold[2*N+2], xold4draw[2*N+2]; /* 車の位置,座標を記憶する配列 0〜Nまでが位置,N+1〜2N+1までが速度*/ /* xold4drawは、描画用の「以前の位置」 */ long n = 0, m; double t=0, told=0; double params[1]; tanhc = tanh(c); printf("道路の長さは?(%4.0f程度) ", c*N); scanf("%lf", &L); printf("感度aは? (臨界速度=%.3f)", 2./cosh(L/N-2.0)); scanf("%lf", ¶ms[0]); cpgopen("/xserv"); /* Xwindow(Xサーバ)上へ描画 */ cpgpap(12.0,0.6); /* ウィンドウの大きさと縦横比 */ cpgenv(0.0, (float) UpdateMax*DT, 0.0, (float)L, 0,0); /* 座標の設設定 */ srand(323211); /* 乱数の種 (適当な整数) */ /* 初期位置と速度を乱数で構成 */ for(n = 0; n < N; n++) { xold[N+1+n] = (double)rand()/RAND_MAX * tanh(2.0); xold[n] = L * (double)n/N ; /* 初期位置は等間隔 */ } xold[N] = xold[0]; xold[2*N+1] = xold[N+1]; memcpy(x, xold, sizeof(x)); memcpy(xold4draw, xold, sizeof(x)); /* update */ while (t < UpdateMax*DT) { told = t; for(n=0; n< 100; n++) { /* 時間発展 */ rk4(2*N+2, xold, x, DT, func, params); memcpy(xold, x, sizeof(x)); for(m=0; m<N; m++) { if(xold[m]> L) xold[m] -= L; // 周期境界条件 } xold[N]=xold[0]; t+=DT; } /* 描画 */ cpgsci(2); cpgslw(2); for (n=0; n< N; n++) { if(xold4draw[n] < x[n]) { // 境界を超えたときは書かない cpgmove((float) told, xold4draw[n]); cpgdraw((float) t, x[n]); } } memcpy(xold4draw, x, sizeof(x)); // 描画した前の位置を覚えておく } cpgclos(); exit(0); } void func(double *x, double *funcVal, double *params) { long i; double headway; double sensitivity = params[0]; for(i = 0; i < N; i++) { headway = x[i+1] - x[i]; if(headway < 0) headway += L; funcVal[i] = x[N+1+i]; funcVal[N+1+i] = sensitivity * (tanh( headway - 2.0) + tanh(2.0) - x[N+1+i]); } /* 周期境界条件 */ funcVal[N] = funcVal[0]; funcVal[2*N+1] = funcVal[N+1]; }
printf("道路の長さは?(%4.0f程度) ", c*N); scanf("%lf", &L); printf("感度aは? (臨界速度=%.3f)", 2./cosh(L/N-2.0)); scanf("%lf", ¶ms[0]);の代わりに
if (ARGC != 3) { printf("コマンドライン引数としてLとaを与えてください。(典型例: 160 1.5)\n"); exit(0); } L = strtof(ARGV[1],NULL); params[0] = strtof(ARGV[2], NULL); printf("L=%f, a=%f\n", L, params[0]);のように与える。
コマンドライン引数は文字列として受け取るので、数値(flot)に変換する。変換にはatofという関数が用いられるが、エラー処理の安全性のためにはstrtofが良いという解説もあるので、ここではstrtofを使ってみた。(atof(ARGV[0])のような記述でも良い。)
こちらに手順を書いた。
複数のパラメータがある場合は、それぞれどれか一つのパラメータに対する注目して、値をすこしずつかえ変化の様子を観察することが重要である。
数値データとしてまず考えられるのは「平均速度」である。