Quantum.tb_harrison のソースコード

"""
Slater-Kosterタイトバインディングモデルを実装するモジュール。

s, p, d軌道を持つ有限クラスター(分子)用の最小限のSlater-Kosterタイトバインディング(TB)モデルを提供します。
直交基底(S = I)と実対称ハミルトニアン(H)を仮定します。ハリスン則による積分パラメータのスケーリングに対応しています。

:doc:`tb_harrison_usage`
"""
import numpy as np
from scipy.linalg import eigh


[ドキュメント] class SlaterKosterTB: """Slater-Kosterタイトバインディング法を実装するクラス。 s, p, d軌道を持つ有限クラスター(分子)用の最小限のSlater-Kosterタイトバインディング(TB)モデルを提供します。 直交基底(S = I)と実対称ハミルトニアン(H)を仮定します。 ハリスン則によるスケーリングをサポートしており、`sk_params` の "harrison" エントリを通じて積分キーごとの指数を指定できます。 `harrison` パラメータの例: :: "harrison": { "d0": 1.0, "power_default": 2.0, "power_map": { "sd_sigma": 3.5, "pd_sigma": 3.5, "pd_pi": 3.5, # "dd_sigma": 5.0, "dd_pi": 5.0, "dd_delta": 5.0, # if you implement dd later }, # オプション: キーごとのd0 # "d0_map": {"pd_sigma": 2.0} } `harrison` が省略された場合、スケーリングは行われません(スケールは1)。 後方互換性のため、`{"d0":..., "power":...}` のみが与えられた場合、それがデフォルトとして使用されます。 """ def __init__(self): """SlaterKosterTBクラスの新しいインスタンスを初期化します。 既知の軌道タイプ(s, p, d)とそれに対応する軌道のリストを設定し、 原子リストとハミルトニアン行列を空の状態に初期化します。 """ self.orbitals = { "s": ["s"], "p": ["px", "py", "pz"], "d": ["dxy", "dyz", "dzx", "dx2-y2", "dz2"], } self.atoms = [] self.hamiltonian = None
[ドキュメント] def add_atom(self, symbol, orb_type, x, y, z): """モデルに原子を追加します。 指定された記号、軌道タイプ、および空間座標を持つ原子を内部リストに追加します。 軌道タイプが`orbitals`辞書に存在しない場合はエラーを発生させます。 :param symbol: str: 原子の元素記号。 :param orb_type: str: 原子の軌道タイプ(例: "s", "p", "d")。 :param x: float: 原子のX座標。 :param y: float: 原子のY座標。 :param z: float: 原子のZ座標。 :returns: None :raises ValueError: 未知の軌道タイプが指定された場合。 """ if orb_type not in self.orbitals: raise ValueError(f"Unknown orb_type='{orb_type}'. Choose from {list(self.orbitals.keys())}") self.atoms.append( { "symbol": symbol, "orb_type": orb_type, "pos": np.array([x, y, z], dtype=float), "orbs": self.orbitals[orb_type], } )
@staticmethod def _need_param(p, key): """指定されたSKパラメータが存在するか確認し、その値を返します。 `sk_params` 辞書から `key` に対応する値を取得します。 `key` が存在しない場合は `KeyError` を発生させます。 :param p: dict: Slater-Kosterパラメータを格納する辞書。 :param key: str: 取得するパラメータのキー。 :returns: float or int: 指定されたキーに対応するパラメータ値。 :raises KeyError: 指定されたキーが `sk_params` に存在しない場合。 """ if key not in p: raise KeyError(f"Missing SK parameter '{key}' in sk_params.") return p[key] @staticmethod def _harrison_scale(d, sk_params, key=None): """ハリソン則に基づいて、距離によるスケーリング因子を計算します。 サイト間距離 `d` と `sk_params` の "harrison" セクションに定義されたルールに従って、 スケーリング因子を計算します。 `d0` (基準距離) と `power` (指数) は、グローバルまたはキーごとに指定できます。 `harrison` パラメータが指定されていない場合、または `d` が0以下の場合、スケーリングは行われず1.0を返します。 指数の選択順序は、1) `power_map[key]` -> 2) `power_default` -> 3) 従来の `power` です。 :param d: float: サイト間の距離。 :param sk_params: dict: Slater-Kosterパラメータを含む辞書。 :param key: str, optional: スケーリング対象の積分キー(例: "ss_sigma")。キー固有のスケーリング設定に使用されます。デフォルトはNone。 :returns: float: 計算されたハリスン則によるスケーリング因子。 """ hs = sk_params.get("harrison", None) if hs is None: return 1.0 if d <= 0: return 1.0 # d0 (キーごとに上書き可能) d0 = float(hs.get("d0", 1.0)) d0_map = hs.get("d0_map", None) if key is not None and isinstance(d0_map, dict) and key in d0_map: d0 = float(d0_map[key]) # 指数 (power) の選択順序: # 1) power_map[key] # 2) power_default # 3) 従来の power power_map = hs.get("power_map", None) if key is not None and isinstance(power_map, dict) and key in power_map: power = float(power_map[key]) elif "power_default" in hs: power = float(hs["power_default"]) else: power = float(hs.get("power", 2.0)) # 従来の形式 return (d0 / d) ** power def _scaled_param(self, d, sk_params, key): """ハリスン則でスケーリングされたSlater-Kosterパラメータを返します。 まず `_need_param` を使用して元のパラメータ値を取得し、 次に `_harrison_scale` を使用してスケーリング因子を計算します。 最後に、元のパラメータ値にスケーリング因子を乗じて結果を返します。 :param d: float: サイト間の距離。 :param sk_params: dict: Slater-Kosterパラメータを含む辞書。 :param key: str: スケーリング対象のSlater-Koster積分キー。 :returns: float: ハリスン則でスケーリングされたパラメータ値。 """ v = self._need_param(sk_params, key) s = self._harrison_scale(d, sk_params, key=key) return v * s def _get_sk_elements(self, vec, orb1, orb2, p): """2つの軌道間のSlater-Koster行列要素を計算します。 2つの原子間の相対位置ベクトル `vec` とそれぞれの軌道タイプ `orb1`, `orb2` を基に、 ハミルトニアンのオフサイト要素を計算します。 距離 `d`、方向余弦 `l, m, n` を用いて、s-s, s-p, s-d, p-p, p-d の各相互作用を処理します。 d-d 相互作用は現在未実装です。 軌道間の対称性に基づいて、特定の相互作用では符号が反転することがあります。 :param vec: numpy.ndarray: 原子間の相対位置ベクトル `(x, y, z)`。 :param orb1: str: 1番目の原子の軌道タイプ(例: "s", "px", "dxy")。 :param orb2: str: 2番目の原子の軌道タイプ(例: "s", "px", "dxy")。 :param p: dict: Slater-Kosterパラメータを含む辞書。 :returns: float: 計算されたSlater-Koster行列要素の値。 :raises NotImplementedError: d-d ホッピングまたはサポートされていない軌道ペアが指定された場合。 """ d = np.linalg.norm(vec) if d < 1e-12: return 0.0 l, m, n = vec / d # quick type flags is_s1, is_s2 = (orb1 == "s"), (orb2 == "s") is_p1, is_p2 = orb1.startswith("p"), orb2.startswith("p") is_d1, is_d2 = orb1.startswith("d"), orb2.startswith("d") # 未実装チャネルでの高速失敗 if is_d1 and is_d2: raise NotImplementedError("d-d hopping (dd_sigma, dd_pi, dd_delta) is not implemented.") # --- 1. s-s --- if is_s1 and is_s2: return self._scaled_param(d, p, "ss_sigma") # --- 2. s-p --- if is_s1 and is_p2: vsp = self._scaled_param(d, p, "sp_sigma") if orb2 == "px": return l * vsp if orb2 == "py": return m * vsp if orb2 == "pz": return n * vsp if is_p1 and is_s2: return -self._get_sk_elements(vec, orb2, orb1, p) # --- 3. s-d --- if is_s1 and is_d2: vsd = self._scaled_param(d, p, "sd_sigma") if orb2 == "dz2": return (n**2 - 0.5 * (l**2 + m**2)) * vsd if orb2 == "dx2-y2": return 0.5 * np.sqrt(3.0) * (l**2 - m**2) * vsd if orb2 == "dxy": return np.sqrt(3.0) * l * m * vsd if orb2 == "dyz": return np.sqrt(3.0) * m * n * vsd if orb2 == "dzx": return np.sqrt(3.0) * l * n * vsd if is_d1 and is_s2: return self._get_sk_elements(vec, orb2, orb1, p) # --- 4. p-p --- if is_p1 and is_p2: vpps = self._scaled_param(d, p, "pp_sigma") vppp = self._scaled_param(d, p, "pp_pi") coord = {"px": l, "py": m, "pz": n} c1, c2 = coord[orb1], coord[orb2] if orb1 == orb2: return c1**2 * vpps + (1.0 - c1**2) * vppp else: return c1 * c2 * (vpps - vppp) # --- 5. p-d --- if is_p1 and is_d2: vpds = self._scaled_param(d, p, "pd_sigma") vpdp = self._scaled_param(d, p, "pd_pi") if orb1 == "px": if orb2 == "dxy": return m * (l**2 * vpds + (1 - 2 * l**2) * vpdp) if orb2 == "dyz": return l * m * n * (vpds - 2 * vpdp) if orb2 == "dzx": return n * (l**2 * vpds + (1 - 2 * l**2) * vpdp) if orb2 == "dx2-y2": return 0.5 * l * (l**2 - m**2) * vpds + l * (1 - l**2 + m**2) * vpdp if orb2 == "dz2": return l * (n**2 - 0.5 * (l**2 + m**2)) * vpds - np.sqrt(3.0) * l * n**2 * vpdp if orb1 == "py": if orb2 == "dxy": return l * (m**2 * vpds + (1 - 2 * m**2) * vpdp) if orb2 == "dyz": return n * (m**2 * vpds + (1 - 2 * m**2) * vpdp) if orb2 == "dzx": return l * m * n * (vpds - 2 * vpdp) if orb2 == "dx2-y2": return 0.5 * m * (l**2 - m**2) * vpds - m * (1 + l**2 - m**2) * vpdp if orb2 == "dz2": return m * (n**2 - 0.5 * (l**2 + m**2)) * vpds - np.sqrt(3.0) * m * n**2 * vpdp if orb1 == "pz": if orb2 == "dxy": return l * m * n * (vpds - 2 * vpdp) if orb2 == "dyz": return m * (n**2 * vpds + (1 - 2 * n**2) * vpdp) if orb2 == "dzx": return l * (n**2 * vpds + (1 - 2 * n**2) * vpdp) if orb2 == "dx2-y2": return 0.5 * n * (l**2 - m**2) * vpds - n * (l**2 - m**2) * vpdp if orb2 == "dz2": return n * (n**2 - 0.5 * (l**2 + m**2)) * vpds + np.sqrt(3.0) * n * (l**2 + m**2) * vpdp if is_d1 and is_p2: return -self._get_sk_elements(vec, orb2, orb1, p) raise NotImplementedError(f"Unsupported orbital pair: ({orb1}, {orb2})")
[ドキュメント] def build_hamiltonian(self, sk_params, onsite_energies): """システムのハミルトニアン行列を構築します。 追加された原子とその軌道情報、Slater-Kosterパラメータ、オンサイトエネルギーに基づいて、 全体のハミルトニアン行列を構築します。 まず、オンサイト項を対角要素に設定します。次に、異なる原子間のオフサイト項を `_get_sk_elements` を使用して計算し、行列を埋めます。 ハミルトニアンは実対称行列として構築されます。 :param sk_params: dict: Slater-Kosterパラメータを含む辞書。 :param onsite_energies: dict: 原子の記号と軌道タイプごとのオンサイトエネルギーを含む辞書。 :returns: numpy.ndarray: 構築されたハミルトニアン行列。 """ total_orbs = sum(len(a["orbs"]) for a in self.atoms) H = np.zeros((total_orbs, total_orbs), dtype=float) # Onsite terms idx = 0 for a in self.atoms: n_orb = len(a["orbs"]) e_on = onsite_energies.get(a["symbol"], {}).get(a["orb_type"], 0.0) for k in range(n_orb): H[idx + k, idx + k] = e_on idx += n_orb # Offsite terms idx_i = 0 for i, atom_i in enumerate(self.atoms): idx_j = 0 for j, atom_j in enumerate(self.atoms): if i < j: vec = atom_j["pos"] - atom_i["pos"] for io, orb_i in enumerate(atom_i["orbs"]): for jo, orb_j in enumerate(atom_j["orbs"]): val = self._get_sk_elements(vec, orb_i, orb_j, sk_params) H[idx_i + io, idx_j + jo] = val H[idx_j + jo, idx_i + io] = val idx_j += len(atom_j["orbs"]) idx_i += len(atom_i["orbs"]) self.hamiltonian = H return H
[ドキュメント] def orbital_labels(self): """各原子の軌道に対応するラベルのリストを生成します。 各原子の位置と軌道タイプを組み合わせて、ハミルトニアン行列の各要素に対応する 識別のための文字列ラベルを生成します。 例: "Ba(0.000,0.000,0.000)_s", "N(0.000,0.000,0.800)_px" :returns: list[str]: 各軌道に対応するラベルのリスト。 """ labels = [] for a in self.atoms: x, y, z = a["pos"] for o in a["orbs"]: labels.append(f"{a['symbol']}({x:.3f},{y:.3f},{z:.3f})_{o}") return labels
[ドキュメント] def main(): """`SlaterKosterTB` クラスの使用例を示します。 `SlaterKosterTB` クラスを初期化し、複数の原子をモデルに追加します。 定義済みのSlater-Kosterパラメータとオンサイトエネルギーを使用してハミルトニアンを構築し、 `scipy.linalg.eigh` を使用してハミルトニアンを対角化し、固有値(軌道エネルギー)と固有ベクトルを計算します。 結果として得られたエネルギーと、それぞれの固有状態における支配的な軌道成分を表示します。 """ tb = SlaterKosterTB() tb.add_atom("Ba", "d", 0, 0, 0) tb.add_atom("N", "p", 0, 0, 0.8) tb.add_atom("N", "p", 1, 0, 0) tb.add_atom("N", "p", -1, 0, 0) tb.add_atom("N", "p", 0, 1, 0) tb.add_atom("N", "p", 0, -1, 0) params = { "ss_sigma": -1.40, "sp_sigma": 1.84, "pp_sigma": 3.24, "pp_pi": -0.81, "sd_sigma": -2.0, "pd_sigma": -2.2, "pd_pi": 1.0, # キーに依存するハリスン則スケーリング: "harrison": { "d0": 1.0, "power_default": 2.0, # ss/sp/pp はデフォルトで 2.0 "power_map": { # 選択された積分でのオーバーライド "sd_sigma": 3.5, "pd_sigma": 3.5, "pd_pi": 3.5, }, }, } onsite = {"Ba": {"d": 0.0}, "N": {"p": -3.0}} H = tb.build_hamiltonian(params, onsite) energies, vectors = eigh(H) idx_sort = energies.argsort() evals = energies[idx_sort] evecs = vectors[:, idx_sort] labels = tb.orbital_labels() print("Eigenvalues (Orbital Energies) [eV]:") print(evals) print("\nNo. | Energy (eV) | Dominant components") print("-" * 72) for i in range(len(evals)): coeffs = evecs[:, i] w = coeffs**2 top = np.argsort(w)[::-1][:5] # desc = ", ".join([f"{labels[k]}:{coeffs[k]:+.2f}" for k in top]) # print(f"{i+1:2d} | {evals[i]:8.4f} | {desc}") print(f"{i+1:2d} | {evals[i]:8.4f}") for l, c in zip(labels, coeffs): print(f" {l}:{c:+.3f}")
if __name__ == "__main__": main()