Upload
katsuhiro-morishita
View
83
Download
0
Embed Size (px)
Citation preview
2017-01更新 熊本高専森下功啓
VBAで数値計算09
本資料の目次
掃き出し法で連立方程式を解く練習問題その他
2
掃き出し法3
掃き出し法とは
掃き出し法とは、連立一次方程式を解くためのアルゴリズムの一つである。ガウス・ジョルダン法やガウスの消去法とも呼ばれる。応用すれば逆行列を求めることができる。なお、連立一次方程式を解く方法には他にもクラメールの公式など、多数存在する。掃き出し法の計算オーダーは𝑂𝑂(𝑛𝑛3)で、巨大な行列の計算には向いていない。また、安定性(理論解が存在する場合に必ず数値計算で解が求まるなら安定)も良いとは言えない。しかしながらアルゴリズムがわかり易く、他のアルゴリズムでのミスを発見するのに役立つポピュラーな手法である。
4
連立一次方程式の行列表現
式(1)の連立方程式を行列に書き換えると、式(1)は式(2)に変形できます。以降ではm=nとして掃き出し法を解説します。
5
𝑦𝑦1 = 𝑎𝑎11𝑥𝑥1 + 𝑎𝑎12𝑥𝑥2 + ⋯+ 𝑎𝑎1𝑛𝑛𝑥𝑥𝑚𝑚𝑦𝑦2 = 𝑎𝑎21𝑥𝑥1 + 𝑎𝑎22𝑥𝑥2 + ⋯+ 𝑎𝑎2𝑛𝑛𝑥𝑥𝑚𝑚
⋮𝑦𝑦𝑚𝑚 = 𝑎𝑎𝑚𝑚1𝑥𝑥1 + 𝑎𝑎𝑚𝑚2𝑥𝑥2 + ⋯+ 𝑎𝑎𝑚𝑚𝑛𝑛𝑥𝑥𝑚𝑚
𝑦𝑦1⋮𝑦𝑦𝑚𝑚
=𝑎𝑎11 ⋯ 𝑎𝑎1𝑛𝑛⋮ ⋱ ⋮
𝑎𝑎𝑚𝑚1 ⋯ 𝑎𝑎𝑚𝑚𝑛𝑛
𝑥𝑥1⋮𝑥𝑥𝑚𝑚
式(1)
*未知数を求めるには、未知数と同数以上の方程式が必要となる。ただし、𝑦𝑦𝑖𝑖が厳密であれば方程式の数は未知数の数と同数で良い。未知数と方程式の数が等しい場合、Aは正方行列となり、n=mである。𝐲𝐲 = 𝐀𝐀𝐀𝐀
𝑎𝑎𝑖𝑖𝑖𝑖
行番号 列番号
添字の見方
式(2)
行列の操作
連立方程式の解法では、i行目をc倍して、c倍されたi行目を他の行から引いても良いことを思い出そう。
6
𝑦𝑦1𝑦𝑦2𝑦𝑦3⋮𝑦𝑦𝑚𝑚
=
𝑎𝑎11𝑎𝑎21𝑎𝑎31⋮
𝑎𝑎𝑚𝑚1
𝑎𝑎12𝑎𝑎22𝑎𝑎32⋮
𝑎𝑎𝑚𝑚2
⋯
𝑎𝑎1𝑛𝑛𝑎𝑎2𝑛𝑛𝑎𝑎3𝑛𝑛⋮
𝑎𝑎𝑚𝑚𝑛𝑛
𝑥𝑥1𝑥𝑥2𝑥𝑥3⋮𝑥𝑥𝑚𝑚
𝑦𝑦1/𝑎𝑎11𝑦𝑦2𝑦𝑦3⋮𝑦𝑦𝑚𝑚
=
1𝑎𝑎21𝑎𝑎31⋮
𝑎𝑎𝑚𝑚1
𝑎𝑎12/𝑎𝑎11𝑎𝑎22𝑎𝑎32⋮
𝑎𝑎𝑚𝑚2
⋯
𝑎𝑎1𝑛𝑛/𝑎𝑎11𝑎𝑎2𝑛𝑛𝑎𝑎3𝑛𝑛⋮
𝑎𝑎𝑚𝑚𝑛𝑛
𝑥𝑥1𝑥𝑥2𝑥𝑥3⋮𝑥𝑥𝑚𝑚
Step 1: 1行目の1列目以降と𝑦𝑦1を𝑎𝑎11で割る。
origin
𝑦𝑦1/𝑎𝑎11𝑦𝑦2 − 𝑎𝑎21𝑦𝑦1/𝑎𝑎11𝑦𝑦3 − 𝑎𝑎31𝑦𝑦1/𝑎𝑎11
⋮𝑦𝑦𝑚𝑚 − 𝑎𝑎𝑛𝑛1𝑦𝑦1/𝑎𝑎11
=
100⋮0
𝑎𝑎12/𝑎𝑎11𝑎𝑎22 − 𝑎𝑎21𝑎𝑎12/𝑎𝑎11𝑎𝑎32 − 𝑎𝑎31𝑎𝑎12/𝑎𝑎11
⋮𝑎𝑎𝑚𝑚2 − 𝑎𝑎𝑚𝑚1𝑎𝑎12/𝑎𝑎11
⋯
𝑎𝑎1𝑛𝑛/𝑎𝑎11𝑎𝑎2𝑛𝑛 − 𝑎𝑎21𝑎𝑎1𝑛𝑛/𝑎𝑎11𝑎𝑎3𝑛𝑛 − 𝑎𝑎31𝑎𝑎1𝑛𝑛/𝑎𝑎11
⋮𝑎𝑎𝑚𝑚𝑛𝑛 − 𝑎𝑎𝑚𝑚1𝑎𝑎1𝑛𝑛/𝑎𝑎11
𝑥𝑥1𝑥𝑥2𝑥𝑥3⋮𝑥𝑥𝑚𝑚
Step 2: 1行目にk行目1列の係数を掛けたものをk行目から引く。ただし1行目は除く。
*1列目が1行目を除いて0になった事に注目しよう。
7
𝑦𝑦1𝑦𝑦2𝑦𝑦3⋮𝑦𝑦𝑚𝑚
=
100⋮0
𝑎𝑎12𝑎𝑎22𝑎𝑎32⋮𝑎𝑎𝑚𝑚2
⋯
𝑎𝑎1𝑛𝑛𝑎𝑎2𝑛𝑛𝑎𝑎3𝑛𝑛⋮𝑎𝑎𝑚𝑚𝑛𝑛
𝑥𝑥1𝑥𝑥2𝑥𝑥3⋮𝑥𝑥𝑚𝑚
𝑦𝑦1𝑦𝑦2/ 𝑎𝑎22𝑦𝑦3⋮𝑦𝑦𝑚𝑚
=
100⋮0
𝑎𝑎121𝑎𝑎32⋮𝑎𝑎𝑚𝑚2
⋯
𝑎𝑎1𝑛𝑛𝑎𝑎2𝑛𝑛/ 𝑎𝑎22𝑎𝑎3𝑛𝑛⋮𝑎𝑎𝑚𝑚𝑛𝑛
𝑥𝑥1𝑥𝑥2𝑥𝑥3⋮𝑥𝑥𝑚𝑚
Step 1’: 2行目の2列目以降と 𝑦𝑦2を 𝑎𝑎22で割る(2列目より前は0なので処理を要しない)
Step 2’: 2行目にk行目2列の係数を掛けたものをk行目から引く。ただし2行目は除く。
*2列目が2行目を除いて0になった事に注目しよう。
表記が長いのでダッシュを付けて書き直した。
𝑦𝑦1 − 𝑎𝑎12 𝑦𝑦2/ 𝑎𝑎22𝑦𝑦2/ 𝑎𝑎22
𝑦𝑦3 − 𝑎𝑎32 𝑦𝑦2/ 𝑎𝑎22⋮
𝑦𝑦𝑚𝑚 − 𝑎𝑎𝑚𝑚2 𝑦𝑦2/ 𝑎𝑎22
=
100⋮0
010⋮0
⋯
𝑎𝑎1𝑛𝑛 − 𝑎𝑎12 𝑎𝑎2𝑛𝑛/ 𝑎𝑎22𝑎𝑎2𝑛𝑛/ 𝑎𝑎22
𝑎𝑎3𝑛𝑛 − 𝑎𝑎32 𝑎𝑎2𝑛𝑛/ 𝑎𝑎22⋮
𝑎𝑎𝑚𝑚𝑛𝑛 − 𝑎𝑎𝑚𝑚2 𝑎𝑎2𝑛𝑛/ 𝑎𝑎22
𝑥𝑥1𝑥𝑥2𝑥𝑥3⋮𝑥𝑥𝑚𝑚
𝑦𝑦1𝑦𝑦2𝑦𝑦3⋮𝑦𝑦𝑚𝑚
=
100⋮0
010⋮0
⋯
000⋮1
𝑥𝑥1𝑥𝑥2𝑥𝑥3⋮𝑥𝑥𝑚𝑚
��𝒚 = 𝐈𝐈𝒙𝒙
m行目まで繰り返す。
*𝒙𝒙が求まった。
アルゴリズムを考える
8
ループで全行処理For row=0 to m-1
𝑎𝑎𝑖𝑖𝑖𝑖で割る
全ての行から引くので、全体のループの中にループのある2重ループ構造が必要
と分かる。
実装イメージ
9
Sub get_x(vector_Y, matrix_A)For Row = 0 To matrix_Aのサイズ' Step 1‘ vector_Yもmatrix_AもRow行をmatrix_A(Row)(Row)で割る' Step 2For row2 = 0 To matrix_Aのサイズ
If Row <> row2 ThenFor col = Row To matrix_Aのサイズ‘ matrix_A(row2)(col)からmatrix_A(Row2)(Row) * matrix_A(Row)(col) を引く‘ vector_Yも同様に処理する
Next colEnd If
Next row2 Next RowEnd Sub
@VBA
ループで全行処理For row=0 to m-1
全ての行から引くので、全体のループの中にループのある2重ルー
プ構造が必要と分かる。
𝑎𝑎𝑖𝑖𝑖𝑖で割る正方行列を仮定
1次元ベクトルを仮定
掃き出し法 実装例1
10
Function get_x_0(vector_Y_, matrix_A_)' 掃き出し法により解を求めるvector_Y = vector_Y_ ' Variant型ならDeep copyされるmatrix_A = matrix_A_For Row = 0 To UBound(vector_Y)' row行の係数を(row行, row列)の係数で割るcoef = matrix_A(Row)(Row)vector_Y(Row) = vector_Y(Row) / coefFor col = Row To UBound(vector_Y)
matrix_A(Row)(col) = matrix_A(Row)(col) / coefNext col' row行に(row2行, row列)の係数を掛けたものをrow2行から引く。ただしrow=row2は除く。For row2 = 0 To UBound(vector_Y)
If Row <> row2 Thencoef = matrix_A(row2)(Row)vector_Y(row2) = vector_Y(row2) - vector_Y(Row) * coefFor col = Row To UBound(vector_Y)matrix_A(row2)(col) = matrix_A(row2)(col) - matrix_A(Row)(col) * coef
Next colEnd If
Next row2Next Rowget_x_0 = vector_Y
End Function
正方行列を仮定
1次元ベクトルを仮定
Step 1
Step2
@VBA
変数をDeep copyすることで、引数への副作用を避けた
変数の変化の様子を追う
下記の様にブレイクポイントを設置し、変数vector_Yとmatrix_Aをウォッチ式に追加する。その上でプログラムをデバッグモードで実行しながら、これらの変化を追ってみよう。
11
12
これが初期状態での変数の状態である。
1行目のデータ
行のインデックス
列のインデックス
実行停止中(矢印のある行は未実行)
13
1になった
0になった
実行停止中(矢印のある行は未実行)
14
1になった
0になった
実行停止中(矢印のある行は未実行)
ブレークポイントの位置は変えた。
15
1になった
0になった
実行停止中(矢印のある行は未実行)
16
1になった
0になった
実行停止中(矢印のある行は未実行)
解
プログラム簡易化のための一工夫
𝐲𝐲はよく見ると、計算過程が係数行列Aと同じである。そこで、 𝐲𝐲を係数行列の最終列に追加することを考える。
17
Aと同じ計算過程
𝑎𝑎11𝑎𝑎21𝑎𝑎31⋮
𝑎𝑎𝑚𝑚1
𝑎𝑎12𝑎𝑎22𝑎𝑎32⋮
𝑎𝑎𝑚𝑚2
⋯
𝑎𝑎1𝑛𝑛𝑎𝑎2𝑛𝑛𝑎𝑎3𝑛𝑛⋮
𝑎𝑎𝑚𝑚𝑛𝑛
𝑦𝑦1𝑦𝑦2𝑦𝑦3⋮𝑦𝑦𝑚𝑚
関数に渡す引数を↓のように変更する。100⋮0
010⋮0
⋯
000⋮1
𝑦𝑦1𝑦𝑦2𝑦𝑦3⋮𝑦𝑦𝑚𝑚
最終的な解はn+1列に格納される。
𝑦𝑦1 − 𝑎𝑎12 𝑦𝑦2/ 𝑎𝑎22𝑦𝑦2/ 𝑎𝑎22
𝑦𝑦3 − 𝑎𝑎32 𝑦𝑦2/ 𝑎𝑎22⋮
𝑦𝑦𝑚𝑚 − 𝑎𝑎𝑚𝑚2 𝑦𝑦2/ 𝑎𝑎22
=
100⋮0
010⋮0
⋯
𝑎𝑎1𝑛𝑛 − 𝑎𝑎12 𝑎𝑎2𝑛𝑛/ 𝑎𝑎22𝑎𝑎2𝑛𝑛/ 𝑎𝑎22
𝑎𝑎3𝑛𝑛 − 𝑎𝑎32 𝑎𝑎2𝑛𝑛/ 𝑎𝑎22⋮
𝑎𝑎𝑚𝑚𝑛𝑛 − 𝑎𝑎𝑚𝑚2 𝑎𝑎2𝑛𝑛/ 𝑎𝑎22
𝑥𝑥1𝑥𝑥2𝑥𝑥3⋮𝑥𝑥𝑚𝑚
行列の最終列にベクトルを追加するコード例
18
𝑎𝑎11𝑎𝑎21𝑎𝑎31⋮
𝑎𝑎𝑚𝑚1
𝑎𝑎12𝑎𝑎22𝑎𝑎32⋮
𝑎𝑎𝑚𝑚2
⋯
𝑎𝑎1𝑛𝑛𝑎𝑎2𝑛𝑛𝑎𝑎3𝑛𝑛⋮
𝑎𝑎𝑚𝑚𝑛𝑛
Function matrix_append(matrix_A_, vector_Y_)' 行列の最終列にvector_Y_の要素をコピーする。実用には要素数のチェックを入れたほうが良い。new_matrix = matrix_A_For i = 0 To UBound(new_matrix)
Dim row_data As Variantrow_data = new_matrix(i)ReDim Preserve row_data(UBound(row_data) + 1) ' 内容保持したまま拡張row_data(UBound(row_data)) = vector_Y_(i)new_matrix(i) = row_data
Next imatrix_append = new_matrix
End Function @VBA
𝑎𝑎11𝑎𝑎21𝑎𝑎31⋮
𝑎𝑎𝑚𝑚1
𝑎𝑎12𝑎𝑎22𝑎𝑎32⋮
𝑎𝑎𝑚𝑚2
⋯
𝑎𝑎1𝑛𝑛𝑎𝑎2𝑛𝑛𝑎𝑎3𝑛𝑛⋮
𝑎𝑎𝑚𝑚𝑛𝑛
𝑦𝑦1𝑦𝑦2𝑦𝑦3⋮𝑦𝑦𝑚𝑚
𝑦𝑦1𝑦𝑦2𝑦𝑦3⋮𝑦𝑦𝑚𝑚
2つを結合して・・・と
掃き出し法 実装例2
19
Function get_x_1(vector_Y_, matrix_A_)' 掃き出し法により解を求めるmatrix_data = matrix_append(matrix_A_, vector_Y_)For Row = 0 To UBound(matrix_data)' row行の係数を(row行, row列)の係数で割るcoef = matrix_data(Row)(Row)For col = Row To UBound(matrix_data(0))
matrix_data(Row)(col) = matrix_data(Row)(col) / coefNext col' row行に(row2行, row列)の係数を掛けたものをrow2行から引く。ただしrow=row2は除く。For row2 = 0 To UBound(matrix_data)
If Row <> row2 Thencoef = matrix_data(row2)(Row)For col = Row To UBound(matrix_data(0))matrix_data(row2)(col) = matrix_data(row2)(col) - matrix_data(Row)(col) * coef
Next colEnd If
Next row2Next Row' 解を取り出す(なんて面倒なんだ)Dim ans As Variantans = Array()ReDim ans(UBound(matrix_data))For i = 0 To UBound(matrix_data)ans(i) = matrix_data(i)(UBound(matrix_data(0)))
Next i
get_x_1 = ansEnd Function
matrix_dataはn行n+1列の2次元配列
Step 1
Step2
@VBA
*VBAでは行数が増えたが、VBA以外ではシンプルになって計算速度も上がる。
**逆行列計算での利用を視野に入れると関数を整理した方が良いが、ここでは考慮しない。
0除算対策&計算精度向上の工夫
上記の部分において、対角成分のcoefが0だとエラーとなる。つまり、対角成分に0が1つでも有ると計算できない。ただし、今回の場合は𝐲𝐲と𝐀𝐀が連動していれば行の入れ替えが可能なので、 𝑎𝑎𝑖𝑖𝑖𝑖が0の場合は𝑖𝑖列の成分が0以外である行と入れ替えれば良い。また、計算精度を上げるためには0に近い数で割らない方が良い。したがって、交換する行は絶対値が最も大きいものを選択する。この様な操作をピボット操作という。ピボット操作のために、𝑖𝑖列で最大の係数を持つ行𝑘𝑘 (𝑖𝑖 ≤ 𝑘𝑘 ≤ 𝑛𝑛 − 1)を返す関数と、 𝑖𝑖行と𝑘𝑘行を入れ替える関数を用意しよう。
20
matrix_data(Row)(col) = matrix_data(Row)(col) / coef
最大係数の行を探す関数
指定列𝑖𝑖で最大の係数を持つ行番号𝑘𝑘を返す関数例を以下に示す。ここで、 𝑖𝑖行より上の行は既に調整済みなので調整範囲から外し、 𝑘𝑘の初期値は𝑖𝑖とする。(プログラム中では𝑘𝑘はRow,走査列𝑖𝑖はsearch_colで表している。)
21
Function search_max_coef(search_col, matrix_data)' search_col列の最大値を持つ行を返すrow_at_max = -1max_val = 0For Row = search_col To UBound(matrix_data)Value = Abs(matrix_data(Row)(search_col))If Value > max_val Then
max_val = Valuerow_at_max = Row
End IfNext Rowsearch_max_coef = row_at_max
End Function@VBA
行の入れ替えを行う関数
行を入れ替える関数例を以下に示す。この関数は、引数で渡されたmatrix_data(Arrayの入れ子による行列を想定)のうち、row_index1行とrow_index2行(いずれも0からカウントの整数を想定)を入れ替える。
22
Function switch_row(row_index1, row_index2, matrix_data)' row_index1行目とrow_index2行目を入れ替えるrow1 = matrix_data(row_index1)row2 = matrix_data(row_index2)matrix_data(row_index1) = row2matrix_data(row_index2) = row1switch_row = matrix_data
End Function@VBA
行のデータを取り出して、入れ替えた上で再代入している。
23
Function get_x_2(vector_Y_, matrix_A_)' 掃き出し法により解を求めるmatrix_data = matrix_append(matrix_A_, vector_Y_)For Row = 0 To UBound(matrix_data)’最大係数となる行を探して、row行と入れ替えるswitch_row_index = search_max_coef(Row, matrix_data)matrix_data = switch_row(Row, switch_row_index, matrix_data)
' row行の係数を(row行, row列)の係数で割るcoef = matrix_data(Row)(Row)For col = Row To UBound(matrix_data(0))matrix_data(Row)(col) = matrix_data(Row)(col) / coef
Next col
' row行に(row2行, row列)の係数を掛けたものをrow2行から引く。ただしrow=row2は除く。For row2 = 0 To UBound(matrix_data)If Row <> row2 Thencoef = matrix_data(row2)(Row)For col = Row To UBound(matrix_data(0))matrix_data(row2)(col) = matrix_data(row2)(col) - matrix_data(Row)(col) * coef
Next colEnd If
Next row2Next Row
' 解を取り出す(なんて面倒なんだ)Dim ans As Variantans = Array()ReDim ans(UBound(matrix_data))For i = 0 To UBound(matrix_data)ans(i) = matrix_data(i)(UBound(matrix_data(0)))
Next i
get_x_2 = ansEnd Function
掃き出し法 実装例3
係数値最大の行を探して、row行と入れ替えている。
エラー対策のコードも仕込めば、実用的に使える。
@VBA
関数の呼び出し例
これまで解説してきた関数の呼び出し例を以下に示す。ここでは、変数temp_matrixに係数行列を代入し、Yに式の値を代入している。get_x_0()は連立方程式の解を返してresultに代入される。
24
Sub hakidashi_test()row1 = Array(1, -2, -2, 2)row2 = Array(2, -2, -3, 3)row3 = Array(-1, 6, 3, -2)row4 = Array(1, 4, 0, -1)temp_matrix = Array(row1, row2, row3, row4)Y = Array(5, 10, 2, -10)result = get_x_0(Y, temp_matrix)result = get_x_1(Y, temp_matrix)result = get_x_2(Y, temp_matrix)
End Sub @VBA
練習問題25
問1
以下の問題を解け。解答は次のページ。狙い:数値計算的な解法を身に着けるのが第一である。解が存在しない場合の計算過程で変数がどのように変化するか確認して感覚をつかんで欲しい。係数行列の行列式を求めてみると面白いだろう。
26言語指定:VBA
[引用元] 押川元重・他,精選 線形代数初版第6版発行,培風館,2005.
問1の問題の数学的解
27
その他28
参考文献
SAK Streets - VB 開発言語資料 http://sak.cool.coocan.jp/w_sak3/doc/sysbrd/sak3vb.htm 基本的にVB6.0の解説だが、VBAにほぼそのまま適用できる。かなり詳しい。
筑波大の教員HP 線形代数I/連立一次方程式 http://dora.bk.tsukuba.ac.jp/~takeuchi/?%E7%B7%9A%E5%BD%A2%E4%BB%
A3%E6%95%B0%EF%BC%A9%2F%E9%80%A3%E7%AB%8B%E4%B8%80%E6%AC%A1%E6%96%B9%E7%A8%8B%E5%BC%8F#oa5d8bb5
29