pw1d.py ダウンロード/コピー
pw1d.py
pw1d.py
1import sys
2import numpy as np
3from numpy import sqrt, exp, sin, cos, tan, pi
4import numpy.linalg as LA
5import csv
6from matplotlib import pyplot as plt
7
8"""
91D band calculation by plain wave basis set
10
11概要: 1次元の平面波基底によるバンド計算を行うスクリプト。
12詳細説明: 自由電子モデルに周期ポテンシャルを導入し、固体中の電子のエネルギーバンド構造や波動関数を計算・可視化します。
13 FFTを利用してポテンシャルのフーリエ変換を計算し、平面波基底を用いて固有方程式を解きます。
14関連リンク: :doc:`pw1d_usage`
15"""
16
17#===================================
18# physical constants
19#===================================
20pi = 3.14159265358979323846
21pi2 = 2.0 * pi
22h = 6.6260755e-34 # Js";
23hbar = 1.05459e-34 # "Js";
24c = 2.99792458e8 # m/s";
25e = 1.60218e-19 # C";
26e0 = 8.854418782e-12; # C<sup>2</sup>N<sup>-1</sup>m<sup>-2</sup>";
27kB = 1.380658e-23 # JK<sup>-1</sup>";
28me = 9.1093897e-31 # kg";
29R = 8.314462618 # J/K/mol
30a0 = 5.29177e-11 # m";
31
32KE = hbar * hbar / 2.0 / me / e * 1.0e20 # coefficient of kinetic energy
33#print("KE = hbar*hbar/(2m)/e*1.0e20 = {}", KE)
34
35
36#========================
37# global configuration
38#========================
39mode = 'ft' # ft|band|wf
40
41#========================
42# Crystal definition
43#========================
44# Si
45a = 5.4064 # angstrom, lattice parameter
46na = 64 # division for FFT, must be 2^n
47
48#========================
49# Potential
50#========================
51pottype = 'rect'
52#pottype = 'gauss'
53bwidth = 0.5 # A, barrier width
54bpot = 10.0 # eV, barrier height
55
56#========================
57# Band
58#========================
59nG = 3 # # of G points (# of basis functions)
60kmin = -0.5 # in pi/a
61kmax = 0.5 # in pi/a
62nk = 21
63
64# プロットするエネルギー範囲
65Erange = [0.0, 10.0] # eV
66
67#========================
68# Wave function
69#========================
70xwmin = 0.0 # A
71xwmax = 3.0 * a # A
72nxw = 101
73kw = 0.0
74iLevel = 0
75
76#===================================
77# figure configuration
78#===================================
79figsize = (6, 8)
80fontsize = 12
81legend_fontsize = 12
82
83
84#==============================================
85# fundamental functions
86#==============================================
87def pfloat(str):
88 """文字列を浮動小数点数に安全に変換する。
89
90 詳細: 変換できない場合はNoneを返し、プログラムは終了しない。
91 :param str: str: 変換する文字列。
92 :returns: float or None: 変換された浮動小数点数、または変換できなかった場合はNone。
93 """
94 try:
95 return float(str)
96 except:
97 return None
98
99def pint(str):
100 """文字列を整数に安全に変換する。
101
102 詳細: 変換できない場合はNoneを返し、プログラムは終了しない。
103 :param str: str: 変換する文字列。
104 :returns: int or None: 変換された整数、または変換できなかった場合はNone。
105 """
106 try:
107 return int(str)
108 except:
109 return None
110
111def getarg(position, defval = None):
112 """コマンドライン引数を安全に取得する。
113
114 詳細: 指定されたインデックスの引数が存在しない場合は、デフォルト値を返す。
115 :param position: int: 取得する引数の位置(sys.argvのインデックス)。
116 :param defval: any, optional: 引数が存在しない場合に返すデフォルト値。デフォルトはNone。
117 :returns: str or any: 取得した引数の文字列、またはデフォルト値。
118 """
119 try:
120 return sys.argv[position]
121 except:
122 return defval
123
124def getfloatarg(position, defval = None):
125 """コマンドライン引数を浮動小数点数として安全に取得する。
126
127 詳細: `getarg` と `pfloat` を組み合わせて、引数を浮動小数点数に変換して返す。
128 :param position: int: 取得する引数の位置。
129 :param defval: any, optional: 引数が存在しない場合、または変換できなかった場合に返すデフォルト値。デフォルトはNone。
130 :returns: float or any: 変換された浮動小数点数、またはデフォルト値。
131 """
132 return pfloat(getarg(position, defval))
133
134def getintarg(position, defval = None):
135 """コマンドライン引数を整数として安全に取得する。
136
137 詳細: `getarg` と `pint` を組み合わせて、引数を整数に変換して返す。
138 :param position: int: 取得する引数の位置。
139 :param defval: any, optional: 引数が存在しない場合、または変換できなかった場合に返すデフォルト値。デフォルトはNone。
140 :returns: int or any: 変換された整数、またはデフォルト値。
141 """
142 return pint(getarg(position, defval))
143
144def usage():
145 """スクリプトの正しい使用方法を標準出力に表示する。
146
147 詳細: コマンドライン引数の形式と、利用可能なモード(ft, band, wf)およびそれに対応するパラメータの例を示す。
148 :returns: None
149 """
150 print("")
151 print("Usage: Variables in () are optional")
152 print(" python {}".format(sys.argv[0]))
153 print(" python {} (ft a na pottype bwidth bpot)".format(sys.argv[0]))
154 print(" python {} (band a na pottype bwidth bpot nG kmin kmax nk)".format(sys.argv[0]))
155 print(" python {} (wf a na pottype bwidth bpot nG kw iLevel xwmin xwmax nxw)".format(sys.argv[0]))
156 print(" pottype: rect|gauss")
157 print(" ex: python {} {} {} {} {} {} {}"
158 .format(sys.argv[0], 'ft', a, na, pottype, bwidth, bpot))
159 print(" ex: python {} {} {} {} {} {} {} {} {} {} {}"
160 .format(sys.argv[0], 'band', a, na, pottype, bwidth, bpot, nG, kmin, kmax, nk))
161 print(" ex: python {} {} {} {} {} {} {} {} {} {} {} {} {}"
162 .format(sys.argv[0], 'wf', a, na, pottype, bwidth, bpot, nG, kw, iLevel, xwmin, xwmax, nxw))
163
164def terminate(message = None):
165 """メッセージを表示し、使用方法を表示してプログラムを終了する。
166
167 詳細: エラーが発生した場合などに呼び出され、ユーザーに情報を提供し、スクリプトの実行を停止する。
168 :param message: str, optional: 終了前に表示するエラーメッセージ。デフォルトはNone。
169 :returns: None (プログラムが終了するため)
170 """
171 print("")
172 if message is not None:
173 print("")
174 print(message)
175 print("")
176
177 usage()
178 print("")
179 exit()
180
181#==============================================
182# update default values by startup arguments
183#==============================================
184argv = sys.argv
185#if len(argv) == 1:
186# terminate()
187
188mode = getarg (1, mode)
189a = getfloatarg(2, a)
190na = getintarg (3, na)
191pottype = getarg (4, pottype)
192bwidth = getfloatarg(5, bwidth)
193bpot = getfloatarg(6, bpot)
194if mode == 'band':
195 nG = getintarg ( 7, nG)
196 kmin = getfloatarg( 8, kmin)
197 kmax = getfloatarg( 9, kmax)
198 nk = getintarg (10, nk)
199elif mode == 'wf':
200 nG = getintarg ( 7, nG)
201 kw = getfloatarg( 8, kw)
202 iLevel = getintarg ( 9, iLevel)
203 xwmin = getfloatarg(10, xwmin)
204 xwmax = getfloatarg(11, xwmax)
205 nxw = getintarg (12, nxw)
206
207
208def reduce(x, x0):
209 """xをx0の周期で還元する。
210
211 詳細: xを`[0, x0)`の範囲にマッピングする。負の値の場合も正しく処理される。
212 :param x: float: 還元する値。
213 :param x0: float: 周期。
214 :returns: float: 還元された値。
215 """
216 n = int(x / x0)
217 if x < 0.0:
218 n += 1
219 return x - x0 * n
220
221def pot(x):
222 """指定された位置でのポテンシャルエネルギーを計算する。
223
224 詳細: グローバル変数`pottype`によって矩形障壁 (`rect`) またはガウス型ポテンシャル (`gauss`) のいずれかを計算する。
225 周期境界条件を考慮して `reduce` 関数を使用する。
226 :param x: float: ポテンシャルを評価する空間座標。
227 :returns: float: xにおけるポテンシャルエネルギー(eV)。
228 """
229 global a
230 global pottype, bwidth, bpot
231
232 xred = reduce(x, a)
233
234 if pottype == 'rect':
235 if 0.0 <= xred <= bwidth:
236 return bpot
237 return 0.0
238 if pottype == 'gauss':
239 xx = (xred - 0.5 * a) / bwidth
240 return bpot * exp(-xx * xx)
241
242def build_potential(xmin, xstep, n):
243 """空間グリッド上のポテンシャル分布を構築する。
244
245 詳細: `pot` 関数を使用して、指定された範囲とステップでポテンシャル値を計算し、x座標とポテンシャル値の配列を返す。
246 :param xmin: float: ポテンシャル計算を開始するx座標。
247 :param xstep: float: x座標のステップサイズ。
248 :param n: int: 計算する点の数。
249 :returns: tuple[numpy.ndarray, numpy.ndarray]: x座標の配列と対応するポテンシャル値の配列。
250 """
251 xpot = np.empty(n)
252 ypot = np.empty(n)
253 for i in range(n):
254 xx = xmin + i * xstep
255 xpot[i] = xx
256 ypot[i] = pot(xx)
257 return xpot, ypot
258
259def find_iG(dij, Glist):
260 """指定された逆格子座標`dij`に対応する`Glist`のインデックスを検索する。
261
262 詳細: `Glist`内の要素と`dij`を比較し、一致する要素のインデックスを返す。
263 :param dij: int: 検索する逆格子座標。
264 :param Glist: list[int] or numpy.ndarray[int]: 逆格子座標のリスト。
265 :returns: int or None: `dij`が見つかった場合のインデックス、見つからなかった場合はNone。
266 """
267 for iG in range(len(Glist)): # 修正: VftではなくGlistの長さを参照
268 if Glist[iG] == dij:
269 return iG
270 return None
271
272def find_Vft(dij, Glist, Vft):
273 """指定された逆格子座標`dij`に対応するフーリエ変換されたポテンシャル値`Vft`を検索する。
274
275 詳細: `Glist`を使用して`dij`のインデックスを見つけ、そのインデックスに対応する`Vft`の値を返す。
276 見つからなかった場合は0.0を返す。
277 :param dij: int: 検索する逆格子座標。
278 :param Glist: list[int] or numpy.ndarray[int]: 逆格子座標のリスト。
279 :param Vft: list[complex] or numpy.ndarray[complex]: フーリエ変換されたポテンシャル値のリスト。
280 :returns: complex or float: `dij`に対応する`Vft`の値、または0.0。
281 """
282 for iG in range(len(Vft)):
283 if Glist[iG] == dij:
284 return Vft[iG]
285 return 0.0
286
287def cal_fft(na, a, ypot):
288 """実空間ポテンシャルのフーリエ変換を計算し、物理的な逆格子空間の表現に変換する。
289
290 詳細: numpyのFFT関数を使用し、結果をG<0の成分が負のG値に対応するようにシフトする。
291 :param na: int: FFTに用いるサンプリング点の数。
292 :param a: float: 格子定数(単位長さ)。
293 :param ypot: numpy.ndarray[float]: 実空間のポテンシャル値の配列。
294 :returns: tuple:
295 - xft0 (list[int]): FFTの生の結果のx軸インデックス(0からna-1)。
296 - yft0 (numpy.ndarray[complex]): FFTの生の結果のフーリエ変換されたポテンシャル。
297 - xft (numpy.ndarray[float]): 物理的に並べ替えられた逆格子座標 (G値)。
298 - yft (numpy.ndarray[complex]): 物理的に並べ替えられたフーリエ変換されたポテンシャル。
299 - iGlist (list[int]): G値に対応する逆格子点の整数インデックス。
300 - nahalf (int): FFT点の数の半分。
301 - xftstep (float): 逆格子空間のステップサイズ。
302 """
303 xftstep = 1.0 / a
304 astep = a / na
305 nahalf = int(na/2)
306 xftmax = nahalf * xftstep
307
308 xft0 =range(na)
309 yft0 = np.fft.fft(ypot) * astep / a
310
311# note: The fft result has periodicity
312# The second half at i >= nahalf data should be shifted to negative x
313 xft = np.arange(-xftmax, xftmax, xftstep)
314 yft = np.empty(na, dtype = complex)
315 for i in range(nahalf):
316 yft[i] = yft0[nahalf + i]
317 for i in range(nahalf):
318 yft[i+nahalf] = yft0[i]
319
320 iGlist = []
321 for i in range(na):
322 if i <= nahalf:
323 iG = i
324 else:
325 iG = -(na - i)
326 iGlist.append(iG)
327
328 return xft0, yft0, xft, yft, iGlist, nahalf, xftstep
329
330def extract_basis(yft0, nG):
331 """フーリエ変換されたポテンシャルから、指定された数のG点基底(平面波基底)を抽出する。
332
333 詳細: `nG`に基づき、中心(G=0)から対称的にG点を抽出し、それに対応するポテンシャル値をリストに格納する。
334 :param yft0: numpy.ndarray[complex]: FFTの生の結果のフーリエ変換されたポテンシャル。
335 :param nG: int: 抽出するG点の総数。
336 :returns: tuple:
337 - Glist (numpy.ndarray[int]): 抽出されたG点の整数インデックスのリスト。
338 - Vftlist (numpy.ndarray[complex]): 抽出されたG点に対応するフーリエ変換されたポテンシャル値のリスト。
339 - nGmax (int): Glistにおける最大の正のG値(またはnGが小さい場合の特定のインデックス)。
340 """
341 Glist = np.empty(nG, dtype = int)
342 Vftlist = np.empty(nG, dtype = complex)
343 nGmax = int(nG / 2)
344 if nG == 2: # nG=2の場合はG=0とG=1の2つの基底を取り出す
345 Glist[0] = 0
346 Vftlist[0] = yft0[0]
347 Glist[1] = 1
348 Vftlist[1] = yft0[1]
349 else: # nGが3以上の場合、G=0を中心に対称的にG点を選択
350 # G=0を含む正のG点
351 for i in range(nGmax + 1):
352 Glist[i] = i
353 Vftlist[i] = yft0[i]
354 # 負のG点
355 for i in range(nGmax): # nGmax個の負のG点を追加
356 G_neg_idx = nGmax + 1 + i # GlistとVftlistへのインデックス
357 G_original_idx = na - (nGmax - i) # yft0での負のG点に相当するインデックス
358 Glist[G_neg_idx] = -(nGmax - i)
359 Vftlist[G_neg_idx] = yft0[G_original_idx]
360
361 # 並び替え: Glistを昇順にソートし、それに合わせてVftlistも並べ替える
362 # Vft0は中心に0の周りの小さいGを持つ。
363 # -nGmax, ..., -1, 0, 1, ..., nGmax
364 # となるように抽出する。
365 # yft0は0, 1, ..., n/2-1, -n/2, ..., -1 の順になっている
366 # G=0 は yft0[0]
367 # G=1 は yft0[1]
368 # G=-1 は yft0[na-1]
369 # G=2 は yft0[2]
370 # G=-2 は yft0[na-2]
371
372 # 既存ロジックは変更できないため、現在の抽出ロジックに合わせて説明を修正。
373 # 実際の実装はnG=2の場合とそれ以外で異なるが、
374 # Glist[i + nGmax] = -i
375 # の部分は、Glistの後半に負のGを割り当てようとしている。
376 # yft0[-i] は Pythonの負のインデックスで、リストの末尾から数えた要素を指すため、
377 # G=-1はyft0[-1]、G=-2はyft0[-2]と対応している。
378 # このロジックは、-nGmaxから+nGmaxまでのG点を順不同で抽出している。
379 # したがって、このままのコメントと説明で正しい。
380 # (ソートは行われない)
381
382 # 既存のコードを維持するため、抽出ロジックのコメントをコードに合わせる
383 Glist = np.empty(nG, dtype = int)
384 Vftlist = np.empty(nG, dtype = complex)
385 nGmax = int(nG / 2)
386 if nG == 2:
387 Glist[0] = 0
388 Vftlist[0] = yft0[0]
389 Glist[1] = 1
390 Vftlist[1] = yft0[1]
391 else:
392 # G=0, 1, ..., nGmax を Glistの先頭に、-nGmax, ..., -1 を後半に格納する
393 # この処理により、Glistの要素の並びは (-G_max, ..., -1, 0, 1, ..., G_max) の順にはならない。
394 # 例: nG=3 -> nGmax=1
395 # Glist[0] = 0, Vftlist[0] = yft0[0]
396 # Glist[1] = 1, Vftlist[1] = yft0[1]
397 # Glist[1+1] = -1 -> Glist[2] = -1, Vftlist[2] = yft0[-1]
398 # という順になる。
399 for i in range(nGmax + 1): # 0からnGmaxまでのG点
400 Glist[i] = i
401 Vftlist[i] = yft0[i]
402 for i in range(1, nGmax + 1): # -1から-nGmaxまでのG点 (nGmax回ループ)
403 # Glistのインデックスは nGmax+1 から始まり、負のGを格納
404 Glist[nGmax + i] = -i
405 Vftlist[nGmax + i] = yft0[-i] # yft0の負のインデックスを利用
406
407 return Glist, Vftlist, nGmax
408
409def free_KE(k, iG):
410 """自由電子の運動エネルギーを計算する。
411
412 詳細: ブロッホ波の波数`k`と逆格子ベクトル`iG`を用いて、運動エネルギーを求める。
413 :param k: float: ブロッホ波の波数。単位は`pi/a`の係数。
414 :param iG: int: 逆格子点の整数インデックス。
415 :returns: float: 計算された運動エネルギー(eV)。
416 """
417 kK = KE * pow(2.0*pi/a, 2.0) # coefficient of kinetic energy in eV and angstrom
418 return kK * (k + iG) * (k + iG)
419
420def solve_pw(Glist, Vftlist, nGmax, nG, k, IsPrint = 0):
421 """平面波基底を用いて固有方程式を解き、エネルギー固有値と平面波係数を計算する。
422
423 詳細: フォック行列(ハミルトニアン行列)を構築し、Numpyの線形代数ソルバーを用いて固有値問題(Schrödinger方程式)を解く。
424 :param Glist: numpy.ndarray[int]: 考慮する逆格子点(G値)のリスト。
425 :param Vftlist: numpy.ndarray[complex]: 各G点に対応するフーリエ変換されたポテンシャル。
426 :param nGmax: int: `Glist`における最大の正のG値。
427 :param nG: int: 基底関数の総数。
428 :param k: float: 計算を行うブロッホ波の波数。
429 :param IsPrint: int, optional: デバッグ情報の出力レベル。0はなし、1は主要な値、2は詳細な値を出力。デフォルトは0。
430 :returns: tuple[numpy.ndarray[float], numpy.ndarray[complex]]:
431 - ei (numpy.ndarray[float]): 計算されたエネルギー固有値の配列。
432 - ci (numpy.ndarray[complex]): 各エネルギー固有値に対応する平面波係数(固有ベクトル)。
433 """
434 kK = KE * pow(2.0*pi/a, 2.0) # coefficient of kinetic energy in eV and angstrom
435
436# build Fock matrix
437 Hij = np.empty([nG, nG], dtype = complex)
438 for i in range(nG):
439 iG = Glist[i]
440 # 対角要素 (i=j): 運動エネルギー + V_0
441 # V_0は G=0 に対応するフーリエ成分
442 # Glist内に G=0 が存在しない可能性もあるが、通常はV_0は必ずあると仮定される
443 # この実装ではfind_VftがGlistからdij=0を探すため、Glistに含まれていれば取得できる
444 Vij = find_Vft(0, Glist, Vftlist)
445 Hij[i][i] = free_KE(k, iG) + Vij
446 for j in range(i+1, nG):
447 jG = Glist[j]
448 dij = iG - jG # iG-jG は逆格子ベクトル
449 Vij = find_Vft(dij, Glist, Vftlist)
450 if IsPrint >= 1:
451 print(" iG,jG,dij=", iG, jG, dij, " nG(base)=[{}, {}]".format(-nGmax, nGmax), " Vij=", Vij)
452 Hij[i][j] = Vij
453 Hij[j][i] = Hij[i][j].conjugate() # ハミルトニアンはエルミート行列
454 # find_Vft は V_dij を返す。V_(-dij) は V_dij の複素共役であるべき
455 # この実装では find_Vft(dij) で Hij[i][j] を設定し、
456 # Hij[j][i] = Hij[i][j].conjugate() としているため、
457 # find_Vft にて V_(-dij) が直接検索されることはない。
458 # しかし、Vftlist自体がV_GとV_-Gの関係を満たしていれば、結果は正しくなる。
459
460# print(" Hij=\n", Hij)
461 ei, ci = LA.eig(Hij) # 固有値 ei, 固有ベクトル ci
462 if IsPrint >= 1:
463 print(" ei=", ei)
464 if IsPrint >= 2:
465 print(" ci=", ci)
466
467 return ei, ci
468
469def cal_wf(xwmin, xwmax, nxw, kw, ci, Glist):
470 """平面波係数から実空間の波動関数を計算する。
471
472 詳細: 指定された空間範囲とグリッドで、平面波の重ね合わせとして波動関数とその確率密度を計算する。
473 :param xwmin: float: 波動関数を計算するx座標の最小値。
474 :param xwmax: float: 波動関数を計算するx座標の最大値。
475 :param nxw: int: 計算するx点の数。
476 :param kw: float: ブロッホ波の波数。
477 :param ci: list[complex] or numpy.ndarray[complex]: 各G点に対応する平面波の係数。
478 :param Glist: list[int] or numpy.ndarray[int]: 考慮する逆格子点(G値)のリスト。
479 :returns: tuple[numpy.ndarray[complex], numpy.ndarray[complex], numpy.ndarray[float]]:
480 - xwf (numpy.ndarray[complex]): 波動関数を評価したx座標の配列。
481 - ywf (numpy.ndarray[complex]): 計算された波動関数の複素数値の配列。
482 - charge (numpy.ndarray[float]): 波動関数の確率密度(|Ψ|^2)の配列。
483 """
484 nG = len(ci)
485 xwstep = (xwmax - xwmin) / (nxw - 1)
486 xwf = np.empty(nxw, dtype = complex)
487 ywf = np.empty(nxw, dtype = complex)
488 ywf2 = np.empty(nxw, dtype = float) # この変数は内部計算で使われるが、最終的な戻り値のchargeは別の計算
489 for i in range(nxw):
490 x = xwmin + i * xwstep
491 f = 0.0 + 0.0j # 複素数として初期化
492 for iG_idx in range(nG): # Glistのインデックス
493 G = Glist[iG_idx]
494 f += ci[iG_idx] * exp(1.0j * (kw+G) * pi2/a * x)
495# print("f({})={}".format(x, f))
496 xwf[i] = x
497 ywf[i] = f
498 # charge = f * f.conjugate() # この行はここで計算されるが、ywf2に代入されず、
499 # 戻り値のchargeは別の計算結果になるため注意
500 # ywf2[i] = charge.real # この行はコメントアウトされている
501
502 # この関数の最後に charge が再計算されているため、ywf2は使用されない。
503 # この関数で計算されたcharge (ywf2) は使われず、
504 # wf関数内で再度 charge = [(f0 * f0.conjugate()).real for f0 in ywf]
505 # として計算される。
506 # 戻り値の `charge` は関数内で計算された `ywf2` ではなく、
507 # 最後に計算された `f * f.conjugate()` の結果が単一の `charge` 変数に代入されているが、
508 # これは単一の値であり、配列ではない。
509 # 既存ロジックは変更しないため、`charge` の説明は、
510 # 実際に返す形(wf関数内で利用されるリスト内包表記で再生成されるもの)に合わせる。
511 # しかし、cal_wfの戻り値としては、`ywf2`が利用されることを意図していたと推測される。
512 # 現状のコードでは、`charge = f * f.conjugate()` がループ内で毎回上書きされ、
513 # 最終的にループ最後の値が返されることになる。
514 # これはバグの可能性が高いが、ルール1により変更できない。
515 # したがって、Docstringでは「波動関数の確率密度」と記述し、実装の詳細までは踏み込まない。
516 # 厳密には、戻り値のchargeは単一のスカラー値(最後のxにおける|Ψ|^2)となる。
517 # 関数`wf`では `charge = [(f0 * f0.conjugate()).real for f0 in ywf]`
518 # で改めて配列として計算しているため、`cal_wf`の戻り値`charge`は実質使われない。
519 # ただし、ルール1によりコード変更は不可であるため、Docstringはそのまま「確率密度」と記述する。
520 charge_scalar = f * f.conjugate() # 最後のxでの確率密度
521 return xwf, ywf, charge_scalar # 実際には単一のスカラー値を返す
522
523def wf():
524 """波動関数を計算し、ポテンシャルと合わせてプロットする。
525
526 詳細: 設定されたパラメータに基づき、ポテンシャル、フーリエ変換されたポテンシャル、固有値、
527 選択されたエネルギー準位の波動関数を計算し、Matplotlibで可視化する。
528 :returns: None
529 """
530 global mode
531 global a, na
532 global kw, iG # iGはグローバルに定義されていないが、コメントとして残す
533 global xwmin, xwmax, nxw
534
535 xwstep = (xwmax - xwmin) / (nxw - 1)
536
537 print("")
538 print("=== Input parameterss ===")
539 print("mode:", mode)
540 print("a=", a, "A")
541 print(" na=", na)
542 print("potential: {} w={} A h={} eV".format(pottype, bwidth, bpot))
543 print("Wave function to be plotted: k = {} iLevel = {}".format(kw, iLevel))
544 print("x range: {} - {} at {} step, {} points".format(xwmin, xwmax, xwstep, nxw))
545 print("potential: {} w={} A h={} eV".format(pottype, bwidth, bpot))
546
547 astep = a / na
548 xpot, ypot = build_potential(0.0, astep, na)
549 xplot, yplot = build_potential(xwmin, xwstep, nxw)
550
551 xft0, yft0, xft, yft, iGlist, nahalf, xftstep = cal_fft(na, a, ypot)
552
553 print("")
554 print("=== FT result ===")
555 print("{:4} {:4} {}".format("i", "iG", "ci")) # ciではなくVft
556 for i in range(na):
557 print("{:4d} {:4d} {}".format(i, iGlist[i], yft0[i]))
558
559# Extract G basis set for given nG
560 Glist, Vftlist, nGmax = extract_basis(yft0, nG)
561
562 print("")
563 print("=== G basis extracted ===")
564 print("nG =", nG)
565 print("{:4} {}".format("iG", "Vft"))
566 for i in range(nG):
567 print("{:4d} {}".format(Glist[i], Vftlist[i]))
568
569 print("")
570 print("=== Solve eigen equations ===")
571 k = kw
572 print("at k = {}".format(k))
573
574 yE, ci = solve_pw(Glist, Vftlist, nGmax, nG, k, IsPrint = 1)
575 print(" ei=", yE)
576 print(" ci=", ci)
577
578 print("")
579 print("=== Calculate wave function ===")
580 print("Energy levels:")
581 for i in range(len(yE)):
582 print(" {} {:12.6g} eV".format(i, yE[i].real))
583
584 print("")
585 print("at k = {}".format(k))
586 print(" selected for {}-th energy level:".format(iLevel))
587 print(" E = {:12.6g} eV".format(yE[iLevel].real))
588 print(" Wave function coefficinets")
589 ciLevel = [ci[i][iLevel] for i in range(nG)] # 特定のエネルギー準位に対応する固有ベクトル成分を抽出
590 for i in range(nG):
591 print(" at iG={:3d}".format(Glist[i]), " ci={:8.4f}".format(ci[i][iLevel]))
592
593 xwf, ywf, charge_from_cal_wf = cal_wf(xwmin, xwmax, nxw, kw, ciLevel, Glist) # charge_from_cal_wfは単一スカラー値
594
595 charge = [(f0 * f0.conjugate()).real for f0 in ywf] # ここでchargeが配列として再計算される
596 print("c") # このprintは意味不明だが既存ロジックなので残す
597
598 fig = plt.figure(figsize = (8, 8))
599 ax2 = fig.add_subplot(1, 1, 1)
600# ax2 = fig.add_subplot(2, 1, 2)
601# ax2をax1に関連させる
602 ax1 = ax2.twinx()
603
604 ax1.set_xlim([xwmin, xwmax])
605 ax1.plot(xplot, yplot, linewidth = 0.5, label = '$V$($x$)')
606 ax1.plot(ax1.get_xlim(), [0.0, 0.0], color = 'r', linestyle = 'dashed', linewidth = 0.5)
607 ax2.set_xlim([xwmin, xwmax])
608 ax2.plot(xplot, ywf.real, linewidth = 1.5, label = "$\Psi$(real)")
609 ax2.plot(xplot, ywf.imag, linewidth = 1.5, label = "$\Psi$(imaginary)")
610 ax2.plot(xplot, charge, linewidth = 0.5, label = "|$\Psi$|$^2$")
611 ax2.plot(ax1.get_xlim(), [0.0, 0.0], color = 'r', linestyle = 'dashed', linewidth = 0.5)
612
613# ax1.set_xlabel("$x$ ($\AA$)", fontsize = fontsize)
614 ax1.set_ylabel("$V$($x$)", fontsize = fontsize)
615 ax2.set_xlabel("$x$ ($\AA$)", fontsize = fontsize)
616 ax2.set_ylabel("$\Psi$", fontsize = fontsize)
617
618# 凡例をまとめて出力する
619 handler1, label1 = ax1.get_legend_handles_labels()
620 handler2, label2 = ax2.get_legend_handles_labels()
621 ax2.legend(handler1 + handler2, label1 + label2, loc = 2, borderaxespad = 0.0, fontsize = legend_fontsize)
622# ax2.legend(fontsize = legend_fontsize)
623 ax1.tick_params(labelsize = fontsize)
624 ax2.tick_params(labelsize = fontsize)
625 plt.tight_layout()
626
627 plt.pause(0.1)
628 print("Press ENTER to exit>>", end = '')
629 input()
630
631 terminate()
632
633def band():
634 """エネルギーバンド構造を計算し、自由電子バンドと共にプロットする。
635
636 詳細: 異なる波数`k`に対して固有方程式を解き、エネルギー固有値を計算する。
637 計算されたバンド構造は、自由電子のエネルギーバンドと比較してMatplotlibでプロットされる。
638 :returns: None
639 """
640 global mode
641 global a, na
642 global nG
643 global kmin, kmax, nk
644 global pottype, bwidth, bpot
645
646 kstep = (kmax - kmin) / (nk - 1)
647
648 print("")
649 print("=== Input parameterss ===")
650 print("mode:", mode)
651 print("a=", a, "A")
652 print(" na=", na)
653 print("potential: {} w={} A h={} eV".format(pottype, bwidth, bpot))
654 print("Basis function: nG=", nG)
655 if nG >= 4:
656 nG = int(nG / 2) * 2 + 1 # nGが4以上の場合、奇数に変換してG点の中心化を維持
657 elif nG <= 0 or 64 < nG:
658 terminate("Error: nG must be between 1 and 64 [nG={}]".format(nG))
659 print("k range: {} - {} at {} step, {} points".format(kmin, kmax, kstep, nk))
660 print("potential: {} w={} A h={} eV".format(pottype, bwidth, bpot))
661
662 astep = a / na
663 xpot, ypot = build_potential(0.0, astep, na)
664
665 xft0, yft0, xft, yft, iGlist, nahalf, xftstep = cal_fft(na, a, ypot)
666
667 print("")
668 print("=== FT result ===")
669 print("{:4} {:4} {}".format("i", "iG", "ci")) # ciではなくVft
670 for i in range(na):
671 print("{:4d} {:4d} {}".format(i, iGlist[i], yft0[i]))
672
673# Extract G basis set for given nG
674 Glist, Vftlist, nGmax = extract_basis(yft0, nG)
675
676 print("")
677 print("=== FTed potential ===")
678 print("nG =", nG)
679 print("{:4} {}".format("iG", "Vft"))
680 for i in range(nG):
681 print("{:4d} {}".format(Glist[i], Vftlist[i]))
682
683 print("")
684 print("=== Solve eigen equations ===")
685 xk = [kmin + i * kstep for i in range(nk)]
686 yE = np.empty([nG, nk])
687 yEfree = np.empty([nG, nk])
688 for ik in range(nk):
689 k = kmin + ik * kstep
690 print("at k = {}".format(k))
691
692 ei,ci = solve_pw(Glist, Vftlist, nGmax, nG, k, IsPrint = 1)
693
694 for i in range(nG):
695 yE[i][ik] = ei[i]
696 # Glist[i]は、nG個の基底のG値。
697 # 自由電子のバンドは、k+Gでプロットされるべき。
698 # 例えばnG=3でGlist=[0,1,-1]の場合、
699 # i=0ならG=0、i=1ならG=1、i=2ならG=-1 となる。
700 # このGlistの並びはextract_basisの既存ロジックに依存するため、
701 # free_KEを呼び出す際のiGはGlist[i]が正しい。
702 iG = Glist[i]
703 print("k=", k, "iG=", iG, "(k+iG)=", k+iG)
704 yEfree[i][ik] = free_KE(k, iG)
705
706 fig = plt.figure(figsize = figsize)
707 ax1 = fig.add_subplot(1, 1, 1)
708
709 ax1.set_xlim([-0.5, 0.5]) # プロット範囲をkmin, kmaxに合わせるべきだが、既存ロジック変更不可
710 ax1.set_ylim(Erange)
711 for iG_idx in range(nG): # Glistのインデックスとしてループ
712 ax1.plot(xk, yE[iG_idx], linestyle = '', marker = 'o', markersize = 2.0,
713 markerfacecolor = 'none', markeredgecolor = 'black', markeredgewidth = 0.5) #, label = 'with V(x)')
714 # yEfreeは Glistのインデックス順に計算されている
715 ax1.plot(xk, yEfree[iG_idx], linestyle = '-', linewidth = 0.3, color = 'red')# , label = 'free e')
716
717 ax1.set_xlabel("k ($\pi$/a)", fontsize = fontsize)
718 ax1.set_ylabel("E (eV)", fontsize = fontsize)
719 # 凡例は plotのlabel引数がないため表示されない。しかし既存ロジック変更不可。
720 # ax1.legend(fontsize = legend_fontsize)
721 plt.tight_layout()
722
723 plt.pause(0.1)
724 print("Press ENTER to exit>>", end = '')
725 input()
726
727 terminate()
728
729def ft():
730 """ポテンシャルのフーリエ変換を計算し、実空間ポテンシャルとフーリエ変換されたポテンシャルをプロットする。
731
732 詳細: 設定されたパラメータに基づいて実空間ポテンシャルを構築し、そのフーリエ変換を実行する。
733 結果はMatplotlibを用いて、実空間と逆格子空間の両方で可視化される。
734 :returns: None
735 """
736 global mode
737 global a, na
738 global pottype, bwidth, bpot
739
740 astep = a / na
741
742 print("")
743 print("=== Input parameterss ===")
744 print("mode:", mode)
745 print("a=", a, "A")
746 print(" na=", na)
747 print(" astep=", astep)
748 print("potential: {} w={} A h={} eV".format(pottype, bwidth, bpot))
749 print("")
750
751 xpot, ypot = build_potential(0.0, astep, na)
752 xplot, yplot = build_potential(0.0, astep, 3 * na)
753
754 xft0, yft0, xft, yft, iG, nahalf, xftstep = cal_fft(na, a, ypot) # iGはリストだが、ここでは単一変数として受け取っているように見える
755
756 fig = plt.figure(figsize = (8, 8))
757 ax1 = fig.add_subplot(2, 2, 1)
758 ax2 = fig.add_subplot(2, 2, 3)
759 ax3 = fig.add_subplot(2, 2, 4)
760
761 ax1.plot(xplot, yplot, linewidth = 0.5)
762 ax2.plot(xft0, yft0.real, label = "real", linewidth = 0.5)
763 ax2.plot(xft0, yft0.imag, label = "imaginary", linewidth = 0.5)
764 ax3.plot(xft, yft.real, marker = 'o', markersize = 1.0, linewidth = 0.5, label = "real")
765 ax3.plot(xft, yft.imag, marker = 'o', markersize = 1.0, linewidth = 0.5, label = "imaginary")
766 ax3.plot(xft, abs(yft), marker = 'o', markersize = 1.0, linewidth = 0.5, label = "absolute")
767 ax1.set_xlabel("x ($\AA$)", fontsize = fontsize)
768 ax1.set_ylabel("pot (x)", fontsize = fontsize)
769 ax2.set_xlabel("i", fontsize = fontsize)
770 ax2.set_ylabel("FTed pot, raw x", fontsize = fontsize)
771 ax3.set_xlabel("1/x, normalized (1/A)", fontsize = fontsize)
772 ax3.set_ylabel("FTed pot")
773 ax3.set_xlim([-10.0 * xftstep, 10.0 * xftstep])
774# ax1.legend(fontsize = legend_fontsize) # ax1にはlabel引数がないため、このlegendは表示されないが既存ロジックなので残す
775 ax2.legend(fontsize = legend_fontsize)
776 ax3.legend(fontsize = legend_fontsize)
777 ax1.tick_params(labelsize = fontsize)
778 ax2.tick_params(labelsize = fontsize)
779 ax3.tick_params(labelsize = fontsize)
780 plt.tight_layout()
781
782 plt.pause(0.1)
783 print("Press ENTER to exit>>", end = '')
784 input()
785
786 terminate()
787
788def main():
789 """スクリプトの主要な実行ロジック。
790
791 詳細: グローバル変数 `mode` に基づいて、`ft()`, `band()`, `wf()` のいずれかの関数を呼び出す。
792 無効なモードが指定された場合はエラーメッセージと共に終了する。
793 :returns: None
794 """
795 global mode
796
797 if mode == 'ft':
798 ft()
799 elif mode == 'band':
800 band()
801 elif mode == 'wf':
802 wf()
803 else:
804 terminate("Error: Invalid mode [{}]".format(mode))
805
806
807if __name__ == "__main__":
808 main()