第 5 章 Gauss の消去法と LU 分解 23
5.4 Gauss の消去法と LU 分解の関係
5.4.5 LU 分解のプログラムと実験
素朴なプログラム
lu-ver1.c
/*
* lu-ver1.c
*/
#include <matrix.h>
#include "lu-ver1.h"
void lu(int n, matrix a, matrix L, matrix u) {
int i, j, k;
double q;
for (i = 1; i <= n; i++) { for (j = 1; j <= n; j++) {
u[i][j] = a[i][j];
L[i][j] = 0.0;
}
L[i][i] = 1.0;
}
for (k = 1; k < n; k++)
for (i = k + 1; i <= n; i++) { q = u[i][k] / u[k][k];
/* 次の j を 1 からとするのは、定義式通りだが、
実は j を k から取っても OK (k+1 とは出来ない!) */
#ifdef ORIGINAL
for (j = 1; j <= n; j++)
#else
for (j = k; j <= n; j++)
#endif
u[i][j] = u[i][j] - q * u[k][j];
L[i][k] = q;
} }
/* 行列 A の LU 分解を用いて A x = b を解く */
void solve(int n, matrix L, matrix U, vector b, vector x) {
/* 以下の計算手順はプリント 4.3.4 を見よ */
int i, j;
double sum;
vector y = new_vector(n + 1);
/* 前進消去 */
for (i = 1; i <= n; i++) { sum = b[i];
for (j = 1; j < i; j++) sum -= L[i][j] * y[j];
y[i] = sum / L[i][i];
}
/* 後退代入 */
for (i = n; i >= 1; i--) { sum = y[i];
for (j = i + 1; j <= n; j++) sum -= U[i][j] * x[j];
x[i] = sum / U[i][i];
}
/* 不要になったメモリーを再利用可能なように解放する */
free_vector(y);
}
このようにして
A = LU
とLU
分解したとき、その結果は2
つの行列L, U
であるが、• L
の対角線とその上部にある成分は覚える必要がない(
対角線上の成分は1,
対角線より上に あるものは0)
• U
の対角線の下部にある成分は覚える必要がない(すべて 0
である)から、実際には自由度は
n
2 個しかない(
言い換えると、本当に記憶していなければならないのは、L
の対角線の下側と、U
の対角成分と対角線の上側の成分である)
。それを
u
11u
12u
13· · · u
1nq
2(1)u
22u
23· · · u
2nq
3(1)q
(2)3u
33· · · u
3n.. . .. . . .. . .. .. . q
n(1)q
(2)n· · · q
n(n−1)u
nn
のように一つの行列に詰め込んで記憶することがよく行われる。
matrix a
の内容を保存しなくて良いならば、次のようにして、L, U
のうちの記憶すべき内容をa
に上書きするコードが作れる。(j についてのループを
k+1
から始めているのがミソである。) 詰め込みをしたプログラムlu-ver2.c
/*
* lu-ver2.c
*/
#include <matrix.h>
#include "lu-ver2.h"
void lu(int n, matrix a) {
int i, j, k;
double q;
for (k = 1; k < n; k++)
for (i = k + 1; i <= n; i++) { q = a[i][k] / a[k][k];
for (j = k + 1; j <= n; j++)
a[i][j] = a[i][j] - q * a[k][j];
a[i][k] = q;
} }
/* 行列 A の LU 分解を用いて A x = b を解く */
void solve(int n, matrix a, vector b) {
/* 以下の計算手順はプリント 4.3.4 を見よ */
int i, j;
/* 前進消去 */
for (i = 1; i <= n; i++) for (j = 1; j < i; j++)
b[i] -= a[i][j] * b[j];
/* 注意: L_{i,i}=1 なので割り算は必要ない。 */
/* 後退代入 */
for (i = n; i >= 1; i--) { for (j = i + 1; j <= n; j++)
b[i] -= a[i][j] * b[j];
b[i] /= a[i][i];
}
}
問 プログラム
lu-ver2.c
に採用されたアルゴリズムの計算量を求めよ。実験
(lu-ver1.c) util.c
/*
* util.c
*/
#include <stdio.h>
#include <matrix.h>
#include "util.h"
/* n 次正方行列 A を表示する */
void print_matrix(int n, matrix A) {
int i, j;
for (i = 1; i <= n; i++) { for (j = 1; j <= n; j++)
printf("%10g ", A[i][j]);
printf("\n");
} }
/* n 次元ベクトル x を表示する */
void print_vector(int n, vector x) {
int i;
for (i = 1; i <= n; i++) printf("%10g ", x[i]);
printf("\n");
}
/* n 次正方行列 A, B の積を計算し、AB に代入する */
void mul_matrix(int n, matrix AB, matrix A, matrix B) {
/* 各自 */
int i, j, k;
double sum;
for (i = 1; i <= n; i++) for (j = 1; j <= n; j++) {
sum = 0.0;
for (k = 1; k <= n; k++) sum += A[i][k] * B[k][j];
AB[i][j] = sum;
} }
test-lu-ver1.c
/*
* test-lu-ver1.c --- 行列の LU 分解
* gcc -I/usr/local/include -c util.c
* gcc -I/usr/local/include -c lu-ver1.c
* ccmg test-lu-ver1.c util.o lu-ver1.o
*/
#include <stdio.h>
#include <matrix.h>
#include "util.h"
#include "lu-ver1.h"
int main() {
int n;
matrix a, L, u, Lu;
vector b, x;
n = 3;
/* n 次正方行列 a, L, u, Lu を準備 */
a = new_matrix(n + 1, n + 1);
L = new_matrix(n + 1, n + 1);
u = new_matrix(n + 1, n + 1);
Lu = new_matrix(n + 1, n + 1);
/* n 次元ベクトル b, x を準備 */
b = new_vector(n + 1);
x = new_vector(n + 1);
/* 行列 a に値を設定 (プリント p.27 を見よ) */
a[1][1] = 2; a[1][2] = 3; a[1][3] = -1;
a[2][1] = 4; a[2][2] = 4; a[2][3] = -3;
a[3][1] = -2; a[3][2] = 3; a[3][3] = -1;
/* LU 分解する */
lu(n, a, L, u);
/* a, L, u の内容を表示する */
printf("A=\n"); print_matrix(n, a);
printf("L=\n"); print_matrix(n, L);
printf("U=\n"); print_matrix(n, u);
/* 確認用に L と u の積を計算して表示する */
mul_matrix(n, Lu, L, u);
printf("L U=\n"); print_matrix(n, Lu);
/* 連立1次方程式 (p.27) を解く */
b[1] = 5; b[2] = 3; b[3] = 1;
solve(n, L, u, b, x);
printf("x=\n"); print_vector(n, x);
return 0;
}
コンパイル、実行
oyabun% gcc -I/usr/local/include -c util.c oyabun% gcc -I/usr/local/include -c lu-ver1.c oyabun% ccmg test-lu-ver1.c util.o lu-ver1.o oyabun% ./test-lu-ver1
A=
2 3 -1
4 4 -3
-2 3 -1
L=
1 0 0
2 1 0
-1 -3 1
U=
2 3 -1
0 -2 -1
0 0 -5
L U=
2 3 -1
4 4 -3
-2 3 -1
x=
1 2 3
oyabun%
確かに
LU = A
となっていて、解も求まっていることが分かる。実験
(lu-ver2.c) test-lu-ver2.c
/*
* test-lu-ver2.c --- 行列の LU 分解
* gcc -I/usr/local/include -c util.c
* gcc -I/usr/local/include -c lu-ver2.c
* ccmg test-lu-ver2.c util.o lu-ver2.o
*/
#include <stdio.h>
#include <matrix.h>
#include "util.h"
#include "lu-ver2.h"
int main() {
int n;
matrix a;
vector b;
n = 3;
/* n 次正方行列 a を準備 */
a = new_matrix(n + 1, n + 1);
/* n 次元ベクトル b, x を準備 */
b = new_vector(n + 1);
/* 行列 a に値を設定 (プリント p.27 を見よ) */
a[1][1] = 2; a[1][2] = 3; a[1][3] = -1;
a[2][1] = 4; a[2][2] = 4; a[2][3] = -3;
a[3][1] = -2; a[3][2] = 3; a[3][3] = -1;
/* a の内容を表示する */
printf("A=\n"); print_matrix(n, a);
/* b に値を設定して、表示する */
b[1] = 5; b[2] = 3; b[3] = 1;
printf("b=\n"); print_vector(n, b);
/* LU 分解する */
lu(n, a);
/* a の内容を表示する */
printf("LU 分解後の A=\n"); print_matrix(n, a);
/* 連立1次方程式 (p.27) を解く */
solve(n, a, b);
printf("x=\n"); print_vector(n, b);
return 0;
}
コンパイル、実行
oyabun% gcc -I/usr/local/include -c util.c oyabun% gcc -I/usr/local/include -c lu-ver2.c oyabun% ccmg test-lu-ver2.c util.o lu-ver2.o oyabun% ./test-lui--ver2
A=
2 3 -1
4 4 -3
-2 3 -1
b=
5 3 1
LU 分解後の A=
2 3 -1
2 -2 -1
-1 -3 -5
x=
1 2 3
oyabun%