ヤコビ反復法

ls_jacobi.eq1.gifが大きいと,ガウスの消去法では時間がかかりすぎる(ls_jacobi.eq2.gif).これに対して,ガウスの消去法のように直接解くのではなく,適当な初期値から出発して計算を反復することで解に近づけていく方法を反復法と呼ぶ.ここでは最も基本的な反復法であるヤコビ(Jacobi)反復法について述べる.

以下に示す3元連立方程式を考える.

ls_jacobi.eq3.gif

係数行列の対角成分ls_jacobi.eq4.gifならば,

ls_jacobi.eq5.gif

が導かれる. ls_jacobi.eq6.gifを初期値として与え,上式に基づきls_jacobi.eq7.gifと計算していく. 反復回数をls_jacobi.eq8.gifとし,ls_jacobi.eq8.gifを十分大きくするとls_jacobi.eq9.gifは解に収束する. n元連立1次方程式の場合は以下となる.

ls_jacobi.eq10.gif

ここで,ls_jacobi.eq11.gifである.

このようにして解を求める方法がヤコビ反復法である. 解が収束したかどうかはls_jacobi.eq9.gifls_jacobi.eq12.gifの値を比べて,その差が許容誤差内に収まったかどうかで判断する. 絶対誤差を用いる場合の収束条件は以下となる.

ls_jacobi.eq13.gif

また,相対誤差を用いた場合は,

ls_jacobi.eq14.gif

ここでls_jacobi.eq15.gifは許容誤差であり,ユーザが任意の値を設定する. 計算量は反復回数をls_jacobi.eq8.gifとするとls_jacobi.eq16.gifとなり, ls_jacobi.eq8.gifをなるべく小さくすることが計算量を減らすために重要となる.

また,反復法で問題となるのは収束するための条件である. そもそも収束しないのであれば反復を繰り返しても解が発散するだけである. 収束条件を上記の反復式から導出する. まず,反復式を以下のような形で表す.

ls_jacobi.eq17.gif

反復回数が十分大きい数ls_jacobi.eq18.gifであるとき,以下の式が成り立つ.

ls_jacobi.eq19.gif

この2つの式から以下が導かれる.

ls_jacobi.eq20.gif

ls_jacobi.eq21.gifls_jacobi.eq22.gifステップでの誤差, ls_jacobi.eq23.gifls_jacobi.eq8.gifステップでの誤差である. つまり,反復を繰り返すと1ステップごとに誤差がls_jacobi.eq24.gif倍になるということである. そのため,解が収束するためには以下の条件を満たす必要がある.

ls_jacobi.eq25.gif

反復式から係数行列の要素を使って表すと,

ls_jacobi.eq26.gif

この式より,係数行列ls_jacobi.eq27.gifの各行において対角要素の絶対値が非対角要素の絶対値の和より大きい場合に収束する.このような条件を対角優位(diagonal dominant)と呼ぶ.

ヤコビ反復法の実装

ヤコビ反復法をC++で実装した例を以下に示す.

/*!
 * ヤコビ反復法(Jacobi iterative method)
 *  - 解が収束するのは
 *      ・対角有利(diagonal dominant, 対角要素の絶対値>その行の他の要素の絶対値の和)
 *      ・係数行列が対称(symmetric)かつ正定(positive definite)
 *    のどちらかの場合
 * @param[inout] A n×nの係数行列とn×1の定数項(b)を併せたn×(n+1)の拡大行列.n+1列目に解が入る.
 * @param[in] n n元連立一次方程式
 * @param[inout] max_iter 最大反復数(反復終了後,実際の反復数を返す)
 * @param[inout] eps 許容誤差(反復終了後,実際の誤差を返す) 
 * @return 1:成功,0:失敗
 */
int JacobiIteration(vector< vector<double> > &A, int n, int &max_iter, double &eps)
{
	vector<double> x(n, 0.0);	// 初期値はすべて0とする
	vector<double> y(n, 0.0);

	double e = 0.0;
	int k;
	for(k = 0; k < max_iter; ++k){
		// 現在の値を代入して,次の解候補を計算
		for(int i = 0; i < n; ++i){
			y[i] = A[i][n];
			for(int j = 0; j < n; ++j){
				y[i] -= (j != i ? A[i][j]*x[j] : 0.0);
			}
			y[i] /= A[i][i];
		}

		// 収束判定
		e = 0.0;
		for(int i = 0; i < n; ++i){
			e += fabs(y[i]-x[i]);	// 絶対誤差の場合
			//e += fabs((y[i]-x[i])/y[i]);	// 相対誤差の場合
		}
		if(e <= eps){
			break;
		}

		swap(x, y);
	}

	max_iter = k;
	eps = e;

	for(int i = 0; i < n; ++i){
		A[i][n] = y[i];
	}

	return 1;
}

ガウス・ザイデル反復法

ヤコビ反復法ではls_gauss_seidel.eq1.gifステップでのls_gauss_seidel.eq2.gifだけを使ってls_gauss_seidel.eq3.gifでの値を求めていた. しかし,ls_gauss_seidel.eq4.gifと順番に計算した場合, ls_gauss_seidel.eq5.gifを求めるときにはls_gauss_seidel.eq6.gifが, ls_gauss_seidel.eq7.gifを求めるときにはls_gauss_seidel.eq8.gifがすでに計算されている. これら最新の値を使うのがガウス・ザイデル反復法(Gauss-Seidel iteration method)である.

たとえば,3元連立1次方程式の場合,反復式は以下となる.

ls_gauss_seidel.eq9.gif

収束判定条件はヤコビ法と同じであり, 収束するための係数行列の条件も同じである. ただし,収束までの反復回数はヤコビ法よりも少なくすむ.

ガウス・ザイデル反復法の実装

ヤコビ反復法ではls_gauss_seidel.eq10.gifls_gauss_seidel.eq11.gifの2つを格納するために2つの配列を用意したが, ガウス・ザイデル法では常に最新の値を用いるため配列は1つでよい. ガウス・ザイデル反復法をC++で実装した例を以下に示す.

/*!
 * ガウス-ザイデル反復法(Gauss Seidel iterative method)
 *  - 解が収束するのは
 *      ・対角有利(diagonal dominant, 対角要素の絶対値>その行の他の要素の絶対値の和)
 *      ・係数行列が対称(symmetric)かつ正定(positive definite)
 *      ・Σ_j |a_ij/a_ii| < 1 (i = 1〜n, j != i) 
 * @param[inout] A n×nの係数行列とn×1の定数項(b)を併せたn×(n+1)の行列.n+1列目に解が入る.
 * @param[in] n n元連立一次方程式
 * @param[inout] max_iter 最大反復数(反復終了後,実際の反復数を返す)
 * @param[inout] eps 許容誤差(反復終了後,実際の誤差を返す) 
 * @return 1:成功,0:失敗
 */
int GaussSeidel(vector< vector<double> > &A, int n, int &max_iter, double &eps)
{
	vector<double> x(n, 0.0);	// 初期値はすべて0とする
	double tmp;

	double e = 0.0;
	int k;
	for(k = 0; k < max_iter; ++k){
		// 現在の値を代入して,次の解候補を計算
		e = 0.0;
		for(int i = 0; i < n; ++i){
			tmp = x[i];
			x[i] = A[i][n];
			for(int j = 0; j < n; ++j){
				x[i] -= (j != i ? A[i][j]*x[j] : 0.0);
			}
			x[i] /= A[i][i];

			e += fabs(tmp-x[i]);	// 絶対誤差の場合
			//e += fabs((tmp-x[i])/tmp);	// 相対誤差の場合
		}

		// 収束判定
		if(e <= eps){
			break;
		}
	}

	max_iter = k;
	eps = e;

	for(int i = 0; i < n; ++i){
		A[i][n] = x[i];
	}

	return 1;
}

SOR法

ヤコビ法やガウス・ザイデル法において各ステップで計算されたls_sor.eq1.gifls_sor.eq2.gifを用いて,さらに収束を加速させる方法が逐次加速緩和法(SOR法:Successive Over-Relaxation method)である.

各ステップにおいて補正量ls_sor.eq3.gifを用い,次の式で次ステップの値ls_sor.eq4.gifを計算する.

ls_sor.eq5.gif

ここで,ls_sor.eq6.gifは加速係数であり,通常,ls_sor.eq7.gifである. SOR法を使うとls_sor.eq6.gifの値によっては解の収束を速めることができる. ただし,問題にも依存するため最適な加速係数の算出が難しい.

SOR法の実装

SOR法をC++で実装した例を以下に示す.

/*!
 * 逐次加速緩和法(SOR法:Successive Over-Relaxation)
 *  - ガウスザイデル反復法に加速係数をかけたもの
 * @param[inout] A n×nの係数行列とn×1の定数項(b)を併せたn×(n+1)の行列.n+1列目に解が入る.
 * @param[in] n n元連立一次方程式
 * @param[in] w 加速緩和乗数 ([0,2])
 * @param[inout] max_iter 最大反復数(反復終了後,実際の反復数を返す)
 * @param[inout] eps 許容誤差(反復終了後,実際の誤差を返す) 
 * @return 1:成功,0:失敗
 */
int SOR(vector< vector<double> > &A, int n, double w, int &max_iter, double &eps)
{
	vector<double> x(n, 0.0);	// 初期値はすべて0とする
	double tmp;

	double e = 0.0;
	int k;
	for(k = 0; k < max_iter; ++k){
		// 現在の値を代入して,次の解候補を計算
		e = 0.0;
		for(int i = 0; i < n; ++i){
			tmp = x[i];
			x[i] = A[i][n];
			for(int j = 0; j < n; ++j){
				x[i] -= (j != i ? A[i][j]*x[j] : 0.0);
			}
			x[i] /= A[i][i];

			x[i] = tmp+w*(x[i]-tmp);

			e += fabs(tmp-x[i]);	// 絶対誤差の場合
			//e += fabs((tmp-x[i])/tmp);	// 相対誤差の場合
		}

		// 収束判定
		if(e <= eps){
			break;
		}
	}

	max_iter = k;
	eps = e;

	for(int i = 0; i < n; ++i){
		A[i][n] = x[i];
	}

	return 1;
}

参考文献

  • 佐藤次男, 中村理一郎, “よくわかる数値計算 アルゴリズムと誤差解析の実際”, 日刊工業新聞社, 2001.
  • 川上一郎, “数値計算の基礎”, http://www7.ocn.ne.jp/~kawa1/

トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2024-03-08 (金) 18:06:11