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

Seebeck_scan.py をダウンロード

Seebeck_scan.py
Seebeck_scan.py
  1"""
  2概要:
  3このスクリプトは、フェルミ準位、キャリア濃度、温度、有効質量を様々にスキャンしながら、熱電材料のゼーベック係数を計算しプロットします。
  4
  5詳細説明:
  6散乱因子 r の異なる値に対して、以下のスキャンモードをサポートしています。
  7- EF (フェルミ準位): フェルミ準位をスキャンし、ゼーベック係数を計算します。
  8- Ne (キャリア濃度): キャリア濃度をスキャンし、ゼーベック係数を計算します。
  9- T (温度): 温度をスキャンし、ゼーベック係数を計算します。このモードでは、フェルミ準位、キャリア濃度、または還元フェルミ準位のいずれかを固定できます。
 10- m_eff (有効質量): 有効質量をスキャンし、ゼーベック係数を計算します。このモードでも、フェルミ準位、キャリア濃度、または還元フェルミ準位のいずれかを固定できます。
 11
 12計算はFermi–Dirac積分に基づき、キャリアタイプ(電子または正孔)を指定できます。
 13結果はCSVファイルとして保存することも可能です。
 14
 15散乱因子 r はキャリアの緩和時間 tau(E) がエネルギー E の (r-1/2)乗に比例することで定義されます。
 16
 17関連リンク:
 18Seebeck_scan_usage
 19"""
 20import argparse
 21import csv
 22from functools import lru_cache
 23
 24import matplotlib.pyplot as plt
 25import numpy as np
 26from scipy.integrate import quad
 27from scipy.optimize import brentq
 28from scipy.special import expit, gamma
 29
 30# =========================
 31# constants (SI)
 32# =========================
 33kB_SI = 1.380649e-23           # J/K
 34e_SI  = 1.602176634e-19        # C
 35h_SI  = 6.62607015e-34         # J s
 36m0_SI = 9.1093837015e-31       # kg
 37kB_eV = 8.617333262145e-5      # eV/K
 38
 39
 40# ============================================================
 41# Fermi–Dirac integral (NO 1/Gamma(j+1) prefactor)
 42#   Fj(j,xi) = ∫_0^∞ x^j / (1+exp(x-xi)) dx
 43# ============================================================
 44@lru_cache(maxsize=20000)
 45def Fj(j, xi):
 46    """
 47    概要:
 48        フェルミ・ディラック積分を計算します(ガンマ関数による前因子なし)。
 49    詳細説明:
 50        Fj(j, xi) = ∫_0^∞ x^j / (1+exp(x-xi)) dx を数値的に評価します。
 51        この関数は、引数 j と xi に対してフェルミ・ディラック積分を計算します。
 52        標準的な定義に含まれる 1/Gamma(j+1) の前因子は含まれません。
 53        計算結果は lru_cache デコレータによってキャッシュされ、同じ引数での再計算を防ぎ、
 54        パフォーマンスを向上させます。
 55        j が -1.0 以下の場合、ValueError を発生させます。
 56    引数:
 57        :param j: フェルミ・ディラック積分の次数。j は -1 より大きい必要があります。
 58        :type j: float
 59        :param xi: 還元フェルミ準位 (xi = E_F / (k_B T))。
 60        :type xi: float
 61    戻り値:
 62        :returns: 計算されたフェルミ・ディラック積分の値。
 63        :rtype: float
 64    例外:
 65        :raises ValueError: j が -1.0 以下の場合。
 66    """
 67    j = float(j)
 68    xi = float(np.round(xi, 12))
 69    if j <= -1.0:
 70        raise ValueError(f"Fj(j, xi) requires j > -1, got j={j}")
 71
 72    integrand = lambda x: (x**j) * expit(xi - x)
 73    val, _ = quad(integrand, 0.0, np.inf, epsabs=1e-10, epsrel=1e-10, limit=400)
 74    return val
 75
 76
 77def Nc_3D(T, m_eff=1.0):
 78    """
 79    概要:
 80        3次元における有効状態密度 Nc を計算します。
 81    詳細説明:
 82        温度 T と有効質量 m_eff に基づいて、3次元バンドの有効状態密度 Nc を計算します。
 83        Nc は以下の式で計算されます。
 84        Nc = 2 * (2 * pi * m_eff * m0_SI * kB_SI * T / (h_SI**2)) ** 1.5
 85    引数:
 86        :param T: 温度 [K]。
 87        :type T: float
 88        :param m_eff: 有効質量比 m*/m0。m0 は自由電子質量です。
 89        :type m_eff: float
 90    戻り値:
 91        :returns: 計算された有効状態密度 Nc [m^-3]。
 92        :rtype: float
 93    """
 94    mstar = m_eff * m0_SI
 95    return 2.0 * (2.0 * np.pi * mstar * kB_SI * T / (h_SI**2)) ** 1.5
 96
 97
 98def electron_density_from_xi(xi, T, m_eff=1.0):
 99    """
100    概要:
101        還元フェルミ準位 xi から電子濃度を計算します。
102    詳細説明:
103        還元フェルミ準位 xi、温度 T、有効質量 m_eff を用いて電子濃度 n を計算します。
104        電子濃度 n は以下の式で与えられます:
105        n = Nc * Fj(0.5, xi) / Gamma(1.5)
106        ここで、Nc は有効状態密度、Fj(0.5, xi) はガンマ関数による前因子を含まないフェルミ・ディラック積分です。
107    引数:
108        :param xi: 還元フェルミ準位。
109        :type xi: float
110        :param T: 温度 [K]。
111        :type T: float
112        :param m_eff: 有効質量比 m*/m0。
113        :type m_eff: float
114    戻り値:
115        :returns: 計算された電子濃度 n [m^-3]。
116        :rtype: float
117    """
118    Nc = Nc_3D(T, m_eff=m_eff)
119    F12_std = Fj(0.5, xi) / gamma(1.5)
120    return Nc * F12_std
121
122
123def xi_from_electron_density(n_m3, T, m_eff=1.0, xi_low=-40.0, xi_high=120.0):
124    """
125    概要:
126        与えられた電子濃度から還元フェルミ準位 xi を計算します。
127    詳細説明:
128        この関数は、electron_density_from_xi 関数を解いて xi を見つけるために、
129        Brentのアルゴリズム (scipy.optimize.brentq) を使用します。
130        初期の探索範囲 [xi_low, xi_high] で解が見つからない場合、
131        自動的に探索範囲を拡張しようと試みます。
132        電子濃度が0以下の場合、ValueError を発生させます。
133    引数:
134        :param n_m3: 電子濃度 [m^-3]。正の値である必要があります。
135        :type n_m3: float
136        :param T: 温度 [K]。
137        :type T: float
138        :param m_eff: 有効質量比 m*/m0。
139        :type m_eff: float
140        :param xi_low: brentqの初期探索範囲の下限。
141        :type xi_low: float
142        :param xi_high: brentqの初期探索範囲の上限。
143        :type xi_high: float
144    戻り値:
145        :returns: 計算された還元フェルミ準位 xi。
146        :rtype: float
147    例外:
148        :raises ValueError: 電子濃度が正でない場合。
149        :raises RuntimeError: 指定された電子濃度に対するxiの探索範囲を見つけられなかった場合。
150    """
151    if n_m3 <= 0.0:
152        raise ValueError(f"Electron density must be positive, got {n_m3}")
153
154    def f(xi):
155        return electron_density_from_xi(xi, T, m_eff) - n_m3
156
157    fl = f(xi_low)
158    fh = f(xi_high)
159
160    # 探索範囲を動的に拡張
161    n_expand = 0
162    while fl > 0.0 and n_expand < 20:
163        xi_low -= 20.0
164        fl = f(xi_low)
165        n_expand += 1
166
167    n_expand = 0
168    while fh < 0.0 and n_expand < 20:
169        xi_high += 20.0
170        fh = f(xi_high)
171        n_expand += 1
172
173    if fl * fh > 0.0:
174        raise RuntimeError(
175            "Failed to bracket xi for the requested electron density. "
176            f"n={n_m3:.6e} m^-3, T={T}, m_eff={m_eff}, "
177            f"f({xi_low})={fl:.6e}, f({xi_high})={fh:.6e}"
178        )
179
180    return brentq(f, xi_low, xi_high, xtol=1e-12, rtol=1e-12, maxiter=200)
181
182
183def seebeck_from_xi(xi, r, carrier="electron"):
184    """
185    概要:
186        還元フェルミ準位 xi からゼーベック係数を計算します。
187    詳細説明:
188        還元フェルミ準位 xi、散乱因子 r、キャリアタイプに基づいてゼーベック係数 S を計算します。
189        ゼーベック係数は以下の式で計算されます。
190        S = (k_B / q) * [((r + 2) / (r + 1)) * (Fj(r+1, xi) / Fj(r, xi)) - xi]
191        ここで q はキャリアの電荷(電子の場合は -e_SI、正孔の場合は +e_SI)です。
192        散乱因子 r は -1.0 より大きい必要があります。
193    引数:
194        :param xi: 還元フェルミ準位。
195        :type xi: float
196        :param r: 散乱因子。r は -1 より大きい必要があります。
197        :type r: float
198        :param carrier: キャリアのタイプ ("electron" または "hole")。
199        :type carrier: str
200    戻り値:
201        :returns: 計算されたゼーベック係数 S [V/K]。
202        :rtype: float
203    例外:
204        :raises ValueError: キャリアタイプが 'electron' または 'hole' ではない場合、または r が -1.0 以下の場合。
205    """
206    if carrier.lower().startswith("e"):
207        q = -e_SI
208    elif carrier.lower().startswith("h"):
209        q = +e_SI
210    else:
211        raise ValueError("carrier must be 'electron' or 'hole'")
212
213    if r <= -1.0:
214        raise ValueError(f"r must be > -1, got r={r}")
215
216    Fr = Fj(r, xi)
217    Fr1 = Fj(r + 1.0, xi)
218    bracket = ((r + 2.0) / (r + 1.0)) * (Fr1 / Fr) - xi
219    return (kB_SI / q) * bracket
220
221
222def parse_r_list(text):
223    """
224    概要:
225        カンマ区切りの文字列から散乱因子 r のリストをパースします。
226    詳細説明:
227        入力文字列を解析し、浮動小数点数の散乱因子 r のリストを返します。
228        各散乱因子は -1 より大きい必要があります。
229    引数:
230        :param text: カンマで区切られた散乱因子の文字列(例: "2,1.5,1")。
231        :type text: str
232    戻り値:
233        :returns: パースされた散乱因子 r のリスト。
234        :rtype: list[float]
235    例外:
236        :raises ValueError: r のリストが空の場合、またはリスト内のいずれかの r が -1.0 以下の場合。
237    """
238    vals = [float(x.strip()) for x in text.split(",") if x.strip()]
239    if not vals:
240        raise ValueError("r-list is empty")
241    for r in vals:
242        if r <= -1.0:
243            raise ValueError(f"Each r must be > -1, got {r}")
244    return vals
245
246
247def default_scan_settings(mode):
248    """
249    概要:
250        指定されたスキャンモードのデフォルト設定を返します。
251    詳細説明:
252        スキャンモードに基づいて、スキャン範囲の最小値、最大値、点数、および対数スケールフラグのデフォルト値を決定します。
253    引数:
254        :param mode: スキャンモード ("EF", "Ne", "T", "m_eff")。
255        :type mode: str
256    戻り値:
257        :returns: (scan_min, scan_max, npts, logscan) のタプル。
258        :rtype: tuple
259    例外:
260        :raises ValueError: 未知のスキャンモードが指定された場合。
261    """
262    if mode == "EF":
263        return -0.2, 0.5, 121, False
264    if mode == "Ne":
265        return 1e15, 1e21, 121, True
266    if mode == "T":
267        return 100.0, 800.0, 121, False
268    if mode == "m_eff":
269        return 0.1, 3.0, 121, False
270    raise ValueError(f"Unknown mode: {mode}")
271
272
273def make_scan_values(mode, scan_min, scan_max, npts, logscan):
274    """
275    概要:
276        スキャンする値の配列を生成します。
277    詳細説明:
278        指定された範囲 (scan_min, scan_max) と点数 npts に基づいて、
279        線形または対数スケールのスキャン値配列を生成します。
280        対数スケールの場合、scan_min と scan_max は正の値である必要があります。
281    引数:
282        :param mode: スキャンモード。この引数は主にエラーメッセージの表示に使用されます。
283        :type mode: str
284        :param scan_min: スキャン範囲の最小値。
285        :type scan_min: float
286        :param scan_max: スキャン範囲の最大値。
287        :type scan_max: float
288        :param npts: スキャン点の数。
289        :type npts: int
290        :param logscan: スキャン値を対数スケールで生成するかどうか。
291        :type logscan: bool
292    戻り値:
293        :returns: 生成されたスキャン値の配列。
294        :rtype: numpy.ndarray
295    例外:
296        :raises ValueError: 対数スキャンで scan_min または scan_max が0以下の場合。
297    """
298    if logscan:
299        if scan_min <= 0.0 or scan_max <= 0.0:
300            raise ValueError("log scan requires scan_min > 0 and scan_max > 0")
301        return np.logspace(np.log10(scan_min), np.log10(scan_max), npts)
302    return np.linspace(scan_min, scan_max, npts)
303
304
305def resolve_xi(mode, scan_value, T0, m_eff0, EF0_eV, Ne0_cm3, xi0, fixed):
306    """
307    概要:
308        現在のスキャン値とモードに基づいて、還元フェルミ準位 (xi)、フェルミ準位 (EF_eV)、
309        電子濃度 (Ne_cm3)、温度 (T_K)、および有効質量 (m_eff) を解決します。
310    詳細説明:
311        この関数は、mode 引数に応じて、scan_value を基に物理量を計算します。
312        - mode が "EF" の場合、scan_value は EF_eV として扱われ、Tとm_effは T0 と m_eff0 を使用します。
313        - mode が "Ne" の場合、scan_value は Ne_cm3 として扱われ、Tとm_effは T0 と m_eff0 を使用します。
314        - mode が "T" の場合、scan_value は T_K として扱われ、m_effは m_eff0 を使用し、
315          fixed 引数("EF", "Ne", "xi")に基づいて EF_eV または Ne_cm3 を固定して xi を計算します。
316        - mode が "m_eff" の場合、scan_value は m_eff として扱われ、Tは T0 を使用し、
317          fixed 引数("EF", "Ne", "xi")に基づいて EF_eV または Ne_cm3 を固定して xi を計算します。
318    引数:
319        :param mode: 現在のスキャンモード ("EF", "Ne", "T", "m_eff")。
320        :type mode: str
321        :param scan_value: 現在のスキャン対象の値。
322        :type scan_value: float
323        :param T0: 基準温度 [K]。
324        :type T0: float
325        :param m_eff0: 基準有効質量比 m*/m0。
326        :type m_eff0: float
327        :param EF0_eV: 基準フェルミ準位 [eV]。mode が "T" または "m_eff" で fixed が "EF" の場合に固定値として使用。
328        :type EF0_eV: float
329        :param Ne0_cm3: 基準電子濃度 [cm^-3]。mode が "T" または "m_eff" で fixed が "Ne" の場合に固定値として使用。
330        :type Ne0_cm3: float
331        :param xi0: 基準還元フェルミ準位。mode が "T" または "m_eff" で fixed が "xi" の場合に固定値として使用。
332        :type xi0: float
333        :param fixed: mode が "T" または "m_eff" の場合に固定する変数 ("EF", "Ne", "xi")。
334        :type fixed: str
335    戻り値:
336        :returns: (xi, EF_eV, Ne_cm3, T_K, m_eff) のタプル。
337        :rtype: tuple[float, float, float, float, float]
338    例外:
339        :raises ValueError: 未知のスキャンモードまたは固定条件が指定された場合。
340    """
341    if mode == "EF":
342        T = T0
343        m_eff = m_eff0
344        EF_eV = scan_value
345        xi = EF_eV / (kB_eV * T)
346    elif mode == "Ne":
347        T = T0
348        m_eff = m_eff0
349        Ne_cm3 = scan_value
350        xi = xi_from_electron_density(Ne_cm3 * 1e6, T=T, m_eff=m_eff)
351        EF_eV = xi * kB_eV * T
352    elif mode == "T":
353        T = scan_value
354        m_eff = m_eff0
355        if fixed == "EF":
356            EF_eV = EF0_eV
357            xi = EF_eV / (kB_eV * T)
358        elif fixed == "Ne":
359            xi = xi_from_electron_density(Ne0_cm3 * 1e6, T=T, m_eff=m_eff)
360            EF_eV = xi * kB_eV * T
361        elif fixed == "xi":
362            xi = xi0
363            EF_eV = xi * kB_eV * T
364        else:
365            raise ValueError(f"Unknown fixed condition: {fixed}")
366    elif mode == "m_eff":
367        T = T0
368        m_eff = scan_value
369        if fixed == "EF":
370            EF_eV = EF0_eV
371            xi = EF_eV / (kB_eV * T)
372        elif fixed == "Ne":
373            xi = xi_from_electron_density(Ne0_cm3 * 1e6, T=T, m_eff=m_eff)
374            EF_eV = xi * kB_eV * T
375        elif fixed == "xi":
376            xi = xi0
377            EF_eV = xi * kB_eV * T
378        else:
379            raise ValueError(f"Unknown fixed condition: {fixed}")
380    else:
381        raise ValueError(f"Unknown mode: {mode}")
382
383    Ne_cm3 = electron_density_from_xi(xi, T=T, m_eff=m_eff) / 1e6
384    return xi, EF_eV, Ne_cm3, T, m_eff
385
386
387def x_label(mode):
388    """
389    概要:
390        指定されたスキャンモードに対応するX軸ラベル文字列を返します。
391    詳細説明:
392        プロットのX軸に表示する適切なラベルを返します。
393        返されるラベルはLaTeX形式の数式を含む場合があります。
394    引数:
395        :param mode: スキャンモード ("EF", "Ne", "T", "m_eff")。
396        :type mode: str
397    戻り値:
398        :returns: X軸のラベル文字列。
399        :rtype: str
400    """
401    return {
402        "EF": r"$E_F-E_c$ (eV)",
403        "Ne": r"$N_e$ (cm$^{-3}$)",
404        "T": r"$T$ (K)",
405        "m_eff": r"$m^*/m_0$",
406    }[mode]
407
408
409def make_title(mode, carrier, fixed, T0, m_eff0, EF0_eV, Ne0_cm3, xi0):
410    """
411    概要:
412        プロットのタイトル文字列を生成します。
413    詳細説明:
414        スキャンモード、キャリアタイプ、固定条件などのパラメータに基づいて、グラフのタイトルを作成します。
415        タイトルには、スキャンモード、キャリアタイプ、および固定されている物理量とその値が含まれます。
416    引数:
417        :param mode: スキャンモード ("EF", "Ne", "T", "m_eff")。
418        :type mode: str
419        :param carrier: キャリアタイプ ("electron" または "hole")。
420        :type carrier: str
421        :param fixed: 固定された変数 ("EF", "Ne", "xi")。mode が "T" または "m_eff" の場合にのみ関連します。
422        :type fixed: str
423        :param T0: 基準温度 [K]。
424        :type T0: float
425        :param m_eff0: 基準有効質量比 m*/m0。
426        :type m_eff0: float
427        :param EF0_eV: 基準フェルミ準位 [eV]。
428        :type EF0_eV: float
429        :param Ne0_cm3: 基準電子濃度 [cm^-3]。
430        :type Ne0_cm3: float
431        :param xi0: 基準還元フェルミ準位。
432        :type xi0: float
433    戻り値:
434        :returns: 生成されたタイトル文字列。
435        :rtype: str
436    """
437    base = f"Seebeck coefficient scan ({mode}, {carrier})"
438    if mode in ["EF", "Ne"]:
439        return base + f"\nT={T0:g} K, m*/m0={m_eff0:g}"
440    if fixed == "EF":
441        fixed_text = f"fixed EF={EF0_eV:g} eV"
442    elif fixed == "Ne":
443        fixed_text = f"fixed Ne={Ne0_cm3:.3e} cm^-3"
444    else: # fixed == "xi"
445        fixed_text = f"fixed xi={xi0:g}"
446    return base + f"\n{fixed_text}, T={T0:g} K, m*/m0={m_eff0:g}"
447
448
449def save_csv(outfile, rows):
450    """
451    概要:
452        計算結果をCSVファイルに保存します。
453    詳細説明:
454        計算されたゼーベック係数と関連する物理量を、指定されたファイル名でCSV形式で保存します。
455        outfile が空文字列の場合、ファイル保存はスキップされます。
456        CSVヘッダーは固定されており、以下のフィールドが含まれます:
457        "mode", "scan_value", "r", "S_V_per_K", "S_uV_per_K",
458        "xi", "EF_eV", "Ne_cm3", "T_K", "m_eff_m0"
459    引数:
460        :param outfile: 出力CSVファイルのパス。空文字列の場合、ファイルは保存されません。
461        :type outfile: str
462        :param rows: 各行が辞書形式で表現されたデータのリスト。
463        :type rows: list[dict]
464    戻り値:
465        :returns: None
466        :rtype: None
467    """
468    if not outfile:
469        return
470    fieldnames = [
471        "mode", "scan_value", "r", "S_V_per_K", "S_uV_per_K",
472        "xi", "EF_eV", "Ne_cm3", "T_K", "m_eff_m0"
473    ]
474    with open(outfile, "w", newline="", encoding="utf-8-sig") as f:
475        writer = csv.DictWriter(f, fieldnames=fieldnames)
476        writer.writeheader()
477        writer.writerows(rows)
478
479
480def main():
481    """
482    概要:
483        ゼーベック係数スキャンプログラムのメインエントリポイント。
484    詳細説明:
485        コマンドライン引数をパースし、指定されたモードとパラメータに基づいてゼーベック係数を計算し、結果をプロットまたはCSVファイルに保存します。
486        argparse を使用して、スキャンモード、固定条件、キャリアタイプ、基準温度、有効質量、フェルミ準位、電子濃度、還元フェルミ準位、散乱因子のリスト、スキャン範囲、点数、対数スキャンオプション、X軸スケール、CSV出力ファイル名、プロット表示の有無などの引数を定義します。
487        デフォルトのスキャン設定を適用し、スキャン値の配列を生成します。
488        指定された散乱因子のリスト r_list の各値に対して、スキャン値 (xvals) ごとにゼーベック係数を計算します。
489        各計算結果は rows リストに追加され、後でCSVとして保存するために使用されます。
490        計算結果は matplotlib を使用してプロットされ、適切なラベル、タイトル、凡例が設定されます。
491        --csv オプションが指定された場合、save_csv 関数を呼び出して結果を保存します。
492        --no-show オプションが指定されない限り、プロットを表示します。
493    引数:
494        None
495    戻り値:
496        None
497    """
498    parser = argparse.ArgumentParser(
499        description="Unified Seebeck scan script for EF / Ne / T / m_eff"
500    )
501    parser.add_argument("--mode", choices=["EF", "Ne", "T", "m_eff"], default="EF",
502                        help="scan target")
503    parser.add_argument("--fixed", choices=["EF", "Ne", "xi"], default="EF",
504                        help="for mode=T or m_eff, what to keep fixed")
505    parser.add_argument("--carrier", choices=["electron", "hole"], default="electron")
506    parser.add_argument("--T", type=float, default=300.0,
507                        help="reference temperature [K]")
508    parser.add_argument("--m_eff", type=float, default=1.0,
509                        help="reference effective mass m*/m0")
510    parser.add_argument("--EF", type=float, default=0.10,
511                        help="reference EF-Ec [eV] (or corresponding band-edge reference)")
512    parser.add_argument("--Ne", type=float, default=1e18,
513                        help="reference electron density [cm^-3]")
514    parser.add_argument("--xi", type=float, default=0.0,
515                        help="reference reduced Fermi energy xi")
516    parser.add_argument("--r-list", default="2,1.5,1,0.5,0,-0.5",
517                        help="comma-separated r list")
518    parser.add_argument("--scan-min", type=float, default=None)
519    parser.add_argument("--scan-max", type=float, default=None)
520    parser.add_argument("--n", type=int, default=None,
521                        help="number of scan points")
522    parser.add_argument("--logscan", action="store_true",
523                        help="use log spacing for scan values")
524    parser.add_argument("--xscale", choices=["auto", "linear", "log"], default="auto")
525    parser.add_argument("--csv", default="",
526                        help="optional CSV output filename")
527    parser.add_argument("--no-show", action="store_true",
528                        help="do not display plot")
529    args = parser.parse_args()
530
531    r_list = parse_r_list(args.r_list)
532
533    dmin, dmax, dn, dlog = default_scan_settings(args.mode)
534    scan_min = dmin if args.scan_min is None else args.scan_min
535    scan_max = dmax if args.scan_max is None else args.scan_max
536    npts = dn if args.n is None else args.n
537    logscan = args.logscan or dlog
538
539    if npts < 2:
540        raise ValueError("n must be >= 2")
541    if scan_max <= scan_min:
542        raise ValueError("scan-max must be larger than scan-min")
543    if args.T <= 0.0:
544        raise ValueError("T must be positive")
545    if args.m_eff <= 0.0:
546        raise ValueError("m_eff must be positive")
547    if args.Ne <= 0.0:
548        raise ValueError("Ne must be positive")
549
550    xvals = make_scan_values(args.mode, scan_min, scan_max, npts, logscan)
551
552    rows = []
553    plt.figure(figsize=(8, 6))
554
555    for r in r_list:
556        yvals_uV = []
557        for x in xvals:
558            xi, EF_eV, Ne_cm3, T_K, m_eff = resolve_xi(
559                mode=args.mode,
560                scan_value=float(x),
561                T0=args.T,
562                m_eff0=args.m_eff,
563                EF0_eV=args.EF,
564                Ne0_cm3=args.Ne,
565                xi0=args.xi,
566                fixed=args.fixed,
567            )
568            S = seebeck_from_xi(xi, r=r, carrier=args.carrier)
569            yvals_uV.append(S * 1e6)
570            rows.append({
571                "mode": args.mode,
572                "scan_value": float(x),
573                "r": float(r),
574                "S_V_per_K": float(S),
575                "S_uV_per_K": float(S * 1e6),
576                "xi": float(xi),
577                "EF_eV": float(EF_eV),
578                "Ne_cm3": float(Ne_cm3),
579                "T_K": float(T_K),
580                "m_eff_m0": float(m_eff),
581            })
582
583        plt.plot(xvals, yvals_uV, label=f"r = {r:g}")
584
585    save_csv(args.csv, rows)
586
587    plt.axhline(0.0, linewidth=1.0, alpha=0.4)
588    plt.xlabel(x_label(args.mode))
589    plt.ylabel(r"$S$ ($\mu$V/K)")
590    plt.title(make_title(args.mode, args.carrier, args.fixed, args.T, args.m_eff, args.EF, args.Ne, args.xi))
591
592    xscale = args.xscale
593    if xscale == "auto":
594        xscale = "log" if (args.mode == "Ne" or logscan) else "linear"
595    plt.xscale(xscale)
596
597    plt.grid(True, which="both", alpha=0.25)
598    plt.legend()
599    plt.tight_layout()
600
601    if not args.no_show:
602        plt.show()
603
604
605if __name__ == "__main__":
606    main()