Gauss-Seidel法と二次元Laplace方程式の数値解法について
これはなに
おはこんばんにちはてれじょんです。9月に入ると陸の孤島ではクソデカ積乱雲くんや激高気温くんが多く観測され、ようやく夏という風情が出てきました(まじでなに?)。8月もまあまあ暑かったですが、陸の孤島は9月もクソ暑くなりそうなので泣いています。太平洋高気圧、嘘だよな……?
さて今回はGauss-Seidel法による連立方程式の反復解法について書いて、さらにそれを用いてLaplace方程式を数値的に解いていきたいと思います。私が参考にした本ではLaplace方程式の数値解を求めるときにSOR法を用いていましたが、収束条件の証明をよく理解していない(したがって最適なパラメーターのとり方がよく分かってない)のでGauss-Seidel法を使うことにします。
Gauss-Seidel法 is 何
前回は、名前がよく似たGauss-Jordan法を使って連立方程式を解きました。詳しい説明は前回の記事に譲りますが、これは手計算と同じように行基本変形を繰り返していって解を求める方法でした。一方ここでは行列の変形は行わず、未知変数と同じ数の漸化式をつくり、適当な初期値から始めて逐次値を代入し、誤差項がある値より小さくなったら計算を打ち切るという方法で近似解を求めていきます。あくまで求めているのは近似解なので、これを反復法と言ったりもするらしいです。前回扱ったのは直接法です。
具体的な操作を見ていきましょう。まず一般的に、次実正方行列と行縦ベクトルを用いてと書ける元一次連立方程式の解き方を考えます。適当な初期値を作り、漸化式
に上からに代入していくことで順次を得ます。何度か反復を行うととなるので、十分が小さくなったときに収束したとみなして解を得ます。これがGauss-Seidel法です。
は?????????
おそらく皆さん「これでなんで収束するって言えるの?」と思ったでしょう。私も思いました。色々調べてみたりもしましたが、大抵の場所では収束性についてさして言及していなかったのでちょっと丁寧に書いてみたいと思います。
当然この方法は一般の連立方程式に対して収束するものではないので、解を得るためにはいくつかの条件が必要です。そこで出てくるのが以下の2つの定理です。
とする。このとき、スペクトル半径に対してならば反復法はの一意解に収束する。
【証明】
ならばは逆行列をもち、である。
したがってより、は一意解を持つ。それをとする。このとき上記の反復法は
となるので、題意は示された。
が狭義優対角行列または既約優対角行列ならば、Gauss-Seidel法は任意の初期値に対しての解に収束する。
【証明】
を
で定義する。このとき、Gauss-Seidel法の漸化式より
を得る。故に、よりを示せばよく、そのために固有値問題
を考え、を示すことにする。
(i)狭義優対角のとき
となるようなを取る。
このとき、の第成分を考えて
ここで、を仮定すると、三角不等式より
となって、が狭義優対角行列であることに反する。
(ii)既約優対角のとき
狭義優対角のときと同様にをとり、とする。
として矛盾を導く。
・のとき
このとき、(i)と全く同様にして
が言えるので既約優対角性に反する。
・そうでないとき
このとき
が成立する。ところで、ならばである。実際、もしとなるようなが存在するとすれば
なので、(1)より
となって、既約優対角性に反する。したがって、
が分かり、が既約であることに反する。以上よりであり、題意は示された。
証明長すぎん???tex打ちがめんどい
まあ無事が示されたので良しとしましょう。これより、狭義優対角または既約優対角行列に対してはGauss-Seidel法が使えることが分かりました。でもまだなんか胡散臭いような気がする。
二次元Laplace方程式を解く(理論の説明)
さて、ようやく本題です。ここではDirichlet境界条件下におけるLaplace方程式を数値的に解いていこうと思います。簡単のため、の正方形領域で考えることとし、境界条件は
とします。当然計算機に解かせるには離散化しなきゃいけません。正方形領域を方向に等分、方向に等分して、その分点をそれぞれ、とおきます。便宜上、と書いて、分点の間隔をと置きます。このとき、をTaylor展開することで
を得ます。片々足してよしなにしてやれば
を得ます。二次以上の項は無視して良さそうなので無視して、
ここで
とおけば、上の式は
となります。だいぶ見通しが良くなりましたね。これと境界条件をあわせれば、全体で本の連立方程式を成します。未知変数は個なのであとはこの連立方程式を解けば良いことが分かります。
解くぜ!
実装しましょう。解くためにを一本のベクトルに格納してみます。うまいこと格納してやれば実装も楽になると思うのですが、いい方法が思い浮かばなかったので雑に格納してみます。上で得た本の連立方程式をと書いて
と突っ込んでみます。はの第成分になっています。ただし、境界条件上のの値は分かっているのでそこにははじめから値を入れておいて、残りのところには初期値として0を入れます。と置いてみると、の第成分(ただしの第成分は未知とします)は
となります。こいつをGauss-Seidel法の漸化式とみなせば実装できますね。がの条件を満たすことの証明は省略します。(多分)サクッとできるので。
んで、実装したコードは以下のようになりました。
module globals real(8),parameter :: pi=3.141592653589793d0 end module globals module subprog use globals implicit none contains function l(i,j,n) result(k) integer,intent(IN) :: i,j,n integer k k=i+1+(n+1)*j end function l end module subprog program main use globals use subprog implicit none integer :: n,m,i,j,ln,fo=11 real(8),allocatable :: x(:) real(8) p,q,dx,dy,ep,tmp,t1,t2 ep=0.000001d0 write(*,*)"input step n(x-axis),m(y-axis)" read(*,*)n,m allocate(x((n+1)*(m+1))) x(1:(n+1)*(m+1))=0.0d0 p=dble(n)**2/2.0d0/(dble(n)**2+dble(m)**2) q=dble(m)**2/2.0d0/(dble(n)**2+dble(m)**2) dx=1.0d0/dble(n) dy=1.0d0/dble(m) do i=0,n x(l(i,0,n))=sin(2.0d0*pi*dx*dble(i)) enddo call cpu_time(t1) 1 do i=1,n-1 do j=1,m-1 ln=l(i,j,n) x(ln)=p*(x(ln+1)+x(ln-1))+q*(x(ln+n+1)+x(ln-n-1)) enddo enddo do i=1,n-1 do j=1,m-1 ln=l(i,j,n) tmp=abs(x(ln)-p*(x(ln+1)+x(ln-1))-q*(x(ln+n+1)+x(ln-n-1))) if(tmp>=ep)then goto 1 else if(i/=n-1 .or. j/=m-1)then CYCLE endif endif enddo enddo call cpu_time(t2) write(*,*)"cal_time=",t2-t1 open(fo,file="output.d") do i=0,n do j=0,m ln=l(i,j,n) write(fo,*)dx*dble(i),dy*dble(j),x(ln) enddo write(fo,*)" " enddo close(fo) end program main
解析結果
n=m=100としたときの解析結果は以下のようになりました。
これの計算時間はだいたい0.21秒くらいだったので、概ねO((nm)^2)よりやや少ないくらいで計算できてると思います。(私のノートPCではO(10^8)の計算時間が0.25秒くらいでした)。
最後に
打ちがめんどい大変でした。記事の後半雑になってアレですがまあ良いでしょう。誤植とかおかしいところとかあったら言ってください。多分あります。自動車学校に収容されたので進捗のペースが落ちていますが、のんびりやっていきたいです。