Skip to content

Commit 616fa57

Browse files
committed
Added the functionality for computing thermal normal modes and PCA from the matrices of covariances of atomic fluctuations, velocities, forces, accelerations. These methods include PCA, Quasiharmonic approximation, Strachan's and Pereverzev's methods. Also added are the functions for printing normal modes in a format understandable by the VMD, functions for reading and writing XYZ trajectories are added in a NumPy way, the older functions using MATRIX class are removed. The funciton for removal of rigid modes - overall rotations and translations along given trajectory is added.
1 parent 76fde66 commit 616fa57

File tree

5 files changed

+888
-344
lines changed

5 files changed

+888
-344
lines changed

src/libra_py/md_align.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,90 @@
3535
from . import units
3636

3737

38+
def project_out_rigid(X_t, R_t, M):
39+
"""
40+
Remove rigid-body translations and rotations from a snapshot vector
41+
(coordinates, velocities, or accelerations), assuming the mass vector
42+
M has length 3N (one mass per Cartesian DOF).
43+
44+
The projection removes the six rigid-body modes for a nonlinear molecule:
45+
- 3 translational modes
46+
- 3 rotational modes
47+
48+
Equations:
49+
50+
Translation basis:
51+
t_alpha = sqrt(m_i) * e_alpha
52+
Rotation basis:
53+
r_alpha = sqrt(m_i) * (R_i - R_CM) × e_alpha
54+
Projected vector:
55+
X_proj = X - P P^T X
56+
Mass-weighted covariance matrices:
57+
K_ij^δr = 1/2 < sqrt(m_i m_j) δr_i δr_j >
58+
K_ij^v = 1/2 < sqrt(m_i m_j) v_i v_j >
59+
K_ij^a = 1/2 < sqrt(m_i m_j) a_i a_j >
60+
61+
Args:
62+
X_t : np.ndarray of shape (3*N,)
63+
Input vector to project (coordinates, velocities, or accelerations)
64+
R_t : np.ndarray of shape (3*N,)
65+
Instantaneous Cartesian positions of atoms (used to construct rotation basis)
66+
M : np.ndarray of shape (3*N,)
67+
Mass vector per Cartesian degree of freedom
68+
69+
Returns:
70+
X_proj : np.ndarray of shape (3*N,)
71+
Projected vector with rigid translations and rotations removed
72+
73+
Example:
74+
>>> import numpy as np
75+
>>> N_atoms = 5
76+
>>> M_atom = np.array([12.0, 16.0, 16.0, 1.0, 1.0])
77+
>>> M = np.repeat(M_atom, 3) # length 3*N
78+
>>> R = np.random.rand(3*N_atoms*3) # 3 DOF per atom
79+
>>> X = np.random.rand(3*N_atoms*3)
80+
>>> R_proj = project_out_rigid(R, R, M)
81+
>>> X_proj = project_out_rigid(X, R, M)
82+
"""
83+
if X_t.shape != R_t.shape or X_t.shape != M.shape:
84+
raise ValueError(f"Shapes of X_t {X_t.shape}, R_t {R_t.shape}, and M {M.shape} must match (all 3N,)")
85+
86+
N = X_t.size // 3 # number of atoms
87+
X = X_t.reshape(N, 3)
88+
R = R_t.reshape(N, 3)
89+
90+
# ----- 1. Remove translation -----
91+
M3 = M.reshape(N,3)
92+
X_trans = X - np.sum(M3 * X, axis=0) / np.sum(M) * 3 # remove COM motion
93+
94+
# Center R for rotation basis
95+
R_CM = np.sum(M3 * R, axis=0) / np.sum(M)
96+
R_cm = R - R_CM
97+
98+
# ----- 2. Build rotation basis (3 vectors) -----
99+
rot_basis = np.zeros((3*N, 3))
100+
unit_vectors = np.eye(3)
101+
for alpha in range(3):
102+
cross = np.cross(R_cm, unit_vectors[alpha]) # shape (N,3)
103+
rot_basis[:, alpha] = np.ravel(cross * np.sqrt(M3))
104+
105+
# ----- 3. Build translation basis (3 vectors) -----
106+
trans_basis = np.zeros((3*N,3))
107+
for alpha in range(3):
108+
vec = np.zeros((N,3))
109+
vec[:, alpha] = 1.0
110+
trans_basis[:, alpha] = np.ravel(vec * np.sqrt(M3))
111+
112+
# ----- 4. Combine bases and orthonormalize -----
113+
U = np.hstack([trans_basis, rot_basis]) # shape (3N,6)
114+
Q, _ = np.linalg.qr(U)
115+
116+
# ----- 5. Project X onto null space -----
117+
X_proj = np.ravel(X) - Q @ (Q.T @ np.ravel(X))
118+
return X_proj
119+
120+
121+
38122
def compute_com(R, M):
39123
"""
40124
Args:

0 commit comments

Comments
 (0)