Skip to content

Commit

Permalink
Fixed the run() method to use atom indices instead of ids.
Browse files Browse the repository at this point in the history
Fixed the attribute error (MDAnalysis#2396).

Added test cases that check the ids and indices of atoms in a hydrogen bond.

Co-authored-by: p-j-smith <paul.smith@kcl.ac.uk>
  • Loading branch information
bieniekmateusz and p-j-smith committed Mar 1, 2020
1 parent 9bcf6f4 commit ee38518
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 7 deletions.
10 changes: 4 additions & 6 deletions package/MDAnalysis/analysis/hydrogenbonds/hbond_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@
.. autoclass:: HydrogenBondAnalysis
:members:
"""
from __future__ import absolute_import, division

Expand Down Expand Up @@ -408,8 +407,7 @@ def _get_dh_pairs(self):

# If donors_sel is not provided, use topology to find d-h pairs
if not self.donors_sel:

if len(self.u.bonds) == 0:
if not (hasattr(self.u, 'bonds') and len(self.u.bonds) != 0):
raise Exception('Cannot assign donor-hydrogen pairs via topology as no bonded information is present. '
'Please either: load a topology file with bonded information; use the guess_bonds() '
'topology guesser; or set HydrogenBondAnalysis.donors_sel so that a distance cutoff '
Expand Down Expand Up @@ -496,9 +494,9 @@ def _single_frame(self):

# Store data on hydrogen bonds found at this frame
self.hbonds[0].extend(np.full_like(hbond_donors, self._ts.frame))
self.hbonds[1].extend(hbond_donors.ids)
self.hbonds[2].extend(hbond_hydrogens.ids)
self.hbonds[3].extend(hbond_acceptors.ids)
self.hbonds[1].extend(hbond_donors.indices)
self.hbonds[2].extend(hbond_hydrogens.indices)
self.hbonds[3].extend(hbond_acceptors.indices)
self.hbonds[4].extend(hbond_distances)
self.hbonds[5].extend(hbond_angles)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,111 @@ def test_count_by_type(self, h):
counts = h.count_by_type()
assert int(counts[0, 2]) == ref_count

def test_count_by_ids(self, h):
def test_count_by_ids(self, h, universe):

ref_counts = [1.0, 1.0, 0.5, 0.4, 0.2, 0.1]
unique_hbonds = h.count_by_ids()

most_common_hbond_ids = [12, 14, 9]
assert np.allclose(unique_hbonds[0,:3], most_common_hbond_ids)

# count_by_ids() returns raw counts
# convert to fraction of time that bond was observed
counts = unique_hbonds[:, 3] / len(h.timesteps)

assert_array_equal(counts, ref_counts)



class TestHydrogenBondAnalysisMock(object):

kwargs = {
'donors_sel': 'name O',
'hydrogens_sel': 'name H1 H2',
'acceptors_sel': 'name O',
'd_h_cutoff': 1.2,
'd_a_cutoff': 3.0,
'd_h_a_angle_cutoff': 120.0
}

@staticmethod
@pytest.fixture(scope='class')
def universe():
# create 2 atoms
"""
H4
\
O1-H2 .... O2-H3
/
H1
"""
n_residues = 2
u = MDAnalysis.Universe.empty(n_atoms=n_residues*3,
n_residues=n_residues,
atom_resindex=np.repeat(range(n_residues), 3),
residue_segindex=[0] * n_residues,
trajectory=True, # necessary for adding coordinates
)
# in_memory=True)
u.add_TopologyAttr('name', ['O', 'H1', 'H2'] * n_residues)
u.add_TopologyAttr('type', ['O', 'H', 'H'] * n_residues)
u.add_TopologyAttr('resname', ['SOL'] * n_residues)
u.add_TopologyAttr('resid', list(range(1, n_residues + 1)))
print ('indices', u.atoms.indices)
u.add_TopologyAttr('id', list(range(1, (n_residues * 3) + 1)))
print ('ids', u.atoms.ids)
pos1 = np.array([[0, 0, 0], # O1
[-0.249, -0.968, 0], # H1
[1, 0, 0], # H2
[2.5, 0, 0], # O2
[3., 0, 0], # H3
[2.250, 0.968, 0] # H4
])
pos2 = np.array([[0, 0, 0], # O1
[-0.249, -0.968, 0], # H1
[1, 0, 0], # H2
[4.5, 0, 0], # O2
[5., 0, 0], # H3
[4.250, 0.968, 0] # H4
])
#u.atoms.positions = pos
coordinates = np.empty((3, # number of frames
u.atoms.n_atoms,
3))
coordinates[0] = pos1
coordinates[1] = pos2
coordinates[2] = pos1
u.load_new(coordinates, order='fac')

return u

def test_first(self, universe):

h = HydrogenBondAnalysis(universe, **self.kwargs)
h.run()

assert len(h.hbonds) == 2

frame_no, donor_index, hydrogen_index, acceptor_index, da_dst, dha_angle = h.hbonds[0]
assert donor_index == 0
assert hydrogen_index == 2
assert acceptor_index == 3
assert da_dst == 2.5
assert dha_angle == 180

def test_count_by_time(self, universe):

h = HydrogenBondAnalysis(universe, **self.kwargs)
h.run()

ref_times = np.array([0, 1, 2]) # u.trajectory.dt is 1
ref_counts = np.array([1, 0, 1])

counts = h.count_by_time()
assert_array_almost_equal(h.timesteps, ref_times)
assert_array_equal(counts, ref_counts)


class TestHydrogenBondAnalysisTIP3P_GuessAcceptors_GuessHydrogens_UseTopology_(TestHydrogenBondAnalysisTIP3P):
"""Uses the same distance and cutoff hydrogen bond criteria as :class:`TestHydrogenBondAnalysisTIP3P`, so the
results are identical, but the hydrogens and acceptors are guessed whilst the donor-hydrogen pairs are determined
Expand Down

0 comments on commit ee38518

Please sign in to comment.