数値計算法

08数値計算法
2008鳥取大学工学部知能情報工学科
田中美栄子
May 21, 2008
ガウスの消去法
• 連立一次方程式を解く
①式 a11 x1  a12 x2  a13 x3  ... a1n xn  b1
②式 a21 x1  a22 x2  a23 x3  ... a2 n xn  b2
...
an1 x1  an 2 x2  an 3 x3  ... ann xn  bn
a11
①式  新①式 x1  a12 ' x 2  a13' x 3  ...  a1n ' x n  b1'
②式ー①式×a12
②式  新②式 0  a 22' x 2  a 23' x 3  ...  a 2n ' x n  b2 '
③式  新③式 0  a 32 ' x 2  a 33' x 3  ...  a 3n ' x n  b3 '
ガウスの消去法
• 次は右下の(n-1)×(n)行列に注目
①式 a11 x1  a12 x2  a13 x3  ... a1n xn  b1
②式 a21 x1  a22 x2  a23 x3  ... a2 n xn  b2
...
an1 x1  an 2 x2  an 3 x3  ... ann xn  bn
x 2  a 23' ' x 3  ...  a 2n " x n  b1"
②式ー①式×a12
a 23" x 3  ...  a 2n " x n  b2 "
ガウスの消去法(4)
• 更新後、元と等価な次の連立方程式ができる
• ②式以下に注目すると(n-1)次の連立方程式
• この最初の式②をその第一係数で割り、次の式の第一係数
を掛けて次の式から引く、という操作を繰り返すと元と等価な
右上3角連立方程式ができる
①式 x1  a12 x2  a13 x3  ... a1n xn  b1
②式 x2  a23 x3  ... a2 n xn  b2
a33 x3  ... a3 n xn  b3
...
n式 • これを下から順に解いてゆく
xn  bn
ガウスの消去法(5)
最初から整理。係数だけ書くと次の操作となる。
a 11
a 21
a 12
a 22
a 13 . a 1n
a 23 . a 2 n
b1
b2
1 a 12
0 a 22
a 13
a 23
. a 1n
. a 2n
b1
b2
a 31
.
a 32
.
a 33 . a 3n
. . .
b3
.
0 a 32
0 .
a 33
.
. a 3n
. .
b3
.
a n1 a n 2
a n 3 . a nn
bn
0 a n2
a n 3 . a nn
bn
第k回目の操作は第k行目を a kk で割って、j=k,..,n
列目
( k 1)
: a kj
( k 1)
: b k
a kj
bk
(k)
(k)
/ a kk
/ a kk
(k)
(k)
ガウスの消去法(6)
• K+1行目以下は、 i=k+1,…,n行目について、
j=k,k+1,…,n列をそれぞれ a i ,k 倍して引くと
a
( k 1)
ij
: a
( k 1)
: bi
bi
(k)
ij
(k)
 a ik a kj
(k)
 a ik b k
(k)
( k 1)
( k 1)
ガウスの消去法(7)
更新k-1回目で係数は
( k 1)
( k 1)
( k 1)
( k 1)
( k 1)
a k ,k
a k ,k 1
a k ,k  2
. a k ,n
bk
( k 1)
( k 1)
( k 1)
( k 1)
( k 1)
a k 1,k
a k 1,k 1
a k 1,k  2
. a k 1,n
b k 1
a k  2,k
( k 1)
a k  2,k 1
.
a n ,k
1
0
( k 1)
a k  2,k  2
.
( k 1)
( k 1)
(k)
. a k  2,n
.
a n ,k 1
a k ,k 1
1
( k 1)
a n ,k  2
(k)
.
( k 1)
a k ,k  2
(k)
a k 1,k  2
bk2
.
. a n ,n
.
.
( k 1)
.
( k 1)
(k)
a k ,n
(k)
a k 1,n
0
0
1
. a k  2,n
.
.
.
.
.
0
0
0
.
a n ,n
( k 1)
(k)
bn
( k 1)
(k)
bk
(k)
b k 1
bk2
(k)
.
(k)
bn
(k)
ガウスの消去法(8)
• 後退代入で解を順に求める
x n  bn
(n)
/ a n ,n
(n)
• あとはk=n-1,n-2,…,1の順に
x k  bk
(n)

n
a
j k 1
(n)
k, j
xj
•
/** Gauss elimination :written by Mieko Tanaka May31, 2006 */
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
#include <stdio.h>
//#define N 4
//#define N 2
#define N 8
void main(){
double a[N][N+1]={{4.0, 2.0, 1.0, 0.0, 4.0,0.0, 2.0,0.0,3.0},
{1.0,6.0,0.0, 3.0, -1.0, 0.0, -3.0, 2.0, 0.0},
{1.0, -1.0, 2.0, -3.0, 4.0, 5.0, 0.0, 0.0, 3.0},
{2.0, 6.0, 0.0, 3.0,-1.0, 4.0, 0.0, 2.0,3.0},
{1.0, -1.0, 4.0, -3.0, 4.0, 5.0, 3.0, 0.0, 3.0},
{3.0, 6.0, 0.0, 3.0,-1.0, 4.0, 0.0, 2.0,3.0},
{-1.0, -1.0, -4.0, -3.0, 1.0, -2.0, 3.0, 0.0, 3.0},
{1.0, -1.0, -2.0, -3.0, 4.0, 1.0, 0.0, 7.0, 5.0},
};
// double a[N][N+1]={{1.0, 2.0, 3.0},{4.0,1.0,6.0}};
/*
double a[N][N+1]={{1.0,1.0,-3.0,-4.0,-1.0},
{2.0,1.0,5.0,1.0,5.0},
{3.0,6.0,-2.0,1.0,8.0},
{2.0,2.0,2.0,-3.0,2.0}};
*/
int i,j,k, count=0;
double x[N];
double p,q;
//pivot: arrange rows of matrix a to make diagonal nonzero
//forward elimination(always consider small matrix beginning a[k][k]
for(k=0;k<N;k++){
//for all k=0,1…,N-1
p=a[k][k];
//p memorizes a[k][k]
a[k][k]=1;
//Diagonal elements are set to 1
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
for(j=k+1;j<N+1;j++) {a[k][j]/=p;count++;} //Divide right elements (j=k+1,…,N)by p
for(i=k+1;i<N;i++) {
//for every i-th row (i=k+1,…
q=a[i][k]; //q memorizes leftmost element(k-th column)
for(j=k+1;j<N+1;j++) {a[i][j]-=q*a[k][j];count++;} //Subtract k-row times q
a[i][k]=0;
//k-th column below a[k][k]=0
}
}
//backward iteration to compute x[i]
for(i=N-1;i>=0;i--){
x[i]=a[i][N];count++;
for(j=N-1;j>i;j--) {
x[i]-=a[i][j]*x[j];count++;
}
printf("x[%d]=%f\n",i,x[i]);
printf("count=%d\n",count);
}
}
ガウスの消去法の問題点
• まず第一式の第一係数 a11 が0だったり、大変
小さいときは困る。もし a11  1020 のとき a11
で割ると、割らない係数が割った係数より小さ
すぎてアンダーフローしてしまう。ずっと先で
aがゼロになってもまた同じことが起こる。
kk
• ピボット選択(pivot):係数行列の第k列の第
k行目より下の中で絶対値最大のものを探し
それが第m行目にあればk行とm行を入れ替
える
正方行列aのLU分解をCrout法
を用いて行う
• Lのk列目とUのk行目を計算してすぐ、元のa行
列に上書きして行くようにすると、入力も出力
もaだけで済むのでプログラムが短くなる。
•
• #include <stdio.h>
• #include <math.h>
• #define N 3
• void crout(double a[N][N]);
•
• int main(void){
•
double a[N][N]={{2,4,6},{3,8,7},{5,7,21}};
行列の3角分解
• Ax=bを解くとき、A=LUと分解して、
Ax= LUx = Ly= b
• つまり
Ly= b を解いてから
Ux =y を解く
• どちらも三角行列だから解きやすい
3角分解の方法
• 例
2 4 6   11
 3 8 7   

  21
5 7 21  31
2   11
3   21
5   31
0
 22
 32
0  1 u12


0  * 0 1
 33  0 0
4   11u12
8   21u12   22
7   31u12   32
u13 

u23 
1 
6  11u13
7   21u13   22 u23
21   31u13   32u23
よって
11  2,  21  3,  31  5, u12  2, u13  3
3角分解の方法
• 例
2 4 6   11 0 0  u11 u12 u13 
 3 8 7    



0 * 0 u 22 u 23
21
22

 
 

5 7 21  31  32  33   0 0 u 33 
2  11u11
4  11u12
6  11u13
3   21u11 8   21u12   22 u 22 7   21u13   22 u23
5   31u11 7   31u12   32 u22 21   31u13   32u23
u11  u22  u33  1
11  2,  21  3,  31  5, u12  2, u13  3
12変数に対して9方程式なので3つは任意に取れる。
とすると
3角分解の方法
• 以下  22  (8   21u12 ) / u22  (8  3  2) / 1  2
 32  (7   31u12 ) / u22  (7  5  2) / 1  3
u23  (7   21u13 ) /  22  (7  3  3) / 2  1
u 23  (21   31u13   32 u 23 ) / u 33
 (21  5  3  ( 3)  ( 1)) / 1  3
 2 0 0
L   3 2 0


5  3 3
1 2 3 
U  0 1  1


0 0 1 
これを一般化すると
• Crout法(i=1,…,n, j=2,…,n)
k=2,3,…,n に対し
k 1
 kk  a kk    km u mk
m 1
k 1
 ik  a ik    imu mk
11  a11
u kj  (a kj    km u mj ) /  kk
m1
なのでこれを使って
u1 j  a1 j / 11 が求められた
m 1
k 1
u kk  1
 i1  a i1
• 正方行列aのLU分解をCrout法を用いて行
うとき、Lのk列目とUのk行目を計算し
てすぐ、元のa行列に上書きして行くよう
にすると、入力も出力もaだけで済むので
プログラムが短くなる。
•
#include <stdio.h>
• #include <math.h>
• #define N 3
• void crout(double a[N][N]);
•
• int main(void){
•
double a[N][N]={{2,4,6},{3,8,7},{5,7,21}};
•
crout(a);
•
return 0;
• }
•
• void crout(double a[N][N]){
•
int i, j, k;
•
for(k=0;k<N;k++){
•
• /* a[i][k]=L[i][k] for (k<=i) */
• for(i=k;i<N;i++){
•
for(j=0;j<k;j++) a[i][k]-=a[i][j] * a[j][k];
•
}
• /* a[k][j]=U[k][j] for (k<j) */
•
•
for(j=k+1;j<N;j++)
•
for(i=0;i<k;i++) a[k][j] -= a[k][i] * a[i][j];
•
for(j=k+1;j<N;j++) a[k][j] /= a[k][k];
•
•
printf("\n");
•
for(i=0; i<N; i++){
•
for(j=0; j<N; j++) printf("%f ", a[i][j]);
•
printf("\n");
•
}
•
}
• }
•
• <問題> 出力を表示せよ。(k=0の時は
入力したa行列のままであるが、その後は
k列目とk行目が順に書き換わり、最後
はLとUを出力している事に注意。)