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

refineE_schrodinger1d.py をダウンロード

refineE_schrodinger1d.py
refineE_schrodinger1d.py
  1"""
  2概要: シュレーディンガー方程式の固有エネルギーをBrent法で精密化するスクリプト。
  3
  4詳細説明:
  5本スクリプトは、schrodinger1dモジュールを使用してシュレーディンガー方程式を解き、
  6その結果に基づいて境界条件が満たされない残差関数を定義します。
  7与えられた初期エネルギー値から出発し、残差関数の符号反転区間を自動的に探索します。
  8その後、SciPyライブラリのBrent法 (scipy.optimize.brentq) を利用して、
  9残差がゼロとなるエネルギー(固有エネルギー)を高精度で求めます。
 10精密化された固有エネルギーと計算の詳細は、refineE.csvファイルに記録されます。
 11
 12関連リンク:
 13refineE_schrodinger1d_usage
 14"""
 15import os
 16import argparse
 17import numpy as np
 18from scipy.optimize import brentq
 19from schrodinger1d import solve_schrodinger
 20
 21# ----------------------------------------
 22# residual(E): ψ(L) を返す関数
 23# ----------------------------------------
 24def residual(E, args, print_level = 0):
 25    """
 26    概要: シュレーディンガー方程式を解き、波動関数の右端での値 (psi(L)) を返す。
 27
 28    詳細説明:
 29    schrodinger1d.solve_schrodinger 関数を呼び出し、与えられたエネルギー E
 30    に対するシュレーディンガー方程式の波動関数を計算する。
 31    この関数は、Brent法などの根探索アルゴリズムで使用される残差関数として機能し、
 32    波動関数の右端 (x=L) での値がゼロになるエネルギーを探索するために用いられる。
 33
 34    引数:
 35        :param E: 試行するエネルギー値。
 36        :type E: float
 37        :param args: main 関数で定義されたコマンドライン引数を格納するオブジェクト。
 38                     シュレーディンガー方程式のパラメータ (L, nx, psi_max, bc, eps) を含む。
 39        :type args: argparse.Namespace
 40        :param print_level: 0以外の場合、計算されたエネルギーと psi(L) の値を出力する。
 41        :type print_level: int
 42    戻り値:
 43        :returns: 計算された波動関数の右端 (x=L) での値。
 44        :rtype: float
 45    """
 46    x, psi, info = solve_schrodinger(
 47        E=E,
 48        L=args.L,
 49        nx=args.nx,
 50        psi_max=args.psi_max,
 51        report_step=0,
 52        bc=args.bc,
 53        eps=args.eps
 54    )
 55    
 56    if print_level:
 57        print(f"E={E:12.8g}  psi(L)={psi[-1]:14.6e}")
 58
 59    return psi[-1]  # ψ(L)
 60
 61
 62# ----------------------------------------
 63# 初期値 E0 から符号反転区間を自動探索
 64# ----------------------------------------
 65def find_bracket(E0, args, max_expand=20, direction='both'):
 66    """
 67    概要: 与えられた初期エネルギー E0 を中心に、残差関数の符号が反転するエネルギー区間を探索する。
 68
 69    詳細説明:
 70    E0 を出発点とし、初期の delta 値を用いて E0 - delta と E0 + delta
 71    の範囲で residual 関数の符号をチェックする。
 72    もし符号反転が見つからない場合、delta を2倍にして探索範囲を広げ、
 73    max_expand 回数までこのプロセスを繰り返す。
 74    符号反転区間が見つかった場合、その下限 E_low と上限 E_high を返す。
 75
 76    引数:
 77        :param E0: 探索を開始する初期エネルギー値。
 78        :type E0: float
 79        :param args: residual 関数に渡すシュレーディンガー方程式のパラメータ。
 80        :type args: argparse.Namespace
 81        :param max_expand: 探索範囲を拡大する最大回数。この回数を超えても符号反転区間が見つからない場合はエラーとなる。
 82        :type max_expand: int
 83        :param direction: 探索する方向 ('upper', 'lower', 'both')。
 84                          'upper': E0より大きい側のみ探索。
 85                          'lower': E0より小さい側のみ探索。
 86                          'both': E0を挟んで両側を探索。
 87        :type direction: str
 88    戻り値:
 89        :returns: 符号反転区間の下限 E_low と上限 E_high のタプル。
 90        :rtype: tuple[float, float]
 91    例外:
 92        :raises ValueError: direction が 'upper', 'lower', 'both' のいずれでもない場合。
 93        :raises RuntimeError: max_expand 回数内に符号反転区間が見つからなかった場合。
 94    """
 95    delta = 0.05 * abs(E0) + 0.01
 96
 97    # 初期値の符号
 98    f0 = residual(E0, args)
 99
100    for _ in range(max_expand):
101
102        if direction == 'upper':
103            E_low  = E0
104            E_high = E0 + delta
105
106        elif direction == 'lower':
107            E_low  = E0 - delta
108            E_high = E0
109
110        elif direction == 'both':
111            E_low  = E0 - delta
112            E_high = E0 + delta
113
114        else:
115            raise ValueError("direction must be 'upper', 'lower', or 'both'")
116
117        f_low  = residual(E_low, args)
118        f_high = residual(E_high, args)
119
120        if f_low * f_high < 0:
121            print(f"[INFO] Found bracket: [{E_low}, {E_high}]")
122            return E_low, E_high
123
124        delta *= 2
125
126    raise RuntimeError("Could not find sign change interval.")
127
128# ----------------------------------------
129# Brent 法の maxiter を自動計算
130# ----------------------------------------
131def compute_maxiter(E_low, E_high, E_tol):
132    """
133    概要: Brent法の最大反復回数を自動的に計算する。
134
135    詳細説明:
136    Brent法は、探索区間の幅を指数関数的に減少させる。
137    この関数は、初期区間 [E_low, E_high] の幅と目標精度 E_tol に基づいて、
138    必要な反復回数を推定する。これにより、不要な計算を避けつつ、
139    十分な精度に達するための反復回数を設定できる。
140    経験的に5回の余裕を持たせている。
141
142    引数:
143        :param E_low: 符号反転区間の下限。
144        :type E_low: float
145        :param E_high: 符号反転区間の上限。
146        :type E_high: float
147        :param E_tol: ターゲットとするエネルギーの許容誤差。
148        :type E_tol: float
149    戻り値:
150        :returns: Brent法に推奨される最大反復回数。
151        :rtype: int
152    """
153    width = abs(E_high - E_low)
154    return int(np.ceil(np.log2(width / E_tol))) + 5
155
156
157# ----------------------------------------
158# main
159# ----------------------------------------
160def main():
161    """
162    概要: コマンドライン引数を受け取り、Brent法を用いてシュレーディンガー方程式の固有エネルギーを精密化する。
163
164    詳細説明:
165    このメイン関数は以下の手順で動作する:
166    1.  argparse を用いてコマンドライン引数を解析し、初期エネルギー E0、許容誤差 E_tol、
167        およびシュレーディンガー方程式の物理パラメータなどを取得する。
168    2.  find_bracket 関数を呼び出し、与えられた初期エネルギー args.E を含む符号反転区間
169        [E_low, E_high] を探索する。
170    3.  もし maxiter がコマンドラインで指定されていない場合、compute_maxiter 関数を用いて、
171        目標精度に基づいて自動的に最大反復回数を設定する。
172    4.  scipy.optimize.brentq 関数を使用して、residual 関数がゼロとなるエネルギー値
173        (すなわち固有エネルギー)を E_low と E_high の間で精密に探索する。
174        探索中には residual 関数の呼び出しごとに途中経過が出力される。
175    5.  精密化された固有エネルギーと、その探索結果(反復回数、関数呼び出し回数など)を出力する。
176    6.  最終的な結果は refineE.csv ファイルに追記される。ファイルが存在しない場合は、
177        最初にヘッダー行が追加される。
178    """
179    parser = argparse.ArgumentParser(
180        description="Refine eigenvalue using Brent method"
181    )
182
183    parser.add_argument("--E", type=float, required=True,
184                        help="Initial guess for eigenvalue")
185    parser.add_argument("--E_tol", type=float, default=1e-6,
186                        help="Target precision for eigenvalue")
187    parser.add_argument("--maxiter", type=int, default=None,
188                        help="Max iterations for Brent method (optional)")
189
190    # schrodinger1d.py と同じ引数を流用
191    parser.add_argument("--L", type=float, default=10.0)
192    parser.add_argument("--nx", type=int, default=2000)
193    parser.add_argument("--psi_max", type=float, default=1e10)
194    parser.add_argument("--bc", choices=["asymptotic", "zero"], default="asymptotic")
195    parser.add_argument("--eps", type=float, default=1e-6)
196
197    args = parser.parse_args()
198
199    # 1. 初期値 E0 から符号反転区間を探す
200    E_low, E_high = find_bracket(args.E, args, direction = 'upper')
201
202    # 2. maxiter を自動設定
203    if args.maxiter is None:
204        args.maxiter = compute_maxiter(E_low, E_high, args.E_tol)
205        print(f"[INFO] maxiter set to {args.maxiter}")
206
207    # 3. Brent 法で固有値を精密化
208    E_root, result = brentq(
209        lambda E: residual(E, args, print_level=1),
210        E_low, E_high,
211        xtol=args.E_tol,
212        maxiter=args.maxiter,
213        full_output=True
214    )
215
216    print("====================================")
217    print(" Refined eigenvalue:")
218    print(f"   E = {E_root:.12f}")
219    print("====================================")
220    
221    # f_low, f_high を計算
222    f_low  = residual(E_low, args)
223    f_high = residual(E_high, args)
224
225    log_csv = "refineE.csv"
226    print()
227    if not os.path.exists(log_csv):
228        print(f"{log_csv} does not exist. Add header labels.")
229        with open(log_csv, "a") as fp:
230            fp.write("E0, E_low, E_high, E_root, f_low, f_high, iterations, funcalls\n")
231
232    print(f"Append the result to {log_csv}")
233    with open(log_csv, "a") as fp:
234        fp.write(
235            f"{args.E},"
236            f"{E_low},{E_high},"
237            f"{E_root},"
238            f"{f_low},{f_high},"
239            f"{result.iterations},{result.function_calls}\n"
240        )
241
242
243if __name__ == "__main__":
244    main()