有限要素法による非時間依存2次元シュレディンガー方程式
以前, 有限要素法による非時間依存1次元シュレディンガー方程式で1次元の場合を解いたので, 今回は2次元の場合を解きます.
2次元の時間に依存しないシュレディンガー方程式は
と表せます. ただし,
計算編
1.弱形式を求める
1次元のときと同様に弱形式を求めるところから始めます. 冒頭のシュレディンガー方程式の両辺に, 波動関数の複素共役
式(1.1)左辺第1項は
の関係式を用いて
さらに, ガウスの発散定理
を用いると(
と書けます. Dirichlet境界条件
となります. 1次元の場合と同じような形をしていますね. 従って, 以下の式(1.2)に示す弱形式が得られます.
2.形状関数
1次元のときと同じように形状関数を求めます. 今回は1つの要素を三角形1次要素(各頂点で近似する)とします. ちなみに三角形2次要素では頂点と各頂点の中点でも近似を行います.
本来であれば任意の三角形に対応できるようにするべきですが, 今回は簡単のために三角形の形状を固定します.
領域の分割と形状関数
要素の形状が定まったので, ここからは近似関数を求めていきます. 1次元の場合は直線でしたが2次元の場合は平面になります. 平面の方程式は
で与えられます. まずは
を満たすような関数です. ただし,
式(2.1)を求める方法ですが, 今回は法線ベクトルを利用する方法を用います. 三点
で求められます. 法線ベクトルが平面に平行な任意のベクトルと直交している, つまり内積が0であることを利用すると, 平面の方程式
が得られます.
ここまでを実際に計算してみましょう. 法線ベクトルは
と計算できます. よって, 平面の方程式は
変形すると
が得られます. さらに, この式を式(2.1)と見比べることで基底関数が得られます.
3.離散化と要素行列
前節で求めた形状関数を使って式(1.2)の弱形式を離散化していきましょう.
ただし,
と表せます. よって,
と表せます(
と表せます. ただし,
次に式(1.2)左辺第2項を計算していきます. まずは変数を変換して簡単な形に変えましょう.
と表せますが,
となり, 計算が比較的容易になります. ポテンシャル
となります. ただし
です(上付き添え字省略).
以上より, 式(1.2)左辺第2項は
と書けます.
最後に式(1.2)右辺を計算しましょう. とはいえ, これは左辺第2項と計算の流れが同じなので省略し, 結果だけを以下に示しておきます.
とおくと,
となり, 式(1.2)右辺は
以上より
とおくと, 式(1.2)は
と書けます.
4.全体行列を構成
全体行列を構成します. 最終的に得られる方程式は式(2.2)を考慮すると
という形をしています. これに注意しながら要素行列の各要素を全体行列に対応させましょう.
さらに, 得られた方程式に変分原理を用いることにより
を得ます.
5.境界条件処理
Dirichlet条件
プログラミング編
6.Pythonで2次元有限要素法
import numpy as np
import matplotlib.pyplot as plt
from collections.abc import Callable
import scipy.sparse.linalg as spsl
class SchrodingerFEM2():
DIRAC_CONSTANT: float = 1.054571817e-34
DIRAC_CONSTANT_DOUBLE: float = DIRAC_CONSTANT**2
EIG_VAL_FILE = 'eigenvalue.csv'
EIG_VEC_FILE = 'eigenvector.csv'
n: int #要素数
m: float #質量
matrix_genned: bool #行列は生成済みか否か
x: np.ndarray #節点のx座標
y: np.ndarray #節点のy座標
lx: np.ndarray #各要素のx軸に平行な辺の長さ
ly: np.ndarray #各要素のy軸に平行な辺の長さ
A: np.ndarray #A行列
B: np.ndarray #B行列
eigen_val_vec: list #計算結果を保持する
V: Callable[[float, float], float] #ポテンシャル
def __init__(self, n: int, m: float, x: np.ndarray, y: np.ndarray, V: Callable[[float, float], float]):
self.n = n
self.m = m
self.x = x
self.y = y
self.V = V
self.matrix_genned = False
self.eigen_val_vec = []
if len(x) != n+1:
raise ValueError(f'Array x size should be {n+1} but is {len(x)}.')
if len(y) != n+1:
raise ValueError(f'Array x size should be {n+1} but is {len(y)}.')
self.lx = np.zeros(n)
self.ly = np.zeros(n)
for i in range(n):
self.lx[i] = self.x[i+1]-self.x[i]
self.ly[i] = self.y[i+1]-self.y[i]
mat_size = (n+1)*(n+1)
self.A = np.zeros((mat_size, mat_size), np.float64)
self.B = np.zeros((mat_size, mat_size), np.float64)
# ポテンシャル項の計算用の3×3行列が3つ
V_base = np.array([[[6, 2, 2], [2, 2, 1], [2, 1, 2]], [[2, 2, 1], [2, 6, 2], [1, 2, 2]], [[2, 1, 2], [1, 2, 2], [2, 2, 6]]], np.float64)
def gen_V_element(self, i: int, j: int)->tuple[np.ndarray, np.ndarray]:
Vij0 = self.V(self.x[i], self.y[j])*self.V_base[0]+self.V(self.x[i+1], self.y[j])*self.V_base[1]+self.V(self.x[i], self.y[j+1])*self.V_base[2]
Vij1 = self.V(self.x[i+1], self.y[j+1])*self.V_base[0]+self.V(self.x[i], self.y[j+1])*self.V_base[1]+self.V(self.x[i+1], self.y[j])*self.V_base[2]
Vij0 *= (self.lx[i]*self.ly[j])/120
Vij1 *= (self.lx[i]*self.ly[j])/120
return Vij0, Vij1
def gen_L_matrix(self, i: int, j: int)->np.ndarray:
tmpx = 1/self.lx[i]**2
tmpy = 1/self.ly[j]**2
return np.array([[tmpx+tmpy, -tmpx, -tmpy], [-tmpx, tmpx, 0], [-tmpy, 0, tmpy]], np.float64)
def gen_A_element(self, i: int, j: int)->np.ndarray:
Aij0 = np.zeros((3, 3), np.float64)
Aij1 = np.zeros((3, 3), np.float64)
Vij0, Vij1 = self.gen_V_element(i, j)
Sij = self.lx[i]*self.ly[j]/2
tmpij = (self.DIRAC_CONSTANT_DOUBLE*Sij/(2*self.m))*self.gen_L_matrix(i, j)
Aij0 += (tmpij+Vij0)
Aij1 += (tmpij+Vij1)
Aij = np.zeros((4, 4), np.float64)
#2つの三角要素を事前に合体
for k in range(3):
for l in range(3):
Aij[k][l] += Aij0[k][l]
Aij[k+1][l+1] += Aij1[2-k][2-l]
return Aij
def gen_B_element(self, i: int, j: int)->np.ndarray:
tmpBij = np.array([[2, 1, 1], [1, 2, 1], [1, 1, 2]])
Bij = np.zeros((4, 4), np.float64)
#2つの三角要素を事前に合体
for k in range(3):
for l in range(3):
Bij[k][l] += tmpBij[k][l]
Bij[k+1][l+1] += Bij[2-k][2-l]
Bij *= (self.lx[i]*self.ly[j])/24
return Bij
#全体行列への対応のための配列
idx = [(0, 0), (1, 0), (0, 1), (1, 1)]
def gen_matrix(self)->None:
for i in range(self.n):
for j in range(self.n):
Aij = self.gen_A_element(i, j)
Bij = self.gen_B_element(i, j)
for k in range(4):
for l in range(4):
self.A[i+self.idx[k][0]+(j+self.idx[k][1])*(self.n+1)][i+self.idx[l][0]+(j+self.idx[l][1])*(self.n+1)] += Aij[k][l]
self.B[i+self.idx[k][0]+(j+self.idx[k][1])*(self.n+1)][i+self.idx[l][0]+(j+self.idx[l][1])*(self.n+1)] += Bij[k][l]
#境界条件処理
self.A = self.A[self.n+1:-(self.n+1),self.n+1:-(self.n+1)]
self.B = self.B[self.n+1:-(self.n+1),self.n+1:-(self.n+1)]
for i in range(self.n-1):
self.A = np.delete(self.A, i*(self.n-1), 0)
self.A = np.delete(self.A, i*(self.n-1), 1)
self.A = np.delete(self.A, (i+1)*(self.n-1), 0)
self.A = np.delete(self.A, (i+1)*(self.n-1), 1)
self.B = np.delete(self.B, i*(self.n-1), 0)
self.B = np.delete(self.B, i*(self.n-1), 1)
self.B = np.delete(self.B, (i+1)*(self.n-1), 0)
self.B = np.delete(self.B, (i+1)*(self.n-1), 1)
print('Matrix calculation has been done !\n\n', end='')
self.matrix_genned = True
def solve(self)->None:
if not self.matrix_genned:
raise ValueError('Matrix has not been generated.')
val, vec = spsl.eigs(self.A, self.n**2, self.B, maxiter=self.n**2*500, which='SI', sigma=0.05)
print('Eigen value has been calculated !\n\n', end='')
for i in range(len(val)):
self.eigen_val_vec.append((val[i], vec[:, i]))
self.eigen_val_vec.sort(key=lambda x:x[0])
#1次元の解の配列を2次元に変換
def reshape_vec(self, i: int)->np.ndarray:
vec = np.zeros((self.n+1, self.n+1))
for k in range(self.n-1):
for l in range(self.n-1):
vec[l+1][k+1] = self.eigen_val_vec[i][1][l+k*(self.n-1)]
return vec
def get_matrix(self)->tuple[np.ndarray, np.ndarray]:
return self.A, self.B
def get_eigenvalue(self, i)->np.complex64:
return self.eigen_val_vec[i][0]
def get_eigenvector(self, i)->np.ndarray:
return self.reshape_vec(i)
def export_data(self)->None:
val_file_handler = open(self.EIG_VAL_FILE, mode='w')
vec_file_handler = open(self.EIG_VEC_FILE, mode='w')
for val_vec in self.eigen_val_vec:
val_file_handler.write(f'{val_vec[0]}\n')
vec_file_handler.write(','.join(map(str, val_vec[1]))+'\n')
val_file_handler.close()
vec_file_handler.close()
# 無限井戸型ポテンシャル(とみなせる)
def V(x: float, y: float)->float:
return 0
if __name__=='__main__':
N = 25 #要素数
m = SchrodingerFEM2.DIRAC_CONSTANT_DOUBLE/2
L = 5
x = np.linspace(-L, L, N+1)
y = np.linspace(-L, L, N+1)
fem = SchrodingerFEM2(N, m, x, y, V)
fem.gen_matrix()
fem.solve()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y = np.meshgrid(x, y)
print(fem.get_eigenvalue(1))
phi = fem.get_eigenvector(1)
ax.plot_surface(X, Y, phi, cmap='jet')
plt.show()
7.例
無限井戸型ポテンシャルの2番目にエネルギーが小さい状態
Discussion