Quantum.analyze_symmetry のソースコード

"""
配位子構造の点群対称性を解析し、対称適合線形結合 (SALC) を構築するスクリプト。

詳細説明:
本スクリプトは、JSONファイルから配位子の構造データを読み込み、点群対称性を自動検出します。
検出された点群に基づいて、配位子の対称性を既約表現に分解し、
中心金属のd軌道とのハイブリダイゼーションの可否を評価します。
さらに、各既約表現に対応する対称適合線形結合 (SALC) を抽出し、その結果を出力します。

関連リンク:
:doc:`salc_calculation_overview`
"""
import json
import argparse
import numpy as np

import tkpg
from tkpg import core


[ドキュメント] def load_structure_json(path: str) -> dict: """ 指定されたJSONファイルから構造設定を読み込む。 詳細説明: ファイルパスを引数として受け取り、配位子の位置、d軌道タイプ、解析モード、 許容誤差などの設定を辞書形式で抽出して返します。 欠損しているキーにはデフォルト値が適用されます。 :param path: str: 構造設定を含むJSONファイルのパス。 :returns: dict: 読み込まれた設定値を含む辞書。 主なキーとして`ligands_pos` (numpy.ndarray), `d_orbital` (str), `mode` (str), `tol_match_geom` (float), `tol_match_D` (float), `salc_eig_tol` (float), `coeff_tol` (float), `print_coeffs` (bool), `print_salc_thr` (float), `max_salc_per_irrep` (int | None), `autodetect` (dict) などが含まれます。 """ with open(path, "r", encoding="utf-8") as f: obj = json.load(f) ligands_pos = np.array(obj["ligands_pos"], float) d_orbital = obj.get("d_orbital", "d_xy") mode = obj.get("mode", "full") tol = obj.get("tolerances", {}) tol_match_geom = float(tol.get("tol_match_geom", 5e-3)) tol_match_D = float(tol.get("tol_match_D", 5e-3)) salc_cfg = obj.get("salc", {}) cfg = { "ligands_pos": ligands_pos, "d_orbital": d_orbital, "mode": mode, "tol_match_geom": tol_match_geom, "tol_match_D": tol_match_D, "salc_eig_tol": float(salc_cfg.get("salc_eig_tol", 1e-6)), "coeff_tol": float(salc_cfg.get("coeff_tol", 1e-6)), "print_coeffs": bool(salc_cfg.get("print_coeffs", True)), "print_salc_thr": float(salc_cfg.get("print_salc_thr", 1e-3)), "max_salc_per_irrep": salc_cfg.get("max_salc_per_irrep", None), "autodetect": obj.get("autodetect", {}), } return cfg
[ドキュメント] def center_positions(ligands_pos: np.ndarray) -> np.ndarray: """ 配位子の位置を重心が原点に来るように移動する。 詳細説明: 与えられた配位子の三次元座標群の重心を計算し、各座標から重心を引くことで、 重心を原点に移動した新しい座標群を生成します。 :param ligands_pos: numpy.ndarray: shapeが(N, 3)の配位子座標配列。 :returns: numpy.ndarray: 重心が原点に移動された配位子座標配列。 """ return ligands_pos - np.mean(ligands_pos, axis=0, keepdims=True)
[ドキュメント] def choose_point_group( plugins: list[dict], ligands_pos: np.ndarray, tol_match_geom: float, autodetect_cfg: dict | None ) -> tuple[dict, list[dict]]: """ 複数の点群プラグインを評価し、最も適切な点群を自動選択する。 詳細説明: 各プラグインが提供する`align_guess`と`symmetry_hit_rate`の結果を評価し、 以下のルールに基づいて最適な点群を選択します: 1. `min_rate_strict`設定で定義された閾値`strict_ok`を満たすプラグインを優先します。 2. 残りの候補の中から、ヒット数 (`hits`) が最大であるものを選択します。 3. 次に、群の位数 (`Gorder`) が最大であるものを選択します。 4. 最後に、ヒット率 (`rate`) が最大であるものを選択します。 :param plugins: list[dict]: 点群プラグインのリスト。 各プラグインは辞書形式で、`name` (str), `align_guess` (callable), `build_group` (callable), `d_irreps` (dict), `irreps` (callable), `irrep_dim` (callable), `irrep_char` (callable) などの情報を含む。 :param ligands_pos: numpy.ndarray: shapeが(N, 3)の配位子座標配列。 :param tol_match_geom: float: 幾何学的な一致を判定するための許容誤差。 :param autodetect_cfg: dict | None: 自動検出設定を含む辞書。 `min_rate_strict` (dict) などのキーを含むことができる。 :returns: tuple[dict, list[dict]]: - `dict`: 選択された最適な点群プラグインと評価結果を含む辞書。 キーとして`plugin` (dict), `ok_align` (bool), `R_align` (numpy.ndarray), `pos_aligned` (numpy.ndarray), `hits` (int), `rate` (float), `Gorder` (int), `strict_thr` (float), `strict_ok` (bool) を含む。 - `list[dict]`: 評価された全ての点群プラグインの結果リスト。 各要素は上記の`dict`と同じ構造を持つ。 """ autodetect_cfg = autodetect_cfg or {} min_rate = autodetect_cfg.get("min_rate_strict", {}) # 例: {"Oh":0.70, "C4v":0.80} scored = [] for p in plugins: ok, R_align, pos_aligned = p["align_guess"](ligands_pos) group = p["build_group"]() Gorder = len(group) if ok: # hits, rate = core.symmetry_hits(group, pos_aligned, tol_match=tol_match_geom) hits, rate = core.symmetry_hit_rate(group, pos_aligned, tol_match=tol_match_geom) else: hits, rate = 0, 0.0 # strict acceptance flag (rate based: backward compatible) thr = float(min_rate.get(p["name"], 0.0)) strict_ok = (rate >= thr) scored.append({ "plugin": p, "ok_align": ok, "R_align": R_align, "pos_aligned": pos_aligned, "hits": hits, "rate": rate, "Gorder": Gorder, "strict_thr": thr, "strict_ok": strict_ok, }) # ---- selection rule ---- # 1) Prefer strict_ok True (if any) strict = [s for s in scored if s["strict_ok"]] pool = strict if strict else scored # 2) Sort by hits desc, then |G| desc, then rate desc pool.sort(key=lambda s: (s["hits"], s["Gorder"], s["rate"]), reverse=True) best = pool[0] return best, scored
[ドキュメント] def main() -> None: """ コマンドライン引数からJSONファイルを読み込み、配位子構造の点群対称性解析を実行する。 詳細説明: 1. コマンドライン引数(`--infile`)から構造設定JSONファイルのパスを読み込みます。 デフォルトは`structure.json`です。 2. JSONファイルから設定を読み込み、配位子の位置を重心が原点となるように正規化します。 3. `tkpg`プラグインを使用して最適な点群を自動検出します。 4. 検出された点群と設定されたd軌道に基づいて、配位子の既約表現への分解を行います。 5. 中心金属d軌道とのハイブリダイゼーションの対称性による可否を判定します。 6. 対称適合線形結合 (SALC) を抽出し、その係数を出力します。 :returns: None """ ap = argparse.ArgumentParser() ap.add_argument("--infile", default="structure.json", help="structure JSON file (default: structure.json)") args = ap.parse_args() cfg = load_structure_json(args.infile) ligands_pos = center_positions(cfg["ligands_pos"]) d_orbital = cfg["d_orbital"] mode = cfg["mode"] tol_match_geom = cfg["tol_match_geom"] tol_match_D = cfg["tol_match_D"] plugins = tkpg.load_plugins() if not plugins: raise RuntimeError("No tkpg plugins found. Ensure tkpg/Oh.py, tkpg/C4v.py exist.") best, scored = choose_point_group( plugins, ligands_pos, tol_match_geom, cfg["autodetect"] ) plugin = best["plugin"] rate = best["rate"] hits = best["hits"] Gorder = best["Gorder"] R_align = best["R_align"] pos_aligned = best["pos_aligned"] print(f"\n[Auto-detected point group] {plugin['name']} (hits={hits}/{Gorder}, hit_rate={rate:.3f})") print("[Candidates]") # show all candidates, sorted by the SAME selection key (but keep strict_ok shown) scored_sorted = sorted(scored, key=lambda s: (s["strict_ok"], s["hits"], s["Gorder"], s["rate"]), reverse=True) for s in scored_sorted: p = s["plugin"] flag = "STRICT" if s["strict_ok"] else " " thr = s["strict_thr"] print(f" {p['name']:>4s} : hits={s['hits']:>2d}/{s['Gorder']:<2d} rate={s['rate']:.3f} " f"ok_align={s['ok_align']} {flag} (thr={thr:.2f})") # d_zx alias d_orbital_use = "d_xz" if d_orbital == "d_zx" else d_orbital dmap = plugin["d_irreps"] if d_orbital_use not in dmap: raise ValueError(f"d orbital '{d_orbital}' not defined for point group {plugin['name']}") d_irrep = dmap[d_orbital_use] print("\n[Central metal d orbital]") print(f" Orbital : {d_orbital}") print(f" Irrep : {d_irrep}") # Build basis frames = core.compute_local_frames(pos_aligned) basis = core.build_ligand_basis(len(pos_aligned), frames, mode=mode) group = plugin["build_group"]() irreps = plugin["irreps"]() irrep_dim = plugin["irrep_dim"] irrep_char = plugin["irrep_char"] # Decompose Gamma = core.build_reducible_characters(group, pos_aligned, frames, basis, mode=mode, tol_match=tol_match_D) coeffs = core.decompose_generic(Gamma, group, irreps, irrep_char) if cfg["print_coeffs"]: print(f"\n[Ligand reducible rep decomposition ({plugin['name']})]") for ir in irreps: if coeffs.get(ir, 0.0) > cfg["coeff_tol"]: print(f" {ir}: {coeffs[ir]:.3f}") # Hybridization check print("\n[Hybridization check]") if coeffs.get(d_irrep, 0.0) > cfg["coeff_tol"]: print(f" ✔ Hybridization allowed by symmetry: ligand contains {d_irrep}") else: print(f" ✘ Symmetry forbidden: ligand does NOT contain {d_irrep}") # SALCs: show ALL irreps print(f"\n[All ligand SALCs ({plugin['name']})] mode={mode}") any_printed = False for ir in irreps: mult = coeffs.get(ir, 0.0) if mult <= cfg["coeff_tol"]: continue salcs = core.extract_salc( ir, group, irrep_dim, irrep_char, pos_aligned, frames, basis, mode=mode, tol_match=tol_match_D, tol=cfg["salc_eig_tol"] ) if len(salcs) == 0: print(f"\n[SALCs for {ir}] (expected mult≈{mult:.3f}, but none extracted; try lowering salc_eig_tol)") continue any_printed = True core.print_salc( ir, salcs, basis, thr=cfg["print_salc_thr"], max_salc=cfg["max_salc_per_irrep"] ) if not any_printed: print(" (No SALCs extracted. Try increasing tol_match_D or lowering salc_eig_tol.)")
if __name__ == "__main__": main()