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

vib_irreps.py をダウンロード

vib_irreps.py
vib_irreps.py
  1#!/usr/bin/env python3
  2# -*- coding: utf-8 -*-
  3
  4"""
  5点群の指標表を用いて分子の振動既約表現を計算するスクリプト。
  6
  7概要:
  8    指定されたXYZファイルと点群シンボルに基づき、分子の振動モードの既約表現を決定します。
  9
 10詳細説明:
 11    本スクリプトは、分子の対称性を表す点群(Schoenflies表記、例: C2v, D3d, Td, Oh, C3h, C4h, Ch, I, Ih)と
 12    分子の構造情報を含むXYZファイルを引数として受け取ります。
 13    `pymatgen` が利用可能な環境では、ヘルマン・モーガン表記を内部で使用することがありますが、
 14    それ以外の場合や特定の点群(I, Ih, C3hなど)については、既知のクラスサイズと一般位置の仮定に基づく
 15    「抽象モード」にフォールバックします。
 16
 17    計算は、まず全自由度に対応する指標 (Γ_3N) を、固定された原子のカウント(サイトベース)または
 18    一般位置の仮定から構築します。その後、分子の並進運動 (Γ_T) と回転運動 (Γ_R) の指標を差し引き、
 19    純粋な振動モードに対応する指標 (Γ_v) を導出します。
 20    最終的に、このΓ_vを指標の直交性定理を用いて、各既約表現への分解係数(多重度)を決定します。
 21
 22関連リンク:
 23    :doc:`vib_irreps_usage`
 24
 25使用例:
 26  python vib_irreps.py --pg Td  --xyz CH4.xyz
 27  python vib_irreps.py --pg Oh  --xyz SF6.xyz
 28  python vib_irreps.py --pg D6h --xyz C6H6.xyz
 29  python vib_irreps.py --pg I   --xyz C60.xyz
 30"""
 31
 32
 33import sys
 34import argparse
 35import numpy as np
 36from typing import Dict, List
 37from collections import defaultdict
 38
 39import tkpointgroup as pg  # ★ 共通ライブラリ
 40
 41# ---------- XYZ loader ----------
 42def load_coords_from_xyz(path: str) -> np.ndarray:
 43    """
 44    指定されたXYZファイルから原子の座標を読み込みます。
 45
 46    概要:
 47        XYZファイルから原子の座標をNumPy配列として読み込みます。
 48
 49    詳細説明:
 50        ファイル内の空行やコメント行、または座標情報を含まない行をスキップし、
 51        数値データのみを解析してNumPy配列として返します。
 52        最初の数行はヘッダー(原子数、コメント行)であると想定し、
 53        適切な行から座標の読み込みを開始します。
 54        もし最初の行が整数でない場合(例えばコメント行で始まる場合)は、
 55        行の読み込み開始位置を自動的に調整します。
 56
 57    :param path: str: 読み込むXYZファイルのパス。
 58    :returns: numpy.ndarray: 読み込まれた原子座標の配列 (N, 3)。ファイルが空の場合、空の配列を返します。
 59    """
 60    coords = []
 61    with open(path, "r", encoding="utf-8") as f:
 62        lines = [ln.strip() for ln in f if ln.strip()]
 63    start = 0
 64    try:
 65        int(lines[0].split()[0]); start = 2
 66    except Exception:
 67        start = 0
 68    for ln in lines[start:]:
 69        toks = ln.replace(",", " ").split()
 70        if len(toks) < 4:
 71            continue
 72        x, y, z = map(float, toks[-3:])
 73        coords.append([x, y, z])
 74    return np.array(coords, dtype=float)
 75
 76# ---------- Main ----------
 77def main():
 78    """
 79    スクリプトの主処理を実行し、分子の振動既約表現を計算して表示します。
 80
 81    概要:
 82        コマンドライン引数から点群とXYZファイルパスを受け取り、
 83        分子の振動モードの既約表現を計算して結果を表示します。
 84
 85    詳細説明:
 86        1. コマンドライン引数(点群シンボル、XYZファイルパス、許容誤差)を解析します。
 87        2. 指定された点群シンボルを正規化し、サポートされている点群であることを確認します。
 88        3. XYZファイルから原子座標を読み込み、座標が解析できなかった場合はエラーで終了します。
 89        4. 点群の特性表と、点群が「抽象モード」(例: I, Ih, C3h など、操作行列が直接定義されていない高対称点群)
 90           であるかどうかに応じて、処理を分岐します。
 91        5. **通常モードの場合:**
 92            - 実際の対称操作行列 (`ops`) を取得します。
 93            - 各操作の分類 (`raw_labels`) とクラスへの集約 (`class_map`) を行います。
 94            - Γ_3N(全自由度)、Γ_T(並進)、Γ_R(回転)の指標を計算します。
 95            - これらの指標を各対称クラスごとに集約し、Γ_v(振動)の指標を導出します。
 96            - 最終的に、Γ_vを既約表現に分解します。
 97        6. **抽象モードの場合:**
 98            - 事前に定義されたクラスサイズ (`pg.ABSTRACT_CLASS_SIZES`) を使用して、
 99              Γ_3N、Γ_T、Γ_Rの指標(特にΓ_3Nは'E'クラスで3N、他は0)を計算します。
100            - これらからΓ_v(振動)の指標を導出します。
101            - Γ_vを既約表現に分解します。この際、クラスサイズと群の位数をオーバーライドして使用します。
102        7. 計算された各指標のキャラクターと最終的な振動既約表現の結果を標準出力に整形して表示します。
103
104    :returns: None: 計算結果を標準出力に表示します。
105    """
106    sup = ", ".join(sorted(pg.PG_CHAR_TABLES.keys()))
107    ap = argparse.ArgumentParser(
108        description="Vibrational irreps from XYZ and a point group (Schoenflies-like)."
109    )
110    ap.add_argument("--pg", required=True, help=f"Point group (Schoenflies-like). Supported: {sup}")
111    ap.add_argument("--xyz", required=True, help="XYZ file path")
112    ap.add_argument("--tol", type=float, default=1e-5, help="Tolerance (Å) for fixed atoms")
113    args = ap.parse_args()
114
115    symbol = pg.normalize_symbol(args.pg)
116    if symbol not in pg.PG_CHAR_TABLES:
117        print(f"Error: point group '{args.pg}' not supported. Supported: {sorted(pg.PG_CHAR_TABLES.keys())}")
118        sys.exit(1)
119
120    coords = load_coords_from_xyz(args.xyz)
121    if coords.size == 0:
122        print("Error: no coordinates parsed from XYZ.")
123        sys.exit(1)
124
125    tbl = pg.character_table(symbol)
126    classes = tbl["classes"]
127    abstract = symbol in pg.ABSTRACT_CLASS_SIZES
128
129    if not abstract:
130        # --- 通常モード: 実操作行列を使う ---
131        ops = pg.group_ops(symbol)  # 3x3
132        raw_labels = [pg.classify_op_for_table(symbol, R) for R in ops]
133        classes, class_map = pg.class_aggregation(symbol, raw_labels)
134
135        chi_3N_lab = pg.gamma_3N_characters(coords, ops, raw_labels, tol=args.tol)
136        chi_T_lab  = pg.gamma_trans_characters(raw_labels, class_map)
137        chi_R_lab  = pg.gamma_rot_characters(raw_labels, class_map)
138
139        chi_3N = pg.reduce_to_classes(chi_3N_lab, classes, class_map)
140        chi_T  = {c: chi_T_lab.get(c, 0.0) for c in classes}
141        chi_R  = {c: chi_R_lab.get(c, 0.0) for c in classes}
142        chi_v  = {c: chi_3N[c] - chi_T[c] - chi_R[c] for c in classes}
143
144        mults = pg.decompose_irreps(chi_v, symbol)
145
146    else:
147        # --- 抽象モード: クラスサイズのみから構成(I, Ih, C3h) ---
148        class_sizes = pg.ABSTRACT_CLASS_SIZES[symbol]
149        raw_labels = []
150        for cl, sz in class_sizes.items():
151            raw_labels.extend([cl]*sz)
152        # identity class_map
153        chi_T_lab  = pg.gamma_trans_characters(raw_labels, {c:c for c in classes})
154        chi_R_lab  = pg.gamma_rot_characters(raw_labels, {c:c for c in classes})
155        chi_T  = {c: chi_T_lab.get(c, 0.0) for c in classes}
156        chi_R  = {c: chi_R_lab.get(c, 0.0) for c in classes}
157        chi_3N = {c: 0.0 for c in classes}; chi_3N["E"] = 3.0 * coords.shape[0]
158        chi_v  = {c: chi_3N[c] - chi_T[c] - chi_R[c] for c in classes}
159
160        mults = pg.decompose_irreps(chi_v, symbol,
161                                    class_sizes_override=class_sizes,
162                                    order_override=sum(class_sizes.values()))
163
164    def row(d): return "  ".join(f"{cl:>7s}:{d.get(cl,0.0):>6.1f}" for cl in classes)
165
166    print(f"Point group: {symbol}  (HM = {pg.schoenflies_to_hm(symbol)})  mode={'abstract' if abstract else 'normal'}")
167    print("Classes   :", "  ".join(f"{cl:>7s}" for cl in classes))
168    print("Γ_3N chars:", row(chi_3N))
169    print("Γ_T  chars:", row(chi_T))
170    print("Γ_R  chars:", row(chi_R))
171    print("Γ_v  chars:", row(chi_v))
172    print("\nVibrational irreps:", pg.pretty_irreps(symbol, mults))
173
174if __name__ == "__main__":
175    main()
176    input("\nPress ENTER to terminate>>\n")