(情報処理IIの授業で使っていたコンピューター環境は古いので、 当時使っていたプログラムを GLSC を利用するように書き換えたもの。)
/*
* reidai7-1-glsc.c --- reidai7-1.c を GLSC を用いるように書き換えた
* http://nalab.mind.meiji.ac.jp/~mk/program/ode_prog/reidai7-1-glsc.c
*/
/**********************************************************************
**********************************************************************
*
* 2 次元の定数係数線形常微分方程式
* x'(t) = a x + b y
* y'(t) = c x + d y
* に初期条件
* x(0)=x0
* y(0)=y0
* を課した常微分方程式の初期値問題を解いて、相図を描く。
*
* このプログラムは次の4つの部分から出来ている。
* main()
* 主プログラム
* 行列の係数の入力、ウィンドウのオープン等の初期化をした後に、
* ユーザーにメニュー形式でコマンドを入力してもらう。
* 実際の計算のほとんどは他の副プログラムに任せている。
* draworbit(x0,y0,h,tlimit)
* (x0,y0) を初期値とする解の軌道を描く。
* 刻み幅を h、追跡時間を tlimit とする。
* 近似解の計算には Runge-Kutta 法を用いる。
* fx(x,y)
* 微分方程式の右辺の x 成分
* fy(x,y)
* 微分方程式の右辺の y 成分
**********************************************************************/
#include <stdio.h>
#include <stdlib.h> // system()
#include <math.h>
#include <glsc.h>
// 係数行列 A の成分
double a, b, c, d;
// ウィンドウに表示する範囲
double xleft, xright, ybottom, ytop;
void draworbit(double, double, double, double);
void g_dump(char *fname, Display *display, Window wid)
{
char command[256];
sprintf(command, "import -window %lu %s", wid, fname);
system(command);
}
int main(void)
{
// 初期値
double x0, y0;
// 時間の刻み幅、追跡時間
double h, tlimit, newh, newT;
// メニューに対して入力されるコマンドの番号
int cmd;
// マウスのボタンの状態
int but;
//
double win_width, win_height, w_margin, h_margin;
//
Display *display;
Window wid; // Window ID
// ウィンドウに表示する範囲の設定
xleft = -1.0;
xright = 1.0;
ybottom = -1.0;
ytop = 1.0;
// 時間刻み幅、追跡時間(とりあえず設定)
h = 0.01;
tlimit = 10.0;
// 行列の成分を入力
printf(" a,b,c,d=");
scanf("%lf%lf%lf%lf", &a, &b, &c, &d);
// ウィンドウを開く
win_width = 200.0; win_height = 200.0; w_margin = 10.0; h_margin = 10.0;
g_init("GRAPH", win_width + 2 * w_margin, win_height + 2 * h_margin);
g_device(G_BOTH);
g_def_scale(0, xleft, xright, ybottom, ytop,
w_margin, h_margin, win_width, win_height);
g_sel_scale(0);
// x 軸、y 軸を描く
g_move(xleft, 0.0); g_plot(xright, 0.0);
g_move(0.0, ybottom); g_plot(0.0, ytop);
// メイン・ループの入口
do {
// メニューを表示して、何をするか、番号で選択してもらう
printf(" したいことを番号で選んで下さい。\n");
printf(" -1:メニュー終了, 0:初期値のキーボード入力, 1:初期値のマウス入力\n");
printf(" 2: 刻み幅,追跡時間変更(現在 h=%7.4f, T=%7.4f)\n", h,tlimit);
scanf("%d", &cmd);
// 番号 cmd に応じて、指示された仕事をする
if (cmd == 0) {
// 初期値の入力
printf(" 初期値 x0,y0=");
scanf("%lf%lf", &x0, &y0);
draworbit(x0,y0,h,tlimit);
}
else if (cmd == 1) {
do {
printf("マウスの左ボタンで初期値を指定して下さい(右ボタンで中止)。\n");
g_mouse_sence(&x0, &y0, &but);
printf("x0=%g, y0=%g, but=%d\n", x0, y0, but);
if (but == 1) {
printf("左ボタン, (x0,y0)=%f %f\n", x0,y0);
draworbit(x0,y0,h,tlimit);
}
else if (but == 2) {
printf("中ボタン, (x0,y0)=%f %f\n", x0,y0);
draworbit(x0,y0,-h,tlimit);
}
}
while (but != 3);
printf("右ボタンがクリックされました。マウスによる初期値の入力を打ち切ります。\n");
}
else if (cmd == 2) {
// 時間刻み幅、追跡時間の変更
printf("時間刻み幅 h (%g), 追跡時間 T (%g): ", h, tlimit);
scanf("%lf%lf", &newh, &newT);
if (newh != 0 && newT != 0) {
h = newh;
tlimit = newT;
printf("新しい時間刻み幅 h = %g, 新しい追跡時間 T = %g\n", h, tlimit);
}
else {
printf("h=%g, T=%g は不適当です。\n", newh, newT);
}
}
}
while (cmd != -1);
g_dump("reidai7-1.png", g_get_display(), g_get_window());
printf("GLSCウィンドウを左ボタンでクリックして下さい\n");
g_sleep(-1.0);
}
// 指示された初期値に対する解軌道を描く
void draworbit(double x0, double y0, double h, double tlimit)
{
int in;
double x, y, fx(double, double), fy(double, double);
double k1x, k1y, k2x, k2y, k3x, k3y, k4x, k4y, t;
// 時刻を 0 にセットする
t = 0.0;
// 初期値のセット
x = x0;
y = y0;
// 初期点を描く
if ((in = (xleft <= x && x <= xright && ybottom <= y && y <= ytop)) != 0)
g_move(x,y);
// ループの入口
do {
// Runge-Kutta 法による計算
// k1 の計算
k1x = h * fx(x, y);
k1y = h * fy(x, y);
// k2 の計算
k2x = h * fx(x + k1x / 2.0, y + k1y / 2.0);
k2y = h * fy(x + k1x / 2.0, y + k1y / 2.0);
// k3 の計算
k3x = h * fx(x + k2x / 2.0, y + k2y / 2.0);
k3y = h * fy(x + k2x / 2.0, y + k2y / 2.0);
// k4 の計算
k4x = h * fx(x + k3x, y + k3y);
k4y = h * fy(x + k3x, y + k3y);
// (Xn+1,Yn+1) の計算
x += (k1x + 2.0 * k2x + 2.0 * k3x + k4x) / 6.0;
y += (k1y + 2.0 * k2y + 2.0 * k3y + k4y) / 6.0;
// 解軌道を延ばす
if (in) {
if ((in = (xleft <= x && x <= xright && ybottom <= y && y <= ytop)) != 0)
g_plot(x,y);
}
else {
if ((in = (xleft <= x && x <= xright && ybottom <= y && y <= ytop)) != 0)
g_move(x,y);
}
// 時刻を 1 ステップ分進める
t += h;
}
while (fabs(t) <= fabs(tlimit)); // まだ範囲内かどうかチェック
}
// 微分方程式の右辺のベクトル値関数 f の x 成分
double fx(double x, double y)
{
return a * x + b * y;
}
// 微分方程式の右辺のベクトル値関数 f の y 成分
double fy(double x, double y)
{
return c * x + d * y;
}