/* * poisson1d.c --- 1次元Poisson方程式 * * - u''(x)=f(x) (0<x<1) * u(0)=α * u'(1)=β * * を差分法で解くプログラム。cglsc コマンドでコンパイル&実行出来る。 * * u(x)=(x-1/3)^2 の場合、α=1/9, β=4/3 --- 2次関数なので厳密に解ける */ #include <stdio.h> #include <stdlib.h> #include <math.h> #include <glsc.h> #define MAXN (100); // 三重対角行列の Gauss の消去法による LU 分解 void trilu1(int, double *, double *, double *); // LU分解を用いて三項方程式を解く void trisol1(int, double *, double *, double *, double *); // Poisson方程式の右辺 double f(double x) { return - 2.0; } // 厳密解 double solution(double x) { return (x - 1.0 / 3) * (x - 1.0 / 3); } int main(void) { double alpha, beta; int i, n; double *x, *al, *ad, *au, *u; double h, h2; alpha = 1.0 / 9.0; beta = 4.0 / 3.0; printf("n="); scanf("%d", &n); x = malloc(sizeof(double) * (n + 1)); al = malloc(sizeof(double) * (n + 1)); ad = malloc(sizeof(double) * (n + 1)); au = malloc(sizeof(double) * (n + 1)); u = malloc(sizeof(double) * (n + 1)); if (ad == NULL || al == NULL || au == NULL || u == NULL) { fprintf(stderr, "メモリの割り当てに失敗しました。\n"); exit(1); } h = 1.0 / n; for (i = 0; i <= n; i++) x[i] = i * h; h2 = h * h; for (i = 1; i <= n; i++) { ad[i] = 2; au[i] = al[i] = -1; u[i] = h2 * f(i * h); } /* 第N成分は 1/2 倍する */ ad[n] = 1; u[n] *= 0.5; /* 非同次境界条件 */ u[1] += alpha; u[n] += beta * h; /* 連立1次方程式を解く */ trilu1(n, al, ad, au); trisol1(n, al, ad, au, u); /* グラフの準備 */ u[0] = alpha; g_init("POISSON1D", 170.0, 170.0); g_device(G_BOTH); g_def_scale(0, -0.2, 1.2, -0.2, 1.2, 10.0, 10.0, 150.0, 150.0); g_sel_scale(0); g_move(-0.2, 0.0); g_plot(1.2, 0.0); g_move(0.0, -0.2); g_plot(0.0, 1.2); /* 厳密解を描く */ g_move(x[0], solution(x[0])); for (i = 1; i <= n; i++) g_plot(x[i], solution(x[i])); printf("クリックして下さい\n"); g_sleep(-1.0); /* 差分解を赤く描く */ g_line_color(G_RED); g_move(x[0], u[0]); for (i = 1; i <= n; i++) g_plot(x[i], u[i]); printf("x=1 での厳密解 %f, 差分解 %f, 誤差 %e\n", solution(1.0), u[n], fabs(solution(1.0) - u[n])); g_sleep(-1.0); g_term(); return 0; } /* 3項方程式 (係数行列が三重対角である連立1次方程式のこと) Ax=b を解く * * 入力 * n: 未知数の個数 * al,ad,au: 連立1次方程式の係数行列 * (al: 対角線の下側 i.e. 下三角部分 (lower part) * ad: 対角線 i.e. 対角部分 (diagonal part) * au: 対角線の上側 i.e. 上三角部分 (upper part) * つまり * * ad[1] au[1] 0 .................. 0 * al[2] ad[2] au[2] 0 ............. 0 * 0 al[3] ad[3] au[3] 0 ......... 0 * .................... * al[n-1] ad[n-1] au[n-1] * 0 al[n] ad[n] * al[i] = A_{i,i-1}, ad[i] = A_{i,i}, au[i] = A_{i,i+1}, * al[1], au[n] は意味がない) * * b: 連立1次方程式の右辺の既知ベクトル * (添字は 1 から。i.e. b[1],b[2],...,b[n] にデータが入っている。) * 出力 * al,ad,au: 入力した係数行列を LU 分解したもの * b: 連立1次方程式の解 * 能書き * 一度 call すると係数行列を LU 分解したものが返されるので、 * 以後は同じ係数行列に関する連立1次方程式を解くために、 * 関数 trisol1() が使える。 * 注意 * Gauss の消去法を用いているが、ピボットの選択等はしていな * いので、ピボットの選択をしていないので、係数行列が正定値である * などの適切な条件がない場合は結果が保証できない。 */ void trid1(int n, double *al, double *ad, double *au, double *b) { trilu1(n,al,ad,au); trisol1(n,al,ad,au,b); } /* 三重対角行列の LU 分解 (pivoting なし) */ void trilu1(int n, double *al, double *ad, double *au) { int i; /* 前進消去 (forward elimination) */ for (i = 1; i < n; i++) { al[i + 1] /= ad[i]; ad[i + 1] -= au[i] * al[i + 1]; } } /* LU 分解済みの三重対角行列を係数に持つ3項方程式を解く */ void trisol1(int n, double *al, double *ad, double *au, double *b) { int i; /* 前進消去 (forward elimination) */ for (i = 1; i < n; i++) b[i + 1] -= b[i] * al[i + 1]; /* 後退代入 (backward substitution) */ b[n] /= ad[n]; for (i = n - 1; i >= 1; i--) b[i] = (b[i] - au[i] * b[i + 1]) / ad[i]; }