Fortran - 逆行列の計算(余因子行列を使用)!
Updated:
前回、 Fortran 95 で余因子展開による行列式の計算を行いましたが、今回は、それを応用して、逆行列の計算を行ってみました。
少し前に、同じことを Ruby で Array クラスを拡張する方法で実装しています。
0. 前提条件
- Debian GNU/Linux 10.3 (64bit) での作業を想定。
- GCC 9.2.0 (GFortran 9.2.0) でのコンパイルを想定。
1. 行列式の余因子展開について
\(n(>1)\) 次正方行列 \(A=(a _ {ij})\) から第 \(i\) 行と第 \(j\) 列の成分をすべて取り除いて得られる \(n-1\) 次行列の行列式に、 \((-1)^{i+j}\) を掛けたものを \(a _ {ij}\) の 余因子 といい、 \(\tilde{A} _ {ij}\) で表す。すなわち、
\[\begin{eqnarray} \tilde{A}_{ij}=(-1)^{i+j}\left| \begin{array}{cccccc} a_{11} & \cdots & a_{1\ j-1} & a_{1\ j+1} & \cdots & a_{1n} \\ \vdots & & \vdots & \vdots & & \vdots \\ a_{i-1\ 1} & \cdots & a_{i-1\ j-1} & a_{i-1\ j+1} & \cdots & a_{i-1\ n} \\ a_{i+1\ 1} & \cdots & a_{i+1\ j-1} & a_{i+1\ j+1} & \cdots & a_{i+1\ n} \\ \vdots & & \vdots & \vdots & & \vdots \\ a_{n1} & \cdots & a_{n\ j-1} & a_{n\ j+1} & \cdots & a_{nn} \\ \end{array} \right| \end{eqnarray}\]そして、次の定理が成り立つ。(証明略)
【定理】 \(n\) 次正方行列 \(A=(a _ {ij})\) に対して、
\[\begin{eqnarray} |A| &=& a_{i1}\tilde{A}_{i1} + a_{i2}\tilde{A}_{i2} + \cdots + a_{in}\tilde{A}_{in}\ \ (1<i\le n) \\ |A| &=& a_{1j}\tilde{A}_{1j} + a_{2j}\tilde{A}_{2j} + \cdots + a_{nj}\tilde{A}_{nj}\ \ (1<j\le n) \end{eqnarray}\](2)を行列式 |A| の \(i\) 行についての 余因子展開、(3)を行列式 |A| の \(j\) 列についての 余因子展開 という。(余因子展開は、ラプラス展開や単に展開と呼ばれることもある)
2. 余因子行列と逆行列について
\(n(>1)\) 次正方行列 \(A=(a _ {ij})\) に対して、 \(a _ {ij}\) の余因子 \(\tilde{A} _ {ij}\) を \((j,i)\) 成分とするような行列を \(A\) の 余因子行列 といい、 \(\tilde{A}\) で表す。すなわち、
\[\begin{eqnarray} \tilde{A}=\left( \begin{array}{cccc} \tilde{A}_{11} & \tilde{A}_{21} & \cdots & \tilde{A}_{n1} \\ \tilde{A}_{12} & \tilde{A}_{22} & \cdots & \tilde{A}_{n2} \\ \vdots & \vdots & & \vdots \\ \tilde{A}_{1n} & \tilde{A}_{2n} & \cdots & \tilde{A}_{nn} \\ \end{array} \right)\ \ (n>1,\ \tilde{A}_{ij}の並び方に注意) \end{eqnarray}\]そして、次の定理が成り立つ。(証明略)
【定理】 \(n\) 次正方行列 \(A\) の逆行列は次式で求められる。
\[\begin{eqnarray} A^{-1}=\frac{1}{|A|}\tilde{A} \end{eqnarray}\]3. ソースコードの作成
File: inverse_matrix.f95
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
!****************************************************
! 逆行列の計算(余因子行列)
!
! DATE AUTHOR VERSION
! 2019.12.24 mk-mode.com 1.00 新規作成
!
! Copyright(C) 2013 mk-mode.com All Rights Reserved.
!****************************************************
!
module const
! SP: 単精度(4), DP: 倍精度(8)
integer, parameter :: SP = kind(1.0)
integer(SP), parameter :: DP = selected_real_kind(2 * precision(1.0_SP))
end module const
module comp
use const
implicit none
private
public :: det, cof_mtx, inv_mtx
contains
! 行列式計算
! * 再帰的
!
! :param(in) real(8) m(:, :): 行列
! :return real(8) d: 行列式
recursive real(DP) function det(m)
implicit none
real(DP), intent(in) :: m(:, :)
integer(SP) :: j, n
det = 0.0_DP
n = size(m(1, :))
select case(n)
case (1)
det = m(1, 1)
case (2)
det = m(1, 1) * m(2, 2) &
& - m(1, 2) * m(2, 1)
case (3)
det = m(1, 1) * m(2, 2) * m(3, 3) &
& + m(1, 2) * m(2, 3) * m(3, 1) &
& + m(1, 3) * m(2, 1) * m(3, 2) &
& - m(1, 1) * m(2, 3) * m(3, 2) &
& - m(1, 2) * m(2, 1) * m(3, 3) &
& - m(1, 3) * m(2, 2) * m(3, 1)
case (4:)
det = 0
do j = 1, n
det = det + m(1, j) * cof(m, 1, j)
end do
end select
end function det
! 余因子行列
!
! :param(in) m: 行列配列
! :param(out) cm: 余因子行列配列
subroutine cof_mtx(m, cm)
implicit none
real(DP), intent(in) :: m(:, :)
real(DP), intent(out) :: cm(:, :)
integer(SP) :: i, j, n
n = size(m(1, :))
do i = 1, n
do j = 1, n
cm(i, j) = cof(m, j, i)
end do
end do
end subroutine cof_mtx
! 逆行列
!
! :param(in) m: 行列配列
! :param(out) im: 逆行列配列
subroutine inv_mtx(m, im)
implicit none
real(DP), intent(in) :: m(:, :)
real(DP), intent(out) :: im(:, :)
integer(SP) :: n
real(DP), allocatable :: cm(:, :)
n = size(m(1, :))
if (n == 1) then
im = 1.0_DP / m
return
end if
allocate(cm(n, n))
call cof_mtx(m, cm)
im = cm / det(m)
deallocate(cm)
end subroutine inv_mtx
! 余因子(正方行列 A の (i, j) に対する)
!
! :param(in) m: 行列配列
! :param(in) i: 行インデックス
! :param(in) j: 列インデックス
! :param(out) c: 余因子
real(DP) function cof(m, i, j)
implicit none
real(DP), intent(in) :: m(:, :)
integer(SP), intent(in) :: i, j
integer(SP) :: n
real(DP), allocatable :: m2(:, :)
n = size(m(1, :))
allocate(m2(n - 1, n - 1))
m2(1:i-1, 1:j-1) = m(1:i-1, 1:j-1)
m2(1:i-1, j:n ) = m(1:i-1, j+1:n)
m2(i:n , 1:j-1) = m(i+1:n, 1:j-1)
m2(i:n , j:n ) = m(i+1:n, j+1:n)
cof = (-1)**(i+j) * det(m2)
deallocate(m2)
end function cof
end module comp
program inverse_matrix
use const
use comp
implicit none
integer(SP) :: n, i
real(DP) :: d
real(DP), allocatable :: m(:, :), cm(:, :), im(:, :)
! データ数読み込み
read (*, *) n
! 配列用メモリ確保
allocate(m(n, n))
allocate(cm(n, n))
allocate(im(n, n))
! データ読み込み
do i = 1, n
read (*, *) m(i, :)
end do
print *, "Matrix A ="
do i = 1, n
print *, m(i, :)
end do
! 行列式計算
d = det(m)
print *, "det(A) =", d
! 余因子行列計算
call cof_mtx(m, cm)
print *, "Cofactor Matrix of A ="
do i = 1, n
print *, cm(i, :)
end do
! 逆行列計算
call inv_mtx(m, im)
print *, "Inverse Matrix of A ="
do i = 1, n
print *, im(i, :)
end do
! 配列用メモリ解放
deallocate(m)
deallocate(cm)
deallocate(im)
end program inverse_matrix
4. ソースコードのコンパイル
$ gfortran -Wall -O2 -o inverse_matrix inverse_matrix.f95
5. 動作確認
まず、以下のような入力ファイルを用意する。
(先頭行:行数、2行目以降:行列の各要素)
File: input.txt
1
2
3
4
5
6
5
1 3 5 7 9
4 2 8 6 0
9 3 7 5 1
4 0 6 8 2
3 6 9 2 5
そして、実行。
$ ./inverse_matrix < input.txt
Matrix A =
1.0000000000000000 3.0000000000000000 5.0000000000000000 7.0000000000000000 9.0000000000000000
4.0000000000000000 2.0000000000000000 8.0000000000000000 6.0000000000000000 0.0000000000000000
9.0000000000000000 3.0000000000000000 7.0000000000000000 5.0000000000000000 1.0000000000000000
4.0000000000000000 0.0000000000000000 6.0000000000000000 8.0000000000000000 2.0000000000000000
3.0000000000000000 6.0000000000000000 9.0000000000000000 2.0000000000000000 5.0000000000000000
det(A) = 2320.0000000000000
Cofactor Matrix of A =
-192.00000000000000 -600.00000000000000 336.00000000000000 376.00000000000000 128.00000000000000
1208.0000000000000 2180.0000000000000 496.00000000000000 -2704.0000000000000 -1192.0000000000000
-752.00000000000000 -900.00000000000000 -424.00000000000000 1376.0000000000000 888.00000000000000
728.00000000000000 1260.0000000000000 176.00000000000000 -1184.0000000000000 -872.00000000000000
-272.00000000000000 -1140.0000000000000 -104.00000000000000 1016.0000000000000 568.00000000000000
Inverse Matrix of A =
-8.2758620689655171E-002 -0.25862068965517243 0.14482758620689656 0.16206896551724137 5.5172413793103448E-002
0.52068965517241383 0.93965517241379315 0.21379310344827587 -1.1655172413793105 -0.51379310344827589
-0.32413793103448274 -0.38793103448275862 -0.18275862068965518 0.59310344827586203 0.38275862068965516
0.31379310344827588 0.54310344827586210 7.5862068965517240E-002 -0.51034482758620692 -0.37586206896551722
-0.11724137931034483 -0.49137931034482757 -4.4827586206896551E-002 0.43793103448275861 0.24482758620689654
当然、以下のように実行してもよいし、
$ cat input.txt | ./inverse_matrix
以下のように実行してもよい。
$ ./inverse_matrix <<_EOF_
5
1 3 5 7 9
4 2 8 6 0
9 3 7 5 1
4 0 6 8 2
3 6 9 2 5
_EOF_
以上。
Comments