この節のプログラムは、ピボット選択がないため、実用上問題を含んでいます。 しかし、ガウス・ジョルダン法のプログラムの学習には良いでしょう。皆さん が、学習ではなく実際に使うプログラムを組むときはピボット選択は必要不可 欠と考えてください。
さらに、このプログラムは行列が特異な場合でも、計算を続行しようとします。 その場合、ゼロで割ることが生じますので、実行時エラーが発生します。
for(ipv=1 ; ipv <= n ; ipv++){ 対角化の処理 }ここで、ipvは対角化する要素 の添え字を表します。 nは行列のサイズです。
数学では、対角成分を1にするために、その行を対角成分で割ります。しかし、 コンピューターのプログラムでは予め逆数を計算して、それを乗じます。コン ピューターは除算よりも乗算の方が得意なので効率が良くなります。非同次項 の演算は1回ですが、係数行列 は列毎なので回の演算が必 要になります。対角成分を1にする処理は、次のようになります。
inv_pivot = 1.0/a[ipv][ipv]; for(j=1 ; j <= n ; j++){ a[ipv][j] *= inv_pivot; } b[ipv] *= inv_pivot;
このように変形するのは簡単です。例えば、行を処理する場合を考えます。 行を、ピボットのある行を 倍もので引けば良いのです。
for(i=1 ; i<=n ; i++){ if(i != ipv){ temp = a[i][ipv]; for(j=1 ; j<=n ; j++){ a[i][j] -= temp*a[ipv][j]; } b[i] -= temp*b[ipv]; } }
これで対角化の処理はおしまい。
/* ========== ガウスジョルダン法の関数 =================*/ void gauss_jordan(int n, double a[][100], double b[]){ int ipv, i, j; double inv_pivot, temp; for(ipv=1 ; ipv <= n ; ipv++){ /* ---- 対角成分=1(ピボット行の処理) ---- */ inv_pivot = 1.0/a[ipv][ipv]; for(j=1 ; j <= n ; j++){ a[ipv][j] *= inv_pivot; } b[ipv] *= inv_pivot; /* ---- ピボット列=0(ピボット行以外の処理) ---- */ for(i=1 ; i<=n ; i++){ if(i != ipv){ temp = a[i][ipv]; for(j=1 ; j<=n ; j++){ a[i][j] -= temp*a[ipv][j]; } b[i] -= temp*b[ipv]; } } } }
ピボット選択、ここでは行の交換のみの部分選択を考えます。その処理は、
ここで、もし最大値がゼロの場合、行列は特異(行列式がゼロ)ということにな り、解は一意的に決まりません。その場合、関数の値としてゼロを返し、その ことをコールした関数に伝えるのが良いでしょう。
big=0.0; for(i=ipv ; i<=n ; i++){ if(fabs(a[i][ipv]) > big){ big = fabs(a[i][ipv]); pivot_row = i; } } if(big == 0.0){return(0);} row[ipv] = pivot_row;
このプログラムは、以下のことを行っています。
ただし、もともと最大の値が行にある場合は、行の入れ替えは行いませ ん。
if(ipv != pivot_row){ for(i=1 ; i<=n ; i++){ temp = a[ipv][i]; a[ipv][i] = a[pivot_row][i]; a[pivot_row][i] = temp; } temp = b[ipv]; b[ipv] = b[pivot_row]; b[pivot_row] = temp; }
/* ========= ガウスジョルダン法の関数====================== */ int gauss_jordan(int n, double a[][MAXN+10], double b[]){ int ipv, i, j; double inv_pivot, temp; double big; int pivot_row, row[MAXN+10]; for(ipv=1 ; ipv <= n ; ipv++){ /* ---- 最大値探索 ---------------------------- */ big=0.0; for(i=ipv ; i<=n ; i++){ if(fabs(a[i][ipv]) > big){ big = fabs(a[i][ipv]); pivot_row = i; } } if(big == 0.0){return(0);} row[ipv] = pivot_row; /* ---- 行の入れ替え -------------------------- */ if(ipv != pivot_row){ for(i=1 ; i<=n ; i++){ temp = a[ipv][i]; a[ipv][i] = a[pivot_row][i]; a[pivot_row][i] = temp; } temp = b[ipv]; b[ipv] = b[pivot_row]; b[pivot_row] = temp; } /* ---- 対角成分=1(ピボット行の処理) ---------- */ inv_pivot = 1.0/a[ipv][ipv]; for(j=1 ; j <= n ; j++){ a[ipv][j] *= inv_pivot; } b[ipv] *= inv_pivot; /* ---- ピボット列=0(ピボット行以外の処理) ---- */ for(i=1 ; i<=n ; i++){ if(i != ipv){ temp = a[i][ipv]; for(j=1 ; j<=n ; j++){ a[i][j] -= temp*a[ipv][j]; } b[i] -= temp*b[ipv]; } } } return(1); }
これを実現するためには、以下の2つのことをすればよいだけです。
for(i=1 ; i<=n ; i++){ for(j=1 ; j<=n ; j++){ if(i == j){ inv_a[i][j]=1.0; }else{ inv_a[i][j]=0.0; } } }
temp = a[ipv][i]; a[ipv][i] = a[pivot_row][i]; a[pivot_row][i] = temp; temp = inv_a[ipv][i]; /* -- これ追加 -- */ inv_a[ipv][i] = inv_a[pivot_row][i]; /* -- これ追加 -- */ inv_a[pivot_row][i] = temp; /* -- これ追加 -- */と書き換えます。
a[ipv][j] *= inv_pivot; inv_a[ipv][j] *= inv_pivot; /* -- これ追加 -- */と書き換えます。
a[i][j] -= temp*a[ipv][j]; inv_a[i][j] -= temp*inv_a[ipv][j]; /* -- これ追加 -- */と書き換えます。
/* ========= ガウスジョルダン法の関数====================== */ int gauss_jordan(int n, double a[][MAXN+10], double b[], double inv_a[][MAXN+10]){ int ipv, i, j; double inv_pivot, temp; double big; int pivot_row, row[MAXN+10]; /* ---- 単位行列作成 ---------------------------- */ for(i=1 ; i<=n ; i++){ for(j=1 ; j<=n ; j++){ if(i==j){ inv_a[i][j]=1.0; }else{ inv_a[i][j]=0.0; } } } for(ipv=1 ; ipv <= n ; ipv++){ /* ---- 最大値探索 ---------------------------- */ big=0.0; for(i=ipv ; i<=n ; i++){ if(fabs(a[i][ipv]) > big){ big = fabs(a[i][ipv]); pivot_row = i; } } if(big == 0.0){return(0);} row[ipv] = pivot_row; /* ---- 行の入れ替え -------------------------- */ if(ipv != pivot_row){ for(i=1 ; i<=n ; i++){ temp = a[ipv][i]; a[ipv][i] = a[pivot_row][i]; a[pivot_row][i] = temp; temp = inv_a[ipv][i]; inv_a[ipv][i] = inv_a[pivot_row][i]; inv_a[pivot_row][i] = temp; } temp = b[ipv]; b[ipv] = b[pivot_row]; b[pivot_row] = temp; } /* ---- 対角成分=1(ピボット行の処理) ---------- */ inv_pivot = 1.0/a[ipv][ipv]; for(j=1 ; j <= n ; j++){ a[ipv][j] *= inv_pivot; inv_a[ipv][j] *= inv_pivot; } b[ipv] *= inv_pivot; /* ---- ピボット列=0(ピボット行以外の処理) ---- */ for(i=1 ; i<=n ; i++){ if(i != ipv){ temp = a[i][ipv]; for(j=1 ; j<=n ; j++){ a[i][j] -= temp*a[ipv][j]; inv_a[i][j] -= temp*inv_a[ipv][j]; } b[i] -= temp*b[ipv]; } } } return(1); }
メモリーと合わせて、計算効率も重要でした。大規模な計算になると、計算が 終了するまで何日も費やす場合があります。そのような場合、プログラムの改 良により、速度が10%アップするとかなりのメリットがあります。
そこで、ここではメモリーの効率的な利用を考えましょう。
これを実現するのは、簡単です。次のようにプログラムを書けばよいのです。
/* ---- 対角成分=1(ピボット行の処理) ---------- */ inv_pivot = 1.0/a[ipv][ipv]; a[ipv][ipv]=1.0; /* --- この行を追加 --- */ for(j=1 ; j <= n ; j++){ a[ipv][j] *= inv_pivot; } b[ipv] *= inv_pivot; /* ---- ピボット列=0(ピボット行以外の処理) ---- */ for(i=1 ; i<=n ; i++){ if(i != ipv){ temp = a[i][ipv]; a[i][ipv]=0.0; /* --- この行を追加 --- */ for(j=1 ; j<=n ; j++){ a[i][j] -= temp*a[ipv][j]; } b[i] -= temp*b[ipv]; } }
このようにするとメモリーは、2.3.3節のプログラ ムに比べて半分ですみます。さらに、計算時間も半分になります。 2.3.3節では、計算結果が0やの場合も計算し ていたのですが、このプログラムではそれを省いています。
当然、 の逆行列ということは、乗算すると下のように単位行列になり ます。
(元の行列の行の行ベクトル)(逆行列の列の列ベクトル) |
このようなわけで、元の行列の行を入れ替えた場合、その逆行列は元の行列の 逆行列の列を入れ替えたものになります。従って、ピボット選択により係数行 列の行を入れ替えると、逆行列の列を入れ替える必要が生じます。実際にプロ グラムでは、以下のようにします。
/* ---- 列の入れ替え(逆行列) -------------------------- */ for(j=n ; j>=1 ; j--){ if(j != row[j]){ for(i=1 ; i<=n ; i++){ temp = a[i][j]; a[i][j]=a[i][row[j]]; a[i][row[j]]=temp; } } }
これ以上、改良するのは大変なので、ほとんど問題なく使える関数のプログラ ムをいかに示します。
/* ========= ガウスジョルダン法の関数====================== */ int gauss_jordan(int n, double a[][MAXN+10], double b[]){ int ipv, i, j; double inv_pivot, temp; double big; int pivot_row, row[MAXN+10]; for(ipv=1 ; ipv <= n ; ipv++){ /* ---- 最大値探索 ---------------------------- */ big=0.0; for(i=ipv ; i<=n ; i++){ if(fabs(a[i][ipv]) > big){ big = fabs(a[i][ipv]); pivot_row = i; } } if(big == 0.0){return(0);} row[ipv] = pivot_row; /* ---- 行の入れ替え -------------------------- */ if(ipv != pivot_row){ for(i=1 ; i<=n ; i++){ temp = a[ipv][i]; a[ipv][i] = a[pivot_row][i]; a[pivot_row][i] = temp; } temp = b[ipv]; b[ipv] = b[pivot_row]; b[pivot_row] = temp; } /* ---- 対角成分=1(ピボット行の処理) ---------- */ inv_pivot = 1.0/a[ipv][ipv]; a[ipv][ipv]=1.0; for(j=1 ; j <= n ; j++){ a[ipv][j] *= inv_pivot; } b[ipv] *= inv_pivot; /* ---- ピボット列=0(ピボット行以外の処理) ---- */ for(i=1 ; i<=n ; i++){ if(i != ipv){ temp = a[i][ipv]; a[i][ipv]=0.0; for(j=1 ; j<=n ; j++){ a[i][j] -= temp*a[ipv][j]; } b[i] -= temp*b[ipv]; } } } /* ---- 列の入れ替え(逆行列) -------------------------- */ for(j=n ; j>=1 ; j--){ if(j != row[j]){ for(i=1 ; i<=n ; i++){ temp = a[i][j]; a[i][j]=a[i][row[j]]; a[i][row[j]]=temp; } } } return(1); }