last_modified: 2026-02-15
生成AIによる自動生成記事に関する免責事項 本記事は、提供された文献 “arXiv:2508.07544v1” [1] および関連するPython実装コードに基づき、生成AIによって構成された技術解説記事です。内容の正確性、数理的厳密性については原著論文および実装コードを参照してください。
1. 序論:量子化学におけるヘシアン行列計算の課題
分子のポテンシャルエネルギー曲面(Potential Energy Surface; PES)の局所的な曲率を記述するヘシアン行列(Hessian matrix)は、計算化学において極めて重要な役割を担う。 個の原子からなる系において、電子エネルギー の核座標 に対する二階微分係数を要素とするヘシアン行列 は、以下のように定義される:
ここで、 である。
1.1 従来手法の課題
ヘシアンの算出手法は大きく分けて、解析的微分法(Analytic Hessian)と数値的微分法(Numerical Hessian)に分類される。
解析的ヘシアンは精度が高いが、以下の課題がある:
- Coupled-Perturbed SCF(CP-SCF)方程式の解法が必要
- メモリ消費量が 以上でスケール
- TDDFT や post-Hartree-Fock 法での実装が困難
セミ数値的ヘシアンは、解析的勾配の有限差分を用いる:
従来法では、各核座標( 自由度)に対して変位を与えるため、最低でも 回(片側差分)または 回(両側差分)の勾配計算が必要となる。
1.2 O1NumHessの革新性
本稿で解説する O1NumHess は、ヘシアン行列が持つ Off-Diagonal Low-Rank (ODLR) 特性を利用し、勾配計算の回数を原子数に依存しない定数オーダー にまで削減する。実際、大規模系では約100〜120回の勾配計算で収束することが実証されている [1]。
2. 数理的背景:ヘシアン行列のODLR特性
2.1 局所性と低ランク性の観察
大規模分子のヘシアン行列は、空間的に離れた原子間の相互作用が減衰するため、近似的にスパース(疎)であると考えられてきた。しかし、Wang らの研究 [1] は、共役系や励起状態では、遠距離原子間にも無視できない相関が存在することを示した。
図1に示すように:
- 直鎖アルカン -CH のヘシアンはバンド対角行列に近い
- 直鎖ポリエン CH の励起状態(, )では、遠距離の非対角要素が顕著
重要な発見は、これらの非対角ブロックが低ランク構造を持つという点である。特異値分解(SVD)を行うと、少数の支配的な特異値のみが有意であることが確認された。
2.2 量子力学的起源:Hellmann-Feynman定理からの導出
電子状態 のエネルギー に対し、Hellmann-Feynman定理が成立するとき、ヘシアンは以下のように表される:
ここで、 はリゾルベント演算子:
第一項 は局所的(核間反発の二階微分で で減衰)。第二項 は非局所的だが、エネルギー・スケール分離により以下のように分解できる:
- :高エネルギー状態からの寄与(局所的)
- :近接エネルギー状態からの寄与(低ランク)
これにより、ヘシアンは以下のように近似される:
この分解が ODLR特性 の数理的基盤である [1]。
3. O1NumHess アルゴリズムの詳細
O1NumHess は以下の4つの主要ステップで構成される。
3.1 Step 1: 初期モデルヘシアンの生成 (Swart Hessian)
低コストで計算可能な経験的モデルヘシアン を生成する。これは Swart らの手法 [2] を改良したもので、原子間距離と共有結合半径に基づく。
数理モデル
原子対 , に対し、スクリーニング関数 を定義:
ここで は原子 の共有結合半径 [3]、 は原子間距離である。
結合伸縮の力の定数:
角度 に対し( のとき):
Python実装例
import numpy as np
def compute_screening_function(dist_matrix, cov_radii):
"""
スクリーニング関数 ρ_AB の計算
Parameters:
-----------
dist_matrix : ndarray, shape (N, N)
原子間距離行列
cov_radii : ndarray, shape (N,)
各原子の共有結合半径
Returns:
--------
rho : ndarray, shape (N, N)
スクリーニング関数行列
"""
N = len(cov_radii)
# 共有結合半径の和行列
cov_sum = cov_radii[:, np.newaxis] + cov_radii[np.newaxis, :]
# スクリーニング関数の計算
with np.errstate(divide='ignore', invalid='ignore'):
rho = np.exp(1.0 - dist_matrix / cov_sum)
# 対角要素をゼロに
np.fill_diagonal(rho, 0.0)
# 距離がゼロの場合の処理
rho = np.nan_to_num(rho, nan=0.0, posinf=0.0, neginf=0.0)
return rho
def compute_bond_force_constants(rho):
"""
結合伸縮の力の定数を計算
Parameters:
-----------
rho : ndarray, shape (N, N)
スクリーニング関数行列
Returns:
--------
k_bond : ndarray, shape (N, N)
結合力定数行列
"""
return 0.35 * (rho ** 3)
def build_swart_hessian(coords, atomic_numbers, cov_radii):
"""
Swartモデルヘシアンの構築(簡略版)
Parameters:
-----------
coords : ndarray, shape (N, 3)
原子座標 (Bohr)
atomic_numbers : ndarray, shape (N,)
原子番号
cov_radii : ndarray, shape (N,)
共有結合半径 (Bohr)
Returns:
--------
H_swart : ndarray, shape (3N, 3N)
モデルヘシアン行列
"""
N_atoms = len(coords)
N_dof = 3 * N_atoms
# 原子間距離行列の計算
diff_vec = coords[:, np.newaxis, :] - coords[np.newaxis, :, :] # (N, N, 3)
dist_matrix = np.linalg.norm(diff_vec, axis=2) # (N, N)
# スクリーニング関数と結合力定数
rho = compute_screening_function(dist_matrix, cov_radii)
k_bond = compute_bond_force_constants(rho)
# Wilson B行列を用いたヘシアンの構築(ここでは簡略化)
# 実装の詳細は原著論文の Appendix B を参照
H_swart = np.zeros((N_dof, N_dof))
for i in range(N_atoms):
for j in range(i + 1, N_atoms):
if k_bond[i, j] > 1e-10:
# 結合ベクトル
r_ij = diff_vec[i, j]
r = dist_matrix[i, j]
if r < 1e-10:
continue
# 単位ベクトル
e_ij = r_ij / r
# ヘシアンへの寄与(簡略版)
# H = k * (I - e ⊗ e) / r + k * e ⊗ e
I3 = np.eye(3)
outer_prod = np.outer(e_ij, e_ij)
H_block = k_bond[i, j] * ((I3 - outer_prod) / r + outer_prod)
# ヘシアン行列への代入
H_swart[3*i:3*i+3, 3*i:3*i+3] += H_block
H_swart[3*j:3*j+3, 3*j:3*j+3] += H_block
H_swart[3*i:3*i+3, 3*j:3*j+3] -= H_block
H_swart[3*j:3*j+3, 3*i:3*i+3] -= H_block
return H_swart
3.2 Step 2: 最適変位方向の生成 ( Displacements)
O1NumHess の核心は、ヘシアンの再構築に必要な情報を凝縮した少数の変位方向 を決定論的に生成することにある。
アルゴリズム
-
大域的モードの追加(初期7モード):
- 並進モード(3)
- 回転モード(3)
- 呼吸モード(Breathing mode)(1):全原子が動径方向に一様に伸縮
-
局所モードの反復生成:
- 各原子 の近傍領域 を定義(距離カットオフ内の原子集合)
- に対するモデルヘシアンのサブブロックを抽出
- 既存の変位方向に直交する部分空間で、最大固有値を持つ固有ベクトルを選択
- 全原子の局所モードを位相調整して重ね合わせ、大域的変位ベクトルを生成
近傍領域のサイズは であるため、このプロセスは 回で収束する [1]。
Python実装例
def generate_displacement_directions(coords, H_swart, neighbor_list, delta=0.005):
"""
変位方向の生成
Parameters:
-----------
coords : ndarray, shape (N_atoms, 3)
原子座標 (Bohr)
H_swart : ndarray, shape (3N, 3N)
モデルヘシアン
neighbor_list : list of lists
各原子の近傍原子リスト
delta : float
変位幅 (Bohr)
Returns:
--------
displ_directions : ndarray, shape (3N, N_displ)
変位方向行列
"""
N_atoms = len(coords)
N_dof = 3 * N_atoms
displ_directions = []
# 1. 大域的モードの生成
# 1a. 並進モード(3個)
for i in range(3):
trans = np.zeros((N_atoms, 3))
trans[:, i] = 1.0
displ_directions.append(trans.flatten())
# 1b. 回転モード(3個)
# 慣性テンソルの固有ベクトルを回転軸とする
center = np.mean(coords, axis=0)
inertia = np.zeros((3, 3))
for atom_coord in coords:
r = atom_coord - center
inertia += (np.dot(r, r) * np.eye(3) - np.outer(r, r))
_, rot_axes = np.linalg.eigh(inertia)
for axis in rot_axes.T:
rot_displ = np.zeros((N_atoms, 3))
for j, atom_coord in enumerate(coords):
r = atom_coord - center
rot_displ[j] = np.cross(axis, r)
displ_directions.append(rot_displ.flatten())
# 1c. 呼吸モード(1個)
breath = np.zeros((N_atoms, 3))
for j, atom_coord in enumerate(coords):
r = atom_coord - center
breath[j] = r
displ_directions.append(breath.flatten())
# 2. 局所モードの反復生成
n_current = 7 # 現在の変位方向数
while n_current < N_dof:
new_mode_found = False
global_mode = np.zeros(N_dof)
for atom_idx in range(N_atoms):
# 近傍原子のインデックス
neighbors = neighbor_list[atom_idx]
# 近傍の自由度インデックス
neighbor_dofs = []
for nb in neighbors:
neighbor_dofs.extend([3*nb, 3*nb+1, 3*nb+2])
if len(neighbor_dofs) == 0:
continue
# サブヘシアンの抽出
sub_H = H_swart[np.ix_(neighbor_dofs, neighbor_dofs)]
# 既存の変位方向を射影
existing_modes = np.array(displ_directions).T # (N_dof, n_current)
sub_existing = existing_modes[neighbor_dofs, :]
# 直交補空間への射影
if sub_existing.shape[1] >= len(neighbor_dofs):
continue
# QR分解で直交化
Q, _ = np.linalg.qr(sub_existing)
proj = np.eye(len(neighbor_dofs)) - Q @ Q.T
sub_H_proj = proj @ sub_H @ proj
# 最大固有値の固有ベクトル
try:
eig_vals, eig_vecs = np.linalg.eigh(sub_H_proj)
max_idx = np.argmax(np.abs(eig_vals))
local_mode = eig_vecs[:, max_idx]
# 大域モードへの寄与
for i, dof in enumerate(neighbor_dofs):
global_mode[dof] += local_mode[i]
new_mode_found = True
except np.linalg.LinAlgError:
continue
if not new_mode_found or np.linalg.norm(global_mode) < 1e-10:
break
# 既存モードと直交化
for existing in displ_directions:
global_mode -= np.dot(global_mode, existing) * existing
# 正規化とスケーリング
norm = np.linalg.norm(global_mode)
if norm > 1e-10:
global_mode /= norm
max_abs = np.max(np.abs(global_mode))
if max_abs > 1e-10:
global_mode *= (delta / max_abs)
displ_directions.append(global_mode)
n_current += 1
else:
break
return np.array(displ_directions).T
# 使用例
# coords = np.array([...]) # 原子座標
# H_swart = build_swart_hessian(coords, atomic_numbers, cov_radii)
# neighbor_list = build_neighbor_list(coords, cutoff=5.0)
# displ_dirs = generate_displacement_directions(coords, H_swart, neighbor_list)
# print(f"生成された変位方向数: {displ_dirs.shape[1]}")
3.3 Step 3: 数値微分による勾配計算
生成された変位方向 に沿って構造を変位させ、量子化学計算により勾配 を取得する:
実装上の工夫:
- 変位幅: Bohr(デフォルト)
- 並進モード:勾配は常にゼロ(計算不要)
- 回転モード:平衡構造での勾配 から解析的に導出可能
- 呼吸モードのみ両側差分を使用(非調和性の相殺)
3.4 Step 4: ODLR特性を利用したヘシアンの再構築
得られた勾配情報 から、逆問題 を解く。
4.4.1 局所成分の推定
ODLR特性を考慮したペナルティ付き最小二乗法:
ここで:
- :ペナルティ行列(遠距離原子対に大きな値)
- :アダマール積(要素ごとの積)
- a.u.(デフォルト)
ペナルティ行列の定義:
ここで Bohr、 が推奨値である [1]。
この最適化問題は共役勾配法(CG法)で効率的に解ける。
Python実装例
from scipy.sparse.linalg import cg, LinearOperator
def pack_symmetric_matrix(H):
"""対称行列の上三角部分をベクトルに変換"""
N = H.shape[0]
indices = np.triu_indices(N)
return H[indices]
def unpack_symmetric_matrix(h_vec, N):
"""ベクトルを対称行列に復元"""
H = np.zeros((N, N))
indices = np.triu_indices(N)
H[indices] = h_vec
H = H + H.T - np.diag(np.diag(H))
return H
def solve_local_hessian(g, displ_dirs, penalty_matrix, lam=0.01, maxiter=1000):
"""
局所ヘシアンの推定
Parameters:
-----------
g : ndarray, shape (N_dof, N_displ)
変位構造での勾配
displ_dirs : ndarray, shape (N_dof, N_displ)
変位方向(正規化済み)
penalty_matrix : ndarray, shape (N_dof, N_dof)
ペナルティ行列 W
lam : float
正則化パラメータ
Returns:
--------
H_local : ndarray, shape (N_dof, N_dof)
推定された局所ヘシアン
"""
N_dof, N_displ = g.shape
# 対称行列の未知数個数
N_unknowns = N_dof * (N_dof + 1) // 2
# LinearOperatorの定義
def matvec(h_vec):
H_tmp = unpack_symmetric_matrix(h_vec, N_dof)
# Term 1: (H * displ_dirs) * displ_dirs^T
term1 = H_tmp @ displ_dirs @ displ_dirs.T
# Term 2: lam * W^2 * H (regularization)
W_squared = penalty_matrix ** 2
term2 = lam * W_squared * H_tmp
result = term1 + term2
return pack_symmetric_matrix(result)
A_op = LinearOperator((N_unknowns, N_unknowns), matvec=matvec)
# 右辺の構築: g * displ_dirs^T + (g * displ_dirs^T)^T
rhs = g @ displ_dirs.T
rhs = rhs + rhs.T
b = pack_symmetric_matrix(rhs)
# 共役勾配法等で解く
h_solution, info = cg(A_op, b, maxiter=maxiter, tol=1e-8)
if info != 0:
print(f"警告: CG法が収束しませんでした (info={info})")
H_local = unpack_symmetric_matrix(h_solution, N_dof)
return H_local
# ペナルティ行列の構築例
def build_penalty_matrix(coords, vdw_radii, delta_r1=1.0, beta=1.5):
"""
ペナルティ行列 W の構築
Parameters:
-----------
coords : ndarray, shape (N_atoms, 3)
原子座標 (Bohr)
vdw_radii : ndarray, shape (N_atoms,)
van der Waals半径 (Bohr)
delta_r1 : float
カットオフパラメータ (Bohr)
beta : float
ペナルティの指数
Returns:
--------
W : ndarray, shape (3N, 3N)
ペナルティ行列
"""
N_atoms = len(coords)
N_dof = 3 * N_atoms
# 原子間距離行列
diff = coords[:, np.newaxis, :] - coords[np.newaxis, :, :]
dist_matrix = np.linalg.norm(diff, axis=2)
# vdW半径の和
vdw_sum = vdw_radii[:, np.newaxis] + vdw_radii[np.newaxis, :]
# ペナルティ(原子ペア単位)
penalty_atoms = np.maximum(0, dist_matrix - vdw_sum - delta_r1) ** beta
# 座標ペア単位に拡張
W = np.zeros((N_dof, N_dof))
for i in range(N_atoms):
for j in range(N_atoms):
W[3*i:3*i+3, 3*j:3*j+3] = penalty_atoms[i, j]
return W
4.4.2 低ランク補正ループ
局所成分で捉えきれない長距離相関を補正するため、反復的に更新:
def apply_lowrank_correction(H_local, g, displ_dirs, max_iter=100, damp_factor=0.5):
"""
低ランク補正の適用
Parameters:
-----------
H_local : ndarray, shape (N_dof, N_dof)
局所ヘシアン
g : ndarray, shape (N_dof, N_displ)
勾配行列
displ_dirs : ndarray, shape (N_dof, N_displ)
変位方向
max_iter : int
最大反復回数
damp_factor : float
ダンピング係数
Returns:
--------
H_final : ndarray, shape (N_dof, N_dof)
補正後のヘシアン
"""
H = H_local.copy()
epsilon = 1e-3
# 勾配のスケーリング
g_norms = np.linalg.norm(g, axis=0)
g_scaled = g * epsilon / np.maximum(epsilon, g_norms)
displ_scaled = displ_dirs * epsilon / np.maximum(epsilon, g_norms)
for iteration in range(max_iter):
# 残差の計算
residual = g_scaled - H @ displ_scaled
# 収束判定
residual_norm = np.linalg.norm(residual)
if residual_norm < 1e-8:
print(f"収束しました(反復回数: {iteration})")
break
# ランク1補正の追加
H_correction = residual @ displ_scaled.T
# 対称化
H_correction = 0.5 * (H_correction + H_correction.T)
# ダンピング付き更新
H = H + damp_factor * H_correction
return H
# 完全なヘシアン推定パイプライン
def estimate_hessian_o1numhess(coords, g0, g_displaced, displ_dirs,
vdw_radii, lam=0.01):
"""
O1NumHessアルゴリズムによるヘシアン推定
Parameters:
-----------
coords : ndarray, shape (N_atoms, 3)
原子座標
g0 : ndarray, shape (3N_atoms,)
平衡構造での勾配
g_displaced : ndarray, shape (3N_atoms, N_displ)
変位構造での勾配
displ_dirs : ndarray, shape (3N_atoms, N_displ)
変位方向
vdw_radii : ndarray, shape (N_atoms,)
van der Waals半径
Returns:
--------
H_final : ndarray, shape (3N_atoms, 3N_atoms)
推定されたヘシアン行列
"""
# ステップ1: ペナルティ行列の構築
W = build_penalty_matrix(coords, vdw_radii)
# ステップ2: 局所ヘシアンの推定
# 勾配差分の計算
g_diff = g_displaced - g0[:, np.newaxis]
H_local = solve_local_hessian(g_diff, displ_dirs, W, lam=lam)
# ステップ3: 低ランク補正
H_final = apply_lowrank_correction(H_local, g_diff, displ_dirs)
return H_final
4. 実証的成果と性能評価
4.1 モデル系:直鎖アルカンとポリエン
-CH(アルカン)および CH(ポリエン)を用いた検証結果(B3LYP-D3/def2-SV(P)レベル):
| 系 | 必要勾配数 | 振動数MAD (cm) | ギブズ自由エネルギー誤差 (kcal/mol) |
|---|---|---|---|
| -CH (S) | 53 | 0.78 | -0.43 |
| CH (S) | 40 | 6.88 | 2.02 |
| CH (T) | 42 | 11.9 | 2.71 |
| CH (S) | 38 | 6.06 | 2.44 |
従来法(両側差分)の必要勾配数:
- -CH: 588回
- CH: 396回
O1NumHessは約10分の1の勾配計算で実用的な精度を達成 [1]。
4.2 共有結合系:WCCR10ベンチマーク
遷移金属錯体の配位子解離エネルギー(原子数50〜180)に対するベンチマーク結果:
- 必要勾配数:原子数によらず約100〜120回で飽和
- 振動数誤差:MAD = 数 cm(26/30系で < 5 cm)
- 反応ギブズ自由エネルギー誤差:MAD = 1.18 kcal/mol
- 計算速度:従来法に対し線形加速(大規模系で顕著)
- 解析的ヘシアンとの比較:多くの系で O1NumHessの方が高速
例外:重原子(Pt等)を含む系では、DFTグリッド誤差の増幅により振動数誤差が大きくなる傾向 [1]。
4.3 非共有結合系:S30L-CI ベンチマーク
巨大ホスト-ゲスト錯体(原子数100〜200)に対する評価:
- 錯形成ギブズ自由エネルギー誤差:MAD = 0.92 kcal/mol
- メモリ消費量:解析的ヘシアンの 1/10以下(最大系で顕著)
- 低振動数モード:非共有結合系で重要な低振動数領域でも高精度を維持
最も精度の高いDFT手法(ωB97X-D3/def2-QZVP’)のMAD 2.1 kcal/molと比較しても、O1NumHessの誤差は十分に小さい [1]。
5. 実装とソフトウェア
5.1 オープンソース実装
Wang らは、以下の2つのPythonライブラリをGitHubで公開している:
-
O1NumHess (https://github.com/ilcpm/O1NumHess)
- 汎用ヘシアン推定ライブラリ
- 量子化学以外の問題にも適用可能
-
O1NumHess_QC (https://github.com/ilcpm/O1NumHess_QC)
- 量子化学計算専用
- BDF、ORCAとのインターフェース実装
5.2 使用例(擬似コード)
# O1NumHess_QCを使用したヘシアン計算の例
from o1numhess_qc import O1NumHessCalculator
from bdf_interface import BDFGradientCalculator
# 計算器の初期化
calculator = O1NumHessCalculator(
delta=0.005, # 変位幅 (Bohr)
dmax_bohr=5.0, # 近傍カットオフ距離
delta_r1=1.0, # ペナルティカットオフ
lam=0.01, # 正則化パラメータ
maxiter_lr=100 # 低ランク補正の最大反復数
)
# 量子化学計算エンジンの設定
gradient_calc = BDFGradientCalculator(
method='B3LYP-D3',
basis='def2-SV(P)',
scf_conv=1e-8
)
# 分子構造の読み込み
molecule = read_xyz('molecule.xyz')
# ヘシアン計算の実行
hessian, metadata = calculator.compute_hessian(
coords=molecule.coords,
atomic_numbers=molecule.atomic_numbers,
gradient_calculator=gradient_calc,
n_processors=64
)
# 振動解析
frequencies, normal_modes = analyze_vibrations(
hessian,
molecule.masses
)
# 熱力学量の計算
thermo = compute_thermochemistry(
frequencies,
temperature=298.15,
pressure=101325.0
)
print(f"Zero-Point Energy: {thermo.zpe:.6f} Hartree")
print(f"Gibbs Free Energy: {thermo.gibbs:.6f} Hartree")
print(f"使用した勾配計算回数: {metadata['n_gradients']}")
6. 議論と今後の展望
6.1 適用範囲と制限事項
適用可能な系:
- 大規模分子系(数百原子以上)
- TDDFT励起状態ヘシアン(解析的実装が困難)
- メモリ制約のある計算環境
制限事項:
- Hellmann-Feynman定理が成立しない場合(Pulay項が大きい系)の精度低下
- 重原子を含む系での数値ノイズ増幅
- 電子状態計算の収束が不十分な場合の誤差増大
6.2 拡張可能性
-
励起状態への応用
- TDDFT、CASSCF、EOM-CC等の励起状態計算への直接適用が可能
-
他の行列への応用
- 密度行列、Fock行列、電子反発積分テンソル等、ODLR特性を持つ他の行列への展開
- Fast Multipole Method (FMM) [4] との概念的な類似性
-
機械学習との融合
- 変位方向の生成を学習ベースで最適化
- ヘシアンの低ランク近似の精度向上
7. 結論
O1NumHess は、分子ヘシアン行列の ODLR 特性を数学的に厳密に活用し、従来 であった勾配計算回数を (実際には約100〜120回)に削減することに成功した。
主な成果:
- 線形スケーリング:大規模系で従来法に対し線形的な加速
- 高精度:振動数誤差 数 cm、ギブズ自由エネルギー誤差 1〜2 kcal/mol
- 省メモリ:解析的ヘシアンの1/10以下のメモリ消費
- 汎用性:量子化学以外の逆問題にも適用可能
本手法は、巨大生体分子、ナノ材料、励起状態の熱力学的解析における実用的なヘシアン計算を可能にする画期的なアルゴリズムである。
参考文献
[1] Wang, B., Luo, S., Wang, Z., & Liu, W. (2025). O1NumHess: a fast and accurate seminumerical Hessian algorithm using only O(1) gradients. arXiv preprint arXiv:2508.07544v1 [physics.chem-ph].
[2] Swart, M., & Bickelhaupt, F. M. (2006). Optimization of strong and weak coordinates. International Journal of Quantum Chemistry, 106(12), 2536-2544.
[3] Pyykkö, P., & Atsumi, M. (2009). Molecular single-bond covalent radii for elements 1-118. Chemistry–A European Journal, 15(1), 186-197.
[4] Greengard, L., & Rokhlin, V. (1987). A fast algorithm for particle simulations. Journal of Computational Physics, 73(2), 325-348.
[5] Lu, T., & Chen, Q. (2021). Independent gradient model based on Hirshfeld partition: A new method for visual study of interactions in chemical systems. Journal of Computational Chemistry, 42(13), 842-851.
[6] Bao, J. L., & Truhlar, D. G. (2017). Variational transition state theory: theoretical framework and recent developments. Chemical Society Reviews, 46(24), 7548-7596.
付録A: 実装の詳細補足
A.1 近傍リストの構築
def build_neighbor_list(coords, cutoff=5.0):
"""
原子の近傍リストを構築
Parameters:
-----------
coords : ndarray, shape (N_atoms, 3)
原子座標 (Bohr)
cutoff : float
カットオフ距離 (Bohr)
Returns:
--------
neighbor_list : list of lists
各原子の近傍原子インデックスのリスト
"""
N_atoms = len(coords)
neighbor_list = []
for i in range(N_atoms):
neighbors = []
for j in range(N_atoms):
if i != j:
dist = np.linalg.norm(coords[i] - coords[j])
if dist < cutoff:
neighbors.append(j)
neighbor_list.append(neighbors)
return neighbor_list
A.2 質量重み付きヘシアンの対角化
def compute_vibrational_frequencies(hessian, coords, masses):
"""
振動数の計算
Parameters:
-----------
hessian : ndarray, shape (3N, 3N)
ヘシアン行列 (Hartree/Bohr^2)
coords : ndarray, shape (N, 3)
原子座標 (Bohr)
masses : ndarray, shape (N,)
原子質量 (amu)
Returns:
--------
frequencies : ndarray
振動数 (cm^-1)
normal_modes : ndarray
規格化された規準振動
"""
N_atoms = len(masses)
# 質量重み付き行列の構築
mass_weights = np.repeat(masses, 3)
M_sqrt_inv = 1.0 / np.sqrt(mass_weights)
H_mass_weighted = hessian * M_sqrt_inv[:, np.newaxis] * M_sqrt_inv[np.newaxis, :]
# 対角化
eigenvalues, eigenvectors = np.linalg.eigh(H_mass_weighted)
# 単位変換: Hartree/Bohr^2/amu -> cm^-1
# 1 Hartree = 219474.63 cm^-1
# 1 Bohr = 0.529177 Angstrom
# 1 amu = 1822.888 m_e
conversion = np.sqrt(219474.63 / (0.529177**2 * 1822.888))
frequencies = np.sign(eigenvalues) * np.sqrt(np.abs(eigenvalues)) * conversion
# 質量重み付きモードを元に戻す
normal_modes = eigenvectors / M_sqrt_inv[:, np.newaxis]
return frequencies, normal_modes