実対称行列の固有ベクトル計算

 ヤコビ法による固有値計算手順は

		A1 = B1AB1-1
		A2 = B2A1B2-1
		A3 = B3A2B3-1

のようにして、非対角要素がほぼ0になるまで順次A1、A2、A3、・・・を求めるものである。m回の繰
り返しで、非対角要素がほぼ0になったとすると、Am は非対角要素が0であり、対角要素それぞれが
固有値を表す行列となる。また

	Am = Bm ・・・ B2B1AB1-1B2-1 ・・・ Bm-1 = (Bm ・・・ B2B1)A(Bm ・・・ B2B1)-1

であるので

	B = (Bm ・・・B2B1)-1 = B1-1B2-1・・・Bm-1 = B1TB2T ・・・ BmT = (Bm ・・・ B2B1)T

とすれば、Am=B-1AB、すなわち、BAm=AB となる。Am の第i対角要素をλi、B の第i列を bi とすれば

		λibi = Abi

となり、bi は固有値 λi に対応した固有ベクトルとなる。

 したがって、ヤコビ法の手順に赤字で示す操作を追加すれば、固有値と共に固有ベクトルも求められ
ることになる。

【手順0】

 Bを単位行列とする。

【手順1】行列Aの非対角要素で絶対値が最大な要素を見つける。それがA(i, j)=A(j, i)であったもの
  とし、その値が許容される値よりも小さいときは反復計算を終了し手順5へ進み、さもないときは
  手順2へ進む。ただし、A(i, j)は行列Aの上からi行目、左からj列目の要素を意味する。許容され
  る値としては、ほとんど0とみなすことができる値(例えば10-10)を設定する。

【手順2】回転角θを次式により求める。

		

【手順3】単位行列で、(i, i)要素と(j, j)要素をcosθ、(i, j)要素をsinθ、(j, i)要素を-sinθに
  置き換えた回転行列Jを考える。回転行列 J は J-1 = JT という性質を持っている。

【手順4】相似変換JAJ-1 = JAJT と BJT = BJ-1 を求め、その結果をそれぞれ新たなAとBとして手順1
  へ戻る。

【手順5】非対角要素で絶対値が最大なものの値が非常に小さいので、得られた行列は対角行列とみな
  すことができ、各対角要素が固有値であり、Bの各列が固有ベクトルとなる。


 固有値および固有ベクトルを計算するためのEXCELマクロは次のようになる。赤字部分は、固有ベク トルを計算するために固有値計算マクロに追加される部分である。ただし、上記説明でBとした行列は、 配列Wとしてあるので、注意して下さい。

    n = ActiveCell
    ReDim A(n, n), B(n, n), C(n, n), W(n, n), D(n, n)
    ActiveCell.Offset(1, 0).Range("A1").Select
    For i = 1 To n
        For j = 1 To n
            A(i, j) = ActiveCell
            ActiveCell.Offset(0, 1).Range("A1").Select
        Next j
        ActiveCell.Offset(1, -n).Range("A1").Select
    Next i
    For i = 1 To n
        For j = 1 To n
            W(i, j) = 0
        Next j
        W(i, i) = 1
    Next i
'
abc:
    z = -1
    For i = 1 To n - 1
        For j = i + 1 To n
            If Abs(A(i, j)) > z Then
                z = Abs(A(i, j)): ii = i: jj = j
            End If
        Next j
    Next i
    If z < 0.0000000001 Then GoTo xyz
    AA = A(ii, ii) - A(jj, jj)
    If Abs(AA) < 0.0000000001 Then
        t = 3.1415926538979 / 4
    Else
        t = Atn(2 * A(ii, jj) / AA) / 2
    End If
    For i = 1 To n
        For j = 1 To n
            B(i, j) = 0
        Next j
        B(i, i) = 1
    Next i
    B(ii, ii) = Cos(t): B(jj, jj) = Cos(t)
    B(ii, jj) = Sin(t): B(jj, ii) = -Sin(t)
    For i = 1 To n: For j = 1 To n
        s = 0
        For p = 1 To n: For q = 1 To n
            s = s + B(i, p) * A(p, q) * B(j, q)
        Next q, p
        C(i, j) = s
    Next j, i
    For i = 1 To n: For j = 1 To n
        s = 0
        For p = 1 To n
            s = s + W(i, p) * B(j, p)
        Next p
        D(i, j) = s
    Next j, i
    For i = 1 To n: For j = 1 To n
        A(i, j) = C(i, j)
        W(i, j) = D(i, j)
    Next j, i
    GoTo abc
xyz:
    ActiveCell.Offset(1, 0).Range("A1").Select
    For i = 1 To n
        ActiveCell = A(i, i)
        ActiveCell.Offset(0, 1).Range("A1").Select
    Next i
    ActiveCell.Offset(2, -n).Range("A1").Select
    For i = 1 To n
        For j = 1 To n
            ActiveCell = W(i, j)
            ActiveCell.Offset(0, 1).Range("A1").Select
        Next j
        ActiveCell.Offset(1, -n).Range("A1").Select
    Next i
固有値・固有ベクトル計算マクロのSUBプロシージャ化
 固有値・固有ベクトル計算部分 For i = 1 To n For j = 1 To n: W(i, j) = 0: Next j W(i, i) = 1 Next i abc: z = -1 For i = 1 To n-1: For j = i+1 To n If Abs(A(i, j)) > z Then z = Abs(A(i, j)): ii = i: jj = j Next j, i If z < 1E-10 Then GoTo xyz AA = A(ii, ii) - A(jj, jj) If Abs(AA) < 1E-10 Then t = 3.14159265358979 / 4 Else t = Atn(2 * A(ii, jj) / AA) / 2 End If For i = 1 To n For j = 1 To n: B(i, j) = 0: Next j B(i, i) = 1 Next i B(ii, ii) = Cos(t): B(jj, jj) = Cos(t) B(ii, jj) = Sin(t): B(jj, ii) = -Sin(t) For i = 1 To n: For j = 1 To n S = 0 For p = 1 To n: For q = 1 To n S = S + B(i, p) * A(p, q) * B(j, q) Next q, p C(i, j) = S Next j, i For i = 1 To n: For j = 1 To n s = 0 For p = 1 To n s = s + W(i, p) * B(j, p) Next p D(i, j) = s Next j, i For i = 1 To n: For j = 1 To n A(i, j) = C(i, j) W(i, j) = D(i, j) Next j, i GoTo abc xyz: で、この部分だけで使用される変数は、z、i、j、ii、jj、t、B、S、p、q、C、Dであり、nとAは他の 部分で決められており、ここで計算されるAとWは他の部分で使用される。  したがって、引数をA、W、nにすればよいことになる。SUBプロシージャ名をeigenとしたとき、上の プログラムをSUBプロシージャ化したものは次のようになる。 Sub eigen(A, W, n) ReDim B(n, n), C(n, n), D(n, n) For i = 1 To n For j = 1 To n: W(i, j) = 0: Next j W(i, i) = 1 Next i abc: z = -1 For i = 1 To n-1: For j = i+1 To n If Abs(A(i, j)) > z Then z = Abs(A(i, j)): ii = i: jj = j Next j, i If z < 1E-10 Then Exit Sub AA = A(ii, ii) - A(jj, jj) If Abs(AA) < 1E-10 Then t = 3.14159265358979 / 4 Else t = Atn(2 * A(ii, jj) / AA) / 2 End If For i = 1 To n For j = 1 To n: B(i, j) = 0: Next j B(i, i) = 1 Next i B(ii, ii) = Cos(t): B(jj, jj) = Cos(t) B(ii, jj) = Sin(t): B(jj, ii) = -Sin(t) For i = 1 To n: For j = 1 To n S = 0 For p = 1 To n: For q = 1 To n S = S + B(i, p) * A(p, q) * B(j, q) Next q, p C(i, j) = S Next j, i For i = 1 To n: For j = 1 To n s = 0 For p = 1 To n s = s + W(i, p) * B(j, p) Next p D(i, j) = s Next j, i For i = 1 To n: For j = 1 To n A(i, j) = C(i, j) W(i, j) = D(i, j) Next j, i GoTo abc End Sub  ここで注意すべきことは、赤字の部分である。  まず、配列B、C、DはこのSUBプロシージャでのみ使用され、他の部分で定義することができないの でReDim B(n, n), C(n, n), D(n, n)と、定義しなければならない。ただし、引数であるAとWはこの SUBプロシージャを呼び出す部分で定義してあるはずであるから、ここで定義する必要がない。  Exit Subは、SUBプロシージャを終了するための命令であり、End Subに達したときと同様に、呼び 出したところに処理を戻すことになる。  元々が GoTo xyz → xyz: → End Sub となるものであることから、GoTo xyz を Exit Sub に置き 換えてよく、ラベル xyz: が不要になったため省略してある。  さらに、青字にした部分を見比べると、配列がWとBで異なるのみで、同型であることに気付くであ ろう。このように同型になっている部分は、SUBプロシージャ化すべき候補となる。  これらのプログラムは、配列WないしBを単位行列にするためのものであり、SUBプロシージャ化す れば、次のようになる。 Sub S2(x, n) For i = 1 To n For j = 1 To n: x(i, j) = 0: Next j x(i, i) = 1 Next i End Sub このSUBプロシージャS2を用いれば、先のSUBプロシージャeigenは次のようになる。 Sub eigen(A, W, n) ReDim B(n, n), C(n, n), D(n, n) Call S2(W, n) abc: z = -1 For i = 1 To n-1: For j = i+1 To n If Abs(A(i, j)) > z Then z = Abs(A(i, j)): ii = i: jj = j Next j, i If z < 1E-10 Then Exit Sub AA = A(ii, ii) - A(jj, jj) If Abs(AA) < 1E-10 Then t = 3.14159265358979 / 4 Else t = Atn(2 * A(ii, jj) / AA) / 2 End If Call S2(B, n) B(ii, ii) = Cos(t): B(jj, jj) = Cos(t) B(ii, jj) = Sin(t): B(jj, ii) = -Sin(t) For i = 1 To n: For j = 1 To n S = 0 For p = 1 To n: For q = 1 To n S = S + B(i, p) * A(p, q) * B(j, q) Next q, p C(i, j) = S Next j, i For i = 1 To n: For j = 1 To n s = 0 For p = 1 To n s = s + W(i, p) * B(j, p) Next p D(i, j) = s Next j, i For i = 1 To n: For j = 1 To n A(i, j) = C(i, j) W(i, j) = D(i, j) Next j, i GoTo abc End Sub  プログラムを、上記のSUBプロシージャを用いて書き直せば、次のようになる。  n = ActiveCell ReDim A(n, n), W(n, n) ActiveCell.Offset(1, 0).Range("A1").Select For i = 1 To n For j = 1 To n A(i, j) = ActiveCell ActiveCell.Offset(0, 1).Range("A1").Select Next j ActiveCell.Offset(1, -n).Range("A1").Select Next i ' Call eigen(A, W, n) ' ActiveCell.Offset(1, 0).Range("A1").Select For i = 1 To n ActiveCell = A(i, i) ActiveCell.Offset(0, 1).Range("A1").Select Next i ActiveCell.Offset(2, -n).Range("A1").Select For i = 1 To n For j = 1 To n ActiveCell = W(i, j) ActiveCell.Offset(0, 1).Range("A1").Select Next j ActiveCell.Offset(1, -n).Range("A1").Select Next i さらに、頻繁に出てくるアクティブセル移動命令 ActiveCell.Offset(*, *).Range("A1").Select をSUBプロシージャ化する。SUBプロシージャ名をS1とすればSUBプロシージャは次にようになる。 Sub S1(x, y) ActiveCell.Offset(x, y).Range("A1").Select End Sub このとき、本文での ActiveCell.Offset(0, 1).Range("A1").Select は Call S1(0, 1) と簡単化される。したがって、固有値計算マクロ本文は次のようになる。 n = ActiveCell ReDim A(n, n), W(n, n) Call S1(1, 0) For i = 1 To n For j = 1 To n A(i, j) = ActiveCell: Call S1(0, 1) Next j Call S1(1, -n) Next i ' Call eigen(A, W, n) ' Call S1(1, 0) For i = 1 To n ActiveCell = A(i, i): Call S1(0, 1) Next i Call S1(2, -n) For i = 1 To n For j = 1 To n ActiveCell = W(i, j): Call S1(0, 1) Next j Call S1(1, -n) Next i