Cyclic Reductionでポアソン方程式を解く【コードあり】
Cyclic Reductionの"くりっくり"の部分
アブストラクト
- ポアソン方程式を直交格子上で離散化すると、三重対角行列を係数行列とする線形連立方程式になる。
- 三重対角行列を係数行列とする線形連立方程式の解法としてCyclic Reduction法がある。
- 今回は一次元のポアソン方程式を対象とする。この場合Cyclic Reduction法は不等間隔格子にも対応できる。
- pythonコードの全体は最後にあります。
ポアソン方程式を不等間隔格子上で離散化する
今回解きたいのは一次元のポアソン方程式
です。

これを図のような一次元の格子上で離散化します。変数は格子中央で定義されます。格子幅は等間隔でなくともよいです。格子幅を
左辺を式変形すると、
となります。
境界条件
今回は境界で
すると
となります。
(補足)
たとえば境界上で
Cyclic Reduction法の準備 ~三重対角行列~
書き下すと、こうなります↓↓
行列形式で書くと係数行列が三重対角行列になります。
後のために式変形をしておきます。
そうすると上式は、行列の形
R= np.zeros(im+2)
L= np.zeros(im+2)
R[1:im ]=(1./dx2[1:im]/dx1[1:im ])/(-1./dx2[0:im-1]/dx1[1:im ]-1./dx2[1:im ]/dx1[1:im ])
L[2:im+1]=(1./dx2[1:im]/dx1[2:im+1])/(-1./dx2[1:im ]/dx1[2:im+1]-1./dx2[2:im+1]/dx1[2:im+1])
S[2:im ]=S[2:im ]/(-1./dx2[1:im-1]/dx1[2:im ]-1./dx2[2:im ]/dx1[2:im ])
R[1] =1./dx2[1 ]/dx1[1] /(-2./dx2[0 ]/dx1[1 ]-1./dx2[1 ]/dx1[1 ])
L[im]=1./dx2[im-1]/dx1[im]/(-1./dx2[im-1]/dx1[im]-2./dx2[im]/dx1[im])
S[1] =S[1] /(-2./dx2[0 ]/dx1[1 ]-1./dx2[1 ]/dx1[1 ])
S[im]=S[im]/(-1./dx2[im-1]/dx1[im]-2./dx2[im]/dx1[im])
Cyclic Reduction法(前半)
Cyclic Reduction法(以下CR法)は次のようなアルゴリズムです。(この記事↓↓を参考にしています)
まず!CR法は格子数が
となり、
となり、元の形にできます。係数と右辺は新たに置きなおしています。
すべての偶数番目の
が得られます。
前半おわり。
N=int(np.log(float(im+1))/np.log(2.))
for n in range(1,N-1+1):
dm=2**(n-1)
for m in range(2**n , im+1-2**n+1 , 2**n):
dc=L[m]*R[m-dm]-1.+R[m]*L[m+dm]
S[m]=(L[m]*S[m-dm]-S[m]+R[m]*S[m+dm])/dc
R[m]=( R[m]*R[m+dm])/dc
L[m]=(L[m]*L[m-dm] )/dc
Cyclic Reduction法(後半)
得られた
具体的には、(2)のひとつ前の段階で
次の段階では、ここまで得られた3つの値から、それぞれの
for n in range(N-1,0,-1):
dm=2**(n-1)
for m in range(2**n , im+1-2**n+1 , 2**(n+1)):
S[m-dm]=S[m-dm]-L[m-dm]*S[m-2*dm]-R[m-dm]*S[m]
S[m+dm]=S[m+dm]-L[m+dm]*S[m] -R[m+dm]*S[m+2*dm]
p[1:im+1]=S[1:im+1]
# 境界条件
p[0]=-p[1]
p[im+1]=-p[im]
検証
テスト問題として、なんでもいいですが、簡単に、
としてやってみます。
となるはず。
計算条件
- 格子数
(領域外含めるとim=2^5-1=31 )33 - 領域サイズ
L=2\pi - 一旦、等間隔格子
\Delta x_1=\Delta x_2=L/im
結果
解析解と併せて描いています↓↓

うまくできていそうです。
検証:不等間隔格子の場合
CR法のイイトコロは格子が等間隔でなくてもよいことと、処理がそこそこはやい(らしい↓↓)ことです。
高速なポアソン方程式解法として名高い、高速フーリエ変換やコサイン変換を使うものがあります↓↓
が、これは等間隔格子でのみ適用可能です。その点CR法は不等間隔でもいいというのがいい。計算速度については今回は調べませんが、SOR法とかよりはさすがにはやそうな気がします。
上のテスト問題を格子幅を変えてやってみます。
ケース1
等間隔(
境界近くだけ
# 不等間隔格子(ケース1)------------------------------
dx1[:]=Lx/float(im)
dx1[3:im-1:3]=2.*Lx/float(im)
dx1[4:im:3]=0.5*Lx/float(im)
dx1[5:im:3]=0.5*Lx/float(im)
結果

できていそうです。少し精度悪いか?格子数を増やせば大丈夫...。間違いがあれば教えてください。
ケース2
領域の端で大きめの格子で、中央に近づくにつれて線形に細かくなっていくようにします。
- 端 :
\Delta x_1(1)=\Delta x_1(im)=1.7L/im - 中央:
\Delta x_1((im+1)/2)\risingdotseq 0.25L/im
# 不等間隔格子(ケース2)------------------------------
dmax=1.7*Lx/float(im) # L/im < dmax < 2L/(im+1)
dmin=2./float(im-1)*(Lx-float(im+1)/2.*dmax)
dx1[1]=dmax
dx1[int((im+1)/2)]=dmin
for i in range(2,int((im+1)/2)):
dx1[i]=(dmin-dmax)/float((im+1)/2-1)*float(i-1)+dmax
dx1[int((im+1)/2+1):im+1:1]=dx1[int((im+1)/2-1):0:-1]
dmaxを自分で決めればあとは勝手に決まるようにしています。L/im < dmax < 2L/(im+1)の範囲でないと、dminがdmaxより大きくなったりdminが負の値になったりするので注意。
結果

できていそうです。
おしまい(Unfortunately)
うまく実装できていそうでよかったです。短いコードで、特別なライブラリも使わず実装できるかっこいいアルゴリズムだと思います。
残念ながら二次元とか三次元のポアソン方程式をCR法で解こうとなると、難しいようです。二次元の場合は調べるといくつか出てきますが、等間隔格子の場合ばかりです。等間隔ならFFTでいいからなあ。本記事で言うところの
なので、
コード全体
import numpy as np
import matplotlib.pyplot as plt
im=32-1
Lx=2.*np.pi
dx1=np.zeros(im+2)
dx2=np.zeros(im+2)
x =np.zeros(im+2)
p =np.zeros(im+2)
# 等間隔格子----------------------------
dx1[:]=Lx/float(im)
# -------------------------------------
# # 不等間隔格子(ケース1)-------------------
# dx1[:]=Lx/float(im)
# dx1[3:im-1:3]=2.*Lx/float(im)
# dx1[4:im:3]=0.5*Lx/float(im)
# dx1[5:im:3]=0.5*Lx/float(im)
# # ----------------------------------------
# # 不等間隔格子(ケース2)------------------------------
# dmax=1.7*Lx/float(im) # L/im < dmax < 2L/(im+1)
# dmin=2./float(im-1)*(Lx-float(im+1)/2.*dmax)
# dx1[1]=dmax
# dx1[int((im+1)/2)]=dmin
# for i in range(2,int((im+1)/2)):
# dx1[i]=(dmin-dmax)/float((im+1)/2-1)*float(i-1)+dmax
# dx1[int((im+1)/2+1):im+1:1]=dx1[int((im+1)/2-1):0:-1]
# #--------------------------------------------------------
dx1[0]=dx1[1]
dx1[im+1]=dx1[im]
x[0]=-0.5*dx1[0]
for i in range(1,im+2):
x[i]=x[i-1]+0.5*(dx1[i-1]+dx1[i])
dx2[0:im+1]=x[1:im+2]-x[0:im+1]
S= np.zeros(im+2)
for i in range(0,im+2):
S[i]=np.cos(x[i])+np.sin(x[i])
S[0]=0.
S[im+1]=0.
R= np.zeros(im+2)
L= np.zeros(im+2)
R[1:im ]=(1./dx2[1:im]/dx1[1:im ])/(-1./dx2[0:im-1]/dx1[1:im ]-1./dx2[1:im ]/dx1[1:im ])
L[2:im+1]=(1./dx2[1:im]/dx1[2:im+1])/(-1./dx2[1:im ]/dx1[2:im+1]-1./dx2[2:im+1]/dx1[2:im+1])
S[2:im ]=S[2:im ]/(-1./dx2[1:im-1]/dx1[2:im ]-1./dx2[2:im ]/dx1[2:im ])
R[1] =1./dx2[1 ]/dx1[1] /(-2./dx2[0 ]/dx1[1 ]-1./dx2[1 ]/dx1[1 ])
L[im]=1./dx2[im-1]/dx1[im]/(-1./dx2[im-1]/dx1[im]-2./dx2[im]/dx1[im])
S[1] =S[1] /(-2./dx2[0 ]/dx1[1 ]-1./dx2[1 ]/dx1[1 ])
S[im]=S[im]/(-1./dx2[im-1]/dx1[im]-2./dx2[im]/dx1[im])
N=int(np.log(float(im+1))/np.log(2.))
for n in range(1,N-1+1):
dm=2**(n-1)
for m in range(2**n , im+1-2**n+1 , 2**n):
dc=L[m]*R[m-dm]-1.+R[m]*L[m+dm]
S[m]=(L[m]*S[m-dm]-S[m]+R[m]*S[m+dm])/dc
R[m]=( R[m]*R[m+dm])/dc
L[m]=(L[m]*L[m-dm] )/dc
for n in range(N-1,0,-1):
dm=2**(n-1)
for m in range(2**n , im+1-2**n+1 , 2**(n+1)):
S[m-dm]=S[m-dm]-L[m-dm]*S[m-2*dm]-R[m-dm]*S[m]
S[m+dm]=S[m+dm]-L[m+dm]*S[m] -R[m+dm]*S[m+2*dm]
p[1:im+1]=S[1:im+1]
# 境界条件
p[0]=-p[1]
p[im+1]=-p[im]
fig1=plt.figure(figsize=(12,6))
ax1= fig1.add_subplot()
ax1.plot(x,p,'.',label='CR sol.')
x=np.linspace(0,Lx,10000)
print(x)
ax1.plot(x,-np.sin(x)-np.cos(x)+1.,label='analytical sol.')
ax1.legend()
# plt.savefig('CR.png')
plt.show()
Discussion