pw1d.py ダウンロード/コピー

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()