【科学技術計算講座ミニ】QR法で行列の固有値を求める②
前回、行列の固有値をQR法で求める方法について解説しました。
今回はPythonでQR法のプログラムを作成していきます。
目次
QR法のプログラム
早速ですが全体のプログラムを示しておきます。
import numpy as np# sign functiondef sign(x): if x >= 0: return 1 else: return -1# Hessenberg matrixdef hessenberg(a): n = a.shape[0] r = a.copy() u = np.zeros(n) for k in range(n-2): # |x| absx = 0 for i in range(k+1, n): absx += r[i][k]**2 absx = np.sqrt(absx) if absx == 0: continue # u=x-y u[k+1] = r[k+1][k] + sign(r[k+1][k]) * absx absu = u[k+1]**2 for i in range(k+2, n): u[i] = r[i][k] absu += u[i]**2 # H=I-2uu'/|u|^2 h = np.eye(n) for i in range(k+1, n): for j in range(k+1, n): h[i][j] -= 2 * u[i] * u[j] / absu # R=H'RH r = np.dot(h, r) r = np.dot(r, h) return r # QR decomposition for Hessenberg matrixdef qr_decomposition(a): n = a.shape[0] r = a.copy() q = np.eye(n) u = np.zeros(n) for k in range(n-1): # |x| absx = 0 for i in range(k, k+2): absx += r[i][k]**2 absx = np.sqrt(absx) if absx == 0: continue # u=x-y u[k] = r[k][k] + sign(r[k][k]) * absx u[k+1] = r[k+1][k] absu = u[k]**2 + u[k+1]**2 # H=I-2uu'/|u|^2 h = np.eye(n) for i in range(k, k+2): for j in range(k, k+2): h[i][j] -= 2 * u[i] * u[j] / absu r = np.dot(h, r) q = np.dot(q, h) return q, r # Wilkinson shiftdef wilkinson_shift(a): n = a.shape[0] a11 = a[n-2][n-2] a12 = a[n-2][n-1] a21 = a[n-1][n-2] a22 = a[n-1][n-1] c1 = a11 + a22 c2 = np.sqrt((a11 - a22)**2 + 4 * a12 * a21) mu1 = 0.5 * (c1 + c2) mu2 = 0.5 * (c1 - c2) dmu1 = np.abs(a22 - mu1) dmu2 = np.abs(a22 - mu2) if dmu1 <= dmu2: return mu1 else: return mu2 # input parameteriter_max = 1000eps = 1.0e-12a0 = np.array([[6., -3., 5.], [-1., 4., -5.], [-3., 3., -4.]]) n = a0.shape[0]eigen_value = np.zeros(n)h = hessenberg(a0)print("A =\n", a0, "\n")print("Hessenberg =\n", h, "\n")#QR methodfor k in range(n, 1, -1): # deflation a = np.zeros((k, k)) for i in range(k): for j in range(k): a[i][j] = h[i][j] #QR method with Wilkinson shift for iter in range(iter_max): mu = wilkinson_shift(a) for i in range(k): a[i][i] -= mu q, r = qr_decomposition(a) a = np.dot(r, q) for i in range(k): a[i][i] += mu if np.abs(a[k-1][k-2]) < eps: eigen_value[k-1] = a[k-1][k-1] if k == 2: eigen_value[0] = a[0][0] h = a.copy() break print("Eigenvalue =\n", eigen_value, "\n")プログラム全体のフローは次のようになっています。

以下プログラムの内容を説明していきますが、QR分解に関する部分は以下のページで詳しく解説しているので要点だけ示します。
ヘッセンベルグ行列
ヘッセンベルグ行列を計算する関数hessenbergを定義しています。
# Hessenberg matrixdef hessenberg(a):基本的にはQR分解のコードと同じです。ただし、対角要素の一つ下まで値が入るため行が一つずれていることに注意してください。最後にハウスホルダー行列を右と左から掛け合わせています。
QR分解
QR分解を行う関数qr_decompositionを定義しています。
# QR decomposition for Hessenberg matrixdef qr_decomposition(a):以前のQR分解のコードと違うのは、扱う行列がヘッセンベルグ行列であるため、行に対しては対角要素の一つ下の要素までしか演算していないところです。
Wilkinsonの移動法
原点移動法のWilkinsonシフトの関数wilkinson_shiftを定義します。
# Wilkinson shiftdef wilkinson_shift(a): n = a.shape[0] a11 = a[n-2][n-2] a12 = a[n-2][n-1] a21 = a[n-1][n-2] a22 = a[n-1][n-1] c1 = a11 + a22 c2 = np.sqrt((a11 - a22)**2 + 4 * a12 * a21) mu1 = 0.5 * (c1 + c2) mu2 = 0.5 * (c1 - c2) dmu1 = np.abs(a22 - mu1) dmu2 = np.abs(a22 - mu2) if dmu1 <= dmu2: return mu1 else: return mu2与えられた行列aの右下角の2×2の行列で固有値mu1、mu2を求めます。2次の行列なので2次方程式の解の公式から求めています。このうち右下角の要素a22に近い方の固有値を返しています。
パラメータの入力
計算に必要なパラメータを入力します。
# input parameteriter_max = 1000eps = 1.0e-12a0 = np.array([[6., -3., 5.], [-1., 4., -5.], [-3., 3., -4.]]) n = a0.shape[0]eigen_value = np.zeros(n)h = hessenberg(a0)print("A =\n", a0, "\n")print("Hessenberg =\n", h, "\n")iter_maxはQR法の繰り返し計算の最大繰り返し数。epsはQR法で要素がゼロになったかどうかの収束判定値。a0は固有値を求める行列。nは行列の次数。eigen_valueは算出された固有値を格納する配列です。
hはa0を変換したヘッセンベルグ行列です。a0とhはprintで出力しています。
QR法
ここからがQR法のメイン部分です。
#QR methodfor k in range(n, 1, -1): # deflation a = np.zeros((k, k)) for i in range(k): for j in range(k): a[i][j] = h[i][j]最終的に、固有値はN個見つかります。1回のステップで、行列の一番右下角に求める固有値が計算されます。固有値が1つ見つかるとデフレーションで行列の次数を減らします。最終的にN-1回目のループで2×2の行列となり、2つの対角成分がそれぞれ固有値なので、全て見つかったことになります。
最初のfor文はN-1回のループで、インデックスkはNから2までループされます。
デフレーションで次数kの行列aを作っています。
#QR method with Wilkinson shift for iter in range(iter_max): mu = wilkinson_shift(a) for i in range(k): a[i][i] -= muQR法の繰り返し部分です。先ほど定義したiter_maxまで最大でループされますが、この後出てくる収束判定により途中でループを抜けることになります。
まずmuにWilkinsonシフトで求めた固有値を入れます。そして、対角成分から引きます。
q, r = qr_decomposition(a) a = np.dot(r, q) for i in range(k): a[i][i] += muq、rにQR分解をしたQとRの行列を入れます。そして、それを逆にかけてaとします。最後に対角成分にmuを加えます。これで原点移動付きのQR法の計算になります。
if np.abs(a[k-1][k-2]) < eps: eigen_value[k-1] = a[k-1][k-1] if k == 2: eigen_value[0] = a[0][0] h = a.copy() break最後に収束判定をします。a[k-1][k-2]がepsより小さくゼロになったと判定されれば、QR法が収束したものとみなします。a[k-1][k-2]は行列の一番右下角の一つ左の要素です。今はヘッセンベルグ行列を考えているので、この要素がゼロになれば、最後の行の対角要素以外が全てゼロになったとみなすことができます。
収束すれば、eigen_valueに対角要素a[k-1][k-1](これが求める固有値)を格納します。k=2のときは2×2行列になったので、a[0][0]も格納してやります。
そして、aをhにコピーしてbreakでループを抜けます。
プログラムの実行結果
プログラムを実行すると、次のように出力されます。
A = [[ 6. -3. 5.] [-1. 4. -5.] [-3. 3. -4.]]Hessenberg = [[ 6.00000000e+00 -3.79473319e+00 4.42718872e+00] [ 3.16227766e+00 -3.80000000e+00 5.60000000e+00] [-5.55111512e-16 -2.40000000e+00 3.80000000e+00]]Eigenvalue = [3. 2. 1.]※このプログラムは固有値が複素数になる問題には対応していません。
まとめ
QR法による行列の固有値計算のプログラムをPythonで作成しました。今回は固有値のみを求めましたが、固有ベクトルは別途求める必要があります。固有ベクトルの求め方はまた別の機会にお話しします。
当サイトの以下のページでは行列の固有値を実際に計算することができますので試してみてください。











