From 4a453aff9b1dd3019726265953b2a3b370a35af0 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Wed, 3 Oct 2018 10:44:19 +0100 Subject: [PATCH 01/23] Move the water bridge analysis to the new analysis class Move the water bridge analysis to the new analysis class, add high Order water bridge support. (PR #2087) --- package/CHANGELOG | 2 + .../analysis/hbonds/wbridge_analysis.py | 1191 ++++++++++------- testsuite/CHANGELOG | 1 + .../MDAnalysisTests/analysis/test_wbridge.py | 661 ++++++++- 4 files changed, 1318 insertions(+), 537 deletions(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index acb0c32cbb5..ab05050086a 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -20,6 +20,8 @@ The rules for this file: * 0.18.1 Enhancements + * Move the water bridge analysis to the new analysis class, add high + Order water bridge support. (PR #2087) * Added bond/angle/dihedral reading in PARM7 TOPParser (PR #2052) * Replaced multiple apply (_apply_distmat, _apply_kdtree) methods in distance based selections with diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index 6f81b22c029..69f5fa333ea 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -2,7 +2,7 @@ # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # # MDAnalysis --- https://www.mdanalysis.org -# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors +# Copyright (c) 2006-2018 The MDAnalysis Development Team and contributors # (see the file AUTHORS for the full list of names) # # Released under the GNU Public Licence, v2 or any higher version @@ -25,7 +25,7 @@ =============================================================================== :Author: Zhiyi Wu -:Year: 2017 +:Year: 2017-2018 :Copyright: GNU Public License v3 :Maintainer: Zhiyi Wu , `@xiki-tempula`_ on GitHub @@ -52,14 +52,17 @@ e.g. -CO\ :sub:`2`\ :sup:`-`:···H−O:···HN- (where H−O is part of **H−O**\ −H) -The :class:`WaterBridgeAnalysis` class is modeled after the \ -:class:`~MDAnalysis.analysis.hbonds.hbond_analysis.HydrogenBondAnalysis`. +A higher order water bridge is defined as more than one water bridging +hydrogen bond acceptor and donor. An example of a second order water bridge: + +e.g. -CO\ :sub:`2`\ :sup:`-`:···H−O:···H−O:···HN- (where H−O is part of **H−O**\ −H) The following keyword arguments are important to control the behavior of the water bridge analysis: - *water_selection* (``resname SOL``): the selection string for the bridging water + - *order* the maximum number of water bridging both ends - donor-acceptor *distance* (Å): 3.0 - Angle *cutoff* (degrees): 120.0 - *forcefield* to switch between default values for different force fields @@ -71,13 +74,13 @@ ------ The results are a list of hydrogen bonds between the selection 1 or selection 2 -and the bridging water. +and the bridging waters. Each list is formated similar to the \ :attr:`HydrogenBondAnalysis.timeseries ` and contains - - the **identities** of donor and acceptor heavy-atoms, + - the **identities** of donor and acceptor atoms, - the **distance** between the heavy atom acceptor atom and the hydrogen atom - the **angle** donor-hydrogen-acceptor angle (180º is linear). @@ -89,39 +92,34 @@ [ # frame 1 # hbonds linking the selection 1 and selection 2 to the bridging # water 1 - [ # hbond 1 from selection 1 to the bridging water 1 - , - , , , - , - ], - [ # hbond 2 from selection 1 to the bridging water 1 - , - , , , - , - ], - [ # hbond 1 from selection 2 to the bridging water 1 + [[ # hbond 1 from selection 1 to the bridging water 1 , - , , , + , , , , - ], - [ # hbond 2 from selection 2 to the bridging water 1 + ], + [ # hbond 2 from bridging water 1 to the selection 2 , - , , , + , , , , - ], + ]], # hbonds linking the selection 1 and selection 2 to the bridging - # water 2 - [ # hbond 1 from selection 1 to the bridging water 2 + # water 1 and bridging water 2 + [[ # hbond 3 from selection 1 to the bridging water 1 , - , , , + , , , , - ], - [ # hbond 1 from selection 2 to the bridging water 2 + ], + [ # hbond 4 from bridging water 1 to the bridging water 2 , - , , , + , , , , - ], + ], + [ # hbond 5 from bridging water 2 to the selection 2 + , + , , , + , + ]], .... ], [ # frame 2 @@ -130,15 +128,10 @@ ... ] -Using the :meth:`WaterBridgeAnalysis.generate_table` method one can reformat -the results as a flat "normalised" table that is easier to import into a -database or dataframe for further processing. -:meth:`WaterBridgeAnalysis.save_table` saves the table to a pickled file. The -table itself is a :class:`numpy.recarray`. Detection of water bridges --------------------------- -Water bridges are recorded if a bridging water simultaneously forms two +Water bridges are recorded if a bridging water simultaneously forms hydrogen bonds with selection 1 and selection 2. Hydrogen bonds are detected as is described in \ @@ -173,116 +166,301 @@ class WaterBridgeAnalysis_OtherFF(WaterBridgeAnalysis): w = MDAnalysis.analysis.hbonds.WaterBridgeAnalysis(u, 'resname ARG', 'resname ASP') w.run() +The maximum number of bridging waters detected can be changed using the order keyword. :: + + w = MDAnalysis.analysis.hbonds.WaterBridgeAnalysis(u, 'resname ARG', 'resname ASP', + order=3) + +Thus, a maximum of three bridging waters will be detected. The results are stored as the attribute :attr:`WaterBridgeAnalysis.timeseries`; see :ref:`wb_Analysis_Output` for the format. -An example of using the :attr:`~WaterBridgeAnalysis.timeseries` would be +An example of using the :attr:`~WaterBridgeAnalysis` would be detecting the percentage of time a certain water bridge exits. Trajectory :code:`u` has two frames, where the first frame contains a water -bridge from the oxygen of the first arginine to the oxygen of the third -aspartate. No water bridge is detected in the second frame. :: +bridge from the oxygen of the first arginine to one of the oxygens in the carboxylic +group of aspartate (ASP3:OD1). In the second frame, the same water bridge forms but +is between the oxygen of the arginine and the other oxygen in the carboxylic +group (ASP3:OD2). :: print(w.timeseries) -prints out (the comments are not part of the data structure but are added here -for clarity): :: +prints out. :: [ # frame 1 - # A water bridge SOL2 links O from ARG1 and ASP3 - [[0,1,'ARG1:O', 'SOL2:HW1',3.0,180], - [2,3,'SOL2:HW2','ASP3:O', 3.0,180], + # A water bridge SOL2 links O from ARG1 to the carboxylic group OD1 of ASP3 + [[[0,1,('ARG',1,'O'), ('SOL',2,'HW1'), 3.0,180], + [2,3,('SOL',2,'HW2'),('ASP',3,'OD1'), 3.0,180],], ], # frame 2 - # No water bridge detected - [] + # Another water bridge SOL2 links O from ARG1 to the other oxygen of the + # carboxylic group OD2 of ASP3 + [[[0,1,('ARG',1,'O'), ('SOL',2,'HW1'), 3.0,180], + [2,4,('SOL',2,'HW2'),('ASP',3,'OD2'), 3.0,180],], + ], ] -To calculate the percentage, we can iterate through :code:`w.timeseries`. :: - water_bridge_presence = [] - for frame in w.timeseries: - if frame: - water_bridge_presence.append(True) - else: - water_bridge_presence.append(False) - p_bridge = float(sum(water_bridge_presence))/len(water_bridge_presence) - print("Fraction of time with water bridge present: {}".format(p_bridge)) +.. _wb_count_by_type: -In the example above, :code:`p_bridge` would become 0.5, i.e., for 50% of the -trajectory a water bridge was detected between the selected residues. +Use count_by_type +--------------------------- -Alternatively, :meth:`~WaterBridgeAnalysis.count_by_type` can also be used to +To calculate the percentage, we can use the :meth:`~WaterBridgeAnalysis.count_by_type` to generate the frequence of all water bridges in the simulation. :: w.count_by_type() Returns :: - [(0, 3, 'ARG', 1, 'O', 'ASP', 3, 'O', 0.5)] + [(0, 3, 'ARG', 1, 'O', 'ASP', 3, 'OD1', 0.5), + (0, 4, 'ARG', 1, 'O', 'ASP', 3, 'OD2', 0.5),] -For further data analysis, it is convenient to process the -:attr:`~WaterBridgeAnalysis.timeseries` data into a normalized table with the -:meth:`~WaterBridgeAnalysis.generate_table` method, which creates a new data -structure :attr:`WaterBridgeAnalysis.table` that contains one row for each -observation of a hydrogen bond:: +You might think that the OD1 and OD2 are the same oxygen and the aspartate has just flipped +and thus, they should be counted as the same type of water bridge. The type of the water +bridge can be customised by supplying an analsysis function to +:meth:`~WaterBridgeAnalysis.count_by_type`. :: - w.generate_table() + def analysis(current, output): + '''This function defines how the type of water bridge should be specified. -This table can then be easily turned into, e.g., a `pandas.DataFrame`_, and -further analyzed:: + Parameters + ---------- + current : list + The current water bridge being analysed is a list of hydrogen bonds from + selection 1 to selection 2. + output : dict + A dictionary where the key is the type of the water bridge and the value + is the number of this type of water bridge. + ''' + + # decompose the first hydrogen bond. + s1_index, to_index, (s1_resname, s1_resid, s1_name), + (to_resname, to_resid, to_name), dist, angle = current[0] + # decompose the last hydrogen bond. + from_index, s2_index, (from_resname, from_resid, from_name), + (s2_resname, s2_resid, s2_name), dist, angle = current[-1] + # if the residue name is ASP and the atom name is OD2 or OD1, + # the atom name is changed to OD + if s2_resname == 'ASP' and (s2_name == 'OD1' or s2_name == 'OD2'): + s2_name = 'OD' + # setting up the key which defines this type of water bridge. + key = (s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) + # The number of this type of water bridge is incremented by 1. + output[key] += 1 + + w.count_by_type(analysis_func=analysis) - import pandas as pd - df = pd.DataFrame.from_records(w.table) +Returns :: + [('ARG', 1, 'O', 'ASP', 3, 'OD', 1.0),] -.. _pandas.DataFrame: http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html +Some people might only interested in contacts between residues and pay no attention +to the details regarding the atom name. This can also be achieved by supplying an analysis +function to :meth:`~WaterBridgeAnalysis.count_by_type`. :: -Classes -------- + def analysis(current, output): + '''This function defines how the type of water bridge should be specified. -.. autoclass:: WaterBridgeAnalysis - :members: + Parameters + ---------- + current : list + The current water bridge being analysed is a list of hydrogen bonds from + selection 1 to selection 2. + output : dict + A dictionary where the key is the type of the water bridge and the value + is the number of this type of water bridge. + ''' + + s1_index, to_index, (s1_resname, s1_resid, s1_name), + (to_resname, to_resid, to_name), dist, angle = current[0] + from_index, s2_index, (from_resname, from_resid, from_name), + (s2_resname, s2_resid, s2_name), dist, angle = current[-1] + # s1_name and s2_name are not included in the key + key = (s1_resname, s1_resid, s2_resname, s2_resid) + output[key] += 1 + + w.count_by_type(analysis_func=analysis) - .. attribute:: timesteps +Returns :: - List of the times of each timestep. This can be used together with - :attr:`~WaterBridgeAnalysis.timeseries` to find the specific time point - of a water bridge existence, or see :attr:`~WaterBridgeAnalysis.table`. + [('ARG', 1, 'ASP', 3, 1.0),] + +On the other hand, other people may insist that first order and second order water +bridges shouldn't be mixed together, which can also be achieved by supplying an analysis +function to :meth:`~WaterBridgeAnalysis.count_by_type`. :: + + def analysis(current, output): + '''This function defines how the type of water bridge should be specified. + + Parameters + ---------- + current : list + The current water bridge being analysed is a list of hydrogen bonds from + selection 1 to selection 2. + output : dict + A dictionary where the key is the type of the water bridge and the value + is the number of this type of water bridge. + ''' + + s1_index, to_index, (s1_resname, s1_resid, s1_name), + (to_resname, to_resid, to_name), dist, angle = current[0] + from_index, s2_index, (from_resname, from_resid, from_name), + (s2_resname, s2_resid, s2_name), dist, angle = current[-1] + # order of the current water bridge is computed + order_of_water_bridge = len(current) - 1 + # and is included in the key + key = (s1_resname, s1_resid, s2_resname, s2_resid, order_of_water_bridge) + # The number of this type of water bridge is incremented by 1. + output[key] += 1 + + w.count_by_type(analysis_func=analysis) + +The extra number 1 precede the 1.0 indicate that this is a first order water bridge :: + + [('ARG', 1, 'ASP', 3, 1, 1.0),] + +Some people might not be interested in the interactions related to arginine. The undesirable +interactions can be discarded by supplying an analysis function to +:meth:`~WaterBridgeAnalysis.count_by_type`. :: + + def analysis(current, output): + '''This function defines how the type of water bridge should be specified. + + Parameters + ---------- + current : list + The current water bridge being analysed is a list of hydrogen bonds from + selection 1 to selection 2. + output : dict + A dictionary where the key is the type of the water bridge and the value + is the number of this type of water bridge. + ''' + + s1_index, to_index, (s1_resname, s1_resid, s1_name), + (to_resname, to_resid, to_name), dist, angle = current[0] + from_index, s2_index, (from_resname, from_resid, from_name), + (s2_resname, s2_resid, s2_name), dist, angle = current[-1] + if not s1_resname == 'ARG': + key = (s1_resname, s1_resid, s2_resname, s2_resid) + output[key] += 1 + + w.count_by_type(analysis_func=analysis) + +Returns nothing in this case :: + + [,] + +Additional key words can be supplied to the analysis function by passing through +:meth:`~WaterBridgeAnalysis.count_by_type`. :: - .. attribute:: table + def analysis(current, output, **kwargs): + ... + w.count_by_type(analysis_func=analysis, **kwargs) - A normalised table of the data in - :attr:`WaterBridgeAnalysis.timeseries`, generated by - :meth:`WaterBridgeAnalysis.generate_table`. It is a - :class:`numpy.recarray` with the following columns: - 0. "time" - 1. "donor_index" - 2. "acceptor_index" - 3. "donor_resnm" - 4. "donor_resid" - 5. "donor_atom" - 6. "acceptor_resnm" - 7. "acceptor_resid" - 8. "acceptor_atom" - 9. "distance" - 10. "angle" +.. _wb_count_by_time: - It takes up more space than :attr:`~WaterBridgeAnalysis.timeseries` but - it is easier to analyze and to import into databases or dataframes. +Use count_by_time +--------------------------- + +:meth:`~WaterBridgeAnalysis.count_by_type` aggregates data across frames, which +might be desirable in some cases but not the others. :meth:`~WaterBridgeAnalysis.count_by_time` +provides additional functionality for aggregating results for each frame. + +The default behaviour of :meth:`~WaterBridgeAnalysis.count_by_time` is counting the number of +water bridges from selection 1 to selection 2 for each frame. Take the previous ASP, ARG salt +bridge for example: :: + + w.count_by_time() + +As one water bridge is found in both frames, the method returns :: + [1, 1, ] - .. rubric:: Example +Similar to :meth:`~WaterBridgeAnalysis.count_by_type` +The behaviour of :meth:`~WaterBridgeAnalysis.count_by_time` can also be modified by supplying +an analysis function. - For example, to create a `pandas.DataFrame`_ from ``h.table``:: +Suppose we want to count + + - the **number** of water molecules involved in bridging selection 1 to selection 2. + - only if water bridge terminates in atom name **OD1 of ASP**. + - only when water bridge is joined by less than **two** water. + +The analysis function can be written as:: + + def analysis(current, output, **kwargs): + '''This function defines how the counting of water bridge should be specified. + + Parameters + ---------- + current : list + The current water bridge being analysed is a list of hydrogen bonds from + selection 1 to selection 2. + output : dict + A dictionary where the key is the type of the water bridge and the value + is the number of this type of water bridge. + The output of this frame is the sum of all the values in this dictionary. + ''' + + # decompose the first hydrogen bond. + s1_index, to_index, (s1_resname, s1_resid, s1_name), + (to_resname, to_resid, to_name), dist, angle = current[0] + # decompose the last hydrogen bond. + from_index, s2_index, (from_resname, from_resid, from_name), + (s2_resname, s2_resid, s2_name), dist, angle = current[-1] + + # only the residue name is ASP and the atom name is OD1, + if s2_resname == 'ASP' and s2_name == 'OD1': + # only if the order of water bridge is less than 2 + if len(current) -1 < 2: + # extract all water molecules involved in the water bridge + # extract the first water from selection 1 + s1_index, to_index, (s1_resname, s1_resid, s1_name), + (to_resname, to_resid, to_name), dist, angle = current[0] + key = (to_resname, to_resid) + output[key] = 1 + + # extract all the waters between selection 1 and selection 2 + for hbond in current[1:-1]: + # decompose the hydrogen bond. + from_index, to_index, (from_resname, from_resid, from_name), + (to_resname, to_resid, to_name), dist, angle = hbond + # add first water + key1 = (from_resname, from_resid) + output[key1] = 1 + # add second water + key2 = (to_resname, to_resid) + output[key2] = 1 + + # extract the last water to selection 2 + from_index, s2_index, (from_resname, from_resid, from_name), + (s2_resname, s2_resid, s2_name), dist, angle = current[-1] + key = (from_resname, from_resid) + output[key] = 1 + + w.count_by_time(analysis_func=analysis) + +Returns :: + + [1, 0,] + +Classes +------- + +.. autoclass:: WaterBridgeAnalysis + :members: + + .. attribute:: timesteps + + List of the times of each timestep. This can be used together with + :attr:`~WaterBridgeAnalysis.timeseries` to find the specific time point + of a water bridge existence. - import pandas as pd - df = pd.DataFrame.from_records(w.table) """ -from __future__ import absolute_import, division -import six from collections import defaultdict import logging @@ -290,15 +468,14 @@ class WaterBridgeAnalysis_OtherFF(WaterBridgeAnalysis): import numpy as np -from .hbond_analysis import HydrogenBondAnalysis +from ..base import AnalysisBase from MDAnalysis.lib.NeighborSearch import AtomNeighborSearch -from MDAnalysis.lib.log import ProgressMeter +from MDAnalysis import NoDataError from MDAnalysis.lib import distances -from MDAnalysis import SelectionWarning logger = logging.getLogger('MDAnalysis.analysis.wbridges') -class WaterBridgeAnalysis(HydrogenBondAnalysis): +class WaterBridgeAnalysis(AnalysisBase): """Perform a water bridge analysis The analysis of the trajectory is performed with the @@ -313,13 +490,42 @@ class WaterBridgeAnalysis(HydrogenBondAnalysis): .. versionadded:: 0.17.0 """ + # use tuple(set()) here so that one can just copy&paste names from the + # table; set() takes care for removing duplicates. At the end the + # DEFAULT_DONORS and DEFAULT_ACCEPTORS should simply be tuples. + + #: default heavy atom names whose hydrogens are treated as *donors* + #: (see :ref:`Default atom names for hydrogen bonding analysis`); + #: use the keyword `donors` to add a list of additional donor names. + DEFAULT_DONORS = { + 'CHARMM27': tuple(set([ + 'N', 'OH2', 'OW', 'NE', 'NH1', 'NH2', 'ND2', 'SG', 'NE2', 'ND1', 'NZ', 'OG', 'OG1', 'NE1', 'OH'])), + 'GLYCAM06': tuple(set(['N', 'NT', 'N3', 'OH', 'OW'])), + 'other': tuple(set([]))} + + #: default atom names that are treated as hydrogen *acceptors* + #: (see :ref:`Default atom names for hydrogen bonding analysis`); + #: use the keyword `acceptors` to add a list of additional acceptor names. + DEFAULT_ACCEPTORS = { + 'CHARMM27': tuple(set([ + 'O', 'OC1', 'OC2', 'OH2', 'OW', 'OD1', 'OD2', 'SG', 'OE1', 'OE1', 'OE2', 'ND1', 'NE2', 'SD', 'OG', 'OG1', 'OH'])), + 'GLYCAM06': tuple(set(['N', 'NT', 'O', 'O2', 'OH', 'OS', 'OW', 'OY', 'SM'])), + 'other': tuple(set([]))} + + #: A :class:`collections.defaultdict` of covalent radii of common donors + #: (used in :meth`_get_bonded_hydrogens_list` to check if a hydrogen is + #: sufficiently close to its donor heavy atom). Values are stored for + #: N, O, P, and S. Any other heavy atoms are assumed to have hydrogens + #: covalently bound at a maximum distance of 1.5 Å. + r_cov = defaultdict(lambda: 1.5, # default value + N=1.31, O=1.31, P=1.58, S=1.55) + def __init__(self, universe, selection1='protein', - selection2='not resname SOL', water_selection='resname SOL', - selection1_type='both', update_selection1=False, - update_selection2=False, update_water_selection=True, + selection2='not resname SOL', water_selection='resname SOL', order=1, + selection1_type='both', update_selection=False, update_water_selection=True, filter_first=True, distance_type='hydrogen', distance=3.0, angle=120.0, forcefield='CHARMM27', donors=None, - acceptors=None, debug=None, verbose=False, **kwargs): + acceptors=None, debug=None, verbose=False, pbc=False, **kwargs): """Set up the calculation of water bridges between two selections in a universe. @@ -351,6 +557,15 @@ def __init__(self, universe, selection1='protein', However, in theory this selection can be anything which forms hydrogen bond with selection 1 and selection 2. + order : int (optional) + The maximum number of water bridges linking both selections. + if order is set to 3, then all the residues linked with less than + three water molecules wil be detected. [1] + + Computation of high order water bridges can be very time consuming. + Think carefully before running the calculation, do you really want + to compute the 20th order water bridge between domain A and domain B + or you just want to know the third order water bridge between two residues. selection1_type : {"donor", "acceptor", "both"} (optional) Selection 1 can be 'donor', 'acceptor' or 'both'. Note that the value for `selection1_type` automatically determines how @@ -358,15 +573,12 @@ def __init__(self, universe, selection1='protein', 'both' then `selection2` will also contain 'both'. If `selection1` is set to 'donor' then `selection2` is 'acceptor' (and vice versa). ['both']. - update_selection1 : bool (optional) - Update selection 1 at each frame. Setting to ``True`` if the - selection is not static. Selection 1 is filtered first to speed up + update_selection : bool (optional) + Update selection 1 and 2 at each frame. Setting to ``True`` if the + selection is not static. Selections are filtered first to speed up performance. Thus, setting to ``True`` is recommended if contact surface between selection 1 and selection 2 is constantly changing. [``False``] - update_selection2 : bool (optional) - Similiar to *update_selection1* but is acted upon selection 2. - [``False``] update_water_selection : bool (optional) Update selection of water at each frame. Setting to ``False`` is **only** recommended when the total amount of water molecules in the @@ -379,10 +591,10 @@ def __init__(self, universe, selection1='protein', ``True`` so as to filter out water not residing between the two selections. [``True``] filter_first : bool (optional) - Filter the water selection to only include water within 3 * - `distance` away from `both` selection 1 and selection 2. + Filter the water selection to only include water within 4Å + `order` * + (2Å + `distance`) away from `both` selection 1 and selection 2. Selection 1 and selection 2 are both filtered to only include atoms - 3 * `distance` away from the other selection. [``True``] + with the same distance away from the other selection. [``True``] distance : float (optional) Distance cutoff for hydrogen bonds; only interactions with a H-A distance <= `distance` (and the appropriate D-H-A angle, see @@ -427,28 +639,52 @@ def __init__(self, universe, selection1='protein', If selection 1 and selection 2 are very mobile during the simulation and the contact surface is constantly changing (i.e. residues are - moving farther than 3 x `distance`), you might consider setting the - `update_selection1` and `update_selection2` keywords to ``True`` to - ensure correctness. + moving farther than 4Å + `order` * (2Å + `distance`)), you might + consider setting the `update_selection` keywords to ``True`` + to ensure correctness. """ + super(WaterBridgeAnalysis, self).__init__(universe.trajectory, + **kwargs) self.water_selection = water_selection self.update_water_selection = update_water_selection - super(WaterBridgeAnalysis, self).__init__( - universe=universe, selection1=selection1, selection2=selection2, - selection1_type=selection1_type, - update_selection1=update_selection1, - update_selection2=update_selection2, filter_first=filter_first, - distance_type=distance_type, distance=distance, angle=angle, - forcefield=forcefield, donors=donors, acceptors=acceptors, - debug=debug, verbose=verbose, **kwargs) - self._update_water_selection() + # per-frame debugging output? + self.debug = debug + + self.u = universe + self.selection1 = selection1 + self.selection2 = selection2 + self.selection1_type = selection1_type + self.update_selection = update_selection + self.filter_first = filter_first + self.distance = distance + self.distance_type = distance_type # note: everything except 'heavy' will give the default behavior + self.angle = angle + self.pbc = pbc and all(self.u.dimensions[:3]) + self.order = order + + # set up the donors/acceptors lists + if donors is None: + donors = [] + if acceptors is None: + acceptors = [] + self.forcefield = forcefield + self.donors = tuple(set(self.DEFAULT_DONORS[forcefield]).union(donors)) + self.acceptors = tuple(set(self.DEFAULT_ACCEPTORS[forcefield]).union(acceptors)) + + if not (self.selection1 and self.selection2): + raise ValueError('HydrogenBondAnalysis: invalid selections') + elif self.selection1_type not in ('both', 'donor', 'acceptor'): + raise ValueError('HydrogenBondAnalysis: Invalid selection type {0!s}'.format(self.selection1_type)) + + self._network = [] # final result accessed as self.network + self.timesteps = None # time for each frame + + self._log_parameters() def _log_parameters(self): """Log important parameters to the logfile.""" - logger.info("WBridge analysis: selection1 = %r (update: %r)", - self.selection1, self.update_selection1) - logger.info("WBridge analysis: selection2 = %r (update: %r)", - self.selection2, self.update_selection2) + logger.info("WBridge analysis: selection = %r (update: %r)", + self.selection2, self.update_selection) logger.info("WBridge analysis: water selection = %r (update: %r)", self.water_selection, self.update_water_selection) logger.info("WBridge analysis: criterion: donor %s atom and acceptor \ @@ -458,27 +694,46 @@ def _log_parameters(self): self.angle) logger.info("WBridge analysis: force field %s to guess donor and \ acceptor names", self.forcefield) - logger.info("WBridge analysis: bonded hydrogen detection algorithm: \ - %r", self.detect_hydrogens) - def _update_selection_1(self): + def _update_selection(self): + box = self.u.dimensions if self.pbc else None + self._s1 = self.u.select_atoms(self.selection1) self._s2 = self.u.select_atoms(self.selection2) + if self.filter_first and self._s1: self.logger_debug('Size of selection 1 before filtering:' ' {} atoms'.format(len(self._s1))) ns_selection_1 = AtomNeighborSearch(self._s1) - self._s1 = ns_selection_1.search(self._s2, 3. * self.distance) + self._s1 = ns_selection_1.search(self._s2, self.selection_distance) self.logger_debug("Size of selection 1: {0} atoms".format(len(self._s1))) - self._s1_donors = {} - self._s1_donors_h = {} - self._s1_acceptors = {} + + if not self._s1: + logger.warning('Selection 1 "{0}" did not select any atoms.' + .format(str(self.selection1)[:80])) + return + + + + if self.filter_first and self._s2: + self.logger_debug('Size of selection 2 before filtering:' + ' {} atoms'.format(len(self._s2))) + ns_selection_2 = AtomNeighborSearch(self._s2, box) + self._s2 = ns_selection_2.search(self._s1, self.selection_distance) + self.logger_debug('Size of selection 2: {0} atoms'.format(len(self._s2))) + + if not self._s2: + logger.warning('Selection 2 "{0}" did not select any atoms.' + .format(str(self.selection2)[:80])) + return + + if self.selection1_type in ('donor', 'both'): self._s1_donors = self._s1.select_atoms( 'name {0}'.format(' '.join(self.donors))) self._s1_donors_h = {} - for i, d in enumerate(self._s1_donors): - tmp = self._get_bonded_hydrogens(d) + for i, atom in enumerate(self._s1_donors): + tmp = self._get_bonded_hydrogens(atom) if tmp: self._s1_donors_h[i] = tmp self.logger_debug("Selection 1 donors: {0}".format(len(self._s1_donors))) @@ -488,37 +743,36 @@ def _update_selection_1(self): 'name {0}'.format(' '.join(self.acceptors))) self.logger_debug("Selection 1 acceptors: {0}".format(len(self._s1_acceptors))) - def _sanity_check(self, selection, htype): - """sanity check the selections 1 and 2 - - *selection* is 1 or 2, *htype* is "donors" or "acceptors" - """ - assert selection in (1, 2) - assert htype in ("donors", "acceptors") - atoms = getattr(self, "_s{0}_{1}".format(selection, htype)) - update = getattr(self, "update_selection{0}".format(selection)) - if not atoms: - errmsg = "No {1} found in selection {0}. " \ - "You might have to specify a custom '{1}' keyword.".format( - selection, htype) - warnings.warn(errmsg, category=SelectionWarning) - logger.warning(errmsg) + if not self._s2: + return None + if self.selection1_type in ('donor', 'both'): + self._s2_acceptors = self._s2.select_atoms( + 'name {0}'.format(' '.join(self.acceptors))) + self.logger_debug("Selection 2 acceptors: {0:d}".format(len(self._s2_acceptors))) + if self.selection1_type in ('acceptor', 'both'): + self._s2_donors = self._s2.select_atoms( + 'name {0}'.format(' '.join(self.donors))) + self._s2_donors_h = {} + for i, d in enumerate(self._s2_donors): + tmp = self._get_bonded_hydrogens(d) + if tmp: + self._s2_donors_h[i] = tmp + self.logger_debug("Selection 2 donors: {0:d}".format(len(self._s2_donors))) + self.logger_debug("Selection 2 donor hydrogens: {0:d}".format(len(self._s2_donors_h))) def _update_water_selection(self): self._water = self.u.select_atoms(self.water_selection) self.logger_debug('Size of water selection before filtering:' ' {} atoms'.format(len(self._water))) - if self.filter_first: - ns_water_selection = AtomNeighborSearch(self._water) - self._water = ns_water_selection.search(self._s1, - 3. * self.distance) - self._water = ns_water_selection.search(self._s2, - 3. * self.distance) + if self._water and self.filter_first: + filtered_s1 = AtomNeighborSearch(self._water).search(self._s1, + self.selection_distance) + if filtered_s1: + self._water = AtomNeighborSearch(filtered_s1).search(self._s2, + self.selection_distance) self.logger_debug("Size of water selection: {0} atoms".format(len(self._water))) - self._water_donors = {} - self._water_donors_h = {} - self._water_acceptors = {} + if not self._water: logger.warning("Water selection '{0}' did not select any atoms." .format(str(self.water_selection)[:80])) @@ -536,150 +790,160 @@ def _update_water_selection(self): 'name {0}'.format(' '.join(self.acceptors))) self.logger_debug("Water acceptors: {0}".format(len(self._water_acceptors))) - def run(self, start=None, stop=None, step=None, verbose=None, debug=None): - """Analyze trajectory and produce timeseries. + def _get_bonded_hydrogens(self, atom): + """Find hydrogens bonded within cutoff to `atom`. + + Hydrogens are detected by either name ("H*", "[123]H*") or type ("H"); + this is not fool-proof as the atom type is not always a character but + the name pattern should catch most typical occurrences. + + The distance from `atom` is calculated for all hydrogens in the residue + and only those within a cutoff are kept. The cutoff depends on the + heavy atom (more precisely, on its element, which is taken as the first + letter of its name ``atom.name[0]``) and is parameterized in + :attr:`HydrogenBondAnalysis.r_cov`. If no match is found then the + default of 1.5 Å is used. - Stores the water bridge data per frame as - :attr:`WaterBridgeAnalysis.timeseries` (see there for output - format). Parameters ---------- - start : int (optional) - starting frame-index for analysis, ``None`` is the first one, 0. - `start` and `stop` are 0-based frame indices and are used to slice - the trajectory (if supported) [``None``] - stop : int (optional) - last trajectory frame for analysis, ``None`` is the last one - [``None``] - step : int (optional) - read every `step` between `start` (included) and `stop` (excluded), - ``None`` selects 1. [``None``] - verbose : bool (optional) - toggle progress meter output - :class:`~MDAnalysis.lib.log.ProgressMeter` [``True``] - debug : bool (optional) - enable detailed logging of debugging information; this can create - *very big* log files so it is disabled (``False``) by default; - setting `debug` toggles the debug status for - :class:`WaterBridgeAnalysis`, namely the value of - :attr:`WaterBridgeAnalysis.debug`. - - See Also - -------- - :meth:`WaterBridgeAnalysis.generate_table` : - processing the data into a different format. + atom : groups.Atom + heavy atom + + Returns + ------- + hydrogen_atoms : AtomGroup or [] + list of hydrogens (can be a :class:`~MDAnalysis.core.groups.AtomGroup`) + or empty list ``[]`` if none were found. """ - self._setup_frames(self.u.trajectory, start, stop, step) + try: + return atom.residue.atoms.select_atoms( + "(name H* 1H* 2H* 3H* or type H) and around {0:f} name {1!s}" + "".format(self.r_cov[atom.name[0]], atom.name)) + except NoDataError: + return [] + + def logger_debug(self, *args): + if self.debug: + logger.debug(*args) + + + def _prepare(self): + # The distance for selection is defined as twice the maximum bond length of an O-H bond (2A) + # plus order of water bridge times the length of OH bond plus hydrogne bond distance + self.selection_distance = 1.0 * (2 * 2 + self.order * (2 + self.distance)) + + self._s1_donors = {} + self._s1_donors_h = {} + self._s1_acceptors = {} + + self._s2_donors = {} + self._s2_donors_h = {} + self._s2_acceptors = {} + + self._update_selection() + + self._water_donors = {} + self._water_donors_h = {} + self._water_acceptors = {} + + self.timesteps = [] + + if self._s1 and self._s2: + self._update_water_selection() + logger.info("WBridge analysis: no atoms found in the selection.") + + logger.info("WBridge analysis: initial checks passed.") logger.info("WBridge analysis: starting") logger.debug("WBridge analysis: donors %r", self.donors) logger.debug("WBridge analysis: acceptors %r", self.acceptors) logger.debug("WBridge analysis: water bridge %r", self.water_selection) - if debug is not None and debug != self.debug: - self.debug = debug + if self.debug: logger.debug("Toggling debug to %r", self.debug) - if not self.debug: + else: logger.debug("WBridge analysis: For full step-by-step debugging output use debug=True") - self._timeseries = [] - self.timesteps = [] - self._water_network = [] - - if verbose is None: - verbose = self._verbose - pm = ProgressMeter(self.n_frames, - format="WBridge frame {current_step:5d}: {step:5d}/{numsteps} [{percentage:5.1f}%]\r", - verbose=verbose) - logger.info("Starting analysis (frame index start=%d stop=%d, step=%d)", self.start, self.stop, self.step) - for progress, ts in enumerate(self.u.trajectory[self.start:self.stop:self.step]): - # all bonds for this timestep - - # dict of tuples (atom.index, atom.index) for quick check if - # we already have the bond (to avoid duplicates) - - frame = ts.frame - timestep = ts.time - self.timesteps.append(timestep) - pm.echo(progress, current_step=frame) - self.logger_debug("Analyzing frame %(frame)d, timestep %(timestep)f ps", vars()) - if self.update_selection1: - self._update_selection_1() - if self.update_selection2: - self._update_selection_2() + def _donor2acceptor(self, donors, donor_hs, acceptor): + result = [] + ns_acceptors = AtomNeighborSearch(acceptor) + for i, donor_h_set in donor_hs.items(): + d = donors[i] + for h in donor_h_set: + res = ns_acceptors.search(h, self.distance) + for a in res: + donor_atom = h if self.distance_type != 'heavy' else d + dist = distances.calc_bonds(donor_atom.position, + a.position) + if dist <= self.distance: + angle = distances.calc_angles(d.position, h.position, + a.position) + angle = np.rad2deg(angle) + if angle >= self.angle: + self.logger_debug( + "D: {0!s} <-> A: {1!s} {2:f} A, {3:f} DEG" \ + .format(h.index, a.index, dist, angle)) + result.append((h.index, a.index, + (h.resname, h.resid, h.name), + (a.resname, a.resid, a.name), + dist, angle)) + return result + + + + def _single_frame(self): + self.timesteps.append(self._ts.time) + if self.update_selection: + self._update_selection() + if self._s1 and self._s2: if self.update_water_selection: self._update_water_selection() + else: + self._network.append(defaultdict(dict)) + + selection_1 = [] + water_pool = defaultdict(list) + next_round_water = set([]) + selection_2 = [] - s1_frame_results_dict = defaultdict(list) - if (self.selection1_type in ('donor', 'both') and + if (self.selection1_type in ('donor', 'both') and self._water_acceptors): - self.logger_debug("Selection 1 Donors <-> Water Acceptors") - ns_acceptors = AtomNeighborSearch(self._water_acceptors) - for i, donor_h_set in self._s1_donors_h.items(): - d = self._s1_donors[i] - for h in donor_h_set: - res = ns_acceptors.search(h, self.distance) - for a in res: - donor_atom = h if self.distance_type != 'heavy' else d - dist = distances.calc_bonds(donor_atom.position, - a.position) - if dist <= self.distance: - angle = distances.calc_angles(d.position, h.position, - a.position) - angle = np.rad2deg(angle) - if angle >= self.angle: - self.logger_debug( - "S1-D: {0!s} <-> W-A: {1!s} {2:f} A, {3:f} DEG"\ - .format(h.index, a.index, dist, angle)) - s1_frame_results_dict[(a.resname, a.resid)].append( - (h.index, a.index, - (h.resname, h.resid, h.name), - (a.resname, a.resid, a.name), - dist, angle)) - - if (self.selection1_type in ('acceptor', 'both') and - self._s1_acceptors): + self.logger_debug("Selection 1 Donors <-> Water Acceptors") + results = self._donor2acceptor(self._s1_donors, self._s1_donors_h, self._water_acceptors) + for line in results: + h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line - self.logger_debug("Selection 1 Acceptors <-> Water Donors") - ns_acceptors = AtomNeighborSearch(self._s1_acceptors) - for i, donor_h_set in self._water_donors_h.items(): - d = self._water_donors[i] - for h in donor_h_set: - res = ns_acceptors.search(h, self.distance) - for a in res: - donor_atom = h if self.distance_type != 'heavy' else d - dist = distances.calc_bonds(donor_atom.position, - a.position) - if dist <= self.distance: - angle = distances.calc_angles(d.position, h.position, - a.position) - angle = np.rad2deg(angle) - if angle >= self.angle: - self.logger_debug( - "S1-A: {0!s} <-> W-D: {1!s} {2:f} A, {3:f} DEG"\ - .format(a.index, h.index, dist, angle)) - s1_frame_results_dict[(h.resname, h.resid)].append( - (h.index, a.index, - (h.resname, h.resid, h.name), - (a.resname, a.resid, a.name), - dist, angle)) + next_round_water.add((a_resname, a_resid)) + selection_1.append(line) + if (self.selection1_type in ('acceptor', 'both') and + self._s1_acceptors): + self.logger_debug("Selection 1 Acceptors <-> Water Donors") + results = self._donor2acceptor(self._water_donors, self._water_donors_h, self._s1_acceptors) + for line in results: + h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + line = (a_index, h_index, (a_resname, a_resid, a_name), (h_resname, h_resid, h_name), dist, angle) + next_round_water.add((h_resname, h_resid)) + selection_1.append(line) + + for i in range(self.order): # Narrow down the water selection - selection_resn_id = list(s1_frame_results_dict.keys()) + selection_resn_id = list(next_round_water) if not selection_resn_id: - self._timeseries.append([]) - continue + self._network.append(defaultdict(dict)) + logger.warning("No water forming hydrogen bonding with selection 1.") + return selection_resn_id = ['(resname {} and resid {})'.format( resname, resid) for resname, resid in selection_resn_id] water_bridges = self._water.select_atoms(' or '.join(selection_resn_id)) - self.logger_debug("Size of water bridge selection: {0} atoms".format(len(water_bridges))) - if not water_bridges: - logger.warning("No water forming hydrogen bonding with selection 1.") + self.logger_debug("Size of water bridge selection for the {} order of water bridge: {} atoms".format(i+1, + len(water_bridges))) + water_bridges_donors = water_bridges.select_atoms( 'name {0}'.format(' '.join(self.donors))) water_bridges_donors_h = {} @@ -691,120 +955,108 @@ def run(self, start=None, stop=None, step=None, verbose=None, debug=None): self.logger_debug("water bridge donor hydrogens: {0}".format(len(water_bridges_donors_h))) water_bridges_acceptors = water_bridges.select_atoms( 'name {0}'.format(' '.join(self.acceptors))) - self.logger_debug("water bridge: {0}".format(len(water_bridges_acceptors))) - - # Finding the hydrogen bonds between water bridge and selection 2 - s2_frame_results_dict = defaultdict(list) - if self._s2_acceptors: - self.logger_debug("Water bridge Donors <-> Selection 2 Acceptors") - ns_acceptors = AtomNeighborSearch(self._s2_acceptors) - for i, donor_h_set in water_bridges_donors_h.items(): - d = water_bridges_donors[i] - for h in donor_h_set: - res = ns_acceptors.search(h, self.distance) - for a in res: - donor_atom = h if self.distance_type != 'heavy' else d - dist = distances.calc_bonds(donor_atom.position, - a.position) - if dist <= self.distance: - angle = distances.calc_angles(d.position, h.position, - a.position) - angle = np.rad2deg(angle) - if angle >= self.angle: - self.logger_debug( - "WB-D: {0!s} <-> S2-A: {1!s} {2:f} A, {3:f} DEG"\ - .format(h.index, a.index, dist, angle)) - s2_frame_results_dict[(h.resname, h.resid)].append( - (h.index, a.index, - (h.resname, h.resid, h.name), - (a.resname, a.resid, a.name), - dist, angle)) - - if water_bridges_acceptors: - self.logger_debug("Selection 2 Donors <-> Selection 2 Acceptors") - ns_acceptors = AtomNeighborSearch(water_bridges_acceptors) - for i, donor_h_set in self._s2_donors_h.items(): - d = self._s2_donors[i] - for h in donor_h_set: - res = ns_acceptors.search(h, self.distance) - for a in res: - donor_atom = h if self.distance_type != 'heavy' else d - dist = distances.calc_bonds(donor_atom.position, - a.position) - if dist <= self.distance: - angle = distances.calc_angles(d.position, h.position, - a.position) - angle = np.rad2deg(angle) - if angle >= self.angle: - self.logger_debug( - "WB-A: {0!s} <-> S2-D: {1!s} {2:f} A, {3:f} DEG"\ - .format(a.index, h.index, dist, angle)) - s2_frame_results_dict[(a.resname, a.resid)].append( - (h.index, a.index, - (h.resname, h.resid, h.name), - (a.resname, a.resid, a.name), - dist, angle)) - - # Generate the water network - water_network = {} - for key in s2_frame_results_dict: - s1_frame_results = set(s1_frame_results_dict[key]) - s2_frame_results = set(s2_frame_results_dict[key]) - if len(s1_frame_results.union(s2_frame_results)) > 1: - # Thus if selection 1 and selection 2 are the same and both - # only form a single hydrogen bond with a water, this entry - # won't be included. - water_network[key] = [s1_frame_results, - s2_frame_results.difference(s1_frame_results)] - # Generate frame_results - frame_results = [] - for s1_frame_results, s2_frame_results in water_network.values(): - frame_results.extend(list(s1_frame_results)) - frame_results.extend(list(s2_frame_results)) - - self._timeseries.append(frame_results) - self._water_network.append(water_network) - - - logger.info("WBridge analysis: complete; timeseries %s.timeseries", - self.__class__.__name__) - @staticmethod - def _reformat_hb(hb, atomformat="{0[0]!s}{0[1]!s}:{0[2]!s}"): - """ - .. deprecated:: 1.0 - This is a compatibility layer so the water bridge - _timeseries to timeserie conversion can perform as it does in the - hydrogen bond analysis. + self.logger_debug("water bridge acceptors: {0}".format(len(water_bridges_acceptors))) + + if i < self.order: + next_round_water = set([]) + # first check if the water can touch the other end + # Finding the hydrogen bonds between water bridge and selection 2 + if self._s2_acceptors: + self.logger_debug("Order {} water donor <-> Selection 2 Acceptors".format(i + 1)) + results = self._donor2acceptor(water_bridges_donors, water_bridges_donors_h, self._s2_acceptors) + for line in results: + h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + water_pool[(h_resname, h_resid)].append(line) + selection_2.append((a_resname, a_resid, a_name)) + + # Finding the hydrogen bonds between water bridge and selection 2 + if water_bridges_acceptors: + self.logger_debug("Selection 2 Donors <-> Order {} water".format(i + 1)) + results = self._donor2acceptor(self._s2_donors, self._s2_donors_h, water_bridges_acceptors) + for line in results: + h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + line = (a_index, h_index, (a_resname, a_resid, a_name), (h_resname, h_resid, h_name), dist, angle) + water_pool[(a_resname, a_resid)].append(line) + selection_2.append((h_resname, h_resid, h_name)) + + # find the water water hydrogen bond + if water_bridges_acceptors: + self.logger_debug("Order {} water acceptor <-> Order {} water donor".format(i + 1, i + 2)) + results = self._donor2acceptor(self._water_donors, self._water_donors_h, water_bridges_acceptors) + for line in results: + h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + line = (a_index, h_index, (a_resname, a_resid, a_name), (h_resname, h_resid, h_name), dist, angle) + water_pool[(a_resname, a_resid)].append(line) + next_round_water.add((h_resname, h_resid)) + + if water_bridges_donors_h: + self.logger_debug("Order {} water donor <-> Order {} water acceptor".format(i + 1, i + 2)) + results = self._donor2acceptor(water_bridges_donors, water_bridges_donors_h, self._water_acceptors) + for line in results: + h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + water_pool[(h_resname, h_resid)].append(line) + next_round_water.add((a_resname, a_resid)) + + # solve the connectivity network + result = {'start': defaultdict(dict), 'water': defaultdict(dict)} + + def add_route(result, route): + # exclude the the selection which goes back to itself + if sorted(route[0][:2]) == sorted(route[-1][:2]): + return + + # selection 2 to water + result['water'][route[-1]] = None + # water to water + for i in range(1, len(route) - 1): + result['water'][route[i]][route[i+1]] = result['water'][route[i+1]] + # selection 1 to water + result['start'][route[0]][route[1]] = result['water'][route[1]] + + def traverse_water_network(graph, node, end, route, maxdepth, result): + if len(route) > self.order + 1: + return + else: + for new_node in graph[node]: + new_route = route[:] + new_route.append(new_node) + if new_node[3] in end: + # check if any duplication happens + if len(new_route) == len(set(new_route)): + add_route(result, new_route) + else: + new_node = new_node[3][:2] + traverse_water_network(graph, new_node, end, new_route, maxdepth, result) - For more information about the function of _reformat_hb in hydrogen - bond analysis, please refer to the _reformat_hb in the hydrogen - bond analysis module. + for s1 in selection_1: + route = [s1, ] + next_mol = s1[3][:2] + traverse_water_network(water_pool, next_mol, selection_2, route[:], self.order, result) - As the _timeseries to timeserie conversion will be deprecated in 1.0 - this function will automatically lose its value. - """ + self._network.append(result['start']) + + def _traverse_water_network(self, graph, current, analysis_func=None, output=None, link_func=None, **kwargs): + if link_func is None: + link_func = self._full_link - return (list(hb[:2]) - + [atomformat.format(hb[2]), atomformat.format(hb[3])] - + list(hb[4:])) + if graph is None: + if not analysis_func is None: + analysis_func(current, output, **kwargs) + else: + # make sure no loop can occur + if len(current) <= self.order: + for node in graph: + new = link_func(current, node, **kwargs) + self._traverse_water_network(graph[node], new, analysis_func, output, link_func) @property def timeseries(self): r'''Time series of water bridges. - Due to the intrinsic complexity of water bridge problem, where several - atoms :math:`n` can be linked by the same water. A simple ``atom 1 - - water bridge - atom 2`` mapping will create a very large list - :math:`n!/(2(n-2)!)` due to the rule of combination. - - Thus, the output is arranged based on each individual bridging water - allowing the later reconstruction of the water network. The hydrogen - bonds from selection 1 to the **first** bridging water is followed by - the hydrogen bonds from selection 2 to the **same** bridging water. - After that, hydrogen bonds from selection 1 to the **second** bridging - water is succeeded by hydrogen bonds from selection 2 to the **same - second** bridging water. An example of the output is given in - :ref:`wb_Analysis_Output`. + The output is arranged based on each individual link from selection 1 + to selection 2 allowing the later reconstruction of the water network. + Each entity is a list of hydrogen bonds from selection 1 to selection 2. + An example of the output is given in :ref:`wb_Analysis_Output`. Note ---- @@ -812,7 +1064,7 @@ def timeseries(self): *index* one would use ``u.atoms[acceptor_index]``. The :attr:`timeseries` is a managed attribute and it is generated - from the underlying data in :attr:`_timeseries` every time the + from the underlying data in :attr:`_network` every time the attribute is accessed. It is therefore costly to call and if :attr:`timeseries` is needed repeatedly it is recommended that you assign to a variable:: @@ -821,94 +1073,105 @@ def timeseries(self): w.run() timeseries = w.timeseries - See Also - -------- - :attr:`table` : structured array of the data ''' - return super(WaterBridgeAnalysis, self).timeseries - def generate_table(self): - """Generate a normalised table of the results. + def analysis(current, output): + output.append(current) - The table is stored as a :class:`numpy.recarray` in the - attribute :attr:`~WaterBridgeAnalysis.table`. + timeseries = [] + for frame in self._network: + new_frame = [] + self._traverse_water_network(frame, [], analysis_func=analysis, output=new_frame, link_func=self._full_link) + timeseries.append(new_frame) + return timeseries - See Also - -------- - WaterBridgeAnalysis.table - """ - super(WaterBridgeAnalysis, self).generate_table() + @classmethod + def _full_link(self, output, node): + result = output[:] + result.append(node) + return result - def count_by_type(self): + @classmethod + def _count_by_type_analysis(self, current, output): + ''' + Generates the key for count_by_type analysis. + :return: + ''' + s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ + current[0] + from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ + current[-1] + key = (s1_index, s2_index, s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) + output[key] += 1 + + def count_by_type(self, analysis_func=None, **kwargs): """Counts the frequency of water bridge of a specific type. If one atom *A* from *selection 1* is linked to atom *B* from - *selection 2* through a bridging water, an entity will be created and + *selection 2* through one or more bridging waters, an entity will be created and the proportion of time that this linkage exists in the whole simulation will be calculated. - Returns a :class:`numpy.recarray` containing atom indices for *A* and - *B*, residue names, residue numbers, atom names (for both A and B) and - the fraction of the total time during which the water bridge was - detected. This method returns None if method - :meth:`WaterBridgeAnalysis.run` was not executed first. + The identification of a specific type water bridge can be modified by + supplying the analysis_func function. See :ref:`wb_count_by_type` + for detail. Returns ------- - counts : numpy.recarray - Each row of the array contains data to define a unique water - bridge together with the frequency (fraction of the total time) - that it has been observed. + counts : list + Returns a :class:`list` containing atom indices for *A* and + *B*, residue names, residue numbers, atom names (for both A and B) and + the fraction of the total time during which the water bridge was + detected. This method returns None if method + :meth:`WaterBridgeAnalysis.run` was not executed first. + + """ - if not self._has_timeseries(): - return + if analysis_func is None: + analysis_func = self._count_by_type_analysis + + if self._network: + length = len(self._network) + result_dict = defaultdict(int) + for frame in self._network: + self._traverse_water_network(frame, [], analysis_func=analysis_func, output=result_dict, + link_func=self._full_link, **kwargs) + result = [(*key, result_dict[key]*1.0/length) for key in result_dict] + return result + else: + return None - wbridges = defaultdict(int) - for wframe in self._water_network: - pairs = set([]) - for water, (selection1, selection2) in wframe.items(): - for (donor_index, acceptor_index, donor, acceptor, distance, - angle) in selection1: - if donor[:2] == water: - sele1 = acceptor - sele1_index = acceptor_index - else: - sele1 = donor - sele1_index = donor_index - sele1_resnm, sele1_resid, sele1_atom = sele1 - - for (donor_index, acceptor_index, donor, acceptor, - distance, angle) in selection2: - if donor[:2] == water: - sele2 = acceptor - sele2_index = acceptor_index - else: - sele2 = donor - sele2_index = donor_index - sele2_resnm, sele2_resid, sele2_atom = sele2 - - key = (sele1_index, sele2_index) - if key in pairs: - continue - else: - pairs.add(key) - wb_key = (sele1_index, sele2_index, sele1_resnm, - sele1_resid, sele1_atom, sele2_resnm, sele2_resid, - sele2_atom) - wbridges[wb_key] += 1 - - # build empty output table - dtype = [ - ("sele1_index", int), ("sele2_index", int), ('sele1_resnm', 'U4'), - ('sele1_resid', int), ('sele1_atom', 'U4'), ('sele2_resnm', 'U4'), - ('sele2_resid', int), ('sele2_atom', 'U4'), - ('frequency', float) - ] - out = np.empty((len(wbridges),), dtype=dtype) - - tsteps = float(len(self.timesteps)) - for cursor, (key, count) in enumerate(six.iteritems(wbridges)): - out[cursor] = key + (count / tsteps,) - - r = out.view(np.recarray) - return r + @classmethod + def _count_by_time_analysis(self, current, output): + s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ + current[0] + from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ + current[-1] + key = (s1_index, s2_index, s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) + output[key] += 1 + + def count_by_time(self, analysis_func=None, **kwargs): + """Counts the number of water bridges per timestep. + + The counting behaviour can be adjusted by supplying analysis_func. + See :ref:`wb_count_by_time` for details. + + Returns + ------- + counts : list + Returns a time series ``N(t)`` where ``N`` is the total + number of observed water bridges at time ``t``. + + """ + if analysis_func is None: + analysis_func = self._count_by_time_analysis + if self._network: + result = [] + for frame in self._network: + result_dict = defaultdict(int) + self._traverse_water_network(frame, [], analysis_func=analysis_func, output=result_dict, + link_func=self._full_link, **kwargs) + result.append(sum([result_dict[key] for key in result_dict])) + return result + else: + return None \ No newline at end of file diff --git a/testsuite/CHANGELOG b/testsuite/CHANGELOG index 2005a6397b7..5ce98bf9042 100644 --- a/testsuite/CHANGELOG +++ b/testsuite/CHANGELOG @@ -18,6 +18,7 @@ mm/dd/18 orbeckst, arm61 - skip tests for duecredit when duecredit is not installed (#1906) - updated meta data for PyPi and updated README and INSTALL - added test for fix to issue #1897 + - add tests for the new water bridge analysis (PR #2087) 04/15/18 tylerjereddy, zemanj diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index eae136fc3d7..8809a41ed7d 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -1,7 +1,8 @@ from __future__ import print_function, absolute_import from six import StringIO +from collections import defaultdict -from numpy.testing import assert_equal +from numpy.testing import assert_equal, assert_almost_equal import MDAnalysis from MDAnalysis.analysis.hbonds.wbridge_analysis import WaterBridgeAnalysis @@ -29,9 +30,12 @@ def test_acceptor_water_accepter(self): u = MDAnalysis.Universe(StringIO(grofile), format='gro') wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') wb.run(verbose=False) - timeseries = wb._timeseries - assert_equal(timeseries[0][0][:4], (2, 0, ('SOL', 2, 'HW1'), ('ALA', 1, 'O'))) - assert_equal(timeseries[0][1][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) + network = wb._network[0] + + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) + assert_equal(second[list(second.keys())[0]], None) def test_donor_water_accepter(self): '''Test case where the hydrogen bond donor from selection 1 form @@ -47,9 +51,11 @@ def test_donor_water_accepter(self): u = MDAnalysis.Universe(StringIO(grofile), format='gro') wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') wb.run(verbose=False) - timeseries = wb._timeseries - assert_equal(timeseries[0][0][:4], (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'))) - assert_equal(timeseries[0][1][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) + assert_equal(second[list(second.keys())[0]], None) def test_acceptor_water_donor(self): '''Test case where the hydrogen bond acceptor from selection 1 form @@ -65,9 +71,11 @@ def test_acceptor_water_donor(self): u = MDAnalysis.Universe(StringIO(grofile), format='gro') wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') wb.run(verbose=False) - timeseries = wb._timeseries - assert_equal(timeseries[0][0][:4], (2, 0, ('SOL', 2, 'HW1'), ('ALA', 1, 'O'))) - assert_equal(timeseries[0][1][:4], (3, 1, ('ALA', 4, 'H'), ('SOL', 2, 'OW'))) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'))) + assert_equal(second[list(second.keys())[0]], None) def test_donor_water_donor(self): '''Test case where the hydrogen bond donor from selection 1 form @@ -83,9 +91,11 @@ def test_donor_water_donor(self): u = MDAnalysis.Universe(StringIO(grofile), format='gro') wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') wb.run(verbose=False) - timeseries = wb._timeseries - assert_equal(timeseries[0][0][:4], (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'))) - assert_equal(timeseries[0][1][:4], (3, 2, ('ALA', 4, 'H'), ('SOL', 2, 'OW'))) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'))) + assert_equal(second[list(second.keys())[0]], None) def test_empty(self): '''Test case where no water bridge exists''' @@ -100,7 +110,7 @@ def test_empty(self): u = MDAnalysis.Universe(StringIO(grofile), format='gro') wb = WaterBridgeAnalysis(u, 'protein', 'protein') wb.run(verbose=False) - assert_equal(wb._timeseries, [[]]) + assert_equal(wb._network[0], defaultdict(dict)) def test_same_selection(self): ''' @@ -118,11 +128,228 @@ def test_same_selection(self): u = MDAnalysis.Universe(StringIO(grofile), format='gro') wb = WaterBridgeAnalysis(u, 'protein', 'protein') wb.run(verbose=False) - assert_equal(wb._timeseries, [[]]) + assert_equal(wb._network[0], defaultdict(dict)) + + def test_acceptor_2water_accepter(self): + '''Test case where the hydrogen bond acceptor from selection 1 form second order + water bridge with hydrogen bond acceptor from selection 2''' + grofile = '''Test gro file +7 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 3SOL OW 5 0.600 0.000 0.000 + 3SOL HW1 6 0.700 0.000 0.000 + 4ALA O 7 0.900 0.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + # test first order + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') + wb.run(verbose=False) + assert_equal(wb._network[0], defaultdict(dict)) + # test second order + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=2) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + third = second[list(second.keys())[0]] + assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) + assert_equal(third[list(third.keys())[0]], None) + # test third order + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=3) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + third = second[list(second.keys())[0]] + assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) + assert_equal(third[list(third.keys())[0]], None) - def test_water_network(self): + def test_acceptor_3water_accepter(self): + '''Test case where the hydrogen bond acceptor from selection 1 form third order + water bridge with hydrogen bond acceptor from selection 2''' + grofile = '''Test gro file +9 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 3SOL OW 5 0.600 0.000 0.000 + 3SOL HW1 6 0.700 0.000 0.000 + 4SOL OW 7 0.900 0.000 0.000 + 4SOL HW1 8 1.000 0.000 0.000 + 5ALA O 9 1.200 0.000 0.000 + 10.0 10.0 10.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 5)', order=2) + wb.run(verbose=False) + assert_equal(wb._network[0], defaultdict(dict)) + + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 5)', order=3) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + third = second[list(second.keys())[0]] + assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) + fourth = third[list(third.keys())[0]] + assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'))) + assert_equal(fourth[list(fourth.keys())[0]], None) + + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 5)', order=4) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + third = second[list(second.keys())[0]] + assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) + fourth = third[list(third.keys())[0]] + assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'))) + assert_equal(fourth[list(fourth.keys())[0]], None) + + def test_acceptor_4water_accepter(self): + '''Test case where the hydrogen bond acceptor from selection 1 form fourth order + water bridge with hydrogen bond acceptor from selection 2''' + grofile = '''Test gro file +11 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 3SOL OW 5 0.600 0.000 0.000 + 3SOL HW1 6 0.700 0.000 0.000 + 4SOL OW 7 0.900 0.000 0.000 + 4SOL HW1 8 1.000 0.000 0.000 + 5SOL OW 9 1.200 0.000 0.000 + 5SOL HW1 10 1.300 0.000 0.000 + 6ALA O 11 1.400 0.000 0.000 + 10.0 10.0 10.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 6)', order=3) + wb.run(verbose=False) + assert_equal(wb._network[0], defaultdict(dict)) + + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 6)', order=4) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + third = second[list(second.keys())[0]] + assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) + fourth = third[list(third.keys())[0]] + assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'))) + fifth = fourth[list(fourth.keys())[0]] + assert_equal(list(fifth.keys())[0][:4], (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'))) + assert_equal(fifth[list(fifth.keys())[0]], None) + + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 6)', order=5) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + third = second[list(second.keys())[0]] + assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) + fourth = third[list(third.keys())[0]] + assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'))) + fifth = fourth[list(fourth.keys())[0]] + assert_equal(list(fifth.keys())[0][:4], (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'))) + assert_equal(fifth[list(fifth.keys())[0]], None) + + def test_acceptor_22water_accepter(self): + '''Test case where the hydrogen bond acceptor from selection 1 form a second order + water bridge with hydrogen bond acceptor from selection 2 + and the last water is linked to two residues in selection 2''' + grofile = '''Test gro file +9 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 3SOL OW 5 0.600 0.000 0.000 + 3SOL HW1 6 0.700 0.000 0.000 + 3SOL HW2 7 0.600 0.100 0.000 + 4ALA O 8 0.900 0.000 0.000 + 5ALA O 9 0.600 0.300 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + third = second[list(second.keys())[0]] + assert_equal([(5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O')), (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'))], + sorted([key[:4] for key in list(third.keys())])) + + def test_timeseries(self): + '''Test if the time series data is correctly generated''' + grofile = '''Test gro file +9 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 3SOL OW 5 0.600 0.000 0.000 + 3SOL HW1 6 0.700 0.000 0.000 + 3SOL HW2 7 0.600 0.100 0.000 + 4ALA O 8 0.900 0.000 0.000 + 5ALA O 9 0.600 0.300 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) + wb.run(verbose=False) + timeseries = wb.timeseries[0] + assert_equal(timeseries[0][0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(timeseries[0][1][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal(timeseries[1][0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(timeseries[1][1][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal([(5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O')), (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'))], + sorted([line[2][:4] for line in timeseries])) + + def test_acceptor_12water_accepter(self): + '''Test of independent first order and second can be recognised correctely''' + grofile = '''Test gro file +12 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 5ALA O 6 0.000 1.000 0.000 + 6SOL OW 7 0.300 1.000 0.000 + 6SOL HW1 8 0.200 1.000 0.000 + 6SOL HW2 9 0.400 1.000 0.000 + 7SOL OW 10 0.600 1.000 0.000 + 7SOL HW1 11 0.700 1.000 0.000 + 8ALA O 12 0.900 1.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=1) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) + assert_equal(second[list(second.keys())[0]], None) + network = wb._network[0] + wb = WaterBridgeAnalysis(u, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) + wb.run(verbose=False) + network = wb._network[0] + assert_equal([(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1')), (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'))], + sorted([key[:4] for key in list(network.keys())])) + + def test_count_by_type_single_link(self): ''' - This test tests if the internal water object is generated correctly. + This test tests the simplest water bridge to see if count_by_type() works. :return: ''' grofile = '''Test gro file @@ -136,15 +363,37 @@ def test_water_network(self): u = MDAnalysis.Universe(StringIO(grofile), format='gro') wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') wb.run(verbose=False) - water_network = wb._water_network[0] - assert_equal(list(water_network.keys()), [('SOL', 2)]) - values = list(water_network.values()) - assert_equal(list(values[0][0])[0][:4], (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'))) - assert_equal(list(values[0][1])[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) + assert_equal(wb.count_by_type(), [(1, 4, 'ALA', 1, 'H', 'ALA', 4, 'O', 1.)]) - def test_count_by_type_single_link(self): + def test_count_by_type_multiple_link(self): ''' - This test tests the simplest water bridge to see if count_by_type() works. + This test tests if count_by_type() can give the correct result for more than 1 links. + :return: + ''' + grofile = '''Test gro file +12 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 5ALA O 6 0.000 1.000 0.000 + 6SOL OW 7 0.300 1.000 0.000 + 6SOL HW1 8 0.200 1.000 0.000 + 6SOL HW2 9 0.400 1.000 0.000 + 7SOL OW 10 0.600 1.000 0.000 + 7SOL HW1 11 0.700 1.000 0.000 + 8ALA O 12 0.900 1.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) + wb.run(verbose=False) + assert_equal(sorted(wb.count_by_type()), [[0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 1.0], [5, 11, 'ALA', 5, 'O', 'ALA', 8, 'O', 1.0]]) + + + def test_count_by_type_multiple_frame(self): + ''' + This test tests if count_by_type() works in multiply situations. :return: ''' grofile = '''Test gro file @@ -156,13 +405,55 @@ def test_count_by_type_single_link(self): 4ALA O 5 0.600 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') - wb.run(verbose=False) - assert_equal(wb.count_by_type().tolist(), [(1, 4, 'ALA', 1, 'H', 'ALA', 4, 'O', 1.)]) + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) + # Build an dummy WaterBridgeAnalysis object for testing + wb._network = [] + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { + (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, + (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, + (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { + (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { + (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) + result = [(0, 3, 'ALA', 1, 'O', 'ALA', 4, 'H', 0.1), + (0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 0.3), + (0, 6, 'ALA', 1, 'O', 'ALA', 4, 'O', 0.1), + (0, 7, 'ALA', 1, 'O', 'ALA', 4, 'O', 0.1), + (0, 8, 'ALA', 1, 'O', 'ALA', 5, 'O', 0.2), + (0, 10, 'ALA', 1, 'O', 'ALA', 6, 'O', 0.1), + (1, 3, 'ALA', 1, 'H', 'ALA', 4, 'H', 0.1), + (1, 4, 'ALA', 1, 'H', 'ALA', 4, 'O', 0.1), + (5, 11, 'ALA', 5, 'O', 'ALA', 8, 'O', 0.1)] + assert_equal(sorted(wb.count_by_type()), result) - def test_count_by_type_multiple_link(self): + def test_count_by_type_filter(self): ''' - This test tests if count_by_type() can assemble linkage from water network. + This test tests if modifying analysis_func + allows some results to be filtered out in count_by_type(). :return: ''' grofile = '''Test gro file @@ -174,23 +465,57 @@ def test_count_by_type_multiple_link(self): 4ALA O 5 0.600 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) # Build an dummy WaterBridgeAnalysis object for testing - wb._timeseries = True - wb.timesteps = [0] - wb._water_network = [{('SOL', 2): [{(2, 0, ('SOL', 2, 'HW1'), ('ALA', 1, 'O'), 2.0, 179.99998), - (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 179.99998)}, - {(3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 179.99998), - (3, 2, ('ALA', 4, 'H'), ('SOL', 2, 'OW'), 2.0, 179.99998)}]}] - result = [(0, 3, 'ALA', 1, 'O', 'ALA', 4, 'H', 1.0), - (0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 1.0), - (1, 3, 'ALA', 1, 'H', 'ALA', 4, 'H', 1.0), - (1, 4, 'ALA', 1, 'H', 'ALA', 4, 'O', 1.0)] - assert_equal(sorted(wb.count_by_type().tolist()), result) + wb._network = [] + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { + (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, + (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, + (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { + (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { + (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) - def test_count_by_type_multiple_frame(self): + def analysis(current, output): + s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ + current[0] + from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ + current[-1] + key = (s1_index, s2_index, s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) + if s2_name == 'H': + output[key] += 1 + result = [(0, 3, 'ALA', 1, 'O', 'ALA', 4, 'H', 0.1), + (1, 3, 'ALA', 1, 'H', 'ALA', 4, 'H', 0.1)] + assert_equal(sorted(wb.count_by_type(analysis_func=analysis)), result) + + def test_count_by_type_merge(self): ''' - This test tests if count_by_type() works in multiply situations. + This test tests if modifying analysis_func + allows some same residue to be merged in count_by_type(). :return: ''' grofile = '''Test gro file @@ -202,34 +527,224 @@ def test_count_by_type_multiple_frame(self): 4ALA O 5 0.600 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) # Build an dummy WaterBridgeAnalysis object for testing - wb._timeseries = True - wb.timesteps = [0, 1, 2, 3, 4, 5] - wb._water_network = [# a 2 * 2 water netwrok consists of all four links - {('SOL', 2): [{(2, 0, ('SOL', 2, 'HW1'), ('ALA', 1, 'O'), 2.0, 179.99998), - (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 179.99998)}, - {(3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 179.99998), - (3, 2, ('ALA', 4, 'H'), ('SOL', 2, 'OW'), 2.0, 179.99998)}]}, - # a repeat - {('SOL', 2): [{(2, 0, ('SOL', 2, 'HW1'), ('ALA', 1, 'O'), 2.0, 179.99998), - (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 179.99998)}, - {(3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 179.99998), - (3, 2, ('ALA', 4, 'H'), ('SOL', 2, 'OW'), 2.0, 179.99998)}]}, - # single link 1 - {('SOL', 2): [{(2, 0, ('SOL', 2, 'HW1'), ('ALA', 1, 'O'), 2.0, 179.99998)}, - {(3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 179.99998)}]}, - # single link 2 - {('SOL', 2): [{(2, 0, ('SOL', 2, 'HW1'), ('ALA', 1, 'O'), 2.0, 179.99998)}, - {(3, 1, ('ALA', 4, 'H'), ('SOL', 2, 'OW'), 2.0, 179.99998)}]}, - # single link 3 - {('SOL', 2): [{(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 179.99998)}, - {(3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 179.99998)}]}, - # single link 4 - {('SOL', 2): [{(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 179.99998)}, - {(3, 2, ('ALA', 4, 'H'), ('SOL', 2, 'OW'), 2.0, 179.99998)}]}] - result = [(0, 3, 'ALA', 1, 'O', 'ALA', 4, 'H', 0.5), - (0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 0.5), - (1, 3, 'ALA', 1, 'H', 'ALA', 4, 'H', 0.5), - (1, 4, 'ALA', 1, 'H', 'ALA', 4, 'O', 0.5)] - assert_equal(sorted(wb.count_by_type().tolist()), result) + wb._network = [] + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { + (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, + (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, + (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { + (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { + (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) + def analysis(current, output): + s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ + current[0] + from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ + current[-1] + key = (s1_resname, s1_resid, s2_resname, s2_resid) + output[key] += 1 + result = [('ALA', 1, 'ALA', 4, 0.8), + ('ALA', 1, 'ALA', 5, 0.2), + ('ALA', 1, 'ALA', 6, 0.1), + ('ALA', 5, 'ALA', 8, 0.1)] + assert_equal(sorted(wb.count_by_type(analysis_func=analysis)), result) + + def test_count_by_type_order(self): + ''' + This test tests if modifying analysis_func + allows the order of water bridge to be separated in count_by_type(). + :return: + ''' + grofile = '''Test gro file +5 + 1ALA N 1 0.000 0.000 0.000 + 1ALA H 2 0.100 0.000 0.000 + 2SOL OW 3 0.300 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) + # Build an dummy WaterBridgeAnalysis object for testing + wb._network = [] + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { + (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, + (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, + (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { + (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { + (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) + def analysis(current, output): + s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ + current[0] + from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ + current[-1] + key = (s1_resname, s1_resid, s2_resname, s2_resid, len(current)-1) + output[key] += 1 + result = [('ALA', 1, 'ALA', 4, 1, 0.6), + ('ALA', 1, 'ALA', 4, 2, 0.2), + ('ALA', 1, 'ALA', 5, 2, 0.1), + ('ALA', 1, 'ALA', 5, 3, 0.1), + ('ALA', 1, 'ALA', 6, 4, 0.1), + ('ALA', 5, 'ALA', 8, 2, 0.1)] + assert_equal(sorted(wb.count_by_type(analysis_func=analysis)), result) + + def test_count_by_time(self): + ''' + This test tests if count_by_times() works. + :return: + ''' + grofile = '''Test gro file +5 + 1ALA N 1 0.000 0.000 0.000 + 1ALA H 2 0.100 0.000 0.000 + 2SOL OW 3 0.300 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) + # Build an dummy WaterBridgeAnalysis object for testing + wb._network = [] + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { + (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, + (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, + (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { + (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { + (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) + assert_equal(wb.count_by_time(), [1, 1, 1, 1, 1, 1, 1, 1, 2, 2]) + + + def test_count_by_time_weight(self): + ''' + This test tests if modyfing the analysis_func allows the weight to be changed + in count_by_type(). + :return: + ''' + grofile = '''Test gro file +12 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 5ALA O 6 0.000 1.000 0.000 + 6SOL OW 7 0.300 1.000 0.000 + 6SOL HW1 8 0.200 1.000 0.000 + 6SOL HW2 9 0.400 1.000 0.000 + 7SOL OW 10 0.600 1.000 0.000 + 7SOL HW1 11 0.700 1.000 0.000 + 8ALA O 12 0.900 1.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) + wb.run(verbose=False) + def analysis(current, output): + s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ + current[0] + from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ + current[-1] + key = (s1_resname, s1_resid, s2_resname, s2_resid) + output[key] += len(current)-1 + assert_equal(wb.count_by_time(analysis_func=analysis), [3, ]) + + def test_count_by_time_empty(self): + ''' + See if count_by_type() can handle zero well. + :return: + ''' + grofile = '''Test gro file +12 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 5ALA O 6 0.000 1.000 0.000 + 6SOL OW 7 0.300 1.000 0.000 + 6SOL HW1 8 0.200 1.000 0.000 + 6SOL HW2 9 0.400 1.000 0.000 + 7SOL OW 10 0.600 1.000 0.000 + 7SOL HW1 11 0.700 1.000 0.000 + 8ALA O 12 0.900 1.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) + wb.run(verbose=False) + def analysis(current, output): + pass + assert_equal(wb.count_by_time(analysis_func=analysis), [0, ]) \ No newline at end of file From 87b5c4e3e09d6a8e12fd3923f8b3ba91421f3324 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Fri, 5 Oct 2018 10:20:27 +0100 Subject: [PATCH 02/23] Fix pbc error --- .../analysis/hbonds/wbridge_analysis.py | 261 ++++++++++++++---- 1 file changed, 211 insertions(+), 50 deletions(-) diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index 69f5fa333ea..81fed606aef 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -461,16 +461,17 @@ def analysis(current, output, **kwargs): of a water bridge existence. """ +from __future__ import print_function, absolute_import from collections import defaultdict import logging import warnings - +import six import numpy as np from ..base import AnalysisBase from MDAnalysis.lib.NeighborSearch import AtomNeighborSearch -from MDAnalysis import NoDataError +from MDAnalysis import NoDataError, MissingDataWarning, SelectionError from MDAnalysis.lib import distances logger = logging.getLogger('MDAnalysis.analysis.wbridges') @@ -654,6 +655,11 @@ def __init__(self, universe, selection1='protein', self.selection1 = selection1 self.selection2 = selection2 self.selection1_type = selection1_type + + # if the selection 1 and selection 2 are the same + if selection1 == selection2: + # eliminate the duplication + self.selection1_type = "donor" self.update_selection = update_selection self.filter_first = filter_first self.distance = distance @@ -696,15 +702,13 @@ def _log_parameters(self): acceptor names", self.forcefield) def _update_selection(self): - box = self.u.dimensions if self.pbc else None - self._s1 = self.u.select_atoms(self.selection1) self._s2 = self.u.select_atoms(self.selection2) if self.filter_first and self._s1: self.logger_debug('Size of selection 1 before filtering:' ' {} atoms'.format(len(self._s1))) - ns_selection_1 = AtomNeighborSearch(self._s1) + ns_selection_1 = AtomNeighborSearch(self._s1, box=self.box) self._s1 = ns_selection_1.search(self._s2, self.selection_distance) self.logger_debug("Size of selection 1: {0} atoms".format(len(self._s1))) @@ -718,7 +722,7 @@ def _update_selection(self): if self.filter_first and self._s2: self.logger_debug('Size of selection 2 before filtering:' ' {} atoms'.format(len(self._s2))) - ns_selection_2 = AtomNeighborSearch(self._s2, box) + ns_selection_2 = AtomNeighborSearch(self._s2, box=self.box) self._s2 = ns_selection_2.search(self._s1, self.selection_distance) self.logger_debug('Size of selection 2: {0} atoms'.format(len(self._s2))) @@ -765,10 +769,10 @@ def _update_water_selection(self): self.logger_debug('Size of water selection before filtering:' ' {} atoms'.format(len(self._water))) if self._water and self.filter_first: - filtered_s1 = AtomNeighborSearch(self._water).search(self._s1, + filtered_s1 = AtomNeighborSearch(self._water, box=self.box).search(self._s1, self.selection_distance) if filtered_s1: - self._water = AtomNeighborSearch(filtered_s1).search(self._s2, + self._water = AtomNeighborSearch(filtered_s1, box=self.box).search(self._s2, self.selection_distance) self.logger_debug("Size of water selection: {0} atoms".format(len(self._water))) @@ -790,6 +794,26 @@ def _update_water_selection(self): 'name {0}'.format(' '.join(self.acceptors))) self.logger_debug("Water acceptors: {0}".format(len(self._water_acceptors))) + def _sanity_check(self): + """sanity check the selections 1 and 2 + *selection* is 1 or 2, *htype* is "donors" or "acceptors" + If selections do not update and the required donor and acceptor + selections are empty then a :exc:`SelectionError` is immediately + raised. + If selections update dynamically then it is possible that the selection + will yield donors/acceptors at a later step and we only issue a + warning. + .. versionadded:: 0.11.0 + """ + + for s in (1,2): + for htype in ('donors', "acceptors"): + atoms = getattr(self, "_s{0}_{1}".format(s, htype)) + if not atoms and not getattr(self, "update_selection") and not getattr(self, "update_water_selection"): + errmsg = '''No {1} found in selection {0}. \ +You might have to specify a custom '{1}' keyword.'''.format(s, htype) + raise SelectionError(errmsg) + def _get_bonded_hydrogens(self, atom): """Find hydrogens bonded within cutoff to `atom`. @@ -841,6 +865,7 @@ def _prepare(self): self._s2_donors_h = {} self._s2_acceptors = {} + self.box = self.u.dimensions if self.pbc else None self._update_selection() self._water_donors = {} @@ -848,7 +873,6 @@ def _prepare(self): self._water_acceptors = {} self.timesteps = [] - if self._s1 and self._s2: self._update_water_selection() logger.info("WBridge analysis: no atoms found in the selection.") @@ -868,9 +892,11 @@ def _prepare(self): logger.info("Starting analysis (frame index start=%d stop=%d, step=%d)", self.start, self.stop, self.step) + self._sanity_check() + def _donor2acceptor(self, donors, donor_hs, acceptor): result = [] - ns_acceptors = AtomNeighborSearch(acceptor) + ns_acceptors = AtomNeighborSearch(acceptor, self.box) for i, donor_h_set in donor_hs.items(): d = donors[i] for h in donor_h_set: @@ -878,10 +904,10 @@ def _donor2acceptor(self, donors, donor_hs, acceptor): for a in res: donor_atom = h if self.distance_type != 'heavy' else d dist = distances.calc_bonds(donor_atom.position, - a.position) + a.position, box=self.box) if dist <= self.distance: angle = distances.calc_angles(d.position, h.position, - a.position) + a.position, box=self.box) angle = np.rad2deg(angle) if angle >= self.angle: self.logger_debug( @@ -897,6 +923,8 @@ def _donor2acceptor(self, donors, donor_hs, acceptor): def _single_frame(self): self.timesteps.append(self._ts.time) + self.box = self.u.dimensions if self.pbc else None + if self.update_selection: self._update_selection() if self._s1 and self._s2: @@ -904,22 +932,33 @@ def _single_frame(self): self._update_water_selection() else: self._network.append(defaultdict(dict)) + return + selection_1 = [] water_pool = defaultdict(list) next_round_water = set([]) selection_2 = [] - if (self.selection1_type in ('donor', 'both') and - self._water_acceptors): + if self.selection1_type in ('donor', 'both'): + # check for direct hbond from s1 to s2 + if self._s2_acceptors: + self.logger_debug("Selection 1 Donors <-> Selection 2 Acceptors") + results = self._donor2acceptor(self._s1_donors, self._s1_donors_h, self._s2_acceptors) + for line in results: + h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + water_pool[(a_resname, a_resid)] = None + selection_1.append(line) + selection_2.append((a_resname, a_resid)) + if self._water_acceptors: + self.logger_debug("Selection 1 Donors <-> Water Acceptors") + results = self._donor2acceptor(self._s1_donors, self._s1_donors_h, self._water_acceptors) + for line in results: + h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + next_round_water.add((a_resname, a_resid)) + selection_1.append(line) - self.logger_debug("Selection 1 Donors <-> Water Acceptors") - results = self._donor2acceptor(self._s1_donors, self._s1_donors_h, self._water_acceptors) - for line in results: - h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line - next_round_water.add((a_resname, a_resid)) - selection_1.append(line) if (self.selection1_type in ('acceptor', 'both') and self._s1_acceptors): @@ -931,6 +970,15 @@ def _single_frame(self): next_round_water.add((h_resname, h_resid)) selection_1.append(line) + self.logger_debug("Selection 2 Donors <-> Selection 1 Acceptors") + results = self._donor2acceptor(self._s2_donors, self._s2_donors_h, self._s1_acceptors) + for line in results: + h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + line = (a_index, h_index, (a_resname, a_resid, a_name), (h_resname, h_resid, h_name), dist, angle) + water_pool[(h_resname, h_resid)] = None + selection_1.append(line) + selection_2.append((h_resname, h_resid)) + for i in range(self.order): # Narrow down the water selection selection_resn_id = list(next_round_water) @@ -967,7 +1015,7 @@ def _single_frame(self): for line in results: h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line water_pool[(h_resname, h_resid)].append(line) - selection_2.append((a_resname, a_resid, a_name)) + selection_2.append((a_resname, a_resid)) # Finding the hydrogen bonds between water bridge and selection 2 if water_bridges_acceptors: @@ -977,7 +1025,7 @@ def _single_frame(self): h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line line = (a_index, h_index, (a_resname, a_resid, a_name), (h_resname, h_resid, h_name), dist, angle) water_pool[(a_resname, a_resid)].append(line) - selection_2.append((h_resname, h_resid, h_name)) + selection_2.append((h_resname, h_resid)) # find the water water hydrogen bond if water_bridges_acceptors: @@ -1001,34 +1049,38 @@ def _single_frame(self): result = {'start': defaultdict(dict), 'water': defaultdict(dict)} def add_route(result, route): - # exclude the the selection which goes back to itself - if sorted(route[0][:2]) == sorted(route[-1][:2]): - return - - # selection 2 to water - result['water'][route[-1]] = None - # water to water - for i in range(1, len(route) - 1): - result['water'][route[i]][route[i+1]] = result['water'][route[i+1]] - # selection 1 to water - result['start'][route[0]][route[1]] = result['water'][route[1]] + if len(route) == 1: + result['start'][route[0]] = None + else: + # exclude the the selection which goes back to itself + if (sorted(route[0][:2]) == sorted(route[-1][:2])): + return + + # selection 2 to water + result['water'][route[-1]] = None + # water to water + for i in range(1, len(route) - 1): + result['water'][route[i]][route[i+1]] = result['water'][route[i+1]] + # selection 1 to water + result['start'][route[0]][route[1]] = result['water'][route[1]] def traverse_water_network(graph, node, end, route, maxdepth, result): if len(route) > self.order + 1: return else: - for new_node in graph[node]: - new_route = route[:] - new_route.append(new_node) - if new_node[3] in end: - # check if any duplication happens - if len(new_route) == len(set(new_route)): - add_route(result, new_route) - else: + if node in end: + # check if any duplication happens + if len(route) == len(set(route)): + add_route(result, route) + else: + for new_node in graph[node]: + new_route = route[:] + new_route.append(new_node) new_node = new_node[3][:2] traverse_water_network(graph, new_node, end, new_route, maxdepth, result) for s1 in selection_1: + route = [s1, ] next_mol = s1[3][:2] traverse_water_network(water_pool, next_mol, selection_2, route[:], self.order, result) @@ -1046,8 +1098,23 @@ def _traverse_water_network(self, graph, current, analysis_func=None, output=Non # make sure no loop can occur if len(current) <= self.order: for node in graph: - new = link_func(current, node, **kwargs) - self._traverse_water_network(graph[node], new, analysis_func, output, link_func) + new = link_func(current, node) + self._traverse_water_network(graph[node], new, analysis_func, output, link_func, **kwargs) + + @staticmethod + def _reformat_hb(hb, atomformat="{0[0]!s}{0[1]!s}:{0[2]!s}"): + """Convert 0.16.1 _timeseries hbond item to 0.16.0 hbond item. + In 0.16.1, donor and acceptor are stored as a tuple(resname, + resid, atomid). In 0.16.0 and earlier they were stored as a string. + .. deprecated:: 1.0 + This is a compatibility layer so that we can provide the same output + in timeseries as before. However, for 1.0 we should make timeseries + just return _timeseries, i.e., change the format of timeseries to + the un-ambiguous representation provided in _timeseries. + """ + return (list(hb[:2]) + + [atomformat.format(hb[2]), atomformat.format(hb[3])] + + list(hb[4:])) @property def timeseries(self): @@ -1076,14 +1143,16 @@ def timeseries(self): ''' def analysis(current, output): - output.append(current) + output.extend(current) timeseries = [] for frame in self._network: new_frame = [] self._traverse_water_network(frame, [], analysis_func=analysis, output=new_frame, link_func=self._full_link) timeseries.append(new_frame) - return timeseries + self._timeseries = timeseries + # after 1.0 will be return the unformated _timeseries + return [[self._reformat_hb(hb) for hb in hframe] for hframe in self._timeseries] @classmethod def _full_link(self, output, node): @@ -1104,7 +1173,7 @@ def _count_by_type_analysis(self, current, output): key = (s1_index, s2_index, s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) output[key] += 1 - def count_by_type(self, analysis_func=None, **kwargs): + def count_by_type(self, analysis_func=None, output='expand', **kwargs): """Counts the frequency of water bridge of a specific type. If one atom *A* from *selection 1* is linked to atom *B* from @@ -1136,7 +1205,8 @@ def count_by_type(self, analysis_func=None, **kwargs): for frame in self._network: self._traverse_water_network(frame, [], analysis_func=analysis_func, output=result_dict, link_func=self._full_link, **kwargs) - result = [(*key, result_dict[key]*1.0/length) for key in result_dict] + result = [[i for i in key] for key in result_dict] + [result[i].append(result_dict[key]*1.0/length) for i, key in enumerate(result_dict)] return result else: return None @@ -1167,11 +1237,102 @@ def count_by_time(self, analysis_func=None, **kwargs): analysis_func = self._count_by_time_analysis if self._network: result = [] - for frame in self._network: + for time, frame in zip(self.timesteps, self._network): result_dict = defaultdict(int) self._traverse_water_network(frame, [], analysis_func=analysis_func, output=result_dict, link_func=self._full_link, **kwargs) - result.append(sum([result_dict[key] for key in result_dict])) + result.append((time, sum([result_dict[key] for key in result_dict]))) return result else: - return None \ No newline at end of file + return None + + @classmethod + def _timesteps_by_type_analysis(self, current, output, **kwargs): + s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ + current[0] + from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ + current[-1] + key = (s1_index, s2_index, s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) + output[key].append(kwargs.pop('time')) + + def timesteps_by_type(self, analysis_func=None, **kwargs): + """Frames during which each water bridges existed, sorted by each water bridges. + + Processes :attr:`WaterBridgeAnalysis._network` and returns a + :class:`numpy.recarray` containing atom indices, residue names, residue + numbers (for donors and acceptors) and each timestep at which the + hydrogen bond was detected. + + In principle, this is the same as :attr:`~WaterBridgeAnalysis.table` + but sorted by hydrogen bond and with additional data for the + *donor_heavy_atom* and angle and distance omitted. + + + Returns + ------- + data : numpy.recarray + + + + """ + if analysis_func is None: + analysis_func = self._timesteps_by_type_analysis + + if self._network: + result = defaultdict(list) + for time, frame in zip(self.timesteps, self._network): + self._traverse_water_network(frame, [], analysis_func=analysis_func, output=result, + link_func=self._full_link, time=time, **kwargs) + + result_list = [] + for key, time_list in six.iteritems(result): + for time in time_list: + key = list(key) + key.append(time) + result_list.append(key) + return result_list + else: + return None + + def generate_table(self): + """Generate a normalised table of the results. + + The table is stored as a :class:`numpy.recarray` in the + attribute :attr:`~HydrogenBondAnalysis.table`. + + See Also + -------- + HydrogenBondAnalysis.table + + """ + if self._network is None: + msg = "No data computed, do run() first." + warnings.warn(msg, category=MissingDataWarning) + logger.warning(msg) + return + if not hasattr(self, '_timeseries'): + self.timeseries + timeseries = self._timeseries + + num_records = np.sum([len(hframe) for hframe in timeseries]) + # build empty output table + dtype = [ + ("time", float), + ("donor_index", int), ("acceptor_index", int), + ("donor_resnm", "|U4"), ("donor_resid", int), ("donor_atom", "|U4"), + ("acceptor_resnm", "|U4"), ("acceptor_resid", int), ("acceptor_atom", "|U4"), + ("distance", float), ("angle", float)] + # according to Lukas' notes below, using a recarray at this stage is ineffective + # and speedups of ~x10 can be achieved by filling a standard array, like this: + out = np.empty((num_records,), dtype=dtype) + cursor = 0 # current row + for t, hframe in zip(self.timesteps, timeseries): + for (donor_index, acceptor_index, donor, + acceptor, distance, angle) in hframe: + # donor|acceptor = (resname, resid, atomid) + out[cursor] = (t, donor_index, acceptor_index) + \ + donor + acceptor + (distance, angle) + cursor += 1 + assert cursor == num_records, "Internal Error: Not all HB records stored" + self.table = out.view(np.recarray) + logger.debug("HBond: Stored results as table with %(num_records)d entries.", vars()) \ No newline at end of file From 0f566663a2d81c85f07eb9757742d2f2d084192e Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Fri, 5 Oct 2018 12:31:40 +0100 Subject: [PATCH 03/23] Add hydrogen bond testing to the water bridge test cases --- .../analysis/hbonds/wbridge_analysis.py | 152 ++++---- .../MDAnalysisTests/analysis/test_wbridge.py | 327 ++++++++++++++++-- 2 files changed, 368 insertions(+), 111 deletions(-) diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index 81fed606aef..dbd56a52c6a 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -92,34 +92,34 @@ [ # frame 1 # hbonds linking the selection 1 and selection 2 to the bridging # water 1 - [[ # hbond 1 from selection 1 to the bridging water 1 - , - , , , - , - ], - [ # hbond 2 from bridging water 1 to the selection 2 - , - , , , - , - ]], + [ # hbond 1 from selection 1 to the bridging water 1 + , + , , , + , + ], + [ # hbond 2 from bridging water 1 to the selection 2 + , + , , , + , + ], # hbonds linking the selection 1 and selection 2 to the bridging # water 1 and bridging water 2 - [[ # hbond 3 from selection 1 to the bridging water 1 - , - , , , - , - ], - [ # hbond 4 from bridging water 1 to the bridging water 2 - , - , , , - , - ], - [ # hbond 5 from bridging water 2 to the selection 2 - , - , , , - , - ]], + [ # hbond 3 from selection 1 to the bridging water 1 + , + , , , + , + ], + [ # hbond 4 from bridging water 1 to the bridging water 2 + , + , , , + , + ], + [ # hbond 5 from bridging water 2 to the selection 2 + , + , , , + , + ], .... ], [ # frame 2 @@ -191,14 +191,14 @@ class WaterBridgeAnalysis_OtherFF(WaterBridgeAnalysis): [ # frame 1 # A water bridge SOL2 links O from ARG1 to the carboxylic group OD1 of ASP3 - [[[0,1,('ARG',1,'O'), ('SOL',2,'HW1'), 3.0,180], - [2,3,('SOL',2,'HW2'),('ASP',3,'OD1'), 3.0,180],], + [[0,1,('ARG',1,'O'), ('SOL',2,'HW1'), 3.0,180], + [2,3,('SOL',2,'HW2'),('ASP',3,'OD1'), 3.0,180], ], # frame 2 # Another water bridge SOL2 links O from ARG1 to the other oxygen of the # carboxylic group OD2 of ASP3 - [[[0,1,('ARG',1,'O'), ('SOL',2,'HW1'), 3.0,180], - [2,4,('SOL',2,'HW2'),('ASP',3,'OD2'), 3.0,180],], + [[0,1,('ARG',1,'O'), ('SOL',2,'HW1'), 3.0,180], + [2,4,('SOL',2,'HW2'),('ASP',3,'OD2'), 3.0,180], ], ] @@ -255,8 +255,10 @@ def analysis(current, output): Returns :: - [('ARG', 1, 'O', 'ASP', 3, 'OD', 1.0),] + [(('ARG', 1, 'O', 'ASP', 3, 'OD'), 1.0),] +Note that the result is arranged in the format of (key, proportion of time). When no +custom analysis function is supplied, the key is expended for backward compatibility. Some people might only interested in contacts between residues and pay no attention to the details regarding the atom name. This can also be achieved by supplying an analysis function to :meth:`~WaterBridgeAnalysis.count_by_type`. :: @@ -286,7 +288,7 @@ def analysis(current, output): Returns :: - [('ARG', 1, 'ASP', 3, 1.0),] + [(('ARG', 1, 'ASP', 3), 1.0),] On the other hand, other people may insist that first order and second order water bridges shouldn't be mixed together, which can also be achieved by supplying an analysis @@ -320,7 +322,7 @@ def analysis(current, output): The extra number 1 precede the 1.0 indicate that this is a first order water bridge :: - [('ARG', 1, 'ASP', 3, 1, 1.0),] + [(('ARG', 1, 'ASP', 3, 1), 1.0),] Some people might not be interested in the interactions related to arginine. The undesirable interactions can be discarded by supplying an analysis function to @@ -378,7 +380,7 @@ def analysis(current, output, **kwargs): As one water bridge is found in both frames, the method returns :: - [1, 1, ] + [(1.0, 1), (2.0, 1), ] Similar to :meth:`~WaterBridgeAnalysis.count_by_type` The behaviour of :meth:`~WaterBridgeAnalysis.count_by_time` can also be modified by supplying @@ -446,7 +448,7 @@ def analysis(current, output, **kwargs): Returns :: - [1, 0,] + [(1.0, 1), (2.0, 0),] Classes ------- @@ -717,8 +719,6 @@ def _update_selection(self): .format(str(self.selection1)[:80])) return - - if self.filter_first and self._s2: self.logger_debug('Size of selection 2 before filtering:' ' {} atoms'.format(len(self._s2))) @@ -731,7 +731,6 @@ def _update_selection(self): .format(str(self.selection2)[:80])) return - if self.selection1_type in ('donor', 'both'): self._s1_donors = self._s1.select_atoms( 'name {0}'.format(' '.join(self.donors))) @@ -794,26 +793,6 @@ def _update_water_selection(self): 'name {0}'.format(' '.join(self.acceptors))) self.logger_debug("Water acceptors: {0}".format(len(self._water_acceptors))) - def _sanity_check(self): - """sanity check the selections 1 and 2 - *selection* is 1 or 2, *htype* is "donors" or "acceptors" - If selections do not update and the required donor and acceptor - selections are empty then a :exc:`SelectionError` is immediately - raised. - If selections update dynamically then it is possible that the selection - will yield donors/acceptors at a later step and we only issue a - warning. - .. versionadded:: 0.11.0 - """ - - for s in (1,2): - for htype in ('donors', "acceptors"): - atoms = getattr(self, "_s{0}_{1}".format(s, htype)) - if not atoms and not getattr(self, "update_selection") and not getattr(self, "update_water_selection"): - errmsg = '''No {1} found in selection {0}. \ -You might have to specify a custom '{1}' keyword.'''.format(s, htype) - raise SelectionError(errmsg) - def _get_bonded_hydrogens(self, atom): """Find hydrogens bonded within cutoff to `atom`. @@ -892,8 +871,6 @@ def _prepare(self): logger.info("Starting analysis (frame index start=%d stop=%d, step=%d)", self.start, self.stop, self.step) - self._sanity_check() - def _donor2acceptor(self, donors, donor_hs, acceptor): result = [] ns_acceptors = AtomNeighborSearch(acceptor, self.box) @@ -919,8 +896,6 @@ def _donor2acceptor(self, donors, donor_hs, acceptor): dist, angle)) return result - - def _single_frame(self): self.timesteps.append(self._ts.time) self.box = self.u.dimensions if self.pbc else None @@ -934,7 +909,6 @@ def _single_frame(self): self._network.append(defaultdict(dict)) return - selection_1 = [] water_pool = defaultdict(list) next_round_water = set([]) @@ -958,8 +932,6 @@ def _single_frame(self): next_round_water.add((a_resname, a_resid)) selection_1.append(line) - - if (self.selection1_type in ('acceptor', 'both') and self._s1_acceptors): self.logger_debug("Selection 1 Acceptors <-> Water Donors") @@ -1078,7 +1050,6 @@ def traverse_water_network(graph, node, end, route, maxdepth, result): new_route.append(new_node) new_node = new_node[3][:2] traverse_water_network(graph, new_node, end, new_route, maxdepth, result) - for s1 in selection_1: route = [s1, ] @@ -1120,9 +1091,8 @@ def _reformat_hb(hb, atomformat="{0[0]!s}{0[1]!s}:{0[2]!s}"): def timeseries(self): r'''Time series of water bridges. - The output is arranged based on each individual link from selection 1 - to selection 2 allowing the later reconstruction of the water network. - Each entity is a list of hydrogen bonds from selection 1 to selection 2. + The output is arranged so that every individual link from selection 1 + to selection 2 is appended to the resulting list. An example of the output is given in :ref:`wb_Analysis_Output`. Note @@ -1173,7 +1143,7 @@ def _count_by_type_analysis(self, current, output): key = (s1_index, s2_index, s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) output[key] += 1 - def count_by_type(self, analysis_func=None, output='expand', **kwargs): + def count_by_type(self, analysis_func=None, **kwargs): """Counts the frequency of water bridge of a specific type. If one atom *A* from *selection 1* is linked to atom *B* from @@ -1196,8 +1166,10 @@ def count_by_type(self, analysis_func=None, output='expand', **kwargs): """ + output = None if analysis_func is None: analysis_func = self._count_by_type_analysis + output = 'combined' if self._network: length = len(self._network) @@ -1205,8 +1177,12 @@ def count_by_type(self, analysis_func=None, output='expand', **kwargs): for frame in self._network: self._traverse_water_network(frame, [], analysis_func=analysis_func, output=result_dict, link_func=self._full_link, **kwargs) - result = [[i for i in key] for key in result_dict] - [result[i].append(result_dict[key]*1.0/length) for i, key in enumerate(result_dict)] + + if output is 'combined': + result = [[i for i in key] for key in result_dict] + [result[i].append(result_dict[key]/length) for i, key in enumerate(result_dict)] + else: + result = [(key, result_dict[key]/length) for key in result_dict] return result else: return None @@ -1259,24 +1235,23 @@ def timesteps_by_type(self, analysis_func=None, **kwargs): """Frames during which each water bridges existed, sorted by each water bridges. Processes :attr:`WaterBridgeAnalysis._network` and returns a - :class:`numpy.recarray` containing atom indices, residue names, residue - numbers (for donors and acceptors) and each timestep at which the - hydrogen bond was detected. - - In principle, this is the same as :attr:`~WaterBridgeAnalysis.table` - but sorted by hydrogen bond and with additional data for the - *donor_heavy_atom* and angle and distance omitted. + :class:`list` containing atom indices, residue names, residue + numbers (from selection 1 and selection 2) and each timestep at which the + water bridge was detected. + Similar to :meth:`~WaterBridgeAnalysis.count_by_type` and + :meth:`~WaterBridgeAnalysis.count_by_time`, the behavior can be adjusted by + supplying an analysis_func. Returns ------- - data : numpy.recarray - - + data : list """ + output = None if analysis_func is None: analysis_func = self._timesteps_by_type_analysis + output = 'combined' if self._network: result = defaultdict(list) @@ -1287,9 +1262,12 @@ def timesteps_by_type(self, analysis_func=None, **kwargs): result_list = [] for key, time_list in six.iteritems(result): for time in time_list: - key = list(key) - key.append(time) - result_list.append(key) + if output is 'combined': + key = list(key) + key.append(time) + result_list.append(key) + else: + result_list.append((key, time)) return result_list else: return None @@ -1298,11 +1276,11 @@ def generate_table(self): """Generate a normalised table of the results. The table is stored as a :class:`numpy.recarray` in the - attribute :attr:`~HydrogenBondAnalysis.table`. + attribute :attr:`~WaterBridgeAnalysis.table`. See Also -------- - HydrogenBondAnalysis.table + WaterBridgeAnalysis.table """ if self._network is None: @@ -1335,4 +1313,4 @@ def generate_table(self): cursor += 1 assert cursor == num_records, "Internal Error: Not all HB records stored" self.table = out.view(np.recarray) - logger.debug("HBond: Stored results as table with %(num_records)d entries.", vars()) \ No newline at end of file + logger.debug("WBridge: Stored results as table with %(num_records)d entries.", vars()) \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 8809a41ed7d..d78ebb6ff06 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -2,10 +2,20 @@ from six import StringIO from collections import defaultdict -from numpy.testing import assert_equal, assert_almost_equal +import numpy as np +from numpy.testing import ( + assert_equal, assert_array_equal, assert_almost_equal, + assert_array_almost_equal, assert_allclose,) +import pytest import MDAnalysis +import MDAnalysis.analysis.hbonds from MDAnalysis.analysis.hbonds.wbridge_analysis import WaterBridgeAnalysis +from MDAnalysisTests.datafiles import PDB_helix, GRO, XTC, waterPSF, waterDCD + +# For type guessing: +from MDAnalysis.topology.core import guess_atom_type +from MDAnalysis.core.topologyattrs import Atomtypes def test_import_from_hbonds(): try: @@ -16,6 +26,48 @@ def test_import_from_hbonds(): "MDAnalysis.analysis.hbonds failed.'") class TestWaterBridgeAnalysis(object): + def test_donor_accepter(self): + '''Test zeroth order donor to acceptor hydrogen bonding''' + grofile = '''Test gro file +3 + 1ALA N 1 0.000 0.000 0.000 + 1ALA H 2 0.100 0.000 0.000 + 4ALA O 3 0.300 0.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('ALA', 4, 'O'))) + + def test_donor_accepter_pbc(self): + '''Test zeroth order donor to acceptor hydrogen bonding''' + grofile = '''Test gro file +3 + 1ALA N 1 0.800 0.000 0.000 + 1ALA H 2 0.900 0.000 0.000 + 4ALA O 3 0.100 0.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0, pbc=True) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('ALA', 4, 'O'))) + + def test_accepter_donor(self): + '''Test zeroth order acceptor to donor hydrogen bonding''' + grofile = '''Test gro file +3 + 1ALA O 1 0.000 0.000 0.000 + 4ALA H 2 0.200 0.000 0.000 + 4ALA N 3 0.300 0.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 1, ('ALA', 1, 'O'), ('ALA', 4, 'H'))) + def test_acceptor_water_accepter(self): '''Test case where the hydrogen bond acceptor from selection 1 form water bridge with hydrogen bond acceptor from selection 2''' @@ -31,7 +83,6 @@ def test_acceptor_water_accepter(self): wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) second = network[list(network.keys())[0]] assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) @@ -307,13 +358,14 @@ def test_timeseries(self): u = MDAnalysis.Universe(StringIO(grofile), format='gro') wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) wb.run(verbose=False) - timeseries = wb.timeseries[0] - assert_equal(timeseries[0][0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - assert_equal(timeseries[0][1][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) - assert_equal(timeseries[1][0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - assert_equal(timeseries[1][1][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) - assert_equal([(5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O')), (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'))], - sorted([line[2][:4] for line in timeseries])) + wb.timeseries + timeseries = sorted(wb._timeseries[0]) + assert_equal(timeseries[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(timeseries[1][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(timeseries[2][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal(timeseries[3][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal(timeseries[4][:4], (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) + assert_equal(timeseries[5][:4], (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'))) def test_acceptor_12water_accepter(self): '''Test of independent first order and second can be recognised correctely''' @@ -508,8 +560,8 @@ def analysis(current, output): key = (s1_index, s2_index, s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) if s2_name == 'H': output[key] += 1 - result = [(0, 3, 'ALA', 1, 'O', 'ALA', 4, 'H', 0.1), - (1, 3, 'ALA', 1, 'H', 'ALA', 4, 'H', 0.1)] + result = [((0, 3, 'ALA', 1, 'O', 'ALA', 4, 'H'), 0.1), + ((1, 3, 'ALA', 1, 'H', 'ALA', 4, 'H'), 0.1)] assert_equal(sorted(wb.count_by_type(analysis_func=analysis)), result) def test_count_by_type_merge(self): @@ -568,10 +620,10 @@ def analysis(current, output): current[-1] key = (s1_resname, s1_resid, s2_resname, s2_resid) output[key] += 1 - result = [('ALA', 1, 'ALA', 4, 0.8), - ('ALA', 1, 'ALA', 5, 0.2), - ('ALA', 1, 'ALA', 6, 0.1), - ('ALA', 5, 'ALA', 8, 0.1)] + result = [(('ALA', 1, 'ALA', 4), 0.8), + (('ALA', 1, 'ALA', 5), 0.2), + (('ALA', 1, 'ALA', 6), 0.1), + (('ALA', 5, 'ALA', 8), 0.1)] assert_equal(sorted(wb.count_by_type(analysis_func=analysis)), result) def test_count_by_type_order(self): @@ -630,12 +682,12 @@ def analysis(current, output): current[-1] key = (s1_resname, s1_resid, s2_resname, s2_resid, len(current)-1) output[key] += 1 - result = [('ALA', 1, 'ALA', 4, 1, 0.6), - ('ALA', 1, 'ALA', 4, 2, 0.2), - ('ALA', 1, 'ALA', 5, 2, 0.1), - ('ALA', 1, 'ALA', 5, 3, 0.1), - ('ALA', 1, 'ALA', 6, 4, 0.1), - ('ALA', 5, 'ALA', 8, 2, 0.1)] + result = [(('ALA', 1, 'ALA', 4, 1), 0.6), + (('ALA', 1, 'ALA', 4, 2), 0.2), + (('ALA', 1, 'ALA', 5, 2), 0.1), + (('ALA', 1, 'ALA', 5, 3), 0.1), + (('ALA', 1, 'ALA', 6, 4), 0.1), + (('ALA', 5, 'ALA', 8, 2), 0.1)] assert_equal(sorted(wb.count_by_type(analysis_func=analysis)), result) def test_count_by_time(self): @@ -653,6 +705,7 @@ def test_count_by_time(self): 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) + # Build an dummy WaterBridgeAnalysis object for testing wb._network = [] wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { @@ -686,7 +739,8 @@ def test_count_by_time(self): (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) - assert_equal(wb.count_by_time(), [1, 1, 1, 1, 1, 1, 1, 1, 2, 2]) + wb.timesteps = range(len(wb._network)) + assert_equal(wb.count_by_time(), [(0,1), (1,1), (2,1), (3,1), (4,1), (5,1), (6,1), (7,1), (8,2), (9,2)]) def test_count_by_time_weight(self): @@ -720,7 +774,7 @@ def analysis(current, output): current[-1] key = (s1_resname, s1_resid, s2_resname, s2_resid) output[key] += len(current)-1 - assert_equal(wb.count_by_time(analysis_func=analysis), [3, ]) + assert_equal(wb.count_by_time(analysis_func=analysis), [(0,3), ]) def test_count_by_time_empty(self): ''' @@ -747,4 +801,229 @@ def test_count_by_time_empty(self): wb.run(verbose=False) def analysis(current, output): pass - assert_equal(wb.count_by_time(analysis_func=analysis), [0, ]) \ No newline at end of file + assert_equal(wb.count_by_time(analysis_func=analysis), [(0,0), ]) + +def guess_types(names): + """GRO doesn't supply types, this returns an Attr""" + return Atomtypes(np.array([guess_atom_type(name) for name in names], dtype=object)) + + +class TestHydrogenBondAnalysis(object): + @staticmethod + @pytest.fixture(scope='class') + def universe(): + return MDAnalysis.Universe(PDB_helix) + + @staticmethod + @pytest.fixture(scope='class') + def values(universe): + return { + 'num_bb_hbonds': universe.atoms.n_residues - universe.select_atoms('resname PRO').n_residues - 4, + 'donor_resid': np.array([5, 6, 8, 9, 10, 11, 12, 13]), + 'acceptor_resnm': np.array(['ALA', 'ALA', 'ALA', 'ALA', 'ALA', 'PRO', 'ALA', 'ALA'], dtype='U4'), + } + + kwargs = { + 'selection1': 'protein', + 'selection2': 'protein', + 'detect_hydrogens': "distance", + 'distance': 3.0, + 'angle': 150.0, + } + + @pytest.fixture(scope='class') + def h(self, universe): + kw = self.kwargs.copy() + # kw.update(kwargs) + h = MDAnalysis.analysis.hbonds.WaterBridgeAnalysis(universe, order=0, **kw) + # remove in 1.0 + if kw['detect_hydrogens'] == 'heuristic': + with pytest.warns(DeprecationWarning): + h.run(verbose=False) + else: + h.run(verbose=False) + return h + + def test_helix_backbone(self, values, h): + assert len(h.timeseries[0]) == values['num_bb_hbonds'], "wrong number of backbone hydrogen bonds" + assert h.timesteps, [0.0] + + def test_generate_table(self, values, h): + + h.generate_table() + assert len(h.table) == values['num_bb_hbonds'], "wrong number of backbone hydrogen bonds in table" + assert isinstance(h.table, np.core.records.recarray) + + assert_array_equal(h.table.donor_resid, values['donor_resid']) + assert_array_equal(h.table.acceptor_resnm, values['acceptor_resnm']) + + def test_atoms_too_far(self): + pdb = ''' +ATOM 1 N LEU 1 32.310 13.778 14.372 1.00 0.00 SYST N 0 +ATOM 2 OW SOL 2 3.024 4.456 4.147 1.00 0.00 SYST H 0''' + + u = MDAnalysis.Universe(StringIO(pdb), format="pdb") + h = WaterBridgeAnalysis(u, 'resname SOL', 'protein', order=0) + h.run(verbose=False) + assert h.timeseries == [[]] + + def test_acceptor_OC1_OC2(self): + gro = '''test +3 + 1ALA OC1 1 0.000 0.000 0.000 + 2ALA N 2 0.300 0.000 0.000 + 2ALA H1 3 0.200 0.000 0.000 +7.29748 7.66094 9.82962''' + + u = MDAnalysis.Universe(StringIO(gro), format="gro") + h = WaterBridgeAnalysis(u, 'protein', 'protein', order=0) + h.run(verbose=False) + assert h.timeseries[0][0][2] == 'ALA2:H1' + + def test_true_traj(self): + u = MDAnalysis.Universe(GRO, XTC) + u.add_TopologyAttr(guess_types(u.atoms.names)) + h = WaterBridgeAnalysis(u, 'protein', 'resname ASP', distance=3.0, angle=120.0, order=0) + h.run() + assert len(h.timeseries) == 10 + + def test_count_by_time(self, values, h): + + c = h.count_by_time() + assert c, [(0.0, values['num_bb_hbonds'])] + + def test_count_by_type(self, values, h): + + c = h.count_by_type() + assert_equal([line[-1] for line in c][:8], values['num_bb_hbonds'] * [1.0]) + + def test_timesteps_by_type(self, values, h): + + t = h.timesteps_by_type() + assert_equal([i[-1] for i in t][:8], values['num_bb_hbonds'] * [0.0]) + + +class TestHydrogenBondAnalysisPBC(TestHydrogenBondAnalysis): + # This system is identical to above class + # But has a box introduced, and atoms moved into neighbouring images + # The results however should remain identical if PBC is used + # If pbc:True in kwargs is changed, these tests should fail + @staticmethod + @pytest.fixture(scope='class') + def universe(): + u = MDAnalysis.Universe(PDB_helix) + # transfer to memory to changes to coordinates are reset + u.transfer_to_memory() + # place in huge oversized box + # real system is about 30A wide at most + boxsize = 150. + box = np.array([boxsize, boxsize, boxsize, 90., 90., 90.]) + u.dimensions = box + + # then scatter the atoms throughout various images of this box + u.atoms[::4].translate([boxsize * 2, 0, 0]) + u.atoms[1::4].translate([0, boxsize * 4, 0]) + u.atoms[2::4].translate([-boxsize * 5, 0, -boxsize * 2]) + + return u + + kwargs = { + 'selection1': 'protein', + 'selection2': 'protein', + 'detect_hydrogens': "distance", + 'distance': 3.0, + 'angle': 150.0, + 'pbc': True, + } + +class TestHydrogenBondAnalysisTIP3P(object): + @staticmethod + @pytest.fixture() + def universe(): + return MDAnalysis.Universe(waterPSF, waterDCD) + + kwargs = { + 'selection1': 'all', + 'selection2': 'all', + 'detect_hydrogens': "distance", + 'distance': 3.0, + 'angle': 120.0, + } + + @pytest.fixture() + def h(self, universe): + h = WaterBridgeAnalysis(universe, order=0, **self.kwargs) + h.run(verbose=False) + h.generate_table() + return h + + @pytest.fixture() + def normalized_timeseries(self, h): + # timeseries in normalized form: (t, d_indx1, a_indx1, d_index0, a_index0, donor, acceptor, dist, angle) + # array index: 0 1 2 3 4 5 6 7 8 + timeseries = [[t] + item + for t, hframe in zip(h.timesteps, h.timeseries) + for item in hframe] + return timeseries + + # keys are the names in the h.table + reference = { + 'distance': {'mean': 2.0208776, 'std': 0.31740859}, + 'angle': {'mean': 155.13521, 'std': 12.98955}, + } + + @pytest.fixture() + def reference_table(self, normalized_timeseries): + # reference values for the table only + return { + 'donor_resnm': ["TIP3"] * len(normalized_timeseries), + 'acceptor_resnm': ["TIP3"] * len(normalized_timeseries), + } + + # index into timeseries (ADJUST ONCE donor_idx and acceptor_ndx are removed) + # with keys being field names in h.table + columns = { + 'time': 0, + 'donor_index': 1, + 'acceptor_index': 2, + 'distance': 5, + 'angle': 6, + } + + # hackish way to allow looping over self.reference and generating tests + _functions = { + 'mean': np.mean, + 'std': np.std, + } + + def test_timeseries(self, h, normalized_timeseries): + h = h + assert len(h.timeseries) == 10 + assert len(normalized_timeseries) == 29 + + for observable in self.reference: + idx = self.columns[observable] + for quantity, reference in self.reference[observable].items(): + func = self._functions[quantity] + assert_allclose( + func([item[idx] for item in normalized_timeseries]), reference, + rtol=1e-5, atol=0, + err_msg="{quantity}({observable}) does not match reference".format(**vars()) + ) + + def test_table_atoms(self, h, normalized_timeseries, reference_table): + h = h + table = h.table + + assert len(h.table) == len(normalized_timeseries) + + # test that timeseries and table agree on index data and + # hydrogen bond information at atom level + for name, idx in self.columns.items(): + assert_array_almost_equal(table.field(name), [data[idx] for data in normalized_timeseries], + err_msg="table[{name}] and timeseries[{idx} do not agree".format(**vars())) + + # test at residue level (issue #801 + # https://github.com/MDAnalysis/mdanalysis/issues/801) + for name, ref in reference_table.items(): + assert_array_equal(h.table.field(name), ref, err_msg="resname for {0} do not match (Issue #801)") \ No newline at end of file From 3337e25ba79371dcd5074abb52ca21f571aae15c Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Fri, 5 Oct 2018 14:09:49 +0100 Subject: [PATCH 04/23] fixed test --- package/MDAnalysis/analysis/hbonds/wbridge_analysis.py | 2 +- testsuite/MDAnalysisTests/analysis/test_hbonds.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index dbd56a52c6a..2b98ea80c91 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -463,7 +463,7 @@ def analysis(current, output, **kwargs): of a water bridge existence. """ -from __future__ import print_function, absolute_import +from __future__ import print_function, absolute_import, division from collections import defaultdict import logging diff --git a/testsuite/MDAnalysisTests/analysis/test_hbonds.py b/testsuite/MDAnalysisTests/analysis/test_hbonds.py index 0156b441ae4..a7441d868cf 100644 --- a/testsuite/MDAnalysisTests/analysis/test_hbonds.py +++ b/testsuite/MDAnalysisTests/analysis/test_hbonds.py @@ -95,7 +95,7 @@ def test_generate_table(self, values, h): h.generate_table() assert len(h.table) == values['num_bb_hbonds'], "wrong number of backbone hydrogen bonds in table" assert isinstance(h.table, np.core.records.recarray) - assert_array_equal(h.table.donor_resid, values['donor_resid']) + assert_array_equal(sorted(h.table.donor_resid), values['donor_resid']) assert_array_equal(h.table.acceptor_resnm, values['acceptor_resnm']) def test_atoms_too_far(self): From 0cae5fedbde9df7074b352c6b9fa3be518d76e61 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Fri, 5 Oct 2018 16:24:38 +0100 Subject: [PATCH 05/23] Change the wrong test file --- testsuite/MDAnalysisTests/analysis/test_hbonds.py | 2 +- testsuite/MDAnalysisTests/analysis/test_wbridge.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_hbonds.py b/testsuite/MDAnalysisTests/analysis/test_hbonds.py index a7441d868cf..0156b441ae4 100644 --- a/testsuite/MDAnalysisTests/analysis/test_hbonds.py +++ b/testsuite/MDAnalysisTests/analysis/test_hbonds.py @@ -95,7 +95,7 @@ def test_generate_table(self, values, h): h.generate_table() assert len(h.table) == values['num_bb_hbonds'], "wrong number of backbone hydrogen bonds in table" assert isinstance(h.table, np.core.records.recarray) - assert_array_equal(sorted(h.table.donor_resid), values['donor_resid']) + assert_array_equal(h.table.donor_resid, values['donor_resid']) assert_array_equal(h.table.acceptor_resnm, values['acceptor_resnm']) def test_atoms_too_far(self): diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index d78ebb6ff06..01fd5e42cbb 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -854,7 +854,7 @@ def test_generate_table(self, values, h): assert len(h.table) == values['num_bb_hbonds'], "wrong number of backbone hydrogen bonds in table" assert isinstance(h.table, np.core.records.recarray) - assert_array_equal(h.table.donor_resid, values['donor_resid']) + assert_array_equal(sorted(h.table.donor_resid), values['donor_resid']) assert_array_equal(h.table.acceptor_resnm, values['acceptor_resnm']) def test_atoms_too_far(self): From ae9e3862b6b51350f73c42ed7d598ecad5a1c743 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Fri, 5 Oct 2018 17:24:12 +0100 Subject: [PATCH 06/23] solve unordered table problem --- .../MDAnalysisTests/analysis/test_wbridge.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 01fd5e42cbb..d480530eedc 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -853,8 +853,8 @@ def test_generate_table(self, values, h): h.generate_table() assert len(h.table) == values['num_bb_hbonds'], "wrong number of backbone hydrogen bonds in table" assert isinstance(h.table, np.core.records.recarray) - - assert_array_equal(sorted(h.table.donor_resid), values['donor_resid']) + h.table.sort(order='donor_resid') + assert_array_equal(h.table.donor_resid, values['donor_resid']) assert_array_equal(h.table.acceptor_resnm, values['acceptor_resnm']) def test_atoms_too_far(self): @@ -1026,4 +1026,14 @@ def test_table_atoms(self, h, normalized_timeseries, reference_table): # test at residue level (issue #801 # https://github.com/MDAnalysis/mdanalysis/issues/801) for name, ref in reference_table.items(): - assert_array_equal(h.table.field(name), ref, err_msg="resname for {0} do not match (Issue #801)") \ No newline at end of file + assert_array_equal(h.table.field(name), ref, err_msg="resname for {0} do not match (Issue #801)") + +b=TestHydrogenBondAnalysis() +b.test_helix_backbone(b.values(b.universe()), b.h(b.universe())) +b.test_generate_table(b.values(b.universe()), b.h(b.universe())) +b.test_atoms_too_far() +b.test_acceptor_OC1_OC2() +b.test_true_traj() +b.test_count_by_time(b.values(b.universe()), b.h(b.universe())) +b.test_count_by_type(b.values(b.universe()), b.h(b.universe())) +b.test_timesteps_by_type(b.values(b.universe()), b.h(b.universe())) \ No newline at end of file From 635be54263f193399b6ae58e05ca4bf371916f0f Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Fri, 5 Oct 2018 17:25:21 +0100 Subject: [PATCH 07/23] remove test --- testsuite/MDAnalysisTests/analysis/test_wbridge.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index d480530eedc..3b08d00761b 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -1026,14 +1026,4 @@ def test_table_atoms(self, h, normalized_timeseries, reference_table): # test at residue level (issue #801 # https://github.com/MDAnalysis/mdanalysis/issues/801) for name, ref in reference_table.items(): - assert_array_equal(h.table.field(name), ref, err_msg="resname for {0} do not match (Issue #801)") - -b=TestHydrogenBondAnalysis() -b.test_helix_backbone(b.values(b.universe()), b.h(b.universe())) -b.test_generate_table(b.values(b.universe()), b.h(b.universe())) -b.test_atoms_too_far() -b.test_acceptor_OC1_OC2() -b.test_true_traj() -b.test_count_by_time(b.values(b.universe()), b.h(b.universe())) -b.test_count_by_type(b.values(b.universe()), b.h(b.universe())) -b.test_timesteps_by_type(b.values(b.universe()), b.h(b.universe())) \ No newline at end of file + assert_array_equal(h.table.field(name), ref, err_msg="resname for {0} do not match (Issue #801)") \ No newline at end of file From 9b28e5807775f79736addfc52d0fb479d67fa7e6 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Thu, 15 Nov 2018 12:39:56 +0000 Subject: [PATCH 08/23] resolve conflict --- testsuite/AUTHORS | 145 -------------------------------------------- testsuite/CHANGELOG | 140 ------------------------------------------ 2 files changed, 285 deletions(-) delete mode 100644 testsuite/AUTHORS delete mode 100644 testsuite/CHANGELOG diff --git a/testsuite/AUTHORS b/testsuite/AUTHORS deleted file mode 100644 index 2ce97744f5b..00000000000 --- a/testsuite/AUTHORS +++ /dev/null @@ -1,145 +0,0 @@ -.. -*- coding: utf-8 -*- - -======================= - Authors of MDAnalysis -======================= - -MDAnalysis was originally written by Naveen Michaud-Agrawal. - -The project is maintained by the MDAnalysis Core Developer Team -(@MDAnalysis/coredevs on https://github.com/MDAnalysis). - -All contributing authors for the testsuite are listed in this file below. The -repository history at https://github.com/MDAnalysis/mdanalysis and the -CHANGELOG show individual code contributions. - - -Chronological list of authors ------------------------------ - -2005 - - Naveen Michaud-Agrawal -2006 - - Elizabeth J. Denning -2007 - - Oliver Beckstein -2010 - - Danny Parton - - Philip Fowler - - Tyler Reddy - - Joseph Goose - - Jan Domanski -2011 - - Benjamin Hall - - Paul Rigor - - David Caplan - - Christian Beckstein (logo) - - Sébastien Buchoux - - Joshua L. Adelman -2012 - - Lukas Grossar - - Andy Somogyi - - Lukas Stelzl - - Jinju Lu - - Joshua L. Phillips -2013 - - Zhuyi Xue - - Xavier Deupi - - Manuel Nuno Melo - - Robert McGibbon - - Richard J. Gowers - - Alejandro Bernardin -2014 - - Matthieu Chavent - - Joe Jordan -2015 - - Alex Nesterenko - - Caio S. Souza - - Sean L. Seyler - - David L. Dotson - - Carlos Yanez S. - - Kyle J. Huston - - Isaac Virshup - - Max Linke - - Gorman Stock - - Jonathan Barnoud - - Hai Nguyen -2016 - - Balasubramanian - - Abhinav Gupta - - Pedro Reis - - Fiona B. Naughton - - Robert Delgado -2017 - - Utkarsh Bansal - - Shobhit Agarwal - - Vedant Rathore - - Kashish Punjani - - Xiki Tempula - - Jose Borreguero - - Ruggero Cortini - - Sören von Bülow - - Mateusz Bieniek - -2018 - - Johannes Zeman - - Ayush Suhane - - Paul Smith - - Mateusz Bieniek - - -External code -------------- - -External code (under a GPL-compatible licence) was obtained from -various sources. The authors (as far as we know them) are listed here. - -xdrfile - - David van der Spoel - - Erik Lindahl - - Frans van Hoesel (not listed as an xdrfile - author, but the original author of the XTC coordinate compression code) - - The Gromacs libxdrfile (LGPL-licensed) was used before MDAnalysis - version 0.8.0. - Between MDAnalysis versions 0.8.0 and 0.13.0 libxdrfile was replaced by - libxdrfile2, our GPLv2 enhanced derivative of libxdrfile. - Since version 0.14.0 xdr enhanecments were rebased onto Gromacs' - xdrfile 1.1.4 code (now BSD-licensed). Our contributions remain GPLv2 - and were split into files xtc_seek.c, trr_seek.c, xtc_seek.h, and - trr_seek.h, for clarity (xdrfile 1.1.4 code is distributed with minor - modifications). Also for clarity we now name the resulting enhanced - xdr-reading library, complete with cython bindings, 'libmdaxdr'. - -KDTree - - Thomas Hamelryck - -DCD reader from catdcd - - Justin Gullingsrud - -transformations - - Christoph Gohlke - -pyqcprot - - Joshua L. Adelman, University of Pittsburgh - - https://github.com/synapticarbors/pyqcprot - -helanal - - Benjamin Hall, University of Oxford - - based on HELANAL from - http://www.ccrnp.ncifcrf.gov/users/kumarsan/HELANAL/helanal.html - - -Other contributions -------------------- - -- MDAnalysis includes code generated by quantifiedcode - https://www.quantifiedcode.com/ under the handle @quantifiedcode-bot - - -Logo ----- - -The MDAnalysis 'Atom' logo was designed by Christian Beckstein; it is -Copyright (c) 2011 Christian Beckstein and made available under a -Creative Commons Attribution-NoDerivs 3.0 Unported License. diff --git a/testsuite/CHANGELOG b/testsuite/CHANGELOG deleted file mode 100644 index 5ce98bf9042..00000000000 --- a/testsuite/CHANGELOG +++ /dev/null @@ -1,140 +0,0 @@ -============================== - MDAnalysisTestData CHANGELOG -============================== - -The rules for this file: - * entries are sorted newest-first. - * summarize sets of changes - don't reproduce every subversion log comment here. - * don't ever delete anything. - * keep the format consistent (79 char width, M/D/Y date format) and do not - use tabs but use spaces for formatting - -Also see https://github.com/MDAnalysis/mdanalysis/wiki/MDAnalysisTests -and https://github.com/MDAnalysis/mdanalysis/wiki/UnitTests - ------------------------------------------------------------------------------- -mm/dd/18 orbeckst, arm61 - * 0.18.1 - - skip tests for duecredit when duecredit is not installed (#1906) - - updated meta data for PyPi and updated README and INSTALL - - added test for fix to issue #1897 - - add tests for the new water bridge analysis (PR #2087) - - -04/15/18 tylerjereddy, zemanj - * 0.18.0 - - Unit tests for unwanted side effects when importing MDAnalysis - - MDAnalysis.visualization is now tested - -01/24/18 sobuelow, Zhiyi Wu - * 0.17.0 - - Unit test for residue names in 'protein' atom selection (PR #1704) - - Test for waterdynamics.SurvivalProbability starting time t0 (PR #1759) - - Add Tests for the reindex keyword of GROwriter (PR #1781) - -mm/dd/yy - - * 0.16.2 utkbansal - - Unit tests for pdktree (PR #1660) - - Port to pytest - removed nose as a dependency (Issue #884) - - Unit tests for GSD file format topology/trajectory (PR #1693) - -06/03/17 xiki_tempula - - * 0.16.1 - - Added two unit tests for PDB and GRO to not merge two residue if they - have the the same residue id - - Change core.util string.uppercase to string.ascii_uppercase for py3 - compatibility - - -04/10/17 jbarnoud, orbeckst, fiona-naughton, manuel.nuno.melo, richardjgowers - tyler.je.reddy, utkbansal, vedantrathore, kash1102 - - * 0.16 - - Added two unit tests for MDAnalysis.coordinates.memory.MemoryReader - - Make knownfailure work even without parentheses. - - added two unit tests for MDAnalysis.analysis.polymer - - added test for LeafletFinder handling a selection string - - Added unit test for MDAnalysis.analysis.distances.between() - - Added unit tests for MDAnalysis.analysis.distances.dist() - - Added unit tests for clip_matrix frustrum boundary checks - - Added unit tests for bad file mode passed to SelectionWriter - - Added regression tests for MDAnalysis.analysis.nuclinfo (Issue #790) - - test_imports now recursively checks subdirectories (Issue #964). - - removed usage of random numbers from tests (Issue #958) - - test_imports now always uses the correct source directory (Issue #939). - - Added a plugin to list the non-closed file handle (Issue #853, PR #874). - The plugin can be disabled with --no-open-files. - - The test_failure test can be made fail by setting the MDA_FAILURE_TEST - environment variable (PR #874) - - replaced XTC_HOLE test trajectory with more meaningful one MULTIPDB_HOLE - - install external packages on Travis (SETUP == full: HOLE, clustalw) - to test additional analysis code (Issue #898) - - add tests for new auxiliary module - - trajectory slices can use any type of integer PR #1233 - -05/15/16 orbeckst, jbarnoud, pedrishi, fiona-naughton, jdetle - * 0.15.0 - - removed biopython PDB parser for coordinates and topology (Issue #777) - - Added test for weighted rmsd (issue #814) - - metadata update: link download_url to GitHub releases so that - Depsy recognizes contributors (issue #749) and added - @richardjgowers as maintainer - - a __version__ variable is now exposed; it is built by setup.py from the - AUTHORS file (Issue #784) - - Removed all bare assert (Issue #724) - - added tests for GRO format - - added tempdir module - -02/28/16 manuel.nuno.melo - * 0.14.0 - - - Added the cleanup plugin (--with-mda_cleanup) to delete offset files - after tests have run (Issue 669) - - Made memleak testing python 3 compliant (Issue 662). It may be a moot - point since python 3.4 now cleverly deals with leaks. - -01/16/16 orbeckst - * 0.13.0 - - - removed ez_setup.py - -09/07/2015 manuel.nuno.melo, orbeckst - * 0.11.0 - - - Overhaul of the test subsystem. - - Tests now implement nose plugins, as a submodule. (Issue 331) - Available plugins are: memleak testing, stderr capturing (for quieter - test runs), and proper knownfailure implementation (Issue 338). - - renamed test_selections.py to test_atomselections.py - - new test_selections.py is now testing MDAnalysis.selections - (Issue #353) - - Added new DIMS DCD test datafile - - Added tests for PSAnalysis - - Update TPR parser tests for Gromacs 5 - -02/11/2012 orbeckst, seb-buch - - * 0.7.5 - - - test package is now called 'MDAnalysisTests' - - tests AND data are now bundled together in MDAnalysisTests - - MDAnalysis and MDAnalysisTests packages MUST have the same - release number (they need to stay in sync); MDAnalysisTests - will NOT run if a release mismatch is detected - - see Issue #87 and - https://github.com/MDAnalysis/mdanalysis/wiki/UnitTests - - -09/07/2011 orbeckst - - * 0.7.4 - - - Split off test data trajectories and structures from - MDAnalaysis/tests/data into separate package. (Issue 28) - - - Numbering matches the earliest MDAnalysis release for which the data is - needed. Any later releases of MDAnalysis will also use these test data - unless a MDAnalysisTestData package with a higher release number is - available. From f9921e5ffc1ca306c708282288150b57ffd3c217 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Thu, 15 Nov 2018 13:49:18 +0000 Subject: [PATCH 09/23] Correct an error --- package/MDAnalysis/analysis/hbonds/wbridge_analysis.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index 2b98ea80c91..063998f58a1 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -14,6 +14,7 @@ # MDAnalysis: A Python package for the rapid analysis of molecular dynamics # simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th # Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy. +# doi: 10.25080/majora-629e541a-00e # # N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. @@ -967,10 +968,10 @@ def _single_frame(self): water_bridges_donors = water_bridges.select_atoms( 'name {0}'.format(' '.join(self.donors))) water_bridges_donors_h = {} - for i, d in enumerate(water_bridges_donors): + for j, d in enumerate(water_bridges_donors): tmp = self._get_bonded_hydrogens(d) if tmp: - water_bridges_donors_h[i] = tmp + water_bridges_donors_h[j] = tmp self.logger_debug("water bridge donors: {0}".format(len(water_bridges_donors))) self.logger_debug("water bridge donor hydrogens: {0}".format(len(water_bridges_donors_h))) water_bridges_acceptors = water_bridges.select_atoms( From a82026e628aef60a802f9d401fafa1271cc789f9 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Tue, 27 Nov 2018 15:34:44 +0000 Subject: [PATCH 10/23] Two tests were added. --- .../MDAnalysisTests/analysis/test_wbridge.py | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 3b08d00761b..64e3c059fce 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -803,6 +803,107 @@ def analysis(current, output): pass assert_equal(wb.count_by_time(analysis_func=analysis), [(0,0), ]) + def test_generate_table(self): + '''Test generate table''' + grofile = '''Test gro file +5 + 1ALA N 1 0.000 0.000 0.000 + 1ALA H 2 0.100 0.000 0.000 + 2SOL OW 3 0.300 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) + + # Build an dummy WaterBridgeAnalysis object for testing + wb._network = [] + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { + (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, + (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, + (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { + (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { + (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) + wb.timesteps = range(len(wb._network)) + wb.generate_table() + table = wb.table + assert_array_equal(sorted(wb.table.donor_resid), [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 4, 4, 5, 5, 6, 7]) + + def test_timesteps_by_type(self): + grofile = '''Test gro file +5 + 1ALA N 1 0.000 0.000 0.000 + 1ALA H 2 0.100 0.000 0.000 + 2SOL OW 3 0.300 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) + # Build an dummy WaterBridgeAnalysis object for testing + wb._network = [] + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { + (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, + (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, + (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { + (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { + (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) + wb.timesteps = range(len(wb._network)) + timesteps = sorted(wb.timesteps_by_type()) + assert_array_equal(timesteps[3], [0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 0, 1, 9]) + + def guess_types(names): """GRO doesn't supply types, this returns an Attr""" return Atomtypes(np.array([guess_atom_type(name) for name in names], dtype=object)) From 3f22767931d46281d3c14d74904ebc4bab9717f0 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Tue, 4 Dec 2018 14:38:54 +0000 Subject: [PATCH 11/23] Merge branch 'develop' of https://github.com/MDAnalysis/mdanalysis into new_wba --- package/.pylintrc | 1 - package/AUTHORS | 2 + package/CHANGELOG | 15 ++++- package/MDAnalysis/analysis/base.py | 4 +- package/MDAnalysis/coordinates/base.py | 12 ++-- package/MDAnalysis/core/groups.py | 12 +++- package/MDAnalysis/due.py | 4 +- package/MDAnalysis/lib/__init__.py | 2 +- package/MDAnalysis/selections/base.py | 6 +- package/MDAnalysis/topology/GSDParser.py | 10 ++-- package/MDAnalysis/topology/HoomdXMLParser.py | 2 +- package/setup.py | 12 +++- .../core/test_atomselections.py | 8 +++ .../MDAnalysisTests/data/example_bonds.gsd | Bin 0 -> 56612 bytes testsuite/MDAnalysisTests/datafiles.py | 3 +- .../MDAnalysisTests/topology/test_gsd.py | 52 ++++++++++++++++++ .../MDAnalysisTests/topology/test_hoomdxml.py | 21 +++++++ 17 files changed, 136 insertions(+), 30 deletions(-) create mode 100644 testsuite/MDAnalysisTests/data/example_bonds.gsd diff --git a/package/.pylintrc b/package/.pylintrc index da780ce8ac3..28aed865206 100644 --- a/package/.pylintrc +++ b/package/.pylintrc @@ -105,7 +105,6 @@ enable=abstract-class-instantiated, global-variable-not-assigned, global-variable-undefined, hex-method, - import-self, import-star-module-level, inconsistent-mro, inherit-non-class, diff --git a/package/AUTHORS b/package/AUTHORS index ab728bc6321..2a105fb520d 100644 --- a/package/AUTHORS +++ b/package/AUTHORS @@ -113,6 +113,8 @@ Chronological list of authors - Irfan Alibay - Philip Loche - Matthew W. Thompson + - Ali Ehlen + - Daniele Padula External code diff --git a/package/CHANGELOG b/package/CHANGELOG index d87a1095594..dcbc2fb51cd 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -13,14 +13,23 @@ The rules for this file: * release numbers follow "Semantic Versioning" http://semver.org ------------------------------------------------------------------------------ -mm/dd/yy +mm/dd/yy micaela-matta, xiki-tempula, zemanj, mattwthompson, orbeckst, aliehlen, + dpadula85 * 0.19.3 Fixes + * fixed error when reading bonds/angles/dihedrals from gsd file (Issue #2152, + PR #2154)) + * fixed error when reading bonds/angles/dihedrals from hoomdxml file + (Issue #2151, PR #2153)) + * fixed MacOS (XCode) library compatibility + (Issue #2144) * fixed lib.nsgrid segfault for coordinates very close to the box boundary (Issue #2132, PR #2136) - -Deprecations + * fixed Lint error + (Issue #2148, PR #2149) + * Fixed Empty select_atoms string, now raises warning and returns empty + AtomGroup (Issue #2112) 11/06/18 richardjgowers diff --git a/package/MDAnalysis/analysis/base.py b/package/MDAnalysis/analysis/base.py index 34bc818c6ab..590f1f0bdc5 100644 --- a/package/MDAnalysis/analysis/base.py +++ b/package/MDAnalysis/analysis/base.py @@ -159,14 +159,14 @@ def _single_frame(self): def _prepare(self): """Set things up before the analysis loop begins""" - pass + pass # pylint: disable=unnecessary-pass def _conclude(self): """Finalise the results you've gathered. Called at the end of the run() method to finish everything up. """ - pass + pass # pylint: disable=unnecessary-pass def run(self, start=None, stop=None, step=None, verbose=None): """Perform the calculation diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index 9400fd9ff43..d29b741fd75 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -26,7 +26,7 @@ Base classes --- :mod:`MDAnalysis.coordinates.base` =================================================== -Derive other Timestep, FrameIterator, Reader and Writer classes from the classes +Derive other Timestep, FrameIterator, Reader and Writer classes from the classes in this module. The derived classes must follow the :ref:`Trajectory API` in :mod:`MDAnalysis.coordinates.__init__`. @@ -122,11 +122,11 @@ Iterator classes used by the by the :class:`ProtoReader`. .. autoclass:: FrameIteratorBase - + .. autoclass:: FrameIteratorSliced - + .. autoclass:: FrameIteratorAll - + .. autoclass:: FrameIteratorIndices @@ -1326,7 +1326,7 @@ def convert_time_to_native(self, t, inplace=True): def close(self): """Close the trajectory file.""" - pass + pass # pylint: disable=unnecessary-pass def __enter__(self): return self @@ -1511,7 +1511,7 @@ def _reopen(self): Calling next after this should return the first frame """ - pass + pass # pylint: disable=unnecessary-pass def _apply_limits(self, frame): if frame < 0: diff --git a/package/MDAnalysis/core/groups.py b/package/MDAnalysis/core/groups.py index 299e21ad073..f1e10ab19a3 100644 --- a/package/MDAnalysis/core/groups.py +++ b/package/MDAnalysis/core/groups.py @@ -2365,7 +2365,15 @@ def select_atoms(self, sel, *othersel, **selgroups): .. versionchanged:: 0.19.0 Added strict type checking for passed groups. Added periodic kwarg (default True) + .. versionchanged:: 0.19.2 + Empty sel string now returns an empty Atom group. """ + + if not sel: + warnings.warn("Empty string to select atoms, empty group returned.", + UserWarning) + return self[[]] + # once flags removed, replace with default=True periodic = selgroups.pop('periodic', flags['use_periodic_selections']) @@ -2541,7 +2549,7 @@ def write(self, filename=None, file_format="PDB", """Write `AtomGroup` to a file. The output can either be a coordinate file or a selection, depending on - the format. + the format. Examples -------- @@ -2628,7 +2636,7 @@ def write(self, filename=None, file_format="PDB", raise ValueError( 'Cannot explicitely set "multiframe" to False and request ' 'more than 1 frame with the "frames" keyword argument.' - ) + ) elif multiframe is None: if frames is None: # By default we only write the current frame. diff --git a/package/MDAnalysis/due.py b/package/MDAnalysis/due.py index 9f5692f36cd..41724c0d886 100644 --- a/package/MDAnalysis/due.py +++ b/package/MDAnalysis/due.py @@ -34,7 +34,7 @@ class InactiveDueCreditCollector(object): """Just a stub at the Collector which would not do anything""" def _donothing(self, *args, **kwargs): """Perform no good and no bad""" - pass + pass # pylint: disable=unnecessary-pass def dcite(self, *args, **kwargs): """If I could cite I would""" @@ -50,7 +50,7 @@ def __repr__(self): def _donothing_func(*args, **kwargs): """Perform no good and no bad""" - pass + pass # pylint: disable=unnecessary-pass try: diff --git a/package/MDAnalysis/lib/__init__.py b/package/MDAnalysis/lib/__init__.py index c2d84bc88af..6ba64782d03 100644 --- a/package/MDAnalysis/lib/__init__.py +++ b/package/MDAnalysis/lib/__init__.py @@ -1,5 +1,5 @@ # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # # MDAnalysis --- https://www.mdanalysis.org # Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors diff --git a/package/MDAnalysis/selections/base.py b/package/MDAnalysis/selections/base.py index 433b4d93ec9..277e8f4b810 100644 --- a/package/MDAnalysis/selections/base.py +++ b/package/MDAnalysis/selections/base.py @@ -1,5 +1,5 @@ # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 # # MDAnalysis --- https://www.mdanalysis.org # Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors @@ -238,11 +238,11 @@ def _translate(self, atoms, **kwargs): def _write_head(self, out, **kwargs): """Initial output to open file object *out*.""" - pass + pass # pylint: disable=unnecessary-pass def _write_tail(self, out, **kwargs): """Last output to open file object *out*.""" - pass + pass # pylint: disable=unnecessary-pass # Context manager support to match Coordinate writers # all file handles use a with block in their write method, so these do nothing special diff --git a/package/MDAnalysis/topology/GSDParser.py b/package/MDAnalysis/topology/GSDParser.py index b1d4de9ee2d..d7bb216cc03 100644 --- a/package/MDAnalysis/topology/GSDParser.py +++ b/package/MDAnalysis/topology/GSDParser.py @@ -137,14 +137,14 @@ def parse(self, **kwargs): # set bonds, angles, dihedrals, impropers for attrname, attr, in ( - ('bond', Bonds), - ('angle', Angles), - ('dihedral', Dihedrals), - ('improper', Impropers), + ('bonds', Bonds), + ('angles', Angles), + ('dihedrals', Dihedrals), + ('impropers', Impropers), ): try: val = getattr(snap,attrname) - vals = val.group + vals = [tuple(b_instance) for b_instance in val.group] except: pass else: diff --git a/package/MDAnalysis/topology/HoomdXMLParser.py b/package/MDAnalysis/topology/HoomdXMLParser.py index 7816cc6fd63..95a36f75586 100644 --- a/package/MDAnalysis/topology/HoomdXMLParser.py +++ b/package/MDAnalysis/topology/HoomdXMLParser.py @@ -137,7 +137,7 @@ def parse(self, **kwargs): ): try: val = configuration.find(attrname) - vals = [(int(el) for el in line.split()[1:]) + vals = [tuple(int(el) for el in line.split()[1:]) for line in val.text.strip().split('\n') if line.strip()] except: diff --git a/package/setup.py b/package/setup.py index a84c3ff5654..19f93ee8771 100755 --- a/package/setup.py +++ b/package/setup.py @@ -259,9 +259,12 @@ def extensions(config): cpp_extra_compile_args = [a for a in extra_compile_args if 'std' not in a] cpp_extra_compile_args.append('-std=c++11') + cpp_extra_link_args=[] # needed to specify c++ runtime library on OSX if platform.system() == 'Darwin': cpp_extra_compile_args.append('-stdlib=libc++') + cpp_extra_compile_args.append('-mmacosx-version-min=10.9') + cpp_extra_link_args.append('-stdlib=libc++') # Needed for large-file seeking under 32bit systems (for xtc/trr indexing # and access). @@ -354,13 +357,15 @@ def extensions(config): libraries=mathlib, include_dirs=include_dirs + ['MDAnalysis/lib/include'], define_macros=define_macros, - extra_compile_args=cpp_extra_compile_args) + extra_compile_args=cpp_extra_compile_args, + extra_link_args= cpp_extra_link_args) augment = MDAExtension('MDAnalysis.lib._augment', sources=['MDAnalysis/lib/_augment' + cpp_source_suffix], language='c++', include_dirs=include_dirs, define_macros=define_macros, - extra_compile_args=cpp_extra_compile_args) + extra_compile_args=cpp_extra_compile_args, + extra_link_args= cpp_extra_link_args) encore_utils = MDAExtension('MDAnalysis.analysis.encore.cutils', @@ -387,7 +392,8 @@ def extensions(config): include_dirs=include_dirs, language='c++', define_macros=define_macros, - extra_compile_args=cpp_extra_compile_args) + extra_compile_args=cpp_extra_compile_args, + extra_link_args= cpp_extra_link_args) pre_exts = [libdcd, distances, distances_omp, qcprot, transformation, libmdaxdr, util, encore_utils, ap_clustering, spe_dimred, cutil, augment, nsgrid] diff --git a/testsuite/MDAnalysisTests/core/test_atomselections.py b/testsuite/MDAnalysisTests/core/test_atomselections.py index 78dbf15362c..1ead8746246 100644 --- a/testsuite/MDAnalysisTests/core/test_atomselections.py +++ b/testsuite/MDAnalysisTests/core/test_atomselections.py @@ -1030,3 +1030,11 @@ def test_arbitrary_atom_group_raises_error(): u = make_Universe(trajectory=True) with pytest.raises(TypeError): u.select_atoms('around 2.0 group this', this=u.atoms[0]) + + +def test_empty_sel(): + u = make_Universe(trajectory=True) + with pytest.warns(UserWarning): + ag = u.atoms.select_atoms("") + assert_equal(len(ag), 0) + assert isinstance(ag, mda.AtomGroup) diff --git a/testsuite/MDAnalysisTests/data/example_bonds.gsd b/testsuite/MDAnalysisTests/data/example_bonds.gsd new file mode 100644 index 0000000000000000000000000000000000000000..3aafb6df9e1eaf25b3ead41e513dd4c643463a97 GIT binary patch literal 56612 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 yFi(cbarw|;dl!z)}JTL$b*8}>F|Gp%$|w> literal 0 HcmV?d00001 diff --git a/testsuite/MDAnalysisTests/datafiles.py b/testsuite/MDAnalysisTests/datafiles.py index f29b4d1627e..970fe0afe58 100644 --- a/testsuite/MDAnalysisTests/datafiles.py +++ b/testsuite/MDAnalysisTests/datafiles.py @@ -157,7 +157,7 @@ "legacy_DCD_ADK_coords", # frames 5 and 29 read in for adk_dims.dcd using legacy DCD reader "legacy_DCD_NAMD_coords", # frame 0 read in for SiN_tric_namd.dcd using legacy DCD reader "legacy_DCD_c36_coords", # frames 1 and 4 read in for tip125_tric_C36.dcd using legacy DCD reader - "GSD", + "GSD", "GSD_bonds", "GRO_MEMPROT", "XTC_MEMPROT", # YiiP transporter in POPE:POPG lipids with Na+, Cl-, Zn2+ dummy model without water "DihedralArray", "DihedralsArray", # time series of single dihedral "RamaArray", "GLYRamaArray", # time series of phi/psi angles @@ -442,6 +442,7 @@ ALIGN_UNBOUND = resource_filename(__name__, 'data/analysis/align_unbound.pdb.gz') GSD = resource_filename(__name__, 'data/example.gsd') +GSD_bonds = resource_filename(__name__, 'data/example_bonds.gsd') DihedralArray = resource_filename(__name__, 'data/adk_oplsaa_dihedral.npy') DihedralsArray = resource_filename(__name__, 'data/adk_oplsaa_dihedral_list.npy') diff --git a/testsuite/MDAnalysisTests/topology/test_gsd.py b/testsuite/MDAnalysisTests/topology/test_gsd.py index fbe3a44fc46..44700694c37 100644 --- a/testsuite/MDAnalysisTests/topology/test_gsd.py +++ b/testsuite/MDAnalysisTests/topology/test_gsd.py @@ -28,6 +28,7 @@ from MDAnalysisTests.topology.base import ParserBase from MDAnalysisTests.datafiles import GSD +from MDAnalysisTests.datafiles import GSD_bonds from numpy.testing import assert_equal import os @@ -47,3 +48,54 @@ def test_attr_size(self, top): assert len(top.names) == top.n_atoms assert len(top.resids) == top.n_residues assert len(top.resnames) == top.n_residues + + +class TestGSDParserBonds(ParserBase): + parser = mda.topology.GSDParser.GSDParser + ref_filename = GSD_bonds + expected_attrs = ['ids', 'names', 'resids', 'resnames', 'masses'] + expected_n_atoms = 490 + expected_n_bonds = 441 + expected_n_angles = 392 + expected_n_dihedrals = 343 + expected_n_residues = 1 + expected_n_segments = 1 + + def test_attr_size(self, top): + assert len(top.ids) == top.n_atoms + assert len(top.names) == top.n_atoms + assert len(top.resids) == top.n_residues + assert len(top.resnames) == top.n_residues + + def test_atoms(self, top): + assert top.n_atoms == self.expected_n_atoms + + def test_bonds(self, top): + assert len(top.bonds.values) == self.expected_n_bonds + assert isinstance(top.bonds.values[0], tuple) + + def test_angles(self, top): + assert len(top.angles.values) == self.expected_n_angles + assert isinstance(top.angles.values[0], tuple) + + def test_dihedrals(self, top): + assert len(top.dihedrals.values) == self.expected_n_dihedrals + assert isinstance(top.angles.values[0], tuple) + + def test_bonds_identity(self, top): + vals = top.bonds.values + for b in ((0, 1), (1, 2), (2, 3), (3, 4)): + assert (b in vals) or (b[::-1] in vals) + assert ((0, 450) not in vals) + + def test_angles_identity(self, top): + vals = top.angles.values + for b in ((0, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5)): + assert (b in vals) or (b[::-1] in vals) + assert ((0, 350, 450) not in vals) + + def test_dihedrals_identity(self, top): + vals = top.dihedrals.values + for b in ((0, 1, 2, 3), (1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 6)): + assert (b in vals) or (b[::-1] in vals) + assert ((0, 250, 350, 450) not in vals) diff --git a/testsuite/MDAnalysisTests/topology/test_hoomdxml.py b/testsuite/MDAnalysisTests/topology/test_hoomdxml.py index 4821bacf2f1..b032c14e658 100644 --- a/testsuite/MDAnalysisTests/topology/test_hoomdxml.py +++ b/testsuite/MDAnalysisTests/topology/test_hoomdxml.py @@ -45,9 +45,30 @@ def test_attr_size(self, top): def test_bonds(self, top): assert len(top.bonds.values) == 704 + assert isinstance(top.bonds.values[0], tuple) def test_angles(self, top): assert len(top.angles.values) == 640 + assert isinstance(top.angles.values[0], tuple) def test_dihedrals(self, top): assert len(top.dihedrals.values) == 576 + assert isinstance(top.dihedrals.values[0], tuple) + + def test_bonds_identity(self, top): + vals = top.bonds.values + for b in ((0, 1), (1, 2), (2, 3), (3, 4)): + assert (b in vals) or (b[::-1] in vals) + assert ((0, 450) not in vals) + + def test_angles_identity(self, top): + vals = top.angles.values + for b in ((0, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5)): + assert (b in vals) or (b[::-1] in vals) + assert ((0, 350, 450) not in vals) + + def test_dihedrals_identity(self, top): + vals = top.dihedrals.values + for b in ((0, 1, 2, 3), (1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 6)): + assert (b in vals) or (b[::-1] in vals) + assert ((0, 250, 350, 450) not in vals) From 2d5576a88e36099e895d3702f5108178c6e272e1 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Thu, 6 Dec 2018 16:20:06 +0000 Subject: [PATCH 12/23] Update CHANGELOG --- package/CHANGELOG | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index dcbc2fb51cd..0de627e0a07 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -18,6 +18,8 @@ mm/dd/yy micaela-matta, xiki-tempula, zemanj, mattwthompson, orbeckst, aliehlen, * 0.19.3 Fixes + * Move the water bridge analysis to the new analysis class, add high + Order water bridge support. (PR #2087) * fixed error when reading bonds/angles/dihedrals from gsd file (Issue #2152, PR #2154)) * fixed error when reading bonds/angles/dihedrals from hoomdxml file @@ -29,7 +31,9 @@ Fixes * fixed Lint error (Issue #2148, PR #2149) * Fixed Empty select_atoms string, now raises warning and returns empty - AtomGroup (Issue #2112) + +Testsuite + * Add tests for the new water bridge analysis (PR #2087) 11/06/18 richardjgowers @@ -61,8 +65,6 @@ Deprecations * 0.19.0 Enhancements - * Move the water bridge analysis to the new analysis class, add high - Order water bridge support. (PR #2087) * Added bond/angle/dihedral reading in PARM7 TOPParser (PR #2052) * Replaced multiple apply (_apply_distmat, _apply_kdtree) methods in distance based selections with From f3f0013bb780e33f99a353230a9087f323f35a3f Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Thu, 6 Dec 2018 18:39:50 +0000 Subject: [PATCH 13/23] Crank up the test converage --- .../analysis/hbonds/wbridge_analysis.py | 7 +-- .../MDAnalysisTests/analysis/test_wbridge.py | 59 ++++++++++++++++++- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index 063998f58a1..8e4ba087780 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -680,9 +680,7 @@ def __init__(self, universe, selection1='protein', self.donors = tuple(set(self.DEFAULT_DONORS[forcefield]).union(donors)) self.acceptors = tuple(set(self.DEFAULT_ACCEPTORS[forcefield]).union(acceptors)) - if not (self.selection1 and self.selection2): - raise ValueError('HydrogenBondAnalysis: invalid selections') - elif self.selection1_type not in ('both', 'donor', 'acceptor'): + if self.selection1_type not in ('both', 'donor', 'acceptor'): raise ValueError('HydrogenBondAnalysis: Invalid selection type {0!s}'.format(self.selection1_type)) self._network = [] # final result accessed as self.network @@ -1284,10 +1282,11 @@ def generate_table(self): WaterBridgeAnalysis.table """ - if self._network is None: + if self._network == []: msg = "No data computed, do run() first." warnings.warn(msg, category=MissingDataWarning) logger.warning(msg) + self.table = None return if not hasattr(self, '_timeseries'): self.timeseries diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 64e3c059fce..67bb7de8334 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -26,6 +26,63 @@ def test_import_from_hbonds(): "MDAnalysis.analysis.hbonds failed.'") class TestWaterBridgeAnalysis(object): + def test_nodata(self): + grofile = '''Test gro file +3 + 1ALA N 1 0.000 0.000 0.000 + 1ALA H 2 0.100 0.000 0.000 + 4ALA O 3 0.300 0.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0) + wb.generate_table() + assert_equal(wb.table, None) + assert_equal(wb.timesteps_by_type(), None) + assert_equal(wb.count_by_time(), None) + assert_equal(wb.count_by_type(), None) + + def test_selection_type_error(self): + grofile = '''Test gro file +3 + 1ALA N 1 0.000 0.000 0.000 + 1ALA H 2 0.100 0.000 0.000 + 4ALA O 3 0.300 0.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + try: + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0, selection1_type='aaa') + except ValueError: + pass + else: + raise pytest.fail("selection_type aaa should rasie error") + + def test_empty_selection(self): + grofile = '''Test gro file +3 + 1ALA A 9 0.000 0.000 0.000 + 1ALA A 9 0.100 0.000 0.000 + 4ALA A 9 0.300 0.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0) + wb.run() + assert wb._network == [{}] + + def test_loop(self): + '''Test if loop can be handled correctly''' + grofile = '''Test gro file +5 + 1ALA O 1 0.000 0.001 0.000 + 2SOL OW 2 0.300 0.001 0.000 + 2SOL HW1 3 0.200 0.002 0.000 + 2SOL HW2 4 0.200 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 1 or resid 4)') + wb.run() + assert_equal(len(wb._network[0].keys()), 2) + def test_donor_accepter(self): '''Test zeroth order donor to acceptor hydrogen bonding''' grofile = '''Test gro file @@ -35,7 +92,7 @@ def test_donor_accepter(self): 4ALA O 3 0.300 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0) + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0, update_selection=True, debug=True) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('ALA', 4, 'O'))) From 2cc94e535c5f10775c33b4febd671fea0a32215a Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Tue, 18 Dec 2018 11:37:17 +0000 Subject: [PATCH 14/23] Updated according to comments --- package/.DS_Store | Bin 0 -> 10244 bytes .../analysis/hbonds/wbridge_analysis.py | 125 +- .../MDAnalysisTests/analysis/test_wbridge.py | 1085 ++++++----------- 3 files changed, 477 insertions(+), 733 deletions(-) create mode 100644 package/.DS_Store diff --git a/package/.DS_Store b/package/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a6c1cc9c120dc0c557ce2175ccc1e6d966db666b GIT binary patch literal 10244 zcmeHMZEO@p7=E9&!0rlmiwJjxgOgf5YJs+Vlp^5u3#fviEwl()?s_Y%UhnGdT?^D= z4gN4jMT3BX8ly&|U`$jr`bUjF{Qky>7)*@u`-lEB8jZd)vv*#P))PXL z_q-2~ZGx^1xpq)l*_5BDb`Nly!rfv(HD`ZNRA(DP5hdz^M@sv>8PaN|Q~oA^Z2&B0VwNNmW&Sh=RhRQKQFbnxPrvjLTcMCHh)Z z;Z)jb2q!m*+`4dTTg1I@OC)01`*37~ZS{4U6B-loRM?JNN!i$rT3lswyB&`v`Wh1H zc+`>4X;ltQ(>1-*^!N8KTvS~dteHQ5pfcEBy=d{G%3y76)xdzRO)9Tm*0QC0$Ikw1 z2X-A)!-6|CP;uTc4-Fk-%;{}ZXA}hoGGjcR8KZ8T;cvOnSlwZFCE~62POH-_c6f*_ zoL!f+dU-hQU5Qw_JMMIvC0*fIS2`9>S@p4)sO)s4tnDdrcbSt&rW$3%PSZ>~)}9XQcJHGWY4q7~AUuj!Oy`=Cfy)7Q@wvJ7ledRmmJSpi=MX zN_2PgcpUlWP`%%Og?RJou#;NbW5u~wukh>5Nxq5A+^dbkS09avK51p4St@=q0=g+A z#ZS{|0WGCo?x+E4e>VS19DrI+X}dYj&(_vtu&N?+4=^dtR5KhrN5jbco} zR7^tT@i(TFCr;8L_=JvLwyqOhchR>R7h@eRVFYf%<>5y@owCMhj2I#cP$yjDT^aRao5|LjK>o3tz0UQYS0>LqI!x`n5AL|IV>4Y z&VldgAQ+45sw#uku3*$G6oOIr*92qO(dI};ayTaXR+Jd4hMnC15gEyH-h|MM(rGUD z_!_uZ z!)t?{Rawc-xxBqtMVrZ@9@@?7a4YShL-Zg$PDkl^dYRr}eRzl7r4#fS^Yjb)lD?;3 z=vQVdU@%)J!H07&1G88qDli|5u>|L1DVlK+)-YGsqJudaK^H4UH+q?&LL+X#UhKnu z9Kd~e6o+vHkFiEPqga?#g?x6tFcJrI^r3w?RF*bRFjoFIs2m~;Gv$gV`ziB2-jMk| z7gB$y5?Nu$NI*W$m0-xVg*ism4whzwL8yvRUaIRX49ZttTA^h$!kAOa%bBod7Al{+ zmN7@ktikITvDh(-lvk`^7_;VR<*hdwbwMkWewT$m0Y0%bd5(c6mL#7r=)PsZ{m5V& zgAxh0$#PjTlL1$O^Dsv)OzIePVqwyZl~Np9(Z-7tAr1m~mK29;kVHy~!yvBDS)}a6 z9V`v^=0R_j%4bqMJQDQsBqGzmvt9(e2%PQ_C{!H`iu3>Gk^lcc-G|}z&x?Q;fxi&} z6g9OrHShy6=MnkO5@+o}{%zr(xJ11{Wn~ji= %.3f degrees", + logger.info("WaterBridgeAnalysis: criterion: angle D-H-A >= %.3f degrees", self.angle) - logger.info("WBridge analysis: force field %s to guess donor and \ + logger.info("WaterBridgeAnalysis: force field %s to guess donor and \ acceptor names", self.forcefield) def _update_selection(self): @@ -833,7 +851,7 @@ def logger_debug(self, *args): def _prepare(self): # The distance for selection is defined as twice the maximum bond length of an O-H bond (2A) # plus order of water bridge times the length of OH bond plus hydrogne bond distance - self.selection_distance = 1.0 * (2 * 2 + self.order * (2 + self.distance)) + self.selection_distance = (2 * 2 + self.order * (2 + self.distance)) self._s1_donors = {} self._s1_donors_h = {} @@ -853,19 +871,19 @@ def _prepare(self): self.timesteps = [] if self._s1 and self._s2: self._update_water_selection() - logger.info("WBridge analysis: no atoms found in the selection.") + logger.info("WaterBridgeAnalysis: no atoms found in the selection.") - logger.info("WBridge analysis: initial checks passed.") + logger.info("WaterBridgeAnalysis: initial checks passed.") - logger.info("WBridge analysis: starting") - logger.debug("WBridge analysis: donors %r", self.donors) - logger.debug("WBridge analysis: acceptors %r", self.acceptors) - logger.debug("WBridge analysis: water bridge %r", self.water_selection) + logger.info("WaterBridgeAnalysis: starting") + logger.debug("WaterBridgeAnalysis: donors %r", self.donors) + logger.debug("WaterBridgeAnalysis: acceptors %r", self.acceptors) + logger.debug("WaterBridgeAnalysis: water bridge %r", self.water_selection) if self.debug: logger.debug("Toggling debug to %r", self.debug) else: - logger.debug("WBridge analysis: For full step-by-step debugging output use debug=True") + logger.debug("WaterBridgeAnalysis: For full step-by-step debugging output use debug=True") logger.info("Starting analysis (frame index start=%d stop=%d, step=%d)", self.start, self.stop, self.step) @@ -1017,6 +1035,17 @@ def _single_frame(self): next_round_water.add((a_resname, a_resid)) # solve the connectivity network + # The following code attempt to generate a water network which is formed by the class dict. + # Suppose we have a water bridge connection ARG1 to ASP3 via the two hydrogen bonds. + # [0,1,('ARG',1,'O'), ('SOL',2,'HW1'), 3.0,180], + # [2,3,('SOL',2,'HW2'),('ASP',3,'OD1'), 3.0,180], + # The resulting network will be + #{(0,1,('ARG',1,'O'), ('SOL',2,'HW1'), 3.0,180): {(2,3,('SOL',2,'HW2'),('ASP',3,'OD1'), 3.0,180): None}} + # Where the key of the a dict will be all the hydrogen bonds starting from this nodes. + # The corresponding value of a certain key will be a dictionary whose key will be all the hydrogen bonds from + # the destination of in the key. + # If the value of a certain key is None, which means it is reaching selection 2. + result = {'start': defaultdict(dict), 'water': defaultdict(dict)} def add_route(result, route): @@ -1050,7 +1079,6 @@ def traverse_water_network(graph, node, end, route, maxdepth, result): new_node = new_node[3][:2] traverse_water_network(graph, new_node, end, new_route, maxdepth, result) for s1 in selection_1: - route = [s1, ] next_mol = s1[3][:2] traverse_water_network(water_pool, next_mol, selection_2, route[:], self.order, result) @@ -1058,34 +1086,37 @@ def traverse_water_network(graph, node, end, route, maxdepth, result): self._network.append(result['start']) def _traverse_water_network(self, graph, current, analysis_func=None, output=None, link_func=None, **kwargs): + ''' + This function recursively traverses the water network self._network and finds the hydrogen bonds which connect + the current atom to the next atom. The newly found hydrogen bond will be appended to the hydrogen bonds + connecting the selection 1 to the current atom via link_func. When selection 2 is reached, the full list of + hydrogen bonds connecting the selection 1 to selection 2 will be fed into analysis_func, which will then modify + the output in place. + :param graph: The connection network describes the connection between the atoms in the water network. + :param current: The hydrogen bonds from selection 1 until now. + :param analysis_func: The analysis function which is called to analysis the hydrogen bonds. + :param output: where the result is stored. + :param link_func: The new hydrogen bonds will be appended to current. + :param kwargs: the keywords which are passed into the analysis_func. + :return: + ''' if link_func is None: + # If no link_func is provided, the default link_func will be used link_func = self._full_link if graph is None: + # if selection 2 is reached if not analysis_func is None: + # the result is analysed by analysis_func which will change the output analysis_func(current, output, **kwargs) else: # make sure no loop can occur if len(current) <= self.order: for node in graph: + # the new hydrogen bond will be added to the existing bonds new = link_func(current, node) self._traverse_water_network(graph[node], new, analysis_func, output, link_func, **kwargs) - @staticmethod - def _reformat_hb(hb, atomformat="{0[0]!s}{0[1]!s}:{0[2]!s}"): - """Convert 0.16.1 _timeseries hbond item to 0.16.0 hbond item. - In 0.16.1, donor and acceptor are stored as a tuple(resname, - resid, atomid). In 0.16.0 and earlier they were stored as a string. - .. deprecated:: 1.0 - This is a compatibility layer so that we can provide the same output - in timeseries as before. However, for 1.0 we should make timeseries - just return _timeseries, i.e., change the format of timeseries to - the un-ambiguous representation provided in _timeseries. - """ - return (list(hb[:2]) - + [atomformat.format(hb[2]), atomformat.format(hb[3])] - + list(hb[4:])) - @property def timeseries(self): r'''Time series of water bridges. @@ -1109,6 +1140,12 @@ def timeseries(self): w.run() timeseries = w.timeseries + + The output format of :attr:`WaterBridgeAnalysis.timeseries` has changed + so that it has the same format as + :attr:`~HydrogenBondAnalysis.timeseries`. + + .. versionchanged:: 0.20.0 ''' def analysis(current, output): @@ -1119,12 +1156,16 @@ def analysis(current, output): new_frame = [] self._traverse_water_network(frame, [], analysis_func=analysis, output=new_frame, link_func=self._full_link) timeseries.append(new_frame) - self._timeseries = timeseries - # after 1.0 will be return the unformated _timeseries - return [[self._reformat_hb(hb) for hb in hframe] for hframe in self._timeseries] + return timeseries @classmethod def _full_link(self, output, node): + ''' + A function used in _traverse_water_network to add the new hydrogen bond to the existing bonds. + :param output: The existing hydrogen bonds from selection 1 + :param node: The new hydrogen bond + :return: The hydrogen bonds from selection 1 with the new hydrogen bond added + ''' result = output[:] result.append(node) return result @@ -1313,4 +1354,4 @@ def generate_table(self): cursor += 1 assert cursor == num_records, "Internal Error: Not all HB records stored" self.table = out.view(np.recarray) - logger.debug("WBridge: Stored results as table with %(num_records)d entries.", vars()) \ No newline at end of file + logger.debug("WBridge: Stored results as table with %(num_records)d entries.", vars()) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 67bb7de8334..ca09194d688 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -26,22 +26,25 @@ def test_import_from_hbonds(): "MDAnalysis.analysis.hbonds failed.'") class TestWaterBridgeAnalysis(object): - def test_nodata(self): + @staticmethod + @pytest.fixture(scope='class') + def universe_empty(): + '''A universe with no hydrogen bonds''' grofile = '''Test gro file -3 +5 1ALA N 1 0.000 0.000 0.000 1ALA H 2 0.100 0.000 0.000 - 4ALA O 3 0.300 0.000 0.000 + 2SOL OW 3 3.000 0.000 0.000 + 4ALA H 4 0.500 0.000 0.000 + 4ALA N 5 0.600 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0) - wb.generate_table() - assert_equal(wb.table, None) - assert_equal(wb.timesteps_by_type(), None) - assert_equal(wb.count_by_time(), None) - assert_equal(wb.count_by_type(), None) + return u - def test_selection_type_error(self): + @staticmethod + @pytest.fixture(scope='class') + def universe_DA(): + '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond donor''' grofile = '''Test gro file 3 1ALA N 1 0.000 0.000 0.000 @@ -49,70 +52,25 @@ def test_selection_type_error(self): 4ALA O 3 0.300 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - try: - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0, selection1_type='aaa') - except ValueError: - pass - else: - raise pytest.fail("selection_type aaa should rasie error") - - def test_empty_selection(self): - grofile = '''Test gro file -3 - 1ALA A 9 0.000 0.000 0.000 - 1ALA A 9 0.100 0.000 0.000 - 4ALA A 9 0.300 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0) - wb.run() - assert wb._network == [{}] - - def test_loop(self): - '''Test if loop can be handled correctly''' - grofile = '''Test gro file -5 - 1ALA O 1 0.000 0.001 0.000 - 2SOL OW 2 0.300 0.001 0.000 - 2SOL HW1 3 0.200 0.002 0.000 - 2SOL HW2 4 0.200 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 1 or resid 4)') - wb.run() - assert_equal(len(wb._network[0].keys()), 2) + return u - def test_donor_accepter(self): - '''Test zeroth order donor to acceptor hydrogen bonding''' + @staticmethod + @pytest.fixture(scope='class') + def universe_DA_PBC(): + '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond donor but in a PBC condition''' grofile = '''Test gro file -3 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 4ALA O 3 0.300 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0, update_selection=True, debug=True) - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('ALA', 4, 'O'))) - - def test_donor_accepter_pbc(self): - '''Test zeroth order donor to acceptor hydrogen bonding''' - grofile = '''Test gro file 3 1ALA N 1 0.800 0.000 0.000 1ALA H 2 0.900 0.000 0.000 4ALA O 3 0.100 0.000 0.000 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0, pbc=True) - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('ALA', 4, 'O'))) + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + return u - def test_accepter_donor(self): - '''Test zeroth order acceptor to donor hydrogen bonding''' + @staticmethod + @pytest.fixture(scope='class') + def universe_AD(): + '''A universe with one hydrogen bond donor bonding to a hydrogen bond acceptor''' grofile = '''Test gro file 3 1ALA O 1 0.000 0.000 0.000 @@ -120,34 +78,28 @@ def test_accepter_donor(self): 4ALA N 3 0.300 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=0) - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 1, ('ALA', 1, 'O'), ('ALA', 4, 'H'))) + return u - def test_acceptor_water_accepter(self): - '''Test case where the hydrogen bond acceptor from selection 1 form - water bridge with hydrogen bond acceptor from selection 2''' + @staticmethod + @pytest.fixture(scope='class') + def universe_loop(): + '''A universe with one hydrogen bond acceptor bonding to a water which bonds back to the first hydrogen bond + acceptor and thus form a loop''' grofile = '''Test gro file 5 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 + 1ALA O 1 0.000 0.001 0.000 + 2SOL OW 2 0.300 0.001 0.000 + 2SOL HW1 3 0.200 0.002 0.000 + 2SOL HW2 4 0.200 0.000 0.000 4ALA O 5 0.600 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) - assert_equal(second[list(second.keys())[0]], None) + return u - def test_donor_water_accepter(self): - '''Test case where the hydrogen bond donor from selection 1 form - water bridge with hydrogen bond acceptor from selection 2''' + @staticmethod + @pytest.fixture(scope='class') + def universe_DWA(): + '''A universe with one hydrogen bond donor bonding to a hydrogen bond acceptor through a water''' grofile = '''Test gro file 5 1ALA N 1 0.000 0.000 0.000 @@ -157,37 +109,12 @@ def test_donor_water_accepter(self): 4ALA O 5 0.600 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'))) - second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) - assert_equal(second[list(second.keys())[0]], None) - - def test_acceptor_water_donor(self): - '''Test case where the hydrogen bond acceptor from selection 1 form - water bridge with hydrogen bond donor from selection 2''' - grofile = '''Test gro file -5 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 4ALA H 4 0.500 0.000 0.000 - 4ALA N 5 0.600 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'))) - assert_equal(second[list(second.keys())[0]], None) + return u - def test_donor_water_donor(self): - '''Test case where the hydrogen bond donor from selection 1 form - water bridge with hydrogen bond donor from selection 2''' + @staticmethod + @pytest.fixture(scope='class') + def universe_DWD(): + '''A universe with one hydrogen bond donor bonding to a hydrogen bond donor through a water''' grofile = '''Test gro file 5 1ALA N 1 0.000 0.000 0.000 @@ -197,50 +124,42 @@ def test_donor_water_donor(self): 4ALA N 5 0.600 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'))) - second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'))) - assert_equal(second[list(second.keys())[0]], None) + return u - def test_empty(self): - '''Test case where no water bridge exists''' + @staticmethod + @pytest.fixture(scope='class') + def universe_AWA(): + '''A universe with two hydrogen bond acceptor are joined by a water''' grofile = '''Test gro file 5 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 3.000 0.000 0.000 - 4ALA H 4 0.500 0.000 0.000 - 4ALA N 5 0.600 0.000 0.000 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein', 'protein') - wb.run(verbose=False) - assert_equal(wb._network[0], defaultdict(dict)) + return u - def test_same_selection(self): - ''' - This test tests that if the selection 1 and selection 2 are both protein. - However, the protein only forms one hydrogen bond with the water. - This entry won't be included. - :return: - ''' + @staticmethod + @pytest.fixture(scope='class') + def universe_AWD(): + '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond donor through a water''' grofile = '''Test gro file -3 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 0.300 0.000 0.000 +5 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 4ALA H 4 0.500 0.000 0.000 + 4ALA N 5 0.600 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein', 'protein') - wb.run(verbose=False) - assert_equal(wb._network[0], defaultdict(dict)) + return u - def test_acceptor_2water_accepter(self): - '''Test case where the hydrogen bond acceptor from selection 1 form second order - water bridge with hydrogen bond acceptor from selection 2''' + @staticmethod + @pytest.fixture(scope='class') + def universe_AWWA(): + '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through two waters''' grofile = '''Test gro file 7 1ALA O 1 0.000 0.000 0.000 @@ -252,36 +171,14 @@ def test_acceptor_2water_accepter(self): 4ALA O 7 0.900 0.000 0.000 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - # test first order - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') - wb.run(verbose=False) - assert_equal(wb._network[0], defaultdict(dict)) - # test second order - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=2) - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) - third = second[list(second.keys())[0]] - assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) - assert_equal(third[list(third.keys())[0]], None) - # test third order - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=3) - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) - third = second[list(second.keys())[0]] - assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) - assert_equal(third[list(third.keys())[0]], None) + return u - def test_acceptor_3water_accepter(self): - '''Test case where the hydrogen bond acceptor from selection 1 form third order - water bridge with hydrogen bond acceptor from selection 2''' + @staticmethod + @pytest.fixture(scope='class') + def universe_AWWWA(): + '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through three waters''' grofile = '''Test gro file -9 +11 1ALA O 1 0.000 0.000 0.000 2SOL OW 2 0.300 0.000 0.000 2SOL HW1 3 0.200 0.000 0.000 @@ -290,78 +187,292 @@ def test_acceptor_3water_accepter(self): 3SOL HW1 6 0.700 0.000 0.000 4SOL OW 7 0.900 0.000 0.000 4SOL HW1 8 1.000 0.000 0.000 - 5ALA O 9 1.200 0.000 0.000 + 5SOL OW 9 1.200 0.000 0.000 + 5SOL HW1 10 1.300 0.000 0.000 + 6ALA O 11 1.400 0.000 0.000 10.0 10.0 10.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 5)', order=2) - wb.run(verbose=False) - assert_equal(wb._network[0], defaultdict(dict)) - - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 5)', order=3) - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) - third = second[list(second.keys())[0]] - assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) - fourth = third[list(third.keys())[0]] - assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'))) - assert_equal(fourth[list(fourth.keys())[0]], None) - - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 5)', order=4) - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) - third = second[list(second.keys())[0]] - assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) - fourth = third[list(third.keys())[0]] - assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'))) - assert_equal(fourth[list(fourth.keys())[0]], None) + return u - def test_acceptor_4water_accepter(self): - '''Test case where the hydrogen bond acceptor from selection 1 form fourth order - water bridge with hydrogen bond acceptor from selection 2''' + @staticmethod + @pytest.fixture(scope='class') + def universe_branch(): + '''A universe with one hydrogen bond acceptor bonding to two hydrogen bond acceptor in selection 2''' grofile = '''Test gro file -11 +9 1ALA O 1 0.000 0.000 0.000 2SOL OW 2 0.300 0.000 0.000 2SOL HW1 3 0.200 0.000 0.000 2SOL HW2 4 0.400 0.000 0.000 3SOL OW 5 0.600 0.000 0.000 3SOL HW1 6 0.700 0.000 0.000 - 4SOL OW 7 0.900 0.000 0.000 - 4SOL HW1 8 1.000 0.000 0.000 - 5SOL OW 9 1.200 0.000 0.000 - 5SOL HW1 10 1.300 0.000 0.000 - 6ALA O 11 1.400 0.000 0.000 - 10.0 10.0 10.0''' + 3SOL HW2 7 0.600 0.100 0.000 + 4ALA O 8 0.900 0.000 0.000 + 5ALA O 9 0.600 0.300 0.000 + 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 6)', order=3) - wb.run(verbose=False) - assert_equal(wb._network[0], defaultdict(dict)) - - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 6)', order=4) - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) - third = second[list(second.keys())[0]] - assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) - fourth = third[list(third.keys())[0]] - assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'))) - fifth = fourth[list(fourth.keys())[0]] - assert_equal(list(fifth.keys())[0][:4], (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'))) - assert_equal(fifth[list(fifth.keys())[0]], None) + return u - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 6)', order=5) - wb.run(verbose=False) - network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - second = network[list(network.keys())[0]] + @staticmethod + @pytest.fixture(scope='class') + def universe_AWA_AWWA(): + '''A universe with one hydrogen bond acceptors are bonded through one or two water''' + grofile = '''Test gro file +12 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 5ALA O 6 0.000 1.000 0.000 + 6SOL OW 7 0.300 1.000 0.000 + 6SOL HW1 8 0.200 1.000 0.000 + 6SOL HW2 9 0.400 1.000 0.000 + 7SOL OW 10 0.600 1.000 0.000 + 7SOL HW1 11 0.700 1.000 0.000 + 8ALA O 12 0.900 1.000 0.000 + 1.0 1.0 1.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + return u + + @staticmethod + @pytest.fixture(scope='class') + def wb_multiframe(universe_DWA): + '''A water bridge object with multipley frames''' + wb = WaterBridgeAnalysis(universe_DWA, 'protein and (resid 1)', 'protein and (resid 4)', order=4) + # Build an dummy WaterBridgeAnalysis object for testing + wb._network = [] + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { + (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { + (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { + (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { + (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, + (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { + (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, + (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { + (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { + (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) + wb.timesteps = range(len(wb._network)) + return wb + + def test_nodata(self, universe_DA): + '''Test if the funtions can run when there is no data. + This is achieved by not runing the run() first.''' + wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', 'protein and (resid 4)', order=0) + wb.generate_table() + assert_equal(wb.table, None) + assert_equal(wb.timesteps_by_type(), None) + assert_equal(wb.count_by_time(), None) + assert_equal(wb.count_by_type(), None) + + def test_selection_type_error(self, universe_DA): + '''Test the case when the wrong selection1_type is given''' + try: + wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', 'protein and (resid 4)', order=0, selection1_type='aaa') + except ValueError: + pass + else: + raise pytest.fail("selection_type aaa should rasie error") + + def test_empty_selection(self, universe_DA): + '''Test the case when selection yields empty result''' + wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 9)', 'protein and (resid 10)', order=0) + wb.run() + assert wb._network == [{}] + + def test_loop(self, universe_loop): + '''Test if loop can be handled correctly''' + wb = WaterBridgeAnalysis(universe_loop, 'protein and (resid 1)', 'protein and (resid 1 or resid 4)') + wb.run() + assert_equal(len(wb._network[0].keys()), 2) + + def test_donor_accepter(self, universe_DA): + '''Test zeroth order donor to acceptor hydrogen bonding''' + wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', 'protein and (resid 4)', order=0, update_selection=True, debug=True) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('ALA', 4, 'O'))) + + def test_donor_accepter_pbc(self, universe_DA_PBC): + '''Test zeroth order donor to acceptor hydrogen bonding in PBC conditions''' + wb = WaterBridgeAnalysis(universe_DA_PBC, 'protein and (resid 1)', 'protein and (resid 4)', order=0, pbc=True) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('ALA', 4, 'O'))) + + def test_accepter_donor(self, universe_AD): + '''Test zeroth order acceptor to donor hydrogen bonding''' + wb = WaterBridgeAnalysis(universe_AD, 'protein and (resid 1)', 'protein and (resid 4)', order=0) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 1, ('ALA', 1, 'O'), ('ALA', 4, 'H'))) + + def test_acceptor_water_accepter(self, universe_AWA): + '''Test case where the hydrogen bond acceptor from selection 1 form + water bridge with hydrogen bond acceptor from selection 2''' + wb = WaterBridgeAnalysis(universe_AWA, 'protein and (resid 1)', 'protein and (resid 4)') + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) + assert_equal(second[list(second.keys())[0]], None) + + def test_donor_water_accepter(self, universe_DWA): + '''Test case where the hydrogen bond donor from selection 1 form + water bridge with hydrogen bond acceptor from selection 2''' + wb = WaterBridgeAnalysis(universe_DWA, 'protein and (resid 1)', 'protein and (resid 4)') + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) + assert_equal(second[list(second.keys())[0]], None) + + def test_acceptor_water_donor(self, universe_AWD): + '''Test case where the hydrogen bond acceptor from selection 1 form + water bridge with hydrogen bond donor from selection 2''' + wb = WaterBridgeAnalysis(universe_AWD, 'protein and (resid 1)', 'protein and (resid 4)') + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'))) + assert_equal(second[list(second.keys())[0]], None) + + def test_donor_water_donor(self, universe_DWD): + '''Test case where the hydrogen bond donor from selection 1 form + water bridge with hydrogen bond donor from selection 2''' + wb = WaterBridgeAnalysis(universe_DWD, 'protein and (resid 1)', 'protein and (resid 4)') + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'))) + assert_equal(second[list(second.keys())[0]], None) + + def test_empty(self, universe_empty): + '''Test case where no water bridge exists''' + wb = WaterBridgeAnalysis(universe_empty, 'protein', 'protein') + wb.run(verbose=False) + assert_equal(wb._network[0], defaultdict(dict)) + + def test_same_selection(self, universe_DWA): + ''' + This test tests that if the selection 1 and selection 2 are both protein. + However, the protein only forms one hydrogen bond with the water. + This entry won't be included. + ''' + wb = WaterBridgeAnalysis(universe_DWA, 'protein and resid 1', 'protein and resid 1') + wb.run(verbose=False) + assert_equal(wb._network[0], defaultdict(dict)) + + def test_acceptor_2water_accepter(self, universe_AWWA): + '''Test case where the hydrogen bond acceptor from selection 1 form second order + water bridge with hydrogen bond acceptor from selection 2''' + # test first order + wb = WaterBridgeAnalysis(universe_AWWA, 'protein and (resid 1)', 'protein and (resid 4)') + wb.run(verbose=False) + assert_equal(wb._network[0], defaultdict(dict)) + # test second order + wb = WaterBridgeAnalysis(universe_AWWA, 'protein and (resid 1)', 'protein and (resid 4)', order=2) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + third = second[list(second.keys())[0]] + assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) + assert_equal(third[list(third.keys())[0]], None) + # test third order + wb = WaterBridgeAnalysis(universe_AWWA, 'protein and (resid 1)', 'protein and (resid 4)', order=3) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + third = second[list(second.keys())[0]] + assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) + assert_equal(third[list(third.keys())[0]], None) + + def test_acceptor_3water_accepter(self, universe_AWWWA): + '''Test case where the hydrogen bond acceptor from selection 1 form third order + water bridge with hydrogen bond acceptor from selection 2''' + wb = WaterBridgeAnalysis(universe_AWWWA, 'protein and (resid 1)', 'protein and (resid 5)', order=2) + wb.run(verbose=False) + assert_equal(wb._network[0], defaultdict(dict)) + + wb = WaterBridgeAnalysis(universe_AWWWA, 'protein and (resid 1)', 'protein and (resid 5)', order=3) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + third = second[list(second.keys())[0]] + assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) + fourth = third[list(third.keys())[0]] + assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'))) + assert_equal(fourth[list(fourth.keys())[0]], None) + + wb = WaterBridgeAnalysis(universe_AWWWA, 'protein and (resid 1)', 'protein and (resid 5)', order=4) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + third = second[list(second.keys())[0]] + assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) + fourth = third[list(third.keys())[0]] + assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'))) + assert_equal(fourth[list(fourth.keys())[0]], None) + + def test_acceptor_4water_accepter(self, universe_AWWWWA): + '''Test case where the hydrogen bond acceptor from selection 1 form fourth order + water bridge with hydrogen bond acceptor from selection 2''' + wb = WaterBridgeAnalysis(universe_AWWWWA, 'protein and (resid 1)', 'protein and (resid 6)', order=3) + wb.run(verbose=False) + assert_equal(wb._network[0], defaultdict(dict)) + + wb = WaterBridgeAnalysis(universe_AWWWWA, 'protein and (resid 1)', 'protein and (resid 6)', order=4) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] + assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + third = second[list(second.keys())[0]] + assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) + fourth = third[list(third.keys())[0]] + assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'))) + fifth = fourth[list(fourth.keys())[0]] + assert_equal(list(fifth.keys())[0][:4], (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'))) + assert_equal(fifth[list(fifth.keys())[0]], None) + + wb = WaterBridgeAnalysis(universe_AWWWWA, 'protein and (resid 1)', 'protein and (resid 6)', order=5) + wb.run(verbose=False) + network = wb._network[0] + assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + second = network[list(network.keys())[0]] assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) third = second[list(second.keys())[0]] assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) @@ -371,24 +482,11 @@ def test_acceptor_4water_accepter(self): assert_equal(list(fifth.keys())[0][:4], (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'))) assert_equal(fifth[list(fifth.keys())[0]], None) - def test_acceptor_22water_accepter(self): + def test_acceptor_22water_accepter(self, universe_branch): '''Test case where the hydrogen bond acceptor from selection 1 form a second order water bridge with hydrogen bond acceptor from selection 2 and the last water is linked to two residues in selection 2''' - grofile = '''Test gro file -9 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 3SOL OW 5 0.600 0.000 0.000 - 3SOL HW1 6 0.700 0.000 0.000 - 3SOL HW2 7 0.600 0.100 0.000 - 4ALA O 8 0.900 0.000 0.000 - 5ALA O 9 0.600 0.300 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) + wb = WaterBridgeAnalysis(universe_branch, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) @@ -398,22 +496,9 @@ def test_acceptor_22water_accepter(self): assert_equal([(5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O')), (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'))], sorted([key[:4] for key in list(third.keys())])) - def test_timeseries(self): + def test_timeseries(self, universe_branch): '''Test if the time series data is correctly generated''' - grofile = '''Test gro file -9 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 3SOL OW 5 0.600 0.000 0.000 - 3SOL HW1 6 0.700 0.000 0.000 - 3SOL HW2 7 0.600 0.100 0.000 - 4ALA O 8 0.900 0.000 0.000 - 5ALA O 9 0.600 0.300 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) + wb = WaterBridgeAnalysis(universe_branch, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) wb.run(verbose=False) wb.timeseries timeseries = sorted(wb._timeseries[0]) @@ -424,25 +509,9 @@ def test_timeseries(self): assert_equal(timeseries[4][:4], (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) assert_equal(timeseries[5][:4], (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'))) - def test_acceptor_12water_accepter(self): + def test_acceptor_12water_accepter(self, universe_AWA_AWWA): '''Test of independent first order and second can be recognised correctely''' - grofile = '''Test gro file -12 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 5ALA O 6 0.000 1.000 0.000 - 6SOL OW 7 0.300 1.000 0.000 - 6SOL HW1 8 0.200 1.000 0.000 - 6SOL HW2 9 0.400 1.000 0.000 - 7SOL OW 10 0.600 1.000 0.000 - 7SOL HW1 11 0.700 1.000 0.000 - 8ALA O 12 0.900 1.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=1) + wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=1) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) @@ -450,165 +519,51 @@ def test_acceptor_12water_accepter(self): assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) assert_equal(second[list(second.keys())[0]], None) network = wb._network[0] - wb = WaterBridgeAnalysis(u, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) + wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) wb.run(verbose=False) network = wb._network[0] assert_equal([(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1')), (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'))], sorted([key[:4] for key in list(network.keys())])) - def test_count_by_type_single_link(self): + def test_count_by_type_single_link(self, universe_DWA): ''' This test tests the simplest water bridge to see if count_by_type() works. - :return: ''' - grofile = '''Test gro file -5 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 0.300 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)') + wb = WaterBridgeAnalysis(universe_DWA, 'protein and (resid 1)', 'protein and (resid 4)') wb.run(verbose=False) assert_equal(wb.count_by_type(), [(1, 4, 'ALA', 1, 'H', 'ALA', 4, 'O', 1.)]) - def test_count_by_type_multiple_link(self): + def test_count_by_type_multiple_link(self, universe_AWA_AWWA): ''' This test tests if count_by_type() can give the correct result for more than 1 links. - :return: ''' - grofile = '''Test gro file -12 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 5ALA O 6 0.000 1.000 0.000 - 6SOL OW 7 0.300 1.000 0.000 - 6SOL HW1 8 0.200 1.000 0.000 - 6SOL HW2 9 0.400 1.000 0.000 - 7SOL OW 10 0.600 1.000 0.000 - 7SOL HW1 11 0.700 1.000 0.000 - 8ALA O 12 0.900 1.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) + wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) wb.run(verbose=False) assert_equal(sorted(wb.count_by_type()), [[0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 1.0], [5, 11, 'ALA', 5, 'O', 'ALA', 8, 'O', 1.0]]) - def test_count_by_type_multiple_frame(self): + def test_count_by_type_multiple_frame(self, wb_multiframe): ''' This test tests if count_by_type() works in multiply situations. :return: ''' - grofile = '''Test gro file -5 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 0.300 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) - # Build an dummy WaterBridgeAnalysis object for testing - wb._network = [] - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { - (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, - (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, - (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { - (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { - (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) result = [(0, 3, 'ALA', 1, 'O', 'ALA', 4, 'H', 0.1), (0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 0.3), - (0, 6, 'ALA', 1, 'O', 'ALA', 4, 'O', 0.1), - (0, 7, 'ALA', 1, 'O', 'ALA', 4, 'O', 0.1), - (0, 8, 'ALA', 1, 'O', 'ALA', 5, 'O', 0.2), - (0, 10, 'ALA', 1, 'O', 'ALA', 6, 'O', 0.1), - (1, 3, 'ALA', 1, 'H', 'ALA', 4, 'H', 0.1), - (1, 4, 'ALA', 1, 'H', 'ALA', 4, 'O', 0.1), - (5, 11, 'ALA', 5, 'O', 'ALA', 8, 'O', 0.1)] - assert_equal(sorted(wb.count_by_type()), result) - - def test_count_by_type_filter(self): - ''' - This test tests if modifying analysis_func - allows some results to be filtered out in count_by_type(). - :return: - ''' - grofile = '''Test gro file -5 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 0.300 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) - # Build an dummy WaterBridgeAnalysis object for testing - wb._network = [] - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { - (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, - (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, - (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { - (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { - (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) + (0, 6, 'ALA', 1, 'O', 'ALA', 4, 'O', 0.1), + (0, 7, 'ALA', 1, 'O', 'ALA', 4, 'O', 0.1), + (0, 8, 'ALA', 1, 'O', 'ALA', 5, 'O', 0.2), + (0, 10, 'ALA', 1, 'O', 'ALA', 6, 'O', 0.1), + (1, 3, 'ALA', 1, 'H', 'ALA', 4, 'H', 0.1), + (1, 4, 'ALA', 1, 'H', 'ALA', 4, 'O', 0.1), + (5, 11, 'ALA', 5, 'O', 'ALA', 8, 'O', 0.1)] + assert_equal(sorted(wb_multiframe.count_by_type()), result) + def test_count_by_type_filter(self, wb_multiframe): + ''' + This test tests if modifying analysis_func + allows some results to be filtered out in count_by_type(). + :return: + ''' def analysis(current, output): s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ current[0] @@ -619,57 +574,13 @@ def analysis(current, output): output[key] += 1 result = [((0, 3, 'ALA', 1, 'O', 'ALA', 4, 'H'), 0.1), ((1, 3, 'ALA', 1, 'H', 'ALA', 4, 'H'), 0.1)] - assert_equal(sorted(wb.count_by_type(analysis_func=analysis)), result) + assert_equal(sorted(wb_multiframe.count_by_type(analysis_func=analysis)), result) - def test_count_by_type_merge(self): + def test_count_by_type_merge(self, wb_multiframe): ''' This test tests if modifying analysis_func allows some same residue to be merged in count_by_type(). - :return: ''' - grofile = '''Test gro file -5 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 0.300 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) - # Build an dummy WaterBridgeAnalysis object for testing - wb._network = [] - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { - (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, - (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, - (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { - (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { - (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) def analysis(current, output): s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ current[0] @@ -681,57 +592,14 @@ def analysis(current, output): (('ALA', 1, 'ALA', 5), 0.2), (('ALA', 1, 'ALA', 6), 0.1), (('ALA', 5, 'ALA', 8), 0.1)] - assert_equal(sorted(wb.count_by_type(analysis_func=analysis)), result) + assert_equal(sorted(wb_multiframe.count_by_type(analysis_func=analysis)), result) - def test_count_by_type_order(self): + def test_count_by_type_order(self, wb_multiframe): ''' This test tests if modifying analysis_func allows the order of water bridge to be separated in count_by_type(). :return: ''' - grofile = '''Test gro file -5 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 0.300 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) - # Build an dummy WaterBridgeAnalysis object for testing - wb._network = [] - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { - (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, - (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, - (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { - (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { - (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) def analysis(current, output): s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ current[0] @@ -745,84 +613,23 @@ def analysis(current, output): (('ALA', 1, 'ALA', 5, 3), 0.1), (('ALA', 1, 'ALA', 6, 4), 0.1), (('ALA', 5, 'ALA', 8, 2), 0.1)] - assert_equal(sorted(wb.count_by_type(analysis_func=analysis)), result) + assert_equal(sorted(wb_multiframe.count_by_type(analysis_func=analysis)), result) - def test_count_by_time(self): + def test_count_by_time(self, wb_multiframe): ''' This test tests if count_by_times() works. :return: ''' - grofile = '''Test gro file -5 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 0.300 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) - - # Build an dummy WaterBridgeAnalysis object for testing - wb._network = [] - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { - (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, - (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, - (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { - (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { - (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) - wb.timesteps = range(len(wb._network)) - assert_equal(wb.count_by_time(), [(0,1), (1,1), (2,1), (3,1), (4,1), (5,1), (6,1), (7,1), (8,2), (9,2)]) + assert_equal(wb_multiframe.count_by_time(), [(0,1), (1,1), (2,1), (3,1), (4,1), (5,1), (6,1), (7,1), (8,2), (9,2)]) - def test_count_by_time_weight(self): + def test_count_by_time_weight(self, universe_AWA_AWWA): ''' This test tests if modyfing the analysis_func allows the weight to be changed in count_by_type(). :return: ''' - grofile = '''Test gro file -12 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 5ALA O 6 0.000 1.000 0.000 - 6SOL OW 7 0.300 1.000 0.000 - 6SOL HW1 8 0.200 1.000 0.000 - 6SOL HW2 9 0.400 1.000 0.000 - 7SOL OW 10 0.600 1.000 0.000 - 7SOL HW1 11 0.700 1.000 0.000 - 8ALA O 12 0.900 1.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) + wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) wb.run(verbose=False) def analysis(current, output): s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ @@ -833,131 +640,27 @@ def analysis(current, output): output[key] += len(current)-1 assert_equal(wb.count_by_time(analysis_func=analysis), [(0,3), ]) - def test_count_by_time_empty(self): + def test_count_by_time_empty(self, universe_AWA_AWWA): ''' See if count_by_type() can handle zero well. :return: ''' - grofile = '''Test gro file -12 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 5ALA O 6 0.000 1.000 0.000 - 6SOL OW 7 0.300 1.000 0.000 - 6SOL HW1 8 0.200 1.000 0.000 - 6SOL HW2 9 0.400 1.000 0.000 - 7SOL OW 10 0.600 1.000 0.000 - 7SOL HW1 11 0.700 1.000 0.000 - 8ALA O 12 0.900 1.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) + wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) wb.run(verbose=False) def analysis(current, output): pass assert_equal(wb.count_by_time(analysis_func=analysis), [(0,0), ]) - def test_generate_table(self): + def test_generate_table(self, wb_multiframe): '''Test generate table''' - grofile = '''Test gro file -5 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 0.300 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) - - # Build an dummy WaterBridgeAnalysis object for testing - wb._network = [] - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { - (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, - (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, - (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { - (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { - (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) - wb.timesteps = range(len(wb._network)) - wb.generate_table() - table = wb.table - assert_array_equal(sorted(wb.table.donor_resid), [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, + wb_multiframe.generate_table() + table = wb_multiframe.table + assert_array_equal(sorted(wb_multiframe.table.donor_resid), [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 5, 5, 6, 7]) - def test_timesteps_by_type(self): - grofile = '''Test gro file -5 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 0.300 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) - # Build an dummy WaterBridgeAnalysis object for testing - wb._network = [] - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { - (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, - (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, - (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { - (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { - (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) - wb.timesteps = range(len(wb._network)) - timesteps = sorted(wb.timesteps_by_type()) + def test_timesteps_by_type(self, wb_multiframe): + '''Test the timesteps_by_type function''' + timesteps = sorted(wb_multiframe.timesteps_by_type()) assert_array_equal(timesteps[3], [0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 0, 1, 9]) From 1051c0c520041deef4742fc3bb84f366bbbfc3e2 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Tue, 18 Dec 2018 12:30:01 +0000 Subject: [PATCH 15/23] Fix the timeseries --- package/MDAnalysis/analysis/hbonds/wbridge_analysis.py | 4 +--- testsuite/MDAnalysisTests/analysis/test_wbridge.py | 5 ++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index f4f01ab1399..e79cff1345d 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -1329,9 +1329,7 @@ def generate_table(self): logger.warning(msg) self.table = None return - if not hasattr(self, '_timeseries'): - self.timeseries - timeseries = self._timeseries + timeseries = self.timeseries num_records = np.sum([len(hframe) for hframe in timeseries]) # build empty output table diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index ca09194d688..25fe0ace9a6 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -500,8 +500,7 @@ def test_timeseries(self, universe_branch): '''Test if the time series data is correctly generated''' wb = WaterBridgeAnalysis(universe_branch, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) wb.run(verbose=False) - wb.timeseries - timeseries = sorted(wb._timeseries[0]) + timeseries = sorted(wb.timeseries[0]) assert_equal(timeseries[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) assert_equal(timeseries[1][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) assert_equal(timeseries[2][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) @@ -739,7 +738,7 @@ def test_acceptor_OC1_OC2(self): u = MDAnalysis.Universe(StringIO(gro), format="gro") h = WaterBridgeAnalysis(u, 'protein', 'protein', order=0) h.run(verbose=False) - assert h.timeseries[0][0][2] == 'ALA2:H1' + assert h.timeseries[0][0][2] == ('ALA',2,'H1') def test_true_traj(self): u = MDAnalysis.Universe(GRO, XTC) From ab7ed0c8c202c08fa55bc0fa2bdaeecafe84fdc6 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Tue, 18 Dec 2018 13:05:09 +0000 Subject: [PATCH 16/23] remove tests from hydrogen bond analysis --- .../MDAnalysisTests/analysis/test_wbridge.py | 266 ++---------------- 1 file changed, 28 insertions(+), 238 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 25fe0ace9a6..e967857ff44 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -2,20 +2,13 @@ from six import StringIO from collections import defaultdict -import numpy as np from numpy.testing import ( - assert_equal, assert_array_equal, assert_almost_equal, - assert_array_almost_equal, assert_allclose,) + assert_equal, assert_array_equal,) import pytest import MDAnalysis import MDAnalysis.analysis.hbonds from MDAnalysis.analysis.hbonds.wbridge_analysis import WaterBridgeAnalysis -from MDAnalysisTests.datafiles import PDB_helix, GRO, XTC, waterPSF, waterDCD - -# For type guessing: -from MDAnalysis.topology.core import guess_atom_type -from MDAnalysis.core.topologyattrs import Atomtypes def test_import_from_hbonds(): try: @@ -161,15 +154,17 @@ def universe_AWD(): def universe_AWWA(): '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through two waters''' grofile = '''Test gro file -7 +9 1ALA O 1 0.000 0.000 0.000 2SOL OW 2 0.300 0.000 0.000 2SOL HW1 3 0.200 0.000 0.000 2SOL HW2 4 0.400 0.000 0.000 3SOL OW 5 0.600 0.000 0.000 3SOL HW1 6 0.700 0.000 0.000 - 4ALA O 7 0.900 0.000 0.000 - 1.0 1.0 1.0''' + 4SOL OW 7 0.900 0.000 0.000 + 4SOL HW1 8 1.000 0.000 0.000 + 5ALA O 9 1.200 0.000 0.000 + 10.0 10.0 10.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') return u @@ -194,6 +189,27 @@ def universe_AWWWA(): u = MDAnalysis.Universe(StringIO(grofile), format='gro') return u + @staticmethod + @pytest.fixture(scope='class') + def universe_AWWWWA(): + '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through three waters''' + grofile = '''Test gro file +11 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 3SOL OW 5 0.600 0.000 0.000 + 3SOL HW1 6 0.700 0.000 0.000 + 4SOL OW 7 0.900 0.000 0.000 + 4SOL HW1 8 1.000 0.000 0.000 + 5SOL OW 9 1.200 0.000 0.000 + 5SOL HW1 10 1.300 0.000 0.000 + 6ALA O 11 1.400 0.000 0.000 + 10.0 10.0 10.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + return u + @staticmethod @pytest.fixture(scope='class') def universe_branch(): @@ -660,230 +676,4 @@ def test_generate_table(self, wb_multiframe): def test_timesteps_by_type(self, wb_multiframe): '''Test the timesteps_by_type function''' timesteps = sorted(wb_multiframe.timesteps_by_type()) - assert_array_equal(timesteps[3], [0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 0, 1, 9]) - - -def guess_types(names): - """GRO doesn't supply types, this returns an Attr""" - return Atomtypes(np.array([guess_atom_type(name) for name in names], dtype=object)) - - -class TestHydrogenBondAnalysis(object): - @staticmethod - @pytest.fixture(scope='class') - def universe(): - return MDAnalysis.Universe(PDB_helix) - - @staticmethod - @pytest.fixture(scope='class') - def values(universe): - return { - 'num_bb_hbonds': universe.atoms.n_residues - universe.select_atoms('resname PRO').n_residues - 4, - 'donor_resid': np.array([5, 6, 8, 9, 10, 11, 12, 13]), - 'acceptor_resnm': np.array(['ALA', 'ALA', 'ALA', 'ALA', 'ALA', 'PRO', 'ALA', 'ALA'], dtype='U4'), - } - - kwargs = { - 'selection1': 'protein', - 'selection2': 'protein', - 'detect_hydrogens': "distance", - 'distance': 3.0, - 'angle': 150.0, - } - - @pytest.fixture(scope='class') - def h(self, universe): - kw = self.kwargs.copy() - # kw.update(kwargs) - h = MDAnalysis.analysis.hbonds.WaterBridgeAnalysis(universe, order=0, **kw) - # remove in 1.0 - if kw['detect_hydrogens'] == 'heuristic': - with pytest.warns(DeprecationWarning): - h.run(verbose=False) - else: - h.run(verbose=False) - return h - - def test_helix_backbone(self, values, h): - assert len(h.timeseries[0]) == values['num_bb_hbonds'], "wrong number of backbone hydrogen bonds" - assert h.timesteps, [0.0] - - def test_generate_table(self, values, h): - - h.generate_table() - assert len(h.table) == values['num_bb_hbonds'], "wrong number of backbone hydrogen bonds in table" - assert isinstance(h.table, np.core.records.recarray) - h.table.sort(order='donor_resid') - assert_array_equal(h.table.donor_resid, values['donor_resid']) - assert_array_equal(h.table.acceptor_resnm, values['acceptor_resnm']) - - def test_atoms_too_far(self): - pdb = ''' -ATOM 1 N LEU 1 32.310 13.778 14.372 1.00 0.00 SYST N 0 -ATOM 2 OW SOL 2 3.024 4.456 4.147 1.00 0.00 SYST H 0''' - - u = MDAnalysis.Universe(StringIO(pdb), format="pdb") - h = WaterBridgeAnalysis(u, 'resname SOL', 'protein', order=0) - h.run(verbose=False) - assert h.timeseries == [[]] - - def test_acceptor_OC1_OC2(self): - gro = '''test -3 - 1ALA OC1 1 0.000 0.000 0.000 - 2ALA N 2 0.300 0.000 0.000 - 2ALA H1 3 0.200 0.000 0.000 -7.29748 7.66094 9.82962''' - - u = MDAnalysis.Universe(StringIO(gro), format="gro") - h = WaterBridgeAnalysis(u, 'protein', 'protein', order=0) - h.run(verbose=False) - assert h.timeseries[0][0][2] == ('ALA',2,'H1') - - def test_true_traj(self): - u = MDAnalysis.Universe(GRO, XTC) - u.add_TopologyAttr(guess_types(u.atoms.names)) - h = WaterBridgeAnalysis(u, 'protein', 'resname ASP', distance=3.0, angle=120.0, order=0) - h.run() - assert len(h.timeseries) == 10 - - def test_count_by_time(self, values, h): - - c = h.count_by_time() - assert c, [(0.0, values['num_bb_hbonds'])] - - def test_count_by_type(self, values, h): - - c = h.count_by_type() - assert_equal([line[-1] for line in c][:8], values['num_bb_hbonds'] * [1.0]) - - def test_timesteps_by_type(self, values, h): - - t = h.timesteps_by_type() - assert_equal([i[-1] for i in t][:8], values['num_bb_hbonds'] * [0.0]) - - -class TestHydrogenBondAnalysisPBC(TestHydrogenBondAnalysis): - # This system is identical to above class - # But has a box introduced, and atoms moved into neighbouring images - # The results however should remain identical if PBC is used - # If pbc:True in kwargs is changed, these tests should fail - @staticmethod - @pytest.fixture(scope='class') - def universe(): - u = MDAnalysis.Universe(PDB_helix) - # transfer to memory to changes to coordinates are reset - u.transfer_to_memory() - # place in huge oversized box - # real system is about 30A wide at most - boxsize = 150. - box = np.array([boxsize, boxsize, boxsize, 90., 90., 90.]) - u.dimensions = box - - # then scatter the atoms throughout various images of this box - u.atoms[::4].translate([boxsize * 2, 0, 0]) - u.atoms[1::4].translate([0, boxsize * 4, 0]) - u.atoms[2::4].translate([-boxsize * 5, 0, -boxsize * 2]) - - return u - - kwargs = { - 'selection1': 'protein', - 'selection2': 'protein', - 'detect_hydrogens': "distance", - 'distance': 3.0, - 'angle': 150.0, - 'pbc': True, - } - -class TestHydrogenBondAnalysisTIP3P(object): - @staticmethod - @pytest.fixture() - def universe(): - return MDAnalysis.Universe(waterPSF, waterDCD) - - kwargs = { - 'selection1': 'all', - 'selection2': 'all', - 'detect_hydrogens': "distance", - 'distance': 3.0, - 'angle': 120.0, - } - - @pytest.fixture() - def h(self, universe): - h = WaterBridgeAnalysis(universe, order=0, **self.kwargs) - h.run(verbose=False) - h.generate_table() - return h - - @pytest.fixture() - def normalized_timeseries(self, h): - # timeseries in normalized form: (t, d_indx1, a_indx1, d_index0, a_index0, donor, acceptor, dist, angle) - # array index: 0 1 2 3 4 5 6 7 8 - timeseries = [[t] + item - for t, hframe in zip(h.timesteps, h.timeseries) - for item in hframe] - return timeseries - - # keys are the names in the h.table - reference = { - 'distance': {'mean': 2.0208776, 'std': 0.31740859}, - 'angle': {'mean': 155.13521, 'std': 12.98955}, - } - - @pytest.fixture() - def reference_table(self, normalized_timeseries): - # reference values for the table only - return { - 'donor_resnm': ["TIP3"] * len(normalized_timeseries), - 'acceptor_resnm': ["TIP3"] * len(normalized_timeseries), - } - - # index into timeseries (ADJUST ONCE donor_idx and acceptor_ndx are removed) - # with keys being field names in h.table - columns = { - 'time': 0, - 'donor_index': 1, - 'acceptor_index': 2, - 'distance': 5, - 'angle': 6, - } - - # hackish way to allow looping over self.reference and generating tests - _functions = { - 'mean': np.mean, - 'std': np.std, - } - - def test_timeseries(self, h, normalized_timeseries): - h = h - assert len(h.timeseries) == 10 - assert len(normalized_timeseries) == 29 - - for observable in self.reference: - idx = self.columns[observable] - for quantity, reference in self.reference[observable].items(): - func = self._functions[quantity] - assert_allclose( - func([item[idx] for item in normalized_timeseries]), reference, - rtol=1e-5, atol=0, - err_msg="{quantity}({observable}) does not match reference".format(**vars()) - ) - - def test_table_atoms(self, h, normalized_timeseries, reference_table): - h = h - table = h.table - - assert len(h.table) == len(normalized_timeseries) - - # test that timeseries and table agree on index data and - # hydrogen bond information at atom level - for name, idx in self.columns.items(): - assert_array_almost_equal(table.field(name), [data[idx] for data in normalized_timeseries], - err_msg="table[{name}] and timeseries[{idx} do not agree".format(**vars())) - - # test at residue level (issue #801 - # https://github.com/MDAnalysis/mdanalysis/issues/801) - for name, ref in reference_table.items(): - assert_array_equal(h.table.field(name), ref, err_msg="resname for {0} do not match (Issue #801)") \ No newline at end of file + assert_array_equal(timesteps[3], [0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 0, 1, 9]) \ No newline at end of file From 160d564e33d15149c7923d6e36f87f751ed60fdc Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Tue, 18 Dec 2018 13:06:21 +0000 Subject: [PATCH 17/23] a bit of error --- testsuite/MDAnalysisTests/analysis/test_wbridge.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index e967857ff44..f177eec2824 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -154,17 +154,15 @@ def universe_AWD(): def universe_AWWA(): '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through two waters''' grofile = '''Test gro file -9 +7 1ALA O 1 0.000 0.000 0.000 2SOL OW 2 0.300 0.000 0.000 2SOL HW1 3 0.200 0.000 0.000 2SOL HW2 4 0.400 0.000 0.000 3SOL OW 5 0.600 0.000 0.000 3SOL HW1 6 0.700 0.000 0.000 - 4SOL OW 7 0.900 0.000 0.000 - 4SOL HW1 8 1.000 0.000 0.000 - 5ALA O 9 1.200 0.000 0.000 - 10.0 10.0 10.0''' + 4ALA O 7 0.900 0.000 0.000 + 1.0 1.0 1.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') return u @@ -173,7 +171,7 @@ def universe_AWWA(): def universe_AWWWA(): '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through three waters''' grofile = '''Test gro file -11 +9 1ALA O 1 0.000 0.000 0.000 2SOL OW 2 0.300 0.000 0.000 2SOL HW1 3 0.200 0.000 0.000 @@ -182,9 +180,7 @@ def universe_AWWWA(): 3SOL HW1 6 0.700 0.000 0.000 4SOL OW 7 0.900 0.000 0.000 4SOL HW1 8 1.000 0.000 0.000 - 5SOL OW 9 1.200 0.000 0.000 - 5SOL HW1 10 1.300 0.000 0.000 - 6ALA O 11 1.400 0.000 0.000 + 5ALA O 9 1.200 0.000 0.000 10.0 10.0 10.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') return u From 34b04e902b985e5a84a071fa12e0facb607b9094 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Thu, 10 Jan 2019 10:52:30 +0000 Subject: [PATCH 18/23] Add a brief theory session --- package/MDAnalysis/.DS_Store | Bin 0 -> 8196 bytes .../analysis/hbonds/wbridge_analysis.py | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 package/MDAnalysis/.DS_Store diff --git a/package/MDAnalysis/.DS_Store b/package/MDAnalysis/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0c39fb668a3f076b9650623d4941abfa76d604b0 GIT binary patch literal 8196 zcmeI1O;8*~6oB6Yl5~>*1H$sVy1F75H_8%(P=wU75CY{-qRU?(3bQ+tFy$~qc4i@f zq_)&!qE-GK)Y7s%SOza1I9OI$p42iI%c3gA(u)TVUc7p+qOYfCDZxGN(d-VYI~LMD8fm(qK6P~)WlBt;6~ z73xzS;Mqicneb&^O3#$8DYFOkP0>#=P@0oIB9@a(_%bi0GzXOCfd0I>xGc*Zt!a~5eTGpd=%(PF*NG*ia1?o7D)C0Fz&9XspxdkZHFvK4dF zN|mrbJ?|Q}<&5cBha~4~hiV$`i?$wV?>gSMQC41|@G4$CzF;^;+SErE4b7cBt2!Co z(hTduB*n~fjq`?{<(0*ye8f`cbk8bO8>(kYluhK)#>JfBE^R2~6=jW0A%0Gy6Fy~Y z!b(%t8TT}irch`~X!^4{wHjk`^X5DVIJd(!$)4A8z{GWjK`Ux@ljJv z>!zIQ;GIl)f{!mqx8!6u?_re~;}aQsZjR3Fcsuu=2!);(J3py9?&*2mqEmfK+l%ui z=~QQhu20j%nOHQ@DsOKz(Iz{J_=MC$3{JoxOhSf`UW6-f6<&pR;3HUr&)^IA8oq(= z;5vK{H{mDv8GeD^;1Bon6d%VV zz)76K44OEH^LP=L@nw7+-^91@ZTtW~!E3mNpW-@xDNj~ZAaYoa3oU*{y&Vr95jpP@ zIldc_VIs2SKOyqwA%S6K)jicU_eNV|@y?#a<{jL2<8EVXD^4Ci5)coZ0vHn5P;Fg( zLn9B55>oo5?T{DXR}Khx*hYPAT__w0KfnkRMGkp$m@r*u3QBEr_z}jGq78B3Baeo; zQcMhUZ`gj6hn0PVEAxh3$2eC?h-axi-b>p3M6T5KB}oh7$>O`9w24sjOw%)&JGp1Q7@#uq6UmGLRZb(!^dbevxZOsLoIoMXbw9>6uXDrQ;;M lbe!boABNPA&{AQO@MT^~nxXQq{}2#-|AX(pN8a7t`~&6R$}Io@ literal 0 HcmV?d00001 diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index e79cff1345d..9cfe331fcb4 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -69,6 +69,16 @@ - *forcefield* to switch between default values for different force fields - *donors* and *acceptors* atom types (to add additional atom names) +Theory +------ + +This module attempts to find multi-order water bridge by an approach similar +to breadth-first search, where the first solvation shell of selection 1 is +selected, followed by the selection of the second solvation shell as well as +any hydrogen bonding partner from selection 1. After that, the third solvation +shell, as well as any binding partners from selection 2, are detected. This +process is repeated until the maximum order of water bridge is reached. + .. _wb_Analysis_Output: Output From 61a3762dd20cde83018051398d25435a6ad00055 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Thu, 10 Jan 2019 12:20:57 +0000 Subject: [PATCH 19/23] Update the doc to make it more clear --- .../analysis/hbonds/wbridge_analysis.py | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index 9cfe331fcb4..add10b5f0ad 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -237,6 +237,18 @@ class WaterBridgeAnalysis_OtherFF(WaterBridgeAnalysis): bridge can be customised by supplying an analsysis function to :meth:`~WaterBridgeAnalysis.count_by_type`. :: +The analysis function has two parameters. The current and the output. The current is a list +of hydrogen bonds from selection 1 to selection 2, and an example will be :: + +[[0,1,('ARG',1,'O'), ('SOL',2,'HW1'), 3.0,180], + [2,3,('SOL',2,'HW2'),('ASP',3,'OD1'), 3.0,180],] + +Where current[0] is the first hydrogen bond originating from selection 1 and current[-1] is +the final hydrogen bond ending in selection 2. The output sums up all the information in the +current frame and is a dictionary with a user-defined key and the value is the weight of the +corresponding key. At the end of the analysis, the keys from all the frames are collected +and the corresponding values will be summed up and returned. + def analysis(current, output): '''This function defines how the type of water bridge should be specified. @@ -247,9 +259,7 @@ def analysis(current, output): selection 1 to selection 2. output : dict A dictionary where the key is the type of the water bridge and the value - is the number of this type of water bridge. - Acknowledging the new water bridge, this variable will be *modified* in place - so that this function is not retruning any variable. + is the weight of this type of water bridge. ''' # decompose the first hydrogen bond. @@ -275,9 +285,14 @@ def analysis(current, output): Note that the result is arranged in the format of (key, proportion of time). When no custom analysis function is supplied, the key is expended for backward compatibility. + Some people might only interested in contacts between residues and pay no attention -to the details regarding the atom name. This can also be achieved by supplying an analysis -function to :meth:`~WaterBridgeAnalysis.count_by_type`. :: +to the details regarding the atom name. However, since multiple water bridges can +exist between two residues, which sometimes can give a result such that the water +bridge between two residues exists 300% of the time. Though this might be a desirable +result for some people, others might want the water bridge between two residues to be +only counted once per frame. This can also be achieved by supplying an analysis function +to :meth:`~WaterBridgeAnalysis.count_by_type`. :: def analysis(current, output): '''This function defines how the type of water bridge should be specified. @@ -289,9 +304,7 @@ def analysis(current, output): selection 1 to selection 2. output : dict A dictionary where the key is the type of the water bridge and the value - is the number of this type of water bridge. - Acknowledging the new water bridge, this variable will be *modified* in place - so that this function is not retruning any variable. + is the weight of this type of water bridge. ''' s1_index, to_index, (s1_resname, s1_resid, s1_name), @@ -300,7 +313,9 @@ def analysis(current, output): (s2_resname, s2_resid, s2_name), dist, angle = current[-1] # s1_name and s2_name are not included in the key key = (s1_resname, s1_resid, s2_resname, s2_resid) - output[key] += 1 + + # Each residue is only counted once per frame + output[key] = 1 w.count_by_type(analysis_func=analysis) @@ -322,9 +337,7 @@ def analysis(current, output): selection 1 to selection 2. output : dict A dictionary where the key is the type of the water bridge and the value - is the number of this type of water bridge. - Acknowledging the new water bridge, this variable will be *modified* in place - so that this function is not retruning any variable. + is the weight of this type of water bridge. ''' s1_index, to_index, (s1_resname, s1_resid, s1_name), @@ -359,8 +372,6 @@ def analysis(current, output): output : dict A dictionary where the key is the type of the water bridge and the value is the number of this type of water bridge. - Acknowledging the new water bridge, this variable will be *modified* in place - so that this function is not retruning any variable. ''' s1_index, to_index, (s1_resname, s1_resid, s1_name), @@ -427,9 +438,6 @@ def analysis(current, output, **kwargs): output : dict A dictionary where the key is the type of the water bridge and the value is the number of this type of water bridge. - Acknowledging the new water bridge, this variable will be *modified* in place - so that this function is not retruning any variable. - The output of this frame is the sum of all the values in this dictionary. ''' # decompose the first hydrogen bond. @@ -1225,8 +1233,11 @@ def count_by_type(self, analysis_func=None, **kwargs): length = len(self._network) result_dict = defaultdict(int) for frame in self._network: - self._traverse_water_network(frame, [], analysis_func=analysis_func, output=result_dict, + frame_dict = defaultdict(int) + self._traverse_water_network(frame, [], analysis_func=analysis_func, output=frame_dict, link_func=self._full_link, **kwargs) + for key, value in frame_dict.items(): + result_dict[key] += frame_dict[key] if output is 'combined': result = [[i for i in key] for key in result_dict] From b5791c5b42a40683304ba3d06644b68925c82748 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Thu, 10 Jan 2019 14:50:28 +0000 Subject: [PATCH 20/23] minor update --- package/MDAnalysis/analysis/hbonds/wbridge_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index add10b5f0ad..ab499fdd6c9 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -989,7 +989,7 @@ def _single_frame(self): for i in range(self.order): # Narrow down the water selection selection_resn_id = list(next_round_water) - if not selection_resn_id: + if (not selection_resn_id) or (not self._water): self._network.append(defaultdict(dict)) logger.warning("No water forming hydrogen bonding with selection 1.") return From f20a904c8c123162f37bb5fbcb0924a4d7d3f605 Mon Sep 17 00:00:00 2001 From: xiki-tempula Date: Sun, 7 Apr 2019 01:49:39 +0100 Subject: [PATCH 21/23] Update the doc and the underlying data --- .../analysis/hbonds/wbridge_analysis.py | 519 ++++++++++++------ .../MDAnalysisTests/analysis/test_wbridge.py | 274 ++++----- 2 files changed, 480 insertions(+), 313 deletions(-) diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index ab499fdd6c9..7f25eb60bae 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -58,7 +58,7 @@ e.g. -CO\ :sub:`2`\ :sup:`-`:···H−O:···H−O:···HN- (where H−O is part of **H−O**\ −H) -The following keyword arguments are important to control the behavior of the +The following keyword arguments are important to control the behaviour of the water bridge analysis: - *water_selection* (``resname SOL``): the selection string for the bridging @@ -77,60 +77,83 @@ selected, followed by the selection of the second solvation shell as well as any hydrogen bonding partner from selection 1. After that, the third solvation shell, as well as any binding partners from selection 2, are detected. This -process is repeated until the maximum order of water bridge is reached. +process is repeated until the maximum order of water bridges is reached. -.. _wb_Analysis_Output: +.. _wb_Analysis_Network: -Output ------- - -The results are a list of hydrogen bonds between the selection 1 or selection 2 -and the bridging waters. +Output as Network +----------------- -Each list is formated similar to the \ :attr:`HydrogenBondAnalysis.timeseries -` -and contains +Since the waters connecting the two ends of the selections are by nature a network. +We provide a network representation of the water network. Water bridge data are +returned per frame, which is stored in :attr:`WaterBridgeAnalysis.network`. Each +frame is represented as a dictionary, where the keys are the hydrogen bonds +originating from selection 1 and the values are new dictionaries representing +the hydrogen bonds coming out of the corresponding molecules making hydrogen bonds +with selection 1. + +As for the hydrogen bonds which reach the selection 2, the values of the +corresponding keys are None. One example where selection 1 and selection 2 are +joined by one water molecule (A) which also hydrogen bond to another water (B) +which also hydrogen bond to selection 2 would be represented as :: + + # (selection 1)-O:···H-O(water 1):···H-(selection 2) + # | : + # H·············O-H(water2) + # H + {(sele1_acceptor, None, water1_donor, water1_donor_heavy, distance, angle): + {(water1_acceptor, None, sele2_donor, sele2_donor_heavy, distance, angle): None}, + {(water1_donor, water1_donor_heavy, water2_acceptor, None, distance, angle): + {(water2_acceptor, None, sele2_donor, sele2_donor_heavy, distance, angle): None} + }, + } + +The atoms are represented by atom index and if the atom is hydrogen bond donor, +it is followed by the index of the corresponding heavy atom +``(donor_proton, donor_heavy_atom)``. +If the atom is a hydrogen bond acceptor, it is followed by none. + +.. _wb_Analysis_Timeseries: + +Output as Timeseries +-------------------- + +For lower order water bridges, it might be desirable to represent the connections as +:attr:`WaterBridgeAnalysis.timeseries`. The results are returned per frame and +are a list of hydrogen bonds between the selection 1 or selection 2 and the +bridging waters. Due to the complexity of the higher order water bridge and the +fact that one hydrogen bond between two waters can appear in both third and +fourth order water bridge, the hydrogen bonds in the +:attr:`WaterBridgeAnalysis.timeseries` attribute are generated in a depth-first +search manner to avoid duplication. Example code of how +:attr:`WaterBridgeAnalysis.timeseries` is generated:: + + def network2timeseries(network, timeseries): + '''Traverse the network in a depth-first fashion. + expand_timeseries will expand the compact representation to the familiar + timeseries representation.''' + + if network is None: + return + else: + for node in network: + timeseries.append(expand_timeseries(node)) + network2timeseries(network[node], timeseries) - - the **identities** of donor and acceptor atoms, - - the **distance** between the heavy atom acceptor atom and the hydrogen atom - - the **angle** donor-hydrogen-acceptor angle (180º is linear). + timeseries = [] + network2timeseries(network, timeseries) -Water bridge data are returned per frame, which is stored in \ -:attr:`WaterBridgeAnalysis.timeseries` (In the following description, ``#`` -indicates comments that are not part of the output.):: +The list is formatted similar to the \ :attr:`HydrogenBondAnalysis.timeseries +` +except that the atom identifier is expressed as (residue name, residue number, +atom name). An example would be. :: results = [ [ # frame 1 - # hbonds linking the selection 1 and selection 2 to the bridging - # water 1 - [ # hbond 1 from selection 1 to the bridging water 1 - , - , , , - , - ], - [ # hbond 2 from bridging water 1 to the selection 2 - , - , , , - , - ], - - # hbonds linking the selection 1 and selection 2 to the bridging - # water 1 and bridging water 2 - [ # hbond 3 from selection 1 to the bridging water 1 - , - , , , - , - ], - [ # hbond 4 from bridging water 1 to the bridging water 2 - , - , , , - , - ], - [ # hbond 5 from bridging water 2 to the selection 2 - , - , , , - , - ], + [ , , + (, , ), + (, , ), + , ], .... ], [ # frame 2 @@ -157,7 +180,7 @@ :class:`WaterBridgeAnalysis`. If the lists are entirely inappropriate (e.g. when analysing simulations done with a force field that uses very different atom names) then one should either use the value "other" for -`forcefield` to set no default values, or derive a new class and set the +`forcefield` to set no default values or derive a new class and set the default list oneself:: class WaterBridgeAnalysis_OtherFF(WaterBridgeAnalysis): @@ -165,7 +188,7 @@ class WaterBridgeAnalysis_OtherFF(WaterBridgeAnalysis): DEFAULT_ACCEPTORS = {"OtherFF": tuple(set([...]))} Then simply use the new class instead of the parent class and call it with -`forcefield` = "OtherFF". Please also consider contributing the list of heavy +```forcefield` = "OtherFF"``. Please also consider contributing the list of heavy atom names to MDAnalysis. How to perform WaterBridgeAnalysis @@ -186,9 +209,6 @@ class WaterBridgeAnalysis_OtherFF(WaterBridgeAnalysis): order=3) Thus, a maximum of three bridging waters will be detected. -The results are stored as the attribute -:attr:`WaterBridgeAnalysis.timeseries`; see :ref:`wb_Analysis_Output` for the -format. An example of using the :attr:`~WaterBridgeAnalysis` would be detecting the percentage of time a certain water bridge exits. @@ -199,20 +219,27 @@ class WaterBridgeAnalysis_OtherFF(WaterBridgeAnalysis): is between the oxygen of the arginine and the other oxygen in the carboxylic group (ASP3:OD2). :: + # index residue id residue name atom name + # 0 1 ARG O + # 1 2 SOL OW + # 2 2 SOL HW1 + # 3 2 SOL HW2 + # 4 3 ASP OD1 + # 5 3 ASP OD2 print(w.timeseries) prints out. :: [ # frame 1 # A water bridge SOL2 links O from ARG1 to the carboxylic group OD1 of ASP3 - [[0,1,('ARG',1,'O'), ('SOL',2,'HW1'), 3.0,180], - [2,3,('SOL',2,'HW2'),('ASP',3,'OD1'), 3.0,180], + [[0,2,('ARG',1, 'O'),('SOL',2,'HW1'), 3.0,180], + [3,4,('SOL',2,'HW2'),('ASP',3,'OD1'), 3.0,180], ], # frame 2 # Another water bridge SOL2 links O from ARG1 to the other oxygen of the # carboxylic group OD2 of ASP3 - [[0,1,('ARG',1,'O'), ('SOL',2,'HW1'), 3.0,180], - [2,4,('SOL',2,'HW2'),('ASP',3,'OD2'), 3.0,180], + [[0,2,('ARG',1, 'O'),('SOL',2,'HW1'), 3.0,180], + [3,5,('SOL',2,'HW2'),('ASP',3,'OD2'), 3.0,180], ], ] @@ -222,7 +249,7 @@ class WaterBridgeAnalysis_OtherFF(WaterBridgeAnalysis): Use count_by_type ----------------- -To calculate the percentage, we can use the :meth:`~WaterBridgeAnalysis.count_by_type` to +We can use the :meth:`~WaterBridgeAnalysis.count_by_type` to generate the frequence of all water bridges in the simulation. :: w.count_by_type() @@ -234,23 +261,26 @@ class WaterBridgeAnalysis_OtherFF(WaterBridgeAnalysis): You might think that the OD1 and OD2 are the same oxygen and the aspartate has just flipped and thus, they should be counted as the same type of water bridge. The type of the water -bridge can be customised by supplying an analsysis function to -:meth:`~WaterBridgeAnalysis.count_by_type`. :: +bridge can be customised by supplying an analysis function to +:meth:`~WaterBridgeAnalysis.count_by_type`. The analysis function has two parameters. The current and the output. The current is a list -of hydrogen bonds from selection 1 to selection 2, and an example will be :: +of hydrogen bonds from selection 1 to selection 2, formatted in the same fashion as +:attr:`WaterBridgeAnalysis.network`, and an example will be :: -[[0,1,('ARG',1,'O'), ('SOL',2,'HW1'), 3.0,180], - [2,3,('SOL',2,'HW2'),('ASP',3,'OD1'), 3.0,180],] + [ # sele1 acceptor idx, , water donor index, donor heavy atom idx, dist, ang. + [ 0, None, 2, 1, 3.0,180], + # water donor idx, donor heavy atom idx, sele2 acceptor idx, distance, angle. + [ 3, 1, 4, None, 3.0,180],] -Where current[0] is the first hydrogen bond originating from selection 1 and current[-1] is +Where ``current[0]`` is the first hydrogen bond originating from selection 1 and ``current[-1]`` is the final hydrogen bond ending in selection 2. The output sums up all the information in the current frame and is a dictionary with a user-defined key and the value is the weight of the corresponding key. At the end of the analysis, the keys from all the frames are collected -and the corresponding values will be summed up and returned. +and the corresponding values will be summed up and returned. :: - def analysis(current, output): - '''This function defines how the type of water bridge should be specified. + def analysis(current, output, u): + r'''This function defines how the type of water bridge should be specified. Parameters ---------- @@ -260,14 +290,18 @@ def analysis(current, output): output : dict A dictionary where the key is the type of the water bridge and the value is the weight of this type of water bridge. - ''' + u : MDAnalysis.universe + The current Universe for looking up atoms.''' # decompose the first hydrogen bond. - s1_index, to_index, (s1_resname, s1_resid, s1_name), - (to_resname, to_resid, to_name), dist, angle = current[0] + sele1_index, sele1_heavy_index, atom2, heavy_atom2, dist, angle = current[0] # decompose the last hydrogen bond. - from_index, s2_index, (from_resname, from_resid, from_name), - (s2_resname, s2_resid, s2_name), dist, angle = current[-1] + atom1, heavy_atom1, sele2_index, sele2_heavy_index, dist, angle = current[-1] + # expand the atom index to the resname, resid, atom names + sele1 = u.atoms[sele1_index] + sele2 = u.atoms[sele2_index] + (s1_resname, s1_resid, s1_name) = (sele1.resname, sele1.resid, sele1.name) + (s2_resname, s2_resid, s2_name) = (sele2.resname, sele2.resid, sele2.name) # if the residue name is ASP and the atom name is OD2 or OD1, # the atom name is changed to OD if s2_resname == 'ASP' and (s2_name == 'OD1' or s2_name == 'OD2'): @@ -283,7 +317,7 @@ def analysis(current, output): [(('ARG', 1, 'O', 'ASP', 3, 'OD'), 1.0),] -Note that the result is arranged in the format of (key, proportion of time). When no +Note that the result is arranged in the format of ``(key, the proportion of time)``. When no custom analysis function is supplied, the key is expended for backward compatibility. Some people might only interested in contacts between residues and pay no attention @@ -294,7 +328,7 @@ def analysis(current, output): only counted once per frame. This can also be achieved by supplying an analysis function to :meth:`~WaterBridgeAnalysis.count_by_type`. :: - def analysis(current, output): + def analysis(current, output, u): '''This function defines how the type of water bridge should be specified. Parameters @@ -305,15 +339,22 @@ def analysis(current, output): output : dict A dictionary where the key is the type of the water bridge and the value is the weight of this type of water bridge. + u : MDAnalysis.universe + The current Universe for looking up atoms. ''' - s1_index, to_index, (s1_resname, s1_resid, s1_name), - (to_resname, to_resid, to_name), dist, angle = current[0] - from_index, s2_index, (from_resname, from_resid, from_name), - (s2_resname, s2_resid, s2_name), dist, angle = current[-1] + # decompose the first hydrogen bond. + sele1_index, sele1_heavy_index, atom2, heavy_atom2, dist, angle = current[0] + # decompose the last hydrogen bond. + atom1, heavy_atom1, sele2_index, sele2_heavy_index, dist, angle = current[-1] + # expand the atom index to the resname, resid, atom names + sele1 = u.atoms[sele1_index] + sele2 = u.atoms[sele2_index] + (s1_resname, s1_resid, s1_name) = (sele1.resname, sele1.resid, sele1.name) + (s2_resname, s2_resid, s2_name) = (sele2.resname, sele2.resid, sele2.name) # s1_name and s2_name are not included in the key key = (s1_resname, s1_resid, s2_resname, s2_resid) - + # Each residue is only counted once per frame output[key] = 1 @@ -323,11 +364,11 @@ def analysis(current, output): [(('ARG', 1, 'ASP', 3), 1.0),] -On the other hand, other people may insist that first order and second order water +On the other hand, other people may insist that the first order and second-order water bridges shouldn't be mixed together, which can also be achieved by supplying an analysis function to :meth:`~WaterBridgeAnalysis.count_by_type`. :: - def analysis(current, output): + def analysis(current, output, u): '''This function defines how the type of water bridge should be specified. Parameters @@ -338,12 +379,19 @@ def analysis(current, output): output : dict A dictionary where the key is the type of the water bridge and the value is the weight of this type of water bridge. + u : MDAnalysis.universe + The current Universe for looking up atoms. ''' - s1_index, to_index, (s1_resname, s1_resid, s1_name), - (to_resname, to_resid, to_name), dist, angle = current[0] - from_index, s2_index, (from_resname, from_resid, from_name), - (s2_resname, s2_resid, s2_name), dist, angle = current[-1] + # decompose the first hydrogen bond. + sele1_index, sele1_heavy_index, atom2, heavy_atom2, dist, angle = current[0] + # decompose the last hydrogen bond. + atom1, heavy_atom1, sele2_index, sele2_heavy_index, dist, angle = current[-1] + # expand the atom index to the resname, resid, atom names + sele1 = u.atoms[sele1_index] + sele2 = u.atoms[sele2_index] + (s1_resname, s1_resid, s1_name) = (sele1.resname, sele1.resid, sele1.name) + (s2_resname, s2_resid, s2_name) = (sele2.resname, sele2.resid, sele2.name) # order of the current water bridge is computed order_of_water_bridge = len(current) - 1 # and is included in the key @@ -361,7 +409,7 @@ def analysis(current, output): interactions can be discarded by supplying an analysis function to :meth:`~WaterBridgeAnalysis.count_by_type`. :: - def analysis(current, output): + def analysis(current, output, u): '''This function defines how the type of water bridge should be specified. Parameters @@ -372,12 +420,19 @@ def analysis(current, output): output : dict A dictionary where the key is the type of the water bridge and the value is the number of this type of water bridge. + u : MDAnalysis.universe + The current Universe for looking up atoms. ''' - s1_index, to_index, (s1_resname, s1_resid, s1_name), - (to_resname, to_resid, to_name), dist, angle = current[0] - from_index, s2_index, (from_resname, from_resid, from_name), - (s2_resname, s2_resid, s2_name), dist, angle = current[-1] + # decompose the first hydrogen bond. + sele1_index, sele1_heavy_index, atom2, heavy_atom2, dist, angle = current[0] + # decompose the last hydrogen bond. + atom1, heavy_atom1, sele2_index, sele2_heavy_index, dist, angle = current[-1] + # expand the atom index to the resname, resid, atom names + sele1 = u.atoms[sele1_index] + sele2 = u.atoms[sele2_index] + (s1_resname, s1_resid, s1_name) = (sele1.resname, sele1.resid, sele1.name) + (s2_resname, s2_resid, s2_name) = (sele2.resname, sele2.resid, sele2.name) if not s1_resname == 'ARG': key = (s1_resname, s1_resid, s2_resname, s2_resid) output[key] += 1 @@ -388,7 +443,7 @@ def analysis(current, output): [,] -Additional key words can be supplied to the analysis function by passing through +Additional keywords can be supplied to the analysis function by passing through :meth:`~WaterBridgeAnalysis.count_by_type`. :: def analysis(current, output, **kwargs): @@ -427,7 +482,7 @@ def analysis(current, output, **kwargs): The analysis function can be written as:: - def analysis(current, output, **kwargs): + def analysis(current, output, u, **kwargs): '''This function defines how the counting of water bridge should be specified. Parameters @@ -438,14 +493,19 @@ def analysis(current, output, **kwargs): output : dict A dictionary where the key is the type of the water bridge and the value is the number of this type of water bridge. + u : MDAnalysis.universe + The current Universe for looking up atoms. ''' # decompose the first hydrogen bond. - s1_index, to_index, (s1_resname, s1_resid, s1_name), - (to_resname, to_resid, to_name), dist, angle = current[0] + sele1_index, sele1_heavy_index, atom2, heavy_atom2, dist, angle = current[0] # decompose the last hydrogen bond. - from_index, s2_index, (from_resname, from_resid, from_name), - (s2_resname, s2_resid, s2_name), dist, angle = current[-1] + atom1, heavy_atom1, sele2_index, sele2_heavy_index, dist, angle = current[-1] + # expand the atom index to the resname, resid, atom names + sele1 = u.atoms[sele1_index] + sele2 = u.atoms[sele2_index] + (s1_resname, s1_resid, s1_name) = (sele1.resname, sele1.resid, sele1.name) + (s2_resname, s2_resid, s2_name) = (sele2.resname, sele2.resid, sele2.name) # only the residue name is ASP and the atom name is OD1, if s2_resname == 'ASP' and s2_name == 'OD1': @@ -565,7 +625,8 @@ def __init__(self, universe, selection1='protein', selection1_type='both', update_selection=False, update_water_selection=True, filter_first=True, distance_type='hydrogen', distance=3.0, angle=120.0, forcefield='CHARMM27', donors=None, - acceptors=None, debug=None, verbose=False, pbc=False, **kwargs): + acceptors=None, output_format="sele1_sele2", debug=None, verbose=False, + pbc=False, **kwargs): """Set up the calculation of water bridges between two selections in a universe. @@ -595,14 +656,14 @@ def __init__(self, universe, selection1='protein', name "SOL". Change it to the appropriate selection for your specific force field. - However, in theory this selection can be anything which forms - hydrogen bond with selection 1 and selection 2. + However, in theory, this selection can be anything which forms + a hydrogen bond with selection 1 and selection 2. order : int (optional) The maximum number of water bridges linking both selections. - if order is set to 3, then all the residues linked with less than - three water molecules wil be detected. [1] + if the order is set to 3, then all the residues linked with less than + three water molecules will be detected. [1] - Computation of high order water bridges can be very time consuming. + Computation of high order water bridges can be very time-consuming. Think carefully before running the calculation, do you really want to compute the 20th order water bridge between domain A and domain B or you just want to know the third order water bridge between two residues. @@ -644,7 +705,7 @@ def __init__(self, universe, selection1='protein', Angle cutoff for hydrogen bonds; an ideal H-bond has an angle of 180º. A hydrogen bond is only recorded if the D-H-A angle is >= `angle`. The default of 120º also finds fairly non-specific - hydrogen interactions and a possibly better value is 150º. [120.0] + hydrogen interactions and possibly better value is 150º. [120.0] forcefield : {"CHARMM27", "GLYCAM06", "other"} (optional) Name of the forcefield used. Switches between different :attr:`~HydrogenBondAnalysis.DEFAULT_DONORS` and @@ -659,9 +720,14 @@ def __init__(self, universe, selection1='protein', sequence. distance_type : {"hydrogen", "heavy"} (optional) Measure hydrogen bond lengths between donor and acceptor heavy - attoms ("heavy") or between donor hydrogen and acceptor heavy + atoms ("heavy") or between donor hydrogen and acceptor heavy atom ("hydrogen"). If using "heavy" then one should set the *distance* cutoff to a higher value such as 3.5 Å. ["hydrogen"] + output_format: {"sele1_sele2", "donor_acceptor"} (optional) + Setting the output format for timeseries and table. If set to + "sele1_sele2", for each hydrogen bond, the one close to selection 1 + will be placed before selection 2. If set to "donor_acceptor", the + donor will be placed before acceptor. "sele1_sele2"] debug : bool (optional) If set to ``True`` enables per-frame debug logging. This is disabled by default because it generates a very large amount of output in @@ -690,6 +756,9 @@ def __init__(self, universe, selection1='protein', # per-frame debugging output? self.debug = debug + # set the output format + self.output_format = output_format + self.u = universe self.selection1 = selection1 self.selection2 = selection2 @@ -925,7 +994,7 @@ def _donor2acceptor(self, donors, donor_hs, acceptor): self.logger_debug( "D: {0!s} <-> A: {1!s} {2:f} A, {3:f} DEG" \ .format(h.index, a.index, dist, angle)) - result.append((h.index, a.index, + result.append((h.index, d.index, a.index, (h.resname, h.resid, h.name), (a.resname, a.resid, a.name), dist, angle)) @@ -955,37 +1024,36 @@ def _single_frame(self): self.logger_debug("Selection 1 Donors <-> Selection 2 Acceptors") results = self._donor2acceptor(self._s1_donors, self._s1_donors_h, self._s2_acceptors) for line in results: - h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + h_index, d_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line water_pool[(a_resname, a_resid)] = None - selection_1.append(line) + selection_1.append((h_index, d_index, a_index, None, dist, angle)) selection_2.append((a_resname, a_resid)) - if self._water_acceptors: + if self.order > 0 and self._water_acceptors: self.logger_debug("Selection 1 Donors <-> Water Acceptors") results = self._donor2acceptor(self._s1_donors, self._s1_donors_h, self._water_acceptors) for line in results: - h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + h_index, d_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line next_round_water.add((a_resname, a_resid)) - selection_1.append(line) + selection_1.append((h_index, d_index, a_index, None, dist, angle)) if (self.selection1_type in ('acceptor', 'both') and self._s1_acceptors): - self.logger_debug("Selection 1 Acceptors <-> Water Donors") - results = self._donor2acceptor(self._water_donors, self._water_donors_h, self._s1_acceptors) - for line in results: - h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line - line = (a_index, h_index, (a_resname, a_resid, a_name), (h_resname, h_resid, h_name), dist, angle) - next_round_water.add((h_resname, h_resid)) - selection_1.append(line) - self.logger_debug("Selection 2 Donors <-> Selection 1 Acceptors") results = self._donor2acceptor(self._s2_donors, self._s2_donors_h, self._s1_acceptors) for line in results: - h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line - line = (a_index, h_index, (a_resname, a_resid, a_name), (h_resname, h_resid, h_name), dist, angle) + h_index, d_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line water_pool[(h_resname, h_resid)] = None - selection_1.append(line) + selection_1.append((a_index, None, h_index, d_index, dist, angle)) selection_2.append((h_resname, h_resid)) + if self.order > 0: + self.logger_debug("Selection 1 Acceptors <-> Water Donors") + results = self._donor2acceptor(self._water_donors, self._water_donors_h, self._s1_acceptors) + for line in results: + h_index, d_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + next_round_water.add((h_resname, h_resid)) + selection_1.append((a_index, None, h_index, d_index, dist, angle)) + for i in range(self.order): # Narrow down the water selection selection_resn_id = list(next_round_water) @@ -1020,8 +1088,8 @@ def _single_frame(self): self.logger_debug("Order {} water donor <-> Selection 2 Acceptors".format(i + 1)) results = self._donor2acceptor(water_bridges_donors, water_bridges_donors_h, self._s2_acceptors) for line in results: - h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line - water_pool[(h_resname, h_resid)].append(line) + h_index, d_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + water_pool[(h_resname, h_resid)].append((h_index, d_index, a_index, None, dist, angle)) selection_2.append((a_resname, a_resid)) # Finding the hydrogen bonds between water bridge and selection 2 @@ -1029,9 +1097,8 @@ def _single_frame(self): self.logger_debug("Selection 2 Donors <-> Order {} water".format(i + 1)) results = self._donor2acceptor(self._s2_donors, self._s2_donors_h, water_bridges_acceptors) for line in results: - h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line - line = (a_index, h_index, (a_resname, a_resid, a_name), (h_resname, h_resid, h_name), dist, angle) - water_pool[(a_resname, a_resid)].append(line) + h_index, d_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + water_pool[(a_resname, a_resid)].append((a_index, None, h_index, d_index, dist, angle)) selection_2.append((h_resname, h_resid)) # find the water water hydrogen bond @@ -1039,19 +1106,21 @@ def _single_frame(self): self.logger_debug("Order {} water acceptor <-> Order {} water donor".format(i + 1, i + 2)) results = self._donor2acceptor(self._water_donors, self._water_donors_h, water_bridges_acceptors) for line in results: - h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line - line = (a_index, h_index, (a_resname, a_resid, a_name), (h_resname, h_resid, h_name), dist, angle) - water_pool[(a_resname, a_resid)].append(line) + h_index, d_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + water_pool[(a_resname, a_resid)].append((a_index, None, h_index, d_index, dist, angle)) next_round_water.add((h_resname, h_resid)) if water_bridges_donors_h: self.logger_debug("Order {} water donor <-> Order {} water acceptor".format(i + 1, i + 2)) results = self._donor2acceptor(water_bridges_donors, water_bridges_donors_h, self._water_acceptors) for line in results: - h_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line - water_pool[(h_resname, h_resid)].append(line) + h_index, d_index, a_index, (h_resname, h_resid, h_name), (a_resname, a_resid, a_name), dist, angle = line + water_pool[(h_resname, h_resid)].append((h_index, d_index, a_index, None, dist, angle)) next_round_water.add((a_resname, a_resid)) + # eliminate the water which has been tested + next_round_water = next_round_water.difference(set(water_pool.keys())) + # solve the connectivity network # The following code attempt to generate a water network which is formed by the class dict. # Suppose we have a water bridge connection ARG1 to ASP3 via the two hydrogen bonds. @@ -1071,7 +1140,7 @@ def add_route(result, route): result['start'][route[0]] = None else: # exclude the the selection which goes back to itself - if (sorted(route[0][:2]) == sorted(route[-1][:2])): + if (sorted(route[0][0:3:2]) == sorted(route[-1][0:3:2])): return # selection 2 to water @@ -1094,11 +1163,11 @@ def traverse_water_network(graph, node, end, route, maxdepth, result): for new_node in graph[node]: new_route = route[:] new_route.append(new_node) - new_node = new_node[3][:2] + new_node = self._expand_timeseries(new_node,'sele1_sele2')[3][:2] traverse_water_network(graph, new_node, end, new_route, maxdepth, result) for s1 in selection_1: route = [s1, ] - next_mol = s1[3][:2] + next_mol = self._expand_timeseries(s1,'sele1_sele2')[3][:2] traverse_water_network(water_pool, next_mol, selection_2, route[:], self.order, result) self._network.append(result['start']) @@ -1126,7 +1195,7 @@ def _traverse_water_network(self, graph, current, analysis_func=None, output=Non # if selection 2 is reached if not analysis_func is None: # the result is analysed by analysis_func which will change the output - analysis_func(current, output, **kwargs) + analysis_func(current, output, self.u, **kwargs) else: # make sure no loop can occur if len(current) <= self.order: @@ -1135,13 +1204,61 @@ def _traverse_water_network(self, graph, current, analysis_func=None, output=Non new = link_func(current, node) self._traverse_water_network(graph[node], new, analysis_func, output, link_func, **kwargs) - @property - def timeseries(self): + def _expand_index(self, index): + ''' + Expand the index into (resname, resid, name). + ''' + atom = self.u.atoms[index] + return (atom.resname, atom.resid, atom.name) + + def _expand_timeseries(self, entry, output_format=None): + ''' + Expand the compact data format into the old timeseries form. + The function takes in the argument `output_format` to see which output format will be chosen. + if `output_format` is not specified, the value will be taken from :attr:`output_format`. + If `output_format` is 'sele1_sele2', the output will be the old water bridge analysis format:: + + # donor from selection 1 to acceptor in selection 2 + [sele1_index, sele2_index, + (sele1_resname, sele1_resid, sele1_name), + (sele2_resname, sele2_resid, sele2_name), dist, angle] + + If `output_format` is 'donor_acceptor', the output will be the old hydrogen bond analysis format:: + + # From donor to acceptor + [donor_index, acceptor_index, + (donor_resname, donor_resid, donor_name), + (acceptor_resname, acceptor_resid, acceptor_name), dist, angle] + ''' + output_format = output_format or self.output_format + # Expand the compact entry into atom1, which is the first index in the output and atom2, which is the second + # entry. + atom1, heavy_atom1, atom2, heavy_atom2, dist, angle = entry + if output_format == 'sele1_sele2': + # If the output format is the sele1_sele2, no change will be executed + atom1, atom2 = atom1, atom2 + elif output_format == 'donor_acceptor': + # If the output format is donor_acceptor, use heavy atom position to check which is donor and which is + # acceptor + if heavy_atom1 is None: + # atom1 is hydrogen bond acceptor and thus, the position of atom1 and atom2 are swapped. + atom1, atom2 = atom2, atom1 + else: + # atom1 is hydrogen bond donor, position not swapped. + atom1, atom2 = atom1, atom2 + else: + raise KeyError('Only \'sele1_sele2\' or \'donor_acceptor\' are allowed as output format.') + + return (atom1, atom2, self._expand_index(atom1), self._expand_index(atom2), dist, angle) + + def _generate_timeseries(self, output_format=None): r'''Time series of water bridges. - The output is arranged so that every individual link from selection 1 - to selection 2 is appended to the resulting list. - An example of the output is given in :ref:`wb_Analysis_Output`. + The output is generated per frame as is explained in :ref:`wb_Analysis_Timeseries`. + The format of output can be changed via the output_format selection. + If ``output_format="sele1_sele2"``, the hydrogen bond forms a directional + link from selection 1 to selection 2. If ``output_format="donor_acceptor"``, + for each hydrogen bond, the donor is always written before the acceptor. Note ---- @@ -1158,24 +1275,48 @@ def timeseries(self): w.run() timeseries = w.timeseries - - The output format of :attr:`WaterBridgeAnalysis.timeseries` has changed - so that it has the same format as - :attr:`~HydrogenBondAnalysis.timeseries`. - .. versionchanged:: 0.20.0 ''' - - def analysis(current, output): - output.extend(current) + output_format = output_format or self.output_format + def analysis(current, output, *args, **kwargs): + output = current timeseries = [] for frame in self._network: new_frame = [] - self._traverse_water_network(frame, [], analysis_func=analysis, output=new_frame, link_func=self._full_link) - timeseries.append(new_frame) + self._traverse_water_network(frame, new_frame, analysis_func=analysis, output=new_frame, link_func=self._compact_link) + timeseries.append([self._expand_timeseries(entry, output_format) for entry in new_frame]) return timeseries + timeseries = property(_generate_timeseries) + + def _get_network(self): + r'''Network representation of the water network. + + The output is generated per frame as is explained in :ref:`wb_Analysis_Network`. + Each hydrogen bond has a compact representation of :: + + [sele1_acceptor_idx, None, sele2_donor_idx, donor_heavy_idx, distance, angle] + + or :: + + [sele1_donor_idx, donor_heavy_idx, sele1_acceptor_idx, None, distance, angle] + + The donor_heavy_idx is the heavy atom bonding to the proton and atoms + can be retrived from the universe:: + + atom = u.atoms[idx] + + .. versionadded:: 0.20.0 + + ''' + return self._network + + def set_network(self, network): + self._network = network + + network = property(_get_network, set_network) + @classmethod def _full_link(self, output, node): ''' @@ -1189,15 +1330,27 @@ def _full_link(self, output, node): return result @classmethod - def _count_by_type_analysis(self, current, output): + def _compact_link(self, output, node): + ''' + A function used in _traverse_water_network to add the new hydrogen bond to the existing bonds. + In this form no new list is created and thus, one bridge will only appear once. + :param output: The existing hydrogen bonds from selection 1 + :param node: The new hydrogen bond + :return: The hydrogen bonds from selection 1 with the new hydrogen bond added + ''' + output.append(node) + return output + + def _count_by_type_analysis(self, current, output, *args, **kwargs): ''' Generates the key for count_by_type analysis. :return: ''' + s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ - current[0] + self._expand_timeseries(current[0]) from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ - current[-1] + self._expand_timeseries(current[-1]) key = (s1_index, s2_index, s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) output[key] += 1 @@ -1209,7 +1362,7 @@ def count_by_type(self, analysis_func=None, **kwargs): the proportion of time that this linkage exists in the whole simulation will be calculated. - The identification of a specific type water bridge can be modified by + The identification of a specific type of water bridge can be modified by supplying the analysis_func function. See :ref:`wb_count_by_type` for detail. @@ -1248,12 +1401,11 @@ def count_by_type(self, analysis_func=None, **kwargs): else: return None - @classmethod - def _count_by_time_analysis(self, current, output): + def _count_by_time_analysis(self, current, output, *args, **kwargs): s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ - current[0] + self._expand_timeseries(current[0]) from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ - current[-1] + self._expand_timeseries(current[-1]) key = (s1_index, s2_index, s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) output[key] += 1 @@ -1283,12 +1435,11 @@ def count_by_time(self, analysis_func=None, **kwargs): else: return None - @classmethod - def _timesteps_by_type_analysis(self, current, output, **kwargs): + def _timesteps_by_type_analysis(self, current, output, *args, **kwargs): s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ - current[0] + self._expand_timeseries(current[0]) from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ - current[-1] + self._expand_timeseries(current[-1]) key = (s1_index, s2_index, s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) output[key].append(kwargs.pop('time')) @@ -1316,7 +1467,11 @@ def timesteps_by_type(self, analysis_func=None, **kwargs): if self._network: result = defaultdict(list) - for time, frame in zip(self.timesteps, self._network): + if self.timesteps is None: + timesteps = range(len(self._network)) + else: + timesteps = self.timesteps + for time, frame in zip(timesteps, self._network): self._traverse_water_network(frame, [], analysis_func=analysis_func, output=result, link_func=self._full_link, time=time, **kwargs) @@ -1333,33 +1488,40 @@ def timesteps_by_type(self, analysis_func=None, **kwargs): else: return None - def generate_table(self): + def generate_table(self, output_format=None): """Generate a normalised table of the results. The table is stored as a :class:`numpy.recarray` in the attribute :attr:`~WaterBridgeAnalysis.table`. - See Also - -------- - WaterBridgeAnalysis.table - + The output format of :attr:`~WaterBridgeAnalysis.table` can also be + changed using output_format in a fashion similar to :attr:`WaterBridgeAnalysis.timeseries` """ + output_format = output_format or self.output_format if self._network == []: msg = "No data computed, do run() first." warnings.warn(msg, category=MissingDataWarning) logger.warning(msg) - self.table = None - return - timeseries = self.timeseries + return None + timeseries = self._generate_timeseries(output_format) num_records = np.sum([len(hframe) for hframe in timeseries]) # build empty output table - dtype = [ - ("time", float), - ("donor_index", int), ("acceptor_index", int), - ("donor_resnm", "|U4"), ("donor_resid", int), ("donor_atom", "|U4"), - ("acceptor_resnm", "|U4"), ("acceptor_resid", int), ("acceptor_atom", "|U4"), - ("distance", float), ("angle", float)] + if output_format == 'sele1_sele2': + dtype = [ + ("time", float), + ("sele1_index", int), ("sele2_index", int), + ("sele1_resnm", "|U4"), ("sele1_resid", int), ("sele1_atom", "|U4"), + ("sele2_resnm", "|U4"), ("sele2_resid", int), ("sele2_atom", "|U4"), + ("distance", float), ("angle", float)] + elif output_format == 'donor_acceptor': + dtype = [ + ("time", float), + ("donor_index", int), ("acceptor_index", int), + ("donor_resnm", "|U4"), ("donor_resid", int), ("donor_atom", "|U4"), + ("acceptor_resnm", "|U4"), ("acceptor_resid", int), ("acceptor_atom", "|U4"), + ("distance", float), ("angle", float)] + # according to Lukas' notes below, using a recarray at this stage is ineffective # and speedups of ~x10 can be achieved by filling a standard array, like this: out = np.empty((num_records,), dtype=dtype) @@ -1371,6 +1533,7 @@ def generate_table(self): out[cursor] = (t, donor_index, acceptor_index) + \ donor + acceptor + (distance, angle) cursor += 1 - assert cursor == num_records, "Internal Error: Not all HB records stored" - self.table = out.view(np.recarray) + assert cursor == num_records, "Internal Error: Not all wb records stored" + table = out.view(np.recarray) logger.debug("WBridge: Stored results as table with %(num_records)d entries.", vars()) + self.table = table diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index f177eec2824..26b7e023e30 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -249,42 +249,32 @@ def universe_AWA_AWWA(): @staticmethod @pytest.fixture(scope='class') - def wb_multiframe(universe_DWA): + def wb_multiframe(): '''A water bridge object with multipley frames''' - wb = WaterBridgeAnalysis(universe_DWA, 'protein and (resid 1)', 'protein and (resid 4)', order=4) + grofile = '''Test gro file + 13 + 1ALA O 1 0.000 0.000 0.000 + 1ALA H 2 0.000 0.000 0.000 + 2SOL OW 3 0.300 0.000 0.000 + 2SOL HW1 4 0.200 0.000 0.000 + 2SOL HW2 5 0.400 0.000 0.000 + 3SOL OW 6 0.600 0.000 0.000 + 3SOL HW1 7 0.700 0.000 0.000 + 4SOL OW 8 0.900 0.000 0.000 + 4SOL HW1 9 1.000 0.000 0.000 + 5SOL OW 10 1.200 0.000 0.000 + 5SOL HW1 11 1.300 0.000 0.000 + 6ALA H 12 1.400 0.000 0.000 + 6ALA O 13 1.400 0.000 0.000 + 10.0 10.0 10.0''' + u = MDAnalysis.Universe(StringIO(grofile), format='gro') + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) # Build an dummy WaterBridgeAnalysis object for testing wb._network = [] - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'), 2.0, 180.0): { - (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'), 2.0, 180.0): None}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'), 2.0, 180.0): { - (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'), 2.0, 180.0): { - (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'), 1.0, 180.0): None}}}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'), 2.0, 180.0): { - (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'), 2.0, 180.0): None, - (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'), 2.0, 180.0): None}}}) - wb._network.append({(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'), 2.0, 180.0): { - (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'), 2.0, 180.0): None}, - (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'), 2.0, 180.0): { - (8, 9, ('SOL', 6, 'HW2'), ('SOL', 7, 'OW'), 2.0, 180.0): { - (10, 11, ('SOL', 7, 'HW1'), ('ALA', 8, 'O'), 2.0, 180.0): None}}}) + wb._network.append({(1, 0, 12, None, 2.0, 180.0): None}) + wb._network.append({(0, None, 12, 13, 2.0, 180.0): None}) + wb._network.append({(1, 0, 3, None, 2.0, 180.0): {(4, 2, 12, None, 2.0, 180.0): None}}) + wb._network.append({(0, None, 3, 2, 2.0, 180.0): {(4, 2, 5, None, 2.0, 180.0): {(5, None, 11, 12, 2.0, 180.0): None}}}) wb.timesteps = range(len(wb._network)) return wb @@ -324,21 +314,21 @@ def test_donor_accepter(self, universe_DA): wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', 'protein and (resid 4)', order=0, update_selection=True, debug=True) wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('ALA', 4, 'O'))) + assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) def test_donor_accepter_pbc(self, universe_DA_PBC): '''Test zeroth order donor to acceptor hydrogen bonding in PBC conditions''' wb = WaterBridgeAnalysis(universe_DA_PBC, 'protein and (resid 1)', 'protein and (resid 4)', order=0, pbc=True) wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('ALA', 4, 'O'))) + assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) def test_accepter_donor(self, universe_AD): '''Test zeroth order acceptor to donor hydrogen bonding''' wb = WaterBridgeAnalysis(universe_AD, 'protein and (resid 1)', 'protein and (resid 4)', order=0) wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 1, ('ALA', 1, 'O'), ('ALA', 4, 'H'))) + assert_equal(list(network.keys())[0][:4], (0, None, 1, 2)) def test_acceptor_water_accepter(self, universe_AWA): '''Test case where the hydrogen bond acceptor from selection 1 form @@ -346,9 +336,9 @@ def test_acceptor_water_accepter(self, universe_AWA): wb = WaterBridgeAnalysis(universe_AWA, 'protein and (resid 1)', 'protein and (resid 4)') wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) + assert_equal(list(second.keys())[0][:4], (3, 1, 4, None)) assert_equal(second[list(second.keys())[0]], None) def test_donor_water_accepter(self, universe_DWA): @@ -357,9 +347,9 @@ def test_donor_water_accepter(self, universe_DWA): wb = WaterBridgeAnalysis(universe_DWA, 'protein and (resid 1)', 'protein and (resid 4)') wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'))) + assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) + assert_equal(list(second.keys())[0][:4], (3, 2, 4, None)) assert_equal(second[list(second.keys())[0]], None) def test_acceptor_water_donor(self, universe_AWD): @@ -368,9 +358,9 @@ def test_acceptor_water_donor(self, universe_AWD): wb = WaterBridgeAnalysis(universe_AWD, 'protein and (resid 1)', 'protein and (resid 4)') wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (1, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'))) + assert_equal(list(second.keys())[0][:4], (1, None, 3, 4)) assert_equal(second[list(second.keys())[0]], None) def test_donor_water_donor(self, universe_DWD): @@ -379,9 +369,9 @@ def test_donor_water_donor(self, universe_DWD): wb = WaterBridgeAnalysis(universe_DWD, 'protein and (resid 1)', 'protein and (resid 4)') wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (1, 2, ('ALA', 1, 'H'), ('SOL', 2, 'OW'))) + assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (2, 3, ('SOL', 2, 'OW'), ('ALA', 4, 'H'))) + assert_equal(list(second.keys())[0][:4], (2, None, 3, 4)) assert_equal(second[list(second.keys())[0]], None) def test_empty(self, universe_empty): @@ -411,21 +401,21 @@ def test_acceptor_2water_accepter(self, universe_AWWA): wb = WaterBridgeAnalysis(universe_AWWA, 'protein and (resid 1)', 'protein and (resid 4)', order=2) wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal(list(second.keys())[0][:4], (3, 1, 4, None)) third = second[list(second.keys())[0]] - assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) + assert_equal(list(third.keys())[0][:4], (5, 4, 6, None)) assert_equal(third[list(third.keys())[0]], None) # test third order wb = WaterBridgeAnalysis(universe_AWWA, 'protein and (resid 1)', 'protein and (resid 4)', order=3) wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal(list(second.keys())[0][:4], (3, 1, 4, None)) third = second[list(second.keys())[0]] - assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) + assert_equal(list(third.keys())[0][:4], (5, 4, 6, None)) assert_equal(third[list(third.keys())[0]], None) def test_acceptor_3water_accepter(self, universe_AWWWA): @@ -438,25 +428,25 @@ def test_acceptor_3water_accepter(self, universe_AWWWA): wb = WaterBridgeAnalysis(universe_AWWWA, 'protein and (resid 1)', 'protein and (resid 5)', order=3) wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal(list(second.keys())[0][:4], (3, 1, 4, None)) third = second[list(second.keys())[0]] - assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) + assert_equal(list(third.keys())[0][:4], (5, 4, 6, None)) fourth = third[list(third.keys())[0]] - assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'))) + assert_equal(list(fourth.keys())[0][:4], (7, 6, 8, None)) assert_equal(fourth[list(fourth.keys())[0]], None) wb = WaterBridgeAnalysis(universe_AWWWA, 'protein and (resid 1)', 'protein and (resid 5)', order=4) wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal(list(second.keys())[0][:4], (3, 1, 4, None)) third = second[list(second.keys())[0]] - assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) + assert_equal(list(third.keys())[0][:4], (5, 4, 6, None)) fourth = third[list(third.keys())[0]] - assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('ALA', 5, 'O'))) + assert_equal(list(fourth.keys())[0][:4], (7, 6, 8, None)) assert_equal(fourth[list(fourth.keys())[0]], None) def test_acceptor_4water_accepter(self, universe_AWWWWA): @@ -469,29 +459,29 @@ def test_acceptor_4water_accepter(self, universe_AWWWWA): wb = WaterBridgeAnalysis(universe_AWWWWA, 'protein and (resid 1)', 'protein and (resid 6)', order=4) wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal(list(second.keys())[0][:4], (3, 1, 4, None)) third = second[list(second.keys())[0]] - assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) + assert_equal(list(third.keys())[0][:4], (5, 4, 6, None)) fourth = third[list(third.keys())[0]] - assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'))) + assert_equal(list(fourth.keys())[0][:4], (7, 6, 8, None)) fifth = fourth[list(fourth.keys())[0]] - assert_equal(list(fifth.keys())[0][:4], (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'))) + assert_equal(list(fifth.keys())[0][:4], (9, 8, 10, None)) assert_equal(fifth[list(fifth.keys())[0]], None) wb = WaterBridgeAnalysis(universe_AWWWWA, 'protein and (resid 1)', 'protein and (resid 6)', order=5) wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal(list(second.keys())[0][:4], (3, 1, 4, None)) third = second[list(second.keys())[0]] - assert_equal(list(third.keys())[0][:4], (5, 6, ('SOL', 3, 'HW1'), ('SOL', 4, 'OW'))) + assert_equal(list(third.keys())[0][:4], (5, 4, 6, None)) fourth = third[list(third.keys())[0]] - assert_equal(list(fourth.keys())[0][:4], (7, 8, ('SOL', 4, 'HW1'), ('SOL', 5, 'OW'))) + assert_equal(list(fourth.keys())[0][:4], (7, 6, 8, None)) fifth = fourth[list(fourth.keys())[0]] - assert_equal(list(fifth.keys())[0][:4], (9, 10, ('SOL', 5, 'HW1'), ('ALA', 6, 'O'))) + assert_equal(list(fifth.keys())[0][:4], (9, 8, 10, None)) assert_equal(fifth[list(fifth.keys())[0]], None) def test_acceptor_22water_accepter(self, universe_branch): @@ -501,39 +491,51 @@ def test_acceptor_22water_accepter(self, universe_branch): wb = WaterBridgeAnalysis(universe_branch, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal(list(second.keys())[0][:4], (3, 1, 4, None)) third = second[list(second.keys())[0]] - assert_equal([(5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O')), (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'))], + assert_equal([(5, 4, 7, None), (6, 4, 8, None)], sorted([key[:4] for key in list(third.keys())])) - def test_timeseries(self, universe_branch): - '''Test if the time series data is correctly generated''' + def test_timeseries_wba(self, universe_branch): + '''Test if the time series data is correctly generated in water bridge analysis format''' wb = WaterBridgeAnalysis(universe_branch, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) + wb.output_format = 'sele1_sele2' wb.run(verbose=False) timeseries = sorted(wb.timeseries[0]) + assert_equal(timeseries[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - assert_equal(timeseries[1][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - assert_equal(timeseries[2][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) - assert_equal(timeseries[3][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) - assert_equal(timeseries[4][:4], (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) - assert_equal(timeseries[5][:4], (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'))) + assert_equal(timeseries[1][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal(timeseries[2][:4], (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) + assert_equal(timeseries[3][:4], (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'))) + + def test_timeseries_hba(self, universe_branch): + '''Test if the time series data is correctly generated in hydrogen bond analysis format''' + wb = WaterBridgeAnalysis(universe_branch, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) + wb.output_format = 'donor_acceptor' + wb.run(verbose=False) + timeseries = sorted(wb.timeseries[0]) + + assert_equal(timeseries[0][:4], (2, 0, ('SOL', 2, 'HW1'), ('ALA', 1, 'O'))) + assert_equal(timeseries[1][:4], (3, 4, ('SOL', 2, 'HW2'), ('SOL', 3, 'OW'))) + assert_equal(timeseries[2][:4], (5, 7, ('SOL', 3, 'HW1'), ('ALA', 4, 'O'))) + assert_equal(timeseries[3][:4], (6, 8, ('SOL', 3, 'HW2'), ('ALA', 5, 'O'))) def test_acceptor_12water_accepter(self, universe_AWA_AWWA): '''Test of independent first order and second can be recognised correctely''' wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=1) wb.run(verbose=False) network = wb._network[0] - assert_equal(list(network.keys())[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) + assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] - assert_equal(list(second.keys())[0][:4], (3, 4, ('SOL', 2, 'HW2'), ('ALA', 4, 'O'))) + assert_equal(list(second.keys())[0][:4], (3, 1, 4, None)) assert_equal(second[list(second.keys())[0]], None) network = wb._network[0] wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) wb.run(verbose=False) network = wb._network[0] - assert_equal([(0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1')), (5, 7, ('ALA', 5, 'O'), ('SOL', 6, 'HW1'))], + assert_equal([(0, None, 2, 1), (5, None, 7, 6)], sorted([key[:4] for key in list(network.keys())])) def test_count_by_type_single_link(self, universe_DWA): @@ -558,15 +560,10 @@ def test_count_by_type_multiple_frame(self, wb_multiframe): This test tests if count_by_type() works in multiply situations. :return: ''' - result = [(0, 3, 'ALA', 1, 'O', 'ALA', 4, 'H', 0.1), - (0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 0.3), - (0, 6, 'ALA', 1, 'O', 'ALA', 4, 'O', 0.1), - (0, 7, 'ALA', 1, 'O', 'ALA', 4, 'O', 0.1), - (0, 8, 'ALA', 1, 'O', 'ALA', 5, 'O', 0.2), - (0, 10, 'ALA', 1, 'O', 'ALA', 6, 'O', 0.1), - (1, 3, 'ALA', 1, 'H', 'ALA', 4, 'H', 0.1), - (1, 4, 'ALA', 1, 'H', 'ALA', 4, 'O', 0.1), - (5, 11, 'ALA', 5, 'O', 'ALA', 8, 'O', 0.1)] + result = [[0, 11, 'ALA', 1, 'O', 'ALA', 6, 'H', 0.25], + [0, 12, 'ALA', 1, 'O', 'ALA', 6, 'O', 0.25], + [1, 12, 'ALA', 1, 'H', 'ALA', 6, 'O', 0.5]] + assert_equal(sorted(wb_multiframe.count_by_type()), result) def test_count_by_type_filter(self, wb_multiframe): @@ -575,16 +572,18 @@ def test_count_by_type_filter(self, wb_multiframe): allows some results to be filtered out in count_by_type(). :return: ''' - def analysis(current, output): - s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ - current[0] - from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ - current[-1] - key = (s1_index, s2_index, s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) + def analysis(current, output, u): + sele1_index, sele1_heavy_index, atom2, heavy_atom2, dist, angle = current[0] + atom1, heavy_atom1, sele2_index, sele2_heavy_index, dist, angle = current[-1] + sele1 = u.atoms[sele1_index] + sele2 = u.atoms[sele2_index] + (s1_resname, s1_resid, s1_name) = (sele1.resname, sele1.resid, sele1.name) + (s2_resname, s2_resid, s2_name) = (sele2.resname, sele2.resid, sele2.name) + + key = (sele1_index, sele2_index, s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) if s2_name == 'H': output[key] += 1 - result = [((0, 3, 'ALA', 1, 'O', 'ALA', 4, 'H'), 0.1), - ((1, 3, 'ALA', 1, 'H', 'ALA', 4, 'H'), 0.1)] + result = [((0, 11, 'ALA', 1, 'O', 'ALA', 6, 'H'), 0.25)] assert_equal(sorted(wb_multiframe.count_by_type(analysis_func=analysis)), result) def test_count_by_type_merge(self, wb_multiframe): @@ -592,17 +591,17 @@ def test_count_by_type_merge(self, wb_multiframe): This test tests if modifying analysis_func allows some same residue to be merged in count_by_type(). ''' - def analysis(current, output): - s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ - current[0] - from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ - current[-1] + def analysis(current, output, u): + sele1_index, sele1_heavy_index, atom2, heavy_atom2, dist, angle = current[0] + atom1, heavy_atom1, sele2_index, sele2_heavy_index, dist, angle = current[-1] + sele1 = u.atoms[sele1_index] + sele2 = u.atoms[sele2_index] + (s1_resname, s1_resid, s1_name) = (sele1.resname, sele1.resid, sele1.name) + (s2_resname, s2_resid, s2_name) = (sele2.resname, sele2.resid, sele2.name) + key = (s1_resname, s1_resid, s2_resname, s2_resid) - output[key] += 1 - result = [(('ALA', 1, 'ALA', 4), 0.8), - (('ALA', 1, 'ALA', 5), 0.2), - (('ALA', 1, 'ALA', 6), 0.1), - (('ALA', 5, 'ALA', 8), 0.1)] + output[key] = 1 + result = [(('ALA', 1, 'ALA', 6), 1.0)] assert_equal(sorted(wb_multiframe.count_by_type(analysis_func=analysis)), result) def test_count_by_type_order(self, wb_multiframe): @@ -611,19 +610,18 @@ def test_count_by_type_order(self, wb_multiframe): allows the order of water bridge to be separated in count_by_type(). :return: ''' - def analysis(current, output): - s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ - current[0] - from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ - current[-1] + def analysis(current, output, u): + sele1_index, sele1_heavy_index, atom2, heavy_atom2, dist, angle = current[0] + atom1, heavy_atom1, sele2_index, sele2_heavy_index, dist, angle = current[-1] + sele1 = u.atoms[sele1_index] + sele2 = u.atoms[sele2_index] + (s1_resname, s1_resid, s1_name) = (sele1.resname, sele1.resid, sele1.name) + (s2_resname, s2_resid, s2_name) = (sele2.resname, sele2.resid, sele2.name) key = (s1_resname, s1_resid, s2_resname, s2_resid, len(current)-1) - output[key] += 1 - result = [(('ALA', 1, 'ALA', 4, 1), 0.6), - (('ALA', 1, 'ALA', 4, 2), 0.2), - (('ALA', 1, 'ALA', 5, 2), 0.1), - (('ALA', 1, 'ALA', 5, 3), 0.1), - (('ALA', 1, 'ALA', 6, 4), 0.1), - (('ALA', 5, 'ALA', 8, 2), 0.1)] + output[key] = 1 + result = [(('ALA', 1, 'ALA', 6, 0), 0.5), + (('ALA', 1, 'ALA', 6, 1), 0.25), + (('ALA', 1, 'ALA', 6, 2), 0.25)] assert_equal(sorted(wb_multiframe.count_by_type(analysis_func=analysis)), result) def test_count_by_time(self, wb_multiframe): @@ -631,7 +629,7 @@ def test_count_by_time(self, wb_multiframe): This test tests if count_by_times() works. :return: ''' - assert_equal(wb_multiframe.count_by_time(), [(0,1), (1,1), (2,1), (3,1), (4,1), (5,1), (6,1), (7,1), (8,2), (9,2)]) + assert_equal(wb_multiframe.count_by_time(), [(0, 1), (1, 1), (2, 1), (3, 1)]) def test_count_by_time_weight(self, universe_AWA_AWWA): @@ -642,34 +640,40 @@ def test_count_by_time_weight(self, universe_AWA_AWWA): ''' wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) wb.run(verbose=False) - def analysis(current, output): - s1_index, to_index, (s1_resname, s1_resid, s1_name), (to_resname, to_resid, to_name), dist, angle = \ - current[0] - from_index, s2_index, (from_resname, from_resid, from_name), (s2_resname, s2_resid, s2_name), dist, angle = \ - current[-1] + def analysis(current, output, u): + sele1_index, sele1_heavy_index, atom2, heavy_atom2, dist, angle = current[0] + atom1, heavy_atom1, sele2_index, sele2_heavy_index, dist, angle = current[-1] + sele1 = u.atoms[sele1_index] + sele2 = u.atoms[sele2_index] + (s1_resname, s1_resid, s1_name) = (sele1.resname, sele1.resid, sele1.name) + (s2_resname, s2_resid, s2_name) = (sele2.resname, sele2.resid, sele2.name) key = (s1_resname, s1_resid, s2_resname, s2_resid) output[key] += len(current)-1 assert_equal(wb.count_by_time(analysis_func=analysis), [(0,3), ]) def test_count_by_time_empty(self, universe_AWA_AWWA): ''' - See if count_by_type() can handle zero well. + See if count_by_time() can handle zero well. :return: ''' wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) wb.run(verbose=False) - def analysis(current, output): + def analysis(current, output, u): pass assert_equal(wb.count_by_time(analysis_func=analysis), [(0,0), ]) - def test_generate_table(self, wb_multiframe): - '''Test generate table''' - wb_multiframe.generate_table() - table = wb_multiframe.table - assert_array_equal(sorted(wb_multiframe.table.donor_resid), [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 3, 3, 3, 3, 3, 4, 4, 5, 5, 6, 7]) + def test_generate_table_hba(self, wb_multiframe): + '''Test generate table using hydrogen bond analysis format''' + wb_multiframe.generate_table(output_format='donor_acceptor') + assert_array_equal(sorted(wb_multiframe.table.donor_resid), [1, 1, 2, 2, 2, 6, 6]) + + def test_generate_table_s1s2(self, wb_multiframe): + '''Test generate table using hydrogen bond analysis format''' + wb_multiframe.generate_table(output_format='sele1_sele2') + assert_array_equal(sorted(wb_multiframe.table.sele1_resid), [1, 1, 1, 1, 2, 2, 3]) def test_timesteps_by_type(self, wb_multiframe): '''Test the timesteps_by_type function''' + timesteps = sorted(wb_multiframe.timesteps_by_type()) - assert_array_equal(timesteps[3], [0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 0, 1, 9]) \ No newline at end of file + assert_array_equal(timesteps[3], [1, 12, 'ALA', 1, 'H', 'ALA', 6, 'O', 0, 2]) From be3fde371777e00f08d1df40cefa7cc5ef7bc645 Mon Sep 17 00:00:00 2001 From: zhiyi wu Date: Sun, 7 Apr 2019 08:54:15 +0100 Subject: [PATCH 22/23] fix bug --- package/MDAnalysis/analysis/hbonds/wbridge_analysis.py | 3 +-- testsuite/MDAnalysisTests/analysis/test_wbridge.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index 7f25eb60bae..e840b62a0bd 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -1058,9 +1058,8 @@ def _single_frame(self): # Narrow down the water selection selection_resn_id = list(next_round_water) if (not selection_resn_id) or (not self._water): - self._network.append(defaultdict(dict)) logger.warning("No water forming hydrogen bonding with selection 1.") - return + break selection_resn_id = ['(resname {} and resid {})'.format( resname, resid) for resname, resid in selection_resn_id] water_bridges = self._water.select_atoms(' or '.join(selection_resn_id)) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 26b7e023e30..1db0530dbaf 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -283,7 +283,6 @@ def test_nodata(self, universe_DA): This is achieved by not runing the run() first.''' wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', 'protein and (resid 4)', order=0) wb.generate_table() - assert_equal(wb.table, None) assert_equal(wb.timesteps_by_type(), None) assert_equal(wb.count_by_time(), None) assert_equal(wb.count_by_type(), None) From ebf2fb23c617e5b52d434b3bd3fd4fc4bec35de0 Mon Sep 17 00:00:00 2001 From: zhiyiwu Date: Sun, 11 Aug 2019 01:16:12 +0100 Subject: [PATCH 23/23] Corrections being made --- .gitignore | 2 + package/.DS_Store | Bin 10244 -> 0 bytes package/CHANGELOG | 1 + package/MDAnalysis/.DS_Store | Bin 8196 -> 0 bytes .../analysis/hbonds/wbridge_analysis.py | 74 +++++---- .../MDAnalysisTests/analysis/test_wbridge.py | 141 ++++++++++++------ 6 files changed, 146 insertions(+), 72 deletions(-) delete mode 100644 package/.DS_Store delete mode 100644 package/MDAnalysis/.DS_Store diff --git a/.gitignore b/.gitignore index 18682c0c635..67f2bf4528e 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ package/doc/html/ MDAnalysis.log # Ignore the authors.py files as they are generated files authors.py +# Ignore the .DS_Store file in the osx file system +*.DS_Store diff --git a/package/.DS_Store b/package/.DS_Store deleted file mode 100644 index a6c1cc9c120dc0c557ce2175ccc1e6d966db666b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 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 diff --git a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py index e840b62a0bd..7229a521690 100644 --- a/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hbonds/wbridge_analysis.py @@ -276,8 +276,9 @@ class WaterBridgeAnalysis_OtherFF(WaterBridgeAnalysis): Where ``current[0]`` is the first hydrogen bond originating from selection 1 and ``current[-1]`` is the final hydrogen bond ending in selection 2. The output sums up all the information in the current frame and is a dictionary with a user-defined key and the value is the weight of the -corresponding key. At the end of the analysis, the keys from all the frames are collected -and the corresponding values will be summed up and returned. :: +corresponding key. During the analysis phase, the function analysis iterates through all the +water bridges and modify the output in-place. At the end of the analysis, the keys from +all the frames are collected and the corresponding values will be summed up and returned. :: def analysis(current, output, u): r'''This function defines how the type of water bridge should be specified. @@ -288,8 +289,8 @@ def analysis(current, output, u): The current water bridge being analysed is a list of hydrogen bonds from selection 1 to selection 2. output : dict - A dictionary where the key is the type of the water bridge and the value - is the weight of this type of water bridge. + A dictionary which is modified in-place where the key is the type of + the water bridge and the value is the weight of this type of water bridge. u : MDAnalysis.universe The current Universe for looking up atoms.''' @@ -318,7 +319,10 @@ def analysis(current, output, u): [(('ARG', 1, 'O', 'ASP', 3, 'OD'), 1.0),] Note that the result is arranged in the format of ``(key, the proportion of time)``. When no -custom analysis function is supplied, the key is expended for backward compatibility. +custom analysis function is supplied, the key is expanded for backward compatibility. So +that when the same code is executed, the result returned will be the same as the result +given since version 0.17.0 and the same as the +:meth:`HydrogenBondAnalysis.count_by_type`. Some people might only interested in contacts between residues and pay no attention to the details regarding the atom name. However, since multiple water bridges can @@ -337,8 +341,8 @@ def analysis(current, output, u): The current water bridge being analysed is a list of hydrogen bonds from selection 1 to selection 2. output : dict - A dictionary where the key is the type of the water bridge and the value - is the weight of this type of water bridge. + A dictionary which is modified in-place where the key is the type of + the water bridge and the value is the weight of this type of water bridge. u : MDAnalysis.universe The current Universe for looking up atoms. ''' @@ -377,8 +381,8 @@ def analysis(current, output, u): The current water bridge being analysed is a list of hydrogen bonds from selection 1 to selection 2. output : dict - A dictionary where the key is the type of the water bridge and the value - is the weight of this type of water bridge. + A dictionary which is modified in-place where the key is the type of + the water bridge and the value is the weight of this type of water bridge. u : MDAnalysis.universe The current Universe for looking up atoms. ''' @@ -418,8 +422,8 @@ def analysis(current, output, u): The current water bridge being analysed is a list of hydrogen bonds from selection 1 to selection 2. output : dict - A dictionary where the key is the type of the water bridge and the value - is the number of this type of water bridge. + A dictionary which is modified in-place where the key is the type of + the water bridge and the value is the number of this type of water bridge. u : MDAnalysis.universe The current Universe for looking up atoms. ''' @@ -491,8 +495,8 @@ def analysis(current, output, u, **kwargs): The current water bridge being analysed is a list of hydrogen bonds from selection 1 to selection 2. output : dict - A dictionary where the key is the type of the water bridge and the value - is the number of this type of water bridge. + A dictionary which is modified in-place where the key is the type of + the water bridge and the value is the number of this type of water bridge. u : MDAnalysis.universe The current Universe for looking up atoms. ''' @@ -585,10 +589,7 @@ class WaterBridgeAnalysis(AnalysisBase): .. versionadded:: 0.17.0 - The :attr:`WaterBridgeAnalysis.timeseries` has been updated to cope with - higher order water bridge and zero-order hydrogen bonds. - .. versionchanged 0.20.0 """ # use tuple(set()) here so that one can just copy&paste names from the # table; set() takes care for removing duplicates. At the end the @@ -599,7 +600,8 @@ class WaterBridgeAnalysis(AnalysisBase): #: use the keyword `donors` to add a list of additional donor names. DEFAULT_DONORS = { 'CHARMM27': tuple(set([ - 'N', 'OH2', 'OW', 'NE', 'NH1', 'NH2', 'ND2', 'SG', 'NE2', 'ND1', 'NZ', 'OG', 'OG1', 'NE1', 'OH'])), + 'N', 'OH2', 'OW', 'NE', 'NH1', 'NH2', 'ND2', 'SG', 'NE2', 'ND1', + 'NZ', 'OG', 'OG1', 'NE1', 'OH'])), 'GLYCAM06': tuple(set(['N', 'NT', 'N3', 'OH', 'OW'])), 'other': tuple(set([]))} @@ -608,7 +610,8 @@ class WaterBridgeAnalysis(AnalysisBase): #: use the keyword `acceptors` to add a list of additional acceptor names. DEFAULT_ACCEPTORS = { 'CHARMM27': tuple(set([ - 'O', 'OC1', 'OC2', 'OH2', 'OW', 'OD1', 'OD2', 'SG', 'OE1', 'OE1', 'OE2', 'ND1', 'NE2', 'SD', 'OG', 'OG1', 'OH'])), + 'O', 'OC1', 'OC2', 'OH2', 'OW', 'OD1', 'OD2', 'SG', 'OE1', 'OE1', + 'OE2', 'ND1', 'NE2', 'SD', 'OG', 'OG1', 'OH'])), 'GLYCAM06': tuple(set(['N', 'NT', 'O', 'O2', 'OH', 'OS', 'OW', 'OY', 'SM'])), 'other': tuple(set([]))} @@ -748,6 +751,14 @@ def __init__(self, universe, selection1='protein', moving farther than 4 Å + `order` * (2 Å + `distance`)), you might consider setting the `update_selection` keywords to ``True`` to ensure correctness. + + .. versionchanged 0.20.0 + The :attr:`WaterBridgeAnalysis.timeseries` has been updated + see :attr:`WaterBridgeAnalysis.timeseries` for detail. + This class is now based on + :class:`~MDAnalysis.analysis.base.AnalysisBase`. + + """ super(WaterBridgeAnalysis, self).__init__(universe.trajectory, **kwargs) @@ -786,7 +797,8 @@ def __init__(self, universe, selection1='protein', self.acceptors = tuple(set(self.DEFAULT_ACCEPTORS[forcefield]).union(acceptors)) if self.selection1_type not in ('both', 'donor', 'acceptor'): - raise ValueError('HydrogenBondAnalysis: Invalid selection type {0!s}'.format(self.selection1_type)) + raise ValueError('HydrogenBondAnalysis: Invalid selection type {0!s}'.format( + self.selection1_type)) self._network = [] # final result accessed as self.network self.timesteps = None # time for each frame @@ -1213,6 +1225,9 @@ def _expand_index(self, index): def _expand_timeseries(self, entry, output_format=None): ''' Expand the compact data format into the old timeseries form. + The old is defined as the format for release up to 0.19.2. + As is discussed in Issue #2177, the internal storage of the hydrogen + bond information has been changed to the compact format. The function takes in the argument `output_format` to see which output format will be chosen. if `output_format` is not specified, the value will be taken from :attr:`output_format`. If `output_format` is 'sele1_sele2', the output will be the old water bridge analysis format:: @@ -1246,7 +1261,7 @@ def _expand_timeseries(self, entry, output_format=None): # atom1 is hydrogen bond donor, position not swapped. atom1, atom2 = atom1, atom2 else: - raise KeyError('Only \'sele1_sele2\' or \'donor_acceptor\' are allowed as output format.') + raise KeyError("Only 'sele1_sele2' or 'donor_acceptor' are allowed as output format") return (atom1, atom2, self._expand_index(atom1), self._expand_index(atom2), dist, angle) @@ -1274,7 +1289,12 @@ def _generate_timeseries(self, output_format=None): w.run() timeseries = w.timeseries - .. versionchanged:: 0.20.0 + .. versionchanged 0.20.0 + The :attr:`WaterBridgeAnalysis.timeseries` has been updated where + the donor and acceptor string has been changed to tuple + (resname string, resid, name_string). + + ''' output_format = output_format or self.output_format def analysis(current, output, *args, **kwargs): @@ -1283,7 +1303,8 @@ def analysis(current, output, *args, **kwargs): timeseries = [] for frame in self._network: new_frame = [] - self._traverse_water_network(frame, new_frame, analysis_func=analysis, output=new_frame, link_func=self._compact_link) + self._traverse_water_network(frame, new_frame, analysis_func=analysis, + output=new_frame, link_func=self._compact_link) timeseries.append([self._expand_timeseries(entry, output_format) for entry in new_frame]) return timeseries @@ -1386,7 +1407,8 @@ def count_by_type(self, analysis_func=None, **kwargs): result_dict = defaultdict(int) for frame in self._network: frame_dict = defaultdict(int) - self._traverse_water_network(frame, [], analysis_func=analysis_func, output=frame_dict, + self._traverse_water_network(frame, [], analysis_func=analysis_func, + output=frame_dict, link_func=self._full_link, **kwargs) for key, value in frame_dict.items(): result_dict[key] += frame_dict[key] @@ -1427,7 +1449,8 @@ def count_by_time(self, analysis_func=None, **kwargs): result = [] for time, frame in zip(self.timesteps, self._network): result_dict = defaultdict(int) - self._traverse_water_network(frame, [], analysis_func=analysis_func, output=result_dict, + self._traverse_water_network(frame, [], analysis_func=analysis_func, + output=result_dict, link_func=self._full_link, **kwargs) result.append((time, sum([result_dict[key] for key in result_dict]))) return result @@ -1471,7 +1494,8 @@ def timesteps_by_type(self, analysis_func=None, **kwargs): else: timesteps = self.timesteps for time, frame in zip(timesteps, self._network): - self._traverse_water_network(frame, [], analysis_func=analysis_func, output=result, + self._traverse_water_network(frame, [], analysis_func=analysis_func, + output=result, link_func=self._full_link, time=time, **kwargs) result_list = [] diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 1db0530dbaf..c613105b53c 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -37,7 +37,8 @@ def universe_empty(): @staticmethod @pytest.fixture(scope='class') def universe_DA(): - '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond donor''' + '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond + donor''' grofile = '''Test gro file 3 1ALA N 1 0.000 0.000 0.000 @@ -50,7 +51,8 @@ def universe_DA(): @staticmethod @pytest.fixture(scope='class') def universe_DA_PBC(): - '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond donor but in a PBC condition''' + '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond + donor but in a PBC condition''' grofile = '''Test gro file 3 1ALA N 1 0.800 0.000 0.000 @@ -63,7 +65,8 @@ def universe_DA_PBC(): @staticmethod @pytest.fixture(scope='class') def universe_AD(): - '''A universe with one hydrogen bond donor bonding to a hydrogen bond acceptor''' + '''A universe with one hydrogen bond donor bonding to a hydrogen bond + acceptor''' grofile = '''Test gro file 3 1ALA O 1 0.000 0.000 0.000 @@ -76,8 +79,8 @@ def universe_AD(): @staticmethod @pytest.fixture(scope='class') def universe_loop(): - '''A universe with one hydrogen bond acceptor bonding to a water which bonds back to the first hydrogen bond - acceptor and thus form a loop''' + '''A universe with one hydrogen bond acceptor bonding to a water which + bonds back to the first hydrogen bond acceptor and thus form a loop''' grofile = '''Test gro file 5 1ALA O 1 0.000 0.001 0.000 @@ -92,7 +95,8 @@ def universe_loop(): @staticmethod @pytest.fixture(scope='class') def universe_DWA(): - '''A universe with one hydrogen bond donor bonding to a hydrogen bond acceptor through a water''' + '''A universe with one hydrogen bond donor bonding to a hydrogen bond + acceptor through a water''' grofile = '''Test gro file 5 1ALA N 1 0.000 0.000 0.000 @@ -107,7 +111,8 @@ def universe_DWA(): @staticmethod @pytest.fixture(scope='class') def universe_DWD(): - '''A universe with one hydrogen bond donor bonding to a hydrogen bond donor through a water''' + '''A universe with one hydrogen bond donor bonding to a hydrogen bond + donor through a water''' grofile = '''Test gro file 5 1ALA N 1 0.000 0.000 0.000 @@ -137,7 +142,8 @@ def universe_AWA(): @staticmethod @pytest.fixture(scope='class') def universe_AWD(): - '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond donor through a water''' + '''A universe with one hydrogen bond acceptor bonding to a hydrogen + bond donor through a water''' grofile = '''Test gro file 5 1ALA O 1 0.000 0.000 0.000 @@ -152,7 +158,8 @@ def universe_AWD(): @staticmethod @pytest.fixture(scope='class') def universe_AWWA(): - '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through two waters''' + '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond + acceptor through two waters''' grofile = '''Test gro file 7 1ALA O 1 0.000 0.000 0.000 @@ -169,7 +176,8 @@ def universe_AWWA(): @staticmethod @pytest.fixture(scope='class') def universe_AWWWA(): - '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through three waters''' + '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond + acceptor through three waters''' grofile = '''Test gro file 9 1ALA O 1 0.000 0.000 0.000 @@ -188,7 +196,8 @@ def universe_AWWWA(): @staticmethod @pytest.fixture(scope='class') def universe_AWWWWA(): - '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through three waters''' + '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond + acceptor through three waters''' grofile = '''Test gro file 11 1ALA O 1 0.000 0.000 0.000 @@ -209,7 +218,8 @@ def universe_AWWWWA(): @staticmethod @pytest.fixture(scope='class') def universe_branch(): - '''A universe with one hydrogen bond acceptor bonding to two hydrogen bond acceptor in selection 2''' + '''A universe with one hydrogen bond acceptor bonding to two hydrogen + bond acceptor in selection 2''' grofile = '''Test gro file 9 1ALA O 1 0.000 0.000 0.000 @@ -228,7 +238,8 @@ def universe_branch(): @staticmethod @pytest.fixture(scope='class') def universe_AWA_AWWA(): - '''A universe with one hydrogen bond acceptors are bonded through one or two water''' + '''A universe with one hydrogen bond acceptors are bonded through one or + two water''' grofile = '''Test gro file 12 1ALA O 1 0.000 0.000 0.000 @@ -268,20 +279,25 @@ def wb_multiframe(): 6ALA O 13 1.400 0.000 0.000 10.0 10.0 10.0''' u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', order=4) + wb = WaterBridgeAnalysis(u, 'protein and (resid 1)', 'protein and (resid 4)', + order=4) # Build an dummy WaterBridgeAnalysis object for testing wb._network = [] wb._network.append({(1, 0, 12, None, 2.0, 180.0): None}) wb._network.append({(0, None, 12, 13, 2.0, 180.0): None}) - wb._network.append({(1, 0, 3, None, 2.0, 180.0): {(4, 2, 12, None, 2.0, 180.0): None}}) - wb._network.append({(0, None, 3, 2, 2.0, 180.0): {(4, 2, 5, None, 2.0, 180.0): {(5, None, 11, 12, 2.0, 180.0): None}}}) + wb._network.append({(1, 0, 3, None, 2.0, 180.0): + {(4, 2, 12, None, 2.0, 180.0): None}}) + wb._network.append({(0, None, 3, 2, 2.0, 180.0): + {(4, 2, 5, None, 2.0, 180.0): + {(5, None, 11, 12, 2.0, 180.0): None}}}) wb.timesteps = range(len(wb._network)) return wb def test_nodata(self, universe_DA): '''Test if the funtions can run when there is no data. This is achieved by not runing the run() first.''' - wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', 'protein and (resid 4)', order=0) + wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', + 'protein and (resid 4)', order=0) wb.generate_table() assert_equal(wb.timesteps_by_type(), None) assert_equal(wb.count_by_time(), None) @@ -290,7 +306,8 @@ def test_nodata(self, universe_DA): def test_selection_type_error(self, universe_DA): '''Test the case when the wrong selection1_type is given''' try: - wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', 'protein and (resid 4)', order=0, selection1_type='aaa') + wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', + 'protein and (resid 4)', order=0, selection1_type='aaa') except ValueError: pass else: @@ -298,33 +315,38 @@ def test_selection_type_error(self, universe_DA): def test_empty_selection(self, universe_DA): '''Test the case when selection yields empty result''' - wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 9)', 'protein and (resid 10)', order=0) + wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 9)', + 'protein and (resid 10)', order=0) wb.run() assert wb._network == [{}] def test_loop(self, universe_loop): '''Test if loop can be handled correctly''' - wb = WaterBridgeAnalysis(universe_loop, 'protein and (resid 1)', 'protein and (resid 1 or resid 4)') + wb = WaterBridgeAnalysis(universe_loop, 'protein and (resid 1)', + 'protein and (resid 1 or resid 4)') wb.run() assert_equal(len(wb._network[0].keys()), 2) def test_donor_accepter(self, universe_DA): '''Test zeroth order donor to acceptor hydrogen bonding''' - wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', 'protein and (resid 4)', order=0, update_selection=True, debug=True) + wb = WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', + 'protein and (resid 4)', order=0, update_selection=True, debug=True) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) def test_donor_accepter_pbc(self, universe_DA_PBC): '''Test zeroth order donor to acceptor hydrogen bonding in PBC conditions''' - wb = WaterBridgeAnalysis(universe_DA_PBC, 'protein and (resid 1)', 'protein and (resid 4)', order=0, pbc=True) + wb = WaterBridgeAnalysis(universe_DA_PBC, 'protein and (resid 1)', + 'protein and (resid 4)', order=0, pbc=True) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) def test_accepter_donor(self, universe_AD): '''Test zeroth order acceptor to donor hydrogen bonding''' - wb = WaterBridgeAnalysis(universe_AD, 'protein and (resid 1)', 'protein and (resid 4)', order=0) + wb = WaterBridgeAnalysis(universe_AD, 'protein and (resid 1)', + 'protein and (resid 4)', order=0) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, None, 1, 2)) @@ -332,7 +354,8 @@ def test_accepter_donor(self, universe_AD): def test_acceptor_water_accepter(self, universe_AWA): '''Test case where the hydrogen bond acceptor from selection 1 form water bridge with hydrogen bond acceptor from selection 2''' - wb = WaterBridgeAnalysis(universe_AWA, 'protein and (resid 1)', 'protein and (resid 4)') + wb = WaterBridgeAnalysis(universe_AWA, 'protein and (resid 1)', + 'protein and (resid 4)') wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -343,7 +366,8 @@ def test_acceptor_water_accepter(self, universe_AWA): def test_donor_water_accepter(self, universe_DWA): '''Test case where the hydrogen bond donor from selection 1 form water bridge with hydrogen bond acceptor from selection 2''' - wb = WaterBridgeAnalysis(universe_DWA, 'protein and (resid 1)', 'protein and (resid 4)') + wb = WaterBridgeAnalysis(universe_DWA, 'protein and (resid 1)', + 'protein and (resid 4)') wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) @@ -354,7 +378,8 @@ def test_donor_water_accepter(self, universe_DWA): def test_acceptor_water_donor(self, universe_AWD): '''Test case where the hydrogen bond acceptor from selection 1 form water bridge with hydrogen bond donor from selection 2''' - wb = WaterBridgeAnalysis(universe_AWD, 'protein and (resid 1)', 'protein and (resid 4)') + wb = WaterBridgeAnalysis(universe_AWD, 'protein and (resid 1)', + 'protein and (resid 4)') wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -365,7 +390,8 @@ def test_acceptor_water_donor(self, universe_AWD): def test_donor_water_donor(self, universe_DWD): '''Test case where the hydrogen bond donor from selection 1 form water bridge with hydrogen bond donor from selection 2''' - wb = WaterBridgeAnalysis(universe_DWD, 'protein and (resid 1)', 'protein and (resid 4)') + wb = WaterBridgeAnalysis(universe_DWD, 'protein and (resid 1)', + 'protein and (resid 4)') wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) @@ -385,7 +411,8 @@ def test_same_selection(self, universe_DWA): However, the protein only forms one hydrogen bond with the water. This entry won't be included. ''' - wb = WaterBridgeAnalysis(universe_DWA, 'protein and resid 1', 'protein and resid 1') + wb = WaterBridgeAnalysis(universe_DWA, 'protein and resid 1', + 'protein and resid 1') wb.run(verbose=False) assert_equal(wb._network[0], defaultdict(dict)) @@ -393,11 +420,13 @@ def test_acceptor_2water_accepter(self, universe_AWWA): '''Test case where the hydrogen bond acceptor from selection 1 form second order water bridge with hydrogen bond acceptor from selection 2''' # test first order - wb = WaterBridgeAnalysis(universe_AWWA, 'protein and (resid 1)', 'protein and (resid 4)') + wb = WaterBridgeAnalysis(universe_AWWA, 'protein and (resid 1)', + 'protein and (resid 4)') wb.run(verbose=False) assert_equal(wb._network[0], defaultdict(dict)) # test second order - wb = WaterBridgeAnalysis(universe_AWWA, 'protein and (resid 1)', 'protein and (resid 4)', order=2) + wb = WaterBridgeAnalysis(universe_AWWA, 'protein and (resid 1)', + 'protein and (resid 4)', order=2) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -407,7 +436,8 @@ def test_acceptor_2water_accepter(self, universe_AWWA): assert_equal(list(third.keys())[0][:4], (5, 4, 6, None)) assert_equal(third[list(third.keys())[0]], None) # test third order - wb = WaterBridgeAnalysis(universe_AWWA, 'protein and (resid 1)', 'protein and (resid 4)', order=3) + wb = WaterBridgeAnalysis(universe_AWWA, 'protein and (resid 1)', + 'protein and (resid 4)', order=3) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -420,11 +450,13 @@ def test_acceptor_2water_accepter(self, universe_AWWA): def test_acceptor_3water_accepter(self, universe_AWWWA): '''Test case where the hydrogen bond acceptor from selection 1 form third order water bridge with hydrogen bond acceptor from selection 2''' - wb = WaterBridgeAnalysis(universe_AWWWA, 'protein and (resid 1)', 'protein and (resid 5)', order=2) + wb = WaterBridgeAnalysis(universe_AWWWA, 'protein and (resid 1)', + 'protein and (resid 5)', order=2) wb.run(verbose=False) assert_equal(wb._network[0], defaultdict(dict)) - wb = WaterBridgeAnalysis(universe_AWWWA, 'protein and (resid 1)', 'protein and (resid 5)', order=3) + wb = WaterBridgeAnalysis(universe_AWWWA, 'protein and (resid 1)', + 'protein and (resid 5)', order=3) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -436,7 +468,8 @@ def test_acceptor_3water_accepter(self, universe_AWWWA): assert_equal(list(fourth.keys())[0][:4], (7, 6, 8, None)) assert_equal(fourth[list(fourth.keys())[0]], None) - wb = WaterBridgeAnalysis(universe_AWWWA, 'protein and (resid 1)', 'protein and (resid 5)', order=4) + wb = WaterBridgeAnalysis(universe_AWWWA, 'protein and (resid 1)', + 'protein and (resid 5)', order=4) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -451,11 +484,13 @@ def test_acceptor_3water_accepter(self, universe_AWWWA): def test_acceptor_4water_accepter(self, universe_AWWWWA): '''Test case where the hydrogen bond acceptor from selection 1 form fourth order water bridge with hydrogen bond acceptor from selection 2''' - wb = WaterBridgeAnalysis(universe_AWWWWA, 'protein and (resid 1)', 'protein and (resid 6)', order=3) + wb = WaterBridgeAnalysis(universe_AWWWWA, 'protein and (resid 1)', + 'protein and (resid 6)', order=3) wb.run(verbose=False) assert_equal(wb._network[0], defaultdict(dict)) - wb = WaterBridgeAnalysis(universe_AWWWWA, 'protein and (resid 1)', 'protein and (resid 6)', order=4) + wb = WaterBridgeAnalysis(universe_AWWWWA, 'protein and (resid 1)', + 'protein and (resid 6)', order=4) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -469,7 +504,8 @@ def test_acceptor_4water_accepter(self, universe_AWWWWA): assert_equal(list(fifth.keys())[0][:4], (9, 8, 10, None)) assert_equal(fifth[list(fifth.keys())[0]], None) - wb = WaterBridgeAnalysis(universe_AWWWWA, 'protein and (resid 1)', 'protein and (resid 6)', order=5) + wb = WaterBridgeAnalysis(universe_AWWWWA, 'protein and (resid 1)', + 'protein and (resid 6)', order=5) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -487,7 +523,8 @@ def test_acceptor_22water_accepter(self, universe_branch): '''Test case where the hydrogen bond acceptor from selection 1 form a second order water bridge with hydrogen bond acceptor from selection 2 and the last water is linked to two residues in selection 2''' - wb = WaterBridgeAnalysis(universe_branch, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) + wb = WaterBridgeAnalysis(universe_branch, 'protein and (resid 1)', + 'protein and (resid 4 or resid 5)', order=2) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -499,7 +536,8 @@ def test_acceptor_22water_accepter(self, universe_branch): def test_timeseries_wba(self, universe_branch): '''Test if the time series data is correctly generated in water bridge analysis format''' - wb = WaterBridgeAnalysis(universe_branch, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) + wb = WaterBridgeAnalysis(universe_branch, 'protein and (resid 1)', + 'protein and (resid 4 or resid 5)', order=2) wb.output_format = 'sele1_sele2' wb.run(verbose=False) timeseries = sorted(wb.timeseries[0]) @@ -511,7 +549,8 @@ def test_timeseries_wba(self, universe_branch): def test_timeseries_hba(self, universe_branch): '''Test if the time series data is correctly generated in hydrogen bond analysis format''' - wb = WaterBridgeAnalysis(universe_branch, 'protein and (resid 1)', 'protein and (resid 4 or resid 5)', order=2) + wb = WaterBridgeAnalysis(universe_branch, 'protein and (resid 1)', + 'protein and (resid 4 or resid 5)', order=2) wb.output_format = 'donor_acceptor' wb.run(verbose=False) timeseries = sorted(wb.timeseries[0]) @@ -523,7 +562,8 @@ def test_timeseries_hba(self, universe_branch): def test_acceptor_12water_accepter(self, universe_AWA_AWWA): '''Test of independent first order and second can be recognised correctely''' - wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=1) + wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', + 'protein and (resid 4 or resid 8)', order=1) wb.run(verbose=False) network = wb._network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -531,7 +571,8 @@ def test_acceptor_12water_accepter(self, universe_AWA_AWWA): assert_equal(list(second.keys())[0][:4], (3, 1, 4, None)) assert_equal(second[list(second.keys())[0]], None) network = wb._network[0] - wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) + wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', + 'protein and (resid 4 or resid 8)', order=2) wb.run(verbose=False) network = wb._network[0] assert_equal([(0, None, 2, 1), (5, None, 7, 6)], @@ -541,7 +582,8 @@ def test_count_by_type_single_link(self, universe_DWA): ''' This test tests the simplest water bridge to see if count_by_type() works. ''' - wb = WaterBridgeAnalysis(universe_DWA, 'protein and (resid 1)', 'protein and (resid 4)') + wb = WaterBridgeAnalysis(universe_DWA, 'protein and (resid 1)', + 'protein and (resid 4)') wb.run(verbose=False) assert_equal(wb.count_by_type(), [(1, 4, 'ALA', 1, 'H', 'ALA', 4, 'O', 1.)]) @@ -549,9 +591,12 @@ def test_count_by_type_multiple_link(self, universe_AWA_AWWA): ''' This test tests if count_by_type() can give the correct result for more than 1 links. ''' - wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) + wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', + 'protein and (resid 4 or resid 8)', order=2) wb.run(verbose=False) - assert_equal(sorted(wb.count_by_type()), [[0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 1.0], [5, 11, 'ALA', 5, 'O', 'ALA', 8, 'O', 1.0]]) + assert_equal(sorted(wb.count_by_type()), + [[0, 4, 'ALA', 1, 'O', 'ALA', 4, 'O', 1.0], + [5, 11, 'ALA', 5, 'O', 'ALA', 8, 'O', 1.0]]) def test_count_by_type_multiple_frame(self, wb_multiframe): @@ -637,7 +682,8 @@ def test_count_by_time_weight(self, universe_AWA_AWWA): in count_by_type(). :return: ''' - wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) + wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', + 'protein and (resid 4 or resid 8)', order=2) wb.run(verbose=False) def analysis(current, output, u): sele1_index, sele1_heavy_index, atom2, heavy_atom2, dist, angle = current[0] @@ -655,7 +701,8 @@ def test_count_by_time_empty(self, universe_AWA_AWWA): See if count_by_time() can handle zero well. :return: ''' - wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', 'protein and (resid 4 or resid 8)', order=2) + wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', + 'protein and (resid 4 or resid 8)', order=2) wb.run(verbose=False) def analysis(current, output, u): pass