見出し画像

【数値計算】 ヤコビ法(1):ポアソン方程式の計算方法

ポアソン(Poisson)方程式の計算方法について,
シンプルな反復法による解法,ヤコビ(Jacobi)法による計算について考えよう.


Poisson's equation

$$
\Delta u(\bm{r}) = -f(\bm{r})
$$

この方程式は,電荷周りのクーロンポテンシャルや,質点周りの重力ポテンシャルの計算などで扱われるものだ.

もし,$${f(\bm{r}) = 0}$$なら,$${\Delta u(\bm{r}) = 0}$$ であり,これは,ラプラス方程式(Laplace's equation)として知られる.

この時,境界条件(Boundary condition)によって,解が決定される.
境界条件は,端点の値を直接与える,ディレクレ境界条件Dirichlet boundary condition)や,端点の微分値を与える,ノイマン境界条件Neumann boundary condition)が主に用いられる.

これを数値計算で解こう.
解くためには,ラプラシアン(ラプラス作用素,Laplace operator)
$${ \Delta = \nabla^{2} }$$ の離散化(Discretization)が必要だ.

Discretization

コンピュータで計算させるために,連続的なアナログ値を離散的なデジタル値にする,細かく測定点もしくは,予測点を置くようなものだ(標本化もしくは,samplingとも言われる).まずはどのように点を置くかで,離散化の表現は変わってくる.

余談だが,人間の聴覚や視覚の解像度がそれぞれわかっているので,これの倍くらいで,等間隔に標本化しておけば,人間にはアナログな現実と,デジタル空間の差異はなくなる(視覚と聴覚に関してはね).
もっと言えば,量子物理学によれば,現実の空間や時間はプランクスケールで離散化されている.
詳しくは,標本化定理(サンプリング定理Nyquist-Shannon sampling theorem)あたりを参考にしてほしい.

今回行う,空間の離散化には,デカルト直交座標系で等間隔に標本化する.
簡単に,1次元格子もしくは,2次元の正方格子をここでは取ろう.

1次元格子

格子間隔$${h}$$を持つ,1次元格子において,離散化を行おう.

1次元の座標軸を$${x}$$軸と取れば,
原点$${x = 0}$$から$${i}$$番目の格子点の座標は$${x = ih}$$と表せる.

ここで,$${i}$$番目の格子点上に電荷もしくは質点があるとして考えてみよう.この条件は,$${f(ih) \neq 0}$$ のように書けるだろう.

1次元格子,格子間隔$${h}$$,境界条件$${f(0) =  f(n+1) = 1}$$としておく.
簡単のために,関数の引数についてのみ,$${h = 1}$$のように$${h}$$を省略して表している.

数値計算では,オレンジのラインで描かれたような分布$${u(x)}$$を求めたい.これを実現するには,以下のように端点を除いた内部$${ i \in [1,n]}$$の格子点について離散化する:

$$
\frac{u(i - 1) - 2u(i) + u(i+1)}{h^{2}} + \mathscr{O}(h^4) = -f(i), i \in [1,n]
$$

$${ \mathscr{O}(h^4) }$$ は誤差のオーダーを表す.
ランダウの記号,一番寄与の大きい部分を書くもの)

この関係は以下のように導出する:

1階微分は,前進差分(Forward Differentiation) 
もしくは,後退差分(Backward Differentiation
さらに,中央差分(Central Differentiation) のように離散化できる.

前進差分:
$${\frac{d}{dx} u(x) = \frac{u(i + 1) - u(i)}{h} + \mathscr{O}(h^2)}$$

後退差分:
$${\frac{d}{dx} u(x) = \frac{u(i) - u(i-1)}{h} + \mathscr{O}(h^2)}$$

中央差分:
$${\frac{d}{dx} u(x) = \frac{u(i+1) - u(i-1)}{2h} + \mathscr{O}(h^4)}$$

$${ \mathscr{O}(h^2), \mathscr{O}(h^4)}$$ はそれぞれ$${h^2, h^4}$$に比例する誤差が生じていることを意味する.

これを組み合わせれば,ラプラシアン(2階微分)の演算は,
$${\mathscr{O}(h^4)}$$の誤差を持つ形で,離散化されるだろう.

マクローリンもしくは,テイラー展開を知っているのであれば,証明は簡単だろう.

離散化した結果をもう一度書いておく:

$$
u(i - 1) - 2u(i) + u(i+1) + \mathscr{O}(h^4) = - f(i) h^{2}
$$

これをさらに変形して,
$${u(i)}$$がその周囲の$${u}$$とその点の$${f(i)}$$で決まるように表現しておこう:

$$
u(i) = \frac{u(i+1) + u(i - 1) + f(i) h^2}{2} + \mathscr{O}(h^4)
$$

$${f(i) = 0}$$なら,周囲$${u}$$の平均値によって$${u(i)}$$が得られるように見える.
しかし,$${u(i)}$$が決まれば,また,その周りの$${u(i+1)}$$なども変化させなくてはならない.先に触れておくと,ヤコビ法ではこの繰り返し,反復計算を収束するまで実行して,解を得る.

ちなみに,応用になるが,離散化したPoisson方程式を行列表示すると以下のようになる:

$$
A\bm{u} = \bm{f}
$$

行列$${A}$$は3重対角行列tri-diagonal matrix)である.
(tri)diag(1, -2, 1)と書かれることもある.

2次元格子

1次元の場合と同じように,直交する二つの向きに関して等間隔$${h}$$で離散化し,2次元の正方格子を得る.

2次元正方格子による2次元空間の離散化.

二次元の場合には,Poisson 方程式は以下のように離散化される:

$$
u(i+1, j) + u(i-1, j) + u(i, j+1) + u(i, j-1) - 4 u(i,j) + \mathscr{O}(h^4) = - f(i,j) h^2
$$

この場合も同様に,誤差は$${\mathscr{O}(h^4)}$$である.

これも同様に,$${u(i, j)}$$についての式に変形しておこう:

$$
u(i, j) = \frac{u(i+1, j) + u(i-1, j) + u(i, j+1) + u(i, j-1) + f(i, j) h^2}{4} + \mathscr{O}(h^4)
$$

やはり,2次元の場合も同様に,
隣接する格子点の$${u}$$の和と,その格子点の$${f(i,j)}$$の値から得られる.
$${f(i,j) = 0}$$なら,隣接する格子点の$${u}$$の平均値から得られる.

この計算は画像処理などで,ラプラシアンフィルタなどとして用いられたりもしている.この場合,格子点の近傍をどのように取るか(隣接する格子点はどれを取るか,どの格子点同士がつながっているとみるか)によって,フィルタとなる行列は異なる.これは,グラフ理論の一部として一般化されていて,ラプラシアン行列も合わせてみてほしい.


Jacobi method (a.k.a. Jacobi iteration method)

すでに少し触れたが,Jacobi 法では反復計算を使って,各格子点の$${u(i,j)}$$が収束する(ほとんど変化しなくなる)まで計算を行う.

反復する計算式を1次元と2次元のそれぞれについて,改めて見ておこう:

1次元

$$
u(i) = \frac{u(i+1) + u(i - 1) + f(i) h^2}{2} + \mathscr{O}(h^4)
$$

2次元

$$
u(i, j) = \frac{u(i+1, j) + u(i-1, j) + u(i, j+1) + u(i, j-1) + f(i, j) h^2}{4} + \mathscr{O}(h^4)
$$

3次元以降も同じような計算で行われると推論できるだろう.

反復計算を行うにあたって,計算を終了させる条件,収束条件が必要だ.

元の各格子点の$${u}$$について,
上記のラプラシアン行列の計算から得られた
新しい値を$${u_{\text{new}}(i)}$$とおいて,
次のような各格子点の誤差を表す式を使おう(例として1次元をとった):

$$
e(i) = u_{\text{new}}(i) - u(i), ||e||_{\infty} = \text{max}|e(i)|
$$

各格子点のうち,最大の誤差を持つものを$${||e||_{\infty}}$$と呼んで定義している.これが,こちらで定めた,許容範囲(tolerance)以下になった際に計算を終了させる.

ヤコビ法のアルゴリズム

1次元の場合について,そのアルゴリズムを書いておこう:


// 収束するか,最大反復回数(iter_max)に至るまで計算
for iter in range(iter_max):

  // 内部の各格子点について,$${u_{\text{new}}(i)}$$を計算
  // 計算量 $${\mathscr{O}(n^{d})}$$
  for i in range(1,n):
      $${u_{new}(i) \leftarrow \frac{u(i+1) + u(i - 1) + f(i) h^2}{2}}$$

  // 内部の各格子点について,誤差 $${e(i)}$$ を計算
  // 計算量 $${\mathscr{O}(n^{d})}$$
  for i in range(1,n):
     $${e(i) = u_{\text{new}}(i) - u(i)}$$

  // 誤差の最大値を計算
  $${ ||e||_{\infty} = \text{max}|e(i)| }$$

  // 誤差が許容範囲未満なら反復終了
  if. $${ ||e||_{\infty} }$$ < tolerance:
      break


全体の計算量は,Jacobi法がどの程度で収束するかにかかっている.

tolerance をどの程度に設定するかにもよるが,
ラプラシアンの計算は,1回の計算で,自身の持つ$${u(i)}$$の影響を隣接する格子点に伝播させるだけなので,端から端の一番遠い格子点同士の影響が届くまで,反復計算が必要だと考えられる.
つまり,反復計算は少なくとも,$${n^{d}}$$回は必要になるだろう.

計算量 $${\mathscr{O}(n^{d})}$$ のラプラシアンの計算を$${n^{d}}$$回必要になるので,
全体の計算量は,$${\mathscr{O}(n^{2d})}$$ 程度になるだろう.

行基本変形を用いた,ガウスの消去法(掃き出し法,Gauss elimination)は$${\mathscr{O}(n^{3d})}$$の計算回数が必要になるので,それよりは高速であることがわかる.

反復計算が収束する理由

なぜ,Jacobi 法などの反復計算が収束するのかについて,触れておこう.
例として,1次元の場合について考えながら,簡単に示しておく.

$$
A\bm{u} = \bm{f}
$$

ここで,行列$${A}$$は1次元の場合には,
以下の三重対角行列になる:

$$
A = 
\begin{pmatrix}
-2 & 1  &  & & &\\
1 & -2 & 1  & & & \\
  & 1 & -2 & 1  & & \\
 &  & \ddots & & &\\
& & & 1 & -2 & 1 \\
& & &  & 1 & -2  
\end{pmatrix}
$$

$${ A = D + N }$$ のように対角行列$${D}$$と,非対角行列$${N}$$ に分解して,以下のように変形しよう:

$$
\bm{u} = - D^{-1} (N \bm{u} + \bm{f})
$$

ここで基本的な反復法では,これを以下のようにおいて,繰り返し計算を行う:

$$
\bm{u}^{(k)} = - D^{-1} (N \bm{u}^{(k-1)} + \bm{f})
$$

$${k \in \mathbb{N}}$$ は反復数のインデックスである.

先の等式は,1次元のJacobi法での反復計算では,以下の式のことである:

$$
u_{\text{new}}(i) = \frac{u(i+1) + u(i - 1) + f(i) h^2}{2} + \mathscr{O}(h^4)
$$

これを行列 $${ M = -D^{-1} N }$$を使って表現しておこう:

$$
\bm{u}_{\text{new}} = M \bm{u} - D^{-1} \bm{f} h^2 + \mathscr{O}(h^4)
$$

行列$${M}$$はある格子点について,周りの点の値を取ってきて,平均値を求める演算をしている.
1次元の場合は以下のような三重対角行列になる:

$$
M =
\frac{1}{2} 
\begin{pmatrix}
0 & 1  &  & & &\\
1 & 0 & 1  & & & \\
  & 1 & 0 & 1  & & \\
 &  & \ddots & & &\\
& & & 1 & 0 & 1 \\
& & &  & 1 & 0  
\end{pmatrix}
$$

ここで,誤差ベクトル$${\bm{e}}$$について考えれば,

$$
\begin{equation*}
\begin{split}
\bm{e} &= \bm{u}_{\text{new}} - \bm{u} \\
&=  M \bm{u} - D^{-1} \bm{f} h^2 + \mathscr{O}(h^4) - \bm{u} \\
&= (M - I) \bm{u} - D^{-1} \bm{f} h^2 + \mathscr{O}(h^4)
\end{split}
\end{equation*}
$$

ここで,$${I}$$は単位行列である.

$${k+1}$$回反復後の誤差$${\bm{e}^{(k+1)}}$$を考えよう.$${\bm{e}^{(k)}}$$や,それ以前の誤差との関係はどうなるだろうか?

$$
\begin{equation*}
\begin{split}
\bm{e}^{(k+1)} &= \bm{u}^{(k+1)} - \bm{u}^{(k)} \\
&=M \bm{u}^{(k)} -D^{-1} \bm{f} h^2 - (M \bm{u}^{(k-1)} -D^{-1} \bm{f} h^2) + \mathscr{O}(h^4) \\
&= M ( \bm{u}^{(k)} - \bm{u}^{(k-1)} ) + \mathscr{O}(h^4) \\
&= M \bm{e}^{(k)} + \mathscr{O}(h^4) \\
& \cdots \\
&= M^{k} \bm{e}^{(1)} + \mathscr{O}(h^4)
\end{split}
\end{equation*}
$$

十分大きな$${k}$$において,
$${||\bm{e}^{(k+1)}||_{2} < \|\bm{e}^{(0)}||_{2}}$$
になれば収束することがわかる.
ここで,これを不等式の関係を用いながら,変形していく:

$$
\begin{equation*}
\begin{split}
||\bm{e}^{(k+1)}||_{2}
&=  || M \bm{e}^{(k)} + \mathscr{O}(h^4)||_{2}  \\
&\leq || M \bm{e}^{(k)} ||_{2} \\
&= (M \bm{e}^{(k)})^T M \bm{e}^{(k)} \\
& \cdots \\
&\leq || M^{k} \bm{e}^{(1)} + \mathscr{O}(h^4)||_{2} \\
& \leq || M^{k} \bm{e}^{(1)} ||_{2} \\
&= (M^{k} \bm{e}^{(1)})^T M^{k} \bm{e}^{(1)} \\
\end{split}
\end{equation*}
$$

煩雑ですが,もうちょっと頑張りましょう.

もっと一般化できますが,今回は,ポアソン方程式を例に考えています.
そこで,行列$${M = -D^{-1} N}$$は対角成分がゼロとなっている,三重対角行列でであり,対称行列です.

実対称行列$${M}$$は対角化できる.
ここで,固有値$${\lambda_{p}}$$に対応する固有ベクトルを$${\bm{x}_{p}}$$として,

$$
M \bm{x}_{p} = \lambda_{p} \bm{x}_{p}
$$

さらに,対称行列の固有ベクトルは互いに直交する性質があります.

$$
(\bm{x}_{p})^{T} \bm{x}_{q} = \delta_{p,q}
$$

この固有ベクトル $${ \bm{x}_{p} }$$を用いて,誤差$${\bm{e}^{(k)}}$$ を展開しましょう:

$$
\bm{e}^{(k)} = \sum_{p = 1}^{n} a^{(k)}_{p} \bm{x}_{p}
$$

これより,

$$
M \bm{e}^{(k)} = \sum_{p = 1}^{n} a^{(k)}_{p} \lambda_{p} \bm{x}_{p}
$$

これを先の $${ \bm{e}^{(k + 1)} }$$ の関係式に代入すれば,

$$
\begin{equation*}
\begin{split}
||\bm{e}^{(k+1)}||_{2}
&\leq || M \bm{e}^{(k)} ||_{2} \\
&= (M \bm{e}^{(k)})^T M \bm{e}^{(k)} \\
&= \left(\sum_{p = 1}^{n} a^{(k)}_{p} \lambda_{p} \bm{x}_{p}\right)^{T}
\left(\sum_{q = 1}^{n} a^{(k)}_{q} \lambda_{q} \bm{x}_{q}\right)
\\
&= \sum_{p,q = 1}^{n} a^{(k)}_{p} a^{(k)}_{q} \lambda_{p} \lambda_{q} \delta_{p, q}
\\
&= \sum_{p = 1}^{n} (a^{(k)}_{p} \lambda_{p} )^2
\end{split}
\end{equation*}
$$

さらに,反復を考えれば,
$${ M^{k} \bm{x}_{p} = (\lambda_{p})^{k}  \bm{x}_{p}  }$$
になるから,容易に以下の関係が得られる:

$$
\begin{equation*}
\begin{split}
||\bm{e}^{(k+1)}||_{2}
&\leq  \sum_{p = 1}^{n} (a^{(k)}_{p} \lambda_{p} )^2 \\
& \cdots \\
&\leq  \sum_{p = 1}^{n} \left( a^{(1)}_{p} (\lambda_{p})^{k} \right)^2
\end{split}
\end{equation*}
$$

ここで,行列

$$
M =
\frac{1}{2} 
\begin{pmatrix}
0 & 1  &  & & &\\
1 & 0 & 1  & & & \\
  & 1 & 0 & 1  & & \\
 &  & \ddots & & &\\
& & & 1 & 0 & 1 \\
& & &  & 1 & 0  
\end{pmatrix}
$$

の具体的な固有値を書いておきます:

$$
\lambda_{p} = \cos{ \left(\frac{p}{n+1} \pi \right) }, p \in [1, n]
$$

この導出については,例えば,以下のサイトを参考にしてください:

重要な条件は,$${|\lambda_{p}| < 1}$$ であることです.
この条件から以下のような計算を進めます.

まず,誤差ノルム$${||\bm{e}^{(k+1)}||_{2}}$$については,

$$
||\bm{e}^{(k+1)}||_{2}
\leq  \sum_{p = 1}^{n} \left( a^{(1)}_{p} (\lambda_{p})^{k} \right)^2
$$

が成り立っています.
さらに,もう1回反復した後の誤差ノルム
$${||\bm{e}^{(k+1)}||_{2}}$$を考えれば,

$$
\begin{equation*}
\begin{split}
||\bm{e}^{(k+2)}||_{2}
&\leq
\sum_{p = 1}^{n} \left( a^{(1)}_{p} (\lambda_{p})^{k+1} \right)^2 \\
&=
\sum_{p = 1}^{n} \left( a^{(1)}_{p} (\lambda_{p})^{k} \right)^2 (\lambda_{p})^2 \\
&< ||\bm{e}^{(k+1)}||_{2} \leq\sum_{p = 1}^{n} \left( a^{(1)}_{p} (\lambda_{p})^{k} \right)^2
\end{split}
\end{equation*}
$$

3行目の不等号 $${<}$$ で先の重要な関係 $${|\lambda_{p}| < 1}$$ を用いた.
余談だが,一般の証明では,行列$${M}$$のスペクトル半径(固有値の中で絶対値が最大となるもの)が1未満であれば収束すると証明されている.

これより,誤差ノルムは反復する度に単調に減少していくことが示され,反復計算は収束することがわかる.


以上が,Jacobi法による数値計算方法である.
何か参考になったなら嬉しく思います.

Appendix: 格子点描画のスクリプト

格子を描くのに用いた python スクリプトをおいておこうと思う.
よく使うものだが,毎回,LLMで作成するのはちょっと面倒(プロンプトを書くのが)なので,ここにおいておく.(これだけのためにGIthubレポジトリ置くのも面倒というw)

import numpy as np
import matplotlib.pyplot as plt

def draw_1d_grid(n):
    """
    1D の格子点(2^n 個)を等間隔で描画する。
    """
    num_points = 2 ** n
    x = np.linspace(0, 1, num_points)
    y = np.zeros_like(x)

    plt.figure(figsize=(8, 2))
    plt.plot(x, y, 'ko-', markersize=4)
    plt.axis('off')
    plt.show()

def draw_2d_grid(n):
    """
    2D の正方格子(各方向に 2^n 個の点、全体で 2^n × 2^n 個の点)を描画する。
    """
    num_points = 2 ** n
    x = np.linspace(0, 1, num_points)
    y = np.linspace(0, 1, num_points)
    X, Y = np.meshgrid(x, y)

    plt.figure(figsize=(6, 6))
    for i in range(num_points):
        plt.plot([x[i]] * num_points, y, 'k-', linewidth=0.5)  # 縦線
        plt.plot(x, [y[i]] * num_points, 'k-', linewidth=0.5)  # 横線
    plt.scatter(X, Y, color='k', s=10)
    plt.axis('off')
    plt.show()

if __name__ == "__main__":
    n = 3  
    draw_1d_grid(n)
    draw_2d_grid(n)


いいなと思ったら応援しよう!