Skip to content

Commit 5340484

Browse files
committed
unwrap/make whole molecules for MOI, COM and PA calcs
1 parent 8a6ecdd commit 5340484

2 files changed

Lines changed: 92 additions & 39 deletions

File tree

CodeEntropy/axes.py

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22

33
import numpy as np
4+
from MDAnalysis.lib.mdamath import make_whole
45

56
logger = logging.getLogger(__name__)
67

@@ -49,9 +50,7 @@ def get_residue_axes(self, data_container, index):
4950
f"and bonded resid {index}"
5051
)
5152
residue = data_container.select_atoms(f"resindex {index}")
52-
# set center of rotation to center of mass of the residue
53-
# we might want to change to center of bonding later
54-
center = residue.atoms.center_of_mass()
53+
center = residue.atoms.center_of_mass(unwrap=True)
5554

5655
if len(atom_set) == 0:
5756
# No bonds to other residues
@@ -61,7 +60,7 @@ def get_residue_axes(self, data_container, index):
6160
UAs = residue.select_atoms("mass 2 to 999")
6261
UA_masses = self.get_UA_masses(residue)
6362
moment_of_inertia_tensor = self.get_moment_of_inertia_tensor(
64-
center, UAs.positions, UA_masses
63+
center, UAs.positions, UA_masses, data_container.dimensions[:3]
6564
)
6665
rot_axes, moment_of_inertia = self.get_custom_principal_axes(
6766
moment_of_inertia_tensor
@@ -71,11 +70,13 @@ def get_residue_axes(self, data_container, index):
7170
)
7271
else:
7372
# if bonded to other residues, use default axes and MOI
73+
make_whole(data_container.atoms)
7474
trans_axes = data_container.atoms.principal_axes()
75-
rot_axes = np.real(residue.principal_axes())
76-
eigenvalues, _ = np.linalg.eig(residue.moment_of_inertia())
77-
moment_of_inertia = sorted(eigenvalues, reverse=True)
78-
center = residue.center_of_mass()
75+
rot_axes, moment_of_inertia = self.get_vanilla_axes(residue)
76+
# rot_axes = np.real(residue.principal_axes())
77+
# eigenvalues, _ = np.linalg.eig(residue.moment_of_inertia(unwrap=True))
78+
# moment_of_inertia = sorted(eigenvalues, reverse=True)
79+
center = residue.center_of_mass(unwrap=True)
7980

8081
return trans_axes, rot_axes, center, moment_of_inertia
8182

@@ -96,13 +97,12 @@ def get_UA_axes(self, data_container, index):
9697

9798
index = int(index)
9899

99-
# trans_axes = data_container.atoms.principal_axes()
100100
# use the same customPI trans axes as the residue level
101101
UAs = data_container.select_atoms("mass 2 to 999")
102102
UA_masses = self.get_UA_masses(data_container.atoms)
103-
center = data_container.atoms.center_of_mass()
103+
center = data_container.atoms.center_of_mass(unwrap=True)
104104
moment_of_inertia_tensor = self.get_moment_of_inertia_tensor(
105-
center, UAs.positions, UA_masses
105+
center, UAs.positions, UA_masses, data_container.dimensions[:3]
106106
)
107107
trans_axes, _moment_of_inertia = self.get_custom_principal_axes(
108108
moment_of_inertia_tensor
@@ -185,9 +185,12 @@ def get_bonded_axes(self, system, atom, dimensions):
185185
# find the heavy bonded atoms and light bonded atoms
186186
heavy_bonded, light_bonded = self.find_bonded_atoms(atom.index, system)
187187
UA = atom + light_bonded
188+
UA_all = atom + heavy_bonded + light_bonded
188189

189190
# now find which atoms to select to find the axes for rotating forces:
190-
# case1, won't apply to UA level
191+
# case1
192+
if len(heavy_bonded) == 0:
193+
custom_axes, custom_moment_of_inertia = self.get_vanilla_axes(UA_all)
191194
# case2
192195
if len(heavy_bonded) == 1 and len(light_bonded) == 0:
193196
custom_axes = self.get_custom_axes(
@@ -201,7 +204,7 @@ def get_bonded_axes(self, system, atom, dimensions):
201204
light_bonded[0].position,
202205
dimensions,
203206
)
204-
# case4, not used in Jon's code, use case5 instead
207+
# case4, not used in Jon's 2019 paper code, use case5 instead
205208
# case5
206209
if len(heavy_bonded) >= 2:
207210
custom_axes = self.get_custom_axes(
@@ -214,7 +217,7 @@ def get_bonded_axes(self, system, atom, dimensions):
214217
if custom_moment_of_inertia is None:
215218
# find moment of inertia using custom axes and atom position as COM
216219
custom_moment_of_inertia = self.get_custom_moment_of_inertia(
217-
UA, custom_axes, atom.position
220+
UA, custom_axes, atom.position, dimensions
218221
)
219222

220223
# get the moment of inertia from the custom axes
@@ -243,6 +246,33 @@ def find_bonded_atoms(self, atom_idx: int, system):
243246
bonded_H_atoms = bonded_atoms.select_atoms("mass 1 to 1.1")
244247
return bonded_heavy_atoms, bonded_H_atoms
245248

249+
def get_vanilla_axes(self, molecule):
250+
"""
251+
From a selection of atoms, get the ordered principal axes (3,3) and
252+
the ordered moment of inertia axes (3,) for that selection of atoms
253+
254+
Args:
255+
molecule: mdanalysis instance of molecule
256+
molecule_scale: the length scale of molecule
257+
258+
Returns:
259+
principal_axes: the principal axes, (3,3) array
260+
moment_of_inertia: the moment of inertia, (3,) array
261+
"""
262+
# default moment of inertia
263+
moment_of_inertia = molecule.moment_of_inertia(unwrap=True)
264+
make_whole(molecule.atoms)
265+
principal_axes = molecule.principal_axes()
266+
# diagonalise moment of inertia tensor here
267+
# pylint: disable=unused-variable
268+
eigenvalues, _eigenvectors = np.linalg.eig(moment_of_inertia)
269+
# sort eigenvalues of moi tensor by largest to smallest magnitude
270+
order = sorted(eigenvalues, reverse=True) # decending order
271+
# principal_axes = principal_axes[order] # PI already ordered correctly
272+
moment_of_inertia = eigenvalues[order]
273+
274+
return principal_axes, moment_of_inertia
275+
246276
def get_custom_axes(
247277
self, a: np.ndarray, b_list: list, c: np.ndarray, dimensions: np.ndarray
248278
):
@@ -303,7 +333,11 @@ def get_custom_axes(
303333
return scaled_custom_axes
304334

305335
def get_custom_moment_of_inertia(
306-
self, UA, custom_rotation_axes: np.ndarray, center_of_mass: np.ndarray
336+
self,
337+
UA,
338+
custom_rotation_axes: np.ndarray,
339+
center_of_mass: np.ndarray,
340+
dimensions: np.ndarray,
307341
):
308342
"""
309343
Get the moment of inertia (specifically used for the united atom level)
@@ -318,7 +352,7 @@ def get_custom_moment_of_inertia(
318352
Returns:
319353
custom_moment_of_inertia: (3,) array for moment of inertia
320354
"""
321-
translated_coords = UA.positions - center_of_mass
355+
translated_coords = self.get_vector(center_of_mass, UA.positions, dimensions)
322356
custom_moment_of_inertia = np.zeros(3)
323357
for coord, mass in zip(translated_coords, UA.masses):
324358
axis_component = np.sum(
@@ -366,27 +400,24 @@ def get_vector(self, a: np.ndarray, b: np.ndarray, dimensions: np.ndarray):
366400
For vector of two coordinates over periodic boundary conditions (PBCs).
367401
368402
Args:
369-
a: (3,) array of atom cooordinates
403+
a: (N,3) array of atom cooordinates
370404
b: (3,) array of atom cooordinates
371405
dimensions: (3,) array of system box dimensions.
372406
373407
Returns:
374-
delta_wrapped: (3,) array of the vector between two points
408+
delta_wrapped: (N,3) array of the vector
375409
"""
376410
delta = b - a
377-
delta_wrapped = []
378-
for delt, box in zip(delta, dimensions):
379-
if delt < 0 and delt < 0.5 * box:
380-
delt = delt + box
381-
if delt > 0 and delt > 0.5 * box:
382-
delt = delt - box
383-
delta_wrapped.append(delt)
384-
delta_wrapped = np.array(delta_wrapped)
411+
delta -= dimensions * np.round(delta / dimensions)
385412

386-
return delta_wrapped
413+
return delta
387414

388415
def get_moment_of_inertia_tensor(
389-
self, center_of_mass: np.ndarray, positions: np.ndarray, masses: list
416+
self,
417+
center_of_mass: np.ndarray,
418+
positions: np.ndarray,
419+
masses: list,
420+
dimensions: np.array,
390421
) -> np.ndarray:
391422
"""
392423
Calculate a custom moment of inertia tensor.
@@ -402,7 +433,7 @@ def get_moment_of_inertia_tensor(
402433
Returns:
403434
moment_of_inertia_tensor: a (3,3) moment of inertia tensor
404435
"""
405-
r = positions - center_of_mass
436+
r = self.get_vector(center_of_mass, positions, dimensions)
406437
r2 = np.sum(r**2, axis=1)
407438
moment_of_inertia_tensor = np.eye(3) * np.sum(masses * r2)
408439
moment_of_inertia_tensor -= np.einsum("i,ij,ik->jk", masses, r, r)

CodeEntropy/levels.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22

33
import numpy as np
4+
from MDAnalysis.lib.mdamath import make_whole
45
from rich.progress import (
56
BarColumn,
67
Progress,
@@ -132,11 +133,13 @@ def get_matrices(
132133
axes_manager.get_residue_axes(data_container, bead_index)
133134
)
134135
else:
136+
make_whole(data_container.atoms)
137+
make_whole(bead)
135138
trans_axes = data_container.atoms.principal_axes()
136139
rot_axes = np.real(bead.principal_axes())
137-
eigenvalues, _ = np.linalg.eig(bead.moment_of_inertia())
140+
eigenvalues, _ = np.linalg.eig(bead.moment_of_inertia(unwrap=True))
138141
moment_of_inertia = sorted(eigenvalues, reverse=True)
139-
center = bead.center_of_mass()
142+
center = bead.center_of_mass(unwrap=True)
140143

141144
# Sort out coordinates, forces, and torques for each atom in the bead
142145
weighted_forces[bead_index] = self.get_weighted_forces(
@@ -152,6 +155,7 @@ def get_matrices(
152155
center,
153156
force_partitioning,
154157
moment_of_inertia,
158+
axes_manager,
155159
)
156160

157161
# Create covariance submatrices
@@ -259,11 +263,15 @@ def get_combined_forcetorque_matrices(
259263
axes_manager.get_residue_axes(data_container, bead_index)
260264
)
261265
else:
266+
# ensure molecule is whole for PA calcs
267+
make_whole(data_container.atoms)
268+
make_whole(bead)
262269
trans_axes = data_container.atoms.principal_axes()
263-
rot_axes = np.real(bead.principal_axes())
264-
eigenvalues, _ = np.linalg.eig(bead.moment_of_inertia())
265-
moment_of_inertia = sorted(eigenvalues, reverse=True)
266-
center = bead.center_of_mass()
270+
rot_axes, moment_of_inertia = axes_manager.get_vanilla_axes(bead)
271+
# rot_axes = np.real(bead.principal_axes())
272+
# eigenvalues, _ = np.linalg.eig(bead.moment_of_inertia(unwrap=True))
273+
# moment_of_inertia = sorted(eigenvalues, reverse=True)
274+
center = bead.center_of_mass(unwrap=True)
267275

268276
# Sort out coordinates, forces, and torques for each atom in the bead
269277
weighted_forces[bead_index] = self.get_weighted_forces(
@@ -279,6 +287,7 @@ def get_combined_forcetorque_matrices(
279287
center,
280288
force_partitioning,
281289
moment_of_inertia,
290+
axes_manager,
282291
)
283292

284293
# Create covariance submatrices
@@ -427,7 +436,13 @@ def get_weighted_forces(
427436
return weighted_force
428437

429438
def get_weighted_torques(
430-
self, bead, rot_axes, center, force_partitioning, moment_of_inertia
439+
self,
440+
bead,
441+
rot_axes,
442+
center,
443+
force_partitioning,
444+
moment_of_inertia,
445+
axes_manager,
431446
):
432447
"""
433448
Compute moment-of-inertia weighted torques for a bead.
@@ -466,7 +481,10 @@ def get_weighted_torques(
466481
"""
467482

468483
# translate and rotate positions and forces
469-
translated_coords = bead.positions - center
484+
# translated_coords = bead.positions - center
485+
translated_coords = axes_manager.get_vector(
486+
center, bead.positions, bead.dimensions[:3]
487+
)
470488
rotated_coords = np.tensordot(translated_coords, rot_axes.T, axes=1)
471489
rotated_forces = np.tensordot(bead.forces, rot_axes.T, axes=1)
472490
# scale forces
@@ -481,12 +499,16 @@ def get_weighted_torques(
481499
weighted_torque[dimension] = 0
482500
continue
483501

484-
if moment_of_inertia[dimension] == 0:
502+
if np.iscomplex(moment_of_inertia[dimension]):
485503
weighted_torque[dimension] = 0
486504
continue
487505

506+
if moment_of_inertia[dimension] == 0:
507+
moment_of_inertia[dimension] = 0
508+
continue
509+
488510
if moment_of_inertia[dimension] < 0:
489-
weighted_torque[dimension] = 0
511+
moment_of_inertia[dimension] = 0
490512
continue
491513

492514
# Compute weighted torque

0 commit comments

Comments
 (0)