LU分解による連立一次方程式の解法

数値計算ライブラリLapackを使って線形連立一次方程式
\(
\begin{equation}
A\mathbf{x}=\mathbf{b}
\end{equation}
\)

をLU分解を用いて数値的に解くプログラムを載せます。

詳しくは説明しませんが、逆行列を用いて解く方法と比べ、
行列が疎である時(行列要素にゼロが多い時)に比較的計算量が減らせます。
また、逆行列を求める際に生じる桁落ちの問題を回避することが出来ます。

解法


連立一次方程式
\(
\begin{equation}
A\mathbf{x}=\mathbf{b}
\end{equation}
\)

を行列\(A\)のLU分解を利用して解きます。
ここで、未知なのはベクトル\(\mathbf{x}\)、既知なのは行列\(A\)とベクトル\(\mathbf{b}\)です。

\(A\)がLU分解されているとします。
すなわち、行列\(A\)に適当な操作を施して、

\(
\begin{align}
A=LU
\end{align}
\)

と書けているとします。ここで、行列\(L, U\)は下三角行列、上三角行列であり、
\(
\begin{align}
L=
\left(
\begin{array}{*4{>{\displaystyle}c}}
1&0&\cdots &0 \\[1em]
l_{2,1}&1&\cdots &0 \\[0.5em]
\vdots&\vdots&\ddots&\vdots \\[0.3em]
l_{s,1}&l_{s,2}&\cdots &1
\end{array}
\right) ,~~~
U=
\left(
\begin{array}{*4{>{\displaystyle}c}}
u_{1,1}&u_{1,2}&\cdots &u_{1,s} \\[1em]
0&u_{2,2}&\cdots &u_{2,s} \\[0.5em]
\vdots&\vdots&\ddots&\vdots \\[0.3em]
0 & 0 &\cdots &u_{s,s}
\end{array}
\right)
\end{align}
\)

という行列です。
もし、行列\(A\)がLU分解されていれば、

\(
\begin{align}
& A\mathbf{x}=\mathbf{b} \\
& LU\mathbf{x} = \mathbf{b} \\
& L\mathbf{w} = \mathbf{b}~~~…(*)
\end{align}
\)

と書けます。ここで、\(\mathbf{w} \equiv U\mathbf{x}\)と置きました。
まず、式(*)を解いて、\(\mathbf{w}\)を求めます。これは、下三角行列に対する問題なので、前進代入を用いて簡単に解くことが出来ます。

続いて、
\(
U\mathbf{x}=\mathbf{w}
\)

を後退代入を利用して解\(\mathbf{x}\)を求めます。

以上がLU分解を利用して連立一次方程式を解く方法です。
数値計算のルーチンは大きく2つのステップに分かれており、

  1. 行列\(A\)のLU分解を求める(ルーチン dgetrf)
  2. LU分解された行列\(A\)を用いて連立一次方程式を解く(ルーチン dgetrs)

というステップです。
これは、例えば問題
\(
\begin{equation}
A\mathbf{x}=\mathbf{b}
\end{equation}
\)


\(
\begin{equation}
A\mathbf{x}=\mathbf{b’}
\end{equation}
\)

の右辺だけが変わる複数の問題を解きたい場合なんかに便利です。この問題の場合、行列\(A\)は変わらないため、LU分解した結果を両方の問題に流用することが出来るのです。

プログラム


プログラムは以下の通りです。
いつもと同じように、ワーク配列を減らす為だけに導入したルーチンを挟んでいます。

program main
  implicit none
  integer::i,N
  double precision,allocatable::a(:,:),b(:),x(:)
  integer,allocatable::ipiv(:)
 
  N=3
  allocate(a(1:N,1:N))
  allocate(ipiv(1:N),b(1:N),x(1:N))
  a(1,1:N)=(/1d0,3d0,5d0/)
  a(2,1:N)=(/0d0,3d0,1d0/)
  a(3,1:N)=(/6d0,2d0,5d0/)
  b(1:N)=(/33d0,10d0,66d0/)

  do i=1,N
     write(6,'(3f10.5)')a(i,1:N)
  enddo

  call LUfact(N,a,ipiv)
  call axbsolve(N,a,ipiv,b,x)

  write(6,*)"---- solution ----"
  do i=1,N
     write(6,'(2f10.5)')x(i)
  enddo
 
  stop
end program main

subroutine LUfact(N,LU,ipiv)
  implicit none
  integer,intent(in)::N
  double precision,intent(inout)::LU(1:N,1:N)
  integer,intent(out)::ipiv(1:N)  

  ! LU factorization for lapack
  ! Overwrite matrix LU by factorized LU matrix
 
  integer::m,lda,info

  m=N
  lda=N
  call dgetrf(m,N,LU,lda,ipiv,info)
  if(info.ne.0)then
     write(6,*)"**error at LUlapack"
     stop
  endif
 
  return
end subroutine LUfact

subroutine axbsolve(N,LU,ipiv,b,x)
  implicit none
  integer,intent(in)::N
  integer,intent(in)::ipiv(1:N)
  double precision,intent(in)::LU(1:N,1:N),b(1:N)
  double precision,intent(out)::x(1:N)
  !
  ! Solve simultaneous equations by Lapack
  !
  double precision,allocatable::bl(:,:)
  integer::nrhs,lda,ldb,info
  character(1)::trans  
 
  nrhs=1
  trans='N'
  allocate(bl(1:N,1:nrhs))  
  bl(1:N,1)=b(1:N)
  lda=N
  ldb=N
  call dgetrs(trans,N,nrhs,LU,lda,ipiv,bl,ldb,info)
  if(info.ne.0)then
     write(6,*)"**error at LUlapack"
     stop
  endif
 
  x(1:N)=bl(1:N,1)  

  return
end subroutine axbsolve

上のプログラムをlapackと一緒にコンパイルして動かすと、端末上で

> ./a.out
   1.00000   3.00000   5.00000
   0.00000   3.00000   1.00000
   6.00000   2.00000   5.00000
 -------------
   7.00000
   2.00000
   4.00000
>

という結果が得られるかと思います。
これは、連立一次方程式
\(
\begin{align}
\left(
\begin{array}{ccc}
1& 3& 5 \\
0& 3& 1 \\
6& 2& 5
\end{array}
\right)
\left(
\begin{array}{ccc}
x \\
y \\
z
\end{array}
\right)
=
\left(
\begin{array}{ccc}
33 \\
10 \\
66
\end{array}
\right)
\end{align}
\)
を解いた結果です。
解析解(\(x=7, y=2, z=4\))は、例えばwolframを利用して求められます。
{{1,3,5},{0,3,1},{6,2,5}}.{x,y,z}=={33,10,66} –wolfram alpha


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です