Skip to content

Commit ea35b4e

Browse files
authored
Merge pull request #445 from CCPBioSim/441-all-atom-MOI-refactored
Residue level moment of inertia is now calculated using all atom positions + masses to match polymer and UA levels.
2 parents 54defd6 + 9c4f1ff commit ea35b4e

46 files changed

Lines changed: 393 additions & 484 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CodeEntropy/levels/axes.py‎

Lines changed: 11 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,14 @@ def get_residue_axes(
8282
* Translational axes are principal axes of data_container.
8383
* Find edge heavy atom (i.e. heavy atoms bonded to neighbour residue).
8484
Compute rotation centre and axes as in get_terminal_axes.
85-
Compute custom MOI, using heavy atom positions and
86-
heavy atom + hydrogen masses.
85+
Compute custom MOI, using all atoms positions and masses.
8786
8887
- If bonded to at least two other residues:
8988
* Translational axes are principal axes of data_container.
9089
* Find edge heavy atoms (i.e. heavy atoms bonded to neighbour residues).
9190
Compute rotation centre and axes as in get_non_terminal_axes.
92-
Compute a custom MOI, using heavy atom positions and
93-
heavy atom + hydrogen masses.
91+
Compute custom MOI, using all atoms positions and masses.
92+
9493
9594
Args:
9695
data_container (MDAnalysis.Universe or AtomGroup):
@@ -134,19 +133,8 @@ def get_residue_axes(
134133
f"resindex {index_next})"
135134
)
136135

137-
uas = residue.select_atoms("mass 2 to 999")
138-
ua_masses = self.get_UA_masses(residue)
139-
140136
if len(edge_atom_set) == 0:
141-
moi_tensor = self.get_moment_of_inertia_tensor(
142-
center_of_mass=np.array(residue.center_of_mass()),
143-
positions=uas.positions,
144-
masses=ua_masses,
145-
dimensions=data_container.dimensions[:3],
146-
)
147-
rot_axes, moment_of_inertia = self.get_principal_axes_from_tensor(
148-
moi_tensor
149-
)
137+
rot_axes, moment_of_inertia = self.get_molecule_axes(residue)
150138
trans_axes = rot_axes # per original convention
151139
rot_center = np.array(residue.center_of_mass())
152140
else:
@@ -167,8 +155,8 @@ def get_residue_axes(
167155
)
168156
moment_of_inertia = self.get_custom_residue_moment_of_inertia(
169157
center_of_mass=rot_center,
170-
positions=uas.positions,
171-
masses=ua_masses,
158+
positions=residue.atoms.positions,
159+
masses=residue.atoms.masses,
172160
custom_rot_axes=rot_axes,
173161
dimensions=data_container.dimensions[:3],
174162
)
@@ -181,7 +169,6 @@ def get_residue_axes_from_topology(
181169
mol,
182170
residue_atoms,
183171
topology,
184-
box: np.ndarray | None,
185172
):
186173
"""Compute residue axes using cached static topology.
187174
@@ -195,7 +182,6 @@ def get_residue_axes_from_topology(
195182
mol: Current-frame molecule fragment.
196183
residue_atoms: AtomGroup for the residue in the current frame.
197184
topology: Cached ``ResidueAxesTopology`` for this residue.
198-
box: Current periodic box lengths. If omitted, ``u.dimensions`` is used.
199185
200186
Returns:
201187
Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
@@ -204,24 +190,10 @@ def get_residue_axes_from_topology(
204190
- center: Residue centre, shape ``(3,)``.
205191
- moment_of_inertia: Principal moments, shape ``(3,)``.
206192
"""
207-
dimensions = (
208-
np.asarray(box, dtype=float)
209-
if box is not None
210-
else np.asarray(u.dimensions[:3], dtype=float)
211-
)
212193
center = residue_atoms.center_of_mass(unwrap=True)
213194

214195
if not topology.has_neighbor_bonds:
215-
heavy_atoms = u.atoms[topology.residue_heavy_indices]
216-
moment_of_inertia_tensor = self.get_moment_of_inertia_tensor(
217-
center_of_mass=center,
218-
positions=heavy_atoms.positions,
219-
masses=topology.residue_ua_masses,
220-
dimensions=dimensions,
221-
)
222-
rot_axes, moment_of_inertia = self.get_principal_axes_from_tensor(
223-
moment_of_inertia_tensor
224-
)
196+
rot_axes, moment_of_inertia = self.get_molecule_axes(residue_atoms)
225197
trans_axes = rot_axes
226198
else:
227199
make_whole(mol.atoms)
@@ -277,7 +249,7 @@ def get_UA_axes(self, data_container, index: int, res_position):
277249

278250
index = int(index) # UA bead index
279251
heavy_atoms = data_container.select_atoms("mass 2 to 999")
280-
# use the same customPI trans axes as the residue level
252+
# use the same trans axes as the residue level
281253
if len(heavy_atoms) > 1:
282254
if len(data_container.residues) == 1:
283255
# only the one residue => use principal axes
@@ -411,16 +383,7 @@ def get_UA_axes_from_topology(
411383
heavy_atom = u.atoms[int(topology.heavy_atom_index)]
412384

413385
if len(heavy_atoms) > 1:
414-
center = residue_atoms.center_of_mass(unwrap=True)
415-
moment_of_inertia_tensor = self.get_moment_of_inertia_tensor(
416-
center_of_mass=center,
417-
positions=heavy_atoms.positions,
418-
masses=topology.residue_ua_masses,
419-
dimensions=dimensions,
420-
)
421-
trans_axes, _moment_of_inertia = self.get_principal_axes_from_tensor(
422-
moment_of_inertia_tensor
423-
)
386+
trans_axes = self.get_principal_axes_from_group(residue_atoms)
424387
else:
425388
make_whole(residue_atoms)
426389
trans_axes = self.get_principal_axes_from_group(residue_atoms)
@@ -921,8 +884,8 @@ def get_custom_residue_moment_of_inertia(
921884
922885
Args:
923886
center_of_mass: (3, ) COM for bead
924-
positions: (N,3) positions of the UAs in the bead
925-
masses: (N,) masses of the UAs in the bead
887+
positions: (N,3) positions of the atoms in the bead
888+
masses: (N,) masses of the atoms in the bead
926889
custom_rot_axes: (3,3) array of residue rotation axes
927890
dimensions: (3,) simulation_box_dimensions
928891
@@ -1137,30 +1100,6 @@ def get_principal_axes_from_group(self, group) -> np.ndarray:
11371100
)
11381101
return principal_axes
11391102

1140-
def get_UA_masses(self, molecule) -> list[float]:
1141-
"""Return united-atom (UA) masses for a molecule.
1142-
1143-
For a given molecule, return a list of masses of UAs (combination of the
1144-
heavy atoms + bonded hydrogen atoms). This list is used to get the moment
1145-
of inertia tensor for molecules larger than one UA.
1146-
1147-
Args:
1148-
molecule: MDAnalysis AtomGroup representing the molecule.
1149-
1150-
Returns:
1151-
list[float]: UA masses for each heavy atom.
1152-
"""
1153-
ua_masses: list[float] = []
1154-
for atom in molecule:
1155-
if atom.mass > 1.1:
1156-
ua_mass = float(atom.mass)
1157-
bonded_atoms = molecule.select_atoms(f"bonded index {atom.index}")
1158-
bonded_h_atoms = bonded_atoms.select_atoms("mass 1 to 1.1")
1159-
for h in bonded_h_atoms:
1160-
ua_mass += float(h.mass)
1161-
ua_masses.append(ua_mass)
1162-
return ua_masses
1163-
11641103
def get_chain(self, residue, first, last):
11651104
"""
11661105
For a given MDAnalysis AtomGroup and two given heavy atoms

‎CodeEntropy/levels/nodes/covariance.py‎

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,6 @@ def _build_residue_vectors(
578578
relative_res_i=relative_res_i,
579579
axes_manager=axes_manager,
580580
axes_topology=axes_topology,
581-
box=box,
582581
customised_axes=customised_axes,
583582
)
584583

@@ -615,7 +614,6 @@ def _get_residue_axes(
615614
relative_res_i: int,
616615
axes_manager: Any,
617616
axes_topology: Any | None,
618-
box: np.ndarray | None,
619617
customised_axes: bool,
620618
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
621619
"""Return axes, centre, and inertia data for a residue bead.
@@ -630,7 +628,6 @@ def _get_residue_axes(
630628
might differ from residue index of first residue in topology.
631629
axes_manager: Axes helper used to select axes, centres, and moments.
632630
axes_topology: Optional cached axes topology generated during static setup.
633-
box: Optional periodic box vector.
634631
customised_axes: Whether customised residue axes should be used.
635632
636633
Returns:
@@ -644,11 +641,7 @@ def _get_residue_axes(
644641

645642
if residue_topology is not None:
646643
return axes_manager.get_residue_axes_from_topology(
647-
u=u,
648-
mol=mol,
649-
residue_atoms=res.atoms,
650-
topology=residue_topology,
651-
box=box,
644+
u=u, mol=mol, residue_atoms=res.atoms, topology=residue_topology
652645
)
653646

654647
return axes_manager.get_residue_axes(

‎tests/regression/baselines/benzaldehyde/combined_forcetorque_false.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
"0": {
44
"components": {
55
"united_atom:Transvibrational": 0.06817494136874551,
6-
"united_atom:Rovibrational": 49.686697381523516,
7-
"residue:Transvibrational": 69.25273989458984,
8-
"residue:Rovibrational": 68.6481013342223,
6+
"united_atom:Rovibrational": 49.686697381523466,
7+
"residue:Transvibrational": 69.24977659775257,
8+
"residue:Rovibrational": 69.73922011617867,
99
"united_atom:Conformational": 0.0,
1010
"residue:Conformational": 0.0,
1111
"residue:Orientational": 20.481571492615355
1212
},
13-
"total": 208.13728504431975
13+
"total": 209.22544052943883
1414
}
1515
}
1616
}

‎tests/regression/baselines/benzaldehyde/default.json‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
"groups": {
33
"0": {
44
"components": {
5-
"united_atom:Transvibrational": 24.92182210331405,
6-
"united_atom:Rovibrational": 27.950376507672583,
7-
"residue:FTmat-Transvibrational": 70.97090266225698,
8-
"residue:FTmat-Rovibrational": 59.588669973122094,
5+
"united_atom:Transvibrational": 24.921822103314035,
6+
"united_atom:Rovibrational": 27.950376507672594,
7+
"residue:FTmat-Transvibrational": 71.231562004914,
8+
"residue:FTmat-Rovibrational": 60.43602521189648,
99
"united_atom:Conformational": 7.522643899702263,
1010
"residue:Conformational": 0.0,
1111
"residue:Orientational": 59.43920971558428
1212
},
13-
"total": 250.39362486165226
13+
"total": 251.50163944308366
1414
}
1515
}
1616
}

‎tests/regression/baselines/benzaldehyde/frame_window.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
"0": {
44
"components": {
55
"united_atom:Transvibrational": 37.90368186636453,
6-
"united_atom:Rovibrational": 38.21906858443616,
7-
"residue:FTmat-Transvibrational": 73.76730786578054,
8-
"residue:FTmat-Rovibrational": 57.689489298548764,
6+
"united_atom:Rovibrational": 38.219068584436165,
7+
"residue:FTmat-Transvibrational": 74.05827280885852,
8+
"residue:FTmat-Rovibrational": 58.53451127009362,
99
"united_atom:Conformational": 0.0,
1010
"residue:Conformational": 0.0,
1111
"residue:Orientational": 20.481571492615355
1212
},
13-
"total": 228.06111910774533
13+
"total": 229.1971060223682
1414
}
1515
}
1616
}

0 commit comments

Comments
 (0)