heat1d-i-glsc.c -- これを別名で保存するのが簡単かも (Safari ならば、control-クリック)。
/*
* heat1d-i-glsc.c -- 陰的スキームで熱方程式を解く
*
* 菊地・山本, 微分方程式と計算機演習, 山海堂 (1991) 第11章
* p237 のプログラムを修正・拡張したもの
*
C ** IMPLICIT FINITE DIFFERENCE METHOD **
C ** FOR HEAT EQUATION **
C nfunc : 関数の番号(5種類の関数を用意している)
C theta : スキームのパラメーター
C N : 分割の数
C t : 時刻
C tau : 時間の刻み幅
C Tmax : 時刻の上限
C h : 空間の刻み幅
C AL,AD,AU : 係数行列
C u : DIMENSION FOR UNKNOWN FUNCTION
*
* このプログラムは
* http://nalab.mind.meiji.ac.jp/%7Emk/program/
* から入手可能
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define G_DOUBLE
#include <glsc.h>
/* 円周率 --- main() で pi = 4.0 * atan(1.0); と代入 */
double pi;
void trilu(int, double *, double *, double *);
void trisol(int, double *, double *, double *, double *);
double f(double, int);
void print100(int, double, double *);
void axis(double, double, double, double);
void fsymbol(double, double, char *);
void print_parameters(double, double, double, double,
int, double, int,
double, double, double, double);
void save_picture(void);
int main()
{
int N, nfunc, i, n, nMax;
double a, b, theta, h, Tmax, tau, c1, c2, c3, c4, lambda, t;
double *AL, *AD, *AU, *ff, *u;
double xleft, ybottom, xright, ytop;
/* グラフを書き換える時間間隔 */
double dt;
/* 何ステップおきにグラフを書き換えるか */
int skip;
/* 以前描いたグラフを消すか? (0=No, 1=Yes) */
int erase_always = 0;
/* 数値を表示するか? (0=No, 1=Yes) */
int print_numerical_data = 0;
/* */
double win_width, win_height, w_margin, h_margin;
/* 円周率 */
pi = 4.0 * atan(1.0);
/* 区間の左端、右端 */
a = 0.0;
b = 1.0;
/* 入力 */
printf("入力して下さい : nfunc(1..5)=");
scanf("%d", &nfunc);
printf("入力して下さい : θ=");
scanf("%lf", &theta);
printf("入力して下さい : N=");
scanf("%d", &N);
printf("入力して下さい : λ=");
scanf("%lf", &lambda);
/* ベクトルの準備 */
if ((AL = malloc(N * sizeof(double))) == NULL) {
fprintf(stderr, "cannot allocate AL[]\n");
exit(1);
}
if ((AD = malloc(N * sizeof(double))) == NULL) {
fprintf(stderr, "cannot allocate AD[]\n");
exit(1);
}
if ((AU = malloc(N * sizeof(double))) == NULL) {
fprintf(stderr, "cannot allocate AU[]\n");
exit(1);
}
if ((ff = malloc(N * sizeof(double))) == NULL) {
fprintf(stderr, "cannot allocate ff[]\n");
exit(1);
}
if ((u = malloc((N + 1) * sizeof(double))) == NULL) {
fprintf(stderr, "cannot allocate u[]\n");
exit(1);
}
/* */
h = (b - a) / N;
tau = lambda * h * h;
printf(" 時間の刻み幅τ= %g になりました。\n", tau);
/* 入力 */
printf("入力して下さい : 最終時刻 Tmax=");
scanf("%lf", &Tmax);
printf("入力して下さい : グラフ書き換え時間間隔 Δt=");
scanf("%lf", &dt);
skip = (int) rint(dt / tau);
/* skip が 0 になっていないかチェック */
if (skip == 0) {
fprintf(stderr, "Δtが小さすぎるので、Δt=τ=%g にします。\n", tau);
skip = 1;
dt = tau;
}
/* 係数行列の準備 */
c1 = - theta * lambda;
c2 = 1.0 + 2.0 * theta * lambda;
c3 = 1.0 - 2.0 * (1.0 - theta) * lambda;
c4 = (1.0 - theta) * lambda;
for (i = 1; i < N; i++) {
AL[i] = c1;
AD[i] = c2;
AU[i] = c1;
}
/* 初期値 */
for (i = 0; i <= N; i++)
u[i] = f(a + i * h, nfunc);
/* 出力(t=0) */
/* 出力(ここでは画面に数値を表示すること)は FORTRAN と C で
かなり異なるので、直接の翻訳は出来ない。ごたごたするので、
print100 という関数にまとめた */
t = 0.0;
print100(N, t, u);
/* ***************** グラフィックスの準備 ***************** */
/* グラフィックス画面の準備 -- まず画面の範囲を決めてから */
xleft = a - (b - a) / 10;
xright = b + (b - a) / 10;
ybottom = - 0.1;
ytop = 1.1;
/* メタファイル名は "HEAT",
* ウィンドウのサイズは、
横 win_width + 2 * w_margin, 縦 win_height + 2 * h_margin */
win_width = 150.0; win_height = 150.0; w_margin = 10.0; h_margin = 10.0;
g_init("HEAT", win_width + 2 * w_margin, win_height + 2 * h_margin);
/* 画面とメタファイルの両方に記録する */
g_device(G_BOTH);
/* 座標系の定義: [-0.1,1.1]×[-0.1,1.1] という閉領域を表示する */
g_def_scale(0,
-0.1, 1.1, -0.1, 1.1,
w_margin, h_margin, win_width, win_height);
/* 線を二種類用意する */
g_def_line(0, G_BLACK, 0, G_LINE_SOLID);
g_def_line(1, G_BLACK, 0, G_LINE_DOTS);
/* 表示するための文字列の属性を定義する */
g_def_text(0, G_BLACK, 3);
/* 定義したものを選択する */
g_sel_scale(0); g_sel_line(0); g_sel_text(0);
/* タイトルと入力パラメーターを表示する */
print_parameters(xleft, ybottom, xright, ytop,
nfunc, theta, N,
lambda, tau, Tmax, t);
/* 座標軸を表示する */
g_sel_line(1);
g_move(-0.1, 0.0); g_plot(1.1, 0.0);
g_move(0.0, -0.1); g_plot(0.0, 1.1);
g_sel_line(0);
/* t=0 の状態を表示する */
g_move(0.0, u[0]);
for (i = 1; i <= N; i++)
g_plot(i * h, u[i]);
/* 係数行列の LU 分解 */
trilu(N - 1, AL + 1, AD + 1, AU + 1);
/* 繰り返し計算 */
nMax = (int) rint(Tmax / tau);
for (n = 1; n <= nMax; n++) {
/* 連立1次方程式を作って解く */
for (i = 1; i < N; i++)
ff[i] = c3 * u[i] + c4 * (u[i - 1] + u[i + 1]);
trisol(N - 1, AL + 1, AD + 1, AU + 1, ff + 1);
for (i = 1; i < N; i++)
u[i] = ff[i];
/* 境界条件 */
u[0] = 0.0;
u[N] = 0.0;
/* 時刻 */
t = n * tau;
if (n % skip == 0) {
/* 数値データの表示 */
if (print_numerical_data)
print100(N, t, u);
/* 解のグラフを描く */
if (erase_always) {
/* これまで描いたものを消し、パラメーターを再表示する */
g_cls();
print_parameters(xleft, ybottom, xright, ytop,
nfunc, theta, N, lambda, tau, Tmax, t);
}
g_move(0.0, u[0]);
for (i = 1; i <= N; i++)
g_plot(i * h, u[i]);
}
}
printf("マウスでウィンドウをクリックして下さい。\n");
g_sleep(-1.0);
g_term();
return 0;
}
/**********************************************************/
/* 三重対角行列の LU 分解 (pivoting なし) */
void trilu(int n, double *al, double *ad, double *au)
{
int i, nm1 = n - 1;
/* 前進消去 (forward elimination) */
for (i = 0; i < nm1; i++) {
al[i + 1] /= ad[i];
ad[i + 1] -= au[i] * al[i + 1];
}
}
/* LU 分解済みの三重対角行列を係数に持つ三項方程式を解く */
void trisol(int n, double *al, double *ad, double *au, double *b)
{
int i, nm1 = n - 1;
/* 前進消去 (forward elimination) */
for (i = 0; i < nm1; i++) b[i + 1] -= b[i] * al[i + 1];
/* 後退代入 (backward substitution) */
b[nm1] /= ad[nm1];
for (i = n - 2; i >= 0; i--) b[i] = (b[i] - au[i] * b[i + 1]) / ad[i];
}
/**********************************************************/
/* 初期条件を与える関数。複数登録して番号 nfunc で選択する。 */
double f(double x, int nfunc)
{
/* f(x)=max(x,1-x) */
if (nfunc == 1) {
if (x <= 0.5)
return x;
else
return 1.0 - x;
}
/* f(x)=1 */
else if (nfunc == 2)
return 1.0;
else if (nfunc == 3)
return sin(pi * x);
/* f(x)= 変な形をしたもの(by M.Sakaue) */
else if (nfunc == 4) {
if (x <= 0.1)
return 5 * x;
else if (x <= 0.3)
return -2 * x + 0.7;
else if (x <= 0.5)
return 4.5 * x - 1.25;
else if (x <= 0.7)
return -x + 1.5;
else if (x <= 0.9)
return x + 0.1;
else
return -10 * x + 10.0;
}
/* やはり非対称な形をしたもの(by M.Sakaue) */
else if (nfunc == 5) {
if (x <= 0.2)
return -x + 0.8;
else if (x <= 0.3)
return 4 * x - 0.2;
else if (x <= 0.4)
return -4 * x + 2.2;
else if (x <= 0.7)
return -x + 1.0;
else if (x <= 0.8)
return 4 * x - 2.6;
else
return -x + 1.5;
} else
return 0;
}
/**********************************************************/
/* 配列 u[] の内容(u[0],u[1],...,u[n])を一行 5 個ずつ表示する。 */
void print100(int N, double t, double *u)
{
int i;
printf("T= %12.4e\n", t);
for (i = 0; i < 5; i++)
printf(" I u(i) ");
printf("\n");
for (i = 0; i <= N; i++) {
printf("%3d%12.4e ", i, u[i]);
if (i % 5 == 4)
printf("\n");
}
printf("\n");
}
/**********************************************************/
/* ウィンドウに座標軸を表示する */
void axis(double xleft, double ybottom, double xright, double ytop)
{
g_sel_line(1);
g_move(xleft, 0.0); g_plot(xright, 0.0);
g_move(0.0, ybottom); g_plot(0.0, ytop);
g_sel_line(0);
}
/* パラメーターの値等をウィンドウに表示する */
void print_parameters(double xleft, double ybottom, double xright, double ytop,
int nfunc, double theta, int N,
double lambda, double tau, double Tmax, double t)
{
char message[80];
axis(xleft, ybottom, xright, ytop);
g_text(30.0, 30.0, "heat equation, u(0)=u(1)=0");
sprintf(message, "nfunc=%d, theta=%g, N=%d, lambda=%g, tau=%g, Tmax=%g",
nfunc, theta, N, lambda, tau, Tmax);
g_text(30.0, 60.0, message);
if (t != 0.0) {
sprintf(message, "t = %g", t);
g_text(30.0, 90.0, message);
}
}