tiny_simulations.anharmonic3D のソースコード

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
1次元単原子チェーン(最近接相互作用)における非調和フォノン振動数の温度シフトを計算するモジュール。

関連リンク: :doc:`anharmonic3D_usage`

詳細説明:
モデル(相対変位 Δ_n = u_{n+1} - u_n):
  V = Σ_n [ (K2/2) Δ_n^2 + (K3/3!) Δ_n^3 + (K4/4!) Δ_n^4 ]

以下の摂動論的計算を行います:
  ω_q(T) ≈ ω_q + Δω_q^(4)(T) + Δω_q^(3)(T)
ここで
  Δω_q ≈ Re Σ(q, ω_q) / (2 ω_q)

- 4次 (K4) "Tadpole" シフト: ⟨|u_k|^2⟩ の単純な熱平均。
- 3次 (K3) "Bubble" シフト: kに対する主値積分(小さな eta で正則化)。

単位系:
- デフォルトでSI単位系を使用 (a: m, M: kg, K2: N/m, K3: N/m^2, K4: N/m^3)。
- 内部の振動数は rad/s で計算され、出力時に THz へ変換可能です。

k2_func/k3_func/k4_func を編集することで、K2/K3/K4 を (delta, T) の関数としてカスタマイズできます。
"""

from __future__ import annotations
import argparse
import math
from dataclasses import dataclass
from typing import Callable, Tuple, List

import numpy as np

# Physical constants (SI)
HBAR = 1.054_571_817e-34  # J*s
KB   = 1.380_649e-23      # J/K
TWOPI = 2.0 * math.pi


# -----------------------------
# User-customizable force constants (as functions)
# -----------------------------
[ドキュメント] def k2_func(delta: float = 0.0, T: float | None = None) -> float: """ 調和力の定数K2を計算する。 詳細説明: 調和力の定数K2 [N/m] (無次元規則を使用する場合は一貫した単位)。 任意の関数に置き換え可能。 :param delta: float, 歪み (デフォルト: 0.0) :param T: float | None, 温度 (デフォルト: None) :returns: float, 調和力の定数K2 """ return 10.0 # example
[ドキュメント] def k3_func(delta: float = 0.0, T: float | None = None) -> float: """ 3次の非調和定数K3を計算する。 詳細説明: 3次の非調和定数K3 [N/m^2]。 任意の関数に置き換え可能。ゼロ以外の値を設定するとBubbleシフトが有効になります。 :param delta: float, 歪み (デフォルト: 0.0) :param T: float | None, 温度 (デフォルト: None) :returns: float, 3次の非調和定数K3 """ return 0.0 # example (set nonzero to enable bubble shift)
[ドキュメント] def k4_func(delta: float = 0.0, T: float | None = None) -> float: """ 4次の非調和定数K4を計算する。 詳細説明: 4次の非調和定数K4 [N/m^3]。 任意の関数に置き換え可能。ゼロ以外の値を設定するとTadpoleシフトが有効になります。 :param delta: float, 歪み (デフォルト: 0.0) :param T: float | None, 温度 (デフォルト: None) :returns: float, 4次の非調和定数K4 """ return 1.0e-5 # example (set nonzero to enable tadpole shift)
# ----------------------------- # Core model helpers # -----------------------------
[ドキュメント] def C_of_q(q: np.ndarray, a: float) -> np.ndarray: """ 関数 C(q) = e^{iqa} - 1 を計算する。 詳細説明: 主に |C(q)|^2 = 4 sin^2(qa/2) として利用される。 :param q: np.ndarray, 波数ベクトル :param a: float, 格子定数 :returns: np.ndarray, 計算されたC(q)の複素数値 """ return np.exp(1j * q * a) - 1.0
[ドキュメント] def absC2(q: np.ndarray, a: float) -> np.ndarray: """ |C(q)|^2 = 4 sin^2(qa/2) を計算する。 :param q: np.ndarray, 波数ベクトル :param a: float, 格子定数 :returns: np.ndarray, |C(q)|^2の実数値 """ return 4.0 * np.sin(0.5 * q * a) ** 2
[ドキュメント] def omega_harmonic(q: np.ndarray, a: float, M: float, K2: float) -> np.ndarray: """ 調和分散(最近接相互作用)による振動数を計算する。 詳細説明: ω_q^2 = (4 K2 / M) sin^2(qa/2) を用いて振動数を求める。 :param q: np.ndarray, 波数ベクトル :param a: float, 格子定数 :param M: float, 質量 :param K2: float, 調和力の定数 :returns: np.ndarray, 計算された調和振動数 """ return np.sqrt((4.0 * K2 / M) * np.sin(0.5 * q * a) ** 2)
[ドキュメント] def bose_n(omega: np.ndarray, T: float, use_quantum: bool = True) -> np.ndarray: """ ボース分布関数 n(ω) = 1/(exp(β ħ ω) - 1) を計算する。 詳細説明: use_quantum=False の場合は古典極限 n ~ kT/(ħ ω) を用いる。 :param omega: np.ndarray, 振動数 :param T: float, 温度 :param use_quantum: bool, 量子論的計算を使用するかどうか (デフォルト: True) :returns: np.ndarray, ボース分布関数の値 """ if T <= 0.0: return np.zeros_like(omega) if not use_quantum: # classical limit return (KB * T) / (HBAR * np.maximum(omega, 1e-300)) x = (HBAR * omega) / (KB * T) # avoid overflow for large x x = np.clip(x, 0.0, 700.0) return 1.0 / (np.expm1(x) + 1e-300)
[ドキュメント] def coth_halfbeta_hw(omega: np.ndarray, T: float, use_quantum: bool = True) -> np.ndarray: """ 双曲線余接関数 coth(βħω/2) を計算する。 詳細説明: T -> 0 で値は 1 に漸近する。古典極限では coth ~ 2kT/(ħω) を用いる。 :param omega: np.ndarray, 振動数 :param T: float, 温度 :param use_quantum: bool, 量子論的計算を使用するかどうか (デフォルト: True) :returns: np.ndarray, coth(βħω/2) の値 """ if T <= 0.0: return np.ones_like(omega) if not use_quantum: return (2.0 * KB * T) / (HBAR * np.maximum(omega, 1e-300)) x = (HBAR * omega) / (2.0 * KB * T) x = np.clip(x, 0.0, 350.0) # coth(x) = cosh(x)/sinh(x) = (e^x + e^-x)/(e^x - e^-x) ex = np.exp(x) emx = 1.0 / ex return (ex + emx) / (ex - emx + 1e-300)
# ----------------------------- # Quartic (K4) tadpole-like shift # -----------------------------
[ドキュメント] def delta_omega_quartic_tadpole( q: float, kgrid: np.ndarray, a: float, M: float, K2: float, K4: float, T: float, use_quantum: bool, k_cut: float, ) -> float: """ 4次(K4)のTadpoleシフト Δω_q^(4) を計算する。 詳細説明: 以下の構造を持つ1次元の実装: Σ^(4)(q) ∝ K4 |C(q)|^2 * (1/N) Σ_k |C(k)|^2 <|u_k|^2> <|u_k|^2> = ħ/(2 M ω_k) coth(βħω_k/2) Δω_q^(4) = ReΣ/(2ω_q) を返す。 :param q: float, 波数q :param kgrid: np.ndarray, 波数kのグリッド :param a: float, 格子定数 :param M: float, 質量 :param K2: float, 調和力の定数 :param K4: float, 4次非調和定数 :param T: float, 温度 :param use_quantum: bool, 量子論的計算を使用するかどうか :param k_cut: float, 赤外発散を防ぐためのkのカットオフ :returns: float, 4次周波数シフト量 Δω_q^(4) """ omega_k = omega_harmonic(kgrid, a, M, K2) omega_q = float(omega_harmonic(np.array([q]), a, M, K2)[0]) if omega_q <= 0.0: return 0.0 mask = np.abs(kgrid) >= k_cut kk = kgrid[mask] omk = omega_k[mask] # <|u_k|^2> u2 = (HBAR / (2.0 * M * np.maximum(omk, 1e-300))) * coth_halfbeta_hw(omk, T, use_quantum=use_quantum) S = np.mean(absC2(kk, a) * u2) # (1/N) Σ_k ... # prefactor (model choice) # Σ^(4) ~ (K4 / 2) |C(q)|^2 * S Sigma4 = 0.5 * K4 * absC2(np.array([q]), a)[0] * S d_omega = Sigma4 / (2.0 * omega_q) return float(d_omega)
# ----------------------------- # Cubic (K3) bubble-like shift # -----------------------------
[ドキュメント] def v3_squared( q: float, k: np.ndarray, a: float, M: float, K3: float, omega_q: float, omega_k: np.ndarray, omega_qmk: np.ndarray, ) -> np.ndarray: """ 3次相互作用の行列要素の2乗 |V3(q,k,q-k)|^2 を計算する。 詳細説明: V3 ∝ K3 * C(q)C(k)C(q-k) / sqrt( (2M)^3 ω_q ω_k ω_{q-k} ) 複素位相を避けるため |C|^2 = 4 sin^2(...) を使用する。 :param q: float, 波数q :param k: np.ndarray, 波数kの配列 :param a: float, 格子定数 :param M: float, 質量 :param K3: float, 3次非調和定数 :param omega_q: float, 波数qにおける調和振動数 :param omega_k: np.ndarray, 波数kにおける調和振動数 :param omega_qmk: np.ndarray, 波数q-kにおける調和振動数 :returns: np.ndarray, |V3(q,k,q-k)|^2 の値 """ # use |C| instead of C itself (phases cancel in |V|^2) num = absC2(np.array([q]), a)[0] * absC2(k, a) * absC2((q - k), a) den = (2.0 * M) ** 3 * np.maximum(omega_q * omega_k * omega_qmk, 1e-300) return (K3 ** 2) * num / den
[ドキュメント] def re_sigma_cubic_bubble( q: float, kgrid: np.ndarray, a: float, M: float, K2: float, K3: float, T: float, use_quantum: bool, eta: float, k_cut: float, ) -> float: """ Bubble構造による自己エネルギーの実部 Re Σ^(3)(q, ω_q) を計算する。 詳細説明: etaを用いた正則化により主値積分を近似する: Re[1/(x + iη)] = x/(x^2 + η^2) :param q: float, 波数q :param kgrid: np.ndarray, 波数kのグリッド :param a: float, 格子定数 :param M: float, 質量 :param K2: float, 調和力の定数 :param K3: float, 3次非調和定数 :param T: float, 温度 :param use_quantum: bool, 量子論的計算を使用するかどうか :param eta: float, 正則化パラメータ :param k_cut: float, 赤外発散を防ぐためのkのカットオフ :returns: float, 自己エネルギーの実部 Re Σ^(3)(q, ω_q) """ omega_q = float(omega_harmonic(np.array([q]), a, M, K2)[0]) if omega_q <= 0.0: return 0.0 mask = np.abs(kgrid) >= k_cut k = kgrid[mask] omega_k = omega_harmonic(k, a, M, K2) omega_qmk = omega_harmonic(q - k, a, M, K2) nk = bose_n(omega_k, T, use_quantum=use_quantum) nqm = bose_n(omega_qmk, T, use_quantum=use_quantum) V2 = v3_squared(q, k, a, M, K3, omega_q, omega_k, omega_qmk) def re_inv(x: np.ndarray) -> np.ndarray: """ 実部の逆数近似を計算する内部関数。 詳細説明: x / (x^2 + eta^2) を計算し、主値積分を正則化する。 :param x: np.ndarray, 分母の変数 :returns: np.ndarray, 正則化された値 """ return x / (x * x + eta * eta) w = omega_q x1 = w - omega_k - omega_qmk x2 = w - omega_k + omega_qmk x3 = w + omega_k - omega_qmk x4 = w + omega_k + omega_qmk term1 = (nk + nqm + 1.0) * re_inv(x1) term2 = (nk - nqm) * re_inv(x2) term3 = (nqm - nk) * re_inv(x3) term4 = (nk + nqm + 1.0) * re_inv(x4) Sigma3 = np.sum(V2 * (term1 + term2 + term3 + term4)) / k.size return float(Sigma3)
[ドキュメント] def delta_omega_cubic_bubble( q: float, kgrid: np.ndarray, a: float, M: float, K2: float, K3: float, T: float, use_quantum: bool, eta: float, k_cut: float, ) -> float: """ 3次(K3)のBubbleシフト Δω_q^(3) を計算する。 詳細説明: Δω_q^(3) = Re Σ^(3)(q, ω_q) / (2 ω_q) :param q: float, 波数q :param kgrid: np.ndarray, 波数kのグリッド :param a: float, 格子定数 :param M: float, 質量 :param K2: float, 調和力の定数 :param K3: float, 3次非調和定数 :param T: float, 温度 :param use_quantum: bool, 量子論的計算を使用するかどうか :param eta: float, 正則化パラメータ :param k_cut: float, 赤外発散を防ぐためのkのカットオフ :returns: float, 3次周波数シフト量 Δω_q^(3) """ omega_q = float(omega_harmonic(np.array([q]), a, M, K2)[0]) if omega_q <= 0.0: return 0.0 reS = re_sigma_cubic_bubble(q, kgrid, a, M, K2, K3, T, use_quantum, eta, k_cut) return reS / (2.0 * omega_q)
# ----------------------------- # Main calculation # -----------------------------
[ドキュメント] @dataclass class Params: """ 計算に使用する物理パラメータや条件を保持するデータクラス。 詳細説明: 格子定数、質量、k点数、カットオフ、量子論フラグ、正則化係数などをまとめている。 :param a: float, 格子定数 :param M: float, 質量 :param Nk: int, k点数 :param k_cut: float, 波数kのカットオフ :param use_quantum: bool, 量子論的計算を使用するかどうか :param eta_rel: float, 正則化パラメータの相対値 """ a: float M: float Nk: int k_cut: float use_quantum: bool eta_rel: float
[ドキュメント] def make_kgrid(Nk: int, a: float) -> np.ndarray: """ 第1ブリュアンゾーン内の均一なk点グリッドを作成する。 詳細説明: 区間 (-pi/a, pi/a] における均一なk点グリッドを生成する。 :param Nk: int, k点グリッドの数 :param a: float, 格子定数 :returns: np.ndarray, k点グリッドの配列 """ k = np.linspace(-math.pi / a, math.pi / a, Nk, endpoint=False) return k
[ドキュメント] def omega_T_for_q( q: float, T: float, kgrid: np.ndarray, p: Params, k2f: Callable[[float, float | None], float], k3f: Callable[[float, float | None], float], k4f: Callable[[float, float | None], float], ) -> Tuple[float, float, float, float]: """ 指定された波数qと温度Tに対する振動数および各シフト量を計算する。 :param q: float, 波数q :param T: float, 温度 :param kgrid: np.ndarray, 波数kのグリッド :param p: Params, 計算パラメータを保持するデータクラス :param k2f: Callable, K2を計算する関数 :param k3f: Callable, K3を計算する関数 :param k4f: Callable, K4を計算する関数 :returns: Tuple[float, float, float, float], (初期振動数, 3次シフト量, 4次シフト量, シフト後振動数) """ K2 = float(k2f(0.0, T)) K3 = float(k3f(0.0, T)) K4 = float(k4f(0.0, T)) omega0 = float(omega_harmonic(np.array([q]), p.a, p.M, K2)[0]) # regularization scale for principal value eta = max(p.eta_rel * omega0, 1e-12) d3 = 0.0 d4 = 0.0 if abs(K3) > 0.0 and omega0 > 0.0: d3 = delta_omega_cubic_bubble(q, kgrid, p.a, p.M, K2, K3, T, p.use_quantum, eta, p.k_cut) if abs(K4) > 0.0 and omega0 > 0.0: d4 = delta_omega_quartic_tadpole(q, kgrid, p.a, p.M, K2, K4, T, p.use_quantum, p.k_cut) omegaT = omega0 + d3 + d4 return omega0, d3, d4, omegaT
[ドキュメント] def parse_args() -> argparse.Namespace: """ コマンドライン引数をパースする。 :returns: argparse.Namespace, パースされた引数 """ ap = argparse.ArgumentParser(description="Anharmonic phonon frequency shift vs T (1D chain, K2/K3/K4 as functions).") ap.add_argument("--a", type=float, default=1.0, help="Lattice constant a [m] (or your unit).") ap.add_argument("--M", type=float, default=1.0, help="Mass M [kg] (or your unit).") ap.add_argument("--Nk", type=int, default=4096, help="Number of k-points in BZ.") ap.add_argument("--kcut", type=float, default=0.0, help="Exclude |k| < kcut to avoid IR issues (1D). Unit: 1/a.") ap.add_argument("--q", type=float, default=0.5, help="Target q as fraction of pi/a (q = frac * pi/a).") ap.add_argument("--Tmin", type=float, default=0.0, help="Minimum temperature [K].") ap.add_argument("--Tmax", type=float, default=300.0, help="Maximum temperature [K].") ap.add_argument("--nT", type=int, default=31, help="Number of temperature points.") ap.add_argument("--classical", action="store_true", help="Use classical (high-T) occupations instead of quantum.") ap.add_argument("--eta_rel", type=float, default=1e-3, help="Regularization eta = eta_rel * omega0 for bubble principal value.") ap.add_argument("--out", type=str, default="omega_vs_T.csv", help="Output CSV filename.") ap.add_argument("--thz", action="store_true", help="Output frequencies in THz (cycles/s) instead of rad/s.") return ap.parse_args()
[ドキュメント] def main() -> None: """ メインの実行関数。 詳細説明: コマンドライン引数を読み込み、計算パラメータを設定して各温度における フォノン振動数のシフトを計算し、CSVファイルとして出力する。 :returns: None """ args = parse_args() a = args.a M = args.M Nk = args.Nk q = args.q * (math.pi / a) # q in 1/m (or 1/a) k_cut = args.kcut * (1.0 / a) if args.kcut > 0 else 0.0 p = Params( a=a, M=M, Nk=Nk, k_cut=k_cut, use_quantum=(not args.classical), eta_rel=args.eta_rel, ) kgrid = make_kgrid(Nk, a) Ts = np.linspace(args.Tmin, args.Tmax, args.nT) rows: List[List[float]] = [] for T in Ts: omega0, d3, d4, omegaT = omega_T_for_q(q, float(T), kgrid, p, k2_func, k3_func, k4_func) rows.append([float(T), omega0, d3, d4, omegaT]) arr = np.array(rows, dtype=float) # unit conversion for output # rad/s -> THz (cycles/s) = omega/(2π)/1e12 if args.thz: conv = 1.0 / (TWOPI * 1e12) arr[:, 1:] *= conv header = "T,omega0,domega3_bubble,domega4_tadpole,omegaT" np.savetxt(args.out, arr, delimiter=",", header=header, comments="") print(f"Wrote: {args.out}") print("Columns:", header) if args.thz: print("Frequencies are in THz.") else: print("Frequencies are in rad/s.")
if __name__ == "__main__": main()