vbaで数値計算 09 掃き出し法

29
2017-01 更新 熊本高専 森下功啓 VBA数値計算09

Upload: katsuhiro-morishita

Post on 13-Apr-2017

67 views

Category:

Education


0 download

TRANSCRIPT

Page 1: VBAで数値計算 09 掃き出し法

2017-01更新 熊本高専森下功啓

VBAで数値計算09

Page 2: VBAで数値計算 09 掃き出し法

本資料の目次

掃き出し法で連立方程式を解く練習問題その他

2

Page 3: VBAで数値計算 09 掃き出し法

掃き出し法3

Page 4: VBAで数値計算 09 掃き出し法

掃き出し法とは

掃き出し法とは、連立一次方程式を解くためのアルゴリズムの一つである。ガウス・ジョルダン法やガウスの消去法とも呼ばれる。応用すれば逆行列を求めることができる。なお、連立一次方程式を解く方法には他にもクラメールの公式など、多数存在する。掃き出し法の計算オーダーは𝑂𝑂(𝑛𝑛3)で、巨大な行列の計算には向いていない。また、安定性(理論解が存在する場合に必ず数値計算で解が求まるなら安定)も良いとは言えない。しかしながらアルゴリズムがわかり易く、他のアルゴリズムでのミスを発見するのに役立つポピュラーな手法である。

4

Page 5: VBAで数値計算 09 掃き出し法

連立一次方程式の行列表現

式(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)

Page 6: VBAで数値計算 09 掃き出し法

行列の操作

連立方程式の解法では、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になった事に注目しよう。

Page 7: VBAで数値計算 09 掃き出し法

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行目まで繰り返す。

*𝒙𝒙が求まった。

Page 8: VBAで数値計算 09 掃き出し法

アルゴリズムを考える

8

ループで全行処理For row=0 to m-1

𝑎𝑎𝑖𝑖𝑖𝑖で割る

全ての行から引くので、全体のループの中にループのある2重ループ構造が必要

と分かる。

Page 9: VBAで数値計算 09 掃き出し法

実装イメージ

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次元ベクトルを仮定

Page 10: VBAで数値計算 09 掃き出し法

掃き出し法 実装例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することで、引数への副作用を避けた

Page 11: VBAで数値計算 09 掃き出し法

変数の変化の様子を追う

下記の様にブレイクポイントを設置し、変数vector_Yとmatrix_Aをウォッチ式に追加する。その上でプログラムをデバッグモードで実行しながら、これらの変化を追ってみよう。

11

Page 12: VBAで数値計算 09 掃き出し法

12

これが初期状態での変数の状態である。

1行目のデータ

行のインデックス

列のインデックス

実行停止中(矢印のある行は未実行)

Page 13: VBAで数値計算 09 掃き出し法

13

1になった

0になった

実行停止中(矢印のある行は未実行)

Page 14: VBAで数値計算 09 掃き出し法

14

1になった

0になった

実行停止中(矢印のある行は未実行)

ブレークポイントの位置は変えた。

Page 15: VBAで数値計算 09 掃き出し法

15

1になった

0になった

実行停止中(矢印のある行は未実行)

Page 16: VBAで数値計算 09 掃き出し法

16

1になった

0になった

実行停止中(矢印のある行は未実行)

Page 17: VBAで数値計算 09 掃き出し法

プログラム簡易化のための一工夫

𝐲𝐲はよく見ると、計算過程が係数行列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⋮𝑥𝑥𝑚𝑚

Page 18: VBAで数値計算 09 掃き出し法

行列の最終列にベクトルを追加するコード例

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つを結合して・・・と

Page 19: VBAで数値計算 09 掃き出し法

掃き出し法 実装例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以外ではシンプルになって計算速度も上がる。

**逆行列計算での利用を視野に入れると関数を整理した方が良いが、ここでは考慮しない。

Page 20: VBAで数値計算 09 掃き出し法

0除算対策&計算精度向上の工夫

上記の部分において、対角成分のcoefが0だとエラーとなる。つまり、対角成分に0が1つでも有ると計算できない。ただし、今回の場合は𝐲𝐲と𝐀𝐀が連動していれば行の入れ替えが可能なので、 𝑎𝑎𝑖𝑖𝑖𝑖が0の場合は𝑖𝑖列の成分が0以外である行と入れ替えれば良い。また、計算精度を上げるためには0に近い数で割らない方が良い。したがって、交換する行は絶対値が最も大きいものを選択する。この様な操作をピボット操作という。ピボット操作のために、𝑖𝑖列で最大の係数を持つ行𝑘𝑘 (𝑖𝑖 ≤ 𝑘𝑘 ≤ 𝑛𝑛 − 1)を返す関数と、 𝑖𝑖行と𝑘𝑘行を入れ替える関数を用意しよう。

20

matrix_data(Row)(col) = matrix_data(Row)(col) / coef

Page 21: VBAで数値計算 09 掃き出し法

最大係数の行を探す関数

指定列𝑖𝑖で最大の係数を持つ行番号𝑘𝑘を返す関数例を以下に示す。ここで、 𝑖𝑖行より上の行は既に調整済みなので調整範囲から外し、 𝑘𝑘の初期値は𝑖𝑖とする。(プログラム中では𝑘𝑘は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

Page 22: VBAで数値計算 09 掃き出し法

行の入れ替えを行う関数

行を入れ替える関数例を以下に示す。この関数は、引数で渡された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

行のデータを取り出して、入れ替えた上で再代入している。

Page 23: VBAで数値計算 09 掃き出し法

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

Page 24: VBAで数値計算 09 掃き出し法

関数の呼び出し例

これまで解説してきた関数の呼び出し例を以下に示す。ここでは、変数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

Page 25: VBAで数値計算 09 掃き出し法

練習問題25

Page 26: VBAで数値計算 09 掃き出し法

問1

以下の問題を解け。解答は次のページ。狙い:数値計算的な解法を身に着けるのが第一である。解が存在しない場合の計算過程で変数がどのように変化するか確認して感覚をつかんで欲しい。係数行列の行列式を求めてみると面白いだろう。

26言語指定:VBA

[引用元] 押川元重・他,精選 線形代数初版第6版発行,培風館,2005.

Page 27: VBAで数値計算 09 掃き出し法

問1の問題の数学的解

27

Page 28: VBAで数値計算 09 掃き出し法

その他28

Page 29: VBAで数値計算 09 掃き出し法

参考文献

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