diff --git a/package/CHANGELOG b/package/CHANGELOG index 248a2b35371..47aaac06fc0 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -28,6 +28,7 @@ Fixes Enhancements * Enables parallelization for analysis.polymer.PersistenceLength (Issue #4671) + * Addition of 'water' token for water selection (Issue #4839) * Enables parallelization for analysis.density.DensityAnalysis (Issue #4677, PR #4729) * Enables parallelization for analysis.contacts.Contacts (Issue #4660) * Enable parallelization for analysis.nucleicacids.NucPairDist (Issue #4670) diff --git a/package/MDAnalysis/analysis/__init__.py b/package/MDAnalysis/analysis/__init__.py index 056c7899826..804f9dbf24c 100644 --- a/package/MDAnalysis/analysis/__init__.py +++ b/package/MDAnalysis/analysis/__init__.py @@ -22,31 +22,31 @@ # __all__ = [ - 'align', - 'backends', - 'base', - 'contacts', - 'density', - 'distances', - 'diffusionmap', - 'dihedrals', - 'distances', - 'dielectric', - 'gnm', - 'hbonds', - 'helix_analysis', - 'hole2', - 'hydrogenbonds', - 'leaflet', - 'lineardensity', - 'msd', - 'nuclinfo', - 'nucleicacids', - 'polymer', - 'pca', - 'psa', - 'rdf', - 'results', - 'rms', - 'waterdynamics', + "align", + "backends", + "base", + "contacts", + "density", + "distances", + "diffusionmap", + "dihedrals", + "distances", + "dielectric", + "gnm", + "hbonds", + "helix_analysis", + "hole2", + "hydrogenbonds", + "leaflet", + "lineardensity", + "msd", + "nuclinfo", + "nucleicacids", + "polymer", + "pca", + "psa", + "rdf", + "results", + "rms", + "waterdynamics", ] diff --git a/package/MDAnalysis/analysis/backends.py b/package/MDAnalysis/analysis/backends.py index 38917cb2ae7..0cfad4e1c31 100644 --- a/package/MDAnalysis/analysis/backends.py +++ b/package/MDAnalysis/analysis/backends.py @@ -35,6 +35,7 @@ ------- """ + import warnings from typing import Callable from MDAnalysis.lib.util import is_installed @@ -102,8 +103,9 @@ def _get_checks(self): checked during ``_validate()`` run """ return { - isinstance(self.n_workers, int) and self.n_workers > 0: - f"n_workers should be positive integer, got {self.n_workers=}", + isinstance(self.n_workers, int) + and self.n_workers + > 0: f"n_workers should be positive integer, got {self.n_workers=}", } def _get_warnings(self): @@ -183,8 +185,8 @@ def _get_warnings(self): checked during ``_validate()`` run """ return { - self.n_workers == 1: - "n_workers is ignored when executing with backend='serial'" + self.n_workers + == 1: "n_workers is ignored when executing with backend='serial'" } def apply(self, func: Callable, computations: list) -> list: @@ -307,10 +309,12 @@ def apply(self, func: Callable, computations: list) -> list: import dask computations = [delayed(func)(task) for task in computations] - results = dask.compute(computations, - scheduler="processes", - chunksize=1, - num_workers=self.n_workers)[0] + results = dask.compute( + computations, + scheduler="processes", + chunksize=1, + num_workers=self.n_workers, + )[0] return results def _get_checks(self): @@ -326,8 +330,9 @@ def _get_checks(self): """ base_checks = super()._get_checks() checks = { - is_installed("dask"): - ("module 'dask' is missing. Please install 'dask': " - "https://docs.dask.org/en/stable/install.html") + is_installed("dask"): ( + "module 'dask' is missing. Please install 'dask': " + "https://docs.dask.org/en/stable/install.html" + ) } return base_checks | checks diff --git a/package/MDAnalysis/analysis/base.py b/package/MDAnalysis/analysis/base.py index 4e7f58dc0bd..675c6d6967b 100644 --- a/package/MDAnalysis/analysis/base.py +++ b/package/MDAnalysis/analysis/base.py @@ -159,7 +159,12 @@ def _get_aggregator(self): from ..core.groups import AtomGroup from ..lib.log import ProgressBar -from .backends import BackendDask, BackendMultiprocessing, BackendSerial, BackendBase +from .backends import ( + BackendDask, + BackendMultiprocessing, + BackendSerial, + BackendBase, +) from .results import Results, ResultsGroup logger = logging.getLogger(__name__) @@ -288,10 +293,10 @@ def get_supported_backends(cls): """ return ("serial",) - # class authors: override _analysis_algorithm_is_parallelizable - # in derived classes and only set to True if you have confirmed - # that your algorithm works reliably when parallelized with - # the split-apply-combine approach (see docs) + # class authors: override _analysis_algorithm_is_parallelizable + # in derived classes and only set to True if you have confirmed + # that your algorithm works reliably when parallelized with + # the split-apply-combine approach (see docs) _analysis_algorithm_is_parallelizable = False @property @@ -301,13 +306,13 @@ def parallelizable(self): :meth:`_single_frame` to multiple workers and then combine them with a proper :meth:`_conclude` call. If set to ``False``, no backends except for ``serial`` are supported. - + .. note:: If you want to check parallelizability of the whole class, without explicitly creating an instance of the class, see :attr:`_analysis_algorithm_is_parallelizable`. Note that you setting it to other value will break things if the algorithm behind the analysis is not trivially parallelizable. - + Returns ------- @@ -325,9 +330,9 @@ def __init__(self, trajectory, verbose=False, **kwargs): self._verbose = verbose self.results = Results() - def _define_run_frames(self, trajectory, - start=None, stop=None, step=None, frames=None - ) -> Union[slice, np.ndarray]: + def _define_run_frames( + self, trajectory, start=None, stop=None, step=None, frames=None + ) -> Union[slice, np.ndarray]: """Defines limits for the whole run, as passed by self.run() arguments Parameters @@ -362,10 +367,14 @@ def _define_run_frames(self, trajectory, self._trajectory = trajectory if frames is not None: if not all(opt is None for opt in [start, stop, step]): - raise ValueError("start/stop/step cannot be combined with frames") + raise ValueError( + "start/stop/step cannot be combined with frames" + ) slicer = frames else: - start, stop, step = trajectory.check_slice_indices(start, stop, step) + start, stop, step = trajectory.check_slice_indices( + start, stop, step + ) slicer = slice(start, stop, step) self.start, self.stop, self.step = start, stop, step return slicer @@ -388,7 +397,9 @@ def _prepare_sliced_trajectory(self, slicer: Union[slice, np.ndarray]): self.frames = np.zeros(self.n_frames, dtype=int) self.times = np.zeros(self.n_frames) - def _setup_frames(self, trajectory, start=None, stop=None, step=None, frames=None): + def _setup_frames( + self, trajectory, start=None, stop=None, step=None, frames=None + ): """Pass a Reader object and define the desired iteration pattern through the trajectory @@ -417,11 +428,11 @@ def _setup_frames(self, trajectory, start=None, stop=None, step=None, frames=Non .. versionchanged:: 1.0.0 Added .frames and .times arrays as attributes - + .. versionchanged:: 2.2.0 Added ability to iterate through trajectory by passing a list of frame indices in the `frames` keyword argument - + .. versionchanged:: 2.8.0 Split function into two: :meth:`_define_run_frames` and :meth:`_prepare_sliced_trajectory`: first one defines the limits @@ -450,8 +461,8 @@ def _single_frame(self): def _prepare(self): """ - Set things up before the analysis loop begins. - + Set things up before the analysis loop begins. + Notes ----- ``self.results`` is initialized already in :meth:`self.__init__` with an @@ -477,9 +488,13 @@ def _conclude(self): """ pass # pylint: disable=unnecessary-pass - def _compute(self, indexed_frames: np.ndarray, - verbose: bool = None, - *, progressbar_kwargs=None) -> "AnalysisBase": + def _compute( + self, + indexed_frames: np.ndarray, + verbose: bool = None, + *, + progressbar_kwargs=None, + ) -> "AnalysisBase": """Perform the calculation on a balanced slice of frames that have been setup prior to that using _setup_computation_groups() @@ -504,7 +519,9 @@ def _compute(self, indexed_frames: np.ndarray, progressbar_kwargs = {} logger.info("Choosing frames to analyze") # if verbose unchanged, use class default - verbose = getattr(self, "_verbose", False) if verbose is None else verbose + verbose = ( + getattr(self, "_verbose", False) if verbose is None else verbose + ) frames = indexed_frames[:, 1] @@ -514,10 +531,11 @@ def _compute(self, indexed_frames: np.ndarray, if len(frames) == 0: # if `frames` were empty in `run` or `stop=0` return self - for idx, ts in enumerate(ProgressBar( - self._sliced_trajectory, - verbose=verbose, - **progressbar_kwargs)): + for idx, ts in enumerate( + ProgressBar( + self._sliced_trajectory, verbose=verbose, **progressbar_kwargs + ) + ): self._frame_index = idx # accessed later by subclasses self._ts = ts self.frames[idx] = ts.frame @@ -527,9 +545,12 @@ def _compute(self, indexed_frames: np.ndarray, return self def _setup_computation_groups( - self, n_parts: int, - start: int = None, stop: int = None, step: int = None, - frames: Union[slice, np.ndarray] = None + self, + n_parts: int, + start: int = None, + stop: int = None, + step: int = None, + frames: Union[slice, np.ndarray] = None, ) -> list[np.ndarray]: """ Splits the trajectory frames, defined by ``start/stop/step`` or @@ -566,7 +587,9 @@ def _setup_computation_groups( .. versionadded:: 2.8.0 """ if frames is None: - start, stop, step = self._trajectory.check_slice_indices(start, stop, step) + start, stop, step = self._trajectory.check_slice_indices( + start, stop, step + ) used_frames = np.arange(start, stop, step) elif not all(opt is None for opt in [start, stop, step]): raise ValueError("start/stop/step cannot be combined with frames") @@ -578,23 +601,27 @@ def _setup_computation_groups( used_frames = arange[used_frames] # similar to list(enumerate(frames)) - enumerated_frames = np.vstack([np.arange(len(used_frames)), used_frames]).T + enumerated_frames = np.vstack( + [np.arange(len(used_frames)), used_frames] + ).T if len(enumerated_frames) == 0: return [np.empty((0, 2), dtype=np.int64)] elif len(enumerated_frames) < n_parts: # Issue #4685 n_parts = len(enumerated_frames) - warnings.warn(f"Set `n_parts` to {n_parts} to match the total " - "number of frames being analyzed") + warnings.warn( + f"Set `n_parts` to {n_parts} to match the total " + "number of frames being analyzed" + ) return np.array_split(enumerated_frames, n_parts) def _configure_backend( - self, - backend: Union[str, BackendBase], - n_workers: int, - unsupported_backend: bool = False - ) -> BackendBase: + self, + backend: Union[str, BackendBase], + n_workers: int, + unsupported_backend: bool = False, + ) -> BackendBase: """Matches a passed backend string value with class attributes :attr:`parallelizable` and :meth:`get_supported_backends()` to check if downstream calculations can be performed. @@ -642,13 +669,12 @@ def _configure_backend( builtin_backends = { "serial": BackendSerial, "multiprocessing": BackendMultiprocessing, - "dask": BackendDask + "dask": BackendDask, } backend_class = builtin_backends.get(backend, backend) supported_backend_classes = [ - builtin_backends.get(b) - for b in self.get_supported_backends() + builtin_backends.get(b) for b in self.get_supported_backends() ] # check for serial-only classes @@ -656,20 +682,28 @@ def _configure_backend( raise ValueError(f"Can not parallelize class {self.__class__}") # make sure user enabled 'unsupported_backend=True' for custom classes - if (not unsupported_backend and self.parallelizable - and backend_class not in supported_backend_classes): - raise ValueError(( - f"Must specify 'unsupported_backend=True'" - f"if you want to use a custom {backend_class=} for {self.__class__}" - )) + if ( + not unsupported_backend + and self.parallelizable + and backend_class not in supported_backend_classes + ): + raise ValueError( + ( + f"Must specify 'unsupported_backend=True'" + f"if you want to use a custom {backend_class=} for {self.__class__}" + ) + ) # check for the presence of parallelizable transformations if backend_class is not BackendSerial and any( - not t.parallelizable - for t in self._trajectory.transformations): - raise ValueError(( - "Trajectory should not have " - "associated unparallelizable transformations")) + not t.parallelizable for t in self._trajectory.transformations + ): + raise ValueError( + ( + "Trajectory should not have " + "associated unparallelizable transformations" + ) + ) # conclude mapping from string to backend class if it's a builtin backend if isinstance(backend, str): @@ -679,21 +713,27 @@ def _configure_backend( if ( isinstance(backend, BackendBase) and n_workers is not None - and hasattr(backend, 'n_workers') + and hasattr(backend, "n_workers") and backend.n_workers != n_workers ): - raise ValueError(( - f"n_workers specified twice: in {backend.n_workers=}" - f"and in run({n_workers=}). Remove it from run()" - )) + raise ValueError( + ( + f"n_workers specified twice: in {backend.n_workers=}" + f"and in run({n_workers=}). Remove it from run()" + ) + ) # or pass along an instance of the class itself # after ensuring it has apply method - if not isinstance(backend, BackendBase) or not hasattr(backend, "apply"): - raise ValueError(( - f"{backend=} is invalid: should have 'apply' method " - "and be instance of MDAnalysis.analysis.backends.BackendBase" - )) + if not isinstance(backend, BackendBase) or not hasattr( + backend, "apply" + ): + raise ValueError( + ( + f"{backend=} is invalid: should have 'apply' method " + "and be instance of MDAnalysis.analysis.backends.BackendBase" + ) + ) return backend def run( @@ -775,18 +815,23 @@ def run( # default to serial execution backend = "serial" if backend is None else backend - progressbar_kwargs = {} if progressbar_kwargs is None else progressbar_kwargs - if ((progressbar_kwargs or verbose) and - not (backend == "serial" or - isinstance(backend, BackendSerial))): - raise ValueError("Can not display progressbar with non-serial backend") + progressbar_kwargs = ( + {} if progressbar_kwargs is None else progressbar_kwargs + ) + if (progressbar_kwargs or verbose) and not ( + backend == "serial" or isinstance(backend, BackendSerial) + ): + raise ValueError( + "Can not display progressbar with non-serial backend" + ) # if number of workers not specified, try getting the number from # the backend instance if possible, or set to 1 if n_workers is None: n_workers = ( backend.n_workers - if isinstance(backend, BackendBase) and hasattr(backend, "n_workers") + if isinstance(backend, BackendBase) + and hasattr(backend, "n_workers") else 1 ) @@ -798,22 +843,31 @@ def run( executor = self._configure_backend( backend=backend, n_workers=n_workers, - unsupported_backend=unsupported_backend) + unsupported_backend=unsupported_backend, + ) if ( hasattr(executor, "n_workers") and n_parts < executor.n_workers ): # using executor's value here for non-default executors - warnings.warn(( - f"Analysis not making use of all workers: " - f"{executor.n_workers=} is greater than {n_parts=}")) + warnings.warn( + ( + f"Analysis not making use of all workers: " + f"{executor.n_workers=} is greater than {n_parts=}" + ) + ) # start preparing the run worker_func = partial( self._compute, progressbar_kwargs=progressbar_kwargs, - verbose=verbose) + verbose=verbose, + ) self._setup_frames( trajectory=self._trajectory, - start=start, stop=stop, step=step, frames=frames) + start=start, + stop=stop, + step=step, + frames=frames, + ) computation_groups = self._setup_computation_groups( start=start, stop=stop, step=step, frames=frames, n_parts=n_parts ) @@ -822,7 +876,8 @@ def run( # we need `AnalysisBase` classes # since they hold `frames`, `times` and `results` attributes remote_objects: list["AnalysisBase"] = executor.apply( - worker_func, computation_groups) + worker_func, computation_groups + ) self.frames = np.hstack([obj.frames for obj in remote_objects]) self.times = np.hstack([obj.times for obj in remote_objects]) @@ -911,8 +966,9 @@ def get_supported_backends(cls): return ("serial", "multiprocessing", "dask") def __init__(self, function, trajectory=None, *args, **kwargs): - if (trajectory is not None) and (not isinstance( - trajectory, coordinates.base.ProtoReader)): + if (trajectory is not None) and ( + not isinstance(trajectory, coordinates.base.ProtoReader) + ): args = (trajectory,) + args trajectory = None @@ -940,7 +996,9 @@ def _get_aggregator(self): return ResultsGroup({"timeseries": ResultsGroup.flatten_sequence}) def _single_frame(self): - self.results.timeseries.append(self.function(*self.args, **self.kwargs)) + self.results.timeseries.append( + self.function(*self.args, **self.kwargs) + ) def _conclude(self): self.results.frames = self.frames @@ -1001,7 +1059,9 @@ def RotationMatrix(mobile, ref): class WrapperClass(AnalysisFromFunction): def __init__(self, trajectory=None, *args, **kwargs): - super(WrapperClass, self).__init__(function, trajectory, *args, **kwargs) + super(WrapperClass, self).__init__( + function, trajectory, *args, **kwargs + ) @classmethod def get_supported_backends(cls): @@ -1045,8 +1105,9 @@ def _filter_baseanalysis_kwargs(function, kwargs): n_base_defaults = len(base_argspec.defaults) base_kwargs = { name: val - for name, val in zip(base_argspec.args[-n_base_defaults:], - base_argspec.defaults) + for name, val in zip( + base_argspec.args[-n_base_defaults:], base_argspec.defaults + ) } try: diff --git a/package/MDAnalysis/analysis/bat.py b/package/MDAnalysis/analysis/bat.py index 9c1995f7ccc..9b08c7dbff4 100644 --- a/package/MDAnalysis/analysis/bat.py +++ b/package/MDAnalysis/analysis/bat.py @@ -215,20 +215,30 @@ def _find_torsions(root, atoms): torsionAdded = False for a1 in selected_atoms: # Find a0, which is a new atom connected to the selected atom - a0_list = _sort_atoms_by_mass(a for a in a1.bonded_atoms \ - if (a in atoms) and (a not in selected_atoms)) + a0_list = _sort_atoms_by_mass( + a + for a in a1.bonded_atoms + if (a in atoms) and (a not in selected_atoms) + ) for a0 in a0_list: # Find a2, which is connected to a1, is not a terminal atom, # and has been selected - a2_list = _sort_atoms_by_mass(a for a in a1.bonded_atoms \ - if (a!=a0) and len(a.bonded_atoms)>1 and \ - (a in atoms) and (a in selected_atoms)) + a2_list = _sort_atoms_by_mass( + a + for a in a1.bonded_atoms + if (a != a0) + and len(a.bonded_atoms) > 1 + and (a in atoms) + and (a in selected_atoms) + ) for a2 in a2_list: # Find a3, which is # connected to a2, has been selected, and is not a1 - a3_list = _sort_atoms_by_mass(a for a in a2.bonded_atoms \ - if (a!=a1) and \ - (a in atoms) and (a in selected_atoms)) + a3_list = _sort_atoms_by_mass( + a + for a in a2.bonded_atoms + if (a != a1) and (a in atoms) and (a in selected_atoms) + ) for a3 in a3_list: # Add the torsion to the list of torsions torsions.append(mda.AtomGroup([a0, a1, a2, a3])) @@ -239,11 +249,11 @@ def _find_torsions(root, atoms): break # out of the a3 loop break # out of the a2 loop if torsionAdded is False: - print('Selected atoms:') + print("Selected atoms:") print([a.index + 1 for a in selected_atoms]) - print('Torsions found:') + print("Torsions found:") print([list(t.indices + 1) for t in torsions]) - raise ValueError('Additional torsions not found.') + raise ValueError("Additional torsions not found.") return torsions @@ -254,13 +264,14 @@ class BAT(AnalysisBase): the group of atoms and all frame in the trajectory belonging to `ag`. .. versionchanged:: 2.8.0 - Enabled **parallel execution** with the ``multiprocessing`` and ``dask`` - backends; use the new method :meth:`get_supported_backends` to see all + Enabled **parallel execution** with the ``multiprocessing`` and ``dask`` + backends; use the new method :meth:`get_supported_backends` to see all supported backends. """ + _analysis_algorithm_is_parallelizable = True - + @classmethod def get_supported_backends(cls): return ( @@ -270,11 +281,10 @@ def get_supported_backends(cls): ) @due.dcite( - Doi("10.1002/jcc.26036"), - description="Bond-Angle-Torsions Coordinate Transformation", - path="MDAnalysis.analysis.bat.BAT", + Doi("10.1002/jcc.26036"), + description="Bond-Angle-Torsions Coordinate Transformation", + path="MDAnalysis.analysis.bat.BAT", ) - def __init__(self, ag, initial_atom=None, filename=None, **kwargs): r"""Parameters ---------- @@ -307,20 +317,21 @@ def __init__(self, ag, initial_atom=None, filename=None, **kwargs): self._ag = ag # Check that the ag contains bonds - if not hasattr(self._ag, 'bonds'): - raise AttributeError('AtomGroup has no attribute bonds') + if not hasattr(self._ag, "bonds"): + raise AttributeError("AtomGroup has no attribute bonds") if len(self._ag.fragments) > 1: - raise ValueError('AtomGroup has more than one molecule') + raise ValueError("AtomGroup has more than one molecule") # Determine the root # The initial atom must be a terminal atom - terminal_atoms = _sort_atoms_by_mass(\ - [a for a in self._ag.atoms if len(a.bonds)==1], reverse=True) - if (initial_atom is None): + terminal_atoms = _sort_atoms_by_mass( + [a for a in self._ag.atoms if len(a.bonds) == 1], reverse=True + ) + if initial_atom is None: # Select the heaviest root atoms from the heaviest terminal atom initial_atom = terminal_atoms[0] - elif (not initial_atom in terminal_atoms): - raise ValueError('Initial atom is not a terminal atom') + elif not initial_atom in terminal_atoms: + raise ValueError("Initial atom is not a terminal atom") # The next atom in the root is bonded to the initial atom # Since the initial atom is a terminal atom, there is only # one bonded atom @@ -330,16 +341,25 @@ def __init__(self, ag, initial_atom=None, filename=None, **kwargs): # If there are more than three atoms, # then the last atom cannot be a terminal atom. if self._ag.n_atoms != 3: - third_atom = _sort_atoms_by_mass(\ - [a for a in second_atom.bonded_atoms \ - if (a in self._ag) and (a!=initial_atom) \ - and (a not in terminal_atoms)], \ - reverse=True)[0] + third_atom = _sort_atoms_by_mass( + [ + a + for a in second_atom.bonded_atoms + if (a in self._ag) + and (a != initial_atom) + and (a not in terminal_atoms) + ], + reverse=True, + )[0] else: - third_atom = _sort_atoms_by_mass(\ - [a for a in second_atom.bonded_atoms \ - if (a in self._ag) and (a!=initial_atom)], \ - reverse=True)[0] + third_atom = _sort_atoms_by_mass( + [ + a + for a in second_atom.bonded_atoms + if (a in self._ag) and (a != initial_atom) + ], + reverse=True, + )[0] self._root = mda.AtomGroup([initial_atom, second_atom, third_atom]) # Construct a list of torsion angles @@ -347,18 +367,23 @@ def __init__(self, ag, initial_atom=None, filename=None, **kwargs): # Get indices of the root and torsion atoms # in a Cartesian positions array that matches the AtomGroup - self._root_XYZ_inds = [(self._ag.indices==a.index).nonzero()[0][0] \ - for a in self._root] - self._torsion_XYZ_inds = [[(self._ag.indices==a.index).nonzero()[0][0] \ - for a in t] for t in self._torsions] + self._root_XYZ_inds = [ + (self._ag.indices == a.index).nonzero()[0][0] for a in self._root + ] + self._torsion_XYZ_inds = [ + [(self._ag.indices == a.index).nonzero()[0][0] for a in t] + for t in self._torsions + ] # The primary torsion is the first torsion on the list # with the same central atoms prior_atoms = [sorted([a1, a2]) for (a0, a1, a2, a3) in self._torsions] - self._primary_torsion_indices = [prior_atoms.index(prior_atoms[n]) \ - for n in range(len(prior_atoms))] - self._unique_primary_torsion_indices = \ - list(set(self._primary_torsion_indices)) + self._primary_torsion_indices = [ + prior_atoms.index(prior_atoms[n]) for n in range(len(prior_atoms)) + ] + self._unique_primary_torsion_indices = list( + set(self._primary_torsion_indices) + ) self._ag1 = mda.AtomGroup([ag[0] for ag in self._torsions]) self._ag2 = mda.AtomGroup([ag[1] for ag in self._torsions]) @@ -370,7 +395,8 @@ def __init__(self, ag, initial_atom=None, filename=None, **kwargs): def _prepare(self): self.results.bat = np.zeros( - (self.n_frames, 3*self._ag.n_atoms), dtype=np.float64) + (self.n_frames, 3 * self._ag.n_atoms), dtype=np.float64 + ) def _single_frame(self): # Calculate coordinates based on the root atoms @@ -384,13 +410,24 @@ def _single_frame(self): v01 = p1 - p0 v21 = p1 - p2 # Internal coordinates - r01 = np.sqrt(np.einsum('i,i->',v01,v01)) + r01 = np.sqrt(np.einsum("i,i->", v01, v01)) # Distance between first two root atoms - r12 = np.sqrt(np.einsum('i,i->',v21,v21)) + r12 = np.sqrt(np.einsum("i,i->", v21, v21)) # Distance between second two root atoms # Angle between root atoms - a012 = np.arccos(max(-1.,min(1.,np.einsum('i,i->',v01,v21)/\ - np.sqrt(np.einsum('i,i->',v01,v01)*np.einsum('i,i->',v21,v21))))) + a012 = np.arccos( + max( + -1.0, + min( + 1.0, + np.einsum("i,i->", v01, v21) + / np.sqrt( + np.einsum("i,i->", v01, v01) + * np.einsum("i,i->", v21, v21) + ), + ), + ) + ) # External coordinates e = v01 / r01 phi = np.arctan2(e[1], e[0]) # Polar angle @@ -400,35 +437,41 @@ def _single_frame(self): sp = np.sin(phi) ct = np.cos(theta) st = np.sin(theta) - Rz = np.array([[cp * ct, ct * sp, -st], [-sp, cp, 0], - [cp * st, sp * st, ct]]) + Rz = np.array( + [[cp * ct, ct * sp, -st], [-sp, cp, 0], [cp * st, sp * st, ct]] + ) pos2 = Rz.dot(p2 - p1) # Angle about the rotation axis omega = np.arctan2(pos2[1], pos2[0]) root_based = np.concatenate((p0, [phi, theta, omega, r01, r12, a012])) # Calculate internal coordinates from the torsion list - bonds = calc_bonds(self._ag1.positions, - self._ag2.positions, - box=self._ag1.dimensions) - angles = calc_angles(self._ag1.positions, - self._ag2.positions, - self._ag3.positions, - box=self._ag1.dimensions) - torsions = calc_dihedrals(self._ag1.positions, - self._ag2.positions, - self._ag3.positions, - self._ag4.positions, - box=self._ag1.dimensions) + bonds = calc_bonds( + self._ag1.positions, self._ag2.positions, box=self._ag1.dimensions + ) + angles = calc_angles( + self._ag1.positions, + self._ag2.positions, + self._ag3.positions, + box=self._ag1.dimensions, + ) + torsions = calc_dihedrals( + self._ag1.positions, + self._ag2.positions, + self._ag3.positions, + self._ag4.positions, + box=self._ag1.dimensions, + ) # When appropriate, calculate improper torsions shift = torsions[self._primary_torsion_indices] - shift[self._unique_primary_torsion_indices] = 0. + shift[self._unique_primary_torsion_indices] = 0.0 torsions -= shift # Wrap torsions to between -np.pi and np.pi torsions = ((torsions + np.pi) % (2 * np.pi)) - np.pi self.results.bat[self._frame_index, :] = np.concatenate( - (root_based, bonds, angles, torsions)) + (root_based, bonds, angles, torsions) + ) def load(self, filename, start=None, stop=None, step=None): """Loads the bat trajectory from a file in numpy binary format @@ -455,16 +498,20 @@ def load(self, filename, start=None, stop=None, step=None): self.results.bat = np.load(filename) # Check array dimensions - if self.results.bat.shape != (self.n_frames, 3*self._ag.n_atoms): - errmsg = ('Dimensions of array in loaded file, ' - f'({self.results.bat.shape[0]},' - f'{self.results.bat.shape[1]}), differ from required ' - f'dimensions of ({self.n_frames, 3*self._ag.n_atoms})') + if self.results.bat.shape != (self.n_frames, 3 * self._ag.n_atoms): + errmsg = ( + "Dimensions of array in loaded file, " + f"({self.results.bat.shape[0]}," + f"{self.results.bat.shape[1]}), differ from required " + f"dimensions of ({self.n_frames, 3*self._ag.n_atoms})" + ) raise ValueError(errmsg) # Check position of initial atom if (self.results.bat[0, :3] != self._root[0].position).any(): - raise ValueError('Position of initial atom in file ' + \ - 'inconsistent with current trajectory in starting frame.') + raise ValueError( + "Position of initial atom in file " + + "inconsistent with current trajectory in starting frame." + ) return self def save(self, filename): @@ -501,21 +548,21 @@ def Cartesian(self, bat_frame): origin = bat_frame[:3] (phi, theta, omega) = bat_frame[3:6] (r01, r12, a012) = bat_frame[6:9] - n_torsions = (self._ag.n_atoms - 3) - bonds = bat_frame[9:n_torsions + 9] - angles = bat_frame[n_torsions + 9:2 * n_torsions + 9] - torsions = copy.deepcopy(bat_frame[2 * n_torsions + 9:]) + n_torsions = self._ag.n_atoms - 3 + bonds = bat_frame[9 : n_torsions + 9] + angles = bat_frame[n_torsions + 9 : 2 * n_torsions + 9] + torsions = copy.deepcopy(bat_frame[2 * n_torsions + 9 :]) # When appropriate, convert improper to proper torsions shift = torsions[self._primary_torsion_indices] - shift[self._unique_primary_torsion_indices] = 0. + shift[self._unique_primary_torsion_indices] = 0.0 torsions += shift # Wrap torsions to between -np.pi and np.pi torsions = ((torsions + np.pi) % (2 * np.pi)) - np.pi # Set initial root atom positions based on internal coordinates - p0 = np.array([0., 0., 0.]) - p1 = np.array([0., 0., r01]) - p2 = np.array([r12 * np.sin(a012), 0., r01 - r12 * np.cos(a012)]) + p0 = np.array([0.0, 0.0, 0.0]) + p1 = np.array([0.0, 0.0, r01]) + p2 = np.array([r12 * np.sin(a012), 0.0, r01 - r12 * np.cos(a012)]) # Rotate the third atom by the appropriate value co = np.cos(omega) @@ -529,8 +576,9 @@ def Cartesian(self, bat_frame): ct = np.cos(theta) st = np.sin(theta) # $R_Z(\phi) R_Y(\theta)$ - Re = np.array([[cp * ct, -sp, cp * st], [ct * sp, cp, sp * st], - [-st, 0, ct]]) + Re = np.array( + [[cp * ct, -sp, cp * st], [ct * sp, cp, sp * st], [-st, 0, ct]] + ) p1 = Re.dot(p1) p2 = Re.dot(p2) # Translate the first three atoms by the origin @@ -544,8 +592,9 @@ def Cartesian(self, bat_frame): XYZ[self._root_XYZ_inds[2]] = p2 # Place the remaining atoms - for ((a0,a1,a2,a3), r01, angle, torsion) \ - in zip(self._torsion_XYZ_inds, bonds, angles, torsions): + for (a0, a1, a2, a3), r01, angle, torsion in zip( + self._torsion_XYZ_inds, bonds, angles, torsions + ): p1 = XYZ[a1] p3 = XYZ[a3] @@ -557,19 +606,20 @@ def Cartesian(self, bat_frame): cs_tor = np.cos(torsion) v21 = p1 - p2 - v21 /= np.sqrt(np.einsum('i,i->',v21,v21)) + v21 /= np.sqrt(np.einsum("i,i->", v21, v21)) v32 = p2 - p3 - v32 /= np.sqrt(np.einsum('i,i->',v32,v32)) + v32 /= np.sqrt(np.einsum("i,i->", v32, v32)) vp = np.cross(v32, v21) - cs = np.einsum('i,i->',v21,v32) + cs = np.einsum("i,i->", v21, v32) sn = max(np.sqrt(1.0 - cs * cs), 0.0000000001) vp = vp / sn vu = np.cross(vp, v21) - XYZ[a0] = p1 + \ - r01*(vu*sn_ang*cs_tor + vp*sn_ang*sn_tor - v21*cs_ang) + XYZ[a0] = p1 + r01 * ( + vu * sn_ang * cs_tor + vp * sn_ang * sn_tor - v21 * cs_ang + ) return XYZ @property @@ -578,4 +628,4 @@ def atoms(self): return self._ag def _get_aggregator(self): - return ResultsGroup(lookup={'bat': ResultsGroup.ndarray_vstack}) + return ResultsGroup(lookup={"bat": ResultsGroup.ndarray_vstack}) diff --git a/package/MDAnalysis/analysis/contacts.py b/package/MDAnalysis/analysis/contacts.py index f29fd4961e8..5d63471ae7d 100644 --- a/package/MDAnalysis/analysis/contacts.py +++ b/package/MDAnalysis/analysis/contacts.py @@ -260,7 +260,7 @@ def soft_cut_q(r, r0, beta=5.0, lambda_constant=1.8): """ r = np.asarray(r) r0 = np.asarray(r0) - result = 1/(1 + np.exp(beta*(r - lambda_constant * r0))) + result = 1 / (1 + np.exp(beta * (r - lambda_constant * r0))) return result.sum() / len(r0) @@ -392,8 +392,17 @@ def get_supported_backends(cls): "dask", ) - def __init__(self, u, select, refgroup, method="hard_cut", radius=4.5, - pbc=True, kwargs=None, **basekwargs): + def __init__( + self, + u, + select, + refgroup, + method="hard_cut", + radius=4.5, + pbc=True, + kwargs=None, + **basekwargs, + ): """ Parameters ---------- @@ -437,12 +446,14 @@ def __init__(self, u, select, refgroup, method="hard_cut", radius=4.5, self.fraction_kwargs = kwargs if kwargs is not None else {} - if method == 'hard_cut': + if method == "hard_cut": self.fraction_contacts = hard_cut_q - elif method == 'soft_cut': + elif method == "soft_cut": self.fraction_contacts = soft_cut_q - elif method == 'radius_cut': - self.fraction_contacts = functools.partial(radius_cut_q, radius=radius) + elif method == "radius_cut": + self.fraction_contacts = functools.partial( + radius_cut_q, radius=radius + ) else: if not callable(method): raise ValueError("method has to be callable") @@ -453,7 +464,7 @@ def __init__(self, u, select, refgroup, method="hard_cut", radius=4.5, self.grA, self.grB = (self._get_atomgroup(u, sel) for sel in select) self.pbc = pbc - + # contacts formed in reference self.r0 = [] self.initial_contacts = [] @@ -463,23 +474,37 @@ def __init__(self, u, select, refgroup, method="hard_cut", radius=4.5, if isinstance(refgroup[0], AtomGroup): refA, refB = refgroup - self.r0.append(distance_array(refA.positions, refB.positions, - box=self._get_box(refA.universe))) + self.r0.append( + distance_array( + refA.positions, + refB.positions, + box=self._get_box(refA.universe), + ) + ) self.initial_contacts.append(contact_matrix(self.r0[-1], radius)) else: for refA, refB in refgroup: - self.r0.append(distance_array(refA.positions, refB.positions, - box=self._get_box(refA.universe))) - self.initial_contacts.append(contact_matrix(self.r0[-1], radius)) + self.r0.append( + distance_array( + refA.positions, + refB.positions, + box=self._get_box(refA.universe), + ) + ) + self.initial_contacts.append( + contact_matrix(self.r0[-1], radius) + ) self.n_initial_contacts = self.initial_contacts[0].sum() @staticmethod def _get_atomgroup(u, sel): - select_error_message = ("selection must be either string or a " - "static AtomGroup. Updating AtomGroups " - "are not supported.") + select_error_message = ( + "selection must be either string or a " + "static AtomGroup. Updating AtomGroups " + "are not supported." + ) if isinstance(sel, str): return u.select_atoms(sel) elif isinstance(sel, AtomGroup): @@ -513,17 +538,19 @@ def _get_box_func(ts, pbc): return ts.dimensions if pbc else None def _prepare(self): - self.results.timeseries = np.empty((self.n_frames, len(self.r0)+1)) + self.results.timeseries = np.empty((self.n_frames, len(self.r0) + 1)) def _single_frame(self): self.results.timeseries[self._frame_index][0] = self._ts.frame - + # compute distance array for a frame - d = distance_array(self.grA.positions, self.grB.positions, - box=self._get_box(self._ts)) - - for i, (initial_contacts, r0) in enumerate(zip(self.initial_contacts, - self.r0), 1): + d = distance_array( + self.grA.positions, self.grB.positions, box=self._get_box(self._ts) + ) + + for i, (initial_contacts, r0) in enumerate( + zip(self.initial_contacts, self.r0), 1 + ): # select only the contacts that were formed in the reference state r = d[initial_contacts] r0 = r0[initial_contacts] @@ -532,14 +559,17 @@ def _single_frame(self): @property def timeseries(self): - wmsg = ("The `timeseries` attribute was deprecated in MDAnalysis " - "2.0.0 and will be removed in MDAnalysis 3.0.0. Please use " - "`results.timeseries` instead") + wmsg = ( + "The `timeseries` attribute was deprecated in MDAnalysis " + "2.0.0 and will be removed in MDAnalysis 3.0.0. Please use " + "`results.timeseries` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.timeseries def _get_aggregator(self): - return ResultsGroup(lookup={'timeseries': ResultsGroup.ndarray_vstack}) + return ResultsGroup(lookup={"timeseries": ResultsGroup.ndarray_vstack}) + def _new_selections(u_orig, selections, frame): """create stand alone AGs from selections at frame""" @@ -548,7 +578,7 @@ def _new_selections(u_orig, selections, frame): return [u.select_atoms(s) for s in selections] -def q1q2(u, select='all', radius=4.5): +def q1q2(u, select="all", radius=4.5): """Perform a q1-q2 analysis. Compares native contacts between the starting structure and final structure @@ -568,7 +598,7 @@ def q1q2(u, select='all', radius=4.5): contacts : :class:`Contacts` Contact Analysis that is set up for a q1-q2 analysis - + .. versionchanged:: 1.0.0 Changed `selection` keyword to `select` Support for setting ``start``, ``stop``, and ``step`` has been removed. @@ -577,6 +607,10 @@ def q1q2(u, select='all', radius=4.5): selection = (select, select) first_frame_refs = _new_selections(u, selection, 0) last_frame_refs = _new_selections(u, selection, -1) - return Contacts(u, selection, - (first_frame_refs, last_frame_refs), - radius=radius, method='radius_cut') + return Contacts( + u, + selection, + (first_frame_refs, last_frame_refs), + radius=radius, + method="radius_cut", + ) diff --git a/package/MDAnalysis/analysis/data/filenames.py b/package/MDAnalysis/analysis/data/filenames.py index a747450b86d..68ab3d96551 100644 --- a/package/MDAnalysis/analysis/data/filenames.py +++ b/package/MDAnalysis/analysis/data/filenames.py @@ -103,16 +103,17 @@ __all__ = [ - "Rama_ref", "Janin_ref", + "Rama_ref", + "Janin_ref", # reference plots for Ramachandran and Janin classes ] from importlib import resources -_base_ref = resources.files('MDAnalysis.analysis.data') -Rama_ref = (_base_ref / 'rama_ref_data.npy').as_posix() -Janin_ref = (_base_ref / 'janin_ref_data.npy').as_posix() +_base_ref = resources.files("MDAnalysis.analysis.data") +Rama_ref = (_base_ref / "rama_ref_data.npy").as_posix() +Janin_ref = (_base_ref / "janin_ref_data.npy").as_posix() # This should be the last line: clean up namespace del resources diff --git a/package/MDAnalysis/analysis/density.py b/package/MDAnalysis/analysis/density.py index eb2522531f2..61c4e679899 100644 --- a/package/MDAnalysis/analysis/density.py +++ b/package/MDAnalysis/analysis/density.py @@ -161,8 +161,12 @@ import MDAnalysis from MDAnalysis.core import groups -from MDAnalysis.lib.util import (fixedwidth_bins, iterable, asiterable, - deprecate,) +from MDAnalysis.lib.util import ( + fixedwidth_bins, + iterable, + asiterable, + deprecate, +) from MDAnalysis.lib import NeighborSearch as NS from MDAnalysis import NoDataError, MissingDataWarning from .. import units @@ -400,16 +404,24 @@ class DensityAnalysis(AnalysisBase): for parallel execution on :mod:`multiprocessing` and :mod:`dask` backends. """ + _analysis_algorithm_is_parallelizable = True @classmethod def get_supported_backends(cls): - return ('serial', 'multiprocessing', 'dask') - - def __init__(self, atomgroup, delta=1.0, - metadata=None, padding=2.0, - gridcenter=None, - xdim=None, ydim=None, zdim=None): + return ("serial", "multiprocessing", "dask") + + def __init__( + self, + atomgroup, + delta=1.0, + metadata=None, + padding=2.0, + gridcenter=None, + xdim=None, + ydim=None, + zdim=None, + ): u = atomgroup.universe super(DensityAnalysis, self).__init__(u.trajectory) self._atomgroup = atomgroup @@ -423,16 +435,19 @@ def __init__(self, atomgroup, delta=1.0, # The grid with its dimensions has to be set up in __init__ # so that parallel analysis works correctly: each process # needs to have a results._grid of the same size and the - # same self._bins and self._arange (so this cannot happen - # in _prepare(), which is executed in parallel on different - # parts of the trajectory). + # same self._bins and self._arange (so this cannot happen + # in _prepare(), which is executed in parallel on different + # parts of the trajectory). coord = self._atomgroup.positions - if (self._gridcenter is not None or - any([self._xdim, self._ydim, self._zdim])): + if self._gridcenter is not None or any( + [self._xdim, self._ydim, self._zdim] + ): # Issue 2372: padding is ignored, defaults to 2.0 therefore warn if self._padding > 0: - msg = (f"Box padding (currently set at {self._padding}) " - f"is not used in user defined grids.") + msg = ( + f"Box padding (currently set at {self._padding}) " + f"is not used in user defined grids." + ) warnings.warn(msg) logger.warning(msg) # Generate a copy of smin/smax from coords to later check if the @@ -441,18 +456,25 @@ def __init__(self, atomgroup, delta=1.0, smin = np.min(coord, axis=0) smax = np.max(coord, axis=0) except ValueError as err: - msg = ("No atoms in AtomGroup at input time frame. " - "This may be intended; please ensure that " - "your grid selection covers the atomic " - "positions you wish to capture.") + msg = ( + "No atoms in AtomGroup at input time frame. " + "This may be intended; please ensure that " + "your grid selection covers the atomic " + "positions you wish to capture." + ) warnings.warn(msg) logger.warning(msg) - smin = self._gridcenter #assigns limits to be later - - smax = self._gridcenter #overwritten by _set_user_grid + smin = self._gridcenter # assigns limits to be later - + smax = self._gridcenter # overwritten by _set_user_grid # Overwrite smin/smax with user defined values - smin, smax = self._set_user_grid(self._gridcenter, self._xdim, - self._ydim, self._zdim, smin, - smax) + smin, smax = self._set_user_grid( + self._gridcenter, + self._xdim, + self._ydim, + self._zdim, + smin, + smax, + ) else: # Make the box bigger to avoid as much as possible 'outlier'. This # is important if the sites are defined at a high density: in this @@ -465,18 +487,21 @@ def __init__(self, atomgroup, delta=1.0, smin = np.min(coord, axis=0) - self._padding smax = np.max(coord, axis=0) + self._padding except ValueError as err: - errmsg = ("No atoms in AtomGroup at input time frame. " - "Grid for density could not be automatically" - " generated. If this is expected, a user" - " defined grid will need to be " - "provided instead.") + errmsg = ( + "No atoms in AtomGroup at input time frame. " + "Grid for density could not be automatically" + " generated. If this is expected, a user" + " defined grid will need to be " + "provided instead." + ) raise ValueError(errmsg) from err BINS = fixedwidth_bins(self._delta, smin, smax) - arange = np.transpose(np.vstack((BINS['min'], BINS['max']))) - bins = BINS['Nbins'] + arange = np.transpose(np.vstack((BINS["min"], BINS["max"]))) + bins = BINS["Nbins"] # create empty grid with the right dimensions (and get the edges) - grid, edges = np.histogramdd(np.zeros((1, 3)), bins=bins, - range=arange, density=False) + grid, edges = np.histogramdd( + np.zeros((1, 3)), bins=bins, range=arange, density=False + ) grid *= 0.0 self.results._grid = grid self._edges = edges @@ -484,30 +509,36 @@ def __init__(self, atomgroup, delta=1.0, self._bins = bins def _single_frame(self): - h, _ = np.histogramdd(self._atomgroup.positions, - bins=self._bins, range=self._arange, - density=False) + h, _ = np.histogramdd( + self._atomgroup.positions, + bins=self._bins, + range=self._arange, + density=False, + ) self.results._grid += h def _conclude(self): # average: self.results._grid /= float(self.n_frames) - density = Density(grid=self.results._grid, edges=self._edges, - units={'length': "Angstrom"}, - parameters={'isDensity': False}) + density = Density( + grid=self.results._grid, + edges=self._edges, + units={"length": "Angstrom"}, + parameters={"isDensity": False}, + ) density.make_density() self.results.density = density def _get_aggregator(self): - return ResultsGroup(lookup={ - '_grid': ResultsGroup.ndarray_sum} - ) + return ResultsGroup(lookup={"_grid": ResultsGroup.ndarray_sum}) @property def density(self): - wmsg = ("The `density` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.density` instead") + wmsg = ( + "The `density` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.density` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.density @@ -544,10 +575,12 @@ def _set_user_grid(gridcenter, xdim, ydim, zdim, smin, smax): """ # Check user inputs if any(x is None for x in [gridcenter, xdim, ydim, zdim]): - errmsg = ("Gridcenter or grid dimensions are not provided") + errmsg = "Gridcenter or grid dimensions are not provided" raise ValueError(errmsg) try: - gridcenter = np.asarray(gridcenter, dtype=np.float32).reshape(3,) + gridcenter = np.asarray(gridcenter, dtype=np.float32).reshape( + 3, + ) except ValueError as err: raise ValueError("Gridcenter must be a 3D coordinate") from err try: @@ -557,16 +590,17 @@ def _set_user_grid(gridcenter, xdim, ydim, zdim, smin, smax): if any(np.isnan(gridcenter)) or any(np.isnan(xyzdim)): raise ValueError("Gridcenter or grid dimensions have NaN element") - # Set min/max by shifting by half the edge length of each dimension - umin = gridcenter - xyzdim/2 - umax = gridcenter + xyzdim/2 + umin = gridcenter - xyzdim / 2 + umax = gridcenter + xyzdim / 2 # Here we test if coords of selection fall outside of the defined grid # if this happens, we warn users they may want to resize their grids if any(smin < umin) or any(smax > umax): - msg = ("Atom selection does not fit grid --- " - "you may want to define a larger box") + msg = ( + "Atom selection does not fit grid --- " + "you may want to define a larger box" + ) warnings.warn(msg) logger.warning(msg) return umin, umax @@ -725,22 +759,25 @@ class Density(Grid): """ def __init__(self, *args, **kwargs): - length_unit = 'Angstrom' - - parameters = kwargs.pop('parameters', {}) - if (len(args) > 0 and isinstance(args[0], str) or - isinstance(kwargs.get('grid', None), str)): + length_unit = "Angstrom" + + parameters = kwargs.pop("parameters", {}) + if ( + len(args) > 0 + and isinstance(args[0], str) + or isinstance(kwargs.get("grid", None), str) + ): # try to be smart: when reading from a file then it is likely that # this is a density - parameters.setdefault('isDensity', True) + parameters.setdefault("isDensity", True) else: - parameters.setdefault('isDensity', False) - units = kwargs.pop('units', {}) - units.setdefault('length', length_unit) - if parameters['isDensity']: - units.setdefault('density', length_unit) + parameters.setdefault("isDensity", False) + units = kwargs.pop("units", {}) + units.setdefault("length", length_unit) + if parameters["isDensity"]: + units.setdefault("density", length_unit) else: - units.setdefault('density', None) + units.setdefault("density", None) super(Density, self).__init__(*args, **kwargs) @@ -767,25 +804,31 @@ def _check_set_unit(self, u): # all this unit crap should be a class... try: for unit_type, value in u.items(): - if value is None: # check here, too iffy to use dictionary[None]=None + if ( + value is None + ): # check here, too iffy to use dictionary[None]=None self.units[unit_type] = None continue try: units.conversion_factor[unit_type][value] self.units[unit_type] = value except KeyError: - errmsg = (f"Unit {value} of type {unit_type} is not " - f"recognized.") + errmsg = ( + f"Unit {value} of type {unit_type} is not " + f"recognized." + ) raise ValueError(errmsg) from None except AttributeError: - errmsg = '"unit" must be a dictionary with keys "length" and "density.' + errmsg = ( + '"unit" must be a dictionary with keys "length" and "density.' + ) logger.fatal(errmsg) raise ValueError(errmsg) from None # need at least length and density (can be None) - if 'length' not in self.units: + if "length" not in self.units: raise ValueError('"unit" must contain a unit for "length".') - if 'density' not in self.units: - self.units['density'] = None + if "density" not in self.units: + self.units["density"] = None def make_density(self): """Convert the grid (a histogram, counts in a cell) to a density (counts/volume). @@ -800,7 +843,7 @@ def make_density(self): # Make it a density by dividing by the volume of each grid cell # (from numpy.histogramdd, which is for general n-D grids) - if self.parameters['isDensity']: + if self.parameters["isDensity"]: msg = "Running make_density() makes no sense: Grid is already a density. Nothing done." logger.warning(msg) warnings.warn(msg) @@ -812,11 +855,11 @@ def make_density(self): shape = np.ones(D, int) shape[i] = len(dedges[i]) self.grid /= dedges[i].reshape(shape) - self.parameters['isDensity'] = True + self.parameters["isDensity"] = True # see units.densityUnit_factor for units - self.units['density'] = self.units['length'] + "^{-3}" + self.units["density"] = self.units["length"] + "^{-3}" - def convert_length(self, unit='Angstrom'): + def convert_length(self, unit="Angstrom"): """Convert Grid object to the new `unit`. Parameters @@ -833,14 +876,16 @@ def convert_length(self, unit='Angstrom'): histogram and a length unit and use :meth:`make_density`. """ - if unit == self.units['length']: + if unit == self.units["length"]: return - cvnfact = units.get_conversion_factor('length', self.units['length'], unit) + cvnfact = units.get_conversion_factor( + "length", self.units["length"], unit + ) self.edges = [x * cvnfact for x in self.edges] - self.units['length'] = unit + self.units["length"] = unit self._update() # needed to recalculate midpoints and origin - def convert_density(self, unit='Angstrom^{-3}'): + def convert_density(self, unit="Angstrom^{-3}"): """Convert the density to the physical units given by `unit`. Parameters @@ -879,24 +924,33 @@ def convert_density(self, unit='Angstrom^{-3}'): and floating point artifacts for multiple conversions. """ - if not self.parameters['isDensity']: - errmsg = 'The grid is not a density so convert_density() makes no sense.' + if not self.parameters["isDensity"]: + errmsg = "The grid is not a density so convert_density() makes no sense." logger.fatal(errmsg) raise RuntimeError(errmsg) - if unit == self.units['density']: + if unit == self.units["density"]: return try: - self.grid *= units.get_conversion_factor('density', - self.units['density'], unit) + self.grid *= units.get_conversion_factor( + "density", self.units["density"], unit + ) except KeyError: - errmsg = (f"The name of the unit ({unit} supplied) must be one " - f"of:\n{units.conversion_factor['density'].keys()}") + errmsg = ( + f"The name of the unit ({unit} supplied) must be one " + f"of:\n{units.conversion_factor['density'].keys()}" + ) raise ValueError(errmsg) from None - self.units['density'] = unit + self.units["density"] = unit def __repr__(self): - if self.parameters['isDensity']: - grid_type = 'density' + if self.parameters["isDensity"]: + grid_type = "density" else: - grid_type = 'histogram' - return '' + grid_type = "histogram" + return ( + "" + ) diff --git a/package/MDAnalysis/analysis/dielectric.py b/package/MDAnalysis/analysis/dielectric.py index 4f14eb88074..28a22b5116b 100644 --- a/package/MDAnalysis/analysis/dielectric.py +++ b/package/MDAnalysis/analysis/dielectric.py @@ -37,10 +37,12 @@ from MDAnalysis.due import due, Doi from MDAnalysis.exceptions import NoDataError -due.cite(Doi("10.1080/00268978300102721"), - description="Dielectric analysis", - path="MDAnalysis.analysis.dielectric", - cite_module=True) +due.cite( + Doi("10.1080/00268978300102721"), + description="Dielectric analysis", + path="MDAnalysis.analysis.dielectric", + cite_module=True, +) del Doi @@ -121,9 +123,11 @@ class DielectricConstant(AnalysisBase): .. versionadded:: 2.1.0 """ + def __init__(self, atomgroup, temperature=300, make_whole=True, **kwargs): - super(DielectricConstant, self).__init__(atomgroup.universe.trajectory, - **kwargs) + super(DielectricConstant, self).__init__( + atomgroup.universe.trajectory, **kwargs + ) self.atomgroup = atomgroup self.temperature = temperature self.make_whole = make_whole @@ -132,11 +136,14 @@ def _prepare(self): if not hasattr(self.atomgroup, "charges"): raise NoDataError("No charges defined given atomgroup.") - if not np.allclose(self.atomgroup.total_charge(compound='fragments'), - 0.0, atol=1E-5): - raise NotImplementedError("Analysis for non-neutral systems or" - " systems with free charges are not" - " available.") + if not np.allclose( + self.atomgroup.total_charge(compound="fragments"), 0.0, atol=1e-5 + ): + raise NotImplementedError( + "Analysis for non-neutral systems or" + " systems with free charges are not" + " available." + ) self.volume = 0 self.results.M = np.zeros(3) @@ -163,8 +170,11 @@ def _conclude(self): self.results.fluct = self.results.M2 - self.results.M * self.results.M self.results.eps = self.results.fluct / ( - convert(constants["Boltzmann_constant"], "kJ/mol", "eV") * - self.temperature * self.volume * constants["electric_constant"]) + convert(constants["Boltzmann_constant"], "kJ/mol", "eV") + * self.temperature + * self.volume + * constants["electric_constant"] + ) self.results.eps_mean = self.results.eps.mean() diff --git a/package/MDAnalysis/analysis/dihedrals.py b/package/MDAnalysis/analysis/dihedrals.py index c6a5585f7a0..0c5ecb33cdf 100644 --- a/package/MDAnalysis/analysis/dihedrals.py +++ b/package/MDAnalysis/analysis/dihedrals.py @@ -271,12 +271,16 @@ class Dihedral(AnalysisBase): introduced :meth:`get_supported_backends` allowing for parallel execution on ``multiprocessing`` and ``dask`` backends. """ + _analysis_algorithm_is_parallelizable = True @classmethod def get_supported_backends(cls): - return ('serial', 'multiprocessing', 'dask',) - + return ( + "serial", + "multiprocessing", + "dask", + ) def __init__(self, atomgroups, **kwargs): """Parameters @@ -292,7 +296,8 @@ def __init__(self, atomgroups, **kwargs): """ super(Dihedral, self).__init__( - atomgroups[0].universe.trajectory, **kwargs) + atomgroups[0].universe.trajectory, **kwargs + ) self.atomgroups = atomgroups if any([len(ag) != 4 for ag in atomgroups]): @@ -307,12 +312,16 @@ def _prepare(self): self.results.angles = [] def _get_aggregator(self): - return ResultsGroup(lookup={'angles': ResultsGroup.ndarray_vstack}) + return ResultsGroup(lookup={"angles": ResultsGroup.ndarray_vstack}) def _single_frame(self): - angle = calc_dihedrals(self.ag1.positions, self.ag2.positions, - self.ag3.positions, self.ag4.positions, - box=self.ag1.dimensions) + angle = calc_dihedrals( + self.ag1.positions, + self.ag2.positions, + self.ag3.positions, + self.ag4.positions, + box=self.ag1.dimensions, + ) self.results.angles.append(angle) def _conclude(self): @@ -320,9 +329,11 @@ def _conclude(self): @property def angles(self): - wmsg = ("The `angle` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.angles` instead") + wmsg = ( + "The `angle` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.angles` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.angles @@ -394,16 +405,29 @@ class Ramachandran(AnalysisBase): introduced :meth:`get_supported_backends` allowing for parallel execution on ``multiprocessing`` and ``dask`` backends. """ + _analysis_algorithm_is_parallelizable = True @classmethod def get_supported_backends(cls): - return ('serial', 'multiprocessing', 'dask',) - - def __init__(self, atomgroup, c_name='C', n_name='N', ca_name='CA', - check_protein=True, **kwargs): + return ( + "serial", + "multiprocessing", + "dask", + ) + + def __init__( + self, + atomgroup, + c_name="C", + n_name="N", + ca_name="CA", + check_protein=True, + **kwargs, + ): super(Ramachandran, self).__init__( - atomgroup.universe.trajectory, **kwargs) + atomgroup.universe.trajectory, **kwargs + ) self.atomgroup = atomgroup residues = self.atomgroup.residues @@ -411,12 +435,16 @@ def __init__(self, atomgroup, c_name='C', n_name='N', ca_name='CA', protein = self.atomgroup.universe.select_atoms("protein").residues if not residues.issubset(protein): - raise ValueError("Found atoms outside of protein. Only atoms " - "inside of a 'protein' selection can be used to " - "calculate dihedrals.") + raise ValueError( + "Found atoms outside of protein. Only atoms " + "inside of a 'protein' selection can be used to " + "calculate dihedrals." + ) elif not residues.isdisjoint(protein[[0, -1]]): - warnings.warn("Cannot determine phi and psi angles for the first " - "or last residues") + warnings.warn( + "Cannot determine phi and psi angles for the first " + "or last residues" + ) residues = residues.difference(protein[[0, -1]]) prev = residues._get_prev_residues_by_resid() @@ -425,17 +453,20 @@ def __init__(self, atomgroup, c_name='C', n_name='N', ca_name='CA', keep = keep & np.array([r is not None for r in nxt]) if not np.all(keep): - warnings.warn("Some residues in selection do not have " - "phi or psi selections") + warnings.warn( + "Some residues in selection do not have " + "phi or psi selections" + ) prev = sum(prev[keep]) nxt = sum(nxt[keep]) residues = residues[keep] # find n, c, ca - keep_prev = [sum(r.atoms.names==c_name)==1 for r in prev] + keep_prev = [sum(r.atoms.names == c_name) == 1 for r in prev] rnames = [n_name, c_name, ca_name] - keep_res = [all(sum(r.atoms.names == n) == 1 for n in rnames) - for r in residues] + keep_res = [ + all(sum(r.atoms.names == n) == 1 for n in rnames) for r in residues + ] keep_next = [sum(r.atoms.names == n_name) == 1 for r in nxt] # alright we'll keep these @@ -451,20 +482,27 @@ def __init__(self, atomgroup, c_name='C', n_name='N', ca_name='CA', self.ag4 = res.atoms[rnames == c_name] self.ag5 = nxt.atoms[nxt.atoms.names == n_name] - def _prepare(self): self.results.angles = [] def _get_aggregator(self): - return ResultsGroup(lookup={'angles': ResultsGroup.ndarray_vstack}) + return ResultsGroup(lookup={"angles": ResultsGroup.ndarray_vstack}) def _single_frame(self): - phi_angles = calc_dihedrals(self.ag1.positions, self.ag2.positions, - self.ag3.positions, self.ag4.positions, - box=self.ag1.dimensions) - psi_angles = calc_dihedrals(self.ag2.positions, self.ag3.positions, - self.ag4.positions, self.ag5.positions, - box=self.ag1.dimensions) + phi_angles = calc_dihedrals( + self.ag1.positions, + self.ag2.positions, + self.ag3.positions, + self.ag4.positions, + box=self.ag1.dimensions, + ) + psi_angles = calc_dihedrals( + self.ag2.positions, + self.ag3.positions, + self.ag4.positions, + self.ag5.positions, + box=self.ag1.dimensions, + ) phi_psi = [(phi, psi) for phi, psi in zip(phi_angles, psi_angles)] self.results.angles.append(phi_psi) @@ -499,31 +537,40 @@ def plot(self, ax=None, ref=False, **kwargs): if ax is None: ax = plt.gca() ax.axis([-180, 180, -180, 180]) - ax.axhline(0, color='k', lw=1) - ax.axvline(0, color='k', lw=1) - ax.set(xticks=range(-180, 181, 60), yticks=range(-180, 181, 60), - xlabel=r"$\phi$", ylabel=r"$\psi$") + ax.axhline(0, color="k", lw=1) + ax.axvline(0, color="k", lw=1) + ax.set( + xticks=range(-180, 181, 60), + yticks=range(-180, 181, 60), + xlabel=r"$\phi$", + ylabel=r"$\psi$", + ) degree_formatter = plt.matplotlib.ticker.StrMethodFormatter( - r"{x:g}$\degree$") + r"{x:g}$\degree$" + ) ax.xaxis.set_major_formatter(degree_formatter) ax.yaxis.set_major_formatter(degree_formatter) if ref: - X, Y = np.meshgrid(np.arange(-180, 180, 4), - np.arange(-180, 180, 4)) + X, Y = np.meshgrid( + np.arange(-180, 180, 4), np.arange(-180, 180, 4) + ) levels = [1, 17, 15000] - colors = ['#A1D4FF', '#35A1FF'] + colors = ["#A1D4FF", "#35A1FF"] ax.contourf(X, Y, np.load(Rama_ref), levels=levels, colors=colors) a = self.results.angles.reshape( - np.prod(self.results.angles.shape[:2]), 2) + np.prod(self.results.angles.shape[:2]), 2 + ) ax.scatter(a[:, 0], a[:, 1], **kwargs) return ax @property def angles(self): - wmsg = ("The `angle` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.angles` instead") + wmsg = ( + "The `angle` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.angles` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.angles @@ -549,10 +596,13 @@ class Janin(Ramachandran): """ - def __init__(self, atomgroup, - select_remove="resname ALA CYS* GLY PRO SER THR VAL", - select_protein="protein", - **kwargs): + def __init__( + self, + atomgroup, + select_remove="resname ALA CYS* GLY PRO SER THR VAL", + select_protein="protein", + **kwargs, + ): r"""Parameters ---------- atomgroup : AtomGroup or ResidueGroup @@ -588,20 +638,25 @@ def __init__(self, atomgroup, :class:`MDAnalysis.analysis.base.Results` instance. """ super(Ramachandran, self).__init__( - atomgroup.universe.trajectory, **kwargs) + atomgroup.universe.trajectory, **kwargs + ) self.atomgroup = atomgroup residues = atomgroup.residues protein = atomgroup.select_atoms(select_protein).residues remove = residues.atoms.select_atoms(select_remove).residues if not residues.issubset(protein): - raise ValueError("Found atoms outside of protein. Only atoms " - "inside of a protein " - f"(select_protein='{select_protein}') can be " - "used to calculate dihedrals.") + raise ValueError( + "Found atoms outside of protein. Only atoms " + "inside of a protein " + f"(select_protein='{select_protein}') can be " + "used to calculate dihedrals." + ) elif len(remove) != 0: - warnings.warn(f"All residues selected with '{select_remove}' " - "have been removed from the selection.") + warnings.warn( + f"All residues selected with '{select_remove}' " + "have been removed from the selection." + ) residues = residues.difference(remove) self.ag1 = residues.atoms.select_atoms("name N") @@ -613,14 +668,19 @@ def __init__(self, atomgroup, # if there is an altloc attribute, too many atoms will be selected which # must be removed before using the class, or the file is missing atoms # for some residues which must also be removed - if any(len(self.ag1) != len(ag) for ag in [self.ag2, self.ag3, - self.ag4, self.ag5]): - raise ValueError("Too many or too few atoms selected. Check for " - "missing or duplicate atoms in topology.") + if any( + len(self.ag1) != len(ag) + for ag in [self.ag2, self.ag3, self.ag4, self.ag5] + ): + raise ValueError( + "Too many or too few atoms selected. Check for " + "missing or duplicate atoms in topology." + ) def _conclude(self): - self.results.angles = (np.rad2deg(np.array( - self.results.angles)) + 360) % 360 + self.results.angles = ( + np.rad2deg(np.array(self.results.angles)) + 360 + ) % 360 def plot(self, ax=None, ref=False, **kwargs): """Plots data into standard Janin plot. @@ -650,21 +710,27 @@ def plot(self, ax=None, ref=False, **kwargs): if ax is None: ax = plt.gca() ax.axis([0, 360, 0, 360]) - ax.axhline(180, color='k', lw=1) - ax.axvline(180, color='k', lw=1) - ax.set(xticks=range(0, 361, 60), yticks=range(0, 361, 60), - xlabel=r"$\chi_1$", ylabel=r"$\chi_2$") + ax.axhline(180, color="k", lw=1) + ax.axvline(180, color="k", lw=1) + ax.set( + xticks=range(0, 361, 60), + yticks=range(0, 361, 60), + xlabel=r"$\chi_1$", + ylabel=r"$\chi_2$", + ) degree_formatter = plt.matplotlib.ticker.StrMethodFormatter( - r"{x:g}$\degree$") + r"{x:g}$\degree$" + ) ax.xaxis.set_major_formatter(degree_formatter) ax.yaxis.set_major_formatter(degree_formatter) if ref: X, Y = np.meshgrid(np.arange(0, 360, 6), np.arange(0, 360, 6)) levels = [1, 6, 600] - colors = ['#A1D4FF', '#35A1FF'] + colors = ["#A1D4FF", "#35A1FF"] ax.contourf(X, Y, np.load(Janin_ref), levels=levels, colors=colors) - a = self.results.angles.reshape(np.prod( - self.results.angles.shape[:2]), 2) + a = self.results.angles.reshape( + np.prod(self.results.angles.shape[:2]), 2 + ) ax.scatter(a[:, 0], a[:, 1], **kwargs) return ax diff --git a/package/MDAnalysis/analysis/distances.py b/package/MDAnalysis/analysis/distances.py index 9e81de95688..44a09c4fcbd 100644 --- a/package/MDAnalysis/analysis/distances.py +++ b/package/MDAnalysis/analysis/distances.py @@ -38,28 +38,38 @@ :mod:`MDAnalysis.lib.distances` """ -__all__ = ['distance_array', 'self_distance_array', - 'contact_matrix', 'dist', 'between'] +__all__ = [ + "distance_array", + "self_distance_array", + "contact_matrix", + "dist", + "between", +] import numpy as np import scipy.sparse from MDAnalysis.lib.distances import ( - capped_distance, - self_distance_array, distance_array, # legacy reasons + capped_distance, + self_distance_array, + distance_array, # legacy reasons +) +from MDAnalysis.lib.c_distances import ( + contact_matrix_no_pbc, + contact_matrix_pbc, ) -from MDAnalysis.lib.c_distances import contact_matrix_no_pbc, contact_matrix_pbc from MDAnalysis.lib.NeighborSearch import AtomNeighborSearch from MDAnalysis.lib.distances import calc_bonds import warnings import logging + logger = logging.getLogger("MDAnalysis.analysis.distances") def contact_matrix(coord, cutoff=15.0, returntype="numpy", box=None): - '''Calculates a matrix of contacts. + """Calculates a matrix of contacts. There is a fast, high-memory-usage version for small systems (*returntype* = 'numpy'), and a slower, low-memory-usage version for @@ -97,20 +107,24 @@ def contact_matrix(coord, cutoff=15.0, returntype="numpy", box=None): .. versionchanged:: 0.11.0 Keyword *suppress_progmet* and *progress_meter_freq* were removed. - ''' + """ if returntype == "numpy": adj = np.full((len(coord), len(coord)), False, dtype=bool) - pairs = capped_distance(coord, coord, max_cutoff=cutoff, box=box, return_distances=False) - + pairs = capped_distance( + coord, coord, max_cutoff=cutoff, box=box, return_distances=False + ) + idx, idy = np.transpose(pairs) - adj[idx, idy]=True - + adj[idx, idy] = True + return adj elif returntype == "sparse": # Initialize square List of Lists matrix of dimensions equal to number # of coordinates passed - sparse_contacts = scipy.sparse.lil_matrix((len(coord), len(coord)), dtype='bool') + sparse_contacts = scipy.sparse.lil_matrix( + (len(coord), len(coord)), dtype="bool" + ) if box is not None: # with PBC contact_matrix_pbc(coord, sparse_contacts, box, cutoff) @@ -154,14 +168,16 @@ def dist(A, B, offset=0, box=None): """ if A.atoms.n_atoms != B.atoms.n_atoms: - raise ValueError("AtomGroups A and B do not have the same number of atoms") + raise ValueError( + "AtomGroups A and B do not have the same number of atoms" + ) try: off_A, off_B = offset except (TypeError, ValueError): off_A = off_B = int(offset) residues_A = np.array(A.resids) + off_A residues_B = np.array(B.resids) + off_B - + d = calc_bonds(A.positions, B.positions, box) return np.array([residues_A, residues_B, d]) diff --git a/package/MDAnalysis/analysis/dssp/dssp.py b/package/MDAnalysis/analysis/dssp/dssp.py index 21dd9423e49..d88f1bdbe45 100644 --- a/package/MDAnalysis/analysis/dssp/dssp.py +++ b/package/MDAnalysis/analysis/dssp/dssp.py @@ -292,7 +292,6 @@ def get_supported_backends(cls): "dask", ) - def __init__( self, atoms: Union[Universe, AtomGroup], @@ -317,14 +316,15 @@ def __init__( for t in heavyatom_names } self._hydrogens: list["AtomGroup"] = [ - res.atoms.select_atoms(f"name {hydrogen_name}") for res in ag.residues + res.atoms.select_atoms(f"name {hydrogen_name}") + for res in ag.residues ] # can't do it the other way because I need missing values to exist # so that I could fill them in later if not self._guess_hydrogens: # zip() assumes that _heavy_atoms and _hydrogens is ordered in the # same way. This is true as long as the original AtomGroup ag is - # sorted. With the hard-coded protein selection for ag this is always + # sorted. With the hard-coded protein selection for ag this is always # true but if the code on L277 ever changes, make sure to sort first! for calpha, hydrogen in zip( self._heavy_atoms["CA"][1:], self._hydrogens[1:] @@ -373,7 +373,9 @@ def _get_coords(self) -> np.ndarray: coords = np.array(positions) if not self._guess_hydrogens: - guessed_h_coords = _get_hydrogen_atom_position(coords.swapaxes(0, 1)) + guessed_h_coords = _get_hydrogen_atom_position( + coords.swapaxes(0, 1) + ) h_coords = np.array( [ @@ -402,6 +404,7 @@ def _get_aggregator(self): lookup={"dssp_ndarray": ResultsGroup.flatten_sequence}, ) + def translate(onehot: np.ndarray) -> np.ndarray: """Translate a one-hot encoding summary into char-based secondary structure assignment. diff --git a/package/MDAnalysis/analysis/dssp/pydssp_numpy.py b/package/MDAnalysis/analysis/dssp/pydssp_numpy.py index 1ae8ac369ea..f54ea5443af 100644 --- a/package/MDAnalysis/analysis/dssp/pydssp_numpy.py +++ b/package/MDAnalysis/analysis/dssp/pydssp_numpy.py @@ -65,7 +65,10 @@ def _upsample(a: np.ndarray, window: int) -> np.ndarray: def _unfold(a: np.ndarray, window: int, axis: int): "Helper function for 2D array upsampling" - idx = np.arange(window)[:, None] + np.arange(a.shape[axis] - window + 1)[None, :] + idx = ( + np.arange(window)[:, None] + + np.arange(a.shape[axis] - window + 1)[None, :] + ) unfolded = np.take(a, idx, axis=axis) return np.moveaxis(unfolded, axis - 1, -1) @@ -154,7 +157,9 @@ def get_hbond_map( h_1 = coord[1:, 4] coord = coord[:, :4] else: # pragma: no cover - raise ValueError("Number of atoms should be 4 (N,CA,C,O) or 5 (N,CA,C,O,H)") + raise ValueError( + "Number of atoms should be 4 (N,CA,C,O) or 5 (N,CA,C,O,H)" + ) # after this: # h.shape == (n_atoms, 3) # coord.shape == (n_atoms, 4, 3) @@ -176,7 +181,9 @@ def get_hbond_map( # electrostatic interaction energy # e[i, j] = e(CO_i) - e(NH_j) e = np.pad( - CONST_Q1Q2 * (1.0 / d_on + 1.0 / d_ch - 1.0 / d_oh - 1.0 / d_cn) * CONST_F, + CONST_Q1Q2 + * (1.0 / d_on + 1.0 / d_ch - 1.0 / d_oh - 1.0 / d_cn) + * CONST_F, [[1, 0], [0, 1]], ) diff --git a/package/MDAnalysis/analysis/encore/__init__.py b/package/MDAnalysis/analysis/encore/__init__.py index 49095ecfd5c..880b8b5d46c 100644 --- a/package/MDAnalysis/analysis/encore/__init__.py +++ b/package/MDAnalysis/analysis/encore/__init__.py @@ -20,31 +20,35 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -from .similarity import hes, ces, dres, \ - ces_convergence, dres_convergence +from .similarity import hes, ces, dres, ces_convergence, dres_convergence from .clustering.ClusterCollection import ClusterCollection, Cluster from .clustering.ClusteringMethod import * from .clustering.cluster import cluster from .dimensionality_reduction.DimensionalityReductionMethod import * from .dimensionality_reduction.reduce_dimensionality import ( - reduce_dimensionality) + reduce_dimensionality, +) from .confdistmatrix import get_distance_matrix from .utils import merge_universes -__all__ = ['covariance', 'similarity', 'confdistmatrix', 'clustering'] +__all__ = ["covariance", "similarity", "confdistmatrix", "clustering"] import warnings -wmsg = ("Deprecation in version 2.8.0\n" - "MDAnalysis.analysis.encore is deprecated in favour of the " - "MDAKit mdaencore (https://www.mdanalysis.org/mdaencore/) " - "and will be removed in MDAnalysis version 3.0.0.") +wmsg = ( + "Deprecation in version 2.8.0\n" + "MDAnalysis.analysis.encore is deprecated in favour of the " + "MDAKit mdaencore (https://www.mdanalysis.org/mdaencore/) " + "and will be removed in MDAnalysis version 3.0.0." +) warnings.warn(wmsg, category=DeprecationWarning) from ...due import due, Doi -due.cite(Doi("10.1371/journal.pcbi.1004415"), - description="ENCORE Ensemble Comparison", - path="MDAnalysis.analysis.encore", - cite_module=True) +due.cite( + Doi("10.1371/journal.pcbi.1004415"), + description="ENCORE Ensemble Comparison", + path="MDAnalysis.analysis.encore", + cite_module=True, +) diff --git a/package/MDAnalysis/analysis/encore/bootstrap.py b/package/MDAnalysis/analysis/encore/bootstrap.py index 80761a8fdd2..8784409c7f6 100644 --- a/package/MDAnalysis/analysis/encore/bootstrap.py +++ b/package/MDAnalysis/analysis/encore/bootstrap.py @@ -70,9 +70,13 @@ def bootstrapped_matrix(matrix, ensemble_assignment): indexes = [] for ens in ensemble_identifiers: old_indexes = np.where(ensemble_assignment == ens)[0] - indexes.append(np.random.randint(low=np.min(old_indexes), - high=np.max(old_indexes) + 1, - size=old_indexes.shape[0])) + indexes.append( + np.random.randint( + low=np.min(old_indexes), + high=np.max(old_indexes) + 1, + size=old_indexes.shape[0], + ) + ) indexes = np.hstack(indexes) for j in range(this_m.size): @@ -83,10 +87,9 @@ def bootstrapped_matrix(matrix, ensemble_assignment): return this_m -def get_distance_matrix_bootstrap_samples(distance_matrix, - ensemble_assignment, - samples=100, - ncores=1): +def get_distance_matrix_bootstrap_samples( + distance_matrix, ensemble_assignment, samples=100, ncores=1 +): """ Calculates distance matrices corresponding to bootstrapped ensembles, by resampling with replacement. @@ -113,8 +116,9 @@ def get_distance_matrix_bootstrap_samples(distance_matrix, confdistmatrix : list of encore.utils.TriangularMatrix """ - bs_args = \ - [([distance_matrix, ensemble_assignment]) for i in range(samples)] + bs_args = [ + ([distance_matrix, ensemble_assignment]) for i in range(samples) + ] pc = ParallelCalculation(ncores, bootstrapped_matrix, bs_args) @@ -125,8 +129,7 @@ def get_distance_matrix_bootstrap_samples(distance_matrix, return bootstrap_matrices -def get_ensemble_bootstrap_samples(ensemble, - samples=100): +def get_ensemble_bootstrap_samples(ensemble, samples=100): """ Generates a bootstrapped ensemble by resampling with replacement. @@ -152,9 +155,13 @@ def get_ensemble_bootstrap_samples(ensemble, indices = np.random.randint( low=0, high=ensemble.trajectory.timeseries().shape[1], - size=ensemble.trajectory.timeseries().shape[1]) + size=ensemble.trajectory.timeseries().shape[1], + ) ensembles.append( - mda.Universe(ensemble.filename, - ensemble.trajectory.timeseries(order='fac')[indices,:,:], - format=mda.coordinates.memory.MemoryReader)) + mda.Universe( + ensemble.filename, + ensemble.trajectory.timeseries(order="fac")[indices, :, :], + format=mda.coordinates.memory.MemoryReader, + ) + ) return ensembles diff --git a/package/MDAnalysis/analysis/encore/clustering/ClusterCollection.py b/package/MDAnalysis/analysis/encore/clustering/ClusterCollection.py index e4b7070dcac..8c545c54215 100644 --- a/package/MDAnalysis/analysis/encore/clustering/ClusterCollection.py +++ b/package/MDAnalysis/analysis/encore/clustering/ClusterCollection.py @@ -63,7 +63,7 @@ class Cluster(object): elements : numpy.array array containing the cluster elements. - """ + """ def __init__(self, elem_list=None, centroid=None, idn=None, metadata=None): """Class constructor. If elem_list is None, an empty cluster is created @@ -85,7 +85,7 @@ def __init__(self, elem_list=None, centroid=None, idn=None, metadata=None): metadata, one value for each cluster element. The iterable must have the same length as the elements array. - """ + """ self.id = idn @@ -99,16 +99,20 @@ def __init__(self, elem_list=None, centroid=None, idn=None, metadata=None): self.metadata = {} self.elements = elem_list if centroid not in self.elements: - raise LookupError("Centroid of cluster not found in the element list") + raise LookupError( + "Centroid of cluster not found in the element list" + ) self.centroid = centroid self.size = self.elements.shape[0] if metadata: for name, data in metadata.items(): if len(data) != self.size: - raise TypeError('Size of metadata having label "{0}" ' - 'is not equal to the number of cluster ' - 'elements'.format(name)) + raise TypeError( + 'Size of metadata having label "{0}" ' + "is not equal to the number of cluster " + "elements".format(name) + ) self.add_metadata(name, data) def __iter__(self): @@ -125,8 +129,10 @@ def __len__(self): def add_metadata(self, name, data): if len(data) != self.size: - raise TypeError("Size of metadata is not equal to the number of " - "cluster elements") + raise TypeError( + "Size of metadata is not equal to the number of " + "cluster elements" + ) self.metadata[name] = np.array(data) def __repr__(self): @@ -137,9 +143,9 @@ def __repr__(self): return "" else: return "".format( - self.size, - self.centroid, - self.id) + self.size, self.centroid, self.id + ) + class ClusterCollection(object): """Clusters collection class; this class represents the results of a full @@ -152,38 +158,38 @@ class ClusterCollection(object): clusters : list list of of Cluster objects which are part of the Cluster collection -""" + """ def __init__(self, elements=None, metadata=None): """Class constructor. If elements is None, an empty cluster collection - will be created. Otherwise, the constructor takes as input an - iterable of ints, for instance: + will be created. Otherwise, the constructor takes as input an + iterable of ints, for instance: - [ a, a, a, a, b, b, b, c, c, ... , z, z ] + [ a, a, a, a, b, b, b, c, c, ... , z, z ] - the variables a,b,c,...,z are cluster centroids, here as cluster - element numbers (i.e. 3 means the 4th element of the ordered input - for clustering). The array maps a correspondence between - cluster elements (which are implicitly associated with the - position in the array) with centroids, i. e. defines clusters. - For instance: + the variables a,b,c,...,z are cluster centroids, here as cluster + element numbers (i.e. 3 means the 4th element of the ordered input + for clustering). The array maps a correspondence between + cluster elements (which are implicitly associated with the + position in the array) with centroids, i. e. defines clusters. + For instance: - [ 1, 1, 1, 4, 4, 5 ] + [ 1, 1, 1, 4, 4, 5 ] - means that elements 0, 1, 2 form a cluster which has 1 as centroid, - elements 3 and 4 form a cluster which has 4 as centroid, and - element 5 has its own cluster. + means that elements 0, 1, 2 form a cluster which has 1 as centroid, + elements 3 and 4 form a cluster which has 4 as centroid, and + element 5 has its own cluster. - Parameters - ---------- + Parameters + ---------- - elements : iterable of ints or None - clustering results. See the previous description for details + elements : iterable of ints or None + clustering results. See the previous description for details - metadata : {str:list, str:list,...} or None - metadata for the data elements. The list must be of the same - size as the elements array, with one value per element. + metadata : {str:list, str:list,...} or None + metadata for the data elements. The list must be of the same + size as the elements array, with one value per element. """ idn = 0 @@ -198,9 +204,10 @@ def __init__(self, elements=None, metadata=None): centroids = np.unique(elements_array) for i in centroids: if elements[i] != i: - raise ValueError("element {0}, which is a centroid, doesn't " - "belong to its own cluster".format( - elements[i])) + raise ValueError( + "element {0}, which is a centroid, doesn't " + "belong to its own cluster".format(elements[i]) + ) for c in centroids: this_metadata = {} this_array = np.where(elements_array == c) @@ -208,8 +215,13 @@ def __init__(self, elements=None, metadata=None): for k, v in metadata.items(): this_metadata[k] = np.asarray(v)[this_array] self.clusters.append( - Cluster(elem_list=this_array[0], idn=idn, centroid=c, - metadata=this_metadata)) + Cluster( + elem_list=this_array[0], + idn=idn, + centroid=c, + metadata=this_metadata, + ) + ) idn += 1 @@ -259,4 +271,5 @@ def __repr__(self): return "" else: return "".format( - len(self.clusters)) + len(self.clusters) + ) diff --git a/package/MDAnalysis/analysis/encore/clustering/ClusteringMethod.py b/package/MDAnalysis/analysis/encore/clustering/ClusteringMethod.py index 8071d5eac4a..9af8d48fa86 100644 --- a/package/MDAnalysis/analysis/encore/clustering/ClusteringMethod.py +++ b/package/MDAnalysis/analysis/encore/clustering/ClusteringMethod.py @@ -50,8 +50,10 @@ import sklearn.cluster except ImportError: sklearn = None - msg = "sklearn.cluster could not be imported: some functionality will " \ - "not be available in encore.fit_clusters()" + msg = ( + "sklearn.cluster could not be imported: some functionality will " + "not be available in encore.fit_clusters()" + ) warnings.warn(msg, category=ImportWarning) logging.warning(msg) del msg @@ -69,13 +71,13 @@ def encode_centroid_info(clusters, cluster_centers_indices): return values[indices] -class ClusteringMethod (object): +class ClusteringMethod(object): """ Base class for any Clustering Method """ # Whether the method accepts a distance matrix - accepts_distance_matrix=True + accepts_distance_matrix = True def __call__(self, x): """ @@ -90,21 +92,29 @@ def __call__(self, x): Raises ------ NotImplementedError - Method or behavior needs to be defined by a subclass - + Method or behavior needs to be defined by a subclass + """ - raise NotImplementedError("Class {0} doesn't implement __call__()" - .format(self.__class__.__name__)) + raise NotImplementedError( + "Class {0} doesn't implement __call__()".format( + self.__class__.__name__ + ) + ) class AffinityPropagationNative(ClusteringMethod): """ Interface to the natively implemented Affinity propagation procedure. """ - def __init__(self, - damping=0.9, preference=-1.0, - max_iter=500, convergence_iter=50, - add_noise=True): + + def __init__( + self, + damping=0.9, + preference=-1.0, + max_iter=500, + convergence_iter=50, + add_noise=True, + ): """ Parameters ---------- @@ -150,21 +160,24 @@ def __call__(self, distance_matrix): Returns ------- - numpy.array : array, shape(n_elements) + numpy.array : array, shape(n_elements) centroid frames of the clusters for all of the elements .. versionchanged:: 1.0.0 This method no longer returns ``details`` """ clusters = affinityprop.AffinityPropagation( - s=distance_matrix * -1., # invert sign + s=distance_matrix * -1.0, # invert sign preference=self.preference, lam=self.damping, - max_iterations = self.max_iter, - convergence = self.convergence_iter, - noise=int(self.add_noise)) - + max_iterations=self.max_iter, + convergence=self.convergence_iter, + noise=int(self.add_noise), + ) + return clusters + + if sklearn: class AffinityPropagation(ClusteringMethod): @@ -173,10 +186,14 @@ class AffinityPropagation(ClusteringMethod): in sklearn. """ - def __init__(self, - damping=0.9, preference=-1.0, - max_iter=500, convergence_iter=50, - **kwargs): + def __init__( + self, + damping=0.9, + preference=-1.0, + max_iter=500, + convergence_iter=50, + **kwargs, + ): """ Parameters ---------- @@ -204,14 +221,14 @@ def __init__(self, Other keyword arguments are passed to :class:`sklearn.cluster.AffinityPropagation`. """ - self.ap = \ - sklearn.cluster.AffinityPropagation( - damping=damping, - preference=preference, - max_iter=max_iter, - convergence_iter=convergence_iter, - affinity="precomputed", - **kwargs) + self.ap = sklearn.cluster.AffinityPropagation( + damping=damping, + preference=preference, + max_iter=max_iter, + convergence_iter=convergence_iter, + affinity="precomputed", + **kwargs, + ) def __call__(self, distance_matrix): """ @@ -223,35 +240,40 @@ def __call__(self, distance_matrix): Returns ------- - numpy.array : array, shape(n_elements) + numpy.array : array, shape(n_elements) centroid frames of the clusters for all of the elements .. versionchanged:: 1.0.0 This method no longer returns ``details`` """ - logging.info("Starting Affinity Propagation: {0}".format - (self.ap.get_params())) + logging.info( + "Starting Affinity Propagation: {0}".format( + self.ap.get_params() + ) + ) # Convert from distance matrix to similarity matrix similarity_matrix = distance_matrix.as_array() * -1 clusters = self.ap.fit_predict(similarity_matrix) - clusters = encode_centroid_info(clusters, - self.ap.cluster_centers_indices_) - - return clusters - + clusters = encode_centroid_info( + clusters, self.ap.cluster_centers_indices_ + ) + return clusters class DBSCAN(ClusteringMethod): """ Interface to the DBSCAN clustering procedure implemented in sklearn. """ - def __init__(self, - eps=0.5, - min_samples=5, - algorithm="auto", - leaf_size=30, - **kwargs): + + def __init__( + self, + eps=0.5, + min_samples=5, + algorithm="auto", + leaf_size=30, + **kwargs, + ): """ Parameters ---------- @@ -284,12 +306,14 @@ def __init__(self, """ - self.dbscan = sklearn.cluster.DBSCAN(eps=eps, - min_samples = min_samples, - algorithm=algorithm, - leaf_size = leaf_size, - metric="precomputed", - **kwargs) + self.dbscan = sklearn.cluster.DBSCAN( + eps=eps, + min_samples=min_samples, + algorithm=algorithm, + leaf_size=leaf_size, + metric="precomputed", + **kwargs, + ) def __call__(self, distance_matrix): """ @@ -302,23 +326,23 @@ def __call__(self, distance_matrix): Returns ------- - numpy.array : array, shape(n_elements) + numpy.array : array, shape(n_elements) centroid frames of the clusters for all of the elements .. versionchanged:: 1.0.0 This method no longer returns ``details`` """ - logging.info("Starting DBSCAN: {0}".format( - self.dbscan.get_params())) + logging.info( + "Starting DBSCAN: {0}".format(self.dbscan.get_params()) + ) clusters = self.dbscan.fit_predict(distance_matrix.as_array()) if np.min(clusters == -1): clusters += 1 # No centroid information is provided by DBSCAN, so we just # pick random members cluster_representatives = np.unique(clusters, return_index=True)[1] - clusters = encode_centroid_info(clusters, - cluster_representatives) - + clusters = encode_centroid_info(clusters, cluster_representatives) + return clusters class KMeans(ClusteringMethod): @@ -329,17 +353,20 @@ class KMeans(ClusteringMethod): """ Interface to the KMeans clustering procedure implemented in sklearn. """ - def __init__(self, - n_clusters, - max_iter=300, - n_init=10, - init='k-means++', - algorithm="auto", - tol=1e-4, - verbose=False, - random_state=None, - copy_x=True, - **kwargs): + + def __init__( + self, + n_clusters, + max_iter=300, + n_init=10, + init="k-means++", + algorithm="auto", + tol=1e-4, + verbose=False, + random_state=None, + copy_x=True, + **kwargs, + ): """ Parameters ---------- @@ -388,15 +415,17 @@ def __init__(self, the data mean. """ - self.kmeans = sklearn.cluster.KMeans(n_clusters=n_clusters, - max_iter=max_iter, - n_init=n_init, - init=init, - tol=tol, - verbose=verbose, - random_state=random_state, - copy_x=copy_x, - **kwargs) + self.kmeans = sklearn.cluster.KMeans( + n_clusters=n_clusters, + max_iter=max_iter, + n_init=n_init, + init=init, + tol=tol, + verbose=verbose, + random_state=random_state, + copy_x=copy_x, + **kwargs, + ) def __call__(self, coordinates): """ @@ -409,18 +438,18 @@ def __call__(self, coordinates): Returns ------- - numpy.array : array, shape(n_elements) + numpy.array : array, shape(n_elements) centroid frames of the clusters for all of the elements .. versionchanged:: 1.0.0 This method no longer returns ``details`` """ - logging.info("Starting Kmeans: {0}".format( - (self.kmeans.get_params()))) + logging.info( + "Starting Kmeans: {0}".format((self.kmeans.get_params())) + ) clusters = self.kmeans.fit_predict(coordinates) distances = self.kmeans.transform(coordinates) cluster_center_indices = np.argmin(distances, axis=0) - clusters = encode_centroid_info(clusters, - cluster_center_indices) - + clusters = encode_centroid_info(clusters, cluster_center_indices) + return clusters diff --git a/package/MDAnalysis/analysis/encore/clustering/__init__.py b/package/MDAnalysis/analysis/encore/clustering/__init__.py index 33f828ce5f4..69527468748 100644 --- a/package/MDAnalysis/analysis/encore/clustering/__init__.py +++ b/package/MDAnalysis/analysis/encore/clustering/__init__.py @@ -26,8 +26,9 @@ from .ClusteringMethod import AffinityPropagationNative from .ClusterCollection import ClusterCollection, Cluster -__all__ = ['ClusterCollection', 'Cluster', 'AffinityPropagationNative'] +__all__ = ["ClusterCollection", "Cluster", "AffinityPropagationNative"] if ClusteringMethod.sklearn: from .ClusteringMethod import AffinityPropagation, DBSCAN - __all__ += ['AffinityPropagation', 'DBSCAN'] + + __all__ += ["AffinityPropagation", "DBSCAN"] diff --git a/package/MDAnalysis/analysis/encore/clustering/cluster.py b/package/MDAnalysis/analysis/encore/clustering/cluster.py index 3bffa490236..2173a8d207d 100644 --- a/package/MDAnalysis/analysis/encore/clustering/cluster.py +++ b/package/MDAnalysis/analysis/encore/clustering/cluster.py @@ -160,11 +160,11 @@ def cluster( method = ClusteringMethod.AffinityPropagationNative() # Internally, ensembles are always transformed to a list of lists if ensembles is not None: - if not hasattr(ensembles, '__iter__'): + if not hasattr(ensembles, "__iter__"): ensembles = [ensembles] ensembles_list = ensembles - if not hasattr(ensembles[0], '__iter__'): + if not hasattr(ensembles[0], "__iter__"): ensembles_list = [ensembles] # Calculate merged ensembles and transfer to memory @@ -176,35 +176,41 @@ def cluster( merged_ensembles.append(merge_universes(ensembles)) methods = method - if not hasattr(method, '__iter__'): + if not hasattr(method, "__iter__"): methods = [method] # Check whether any of the clustering methods can make use of a distance # matrix - any_method_accept_distance_matrix = \ - np.any([_method.accepts_distance_matrix for _method in methods]) + any_method_accept_distance_matrix = np.any( + [_method.accepts_distance_matrix for _method in methods] + ) # If distance matrices are provided, check that it matches the number # of ensembles if distance_matrix: - if not hasattr(distance_matrix, '__iter__'): + if not hasattr(distance_matrix, "__iter__"): distance_matrix = [distance_matrix] - if ensembles is not None and \ - len(distance_matrix) != len(merged_ensembles): - raise ValueError("Dimensions of provided list of distance matrices " - "does not match that of provided list of " - "ensembles: {0} vs {1}" - .format(len(distance_matrix), - len(merged_ensembles))) + if ensembles is not None and len(distance_matrix) != len( + merged_ensembles + ): + raise ValueError( + "Dimensions of provided list of distance matrices " + "does not match that of provided list of " + "ensembles: {0} vs {1}".format( + len(distance_matrix), len(merged_ensembles) + ) + ) else: # Calculate distance matrices for all merged ensembles - if not provided if any_method_accept_distance_matrix: distance_matrix = [] for merged_ensemble in merged_ensembles: - distance_matrix.append(get_distance_matrix(merged_ensemble, - select=select, - **kwargs)) + distance_matrix.append( + get_distance_matrix( + merged_ensemble, select=select, **kwargs + ) + ) args = [] for method in methods: @@ -212,11 +218,14 @@ def cluster( args += [(d,) for d in distance_matrix] else: for merged_ensemble in merged_ensembles: - coordinates = merged_ensemble.trajectory.timeseries(order="fac") + coordinates = merged_ensemble.trajectory.timeseries( + order="fac" + ) # Flatten coordinate matrix into n_frame x n_coordinates - coordinates = np.reshape(coordinates, - (coordinates.shape[0], -1)) + coordinates = np.reshape( + coordinates, (coordinates.shape[0], -1) + ) args.append((coordinates,)) @@ -231,14 +240,16 @@ def cluster( if ensembles is not None: ensemble_assignment = [] for i, ensemble in enumerate(ensembles): - ensemble_assignment += [i+1]*len(ensemble.trajectory) + ensemble_assignment += [i + 1] * len(ensemble.trajectory) ensemble_assignment = np.array(ensemble_assignment) - metadata = {'ensemble_membership': ensemble_assignment} + metadata = {"ensemble_membership": ensemble_assignment} # Create clusters collections from clustering results, # one for each cluster. None if clustering didn't work. - ccs = [ClusterCollection(clusters[1], - metadata=metadata) for clusters in results] + ccs = [ + ClusterCollection(clusters[1], metadata=metadata) + for clusters in results + ] if allow_collapsed_result and len(ccs) == 1: ccs = ccs[0] diff --git a/package/MDAnalysis/analysis/encore/confdistmatrix.py b/package/MDAnalysis/analysis/encore/confdistmatrix.py index 739d715865f..95b22bf0563 100644 --- a/package/MDAnalysis/analysis/encore/confdistmatrix.py +++ b/package/MDAnalysis/analysis/encore/confdistmatrix.py @@ -57,11 +57,18 @@ class to compute an RMSD matrix in such a way is also available. from .utils import TriangularMatrix, trm_indices -def conformational_distance_matrix(ensemble, - conf_dist_function, select="", - superimposition_select="", n_jobs=1, pairwise_align=True, weights='mass', - metadata=True, verbose=False, - max_nbytes=None): +def conformational_distance_matrix( + ensemble, + conf_dist_function, + select="", + superimposition_select="", + n_jobs=1, + pairwise_align=True, + weights="mass", + metadata=True, + verbose=False, + max_nbytes=None, +): """ Run the conformational distance matrix calculation. args and kwargs are passed to conf_dist_function. @@ -104,33 +111,44 @@ def conformational_distance_matrix(ensemble, """ # framesn: number of frames - framesn = len(ensemble.trajectory.timeseries( - ensemble.select_atoms(select), order='fac')) + framesn = len( + ensemble.trajectory.timeseries( + ensemble.select_atoms(select), order="fac" + ) + ) # Prepare metadata recarray if metadata: - metadata = np.array([(gethostname(), - getuser(), - str(datetime.now()), - ensemble.filename, - framesn, - pairwise_align, - select, - weights=='mass')], - dtype=[('host', object), - ('user', object), - ('date', object), - ('topology file', object), - ('number of frames', int), - ('pairwise superimposition', bool), - ('superimposition subset', object), - ('mass-weighted', bool)]) + metadata = np.array( + [ + ( + gethostname(), + getuser(), + str(datetime.now()), + ensemble.filename, + framesn, + pairwise_align, + select, + weights == "mass", + ) + ], + dtype=[ + ("host", object), + ("user", object), + ("date", object), + ("topology file", object), + ("number of frames", int), + ("pairwise superimposition", bool), + ("superimposition subset", object), + ("mass-weighted", bool), + ], + ) # Prepare alignment subset coordinates as necessary rmsd_coordinates = ensemble.trajectory.timeseries( - ensemble.select_atoms(select), - order='fac') + ensemble.select_atoms(select), order="fac" + ) if pairwise_align: if superimposition_select: @@ -139,31 +157,45 @@ def conformational_distance_matrix(ensemble, subset_select = select fitting_coordinates = ensemble.trajectory.timeseries( - ensemble.select_atoms(subset_select), - order='fac') + ensemble.select_atoms(subset_select), order="fac" + ) else: fitting_coordinates = None - if not isinstance(weights, (list, tuple, np.ndarray)) and weights == 'mass': + if ( + not isinstance(weights, (list, tuple, np.ndarray)) + and weights == "mass" + ): weights = ensemble.select_atoms(select).masses.astype(np.float64) if pairwise_align: - subset_weights = ensemble.select_atoms(subset_select).masses.astype(np.float64) + subset_weights = ensemble.select_atoms( + subset_select + ).masses.astype(np.float64) else: subset_weights = None elif weights is None: - weights = np.ones((ensemble.trajectory.timeseries( - ensemble.select_atoms(select))[0].shape[0])).astype(np.float64) + weights = np.ones( + ( + ensemble.trajectory.timeseries(ensemble.select_atoms(select))[ + 0 + ].shape[0] + ) + ).astype(np.float64) if pairwise_align: - subset_weights = np.ones((fit_coords[0].shape[0])).astype(np.float64) + subset_weights = np.ones((fit_coords[0].shape[0])).astype( + np.float64 + ) else: subset_weights = None else: if pairwise_align: if len(weights) != 2: - raise RuntimeError("used pairwise alignment with custom " - "weights. Please provide 2 tuple with " - "weights for 'select' and " - "'superimposition_select'") + raise RuntimeError( + "used pairwise alignment with custom " + "weights. Please provide 2 tuple with " + "weights for 'select' and " + "'superimposition_select'" + ) subset_weights = weights[1] weights = weights[0] else: @@ -176,24 +208,38 @@ def conformational_distance_matrix(ensemble, # Initialize workers. Simple worker doesn't perform fitting, # fitter worker does. indices = trm_indices((0, 0), (framesn - 1, framesn - 1)) - Parallel(n_jobs=n_jobs, verbose=verbose, require='sharedmem', - max_nbytes=max_nbytes)(delayed(conf_dist_function)( - np.int64(element), - rmsd_coordinates, - distmat, - weights, - fitting_coordinates, - subset_weights) for element in indices) - + Parallel( + n_jobs=n_jobs, + verbose=verbose, + require="sharedmem", + max_nbytes=max_nbytes, + )( + delayed(conf_dist_function)( + np.int64(element), + rmsd_coordinates, + distmat, + weights, + fitting_coordinates, + subset_weights, + ) + for element in indices + ) # When the workers have finished, return a TriangularMatrix object return TriangularMatrix(distmat, metadata=metadata) -def set_rmsd_matrix_elements(tasks, coords, rmsdmat, weights, fit_coords=None, - fit_weights=None, *args, **kwargs): - - ''' +def set_rmsd_matrix_elements( + tasks, + coords, + rmsdmat, + weights, + fit_coords=None, + fit_weights=None, + *args, + **kwargs, +): + """ RMSD Matrix calculator Parameters @@ -223,51 +269,60 @@ def set_rmsd_matrix_elements(tasks, coords, rmsdmat, weights, fit_coords=None, fit_weights : numpy.array. optional Array of atomic weights, having the same order as the fit_coords array - ''' + """ i, j = tasks if fit_coords is None and fit_weights is None: sumweights = np.sum(weights) - rmsdmat[(i + 1) * i // 2 + j] = PureRMSD(coords[i].astype(np.float64), - coords[j].astype(np.float64), - coords[j].shape[0], - weights, - sumweights) + rmsdmat[(i + 1) * i // 2 + j] = PureRMSD( + coords[i].astype(np.float64), + coords[j].astype(np.float64), + coords[j].shape[0], + weights, + sumweights, + ) elif fit_coords is not None and fit_weights is not None: sumweights = np.sum(weights) subset_weights = np.asarray(fit_weights) / np.mean(fit_weights) - com_i = np.average(fit_coords[i], axis=0, - weights=fit_weights) + com_i = np.average(fit_coords[i], axis=0, weights=fit_weights) translated_i = coords[i] - com_i subset1_coords = fit_coords[i] - com_i - com_j = np.average(fit_coords[j], axis=0, - weights=fit_weights) + com_j = np.average(fit_coords[j], axis=0, weights=fit_weights) translated_j = coords[j] - com_j subset2_coords = fit_coords[j] - com_j - rotamat = rotation_matrix(subset1_coords, subset2_coords, - subset_weights)[0] + rotamat = rotation_matrix( + subset1_coords, subset2_coords, subset_weights + )[0] rotated_i = np.transpose(np.dot(rotamat, np.transpose(translated_i))) rmsdmat[(i + 1) * i // 2 + j] = PureRMSD( - rotated_i.astype(np.float64), translated_j.astype(np.float64), - coords[j].shape[0], weights, sumweights) + rotated_i.astype(np.float64), + translated_j.astype(np.float64), + coords[j].shape[0], + weights, + sumweights, + ) else: - raise TypeError("Both fit_coords and fit_weights must be specified " - "if one of them is given") - - -def get_distance_matrix(ensemble, - select="name CA", - load_matrix=None, - save_matrix=None, - superimpose=True, - superimposition_subset="name CA", - weights='mass', - n_jobs=1, - max_nbytes=None, - verbose=False, - *conf_dist_args, - **conf_dist_kwargs): + raise TypeError( + "Both fit_coords and fit_weights must be specified " + "if one of them is given" + ) + + +def get_distance_matrix( + ensemble, + select="name CA", + load_matrix=None, + save_matrix=None, + superimpose=True, + superimposition_subset="name CA", + weights="mass", + n_jobs=1, + max_nbytes=None, + verbose=False, + *conf_dist_args, + **conf_dist_kwargs, +): """ Retrieves or calculates the conformational distance (RMSD) matrix. The distance matrix is calculated between all the frames of all @@ -321,60 +376,76 @@ def get_distance_matrix(ensemble, # Load the matrix if required if load_matrix: logging.info( - " Loading similarity matrix from: {0}".format(load_matrix)) - confdistmatrix = \ - TriangularMatrix( - size=ensemble.trajectory.timeseries( - ensemble.select_atoms(select), - order='fac').shape[0], - loadfile=load_matrix) + " Loading similarity matrix from: {0}".format(load_matrix) + ) + confdistmatrix = TriangularMatrix( + size=ensemble.trajectory.timeseries( + ensemble.select_atoms(select), order="fac" + ).shape[0], + loadfile=load_matrix, + ) logging.info(" Done!") for key in confdistmatrix.metadata.dtype.names: - logging.info(" {0} : {1}".format( - key, str(confdistmatrix.metadata[key][0]))) + logging.info( + " {0} : {1}".format( + key, str(confdistmatrix.metadata[key][0]) + ) + ) # Check matrix size for consistency - if not confdistmatrix.size == \ - ensemble.trajectory.timeseries( - ensemble.select_atoms(select), - order='fac').shape[0]: + if ( + not confdistmatrix.size + == ensemble.trajectory.timeseries( + ensemble.select_atoms(select), order="fac" + ).shape[0] + ): logging.error( "ERROR: The size of the loaded matrix and of the ensemble" - " do not match") + " do not match" + ) return None - # Calculate the matrix else: # Transfer universe to memory to ensure timeseries() support ensemble.transfer_to_memory() - if not isinstance(weights, (list, tuple, np.ndarray)) and weights == 'mass': - weight_type = 'Mass' + if ( + not isinstance(weights, (list, tuple, np.ndarray)) + and weights == "mass" + ): + weight_type = "Mass" elif weights is None: - weight_type = 'None' + weight_type = "None" else: - weight_type = 'Custom' + weight_type = "Custom" + logging.info( + " Perform pairwise alignment: {0}".format(str(superimpose)) + ) logging.info( - " Perform pairwise alignment: {0}".format(str(superimpose))) - logging.info(" weighted alignment and RMSD: {0}".format(weight_type)) + " weighted alignment and RMSD: {0}".format(weight_type) + ) if superimpose: logging.info( - " Atoms subset for alignment: {0}" - .format(superimposition_subset)) + " Atoms subset for alignment: {0}".format( + superimposition_subset + ) + ) logging.info(" Calculating similarity matrix . . .") # Use superimposition subset, if necessary. If the pairwise alignment # is not required, it will not be performed anyway. - confdistmatrix = conformational_distance_matrix(ensemble, - conf_dist_function=set_rmsd_matrix_elements, - select=select, - pairwise_align=superimpose, - weights=weights, - n_jobs=n_jobs, - max_nbytes=max_nbytes, - verbose=verbose) + confdistmatrix = conformational_distance_matrix( + ensemble, + conf_dist_function=set_rmsd_matrix_elements, + select=select, + pairwise_align=superimpose, + weights=weights, + n_jobs=n_jobs, + max_nbytes=max_nbytes, + verbose=verbose, + ) logging.info(" Done!") diff --git a/package/MDAnalysis/analysis/encore/covariance.py b/package/MDAnalysis/analysis/encore/covariance.py index 5c7b3b363a5..08e014e315c 100644 --- a/package/MDAnalysis/analysis/encore/covariance.py +++ b/package/MDAnalysis/analysis/encore/covariance.py @@ -39,6 +39,7 @@ """ import numpy as np + def ml_covariance_estimator(coordinates, reference_coordinates=None): """ Standard maximum likelihood estimator of the covariance matrix. @@ -70,17 +71,17 @@ def ml_covariance_estimator(coordinates, reference_coordinates=None): coordinates_offset = coordinates - np.average(coordinates, axis=0) # Calculate covariance manually - coordinates_cov = np.zeros((coordinates.shape[1], - coordinates.shape[1])) + coordinates_cov = np.zeros((coordinates.shape[1], coordinates.shape[1])) for frame in coordinates_offset: coordinates_cov += np.outer(frame, frame) coordinates_cov /= coordinates.shape[0] return coordinates_cov -def shrinkage_covariance_estimator( coordinates, - reference_coordinates=None, - shrinkage_parameter=None): + +def shrinkage_covariance_estimator( + coordinates, reference_coordinates=None, shrinkage_parameter=None +): """ Shrinkage estimator of the covariance matrix using the method described in @@ -125,8 +126,11 @@ def shrinkage_covariance_estimator( coordinates, xmkt = np.average(x, axis=1) # Call maximum likelihood estimator (note the additional column) - sample = ml_covariance_estimator(np.hstack([x, xmkt[:, np.newaxis]]), 0)\ - * (t-1)/float(t) + sample = ( + ml_covariance_estimator(np.hstack([x, xmkt[:, np.newaxis]]), 0) + * (t - 1) + / float(t) + ) # Split covariance matrix into components covmkt = sample[0:n, n] @@ -134,53 +138,59 @@ def shrinkage_covariance_estimator( coordinates, sample = sample[:n, :n] # Prior - prior = np.outer(covmkt, covmkt)/varmkt + prior = np.outer(covmkt, covmkt) / varmkt prior[np.ma.make_mask(np.eye(n))] = np.diag(sample) # If shrinkage parameter is not set, estimate it if shrinkage_parameter is None: # Frobenius norm - c = np.linalg.norm(sample - prior, ord='fro')**2 + c = np.linalg.norm(sample - prior, ord="fro") ** 2 y = x**2 - p = 1/float(t)*np.sum(np.dot(np.transpose(y), y))\ - - np.sum(np.sum(sample**2)) - rdiag = 1/float(t)*np.sum(np.sum(y**2))\ - - np.sum(np.diag(sample)**2) + p = 1 / float(t) * np.sum(np.dot(np.transpose(y), y)) - np.sum( + np.sum(sample**2) + ) + rdiag = 1 / float(t) * np.sum(np.sum(y**2)) - np.sum( + np.diag(sample) ** 2 + ) z = x * np.repeat(xmkt[:, np.newaxis], n, axis=1) - v1 = 1/float(t) * np.dot(np.transpose(y), z) \ - - np.repeat(covmkt[:, np.newaxis], n, axis=1)*sample - roff1 = (np.sum( - v1*np.transpose( - np.repeat( - covmkt[:, np.newaxis], n, axis=1) - ) - )/varmkt - - np.sum(np.diag(v1)*covmkt)/varmkt) - v3 = 1/float(t)*np.dot(np.transpose(z), z) - varmkt*sample - roff3 = (np.sum(v3*np.outer(covmkt, covmkt))/varmkt**2 - - np.sum(np.diag(v3)*covmkt**2)/varmkt**2) - roff = 2*roff1-roff3 - r = rdiag+roff + v1 = ( + 1 / float(t) * np.dot(np.transpose(y), z) + - np.repeat(covmkt[:, np.newaxis], n, axis=1) * sample + ) + roff1 = ( + np.sum( + v1 * np.transpose(np.repeat(covmkt[:, np.newaxis], n, axis=1)) + ) + / varmkt + - np.sum(np.diag(v1) * covmkt) / varmkt + ) + v3 = 1 / float(t) * np.dot(np.transpose(z), z) - varmkt * sample + roff3 = ( + np.sum(v3 * np.outer(covmkt, covmkt)) / varmkt**2 + - np.sum(np.diag(v3) * covmkt**2) / varmkt**2 + ) + roff = 2 * roff1 - roff3 + r = rdiag + roff # Shrinkage constant - k = (p-r)/c - shrinkage_parameter = max(0, min(1, k/float(t))) + k = (p - r) / c + shrinkage_parameter = max(0, min(1, k / float(t))) # calculate covariance matrix - sigma = shrinkage_parameter*prior+(1-shrinkage_parameter)*sample + sigma = shrinkage_parameter * prior + (1 - shrinkage_parameter) * sample return sigma - - -def covariance_matrix(ensemble, - select="name CA", - estimator=shrinkage_covariance_estimator, - weights='mass', - reference=None): +def covariance_matrix( + ensemble, + select="name CA", + estimator=shrinkage_covariance_estimator, + weights="mass", + reference=None, +): """ Calculates (optionally mass weighted) covariance matrix @@ -209,8 +219,8 @@ def covariance_matrix(ensemble, """ # Extract coordinates from ensemble coordinates = ensemble.trajectory.timeseries( - ensemble.select_atoms(select), - order='fac') + ensemble.select_atoms(select), order="fac" + ) # Flatten coordinate matrix into n_frame x n_coordinates coordinates = np.reshape(coordinates, (coordinates.shape[0], -1)) @@ -230,7 +240,10 @@ def covariance_matrix(ensemble, # Optionally correct with weights if weights is not None: # Calculate mass-weighted covariance matrix - if not isinstance(weights, (list, tuple, np.ndarray)) and weights == 'mass': + if ( + not isinstance(weights, (list, tuple, np.ndarray)) + and weights == "mass" + ): if select: weights = ensemble.select_atoms(select).masses else: @@ -241,13 +254,15 @@ def covariance_matrix(ensemble, else: req_len = ensemble.atoms.n_atoms if req_len != len(weights): - raise ValueError("number of weights is unequal to number of " - "atoms in ensemble") + raise ValueError( + "number of weights is unequal to number of " + "atoms in ensemble" + ) # broadcast to a (len(weights), 3) array weights = np.repeat(weights, 3) - weight_matrix = np.sqrt(np.identity(len(weights))*weights) + weight_matrix = np.sqrt(np.identity(len(weights)) * weights) sigma = np.dot(weight_matrix, np.dot(sigma, weight_matrix)) return sigma diff --git a/package/MDAnalysis/analysis/encore/dimensionality_reduction/DimensionalityReductionMethod.py b/package/MDAnalysis/analysis/encore/dimensionality_reduction/DimensionalityReductionMethod.py index 50349960bdd..3ca43202f5e 100644 --- a/package/MDAnalysis/analysis/encore/dimensionality_reduction/DimensionalityReductionMethod.py +++ b/package/MDAnalysis/analysis/encore/dimensionality_reduction/DimensionalityReductionMethod.py @@ -50,18 +50,22 @@ except ImportError: sklearn = None import warnings - warnings.warn("sklearn.decomposition could not be imported: some " - "functionality will not be available in " - "encore.dimensionality_reduction()", category=ImportWarning) + warnings.warn( + "sklearn.decomposition could not be imported: some " + "functionality will not be available in " + "encore.dimensionality_reduction()", + category=ImportWarning, + ) -class DimensionalityReductionMethod (object): + +class DimensionalityReductionMethod(object): """ Base class for any Dimensionality Reduction Method """ # Whether the method accepts a distance matrix - accepts_distance_matrix=True + accepts_distance_matrix = True def __call__(self, x): """ @@ -80,21 +84,27 @@ def __call__(self, x): coordinates in reduced space """ - raise NotImplementedError("Class {0} doesn't implement __call__()" - .format(self.__class__.__name__)) + raise NotImplementedError( + "Class {0} doesn't implement __call__()".format( + self.__class__.__name__ + ) + ) class StochasticProximityEmbeddingNative(DimensionalityReductionMethod): """ Interface to the natively implemented Affinity propagation procedure. """ - def __init__(self, - dimension = 2, - distance_cutoff = 1.5, - min_lam = 0.1, - max_lam = 2.0, - ncycle = 100, - nstep = 10000,): + + def __init__( + self, + dimension=2, + distance_cutoff=1.5, + min_lam=0.1, + max_lam=2.0, + ncycle=100, + nstep=10000, + ): """ Parameters ---------- @@ -140,21 +150,21 @@ def __call__(self, distance_matrix): coordinates in reduced space """ - final_stress, coordinates = \ + final_stress, coordinates = ( stochasticproxembed.StochasticProximityEmbedding( - s=distance_matrix, - rco=self.distance_cutoff, - dim=self.dimension, - minlam = self.min_lam, - maxlam = self.max_lam, - ncycle = self.ncycle, - nstep = self.nstep, - stressfreq = self.stressfreq + s=distance_matrix, + rco=self.distance_cutoff, + dim=self.dimension, + minlam=self.min_lam, + maxlam=self.max_lam, + ncycle=self.ncycle, + nstep=self.nstep, + stressfreq=self.stressfreq, + ) ) return coordinates, {"final_stress": final_stress} - if sklearn: class PrincipalComponentAnalysis(DimensionalityReductionMethod): @@ -166,9 +176,7 @@ class PrincipalComponentAnalysis(DimensionalityReductionMethod): # Whether the method accepts a distance matrix accepts_distance_matrix = False - def __init__(self, - dimension = 2, - **kwargs): + def __init__(self, dimension=2, **kwargs): """ Parameters ---------- @@ -177,8 +185,9 @@ def __init__(self, Number of dimensions to which the conformational space will be reduced to (default is 3). """ - self.pca = sklearn.decomposition.PCA(n_components=dimension, - **kwargs) + self.pca = sklearn.decomposition.PCA( + n_components=dimension, **kwargs + ) def __call__(self, coordinates): """ diff --git a/package/MDAnalysis/analysis/encore/dimensionality_reduction/__init__.py b/package/MDAnalysis/analysis/encore/dimensionality_reduction/__init__.py index fefd1b85acd..a2ca041d305 100644 --- a/package/MDAnalysis/analysis/encore/dimensionality_reduction/__init__.py +++ b/package/MDAnalysis/analysis/encore/dimensionality_reduction/__init__.py @@ -24,8 +24,9 @@ from .DimensionalityReductionMethod import StochasticProximityEmbeddingNative -__all__ = ['StochasticProximityEmbeddingNative'] +__all__ = ["StochasticProximityEmbeddingNative"] if DimensionalityReductionMethod.sklearn: from .DimensionalityReductionMethod import PrincipalComponentAnalysis - __all__ += ['PrincipalComponentAnalysis'] + + __all__ += ["PrincipalComponentAnalysis"] diff --git a/package/MDAnalysis/analysis/encore/dimensionality_reduction/reduce_dimensionality.py b/package/MDAnalysis/analysis/encore/dimensionality_reduction/reduce_dimensionality.py index 82e805c91bf..d1e05e1cd2f 100644 --- a/package/MDAnalysis/analysis/encore/dimensionality_reduction/reduce_dimensionality.py +++ b/package/MDAnalysis/analysis/encore/dimensionality_reduction/reduce_dimensionality.py @@ -41,7 +41,8 @@ from ..confdistmatrix import get_distance_matrix from ..utils import ParallelCalculation, merge_universes from ..dimensionality_reduction.DimensionalityReductionMethod import ( - StochasticProximityEmbeddingNative) + StochasticProximityEmbeddingNative, +) def reduce_dimensionality( @@ -157,11 +158,11 @@ def reduce_dimensionality( if method is None: method = StochasticProximityEmbeddingNative() if ensembles is not None: - if not hasattr(ensembles, '__iter__'): + if not hasattr(ensembles, "__iter__"): ensembles = [ensembles] ensembles_list = ensembles - if not hasattr(ensembles[0], '__iter__'): + if not hasattr(ensembles[0], "__iter__"): ensembles_list = [ensembles] # Calculate merged ensembles and transfer to memory @@ -173,37 +174,40 @@ def reduce_dimensionality( merged_ensembles.append(merge_universes(ensembles)) methods = method - if not hasattr(method, '__iter__'): + if not hasattr(method, "__iter__"): methods = [method] # Check whether any of the methods can make use of a distance matrix - any_method_accept_distance_matrix = \ - np.any([_method.accepts_distance_matrix for _method in - methods]) - - + any_method_accept_distance_matrix = np.any( + [_method.accepts_distance_matrix for _method in methods] + ) # If distance matrices are provided, check that it matches the number # of ensembles if distance_matrix: - if not hasattr(distance_matrix, '__iter__'): + if not hasattr(distance_matrix, "__iter__"): distance_matrix = [distance_matrix] - if ensembles is not None and \ - len(distance_matrix) != len(merged_ensembles): - raise ValueError("Dimensions of provided list of distance matrices " - "does not match that of provided list of " - "ensembles: {0} vs {1}" - .format(len(distance_matrix), - len(merged_ensembles))) + if ensembles is not None and len(distance_matrix) != len( + merged_ensembles + ): + raise ValueError( + "Dimensions of provided list of distance matrices " + "does not match that of provided list of " + "ensembles: {0} vs {1}".format( + len(distance_matrix), len(merged_ensembles) + ) + ) else: # Calculate distance matrices for all merged ensembles - if not provided if any_method_accept_distance_matrix: distance_matrix = [] for merged_ensemble in merged_ensembles: - distance_matrix.append(get_distance_matrix(merged_ensemble, - select=select, - **kwargs)) + distance_matrix.append( + get_distance_matrix( + merged_ensemble, select=select, **kwargs + ) + ) args = [] for method in methods: @@ -211,11 +215,14 @@ def reduce_dimensionality( args += [(d,) for d in distance_matrix] else: for merged_ensemble in merged_ensembles: - coordinates = merged_ensemble.trajectory.timeseries(order="fac") + coordinates = merged_ensemble.trajectory.timeseries( + order="fac" + ) # Flatten coordinate matrix into n_frame x n_coordinates - coordinates = np.reshape(coordinates, - (coordinates.shape[0], -1)) + coordinates = np.reshape( + coordinates, (coordinates.shape[0], -1) + ) args.append((coordinates,)) @@ -230,16 +237,16 @@ def reduce_dimensionality( if ensembles is not None: ensemble_assignment = [] for i, ensemble in enumerate(ensembles): - ensemble_assignment += [i+1]*len(ensemble.trajectory) + ensemble_assignment += [i + 1] * len(ensemble.trajectory) ensemble_assignment = np.array(ensemble_assignment) - details['ensemble_membership'] = ensemble_assignment + details["ensemble_membership"] = ensemble_assignment coordinates = [] for result in results: coordinates.append(result[1][0]) # details.append(result[1][1]) - if allow_collapsed_result and len(coordinates)==1: + if allow_collapsed_result and len(coordinates) == 1: coordinates = coordinates[0] # details = details[0] diff --git a/package/MDAnalysis/analysis/encore/similarity.py b/package/MDAnalysis/analysis/encore/similarity.py index c9a8ff1486c..17b9fb28860 100644 --- a/package/MDAnalysis/analysis/encore/similarity.py +++ b/package/MDAnalysis/analysis/encore/similarity.py @@ -180,24 +180,32 @@ from ...coordinates.memory import MemoryReader from .confdistmatrix import get_distance_matrix -from .bootstrap import (get_distance_matrix_bootstrap_samples, - get_ensemble_bootstrap_samples) +from .bootstrap import ( + get_distance_matrix_bootstrap_samples, + get_ensemble_bootstrap_samples, +) from .clustering.cluster import cluster from .clustering.ClusteringMethod import AffinityPropagationNative from .dimensionality_reduction.DimensionalityReductionMethod import ( - StochasticProximityEmbeddingNative) + StochasticProximityEmbeddingNative, +) from .dimensionality_reduction.reduce_dimensionality import ( - reduce_dimensionality) + reduce_dimensionality, +) from .covariance import ( - covariance_matrix, ml_covariance_estimator, shrinkage_covariance_estimator) + covariance_matrix, + ml_covariance_estimator, + shrinkage_covariance_estimator, +) from .utils import merge_universes from .utils import trm_indices_diag, trm_indices_nodiag # Low boundary value for log() argument - ensure no nans -EPSILON = 1E-15 +EPSILON = 1e-15 xlogy = np.vectorize( - lambda x, y: 0.0 if (x <= EPSILON and y <= EPSILON) else x * np.log(y)) + lambda x, y: 0.0 if (x <= EPSILON and y <= EPSILON) else x * np.log(y) +) def discrete_kullback_leibler_divergence(pA, pB): @@ -242,16 +250,15 @@ def discrete_jensen_shannon_divergence(pA, pB): djs : float Discrete Jensen-Shannon divergence -""" - return 0.5 * (discrete_kullback_leibler_divergence(pA, (pA + pB) * 0.5) + - discrete_kullback_leibler_divergence(pB, (pA + pB) * 0.5)) + """ + return 0.5 * ( + discrete_kullback_leibler_divergence(pA, (pA + pB) * 0.5) + + discrete_kullback_leibler_divergence(pB, (pA + pB) * 0.5) + ) # calculate harmonic similarity -def harmonic_ensemble_similarity(sigma1, - sigma2, - x1, - x2): +def harmonic_ensemble_similarity(sigma1, sigma2, x1, x2): """ Calculate the harmonic ensemble similarity measure as defined in :footcite:p:`Tiberti2015`. @@ -288,18 +295,22 @@ def harmonic_ensemble_similarity(sigma1, d_avg = x1 - x2 # Distance measure - trace = np.trace(np.dot(sigma1, sigma2_inv) + - np.dot(sigma2, sigma1_inv) - - 2 * np.identity(sigma1.shape[0])) - - d_hes = 0.25 * (np.dot(np.transpose(d_avg), - np.dot(sigma1_inv + sigma2_inv, - d_avg)) + trace) + trace = np.trace( + np.dot(sigma1, sigma2_inv) + + np.dot(sigma2, sigma1_inv) + - 2 * np.identity(sigma1.shape[0]) + ) + + d_hes = 0.25 * ( + np.dot(np.transpose(d_avg), np.dot(sigma1_inv + sigma2_inv, d_avg)) + + trace + ) return d_hes -def clustering_ensemble_similarity(cc, ens1, ens1_id, ens2, ens2_id, - select="name CA"): +def clustering_ensemble_similarity( + cc, ens1, ens1_id, ens2, ens2_id, select="name CA" +): """Clustering ensemble similarity: calculate the probability densities from the clusters and calculate discrete Jensen-Shannon divergence. @@ -332,16 +343,26 @@ def clustering_ensemble_similarity(cc, ens1, ens1_id, ens2, ens2_id, Jensen-Shannon divergence between the two ensembles, as calculated by the clustering ensemble similarity method """ - ens1_coordinates = ens1.trajectory.timeseries(ens1.select_atoms(select), - order='fac') - ens2_coordinates = ens2.trajectory.timeseries(ens2.select_atoms(select), - order='fac') - tmpA = np.array([np.where(c.metadata['ensemble_membership'] == ens1_id)[ - 0].shape[0] / float(ens1_coordinates.shape[0]) for - c in cc]) - tmpB = np.array([np.where(c.metadata['ensemble_membership'] == ens2_id)[ - 0].shape[0] / float(ens2_coordinates.shape[0]) for - c in cc]) + ens1_coordinates = ens1.trajectory.timeseries( + ens1.select_atoms(select), order="fac" + ) + ens2_coordinates = ens2.trajectory.timeseries( + ens2.select_atoms(select), order="fac" + ) + tmpA = np.array( + [ + np.where(c.metadata["ensemble_membership"] == ens1_id)[0].shape[0] + / float(ens1_coordinates.shape[0]) + for c in cc + ] + ) + tmpB = np.array( + [ + np.where(c.metadata["ensemble_membership"] == ens2_id)[0].shape[0] + / float(ens2_coordinates.shape[0]) + for c in cc + ] + ) # Exclude clusters which have 0 elements in both ensembles pA = tmpA[tmpA + tmpB > EPSILON] @@ -350,8 +371,9 @@ def clustering_ensemble_similarity(cc, ens1, ens1_id, ens2, ens2_id, return discrete_jensen_shannon_divergence(pA, pB) -def cumulative_clustering_ensemble_similarity(cc, ens1_id, ens2_id, - ens1_id_min=1, ens2_id_min=1): +def cumulative_clustering_ensemble_similarity( + cc, ens1_id, ens2_id, ens1_id_min=1, ens2_id_min=1 +): """ Calculate clustering ensemble similarity between joined ensembles. This means that, after clustering has been performed, some ensembles are @@ -383,16 +405,28 @@ def cumulative_clustering_ensemble_similarity(cc, ens1_id, ens2_id, Jensen-Shannon divergence between the two ensembles, as calculated by the clustering ensemble similarity method -""" + """ - ensA = [np.where(np.logical_and( - c.metadata['ensemble_membership'] <= ens1_id, - c.metadata['ensemble_membership']) - >= ens1_id_min)[0].shape[0] for c in cc] - ensB = [np.where(np.logical_and( - c.metadata['ensemble_membership'] <= ens2_id, - c.metadata['ensemble_membership']) - >= ens2_id_min)[0].shape[0] for c in cc] + ensA = [ + np.where( + np.logical_and( + c.metadata["ensemble_membership"] <= ens1_id, + c.metadata["ensemble_membership"], + ) + >= ens1_id_min + )[0].shape[0] + for c in cc + ] + ensB = [ + np.where( + np.logical_and( + c.metadata["ensemble_membership"] <= ens2_id, + c.metadata["ensemble_membership"], + ) + >= ens2_id_min + )[0].shape[0] + for c in cc + ] sizeA = float(np.sum(ensA)) sizeB = float(np.sum(ensB)) @@ -406,8 +440,7 @@ def cumulative_clustering_ensemble_similarity(cc, ens1_id, ens2_id, return discrete_jensen_shannon_divergence(pA, pB) -def gen_kde_pdfs(embedded_space, ensemble_assignment, nensembles, - nsamples): +def gen_kde_pdfs(embedded_space, ensemble_assignment, nensembles, nsamples): """ Generate Kernel Density Estimates (KDE) from embedded spaces and elaborate the coordinates for later use. @@ -452,7 +485,8 @@ def gen_kde_pdfs(embedded_space, ensemble_assignment, nensembles, for i in range(1, nensembles + 1): this_embedded = embedded_space.transpose()[ - np.where(np.array(ensemble_assignment) == i)].transpose() + np.where(np.array(ensemble_assignment) == i) + ].transpose() embedded_ensembles.append(this_embedded) kdes.append(scipy.stats.gaussian_kde(this_embedded)) @@ -467,9 +501,16 @@ def gen_kde_pdfs(embedded_space, ensemble_assignment, nensembles, return (kdes, resamples, embedded_ensembles) -def dimred_ensemble_similarity(kde1, resamples1, kde2, resamples2, - ln_P1_exp_P1=None, ln_P2_exp_P2=None, - ln_P1P2_exp_P1=None, ln_P1P2_exp_P2=None): +def dimred_ensemble_similarity( + kde1, + resamples1, + kde2, + resamples2, + ln_P1_exp_P1=None, + ln_P2_exp_P2=None, + ln_P1P2_exp_P1=None, + ln_P1P2_exp_P2=None, +): r"""Calculate the Jensen-Shannon divergence according the Dimensionality reduction method. @@ -541,21 +582,38 @@ def dimred_ensemble_similarity(kde1, resamples1, kde2, resamples2, """ - if not ln_P1_exp_P1 and not ln_P2_exp_P2 and not ln_P1P2_exp_P1 and not \ - ln_P1P2_exp_P2: + if ( + not ln_P1_exp_P1 + and not ln_P2_exp_P2 + and not ln_P1P2_exp_P1 + and not ln_P1P2_exp_P2 + ): ln_P1_exp_P1 = np.average(np.log(kde1.evaluate(resamples1))) ln_P2_exp_P2 = np.average(np.log(kde2.evaluate(resamples2))) - ln_P1P2_exp_P1 = np.average(np.log( - 0.5 * (kde1.evaluate(resamples1) + kde2.evaluate(resamples1)))) - ln_P1P2_exp_P2 = np.average(np.log( - 0.5 * (kde1.evaluate(resamples2) + kde2.evaluate(resamples2)))) + ln_P1P2_exp_P1 = np.average( + np.log( + 0.5 * (kde1.evaluate(resamples1) + kde2.evaluate(resamples1)) + ) + ) + ln_P1P2_exp_P2 = np.average( + np.log( + 0.5 * (kde1.evaluate(resamples2) + kde2.evaluate(resamples2)) + ) + ) return 0.5 * ( - ln_P1_exp_P1 - ln_P1P2_exp_P1 + ln_P2_exp_P2 - ln_P1P2_exp_P2) + ln_P1_exp_P1 - ln_P1P2_exp_P1 + ln_P2_exp_P2 - ln_P1P2_exp_P2 + ) -def cumulative_gen_kde_pdfs(embedded_space, ensemble_assignment, nensembles, - nsamples, ens_id_min=1, ens_id_max=None): +def cumulative_gen_kde_pdfs( + embedded_space, + ensemble_assignment, + nensembles, + nsamples, + ens_id_min=1, + ens_id_max=None, +): """ Generate Kernel Density Estimates (KDE) from embedded spaces and elaborate the coordinates for later use. However, consider more than @@ -615,9 +673,13 @@ def cumulative_gen_kde_pdfs(embedded_space, ensemble_assignment, nensembles, if not ens_id_max: ens_id_max = nensembles + 1 for i in range(ens_id_min, ens_id_max): - this_embedded = embedded_space.transpose()[np.where( - np.logical_and(ensemble_assignment >= ens_id_min, - ensemble_assignment <= i))].transpose() + this_embedded = embedded_space.transpose()[ + np.where( + np.logical_and( + ensemble_assignment >= ens_id_min, ensemble_assignment <= i + ) + ) + ].transpose() embedded_ensembles.append(this_embedded) kdes.append(scipy.stats.gaussian_kde(this_embedded)) @@ -628,8 +690,9 @@ def cumulative_gen_kde_pdfs(embedded_space, ensemble_assignment, nensembles, return (kdes, resamples, embedded_ensembles) -def write_output(matrix, base_fname=None, header="", suffix="", - extension="dat"): +def write_output( + matrix, base_fname=None, header="", suffix="", extension="dat" +): """ Write output matrix with a nice format, to stdout and optionally a file. @@ -662,9 +725,9 @@ def write_output(matrix, base_fname=None, header="", suffix="", matrix.square_print(header=header, fname=fname) -def prepare_ensembles_for_convergence_increasing_window(ensemble, - window_size, - select="name CA"): +def prepare_ensembles_for_convergence_increasing_window( + ensemble, window_size, select="name CA" +): """ Generate ensembles to be fed to ces_convergence or dres_convergence from a single ensemble. Basically, the different slices the algorithm @@ -693,8 +756,9 @@ def prepare_ensembles_for_convergence_increasing_window(ensemble, """ - ens_size = ensemble.trajectory.timeseries(ensemble.select_atoms(select), - order='fac').shape[0] + ens_size = ensemble.trajectory.timeseries( + ensemble.select_atoms(select), order="fac" + ).shape[0] rest_slices = ens_size // window_size residuals = ens_size % window_size @@ -706,24 +770,30 @@ def prepare_ensembles_for_convergence_increasing_window(ensemble, slices_n.append(slices_n[-1] + window_size) slices_n.append(slices_n[-1] + residuals + window_size) - for s,sl in enumerate(slices_n[:-1]): - tmp_ensembles.append(mda.Universe( - ensemble.filename, - ensemble.trajectory.timeseries(order='fac') - [slices_n[s]:slices_n[s + 1], :, :], - format=MemoryReader)) + for s, sl in enumerate(slices_n[:-1]): + tmp_ensembles.append( + mda.Universe( + ensemble.filename, + ensemble.trajectory.timeseries(order="fac")[ + slices_n[s] : slices_n[s + 1], :, : + ], + format=MemoryReader, + ) + ) return tmp_ensembles -def hes(ensembles, - select="name CA", - cov_estimator="shrinkage", - weights='mass', - align=False, - estimate_error=False, - bootstrapping_samples=100, - calc_diagonal=False): +def hes( + ensembles, + select="name CA", + cov_estimator="shrinkage", + weights="mass", + align=False, + estimate_error=False, + bootstrapping_samples=100, + calc_diagonal=False, +): r"""Calculates the Harmonic Ensemble Similarity (HES) between ensembles. The HES is calculated with the symmetrized version of Kullback-Leibler @@ -835,8 +905,11 @@ def hes(ensembles, """ - if not isinstance(weights, (list, tuple, np.ndarray)) and weights == 'mass': - weights = ['mass' for _ in range(len(ensembles))] + if ( + not isinstance(weights, (list, tuple, np.ndarray)) + and weights == "mass" + ): + weights = ["mass" for _ in range(len(ensembles))] elif weights is not None: if len(weights) != len(ensembles): raise ValueError("need weights for every ensemble") @@ -848,10 +921,9 @@ def hes(ensembles, # on the universe. if align: for e, w in zip(ensembles, weights): - mda.analysis.align.AlignTraj(e, ensembles[0], - select=select, - weights=w, - in_memory=True).run() + mda.analysis.align.AlignTraj( + e, ensembles[0], select=select, weights=w, in_memory=True + ).run() else: for ensemble in ensembles: ensemble.transfer_to_memory() @@ -871,7 +943,8 @@ def hes(ensembles, else: logging.error( "Covariance estimator {0} is not supported. " - "Choose between 'shrinkage' and 'ml'.".format(cov_estimator)) + "Choose between 'shrinkage' and 'ml'.".format(cov_estimator) + ) return None out_matrix_eln = len(ensembles) @@ -885,8 +958,9 @@ def hes(ensembles, for i, ensemble in enumerate(ensembles): ensembles_list.append( get_ensemble_bootstrap_samples( - ensemble, - samples=bootstrapping_samples)) + ensemble, samples=bootstrapping_samples + ) + ) for t in range(bootstrapping_samples): logging.info("The coordinates will be bootstrapped.") @@ -894,21 +968,30 @@ def hes(ensembles, sigmas = [] values = np.zeros((out_matrix_eln, out_matrix_eln)) for i, e_orig in enumerate(ensembles): - xs.append(np.average( - ensembles_list[i][t].trajectory.timeseries( - e_orig.select_atoms(select), - order=('fac')), - axis=0).flatten()) - sigmas.append(covariance_matrix(ensembles_list[i][t], - weights=weights[i], - estimator=covariance_estimator, - select=select)) + xs.append( + np.average( + ensembles_list[i][t].trajectory.timeseries( + e_orig.select_atoms(select), order=("fac") + ), + axis=0, + ).flatten() + ) + sigmas.append( + covariance_matrix( + ensembles_list[i][t], + weights=weights[i], + estimator=covariance_estimator, + select=select, + ) + ) for pair in pairs_indices: - value = harmonic_ensemble_similarity(x1=xs[pair[0]], - x2=xs[pair[1]], - sigma1=sigmas[pair[0]], - sigma2=sigmas[pair[1]]) + value = harmonic_ensemble_similarity( + x1=xs[pair[0]], + x2=xs[pair[1]], + sigma1=sigmas[pair[0]], + sigma2=sigmas[pair[1]], + ) values[pair[0], pair[1]] = value values[pair[1], pair[0]] = value data.append(values) @@ -923,31 +1006,32 @@ def hes(ensembles, for e, w in zip(ensembles, weights): # Extract coordinates from each ensemble - coordinates_system = e.trajectory.timeseries(e.select_atoms(select), - order='fac') + coordinates_system = e.trajectory.timeseries( + e.select_atoms(select), order="fac" + ) # Average coordinates in each system xs.append(np.average(coordinates_system, axis=0).flatten()) # Covariance matrices in each system - sigmas.append(covariance_matrix(e, - weights=w, - estimator=covariance_estimator, - select=select)) + sigmas.append( + covariance_matrix( + e, weights=w, estimator=covariance_estimator, select=select + ) + ) for i, j in pairs_indices: - value = harmonic_ensemble_similarity(x1=xs[i], - x2=xs[j], - sigma1=sigmas[i], - sigma2=sigmas[j]) + value = harmonic_ensemble_similarity( + x1=xs[i], x2=xs[j], sigma1=sigmas[i], sigma2=sigmas[j] + ) values[i, j] = value values[j, i] = value # Save details as required details = {} for i in range(out_matrix_eln): - details['ensemble{0:d}_mean'.format(i + 1)] = xs[i] - details['ensemble{0:d}_covariance_matrix'.format(i + 1)] = sigmas[i] + details["ensemble{0:d}_mean".format(i + 1)] = xs[i] + details["ensemble{0:d}_covariance_matrix".format(i + 1)] = sigmas[i] return values, details @@ -1099,39 +1183,42 @@ def ces( pairs_indices = list(trm_indices_nodiag(len(ensembles))) clustering_methods = clustering_method - if not hasattr(clustering_method, '__iter__'): + if not hasattr(clustering_method, "__iter__"): clustering_methods = [clustering_method] - any_method_accept_distance_matrix = \ - np.any([method.accepts_distance_matrix for method in clustering_methods]) - all_methods_accept_distance_matrix = \ - np.all([method.accepts_distance_matrix for method in clustering_methods]) + any_method_accept_distance_matrix = np.any( + [method.accepts_distance_matrix for method in clustering_methods] + ) + all_methods_accept_distance_matrix = np.all( + [method.accepts_distance_matrix for method in clustering_methods] + ) # Register which ensembles the samples belong to ensemble_assignment = [] for i, ensemble in enumerate(ensembles): - ensemble_assignment += [i+1]*len(ensemble.trajectory) + ensemble_assignment += [i + 1] * len(ensemble.trajectory) # Calculate distance matrix if not provided if any_method_accept_distance_matrix and not distance_matrix: - distance_matrix = get_distance_matrix(merge_universes(ensembles), - select=select, - ncores=ncores) + distance_matrix = get_distance_matrix( + merge_universes(ensembles), select=select, ncores=ncores + ) if estimate_error: if any_method_accept_distance_matrix: - distance_matrix = \ - get_distance_matrix_bootstrap_samples( - distance_matrix, - ensemble_assignment, - samples=bootstrapping_samples, - ncores=ncores) + distance_matrix = get_distance_matrix_bootstrap_samples( + distance_matrix, + ensemble_assignment, + samples=bootstrapping_samples, + ncores=ncores, + ) if not all_methods_accept_distance_matrix: ensembles_list = [] for i, ensemble in enumerate(ensembles): ensembles_list.append( get_ensemble_bootstrap_samples( - ensemble, - samples=bootstrapping_samples)) + ensemble, samples=bootstrapping_samples + ) + ) ensembles = [] for j in range(bootstrapping_samples): ensembles.append([]) @@ -1141,16 +1228,17 @@ def ces( # if all methods accept distances matrices, duplicate # ensemble so that it matches size of distance matrices # (no need to resample them since they will not be used) - ensembles = [ensembles]*bootstrapping_samples - + ensembles = [ensembles] * bootstrapping_samples # Call clustering procedure - ccs = cluster(ensembles, - method= clustering_methods, - select=select, - distance_matrix = distance_matrix, - ncores = ncores, - allow_collapsed_result=False) + ccs = cluster( + ensembles, + method=clustering_methods, + select=select, + distance_matrix=distance_matrix, + ncores=ncores, + allow_collapsed_result=False, + ) # Do error analysis if estimate_error: @@ -1166,20 +1254,20 @@ def ces( failed_runs += 1 k += 1 continue - values[i].append(np.zeros((len(ensembles[j]), - len(ensembles[j])))) + values[i].append( + np.zeros((len(ensembles[j]), len(ensembles[j]))) + ) for pair in pairs_indices: # Calculate dJS - this_djs = \ - clustering_ensemble_similarity(ccs[k], - ensembles[j][ - pair[0]], - pair[0] + 1, - ensembles[j][ - pair[1]], - pair[1] + 1, - select=select) + this_djs = clustering_ensemble_similarity( + ccs[k], + ensembles[j][pair[0]], + pair[0] + 1, + ensembles[j][pair[1]], + pair[1] + 1, + select=select, + ) values[i][-1][pair[0], pair[1]] = this_djs values[i][-1][pair[1], pair[0]] = this_djs k += 1 @@ -1187,7 +1275,7 @@ def ces( avgs.append(np.average(outs, axis=0)) stds.append(np.std(outs, axis=0)) - if hasattr(clustering_method, '__iter__'): + if hasattr(clustering_method, "__iter__"): pass else: avgs = avgs[0] @@ -1205,19 +1293,20 @@ def ces( for pair in pairs_indices: # Calculate dJS - this_val = \ - clustering_ensemble_similarity(ccs[i], - ensembles[pair[0]], - pair[0] + 1, - ensembles[pair[1]], - pair[1] + 1, - select=select) + this_val = clustering_ensemble_similarity( + ccs[i], + ensembles[pair[0]], + pair[0] + 1, + ensembles[pair[1]], + pair[1] + 1, + select=select, + ) values[-1][pair[0], pair[1]] = this_val values[-1][pair[1], pair[0]] = this_val - details['clustering'] = ccs + details["clustering"] = ccs - if allow_collapsed_result and not hasattr(clustering_method, '__iter__'): + if allow_collapsed_result and not hasattr(clustering_method, "__iter__"): values = values[0] return values, details @@ -1374,43 +1463,54 @@ def dres( pairs_indices = list(trm_indices_nodiag(len(ensembles))) dimensionality_reduction_methods = dimensionality_reduction_method - if not hasattr(dimensionality_reduction_method, '__iter__'): + if not hasattr(dimensionality_reduction_method, "__iter__"): dimensionality_reduction_methods = [dimensionality_reduction_method] - any_method_accept_distance_matrix = \ - np.any([method.accepts_distance_matrix for method in dimensionality_reduction_methods]) - all_methods_accept_distance_matrix = \ - np.all([method.accepts_distance_matrix for method in dimensionality_reduction_methods]) + any_method_accept_distance_matrix = np.any( + [ + method.accepts_distance_matrix + for method in dimensionality_reduction_methods + ] + ) + all_methods_accept_distance_matrix = np.all( + [ + method.accepts_distance_matrix + for method in dimensionality_reduction_methods + ] + ) # Register which ensembles the samples belong to ensemble_assignment = [] for i, ensemble in enumerate(ensembles): - ensemble_assignment += [i+1]*len(ensemble.trajectory) + ensemble_assignment += [i + 1] * len(ensemble.trajectory) # Calculate distance matrix if not provided if any_method_accept_distance_matrix and not distance_matrix: - distance_matrix = get_distance_matrix(merge_universes(ensembles), - select=select, - ncores=ncores) + distance_matrix = get_distance_matrix( + merge_universes(ensembles), select=select, ncores=ncores + ) if estimate_error: if any_method_accept_distance_matrix: - distance_matrix = \ - get_distance_matrix_bootstrap_samples( - distance_matrix, - ensemble_assignment, - samples=bootstrapping_samples, - ncores=ncores) + distance_matrix = get_distance_matrix_bootstrap_samples( + distance_matrix, + ensemble_assignment, + samples=bootstrapping_samples, + ncores=ncores, + ) if not all_methods_accept_distance_matrix: ensembles_list = [] for i, ensemble in enumerate(ensembles): ensembles_list.append( get_ensemble_bootstrap_samples( - ensemble, - samples=bootstrapping_samples)) + ensemble, samples=bootstrapping_samples + ) + ) ensembles = [] for j in range(bootstrapping_samples): - ensembles.append(ensembles_list[i, j] for i - in range(ensembles_list.shape[0])) + ensembles.append( + ensembles_list[i, j] + for i in range(ensembles_list.shape[0]) + ) else: # if all methods accept distances matrices, duplicate # ensemble so that it matches size of distance matrices @@ -1422,9 +1522,10 @@ def dres( ensembles, method=dimensionality_reduction_methods, select=select, - distance_matrix = distance_matrix, - ncores = ncores, - allow_collapsed_result = False) + distance_matrix=distance_matrix, + ncores=ncores, + allow_collapsed_result=False, + ) details = {} details["reduced_coordinates"] = coordinates @@ -1435,24 +1536,28 @@ def dres( values = {} avgs = [] stds = [] - for i,method in enumerate(dimensionality_reduction_methods): + for i, method in enumerate(dimensionality_reduction_methods): values[i] = [] for j in range(bootstrapping_samples): - values[i].append(np.zeros((len(ensembles[j]), - len(ensembles[j])))) + values[i].append( + np.zeros((len(ensembles[j]), len(ensembles[j]))) + ) kdes, resamples, embedded_ensembles = gen_kde_pdfs( coordinates[k], ensemble_assignment, len(ensembles[j]), - nsamples=nsamples) + nsamples=nsamples, + ) for pair in pairs_indices: - this_value = dimred_ensemble_similarity(kdes[pair[0]], - resamples[pair[0]], - kdes[pair[1]], - resamples[pair[1]]) + this_value = dimred_ensemble_similarity( + kdes[pair[0]], + resamples[pair[0]], + kdes[pair[1]], + resamples[pair[1]], + ) values[i][-1][pair[0], pair[1]] = this_value values[i][-1][pair[1], pair[0]] = this_value @@ -1461,7 +1566,7 @@ def dres( avgs.append(np.average(outs, axis=0)) stds.append(np.std(outs, axis=0)) - if hasattr(dimensionality_reduction_method, '__iter__'): + if hasattr(dimensionality_reduction_method, "__iter__"): pass else: avgs = avgs[0] @@ -1471,24 +1576,29 @@ def dres( values = [] - for i,method in enumerate(dimensionality_reduction_methods): + for i, method in enumerate(dimensionality_reduction_methods): values.append(np.zeros((len(ensembles), len(ensembles)))) - kdes, resamples, embedded_ensembles = gen_kde_pdfs(coordinates[i], - ensemble_assignment, - len(ensembles), - nsamples=nsamples) + kdes, resamples, embedded_ensembles = gen_kde_pdfs( + coordinates[i], + ensemble_assignment, + len(ensembles), + nsamples=nsamples, + ) for pair in pairs_indices: - this_value = dimred_ensemble_similarity(kdes[pair[0]], - resamples[pair[0]], - kdes[pair[1]], - resamples[pair[1]]) + this_value = dimred_ensemble_similarity( + kdes[pair[0]], + resamples[pair[0]], + kdes[pair[1]], + resamples[pair[1]], + ) values[-1][pair[0], pair[1]] = this_value values[-1][pair[1], pair[0]] = this_value - if allow_collapsed_result and not hasattr(dimensionality_reduction_method, - '__iter__'): + if allow_collapsed_result and not hasattr( + dimensionality_reduction_method, "__iter__" + ): values = values[0] return values, details @@ -1576,13 +1686,16 @@ def ces_convergence( ) ensembles = prepare_ensembles_for_convergence_increasing_window( - original_ensemble, window_size, select=select) + original_ensemble, window_size, select=select + ) - ccs = cluster(ensembles, - select=select, - method=clustering_method, - allow_collapsed_result=False, - ncores=ncores) + ccs = cluster( + ensembles, + select=select, + method=clustering_method, + allow_collapsed_result=False, + ncores=ncores, + ) out = [] for cc in ccs: @@ -1591,9 +1704,8 @@ def ces_convergence( out.append(np.zeros(len(ensembles))) for j, ensemble in enumerate(ensembles): out[-1][j] = cumulative_clustering_ensemble_similarity( - cc, - len(ensembles), - j + 1) + cc, len(ensembles), j + 1 + ) out = np.array(out).T return out @@ -1680,19 +1792,20 @@ def dres_convergence( ) ensembles = prepare_ensembles_for_convergence_increasing_window( - original_ensemble, window_size, select=select) + original_ensemble, window_size, select=select + ) - coordinates, dimred_details = \ - reduce_dimensionality( - ensembles, - select=select, - method=dimensionality_reduction_method, - allow_collapsed_result=False, - ncores=ncores) + coordinates, dimred_details = reduce_dimensionality( + ensembles, + select=select, + method=dimensionality_reduction_method, + allow_collapsed_result=False, + ncores=ncores, + ) ensemble_assignment = [] for i, ensemble in enumerate(ensembles): - ensemble_assignment += [i+1]*len(ensemble.trajectory) + ensemble_assignment += [i + 1] * len(ensemble.trajectory) ensemble_assignment = np.array(ensemble_assignment) out = [] @@ -1700,18 +1813,17 @@ def dres_convergence( out.append(np.zeros(len(ensembles))) - kdes, resamples, embedded_ensembles = \ - cumulative_gen_kde_pdfs( - coordinates[i], - ensemble_assignment=ensemble_assignment, - nensembles=len(ensembles), - nsamples=nsamples) + kdes, resamples, embedded_ensembles = cumulative_gen_kde_pdfs( + coordinates[i], + ensemble_assignment=ensemble_assignment, + nensembles=len(ensembles), + nsamples=nsamples, + ) for j, ensemble in enumerate(ensembles): - out[-1][j] = dimred_ensemble_similarity(kdes[-1], - resamples[-1], - kdes[j], - resamples[j]) + out[-1][j] = dimred_ensemble_similarity( + kdes[-1], resamples[-1], kdes[j], resamples[j] + ) out = np.array(out).T return out diff --git a/package/MDAnalysis/analysis/encore/utils.py b/package/MDAnalysis/analysis/encore/utils.py index 13a028f45c4..ae6407ddd19 100644 --- a/package/MDAnalysis/analysis/encore/utils.py +++ b/package/MDAnalysis/analysis/encore/utils.py @@ -123,14 +123,16 @@ def loadz(self, fname): """ loaded = np.load(fname, allow_pickle=True) - if loaded['metadata'].shape != (): - if loaded['metadata']['number of frames'] != self.size: + if loaded["metadata"].shape != (): + if loaded["metadata"]["number of frames"] != self.size: raise TypeError - self.metadata = loaded['metadata'] + self.metadata = loaded["metadata"] else: - if self.size*(self.size-1)/2+self.size != len(loaded['elements']): + if self.size * (self.size - 1) / 2 + self.size != len( + loaded["elements"] + ): raise TypeError - self._elements = loaded['elements'] + self._elements = loaded["elements"] def __add__(self, scalar): """Add scalar to matrix elements. @@ -142,7 +144,7 @@ def __add__(self, scalar): Scalar to be added. """ newMatrix = self.__class__(self.size) - newMatrix._elements = self._elements + scalar; + newMatrix._elements = self._elements + scalar return newMatrix def __iadd__(self, scalar): @@ -157,7 +159,6 @@ def __iadd__(self, scalar): self._elements += scalar return self - def __mul__(self, scalar): """Multiply with scalar. @@ -168,7 +169,7 @@ def __mul__(self, scalar): Scalar to multiply with. """ newMatrix = self.__class__(self.size) - newMatrix._elements = self._elements * scalar; + newMatrix._elements = self._elements * scalar return newMatrix def __imul__(self, scalar): @@ -237,10 +238,12 @@ class description. self.n_jobs = cpu_count() self.functions = function - if not hasattr(self.functions, '__iter__'): + if not hasattr(self.functions, "__iter__"): self.functions = [self.functions] * len(args) if len(self.functions) != len(args): - self.functions = self.functions[:] * (len(args) // len(self.functions)) + self.functions = self.functions[:] * ( + len(args) // len(self.functions) + ) # Arguments should be present if args is None: @@ -273,10 +276,12 @@ def worker(self, q, results): """ while True: i = q.get() - if i == 'STOP': + if i == "STOP": return - results.put((i, self.functions[i](*self.args[i], **self.kwargs[i]))) + results.put( + (i, self.functions[i](*self.args[i], **self.kwargs[i])) + ) def run(self): r""" @@ -294,20 +299,23 @@ def run(self): results_list = [] if self.n_jobs == 1: for i in range(self.nruns): - results_list.append((i, self.functions[i](*self.args[i], - **self.kwargs[i]))) + results_list.append( + (i, self.functions[i](*self.args[i], **self.kwargs[i])) + ) else: manager = Manager() q = manager.Queue() results = manager.Queue() - workers = [Process(target=self.worker, args=(q, results)) for i in - range(self.n_jobs)] + workers = [ + Process(target=self.worker, args=(q, results)) + for i in range(self.n_jobs) + ] for i in range(self.nruns): q.put(i) for w in workers: - q.put('STOP') + q.put("STOP") for w in workers: w.start() @@ -315,8 +323,8 @@ def run(self): for w in workers: w.join() - results.put('STOP') - for i in iter(results.get, 'STOP'): + results.put("STOP") + for i in iter(results.get, "STOP"): results_list.append(i) return tuple(sorted(results_list, key=lambda x: x[0])) @@ -361,7 +369,7 @@ def trm_indices_nodiag(n): n : int Matrix size -""" + """ for i in range(1, n): for j in range(i): @@ -377,7 +385,7 @@ def trm_indices_diag(n): n : int Matrix size -""" + """ for i in range(0, n): for j in range(i + 1): @@ -403,6 +411,9 @@ def merge_universes(universes): return mda.Universe( universes[0].filename, - np.concatenate(tuple([e.trajectory.timeseries(order='fac') for e in universes]), - axis=0), - format=MemoryReader) + np.concatenate( + tuple([e.trajectory.timeseries(order="fac") for e in universes]), + axis=0, + ), + format=MemoryReader, + ) diff --git a/package/MDAnalysis/analysis/gnm.py b/package/MDAnalysis/analysis/gnm.py index ee42bc165ef..f2f373543ea 100644 --- a/package/MDAnalysis/analysis/gnm.py +++ b/package/MDAnalysis/analysis/gnm.py @@ -21,9 +21,9 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -#Analyse a trajectory using elastic network models, following the approach of Hall et al (JACS 2007) -#Ben Hall (benjamin.a.hall@ucl.ac.uk) is to blame -#Copyright 2011; Consider under GPL v2 or later +# Analyse a trajectory using elastic network models, following the approach of Hall et al (JACS 2007) +# Ben Hall (benjamin.a.hall@ucl.ac.uk) is to blame +# Copyright 2011; Consider under GPL v2 or later r""" Elastic network analysis of MD trajectories --- :mod:`MDAnalysis.analysis.gnm` ============================================================================== @@ -97,11 +97,11 @@ from MDAnalysis.analysis.base import Results -logger = logging.getLogger('MDAnalysis.analysis.GNM') +logger = logging.getLogger("MDAnalysis.analysis.GNM") def _dsq(a, b): - diff = (a - b) + diff = a - b return np.dot(diff, diff) @@ -133,10 +133,14 @@ def generate_grid(positions, cutoff): low_x = x.min() low_y = y.min() low_z = z.min() - #Ok now generate a list with 3 dimensions representing boxes in x, y and z - grid = [[[[] for i in range(int((high_z - low_z) / cutoff) + 1)] - for j in range(int((high_y - low_y) / cutoff) + 1)] - for k in range(int((high_x - low_x) / cutoff) + 1)] + # Ok now generate a list with 3 dimensions representing boxes in x, y and z + grid = [ + [ + [[] for i in range(int((high_z - low_z) / cutoff) + 1)] + for j in range(int((high_y - low_y) / cutoff) + 1) + ] + for k in range(int((high_x - low_x) / cutoff) + 1) + ] for i, pos in enumerate(positions): x_pos = int((pos[0] - low_x) / cutoff) y_pos = int((pos[1] - low_y) / cutoff) @@ -166,7 +170,8 @@ def neighbour_generator(positions, cutoff): n_y = len(grid[0]) n_z = len(grid[0][0]) for cell_x, cell_y, cell_z in itertools.product( - range(n_x), range(n_y), range(n_z)): + range(n_x), range(n_y), range(n_z) + ): atoms = grid[cell_x][cell_y][cell_z] # collect all atoms in own cell and neighboring cell all_atoms = [] @@ -247,8 +252,8 @@ class GNMAnalysis(AnalysisBase): ``eigenvectors`` of the ``results`` attribute. .. versionchanged:: 2.8.0 - Enabled **parallel execution** with the ``multiprocessing`` and ``dask`` - backends; use the new method :meth:`get_supported_backends` to see all + Enabled **parallel execution** with the ``multiprocessing`` and ``dask`` + backends; use the new method :meth:`get_supported_backends` to see all supported backends. """ @@ -257,13 +262,15 @@ class GNMAnalysis(AnalysisBase): @classmethod def get_supported_backends(cls): return ("serial", "multiprocessing", "dask") - - def __init__(self, - universe, - select='protein and name CA', - cutoff=7.0, - ReportVector=None, - Bonus_groups=None): + + def __init__( + self, + universe, + select="protein and name CA", + cutoff=7.0, + ReportVector=None, + Bonus_groups=None, + ): super(GNMAnalysis, self).__init__(universe.trajectory) self.u = universe self.select = select @@ -273,12 +280,16 @@ def __init__(self, self.results.eigenvectors = [] self._timesteps = None # time for each frame self.ReportVector = ReportVector - self.Bonus_groups = [self.u.select_atoms(item) for item in Bonus_groups] \ - if Bonus_groups else [] + self.Bonus_groups = ( + [self.u.select_atoms(item) for item in Bonus_groups] + if Bonus_groups + else [] + ) self.ca = self.u.select_atoms(self.select) - def _generate_output(self, w, v, outputobject, - ReportVector=None, counter=0): + def _generate_output( + self, w, v, outputobject, ReportVector=None, counter=0 + ): """Appends time, eigenvalues and eigenvectors to results. This generates the output by adding eigenvalue and @@ -297,7 +308,8 @@ def _generate_output(self, w, v, outputobject, item[0] + 1, w[list_map[1]], item[1], - file=oup) + file=oup, + ) outputobject.eigenvalues.append(w[list_map[1]]) outputobject.eigenvectors.append(v[list_map[1]]) @@ -316,9 +328,9 @@ def generate_kirchoff(self): """ positions = self.ca.positions - #add the com from each bonus group to the ca_positions list + # add the com from each bonus group to the ca_positions list for item in self.Bonus_groups: - #bonus = self.u.select_atoms(item) + # bonus = self.u.select_atoms(item) positions = np.vstack((positions, item.center_of_mass())) natoms = len(positions) @@ -327,8 +339,10 @@ def generate_kirchoff(self): cutoffsq = self.cutoff**2 for i_atom, j_atom in neighbour_generator(positions, self.cutoff): - if j_atom > i_atom and _dsq(positions[i_atom], - positions[j_atom]) < cutoffsq: + if ( + j_atom > i_atom + and _dsq(positions[i_atom], positions[j_atom]) < cutoffsq + ): matrix[i_atom][j_atom] = -1.0 matrix[j_atom][i_atom] = -1.0 matrix[i_atom][i_atom] = matrix[i_atom][i_atom] + 1 @@ -352,7 +366,8 @@ def _single_frame(self): v, self.results, ReportVector=self.ReportVector, - counter=self._ts.frame) + counter=self._ts.frame, + ) def _conclude(self): self.results.times = self.times @@ -427,16 +442,17 @@ class closeContactGNMAnalysis(GNMAnalysis): ``eigenvectors`` of the `results` attribute. """ - def __init__(self, - universe, - select='protein', - cutoff=4.5, - ReportVector=None, - weights="size"): - super(closeContactGNMAnalysis, self).__init__(universe, - select, - cutoff, - ReportVector) + def __init__( + self, + universe, + select="protein", + cutoff=4.5, + ReportVector=None, + weights="size", + ): + super(closeContactGNMAnalysis, self).__init__( + universe, select, cutoff, ReportVector + ) self.weights = weights def generate_kirchoff(self): @@ -452,17 +468,21 @@ def generate_kirchoff(self): # cache sqrt of residue sizes (slow) so that sr[i]*sr[j] == sqrt(r[i]*r[j]) inv_sqrt_res_sizes = np.ones(len(self.ca.residues)) - if self.weights == 'size': + if self.weights == "size": inv_sqrt_res_sizes = 1 / np.sqrt( - [r.atoms.n_atoms for r in self.ca.residues]) + [r.atoms.n_atoms for r in self.ca.residues] + ) for i_atom, j_atom in neighbour_generator(positions, self.cutoff): - if j_atom > i_atom and _dsq(positions[i_atom], - positions[j_atom]) < cutoffsq: + if ( + j_atom > i_atom + and _dsq(positions[i_atom], positions[j_atom]) < cutoffsq + ): iresidue = residue_index_map[i_atom] jresidue = residue_index_map[j_atom] - contact = (inv_sqrt_res_sizes[iresidue] * - inv_sqrt_res_sizes[jresidue]) + contact = ( + inv_sqrt_res_sizes[iresidue] * inv_sqrt_res_sizes[jresidue] + ) matrix[iresidue][jresidue] -= contact matrix[jresidue][iresidue] -= contact matrix[iresidue][iresidue] += contact diff --git a/package/MDAnalysis/analysis/hbonds/__init__.py b/package/MDAnalysis/analysis/hbonds/__init__.py index b74b96638b4..4f01974ff25 100644 --- a/package/MDAnalysis/analysis/hbonds/__init__.py +++ b/package/MDAnalysis/analysis/hbonds/__init__.py @@ -22,7 +22,8 @@ # __all__ = [ - 'HydrogenBondAutoCorrel', 'find_hydrogen_donors', + "HydrogenBondAutoCorrel", + "find_hydrogen_donors", ] from .hbond_autocorrel import HydrogenBondAutoCorrel, find_hydrogen_donors diff --git a/package/MDAnalysis/analysis/hbonds/hbond_autocorrel.py b/package/MDAnalysis/analysis/hbonds/hbond_autocorrel.py index a5204236a07..3f80affc814 100644 --- a/package/MDAnalysis/analysis/hbonds/hbond_autocorrel.py +++ b/package/MDAnalysis/analysis/hbonds/hbond_autocorrel.py @@ -45,10 +45,12 @@ import warnings with warnings.catch_warnings(): - warnings.simplefilter('always', DeprecationWarning) - wmsg = ("This module was moved to " - "MDAnalysis.analysis.hydrogenbonds.hbond_autocorrel; " - "hbonds.hbond_autocorrel will be removed in 3.0.0.") + warnings.simplefilter("always", DeprecationWarning) + wmsg = ( + "This module was moved to " + "MDAnalysis.analysis.hydrogenbonds.hbond_autocorrel; " + "hbonds.hbond_autocorrel will be removed in 3.0.0." + ) warnings.warn(wmsg, category=DeprecationWarning) from MDAnalysis.lib.util import deprecate @@ -56,16 +58,18 @@ from ..hydrogenbonds import hbond_autocorrel -find_hydrogen_donors = deprecate(hbond_autocorrel.find_hydrogen_donors, - release="2.0.0", remove="3.0.0", - message="The function was moved to " - "MDAnalysis.analysis.hbonds.hbond_autocorrel.") - -HydrogenBondAutoCorrel = deprecate(hbond_autocorrel.HydrogenBondAutoCorrel, - release="2.0.0", remove="3.0.0", - message="The class was moved to " - "MDAnalysis.analysis.hbonds.hbond_autocorrel.") - - - - +find_hydrogen_donors = deprecate( + hbond_autocorrel.find_hydrogen_donors, + release="2.0.0", + remove="3.0.0", + message="The function was moved to " + "MDAnalysis.analysis.hbonds.hbond_autocorrel.", +) + +HydrogenBondAutoCorrel = deprecate( + hbond_autocorrel.HydrogenBondAutoCorrel, + release="2.0.0", + remove="3.0.0", + message="The class was moved to " + "MDAnalysis.analysis.hbonds.hbond_autocorrel.", +) diff --git a/package/MDAnalysis/analysis/helix_analysis.py b/package/MDAnalysis/analysis/helix_analysis.py index 1b1bdf9ce3f..e0edf763708 100644 --- a/package/MDAnalysis/analysis/helix_analysis.py +++ b/package/MDAnalysis/analysis/helix_analysis.py @@ -126,7 +126,7 @@ def vector_of_best_fit(coordinates): # does vector face first local helix origin? angle = mdamath.angle(centered[0], vector) - if angle > np.pi/2: + if angle > np.pi / 2: vector *= -1 return vector @@ -168,7 +168,7 @@ def local_screw_angles(global_axis, ref_axis, helix_directions): # project helix_directions onto global to remove contribution norm_global_sq = np.dot(global_axis, global_axis) - mag_g = np.matmul(global_axis, helix_directions.T)/norm_global_sq + mag_g = np.matmul(global_axis, helix_directions.T) / norm_global_sq # projection onto global_axis proj_g = mag_g.reshape(-1, 1) @ global_axis.reshape(1, -1) # projection onto plane w/o global_axis contribution @@ -176,9 +176,10 @@ def local_screw_angles(global_axis, ref_axis, helix_directions): # angles from projection to perp refs = np.array([perp, ortho]) # (2, 3) - norms = _, ortho_norm = np.outer(mdamath.pnorm(refs), - mdamath.pnorm(proj_plane)) - cos = cos_perp, cos_ortho = np.matmul(refs, proj_plane.T)/norms + norms = _, ortho_norm = np.outer( + mdamath.pnorm(refs), mdamath.pnorm(proj_plane) + ) + cos = cos_perp, cos_ortho = np.matmul(refs, proj_plane.T) / norms to_perp, to_ortho = np.arccos(np.clip(cos, -1, 1)) # (2, n_vec) to_ortho[ortho_norm == 0] = 0 # ? to_ortho[cos_perp < 0] *= -1 @@ -251,11 +252,11 @@ def helix_analysis(positions, ref_axis=(0, 0, 1)): adjacent_mag = bimags[:-1] * bimags[1:] # (n_res-3,) # find angle between bisectors for twist and n_residue/turn - cos_theta = mdamath.pdot(bisectors[:-1], bisectors[1:])/adjacent_mag + cos_theta = mdamath.pdot(bisectors[:-1], bisectors[1:]) / adjacent_mag cos_theta = np.clip(cos_theta, -1, 1) twists = np.arccos(cos_theta) # (n_res-3,) local_twists = np.rad2deg(twists) - local_nres_per_turn = 2*np.pi / twists + local_nres_per_turn = 2 * np.pi / twists # find normal to bisectors for local axes cross_bi = np.cross(bisectors[:-1], bisectors[1:]) # (n_res-3, 3) @@ -266,42 +267,46 @@ def helix_analysis(positions, ref_axis=(0, 0, 1)): # find angles between axes for bends bend_theta = np.matmul(local_axes, local_axes.T) # (n_res-3, n_res-3) # set angles to 0 between zero-vectors - bend_theta = np.where(zero_vectors+zero_vectors.T, # (n_res-3, n_res-3) - bend_theta, 1) + bend_theta = np.where( + zero_vectors + zero_vectors.T, bend_theta, 1 # (n_res-3, n_res-3) + ) bend_matrix = np.rad2deg(np.arccos(np.clip(bend_theta, -1, 1))) # local bends are between axes 3 windows apart local_bends = np.diagonal(bend_matrix, offset=3) # (n_res-6,) # radius of local cylinder - radii = (adjacent_mag**0.5) / (2*(1.0-cos_theta)) # (n_res-3,) + radii = (adjacent_mag**0.5) / (2 * (1.0 - cos_theta)) # (n_res-3,) # special case: angle b/w bisectors is 0 (should virtually never happen) # guesstimate radius = half bisector magnitude - radii = np.where(cos_theta != 1, radii, (adjacent_mag**0.5)/2) + radii = np.where(cos_theta != 1, radii, (adjacent_mag**0.5) / 2) # height of local cylinder heights = np.abs(mdamath.pdot(vectors[1:-1], local_axes)) # (n_res-3,) - local_helix_directions = (bisectors.T/bimags).T # (n_res-2, 3) + local_helix_directions = (bisectors.T / bimags).T # (n_res-2, 3) # get origins by subtracting radius from atom i+1 origins = positions[1:-1].copy() # (n_res-2, 3) - origins[:-1] -= (radii*local_helix_directions[:-1].T).T + origins[:-1] -= (radii * local_helix_directions[:-1].T).T # subtract radius from atom i+2 in last one - origins[-1] -= radii[-1]*local_helix_directions[-1] + origins[-1] -= radii[-1] * local_helix_directions[-1] helix_axes = vector_of_best_fit(origins) - screw = local_screw_angles(helix_axes, np.asarray(ref_axis), - local_helix_directions) - - results = {'local_twists': local_twists, - 'local_nres_per_turn': local_nres_per_turn, - 'local_axes': local_axes, - 'local_bends': local_bends, - 'local_heights': heights, - 'local_helix_directions': local_helix_directions, - 'local_origins': origins, - 'all_bends': bend_matrix, - 'global_axis': helix_axes, - 'local_screw_angles': screw} + screw = local_screw_angles( + helix_axes, np.asarray(ref_axis), local_helix_directions + ) + + results = { + "local_twists": local_twists, + "local_nres_per_turn": local_nres_per_turn, + "local_axes": local_axes, + "local_bends": local_bends, + "local_heights": heights, + "local_helix_directions": local_helix_directions, + "local_origins": origins, + "all_bends": bend_matrix, + "global_axis": helix_axes, + "local_screw_angles": screw, + } return results @@ -377,14 +382,14 @@ class HELANAL(AnalysisBase): # shapes of properties from each frame, relative to n_residues attr_shapes = { - 'local_twists': (-3,), - 'local_bends': (-6,), - 'local_heights': (-3,), - 'local_nres_per_turn': (-3,), - 'local_origins': (-2, 3), - 'local_axes': (-3, 3), - 'local_helix_directions': (-2, 3), - 'local_screw_angles': (-2,), + "local_twists": (-3,), + "local_bends": (-6,), + "local_heights": (-3,), + "local_nres_per_turn": (-3,), + "local_origins": (-2, 3), + "local_axes": (-3, 3), + "local_helix_directions": (-2, 3), + "local_screw_angles": (-2,), } def __init__( @@ -407,9 +412,9 @@ def __init__( groups = util.group_same_or_consecutive_integers(ag.resindices) counter = 0 if len(groups) > 1: - msg = 'Your selection {} has gaps in the residues.'.format(s) + msg = "Your selection {} has gaps in the residues.".format(s) if split_residue_sequences: - msg += ' Splitting into {} helices.'.format(len(groups)) + msg += " Splitting into {} helices.".format(len(groups)) else: groups = [ag.resindices] warnings.warn(msg) @@ -418,22 +423,26 @@ def __init__( ng = len(g) counter += ng if ng < 9: - warnings.warn('Fewer than 9 atoms found for helix in ' - 'selection {} with these resindices: {}. ' - 'This sequence will be skipped. HELANAL ' - 'is designed to work on at sequences of ' - '≥9 residues.'.format(s, g)) + warnings.warn( + "Fewer than 9 atoms found for helix in " + "selection {} with these resindices: {}. " + "This sequence will be skipped. HELANAL " + "is designed to work on at sequences of " + "≥9 residues.".format(s, g) + ) continue ids, counts = np.unique(g, return_counts=True) if np.any(counts > 1): - dup = ', '.join(map(str, ids[counts > 1])) - warnings.warn('Your selection {} includes multiple atoms ' - 'for residues with these resindices: {}.' - 'HELANAL is designed to work on one alpha-' - 'carbon per residue.'.format(s, dup)) + dup = ", ".join(map(str, ids[counts > 1])) + warnings.warn( + "Your selection {} includes multiple atoms " + "for residues with these resindices: {}." + "HELANAL is designed to work on one alpha-" + "carbon per residue.".format(s, dup) + ) - consecutive.append(ag[counter-ng:counter]) + consecutive.append(ag[counter - ng : counter]) self.atomgroups = consecutive self.ref_axis = np.asarray(ref_axis) @@ -442,19 +451,25 @@ def __init__( def _zeros_per_frame(self, dims, n_positions=0): """Create zero arrays where first 2 dims are n_frames, n_values""" first = dims[0] + n_positions - npdims = (self.n_frames, first,) + dims[1:] # py27 workaround + npdims = ( + self.n_frames, + first, + ) + dims[ + 1: + ] # py27 workaround return np.zeros(npdims, dtype=np.float64) def _prepare(self): n_res = [len(ag) for ag in self.atomgroups] for key, dims in self.attr_shapes.items(): - empty = [self._zeros_per_frame( - dims, n_positions=n) for n in n_res] + empty = [self._zeros_per_frame(dims, n_positions=n) for n in n_res] self.results[key] = empty self.results.global_axis = [self._zeros_per_frame((3,)) for n in n_res] - self.results.all_bends = [self._zeros_per_frame((n-3, n-3)) for n in n_res] + self.results.all_bends = [ + self._zeros_per_frame((n - 3, n - 3)) for n in n_res + ] def _single_frame(self): _f = self._frame_index @@ -469,12 +484,13 @@ def _conclude(self): self.results.global_tilts = tilts = [] norm_ref = (self.ref_axis**2).sum() ** 0.5 for axes in self.results.global_axis: - cos = np.matmul(self.ref_axis, axes.T) / \ - (mdamath.pnorm(axes)*norm_ref) + cos = np.matmul(self.ref_axis, axes.T) / ( + mdamath.pnorm(axes) * norm_ref + ) cos = np.clip(cos, -1.0, 1.0) tilts.append(np.rad2deg(np.arccos(cos))) - global_attrs = ['global_axis', 'global_tilts', 'all_bends'] + global_attrs = ["global_axis", "global_tilts", "all_bends"] attrnames = list(self.attr_shapes.keys()) + global_attrs # summarise self.results.summary = [] @@ -483,15 +499,17 @@ def _conclude(self): for name in attrnames: attr = self.results[name] mean = attr[i].mean(axis=0) - dev = np.abs(attr[i]-mean) - stats[name] = {'mean': mean, - 'sample_sd': attr[i].std(axis=0, ddof=1), - 'abs_dev': dev.mean(axis=0)} + dev = np.abs(attr[i] - mean) + stats[name] = { + "mean": mean, + "sample_sd": attr[i].std(axis=0, ddof=1), + "abs_dev": dev.mean(axis=0), + } self.results.summary.append(stats) # flatten? if len(self.atomgroups) == 1 and self._flatten: - for name in attrnames + ['summary']: + for name in attrnames + ["summary"]: attr = self.results[name] self.results[name] = attr[0] @@ -506,7 +524,7 @@ def universe_from_origins(self): try: origins = self.results.local_origins except AttributeError: - raise ValueError('Call run() before universe_from_origins') + raise ValueError("Call run() before universe_from_origins") if not isinstance(origins, list): origins = [origins] @@ -514,9 +532,12 @@ def universe_from_origins(self): universe = [] for xyz in origins: n_res = xyz.shape[1] - u = mda.Universe.empty(n_res, n_residues=n_res, - atom_resindex=np.arange(n_res), - trajectory=True).load_new(xyz) + u = mda.Universe.empty( + n_res, + n_residues=n_res, + atom_resindex=np.arange(n_res), + trajectory=True, + ).load_new(xyz) universe.append(u) if not isinstance(self.results.local_origins, list): universe = universe[0] diff --git a/package/MDAnalysis/analysis/hole2/__init__.py b/package/MDAnalysis/analysis/hole2/__init__.py index d09359f0917..a958b16adf8 100644 --- a/package/MDAnalysis/analysis/hole2/__init__.py +++ b/package/MDAnalysis/analysis/hole2/__init__.py @@ -40,8 +40,10 @@ from mdahole2.analysis import utils, templates from mdahole2.analysis.utils import create_vmd_surface -wmsg = ("Deprecated in version 2.8.0\n" - "MDAnalysis.analysis.hole2 is deprecated in favour of the " - "MDAKit madahole2 (https://www.mdanalysis.org/mdahole2/) " - "and will be removed in MDAnalysis version 3.0.0") +wmsg = ( + "Deprecated in version 2.8.0\n" + "MDAnalysis.analysis.hole2 is deprecated in favour of the " + "MDAKit madahole2 (https://www.mdanalysis.org/mdahole2/) " + "and will be removed in MDAnalysis version 3.0.0" +) warnings.warn(wmsg, category=DeprecationWarning) diff --git a/package/MDAnalysis/analysis/hydrogenbonds/__init__.py b/package/MDAnalysis/analysis/hydrogenbonds/__init__.py index 7bb75ea625f..7297f5b4141 100644 --- a/package/MDAnalysis/analysis/hydrogenbonds/__init__.py +++ b/package/MDAnalysis/analysis/hydrogenbonds/__init__.py @@ -22,9 +22,10 @@ # __all__ = [ - 'HydrogenBondAnalysis', - 'WaterBridgeAnalysis', - 'HydrogenBondAutoCorrel', 'find_hydrogen_donors' + "HydrogenBondAnalysis", + "WaterBridgeAnalysis", + "HydrogenBondAutoCorrel", + "find_hydrogen_donors", ] from .hbond_analysis import HydrogenBondAnalysis diff --git a/package/MDAnalysis/analysis/hydrogenbonds/hbond_autocorrel.py b/package/MDAnalysis/analysis/hydrogenbonds/hbond_autocorrel.py index 51fb1bd19aa..749fe3533c1 100644 --- a/package/MDAnalysis/analysis/hydrogenbonds/hbond_autocorrel.py +++ b/package/MDAnalysis/analysis/hydrogenbonds/hbond_autocorrel.py @@ -214,14 +214,16 @@ from MDAnalysis.core.groups import requires from MDAnalysis.due import due, Doi -due.cite(Doi("10.1063/1.4922445"), - description="Hydrogen bonding autocorrelation time", - path='MDAnalysis.analysis.hydrogenbonds.hbond_autocorrel', + +due.cite( + Doi("10.1063/1.4922445"), + description="Hydrogen bonding autocorrelation time", + path="MDAnalysis.analysis.hydrogenbonds.hbond_autocorrel", ) del Doi -@requires('bonds') +@requires("bonds") def find_hydrogen_donors(hydrogens): """Returns the donor atom for each hydrogen @@ -287,18 +289,24 @@ class HydrogenBondAutoCorrel(object): :attr:`HydrogenBondAutoCorrel.solution['results']` instead. """ - def __init__(self, universe, - hydrogens=None, acceptors=None, donors=None, - bond_type=None, - exclusions=None, - angle_crit=130.0, dist_crit=3.0, # geometric criteria - sample_time=100, # expected length of the decay in ps - time_cut=None, # cutoff time for intermittent hbonds - nruns=1, # number of times to iterate through the trajectory - nsamples=50, # number of different points to sample in a run - pbc=True): - - #warnings.warn("This class is deprecated, use analysis.hbonds.HydrogenBondAnalysis " + def __init__( + self, + universe, + hydrogens=None, + acceptors=None, + donors=None, + bond_type=None, + exclusions=None, + angle_crit=130.0, + dist_crit=3.0, # geometric criteria + sample_time=100, # expected length of the decay in ps + time_cut=None, # cutoff time for intermittent hbonds + nruns=1, # number of times to iterate through the trajectory + nsamples=50, # number of different points to sample in a run + pbc=True, + ): + + # warnings.warn("This class is deprecated, use analysis.hbonds.HydrogenBondAnalysis " # "which has .autocorrelation function", # category=DeprecationWarning) @@ -313,23 +321,27 @@ def __init__(self, universe, self.a = acceptors self.d = donors if not len(self.h) == len(self.d): - raise ValueError("Donors and Hydrogen groups must be identical " - "length. Try using `find_hydrogen_donors`.") + raise ValueError( + "Donors and Hydrogen groups must be identical " + "length. Try using `find_hydrogen_donors`." + ) if exclusions is not None: if len(exclusions[0]) != len(exclusions[1]): raise ValueError( - "'exclusion' must be two arrays of identical length") - self.exclusions = np.column_stack(( - exclusions[0], exclusions[1] - )).astype(np.intp) + "'exclusion' must be two arrays of identical length" + ) + self.exclusions = np.column_stack( + (exclusions[0], exclusions[1]) + ).astype(np.intp) else: self.exclusions = None self.bond_type = bond_type - if self.bond_type not in ['continuous', 'intermittent']: + if self.bond_type not in ["continuous", "intermittent"]: raise ValueError( - "bond_type must be either 'continuous' or 'intermittent'") + "bond_type must be either 'continuous' or 'intermittent'" + ) self.a_crit = np.deg2rad(angle_crit) self.d_crit = dist_crit @@ -341,11 +353,11 @@ def __init__(self, universe, self.time_cut = time_cut self.solution = { - 'results': None, # Raw results - 'time': None, # Time axis of raw results - 'fit': None, # coefficients for fit - 'tau': None, # integral of exponential fit - 'estimate': None # y values of fit against time + "results": None, # Raw results + "time": None, # Time axis of raw results + "fit": None, # coefficients for fit + "tau": None, # integral of exponential fit + "estimate": None, # y values of fit against time } def _slice_traj(self, sample_time): @@ -357,16 +369,22 @@ def _slice_traj(self, sample_time): n_frames = len(self.u.trajectory) if req_frames > n_frames: - warnings.warn("Number of required frames ({}) greater than the" - " number of frames in trajectory ({})" - .format(req_frames, n_frames), RuntimeWarning) + warnings.warn( + "Number of required frames ({}) greater than the" + " number of frames in trajectory ({})".format( + req_frames, n_frames + ), + RuntimeWarning, + ) numruns = self.nruns if numruns > n_frames: numruns = n_frames - warnings.warn("Number of runs ({}) greater than the number of" - " frames in trajectory ({})" - .format(self.nruns, n_frames), RuntimeWarning) + warnings.warn( + "Number of runs ({}) greater than the number of" + " frames in trajectory ({})".format(self.nruns, n_frames), + RuntimeWarning, + ) self._starts = np.arange(0, n_frames, n_frames / numruns, dtype=int) # limit stop points using clip @@ -374,8 +392,12 @@ def _slice_traj(self, sample_time): self._skip = req_frames // self.nsamples if self._skip == 0: # If nsamples > req_frames - warnings.warn("Desired number of sample points too high, using {0}" - .format(req_frames), RuntimeWarning) + warnings.warn( + "Desired number of sample points too high, using {0}".format( + req_frames + ), + RuntimeWarning, + ) self._skip = 1 def run(self, force=False): @@ -387,19 +409,21 @@ def run(self, force=False): Will overwrite previous results if they exist """ # if results exist, don't waste any time - if self.solution['results'] is not None and not force: + if self.solution["results"] is not None and not force: return - main_results = np.zeros_like(np.arange(self._starts[0], - self._stops[0], - self._skip), - dtype=np.float32) + main_results = np.zeros_like( + np.arange(self._starts[0], self._stops[0], self._skip), + dtype=np.float32, + ) # for normalising later counter = np.zeros_like(main_results, dtype=np.float32) - for i, (start, stop) in ProgressBar(enumerate(zip(self._starts, - self._stops)), total=self.nruns, - desc="Performing run"): + for i, (start, stop) in ProgressBar( + enumerate(zip(self._starts, self._stops)), + total=self.nruns, + desc="Performing run", + ): # needed else trj seek thinks a np.int64 isn't an int? results = self._single_run(int(start), int(stop)) @@ -414,10 +438,12 @@ def run(self, force=False): main_results /= counter - self.solution['time'] = np.arange( - len(main_results), - dtype=np.float32) * self.u.trajectory.dt * self._skip - self.solution['results'] = main_results + self.solution["time"] = ( + np.arange(len(main_results), dtype=np.float32) + * self.u.trajectory.dt + * self._skip + ) + self.solution["results"] = main_results def _single_run(self, start, stop): """Perform a single pass of the trajectory""" @@ -427,49 +453,62 @@ def _single_run(self, start, stop): box = self.u.dimensions if self.pbc else None # 2d array of all distances - pair = capped_distance(self.h.positions, self.a.positions, - max_cutoff=self.d_crit, box=box, - return_distances=False) + pair = capped_distance( + self.h.positions, + self.a.positions, + max_cutoff=self.d_crit, + box=box, + return_distances=False, + ) if self.exclusions is not None: - pair = pair[~ _in2d(pair, self.exclusions)] + pair = pair[~_in2d(pair, self.exclusions)] hidx, aidx = np.transpose(pair) - - a = calc_angles(self.d.positions[hidx], self.h.positions[hidx], - self.a.positions[aidx], box=box) + a = calc_angles( + self.d.positions[hidx], + self.h.positions[hidx], + self.a.positions[aidx], + box=box, + ) # from amongst those, who also satisfiess angle crit idx2 = np.where(a > self.a_crit) hidx = hidx[idx2] aidx = aidx[idx2] nbonds = len(hidx) # number of hbonds at t=0 - results = np.zeros_like(np.arange(start, stop, self._skip), - dtype=np.float32) + results = np.zeros_like( + np.arange(start, stop, self._skip), dtype=np.float32 + ) if self.time_cut: # counter for time criteria count = np.zeros(nbonds, dtype=np.float64) - for i, ts in enumerate(self.u.trajectory[start:stop:self._skip]): + for i, ts in enumerate(self.u.trajectory[start : stop : self._skip]): box = self.u.dimensions if self.pbc else None - d = calc_bonds(self.h.positions[hidx], self.a.positions[aidx], - box=box) - a = calc_angles(self.d.positions[hidx], self.h.positions[hidx], - self.a.positions[aidx], box=box) + d = calc_bonds( + self.h.positions[hidx], self.a.positions[aidx], box=box + ) + a = calc_angles( + self.d.positions[hidx], + self.h.positions[hidx], + self.a.positions[aidx], + box=box, + ) winners = (d < self.d_crit) & (a > self.a_crit) results[i] = winners.sum() - if self.bond_type == 'continuous': + if self.bond_type == "continuous": # Remove losers for continuous definition hidx = hidx[np.where(winners)] aidx = aidx[np.where(winners)] - elif self.bond_type == 'intermittent': + elif self.bond_type == "intermittent": if self.time_cut: # Add to counter of where losers are - count[~ winners] += self._skip * self.u.trajectory.dt + count[~winners] += self._skip * self.u.trajectory.dt count[winners] = 0 # Reset timer for winners # Remove if you've lost too many times @@ -521,14 +560,15 @@ def solve(self, p_guess=None): """ - if self.solution['results'] is None: + if self.solution["results"] is None: raise ValueError( - "Results have not been generated use, the run method first") + "Results have not been generated use, the run method first" + ) # Prevents an odd bug with leastsq where it expects # double precision data sometimes... - time = self.solution['time'].astype(np.float64) - results = self.solution['results'].astype(np.float64) + time = self.solution["time"].astype(np.float64) + results = self.solution["results"].astype(np.float64) def within_bounds(p): """Returns True/False if boundary conditions are met or not. @@ -542,13 +582,19 @@ def within_bounds(p): """ if len(p) == 3: A1, tau1, tau2 = p - return (A1 > 0.0) & (A1 < 1.0) & \ - (tau1 > 0.0) & (tau2 > 0.0) + return (A1 > 0.0) & (A1 < 1.0) & (tau1 > 0.0) & (tau2 > 0.0) elif len(p) == 5: A1, A2, tau1, tau2, tau3 = p - return (A1 > 0.0) & (A1 < 1.0) & (A2 > 0.0) & \ - (A2 < 1.0) & ((A1 + A2) < 1.0) & \ - (tau1 > 0.0) & (tau2 > 0.0) & (tau3 > 0.0) + return ( + (A1 > 0.0) + & (A1 < 1.0) + & (A2 > 0.0) + & (A2 < 1.0) + & ((A1 + A2) < 1.0) + & (tau1 > 0.0) + & (tau2 > 0.0) + & (tau3 > 0.0) + ) def err(p, x, y): """Custom residual function, returns real residual if all @@ -561,52 +607,66 @@ def err(p, x, y): return np.full_like(y, 100000) def double(x, A1, tau1, tau2): - """ Sum of two exponential functions """ + """Sum of two exponential functions""" A2 = 1 - A1 return A1 * np.exp(-x / tau1) + A2 * np.exp(-x / tau2) def triple(x, A1, A2, tau1, tau2, tau3): - """ Sum of three exponential functions """ + """Sum of three exponential functions""" A3 = 1 - (A1 + A2) - return A1 * np.exp(-x / tau1) + A2 * np.exp(-x / tau2) + A3 * np.exp(-x / tau3) + return ( + A1 * np.exp(-x / tau1) + + A2 * np.exp(-x / tau2) + + A3 * np.exp(-x / tau3) + ) - if self.bond_type == 'continuous': + if self.bond_type == "continuous": self._my_solve = double if p_guess is None: p_guess = (0.5, 10 * self.sample_time, self.sample_time) p, cov, infodict, mesg, ier = scipy.optimize.leastsq( - err, p_guess, args=(time, results), full_output=True) - self.solution['fit'] = p + err, p_guess, args=(time, results), full_output=True + ) + self.solution["fit"] = p A1, tau1, tau2 = p A2 = 1 - A1 - self.solution['tau'] = A1 * tau1 + A2 * tau2 + self.solution["tau"] = A1 * tau1 + A2 * tau2 else: self._my_solve = triple if p_guess is None: - p_guess = (0.33, 0.33, 10 * self.sample_time, - self.sample_time, 0.1 * self.sample_time) + p_guess = ( + 0.33, + 0.33, + 10 * self.sample_time, + self.sample_time, + 0.1 * self.sample_time, + ) p, cov, infodict, mesg, ier = scipy.optimize.leastsq( - err, p_guess, args=(time, results), full_output=True) - self.solution['fit'] = p + err, p_guess, args=(time, results), full_output=True + ) + self.solution["fit"] = p A1, A2, tau1, tau2, tau3 = p A3 = 1 - A1 - A2 - self.solution['tau'] = A1 * tau1 + A2 * tau2 + A3 * tau3 + self.solution["tau"] = A1 * tau1 + A2 * tau2 + A3 * tau3 - self.solution['infodict'] = infodict - self.solution['mesg'] = mesg - self.solution['ier'] = ier + self.solution["infodict"] = infodict + self.solution["mesg"] = mesg + self.solution["ier"] = ier if ier in [1, 2, 3, 4]: # solution found if ier is one of these values - self.solution['estimate'] = self._my_solve( - self.solution['time'], *p) + self.solution["estimate"] = self._my_solve( + self.solution["time"], *p + ) else: warnings.warn("Solution to results not found", RuntimeWarning) def __repr__(self): - return ("" - "".format(btype=self.bond_type, n=len(self.h))) + return ( + "" + "".format(btype=self.bond_type, n=len(self.h)) + ) diff --git a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py index 69f281b4f75..027c0b71255 100644 --- a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py @@ -706,17 +706,19 @@ def analysis(current, output, u, **kwargs): Will be removed in MDAnalysis 3.0.0. Please use :attr:`results.timeseries` instead. """ -from collections import defaultdict import logging import warnings +from collections import defaultdict + import numpy as np -from ..base import AnalysisBase +from MDAnalysis import MissingDataWarning, NoDataError, SelectionError +from MDAnalysis.lib.distances import calc_angles, capped_distance from MDAnalysis.lib.NeighborSearch import AtomNeighborSearch -from MDAnalysis.lib.distances import capped_distance, calc_angles -from MDAnalysis import NoDataError, MissingDataWarning, SelectionError -logger = logging.getLogger('MDAnalysis.analysis.WaterBridgeAnalysis') +from ..base import AnalysisBase + +logger = logging.getLogger("MDAnalysis.analysis.WaterBridgeAnalysis") class WaterBridgeAnalysis(AnalysisBase): @@ -730,6 +732,7 @@ class WaterBridgeAnalysis(AnalysisBase): .. 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. @@ -738,39 +741,91 @@ class WaterBridgeAnalysis(AnalysisBase): #: (see :ref:`Default atom names for water bridge analysis`); #: use the keyword `donors` to add a list of additional donor names. DEFAULT_DONORS = { - 'CHARMM27': tuple( - {'N', 'OH2', 'OW', 'NE', 'NH1', 'NH2', 'ND2', 'SG', 'NE2', 'ND1', - 'NZ', 'OG', 'OG1', 'NE1', 'OH'}), - 'GLYCAM06': tuple({'N', 'NT', 'N3', 'OH', 'OW'}), - 'other': tuple(set([]))} + "CHARMM27": tuple( + { + "N", + "OH2", + "OW", + "NE", + "NH1", + "NH2", + "ND2", + "SG", + "NE2", + "ND1", + "NZ", + "OG", + "OG1", + "NE1", + "OH", + } + ), + "GLYCAM06": tuple({"N", "NT", "N3", "OH", "OW"}), + "other": tuple(set([])), + } #: default atom names that are treated as hydrogen *acceptors* #: (see :ref:`Default atom names for water bridge analysis`); #: use the keyword `acceptors` to add a list of additional acceptor names. DEFAULT_ACCEPTORS = { - 'CHARMM27': tuple( - {'O', 'OC1', 'OC2', 'OH2', 'OW', 'OD1', 'OD2', 'SG', 'OE1', 'OE1', - 'OE2', 'ND1', 'NE2', 'SD', 'OG', 'OG1', 'OH'}), - 'GLYCAM06': - tuple({'N', 'NT', 'O', 'O2', 'OH', 'OS', 'OW', 'OY', 'SM'}), - 'other': tuple(set([]))} + "CHARMM27": tuple( + { + "O", + "OC1", + "OC2", + "OH2", + "OW", + "OD1", + "OD2", + "SG", + "OE1", + "OE1", + "OE2", + "ND1", + "NE2", + "SD", + "OG", + "OG1", + "OH", + } + ), + "GLYCAM06": tuple( + {"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) # noqa: E741 - - def __init__(self, universe, selection1='protein', - 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, - output_format="sele1_sele2", debug=None, - pbc=False, **kwargs): + r_cov = defaultdict( + lambda: 1.5, N=1.31, O=1.31, P=1.58, S=1.55 # default value + ) # noqa: E741 + + def __init__( + self, + universe, + selection1="protein", + 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, + output_format="sele1_sele2", + debug=None, + pbc=False, + **kwargs, + ): """Set up the calculation of water bridges between two selections in a universe. @@ -904,8 +959,9 @@ def __init__(self, universe, selection1='protein', """ - super(WaterBridgeAnalysis, self).__init__(universe.trajectory, - **kwargs) + super(WaterBridgeAnalysis, self).__init__( + universe.trajectory, **kwargs + ) self.water_selection = water_selection self.update_water_selection = update_water_selection # per-frame debugging output? @@ -929,7 +985,9 @@ def __init__(self, universe, selection1='protein', self.filter_first = filter_first self.distance = distance if distance_type not in {"hydrogen", "heavy"}: - raise ValueError(f"Only 'hydrogen' and 'heavy' are allowed for option `distance_type' ({distance_type}).") + raise ValueError( + f"Only 'hydrogen' and 'heavy' are allowed for option `distance_type' ({distance_type})." + ) self.distance_type = distance_type # will give the default behavior self.angle = angle @@ -943,13 +1001,15 @@ def __init__(self, universe, selection1='protein', acceptors = () self.forcefield = forcefield self.donors = tuple(set(self.DEFAULT_DONORS[forcefield]).union(donors)) - self.acceptors = tuple(set(self.DEFAULT_ACCEPTORS[forcefield]).union( - acceptors)) + self.acceptors = tuple( + set(self.DEFAULT_ACCEPTORS[forcefield]).union(acceptors) + ) - if self.selection1_type not in ('both', 'donor', 'acceptor'): - raise ValueError('WaterBridgeAnalysis: ' - 'Invalid selection type {0!s}'.format( - self.selection1_type)) + if self.selection1_type not in ("both", "donor", "acceptor"): + raise ValueError( + "WaterBridgeAnalysis: " + "Invalid selection type {0!s}".format(self.selection1_type) + ) # final result accessed as self.results.network self.results.network = [] @@ -960,18 +1020,31 @@ def __init__(self, universe, selection1='protein', def _log_parameters(self): """Log important parameters to the logfile.""" - logger.info("WaterBridgeAnalysis: selection = %r (update: %r)", - self.selection2, self.update_selection) - logger.info("WaterBridgeAnalysis: water selection = %r (update: %r)", - self.water_selection, self.update_water_selection) - logger.info("WaterBridgeAnalysis: criterion: donor %s atom and " - "acceptor atom distance <= %.3f A", self.distance_type, - self.distance) - logger.info("WaterBridgeAnalysis: criterion: " - "angle D-H-A >= %.3f degrees", - self.angle) - logger.info("WaterBridgeAnalysis: force field %s to guess donor and \ - acceptor names", self.forcefield) + logger.info( + "WaterBridgeAnalysis: selection = %r (update: %r)", + self.selection2, + self.update_selection, + ) + logger.info( + "WaterBridgeAnalysis: water selection = %r (update: %r)", + self.water_selection, + self.update_water_selection, + ) + logger.info( + "WaterBridgeAnalysis: criterion: donor %s atom and " + "acceptor atom distance <= %.3f A", + self.distance_type, + self.distance, + ) + logger.info( + "WaterBridgeAnalysis: criterion: " "angle D-H-A >= %.3f degrees", + self.angle, + ) + logger.info( + "WaterBridgeAnalysis: force field %s to guess donor and \ + acceptor names", + self.forcefield, + ) def _build_residue_dict(self, selection): # Build the residue_dict where the key is the residue name @@ -985,15 +1058,17 @@ def _build_residue_dict(self, selection): for atom in residue.atoms: if atom.name in self.donors: self._residue_dict[residue.resname][atom.name].update( - self._get_bonded_hydrogens(atom).names) + self._get_bonded_hydrogens(atom).names + ) def _update_donor_h(self, atom_ix, h_donors, donors_h): atom = self.u.atoms[atom_ix] residue = atom.residue hydrogen_names = self._residue_dict[residue.resname][atom.name] if hydrogen_names: - hydrogens = residue.atoms.select_atoms('name {0}'.format( - ' '.join(hydrogen_names))).ix + hydrogens = residue.atoms.select_atoms( + "name {0}".format(" ".join(hydrogen_names)) + ).ix for atom in hydrogens: h_donors[atom] = atom_ix donors_h[atom_ix].append(atom) @@ -1013,68 +1088,108 @@ def _update_selection(self): self._s2 = self.u.select_atoms(self.selection2).ix if self.filter_first and len(self._s1): - self.logger_debug('Size of selection 1 before filtering:' - ' {} atoms'.format(len(self._s1))) - ns_selection_1 = AtomNeighborSearch(self.u.atoms[self._s1], - box=self.box) - self._s1 = ns_selection_1.search(self.u.atoms[self._s2], - self.selection_distance).ix - self.logger_debug("Size of selection 1: {0} atoms".format( - len(self._s1))) + self.logger_debug( + "Size of selection 1 before filtering:" + " {} atoms".format(len(self._s1)) + ) + ns_selection_1 = AtomNeighborSearch( + self.u.atoms[self._s1], box=self.box + ) + self._s1 = ns_selection_1.search( + self.u.atoms[self._s2], self.selection_distance + ).ix + self.logger_debug( + "Size of selection 1: {0} atoms".format(len(self._s1)) + ) if len(self._s1) == 0: - logger.warning('Selection 1 "{0}" did not select any atoms.' - .format(str(self.selection1)[:80])) + logger.warning( + 'Selection 1 "{0}" did not select any atoms.'.format( + str(self.selection1)[:80] + ) + ) return if self.filter_first and len(self._s2): - self.logger_debug('Size of selection 2 before filtering:' - ' {} atoms'.format(len(self._s2))) - ns_selection_2 = AtomNeighborSearch(self.u.atoms[self._s2], - box=self.box) - self._s2 = ns_selection_2.search(self.u.atoms[self._s1], - self.selection_distance).ix - self.logger_debug('Size of selection 2: {0} atoms'.format( - len(self._s2))) + self.logger_debug( + "Size of selection 2 before filtering:" + " {} atoms".format(len(self._s2)) + ) + ns_selection_2 = AtomNeighborSearch( + self.u.atoms[self._s2], box=self.box + ) + self._s2 = ns_selection_2.search( + self.u.atoms[self._s1], self.selection_distance + ).ix + self.logger_debug( + "Size of selection 2: {0} atoms".format(len(self._s2)) + ) if len(self._s2) == 0: - logger.warning('Selection 2 "{0}" did not select any atoms.' - .format(str(self.selection2)[:80])) + 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.u.atoms[self._s1].select_atoms( - 'name {0}'.format(' '.join(self.donors))).ix + if self.selection1_type in ("donor", "both"): + self._s1_donors = ( + self.u.atoms[self._s1] + .select_atoms("name {0}".format(" ".join(self.donors))) + .ix + ) for atom_ix in self._s1_donors: - self._update_donor_h(atom_ix, self._s1_h_donors, - self._s1_donors_h) - self.logger_debug("Selection 1 donors: {0}".format( - len(self._s1_donors))) - self.logger_debug("Selection 1 donor hydrogens: {0}".format( - len(self._s1_h_donors))) - if self.selection1_type in ('acceptor', 'both'): - self._s1_acceptors = self.u.atoms[self._s1].select_atoms( - 'name {0}'.format(' '.join(self.acceptors))).ix - self.logger_debug("Selection 1 acceptors: {0}".format( - len(self._s1_acceptors))) + self._update_donor_h( + atom_ix, self._s1_h_donors, self._s1_donors_h + ) + self.logger_debug( + "Selection 1 donors: {0}".format(len(self._s1_donors)) + ) + self.logger_debug( + "Selection 1 donor hydrogens: {0}".format( + len(self._s1_h_donors) + ) + ) + if self.selection1_type in ("acceptor", "both"): + self._s1_acceptors = ( + self.u.atoms[self._s1] + .select_atoms("name {0}".format(" ".join(self.acceptors))) + .ix + ) + self.logger_debug( + "Selection 1 acceptors: {0}".format(len(self._s1_acceptors)) + ) if len(self._s2) == 0: return None - if self.selection1_type in ('donor', 'both'): - self._s2_acceptors = self.u.atoms[self._s2].select_atoms( - 'name {0}'.format(' '.join(self.acceptors))).ix - self.logger_debug("Selection 2 acceptors: {0:d}".format( - len(self._s2_acceptors))) - if self.selection1_type in ('acceptor', 'both'): - self._s2_donors = self.u.atoms[self._s2].select_atoms( - 'name {0}'.format(' '.join(self.donors))).ix + if self.selection1_type in ("donor", "both"): + self._s2_acceptors = ( + self.u.atoms[self._s2] + .select_atoms("name {0}".format(" ".join(self.acceptors))) + .ix + ) + self.logger_debug( + "Selection 2 acceptors: {0:d}".format(len(self._s2_acceptors)) + ) + if self.selection1_type in ("acceptor", "both"): + self._s2_donors = ( + self.u.atoms[self._s2] + .select_atoms("name {0}".format(" ".join(self.donors))) + .ix + ) for atom_ix in self._s2_donors: - self._update_donor_h(atom_ix, self._s2_h_donors, - self._s2_donors_h) - 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_h_donors))) + self._update_donor_h( + atom_ix, self._s2_h_donors, self._s2_donors_h + ) + 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_h_donors) + ) + ) def _update_water_selection(self): self._water_donors = [] @@ -1083,37 +1198,55 @@ def _update_water_selection(self): self._water_acceptors = [] self._water = self.u.select_atoms(self.water_selection).ix - self.logger_debug('Size of water selection before filtering:' - ' {} atoms'.format(len(self._water))) + self.logger_debug( + "Size of water selection before filtering:" + " {} atoms".format(len(self._water)) + ) if len(self._water) and self.filter_first: - filtered_s1 = AtomNeighborSearch(self.u.atoms[self._water], - box=self.box).search( - self.u.atoms[self._s1], self.selection_distance) + filtered_s1 = AtomNeighborSearch( + self.u.atoms[self._water], box=self.box + ).search(self.u.atoms[self._s1], self.selection_distance) if filtered_s1: - self._water = AtomNeighborSearch(filtered_s1, - box=self.box).search( - self.u.atoms[self._s2], self.selection_distance).ix - - self.logger_debug("Size of water selection: {0} atoms".format( - len(self._water))) + self._water = ( + AtomNeighborSearch(filtered_s1, box=self.box) + .search(self.u.atoms[self._s2], self.selection_distance) + .ix + ) + + self.logger_debug( + "Size of water selection: {0} atoms".format(len(self._water)) + ) if len(self._water) == 0: - logger.warning("Water selection '{0}' did not select any atoms." - .format(str(self.water_selection)[:80])) + logger.warning( + "Water selection '{0}' did not select any atoms.".format( + str(self.water_selection)[:80] + ) + ) else: - self._water_donors = self.u.atoms[self._water].select_atoms( - 'name {0}'.format(' '.join(self.donors))).ix + self._water_donors = ( + self.u.atoms[self._water] + .select_atoms("name {0}".format(" ".join(self.donors))) + .ix + ) for atom_ix in self._water_donors: - self._update_donor_h(atom_ix, self._water_h_donors, - self._water_donors_h) - self.logger_debug("Water donors: {0}".format( - len(self._water_donors))) - self.logger_debug("Water donor hydrogens: {0}".format( - len(self._water_h_donors))) - self._water_acceptors = self.u.atoms[self._water].select_atoms( - 'name {0}'.format(' '.join(self.acceptors))).ix - self.logger_debug("Water acceptors: {0}".format( - len(self._water_acceptors))) + self._update_donor_h( + atom_ix, self._water_h_donors, self._water_donors_h + ) + self.logger_debug( + "Water donors: {0}".format(len(self._water_donors)) + ) + self.logger_debug( + "Water donor hydrogens: {0}".format(len(self._water_h_donors)) + ) + self._water_acceptors = ( + self.u.atoms[self._water] + .select_atoms("name {0}".format(" ".join(self.acceptors))) + .ix + ) + self.logger_debug( + "Water acceptors: {0}".format(len(self._water_acceptors)) + ) def _get_bonded_hydrogens(self, atom): """Find hydrogens bonded within cutoff to `atom`. @@ -1145,7 +1278,8 @@ def _get_bonded_hydrogens(self, atom): 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)) + "".format(self.r_cov[atom.name[0]], atom.name) + ) except NoDataError: return [] @@ -1157,7 +1291,7 @@ 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 = (2 * 2 + self.order * (2 + self.distance)) + self.selection_distance = 2 * 2 + self.order * (2 + self.distance) self.box = self.u.dimensions if self.pbc else None self._residue_dict = {} @@ -1171,42 +1305,51 @@ def _prepare(self): if len(self._s1) and len(self._s2): self._update_water_selection() else: - logger.info("WaterBridgeAnalysis: " - "no atoms found in the selection.") + logger.info( + "WaterBridgeAnalysis: " "no atoms found in the selection." + ) logger.info("WaterBridgeAnalysis: initial checks passed.") 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) + logger.debug( + "WaterBridgeAnalysis: water bridge %r", self.water_selection + ) if self.debug: logger.debug("Toggling debug to %r", self.debug) else: - logger.debug("WaterBridgeAnalysis: 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) + logger.info( + "Starting analysis " "(frame index start=%d stop=%d, step=%d)", + self.start, + self.stop, + self.step, + ) def _donor2acceptor(self, donors, h_donors, acceptor): if len(donors) == 0 or len(acceptor) == 0: return [] - if self.distance_type != 'heavy': + if self.distance_type != "heavy": donors_idx = list(h_donors.keys()) else: donors_idx = list(donors.keys()) result = [] # Code modified from p-j-smith - pairs, distances = capped_distance(self.u.atoms[donors_idx].positions, - self.u.atoms[acceptor].positions, - max_cutoff=self.distance, - box=self.box, - return_distances=True) - if self.distance_type == 'hydrogen': + pairs, distances = capped_distance( + self.u.atoms[donors_idx].positions, + self.u.atoms[acceptor].positions, + max_cutoff=self.distance, + box=self.box, + return_distances=True, + ) + if self.distance_type == "hydrogen": tmp_distances = distances tmp_donors = [h_donors[donors_idx[idx]] for idx in pairs[:, 0]] tmp_hydrogens = [donors_idx[idx] for idx in pairs[:, 0]] @@ -1231,7 +1374,7 @@ def _donor2acceptor(self, donors, h_donors, acceptor): self.u.atoms[tmp_donors].positions, self.u.atoms[tmp_hydrogens].positions, self.u.atoms[tmp_acceptors].positions, - box=self.box + box=self.box, ) ) hbond_indices = np.where(angles > self.angle)[0] @@ -1239,9 +1382,17 @@ def _donor2acceptor(self, donors, h_donors, acceptor): h = tmp_hydrogens[index] d = tmp_donors[index] a = tmp_acceptors[index] - result.append((h, d, a, self._expand_index(h), - self._expand_index(a), - tmp_distances[index], angles[index])) + result.append( + ( + h, + d, + a, + self._expand_index(h), + self._expand_index(a), + tmp_distances[index], + angles[index], + ) + ) return result def _single_frame(self): @@ -1262,87 +1413,151 @@ def _single_frame(self): next_round_water = set([]) selection_2 = [] - if self.selection1_type in ('donor', 'both'): + if self.selection1_type in ("donor", "both"): # check for direct hbond from s1 to s2 self.logger_debug("Selection 1 Donors <-> Selection 2 Acceptors") results = self._donor2acceptor( - self._s1_donors_h, self._s1_h_donors, self._s2_acceptors) + self._s1_donors_h, self._s1_h_donors, self._s2_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 + ( + 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( - (h_index, d_index, a_index, None, dist, angle)) + (h_index, d_index, a_index, None, dist, angle) + ) selection_2.append((a_resname, a_resid)) if self.order > 0: self.logger_debug("Selection 1 Donors <-> Water Acceptors") results = self._donor2acceptor( - self._s1_donors_h, self._s1_h_donors, - self._water_acceptors) + self._s1_donors_h, self._s1_h_donors, self._water_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 + ( + h_index, + d_index, + a_index, + (h_resname, h_resid, h_name), + (a_resname, a_resid, a_name), + dist, + angle, + ) = line selection_1.append( - (h_index, d_index, a_index, None, dist, angle)) + (h_index, d_index, a_index, None, dist, angle) + ) self.logger_debug("Water Donors <-> Selection 2 Acceptors") results = self._donor2acceptor( - self._water_donors_h, self._water_h_donors, - self._s2_acceptors) + self._water_donors_h, + self._water_h_donors, + self._s2_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 + ( + 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)) + (h_index, d_index, a_index, None, dist, angle) + ) selection_2.append((a_resname, a_resid)) - if self.selection1_type in ('acceptor', 'both'): + if self.selection1_type in ("acceptor", "both"): self.logger_debug("Selection 2 Donors <-> Selection 1 Acceptors") - results = self._donor2acceptor(self._s2_donors_h, - self._s2_h_donors, - self._s1_acceptors) + results = self._donor2acceptor( + self._s2_donors_h, self._s2_h_donors, 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 + ( + 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( - (a_index, None, h_index, d_index, dist, angle)) + (a_index, None, h_index, d_index, dist, angle) + ) selection_2.append((h_resname, h_resid)) if self.order > 0: self.logger_debug("Selection 2 Donors <-> Water Acceptors") results = self._donor2acceptor( - self._s2_donors_h, self._s2_h_donors, - self._water_acceptors) + self._s2_donors_h, self._s2_h_donors, self._water_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 + ( + 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)) + (a_index, None, h_index, d_index, dist, angle) + ) selection_2.append((h_resname, h_resid)) self.logger_debug("Selection 1 Acceptors <-> Water Donors") results = self._donor2acceptor( - self._water_donors_h, self._water_h_donors, - self._s1_acceptors) + self._water_donors_h, + self._water_h_donors, + 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 + ( + h_index, + d_index, + a_index, + (h_resname, h_resid, h_name), + (a_resname, a_resid, a_name), + dist, + angle, + ) = line selection_1.append( - (a_index, None, h_index, d_index, dist, angle)) + (a_index, None, h_index, d_index, dist, angle) + ) if self.order > 1: self.logger_debug("Water donor <-> Water Acceptors") - results = self._donor2acceptor(self._water_donors_h, - self._water_h_donors, - self._water_acceptors) + results = self._donor2acceptor( + self._water_donors_h, + self._water_h_donors, + self._water_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 + ( + 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)) + (a_index, None, h_index, d_index, dist, angle) + ) water_pool[(h_resname, h_resid)].append( - (h_index, d_index, a_index, None, dist, angle)) + (h_index, d_index, a_index, None, dist, angle) + ) # solve the connectivity network # The following code attempt to generate a water network which is @@ -1362,24 +1577,25 @@ def _single_frame(self): # If the value of a certain key is None, which means it is reaching # selection 2. - result = {'start': defaultdict(dict), 'water': defaultdict(dict)} + result = {"start": defaultdict(dict), "water": defaultdict(dict)} def add_route(result, route): if len(route) == 1: - result['start'][route[0]] = None + result["start"][route[0]] = None else: # exclude the the selection which goes back to itself - if (sorted(route[0][0:3:2]) == sorted(route[-1][0:3:2])): + if sorted(route[0][0:3:2]) == sorted(route[-1][0:3:2]): return # selection 2 to water - result['water'][route[-1]] = None + 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]] + 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]] + 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: @@ -1395,20 +1611,33 @@ def traverse_water_network(graph, node, end, route, maxdepth, result): new_route = route[:] new_route.append(new_node) 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 = self._expand_timeseries(s1, 'sele1_sele2')[3][:2] - traverse_water_network(water_pool, next_mol, selection_2, route[:], - self.order, result) + new_node, "sele1_sele2" + )[3][:2] + traverse_water_network( + graph, new_node, end, new_route, maxdepth, result + ) - self.results.network.append(result['start']) + for s1 in selection_1: + route = [ + s1, + ] + next_mol = self._expand_timeseries(s1, "sele1_sele2")[3][:2] + traverse_water_network( + water_pool, next_mol, selection_2, route[:], self.order, result + ) - def _traverse_water_network(self, graph, current, analysis_func=None, - output=None, link_func=None, **kwargs): - ''' + self.results.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.results.network and finds the hydrogen bonds which connect the current atom to the next atom. The newly found hydrogen bond will be @@ -1426,7 +1655,7 @@ def _traverse_water_network(self, graph, current, analysis_func=None, :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 @@ -1443,19 +1672,24 @@ def _traverse_water_network(self, graph, current, analysis_func=None, 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) + self._traverse_water_network( + graph[node], + new, + analysis_func, + output, + link_func, + **kwargs, + ) 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 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 @@ -1479,17 +1713,17 @@ def _expand_timeseries(self, entry, output_format=None): [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 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': + 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 @@ -1503,13 +1737,20 @@ def _expand_timeseries(self, entry, output_format=None): else: raise KeyError( "Only 'sele1_sele2' or 'donor_acceptor' are allowed as output " - "format") + "format" + ) - return (atom1, atom2, self._expand_index(atom1), - self._expand_index(atom2), dist, angle) + 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. + r"""Time series of water bridges. The output is generated per frame as is explained in :ref:`wb_Analysis_Timeseries`. The format of output can be changed via @@ -1529,7 +1770,7 @@ def _generate_timeseries(self, output_format=None): (resname string, resid, name_string). - ''' + """ output_format = output_format or self.output_format def analysis(current, output, *args, **kwargs): @@ -1538,40 +1779,47 @@ def analysis(current, output, *args, **kwargs): timeseries = [] for frame in self.results.network: 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]) + 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 - def set_network(self, network): - wmsg = ("The `set_network` method was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.network` instead") + wmsg = ( + "The `set_network` method was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.network` instead" + ) warnings.warn(wmsg, DeprecationWarning) self.results.network = network @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 @classmethod 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. @@ -1579,24 +1827,34 @@ def _compact_link(self, output, node): :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, to_residue, dist, angle = \ + s1_index, to_index, s1, to_residue, dist, angle = ( self._expand_timeseries(current[0]) + ) s1_resname, s1_resid, s1_name = s1 - from_index, s2_index, from_residue, s2, dist, angle = \ + from_index, s2_index, from_residue, s2, dist, angle = ( self._expand_timeseries(current[-1]) + ) s2_resname, s2_resid, s2_name = s2 - key = (s1_index, s2_index, - s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) + 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): @@ -1625,41 +1883,57 @@ def count_by_type(self, analysis_func=None, **kwargs): output = None if analysis_func is None: analysis_func = self._count_by_type_analysis - output = 'combined' + output = "combined" if self.results.network: length = len(self.results.network) result_dict = defaultdict(int) for frame in self.results.network: frame_dict = defaultdict(int) - self._traverse_water_network(frame, [], - analysis_func=analysis_func, - output=frame_dict, - link_func=self._full_link, - **kwargs) + 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 == 'combined': + if output == "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)] + [ + 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] + result = [ + (key, result_dict[key] / length) for key in result_dict + ] return result else: return None def _count_by_time_analysis(self, current, output, *args, **kwargs): - s1_index, to_index, s1, to_residue, dist, angle = \ + s1_index, to_index, s1, to_residue, dist, angle = ( self._expand_timeseries(current[0]) + ) s1_resname, s1_resid, s1_name = s1 - from_index, s2_index, from_residue, s2, dist, angle = \ + from_index, s2_index, from_residue, s2, dist, angle = ( self._expand_timeseries(current[-1]) + ) s2_resname, s2_resid, s2_name = s2 - key = (s1_index, s2_index, - s1_resname, s1_resid, s1_name, s2_resname, s2_resid, s2_name) + 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): @@ -1681,27 +1955,41 @@ def count_by_time(self, analysis_func=None, **kwargs): result = [] for time, frame in zip(self.timesteps, self.results.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((time, - sum([result_dict[key] for key in 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 else: return None def _timesteps_by_type_analysis(self, current, output, *args, **kwargs): - s1_index, to_index, s1, to_residue, dist, angle = \ + s1_index, to_index, s1, to_residue, dist, angle = ( self._expand_timeseries(current[0]) + ) s1_resname, s1_resid, s1_name = s1 - from_index, s2_index, from_residue, s2, dist, angle = \ + from_index, s2_index, from_residue, s2, dist, angle = ( self._expand_timeseries(current[-1]) + ) s2_resname, s2_resid, s2_name = s2 - key = (s1_index, s2_index, s1_resname, s1_resid, s1_name, s2_resname, - s2_resid, s2_name) - output[key].append(kwargs.pop('time')) + 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 @@ -1724,7 +2012,7 @@ def timesteps_by_type(self, analysis_func=None, **kwargs): output = None if analysis_func is None: analysis_func = self._timesteps_by_type_analysis - output = 'combined' + output = "combined" if self.results.network: result = defaultdict(list) @@ -1733,16 +2021,20 @@ def timesteps_by_type(self, analysis_func=None, **kwargs): else: timesteps = self.timesteps for time, frame in zip(timesteps, self.results.network): - self._traverse_water_network(frame, [], - analysis_func=analysis_func, - output=result, - link_func=self._full_link, - time=time, **kwargs) + 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 result.items(): for time in time_list: - if output == 'combined': + if output == "combined": key = list(key) key.append(time) result_list.append(key) @@ -1783,8 +2075,10 @@ def generate_table(self, output_format=None): logger.warning(msg) return None - if self.results.timeseries is not None \ - and output_format == self.output_format: + if ( + self.results.timeseries is not None + and output_format == self.output_format + ): timeseries = self.results.timeseries else: # Recompute timeseries with correct output format @@ -1792,24 +2086,34 @@ def generate_table(self, output_format=None): num_records = np.sum([len(hframe) for hframe in timeseries]) # build empty output table - if output_format == 'sele1_sele2': + if output_format == "sele1_sele2": dtype = [ ("time", float), - ("sele1_index", int), ("sele2_index", int), - ("sele1_resnm", "|U4"), ("sele1_resid", int), + ("sele1_index", int), + ("sele2_index", int), + ("sele1_resnm", "|U4"), + ("sele1_resid", int), ("sele1_atom", "|U4"), - ("sele2_resnm", "|U4"), ("sele2_resid", int), + ("sele2_resnm", "|U4"), + ("sele2_resid", int), ("sele2_atom", "|U4"), - ("distance", float), ("angle", float)] - elif output_format == 'donor_acceptor': + ("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_index", int), + ("acceptor_index", int), + ("donor_resnm", "|U4"), + ("donor_resid", int), ("donor_atom", "|U4"), - ("acceptor_resnm", "|U4"), ("acceptor_resid", int), + ("acceptor_resnm", "|U4"), + ("acceptor_resid", int), ("acceptor_atom", "|U4"), - ("distance", float), ("angle", float)] + ("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 @@ -1817,18 +2121,30 @@ def generate_table(self, output_format=None): 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: + 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) + out[cursor] = ( + (t, donor_index, acceptor_index) + + donor + + acceptor + + (distance, angle) + ) cursor += 1 - assert cursor == num_records, \ - "Internal Error: Not all wb records stored" + assert ( + cursor == num_records + ), "Internal Error: Not all wb records stored" table = out.view(np.rec.recarray) logger.debug( "WBridge: Stored results as table with %(num_records)d entries.", - vars()) + vars(), + ) self.table = table return table @@ -1838,16 +2154,20 @@ def _conclude(self): @property def network(self): - wmsg = ("The `network` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.network` instead") + wmsg = ( + "The `network` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.network` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.network @property def timeseries(self): - wmsg = ("The `timeseries` attribute was deprecated in MDAnalysis " - "2.0.0 and will be removed in MDAnalysis 3.0.0. Please use " - "`results.timeseries` instead") + wmsg = ( + "The `timeseries` attribute was deprecated in MDAnalysis " + "2.0.0 and will be removed in MDAnalysis 3.0.0. Please use " + "`results.timeseries` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.timeseries diff --git a/package/MDAnalysis/analysis/leaflet.py b/package/MDAnalysis/analysis/leaflet.py index 5ea0d362a90..9ba5de87e7f 100644 --- a/package/MDAnalysis/analysis/leaflet.py +++ b/package/MDAnalysis/analysis/leaflet.py @@ -87,10 +87,12 @@ HAS_NX = True -due.cite(Doi("10.1002/jcc.21787"), - description="LeafletFinder algorithm", - path="MDAnalysis.analysis.leaflet", - cite_module=True) +due.cite( + Doi("10.1002/jcc.21787"), + description="LeafletFinder algorithm", + path="MDAnalysis.analysis.leaflet", + cite_module=True, +) del Doi @@ -157,9 +159,11 @@ class LeafletFinder(object): def __init__(self, universe, select, cutoff=15.0, pbc=False, sparse=None): # Raise an error if networkx is not installed if not HAS_NX: - errmsg = ("The LeafletFinder class requires an installation of " - "networkx. Please install networkx " - "https://networkx.org/documentation/stable/install.html") + errmsg = ( + "The LeafletFinder class requires an installation of " + "networkx. Please install networkx " + "https://networkx.org/documentation/stable/install.html" + ) raise ImportError(errmsg) self.universe = universe @@ -194,31 +198,47 @@ def _get_graph(self): if self.sparse is False: # only try distance array try: - adj = distances.contact_matrix(coord, cutoff=self.cutoff, returntype="numpy", box=box) - except ValueError: # pragma: no cover - warnings.warn('N x N matrix too big, use sparse=True or sparse=None', category=UserWarning, - stacklevel=2) + adj = distances.contact_matrix( + coord, cutoff=self.cutoff, returntype="numpy", box=box + ) + except ValueError: # pragma: no cover + warnings.warn( + "N x N matrix too big, use sparse=True or sparse=None", + category=UserWarning, + stacklevel=2, + ) raise elif self.sparse is True: # only try sparse - adj = distances.contact_matrix(coord, cutoff=self.cutoff, returntype="sparse", box=box) + adj = distances.contact_matrix( + coord, cutoff=self.cutoff, returntype="sparse", box=box + ) else: # use distance_array and fall back to sparse matrix try: # this works for small-ish systems and depends on system memory - adj = distances.contact_matrix(coord, cutoff=self.cutoff, returntype="numpy", box=box) - except ValueError: # pragma: no cover + adj = distances.contact_matrix( + coord, cutoff=self.cutoff, returntype="numpy", box=box + ) + except ValueError: # pragma: no cover # but use a sparse matrix method for larger systems for memory reasons warnings.warn( - 'N x N matrix too big - switching to sparse matrix method (works fine, but is currently rather ' - 'slow)', - category=UserWarning, stacklevel=2) - adj = distances.contact_matrix(coord, cutoff=self.cutoff, returntype="sparse", box=box) + "N x N matrix too big - switching to sparse matrix method (works fine, but is currently rather " + "slow)", + category=UserWarning, + stacklevel=2, + ) + adj = distances.contact_matrix( + coord, cutoff=self.cutoff, returntype="sparse", box=box + ) return NX.Graph(adj) def _get_components(self): """Return connected components (as sorted numpy arrays), sorted by size.""" - return [np.sort(list(component)) for component in NX.connected_components(self.graph)] + return [ + np.sort(list(component)) + for component in NX.connected_components(self.graph) + ] def update(self, cutoff=None): """Update components, possibly with a different *cutoff*""" @@ -228,7 +248,12 @@ def update(self, cutoff=None): def sizes(self): """Dict of component index with size of component.""" - return dict(((idx, len(component)) for idx, component in enumerate(self.components))) + return dict( + ( + (idx, len(component)) + for idx, component in enumerate(self.components) + ) + ) def groups(self, component_index=None): """Return a :class:`MDAnalysis.core.groups.AtomGroup` for *component_index*. @@ -265,23 +290,37 @@ def write_selection(self, filename, **kwargs): See :class:`MDAnalysis.selections.base.SelectionWriter` for all options. """ - sw = selections.get_writer(filename, kwargs.pop('format', None)) - with sw(filename, mode=kwargs.pop('mode', 'w'), - preamble="leaflets based on select={selectionstring!r} cutoff={cutoff:f}\n".format( - **vars(self)), - **kwargs) as writer: + sw = selections.get_writer(filename, kwargs.pop("format", None)) + with sw( + filename, + mode=kwargs.pop("mode", "w"), + preamble="leaflets based on select={selectionstring!r} cutoff={cutoff:f}\n".format( + **vars(self) + ), + **kwargs, + ) as writer: for i, ag in enumerate(self.groups_iter()): name = "leaflet_{0:d}".format((i + 1)) writer.write(ag, name=name) def __repr__(self): return "".format( - self.selectionstring, self.cutoff, self.selection.n_atoms, - len(self.components)) - - -def optimize_cutoff(universe, select, dmin=10.0, dmax=20.0, step=0.5, - max_imbalance=0.2, **kwargs): + self.selectionstring, + self.cutoff, + self.selection.n_atoms, + len(self.components), + ) + + +def optimize_cutoff( + universe, + select, + dmin=10.0, + dmax=20.0, + step=0.5, + max_imbalance=0.2, + **kwargs, +): r"""Find cutoff that minimizes number of disconnected groups. Applies heuristics to find best groups: @@ -325,7 +364,7 @@ def optimize_cutoff(universe, select, dmin=10.0, dmax=20.0, step=0.5, .. versionchanged:: 1.0.0 Changed `selection` keyword to `select` """ - kwargs.pop('cutoff', None) # not used, so we filter it + kwargs.pop("cutoff", None) # not used, so we filter it _sizes = [] for cutoff in np.arange(dmin, dmax, step): LF = LeafletFinder(universe, select, cutoff=cutoff, **kwargs) diff --git a/package/MDAnalysis/analysis/legacy/x3dna.py b/package/MDAnalysis/analysis/legacy/x3dna.py index 46a2f5a8f60..2365c181133 100644 --- a/package/MDAnalysis/analysis/legacy/x3dna.py +++ b/package/MDAnalysis/analysis/legacy/x3dna.py @@ -117,31 +117,35 @@ .. autoexception:: ApplicationError """ +import errno import glob +import logging import os -import errno -import shutil -import warnings import os.path +import shutil import subprocess import tempfile import textwrap +import warnings from collections import OrderedDict -import numpy as np import matplotlib.pyplot as plt +import numpy as np from MDAnalysis import ApplicationError -from MDAnalysis.lib.util import which, realpath, asiterable, deprecate - -import logging +from MDAnalysis.lib.util import asiterable, deprecate, realpath, which logger = logging.getLogger("MDAnalysis.analysis.x3dna") -@deprecate(release="2.7.0", remove="3.0.0", - message=("X3DNA module is deprecated and will be removed in" - "MDAnalysis 3.0.0, see #3788")) +@deprecate( + release="2.7.0", + remove="3.0.0", + message=( + "X3DNA module is deprecated and will be removed in" + "MDAnalysis 3.0.0, see #3788" + ), +) def mean_std_from_x3dnaPickle(profile): """Get mean and standard deviation of helicoidal parameters from a saved `profile`. @@ -168,10 +172,15 @@ def mean_std_from_x3dnaPickle(profile): .. deprecated:: 2.7.0 X3DNA will be removed in 3.0.0. """ - warnings.warn("X3DNA module is deprecated and will be removed in MDAnalysis 3.0, see #3788", category=DeprecationWarning) + warnings.warn( + "X3DNA module is deprecated and will be removed in MDAnalysis 3.0, see #3788", + category=DeprecationWarning, + ) if profile.x3dna_param is False: + # fmt: off bp_shear, bp_stretch, bp_stagger, bp_rise, bp_shift, bp_slide, bp_buckle, bp_prop, bp_open, bp_tilt, bp_roll,\ bp_twist = [], [], [], [], [], [], [], [], [], [], [], [] + # fmt: on for i in range(len(profile)): bp_shear.append(profile.values()[i].Shear) bp_stretch.append(profile.values()[i].Stretch) @@ -185,44 +194,94 @@ def mean_std_from_x3dnaPickle(profile): bp_tilt.append(profile.values()[i].Tilt) bp_roll.append(profile.values()[i].Roll) bp_twist.append(profile.values()[i].Twist) + # fmt: off bp_shear, bp_stretch, bp_stagger, bp_rise, bp_shift, bp_slide, bp_buckle, bp_prop, bp_open, bp_tilt, bp_roll,\ bp_twist = np.array(bp_shear), np.array(bp_stretch), np.array(bp_stagger), np.array(bp_rise),\ np.array(bp_shift), np.array(bp_slide), np.array(bp_buckle), np.array(bp_prop), \ np.array(bp_open), np.array(bp_tilt), np.array(bp_roll), np.array(bp_twist) + # fmt: on na_avg, na_std = [], [] for j in range(len(bp_shear[0])): - na_avg.append([ - np.mean(bp_shear[:, j]), np.mean(bp_stretch[:, j]), np.mean(bp_stagger[:, j]), - np.mean(bp_buckle[:, j]), np.mean(bp_prop[:, j]), np.mean(bp_open[:, j]), - np.mean(bp_shift[:, j]), np.mean(bp_slide[:, j]), np.mean(bp_rise[:, j]), - np.mean(bp_tilt[:, j]), np.mean(bp_roll[:, j]), np.mean(bp_twist[:, j])]) - na_std.append([ - np.std(bp_shear[:, j]), np.std(bp_stretch[:, j]), np.std(bp_stagger[:, j]), - np.std(bp_buckle[:, j]), np.std(bp_prop[:, j]), np.std(bp_open[:, j]), - np.std(bp_shift[:, j]), np.std(bp_slide[:, j]), np.std(bp_rise[:, j]), - np.std(bp_tilt[:, j]), np.std(bp_roll[:, j]), np.std(bp_twist[:, j])]) + na_avg.append( + [ + np.mean(bp_shear[:, j]), + np.mean(bp_stretch[:, j]), + np.mean(bp_stagger[:, j]), + np.mean(bp_buckle[:, j]), + np.mean(bp_prop[:, j]), + np.mean(bp_open[:, j]), + np.mean(bp_shift[:, j]), + np.mean(bp_slide[:, j]), + np.mean(bp_rise[:, j]), + np.mean(bp_tilt[:, j]), + np.mean(bp_roll[:, j]), + np.mean(bp_twist[:, j]), + ] + ) + na_std.append( + [ + np.std(bp_shear[:, j]), + np.std(bp_stretch[:, j]), + np.std(bp_stagger[:, j]), + np.std(bp_buckle[:, j]), + np.std(bp_prop[:, j]), + np.std(bp_open[:, j]), + np.std(bp_shift[:, j]), + np.std(bp_slide[:, j]), + np.std(bp_rise[:, j]), + np.std(bp_tilt[:, j]), + np.std(bp_roll[:, j]), + np.std(bp_twist[:, j]), + ] + ) else: + # fmt: off bp_rise, bp_shift, bp_slide, bp_tilt, bp_roll, bp_twist = [], [], [], [], [], [], [], [], [], [], [], [] + # fmt: on for i in range(len(profile)): - #print i + # print i bp_rise.append(profile.values()[i].Rise) bp_shift.append(profile.values()[i].Shift) bp_slide.append(profile.values()[i].Slide) bp_tilt.append(profile.values()[i].Tilt) bp_roll.append(profile.values()[i].Roll) bp_twist.append(profile.values()[i].Twist) - bp_rise, bp_shift, bp_slide, bp_tilt, bp_roll, bp_twist = np.array(bp_shear),np.array(bp_stretch),\ - np.array(bp_stagger), np.array(bp_rise), np.array(bp_shift), np.array(bp_slide),\ - np.array(bp_buckle), np.array(bp_prop), np.array(bp_open), np.array(bp_tilt),\ - np.array(bp_roll), np.array(bp_twist) + bp_rise, bp_shift, bp_slide, bp_tilt, bp_roll, bp_twist = ( + np.array(bp_shear), + np.array(bp_stretch), + np.array(bp_stagger), + np.array(bp_rise), + np.array(bp_shift), + np.array(bp_slide), + np.array(bp_buckle), + np.array(bp_prop), + np.array(bp_open), + np.array(bp_tilt), + np.array(bp_roll), + np.array(bp_twist), + ) na_avg, na_std = [], [] for j in range(len(bp_shift[0])): - na_avg.append([ - np.mean(bp_shift[:, j]), np.mean(bp_slide[:, j]), np.mean(bp_rise[:, j]), - np.mean(bp_tilt[:, j]), np.mean(bp_roll[:, j]), np.mean(bp_twist[:, j])]) - na_std.append([ - np.std(bp_shift[:, j]), np.std(bp_slide[:, j]), np.std(bp_rise[:, j]), - np.std(bp_tilt[:, j]), np.std(bp_roll[:, j]), np.std(bp_twist[:, j])]) + na_avg.append( + [ + np.mean(bp_shift[:, j]), + np.mean(bp_slide[:, j]), + np.mean(bp_rise[:, j]), + np.mean(bp_tilt[:, j]), + np.mean(bp_roll[:, j]), + np.mean(bp_twist[:, j]), + ] + ) + na_std.append( + [ + np.std(bp_shift[:, j]), + np.std(bp_slide[:, j]), + np.std(bp_rise[:, j]), + np.std(bp_tilt[:, j]), + np.std(bp_roll[:, j]), + np.std(bp_twist[:, j]), + ] + ) na_avg, na_std = np.array(na_avg), np.array(na_std) return na_avg, na_std @@ -271,7 +330,9 @@ def save(self, filename="x3dna.pickle"): """ import cPickle - cPickle.dump(self.profiles, open(filename, "wb"), cPickle.HIGHEST_PROTOCOL) + cPickle.dump( + self.profiles, open(filename, "wb"), cPickle.HIGHEST_PROTOCOL + ) def mean_std(self): """Returns the mean and standard deviation of base parameters. @@ -285,8 +346,10 @@ def mean_std(self): roll, twist]``. """ + # fmt: off bp_shear, bp_stretch, bp_stagger, bp_rise, bp_shift, bp_slide, bp_buckle, bp_prop, bp_open, bp_tilt, bp_roll,\ bp_twist = [], [], [], [], [], [], [], [], [], [], [], [] + # fmt: on for i in range(len(self.profiles)): bp_shear.append(self.profiles.values()[i].Shear) bp_stretch.append(self.profiles.values()[i].Stretch) @@ -300,22 +363,46 @@ def mean_std(self): bp_tilt.append(self.profiles.values()[i].Tilt) bp_roll.append(self.profiles.values()[i].Roll) bp_twist.append(self.profiles.values()[i].Twist) + # fmt: off bp_shear, bp_stretch, bp_stagger, bp_rise, bp_shift, bp_slide, bp_buckle, bp_prop, bp_open, bp_tilt, bp_roll,\ bp_twist = np.array(bp_shear), np.array(bp_stretch), np.array(bp_stagger), np.array(bp_rise),\ np.array(bp_shift), np.array(bp_slide), np.array(bp_buckle), np.array(bp_prop),\ np.array(bp_open), np.array(bp_tilt), np.array(bp_roll), np.array(bp_twist) + # fmt: on na_avg, na_std = [], [] for j in range(len(bp_shear[0])): - na_avg.append([ - np.mean(bp_shear[:, j]), np.mean(bp_stretch[:, j]), np.mean(bp_stagger[:, j]), - np.mean(bp_buckle[:, j]), np.mean(bp_prop[:, j]), np.mean(bp_open[:, j]), - np.mean(bp_shift[:, j]), np.mean(bp_slide[:, j]), np.mean(bp_rise[:, j]), - np.mean(bp_tilt[:, j]), np.mean(bp_roll[:, j]), np.mean(bp_twist[:, j])]) - na_std.append([ - np.std(bp_shear[:, j]), np.std(bp_stretch[:, j]), np.std(bp_stagger[:, j]), - np.std(bp_buckle[:, j]), np.std(bp_prop[:, j]), np.std(bp_open[:, j]), - np.std(bp_shift[:, j]), np.std(bp_slide[:, j]), np.std(bp_rise[:, j]), - np.std(bp_tilt[:, j]), np.std(bp_roll[:, j]), np.std(bp_twist[:, j])]) + na_avg.append( + [ + np.mean(bp_shear[:, j]), + np.mean(bp_stretch[:, j]), + np.mean(bp_stagger[:, j]), + np.mean(bp_buckle[:, j]), + np.mean(bp_prop[:, j]), + np.mean(bp_open[:, j]), + np.mean(bp_shift[:, j]), + np.mean(bp_slide[:, j]), + np.mean(bp_rise[:, j]), + np.mean(bp_tilt[:, j]), + np.mean(bp_roll[:, j]), + np.mean(bp_twist[:, j]), + ] + ) + na_std.append( + [ + np.std(bp_shear[:, j]), + np.std(bp_stretch[:, j]), + np.std(bp_stagger[:, j]), + np.std(bp_buckle[:, j]), + np.std(bp_prop[:, j]), + np.std(bp_open[:, j]), + np.std(bp_shift[:, j]), + np.std(bp_slide[:, j]), + np.std(bp_rise[:, j]), + np.std(bp_tilt[:, j]), + np.std(bp_roll[:, j]), + np.std(bp_twist[:, j]), + ] + ) na_avg, na_std = np.array(na_avg), np.array(na_std) return na_avg, na_std @@ -330,8 +417,10 @@ def mean(self): shift, slide, rise, tilt, roll, twist]``. """ + # fmt: off bp_shear, bp_stretch, bp_stagger, bp_rise, bp_shift, bp_slide, bp_buckle, bp_prop, bp_open, bp_tilt, bp_roll,\ bp_twist = [], [], [], [], [], [], [], [], [], [], [], [] + # fmt: on for i in range(len(self.profiles)): bp_shear.append(self.profiles.values()[i].Shear) bp_stretch.append(self.profiles.values()[i].Stretch) @@ -345,18 +434,31 @@ def mean(self): bp_tilt.append(self.profiles.values()[i].Tilt) bp_roll.append(self.profiles.values()[i].Roll) bp_twist.append(self.profiles.values()[i].Twist) + # fmt: off bp_shear, bp_stretch, bp_stagger, bp_rise, bp_shift, bp_slide, bp_buckle, bp_prop, bp_open, bp_tilt, bp_roll,\ bp_twist = np.array(bp_shear), np.array(bp_stretch), np.array(bp_stagger), np.array(bp_rise),\ np.array(bp_shift), np.array(bp_slide), np.array(bp_buckle), np.array(bp_prop),\ np.array(bp_open), np.array(bp_tilt), np.array(bp_roll), np.array(bp_twist) + # fmt: on na_avg = [] for j in range(len(bp_shear[0])): - na_avg.append([ - np.mean(bp_shear[:, j]), np.mean(bp_stretch[:, j]), np.mean(bp_stagger[:, j]), - np.mean(bp_buckle[:j]), np.mean(bp_prop[:, j]), np.mean(bp_open[:, j]), - np.mean(bp_shift[:, j]), np.mean(bp_slide[:, j]), np.mean(bp_rise[:, j]), - np.mean(bp_tilt[:, j]), np.mean(bp_roll[:, j]), np.mean(bp_twist[:, j])]) + na_avg.append( + [ + np.mean(bp_shear[:, j]), + np.mean(bp_stretch[:, j]), + np.mean(bp_stagger[:, j]), + np.mean(bp_buckle[:j]), + np.mean(bp_prop[:, j]), + np.mean(bp_open[:, j]), + np.mean(bp_shift[:, j]), + np.mean(bp_slide[:, j]), + np.mean(bp_rise[:, j]), + np.mean(bp_tilt[:, j]), + np.mean(bp_roll[:, j]), + np.mean(bp_twist[:, j]), + ] + ) na_avg = np.array(na_avg) return na_avg @@ -371,8 +473,10 @@ def std(self): propeller, opening, shift, slide, rise, tilt, roll, twist]``. """ + # fmt: off bp_shear, bp_stretch, bp_stagger, bp_rise, bp_shift, bp_slide, bp_buckle, bp_prop, bp_open, bp_tilt, bp_roll,\ bp_twist = [], [], [], [], [], [], [], [], [], [], [], [] + # fmt: on for i in range(len(self.profiles)): bp_shear.append(self.profiles.values()[i].Shear) bp_stretch.append(self.profiles.values()[i].Stretch) @@ -386,18 +490,31 @@ def std(self): bp_tilt.append(self.profiles.values()[i].Tilt) bp_roll.append(self.profiles.values()[i].Roll) bp_twist.append(self.profiles.values()[i].Twist) + # fmt: off bp_shear, bp_stretch, bp_stagger, bp_rise, bp_shift, bp_slide, bp_buckle, bp_prop, bp_open, bp_tilt, bp_roll,\ bp_twist = np.array(bp_shear), np.array(bp_stretch), np.array(bp_stagger), np.array(bp_rise),\ np.array(bp_shift), np.array(bp_slide), np.array(bp_buckle), np.array(bp_prop),\ np.array(bp_open), np.array(bp_tilt), np.array(bp_roll), np.array(bp_twist) + # fmt: on na_std = [] for j in range(len(bp_shear[0])): - na_std.append([ - np.std(bp_shear[:, j]), np.std(bp_stretch[:, j]), np.std(bp_stagger[:, j]), - np.std(bp_buckle[:j]), np.std(bp_prop[:, j]), np.std(bp_open[:, j]), np.std(bp_shift[:, j]), - np.std(bp_slide[:, j]), np.std(bp_rise[:, j]), np.std(bp_tilt[:, j]), np.std(bp_roll[:, j]), - np.std(bp_twist[:, j])]) + na_std.append( + [ + np.std(bp_shear[:, j]), + np.std(bp_stretch[:, j]), + np.std(bp_stagger[:, j]), + np.std(bp_buckle[:j]), + np.std(bp_prop[:, j]), + np.std(bp_open[:, j]), + np.std(bp_shift[:, j]), + np.std(bp_slide[:, j]), + np.std(bp_rise[:, j]), + np.std(bp_tilt[:, j]), + np.std(bp_roll[:, j]), + np.std(bp_twist[:, j]), + ] + ) na_std = np.array(na_std) return na_std @@ -417,13 +534,20 @@ def plot(self, **kwargs): na_avg, na_std = self.mean_std() for k in range(len(na_avg[0])): - ax = kwargs.pop('ax', plt.subplot(111)) + ax = kwargs.pop("ax", plt.subplot(111)) x = list(range(1, len(na_avg[:, k]) + 1)) - ax.errorbar(x, na_avg[:, k], yerr=na_std[:, k], fmt='-o') + ax.errorbar(x, na_avg[:, k], yerr=na_std[:, k], fmt="-o") ax.set_xlim(0, len(na_avg[:, k]) + 1) ax.set_xlabel(r"Nucleic Acid Number") param = self.profiles.values()[0].dtype.names[k] - if param in ["Shear", "Stretch", "Stagger", "Rise", "Shift", "Slide"]: + if param in [ + "Shear", + "Stretch", + "Stagger", + "Rise", + "Shift", + "Slide", + ]: ax.set_ylabel(r"{!s} ($\AA$)".format(param)) else: ax.set_ylabel("{0!s} (deg)".format((param))) @@ -487,9 +611,14 @@ class X3DNA(BaseX3DNA): .. _`X3DNA docs`: http://forum.x3dna.org/ """ - @deprecate(release="2.7.0", remove="3.0.0", - message=("X3DNA module is deprecated and will be removed in" - "MDAnalysis 3.0.0, see #3788")) + @deprecate( + release="2.7.0", + remove="3.0.0", + message=( + "X3DNA module is deprecated and will be removed in" + "MDAnalysis 3.0.0, see #3788" + ), + ) def __init__(self, filename, **kwargs): """Set up parameters to run X3DNA_ on PDB *filename*. @@ -521,8 +650,17 @@ def __init__(self, filename, **kwargs): """ # list of temporary files, to be cleaned up on __del__ self.tempfiles = [ - "auxiliary.par", "bestpairs.pdb", "bp_order.dat", "bp_helical.par", "cf_7methods.par", - "col_chains.scr", "col_helices.scr", "hel_regions.pdb", "ref_frames.dat", "hstacking.pdb", "stacking.pdb" + "auxiliary.par", + "bestpairs.pdb", + "bp_order.dat", + "bp_helical.par", + "cf_7methods.par", + "col_chains.scr", + "col_helices.scr", + "hel_regions.pdb", + "ref_frames.dat", + "hstacking.pdb", + "stacking.pdb", ] self.tempdirs = [] self.filename = filename @@ -531,28 +669,38 @@ def __init__(self, filename, **kwargs): # guess executables self.exe = {} - x3dna_exe_name = kwargs.pop('executable', 'xdna_ensemble') - self.x3dna_param = kwargs.pop('x3dna_param', True) - self.exe['xdna_ensemble'] = which(x3dna_exe_name) - if self.exe['xdna_ensemble'] is None: - errmsg = "X3DNA binary {x3dna_exe_name!r} not found.".format(**vars()) + x3dna_exe_name = kwargs.pop("executable", "xdna_ensemble") + self.x3dna_param = kwargs.pop("x3dna_param", True) + self.exe["xdna_ensemble"] = which(x3dna_exe_name) + if self.exe["xdna_ensemble"] is None: + errmsg = "X3DNA binary {x3dna_exe_name!r} not found.".format( + **vars() + ) logger.fatal(errmsg) - logger.fatal("%(x3dna_exe_name)r must be on the PATH or provided as keyword argument 'executable'.", - vars()) + logger.fatal( + "%(x3dna_exe_name)r must be on the PATH or provided as keyword argument 'executable'.", + vars(), + ) raise OSError(errno.ENOENT, errmsg) - x3dnapath = os.path.dirname(self.exe['xdna_ensemble']) + x3dnapath = os.path.dirname(self.exe["xdna_ensemble"]) self.logfile = kwargs.pop("logfile", "bp_step.par") if self.x3dna_param is False: - self.template = textwrap.dedent("""x3dna_ensemble analyze -b 355d.bps --one %(filename)r """) + self.template = textwrap.dedent( + """x3dna_ensemble analyze -b 355d.bps --one %(filename)r """ + ) else: - self.template = textwrap.dedent("""find_pair -s %(filename)r stdout |analyze stdin """) + self.template = textwrap.dedent( + """find_pair -s %(filename)r stdout |analyze stdin """ + ) # sanity checks for program, path in self.exe.items(): if path is None or which(path) is None: - logger.error("Executable %(program)r not found, should have been %(path)r.", - vars()) + logger.error( + "Executable %(program)r not found, should have been %(path)r.", + vars(), + ) # results self.profiles = OrderedDict() @@ -563,7 +711,7 @@ def run(self, **kwargs): x3dnaargs = vars(self).copy() x3dnaargs.update(kwargs) - x3dna_param = kwargs.pop('x3dna_param', self.x3dna_param) + x3dna_param = kwargs.pop("x3dna_param", self.x3dna_param) inp = self.template % x3dnaargs if inpname: @@ -571,18 +719,24 @@ def run(self, **kwargs): f.write(inp) logger.debug("Wrote X3DNA input file %r for inspection", inpname) - logger.info("Starting X3DNA on %(filename)r (trajectory: %(dcd)r)", x3dnaargs) - logger.debug("%s", self.exe['xdna_ensemble']) + logger.info( + "Starting X3DNA on %(filename)r (trajectory: %(dcd)r)", x3dnaargs + ) + logger.debug("%s", self.exe["xdna_ensemble"]) with open(outname, "w") as output: x3dna = subprocess.call([inp], shell=True) with open(outname, "r") as output: # X3DNA is not very good at setting returncodes so check ourselves for line in output: - if line.strip().startswith(('*** ERROR ***', 'ERROR')): + if line.strip().startswith(("*** ERROR ***", "ERROR")): x3dna.returncode = 255 break if x3dna.bit_length != 0: - logger.fatal("X3DNA Failure (%d). Check output %r", x3dna.bit_length, outname) + logger.fatal( + "X3DNA Failure (%d). Check output %r", + x3dna.bit_length, + outname, + ) logger.info("X3DNA finished: output file %(outname)r", vars()) def collect(self, **kwargs): @@ -607,10 +761,10 @@ def collect(self, **kwargs): """ # Shear Stretch Stagger Buckle Prop-Tw Opening Shift Slide Rise Tilt Roll Twist - #0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789. + # 0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789. # 11 22 33 44 - #T-A -0.033 -0.176 0.158 -12.177 -8.979 1.440 0.000 0.000 0.000 0.000 0.000 0.000 - #C-G -0.529 0.122 -0.002 -7.983 -10.083 -0.091 -0.911 1.375 3.213 -0.766 -4.065 41.492 + # T-A -0.033 -0.176 0.158 -12.177 -8.979 1.440 0.000 0.000 0.000 0.000 0.000 0.000 + # C-G -0.529 0.122 -0.002 -7.983 -10.083 -0.091 -0.911 1.375 3.213 -0.766 -4.065 41.492 # only parse bp_step.par x3dna_output = kwargs.pop("x3dnaout", self.logfile) @@ -619,20 +773,35 @@ def collect(self, **kwargs): logger.info("Collecting X3DNA profiles for run with id %s", run) length = 1 # length of trajectory --- is this really needed?? No... just for info - if '*' in self.filename: + if "*" in self.filename: import glob filenames = glob.glob(self.filename) length = len(filenames) if length == 0: - logger.error("Glob pattern %r did not find any files.", self.filename) - raise ValueError("Glob pattern {0!r} did not find any files.".format(self.filename)) - logger.info("Found %d input files based on glob pattern %s", length, self.filename) + logger.error( + "Glob pattern %r did not find any files.", self.filename + ) + raise ValueError( + "Glob pattern {0!r} did not find any files.".format( + self.filename + ) + ) + logger.info( + "Found %d input files based on glob pattern %s", + length, + self.filename, + ) # one recarray for each frame, indexed by frame number self.profiles = OrderedDict() - logger.info("Run %s: Reading %d X3DNA profiles from %r", run, length, x3dna_output) + logger.info( + "Run %s: Reading %d X3DNA profiles from %r", + run, + length, + x3dna_output, + ) x3dna_profile_no = 0 records = [] with open(x3dna_output, "r") as x3dna: @@ -644,22 +813,55 @@ def collect(self, **kwargs): read_data = True logger.debug("Started reading data") fields = line.split() - x3dna_profile_no = int(1) # useless int value code based off hole plugin + x3dna_profile_no = int( + 1 + ) # useless int value code based off hole plugin records = [] continue if read_data: if len(line.strip()) != 0: try: - Sequence, Shear, Stretch, Stagger, Buckle, Propeller, Opening, Shift, Slide, Rise, \ - Tilt, Roll, Twist = line.split() + ( + Sequence, + Shear, + Stretch, + Stagger, + Buckle, + Propeller, + Opening, + Shift, + Slide, + Rise, + Tilt, + Roll, + Twist, + ) = line.split() except: - logger.critical("Run %d: Problem parsing line %r", run, line.strip()) - logger.exception("Check input file %r.", x3dna_output) + logger.critical( + "Run %d: Problem parsing line %r", + run, + line.strip(), + ) + logger.exception( + "Check input file %r.", x3dna_output + ) raise records.append( - [float(Shear), float(Stretch), float(Stagger), float(Buckle), float(Propeller), - float(Opening), float(Shift), float(Slide), float(Rise), float(Tilt), float(Roll), - float(Twist)]) + [ + float(Shear), + float(Stretch), + float(Stagger), + float(Buckle), + float(Propeller), + float(Opening), + float(Shift), + float(Slide), + float(Rise), + float(Tilt), + float(Roll), + float(Twist), + ] + ) continue else: # end of records (empty line) @@ -669,34 +871,67 @@ def collect(self, **kwargs): read_data = True logger.debug("Started reading data") fields = line.split() - x3dna_profile_no = int(1) # useless int value code based off hole plugin + x3dna_profile_no = int( + 1 + ) # useless int value code based off hole plugin records = [] continue if read_data: if len(line.strip()) != 0: try: - Sequence, Shift, Slide, Rise, Tilt, Roll, Twist = line.split() + ( + Sequence, + Shift, + Slide, + Rise, + Tilt, + Roll, + Twist, + ) = line.split() except: - logger.critical("Run %d: Problem parsing line %r", run, line.strip()) - logger.exception("Check input file %r.", x3dna_output) + logger.critical( + "Run %d: Problem parsing line %r", + run, + line.strip(), + ) + logger.exception( + "Check input file %r.", x3dna_output + ) raise records.append( - [float(Shift), float(Slide), float(Rise), float(Tilt), float(Roll), float(Twist)]) + [ + float(Shift), + float(Slide), + float(Rise), + float(Tilt), + float(Roll), + float(Twist), + ] + ) continue else: # end of records (empty line) read_data = False if self.x3dna_param is False: - frame_x3dna_output = np.rec.fromrecords(records, formats="f8,f8,f8,f8,f8,f8,f8,f8,f8,f8,f8,f8", - names="Shear,Stretch,Stagger,Buckle,Propeller,Opening," - "Shift,Slide,Rise,Tilt,Roll,Twist") + frame_x3dna_output = np.rec.fromrecords( + records, + formats="f8,f8,f8,f8,f8,f8,f8,f8,f8,f8,f8,f8", + names="Shear,Stretch,Stagger,Buckle,Propeller,Opening," + "Shift,Slide,Rise,Tilt,Roll,Twist", + ) else: - frame_x3dna_output = np.rec.fromrecords(records, formats="f8,f8,f8,f8,f8,f8", - names="Shift,Slide,Rise,Tilt,Roll,Twist") + frame_x3dna_output = np.rec.fromrecords( + records, + formats="f8,f8,f8,f8,f8,f8", + names="Shift,Slide,Rise,Tilt,Roll,Twist", + ) # store the profile self.profiles[x3dna_profile_no] = frame_x3dna_output - logger.debug("Collected X3DNA profile for frame %d (%d datapoints)", - x3dna_profile_no, len(frame_x3dna_output)) + logger.debug( + "Collected X3DNA profile for frame %d (%d datapoints)", + x3dna_profile_no, + len(frame_x3dna_output), + ) # save a profile for each frame (for debugging and scripted processing) # a tmp folder for each trajectory if outdir is not None: @@ -704,14 +939,29 @@ def collect(self, **kwargs): os.system("rm -f tmp*.out") if not os.path.exists(rundir): os.makedirs(rundir) - frame_x3dna_txt = os.path.join(rundir, "bp_step_{0!s}_{1:04d}.dat.gz".format(run, x3dna_profile_no)) + frame_x3dna_txt = os.path.join( + rundir, + "bp_step_{0!s}_{1:04d}.dat.gz".format( + run, x3dna_profile_no + ), + ) np.savetxt(frame_x3dna_txt, frame_x3dna_output) - logger.debug("Finished with frame %d, saved as %r", x3dna_profile_no, frame_x3dna_txt) + logger.debug( + "Finished with frame %d, saved as %r", + x3dna_profile_no, + frame_x3dna_txt, + ) # if we get here then we haven't found anything interesting if len(self.profiles) == length: - logger.info("Collected X3DNA profiles for %d frames", len(self.profiles)) + logger.info( + "Collected X3DNA profiles for %d frames", len(self.profiles) + ) else: - logger.warning("Missing data: Found %d X3DNA profiles from %d frames.", len(self.profiles), length) + logger.warning( + "Missing data: Found %d X3DNA profiles from %d frames.", + len(self.profiles), + length, + ) def __del__(self): for f in self.tempfiles: @@ -736,9 +986,15 @@ class X3DNAtraj(BaseX3DNA): .. deprecated:: 2.7.0 X3DNA will be removed in 3.0.0. """ - @deprecate(release="2.7.0", remove="3.0.0", - message=("X3DNA module is deprecated and will be removed in" - "MDAnalysis 3.0.0, see #3788")) + + @deprecate( + release="2.7.0", + remove="3.0.0", + message=( + "X3DNA module is deprecated and will be removed in" + "MDAnalysis 3.0.0, see #3788" + ), + ) def __init__(self, universe, **kwargs): """Set up the class. @@ -776,10 +1032,10 @@ def __init__(self, universe, **kwargs): self.universe = universe self.selection = kwargs.pop("selection", "nucleic") - self.x3dna_param = kwargs.pop('x3dna_param', True) - self.start = kwargs.pop('start', None) - self.stop = kwargs.pop('stop', None) - self.step = kwargs.pop('step', None) + self.x3dna_param = kwargs.pop("x3dna_param", True) + self.start = kwargs.pop("start", None) + self.stop = kwargs.pop("stop", None) + self.step = kwargs.pop("step", None) self.x3dna_kwargs = kwargs @@ -792,10 +1048,10 @@ def run(self, **kwargs): analyse part of the trajectory. The defaults are the values provided to the class constructor. """ - start = kwargs.pop('start', self.start) - stop = kwargs.pop('stop', self.stop) - step = kwargs.pop('step', self.step) - x3dna_param = kwargs.pop('x3dna_param', self.x3dna_param) + start = kwargs.pop("start", self.start) + stop = kwargs.pop("stop", self.stop) + step = kwargs.pop("step", self.step) + x3dna_param = kwargs.pop("x3dna_param", self.x3dna_param) x3dna_kw = self.x3dna_kwargs.copy() x3dna_kw.update(kwargs) @@ -818,7 +1074,8 @@ def run(self, **kwargs): pass if len(x3dna_profiles) != 1: err_msg = "Got {0} profiles ({1}) --- should be 1 (time step {2})".format( - len(x3dna_profiles), x3dna_profiles.keys(), ts) + len(x3dna_profiles), x3dna_profiles.keys(), ts + ) logger.error(err_msg) warnings.warn(err_msg) profiles[ts.frame] = x3dna_profiles.values()[0] @@ -826,7 +1083,7 @@ def run(self, **kwargs): def run_x3dna(self, pdbfile, **kwargs): """Run X3DNA on a single PDB file `pdbfile`.""" - kwargs['x3dna_param'] = self.x3dna_param + kwargs["x3dna_param"] = self.x3dna_param H = X3DNA(pdbfile, **kwargs) H.run() H.collect() diff --git a/package/MDAnalysis/analysis/lineardensity.py b/package/MDAnalysis/analysis/lineardensity.py index 8970d68d8a0..a0377b47b35 100644 --- a/package/MDAnalysis/analysis/lineardensity.py +++ b/package/MDAnalysis/analysis/lineardensity.py @@ -44,21 +44,26 @@ class Results(Results): the docstring for LinearDensity for details. The Results class is defined here to implement deprecation warnings for the user.""" - _deprecation_dict = {"pos": "mass_density", - "pos_std": "mass_density_stddev", - "char": "charge_density", - "char_std": "charge_density_stddev"} + _deprecation_dict = { + "pos": "mass_density", + "pos_std": "mass_density_stddev", + "char": "charge_density", + "char_std": "charge_density_stddev", + } def _deprecation_warning(self, key): warnings.warn( f"`{key}` is deprecated and will be removed in version 3.0.0. " f"Please use `{self._deprecation_dict[key]}` instead.", - DeprecationWarning) + DeprecationWarning, + ) def __getitem__(self, key): if key in self._deprecation_dict.keys(): self._deprecation_warning(key) - return super(Results, self).__getitem__(self._deprecation_dict[key]) + return super(Results, self).__getitem__( + self._deprecation_dict[key] + ) return super(Results, self).__getitem__(key) def __getattr__(self, attr): @@ -193,9 +198,10 @@ class LinearDensity(AnalysisBase): and :attr:`results.x.charge_density_stddev` instead. """ - def __init__(self, select, grouping='atoms', binsize=0.25, **kwargs): - super(LinearDensity, self).__init__(select.universe.trajectory, - **kwargs) + def __init__(self, select, grouping="atoms", binsize=0.25, **kwargs): + super(LinearDensity, self).__init__( + select.universe.trajectory, **kwargs + ) # allows use of run(parallel=True) self._ags = [select] self._universe = select.universe @@ -222,13 +228,17 @@ def __init__(self, select, grouping='atoms', binsize=0.25, **kwargs): self.nbins = bins.max() slices_vol = self.volume / bins - self.keys = ['mass_density', 'mass_density_stddev', - 'charge_density', 'charge_density_stddev'] + self.keys = [ + "mass_density", + "mass_density_stddev", + "charge_density", + "charge_density_stddev", + ] # Initialize results array with zeros for dim in self.results: - idx = self.results[dim]['dim'] - self.results[dim]['slice_volume'] = slices_vol[idx] + idx = self.results[dim]["dim"] + self.results[dim]["slice_volume"] = slices_vol[idx] for key in self.keys: self.results[dim][key] = np.zeros(self.nbins) @@ -249,7 +259,8 @@ def _single_frame(self): else: raise AttributeError( - f"{self.grouping} is not a valid value for grouping.") + f"{self.grouping} is not a valid value for grouping." + ) self.totalmass = np.sum(self.masses) @@ -257,37 +268,41 @@ def _single_frame(self): self._ags[0].wrap(compound=self.grouping) # Find position of atom/group of atoms - if self.grouping == 'atoms': + if self.grouping == "atoms": positions = self._ags[0].positions # faster for atoms else: # Centre of mass for residues, segments, fragments positions = self._ags[0].center_of_mass(compound=self.grouping) - for dim in ['x', 'y', 'z']: - idx = self.results[dim]['dim'] + for dim in ["x", "y", "z"]: + idx = self.results[dim]["dim"] - key = 'mass_density' - key_std = 'mass_density_stddev' + key = "mass_density" + key_std = "mass_density_stddev" # histogram for positions weighted on masses - hist, _ = np.histogram(positions[:, idx], - weights=self.masses, - bins=self.nbins, - range=(0.0, max(self.dimensions))) + hist, _ = np.histogram( + positions[:, idx], + weights=self.masses, + bins=self.nbins, + range=(0.0, max(self.dimensions)), + ) self.results[dim][key] += hist self.results[dim][key_std] += np.square(hist) - key = 'charge_density' - key_std = 'charge_density_stddev' + key = "charge_density" + key_std = "charge_density_stddev" # histogram for positions weighted on charges - hist, bin_edges = np.histogram(positions[:, idx], - weights=self.charges, - bins=self.nbins, - range=(0.0, max(self.dimensions))) + hist, bin_edges = np.histogram( + positions[:, idx], + weights=self.charges, + bins=self.nbins, + range=(0.0, max(self.dimensions)), + ) self.results[dim][key] += hist self.results[dim][key_std] += np.square(hist) - self.results[dim]['hist_bin_edges'] = bin_edges + self.results[dim]["hist_bin_edges"] = bin_edges def _conclude(self): avogadro = constants["N_Avogadro"] # unit: mol^{-1} @@ -296,9 +311,13 @@ def _conclude(self): k = avogadro * volume_conversion # Average results over the number of configurations - for dim in ['x', 'y', 'z']: - for key in ['mass_density', 'mass_density_stddev', - 'charge_density', 'charge_density_stddev']: + for dim in ["x", "y", "z"]: + for key in [ + "mass_density", + "mass_density_stddev", + "charge_density", + "charge_density_stddev", + ]: self.results[dim][key] /= self.n_frames # Compute standard deviation for the error # For certain tests in testsuite, floating point imprecision @@ -306,29 +325,35 @@ def _conclude(self): # radicand_mass and radicand_charge are therefore calculated first # and negative values set to 0 before the square root # is calculated. - radicand_mass = self.results[dim]['mass_density_stddev'] - \ - np.square(self.results[dim]['mass_density']) + radicand_mass = self.results[dim][ + "mass_density_stddev" + ] - np.square(self.results[dim]["mass_density"]) radicand_mass[radicand_mass < 0] = 0 - self.results[dim]['mass_density_stddev'] = np.sqrt(radicand_mass) + self.results[dim]["mass_density_stddev"] = np.sqrt(radicand_mass) - radicand_charge = self.results[dim]['charge_density_stddev'] - \ - np.square(self.results[dim]['charge_density']) + radicand_charge = self.results[dim][ + "charge_density_stddev" + ] - np.square(self.results[dim]["charge_density"]) radicand_charge[radicand_charge < 0] = 0 - self.results[dim]['charge_density_stddev'] = \ - np.sqrt(radicand_charge) + self.results[dim]["charge_density_stddev"] = np.sqrt( + radicand_charge + ) - for dim in ['x', 'y', 'z']: + for dim in ["x", "y", "z"]: # norming factor, units of mol^-1 cm^3 - norm = k * self.results[dim]['slice_volume'] + norm = k * self.results[dim]["slice_volume"] for key in self.keys: self.results[dim][key] /= norm # TODO: Remove in 3.0.0 - @deprecate(release="2.2.0", remove="3.0.0", - message="It will be replaced by a :meth:`_reduce` " - "method in the future") + @deprecate( + release="2.2.0", + remove="3.0.0", + message="It will be replaced by a :meth:`_reduce` " + "method in the future", + ) def _add_other_results(self, other): """For parallel analysis""" - for dim in ['x', 'y', 'z']: + for dim in ["x", "y", "z"]: for key in self.keys: self.results[dim][key] += other.results[dim][key] diff --git a/package/MDAnalysis/analysis/msd.py b/package/MDAnalysis/analysis/msd.py index 4515ed40983..8d0e7a39e1e 100644 --- a/package/MDAnalysis/analysis/msd.py +++ b/package/MDAnalysis/analysis/msd.py @@ -246,16 +246,20 @@ from ..core import groups from tqdm import tqdm -logger = logging.getLogger('MDAnalysis.analysis.msd') - -due.cite(Doi("10.21105/joss.00877"), - description="Mean Squared Displacements with tidynamics", - path="MDAnalysis.analysis.msd", - cite_module=True) -due.cite(Doi("10.1051/sfn/201112010"), - description="FCA fast correlation algorithm", - path="MDAnalysis.analysis.msd", - cite_module=True) +logger = logging.getLogger("MDAnalysis.analysis.msd") + +due.cite( + Doi("10.21105/joss.00877"), + description="Mean Squared Displacements with tidynamics", + path="MDAnalysis.analysis.msd", + cite_module=True, +) +due.cite( + Doi("10.1051/sfn/201112010"), + description="FCA fast correlation algorithm", + path="MDAnalysis.analysis.msd", + cite_module=True, +) del Doi @@ -297,7 +301,7 @@ class EinsteinMSD(AnalysisBase): .. versionadded:: 2.0.0 """ - def __init__(self, u, select='all', msd_type='xyz', fft=True, **kwargs): + def __init__(self, u, select="all", msd_type="xyz", fft=True, **kwargs): r""" Parameters ---------- @@ -314,8 +318,9 @@ def __init__(self, u, select='all', msd_type='xyz', fft=True, **kwargs): The tidynamics package is required for `fft=True`. """ if isinstance(u, groups.UpdatingAtomGroup): - raise TypeError("UpdatingAtomGroups are not valid for MSD " - "computation") + raise TypeError( + "UpdatingAtomGroups are not valid for MSD " "computation" + ) super(EinsteinMSD, self).__init__(u.universe.trajectory, **kwargs) @@ -337,18 +342,25 @@ def __init__(self, u, select='all', msd_type='xyz', fft=True, **kwargs): def _prepare(self): # self.n_frames only available here # these need to be zeroed prior to each run() call - self.results.msds_by_particle = np.zeros((self.n_frames, - self.n_particles)) + self.results.msds_by_particle = np.zeros( + (self.n_frames, self.n_particles) + ) self._position_array = np.zeros( - (self.n_frames, self.n_particles, self.dim_fac)) + (self.n_frames, self.n_particles, self.dim_fac) + ) # self.results.timeseries not set here def _parse_msd_type(self): - r""" Sets up the desired dimensionality of the MSD. - - """ - keys = {'x': [0], 'y': [1], 'z': [2], 'xy': [0, 1], - 'xz': [0, 2], 'yz': [1, 2], 'xyz': [0, 1, 2]} + r"""Sets up the desired dimensionality of the MSD.""" + keys = { + "x": [0], + "y": [1], + "z": [2], + "xy": [0, 1], + "xz": [0, 2], + "yz": [1, 2], + "xyz": [0, 1, 2], + } self.msd_type = self.msd_type.lower() @@ -356,19 +368,19 @@ def _parse_msd_type(self): self._dim = keys[self.msd_type] except KeyError: raise ValueError( - 'invalid msd_type: {} specified, please specify one of xyz, ' - 'xy, xz, yz, x, y, z'.format(self.msd_type)) + "invalid msd_type: {} specified, please specify one of xyz, " + "xy, xz, yz, x, y, z".format(self.msd_type) + ) self.dim_fac = len(self._dim) def _single_frame(self): - r""" Constructs array of positions for MSD calculation. - - """ + r"""Constructs array of positions for MSD calculation.""" # shape of position array set here, use span in last dimension # from this point on - self._position_array[self._frame_index] = ( - self.ag.positions[:, self._dim]) + self._position_array[self._frame_index] = self.ag.positions[ + :, self._dim + ] def _conclude(self): if self.fft: @@ -377,9 +389,7 @@ def _conclude(self): self._conclude_simple() def _conclude_simple(self): - r""" Calculates the MSD via the simple "windowed" algorithm. - - """ + r"""Calculates the MSD via the simple "windowed" algorithm.""" lagtimes = np.arange(1, self.n_frames) positions = self._position_array.astype(np.float64) for lag in tqdm(lagtimes): @@ -389,13 +399,12 @@ def _conclude_simple(self): self.results.timeseries = self.results.msds_by_particle.mean(axis=1) def _conclude_fft(self): # with FFT, np.float64 bit prescision required. - r""" Calculates the MSD via the FCA fast correlation algorithm. - - """ + r"""Calculates the MSD via the FCA fast correlation algorithm.""" try: import tidynamics except ImportError: - raise ImportError("""ERROR --- tidynamics was not found! + raise ImportError( + """ERROR --- tidynamics was not found! tidynamics is required to compute an FFT based MSD (default) @@ -403,10 +412,12 @@ def _conclude_fft(self): # with FFT, np.float64 bit prescision required. pip install tidynamics - or set fft=False""") + or set fft=False""" + ) positions = self._position_array.astype(np.float64) for n in tqdm(range(self.n_particles)): self.results.msds_by_particle[:, n] = tidynamics.msd( - positions[:, n, :]) + positions[:, n, :] + ) self.results.timeseries = self.results.msds_by_particle.mean(axis=1) diff --git a/package/MDAnalysis/analysis/nucleicacids.py b/package/MDAnalysis/analysis/nucleicacids.py index b0f5013e799..bf68a954cb3 100644 --- a/package/MDAnalysis/analysis/nucleicacids.py +++ b/package/MDAnalysis/analysis/nucleicacids.py @@ -170,20 +170,21 @@ class NucPairDist(AnalysisBase): @classmethod def get_supported_backends(cls): - return ('serial', 'multiprocessing', 'dask') + return ("serial", "multiprocessing", "dask") _s1: mda.AtomGroup _s2: mda.AtomGroup _n_sel: int - - def __init__(self, selection1: List[mda.AtomGroup], - selection2: List[mda.AtomGroup], - **kwargs) -> None: - super( - NucPairDist, - self).__init__( - selection1[0].universe.trajectory, - **kwargs) + + def __init__( + self, + selection1: List[mda.AtomGroup], + selection2: List[mda.AtomGroup], + **kwargs, + ) -> None: + super(NucPairDist, self).__init__( + selection1[0].universe.trajectory, **kwargs + ) if len(selection1) != len(selection2): raise ValueError("Selections must be same length") @@ -199,10 +200,15 @@ def __init__(self, selection1: List[mda.AtomGroup], @staticmethod def select_strand_atoms( - strand1: ResidueGroup, strand2: ResidueGroup, - a1_name: str, a2_name: str, g_name: str = 'G', - a_name: str = 'A', u_name: str = 'U', - t_name: str = 'T', c_name: str = 'C' + strand1: ResidueGroup, + strand2: ResidueGroup, + a1_name: str, + a2_name: str, + g_name: str = "G", + a_name: str = "A", + u_name: str = "U", + t_name: str = "T", + c_name: str = "C", ) -> Tuple[List[mda.AtomGroup], List[mda.AtomGroup]]: r""" A helper method for nucleic acid pair distance analyses. @@ -266,17 +272,18 @@ def select_strand_atoms( f"AtomGroup in {pair} is not a valid nucleic acid" ) - ag1 = pair[0].atoms.select_atoms(f'name {a1}') - ag2 = pair[1].atoms.select_atoms(f'name {a2}') + ag1 = pair[0].atoms.select_atoms(f"name {a1}") + ag2 = pair[1].atoms.select_atoms(f"name {a2}") if not all(len(ag) > 0 for ag in [ag1, ag2]): - err_info: Tuple[Residue, str] = (pair[0], a1) \ - if len(ag1) == 0 else (pair[1], a2) + err_info: Tuple[Residue, str] = ( + (pair[0], a1) if len(ag1) == 0 else (pair[1], a2) + ) raise ValueError( ( f"{err_info[0]} returns an empty AtomGroup" - "with selection string \"name {a2}\"" + 'with selection string "name {a2}"' ) ) @@ -291,22 +298,22 @@ def _prepare(self) -> None: ) def _single_frame(self) -> None: - dist: np.ndarray = calc_bonds( - self._s1.positions, self._s2.positions - ) + dist: np.ndarray = calc_bonds(self._s1.positions, self._s2.positions) self.results.distances[self._frame_index, :] = dist def _conclude(self) -> None: - self.results['pair_distances'] = self.results['distances'] + self.results["pair_distances"] = self.results["distances"] # TODO: remove pair_distances in 3.0.0 def _get_aggregator(self): - return ResultsGroup(lookup={ - 'distances': ResultsGroup.ndarray_vstack, - } + return ResultsGroup( + lookup={ + "distances": ResultsGroup.ndarray_vstack, + } ) + class WatsonCrickDist(NucPairDist): r""" Watson-Crick base pair distance for selected @@ -426,11 +433,19 @@ class WatsonCrickDist(NucPairDist): but it is **deprecated** and will be removed in release 3.0.0. """ - def __init__(self, strand1: ResidueClass, strand2: ResidueClass, - n1_name: str = 'N1', n3_name: str = "N3", - g_name: str = 'G', a_name: str = 'A', u_name: str = 'U', - t_name: str = 'T', c_name: str = 'C', - **kwargs) -> None: + def __init__( + self, + strand1: ResidueClass, + strand2: ResidueClass, + n1_name: str = "N1", + n3_name: str = "N3", + g_name: str = "G", + a_name: str = "A", + u_name: str = "U", + t_name: str = "T", + c_name: str = "C", + **kwargs, + ) -> None: def verify_strand(strand: ResidueClass) -> ResidueGroup: # Helper method to verify the strands @@ -456,11 +471,18 @@ def verify_strand(strand: ResidueClass) -> ResidueGroup: strand1: ResidueGroup = verify_strand(strand1) strand2: ResidueGroup = verify_strand(strand2) - strand_atomgroups: Tuple[List[mda.AtomGroup], List[mda.AtomGroup]] = \ + strand_atomgroups: Tuple[List[mda.AtomGroup], List[mda.AtomGroup]] = ( self.select_strand_atoms( - strand1, strand2, n1_name, n3_name, - g_name=g_name, a_name=a_name, - t_name=t_name, u_name=u_name, c_name=c_name + strand1, + strand2, + n1_name, + n3_name, + g_name=g_name, + a_name=a_name, + t_name=t_name, + u_name=u_name, + c_name=c_name, + ) ) super(WatsonCrickDist, self).__init__( @@ -531,17 +553,32 @@ class MinorPairDist(NucPairDist): .. versionadded:: 2.7.0 """ - def __init__(self, strand1: ResidueGroup, strand2: ResidueGroup, - o2_name: str = 'O2', c2_name: str = "C2", - g_name: str = 'G', a_name: str = 'A', u_name: str = 'U', - t_name: str = 'T', c_name: str = 'C', - **kwargs) -> None: - - selections: Tuple[List[mda.AtomGroup], List[mda.AtomGroup]] = \ + def __init__( + self, + strand1: ResidueGroup, + strand2: ResidueGroup, + o2_name: str = "O2", + c2_name: str = "C2", + g_name: str = "G", + a_name: str = "A", + u_name: str = "U", + t_name: str = "T", + c_name: str = "C", + **kwargs, + ) -> None: + + selections: Tuple[List[mda.AtomGroup], List[mda.AtomGroup]] = ( self.select_strand_atoms( - strand1, strand2, c2_name, o2_name, - g_name=g_name, a_name=a_name, - t_name=t_name, u_name=u_name, c_name=c_name + strand1, + strand2, + c2_name, + o2_name, + g_name=g_name, + a_name=a_name, + t_name=t_name, + u_name=u_name, + c_name=c_name, + ) ) super(MinorPairDist, self).__init__( @@ -614,17 +651,32 @@ class MajorPairDist(NucPairDist): .. versionadded:: 2.7.0 """ - def __init__(self, strand1: ResidueGroup, strand2: ResidueGroup, - n4_name: str = 'N4', o6_name: str = "O6", - g_name: str = 'G', a_name: str = 'A', u_name: str = 'U', - t_name: str = 'T', c_name: str = 'C', - **kwargs) -> None: - - selections: Tuple[List[mda.AtomGroup], List[mda.AtomGroup]] = \ + def __init__( + self, + strand1: ResidueGroup, + strand2: ResidueGroup, + n4_name: str = "N4", + o6_name: str = "O6", + g_name: str = "G", + a_name: str = "A", + u_name: str = "U", + t_name: str = "T", + c_name: str = "C", + **kwargs, + ) -> None: + + selections: Tuple[List[mda.AtomGroup], List[mda.AtomGroup]] = ( self.select_strand_atoms( - strand1, strand2, o6_name, n4_name, g_name=g_name, - a_name=a_name, t_name=t_name, u_name=u_name, - c_name=c_name + strand1, + strand2, + o6_name, + n4_name, + g_name=g_name, + a_name=a_name, + t_name=t_name, + u_name=u_name, + c_name=c_name, + ) ) super(MajorPairDist, self).__init__( diff --git a/package/MDAnalysis/analysis/nuclinfo.py b/package/MDAnalysis/analysis/nuclinfo.py index 39be7191d8a..a6772599142 100644 --- a/package/MDAnalysis/analysis/nuclinfo.py +++ b/package/MDAnalysis/analysis/nuclinfo.py @@ -130,13 +130,32 @@ def wc_pair(universe, i, bp, seg1="SYSTEM", seg2="SYSTEM"): .. versionadded:: 0.7.6 """ - if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in ["DC", "DT", "U", "C", "T", "CYT", "THY", "URA"]: + if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in [ + "DC", + "DT", + "U", + "C", + "T", + "CYT", + "THY", + "URA", + ]: a1, a2 = "N3", "N1" - if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in ["DG", "DA", "A", "G", "ADE", "GUA"]: + if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in [ + "DG", + "DA", + "A", + "G", + "ADE", + "GUA", + ]: a1, a2 = "N1", "N3" - wc_dist = universe.select_atoms("(segid {0!s} and resid {1!s} and name {2!s}) " - "or (segid {3!s} and resid {4!s} and name {5!s}) " - .format(seg1, i, a1, seg2, bp, a2)) + wc_dist = universe.select_atoms( + "(segid {0!s} and resid {1!s} and name {2!s}) " + "or (segid {3!s} and resid {4!s} and name {5!s}) ".format( + seg1, i, a1, seg2, bp, a2 + ) + ) wc = mdamath.norm(wc_dist[0].position - wc_dist[1].position) return wc @@ -172,13 +191,32 @@ def minor_pair(universe, i, bp, seg1="SYSTEM", seg2="SYSTEM"): .. versionadded:: 0.7.6 """ - if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in ["DC", "DT", "U", "C", "T", "CYT", "THY", "URA"]: + if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in [ + "DC", + "DT", + "U", + "C", + "T", + "CYT", + "THY", + "URA", + ]: a1, a2 = "O2", "C2" - if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in ["DG", "DA", "A", "G", "ADE", "GUA"]: + if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in [ + "DG", + "DA", + "A", + "G", + "ADE", + "GUA", + ]: a1, a2 = "C2", "O2" - c2o2_dist = universe.select_atoms("(segid {0!s} and resid {1!s} and name {2!s}) " - "or (segid {3!s} and resid {4!s} and name {5!s})" - .format(seg1, i, a1, seg2, bp, a2)) + c2o2_dist = universe.select_atoms( + "(segid {0!s} and resid {1!s} and name {2!s}) " + "or (segid {3!s} and resid {4!s} and name {5!s})".format( + seg1, i, a1, seg2, bp, a2 + ) + ) c2o2 = mdamath.norm(c2o2_dist[0].position - c2o2_dist[1].position) return c2o2 @@ -215,19 +253,48 @@ def major_pair(universe, i, bp, seg1="SYSTEM", seg2="SYSTEM"): .. versionadded:: 0.7.6 """ - if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in ["DC", "DG", "C", "G", "CYT", "GUA"]: - if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in ["DC", "C", "CYT"]: + if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in [ + "DC", + "DG", + "C", + "G", + "CYT", + "GUA", + ]: + if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in [ + "DC", + "C", + "CYT", + ]: a1, a2 = "N4", "O6" else: a1, a2 = "O6", "N4" - if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in ["DT", "DA", "A", "T", "U", "ADE", "THY", "URA"]: - if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in ["DT", "T", "THY", "U", "URA"]: + if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in [ + "DT", + "DA", + "A", + "T", + "U", + "ADE", + "THY", + "URA", + ]: + if universe.select_atoms(" resid {0!s} ".format(i)).resnames[0] in [ + "DT", + "T", + "THY", + "U", + "URA", + ]: a1, a2 = "O4", "N6" else: a1, a2 = "N6", "O4" - no_dist = universe.select_atoms("(segid {0!s} and resid {1!s} and name {2!s}) " - "or (segid {3!s} and resid {4!s} and name {5!s}) " - .format(seg1, i, a1, seg2, bp, a2)) + no_dist = universe.select_atoms( + "(segid {0!s} and resid {1!s} and name {2!s}) " + "or (segid {3!s} and resid {4!s} and name {5!s}) ".format( + seg1, i, a1, seg2, bp, a2 + ) + ) major = mdamath.norm(no_dist[0].position - no_dist[1].position) return major @@ -255,11 +322,11 @@ def phase_cp(universe, seg, i): .. versionadded:: 0.7.6 """ - atom1 = universe.select_atoms(" atom {0!s} {1!s} O4\' ".format(seg, i)) - atom2 = universe.select_atoms(" atom {0!s} {1!s} C1\' ".format(seg, i)) - atom3 = universe.select_atoms(" atom {0!s} {1!s} C2\' ".format(seg, i)) - atom4 = universe.select_atoms(" atom {0!s} {1!s} C3\' ".format(seg, i)) - atom5 = universe.select_atoms(" atom {0!s} {1!s} C4\' ".format(seg, i)) + atom1 = universe.select_atoms(" atom {0!s} {1!s} O4' ".format(seg, i)) + atom2 = universe.select_atoms(" atom {0!s} {1!s} C1' ".format(seg, i)) + atom3 = universe.select_atoms(" atom {0!s} {1!s} C2' ".format(seg, i)) + atom4 = universe.select_atoms(" atom {0!s} {1!s} C3' ".format(seg, i)) + atom5 = universe.select_atoms(" atom {0!s} {1!s} C4' ".format(seg, i)) data1 = atom1.positions data2 = atom2.positions @@ -274,13 +341,21 @@ def phase_cp(universe, seg, i): r4 = data4 - r0 r5 = data5 - r0 - R1 = ((r1 * sin(2 * pi * 0.0 / 5.0)) + (r2 * sin(2 * pi * 1.0 / 5.0)) + - (r3 * sin(2 * pi * 2.0 / 5.0)) + (r4 * sin(2 * pi * 3.0 / 5.0)) + - (r5 * sin(2 * pi * 4.0 / 5.0))) - - R2 = ((r1 * cos(2 * pi * 0.0 / 5.0)) + (r2 * cos(2 * pi * 1.0 / 5.0)) + - (r3 * cos(2 * pi * 2.0 / 5.0)) + (r4 * cos(2 * pi * 3.0 / 5.0)) + - (r5 * cos(2 * pi * 4.0 / 5.0))) + R1 = ( + (r1 * sin(2 * pi * 0.0 / 5.0)) + + (r2 * sin(2 * pi * 1.0 / 5.0)) + + (r3 * sin(2 * pi * 2.0 / 5.0)) + + (r4 * sin(2 * pi * 3.0 / 5.0)) + + (r5 * sin(2 * pi * 4.0 / 5.0)) + ) + + R2 = ( + (r1 * cos(2 * pi * 0.0 / 5.0)) + + (r2 * cos(2 * pi * 1.0 / 5.0)) + + (r3 * cos(2 * pi * 2.0 / 5.0)) + + (r4 * cos(2 * pi * 3.0 / 5.0)) + + (r5 * cos(2 * pi * 4.0 / 5.0)) + ) x = np.cross(R1[0], R2[0]) n = x / sqrt(pow(x[0], 2) + pow(x[1], 2) + pow(x[2], 2)) @@ -291,15 +366,27 @@ def phase_cp(universe, seg, i): r4_d = np.dot(r4, n) r5_d = np.dot(r5, n) - D = ((r1_d * sin(4 * pi * 0.0 / 5.0)) + (r2_d * sin(4 * pi * 1.0 / 5.0)) - + (r3_d * sin(4 * pi * 2.0 / 5.0)) + (r4_d * sin(4 * pi * 3.0 / 5.0)) - + (r5_d * sin(4 * pi * 4.0 / 5.0))) * -1 * sqrt(2.0 / 5.0) - - C = ((r1_d * cos(4 * pi * 0.0 / 5.0)) + (r2_d * cos(4 * pi * 1.0 / 5.0)) - + (r3_d * cos(4 * pi * 2.0 / 5.0)) + (r4_d * cos(4 * pi * 3.0 / 5.0)) - + (r5_d * cos(4 * pi * 4.0 / 5.0))) * sqrt(2.0 / 5.0) - - phase_ang = (np.arctan2(D, C) + (pi / 2.)) * 180. / pi + D = ( + ( + (r1_d * sin(4 * pi * 0.0 / 5.0)) + + (r2_d * sin(4 * pi * 1.0 / 5.0)) + + (r3_d * sin(4 * pi * 2.0 / 5.0)) + + (r4_d * sin(4 * pi * 3.0 / 5.0)) + + (r5_d * sin(4 * pi * 4.0 / 5.0)) + ) + * -1 + * sqrt(2.0 / 5.0) + ) + + C = ( + (r1_d * cos(4 * pi * 0.0 / 5.0)) + + (r2_d * cos(4 * pi * 1.0 / 5.0)) + + (r3_d * cos(4 * pi * 2.0 / 5.0)) + + (r4_d * cos(4 * pi * 3.0 / 5.0)) + + (r5_d * cos(4 * pi * 4.0 / 5.0)) + ) * sqrt(2.0 / 5.0) + + phase_ang = (np.arctan2(D, C) + (pi / 2.0)) * 180.0 / pi return phase_ang % 360 @@ -325,30 +412,40 @@ def phase_as(universe, seg, i): .. versionadded:: 0.7.6 """ - angle1 = universe.select_atoms(" atom {0!s} {1!s} C1\' ".format(seg, i), - " atom {0!s} {1!s} C2\' ".format(seg, i), - " atom {0!s} {1!s} C3\' ".format(seg, i), - " atom {0!s} {1!s} C4\' ".format(seg, i)) - - angle2 = universe.select_atoms(" atom {0!s} {1!s} C2\' ".format(seg, i), - " atom {0!s} {1!s} C3\' ".format(seg, i), - " atom {0!s} {1!s} C4\' ".format(seg, i), - " atom {0!s} {1!s} O4\' ".format(seg, i)) - - angle3 = universe.select_atoms(" atom {0!s} {1!s} C3\' ".format(seg, i), - " atom {0!s} {1!s} C4\' ".format(seg, i), - " atom {0!s} {1!s} O4\' ".format(seg, i), - " atom {0!s} {1!s} C1\' ".format(seg, i)) - - angle4 = universe.select_atoms(" atom {0!s} {1!s} C4\' ".format(seg, i), - " atom {0!s} {1!s} O4\' ".format(seg, i), - " atom {0!s} {1!s} C1\' ".format(seg, i), - " atom {0!s} {1!s} C2\' ".format(seg, i)) - - angle5 = universe.select_atoms(" atom {0!s} {1!s} O4\' ".format(seg, i), - " atom {0!s} {1!s} C1\' ".format(seg, i), - " atom {0!s} {1!s} C2\' ".format(seg, i), - " atom {0!s} {1!s} C3\' ".format(seg, i)) + angle1 = universe.select_atoms( + " atom {0!s} {1!s} C1' ".format(seg, i), + " atom {0!s} {1!s} C2' ".format(seg, i), + " atom {0!s} {1!s} C3' ".format(seg, i), + " atom {0!s} {1!s} C4' ".format(seg, i), + ) + + angle2 = universe.select_atoms( + " atom {0!s} {1!s} C2' ".format(seg, i), + " atom {0!s} {1!s} C3' ".format(seg, i), + " atom {0!s} {1!s} C4' ".format(seg, i), + " atom {0!s} {1!s} O4' ".format(seg, i), + ) + + angle3 = universe.select_atoms( + " atom {0!s} {1!s} C3' ".format(seg, i), + " atom {0!s} {1!s} C4' ".format(seg, i), + " atom {0!s} {1!s} O4' ".format(seg, i), + " atom {0!s} {1!s} C1' ".format(seg, i), + ) + + angle4 = universe.select_atoms( + " atom {0!s} {1!s} C4' ".format(seg, i), + " atom {0!s} {1!s} O4' ".format(seg, i), + " atom {0!s} {1!s} C1' ".format(seg, i), + " atom {0!s} {1!s} C2' ".format(seg, i), + ) + + angle5 = universe.select_atoms( + " atom {0!s} {1!s} O4' ".format(seg, i), + " atom {0!s} {1!s} C1' ".format(seg, i), + " atom {0!s} {1!s} C2' ".format(seg, i), + " atom {0!s} {1!s} C3' ".format(seg, i), + ) data1 = angle1.dihedral.value() data2 = angle2.dihedral.value() @@ -356,19 +453,31 @@ def phase_as(universe, seg, i): data4 = angle4.dihedral.value() data5 = angle5.dihedral.value() - B = ((data1 * sin(2 * 2 * pi * (1 - 1.) / 5.)) - + (data2 * sin(2 * 2 * pi * (2 - 1.) / 5.)) - + (data3 * sin(2 * 2 * pi * (3 - 1.) / 5.)) - + (data4 * sin(2 * 2 * pi * (4 - 1.) / 5.)) - + (data5 * sin(2 * 2 * pi * (5 - 1.) / 5.))) * -2. / 5. - - A = ((data1 * cos(2 * 2 * pi * (1 - 1.) / 5.)) - + (data2 * cos(2 * 2 * pi * (2 - 1.) / 5.)) - + (data3 * cos(2 * 2 * pi * (3 - 1.) / 5.)) - + (data4 * cos(2 * 2 * pi * (4 - 1.) / 5.)) - + (data5 * cos(2 * 2 * pi * (5 - 1.) / 5.))) * 2. / 5. - - phase_ang = np.arctan2(B, A) * 180. / pi + B = ( + ( + (data1 * sin(2 * 2 * pi * (1 - 1.0) / 5.0)) + + (data2 * sin(2 * 2 * pi * (2 - 1.0) / 5.0)) + + (data3 * sin(2 * 2 * pi * (3 - 1.0) / 5.0)) + + (data4 * sin(2 * 2 * pi * (4 - 1.0) / 5.0)) + + (data5 * sin(2 * 2 * pi * (5 - 1.0) / 5.0)) + ) + * -2.0 + / 5.0 + ) + + A = ( + ( + (data1 * cos(2 * 2 * pi * (1 - 1.0) / 5.0)) + + (data2 * cos(2 * 2 * pi * (2 - 1.0) / 5.0)) + + (data3 * cos(2 * 2 * pi * (3 - 1.0) / 5.0)) + + (data4 * cos(2 * 2 * pi * (4 - 1.0) / 5.0)) + + (data5 * cos(2 * 2 * pi * (5 - 1.0) / 5.0)) + ) + * 2.0 + / 5.0 + ) + + phase_ang = np.arctan2(B, A) * 180.0 / pi return phase_ang % 360 @@ -401,44 +510,60 @@ def tors(universe, seg, i): .. versionadded:: 0.7.6 """ - a = universe.select_atoms(" atom {0!s} {1!s} O3\' ".format(seg, i - 1), - " atom {0!s} {1!s} P ".format(seg, i), - " atom {0!s} {1!s} O5\' ".format(seg, i), - " atom {0!s} {1!s} C5\' ".format(seg, i)) - - b = universe.select_atoms(" atom {0!s} {1!s} P ".format(seg, i), - " atom {0!s} {1!s} O5\' ".format(seg, i), - " atom {0!s} {1!s} C5\' ".format(seg, i), - " atom {0!s} {1!s} C4\' ".format(seg, i)) - - g = universe.select_atoms(" atom {0!s} {1!s} O5\' ".format(seg, i), - " atom {0!s} {1!s} C5\' ".format(seg, i), - " atom {0!s} {1!s} C4\' ".format(seg, i), - " atom {0!s} {1!s} C3\' ".format(seg, i)) - - d = universe.select_atoms(" atom {0!s} {1!s} C5\' ".format(seg, i), - " atom {0!s} {1!s} C4\' ".format(seg, i), - " atom {0!s} {1!s} C3\' ".format(seg, i), - " atom {0!s} {1!s} O3\' ".format(seg, i)) - - e = universe.select_atoms(" atom {0!s} {1!s} C4\' ".format(seg, i), - " atom {0!s} {1!s} C3\' ".format(seg, i), - " atom {0!s} {1!s} O3\' ".format(seg, i), - " atom {0!s} {1!s} P ".format(seg, i + 1)) - - z = universe.select_atoms(" atom {0!s} {1!s} C3\' ".format(seg, i), - " atom {0!s} {1!s} O3\' ".format(seg, i), - " atom {0!s} {1!s} P ".format(seg, i + 1), - " atom {0!s} {1!s} O5\' ".format(seg, i + 1)) - c = universe.select_atoms(" atom {0!s} {1!s} O4\' ".format(seg, i), - " atom {0!s} {1!s} C1\' ".format(seg, i), - " atom {0!s} {1!s} N9 ".format(seg, i), - " atom {0!s} {1!s} C4 ".format(seg, i)) + a = universe.select_atoms( + " atom {0!s} {1!s} O3' ".format(seg, i - 1), + " atom {0!s} {1!s} P ".format(seg, i), + " atom {0!s} {1!s} O5' ".format(seg, i), + " atom {0!s} {1!s} C5' ".format(seg, i), + ) + + b = universe.select_atoms( + " atom {0!s} {1!s} P ".format(seg, i), + " atom {0!s} {1!s} O5' ".format(seg, i), + " atom {0!s} {1!s} C5' ".format(seg, i), + " atom {0!s} {1!s} C4' ".format(seg, i), + ) + + g = universe.select_atoms( + " atom {0!s} {1!s} O5' ".format(seg, i), + " atom {0!s} {1!s} C5' ".format(seg, i), + " atom {0!s} {1!s} C4' ".format(seg, i), + " atom {0!s} {1!s} C3' ".format(seg, i), + ) + + d = universe.select_atoms( + " atom {0!s} {1!s} C5' ".format(seg, i), + " atom {0!s} {1!s} C4' ".format(seg, i), + " atom {0!s} {1!s} C3' ".format(seg, i), + " atom {0!s} {1!s} O3' ".format(seg, i), + ) + + e = universe.select_atoms( + " atom {0!s} {1!s} C4' ".format(seg, i), + " atom {0!s} {1!s} C3' ".format(seg, i), + " atom {0!s} {1!s} O3' ".format(seg, i), + " atom {0!s} {1!s} P ".format(seg, i + 1), + ) + + z = universe.select_atoms( + " atom {0!s} {1!s} C3' ".format(seg, i), + " atom {0!s} {1!s} O3' ".format(seg, i), + " atom {0!s} {1!s} P ".format(seg, i + 1), + " atom {0!s} {1!s} O5' ".format(seg, i + 1), + ) + c = universe.select_atoms( + " atom {0!s} {1!s} O4' ".format(seg, i), + " atom {0!s} {1!s} C1' ".format(seg, i), + " atom {0!s} {1!s} N9 ".format(seg, i), + " atom {0!s} {1!s} C4 ".format(seg, i), + ) if len(c) < 4: - c = universe.select_atoms(" atom {0!s} {1!s} O4\' ".format(seg, i), - " atom {0!s} {1!s} C1\' ".format(seg, i), - " atom {0!s} {1!s} N1 ".format(seg, i), - " atom {0!s} {1!s} C2 ".format(seg, i)) + c = universe.select_atoms( + " atom {0!s} {1!s} O4' ".format(seg, i), + " atom {0!s} {1!s} C1' ".format(seg, i), + " atom {0!s} {1!s} N1 ".format(seg, i), + " atom {0!s} {1!s} C2 ".format(seg, i), + ) alpha = a.dihedral.value() % 360 beta = b.dihedral.value() % 360 @@ -473,10 +598,12 @@ def tors_alpha(universe, seg, i): .. versionadded:: 0.7.6 """ - a = universe.select_atoms(" atom {0!s} {1!s} O3\' ".format(seg, i - 1), - " atom {0!s} {1!s} P ".format(seg, i), - " atom {0!s} {1!s} O5\' ".format(seg, i), - " atom {0!s} {1!s} C5\' ".format(seg, i)) + a = universe.select_atoms( + " atom {0!s} {1!s} O3' ".format(seg, i - 1), + " atom {0!s} {1!s} P ".format(seg, i), + " atom {0!s} {1!s} O5' ".format(seg, i), + " atom {0!s} {1!s} C5' ".format(seg, i), + ) alpha = a.dihedral.value() % 360 return alpha @@ -503,16 +630,18 @@ def tors_beta(universe, seg, i): .. versionadded:: 0.7.6 """ - b = universe.select_atoms(" atom {0!s} {1!s} P ".format(seg, i), - " atom {0!s} {1!s} O5\' ".format(seg, i), - " atom {0!s} {1!s} C5\' ".format(seg, i), - " atom {0!s} {1!s} C4\' ".format(seg, i)) + b = universe.select_atoms( + " atom {0!s} {1!s} P ".format(seg, i), + " atom {0!s} {1!s} O5' ".format(seg, i), + " atom {0!s} {1!s} C5' ".format(seg, i), + " atom {0!s} {1!s} C4' ".format(seg, i), + ) beta = b.dihedral.value() % 360 return beta def tors_gamma(universe, seg, i): - """ Gamma backbone dihedral + """Gamma backbone dihedral The dihedral is computed based on position atoms for resid `i`. @@ -533,10 +662,12 @@ def tors_gamma(universe, seg, i): .. versionadded:: 0.7.6 """ - g = universe.select_atoms(" atom {0!s} {1!s} O5\' ".format(seg, i), - " atom {0!s} {1!s} C5\' ".format(seg, i), - " atom {0!s} {1!s} C4\' ".format(seg, i), - " atom {0!s} {1!s} C3\' ".format(seg, i)) + g = universe.select_atoms( + " atom {0!s} {1!s} O5' ".format(seg, i), + " atom {0!s} {1!s} C5' ".format(seg, i), + " atom {0!s} {1!s} C4' ".format(seg, i), + " atom {0!s} {1!s} C3' ".format(seg, i), + ) gamma = g.dihedral.value() % 360 return gamma @@ -563,10 +694,12 @@ def tors_delta(universe, seg, i): .. versionadded:: 0.7.6 """ - d = universe.select_atoms(" atom {0!s} {1!s} C5\' ".format(seg, i), - " atom {0!s} {1!s} C4\' ".format(seg, i), - " atom {0!s} {1!s} C3\' ".format(seg, i), - " atom {0!s} {1!s} O3\' ".format(seg, i)) + d = universe.select_atoms( + " atom {0!s} {1!s} C5' ".format(seg, i), + " atom {0!s} {1!s} C4' ".format(seg, i), + " atom {0!s} {1!s} C3' ".format(seg, i), + " atom {0!s} {1!s} O3' ".format(seg, i), + ) delta = d.dihedral.value() % 360 return delta @@ -593,10 +726,12 @@ def tors_eps(universe, seg, i): .. versionadded:: 0.7.6 """ - e = universe.select_atoms(" atom {0!s} {1!s} C4\' ".format(seg, i), - " atom {0!s} {1!s} C3\' ".format(seg, i), - " atom {0!s} {1!s} O3\' ".format(seg, i), - " atom {0!s} {1!s} P ".format(seg, i + 1)) + e = universe.select_atoms( + " atom {0!s} {1!s} C4' ".format(seg, i), + " atom {0!s} {1!s} C3' ".format(seg, i), + " atom {0!s} {1!s} O3' ".format(seg, i), + " atom {0!s} {1!s} P ".format(seg, i + 1), + ) epsilon = e.dihedral.value() % 360 return epsilon @@ -623,10 +758,12 @@ def tors_zeta(universe, seg, i): .. versionadded:: 0.7.6 """ - z = universe.select_atoms(" atom {0!s} {1!s} C3\' ".format(seg, i), - " atom {0!s} {1!s} O3\' ".format(seg, i), - " atom {0!s} {1!s} P ".format(seg, i + 1), - " atom {0!s} {1!s} O5\' ".format(seg, i + 1)) + z = universe.select_atoms( + " atom {0!s} {1!s} C3' ".format(seg, i), + " atom {0!s} {1!s} O3' ".format(seg, i), + " atom {0!s} {1!s} P ".format(seg, i + 1), + " atom {0!s} {1!s} O5' ".format(seg, i + 1), + ) zeta = z.dihedral.value() % 360 return zeta @@ -653,15 +790,19 @@ def tors_chi(universe, seg, i): .. versionadded:: 0.7.6 """ - c = universe.select_atoms(" atom {0!s} {1!s} O4\' ".format(seg, i), - " atom {0!s} {1!s} C1\' ".format(seg, i), - " atom {0!s} {1!s} N9 ".format(seg, i), - " atom {0!s} {1!s} C4 ".format(seg, i)) + c = universe.select_atoms( + " atom {0!s} {1!s} O4' ".format(seg, i), + " atom {0!s} {1!s} C1' ".format(seg, i), + " atom {0!s} {1!s} N9 ".format(seg, i), + " atom {0!s} {1!s} C4 ".format(seg, i), + ) if len(c) < 4: - c = universe.select_atoms(" atom {0!s} {1!s} O4\' ".format(seg, i), - " atom {0!s} {1!s} C1\' ".format(seg, i), - " atom {0!s} {1!s} N1 ".format(seg, i), - " atom {0!s} {1!s} C2 ".format(seg, i)) + c = universe.select_atoms( + " atom {0!s} {1!s} O4' ".format(seg, i), + " atom {0!s} {1!s} C1' ".format(seg, i), + " atom {0!s} {1!s} N1 ".format(seg, i), + " atom {0!s} {1!s} C2 ".format(seg, i), + ) chi = c.dihedral.value() % 360 return chi @@ -691,22 +832,27 @@ def hydroxyl(universe, seg, i): .. versionadded:: 0.7.6 """ - h = universe.select_atoms("atom {0!s} {1!s} C1'".format(seg, i), - "atom {0!s} {1!s} C2'".format(seg, i), - "atom {0!s} {1!s} O2'".format(seg, i), - "atom {0!s} {1!s} H2'".format(seg, i)) + h = universe.select_atoms( + "atom {0!s} {1!s} C1'".format(seg, i), + "atom {0!s} {1!s} C2'".format(seg, i), + "atom {0!s} {1!s} O2'".format(seg, i), + "atom {0!s} {1!s} H2'".format(seg, i), + ) try: hydr = h.dihedral.value() % 360 except ValueError: - errmsg = (f"Resid {i} does not contain atoms C1', C2', O2', H2' but " - f"atoms {list(h.atoms)}") + errmsg = ( + f"Resid {i} does not contain atoms C1', C2', O2', H2' but " + f"atoms {list(h.atoms)}" + ) raise ValueError(errmsg) from None return hydr -def pseudo_dihe_baseflip(universe, bp1, bp2, i, - seg1="SYSTEM", seg2="SYSTEM", seg3="SYSTEM"): +def pseudo_dihe_baseflip( + universe, bp1, bp2, i, seg1="SYSTEM", seg2="SYSTEM", seg3="SYSTEM" +): """pseudo dihedral for flipped bases. Useful only for nucleic acid base flipping The dihedral is computed based on position atoms for resid `i` @@ -742,13 +888,25 @@ def pseudo_dihe_baseflip(universe, bp1, bp2, i, """ bf1 = universe.select_atoms( " ( segid {0!s} and resid {1!s} and nucleicbase ) " - "or ( segid {2!s} and resid {3!s} and nucleicbase ) " - .format( seg1, bp1, seg2, bp2)) - bf4 = universe.select_atoms("(segid {0!s} and resid {1!s} and nucleicbase) ".format(seg3, i)) - bf2 = universe.select_atoms("(segid {0!s} and resid {1!s} and nucleicsugar) ".format(seg2, bp2)) - bf3 = universe.select_atoms("(segid {0!s} and resid {1!s} and nucleicsugar) ".format(seg3, i)) - x = [bf1.center_of_mass(), bf2.center_of_mass(), - bf3.center_of_mass(), bf4.center_of_mass()] + "or ( segid {2!s} and resid {3!s} and nucleicbase ) ".format( + seg1, bp1, seg2, bp2 + ) + ) + bf4 = universe.select_atoms( + "(segid {0!s} and resid {1!s} and nucleicbase) ".format(seg3, i) + ) + bf2 = universe.select_atoms( + "(segid {0!s} and resid {1!s} and nucleicsugar) ".format(seg2, bp2) + ) + bf3 = universe.select_atoms( + "(segid {0!s} and resid {1!s} and nucleicsugar) ".format(seg3, i) + ) + x = [ + bf1.center_of_mass(), + bf2.center_of_mass(), + bf3.center_of_mass(), + bf4.center_of_mass(), + ] pseudo = mdamath.dihedral(x[0] - x[1], x[1] - x[2], x[2] - x[3]) pseudo = np.rad2deg(pseudo) % 360 return pseudo diff --git a/package/MDAnalysis/analysis/pca.py b/package/MDAnalysis/analysis/pca.py index cbf4cb588c8..fddbf7d0092 100644 --- a/package/MDAnalysis/analysis/pca.py +++ b/package/MDAnalysis/analysis/pca.py @@ -239,10 +239,18 @@ class PCA(AnalysisBase): incorrectly handle cases where the ``frame`` argument was passed. """ + _analysis_algorithm_is_parallelizable = False - def __init__(self, universe, select='all', align=False, mean=None, - n_components=None, **kwargs): + def __init__( + self, + universe, + select="all", + align=False, + mean=None, + n_components=None, + **kwargs, + ): super(PCA, self).__init__(universe.trajectory, **kwargs) self._u = universe @@ -268,15 +276,18 @@ def _prepare(self): else: self.mean = np.asarray(self._mean) if self.mean.shape[0] != self._n_atoms: - raise ValueError('Number of atoms in reference ({}) does ' - 'not match number of atoms in the ' - 'selection ({})'.format(self._n_atoms, - self.mean.shape[0])) + raise ValueError( + "Number of atoms in reference ({}) does " + "not match number of atoms in the " + "selection ({})".format(self._n_atoms, self.mean.shape[0]) + ) self._calc_mean = False if self.n_frames == 1: - raise ValueError('No covariance information can be gathered from a' - 'single trajectory frame.\n') + raise ValueError( + "No covariance information can be gathered from a" + "single trajectory frame.\n" + ) n_dim = self._n_atoms * 3 self.cov = np.zeros((n_dim, n_dim)) self._ref_atom_positions = self._reference.positions @@ -284,15 +295,20 @@ def _prepare(self): self._ref_atom_positions -= self._ref_cog if self._calc_mean: - for ts in ProgressBar(self._sliced_trajectory, - verbose=self._verbose, desc="Mean Calculation"): + for ts in ProgressBar( + self._sliced_trajectory, + verbose=self._verbose, + desc="Mean Calculation", + ): if self.align: mobile_cog = self._atoms.center_of_geometry() - mobile_atoms, old_rmsd = _fit_to(self._atoms.positions - mobile_cog, - self._ref_atom_positions, - self._atoms, - mobile_com=mobile_cog, - ref_com=self._ref_cog) + mobile_atoms, old_rmsd = _fit_to( + self._atoms.positions - mobile_cog, + self._ref_atom_positions, + self._atoms, + mobile_com=mobile_cog, + ref_com=self._ref_cog, + ) self.mean += self._atoms.positions self.mean /= self.n_frames @@ -301,11 +317,13 @@ def _prepare(self): def _single_frame(self): if self.align: mobile_cog = self._atoms.center_of_geometry() - mobile_atoms, old_rmsd = _fit_to(self._atoms.positions - mobile_cog, - self._ref_atom_positions, - self._atoms, - mobile_com=mobile_cog, - ref_com=self._ref_cog) + mobile_atoms, old_rmsd = _fit_to( + self._atoms.positions - mobile_cog, + self._ref_atom_positions, + self._atoms, + mobile_com=mobile_cog, + ref_com=self._ref_cog, + ) # now all structures are aligned to reference x = mobile_atoms.positions.ravel() else: @@ -324,25 +342,31 @@ def _conclude(self): @property def p_components(self): - wmsg = ("The `p_components` attribute was deprecated in " - "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " - "Please use `results.p_components` instead.") + wmsg = ( + "The `p_components` attribute was deprecated in " + "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " + "Please use `results.p_components` instead." + ) warnings.warn(wmsg, DeprecationWarning) return self.results.p_components @property def variance(self): - wmsg = ("The `variance` attribute was deprecated in " - "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " - "Please use `results.variance` instead.") + wmsg = ( + "The `variance` attribute was deprecated in " + "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " + "Please use `results.variance` instead." + ) warnings.warn(wmsg, DeprecationWarning) return self.results.variance @property def cumulated_variance(self): - wmsg = ("The `cumulated_variance` attribute was deprecated in " - "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " - "Please use `results.cumulated_variance` instead.") + wmsg = ( + "The `cumulated_variance` attribute was deprecated in " + "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " + "Please use `results.cumulated_variance` instead." + ) warnings.warn(wmsg, DeprecationWarning) return self.results.cumulated_variance @@ -356,13 +380,21 @@ def n_components(self, n): if n is None: n = len(self._variance) self.results.variance = self._variance[:n] - self.results.cumulated_variance = (np.cumsum(self._variance) / - np.sum(self._variance))[:n] + self.results.cumulated_variance = ( + np.cumsum(self._variance) / np.sum(self._variance) + )[:n] self.results.p_components = self._p_components[:, :n] self._n_components = n - def transform(self, atomgroup, n_components=None, start=None, stop=None, - step=None, verbose=False): + def transform( + self, + atomgroup, + n_components=None, + start=None, + stop=None, + step=None, + verbose=False, + ): """Apply the dimensionality reduction on a trajectory Parameters @@ -403,31 +435,37 @@ def transform(self, atomgroup, n_components=None, start=None, stop=None, on with ``verbose = True``, or off with ``verbose = False`` """ if not self._calculated: - raise ValueError('Call run() on the PCA before using transform') + raise ValueError("Call run() on the PCA before using transform") if isinstance(atomgroup, Universe): atomgroup = atomgroup.atoms if self._n_atoms != atomgroup.n_atoms: - raise ValueError('PCA has been fit for' - '{} atoms. Your atomgroup' - 'has {} atoms'.format(self._n_atoms, - atomgroup.n_atoms)) + raise ValueError( + "PCA has been fit for" + "{} atoms. Your atomgroup" + "has {} atoms".format(self._n_atoms, atomgroup.n_atoms) + ) if not (self._atoms.types == atomgroup.types).all(): - warnings.warn('Atom types do not match with types used to fit PCA') + warnings.warn("Atom types do not match with types used to fit PCA") traj = atomgroup.universe.trajectory start, stop, step = traj.check_slice_indices(start, stop, step) n_frames = len(range(start, stop, step)) - dim = (n_components if n_components is not None else - self.results.p_components.shape[1]) + dim = ( + n_components + if n_components is not None + else self.results.p_components.shape[1] + ) dot = np.zeros((n_frames, dim)) - for i, ts in tqdm(enumerate(traj[start:stop:step]), disable=not verbose, - total=len(traj[start:stop:step]) - ): + for i, ts in tqdm( + enumerate(traj[start:stop:step]), + disable=not verbose, + total=len(traj[start:stop:step]), + ): xyz = atomgroup.positions.ravel() - self._xmean dot[i] = np.dot(xyz, self._p_components[:, :dim]) @@ -547,20 +585,22 @@ def project_single_frame(self, components=None, group=None, anchor=None): .. versionadded:: 2.2.0 """ if not self._calculated: - raise ValueError('Call run() on the PCA before projecting') + raise ValueError("Call run() on the PCA before projecting") if group is not None: if anchor is None: - raise ValueError("'anchor' cannot be 'None'" + - " if 'group' is not 'None'") + raise ValueError( + "'anchor' cannot be 'None'" + " if 'group' is not 'None'" + ) anchors = group.select_atoms(anchor) anchors_res_ids = anchors.resindices if np.unique(anchors_res_ids).size != anchors_res_ids.size: raise ValueError("More than one 'anchor' found in residues") if not np.isin(group.resindices, anchors_res_ids).all(): - raise ValueError("Some residues in 'group'" + - " do not have an 'anchor'") + raise ValueError( + "Some residues in 'group'" + " do not have an 'anchor'" + ) if not anchors.issubset(self._atoms): raise ValueError("Some 'anchors' are not part of PCA class") @@ -568,18 +608,22 @@ def project_single_frame(self, components=None, group=None, anchor=None): # sure that extrapolation works on residues, not random atoms. non_pca = group.residues.atoms - self._atoms pca_res_indices, pca_res_counts = np.unique( - self._atoms.resindices, return_counts=True) + self._atoms.resindices, return_counts=True + ) non_pca_atoms = np.array([], dtype=int) for res in group.residues: # n_common is the number of pca atoms in a residue - n_common = pca_res_counts[np.where( - pca_res_indices == res.resindex)][0] - non_pca_atoms = np.append(non_pca_atoms, - res.atoms.n_atoms - n_common) + n_common = pca_res_counts[ + np.where(pca_res_indices == res.resindex) + ][0] + non_pca_atoms = np.append( + non_pca_atoms, res.atoms.n_atoms - n_common + ) # index_extrapolate records the anchor number for each non-PCA atom - index_extrapolate = np.repeat(np.arange(anchors.atoms.n_atoms), - non_pca_atoms) + index_extrapolate = np.repeat( + np.arange(anchors.atoms.n_atoms), non_pca_atoms + ) if components is None: components = np.arange(self.results.p_components.shape[1]) @@ -591,22 +635,29 @@ def wrapped(ts): xyz = self._atoms.positions.ravel() - self._xmean self._atoms.positions = np.reshape( - (np.dot(np.dot(xyz, self._p_components[:, components]), - self._p_components[:, components].T) - + self._xmean), (-1, 3) + ( + np.dot( + np.dot(xyz, self._p_components[:, components]), + self._p_components[:, components].T, + ) + + self._xmean + ), + (-1, 3), ) if group is not None: - non_pca.positions += (anchors.positions - - anchors_coords_old)[index_extrapolate] + non_pca.positions += (anchors.positions - anchors_coords_old)[ + index_extrapolate + ] return ts + return wrapped @due.dcite( - Doi('10.1002/(SICI)1097-0134(19990901)36:4<419::AID-PROT5>3.0.CO;2-U'), - Doi('10.1529/biophysj.104.052449'), + Doi("10.1002/(SICI)1097-0134(19990901)36:4<419::AID-PROT5>3.0.CO;2-U"), + Doi("10.1529/biophysj.104.052449"), description="RMSIP", - path='MDAnalysis.analysis.pca', + path="MDAnalysis.analysis.pca", ) def rmsip(self, other, n_components=None): """Compute the root mean square inner product between subspaces. @@ -667,23 +718,24 @@ def rmsip(self, other, n_components=None): try: a = self.results.p_components except AttributeError: - raise ValueError('Call run() on the PCA before using rmsip') + raise ValueError("Call run() on the PCA before using rmsip") try: b = other.results.p_components except AttributeError: if isinstance(other, type(self)): raise ValueError( - 'Call run() on the other PCA before using rmsip') + "Call run() on the other PCA before using rmsip" + ) else: - raise ValueError('other must be another PCA class') + raise ValueError("other must be another PCA class") return rmsip(a.T, b.T, n_components=n_components) @due.dcite( - Doi('10.1016/j.str.2007.12.011'), + Doi("10.1016/j.str.2007.12.011"), description="Cumulative overlap", - path='MDAnalysis.analysis.pca', + path="MDAnalysis.analysis.pca", ) def cumulative_overlap(self, other, i=0, n_components=None): """Compute the cumulative overlap of a vector in a subspace. @@ -722,16 +774,18 @@ def cumulative_overlap(self, other, i=0, n_components=None): a = self.results.p_components except AttributeError: raise ValueError( - 'Call run() on the PCA before using cumulative_overlap') + "Call run() on the PCA before using cumulative_overlap" + ) try: b = other.results.p_components except AttributeError: if isinstance(other, type(self)): raise ValueError( - 'Call run() on the other PCA before using cumulative_overlap') + "Call run() on the other PCA before using cumulative_overlap" + ) else: - raise ValueError('other must be another PCA class') + raise ValueError("other must be another PCA class") return cumulative_overlap(a.T, b.T, i=i, n_components=n_components) @@ -767,15 +821,18 @@ def cosine_content(pca_space, i): t = np.arange(len(pca_space)) T = len(pca_space) cos = np.cos(np.pi * t * (i + 1) / T) - return ((2.0 / T) * (scipy.integrate.simpson(cos*pca_space[:, i])) ** 2 / - scipy.integrate.simpson(pca_space[:, i] ** 2)) + return ( + (2.0 / T) + * (scipy.integrate.simpson(cos * pca_space[:, i])) ** 2 + / scipy.integrate.simpson(pca_space[:, i] ** 2) + ) @due.dcite( - Doi('10.1002/(SICI)1097-0134(19990901)36:4<419::AID-PROT5>3.0.CO;2-U'), - Doi('10.1529/biophysj.104.052449'), + Doi("10.1002/(SICI)1097-0134(19990901)36:4<419::AID-PROT5>3.0.CO;2-U"), + Doi("10.1529/biophysj.104.052449"), description="RMSIP", - path='MDAnalysis.analysis.pca', + path="MDAnalysis.analysis.pca", ) def rmsip(a, b, n_components=None): """Compute the root mean square inner product between subspaces. @@ -845,7 +902,7 @@ def rmsip(a, b, n_components=None): elif len(n_components) == 2: n_a, n_b = n_components else: - raise ValueError('Too many values provided for n_components') + raise ValueError("Too many values provided for n_components") if n_a is None: n_a = len(a) @@ -853,14 +910,14 @@ def rmsip(a, b, n_components=None): n_b = len(b) sip = np.matmul(a[:n_a], b[:n_b].T) ** 2 - msip = sip.sum()/n_a + msip = sip.sum() / n_a return msip**0.5 @due.dcite( - Doi('10.1016/j.str.2007.12.011'), + Doi("10.1016/j.str.2007.12.011"), description="Cumulative overlap", - path='MDAnalysis.analysis.pca', + path="MDAnalysis.analysis.pca", ) def cumulative_overlap(a, b, i=0, n_components=None): """Compute the cumulative overlap of a vector in a subspace. @@ -906,5 +963,5 @@ def cumulative_overlap(a, b, i=0, n_components=None): b = b[:n_components] b_norms = (b**2).sum(axis=1) ** 0.5 - o = np.abs(np.matmul(vec, b.T)) / (b_norms*vec_norm) + o = np.abs(np.matmul(vec, b.T)) / (b_norms * vec_norm) return (o**2).sum() ** 0.5 diff --git a/package/MDAnalysis/analysis/polymer.py b/package/MDAnalysis/analysis/polymer.py index 55119a5d6b8..55641877feb 100644 --- a/package/MDAnalysis/analysis/polymer.py +++ b/package/MDAnalysis/analysis/polymer.py @@ -45,7 +45,7 @@ logger = logging.getLogger(__name__) -@requires('bonds') +@requires("bonds") def sort_backbone(backbone): """Rearrange a linear AtomGroup into backbone order @@ -68,25 +68,30 @@ def sort_backbone(backbone): .. versionadded:: 0.20.0 """ if not backbone.n_fragments == 1: - raise ValueError("{} fragments found in backbone. " - "backbone must be a single contiguous AtomGroup" - "".format(backbone.n_fragments)) + raise ValueError( + "{} fragments found in backbone. " + "backbone must be a single contiguous AtomGroup" + "".format(backbone.n_fragments) + ) - branches = [at for at in backbone - if len(at.bonded_atoms & backbone) > 2] + branches = [at for at in backbone if len(at.bonded_atoms & backbone) > 2] if branches: # find which atom has too many bonds for easier debug raise ValueError( "Backbone is not linear. " "The following atoms have more than two bonds in backbone: {}." - "".format(','.join(str(a) for a in branches))) + "".format(",".join(str(a) for a in branches)) + ) - caps = [atom for atom in backbone - if len(atom.bonded_atoms & backbone) == 1] + caps = [ + atom for atom in backbone if len(atom.bonded_atoms & backbone) == 1 + ] if not caps: # cyclical structure - raise ValueError("Could not find starting point of backbone, " - "is the backbone cyclical?") + raise ValueError( + "Could not find starting point of backbone, " + "is the backbone cyclical?" + ) # arbitrarily choose one of the capping atoms to be the startpoint sorted_backbone = AtomGroup([caps[0]]) @@ -233,15 +238,18 @@ class PersistenceLength(AnalysisBase): backends; use the new method :meth:`get_supported_backends` to see all supported backends. """ + @classmethod def get_supported_backends(cls): return ('serial', 'multiprocessing', 'dask',) _analysis_algorithm_is_parallelizable = True + def __init__(self, atomgroups, **kwargs): super(PersistenceLength, self).__init__( - atomgroups[0].universe.trajectory, **kwargs) + atomgroups[0].universe.trajectory, **kwargs + ) self._atomgroups = atomgroups # Check that all chains are the same length @@ -266,30 +274,36 @@ def _single_frame(self): vecs /= np.sqrt((vecs * vecs).sum(axis=1))[:, None] inner_pr = np.inner(vecs, vecs) - for i in range(n-1): - self._results[:(n-1)-i] += inner_pr[i, i:] + for i in range(n - 1): + self._results[: (n - 1) - i] += inner_pr[i, i:] @property def lb(self): - wmsg = ("The `lb` attribute was deprecated in " - "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " - "Please use `results.variance` instead.") + wmsg = ( + "The `lb` attribute was deprecated in " + "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " + "Please use `results.variance` instead." + ) warnings.warn(wmsg, DeprecationWarning) return self.results.lb @property def lp(self): - wmsg = ("The `lp` attribute was deprecated in " - "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " - "Please use `results.variance` instead.") + wmsg = ( + "The `lp` attribute was deprecated in " + "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " + "Please use `results.variance` instead." + ) warnings.warn(wmsg, DeprecationWarning) return self.results.lp @property def fit(self): - wmsg = ("The `fit` attribute was deprecated in " - "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " - "Please use `results.variance` instead.") + wmsg = ( + "The `fit` attribute was deprecated in " + "MDAnalysis 2.0.0 and will be removed in MDAnalysis 3.0.0. " + "Please use `results.variance` instead." + ) warnings.warn(wmsg, DeprecationWarning) return self.results.fit @@ -322,13 +336,15 @@ def _perform_fit(self): self.results.bond_autocorrelation except AttributeError: raise NoDataError("Use the run method first") from None - self.results.x = self.results.lb *\ - np.arange(len(self.results.bond_autocorrelation)) + self.results.x = self.results.lb * np.arange( + len(self.results.bond_autocorrelation) + ) - self.results.lp = fit_exponential_decay(self.results.x, - self.results.bond_autocorrelation) + self.results.lp = fit_exponential_decay( + self.results.x, self.results.bond_autocorrelation + ) - self.results.fit = np.exp(-self.results.x/self.results.lp) + self.results.fit = np.exp(-self.results.x / self.results.lp) def plot(self, ax=None): """Visualize the results and fit @@ -343,20 +359,21 @@ def plot(self, ax=None): ax : the axis that the graph was plotted on """ import matplotlib.pyplot as plt + if ax is None: fig, ax = plt.subplots() - ax.plot(self.results.x, - self.results.bond_autocorrelation, - 'ro', - label='Result') - ax.plot(self.results.x, - self.results.fit, - label='Fit') - ax.set_xlabel(r'x') - ax.set_ylabel(r'$C(x)$') + ax.plot( + self.results.x, + self.results.bond_autocorrelation, + "ro", + label="Result", + ) + ax.plot(self.results.x, self.results.fit, label="Fit") + ax.set_xlabel(r"x") + ax.set_ylabel(r"$C(x)$") ax.set_xlim(0.0, 40 * self.results.lb) - ax.legend(loc='best') + ax.legend(loc="best") return ax @@ -381,8 +398,9 @@ def fit_exponential_decay(x, y): This function assumes that data starts at 1.0 and decays to 0.0 """ + def expfunc(x, a): - return np.exp(-x/a) + return np.exp(-x / a) a = scipy.optimize.curve_fit(expfunc, x, y)[0][0] diff --git a/package/MDAnalysis/analysis/psa.py b/package/MDAnalysis/analysis/psa.py index b93ea90c64b..853d6ecf622 100644 --- a/package/MDAnalysis/analysis/psa.py +++ b/package/MDAnalysis/analysis/psa.py @@ -64,8 +64,10 @@ ) -wmsg = ('Deprecation in version 2.8.0:\n' - 'MDAnalysis.analysis.psa is deprecated in favour of the MDAKit ' - 'PathSimAnalysis (https://github.com/MDAnalysis/PathSimAnalysis) ' - 'and will be removed in MDAnalysis version 3.0.0') +wmsg = ( + "Deprecation in version 2.8.0:\n" + "MDAnalysis.analysis.psa is deprecated in favour of the MDAKit " + "PathSimAnalysis (https://github.com/MDAnalysis/PathSimAnalysis) " + "and will be removed in MDAnalysis version 3.0.0" +) warnings.warn(wmsg, category=DeprecationWarning) diff --git a/package/MDAnalysis/analysis/rdf.py b/package/MDAnalysis/analysis/rdf.py index 891be116ca5..02ac014d47c 100644 --- a/package/MDAnalysis/analysis/rdf.py +++ b/package/MDAnalysis/analysis/rdf.py @@ -217,24 +217,30 @@ class InterRDF(AnalysisBase): of the `results` attribute of :class:`~MDAnalysis.analysis.AnalysisBase`. """ - def __init__(self, - g1, - g2, - nbins=75, - range=(0.0, 15.0), - norm="rdf", - exclusion_block=None, - exclude_same=None, - **kwargs): + + def __init__( + self, + g1, + g2, + nbins=75, + range=(0.0, 15.0), + norm="rdf", + exclusion_block=None, + exclude_same=None, + **kwargs, + ): super(InterRDF, self).__init__(g1.universe.trajectory, **kwargs) self.g1 = g1 self.g2 = g2 self.norm = str(norm).lower() - self.rdf_settings = {'bins': nbins, - 'range': range} + self.rdf_settings = {"bins": nbins, "range": range} self._exclusion_block = exclusion_block - if exclude_same is not None and exclude_same not in ['residue', 'segment', 'chain']: + if exclude_same is not None and exclude_same not in [ + "residue", + "segment", + "chain", + ]: raise ValueError( "The exclude_same argument to InterRDF must be None, 'residue', 'segment' " "or 'chain'." @@ -243,12 +249,18 @@ def __init__(self, raise ValueError( "The exclude_same argument to InterRDF cannot be used with exclusion_block." ) - name_to_attr = {'residue': 'resindices', 'segment': 'segindices', 'chain': 'chainIDs'} + name_to_attr = { + "residue": "resindices", + "segment": "segindices", + "chain": "chainIDs", + } self.exclude_same = name_to_attr.get(exclude_same) - if self.norm not in ['rdf', 'density', 'none']: - raise ValueError(f"'{self.norm}' is an invalid norm. " - "Use 'rdf', 'density' or 'none'.") + if self.norm not in ["rdf", "density", "none"]: + raise ValueError( + f"'{self.norm}' is an invalid norm. " + "Use 'rdf', 'density' or 'none'." + ) def _prepare(self): # Empty histogram to store the RDF @@ -263,17 +275,19 @@ def _prepare(self): # Cumulative volume for rdf normalization self.volume_cum = 0 # Set the max range to filter the search radius - self._maxrange = self.rdf_settings['range'][1] + self._maxrange = self.rdf_settings["range"][1] def _single_frame(self): - pairs, dist = distances.capped_distance(self.g1.positions, - self.g2.positions, - self._maxrange, - box=self._ts.dimensions) + pairs, dist = distances.capped_distance( + self.g1.positions, + self.g2.positions, + self._maxrange, + box=self._ts.dimensions, + ) # Maybe exclude same molecule distances if self._exclusion_block is not None: - idxA = pairs[:, 0]//self._exclusion_block[0] - idxB = pairs[:, 1]//self._exclusion_block[1] + idxA = pairs[:, 0] // self._exclusion_block[0] + idxB = pairs[:, 1] // self._exclusion_block[1] mask = np.where(idxA != idxB)[0] dist = dist[mask] @@ -295,7 +309,7 @@ def _conclude(self): if self.norm in ["rdf", "density"]: # Volume in each radial shell vols = np.power(self.results.edges, 3) - norm *= 4/3 * np.pi * np.diff(vols) + norm *= 4 / 3 * np.pi * np.diff(vols) if self.norm == "rdf": # Number of each selection @@ -317,33 +331,41 @@ def _conclude(self): @property def edges(self): - wmsg = ("The `edges` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.bins` instead") + wmsg = ( + "The `edges` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.bins` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.edges @property def count(self): - wmsg = ("The `count` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.bins` instead") + wmsg = ( + "The `count` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.bins` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.count @property def bins(self): - wmsg = ("The `bins` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.bins` instead") + wmsg = ( + "The `bins` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.bins` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.bins @property def rdf(self): - wmsg = ("The `rdf` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.rdf` instead") + wmsg = ( + "The `rdf` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.rdf` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.rdf @@ -540,54 +562,69 @@ class InterRDF_s(AnalysisBase): .. deprecated:: 2.3.0 The `universe` parameter is superflous. """ - def __init__(self, - u, - ags, - nbins=75, - range=(0.0, 15.0), - norm="rdf", - density=False, - **kwargs): - super(InterRDF_s, self).__init__(ags[0][0].universe.trajectory, - **kwargs) - - warnings.warn("The `u` attribute is superflous and will be removed " - "in MDAnalysis 3.0.0.", DeprecationWarning) + + def __init__( + self, + u, + ags, + nbins=75, + range=(0.0, 15.0), + norm="rdf", + density=False, + **kwargs, + ): + super(InterRDF_s, self).__init__( + ags[0][0].universe.trajectory, **kwargs + ) + + warnings.warn( + "The `u` attribute is superflous and will be removed " + "in MDAnalysis 3.0.0.", + DeprecationWarning, + ) self.ags = ags self.norm = str(norm).lower() - self.rdf_settings = {'bins': nbins, - 'range': range} + self.rdf_settings = {"bins": nbins, "range": range} - if self.norm not in ['rdf', 'density', 'none']: - raise ValueError(f"'{self.norm}' is an invalid norm. " - "Use 'rdf', 'density' or 'none'.") + if self.norm not in ["rdf", "density", "none"]: + raise ValueError( + f"'{self.norm}' is an invalid norm. " + "Use 'rdf', 'density' or 'none'." + ) if density: - warnings.warn("The `density` attribute was deprecated in " - "MDAnalysis 2.3.0 and will be removed in " - "MDAnalysis 3.0.0. Please use `norm=density` " - "instead.", DeprecationWarning) + warnings.warn( + "The `density` attribute was deprecated in " + "MDAnalysis 2.3.0 and will be removed in " + "MDAnalysis 3.0.0. Please use `norm=density` " + "instead.", + DeprecationWarning, + ) self.norm = "density" def _prepare(self): count, edges = np.histogram([-1], **self.rdf_settings) - self.results.count = [np.zeros((ag1.n_atoms, ag2.n_atoms, len(count)), - dtype=np.float64) for ag1, ag2 in self.ags] + self.results.count = [ + np.zeros((ag1.n_atoms, ag2.n_atoms, len(count)), dtype=np.float64) + for ag1, ag2 in self.ags + ] self.results.edges = edges self.results.bins = 0.5 * (edges[:-1] + edges[1:]) if self.norm == "rdf": # Cumulative volume for rdf normalization self.volume_cum = 0 - self._maxrange = self.rdf_settings['range'][1] + self._maxrange = self.rdf_settings["range"][1] def _single_frame(self): for i, (ag1, ag2) in enumerate(self.ags): - pairs, dist = distances.capped_distance(ag1.positions, - ag2.positions, - self._maxrange, - box=self._ts.dimensions) + pairs, dist = distances.capped_distance( + ag1.positions, + ag2.positions, + self._maxrange, + box=self._ts.dimensions, + ) for j, (idx1, idx2) in enumerate(pairs): count, _ = np.histogram(dist[j], **self.rdf_settings) @@ -601,7 +638,7 @@ def _conclude(self): if self.norm in ["rdf", "density"]: # Volume in each radial shell vols = np.power(self.results.edges, 3) - norm *= 4/3 * np.pi * np.diff(vols) + norm *= 4 / 3 * np.pi * np.diff(vols) if self.norm == "rdf": # Average number density @@ -639,40 +676,50 @@ def get_cdf(self): @property def edges(self): - wmsg = ("The `edges` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.bins` instead") + wmsg = ( + "The `edges` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.bins` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.edges @property def count(self): - wmsg = ("The `count` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.bins` instead") + wmsg = ( + "The `count` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.bins` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.count @property def bins(self): - wmsg = ("The `bins` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.bins` instead") + wmsg = ( + "The `bins` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.bins` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.bins @property def rdf(self): - wmsg = ("The `rdf` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.rdf` instead") + wmsg = ( + "The `rdf` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.rdf` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.rdf @property def cdf(self): - wmsg = ("The `cdf` attribute was deprecated in MDAnalysis 2.0.0 " - "and will be removed in MDAnalysis 3.0.0. Please use " - "`results.cdf` instead") + wmsg = ( + "The `cdf` attribute was deprecated in MDAnalysis 2.0.0 " + "and will be removed in MDAnalysis 3.0.0. Please use " + "`results.cdf` instead" + ) warnings.warn(wmsg, DeprecationWarning) return self.results.cdf diff --git a/package/MDAnalysis/analysis/results.py b/package/MDAnalysis/analysis/results.py index 8aa2062d2bc..7708f3dd881 100644 --- a/package/MDAnalysis/analysis/results.py +++ b/package/MDAnalysis/analysis/results.py @@ -48,6 +48,7 @@ assert r.masses == list((*r1.masses, *r2.masses)) assert (r.vectors == np.vstack([r1.vectors, r2.vectors])).all() """ + from collections import UserDict import numpy as np from typing import Callable, Sequence @@ -100,7 +101,9 @@ class in `scikit-learn`_. def _validate_key(self, key): if key in dir(self): - raise AttributeError(f"'{key}' is a protected dictionary attribute") + raise AttributeError( + f"'{key}' is a protected dictionary attribute" + ) elif isinstance(key, str) and not key.isidentifier(): raise ValueError(f"'{key}' is not a valid attribute") @@ -125,13 +128,17 @@ def __getattr__(self, attr): try: return self[attr] except KeyError as err: - raise AttributeError(f"'Results' object has no attribute '{attr}'") from err + raise AttributeError( + f"'Results' object has no attribute '{attr}'" + ) from err def __delattr__(self, attr): try: del self[attr] except KeyError as err: - raise AttributeError(f"'Results' object has no attribute '{attr}'") from err + raise AttributeError( + f"'Results' object has no attribute '{attr}'" + ) from err def __getstate__(self): return self.data @@ -166,7 +173,7 @@ class ResultsGroup: obj1 = Results(mass=1) obj2 = Results(mass=3) assert {'mass': 2.0} == group.merge([obj1, obj2]) - + .. code-block:: python @@ -182,10 +189,12 @@ class ResultsGroup: def __init__(self, lookup: dict[str, Callable] = None): self._lookup = lookup - def merge(self, objects: Sequence[Results], require_all_aggregators: bool = True) -> Results: - """Merge multiple Results into a single Results instance. + def merge( + self, objects: Sequence[Results], require_all_aggregators: bool = True + ) -> Results: + """Merge multiple Results into a single Results instance. - Merge multiple :class:`Results` instances into a single one, using the + Merge multiple :class:`Results` instances into a single one, using the `lookup` dictionary to determine the appropriate aggregator functions for each named results attribute. If the resulting object only contains a single element, it just returns it without using any aggregators. @@ -213,7 +222,7 @@ def merge(self, objects: Sequence[Results], require_all_aggregators: bool = True if len(objects) == 1: merged_results = objects[0] return merged_results - + merged_results = Results() for key in objects[0].keys(): agg_function = self._lookup.get(key, None) diff --git a/package/MDAnalysis/analysis/rms.py b/package/MDAnalysis/analysis/rms.py index f33d1b761fb..55a1322a75e 100644 --- a/package/MDAnalysis/analysis/rms.py +++ b/package/MDAnalysis/analysis/rms.py @@ -172,7 +172,7 @@ from ..lib.util import asiterable, iterable, get_weights -logger = logging.getLogger('MDAnalysis.analysis.rmsd') +logger = logging.getLogger("MDAnalysis.analysis.rmsd") def rmsd(a, b, weights=None, center=False, superposition=False): @@ -258,7 +258,7 @@ def rmsd(a, b, weights=None, center=False, superposition=False): b = np.asarray(b, dtype=np.float64) N = b.shape[0] if a.shape != b.shape: - raise ValueError('a and b must have same shape') + raise ValueError("a and b must have same shape") # superposition only works if structures are centered if center or superposition: @@ -269,7 +269,7 @@ def rmsd(a, b, weights=None, center=False, superposition=False): if weights is not None: if len(weights) != len(a): - raise ValueError('weights must have same length as a and b') + raise ValueError("weights must have same length as a and b") # weights are constructed as relative to the mean weights = np.asarray(weights, dtype=np.float64) / np.mean(weights) @@ -277,8 +277,7 @@ def rmsd(a, b, weights=None, center=False, superposition=False): return qcp.CalcRMSDRotationalMatrix(a, b, N, None, weights) else: if weights is not None: - return np.sqrt(np.sum(weights[:, np.newaxis] - * ((a - b) ** 2)) / N) + return np.sqrt(np.sum(weights[:, np.newaxis] * ((a - b) ** 2)) / N) else: return np.sqrt(np.sum((a - b) ** 2) / N) @@ -307,28 +306,29 @@ def process_selection(select): """ if isinstance(select, str): - select = {'reference': str(select), 'mobile': str(select)} + select = {"reference": str(select), "mobile": str(select)} elif type(select) is tuple: try: - select = {'mobile': select[0], 'reference': select[1]} + select = {"mobile": select[0], "reference": select[1]} except IndexError: raise IndexError( "select must contain two selection strings " - "(reference, mobile)") from None + "(reference, mobile)" + ) from None elif type(select) is dict: # compatability hack to use new nomenclature try: - select['mobile'] - select['reference'] + select["mobile"] + select["reference"] except KeyError: raise KeyError( - "select dictionary must contain entries for keys " - "'mobile' and 'reference'." - ) from None + "select dictionary must contain entries for keys " + "'mobile' and 'reference'." + ) from None else: raise TypeError("'select' must be either a string, 2-tuple, or dict") - select['mobile'] = asiterable(select['mobile']) - select['reference'] = asiterable(select['reference']) + select["mobile"] = asiterable(select["mobile"]) + select["reference"] = asiterable(select["reference"]) return select @@ -362,16 +362,29 @@ class RMSD(AnalysisBase): introduced :meth:`get_supported_backends` allowing for parallel execution on ``multiprocessing`` and ``dask`` backends. """ + _analysis_algorithm_is_parallelizable = True @classmethod def get_supported_backends(cls): - return ('serial', 'multiprocessing', 'dask',) - - - def __init__(self, atomgroup, reference=None, select='all', - groupselections=None, weights=None, weights_groupselections=False, - tol_mass=0.1, ref_frame=0, **kwargs): + return ( + "serial", + "multiprocessing", + "dask", + ) + + def __init__( + self, + atomgroup, + reference=None, + select="all", + groupselections=None, + weights=None, + weights_groupselections=False, + tol_mass=0.1, + ref_frame=0, + **kwargs, + ): r"""Parameters ---------- atomgroup : AtomGroup or Universe @@ -512,49 +525,67 @@ def __init__(self, atomgroup, reference=None, select='all', `filename` keyword was removed. """ - super(RMSD, self).__init__(atomgroup.universe.trajectory, - **kwargs) + super(RMSD, self).__init__(atomgroup.universe.trajectory, **kwargs) self.atomgroup = atomgroup self.reference = reference if reference is not None else self.atomgroup select = process_selection(select) - self.groupselections = ([process_selection(s) for s in groupselections] - if groupselections is not None else []) + self.groupselections = ( + [process_selection(s) for s in groupselections] + if groupselections is not None + else [] + ) self.weights = weights self.tol_mass = tol_mass self.ref_frame = ref_frame self.weights_groupselections = weights_groupselections - self.ref_atoms = self.reference.select_atoms(*select['reference']) - self.mobile_atoms = self.atomgroup.select_atoms(*select['mobile']) + self.ref_atoms = self.reference.select_atoms(*select["reference"]) + self.mobile_atoms = self.atomgroup.select_atoms(*select["mobile"]) if len(self.ref_atoms) != len(self.mobile_atoms): - err = ("Reference and trajectory atom selections do " - "not contain the same number of atoms: " - "N_ref={0:d}, N_traj={1:d}".format(self.ref_atoms.n_atoms, - self.mobile_atoms.n_atoms)) + err = ( + "Reference and trajectory atom selections do " + "not contain the same number of atoms: " + "N_ref={0:d}, N_traj={1:d}".format( + self.ref_atoms.n_atoms, self.mobile_atoms.n_atoms + ) + ) logger.exception(err) raise SelectionError(err) - logger.info("RMS calculation " - "for {0:d} atoms.".format(len(self.ref_atoms))) - mass_mismatches = (np.absolute((self.ref_atoms.masses - - self.mobile_atoms.masses)) > - self.tol_mass) + logger.info( + "RMS calculation " "for {0:d} atoms.".format(len(self.ref_atoms)) + ) + mass_mismatches = ( + np.absolute((self.ref_atoms.masses - self.mobile_atoms.masses)) + > self.tol_mass + ) if np.any(mass_mismatches): # diagnostic output: logger.error("Atoms: reference | mobile") for ar, at in zip(self.ref_atoms, self.mobile_atoms): if ar.name != at.name: - logger.error("{0!s:>4} {1:3d} {2!s:>3} {3!s:>3} {4:6.3f}" - "| {5!s:>4} {6:3d} {7!s:>3} {8!s:>3}" - "{9:6.3f}".format(ar.segid, ar.resid, - ar.resname, ar.name, - ar.mass, at.segid, at.resid, - at.resname, at.name, - at.mass)) - errmsg = ("Inconsistent selections, masses differ by more than" - "{0:f}; mis-matching atoms" - "are shown above.".format(self.tol_mass)) + logger.error( + "{0!s:>4} {1:3d} {2!s:>3} {3!s:>3} {4:6.3f}" + "| {5!s:>4} {6:3d} {7!s:>3} {8!s:>3}" + "{9:6.3f}".format( + ar.segid, + ar.resid, + ar.resname, + ar.name, + ar.mass, + at.segid, + at.resid, + at.resname, + at.name, + at.mass, + ) + ) + errmsg = ( + "Inconsistent selections, masses differ by more than" + "{0:f}; mis-matching atoms" + "are shown above.".format(self.tol_mass) + ) logger.error(errmsg) raise SelectionError(errmsg) del mass_mismatches @@ -565,27 +596,38 @@ def __init__(self, atomgroup, reference=None, select='all', # *groupselections* groups each a dict with reference/mobile self._groupselections_atoms = [ { - 'reference': self.reference.universe.select_atoms(*s['reference']), - 'mobile': self.atomgroup.universe.select_atoms(*s['mobile']), + "reference": self.reference.universe.select_atoms( + *s["reference"] + ), + "mobile": self.atomgroup.universe.select_atoms(*s["mobile"]), } - for s in self.groupselections] + for s in self.groupselections + ] # sanity check - for igroup, (sel, atoms) in enumerate(zip(self.groupselections, - self._groupselections_atoms)): - if len(atoms['mobile']) != len(atoms['reference']): - logger.exception('SelectionError: Group Selection') + for igroup, (sel, atoms) in enumerate( + zip(self.groupselections, self._groupselections_atoms) + ): + if len(atoms["mobile"]) != len(atoms["reference"]): + logger.exception("SelectionError: Group Selection") raise SelectionError( "Group selection {0}: {1} | {2}: Reference and trajectory " "atom selections do not contain the same number of atoms: " "N_ref={3}, N_traj={4}".format( - igroup, sel['reference'], sel['mobile'], - len(atoms['reference']), len(atoms['mobile']))) + igroup, + sel["reference"], + sel["mobile"], + len(atoms["reference"]), + len(atoms["mobile"]), + ) + ) # check weights type - acceptable_dtypes = (np.dtype('float64'), np.dtype('int64')) - msg = ("weights should only be 'mass', None or 1D float array." - "For weights on groupselections, " - "use **weight_groupselections**") + acceptable_dtypes = (np.dtype("float64"), np.dtype("int64")) + msg = ( + "weights should only be 'mass', None or 1D float array." + "For weights on groupselections, " + "use **weight_groupselections**" + ) if iterable(self.weights): element_lens = [] @@ -605,42 +647,57 @@ def __init__(self, atomgroup, reference=None, select='all', if self.weights_groupselections: if len(self.weights_groupselections) != len(self.groupselections): - raise ValueError("Length of weights_groupselections is not equal to " - "length of groupselections ") - for weights, atoms, selection in zip(self.weights_groupselections, - self._groupselections_atoms, - self.groupselections): + raise ValueError( + "Length of weights_groupselections is not equal to " + "length of groupselections " + ) + for weights, atoms, selection in zip( + self.weights_groupselections, + self._groupselections_atoms, + self.groupselections, + ): try: if iterable(weights) or weights != "mass": - get_weights(atoms['mobile'], weights) + get_weights(atoms["mobile"], weights) except Exception as e: - raise type(e)(str(e) + ' happens in selection %s' % selection['mobile']) - + raise type(e)( + str(e) + + " happens in selection %s" % selection["mobile"] + ) def _prepare(self): self._n_atoms = self.mobile_atoms.n_atoms if not self.weights_groupselections: - if not iterable(self.weights): # apply 'mass' or 'None' to groupselections - self.weights_groupselections = [self.weights] * len(self.groupselections) + if not iterable( + self.weights + ): # apply 'mass' or 'None' to groupselections + self.weights_groupselections = [self.weights] * len( + self.groupselections + ) else: - self.weights_groupselections = [None] * len(self.groupselections) - - for igroup, (weights, atoms) in enumerate(zip(self.weights_groupselections, - self._groupselections_atoms)): - if str(weights) == 'mass': - self.weights_groupselections[igroup] = atoms['mobile'].masses + self.weights_groupselections = [None] * len( + self.groupselections + ) + + for igroup, (weights, atoms) in enumerate( + zip(self.weights_groupselections, self._groupselections_atoms) + ): + if str(weights) == "mass": + self.weights_groupselections[igroup] = atoms["mobile"].masses if weights is not None: - self.weights_groupselections[igroup] = np.asarray(self.weights_groupselections[igroup], - dtype=np.float64) / \ - np.mean(self.weights_groupselections[igroup]) + self.weights_groupselections[igroup] = np.asarray( + self.weights_groupselections[igroup], dtype=np.float64 + ) / np.mean(self.weights_groupselections[igroup]) # add the array of weights to weights_select self.weights_select = get_weights(self.mobile_atoms, self.weights) self.weights_ref = get_weights(self.ref_atoms, self.weights) if self.weights_select is not None: - self.weights_select = np.asarray(self.weights_select, dtype=np.float64) / \ - np.mean(self.weights_select) - self.weights_ref = np.asarray(self.weights_ref, dtype=np.float64) / \ - np.mean(self.weights_ref) + self.weights_select = np.asarray( + self.weights_select, dtype=np.float64 + ) / np.mean(self.weights_select) + self.weights_ref = np.asarray( + self.weights_ref, dtype=np.float64 + ) / np.mean(self.weights_ref) current_frame = self.reference.universe.trajectory.ts.frame @@ -653,10 +710,14 @@ def _prepare(self): # makes a copy self._ref_coordinates = self.ref_atoms.positions - self._ref_com if self._groupselections_atoms: - self._groupselections_ref_coords64 = [(self.reference. - select_atoms(*s['reference']). - positions.astype(np.float64)) for s in - self.groupselections] + self._groupselections_ref_coords64 = [ + ( + self.reference.select_atoms( + *s["reference"] + ).positions.astype(np.float64) + ) + for s in self.groupselections + ] finally: # Move back to the original frame self.reference.universe.trajectory[current_frame] @@ -674,20 +735,28 @@ def _prepare(self): else: self._rot = None - self.results.rmsd = np.zeros((self.n_frames, - 3 + len(self._groupselections_atoms))) + self.results.rmsd = np.zeros( + (self.n_frames, 3 + len(self._groupselections_atoms)) + ) - self._mobile_coordinates64 = self.mobile_atoms.positions.copy().astype(np.float64) + self._mobile_coordinates64 = self.mobile_atoms.positions.copy().astype( + np.float64 + ) def _get_aggregator(self): - return ResultsGroup(lookup={'rmsd': ResultsGroup.ndarray_vstack}) + return ResultsGroup(lookup={"rmsd": ResultsGroup.ndarray_vstack}) def _single_frame(self): - mobile_com = self.mobile_atoms.center(self.weights_select).astype(np.float64) + mobile_com = self.mobile_atoms.center(self.weights_select).astype( + np.float64 + ) self._mobile_coordinates64[:] = self.mobile_atoms.positions self._mobile_coordinates64 -= mobile_com - self.results.rmsd[self._frame_index, :2] = self._ts.frame, self._trajectory.time + self.results.rmsd[self._frame_index, :2] = ( + self._ts.frame, + self._trajectory.time, + ) if self._groupselections_atoms: # superimpose structures: MDAnalysis qcprot needs Nx3 coordinate @@ -696,9 +765,15 @@ def _single_frame(self): # left** so that we can easily use broadcasting and save one # expensive numpy transposition. - self.results.rmsd[self._frame_index, 2] = qcp.CalcRMSDRotationalMatrix( - self._ref_coordinates64, self._mobile_coordinates64, - self._n_atoms, self._rot, self.weights_select) + self.results.rmsd[self._frame_index, 2] = ( + qcp.CalcRMSDRotationalMatrix( + self._ref_coordinates64, + self._mobile_coordinates64, + self._n_atoms, + self._rot, + self.weights_select, + ) + ) self._R[:, :] = self._rot.reshape(3, 3) # Transform each atom in the trajectory (use inplace ops to @@ -713,24 +788,39 @@ def _single_frame(self): # 2) calculate secondary RMSDs (without any further # superposition) for igroup, (refpos, atoms) in enumerate( - zip(self._groupselections_ref_coords64, - self._groupselections_atoms), 3): + zip( + self._groupselections_ref_coords64, + self._groupselections_atoms, + ), + 3, + ): self.results.rmsd[self._frame_index, igroup] = rmsd( - refpos, atoms['mobile'].positions, - weights=self.weights_groupselections[igroup-3], - center=False, superposition=False) + refpos, + atoms["mobile"].positions, + weights=self.weights_groupselections[igroup - 3], + center=False, + superposition=False, + ) else: # only calculate RMSD by setting the Rmatrix to None (no need # to carry out the rotation as we already get the optimum RMSD) - self.results.rmsd[self._frame_index, 2] = qcp.CalcRMSDRotationalMatrix( - self._ref_coordinates64, self._mobile_coordinates64, - self._n_atoms, None, self.weights_select) + self.results.rmsd[self._frame_index, 2] = ( + qcp.CalcRMSDRotationalMatrix( + self._ref_coordinates64, + self._mobile_coordinates64, + self._n_atoms, + None, + self.weights_select, + ) + ) @property def rmsd(self): - wmsg = ("The `rmsd` attribute was deprecated in MDAnalysis 2.0.0 and " - "will be removed in MDAnalysis 3.0.0. Please use " - "`results.rmsd` instead.") + wmsg = ( + "The `rmsd` attribute was deprecated in MDAnalysis 2.0.0 and " + "will be removed in MDAnalysis 3.0.0. Please use " + "`results.rmsd` instead." + ) warnings.warn(wmsg, DeprecationWarning) return self.results.rmsd @@ -754,7 +844,7 @@ class RMSF(AnalysisBase): @classmethod def get_supported_backends(cls): - return ('serial',) + return ("serial",) def __init__(self, atomgroup, **kwargs): r"""Parameters @@ -885,7 +975,9 @@ def _prepare(self): def _single_frame(self): k = self._frame_index - self.sumsquares += (k / (k+1.0)) * (self.atomgroup.positions - self.mean) ** 2 + self.sumsquares += (k / (k + 1.0)) * ( + self.atomgroup.positions - self.mean + ) ** 2 self.mean = (k * self.mean + self.atomgroup.positions) / (k + 1) def _conclude(self): @@ -893,13 +985,17 @@ def _conclude(self): self.results.rmsf = np.sqrt(self.sumsquares.sum(axis=1) / (k + 1)) if not (self.results.rmsf >= 0).all(): - raise ValueError("Some RMSF values negative; overflow " + - "or underflow occurred") + raise ValueError( + "Some RMSF values negative; overflow " + + "or underflow occurred" + ) @property def rmsf(self): - wmsg = ("The `rmsf` attribute was deprecated in MDAnalysis 2.0.0 and " - "will be removed in MDAnalysis 3.0.0. Please use " - "`results.rmsd` instead.") + wmsg = ( + "The `rmsf` attribute was deprecated in MDAnalysis 2.0.0 and " + "will be removed in MDAnalysis 3.0.0. Please use " + "`results.rmsd` instead." + ) warnings.warn(wmsg, DeprecationWarning) return self.results.rmsf diff --git a/package/MDAnalysis/analysis/waterdynamics.py b/package/MDAnalysis/analysis/waterdynamics.py index 2c7a1c4bec3..aaeb1b296f3 100644 --- a/package/MDAnalysis/analysis/waterdynamics.py +++ b/package/MDAnalysis/analysis/waterdynamics.py @@ -49,8 +49,10 @@ ) -wmsg = ("Deprecation in version 2.8.0\n" - "MDAnalysis.analysis.waterdynamics is deprecated in favour of the " - "MDAKit waterdynamics (https://www.mdanalysis.org/waterdynamics/) " - "and will be removed in MDAnalysis version 3.0.0") +wmsg = ( + "Deprecation in version 2.8.0\n" + "MDAnalysis.analysis.waterdynamics is deprecated in favour of the " + "MDAKit waterdynamics (https://www.mdanalysis.org/waterdynamics/) " + "and will be removed in MDAnalysis version 3.0.0" +) warnings.warn(wmsg, category=DeprecationWarning) diff --git a/package/MDAnalysis/core/selection.py b/package/MDAnalysis/core/selection.py index 591c074030d..6c2ea3c8172 100644 --- a/package/MDAnalysis/core/selection.py +++ b/package/MDAnalysis/core/selection.py @@ -1094,6 +1094,37 @@ def _apply(self, group): return group[mask] +class WaterSelection(Selection): + """All atoms in water residues with recognized water residue names. + + Recognized residue names: + + * recognized 3 Letter resnames: 'H2O', 'HOH', 'OH2', 'HHO', 'OHH' + 'TIP', 'T3P', 'T4P', 'T5P', 'SOL', 'WAT' + + * recognized 4 Letter resnames: 'TIP2', 'TIP3', 'TIP4' + + .. versionadded:: 2.9.0 + """ + token = 'water' + + # Recognized water resnames + water_res = { + 'H2O', 'HOH', 'OH2', 'HHO', 'OHH', + 'T3P', 'T4P', 'T5P', 'SOL', 'WAT', + 'TIP', 'TIP2', 'TIP3', 'TIP4' + } + + def _apply(self, group): + resnames = group.universe._topology.resnames + nmidx = resnames.nmidx[group.resindices] + + matches = [ix for (nm, ix) in resnames.namedict.items() + if nm in self.water_res] + mask = np.isin(nmidx, matches) + + return group[mask] + class BackboneSelection(ProteinSelection): """A BackboneSelection contains all atoms with name 'N', 'CA', 'C', 'O'. diff --git a/package/MDAnalysis/topology/CRDParser.py b/package/MDAnalysis/topology/CRDParser.py index d1896423a84..c0322824baa 100644 --- a/package/MDAnalysis/topology/CRDParser.py +++ b/package/MDAnalysis/topology/CRDParser.py @@ -89,7 +89,8 @@ class CRDParser(TopologyReaderBase): Type and mass are not longer guessed here. Until 3.0 these will still be set by default through through universe.guess_TopologyAttrs() API. """ - format = 'CRD' + + format = "CRD" def parse(self, **kwargs): """Create the Topology object @@ -102,8 +103,10 @@ def parse(self, **kwargs): ---- Could use the resnum and temp factor better """ - extformat = FORTRANReader('2I10,2X,A8,2X,A8,3F20.10,2X,A8,2X,A8,F20.10') - stdformat = FORTRANReader('2I5,1X,A4,1X,A4,3F10.5,1X,A4,1X,A4,F10.5') + extformat = FORTRANReader( + "2I10,2X,A8,2X,A8,3F20.10,2X,A8,2X,A8,F20.10" + ) + stdformat = FORTRANReader("2I5,1X,A4,1X,A4,3F10.5,1X,A4,1X,A4,F10.5") atomids = [] atomnames = [] @@ -116,21 +119,36 @@ def parse(self, **kwargs): with openany(self.filename) as crd: for linenum, line in enumerate(crd): # reading header - if line.split()[0] == '*': + if line.split()[0] == "*": continue - elif line.split()[-1] == 'EXT' and int(line.split()[0]): + elif line.split()[-1] == "EXT" and int(line.split()[0]): r = extformat continue - elif line.split()[0] == line.split()[-1] and line.split()[0] != '*': + elif ( + line.split()[0] == line.split()[-1] + and line.split()[0] != "*" + ): r = stdformat continue # anything else should be an atom try: - (serial, resnum, resName, name, - x, y, z, segid, resid, tempFactor) = r.read(line) + ( + serial, + resnum, + resName, + name, + x, + y, + z, + segid, + resid, + tempFactor, + ) = r.read(line) except Exception: - errmsg = (f"Check CRD format at line {linenum + 1}: " - f"{line.rstrip()}") + errmsg = ( + f"Check CRD format at line {linenum + 1}: " + f"{line.rstrip()}" + ) raise ValueError(errmsg) from None atomids.append(serial) @@ -150,22 +168,28 @@ def parse(self, **kwargs): resnums = np.array(resnums, dtype=np.int32) segids = np.array(segids, dtype=object) - atom_residx, (res_resids, res_resnames, res_resnums, res_segids) = change_squash( - (resids, resnames), (resids, resnames, resnums, segids)) - res_segidx, (seg_segids,) = change_squash( - (res_segids,), (res_segids,)) - - top = Topology(len(atomids), len(res_resids), len(seg_segids), - attrs=[ - Atomids(atomids), - Atomnames(atomnames), - Tempfactors(tempfactors), - Resids(res_resids), - Resnames(res_resnames), - Resnums(res_resnums), - Segids(seg_segids), - ], - atom_resindex=atom_residx, - residue_segindex=res_segidx) + atom_residx, (res_resids, res_resnames, res_resnums, res_segids) = ( + change_squash( + (resids, resnames), (resids, resnames, resnums, segids) + ) + ) + res_segidx, (seg_segids,) = change_squash((res_segids,), (res_segids,)) + + top = Topology( + len(atomids), + len(res_resids), + len(seg_segids), + attrs=[ + Atomids(atomids), + Atomnames(atomnames), + Tempfactors(tempfactors), + Resids(res_resids), + Resnames(res_resnames), + Resnums(res_resnums), + Segids(seg_segids), + ], + atom_resindex=atom_residx, + residue_segindex=res_segidx, + ) return top diff --git a/package/MDAnalysis/topology/DLPolyParser.py b/package/MDAnalysis/topology/DLPolyParser.py index 5452dbad3ce..6c03ed62c2d 100644 --- a/package/MDAnalysis/topology/DLPolyParser.py +++ b/package/MDAnalysis/topology/DLPolyParser.py @@ -69,7 +69,8 @@ class ConfigParser(TopologyReaderBase): Removed type and mass guessing (attributes guessing takes place now through universe.guess_TopologyAttrs() API). """ - format = 'CONFIG' + + format = "CONFIG" def parse(self, **kwargs): with openany(self.filename) as inf: @@ -117,10 +118,9 @@ def parse(self, **kwargs): Atomids(ids), Resids(np.array([1])), Resnums(np.array([1])), - Segids(np.array(['SYSTEM'], dtype=object)), + Segids(np.array(["SYSTEM"], dtype=object)), ] - top = Topology(n_atoms, 1, 1, - attrs=attrs) + top = Topology(n_atoms, 1, 1, attrs=attrs) return top @@ -130,7 +130,8 @@ class HistoryParser(TopologyReaderBase): .. versionadded:: 0.10.1 """ - format = 'HISTORY' + + format = "HISTORY" def parse(self, **kwargs): with openany(self.filename) as inf: @@ -143,10 +144,10 @@ def parse(self, **kwargs): line = inf.readline() while not (len(line.split()) == 4 or len(line.split()) == 5): line = inf.readline() - if line == '': + if line == "": raise EOFError("End of file reached when reading HISTORY.") - while line and not line.startswith('timestep'): + while line and not line.startswith("timestep"): name = line[:8].strip() names.append(name) try: @@ -179,9 +180,8 @@ def parse(self, **kwargs): Atomids(ids), Resids(np.array([1])), Resnums(np.array([1])), - Segids(np.array(['SYSTEM'], dtype=object)), + Segids(np.array(["SYSTEM"], dtype=object)), ] - top = Topology(n_atoms, 1, 1, - attrs=attrs) + top = Topology(n_atoms, 1, 1, attrs=attrs) return top diff --git a/package/MDAnalysis/topology/DMSParser.py b/package/MDAnalysis/topology/DMSParser.py index 84c04fd2ac8..691b47eed64 100644 --- a/package/MDAnalysis/topology/DMSParser.py +++ b/package/MDAnalysis/topology/DMSParser.py @@ -63,8 +63,9 @@ class Atomnums(AtomAttr): """The number for each Atom""" - attrname = 'atomnums' - singular = 'atomnum' + + attrname = "atomnums" + singular = "atomnum" class DMSParser(TopologyReaderBase): @@ -100,7 +101,8 @@ class DMSParser(TopologyReaderBase): through universe.guess_TopologyAttrs() API). """ - format = 'DMS' + + format = "DMS" def parse(self, **kwargs): """Parse DMS file *filename* and return the Topology object""" @@ -121,28 +123,29 @@ def dict_factory(cursor, row): attrs = {} # Row factories for different data types - facs = {np.int32: lambda c, r: r[0], - np.float32: lambda c, r: r[0], - object: lambda c, r: str(r[0].strip())} + facs = { + np.int32: lambda c, r: r[0], + np.float32: lambda c, r: r[0], + object: lambda c, r: str(r[0].strip()), + } with sqlite3.connect(self.filename) as con: # Selecting single column, so just strip tuple for attrname, dt in [ - ('id', np.int32), - ('anum', np.int32), - ('mass', np.float32), - ('charge', np.float32), - ('name', object), - ('resname', object), - ('resid', np.int32), - ('chain', object), - ('segid', object), + ("id", np.int32), + ("anum", np.int32), + ("mass", np.float32), + ("charge", np.float32), + ("name", object), + ("resname", object), + ("resid", np.int32), + ("chain", object), + ("segid", object), ]: try: cur = con.cursor() cur.row_factory = facs[dt] - cur.execute('SELECT {} FROM particle' - ''.format(attrname)) + cur.execute("SELECT {} FROM particle" "".format(attrname)) vals = cur.fetchall() except sqlite3.DatabaseError: errmsg = "Failed reading the atoms from DMS Database" @@ -152,7 +155,7 @@ def dict_factory(cursor, row): try: cur.row_factory = dict_factory - cur.execute('SELECT * FROM bond') + cur.execute("SELECT * FROM bond") bonds = cur.fetchall() except sqlite3.DatabaseError: errmsg = "Failed reading the bonds from DMS Database" @@ -161,35 +164,36 @@ def dict_factory(cursor, row): bondlist = [] bondorder = {} for b in bonds: - desc = tuple(sorted([b['p0'], b['p1']])) + desc = tuple(sorted([b["p0"], b["p1"]])) bondlist.append(desc) - bondorder[desc] = b['order'] - attrs['bond'] = bondlist - attrs['bondorder'] = bondorder + bondorder[desc] = b["order"] + attrs["bond"] = bondlist + attrs["bondorder"] = bondorder topattrs = [] # Bundle in Atom level objects for attr, cls in [ - ('id', Atomids), - ('anum', Atomnums), - ('mass', Masses), - ('charge', Charges), - ('name', Atomnames), - ('chain', ChainIDs), + ("id", Atomids), + ("anum", Atomnums), + ("mass", Masses), + ("charge", Charges), + ("name", Atomnames), + ("chain", ChainIDs), ]: topattrs.append(cls(attrs[attr])) # Residues - atom_residx, (res_resids, - res_resnums, - res_resnames, - res_segids) = change_squash( - (attrs['resid'], attrs['resname'], attrs['segid']), - (attrs['resid'], - attrs['resid'].copy(), - attrs['resname'], - attrs['segid']), + atom_residx, (res_resids, res_resnums, res_resnames, res_segids) = ( + change_squash( + (attrs["resid"], attrs["resname"], attrs["segid"]), + ( + attrs["resid"], + attrs["resid"].copy(), + attrs["resname"], + attrs["segid"], + ), ) + ) n_residues = len(res_resids) topattrs.append(Resids(res_resids)) @@ -197,8 +201,9 @@ def dict_factory(cursor, row): topattrs.append(Resnames(res_resnames)) if any(res_segids) and not any(val is None for val in res_segids): - res_segidx, (res_segids,) = change_squash((res_segids,), - (res_segids,)) + res_segidx, (res_segids,) = change_squash( + (res_segids,), (res_segids,) + ) uniq_seg = np.unique(res_segids) idx2seg = {idx: res_segids[idx] for idx in res_segidx} @@ -211,14 +216,18 @@ def dict_factory(cursor, row): topattrs.append(Segids(res_segids)) else: n_segments = 1 - topattrs.append(Segids(np.array(['SYSTEM'], dtype=object))) + topattrs.append(Segids(np.array(["SYSTEM"], dtype=object))) res_segidx = None - topattrs.append(Bonds(attrs['bond'])) + topattrs.append(Bonds(attrs["bond"])) - top = Topology(len(attrs['id']), n_residues, n_segments, - attrs=topattrs, - atom_resindex=atom_residx, - residue_segindex=res_segidx) + top = Topology( + len(attrs["id"]), + n_residues, + n_segments, + attrs=topattrs, + atom_resindex=atom_residx, + residue_segindex=res_segidx, + ) return top diff --git a/package/MDAnalysis/topology/ExtendedPDBParser.py b/package/MDAnalysis/topology/ExtendedPDBParser.py index ef4f25fee48..e1d76791986 100644 --- a/package/MDAnalysis/topology/ExtendedPDBParser.py +++ b/package/MDAnalysis/topology/ExtendedPDBParser.py @@ -60,35 +60,36 @@ class ExtendedPDBParser(PDBParser.PDBParser): """Parser that handles non-standard "extended" PDB file. - Extended PDB files (MDAnalysis format specifier *XPDB*) may contain residue - sequence numbers up to 99,999 by utilizing the insertion character field of - the PDB standard. - - Creates a Topology with the following Attributes (if present): - - serials - - names - - altLocs - - chainids - - tempfactors - - occupancies - - resids - - resnames - - segids - - elements - - bonds - - formalcharges - - .. note:: - - By default, atomtypes and masses will be guessed on Universe creation. - This may change in release 3.0. - See :ref:`Guessers` for more information. - - - See Also - -------- - :class:`MDAnalysis.coordinates.PDB.ExtendedPDBReader` - - .. versionadded:: 0.8 + Extended PDB files (MDAnalysis format specifier *XPDB*) may contain residue + sequence numbers up to 99,999 by utilizing the insertion character field of + the PDB standard. + + Creates a Topology with the following Attributes (if present): + - serials + - names + - altLocs + - chainids + - tempfactors + - occupancies + - resids + - resnames + - segids + - elements + - bonds + - formalcharges + + .. note:: + + By default, atomtypes and masses will be guessed on Universe creation. + This may change in release 3.0. + See :ref:`Guessers` for more information. + + + See Also + -------- + :class:`MDAnalysis.coordinates.PDB.ExtendedPDBReader` + + .. versionadded:: 0.8 """ - format = 'XPDB' + + format = "XPDB" diff --git a/package/MDAnalysis/topology/FHIAIMSParser.py b/package/MDAnalysis/topology/FHIAIMSParser.py index a47d367ec9e..c3aa80aced4 100644 --- a/package/MDAnalysis/topology/FHIAIMSParser.py +++ b/package/MDAnalysis/topology/FHIAIMSParser.py @@ -77,7 +77,8 @@ class FHIAIMSParser(TopologyReaderBase): Removed type and mass guessing (attributes guessing takes place now through universe.guess_TopologyAttrs() API). """ - format = ['IN', 'FHIAIMS'] + + format = ["IN", "FHIAIMS"] def parse(self, **kwargs): """Read the file and return the structure. @@ -99,18 +100,22 @@ def parse(self, **kwargs): continue # we are now seeing something that's neither atom nor lattice raise ValueError( - 'Non-conforming line: ({0})in FHI-AIMS input file {0}'.format(line, self.filename)) + "Non-conforming line: ({0})in FHI-AIMS input file {0}".format( + line, self.filename + ) + ) names = np.asarray(names) natoms = len(names) - attrs = [Atomnames(names), - Atomids(np.arange(natoms) + 1), - Resids(np.array([1])), - Resnums(np.array([1])), - Segids(np.array(['SYSTEM'], dtype=object)), - Elements(names)] + attrs = [ + Atomnames(names), + Atomids(np.arange(natoms) + 1), + Resids(np.array([1])), + Resnums(np.array([1])), + Segids(np.array(["SYSTEM"], dtype=object)), + Elements(names), + ] - top = Topology(natoms, 1, 1, - attrs=attrs) + top = Topology(natoms, 1, 1, attrs=attrs) return top diff --git a/package/MDAnalysis/topology/GMSParser.py b/package/MDAnalysis/topology/GMSParser.py index 2ea7fe23004..41e85f73ec8 100644 --- a/package/MDAnalysis/topology/GMSParser.py +++ b/package/MDAnalysis/topology/GMSParser.py @@ -60,10 +60,11 @@ AtomAttr, ) + class AtomicCharges(AtomAttr): - attrname = 'atomiccharges' - singular = 'atomiccharge' - per_object = 'atom' + attrname = "atomiccharges" + singular = "atomiccharge" + per_object = "atom" class GMSParser(TopologyReaderBase): @@ -86,7 +87,8 @@ class GMSParser(TopologyReaderBase): through universe.guess_TopologyAttrs() API). """ - format = 'GMS' + + format = "GMS" def parse(self, **kwargs): """Read list of atoms from a GAMESS file.""" @@ -98,16 +100,18 @@ def parse(self, **kwargs): line = inf.readline() if not line: raise EOFError - if re.match(r'^\s+ATOM\s+ATOMIC\s+COORDINATES\s*\(BOHR\).*',\ - line): + if re.match( + r"^\s+ATOM\s+ATOMIC\s+COORDINATES\s*\(BOHR\).*", line + ): break - line = inf.readline() # skip + line = inf.readline() # skip while True: line = inf.readline() - _m = re.match(\ -r'^\s*([A-Za-z_][A-Za-z_0-9]*)\s+([0-9]+\.[0-9]+)\s+(\-?[0-9]+\.[0-9]+)\s+(\-?[0-9]+\.[0-9]+)\s+(\-?[0-9]+\.[0-9]+).*', - line) + _m = re.match( + r"^\s*([A-Za-z_][A-Za-z_0-9]*)\s+([0-9]+\.[0-9]+)\s+(\-?[0-9]+\.[0-9]+)\s+(\-?[0-9]+\.[0-9]+)\s+(\-?[0-9]+\.[0-9]+).*", + line, + ) if _m is None: break name = _m.group(1) @@ -115,7 +119,7 @@ def parse(self, **kwargs): names.append(name) at_charges.append(at_charge) - #TODO: may be use coordinates info from _m.group(3-5) ?? + # TODO: may be use coordinates info from _m.group(3-5) ?? n_atoms = len(names) attrs = [ @@ -124,9 +128,8 @@ def parse(self, **kwargs): AtomicCharges(np.array(at_charges, dtype=np.int32)), Resids(np.array([1])), Resnums(np.array([1])), - Segids(np.array(['SYSTEM'], dtype=object)), + Segids(np.array(["SYSTEM"], dtype=object)), ] - top = Topology(n_atoms, 1, 1, - attrs=attrs) + top = Topology(n_atoms, 1, 1, attrs=attrs) return top diff --git a/package/MDAnalysis/topology/GROParser.py b/package/MDAnalysis/topology/GROParser.py index 368b7d5daf0..dab8ef691cd 100644 --- a/package/MDAnalysis/topology/GROParser.py +++ b/package/MDAnalysis/topology/GROParser.py @@ -78,7 +78,8 @@ class GROParser(TopologyReaderBase): through universe.guess_TopologyAttrs() API). """ - format = 'GRO' + + format = "GRO" def parse(self, **kwargs): """Return the *Topology* object for this file""" @@ -104,14 +105,16 @@ def parse(self, **kwargs): indices[i] = int(line[15:20]) except (ValueError, TypeError): errmsg = ( - f"Couldn't read the following line of the .gro file:\n" - f"{line}") + f"Couldn't read the following line of the .gro file:\n" + f"{line}" + ) raise IOError(errmsg) from None # Check all lines had names if not np.all(names): - missing = np.where(names == '') - raise IOError("Missing atom name on line: {0}" - "".format(missing[0][0] + 3)) # 2 header, 1 based + missing = np.where(names == "") + raise IOError( + "Missing atom name on line: {0}" "".format(missing[0][0] + 3) + ) # 2 header, 1 based # Fix wrapping of resids (if we ever saw a wrap) if np.any(resids == 0): @@ -132,9 +135,9 @@ def parse(self, **kwargs): for s in starts: resids[s:] += 100000 - residx, (new_resids, new_resnames) = change_squash( - (resids, resnames), (resids, resnames)) + (resids, resnames), (resids, resnames) + ) # new_resids is len(residues) # so resindex 0 has resid new_resids[0] @@ -144,12 +147,16 @@ def parse(self, **kwargs): Resids(new_resids), Resnums(new_resids.copy()), Resnames(new_resnames), - Segids(np.array(['SYSTEM'], dtype=object)) + Segids(np.array(["SYSTEM"], dtype=object)), ] - top = Topology(n_atoms=n_atoms, n_res=len(new_resids), n_seg=1, - attrs=attrs, - atom_resindex=residx, - residue_segindex=None) + top = Topology( + n_atoms=n_atoms, + n_res=len(new_resids), + n_seg=1, + attrs=attrs, + atom_resindex=residx, + residue_segindex=None, + ) return top diff --git a/package/MDAnalysis/topology/GSDParser.py b/package/MDAnalysis/topology/GSDParser.py index 64746dd87ef..45f7e570b07 100644 --- a/package/MDAnalysis/topology/GSDParser.py +++ b/package/MDAnalysis/topology/GSDParser.py @@ -104,13 +104,16 @@ class GSDParser(TopologyReaderBase): GSD file. """ - format = 'GSD' + + format = "GSD" def __init__(self, filename): if not HAS_GSD: - errmsg = ("GSDParser: To read a Topology from a Hoomd GSD " - "file, please install gsd") + errmsg = ( + "GSDParser: To read a Topology from a Hoomd GSD " + "file, please install gsd" + ) raise ImportError(errmsg) super(GSDParser, self).__init__(filename) @@ -121,35 +124,39 @@ def parse(self, **kwargs): """ attrs = {} - with gsd.hoomd.open(self.filename, mode='r') as t : + with gsd.hoomd.open(self.filename, mode="r") as t: # Here it is assumed that the particle data does not change in the # trajectory. snap = t[0] natoms = snap.particles.N - ptypes = snap.particles.types atypes = [ptypes[idx] for idx in snap.particles.typeid] if len(atypes) != natoms: raise IOError("Number of types does not equal natoms.") - attrs['types'] = Atomtypes(np.array(atypes, dtype=object)) + attrs["types"] = Atomtypes(np.array(atypes, dtype=object)) # set radii, masses, charges p = snap.particles - attrs['diameter'] = Radii(np.array(p.diameter / 2.,dtype=np.float32)) - attrs['mass'] = Masses(np.array(p.mass,dtype=np.float64)) - attrs['charge'] = Charges(np.array(p.charge,dtype=np.float32)) + attrs["diameter"] = Radii( + np.array(p.diameter / 2.0, dtype=np.float32) + ) + attrs["mass"] = Masses(np.array(p.mass, dtype=np.float64)) + attrs["charge"] = Charges(np.array(p.charge, dtype=np.float32)) # set bonds, angles, dihedrals, impropers - for attrname, attr, in ( - ('bonds', Bonds), - ('angles', Angles), - ('dihedrals', Dihedrals), - ('impropers', Impropers), + for ( + attrname, + attr, + ) in ( + ("bonds", Bonds), + ("angles", Angles), + ("dihedrals", Dihedrals), + ("impropers", Impropers), ): try: - val = getattr(snap,attrname) + val = getattr(snap, attrname) vals = [tuple(b_instance) for b_instance in val.group] except: vals = [] @@ -160,7 +167,7 @@ def parse(self, **kwargs): bodies = np.unique(blist).astype(np.int32) # this fixes the fact that the Topology constructor gets stuck in an # infinite loop if any resid is negative. - if (blist<0).any() : + if (blist < 0).any(): m = blist.min() blist += abs(m) bodies = np.unique(blist).astype(np.int32) @@ -172,9 +179,8 @@ def parse(self, **kwargs): attrs.append(Resids(bodies)) attrs.append(Resnums(bodies)) attrs.append(Resnames(bodies)) - attrs.append(Segids(np.array(['SYSTEM'], dtype=object))) + attrs.append(Segids(np.array(["SYSTEM"], dtype=object))) - top = Topology(natoms, nbodies, 1, - attrs=attrs, atom_resindex=blist) + top = Topology(natoms, nbodies, 1, attrs=attrs, atom_resindex=blist) return top diff --git a/package/MDAnalysis/topology/HoomdXMLParser.py b/package/MDAnalysis/topology/HoomdXMLParser.py index b64e91de2ff..7b57bb59002 100644 --- a/package/MDAnalysis/topology/HoomdXMLParser.py +++ b/package/MDAnalysis/topology/HoomdXMLParser.py @@ -81,7 +81,8 @@ class HoomdXMLParser(TopologyReaderBase): - Masses """ - format = 'XML' + + format = "XML" def parse(self, **kwargs): """Parse Hoomd XML file @@ -102,21 +103,21 @@ def parse(self, **kwargs): with openany(self.filename) as stream: tree = ET.parse(stream) root = tree.getroot() - configuration = root.find('configuration') - natoms = int(configuration.get('natoms')) + configuration = root.find("configuration") + natoms = int(configuration.get("natoms")) attrs = {} - atype = configuration.find('type') - atypes = atype.text.strip().split('\n') + atype = configuration.find("type") + atypes = atype.text.strip().split("\n") if len(atypes) != natoms: raise IOError("Number of types does not equal natoms.") - attrs['types'] = Atomtypes(np.array(atypes, dtype=object)) + attrs["types"] = Atomtypes(np.array(atypes, dtype=object)) for attrname, attr, mapper, dtype in ( - ('diameter', Radii, lambda x: float(x) / 2., np.float32), - ('mass', Masses, float, np.float64), - ('charge', Charges, float, np.float32), + ("diameter", Radii, lambda x: float(x) / 2.0, np.float32), + ("mass", Masses, float, np.float64), + ("charge", Charges, float, np.float32), ): try: val = configuration.find(attrname) @@ -125,34 +126,38 @@ def parse(self, **kwargs): pass else: attrs[attrname] = attr(np.array(vals, dtype=dtype)) - for attrname, attr, in ( - ('bond', Bonds), - ('angle', Angles), - ('dihedral', Dihedrals), - ('improper', Impropers), + for ( + attrname, + attr, + ) in ( + ("bond", Bonds), + ("angle", Angles), + ("dihedral", Dihedrals), + ("improper", Impropers), ): try: val = configuration.find(attrname) - vals = [tuple(int(el) for el in line.split()[1:]) - for line in val.text.strip().split('\n') - if line.strip()] + vals = [ + tuple(int(el) for el in line.split()[1:]) + for line in val.text.strip().split("\n") + if line.strip() + ] except: vals = [] attrs[attrname] = attr(vals) - if 'mass' not in attrs: - attrs['mass'] = Masses(np.zeros(natoms)) - if 'charge' not in attrs: - attrs['charge'] = Charges(np.zeros(natoms, dtype=np.float32)) + if "mass" not in attrs: + attrs["mass"] = Masses(np.zeros(natoms)) + if "charge" not in attrs: + attrs["charge"] = Charges(np.zeros(natoms, dtype=np.float32)) attrs = list(attrs.values()) attrs.append(Atomids(np.arange(natoms) + 1)) attrs.append(Resids(np.array([1]))) attrs.append(Resnums(np.array([1]))) - attrs.append(Segids(np.array(['SYSTEM'], dtype=object))) + attrs.append(Segids(np.array(["SYSTEM"], dtype=object))) - top = Topology(natoms, 1, 1, - attrs=attrs) + top = Topology(natoms, 1, 1, attrs=attrs) return top diff --git a/package/MDAnalysis/topology/ITPParser.py b/package/MDAnalysis/topology/ITPParser.py index 9968f854df7..1aa87b3864b 100644 --- a/package/MDAnalysis/topology/ITPParser.py +++ b/package/MDAnalysis/topology/ITPParser.py @@ -162,8 +162,9 @@ class Chargegroups(AtomAttr): """The charge group for each Atom""" - attrname = 'chargegroups' - singular = 'chargegroup' + + attrname = "chargegroups" + singular = "chargegroup" class GmxTopIterator: @@ -197,17 +198,17 @@ def iter_from_file(self, path): self.file_stack.append(infile) for line in self.clean_file_lines(infile): - if line.startswith('#include'): + if line.startswith("#include"): inc = line.split(None, 1)[1][1:-1] for line in self.iter_from_file(inc): yield line - elif line.startswith('#define'): + elif line.startswith("#define"): self.define(line) - elif line.startswith('#if'): + elif line.startswith("#if"): self.do_if(line, infile) - elif line.startswith('#else'): + elif line.startswith("#else"): self.skip_until_endif(infile) - elif line.startswith('#'): # ignore #if and others + elif line.startswith("#"): # ignore #if and others pass elif line: line = self.substitute_defined(line) @@ -230,42 +231,42 @@ def substitute_defined(self, line): for k, v in self.defines.items(): if k in split: split[split.index(k)] = str(v) - line = ' '.join(split) + line = " ".join(split) return line def clean_file_lines(self, infile): for line in infile: - line = line.split(';')[0].strip() # ; is for comments + line = line.split(";")[0].strip() # ; is for comments yield line def do_if(self, line, infile): ifdef, variable = line.split() - if ifdef == '#ifdef': + if ifdef == "#ifdef": if self.defines.get(variable) in (False, None): self.skip_until_else(infile) - elif ifdef == '#ifndef': + elif ifdef == "#ifndef": if self.defines.get(variable) not in (False, None): self.skip_until_else(infile) def skip_until_else(self, infile): """Skip lines until #if condition ends""" for line in self.clean_file_lines(infile): - if line.startswith('#if'): + if line.startswith("#if"): self.skip_until_endif(infile) - elif line.startswith('#endif') or line.startswith('#else'): + elif line.startswith("#endif") or line.startswith("#else"): break else: - raise IOError('Missing #endif in {}'.format(self.current_file)) + raise IOError("Missing #endif in {}".format(self.current_file)) def skip_until_endif(self, infile): """Skip lines until #endif""" for line in self.clean_file_lines(infile): - if line.startswith('#if'): + if line.startswith("#if"): self.skip_until_endif(infile) - elif line.startswith('#endif'): + elif line.startswith("#endif"): break else: - raise IOError('Missing #endif in {}'.format(self.current_file)) + raise IOError("Missing #endif in {}".format(self.current_file)) def find_path(self, path): try: @@ -285,7 +286,7 @@ def find_path(self, path): include_path = os.path.join(self.include_dir, path) if os.path.exists(include_path): return include_path - raise IOError('Could not find {}'.format(path)) + raise IOError("Could not find {}".format(path)) class Molecule: @@ -308,58 +309,78 @@ def __init__(self, name): self.impropers = defaultdict(list) self.parsers = { - 'atoms': self.parse_atoms, - 'bonds': self.parse_bonds, - 'angles': self.parse_angles, - 'dihedrals': self.parse_dihedrals, - 'constraints': self.parse_constraints, - 'settles': self.parse_settles + "atoms": self.parse_atoms, + "bonds": self.parse_bonds, + "angles": self.parse_angles, + "dihedrals": self.parse_dihedrals, + "constraints": self.parse_constraints, + "settles": self.parse_settles, } self.resolved_residue_attrs = False @property def atom_order(self): - return [self.ids, self.types, self.resids, self.resnames, - self.names, self.chargegroups, self.charges, - self.masses] + return [ + self.ids, + self.types, + self.resids, + self.resnames, + self.names, + self.chargegroups, + self.charges, + self.masses, + ] @property def params(self): return [self.bonds, self.angles, self.dihedrals, self.impropers] - + def parse_atoms(self, line): values = line.split() for lst in self.atom_order: try: lst.append(values.pop(0)) except IndexError: # ran out of values - lst.append('') - + lst.append("") + def parse_bonds(self, line): - self.add_param(line, self.bonds, n_funct=2, - funct_values=(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + self.add_param( + line, + self.bonds, + n_funct=2, + funct_values=(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), + ) def parse_angles(self, line): - self.add_param(line, self.angles, n_funct=3, - funct_values=(1, 2, 3, 4, 5, 6, 8, 10)) - + self.add_param( + line, + self.angles, + n_funct=3, + funct_values=(1, 2, 3, 4, 5, 6, 8, 10), + ) + def parse_dihedrals(self, line): - dih = self.add_param(line, self.dihedrals, n_funct=4, - funct_values=(1, 3, 5, 8, 9, 10, 11)) + dih = self.add_param( + line, + self.dihedrals, + n_funct=4, + funct_values=(1, 3, 5, 8, 9, 10, 11), + ) if not dih: - self.add_param(line, self.impropers, n_funct=4, - funct_values=(2, 4)) + self.add_param( + line, self.impropers, n_funct=4, funct_values=(2, 4) + ) def parse_constraints(self, line): self.add_param(line, self.bonds, n_funct=2, funct_values=(1, 2)) def parse_settles(self, line): - # [ settles ] is a triangular constraint for + # [ settles ] is a triangular constraint for # water molecules. - # In ITP files this is defined with only the - # oxygen atom index. The next two atoms are - # assumed to be hydrogens. Unlike TPRParser, + # In ITP files this is defined with only the + # oxygen atom index. The next two atoms are + # assumed to be hydrogens. Unlike TPRParser, # the manual only lists this format (as of 2019). # These are treated as 2 bonds. # No angle component is included to avoid discrepancies @@ -370,21 +391,25 @@ def parse_settles(self, line): except ValueError: pass else: - self.bonds[(base, base+1)].append("settles") - self.bonds[(base, base+2)].append("settles") + self.bonds[(base, base + 1)].append("settles") + self.bonds[(base, base + 2)].append("settles") def resolve_residue_attrs(self): """Figure out residue borders and assign moltypes and molnums""" resids = np.array(self.resids, dtype=np.int32) resnames = np.array(self.resnames, dtype=object) - self.residx, (self.resids, resnames) = change_squash((resids,), (resids, resnames)) + self.residx, (self.resids, resnames) = change_squash( + (resids,), (resids, resnames) + ) self.resnames = list(resnames) self.moltypes = [self.name] * len(self.resids) self.molnums = np.array([1] * len(self.resids)) self.resolved_residue_attrs = True - def shift_indices(self, atomid=0, resid=0, molnum=0, cgnr=0, n_res=0, n_atoms=0): + def shift_indices( + self, atomid=0, resid=0, molnum=0, cgnr=0, n_res=0, n_atoms=0 + ): """ Get attributes ready for adding onto a larger topology. @@ -405,26 +430,33 @@ def shift_indices(self, atomid=0, resid=0, molnum=0, cgnr=0, n_res=0, n_atoms=0) if not self.resolved_residue_attrs: self.resolve_residue_attrs() - resids = list(np.array(self.resids)+resid) - residx = list(np.array(self.residx)+n_res) + resids = list(np.array(self.resids) + resid) + residx = list(np.array(self.residx) + n_res) molnums = list(np.array(self.molnums) + molnum) ids = list(np.array(self.ids, dtype=int) + atomid) try: cg = np.array(self.chargegroups, dtype=int) except ValueError: - cg = np.arange(1, len(self.chargegroups)+1) - chargegroups = list(cg+cgnr) - - atom_order = [ids, self.types, resids, self.resnames, - self.names, chargegroups, self.charges, - self.masses] + cg = np.arange(1, len(self.chargegroups) + 1) + chargegroups = list(cg + cgnr) + + atom_order = [ + ids, + self.types, + resids, + self.resnames, + self.names, + chargegroups, + self.charges, + self.masses, + ] new_params = [] for p in self.params: new = {} for indices, values in p.items(): - new[tuple(np.array(indices)+n_atoms)] = values + new[tuple(np.array(indices) + n_atoms)] = values new_params.append(new) return atom_order, new_params, molnums, self.moltypes, residx @@ -491,11 +523,15 @@ class ITPParser(TopologyReaderBase): mass guessing behavior """ - format = 'ITP' - def parse(self, include_dir='/usr/local/gromacs/share/gromacs/top/', - infer_system=True, - **kwargs): + format = "ITP" + + def parse( + self, + include_dir="/usr/local/gromacs/share/gromacs/top/", + infer_system=True, + **kwargs, + ): """Parse ITP file into Topology Parameters @@ -503,13 +539,13 @@ def parse(self, include_dir='/usr/local/gromacs/share/gromacs/top/', include_dir: str, optional A directory in which to look for other files included from the original file, if the files are not first found - in the current directory. + in the current directory. Default: "/usr/local/gromacs/share/gromacs/top/" infer_system: bool, optional (default True) If a ``[ molecules ]`` directive is not found within the the - topology file, create a Topology with one of every - ``[ moleculetype ]`` defined. If a ``[ molecules ]`` directive is + topology file, create a Topology with one of every + ``[ moleculetype ]`` defined. If a ``[ molecules ]`` directive is found, this keyword is ignored. Returns @@ -522,30 +558,32 @@ def parse(self, include_dir='/usr/local/gromacs/share/gromacs/top/', self._molecules = [] # for order self.current_mol = None self.parser = self._pass - self.system_molecules = [] + self.system_molecules = [] # Open and check itp validity with openany(self.filename) as itpfile: self.lines = GmxTopIterator(itpfile, include_dir, kwargs) for line in self.lines: - if '[' in line and ']' in line: - section = line.split('[')[1].split(']')[0].strip() + if "[" in line and "]" in line: + section = line.split("[")[1].split("]")[0].strip() - if section == 'atomtypes': + if section == "atomtypes": self.parser = self.parse_atomtypes - elif section == 'moleculetype': + elif section == "moleculetype": self.parser = self.parse_moleculetype - elif section == 'molecules': + elif section == "molecules": self.parser = self.parse_molecules - + elif self.current_mol: - self.parser = self.current_mol.parsers.get(section, self._pass) + self.parser = self.current_mol.parsers.get( + section, self._pass + ) else: self.parser = self._pass - + else: self.parser(line) @@ -559,23 +597,23 @@ def parse(self, include_dir='/usr/local/gromacs/share/gromacs/top/', self.masses = np.array(self.masses, dtype=object) if not all(self.charges): - empty = self.charges == '' + empty = self.charges == "" self.charges[empty] = [ ( self.atomtypes.get(x)["charge"] if x in self.atomtypes.keys() - else '' + else "" ) for x in self.types[empty] ] if not all(self.masses): - empty = self.masses == '' + empty = self.masses == "" self.masses[empty] = [ ( self.atomtypes.get(x)["mass"] if x in self.atomtypes.keys() - else '' + else "" ) for x in self.types[empty] ] @@ -593,16 +631,18 @@ def parse(self, include_dir='/usr/local/gromacs/share/gromacs/top/', attrs.append(Attr(np.array(vals, dtype=dtype))) if not all(self.masses): - empty = self.masses == '' + empty = self.masses == "" self.masses[empty] = Masses.missing_value_label - attrs.append(Masses(np.array(self.masses, dtype=np.float64), - guessed=False)) + attrs.append( + Masses(np.array(self.masses, dtype=np.float64), guessed=False) + ) self.elements = DefaultGuesser(None).guess_types(self.types) if all(e.capitalize() in SYMB2Z for e in self.elements): - attrs.append(Elements(np.array(self.elements, - dtype=object), guessed=True)) + attrs.append( + Elements(np.array(self.elements, dtype=object), guessed=True) + ) warnings.warn( "The elements attribute has been populated by guessing " "elements from atom types. This behaviour has been " @@ -610,13 +650,15 @@ def parse(self, include_dir='/usr/local/gromacs/share/gromacs/top/', "to the new guessing API. " "This behavior will be removed in release 3.0. " "Please see issue #4698 for more information. ", - DeprecationWarning + DeprecationWarning, ) else: - warnings.warn("Element information is missing, elements attribute " - "will not be populated. If needed these can be " - "guessed using universe.guess_TopologyAttrs(" - "to_guess=['elements']).") + warnings.warn( + "Element information is missing, elements attribute " + "will not be populated. If needed these can be " + "guessed using universe.guess_TopologyAttrs(" + "to_guess=['elements'])." + ) # residue stuff resids = np.array(self.resids, dtype=np.int32) @@ -627,24 +669,28 @@ def parse(self, include_dir='/usr/local/gromacs/share/gromacs/top/', attrs.append(Resnames(resnames)) attrs.append(Moltypes(np.array(self.moltypes, dtype=object))) attrs.append(Molnums(molnums)) - + n_atoms = len(self.ids) n_residues = len(self.resids) n_segments = len(self.system_molecules) attrs.append(Segids(np.array(self.system_molecules, dtype=object))) - segidx = molnums-1 + segidx = molnums - 1 - top = Topology(n_atoms, n_residues, n_segments, - attrs=attrs, - atom_resindex=self.residx, - residue_segindex=segidx) + top = Topology( + n_atoms, + n_residues, + n_segments, + attrs=attrs, + atom_resindex=self.residx, + residue_segindex=segidx, + ) # connectivity stuff for dct, Attr, attrname in ( - (self.bonds, Bonds, 'bonds'), - (self.angles, Angles, 'angles'), - (self.dihedrals, Dihedrals, 'dihedrals'), - (self.impropers, Impropers, 'impropers') + (self.bonds, Bonds, "bonds"), + (self.angles, Angles, "angles"), + (self.dihedrals, Dihedrals, "dihedrals"), + (self.impropers, Impropers, "impropers"), ): if dct: indices, types = zip(*list(dct.items())) @@ -652,7 +698,7 @@ def parse(self, include_dir='/usr/local/gromacs/share/gromacs/top/', indices, types = [], [] types = [reduce_singular(t) for t in types] - + tattr = Attr(indices, types=types) top.add_TopologyAttr(tattr) @@ -662,17 +708,17 @@ def _pass(self, line): pass def parse_atomtypes(self, line): - keys = ['type_bonded', 'atomic_number', 'mass', 'charge', 'p_type'] + keys = ["type_bonded", "atomic_number", "mass", "charge", "p_type"] fields = line.split() if len(fields[5]) == 1 and fields[5].isalpha(): values = fields[1:6] elif len(fields[3]) == 1 and fields[3].isalpha(): - values = '', '', fields[1], fields[2], fields[3] + values = "", "", fields[1], fields[2], fields[3] elif len(fields[4]) == 1 and fields[4].isalpha(): if fields[1][0].isalpha(): - values = fields[1], '', fields[2], fields[3], fields[4] + values = fields[1], "", fields[2], fields[3], fields[4] else: - values = '', fields[1], fields[2], fields[3], fields[4] + values = "", fields[1], fields[2], fields[3], fields[4] self.atomtypes[fields[0]] = dict(zip(keys, values)) def parse_moleculetype(self, line): @@ -682,7 +728,7 @@ def parse_moleculetype(self, line): def parse_molecules(self, line): name, n_mol = line.split() - self.system_molecules.extend([name]*int(n_mol)) + self.system_molecules.extend([name] * int(n_mol)) def build_system(self): self.ids = [] @@ -697,9 +743,16 @@ def build_system(self): self.molnums = [] self.residx = [] - self.atom_order = [self.ids, self.types, self.resids, self.resnames, - self.names, self.chargegroups, self.charges, - self.masses] + self.atom_order = [ + self.ids, + self.types, + self.resids, + self.resnames, + self.names, + self.chargegroups, + self.charges, + self.masses, + ] self.bonds = defaultdict(list) self.angles = defaultdict(list) @@ -717,9 +770,14 @@ def build_system(self): n_res = len(self.resids) n_atoms = len(self.ids) - shifted = mol.shift_indices(atomid=atomid, resid=resid, - n_res=n_res, cgnr=cgnr, molnum=i, - n_atoms=n_atoms) + shifted = mol.shift_indices( + atomid=atomid, + resid=resid, + n_res=n_res, + cgnr=cgnr, + molnum=i, + n_atoms=n_atoms, + ) atom_order, params, molnums, moltypes, residx = shifted for system_attr, mol_attr in zip(self.atom_order, atom_order): diff --git a/package/MDAnalysis/topology/LAMMPSParser.py b/package/MDAnalysis/topology/LAMMPSParser.py index 2f2ef6ac94a..c7cdcea30b1 100644 --- a/package/MDAnalysis/topology/LAMMPSParser.py +++ b/package/MDAnalysis/topology/LAMMPSParser.py @@ -110,34 +110,36 @@ # Sections will all start with one of these words # and run until the next section title -SECTIONS = set([ - 'Atoms', # Molecular topology sections - 'Velocities', - 'Masses', - 'Ellipsoids', - 'Lines', - 'Triangles', - 'Bodies', - 'Bonds', # Forcefield sections - 'Angles', - 'Dihedrals', - 'Impropers', - 'Pair', - 'Pair LJCoeffs', - 'PairIJ Coeffs', - 'Bond Coeffs', - 'Angle Coeffs', - 'Dihedral Coeffs', - 'Improper Coeffs', - 'BondBond Coeffs', # Class 2 FF sections - 'BondAngle Coeffs', - 'MiddleBondTorsion Coeffs', - 'EndBondTorsion Coeffs', - 'AngleTorsion Coeffs', - 'AngleAngleTorsion Coeffs', - 'BondBond13 Coeffs', - 'AngleAngle Coeffs', -]) +SECTIONS = set( + [ + "Atoms", # Molecular topology sections + "Velocities", + "Masses", + "Ellipsoids", + "Lines", + "Triangles", + "Bodies", + "Bonds", # Forcefield sections + "Angles", + "Dihedrals", + "Impropers", + "Pair", + "Pair LJCoeffs", + "PairIJ Coeffs", + "Bond Coeffs", + "Angle Coeffs", + "Dihedral Coeffs", + "Improper Coeffs", + "BondBond Coeffs", # Class 2 FF sections + "BondAngle Coeffs", + "MiddleBondTorsion Coeffs", + "EndBondTorsion Coeffs", + "AngleTorsion Coeffs", + "AngleAngleTorsion Coeffs", + "BondBond13 Coeffs", + "AngleAngle Coeffs", + ] +) # We usually check by splitting around whitespace, so check # if any SECTION keywords will trip up on this # and add them @@ -146,31 +148,33 @@ SECTIONS.add(val.split()[0]) -HEADERS = set([ - 'atoms', - 'bonds', - 'angles', - 'dihedrals', - 'impropers', - 'atom types', - 'bond types', - 'angle types', - 'dihedral types', - 'improper types', - 'extra bond per atom', - 'extra angle per atom', - 'extra dihedral per atom', - 'extra improper per atom', - 'extra special per atom', - 'ellipsoids', - 'lines', - 'triangles', - 'bodies', - 'xlo xhi', - 'ylo yhi', - 'zlo zhi', - 'xy xz yz', -]) +HEADERS = set( + [ + "atoms", + "bonds", + "angles", + "dihedrals", + "impropers", + "atom types", + "bond types", + "angle types", + "dihedral types", + "improper types", + "extra bond per atom", + "extra angle per atom", + "extra dihedral per atom", + "extra improper per atom", + "extra special per atom", + "ellipsoids", + "lines", + "triangles", + "bodies", + "xlo xhi", + "ylo yhi", + "zlo zhi", + "xy xz yz", + ] +) class DATAParser(TopologyReaderBase): @@ -192,12 +196,13 @@ class as the topology and coordinate reader share many common through universe.guess_TopologyAttrs() API). """ - format = 'DATA' + + format = "DATA" def iterdata(self): with openany(self.filename) as f: for line in f: - line = line.partition('#')[0].strip() + line = line.partition("#")[0].strip() if line: yield line @@ -211,19 +216,19 @@ def grab_datafile(self): """ f = list(self.iterdata()) - starts = [i for i, line in enumerate(f) - if line.split()[0] in SECTIONS] + starts = [i for i, line in enumerate(f) if line.split()[0] in SECTIONS] starts += [None] header = {} - for line in f[:starts[0]]: + for line in f[: starts[0]]: for token in HEADERS: if line.endswith(token): header[token] = line.split(token)[0] continue - sects = {f[l]:f[l+1:starts[i+1]] - for i, l in enumerate(starts[:-1])} + sects = { + f[l]: f[l + 1 : starts[i + 1]] for i, l in enumerate(starts[:-1]) + } return header, sects @@ -248,7 +253,7 @@ def _interpret_atom_style(atom_style): atom_style = atom_style.split() - for attr in ['id', 'type', 'resid', 'charge', 'x', 'y', 'z']: + for attr in ["id", "type", "resid", "charge", "x", "y", "z"]: try: location = atom_style.index(attr) except ValueError: @@ -256,11 +261,13 @@ def _interpret_atom_style(atom_style): else: style_dict[attr] = location - reqd_attrs = ['id', 'type', 'x', 'y', 'z'] + reqd_attrs = ["id", "type", "x", "y", "z"] missing_attrs = [attr for attr in reqd_attrs if attr not in style_dict] if missing_attrs: - raise ValueError("atom_style string missing required field(s): {}" - "".format(', '.join(missing_attrs))) + raise ValueError( + "atom_style string missing required field(s): {}" + "".format(", ".join(missing_attrs)) + ) return style_dict @@ -273,40 +280,43 @@ def parse(self, **kwargs): """ # Can pass atom_style to help parsing try: - self.style_dict = self._interpret_atom_style(kwargs['atom_style']) + self.style_dict = self._interpret_atom_style(kwargs["atom_style"]) except KeyError: self.style_dict = None head, sects = self.grab_datafile() try: - masses = self._parse_masses(sects['Masses']) + masses = self._parse_masses(sects["Masses"]) except KeyError: masses = None - if 'Atoms' not in sects: + if "Atoms" not in sects: raise ValueError("Data file was missing Atoms section") try: - top = self._parse_atoms(sects['Atoms'], masses) + top = self._parse_atoms(sects["Atoms"], masses) except Exception: errmsg = ( "Failed to parse atoms section. You can supply a description " "of the atom_style as a keyword argument, " - "eg mda.Universe(..., atom_style='id resid x y z')") + "eg mda.Universe(..., atom_style='id resid x y z')" + ) raise ValueError(errmsg) from None # create mapping of id to index (ie atom id 10 might be the 0th atom) mapping = {atom_id: i for i, atom_id in enumerate(top.ids.values)} for attr, L, nentries in [ - (Bonds, 'Bonds', 2), - (Angles, 'Angles', 3), - (Dihedrals, 'Dihedrals', 4), - (Impropers, 'Impropers', 4) + (Bonds, "Bonds", 2), + (Angles, "Angles", 3), + (Dihedrals, "Dihedrals", 4), + (Impropers, "Impropers", 4), ]: try: - type, sect = self._parse_bond_section(sects[L], nentries, mapping) + type, sect = self._parse_bond_section( + sects[L], nentries, mapping + ) except KeyError: type, sect = [], [] @@ -314,8 +324,9 @@ def parse(self, **kwargs): return top - def read_DATA_timestep(self, n_atoms, TS_class, TS_kwargs, - atom_style=None): + def read_DATA_timestep( + self, n_atoms, TS_class, TS_kwargs, atom_style=None + ): """Read a DATA file and try and extract x, v, box. - positions @@ -338,19 +349,19 @@ def read_DATA_timestep(self, n_atoms, TS_class, TS_kwargs, unitcell = self._parse_box(header) try: - positions, ordering = self._parse_pos(sects['Atoms']) + positions, ordering = self._parse_pos(sects["Atoms"]) except KeyError as err: errmsg = f"Position information not found: {err}" raise IOError(errmsg) from None - if 'Velocities' in sects: - velocities = self._parse_vel(sects['Velocities'], ordering) + if "Velocities" in sects: + velocities = self._parse_vel(sects["Velocities"], ordering) else: velocities = None - ts = TS_class.from_coordinates(positions, - velocities=velocities, - **TS_kwargs) + ts = TS_class.from_coordinates( + positions, velocities=velocities, **TS_kwargs + ) ts.dimensions = unitcell return ts @@ -365,20 +376,22 @@ def _parse_pos(self, datalines): if self.style_dict is None: if len(datalines[0].split()) in (7, 10): - style_dict = {'id': 0, 'x': 4, 'y': 5, 'z': 6} + style_dict = {"id": 0, "x": 4, "y": 5, "z": 6} else: - style_dict = {'id': 0, 'x': 3, 'y': 4, 'z': 5} + style_dict = {"id": 0, "x": 3, "y": 4, "z": 5} else: style_dict = self.style_dict for i, line in enumerate(datalines): line = line.split() - ids[i] = line[style_dict['id']] + ids[i] = line[style_dict["id"]] - pos[i, :] = [line[style_dict['x']], - line[style_dict['y']], - line[style_dict['z']]] + pos[i, :] = [ + line[style_dict["x"]], + line[style_dict["y"]], + line[style_dict["z"]], + ] order = np.argsort(ids) pos = pos[order] @@ -435,7 +448,9 @@ def _parse_bond_section(self, datalines, nentries, mapping): for line in datalines: line = line.split() # map to 0 based int - section.append(tuple([mapping[int(x)] for x in line[2:2 + nentries]])) + section.append( + tuple([mapping[int(x)] for x in line[2 : 2 + nentries]]) + ) type.append(line[1]) return tuple(type), tuple(section) @@ -471,18 +486,16 @@ def _parse_atoms(self, datalines, massdict=None): n_atoms = len(datalines) if self.style_dict is None: - sd = {'id': 0, - 'resid': 1, - 'type': 2} + sd = {"id": 0, "resid": 1, "type": 2} # Fields per line n = len(datalines[0].split()) if n in (7, 10): - sd['charge'] = 3 + sd["charge"] = 3 else: sd = self.style_dict - has_charge = 'charge' in sd - has_resid = 'resid' in sd + has_charge = "charge" in sd + has_resid = "resid" in sd # atom ids aren't necessarily sequential atom_ids = np.zeros(n_atoms, dtype=np.int32) @@ -500,12 +513,12 @@ def _parse_atoms(self, datalines, massdict=None): # these numpy array are already typed correctly, # so just pass the raw strings # and let numpy handle the conversion - atom_ids[i] = line[sd['id']] + atom_ids[i] = line[sd["id"]] if has_resid: - resids[i] = line[sd['resid']] - types[i] = line[sd['type']] + resids[i] = line[sd["resid"]] + types[i] = line[sd["type"]] if has_charge: - charges[i] = line[sd['charge']] + charges[i] = line[sd["charge"]] # at this point, we've read the atoms section, # but it's still (potentially) unordered @@ -536,11 +549,11 @@ def _parse_atoms(self, datalines, massdict=None): attrs.append(Atomids(atom_ids)) attrs.append(Resids(resids)) attrs.append(Resnums(resids.copy())) - attrs.append(Segids(np.array(['SYSTEM'], dtype=object))) + attrs.append(Segids(np.array(["SYSTEM"], dtype=object))) - top = Topology(n_atoms, n_residues, 1, - attrs=attrs, - atom_resindex=residx) + top = Topology( + n_atoms, n_residues, 1, attrs=attrs, atom_resindex=residx + ) return top @@ -559,18 +572,18 @@ def _parse_masses(self, datalines): return masses def _parse_box(self, header): - x1, x2 = np.float32(header['xlo xhi'].split()) + x1, x2 = np.float32(header["xlo xhi"].split()) x = x2 - x1 - y1, y2 = np.float32(header['ylo yhi'].split()) + y1, y2 = np.float32(header["ylo yhi"].split()) y = y2 - y1 - z1, z2 = np.float32(header['zlo zhi'].split()) + z1, z2 = np.float32(header["zlo zhi"].split()) z = z2 - z1 - if 'xy xz yz' in header: + if "xy xz yz" in header: # Triclinic unitcell = np.zeros((3, 3), dtype=np.float32) - xy, xz, yz = np.float32(header['xy xz yz'].split()) + xy, xz, yz = np.float32(header["xy xz yz"].split()) unitcell[0][0] = x unitcell[1][0] = xy @@ -584,7 +597,7 @@ def _parse_box(self, header): # Orthogonal unitcell = np.zeros(6, dtype=np.float32) unitcell[:3] = x, y, z - unitcell[3:] = 90., 90., 90. + unitcell[3:] = 90.0, 90.0, 90.0 return unitcell @@ -598,7 +611,8 @@ class LammpsDumpParser(TopologyReaderBase): .. versionchanged:: 2.0.0 .. versionadded:: 0.19.0 """ - format = 'LAMMPSDUMP' + + format = "LAMMPSDUMP" def parse(self, **kwargs): with openany(self.filename) as fin: @@ -634,17 +648,25 @@ def parse(self, **kwargs): attrs.append(Atomids(indices)) attrs.append(Atomtypes(types)) attrs.append(Masses(np.ones(natoms, dtype=np.float64), guessed=True)) - warnings.warn('Guessed all Masses to 1.0') + warnings.warn("Guessed all Masses to 1.0") attrs.append(Resids(np.array([1], dtype=int))) attrs.append(Resnums(np.array([1], dtype=int))) - attrs.append(Segids(np.array(['SYSTEM'], dtype=object))) + attrs.append(Segids(np.array(["SYSTEM"], dtype=object))) return Topology(natoms, 1, 1, attrs=attrs) @functools.total_ordering class LAMMPSAtom(object): # pragma: no cover - __slots__ = ("index", "name", "type", "chainid", "charge", "mass", "_positions") + __slots__ = ( + "index", + "name", + "type", + "chainid", + "charge", + "mass", + "_positions", + ) def __init__(self, index, name, type, chain_id, charge=0, mass=1): self.index = index @@ -655,8 +677,15 @@ def __init__(self, index, name, type, chain_id, charge=0, mass=1): self.mass = mass def __repr__(self): - return "" + return ( + "" + ) def __lt__(self, other): return self.index < other.index @@ -668,12 +697,22 @@ def __hash__(self): return hash(self.index) def __getattr__(self, attr): - if attr == 'pos': + if attr == "pos": return self._positions[self.index] else: super(LAMMPSAtom, self).__getattribute__(attr) def __iter__(self): pos = self.pos - return iter((self.index + 1, self.chainid, self.type, self.charge, - self.mass, pos[0], pos[1], pos[2])) + return iter( + ( + self.index + 1, + self.chainid, + self.type, + self.charge, + self.mass, + pos[0], + pos[1], + pos[2], + ) + ) diff --git a/package/MDAnalysis/topology/MMTFParser.py b/package/MDAnalysis/topology/MMTFParser.py index 3abc6a281cb..90af2f46d13 100644 --- a/package/MDAnalysis/topology/MMTFParser.py +++ b/package/MDAnalysis/topology/MMTFParser.py @@ -106,15 +106,15 @@ def _parse_mmtf(fn): - if fn.endswith('gz'): + if fn.endswith("gz"): return mmtf.parse_gzip(fn) else: return mmtf.parse(fn) class Models(SegmentAttr): - attrname = 'models' - singular = 'model' + attrname = "models" + singular = "model" transplants = defaultdict(list) def models(self): @@ -130,16 +130,16 @@ def models(self): """ model_ids = np.unique(self.segments.models) - return [self.select_atoms('model {}'.format(i)) - for i in model_ids] + return [self.select_atoms("model {}".format(i)) for i in model_ids] - transplants['Universe'].append( - ('models', property(models, None, None, models.__doc__))) + transplants["Universe"].append( + ("models", property(models, None, None, models.__doc__)) + ) class ModelSelection(RangeSelection): - token = 'model' - field = 'models' + token = "model" + field = "models" def apply(self, group): mask = np.zeros(len(group), dtype=bool) @@ -157,7 +157,7 @@ def apply(self, group): class MMTFParser(base.TopologyReaderBase): - format = 'MMTF' + format = "MMTF" @staticmethod def _format_hint(thing): @@ -168,9 +168,9 @@ def _format_hint(thing): return isinstance(thing, mmtf.MMTFDecoder) @due.dcite( - Doi('10.1371/journal.pcbi.1005575'), + Doi("10.1371/journal.pcbi.1005575"), description="MMTF Parser", - path='MDAnalysis.topology.MMTFParser', + path="MDAnalysis.topology.MMTFParser", ) def parse(self, **kwargs): if isinstance(self.filename, mmtf.MMTFDecoder): @@ -191,45 +191,58 @@ def iter_atoms(field): attrs = [] # required - charges = Charges(list(iter_atoms('formalChargeList'))) - names = Atomnames(list(iter_atoms('atomNameList'))) - types = Atomtypes(list(iter_atoms('elementList'))) + charges = Charges(list(iter_atoms("formalChargeList"))) + names = Atomnames(list(iter_atoms("atomNameList"))) + types = Atomtypes(list(iter_atoms("elementList"))) attrs.extend([charges, names, types]) - #optional are empty list if empty, sometimes arrays + # optional are empty list if empty, sometimes arrays if len(mtop.atom_id_list): attrs.append(Atomids(mtop.atom_id_list)) else: # must have this attribute for MDA attrs.append(Atomids(np.arange(natoms), guessed=True)) if mtop.alt_loc_list: - attrs.append(AltLocs([val.replace('\x00', '').strip() - for val in mtop.alt_loc_list])) + attrs.append( + AltLocs( + [ + val.replace("\x00", "").strip() + for val in mtop.alt_loc_list + ] + ) + ) else: - attrs.append(AltLocs(['']*natoms)) + attrs.append(AltLocs([""] * natoms)) if len(mtop.b_factor_list): attrs.append(Tempfactors(mtop.b_factor_list)) else: - attrs.append(Tempfactors([0]*natoms)) + attrs.append(Tempfactors([0] * natoms)) if len(mtop.occupancy_list): attrs.append(Occupancies(mtop.occupancy_list)) else: - attrs.append(Occupancies([1]*natoms)) + attrs.append(Occupancies([1] * natoms)) # Residue things # required resids = Resids(mtop.group_id_list) resnums = Resnums(resids.values.copy()) - resnames = Resnames([mtop.group_list[i]['groupName'] - for i in mtop.group_type_list]) + resnames = Resnames( + [mtop.group_list[i]["groupName"] for i in mtop.group_type_list] + ) attrs.extend([resids, resnums, resnames]) # optional # mmtf empty icode is '\x00' rather than '' if mtop.ins_code_list: - attrs.append(ICodes([val.replace('\x00', '').strip() - for val in mtop.ins_code_list])) + attrs.append( + ICodes( + [ + val.replace("\x00", "").strip() + for val in mtop.ins_code_list + ] + ) + ) else: - attrs.append(ICodes(['']*nresidues)) + attrs.append(ICodes([""] * nresidues)) # Segment things # optional @@ -237,18 +250,21 @@ def iter_atoms(field): attrs.append(Segids(mtop.chain_name_list)) else: # required for MDAnalysis - attrs.append(Segids(['SYSTEM'] * nsegments, guessed=True)) + attrs.append(Segids(["SYSTEM"] * nsegments, guessed=True)) mods = np.repeat(np.arange(mtop.num_models), mtop.chains_per_model) attrs.append(Models(mods)) - #attrs.append(chainids) + # attrs.append(chainids) # number of atoms in a given group id - groupID_2_natoms = {i:len(g['atomNameList']) - for i, g in enumerate(mtop.group_list)} + groupID_2_natoms = { + i: len(g["atomNameList"]) for i, g in enumerate(mtop.group_list) + } # mapping of atoms to residues - resindex = np.repeat(np.arange(nresidues), - [groupID_2_natoms[i] for i in mtop.group_type_list]) + resindex = np.repeat( + np.arange(nresidues), + [groupID_2_natoms[i] for i in mtop.group_type_list], + ) # mapping of residues to segments segindex = np.repeat(np.arange(nsegments), mtop.groups_per_chain) @@ -260,7 +276,7 @@ def iter_atoms(field): bonds = [] for gtype in mtop.group_type_list: g = mtop.group_list[gtype] - bondlist = g['bondAtomList'] + bondlist = g["bondAtomList"] for x, y in zip(bondlist[1::2], bondlist[::2]): if x > y: @@ -270,16 +286,21 @@ def iter_atoms(field): offset += groupID_2_natoms[gtype] # add inter group bonds if not mtop.bond_atom_list is None: # optional field - for x, y in zip(mtop.bond_atom_list[1::2], - mtop.bond_atom_list[::2]): + for x, y in zip( + mtop.bond_atom_list[1::2], mtop.bond_atom_list[::2] + ): if x > y: x, y = y, x bonds.append((x, y)) attrs.append(Bonds(bonds)) - top = Topology(natoms, nresidues, nsegments, - atom_resindex=resindex, - residue_segindex=segindex, - attrs=attrs) + top = Topology( + natoms, + nresidues, + nsegments, + atom_resindex=resindex, + residue_segindex=segindex, + attrs=attrs, + ) return top diff --git a/package/MDAnalysis/topology/MOL2Parser.py b/package/MDAnalysis/topology/MOL2Parser.py index 5c81e7346c6..114633c0c41 100644 --- a/package/MDAnalysis/topology/MOL2Parser.py +++ b/package/MDAnalysis/topology/MOL2Parser.py @@ -137,7 +137,8 @@ class MOL2Parser(TopologyReaderBase): through universe.guess_TopologyAttrs() API). """ - format = 'MOL2' + + format = "MOL2" def parse(self, **kwargs): """Parse MOL2 file *filename* and return the dict `structure`. @@ -159,8 +160,10 @@ def parse(self, **kwargs): blocks[-1]["lines"].append(line) if not len(blocks): - raise ValueError("The mol2 file '{0}' needs to have at least one" - " @MOLECULE block".format(self.filename)) + raise ValueError( + "The mol2 file '{0}' needs to have at least one" + " @MOLECULE block".format(self.filename) + ) block = blocks[0] sections = {} @@ -178,8 +181,11 @@ def parse(self, **kwargs): atom_lines, bond_lines = sections["atom"], sections.get("bond") if not len(atom_lines): - raise ValueError("The mol2 block ({0}:{1}) has no atoms".format( - os.path.basename(self.filename), block["start_line"])) + raise ValueError( + "The mol2 block ({0}:{1}) has no atoms".format( + os.path.basename(self.filename), block["start_line"] + ) + ) ids = [] names = [] @@ -187,36 +193,43 @@ def parse(self, **kwargs): resids = [] resnames = [] charges = [] - has_charges = sections['molecule'][3].strip() != 'NO_CHARGES' + has_charges = sections["molecule"][3].strip() != "NO_CHARGES" for a in atom_lines: columns = a.split() if len(columns) >= 9: - aid, name, x, y, z, atom_type, \ - resid, resname, charge = columns[:9] + aid, name, x, y, z, atom_type, resid, resname, charge = ( + columns[:9] + ) elif len(columns) < 6: - raise ValueError(f"The @ATOM block in mol2 file" - f" {os.path.basename(self.filename)}" - f" should have at least 6 fields to be" - f" unpacked: atom_id atom_name x y z" - f" atom_type [subst_id[subst_name" - f" [charge [status_bit]]]]") + raise ValueError( + f"The @ATOM block in mol2 file" + f" {os.path.basename(self.filename)}" + f" should have at least 6 fields to be" + f" unpacked: atom_id atom_name x y z" + f" atom_type [subst_id[subst_name" + f" [charge [status_bit]]]]" + ) else: aid, name, x, y, z, atom_type = columns[:6] id_name_charge = [1, None, None] for i in range(6, len(columns)): - id_name_charge[i-6] = columns[i] + id_name_charge[i - 6] = columns[i] resid, resname, charge = id_name_charge if has_charges: if charge is None: - raise ValueError(f"The mol2 file {self.filename}" - f" indicates a charge model" - f"{sections['molecule'][3]}, but" - f" no charge provided in line: {a}") + raise ValueError( + f"The mol2 file {self.filename}" + f" indicates a charge model" + f"{sections['molecule'][3]}, but" + f" no charge provided in line: {a}" + ) else: if charge is not None: - raise ValueError(f"The mol2 file {self.filename}" - f" indicates no charges, but charge" - f" {charge} provided in line: {a}.") + raise ValueError( + f"The mol2 file {self.filename}" + f" indicates no charges, but charge" + f" {charge} provided in line: {a}." + ) ids.append(aid) names.append(name) @@ -234,13 +247,15 @@ def parse(self, **kwargs): validated_elements[i] = SYBYL2SYMB[at] else: invalid_elements.add(at) - validated_elements[i] = '' + validated_elements[i] = "" # Print single warning for all unknown elements, if any if invalid_elements: - warnings.warn("Unknown elements found for some " - f"atoms: {invalid_elements}. " - "These have been given an empty element record.") + warnings.warn( + "Unknown elements found for some " + f"atoms: {invalid_elements}. " + "These have been given an empty element record." + ) attrs = [] attrs.append(Atomids(np.array(ids, dtype=np.int32))) @@ -249,29 +264,32 @@ def parse(self, **kwargs): if has_charges: attrs.append(Charges(np.array(charges, dtype=np.float32))) - if not np.all(validated_elements == ''): + if not np.all(validated_elements == ""): attrs.append(Elements(validated_elements)) resids = np.array(resids, dtype=np.int32) resnames = np.array(resnames, dtype=object) if np.all(resnames): - residx, resids, (resnames,) = squash_by( - resids, resnames) + residx, resids, (resnames,) = squash_by(resids, resnames) n_residues = len(resids) attrs.append(Resids(resids)) attrs.append(Resnums(resids.copy())) attrs.append(Resnames(resnames)) elif not np.any(resnames): - residx, resids, _ = squash_by(resids,) + residx, resids, _ = squash_by( + resids, + ) n_residues = len(resids) attrs.append(Resids(resids)) attrs.append(Resnums(resids.copy())) else: - raise ValueError(f"Some atoms in the mol2 file {self.filename}" - f" have subst_name while some do not.") + raise ValueError( + f"Some atoms in the mol2 file {self.filename}" + f" have subst_name while some do not." + ) - attrs.append(Segids(np.array(['SYSTEM'], dtype=object))) + attrs.append(Segids(np.array(["SYSTEM"], dtype=object))) # don't add Bonds if there are none (Issue #3057) if bond_lines: @@ -287,8 +305,8 @@ def parse(self, **kwargs): bonds.append(bond) attrs.append(Bonds(bonds, order=bondorder)) - top = Topology(n_atoms, n_residues, 1, - attrs=attrs, - atom_resindex=residx) + top = Topology( + n_atoms, n_residues, 1, attrs=attrs, atom_resindex=residx + ) return top diff --git a/package/MDAnalysis/topology/MinimalParser.py b/package/MDAnalysis/topology/MinimalParser.py index ce0598bf3e7..39f25f66ef7 100644 --- a/package/MDAnalysis/topology/MinimalParser.py +++ b/package/MDAnalysis/topology/MinimalParser.py @@ -56,12 +56,13 @@ class MinimalParser(TopologyReaderBase): This requires that the coordinate format has """ - format = 'MINIMAL' + + format = "MINIMAL" def parse(self, **kwargs): """Return the minimal *Topology* object""" try: - n_atoms = kwargs['n_atoms'] + n_atoms = kwargs["n_atoms"] except KeyError: reader = get_reader_for(self.filename) n_atoms = reader.parse_n_atoms(self.filename, **kwargs) diff --git a/package/MDAnalysis/topology/PDBQTParser.py b/package/MDAnalysis/topology/PDBQTParser.py index 88c3fe3ba40..f3b5c5fa6c6 100644 --- a/package/MDAnalysis/topology/PDBQTParser.py +++ b/package/MDAnalysis/topology/PDBQTParser.py @@ -111,10 +111,11 @@ class PDBQTParser(TopologyReaderBase): Removed mass guessing (attributes guessing takes place now through universe.guess_TopologyAttrs() API). - .. _reference: + .. _reference: https://autodock.scripps.edu/wp-content/uploads/sites/56/2021/10/AutoDock4.2.6_UserGuide.pdf """ - format = 'PDBQT' + + format = "PDBQT" def parse(self, **kwargs): """Parse atom information from PDBQT file *filename*. @@ -139,7 +140,7 @@ def parse(self, **kwargs): with util.openany(self.filename) as f: for line in f: line = line.strip() - if not line.startswith(('ATOM', 'HETATM')): + if not line.startswith(("ATOM", "HETATM")): continue record_types.append(line[:6].strip()) serials.append(int(line[6:11])) @@ -158,14 +159,14 @@ def parse(self, **kwargs): attrs = [] for attrlist, Attr, dtype in ( - (record_types, RecordTypes, object), - (serials, Atomids, np.int32), - (names, Atomnames, object), - (altlocs, AltLocs, object), - (occupancies, Occupancies, np.float32), - (tempfactors, Tempfactors, np.float32), - (charges, Charges, np.float32), - (atomtypes, Atomtypes, object), + (record_types, RecordTypes, object), + (serials, Atomids, np.int32), + (names, Atomnames, object), + (altlocs, AltLocs, object), + (occupancies, Occupancies, np.float32), + (tempfactors, Tempfactors, np.float32), + (charges, Charges, np.float32), + (atomtypes, Atomtypes, object), ): attrs.append(Attr(np.array(attrlist, dtype=dtype))) @@ -177,7 +178,8 @@ def parse(self, **kwargs): attrs.append(ChainIDs(chainids)) residx, (resids, icodes, resnames, chainids) = change_squash( - (resids, icodes), (resids, icodes, resnames, chainids)) + (resids, icodes), (resids, icodes, resnames, chainids) + ) n_residues = len(resids) attrs.append(Resids(resids)) attrs.append(Resnums(resids.copy())) @@ -188,9 +190,13 @@ def parse(self, **kwargs): n_segments = len(segids) attrs.append(Segids(segids)) - top = Topology(n_atoms, n_residues, n_segments, - attrs=attrs, - atom_resindex=residx, - residue_segindex=segidx) + top = Topology( + n_atoms, + n_residues, + n_segments, + attrs=attrs, + atom_resindex=residx, + residue_segindex=segidx, + ) return top diff --git a/package/MDAnalysis/topology/PQRParser.py b/package/MDAnalysis/topology/PQRParser.py index 65c98a70d6e..496ddfb1412 100644 --- a/package/MDAnalysis/topology/PQRParser.py +++ b/package/MDAnalysis/topology/PQRParser.py @@ -103,7 +103,8 @@ class PQRParser(TopologyReaderBase): through universe.guess_TopologyAttrs() API). """ - format = 'PQR' + + format = "PQR" @staticmethod def guess_flavour(line): @@ -126,11 +127,11 @@ def guess_flavour(line): try: float(fields[-1]) except ValueError: - flavour = 'GROMACS' + flavour = "GROMACS" else: - flavour = 'ORIGINAL' + flavour = "ORIGINAL" else: - flavour = 'NO_CHAINID' + flavour = "NO_CHAINID" return flavour def parse(self, **kwargs): @@ -161,20 +162,50 @@ def parse(self, **kwargs): if flavour is None: flavour = self.guess_flavour(line) - if flavour == 'ORIGINAL': - (recordName, serial, name, resName, - chainID, resSeq, x, y, z, charge, - radius) = fields - elif flavour == 'GROMACS': - (recordName, serial, name, resName, - resSeq, x, y, z, charge, - radius, element) = fields + if flavour == "ORIGINAL": + ( + recordName, + serial, + name, + resName, + chainID, + resSeq, + x, + y, + z, + charge, + radius, + ) = fields + elif flavour == "GROMACS": + ( + recordName, + serial, + name, + resName, + resSeq, + x, + y, + z, + charge, + radius, + element, + ) = fields chainID = "SYSTEM" elements.append(element) - elif flavour == 'NO_CHAINID': + elif flavour == "NO_CHAINID": # files without the chainID - (recordName, serial, name, resName, - resSeq, x, y, z, charge, radius) = fields + ( + recordName, + serial, + name, + resName, + resSeq, + x, + y, + z, + charge, + radius, + ) = fields chainID = "SYSTEM" try: @@ -184,7 +215,7 @@ def parse(self, **kwargs): resid = int(resSeq[:-1]) icode = resSeq[-1] else: - icode = '' + icode = "" record_types.append(recordName) serials.append(serial) @@ -216,7 +247,8 @@ def parse(self, **kwargs): residx, (resids, resnames, icodes, chainIDs) = change_squash( (resids, resnames, icodes, chainIDs), - (resids, resnames, icodes, chainIDs)) + (resids, resnames, icodes, chainIDs), + ) n_residues = len(resids) attrs.append(Resids(resids)) @@ -229,9 +261,13 @@ def parse(self, **kwargs): n_segments = len(chainIDs) attrs.append(Segids(chainIDs)) - top = Topology(n_atoms, n_residues, n_segments, - attrs=attrs, - atom_resindex=residx, - residue_segindex=segidx) + top = Topology( + n_atoms, + n_residues, + n_segments, + attrs=attrs, + atom_resindex=residx, + residue_segindex=segidx, + ) return top diff --git a/package/MDAnalysis/topology/PSFParser.py b/package/MDAnalysis/topology/PSFParser.py index f247544262d..531c2ae592d 100644 --- a/package/MDAnalysis/topology/PSFParser.py +++ b/package/MDAnalysis/topology/PSFParser.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 @@ -64,7 +64,7 @@ Bonds, Angles, Dihedrals, - Impropers + Impropers, ) from ..core.topology import Topology @@ -94,7 +94,8 @@ class PSFParser(TopologyReaderBase): .. versionchanged:: 2.8.0 PSFParser now reads string resids and converts them to integers. """ - format = 'PSF' + + format = "PSF" def parse(self, **kwargs): """Parse PSF file into Topology @@ -107,18 +108,19 @@ def parse(self, **kwargs): with openany(self.filename) as psffile: header = next(psffile) if not header.startswith("PSF"): - err = ("{0} is not valid PSF file (header = {1})" - "".format(self.filename, header)) + err = "{0} is not valid PSF file (header = {1})" "".format( + self.filename, header + ) logger.error(err) raise ValueError(err) header_flags = header[3:].split() if "NAMD" in header_flags: - self._format = "NAMD" # NAMD/VMD + self._format = "NAMD" # NAMD/VMD elif "EXT" in header_flags: - self._format = "EXTENDED" # CHARMM + self._format = "EXTENDED" # CHARMM else: - self._format = "STANDARD" # CHARMM + self._format = "STANDARD" # CHARMM next(psffile) title = next(psffile).split() @@ -129,28 +131,28 @@ def parse(self, **kwargs): # psfremarks = [psffile.next() for i in range(int(title[0]))] for _ in range(int(title[0])): next(psffile) - logger.debug("PSF file {0}: format {1}" - "".format(self.filename, self._format)) + logger.debug( + "PSF file {0}: format {1}" + "".format(self.filename, self._format) + ) # Atoms first and mandatory - top = self._parse_sec( - psffile, ('NATOM', 1, 1, self._parseatoms)) + top = self._parse_sec(psffile, ("NATOM", 1, 1, self._parseatoms)) # Then possibly other sections sections = ( - #("atoms", ("NATOM", 1, 1, self._parseatoms)), + # ("atoms", ("NATOM", 1, 1, self._parseatoms)), (Bonds, ("NBOND", 2, 4, self._parsesection)), (Angles, ("NTHETA", 3, 3, self._parsesection)), (Dihedrals, ("NPHI", 4, 2, self._parsesection)), (Impropers, ("NIMPHI", 4, 2, self._parsesection)), - #("donors", ("NDON", 2, 4, self._parsesection)), - #("acceptors", ("NACC", 2, 4, self._parsesection)) + # ("donors", ("NDON", 2, 4, self._parsesection)), + # ("acceptors", ("NACC", 2, 4, self._parsesection)) ) try: for attr, info in sections: next(psffile) - top.add_TopologyAttr( - attr(self._parse_sec(psffile, info))) + top.add_TopologyAttr(attr(self._parse_sec(psffile, info))) except StopIteration: # Reached the end of the file before we expected for attr in (Bonds, Angles, Dihedrals, Impropers): @@ -173,15 +175,16 @@ def _parse_sec(self, psffile, section_info): header = header.split() # Get the number num = float(header[0]) - sect_type = header[1].strip('!:') + sect_type = header[1].strip("!:") # Make sure the section type matches the desc if not sect_type == desc: - err = ("Expected section {0} but found {1}" - "".format(desc, sect_type)) + err = "Expected section {0} but found {1}" "".format( + desc, sect_type + ) logger.error(err) raise ValueError(err) # Now figure out how many lines to read - numlines = int(ceil(num/per_line)) + numlines = int(ceil(num / per_line)) psffile_next = functools.partial(next, psffile) return parsefunc(psffile_next, atoms_per, numlines) @@ -238,23 +241,42 @@ def _parseatoms(self, lines, atoms_per, numlines): """ # how to partition the line into the individual atom components atom_parsers = { - 'STANDARD': lambda l: - (l[:8], l[9:13].strip() or "SYSTEM", l[14:18], - l[19:23].strip(), l[24:28].strip(), - l[29:33].strip(), l[34:48], l[48:62]), + "STANDARD": lambda l: ( + l[:8], + l[9:13].strip() or "SYSTEM", + l[14:18], + l[19:23].strip(), + l[24:28].strip(), + l[29:33].strip(), + l[34:48], + l[48:62], + ), # l[62:70], l[70:84], l[84:98] ignore IMOVE, ECH and EHA, - 'EXTENDED': lambda l: - (l[:10], l[11:19].strip() or "SYSTEM", l[20:28], - l[29:37].strip(), l[38:46].strip(), - l[47:51].strip(), l[52:66], l[66:70]), + "EXTENDED": lambda l: ( + l[:10], + l[11:19].strip() or "SYSTEM", + l[20:28], + l[29:37].strip(), + l[38:46].strip(), + l[47:51].strip(), + l[52:66], + l[66:70], + ), # l[70:78], l[78:84], l[84:98] ignore IMOVE, ECH and EHA, - 'NAMD': lambda l: l.split()[:8], + "NAMD": lambda l: l.split()[:8], } atom_parser = atom_parsers[self._format] # once partitioned, assigned each component the correct type - set_type = lambda x: (int(x[0]) - 1, x[1] or "SYSTEM", - atoi(x[2]), x[3], - x[4], x[5], float(x[6]), float(x[7])) + set_type = lambda x: ( + int(x[0]) - 1, + x[1] or "SYSTEM", + atoi(x[2]), + x[3], + x[4], + x[5], + float(x[6]), + float(x[7]), + ) # Oli: I don't think that this is the correct OUTPUT format: # psf_atom_format = " %5d %4s %4d %4s %-4s %-4s %10.6f %7.4f%s\n" @@ -289,13 +311,17 @@ def _parseatoms(self, lines, atoms_per, numlines): except ValueError: # last ditch attempt: this *might* be a NAMD/VMD # space-separated "PSF" file from VMD version < 1.9.1 - atom_parser = atom_parsers['NAMD'] + atom_parser = atom_parsers["NAMD"] vals = set_type(atom_parser(line)) - logger.warning("Guessing that this is actually a" - " NAMD-type PSF file..." - " continuing with fingers crossed!") - logger.debug("First NAMD-type line: {0}: {1}" - "".format(i, line.rstrip())) + logger.warning( + "Guessing that this is actually a" + " NAMD-type PSF file..." + " continuing with fingers crossed!" + ) + logger.debug( + "First NAMD-type line: {0}: {1}" + "".format(i, line.rstrip()) + ) atomids[i] = vals[0] segids[i] = vals[1] @@ -316,8 +342,8 @@ def _parseatoms(self, lines, atoms_per, numlines): # Residue # resids, resnames residx, (new_resids, new_resnames, perres_segids) = change_squash( - (resids, resnames, segids), - (resids, resnames, segids)) + (resids, resnames, segids), (resids, resnames, segids) + ) # transform from atom:Rid to atom:Rix residueids = Resids(new_resids) residuenums = Resnums(new_resids.copy()) @@ -327,13 +353,24 @@ def _parseatoms(self, lines, atoms_per, numlines): segidx, perseg_segids = squash_by(perres_segids)[:2] segids = Segids(perseg_segids) - top = Topology(len(atomids), len(new_resids), len(segids), - attrs=[atomids, atomnames, atomtypes, - charges, masses, - residueids, residuenums, residuenames, - segids], - atom_resindex=residx, - residue_segindex=segidx) + top = Topology( + len(atomids), + len(new_resids), + len(segids), + attrs=[ + atomids, + atomnames, + atomtypes, + charges, + masses, + residueids, + residuenums, + residuenames, + segids, + ], + atom_resindex=residx, + residue_segindex=segidx, + ) return top @@ -344,5 +381,5 @@ def _parsesection(self, lines, atoms_per, numlines): # Subtract 1 from each number to ensure zero-indexing for the atoms fields = np.int64(lines().split()) - 1 for j in range(0, len(fields), atoms_per): - section.append(tuple(fields[j:j+atoms_per])) + section.append(tuple(fields[j : j + atoms_per])) return section diff --git a/package/MDAnalysis/topology/ParmEdParser.py b/package/MDAnalysis/topology/ParmEdParser.py index 2cfc0df0dae..b7f9331e4ae 100644 --- a/package/MDAnalysis/topology/ParmEdParser.py +++ b/package/MDAnalysis/topology/ParmEdParser.py @@ -27,5 +27,5 @@ "This module is deprecated as of MDAnalysis version 2.0.0." "It will be removed in MDAnalysis version 3.0.0." "Please import the ParmEd classes from MDAnalysis.converters instead.", - category=DeprecationWarning + category=DeprecationWarning, ) diff --git a/package/MDAnalysis/topology/TOPParser.py b/package/MDAnalysis/topology/TOPParser.py index 4f2ce631fc6..ba61bea0b06 100644 --- a/package/MDAnalysis/topology/TOPParser.py +++ b/package/MDAnalysis/topology/TOPParser.py @@ -111,20 +111,21 @@ Bonds, Angles, Dihedrals, - Impropers + Impropers, ) import warnings import logging -logger = logging.getLogger('MDAnalysis.topology.TOPParser') +logger = logging.getLogger("MDAnalysis.topology.TOPParser") class TypeIndices(AtomAttr): """Numerical type of each Atom""" - attrname = 'type_indices' - singular = 'type_index' - level = 'atom' + + attrname = "type_indices" + singular = "type_index" + level = "atom" class TOPParser(TopologyReaderBase): @@ -173,7 +174,8 @@ class TOPParser(TopologyReaderBase): .. versionchanged:: 2.7.0 gets Segments and chainIDs from flag RESIDUE_CHAINID, when present """ - format = ['TOP', 'PRMTOP', 'PARM7'] + + format = ["TOP", "PRMTOP", "PARM7"] def parse(self, **kwargs): """Parse Amber PRMTOP topology file *filename*. @@ -188,8 +190,13 @@ def parse(self, **kwargs): "CHARGE": (1, 5, self.parse_charges, "charge", 0), "ATOMIC_NUMBER": (1, 10, self.parse_elements, "elements", 0), "MASS": (1, 5, self.parse_masses, "mass", 0), - "ATOM_TYPE_INDEX": (1, 10, self.parse_type_indices, "type_indices", - 0), + "ATOM_TYPE_INDEX": ( + 1, + 10, + self.parse_type_indices, + "type_indices", + 0, + ), "AMBER_ATOM_TYPE": (1, 20, self.parse_types, "types", 0), "RESIDUE_LABEL": (1, 20, self.parse_resnames, "resname", 11), "RESIDUE_POINTER": (1, 10, self.parse_residx, "respoint", 11), @@ -198,7 +205,13 @@ def parse(self, **kwargs): "ANGLES_INC_HYDROGEN": (4, 10, self.parse_bonded, "angh", 4), "ANGLES_WITHOUT_HYDROGEN": (4, 10, self.parse_bonded, "anga", 5), "DIHEDRALS_INC_HYDROGEN": (5, 10, self.parse_bonded, "dihh", 6), - "DIHEDRALS_WITHOUT_HYDROGEN": (5, 10, self.parse_bonded, "diha", 7), + "DIHEDRALS_WITHOUT_HYDROGEN": ( + 5, + 10, + self.parse_bonded, + "diha", + 7, + ), "RESIDUE_CHAINID": (1, 20, self.parse_chainids, "segids", 11), } @@ -211,21 +224,26 @@ def parse(self, **kwargs): if not header.startswith("%VE"): raise ValueError( "{0} is not a valid TOP file. %VE Missing in header" - "".format(self.filename)) + "".format(self.filename) + ) title = next(self.topfile).split() if not (title[1] == "TITLE"): # Raise a separate warning if Chamber-style TOP is detected if title[1] == "CTITLE": - emsg = ("{0} is detected as a Chamber-style TOP file. " - "At this time MDAnalysis does not support such " - "topologies".format(self.filename)) + emsg = ( + "{0} is detected as a Chamber-style TOP file. " + "At this time MDAnalysis does not support such " + "topologies".format(self.filename) + ) else: - emsg = ("{0} is not a valid TOP file. " - "'TITLE' missing in header".format(self.filename)) + emsg = ( + "{0} is not a valid TOP file. " + "'TITLE' missing in header".format(self.filename) + ) raise ValueError(emsg) - while not header.startswith('%FLAG POINTERS'): + while not header.startswith("%FLAG POINTERS"): header = next(self.topfile) next(self.topfile) @@ -238,14 +256,17 @@ def parse(self, **kwargs): while next_section is not None: try: - (num_per_record, per_line, - func, name, sect_num) = sections[next_section] + (num_per_record, per_line, func, name, sect_num) = ( + sections[next_section] + ) except KeyError: + def next_getter(): return self.skipper() + else: num = sys_info[sect_num] * num_per_record - numlines = (num // per_line) + numlines = num // per_line if num % per_line != 0: numlines += 1 @@ -265,56 +286,72 @@ def next_getter(): try: next_section = line.split("%FLAG")[1].strip() except IndexError: - errmsg = (f"%FLAG section not found, formatting error " - f"for PARM7 file {self.filename} ") + errmsg = ( + f"%FLAG section not found, formatting error " + f"for PARM7 file {self.filename} " + ) raise IndexError(errmsg) from None # strip out a few values to play with them - n_atoms = len(attrs['name']) + n_atoms = len(attrs["name"]) - resptrs = attrs.pop('respoint') + resptrs = attrs.pop("respoint") resptrs.append(n_atoms) residx = np.zeros(n_atoms, dtype=np.int32) for i, (x, y) in enumerate(zip(resptrs[:-1], resptrs[1:])): residx[x:y] = i - n_res = len(attrs['resname']) + n_res = len(attrs["resname"]) # Deal with recreating bonds and angle records here - attrs['bonds'] = Bonds([i for i in itertools.chain( - attrs.pop('bonda'), attrs.pop('bondh'))]) + attrs["bonds"] = Bonds( + [ + i + for i in itertools.chain( + attrs.pop("bonda"), attrs.pop("bondh") + ) + ] + ) - attrs['angles'] = Angles([i for i in itertools.chain( - attrs.pop('anga'), attrs.pop('angh'))]) + attrs["angles"] = Angles( + [i for i in itertools.chain(attrs.pop("anga"), attrs.pop("angh"))] + ) - attrs['dihedrals'], attrs['impropers'] = self.parse_dihedrals( - attrs.pop('diha'), attrs.pop('dihh')) + attrs["dihedrals"], attrs["impropers"] = self.parse_dihedrals( + attrs.pop("diha"), attrs.pop("dihh") + ) # Warn user if elements not in topology - if 'elements' not in attrs: - msg = ("ATOMIC_NUMBER record not found, elements attribute will " - "not be populated. If needed these can be guessed using " - "universe.guess_TopologyAttrs(to_guess=['elements']).") + if "elements" not in attrs: + msg = ( + "ATOMIC_NUMBER record not found, elements attribute will " + "not be populated. If needed these can be guessed using " + "universe.guess_TopologyAttrs(to_guess=['elements'])." + ) logger.warning(msg) warnings.warn(msg) - elif np.any(attrs['elements'].values == ""): + elif np.any(attrs["elements"].values == ""): # only send out one warning that some elements are unknown - msg = ("Unknown ATOMIC_NUMBER value found for some atoms, these " - "have been given an empty element record. If needed these " - "can be guessed using " - "universe.guess_TopologyAttrs(to_guess=['elements']).") + msg = ( + "Unknown ATOMIC_NUMBER value found for some atoms, these " + "have been given an empty element record. If needed these " + "can be guessed using " + "universe.guess_TopologyAttrs(to_guess=['elements'])." + ) logger.warning(msg) warnings.warn(msg) # atom ids are mandatory - attrs['atomids'] = Atomids(np.arange(n_atoms) + 1) - attrs['resids'] = Resids(np.arange(n_res) + 1) - attrs['resnums'] = Resnums(np.arange(n_res) + 1) + attrs["atomids"] = Atomids(np.arange(n_atoms) + 1) + attrs["resids"] = Resids(np.arange(n_res) + 1) + attrs["resnums"] = Resnums(np.arange(n_res) + 1) # Amber's 'RESIDUE_CHAINID' is a by-residue attribute, turn it into # a by-atom attribute when present. See PR #4007. if "segids" in attrs and len(attrs["segids"]) == n_res: - segidx, (segids,) = change_squash((attrs["segids"],), (attrs["segids"],)) + segidx, (segids,) = change_squash( + (attrs["segids"],), (attrs["segids"],) + ) chainids = [attrs["segids"][r] for r in residx] attrs["segids"] = Segids(segids) @@ -467,8 +504,8 @@ def parse_elements(self, num_per_record, numlines): """ vals = self.parsesection_mapper( - numlines, - lambda x: Z2SYMB[int(x)] if int(x) > 0 else "") + numlines, lambda x: Z2SYMB[int(x)] if int(x) > 0 else "" + ) attr = Elements(np.array(vals, dtype=object)) return attr @@ -556,8 +593,10 @@ def parse_chunks(self, data, chunksize): Therefore, to extract the required information, we split out the list into chunks of size num_per_record, and only extract the atom ids. """ - vals = [tuple(data[x:x+chunksize-1]) - for x in range(0, len(data), chunksize)] + vals = [ + tuple(data[x : x + chunksize - 1]) + for x in range(0, len(data), chunksize) + ] return vals def parse_bonded(self, num_per_record, numlines): @@ -603,7 +642,7 @@ def parsesection_mapper(self, numlines, mapper): section = [] def get_fmt(file): - """ Skips '%COMMENT' lines until it gets the FORMAT specification + """Skips '%COMMENT' lines until it gets the FORMAT specification for the section.""" line = next(file) if line[:7] == "%FORMAT": @@ -622,7 +661,7 @@ def get_fmt(file): for i in range(numlines): l = next(self.topfile) for j in range(len(x.entries)): - val = l[x.entries[j].start:x.entries[j].stop].strip() + val = l[x.entries[j].start : x.entries[j].stop].strip() if val: section.append(mapper(val)) return section @@ -665,7 +704,7 @@ def parse_dihedrals(self, diha, dihh): dihed = [] for i in itertools.chain(diha, dihh): if i[3] < 0: - improp.append(i[:2]+(abs(i[2]),)+(abs(i[3]),)) + improp.append(i[:2] + (abs(i[2]),) + (abs(i[3]),)) elif i[2] < 0: vals = i[:2] + (abs(i[2]),) + i[3:] dihed.append(vals) diff --git a/package/MDAnalysis/topology/TPRParser.py b/package/MDAnalysis/topology/TPRParser.py index 5c648016e87..6adac116e84 100644 --- a/package/MDAnalysis/topology/TPRParser.py +++ b/package/MDAnalysis/topology/TPRParser.py @@ -174,6 +174,7 @@ from ..core.topologyattrs import Resnums import logging + logger = logging.getLogger("MDAnalysis.topology.TPRparser") @@ -183,7 +184,8 @@ class TPRParser(TopologyReaderBase): .. _Gromacs: http://www.gromacs.org .. _TPR file: http://manual.gromacs.org/current/online/tpr.html """ - format = 'TPR' + + format = "TPR" def parse(self, tpr_resid_from_one=True, **kwargs): """Parse a Gromacs TPR file into a MDAnalysis internal topology structure. @@ -206,11 +208,11 @@ def parse(self, tpr_resid_from_one=True, **kwargs): .. versionchanged:: 2.0.0 Changed to ``tpr_resid_from_one=True`` by default. """ - with openany(self.filename, mode='rb') as infile: + with openany(self.filename, mode="rb") as infile: tprf = infile.read() data = tpr_utils.TPXUnpacker(tprf) try: - th = tpr_utils.read_tpxheader(data) # tpxheader + th = tpr_utils.read_tpxheader(data) # tpxheader except (EOFError, ValueError): msg = f"{self.filename}: Invalid tpr file or cannot be recognized" logger.critical(msg) @@ -232,18 +234,21 @@ def parse(self, tpr_resid_from_one=True, **kwargs): raise IOError(msg) data = tpr_utils.TPXUnpacker2020.from_unpacker(data) - state_ngtc = th.ngtc # done init_state() in src/gmxlib/tpxio.c + state_ngtc = th.ngtc # done init_state() in src/gmxlib/tpxio.c if th.bBox: tpr_utils.extract_box_info(data, th.fver) if state_ngtc > 0: - if th.fver < 69: # redundancy due to different versions + if th.fver < 69: # redundancy due to different versions tpr_utils.ndo_real(data, state_ngtc) - tpr_utils.ndo_real(data, state_ngtc) # relevant to Berendsen tcoupl_lambda + tpr_utils.ndo_real( + data, state_ngtc + ) # relevant to Berendsen tcoupl_lambda if th.bTop: - tpr_top = tpr_utils.do_mtop(data, th.fver, - tpr_resid_from_one=tpr_resid_from_one) + tpr_top = tpr_utils.do_mtop( + data, th.fver, tpr_resid_from_one=tpr_resid_from_one + ) else: msg = f"{self.filename}: No topology found in tpr file" logger.critical(msg) @@ -253,7 +258,6 @@ def parse(self, tpr_resid_from_one=True, **kwargs): return tpr_top - def _log_header(self, th): logger.info(f"Gromacs version : {th.ver_str}") logger.info(f"tpx version : {th.fver}") diff --git a/package/MDAnalysis/topology/TXYZParser.py b/package/MDAnalysis/topology/TXYZParser.py index 4b0d248e374..4752f1bf481 100644 --- a/package/MDAnalysis/topology/TXYZParser.py +++ b/package/MDAnalysis/topology/TXYZParser.py @@ -87,7 +87,8 @@ class TXYZParser(TopologyReaderBase): through universe.guess_TopologyAttrs() API). """ - format = ['TXYZ', 'ARC'] + + format = ["TXYZ", "ARC"] def parse(self, **kwargs): """Read the file and return the structure. @@ -97,7 +98,7 @@ def parse(self, **kwargs): MDAnalysis Topology object """ with openany(self.filename) as inf: - #header + # header natoms = int(inf.readline().split()[0]) atomids = np.zeros(natoms, dtype=int) @@ -121,7 +122,7 @@ def parse(self, **kwargs): # Can't infinitely read as XYZ files can be multiframe for i, line in zip(range(natoms), itertools.chain([fline], inf)): line = line.split() - atomids[i]= line[0] + atomids[i] = line[0] names[i] = line[1] types[i] = line[5] bonded_atoms = line[6:] @@ -130,24 +131,26 @@ def parse(self, **kwargs): if i < other_atom: bonds.append((i, other_atom)) - attrs = [Atomnames(names), - Atomids(atomids), - Atomtypes(types), - Bonds(tuple(bonds)), - Resids(np.array([1])), - Resnums(np.array([1])), - Segids(np.array(['SYSTEM'], dtype=object)), - ] + attrs = [ + Atomnames(names), + Atomids(atomids), + Atomtypes(types), + Bonds(tuple(bonds)), + Resids(np.array([1])), + Resnums(np.array([1])), + Segids(np.array(["SYSTEM"], dtype=object)), + ] if all(n.capitalize() in SYMB2Z for n in names): attrs.append(Elements(np.array(names, dtype=object))) else: - warnings.warn("Element information is missing, elements attribute " - "will not be populated. If needed these can be " - "guessed using universe.guess_TopologyAttrs(" - "to_guess=['elements']).") - - top = Topology(natoms, 1, 1, - attrs=attrs) + warnings.warn( + "Element information is missing, elements attribute " + "will not be populated. If needed these can be " + "guessed using universe.guess_TopologyAttrs(" + "to_guess=['elements'])." + ) + + top = Topology(natoms, 1, 1, attrs=attrs) return top diff --git a/package/MDAnalysis/topology/XYZParser.py b/package/MDAnalysis/topology/XYZParser.py index 956c93567bc..45683a1a714 100644 --- a/package/MDAnalysis/topology/XYZParser.py +++ b/package/MDAnalysis/topology/XYZParser.py @@ -75,7 +75,8 @@ class XYZParser(TopologyReaderBase): through universe.guess_TopologyAttrs() API). """ - format = 'XYZ' + + format = "XYZ" def parse(self, **kwargs): """Read the file and return the structure. @@ -95,15 +96,15 @@ def parse(self, **kwargs): name = inf.readline().split()[0] names[i] = name + attrs = [ + Atomnames(names), + Atomids(np.arange(natoms) + 1), + Resids(np.array([1])), + Resnums(np.array([1])), + Segids(np.array(["SYSTEM"], dtype=object)), + Elements(names), + ] - attrs = [Atomnames(names), - Atomids(np.arange(natoms) + 1), - Resids(np.array([1])), - Resnums(np.array([1])), - Segids(np.array(['SYSTEM'], dtype=object)), - Elements(names)] - - top = Topology(natoms, 1, 1, - attrs=attrs) + top = Topology(natoms, 1, 1, attrs=attrs) return top diff --git a/package/MDAnalysis/topology/core.py b/package/MDAnalysis/topology/core.py index 7ee61219827..a0e28f96431 100644 --- a/package/MDAnalysis/topology/core.py +++ b/package/MDAnalysis/topology/core.py @@ -41,9 +41,15 @@ # Deprecated local imports from MDAnalysis.guesser import tables from .guessers import ( - guess_atom_element, guess_atom_type, - get_atom_mass, guess_atom_mass, guess_atom_charge, - guess_bonds, guess_angles, guess_dihedrals, guess_improper_dihedrals, + guess_atom_element, + guess_atom_type, + get_atom_mass, + guess_atom_mass, + guess_atom_charge, + guess_bonds, + guess_angles, + guess_dihedrals, + guess_improper_dihedrals, ) -#tumbleweed +# tumbleweed diff --git a/package/MDAnalysis/topology/guessers.py b/package/MDAnalysis/topology/guessers.py index d1485bad080..6ae316cd026 100644 --- a/package/MDAnalysis/topology/guessers.py +++ b/package/MDAnalysis/topology/guessers.py @@ -131,7 +131,9 @@ def guess_masses(atom_types): atom_masses : np.ndarray dtype float64 """ validate_atom_types(atom_types) - masses = np.array([get_atom_mass(atom_t) for atom_t in atom_types], dtype=np.float64) + masses = np.array( + [get_atom_mass(atom_t) for atom_t in atom_types], dtype=np.float64 + ) return masses @@ -158,7 +160,11 @@ def validate_atom_types(atom_types): try: tables.masses[atom_type.upper()] except KeyError: - warnings.warn("Failed to guess the mass for the following atom types: {}".format(atom_type)) + warnings.warn( + "Failed to guess the mass for the following atom types: {}".format( + atom_type + ) + ) @deprecate(release="2.8.0", remove="3.0.0", message=deprecation_msg) @@ -174,7 +180,9 @@ def guess_types(atom_names): ------- atom_types : np.ndarray dtype object """ - return np.array([guess_atom_element(name) for name in atom_names], dtype=object) + return np.array( + [guess_atom_element(name) for name in atom_names], dtype=object + ) @deprecate(release="2.8.0", remove="3.0.0", message=deprecation_msg) @@ -195,8 +203,8 @@ def guess_atom_type(atomname): return guess_atom_element(atomname) -NUMBERS = re.compile(r'[0-9]') # match numbers -SYMBOLS = re.compile(r'[*+-]') # match *, +, - +NUMBERS = re.compile(r"[0-9]") # match numbers +SYMBOLS = re.compile(r"[*+-]") # match *, +, - @deprecate(release="2.8.0", remove="3.0.0", message=deprecation_msg) @@ -216,19 +224,19 @@ def guess_atom_element(atomname): :func:`guess_atom_type` :mod:`MDAnalysis.topology.tables` """ - if atomname == '': - return '' + if atomname == "": + return "" try: return tables.atomelements[atomname.upper()] except KeyError: # strip symbols - no_symbols = re.sub(SYMBOLS, '', atomname) + no_symbols = re.sub(SYMBOLS, "", atomname) # split name by numbers no_numbers = re.split(NUMBERS, no_symbols) - no_numbers = list(filter(None, no_numbers)) #remove '' + no_numbers = list(filter(None, no_numbers)) # remove '' # if no_numbers is not empty, use the first element of no_numbers - name = no_numbers[0].upper() if no_numbers else '' + name = no_numbers[0].upper() if no_numbers else "" # just in case if name in tables.atomelements: @@ -317,10 +325,10 @@ def guess_bonds(atoms, coords, box=None, **kwargs): if len(atoms) != len(coords): raise ValueError("'atoms' and 'coord' must be the same length") - fudge_factor = kwargs.get('fudge_factor', 0.55) + fudge_factor = kwargs.get("fudge_factor", 0.55) vdwradii = tables.vdwradii.copy() # so I don't permanently change it - user_vdwradii = kwargs.get('vdwradii', None) + user_vdwradii = kwargs.get("vdwradii", None) if user_vdwradii: # this should make algo use their values over defaults vdwradii.update(user_vdwradii) @@ -329,13 +337,16 @@ def guess_bonds(atoms, coords, box=None, **kwargs): # check that all types have a defined vdw if not all(val in vdwradii for val in set(atomtypes)): - raise ValueError(("vdw radii for types: " + - ", ".join([t for t in set(atomtypes) if - not t in vdwradii]) + - ". These can be defined manually using the" + - " keyword 'vdwradii'")) + raise ValueError( + ( + "vdw radii for types: " + + ", ".join([t for t in set(atomtypes) if not t in vdwradii]) + + ". These can be defined manually using the" + + " keyword 'vdwradii'" + ) + ) - lower_bound = kwargs.get('lower_bound', 0.1) + lower_bound = kwargs.get("lower_bound", 0.1) if box is not None: box = np.asarray(box) @@ -347,13 +358,12 @@ def guess_bonds(atoms, coords, box=None, **kwargs): bonds = [] - pairs, dist = distances.self_capped_distance(coords, - max_cutoff=2.0*max_vdw, - min_cutoff=lower_bound, - box=box) + pairs, dist = distances.self_capped_distance( + coords, max_cutoff=2.0 * max_vdw, min_cutoff=lower_bound, box=box + ) for idx, (i, j) in enumerate(pairs): - d = (vdwradii[atomtypes[i]] + vdwradii[atomtypes[j]])*fudge_factor - if (dist[idx] < d): + d = (vdwradii[atomtypes[i]] + vdwradii[atomtypes[j]]) * fudge_factor + if dist[idx] < d: bonds.append((atoms[i].index, atoms[j].index)) return tuple(bonds) @@ -416,8 +426,9 @@ def guess_dihedrals(angles): a_tup = tuple([a.index for a in b]) # angle as tuple of numbers # if searching with b[0], want tuple of (b[2], b[1], b[0], +new) # search the first and last atom of each angle - for atom, prefix in zip([b.atoms[0], b.atoms[-1]], - [a_tup[::-1], a_tup]): + for atom, prefix in zip( + [b.atoms[0], b.atoms[-1]], [a_tup[::-1], a_tup] + ): for other_b in atom.bonds: if not other_b.partner(atom) in b: third_a = other_b.partner(atom) @@ -548,7 +559,9 @@ def guess_gasteiger_charges(atomgroup): """ mol = atomgroup.convert_to("RDKIT") from rdkit.Chem.rdPartialCharges import ComputeGasteigerCharges + ComputeGasteigerCharges(mol, throwOnParamFailure=True) - return np.array([atom.GetDoubleProp("_GasteigerCharge") - for atom in mol.GetAtoms()], - dtype=np.float32) + return np.array( + [atom.GetDoubleProp("_GasteigerCharge") for atom in mol.GetAtoms()], + dtype=np.float32, + ) diff --git a/package/MDAnalysis/topology/tpr/obj.py b/package/MDAnalysis/topology/tpr/obj.py index 6be8b40b746..93c5a83ee75 100644 --- a/package/MDAnalysis/topology/tpr/obj.py +++ b/package/MDAnalysis/topology/tpr/obj.py @@ -35,24 +35,59 @@ from ...guesser.tables import Z2SYMB TpxHeader = namedtuple( - "TpxHeader", [ - "ver_str", "precision", - "fver", "fgen", "file_tag", "natoms", "ngtc", "fep_state", "lamb", - "bIr", "bTop", "bX", "bV", "bF", "bBox", "sizeOfTprBody"]) + "TpxHeader", + [ + "ver_str", + "precision", + "fver", + "fgen", + "file_tag", + "natoms", + "ngtc", + "fep_state", + "lamb", + "bIr", + "bTop", + "bX", + "bV", + "bF", + "bBox", + "sizeOfTprBody", + ], +) Box = namedtuple("Box", "size rel v") Mtop = namedtuple("Mtop", "nmoltype moltypes nmolblock") Params = namedtuple("Params", "atnr ntypes functype reppow fudgeQQ") -Atom = namedtuple("Atom", ["m", "q", "mB", "qB", "tp", "typeB", "ptype", "resind", "atomnumber"]) +Atom = namedtuple( + "Atom", + ["m", "q", "mB", "qB", "tp", "typeB", "ptype", "resind", "atomnumber"], +) Atoms = namedtuple("Atoms", "atoms nr nres type typeB atomnames resnames") Ilist = namedtuple("Ilist", "nr ik, iatoms") -Molblock = namedtuple("Molblock", [ - "molb_type", "molb_nmol", "molb_natoms_mol", - "molb_nposres_xA", "molb_nposres_xB"]) +Molblock = namedtuple( + "Molblock", + [ + "molb_type", + "molb_nmol", + "molb_natoms_mol", + "molb_nposres_xA", + "molb_nposres_xB", + ], +) class MoleculeKind(object): - def __init__(self, name, atomkinds, bonds=None, angles=None, - dihe=None, impr=None, donors=None, acceptors=None): + def __init__( + self, + name, + atomkinds, + bonds=None, + angles=None, + dihe=None, + impr=None, + donors=None, + acceptors=None, + ): self.name = name # name of the molecule self.atomkinds = atomkinds self.bonds = bonds @@ -105,7 +140,8 @@ def remap_impr(self, atom_start_ndx): class AtomKind(object): def __init__( - self, id, name, type, resid, resname, mass, charge, atomic_number): + self, id, name, type, resid, resname, mass, charge, atomic_number + ): # id is only within the scope of a single molecule, not the whole system self.id = id self.name = name @@ -125,7 +161,7 @@ def element_symbol(self): is not recognized, which happens if a particle is not really an atom (e.g a coarse-grained particle), an empty string is returned. """ - return Z2SYMB.get(self.atomic_number, '') + return Z2SYMB.get(self.atomic_number, "") def __repr__(self): return ( @@ -152,4 +188,4 @@ def process(self, atom_ndx): # The format for all record is (type, atom1, atom2, ...) # but we are only interested in the atoms. for cursor in range(0, len(atom_ndx), self.natoms + 1): - yield atom_ndx[cursor + 1: cursor + 1 + self.natoms] + yield atom_ndx[cursor + 1 : cursor + 1 + self.natoms] diff --git a/package/MDAnalysis/topology/tpr/setting.py b/package/MDAnalysis/topology/tpr/setting.py index ebb749d588d..1fd0a682835 100644 --- a/package/MDAnalysis/topology/tpr/setting.py +++ b/package/MDAnalysis/topology/tpr/setting.py @@ -38,8 +38,22 @@ """ #: Gromacs TPR file format versions that can be read by the TPRParser. -SUPPORTED_VERSIONS = (58, 73, 83, 100, 103, 110, 112, - 116, 119, 122, 127, 129, 133, 134) +SUPPORTED_VERSIONS = ( + 58, + 73, + 83, + 100, + 103, + 110, + 112, + 116, + 119, + 122, + 127, + 129, + 133, + 134, +) # Some constants STRLEN = 4096 @@ -50,7 +64,7 @@ NR_FOURDIHS = 4 # /src/gromacs/topology/idef.h egcNR = 10 # include/types/topolog.h TPX_TAG_RELEASE = "release" # /src/gromacs/fileio/tpxio.c -tpx_version = 103 # /src/gromacs/fileio/tpxio.c +tpx_version = 103 # /src/gromacs/fileio/tpxio.c tpx_generation = 27 # /src/gromacs/fileio/tpxio.c tpxv_RestrictedBendingAndCombinedAngleTorsionPotentials = 98 tpxv_GenericInternalParameters = 117 @@ -61,6 +75,7 @@ #: Function types from ``/include/types/idef.h`` +# fmt: off ( F_BONDS, F_G96BONDS, F_MORSE, F_CUBICBONDS, F_CONNBONDS, F_HARMONIC, F_FENEBONDS, F_TABBONDS, @@ -83,9 +98,12 @@ F_ETOT, F_ECONSERVED, F_TEMP, F_VTEMP_NOLONGERUSED, F_PDISPCORR, F_PRES, F_DHDL_CON, F_DVDL, F_DKDL, F_DVDL_COUL, F_DVDL_VDW, F_DVDL_BONDED, - F_DVDL_RESTRAINT, F_DVDL_TEMPERATURE, F_NRE) = list(range(95)) + F_DVDL_RESTRAINT, F_DVDL_TEMPERATURE, F_NRE +) = list(range(95)) +# fmt: on #: Function types from ``/src/gmxlib/tpxio.c`` +# fmt: off ftupd = [ (20, F_CUBICBONDS), (20, F_CONNBONDS), (20, F_HARMONIC), (34, F_FENEBONDS), (43, F_TABBONDS), (43, F_TABBONDSNC), (70, F_RESTRBONDS), @@ -110,6 +128,7 @@ (tpxv_VSite1, F_VSITE1), (tpxv_VSite2FD, F_VSITE2FD), ] +# fmt: on #: Interaction types from ``/gmxlib/ifunc.c`` interaction_types = [ @@ -206,5 +225,5 @@ ("DVV/DL", "dVvdw/dl", None), ("DVB/DL", "dVbonded/dl", None), ("DVR/DL", "dVrestraint/dl", None), - ("DVT/DL", "dVtemperature/dl", None) + ("DVT/DL", "dVtemperature/dl", None), ] diff --git a/package/doc/sphinx/source/documentation_pages/analysis/polymer.rst b/package/doc/sphinx/source/documentation_pages/analysis/polymer.rst index 6641bb56894..6b7d9b81138 100644 --- a/package/doc/sphinx/source/documentation_pages/analysis/polymer.rst +++ b/package/doc/sphinx/source/documentation_pages/analysis/polymer.rst @@ -1,3 +1,3 @@ .. automodule:: MDAnalysis.analysis.polymer :members: - + :inherited-members: diff --git a/package/doc/sphinx/source/documentation_pages/selections.rst b/package/doc/sphinx/source/documentation_pages/selections.rst index 6ee7f26bc2d..124e231207d 100644 --- a/package/doc/sphinx/source/documentation_pages/selections.rst +++ b/package/doc/sphinx/source/documentation_pages/selections.rst @@ -114,6 +114,15 @@ protein, backbone, nucleic, nucleicbackbone is identfied by a hard-coded set of residue names so it may not work for esoteric residues. +water + selects all atoms that belong to a set of water residues; water + is defined with a set of common water abbreviations present in + topology files and may not work with certain water residue names. + Currently the following water resnames are supported: + 3 letter resnames: ``H2O``, ``HOH``, ``OH2``, ``HHO``, ``OHH``, ``TIP``, + ``T3P``, ``T4P``, ``T5P``, ``SOL``, ``WAT``. + 4 letter resnames: ``TIP2``, ``TIP3``, ``TIP4``. + segid *seg-name* select by segid (as given in the topology), e.g. ``segid 4AKE`` or ``segid DMPC`` diff --git a/package/pyproject.toml b/package/pyproject.toml index 19d07228e7f..8be16c7f61a 100644 --- a/package/pyproject.toml +++ b/package/pyproject.toml @@ -135,6 +135,8 @@ tables\.py | MDAnalysis/visualization/.*\.py | MDAnalysis/lib/.*\.py^ | MDAnalysis/transformations/.*\.py +| MDAnalysis/topology/.*\.py +| MDAnalysis/analysis/.*\.py | MDAnalysis/guesser/.*\.py | MDAnalysis/converters/.*\.py | MDAnalysis/coordinates/.*\.py diff --git a/testsuite/MDAnalysisTests/analysis/conftest.py b/testsuite/MDAnalysisTests/analysis/conftest.py index 033a928b601..14b70fad7c7 100644 --- a/testsuite/MDAnalysisTests/analysis/conftest.py +++ b/testsuite/MDAnalysisTests/analysis/conftest.py @@ -48,107 +48,116 @@ def params_for_cls(cls, exclude: list[str] = None): ] params = [ - pytest.param({ - "backend": backend, - "n_workers": nproc - }, ) for backend in installed_backends for nproc in (2, ) + pytest.param( + {"backend": backend, "n_workers": nproc}, + ) + for backend in installed_backends + for nproc in (2,) if backend != "serial" ] params.extend([{"backend": "serial"}]) return params -@pytest.fixture(scope='module', params=params_for_cls(FrameAnalysis)) +@pytest.fixture(scope="module", params=params_for_cls(FrameAnalysis)) def client_FrameAnalysis(request): return request.param -@pytest.fixture(scope='module', params=params_for_cls(AnalysisBase)) +@pytest.fixture(scope="module", params=params_for_cls(AnalysisBase)) def client_AnalysisBase(request): return request.param -@pytest.fixture(scope='module', params=params_for_cls(AnalysisFromFunction)) +@pytest.fixture(scope="module", params=params_for_cls(AnalysisFromFunction)) def client_AnalysisFromFunction(request): return request.param -@pytest.fixture(scope='module', - params=params_for_cls(AnalysisFromFunction, - exclude=['multiprocessing'])) +@pytest.fixture( + scope="module", + params=params_for_cls(AnalysisFromFunction, exclude=["multiprocessing"]), +) def client_AnalysisFromFunctionAnalysisClass(request): return request.param -@pytest.fixture(scope='module', params=params_for_cls(IncompleteAnalysis)) +@pytest.fixture(scope="module", params=params_for_cls(IncompleteAnalysis)) def client_IncompleteAnalysis(request): return request.param -@pytest.fixture(scope='module', params=params_for_cls(OldAPIAnalysis)) +@pytest.fixture(scope="module", params=params_for_cls(OldAPIAnalysis)) def client_OldAPIAnalysis(request): return request.param # MDAnalysis.analysis.rms -@pytest.fixture(scope='module', params=params_for_cls(RMSD)) + +@pytest.fixture(scope="module", params=params_for_cls(RMSD)) def client_RMSD(request): return request.param -@pytest.fixture(scope='module', params=params_for_cls(RMSF)) +@pytest.fixture(scope="module", params=params_for_cls(RMSF)) def client_RMSF(request): return request.param # MDAnalysis.analysis.dihedrals -@pytest.fixture(scope='module', params=params_for_cls(Dihedral)) + +@pytest.fixture(scope="module", params=params_for_cls(Dihedral)) def client_Dihedral(request): return request.param -@pytest.fixture(scope='module', params=params_for_cls(Ramachandran)) +@pytest.fixture(scope="module", params=params_for_cls(Ramachandran)) def client_Ramachandran(request): return request.param -@pytest.fixture(scope='module', params=params_for_cls(Janin)) +@pytest.fixture(scope="module", params=params_for_cls(Janin)) def client_Janin(request): return request.param # MDAnalysis.analysis.gnm - -@pytest.fixture(scope='module', params=params_for_cls(GNMAnalysis)) + + +@pytest.fixture(scope="module", params=params_for_cls(GNMAnalysis)) def client_GNMAnalysis(request): return request.param # MDAnalysis.analysis.bat -@pytest.fixture(scope='module', params=params_for_cls(BAT)) + +@pytest.fixture(scope="module", params=params_for_cls(BAT)) def client_BAT(request): return request.param # MDAnalysis.analysis.dssp.dssp + @pytest.fixture(scope="module", params=params_for_cls(DSSP)) def client_DSSP(request): return request.param - + # MDAnalysis.analysis.hydrogenbonds - -@pytest.fixture(scope='module', params=params_for_cls(HydrogenBondAnalysis)) + + +@pytest.fixture(scope="module", params=params_for_cls(HydrogenBondAnalysis)) def client_HydrogenBondAnalysis(request): return request.param # MDAnalysis.analysis.nucleicacids + @pytest.fixture(scope="module", params=params_for_cls(NucPairDist)) def client_NucPairDist(request): return request.param @@ -156,6 +165,7 @@ def client_NucPairDist(request): # MDAnalysis.analysis.contacts + @pytest.fixture(scope="module", params=params_for_cls(Contacts)) def client_Contacts(request): return request.param @@ -163,7 +173,8 @@ def client_Contacts(request): # MDAnalysis.analysis.density -@pytest.fixture(scope='module', params=params_for_cls(DensityAnalysis)) + +@pytest.fixture(scope="module", params=params_for_cls(DensityAnalysis)) def client_DensityAnalysis(request): return request.param diff --git a/testsuite/MDAnalysisTests/analysis/test_align.py b/testsuite/MDAnalysisTests/analysis/test_align.py index 31455198bec..fbda36b1580 100644 --- a/testsuite/MDAnalysisTests/analysis/test_align.py +++ b/testsuite/MDAnalysisTests/analysis/test_align.py @@ -31,15 +31,23 @@ import pytest from MDAnalysis import SelectionError, SelectionWarning from MDAnalysisTests import executable_not_found -from MDAnalysisTests.datafiles import (PSF, DCD, CRD, FASTA, ALIGN_BOUND, - ALIGN_UNBOUND, PDB_helix) +from MDAnalysisTests.datafiles import ( + PSF, + DCD, + CRD, + FASTA, + ALIGN_BOUND, + ALIGN_UNBOUND, + PDB_helix, +) from numpy.testing import ( assert_equal, assert_array_equal, assert_allclose, ) -#Function for Parametrizing conditional raising + +# Function for Parametrizing conditional raising @contextmanager def does_not_raise(): yield @@ -50,10 +58,13 @@ class TestRotationMatrix: b = np.array([[0.1, 0.1, 0.1], [1.1, 1.1, 1.1]]) w = np.array([1.3, 2.3]) - @pytest.mark.parametrize('a, b, weights, expected', ( + @pytest.mark.parametrize( + "a, b, weights, expected", + ( (a, b, None, 0.15785647734415692), (a, b, w, 0.13424643502242328), - )) + ), + ) def test_rotation_matrix_input(self, a, b, weights, expected): rot, rmsd = align.rotation_matrix(a, b, weights) assert_equal(rot, np.eye(3)) @@ -68,11 +79,8 @@ def test_list_args(self): assert rmsd == pytest.approx(0.13424643502242328) def test_exception(self): - a = [[0.1, 0.2, 0.3], - [1.1, 1.1, 1.1], - [2, 2, 2]] - b = [[0.1, 0.1, 0.1], - [1.1, 1.1, 1.1]] + a = [[0.1, 0.2, 0.3], [1.1, 1.1, 1.1], [2, 2, 2]] + b = [[0.1, 0.1, 0.1], [1.1, 1.1, 1.1]] with pytest.raises(ValueError): align.rotation_matrix(a, b) @@ -91,20 +99,23 @@ def reference(): @staticmethod @pytest.fixture() def reference_small(reference): - return mda.Merge(reference.select_atoms( - "not name H* and not atom 4AKE 1 CA")) + return mda.Merge( + reference.select_atoms("not name H* and not atom 4AKE 1 CA") + ) @pytest.mark.parametrize("strict", (True, False)) - def test_match(self, universe, reference, strict, - selection="protein and backbone"): + def test_match( + self, universe, reference, strict, selection="protein and backbone" + ): ref = reference.select_atoms(selection) mobile = universe.select_atoms(selection) groups = align.get_matching_atoms(ref, mobile, strict=strict) assert_equal(groups[0].names, groups[1].names) @pytest.mark.parametrize("strict", (True, False)) - def test_nomatch_atoms_raise(self, universe, reference, - strict, selection="protein and backbone"): + def test_nomatch_atoms_raise( + self, universe, reference, strict, selection="protein and backbone" + ): # one atom less but same residues; with strict=False should try # to get selections (but current code fails, so we also raise SelectionError) ref = reference.select_atoms(selection).atoms[1:] @@ -115,11 +126,18 @@ def test_nomatch_atoms_raise(self, universe, reference, else: with pytest.warns(SelectionWarning): with pytest.raises(SelectionError): - groups = align.get_matching_atoms(ref, mobile, strict=strict) + groups = align.get_matching_atoms( + ref, mobile, strict=strict + ) @pytest.mark.parametrize("strict", (True, False)) - def test_nomatch_residues_raise_empty(self, universe, reference_small, - strict, selection="protein and backbone"): + def test_nomatch_residues_raise_empty( + self, + universe, + reference_small, + strict, + selection="protein and backbone", + ): # one atom less and all residues different: will currently create # empty selections with strict=False, see also # https://gist.github.com/orbeckst/2686badcd15031e6c946baf9164a683d @@ -131,55 +149,78 @@ def test_nomatch_residues_raise_empty(self, universe, reference_small, else: with pytest.warns(SelectionWarning): with pytest.raises(SelectionError): - groups = align.get_matching_atoms(ref, mobile, strict=strict) + groups = align.get_matching_atoms( + ref, mobile, strict=strict + ) def test_toggle_atom_mismatch_default_error(self, universe, reference): - selection = ('resname ALA and name CA', 'resname ALA and name O') + selection = ("resname ALA and name CA", "resname ALA and name O") with pytest.raises(SelectionError): rmsd = align.alignto(universe, reference, select=selection) def test_toggle_atom_mismatch_kwarg_error(self, universe, reference): - selection = ('resname ALA and name CA', 'resname ALA and name O') + selection = ("resname ALA and name CA", "resname ALA and name O") with pytest.raises(SelectionError): - rmsd = align.alignto(universe, reference, select=selection, match_atoms=True) + rmsd = align.alignto( + universe, reference, select=selection, match_atoms=True + ) def test_toggle_atom_nomatch(self, universe, reference): - selection = ('resname ALA and name CA', 'resname ALA and name O') - rmsd = align.alignto(universe, reference, select=selection, match_atoms=False) + selection = ("resname ALA and name CA", "resname ALA and name O") + rmsd = align.alignto( + universe, reference, select=selection, match_atoms=False + ) assert rmsd[0] > 0.01 def test_toggle_atom_nomatch_mismatch_atoms(self, universe, reference): # mismatching number of atoms, but same number of residues - u = universe.select_atoms('resname ALA and name CA') - u += universe.select_atoms('resname ALA and name O')[-1] - ref = reference.select_atoms('resname ALA and name CA') + u = universe.select_atoms("resname ALA and name CA") + u += universe.select_atoms("resname ALA and name O")[-1] + ref = reference.select_atoms("resname ALA and name CA") with pytest.raises(SelectionError): - align.alignto(u, ref, select='all', match_atoms=False) - - @pytest.mark.parametrize('subselection, expectation', [ - ('resname ALA and name CA', does_not_raise()), - (mda.Universe(PSF, DCD).select_atoms('resname ALA and name CA'), does_not_raise()), - (1234, pytest.raises(TypeError)), - ]) - def test_subselection_alignto(self, universe, reference, subselection, expectation): + align.alignto(u, ref, select="all", match_atoms=False) + + @pytest.mark.parametrize( + "subselection, expectation", + [ + ("resname ALA and name CA", does_not_raise()), + ( + mda.Universe(PSF, DCD).select_atoms("resname ALA and name CA"), + does_not_raise(), + ), + (1234, pytest.raises(TypeError)), + ], + ) + def test_subselection_alignto( + self, universe, reference, subselection, expectation + ): with expectation: - rmsd = align.alignto(universe, reference, subselection=subselection) + rmsd = align.alignto( + universe, reference, subselection=subselection + ) assert_allclose(rmsd[1], 0.0, rtol=0, atol=1.5e-9) def test_no_atom_masses(self, universe): - #if no masses are present - u = mda.Universe.empty(6, 2, atom_resindex=[0, 0, 0, 1, 1, 1], trajectory=True) + # if no masses are present + u = mda.Universe.empty( + 6, 2, atom_resindex=[0, 0, 0, 1, 1, 1], trajectory=True + ) with pytest.warns(SelectionWarning): align.get_matching_atoms(u.atoms, u.atoms) def test_one_universe_has_masses(self, universe): - u = mda.Universe.empty(6, 2, atom_resindex=[0, 0, 0, 1, 1, 1], trajectory=True) - ref = mda.Universe.empty(6, 2, atom_resindex=[0, 0, 0, 1, 1, 1], trajectory=True) - ref.add_TopologyAttr('masses') + u = mda.Universe.empty( + 6, 2, atom_resindex=[0, 0, 0, 1, 1, 1], trajectory=True + ) + ref = mda.Universe.empty( + 6, 2, atom_resindex=[0, 0, 0, 1, 1, 1], trajectory=True + ) + ref.add_TopologyAttr("masses") with pytest.warns(SelectionWarning): align.get_matching_atoms(u.atoms, ref.atoms) + class TestAlign(object): @staticmethod @pytest.fixture() @@ -193,45 +234,62 @@ def reference(): def test_rmsd(self, universe, reference): universe.trajectory[0] # ensure first frame - bb = universe.select_atoms('backbone') + bb = universe.select_atoms("backbone") first_frame = bb.positions universe.trajectory[-1] last_frame = bb.positions - assert_allclose(rms.rmsd(first_frame, first_frame), 0.0, rtol=0, atol=1.5e-5, - err_msg="error: rmsd(X,X) should be 0") + assert_allclose( + rms.rmsd(first_frame, first_frame), + 0.0, + rtol=0, + atol=1.5e-5, + err_msg="error: rmsd(X,X) should be 0", + ) # rmsd(A,B) = rmsd(B,A) should be exact but spurious failures in the # 9th decimal have been observed (see Issue 57 comment #1) so we relax # the test to 6 decimals. rmsd = rms.rmsd(first_frame, last_frame, superposition=True) assert_allclose( - rms.rmsd(last_frame, first_frame, superposition=True), rmsd, rtol=0, atol=1.5e-6, - err_msg="error: rmsd() is not symmetric") - assert_allclose(rmsd, 6.820321761927005, rtol=0, atol=1.5e-5, - err_msg="RMSD calculation between 1st and last AdK frame gave wrong answer") + rms.rmsd(last_frame, first_frame, superposition=True), + rmsd, + rtol=0, + atol=1.5e-6, + err_msg="error: rmsd() is not symmetric", + ) + assert_allclose( + rmsd, + 6.820321761927005, + rtol=0, + atol=1.5e-5, + err_msg="RMSD calculation between 1st and last AdK frame gave wrong answer", + ) # test masses as weights last_atoms_weight = universe.atoms.masses A = universe.trajectory[0] B = reference.trajectory[-1] - rmsd = align.alignto(universe, reference, weights='mass') - rmsd_sup_weight = rms.rmsd(A, B, weights=last_atoms_weight, center=True, - superposition=True) + rmsd = align.alignto(universe, reference, weights="mass") + rmsd_sup_weight = rms.rmsd( + A, B, weights=last_atoms_weight, center=True, superposition=True + ) assert_allclose(rmsd[1], rmsd_sup_weight, rtol=0, atol=1.5e-6) def test_rmsd_custom_mass_weights(self, universe, reference): last_atoms_weight = universe.atoms.masses A = universe.trajectory[0] B = reference.trajectory[-1] - rmsd = align.alignto(universe, reference, - weights=reference.atoms.masses) - rmsd_sup_weight = rms.rmsd(A, B, weights=last_atoms_weight, center=True, - superposition=True) + rmsd = align.alignto( + universe, reference, weights=reference.atoms.masses + ) + rmsd_sup_weight = rms.rmsd( + A, B, weights=last_atoms_weight, center=True, superposition=True + ) assert_allclose(rmsd[1], rmsd_sup_weight, rtol=0, atol=1.5e-6) def test_rmsd_custom_weights(self, universe, reference): weights = np.zeros(universe.atoms.n_atoms) - ca = universe.select_atoms('name CA') + ca = universe.select_atoms("name CA") weights[ca.indices] = 1 - rmsd = align.alignto(universe, reference, select='name CA') + rmsd = align.alignto(universe, reference, select="name CA") rmsd_weights = align.alignto(universe, reference, weights=weights) assert_allclose(rmsd[1], rmsd_weights[1], rtol=0, atol=1.5e-6) @@ -240,19 +298,23 @@ def test_AlignTraj_outfile_default(self, universe, reference, tmpdir): reference.trajectory[-1] x = align.AlignTraj(universe, reference) try: - assert os.path.basename(x.filename) == 'rmsfit_adk_dims.dcd' + assert os.path.basename(x.filename) == "rmsfit_adk_dims.dcd" finally: x._writer.close() - def test_AlignTraj_outfile_default_exists(self, universe, reference, tmpdir): + def test_AlignTraj_outfile_default_exists( + self, universe, reference, tmpdir + ): reference.trajectory[-1] - outfile = str(tmpdir.join('align_test.dcd')) + outfile = str(tmpdir.join("align_test.dcd")) align.AlignTraj(universe, reference, filename=outfile).run() fitted = mda.Universe(PSF, outfile) # ensure default file exists - with mda.Writer(str(tmpdir.join("rmsfit_align_test.dcd")), - n_atoms=fitted.atoms.n_atoms) as w: + with mda.Writer( + str(tmpdir.join("rmsfit_align_test.dcd")), + n_atoms=fitted.atoms.n_atoms, + ) as w: w.write(fitted.atoms) with tmpdir.as_cwd(): @@ -264,13 +326,13 @@ def test_AlignTraj_outfile_default_exists(self, universe, reference, tmpdir): def test_AlignTraj_step_works(self, universe, reference, tmpdir): reference.trajectory[-1] - outfile = str(tmpdir.join('align_test.dcd')) + outfile = str(tmpdir.join("align_test.dcd")) # this shouldn't throw an exception align.AlignTraj(universe, reference, filename=outfile).run(step=10) def test_AlignTraj_deprecated_attribute(self, universe, reference, tmpdir): reference.trajectory[-1] - outfile = str(tmpdir.join('align_test.dcd')) + outfile = str(tmpdir.join("align_test.dcd")) x = align.AlignTraj(universe, reference, filename=outfile).run(stop=2) wmsg = "The `rmsd` attribute was deprecated in MDAnalysis 2.0.0" @@ -279,7 +341,7 @@ def test_AlignTraj_deprecated_attribute(self, universe, reference, tmpdir): def test_AlignTraj(self, universe, reference, tmpdir): reference.trajectory[-1] - outfile = str(tmpdir.join('align_test.dcd')) + outfile = str(tmpdir.join("align_test.dcd")) x = align.AlignTraj(universe, reference, filename=outfile).run() fitted = mda.Universe(PSF, outfile) @@ -293,58 +355,84 @@ def test_AlignTraj(self, universe, reference, tmpdir): self._assert_rmsd(reference, fitted, -1, 0.0) def test_AlignTraj_weighted(self, universe, reference, tmpdir): - outfile = str(tmpdir.join('align_test.dcd')) - x = align.AlignTraj(universe, reference, - filename=outfile, weights='mass').run() + outfile = str(tmpdir.join("align_test.dcd")) + x = align.AlignTraj( + universe, reference, filename=outfile, weights="mass" + ).run() fitted = mda.Universe(PSF, outfile) assert_allclose(x.results.rmsd[0], 0, rtol=0, atol=1.5e-3) assert_allclose(x.results.rmsd[-1], 6.9033, rtol=0, atol=1.5e-3) - self._assert_rmsd(reference, fitted, 0, 0.0, - weights=universe.atoms.masses) - self._assert_rmsd(reference, fitted, -1, 6.929083032629219, - weights=universe.atoms.masses) + self._assert_rmsd( + reference, fitted, 0, 0.0, weights=universe.atoms.masses + ) + self._assert_rmsd( + reference, + fitted, + -1, + 6.929083032629219, + weights=universe.atoms.masses, + ) def test_AlignTraj_custom_weights(self, universe, reference, tmpdir): weights = np.zeros(universe.atoms.n_atoms) - ca = universe.select_atoms('name CA') + ca = universe.select_atoms("name CA") weights[ca.indices] = 1 - outfile = str(tmpdir.join('align_test.dcd')) + outfile = str(tmpdir.join("align_test.dcd")) - x = align.AlignTraj(universe, reference, - filename=outfile, select='name CA').run() - x_weights = align.AlignTraj(universe, reference, - filename=outfile, weights=weights).run() + x = align.AlignTraj( + universe, reference, filename=outfile, select="name CA" + ).run() + x_weights = align.AlignTraj( + universe, reference, filename=outfile, weights=weights + ).run() - assert_allclose(x.results.rmsd, x_weights.results.rmsd, rtol=0, atol=1.5e-7) + assert_allclose( + x.results.rmsd, x_weights.results.rmsd, rtol=0, atol=1.5e-7 + ) def test_AlignTraj_custom_mass_weights(self, universe, reference, tmpdir): - outfile = str(tmpdir.join('align_test.dcd')) - x = align.AlignTraj(universe, reference, - filename=outfile, - weights=reference.atoms.masses).run() + outfile = str(tmpdir.join("align_test.dcd")) + x = align.AlignTraj( + universe, + reference, + filename=outfile, + weights=reference.atoms.masses, + ).run() fitted = mda.Universe(PSF, outfile) assert_allclose(x.results.rmsd[0], 0, rtol=0, atol=1.5e-3) assert_allclose(x.results.rmsd[-1], 6.9033, rtol=0, atol=1.5e-3) - self._assert_rmsd(reference, fitted, 0, 0.0, - weights=universe.atoms.masses) - self._assert_rmsd(reference, fitted, -1, 6.929083032629219, - weights=universe.atoms.masses) + self._assert_rmsd( + reference, fitted, 0, 0.0, weights=universe.atoms.masses + ) + self._assert_rmsd( + reference, + fitted, + -1, + 6.929083032629219, + weights=universe.atoms.masses, + ) def test_AlignTraj_partial_fit(self, universe, reference, tmpdir): - outfile = str(tmpdir.join('align_test.dcd')) + outfile = str(tmpdir.join("align_test.dcd")) # fitting on a partial selection should still write the whole topology - align.AlignTraj(universe, reference, select='resid 1-20', - filename=outfile, weights='mass').run() + align.AlignTraj( + universe, + reference, + select="resid 1-20", + filename=outfile, + weights="mass", + ).run() mda.Universe(PSF, outfile) def test_AlignTraj_in_memory(self, universe, reference, tmpdir): - outfile = str(tmpdir.join('align_test.dcd')) + outfile = str(tmpdir.join("align_test.dcd")) reference.trajectory[-1] - x = align.AlignTraj(universe, reference, filename=outfile, - in_memory=True).run() + x = align.AlignTraj( + universe, reference, filename=outfile, in_memory=True + ).run() assert x.filename is None assert_allclose(x.results.rmsd[0], 6.9290, rtol=0, atol=1.5e-3) assert_allclose(x.results.rmsd[-1], 5.2797e-07, rtol=0, atol=1.5e-3) @@ -357,20 +445,31 @@ def test_AlignTraj_writer_kwargs(self, universe, reference, tmpdir): # Issue 4564 writer_kwargs = dict(precision=2) with tmpdir.as_cwd(): - aligner = align.AlignTraj(universe, reference, - select='protein and name CA', - filename='aligned_traj.xtc', - writer_kwargs=writer_kwargs, - in_memory=False).run() + aligner = align.AlignTraj( + universe, + reference, + select="protein and name CA", + filename="aligned_traj.xtc", + writer_kwargs=writer_kwargs, + in_memory=False, + ).run() assert_equal(aligner._writer.precision, 2) def _assert_rmsd(self, reference, fitted, frame, desired, weights=None): fitted.trajectory[frame] - rmsd = rms.rmsd(reference.atoms.positions, fitted.atoms.positions, - superposition=True) - assert_allclose(rmsd, desired, rtol = 0, atol=1.5e-5, - err_msg="frame {0:d} of fit does not have " - "expected RMSD".format(frame)) + rmsd = rms.rmsd( + reference.atoms.positions, + fitted.atoms.positions, + superposition=True, + ) + assert_allclose( + rmsd, + desired, + rtol=0, + atol=1.5e-5, + err_msg="frame {0:d} of fit does not have " + "expected RMSD".format(frame), + ) def test_alignto_checks_selections(self, universe, reference): """Testing that alignto() fails if selections do not @@ -396,15 +495,16 @@ def different_atoms(): def test_alignto_partial_universe(self, universe, reference): u_bound = mda.Universe(ALIGN_BOUND) u_free = mda.Universe(ALIGN_UNBOUND) - selection = 'segid B' + selection = "segid B" segB_bound = u_bound.select_atoms(selection) segB_free = u_free.select_atoms(selection) segB_free.translate(segB_bound.centroid() - segB_free.centroid()) align.alignto(u_free, u_bound, select=selection) - assert_allclose(segB_bound.positions, segB_free.positions, - rtol=0, atol=1.5e-3) + assert_allclose( + segB_bound.positions, segB_free.positions, rtol=0, atol=1.5e-3 + ) def _get_aligned_average_positions(ref_files, ref, select="all", **kwargs): @@ -412,9 +512,10 @@ def _get_aligned_average_positions(ref_files, ref, select="all", **kwargs): prealigner = align.AlignTraj(u, ref, select=select, **kwargs).run() ag = u.select_atoms(select) reference_coordinates = u.trajectory.timeseries(asel=ag).mean(axis=1) - rmsd = sum(prealigner.results.rmsd/len(u.trajectory)) + rmsd = sum(prealigner.results.rmsd / len(u.trajectory)) return reference_coordinates, rmsd + class TestAverageStructure(object): ref_files = (PSF, DCD) @@ -433,8 +534,10 @@ def test_average_structure_deprecated_attrs(self, universe, reference): wmsg = "The `universe` attribute was deprecated in MDAnalysis 2.0.0" with pytest.warns(DeprecationWarning, match=wmsg): - assert_equal(avg.universe.atoms.positions, - avg.results.universe.atoms.positions) + assert_equal( + avg.universe.atoms.positions, + avg.results.universe.atoms.positions, + ) wmsg = "The `positions` attribute was deprecated in MDAnalysis 2.0.0" with pytest.warns(DeprecationWarning, match=wmsg): @@ -447,34 +550,43 @@ def test_average_structure_deprecated_attrs(self, universe, reference): def test_average_structure(self, universe, reference): ref, rmsd = _get_aligned_average_positions(self.ref_files, reference) avg = align.AverageStructure(universe, reference).run() - assert_allclose(avg.results.universe.atoms.positions, ref, rtol=0, atol=1.5e-4) + assert_allclose( + avg.results.universe.atoms.positions, ref, rtol=0, atol=1.5e-4 + ) assert_allclose(avg.results.rmsd, rmsd, rtol=0, atol=1.5e-7) def test_average_structure_mass_weighted(self, universe, reference): - ref, rmsd = _get_aligned_average_positions(self.ref_files, reference, weights='mass') - avg = align.AverageStructure(universe, reference, weights='mass').run() - assert_allclose(avg.results.universe.atoms.positions, ref, - rtol=0, atol=1.5e-4) + ref, rmsd = _get_aligned_average_positions( + self.ref_files, reference, weights="mass" + ) + avg = align.AverageStructure(universe, reference, weights="mass").run() + assert_allclose( + avg.results.universe.atoms.positions, ref, rtol=0, atol=1.5e-4 + ) assert_allclose(avg.results.rmsd, rmsd, rtol=0, atol=1.5e-7) def test_average_structure_select(self, universe, reference): - select = 'protein and name CA and resid 3-5' - ref, rmsd = _get_aligned_average_positions(self.ref_files, reference, select=select) + select = "protein and name CA and resid 3-5" + ref, rmsd = _get_aligned_average_positions( + self.ref_files, reference, select=select + ) avg = align.AverageStructure(universe, reference, select=select).run() - assert_allclose(avg.results.universe.atoms.positions, ref, - rtol=0, atol=1.5e-4) + assert_allclose( + avg.results.universe.atoms.positions, ref, rtol=0, atol=1.5e-4 + ) assert_allclose(avg.results.rmsd, rmsd, rtol=0, atol=1.5e-7) def test_average_structure_no_ref(self, universe): ref, rmsd = _get_aligned_average_positions(self.ref_files, universe) avg = align.AverageStructure(universe).run() - assert_allclose(avg.results.universe.atoms.positions, ref, - rtol=0, atol=1.5e-4) + assert_allclose( + avg.results.universe.atoms.positions, ref, rtol=0, atol=1.5e-4 + ) assert_allclose(avg.results.rmsd, rmsd, rtol=0, atol=1.5e-7) def test_average_structure_no_msf(self, universe): avg = align.AverageStructure(universe).run() - assert not hasattr(avg, 'msf') + assert not hasattr(avg, "msf") def test_mismatch_atoms(self, universe): u = mda.Merge(universe.atoms[:10]) @@ -493,15 +605,20 @@ def test_average_structure_ref_frame(self, universe): universe.trajectory[0] ref, rmsd = _get_aligned_average_positions(self.ref_files, u) avg = align.AverageStructure(universe, ref_frame=ref_frame).run() - assert_allclose(avg.results.universe.atoms.positions, ref, - rtol=0, atol=1.5e-4) + assert_allclose( + avg.results.universe.atoms.positions, ref, rtol=0, atol=1.5e-4 + ) assert_allclose(avg.results.rmsd, rmsd, rtol=0, atol=1.5e-7) def test_average_structure_in_memory(self, universe): avg = align.AverageStructure(universe, in_memory=True).run() reference_coordinates = universe.trajectory.timeseries().mean(axis=1) - assert_allclose(avg.results.universe.atoms.positions, - reference_coordinates, rtol=0, atol=1.5e-4) + assert_allclose( + avg.results.universe.atoms.positions, + reference_coordinates, + rtol=0, + atol=1.5e-4, + ) assert avg.filename is None @@ -509,57 +626,65 @@ class TestAlignmentProcessing: seq = FASTA error_msg = "selection string has unexpected length" - @pytest.mark.skipif(HAS_BIOPYTHON, reason='biopython is installed') + @pytest.mark.skipif(HAS_BIOPYTHON, reason="biopython is installed") def test_importerror_biopython(self): errmsg = "The `fasta2select` method requires an installation" with pytest.raises(ImportError, match=errmsg): _ = align.fasta2select(self.seq, is_aligned=True) - @pytest.mark.skipif(not HAS_BIOPYTHON, reason='requires biopython') + @pytest.mark.skipif(not HAS_BIOPYTHON, reason="requires biopython") def test_fasta2select_aligned(self): """test align.fasta2select() on aligned FASTA (Issue 112)""" sel = align.fasta2select(self.seq, is_aligned=True) # length of the output strings, not residues or anything real... - assert len(sel['reference']) == 30623, self.error_msg - assert len(sel['mobile']) == 30623, self.error_msg + assert len(sel["reference"]) == 30623, self.error_msg + assert len(sel["mobile"]) == 30623, self.error_msg @pytest.mark.skipif( executable_not_found("clustalw2") or not HAS_BIOPYTHON, - reason="Test skipped because clustalw2 executable not found") + reason="Test skipped because clustalw2 executable not found", + ) def test_fasta2select_file(self, tmpdir): """test align.fasta2select() on a non-aligned FASTA with default filenames""" with tmpdir.as_cwd(): - sel = align.fasta2select(self.seq, is_aligned=False, - alnfilename=None, treefilename=None) - assert len(sel['reference']) == 23080, self.error_msg - assert len(sel['mobile']) == 23090, self.error_msg + sel = align.fasta2select( + self.seq, is_aligned=False, alnfilename=None, treefilename=None + ) + assert len(sel["reference"]) == 23080, self.error_msg + assert len(sel["mobile"]) == 23090, self.error_msg @pytest.mark.skipif( executable_not_found("clustalw2") or not HAS_BIOPYTHON, - reason="Test skipped because clustalw2 executable not found") + reason="Test skipped because clustalw2 executable not found", + ) def test_fasta2select_ClustalW(self, tmpdir): """MDAnalysis.analysis.align: test fasta2select() with ClustalW (Issue 113)""" - alnfile = str(tmpdir.join('alignmentprocessing.aln')) - treefile = str(tmpdir.join('alignmentprocessing.dnd')) - sel = align.fasta2select(self.seq, is_aligned=False, - alnfilename=alnfile, treefilename=treefile) + alnfile = str(tmpdir.join("alignmentprocessing.aln")) + treefile = str(tmpdir.join("alignmentprocessing.dnd")) + sel = align.fasta2select( + self.seq, + is_aligned=False, + alnfilename=alnfile, + treefilename=treefile, + ) # numbers computed from alignment with clustalw 2.1 on Mac OS X # [orbeckst] length of the output strings, not residues or anything # real... - assert len(sel['reference']) == 23080, self.error_msg - assert len(sel['mobile']) == 23090, self.error_msg + assert len(sel["reference"]) == 23080, self.error_msg + assert len(sel["mobile"]) == 23090, self.error_msg - @pytest.mark.skipif(not HAS_BIOPYTHON, reason='requires biopython') + @pytest.mark.skipif(not HAS_BIOPYTHON, reason="requires biopython") def test_fasta2select_resids(self, tmpdir): """test align.fasta2select() when resids provided (Issue #3124)""" resids = [x for x in range(705)] - sel = align.fasta2select(self.seq, is_aligned=True, - ref_resids=resids, target_resids=resids) + sel = align.fasta2select( + self.seq, is_aligned=True, ref_resids=resids, target_resids=resids + ) # length of the output strings, not residues or anything real... - assert len(sel['reference']) == 30621, self.error_msg - assert len(sel['mobile']) == 30621, self.error_msg + assert len(sel["reference"]) == 30621, self.error_msg + assert len(sel["mobile"]) == 30621, self.error_msg class TestSequenceAlignmentFunction: @@ -573,14 +698,14 @@ def atomgroups(): mobile = universe.select_atoms("resid 122-159") return reference, mobile - @pytest.mark.skipif(HAS_BIOPYTHON, reason='biopython installed') + @pytest.mark.skipif(HAS_BIOPYTHON, reason="biopython installed") def test_biopython_import_error(self, atomgroups): ref, mob = atomgroups errmsg = "The `sequence_alignment` method requires an installation of" with pytest.raises(ImportError, match=errmsg): align.sequence_alignment(mob, ref) - @pytest.mark.skipif(not HAS_BIOPYTHON, reason='requires biopython') + @pytest.mark.skipif(not HAS_BIOPYTHON, reason="requires biopython") @pytest.mark.filterwarnings("ignore:`sequence_alignment` is deprecated!") def test_sequence_alignment(self, atomgroups): reference, mobile = atomgroups @@ -589,18 +714,24 @@ def test_sequence_alignment(self, atomgroups): assert len(aln) == 5, "return value has wrong tuple size" seqA, seqB, score, begin, end = aln - assert_equal(seqA, reference.residues.sequence(format="string"), - err_msg="reference sequence mismatch") - assert mobile.residues.sequence( - format="string") in seqB, "mobile sequence mismatch" - assert score == pytest.approx(54.6) + assert_equal( + seqA, + reference.residues.sequence(format="string"), + err_msg="reference sequence mismatch", + ) + assert ( + mobile.residues.sequence(format="string") in seqB + ), "mobile sequence mismatch" + assert score == pytest.approx(54.6) assert_array_equal([begin, end], [0, reference.n_residues]) - @pytest.mark.skipif(not HAS_BIOPYTHON, reason='requires biopython') + @pytest.mark.skipif(not HAS_BIOPYTHON, reason="requires biopython") def test_sequence_alignment_deprecation(self, atomgroups): reference, mobile = atomgroups - wmsg = ("`sequence_alignment` is deprecated!\n" - "`sequence_alignment` will be removed in release 3.0.") + wmsg = ( + "`sequence_alignment` is deprecated!\n" + "`sequence_alignment` will be removed in release 3.0." + ) with pytest.warns(DeprecationWarning, match=wmsg): align.sequence_alignment(mobile, reference) @@ -630,14 +761,13 @@ def test_iterative_average_default(self, mobile): [10.54679871, 9.49505306, -8.61215292], [9.99500556, 9.16624224, -7.75231192], [9.83897407, 9.93134598, -9.29541129], - [11.45760169, 10.5857071, -8.13037669] + [11.45760169, 10.5857071, -8.13037669], ], atol=1e-5, ) def test_iterative_average_eps_high(self, mobile): - res = align.iterative_average(mobile, select="bynum 1:10", - eps=1e-5) + res = align.iterative_average(mobile, select="bynum 1:10", eps=1e-5) assert_allclose( res.results.positions, [ @@ -650,15 +780,15 @@ def test_iterative_average_eps_high(self, mobile): [10.54679871, 9.49505306, -8.61215292], [9.99500556, 9.16624224, -7.75231192], [9.83897407, 9.93134598, -9.29541129], - [11.45760169, 10.5857071, -8.13037669] + [11.45760169, 10.5857071, -8.13037669], ], atol=1e-5, ) def test_iterative_average_weights_mass(self, mobile, reference): - res = align.iterative_average(mobile, reference, - select="bynum 1:10", - niter=10, weights="mass") + res = align.iterative_average( + mobile, reference, select="bynum 1:10", niter=10, weights="mass" + ) assert_allclose( res.results.positions, [ @@ -671,19 +801,17 @@ def test_iterative_average_weights_mass(self, mobile, reference): [10.37499697, 9.13535837, -8.3732586], [9.83883314, 8.57939098, -7.6195549], [9.64405257, 9.55924307, -9.04315991], - [11.0678934, 10.27798773, -7.64881842] + [11.0678934, 10.27798773, -7.64881842], ], atol=1e-5, ) def test_iterative_average_convergence_failure(self, mobile, reference): with pytest.raises(RuntimeError): - _ = align.iterative_average(mobile, reference, - niter=1, eps=0) + _ = align.iterative_average(mobile, reference, niter=1, eps=0) def test_iterative_average_convergence_verbose(self, mobile, reference): - _ = align.iterative_average(mobile, select="bynum 1:10", - verbose=True) + _ = align.iterative_average(mobile, select="bynum 1:10", verbose=True) def test_alignto_reorder_atomgroups(): @@ -691,5 +819,5 @@ def test_alignto_reorder_atomgroups(): u = mda.Universe(PDB_helix) mobile = u.atoms[:4] ref = u.atoms[[3, 2, 1, 0]] - rmsd = align.alignto(mobile, ref, select='bynum 1-4') + rmsd = align.alignto(mobile, ref, select="bynum 1-4") assert_allclose(rmsd, (0.0, 0.0)) diff --git a/testsuite/MDAnalysisTests/analysis/test_atomicdistances.py b/testsuite/MDAnalysisTests/analysis/test_atomicdistances.py index 443173cff70..4697485470b 100644 --- a/testsuite/MDAnalysisTests/analysis/test_atomicdistances.py +++ b/testsuite/MDAnalysisTests/analysis/test_atomicdistances.py @@ -79,8 +79,9 @@ def ad_ag4(): @staticmethod @pytest.fixture() def expected_dist(ad_ag1, ad_ag2): - expected = np.zeros((len(ad_ag1.universe.trajectory), - ad_ag1.atoms.n_atoms)) + expected = np.zeros( + (len(ad_ag1.universe.trajectory), ad_ag1.atoms.n_atoms) + ) # calculate distances without PBCs using dist() for i, ts in enumerate(ad_ag1.universe.trajectory): @@ -90,8 +91,9 @@ def expected_dist(ad_ag1, ad_ag2): @staticmethod @pytest.fixture() def expected_pbc_dist(ad_ag1, ad_ag2): - expected = np.zeros((len(ad_ag1.universe.trajectory), - ad_ag1.atoms.n_atoms)) + expected = np.zeros( + (len(ad_ag1.universe.trajectory), ad_ag1.atoms.n_atoms) + ) # calculate distances with PBCs using dist() for i, ts in enumerate(ad_ag1.universe.trajectory): @@ -99,8 +101,8 @@ def expected_pbc_dist(ad_ag1, ad_ag2): return expected def test_ad_exceptions(self, ad_ag1, ad_ag3, ad_ag4): - '''Test exceptions raised when number of atoms do not - match or AtomGroups come from different trajectories.''' + """Test exceptions raised when number of atoms do not + match or AtomGroups come from different trajectories.""" # number of atoms do not match match_exp_atoms = "AtomGroups do not" @@ -114,22 +116,19 @@ def test_ad_exceptions(self, ad_ag1, ad_ag3, ad_ag4): # only need to test that this class correctly applies distance calcs # calc_bonds() is tested elsewhere - def test_ad_pairwise_dist(self, ad_ag1, ad_ag2, - expected_dist): - '''Ensure that pairwise distances between atoms are - correctly calculated without PBCs.''' - pairwise_no_pbc = (ad.AtomicDistances(ad_ag1, ad_ag2, - pbc=False).run()) + def test_ad_pairwise_dist(self, ad_ag1, ad_ag2, expected_dist): + """Ensure that pairwise distances between atoms are + correctly calculated without PBCs.""" + pairwise_no_pbc = ad.AtomicDistances(ad_ag1, ad_ag2, pbc=False).run() actual = pairwise_no_pbc.results # compare with expected values from dist() assert_allclose(actual, expected_dist) - def test_ad_pairwise_dist_pbc(self, ad_ag1, ad_ag2, - expected_pbc_dist): - '''Ensure that pairwise distances between atoms are - correctly calculated with PBCs.''' - pairwise_pbc = (ad.AtomicDistances(ad_ag1, ad_ag2).run()) + def test_ad_pairwise_dist_pbc(self, ad_ag1, ad_ag2, expected_pbc_dist): + """Ensure that pairwise distances between atoms are + correctly calculated with PBCs.""" + pairwise_pbc = ad.AtomicDistances(ad_ag1, ad_ag2).run() actual = pairwise_pbc.results # compare with expected values from dist() diff --git a/testsuite/MDAnalysisTests/analysis/test_backends.py b/testsuite/MDAnalysisTests/analysis/test_backends.py index a4c105e082a..471d367a603 100644 --- a/testsuite/MDAnalysisTests/analysis/test_backends.py +++ b/testsuite/MDAnalysisTests/analysis/test_backends.py @@ -51,22 +51,36 @@ def test_all_backends_give_correct_results(self, func, iterable, answer): for answ in backends_dict.values(): assert answ == answer - @pytest.mark.parametrize("backend_cls,params,warning_message", [ - (backends.BackendSerial, { - 'n_workers': 5 - }, "n_workers is ignored when executing with backend='serial'"), - ]) + @pytest.mark.parametrize( + "backend_cls,params,warning_message", + [ + ( + backends.BackendSerial, + {"n_workers": 5}, + "n_workers is ignored when executing with backend='serial'", + ), + ], + ) def test_get_warnings(self, backend_cls, params, warning_message): with pytest.warns(UserWarning, match=warning_message): backend_cls(**params) - @pytest.mark.parametrize("backend_cls,params,error_message", [ - pytest.param(backends.BackendDask, {'n_workers': 2}, - ("module 'dask' is missing. Please install 'dask': " - "https://docs.dask.org/en/stable/install.html"), - marks=pytest.mark.skipif(is_installed('dask'), - reason='dask is installed')) - ]) + @pytest.mark.parametrize( + "backend_cls,params,error_message", + [ + pytest.param( + backends.BackendDask, + {"n_workers": 2}, + ( + "module 'dask' is missing. Please install 'dask': " + "https://docs.dask.org/en/stable/install.html" + ), + marks=pytest.mark.skipif( + is_installed("dask"), reason="dask is installed" + ), + ) + ], + ) def test_get_errors(self, backend_cls, params, error_message): with pytest.raises(ValueError, match=error_message): backend_cls(**params) diff --git a/testsuite/MDAnalysisTests/analysis/test_base.py b/testsuite/MDAnalysisTests/analysis/test_base.py index 377d70602ba..e369c4c6021 100644 --- a/testsuite/MDAnalysisTests/analysis/test_base.py +++ b/testsuite/MDAnalysisTests/analysis/test_base.py @@ -20,29 +20,25 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -from collections import UserDict import pickle - -import pytest - -import numpy as np - -from numpy.testing import assert_equal, assert_allclose +from collections import UserDict import MDAnalysis as mda import numpy as np import pytest -from MDAnalysis.analysis import base, backends +from MDAnalysis.analysis import backends, base +from numpy.testing import assert_allclose, assert_almost_equal, assert_equal + from MDAnalysisTests.datafiles import DCD, PSF, TPR, XTC from MDAnalysisTests.util import no_deprecated_call -from numpy.testing import assert_almost_equal, assert_equal class FrameAnalysis(base.AnalysisBase): """Just grabs frame numbers of frames it goes over""" @classmethod - def get_supported_backends(cls): return ('serial', 'dask', 'multiprocessing') + def get_supported_backends(cls): + return ("serial", "dask", "multiprocessing") _analysis_algorithm_is_parallelizable = True @@ -60,7 +56,10 @@ def _conclude(self): self.found_frames = list(self.results.found_frames) def _get_aggregator(self): - return base.ResultsGroup({'found_frames': base.ResultsGroup.ndarray_hstack}) + return base.ResultsGroup( + {"found_frames": base.ResultsGroup.ndarray_hstack} + ) + class IncompleteAnalysis(base.AnalysisBase): def __init__(self, reader, **kwargs): @@ -80,42 +79,57 @@ def _prepare(self): self.results = base.Results() -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def u(): return mda.Universe(PSF, DCD) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def u_xtc(): return mda.Universe(TPR, XTC) # dt = 100 -FRAMES_ERR = 'AnalysisBase.frames is incorrect' -TIMES_ERR = 'AnalysisBase.times is incorrect' +FRAMES_ERR = "AnalysisBase.frames is incorrect" +TIMES_ERR = "AnalysisBase.times is incorrect" + class Parallelizable(base.AnalysisBase): _analysis_algorithm_is_parallelizable = True + @classmethod - def get_supported_backends(cls): return ('multiprocessing', 'dask') - def _single_frame(self): pass + def get_supported_backends(cls): + return ("multiprocessing", "dask") + + def _single_frame(self): + pass + class SerialOnly(base.AnalysisBase): - def _single_frame(self): pass + def _single_frame(self): + pass + class ParallelizableWithDaskOnly(base.AnalysisBase): _analysis_algorithm_is_parallelizable = True + @classmethod - def get_supported_backends(cls): return ('dask',) - def _single_frame(self): pass + def get_supported_backends(cls): + return ("dask",) + + def _single_frame(self): + pass + class CustomSerialBackend(backends.BackendBase): def apply(self, func, computations): return [func(task) for task in computations] + class ManyWorkersBackend(backends.BackendBase): def apply(self, func, computations): return [func(task) for task in computations] + def test_incompatible_n_workers(u): backend = ManyWorkersBackend(n_workers=2) with pytest.raises(ValueError): @@ -126,91 +140,137 @@ def test_frame_values_incompatability(u): start, stop, step = 0, 4, 1 frames = [1, 2, 3, 4] - with pytest.raises(ValueError, - match="start/stop/step cannot be combined with frames"): + with pytest.raises( + ValueError, match="start/stop/step cannot be combined with frames" + ): FrameAnalysis(u.trajectory).run( - frames=frames, - start=start, - stop=stop, - step=step + frames=frames, start=start, stop=stop, step=step ) + def test_n_workers_conflict_raises_value_error(u): backend_instance = ManyWorkersBackend(n_workers=4) with pytest.raises(ValueError, match="n_workers specified twice"): FrameAnalysis(u.trajectory).run( - backend=backend_instance, - n_workers=1, - unsupported_backend=True + backend=backend_instance, n_workers=1, unsupported_backend=True ) -@pytest.mark.parametrize('run_class,backend,n_workers', [ - (Parallelizable, 'not-existing-backend', 2), - (Parallelizable, 'not-existing-backend', None), - (SerialOnly, 'not-existing-backend', 2), - (SerialOnly, 'not-existing-backend', None), - (SerialOnly, 'multiprocessing', 2), - (SerialOnly, 'dask', None), - (ParallelizableWithDaskOnly, 'multiprocessing', None), - (ParallelizableWithDaskOnly, 'multiprocessing', 2), -]) + +@pytest.mark.parametrize( + "run_class,backend,n_workers", + [ + (Parallelizable, "not-existing-backend", 2), + (Parallelizable, "not-existing-backend", None), + (SerialOnly, "not-existing-backend", 2), + (SerialOnly, "not-existing-backend", None), + (SerialOnly, "multiprocessing", 2), + (SerialOnly, "dask", None), + (ParallelizableWithDaskOnly, "multiprocessing", None), + (ParallelizableWithDaskOnly, "multiprocessing", 2), + ], +) def test_backend_configuration_fails(u, run_class, backend, n_workers): u = mda.Universe(TPR, XTC) # dt = 100 with pytest.raises(ValueError): - _ = run_class(u.trajectory).run(backend=backend, n_workers=n_workers, stop=0) + _ = run_class(u.trajectory).run( + backend=backend, n_workers=n_workers, stop=0 + ) + -@pytest.mark.parametrize('run_class,backend,n_workers', [ - (Parallelizable, CustomSerialBackend, 2), - (ParallelizableWithDaskOnly, CustomSerialBackend, 2), -]) -def test_backend_configuration_works_when_unsupported_backend(u, run_class, backend, n_workers): +@pytest.mark.parametrize( + "run_class,backend,n_workers", + [ + (Parallelizable, CustomSerialBackend, 2), + (ParallelizableWithDaskOnly, CustomSerialBackend, 2), + ], +) +def test_backend_configuration_works_when_unsupported_backend( + u, run_class, backend, n_workers +): u = mda.Universe(TPR, XTC) # dt = 100 backend_instance = backend(n_workers=n_workers) - _ = run_class(u.trajectory).run(backend=backend_instance, n_workers=n_workers, stop=0, unsupported_backend=True) + _ = run_class(u.trajectory).run( + backend=backend_instance, + n_workers=n_workers, + stop=0, + unsupported_backend=True, + ) + -@pytest.mark.parametrize('run_class,backend,n_workers', [ - (Parallelizable, CustomSerialBackend, 1), - (ParallelizableWithDaskOnly, CustomSerialBackend, 1), -]) +@pytest.mark.parametrize( + "run_class,backend,n_workers", + [ + (Parallelizable, CustomSerialBackend, 1), + (ParallelizableWithDaskOnly, CustomSerialBackend, 1), + ], +) def test_custom_backend_works(u, run_class, backend, n_workers): backend_instance = backend(n_workers=n_workers) u = mda.Universe(TPR, XTC) # dt = 100 - _ = run_class(u.trajectory).run(backend=backend_instance, n_workers=n_workers, unsupported_backend=True) - -@pytest.mark.parametrize('run_class,backend_instance,n_workers', [ - (Parallelizable, map, 1), - (SerialOnly, list, 1), - (ParallelizableWithDaskOnly, object, 1), -]) -def test_fails_incorrect_custom_backend(u, run_class, backend_instance, n_workers): + _ = run_class(u.trajectory).run( + backend=backend_instance, n_workers=n_workers, unsupported_backend=True + ) + + +@pytest.mark.parametrize( + "run_class,backend_instance,n_workers", + [ + (Parallelizable, map, 1), + (SerialOnly, list, 1), + (ParallelizableWithDaskOnly, object, 1), + ], +) +def test_fails_incorrect_custom_backend( + u, run_class, backend_instance, n_workers +): u = mda.Universe(TPR, XTC) # dt = 100 with pytest.raises(ValueError): - _ = run_class(u.trajectory).run(backend=backend_instance, n_workers=n_workers, unsupported_backend=True) + _ = run_class(u.trajectory).run( + backend=backend_instance, + n_workers=n_workers, + unsupported_backend=True, + ) with pytest.raises(ValueError): - _ = run_class(u.trajectory).run(backend=backend_instance, n_workers=n_workers) + _ = run_class(u.trajectory).run( + backend=backend_instance, n_workers=n_workers + ) -@pytest.mark.parametrize('run_class,backend,n_workers', [ - (SerialOnly, CustomSerialBackend, 1), - (SerialOnly, 'multiprocessing', 1), - (SerialOnly, 'dask', 1), -]) + +@pytest.mark.parametrize( + "run_class,backend,n_workers", + [ + (SerialOnly, CustomSerialBackend, 1), + (SerialOnly, "multiprocessing", 1), + (SerialOnly, "dask", 1), + ], +) def test_fails_for_unparallelizable(u, run_class, backend, n_workers): u = mda.Universe(TPR, XTC) # dt = 100 with pytest.raises(ValueError): if not isinstance(backend, str): backend_instance = backend(n_workers=n_workers) - _ = run_class(u.trajectory).run(backend=backend_instance, n_workers=n_workers, unsupported_backend=True) + _ = run_class(u.trajectory).run( + backend=backend_instance, + n_workers=n_workers, + unsupported_backend=True, + ) else: - _ = run_class(u.trajectory).run(backend=backend, n_workers=n_workers, unsupported_backend=True) - -@pytest.mark.parametrize('run_kwargs,frames', [ - ({}, np.arange(98)), - ({'start': 20}, np.arange(20, 98)), - ({'stop': 30}, np.arange(30)), - ({'step': 10}, np.arange(0, 98, 10)) -]) + _ = run_class(u.trajectory).run( + backend=backend, n_workers=n_workers, unsupported_backend=True + ) + + +@pytest.mark.parametrize( + "run_kwargs,frames", + [ + ({}, np.arange(98)), + ({"start": 20}, np.arange(20, 98)), + ({"stop": 30}, np.arange(30)), + ({"step": 10}, np.arange(0, 98, 10)), + ], +) def test_start_stop_step_parallel(u, run_kwargs, frames, client_FrameAnalysis): # client_FrameAnalysis is defined [here](testsuite/MDAnalysisTests/analysis/conftest.py), # and determines a set of parameters ('backend', 'n_workers'), taking only backends @@ -219,7 +279,7 @@ def test_start_stop_step_parallel(u, run_kwargs, frames, client_FrameAnalysis): assert an.n_frames == len(frames) assert_equal(an.found_frames, frames) assert_equal(an.frames, frames, err_msg=FRAMES_ERR) - assert_almost_equal(an.times, frames+1, decimal=4, err_msg=TIMES_ERR) + assert_almost_equal(an.times, frames + 1, decimal=4, err_msg=TIMES_ERR) def test_reset_n_parts_to_n_frames(u): @@ -228,36 +288,57 @@ def test_reset_n_parts_to_n_frames(u): https://github.com/MDAnalysis/mdanalysis/issues/4685 """ a = FrameAnalysis(u.trajectory) - with pytest.warns(UserWarning, match='Set `n_parts` to'): - a.run(backend='multiprocessing', - start=0, - stop=1, - n_workers=2, - n_parts=2) - - -@pytest.mark.parametrize('run_kwargs,frames', [ - ({}, np.arange(98)), - ({'start': 20}, np.arange(20, 98)), - ({'stop': 30}, np.arange(30)), - ({'step': 10}, np.arange(0, 98, 10)) -]) + with pytest.warns(UserWarning, match="Set `n_parts` to"): + a.run( + backend="multiprocessing", start=0, stop=1, n_workers=2, n_parts=2 + ) + + +@pytest.mark.parametrize( + "run_kwargs,frames", + [ + ({}, np.arange(98)), + ({"start": 20}, np.arange(20, 98)), + ({"stop": 30}, np.arange(30)), + ({"step": 10}, np.arange(0, 98, 10)), + ], +) def test_start_stop_step(u, run_kwargs, frames): an = FrameAnalysis(u.trajectory).run(**run_kwargs) assert an.n_frames == len(frames) assert_equal(an.found_frames, frames) assert_equal(an.frames, frames, err_msg=FRAMES_ERR) - assert_allclose(an.times, frames+1, rtol=0, atol=1.5e-4, err_msg=TIMES_ERR) + assert_allclose( + an.times, frames + 1, rtol=0, atol=1.5e-4, err_msg=TIMES_ERR + ) -@pytest.mark.parametrize('run_kwargs, frames', [ - ({'frames': [4, 5, 6, 7, 8, 9]}, np.arange(4, 10)), - ({'frames': [0, 2, 4, 6, 8]}, np.arange(0, 10, 2)), - ({'frames': [4, 6, 8]}, np.arange(4, 10, 2)), - ({'frames': [0, 3, 4, 3, 5]}, [0, 3, 4, 3, 5]), - ({'frames': [True, True, False, True, False, True, True, False, True, - False]}, (0, 1, 3, 5, 6, 8)), -]) +@pytest.mark.parametrize( + "run_kwargs, frames", + [ + ({"frames": [4, 5, 6, 7, 8, 9]}, np.arange(4, 10)), + ({"frames": [0, 2, 4, 6, 8]}, np.arange(0, 10, 2)), + ({"frames": [4, 6, 8]}, np.arange(4, 10, 2)), + ({"frames": [0, 3, 4, 3, 5]}, [0, 3, 4, 3, 5]), + ( + { + "frames": [ + True, + True, + False, + True, + False, + True, + True, + False, + True, + False, + ] + }, + (0, 1, 3, 5, 6, 8), + ), + ], +) def test_frame_slice(u_xtc, run_kwargs, frames): an = FrameAnalysis(u_xtc.trajectory).run(**run_kwargs) assert an.n_frames == len(frames) @@ -265,14 +346,32 @@ def test_frame_slice(u_xtc, run_kwargs, frames): assert_equal(an.frames, frames, err_msg=FRAMES_ERR) -@pytest.mark.parametrize('run_kwargs, frames', [ - ({'frames': [4, 5, 6, 7, 8, 9]}, np.arange(4, 10)), - ({'frames': [0, 2, 4, 6, 8]}, np.arange(0, 10, 2)), - ({'frames': [4, 6, 8]}, np.arange(4, 10, 2)), - ({'frames': [0, 3, 4, 3, 5]}, [0, 3, 4, 3, 5]), - ({'frames': [True, True, False, True, False, True, True, False, True, - False]}, (0, 1, 3, 5, 6, 8)), -]) +@pytest.mark.parametrize( + "run_kwargs, frames", + [ + ({"frames": [4, 5, 6, 7, 8, 9]}, np.arange(4, 10)), + ({"frames": [0, 2, 4, 6, 8]}, np.arange(0, 10, 2)), + ({"frames": [4, 6, 8]}, np.arange(4, 10, 2)), + ({"frames": [0, 3, 4, 3, 5]}, [0, 3, 4, 3, 5]), + ( + { + "frames": [ + True, + True, + False, + True, + False, + True, + True, + False, + True, + False, + ] + }, + (0, 1, 3, 5, 6, 8), + ), + ], +) def test_frame_slice_parallel(run_kwargs, frames, client_FrameAnalysis): u = mda.Universe(TPR, XTC) # dt = 100 an = FrameAnalysis(u.trajectory).run(**run_kwargs, **client_FrameAnalysis) @@ -281,25 +380,30 @@ def test_frame_slice_parallel(run_kwargs, frames, client_FrameAnalysis): assert_equal(an.frames, frames, err_msg=FRAMES_ERR) -@pytest.mark.parametrize('run_kwargs', [ - ({'start': 4, 'frames': [4, 5, 6, 7, 8, 9]}), - ({'stop': 6, 'frames': [0, 1, 2, 3, 4, 5]}), - ({'step': 2, 'frames': [0, 2, 4, 6, 8]}), - ({'start': 4, 'stop': 7, 'frames': [4, 5, 6]}), - ({'stop': 6, 'step': 2, 'frames': [0, 2, 4, 6]}), - ({'start': 4, 'step': 2, 'frames': [4, 6, 8]}), - ({'start': 0, 'stop': 0, 'step': 0, 'frames': [4, 6, 8]}), -]) +@pytest.mark.parametrize( + "run_kwargs", + [ + ({"start": 4, "frames": [4, 5, 6, 7, 8, 9]}), + ({"stop": 6, "frames": [0, 1, 2, 3, 4, 5]}), + ({"step": 2, "frames": [0, 2, 4, 6, 8]}), + ({"start": 4, "stop": 7, "frames": [4, 5, 6]}), + ({"stop": 6, "step": 2, "frames": [0, 2, 4, 6]}), + ({"start": 4, "step": 2, "frames": [4, 6, 8]}), + ({"start": 0, "stop": 0, "step": 0, "frames": [4, 6, 8]}), + ], +) def test_frame_fail(u, run_kwargs, client_FrameAnalysis): an = FrameAnalysis(u.trajectory) - msg = 'start/stop/step cannot be combined with frames' + msg = "start/stop/step cannot be combined with frames" with pytest.raises(ValueError, match=msg): an.run(**client_FrameAnalysis, **run_kwargs) + def test_parallelizable_transformations(): - # pick any transformation that would allow + # pick any transformation that would allow # for parallelizable attribute - from MDAnalysis.transformations import NoJump + from MDAnalysis.transformations import NoJump + u = mda.Universe(XTC, to_guess=()) u.trajectory.add_transformations(NoJump()) @@ -308,18 +412,18 @@ def test_parallelizable_transformations(): # test that parallel fails with pytest.raises(ValueError): - FrameAnalysis(u.trajectory).run(backend='multiprocessing') + FrameAnalysis(u.trajectory).run(backend="multiprocessing") def test_instance_serial_backend(u): # test that isinstance is checked and the correct ValueError raise appears - msg = 'Can not display progressbar with non-serial backend' + msg = "Can not display progressbar with non-serial backend" with pytest.raises(ValueError, match=msg): FrameAnalysis(u.trajectory).run( backend=backends.BackendMultiprocessing(n_workers=2), verbose=True, progressbar_kwargs={"leave": True}, - unsupported_backend=True + unsupported_backend=True, ) @@ -327,25 +431,31 @@ def test_frame_bool_fail(client_FrameAnalysis): u = mda.Universe(TPR, XTC) # dt = 100 an = FrameAnalysis(u.trajectory) frames = [True, True, False] - msg = 'boolean index did not match indexed array along (axis|dimension) 0' + msg = "boolean index did not match indexed array along (axis|dimension) 0" with pytest.raises(IndexError, match=msg): an.run(**client_FrameAnalysis, frames=frames) def test_rewind(client_FrameAnalysis): u = mda.Universe(TPR, XTC) # dt = 100 - an = FrameAnalysis(u.trajectory).run(**client_FrameAnalysis, frames=[0, 2, 3, 5, 9]) + an = FrameAnalysis(u.trajectory).run( + **client_FrameAnalysis, frames=[0, 2, 3, 5, 9] + ) assert_equal(u.trajectory.ts.frame, 0) def test_frames_times(client_FrameAnalysis): u = mda.Universe(TPR, XTC) # dt = 100 - an = FrameAnalysis(u.trajectory).run(start=1, stop=8, step=2, **client_FrameAnalysis) + an = FrameAnalysis(u.trajectory).run( + start=1, stop=8, step=2, **client_FrameAnalysis + ) frames = np.array([1, 3, 5, 7]) assert an.n_frames == len(frames) assert_equal(an.found_frames, frames) assert_equal(an.frames, frames, err_msg=FRAMES_ERR) - assert_allclose(an.times, frames*100, rtol=0, atol=1.5e-4, err_msg=TIMES_ERR) + assert_allclose( + an.times, frames * 100, rtol=0, atol=1.5e-4, err_msg=TIMES_ERR + ) def test_verbose(u): @@ -356,7 +466,7 @@ def test_verbose(u): def test_warn_nparts_nworkers(u): a = FrameAnalysis(u.trajectory) with pytest.warns(UserWarning): - a.run(backend='multiprocessing', n_workers=3, n_parts=2) + a.run(backend="multiprocessing", n_workers=3, n_parts=2) @pytest.mark.parametrize( @@ -364,8 +474,8 @@ def test_warn_nparts_nworkers(u): [ (base.AnalysisBase, False), (base.AnalysisFromFunction, True), - (FrameAnalysis, True) - ] + (FrameAnalysis, True), + ], ) def test_not_parallelizable(u, classname, is_parallelizable): assert classname._analysis_algorithm_is_parallelizable == is_parallelizable @@ -374,30 +484,34 @@ def test_not_parallelizable(u, classname, is_parallelizable): def test_verbose_progressbar(u, capsys): FrameAnalysis(u.trajectory).run() _, err = capsys.readouterr() - expected = '' - actual = err.strip().split('\r')[-1] + expected = "" + actual = err.strip().split("\r")[-1] assert actual == expected def test_verbose_progressbar_run(u, capsys): FrameAnalysis(u.trajectory).run(verbose=True) _, err = capsys.readouterr() - expected = u'100%|██████████' - actual = err.strip().split('\r')[-1] + expected = "100%|██████████" + actual = err.strip().split("\r")[-1] assert actual[:15] == expected + def test_verbose_progressbar_run_with_kwargs(u, capsys): FrameAnalysis(u.trajectory).run( - verbose=True, progressbar_kwargs={'desc': 'custom'}) + verbose=True, progressbar_kwargs={"desc": "custom"} + ) _, err = capsys.readouterr() - expected = u'custom: 100%|██████████' - actual = err.strip().split('\r')[-1] + expected = "custom: 100%|██████████" + actual = err.strip().split("\r")[-1] assert actual[:23] == expected def test_progressbar_multiprocessing(u): with pytest.raises(ValueError): - FrameAnalysis(u.trajectory).run(backend='multiprocessing', verbose=True) + FrameAnalysis(u.trajectory).run( + backend="multiprocessing", verbose=True + ) def test_incomplete_defined_analysis(u): @@ -413,7 +527,7 @@ def test_filter_baseanalysis_kwargs_VE(): def bad_f(mobile, verbose=2): pass - kwargs = {'step': 3, 'foo': None} + kwargs = {"step": 3, "foo": None} with pytest.raises(ValueError): base._filter_baseanalysis_kwargs(bad_f, kwargs) @@ -423,15 +537,15 @@ def test_filter_baseanalysis_kwargs(): def good_f(mobile, ref): pass - kwargs = {'step': 3, 'foo': None} + kwargs = {"step": 3, "foo": None} base_kwargs, kwargs = base._filter_baseanalysis_kwargs(good_f, kwargs) assert 2 == len(kwargs) - assert kwargs['foo'] == None + assert kwargs["foo"] == None assert len(base_kwargs) == 1 - assert base_kwargs['verbose'] is False + assert base_kwargs["verbose"] is False def simple_function(mobile): @@ -443,13 +557,18 @@ def test_results_type(u): assert type(an.results) == base.Results -@pytest.mark.parametrize('start, stop, step, nframes', [ - (None, None, 2, 49), - (None, 50, 2, 25), - (20, 50, 2, 15), - (20, 50, None, 30) -]) -def test_AnalysisFromFunction(u, start, stop, step, nframes, client_AnalysisFromFunction): +@pytest.mark.parametrize( + "start, stop, step, nframes", + [ + (None, None, 2, 49), + (None, 50, 2, 25), + (20, 50, 2, 15), + (20, 50, None, 30), + ], +) +def test_AnalysisFromFunction( + u, start, stop, step, nframes, client_AnalysisFromFunction +): # client_AnalysisFromFunction is defined [here](testsuite/MDAnalysisTests/analysis/conftest.py), # and determines a set of parameters ('backend', 'n_workers'), taking only backends # that are implemented for a given subclass, to run the test against. @@ -488,7 +607,7 @@ def mass_xyz(atomgroup1, atomgroup2, masses): def test_AnalysisFromFunction_args_content(u, client_AnalysisFromFunction): - protein = u.select_atoms('protein') + protein = u.select_atoms("protein") masses = protein.masses.reshape(-1, 1) another = mda.Universe(TPR, XTC).select_atoms("protein") ans = base.AnalysisFromFunction(mass_xyz, protein, another, masses) @@ -507,7 +626,9 @@ def test_analysis_class(client_AnalysisFromFunctionAnalysisClass): u = mda.Universe(PSF, DCD) step = 2 - ana = ana_class(u.atoms).run(step=step, **client_AnalysisFromFunctionAnalysisClass) + ana = ana_class(u.atoms).run( + step=step, **client_AnalysisFromFunctionAnalysisClass + ) results = [] for ts in u.trajectory[::step]: diff --git a/testsuite/MDAnalysisTests/analysis/test_bat.py b/testsuite/MDAnalysisTests/analysis/test_bat.py index f6bf24a56a8..704cf616cb7 100644 --- a/testsuite/MDAnalysisTests/analysis/test_bat.py +++ b/testsuite/MDAnalysisTests/analysis/test_bat.py @@ -28,8 +28,15 @@ import copy import MDAnalysis as mda -from MDAnalysisTests.datafiles import (PSF, DCD, mol2_comments_header, XYZ_mini, - BATArray, TPR, XTC) +from MDAnalysisTests.datafiles import ( + PSF, + DCD, + mol2_comments_header, + XYZ_mini, + BATArray, + TPR, + XTC, +) from MDAnalysis.analysis.bat import BAT @@ -48,7 +55,7 @@ def bat(self, selected_residues, client_BAT): @pytest.fixture def bat_npz(self, tmpdir, selected_residues, client_BAT): - filename = str(tmpdir / 'test_bat_IO.npy') + filename = str(tmpdir / "test_bat_IO.npy") R = BAT(selected_residues) R.run(**client_BAT) R.save(filename) @@ -56,13 +63,16 @@ def bat_npz(self, tmpdir, selected_residues, client_BAT): def test_bat_root_selection(self, selected_residues): R = BAT(selected_residues) - assert_equal(R._root.indices, [8, 2, 1], - err_msg="error: incorrect root atoms selected") + assert_equal( + R._root.indices, + [8, 2, 1], + err_msg="error: incorrect root atoms selected", + ) def test_bat_number_of_frames(self, bat): - assert_equal(len(bat), - 2, - err_msg="error: list is not length of trajectory") + assert_equal( + len(bat), 2, err_msg="error: list is not length of trajectory" + ) def test_bat_coordinates(self, bat): test_bat = np.load(BATArray) @@ -71,24 +81,35 @@ def test_bat_coordinates(self, bat): test_bat, rtol=0, atol=1.5e-5, - err_msg="error: BAT coordinates should match test values") + err_msg="error: BAT coordinates should match test values", + ) def test_bat_coordinates_single_frame(self, selected_residues, client_BAT): - bat = BAT(selected_residues).run(start=1, stop=2, **client_BAT).results.bat + bat = ( + BAT(selected_residues) + .run(start=1, stop=2, **client_BAT) + .results.bat + ) test_bat = [np.load(BATArray)[1]] assert_allclose( bat, test_bat, rtol=0, atol=1.5e-5, - err_msg="error: BAT coordinates should match test values") + err_msg="error: BAT coordinates should match test values", + ) def test_bat_reconstruction(self, selected_residues, bat): R = BAT(selected_residues) XYZ = R.Cartesian(bat[0]) - assert_allclose(XYZ, selected_residues.positions, rtol=0, atol=1.5e-5, - err_msg="error: Reconstructed Cartesian coordinates " + \ - "don't match original") + assert_allclose( + XYZ, + selected_residues.positions, + rtol=0, + atol=1.5e-5, + err_msg="error: Reconstructed Cartesian coordinates " + + "don't match original", + ) def test_bat_IO(self, bat_npz, selected_residues, bat): R2 = BAT(selected_residues, filename=bat_npz) @@ -98,7 +119,8 @@ def test_bat_IO(self, bat_npz, selected_residues, bat): test_bat, rtol=0, atol=1.5e-5, - err_msg="error: Loaded BAT coordinates should match test values") + err_msg="error: Loaded BAT coordinates should match test values", + ) def test_bat_nobonds(self): u = mda.Universe(XYZ_mini) @@ -107,28 +129,29 @@ def test_bat_nobonds(self): Z = BAT(u.atoms) def test_bat_bad_initial_atom(self, selected_residues): - errmsg = 'Initial atom is not a terminal atom' + errmsg = "Initial atom is not a terminal atom" with pytest.raises(ValueError, match=errmsg): - R = BAT(selected_residues, initial_atom = selected_residues[0]) + R = BAT(selected_residues, initial_atom=selected_residues[0]) def test_bat_disconnected_atom_group(self): u = mda.Universe(PSF, DCD) - selected_residues = u.select_atoms("resid 1-3") + \ - u.select_atoms("resid 5-7") - errmsg = 'Additional torsions not found.' + selected_residues = u.select_atoms("resid 1-3") + u.select_atoms( + "resid 5-7" + ) + errmsg = "Additional torsions not found." with pytest.raises(ValueError, match=errmsg): R = BAT(selected_residues) def test_bat_multifragments_atomgroup(self): u = mda.Universe(TPR, XTC) - errmsg = 'AtomGroup has more than one molecule' + errmsg = "AtomGroup has more than one molecule" with pytest.raises(ValueError, match=errmsg): - BAT(u.select_atoms('resname SOL')) + BAT(u.select_atoms("resname SOL")) def test_bat_incorrect_dims(self, bat_npz): u = mda.Universe(PSF, DCD) selected_residues = u.select_atoms("resid 1-3") - errmsg = 'Dimensions of array in loaded file' + errmsg = "Dimensions of array in loaded file" with pytest.raises(ValueError, match=errmsg): R = BAT(selected_residues, filename=bat_npz) @@ -137,6 +160,9 @@ def test_Cartesian_does_not_modify_input(self, selected_residues, bat): pre_transformation = copy.deepcopy(bat[0]) R.Cartesian(bat[0]) assert_allclose( - pre_transformation, bat[0], rtol=0, atol=1.5e-7, - err_msg="BAT.Cartesian modified input data" + pre_transformation, + bat[0], + rtol=0, + atol=1.5e-7, + err_msg="BAT.Cartesian modified input data", ) diff --git a/testsuite/MDAnalysisTests/analysis/test_contacts.py b/testsuite/MDAnalysisTests/analysis/test_contacts.py index 6b416e27f8e..7f4a5b69ecc 100644 --- a/testsuite/MDAnalysisTests/analysis/test_contacts.py +++ b/testsuite/MDAnalysisTests/analysis/test_contacts.py @@ -21,32 +21,28 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # import warnings + import MDAnalysis as mda +import numpy as np import pytest from MDAnalysis.analysis import contacts from MDAnalysis.analysis.distances import distance_array - -from numpy.testing import ( - assert_equal, - assert_array_equal, - assert_allclose, -) -import numpy as np +from numpy.testing import assert_allclose, assert_array_equal, assert_equal from MDAnalysisTests.datafiles import ( - PSF, DCD, + PSF, TPR, XTC, + contacts_file, contacts_villin_folded, contacts_villin_unfolded, - contacts_file ) def test_soft_cut_q(): # just check some of the extremal points - assert contacts.soft_cut_q([0], [0]) == .5 + assert contacts.soft_cut_q([0], [0]) == 0.5 assert_allclose(contacts.soft_cut_q([100], [0]), 0, rtol=0, atol=1.5e-7) assert_allclose(contacts.soft_cut_q([-100], [0]), 1, rtol=0, atol=1.5e-7) @@ -58,8 +54,10 @@ def test_soft_cut_q_folded(): # indices have been stored 1 indexed indices = contacts_data[:, :2].astype(int) - 1 - r = np.linalg.norm(u.atoms.positions[indices[:, 0]] - - u.atoms.positions[indices[:, 1]], axis=1) + r = np.linalg.norm( + u.atoms.positions[indices[:, 0]] - u.atoms.positions[indices[:, 1]], + axis=1, + ) r0 = contacts_data[:, 2] beta = 5.0 @@ -76,8 +74,10 @@ def test_soft_cut_q_unfolded(): # indices have been stored 1 indexed indices = contacts_data[:, :2].astype(int) - 1 - r = np.linalg.norm(u.atoms.positions[indices[:, 0]] - - u.atoms.positions[indices[:, 1]], axis=1) + r = np.linalg.norm( + u.atoms.positions[indices[:, 0]] - u.atoms.positions[indices[:, 1]], + axis=1, + ) r0 = contacts_data[:, 2] beta = 5.0 @@ -87,25 +87,25 @@ def test_soft_cut_q_unfolded(): assert_allclose(Q.mean(), 0.0, rtol=0, atol=1.5e-1) -@pytest.mark.parametrize('r, cutoff, expected_value', [ - ([1], 2, 1), - ([2], 1, 0), - ([2, 0.5], 1, 0.5), - ([2, 3], [3, 4], 1), - ([4, 5], [3, 4], 0) - -]) +@pytest.mark.parametrize( + "r, cutoff, expected_value", + [ + ([1], 2, 1), + ([2], 1, 0), + ([2, 0.5], 1, 0.5), + ([2, 3], [3, 4], 1), + ([4, 5], [3, 4], 0), + ], +) def test_hard_cut_q(r, cutoff, expected_value): # just check some extremal points assert contacts.hard_cut_q(r, cutoff) == expected_value -@pytest.mark.parametrize('r, r0, radius, expected_value', [ - ([1], None, 2, 1), - ([2], None, 1, 0), - ([2, 0.5], None, 1, 0.5) - -]) +@pytest.mark.parametrize( + "r, r0, radius, expected_value", + [([1], None, 2, 1), ([2], None, 1, 0), ([2, 0.5], None, 1, 0.5)], +) def test_radius_cut_q(r, r0, radius, expected_value): # check some extremal points assert contacts.radius_cut_q(r, r0, radius) == expected_value @@ -126,7 +126,7 @@ def test_contact_matrix(): def test_new_selection(): u = mda.Universe(PSF, DCD) - selections = ('all', ) + selections = ("all",) sel = contacts._new_selections(u, selections, -1)[0] u.trajectory[-1] assert_array_equal(sel.positions, u.atoms.positions) @@ -134,7 +134,7 @@ def test_new_selection(): def soft_cut(ref, u, selA, selB, radius=4.5, beta=5.0, lambda_constant=1.8): """ - Reference implementation for testing + Reference implementation for testing """ # reference groups A and B from selection strings refA, refB = ref.select_atoms(selA), ref.select_atoms(selB) @@ -171,8 +171,13 @@ def universe(): return mda.Universe(PSF, DCD) def _run_Contacts( - self, universe, client_Contacts, start=None, - stop=None, step=None, **kwargs + self, + universe, + client_Contacts, + start=None, + stop=None, + step=None, + **kwargs, ): acidic = universe.select_atoms(self.sel_acidic) basic = universe.select_atoms(self.sel_basic) @@ -181,13 +186,13 @@ def _run_Contacts( select=(self.sel_acidic, self.sel_basic), refgroup=(acidic, basic), radius=6.0, - **kwargs + **kwargs, ).run(**client_Contacts, start=start, stop=stop, step=step) @pytest.mark.parametrize("seltxt", [sel_acidic, sel_basic]) def test_select_valid_types(self, universe, seltxt): """Test if Contacts._get_atomgroup() can take both string and AtomGroup - as selections. + as selections. """ ag = universe.select_atoms(seltxt) @@ -197,8 +202,7 @@ def test_select_valid_types(self, universe, seltxt): assert ag_from_string == ag_from_ag def test_contacts_selections(self, universe, client_Contacts): - """Test if Contacts can take both string and AtomGroup as selections. - """ + """Test if Contacts can take both string and AtomGroup as selections.""" aga = universe.select_atoms(self.sel_acidic) agb = universe.select_atoms(self.sel_basic) @@ -207,8 +211,9 @@ def test_contacts_selections(self, universe, client_Contacts): ) csel = contacts.Contacts( - universe, select=(self.sel_acidic, self.sel_basic), - refgroup=(aga, agb) + universe, + select=(self.sel_acidic, self.sel_basic), + refgroup=(aga, agb), ) cag.run(**client_Contacts) @@ -247,8 +252,11 @@ def test_end_zero(self, universe, client_Contacts): def test_slicing(self, universe, client_Contacts): start, stop, step = 10, 30, 5 CA1 = self._run_Contacts( - universe, client_Contacts=client_Contacts, - start=start, stop=stop, step=step + universe, + client_Contacts=client_Contacts, + start=start, + stop=stop, + step=step, ) frames = np.arange(universe.trajectory.n_frames)[start:stop:step] assert len(CA1.results.timeseries) == len(frames) @@ -261,14 +269,15 @@ def test_villin_folded(self, client_Contacts): grF = f.select_atoms(sel) - q = contacts.Contacts(u, - select=(sel, sel), - refgroup=(grF, grF), - method="soft_cut") + q = contacts.Contacts( + u, select=(sel, sel), refgroup=(grF, grF), method="soft_cut" + ) q.run(**client_Contacts) results = soft_cut(f, u, sel, sel) - assert_allclose(q.results.timeseries[:, 1], results[:, 1], rtol=0, atol=1.5e-7) + assert_allclose( + q.results.timeseries[:, 1], results[:, 1], rtol=0, atol=1.5e-7 + ) def test_villin_unfolded(self, client_Contacts): # both folded @@ -278,39 +287,46 @@ def test_villin_unfolded(self, client_Contacts): grF = f.select_atoms(sel) - q = contacts.Contacts(u, - select=(sel, sel), - refgroup=(grF, grF), - method="soft_cut") + q = contacts.Contacts( + u, select=(sel, sel), refgroup=(grF, grF), method="soft_cut" + ) q.run(**client_Contacts) results = soft_cut(f, u, sel, sel) - assert_allclose(q.results.timeseries[:, 1], results[:, 1], rtol=0, atol=1.5e-7) + assert_allclose( + q.results.timeseries[:, 1], results[:, 1], rtol=0, atol=1.5e-7 + ) def test_hard_cut_method(self, universe, client_Contacts): ca = self._run_Contacts(universe, client_Contacts=client_Contacts) - expected = [1., 0.58252427, 0.52427184, 0.55339806, 0.54368932, - 0.54368932, 0.51456311, 0.46601942, 0.48543689, 0.52427184, - 0.46601942, 0.58252427, 0.51456311, 0.48543689, 0.48543689, - 0.48543689, 0.46601942, 0.51456311, 0.49514563, 0.49514563, - 0.45631068, 0.47572816, 0.49514563, 0.50485437, 0.53398058, - 0.50485437, 0.51456311, 0.51456311, 0.49514563, 0.49514563, - 0.54368932, 0.50485437, 0.48543689, 0.55339806, 0.45631068, - 0.46601942, 0.53398058, 0.53398058, 0.46601942, 0.52427184, - 0.45631068, 0.46601942, 0.47572816, 0.46601942, 0.45631068, - 0.47572816, 0.45631068, 0.48543689, 0.4368932, 0.4368932, - 0.45631068, 0.50485437, 0.41747573, 0.4368932, 0.51456311, - 0.47572816, 0.46601942, 0.46601942, 0.47572816, 0.47572816, - 0.46601942, 0.45631068, 0.44660194, 0.47572816, 0.48543689, - 0.47572816, 0.42718447, 0.40776699, 0.37864078, 0.42718447, - 0.45631068, 0.4368932, 0.4368932, 0.45631068, 0.4368932, - 0.46601942, 0.45631068, 0.48543689, 0.44660194, 0.44660194, - 0.44660194, 0.42718447, 0.45631068, 0.44660194, 0.48543689, - 0.48543689, 0.44660194, 0.4368932, 0.40776699, 0.41747573, - 0.48543689, 0.45631068, 0.46601942, 0.47572816, 0.51456311, - 0.45631068, 0.37864078, 0.42718447] + # fmt: off + expected = [ + 1., 0.58252427, 0.52427184, 0.55339806, 0.54368932, + 0.54368932, 0.51456311, 0.46601942, 0.48543689, 0.52427184, + 0.46601942, 0.58252427, 0.51456311, 0.48543689, 0.48543689, + 0.48543689, 0.46601942, 0.51456311, 0.49514563, 0.49514563, + 0.45631068, 0.47572816, 0.49514563, 0.50485437, 0.53398058, + 0.50485437, 0.51456311, 0.51456311, 0.49514563, 0.49514563, + 0.54368932, 0.50485437, 0.48543689, 0.55339806, 0.45631068, + 0.46601942, 0.53398058, 0.53398058, 0.46601942, 0.52427184, + 0.45631068, 0.46601942, 0.47572816, 0.46601942, 0.45631068, + 0.47572816, 0.45631068, 0.48543689, 0.4368932, 0.4368932, + 0.45631068, 0.50485437, 0.41747573, 0.4368932, 0.51456311, + 0.47572816, 0.46601942, 0.46601942, 0.47572816, 0.47572816, + 0.46601942, 0.45631068, 0.44660194, 0.47572816, 0.48543689, + 0.47572816, 0.42718447, 0.40776699, 0.37864078, 0.42718447, + 0.45631068, 0.4368932, 0.4368932, 0.45631068, 0.4368932, + 0.46601942, 0.45631068, 0.48543689, 0.44660194, 0.44660194, + 0.44660194, 0.42718447, 0.45631068, 0.44660194, 0.48543689, + 0.48543689, 0.44660194, 0.4368932, 0.40776699, 0.41747573, + 0.48543689, 0.45631068, 0.46601942, 0.47572816, 0.51456311, + 0.45631068, 0.37864078, 0.42718447, + ] + # fmt: on assert len(ca.results.timeseries) == len(expected) - assert_allclose(ca.results.timeseries[:, 1], expected, rtol=0, atol=1.5e-7) + assert_allclose( + ca.results.timeseries[:, 1], expected, rtol=0, atol=1.5e-7 + ) def test_radius_cut_method(self, universe, client_Contacts): acidic = universe.select_atoms(self.sel_acidic) @@ -320,7 +336,9 @@ def test_radius_cut_method(self, universe, client_Contacts): expected = [] for ts in universe.trajectory: r = contacts.distance_array(acidic.positions, basic.positions) - expected.append(contacts.radius_cut_q(r[initial_contacts], None, radius=6.0)) + expected.append( + contacts.radius_cut_q(r[initial_contacts], None, radius=6.0) + ) ca = self._run_Contacts( universe, client_Contacts=client_Contacts, method="radius_cut" @@ -333,23 +351,27 @@ def _is_any_closer(r, r0, dist=2.5): def test_own_method(self, universe, client_Contacts): ca = self._run_Contacts( - universe, client_Contacts=client_Contacts, - method=self._is_any_closer + universe, + client_Contacts=client_Contacts, + method=self._is_any_closer, ) - bound_expected = [1., 1., 0., 1., 1., 0., 0., 1., 0., 1., 1., 0., 0., - 1., 0., 0., 0., 0., 1., 1., 0., 0., 0., 1., 0., 1., - 0., 1., 1., 0., 1., 1., 1., 0., 0., 0., 0., 1., 0., - 0., 1., 0., 1., 1., 1., 0., 1., 0., 0., 1., 1., 1., - 0., 1., 0., 1., 1., 0., 0., 0., 1., 1., 1., 0., 0., - 1., 0., 1., 1., 1., 1., 1., 1., 0., 1., 1., 0., 1., - 0., 0., 1., 1., 0., 0., 1., 1., 1., 0., 1., 0., 0., - 1., 0., 1., 1., 1., 1., 1.] + # fmt: off + bound_expected = [ + 1., 1., 0., 1., 1., 0., 0., 1., 0., 1., 1., 0., 0., 1., 0., 0., + 0., 0., 1., 1., 0., 0., 0., 1., 0., 1., 0., 1., 1., 0., 1., 1., + 1., 0., 0., 0., 0., 1., 0., 0., 1., 0., 1., 1., 1., 0., 1., 0., + 0., 1., 1., 1., 0., 1., 0., 1., 1., 0., 0., 0., 1., 1., 1., 0., + 0., 1., 0., 1., 1., 1., 1., 1., 1., 0., 1., 1., 0., 1., 0., 0., + 1., 1., 0., 0., 1., 1., 1., 0., 1., 0., 0., 1., 0., 1., 1., 1., + 1., 1., + ] + # fmt: on assert_array_equal(ca.results.timeseries[:, 1], bound_expected) @staticmethod def _weird_own_method(r, r0): - return 'aaa' + return "aaa" def test_own_method_no_array_cast(self, universe, client_Contacts): with pytest.raises(ValueError): @@ -366,23 +388,59 @@ def test_non_callable_method(self, universe, client_Contacts): universe, client_Contacts=client_Contacts, method=2, stop=2 ) - @pytest.mark.parametrize("pbc,expected", [ - (True, [1., 0.43138152, 0.3989021, 0.43824337, 0.41948765, - 0.42223239, 0.41354071, 0.43641354, 0.41216834, 0.38334858]), - (False, [1., 0.42327791, 0.39192399, 0.40950119, 0.40902613, - 0.42470309, 0.41140143, 0.42897862, 0.41472684, 0.38574822]) - ]) + @pytest.mark.parametrize( + "pbc,expected", + [ + ( + True, + [ + 1.0, + 0.43138152, + 0.3989021, + 0.43824337, + 0.41948765, + 0.42223239, + 0.41354071, + 0.43641354, + 0.41216834, + 0.38334858, + ], + ), + ( + False, + [ + 1.0, + 0.42327791, + 0.39192399, + 0.40950119, + 0.40902613, + 0.42470309, + 0.41140143, + 0.42897862, + 0.41472684, + 0.38574822, + ], + ), + ], + ) def test_distance_box(self, pbc, expected, client_Contacts): u = mda.Universe(TPR, XTC) sel_basic = "(resname ARG LYS)" sel_acidic = "(resname ASP GLU)" acidic = u.select_atoms(sel_acidic) basic = u.select_atoms(sel_basic) - - r = contacts.Contacts(u, select=(sel_acidic, sel_basic), - refgroup=(acidic, basic), radius=6.0, pbc=pbc) + + r = contacts.Contacts( + u, + select=(sel_acidic, sel_basic), + refgroup=(acidic, basic), + radius=6.0, + pbc=pbc, + ) r.run(**client_Contacts) - assert_allclose(r.results.timeseries[:, 1], expected,rtol=0, atol=1.5e-7) + assert_allclose( + r.results.timeseries[:, 1], expected, rtol=0, atol=1.5e-7 + ) def test_warn_deprecated_attr(self, universe, client_Contacts): """Test for warning message emitted on using deprecated `timeseries` @@ -394,13 +452,14 @@ def test_warn_deprecated_attr(self, universe, client_Contacts): with pytest.warns(DeprecationWarning, match=wmsg): assert_equal(CA1.timeseries, CA1.results.timeseries) - @pytest.mark.parametrize("datafiles, expected", [((PSF, DCD), 0), - ([TPR, XTC], 41814)]) + @pytest.mark.parametrize( + "datafiles, expected", [((PSF, DCD), 0), ([TPR, XTC], 41814)] + ) def test_n_initial_contacts(self, datafiles, expected): """Test for n_initial_contacts attribute""" u = mda.Universe(*datafiles) - select = ('protein', 'not protein') - refgroup = (u.select_atoms('protein'), u.select_atoms('not protein')) + select = ("protein", "not protein") + refgroup = (u.select_atoms("protein"), u.select_atoms("not protein")) r = contacts.Contacts(u, select=select, refgroup=refgroup) assert_equal(r.n_initial_contacts, expected) @@ -408,49 +467,61 @@ def test_n_initial_contacts(self, datafiles, expected): def test_q1q2(client_Contacts): u = mda.Universe(PSF, DCD) - q1q2 = contacts.q1q2(u, 'name CA', radius=8) + q1q2 = contacts.q1q2(u, "name CA", radius=8) q1q2.run(**client_Contacts) - q1_expected = [1., 0.98092643, 0.97366031, 0.97275204, 0.97002725, - 0.97275204, 0.96276113, 0.96730245, 0.9582198, 0.96185286, - 0.95367847, 0.96276113, 0.9582198, 0.95186194, 0.95367847, - 0.95095368, 0.94187103, 0.95186194, 0.94277929, 0.94187103, - 0.9373297, 0.93642144, 0.93097184, 0.93914623, 0.93278837, - 0.93188011, 0.9373297, 0.93097184, 0.93188011, 0.92643052, - 0.92824705, 0.92915531, 0.92643052, 0.92461399, 0.92279746, - 0.92643052, 0.93278837, 0.93188011, 0.93369664, 0.9346049, - 0.9373297, 0.94096276, 0.9400545, 0.93642144, 0.9373297, - 0.9373297, 0.9400545, 0.93006358, 0.9400545, 0.93823797, - 0.93914623, 0.93278837, 0.93097184, 0.93097184, 0.92733878, - 0.92824705, 0.92279746, 0.92824705, 0.91825613, 0.92733878, - 0.92643052, 0.92733878, 0.93278837, 0.92733878, 0.92824705, - 0.93097184, 0.93278837, 0.93914623, 0.93097184, 0.9373297, - 0.92915531, 0.93188011, 0.93551317, 0.94096276, 0.93642144, - 0.93642144, 0.9346049, 0.93369664, 0.93369664, 0.93278837, - 0.93006358, 0.93278837, 0.93006358, 0.9346049, 0.92824705, - 0.93097184, 0.93006358, 0.93188011, 0.93278837, 0.93006358, - 0.92915531, 0.92824705, 0.92733878, 0.92643052, 0.93188011, - 0.93006358, 0.9346049, 0.93188011] - assert_allclose(q1q2.results.timeseries[:, 1], q1_expected, rtol=0, atol=1.5e-7) - - q2_expected = [0.94649446, 0.94926199, 0.95295203, 0.95110701, 0.94833948, - 0.95479705, 0.94926199, 0.9501845, 0.94926199, 0.95387454, - 0.95202952, 0.95110701, 0.94649446, 0.94095941, 0.94649446, - 0.9400369, 0.94464945, 0.95202952, 0.94741697, 0.94649446, - 0.94188192, 0.94188192, 0.93911439, 0.94464945, 0.9400369, - 0.94095941, 0.94372694, 0.93726937, 0.93819188, 0.93357934, - 0.93726937, 0.93911439, 0.93911439, 0.93450185, 0.93357934, - 0.93265683, 0.93911439, 0.94372694, 0.93911439, 0.94649446, - 0.94833948, 0.95110701, 0.95110701, 0.95295203, 0.94926199, - 0.95110701, 0.94926199, 0.94741697, 0.95202952, 0.95202952, - 0.95202952, 0.94741697, 0.94741697, 0.94926199, 0.94280443, - 0.94741697, 0.94833948, 0.94833948, 0.9400369, 0.94649446, - 0.94741697, 0.94926199, 0.95295203, 0.94926199, 0.9501845, - 0.95664207, 0.95756458, 0.96309963, 0.95756458, 0.96217712, - 0.95756458, 0.96217712, 0.96586716, 0.96863469, 0.96494465, - 0.97232472, 0.97140221, 0.9695572, 0.97416974, 0.9695572, - 0.96217712, 0.96771218, 0.9704797, 0.96771218, 0.9695572, - 0.97140221, 0.97601476, 0.97693727, 0.98154982, 0.98431734, - 0.97601476, 0.9797048, 0.98154982, 0.98062731, 0.98431734, - 0.98616236, 0.9898524, 1.] - assert_allclose(q1q2.results.timeseries[:, 2], q2_expected, rtol=0, atol=1.5e-7) + # fmt: off + q1_expected = [ + 1.0, 0.98092643, 0.97366031, 0.97275204, 0.97002725, + 0.97275204, 0.96276113, 0.96730245, 0.9582198, 0.96185286, + 0.95367847, 0.96276113, 0.9582198, 0.95186194, 0.95367847, + 0.95095368, 0.94187103, 0.95186194, 0.94277929, 0.94187103, + 0.9373297, 0.93642144, 0.93097184, 0.93914623, 0.93278837, + 0.93188011, 0.9373297, 0.93097184, 0.93188011, 0.92643052, + 0.92824705, 0.92915531, 0.92643052, 0.92461399, 0.92279746, + 0.92643052, 0.93278837, 0.93188011, 0.93369664, 0.9346049, + 0.9373297, 0.94096276, 0.9400545, 0.93642144, 0.9373297, + 0.9373297, 0.9400545, 0.93006358, 0.9400545, 0.93823797, + 0.93914623, 0.93278837, 0.93097184, 0.93097184, 0.92733878, + 0.92824705, 0.92279746, 0.92824705, 0.91825613, 0.92733878, + 0.92643052, 0.92733878, 0.93278837, 0.92733878, 0.92824705, + 0.93097184, 0.93278837, 0.93914623, 0.93097184, 0.9373297, + 0.92915531, 0.93188011, 0.93551317, 0.94096276, 0.93642144, + 0.93642144, 0.9346049, 0.93369664, 0.93369664, 0.93278837, + 0.93006358, 0.93278837, 0.93006358, 0.9346049, 0.92824705, + 0.93097184, 0.93006358, 0.93188011, 0.93278837, 0.93006358, + 0.92915531, 0.92824705, 0.92733878, 0.92643052, 0.93188011, + 0.93006358, 0.9346049, 0.93188011, + ] + # fmt: on + assert_allclose( + q1q2.results.timeseries[:, 1], q1_expected, rtol=0, atol=1.5e-7 + ) + + # fmt: off + q2_expected = [ + 0.94649446, 0.94926199, 0.95295203, 0.95110701, 0.94833948, + 0.95479705, 0.94926199, 0.9501845, 0.94926199, 0.95387454, + 0.95202952, 0.95110701, 0.94649446, 0.94095941, 0.94649446, + 0.9400369, 0.94464945, 0.95202952, 0.94741697, 0.94649446, + 0.94188192, 0.94188192, 0.93911439, 0.94464945, 0.9400369, + 0.94095941, 0.94372694, 0.93726937, 0.93819188, 0.93357934, + 0.93726937, 0.93911439, 0.93911439, 0.93450185, 0.93357934, + 0.93265683, 0.93911439, 0.94372694, 0.93911439, 0.94649446, + 0.94833948, 0.95110701, 0.95110701, 0.95295203, 0.94926199, + 0.95110701, 0.94926199, 0.94741697, 0.95202952, 0.95202952, + 0.95202952, 0.94741697, 0.94741697, 0.94926199, 0.94280443, + 0.94741697, 0.94833948, 0.94833948, 0.9400369, 0.94649446, + 0.94741697, 0.94926199, 0.95295203, 0.94926199, 0.9501845, + 0.95664207, 0.95756458, 0.96309963, 0.95756458, 0.96217712, + 0.95756458, 0.96217712, 0.96586716, 0.96863469, 0.96494465, + 0.97232472, 0.97140221, 0.9695572, 0.97416974, 0.9695572, + 0.96217712, 0.96771218, 0.9704797, 0.96771218, 0.9695572, + 0.97140221, 0.97601476, 0.97693727, 0.98154982, 0.98431734, + 0.97601476, 0.9797048, 0.98154982, 0.98062731, 0.98431734, + 0.98616236, 0.9898524, 1.0, + ] + # fmt: on + assert_allclose( + q1q2.results.timeseries[:, 2], q2_expected, rtol=0, atol=1.5e-7 + ) diff --git a/testsuite/MDAnalysisTests/analysis/test_data.py b/testsuite/MDAnalysisTests/analysis/test_data.py index 44853346c85..d3d570d6c46 100644 --- a/testsuite/MDAnalysisTests/analysis/test_data.py +++ b/testsuite/MDAnalysisTests/analysis/test_data.py @@ -23,9 +23,15 @@ from numpy.testing import assert_equal import pytest + def test_all_exports(): from MDAnalysis.analysis.data import filenames - missing = [name for name in dir(filenames) - if - not name.startswith('_') and name not in filenames.__all__ and name != 'absolute_import'] + + missing = [ + name + for name in dir(filenames) + if not name.startswith("_") + and name not in filenames.__all__ + and name != "absolute_import" + ] assert_equal(missing, [], err_msg="Variables need to be added to __all__.") diff --git a/testsuite/MDAnalysisTests/analysis/test_density.py b/testsuite/MDAnalysisTests/analysis/test_density.py index 68dbed6839d..3547ff9fe91 100644 --- a/testsuite/MDAnalysisTests/analysis/test_density.py +++ b/testsuite/MDAnalysisTests/analysis/test_density.py @@ -41,32 +41,35 @@ class TestDensity(object): nbins = 3, 4, 5 counts = 100 - Lmax = 10. + Lmax = 10.0 - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def bins(self): return [np.linspace(0, self.Lmax, n + 1) for n in self.nbins] @pytest.fixture() def h_and_edges(self, bins): return np.histogramdd( - self.Lmax * np.sin( - np.linspace(0, 1, self.counts * 3)).reshape(self.counts, 3), - bins=bins) + self.Lmax + * np.sin(np.linspace(0, 1, self.counts * 3)).reshape( + self.counts, 3 + ), + bins=bins, + ) @pytest.fixture() def D(self, h_and_edges): h, edges = h_and_edges - d = density.Density(h, edges, parameters={'isDensity': False}, - units={'length': 'A'}) + d = density.Density( + h, edges, parameters={"isDensity": False}, units={"length": "A"} + ) d.make_density() return d @pytest.fixture() def D1(self, h_and_edges): h, edges = h_and_edges - d = density.Density(h, edges, parameters={}, - units={}) + d = density.Density(h, edges, parameters={}, units={}) return d def test_shape(self, D): @@ -74,17 +77,19 @@ def test_shape(self, D): def test_edges(self, bins, D): for dim, (edges, fixture) in enumerate(zip(D.edges, bins)): - assert_almost_equal(edges, fixture, - err_msg="edges[{0}] mismatch".format(dim)) + assert_almost_equal( + edges, fixture, err_msg="edges[{0}] mismatch".format(dim) + ) def test_midpoints(self, bins, D): - midpoints = [0.5*(b[:-1] + b[1:]) for b in bins] + midpoints = [0.5 * (b[:-1] + b[1:]) for b in bins] for dim, (mp, fixture) in enumerate(zip(D.midpoints, midpoints)): - assert_almost_equal(mp, fixture, - err_msg="midpoints[{0}] mismatch".format(dim)) + assert_almost_equal( + mp, fixture, err_msg="midpoints[{0}] mismatch".format(dim) + ) def test_delta(self, D): - deltas = np.array([self.Lmax])/np.array(self.nbins) + deltas = np.array([self.Lmax]) / np.array(self.nbins) assert_almost_equal(D.delta, deltas) def test_grid(self, D): @@ -93,12 +98,12 @@ def test_grid(self, D): assert_almost_equal(D.grid.sum() * dV, self.counts) def test_origin(self, bins, D): - midpoints = [0.5*(b[:-1] + b[1:]) for b in bins] + midpoints = [0.5 * (b[:-1] + b[1:]) for b in bins] origin = [m[0] for m in midpoints] assert_almost_equal(D.origin, origin) def test_check_set_unit_keyerror(self, D): - units = {'weight': 'A'} + units = {"weight": "A"} with pytest.raises(ValueError): D._check_set_unit(units) @@ -108,23 +113,23 @@ def test_check_set_unit_attributeError(self, D): D._check_set_unit(units) def test_check_set_unit_nolength(self, D): - del D.units['length'] - units = {'density': 'A^{-3}'} + del D.units["length"] + units = {"density": "A^{-3}"} with pytest.raises(ValueError): D._check_set_unit(units) def test_check_set_density_none(self, D1): - units = {'density': None} + units = {"density": None} D1._check_set_unit(units) - assert D1.units['density'] is None + assert D1.units["density"] is None def test_check_set_density_not_in_units(self, D1): - del D1.units['density'] + del D1.units["density"] D1._check_set_unit({}) - assert D1.units['density'] is None + assert D1.units["density"] is None def test_parameters_isdensity(self, D): - with pytest.warns(UserWarning, match='Running make_density()'): + with pytest.warns(UserWarning, match="Running make_density()"): D.make_density() def test_check_convert_density_grid_not_density(self, D1): @@ -132,65 +137,64 @@ def test_check_convert_density_grid_not_density(self, D1): D1.convert_density() def test_check_convert_density_value_error(self, D): - unit = 'A^{-2}' + unit = "A^{-2}" with pytest.raises(ValueError, match="The name of the unit"): D.convert_density(unit) def test_check_convert_density_units_same_density_units(self, D): - unit = 'A^{-3}' + unit = "A^{-3}" D_orig = copy.deepcopy(D) D.convert_density(unit) - assert D.units['density'] == D_orig.units['density'] == unit + assert D.units["density"] == D_orig.units["density"] == unit assert_almost_equal(D.grid, D_orig.grid) def test_check_convert_density_units_density(self, D): - unit = 'nm^{-3}' + unit = "nm^{-3}" D_orig = copy.deepcopy(D) D.convert_density(unit) - assert D.units['density'] == 'nm^{-3}' + assert D.units["density"] == "nm^{-3}" assert_almost_equal(D.grid, 10**3 * D_orig.grid) def test_convert_length_same_length_units(self, D): - unit = 'A' + unit = "A" D_orig = copy.deepcopy(D) D.convert_length(unit) - assert D.units['length'] == D_orig.units['length'] == unit + assert D.units["length"] == D_orig.units["length"] == unit assert_almost_equal(D.grid, D_orig.grid) def test_convert_length_other_length_units(self, D): - unit = 'nm' + unit = "nm" D_orig = copy.deepcopy(D) D.convert_length(unit) - assert D.units['length'] == unit + assert D.units["length"] == unit assert_almost_equal(D.grid, D_orig.grid) def test_repr(self, D, D1): - assert str(D) == '' - assert str(D1) == '' + assert str(D) == "" + assert str(D1) == "" def test_check_convert_length_edges(self, D): D1 = copy.deepcopy(D) - unit = 'nm' + unit = "nm" D.convert_length(unit) for prev_edge, conv_edge in zip(D1.edges, D.edges): - assert_almost_equal(prev_edge, 10*conv_edge) + assert_almost_equal(prev_edge, 10 * conv_edge) def test_check_convert_density_edges(self, D): - unit = 'nm^{-3}' + unit = "nm^{-3}" D_orig = copy.deepcopy(D) D.convert_density(unit) for new_den, orig_den in zip(D.edges, D_orig.edges): assert_almost_equal(new_den, orig_den) - @pytest.mark.parametrize('dxtype', - ("float", "double", "int", "byte")) + @pytest.mark.parametrize("dxtype", ("float", "double", "int", "byte")) def test_export_types(self, D, dxtype, tmpdir, outfile="density.dx"): with tmpdir.as_cwd(): D.export(outfile, type=dxtype) dx = gridData.OpenDX.field(0) dx.read(outfile) - data = dx.components['data'] + data = dx.components["data"] assert data.type == dxtype @@ -198,35 +202,48 @@ class DensityParameters(object): topology = TPR trajectory = XTC delta = 2.0 - selections = {'none': "resname None", - 'static': "name OW", - 'dynamic': "name OW and around 4 (protein and resid 1-10)", - 'solute': "protein and not name H*", - } - references = {'static': - {'meandensity': 0.016764271713091212, }, - 'static_sliced': - {'meandensity': 0.016764270747693617, }, - 'static_defined': - {'meandensity': 0.0025000000000000005, }, - 'static_defined_unequal': - {'meandensity': 0.006125, }, - 'dynamic': - {'meandensity': 0.0012063418843728784, }, - 'notwithin': - {'meandensity': 0.015535385132107926, }, - } - cutoffs = {'notwithin': 4.0, } - gridcenters = {'static_defined': np.array([56.0, 45.0, 35.0]), - 'error1': np.array([56.0, 45.0]), - 'error2': [56.0, 45.0, "MDAnalysis"], - } + selections = { + "none": "resname None", + "static": "name OW", + "dynamic": "name OW and around 4 (protein and resid 1-10)", + "solute": "protein and not name H*", + } + references = { + "static": { + "meandensity": 0.016764271713091212, + }, + "static_sliced": { + "meandensity": 0.016764270747693617, + }, + "static_defined": { + "meandensity": 0.0025000000000000005, + }, + "static_defined_unequal": { + "meandensity": 0.006125, + }, + "dynamic": { + "meandensity": 0.0012063418843728784, + }, + "notwithin": { + "meandensity": 0.015535385132107926, + }, + } + cutoffs = { + "notwithin": 4.0, + } + gridcenters = { + "static_defined": np.array([56.0, 45.0, 35.0]), + "error1": np.array([56.0, 45.0]), + "error2": [56.0, 45.0, "MDAnalysis"], + } precision = 5 - outfile = 'density.dx' + outfile = "density.dx" @pytest.fixture() def universe(self): - return mda.Universe(self.topology, self.trajectory, tpr_resid_from_one=False) + return mda.Universe( + self.topology, self.trajectory, tpr_resid_from_one=False + ) class TestDensityAnalysis(DensityParameters): @@ -237,37 +254,42 @@ def check_DensityAnalysis( tmpdir, client_DensityAnalysis, runargs=None, - **kwargs + **kwargs, ): runargs = runargs if runargs else {} with tmpdir.as_cwd(): D = density.DensityAnalysis(ag, delta=self.delta, **kwargs).run( **runargs, **client_DensityAnalysis ) - assert_almost_equal(D.results.density.grid.mean(), ref_meandensity, - err_msg="mean density does not match") + assert_almost_equal( + D.results.density.grid.mean(), + ref_meandensity, + err_msg="mean density does not match", + ) D.results.density.export(self.outfile) D2 = density.Density(self.outfile) assert_almost_equal( - D.results.density.grid, D2.grid, decimal=self.precision, - err_msg="DX export failed: different grid sizes" + D.results.density.grid, + D2.grid, + decimal=self.precision, + err_msg="DX export failed: different grid sizes", ) @pytest.mark.parametrize("mode", ("static", "dynamic")) def test_run(self, mode, universe, tmpdir, client_DensityAnalysis): - updating = (mode == "dynamic") + updating = mode == "dynamic" self.check_DensityAnalysis( universe.select_atoms(self.selections[mode], updating=updating), - self.references[mode]['meandensity'], + self.references[mode]["meandensity"], tmpdir=tmpdir, client_DensityAnalysis=client_DensityAnalysis, ) def test_sliced(self, universe, tmpdir, client_DensityAnalysis): self.check_DensityAnalysis( - universe.select_atoms(self.selections['static']), - self.references['static_sliced']['meandensity'], + universe.select_atoms(self.selections["static"]), + self.references["static_sliced"]["meandensity"], tmpdir=tmpdir, client_DensityAnalysis=client_DensityAnalysis, runargs=dict(start=1, stop=-1, step=2), @@ -278,11 +300,11 @@ def test_userdefn_eqbox(self, universe, tmpdir, client_DensityAnalysis): # Do not need to see UserWarning that box is too small warnings.simplefilter("ignore") self.check_DensityAnalysis( - universe.select_atoms(self.selections['static']), - self.references['static_defined']['meandensity'], + universe.select_atoms(self.selections["static"]), + self.references["static_defined"]["meandensity"], tmpdir=tmpdir, client_DensityAnalysis=client_DensityAnalysis, - gridcenter=self.gridcenters['static_defined'], + gridcenter=self.gridcenters["static_defined"], xdim=10.0, ydim=10.0, zdim=10.0, @@ -290,11 +312,11 @@ def test_userdefn_eqbox(self, universe, tmpdir, client_DensityAnalysis): def test_userdefn_neqbox(self, universe, tmpdir, client_DensityAnalysis): self.check_DensityAnalysis( - universe.select_atoms(self.selections['static']), - self.references['static_defined_unequal']['meandensity'], + universe.select_atoms(self.selections["static"]), + self.references["static_defined_unequal"]["meandensity"], tmpdir=tmpdir, client_DensityAnalysis=client_DensityAnalysis, - gridcenter=self.gridcenters['static_defined'], + gridcenter=self.gridcenters["static_defined"], xdim=10.0, ydim=15.0, zdim=20.0, @@ -312,8 +334,10 @@ def test_userdefn_boxshape(self, universe, client_DensityAnalysis): assert D.results.density.grid.shape == (8, 12, 17) def test_warn_userdefn_padding(self, universe, client_DensityAnalysis): - regex = (r"Box padding \(currently set at 1\.0\) is not used " - r"in user defined grids\.") + regex = ( + r"Box padding \(currently set at 1\.0\) is not used " + r"in user defined grids\." + ) with pytest.warns(UserWarning, match=regex): D = density.DensityAnalysis( universe.select_atoms(self.selections["static"]), @@ -326,8 +350,10 @@ def test_warn_userdefn_padding(self, universe, client_DensityAnalysis): ).run(step=5, **client_DensityAnalysis) def test_warn_userdefn_smallgrid(self, universe, client_DensityAnalysis): - regex = ("Atom selection does not fit grid --- " - "you may want to define a larger box") + regex = ( + "Atom selection does not fit grid --- " + "you may want to define a larger box" + ) with pytest.warns(UserWarning, match=regex): D = density.DensityAnalysis( universe.select_atoms(self.selections["static"]), @@ -343,7 +369,9 @@ def test_ValueError_userdefn_gridcenter_shape( self, universe, client_DensityAnalysis ): # Test len(gridcenter) != 3 - with pytest.raises(ValueError, match="Gridcenter must be a 3D coordinate"): + with pytest.raises( + ValueError, match="Gridcenter must be a 3D coordinate" + ): D = density.DensityAnalysis( universe.select_atoms(self.selections["static"]), delta=self.delta, @@ -357,7 +385,9 @@ def test_ValueError_userdefn_gridcenter_type( self, universe, client_DensityAnalysis ): # Test gridcenter includes non-numeric strings - with pytest.raises(ValueError, match="Gridcenter must be a 3D coordinate"): + with pytest.raises( + ValueError, match="Gridcenter must be a 3D coordinate" + ): D = density.DensityAnalysis( universe.select_atoms(self.selections["static"]), delta=self.delta, @@ -371,7 +401,7 @@ def test_ValueError_userdefn_gridcenter_missing( self, universe, client_DensityAnalysis ): # Test no gridcenter provided when grid dimensions are given - regex = ("Gridcenter or grid dimensions are not provided") + regex = "Gridcenter or grid dimensions are not provided" with pytest.raises(ValueError, match=regex): D = density.DensityAnalysis( universe.select_atoms(self.selections["static"]), @@ -381,10 +411,13 @@ def test_ValueError_userdefn_gridcenter_missing( zdim=10.0, ).run(step=5, **client_DensityAnalysis) - def test_ValueError_userdefn_xdim_type(self, universe, - client_DensityAnalysis): + def test_ValueError_userdefn_xdim_type( + self, universe, client_DensityAnalysis + ): # Test xdim != int or float - with pytest.raises(ValueError, match="xdim, ydim, and zdim must be numbers"): + with pytest.raises( + ValueError, match="xdim, ydim, and zdim must be numbers" + ): D = density.DensityAnalysis( universe.select_atoms(self.selections["static"]), delta=self.delta, @@ -394,10 +427,11 @@ def test_ValueError_userdefn_xdim_type(self, universe, gridcenter=self.gridcenters["static_defined"], ).run(step=5, **client_DensityAnalysis) - def test_ValueError_userdefn_xdim_nanvalue(self, universe, - client_DensityAnalysis): + def test_ValueError_userdefn_xdim_nanvalue( + self, universe, client_DensityAnalysis + ): # Test xdim set to NaN value - regex = ("Gridcenter or grid dimensions have NaN element") + regex = "Gridcenter or grid dimensions have NaN element" with pytest.raises(ValueError, match=regex): D = density.DensityAnalysis( universe.select_atoms(self.selections["static"]), @@ -409,10 +443,12 @@ def test_ValueError_userdefn_xdim_nanvalue(self, universe, ).run(step=5, **client_DensityAnalysis) def test_warn_noatomgroup(self, universe, client_DensityAnalysis): - regex = ("No atoms in AtomGroup at input time frame. " - "This may be intended; please ensure that " - "your grid selection covers the atomic " - "positions you wish to capture.") + regex = ( + "No atoms in AtomGroup at input time frame. " + "This may be intended; please ensure that " + "your grid selection covers the atomic " + "positions you wish to capture." + ) with pytest.warns(UserWarning, match=regex): D = density.DensityAnalysis( universe.select_atoms(self.selections["none"]), @@ -425,20 +461,24 @@ def test_warn_noatomgroup(self, universe, client_DensityAnalysis): ).run(step=5, **client_DensityAnalysis) def test_ValueError_noatomgroup(self, universe, client_DensityAnalysis): - with pytest.raises(ValueError, match="No atoms in AtomGroup at input" - " time frame. Grid for density" - " could not be automatically" - " generated. If this is" - " expected, a user" - " defined grid will " - "need to be provided instead."): + with pytest.raises( + ValueError, + match="No atoms in AtomGroup at input" + " time frame. Grid for density" + " could not be automatically" + " generated. If this is" + " expected, a user" + " defined grid will " + "need to be provided instead.", + ): D = density.DensityAnalysis( universe.select_atoms(self.selections["none"]) ).run(step=5, **client_DensityAnalysis) def test_warn_results_deprecated(self, universe, client_DensityAnalysis): D = density.DensityAnalysis( - universe.select_atoms(self.selections['static'])) + universe.select_atoms(self.selections["static"]) + ) D.run(stop=1, **client_DensityAnalysis) wmsg = "The `density` attribute was deprecated in MDAnalysis 2.0.0" with pytest.warns(DeprecationWarning, match=wmsg): @@ -451,27 +491,30 @@ def test_density_analysis_conversion_default_unit(self): D.run() D.results.density.convert_density() + class TestGridImport(object): - @block_import('gridData') + @block_import("gridData") def test_absence_griddata(self): - sys.modules.pop('MDAnalysis.analysis.density', None) + sys.modules.pop("MDAnalysis.analysis.density", None) # if gridData package is missing an ImportError should be raised # at the module level of MDAnalysis.analysis.density with pytest.raises(ImportError): import MDAnalysis.analysis.density def test_presence_griddata(self): - sys.modules.pop('MDAnalysis.analysis.density', None) + sys.modules.pop("MDAnalysis.analysis.density", None) # no ImportError exception is raised when gridData is properly # imported by MDAnalysis.analysis.density # mock gridData in case there are testing scenarios where # it is not available mock = Mock() - with patch.dict('sys.modules', {'gridData': mock}): + with patch.dict("sys.modules", {"gridData": mock}): try: import MDAnalysis.analysis.density except ImportError: - pytest.fail(msg='''MDAnalysis.analysis.density should not raise - an ImportError if gridData is available.''') + pytest.fail( + msg="""MDAnalysis.analysis.density should not raise + an ImportError if gridData is available.""" + ) diff --git a/testsuite/MDAnalysisTests/analysis/test_dielectric.py b/testsuite/MDAnalysisTests/analysis/test_dielectric.py index a1a5ccc5062..45d8e722cdb 100644 --- a/testsuite/MDAnalysisTests/analysis/test_dielectric.py +++ b/testsuite/MDAnalysisTests/analysis/test_dielectric.py @@ -41,7 +41,7 @@ def test_broken_molecules(self, ag): ag.wrap() eps = DielectricConstant(ag, make_whole=False).run() - assert_allclose(eps.results['eps_mean'], 721.711, rtol=1e-03) + assert_allclose(eps.results["eps_mean"], 721.711, rtol=1e-03) def test_broken_repaired_molecules(self, ag): # cut molecules apart @@ -50,21 +50,23 @@ def test_broken_repaired_molecules(self, ag): ag.wrap() eps = DielectricConstant(ag, make_whole=True).run() - assert_allclose(eps.results['eps_mean'], 5.088, rtol=1e-03) + assert_allclose(eps.results["eps_mean"], 5.088, rtol=1e-03) def test_temperature(self, ag): eps = DielectricConstant(ag, temperature=100).run() - assert_allclose(eps.results['eps_mean'], 9.621, rtol=1e-03) + assert_allclose(eps.results["eps_mean"], 9.621, rtol=1e-03) def test_non_charges(self): u = mda.Universe(DCD_TRICLINIC, to_guess=()) - with pytest.raises(NoDataError, - match="No charges defined given atomgroup."): + with pytest.raises( + NoDataError, match="No charges defined given atomgroup." + ): DielectricConstant(u.atoms).run() def test_non_neutral(self, ag): - with pytest.raises(NotImplementedError, - match="Analysis for non-neutral systems or"): + with pytest.raises( + NotImplementedError, match="Analysis for non-neutral systems or" + ): DielectricConstant(ag[:-1]).run() def test_free_charges(self, ag): diff --git a/testsuite/MDAnalysisTests/analysis/test_diffusionmap.py b/testsuite/MDAnalysisTests/analysis/test_diffusionmap.py index 11271fd8f4c..99e978b3ff8 100644 --- a/testsuite/MDAnalysisTests/analysis/test_diffusionmap.py +++ b/testsuite/MDAnalysisTests/analysis/test_diffusionmap.py @@ -28,17 +28,17 @@ from numpy.testing import assert_array_almost_equal, assert_allclose -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def u(): return MDAnalysis.Universe(PDB, XTC) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def dist(u): - return diffusionmap.DistanceMatrix(u, select='backbone') + return diffusionmap.DistanceMatrix(u, select="backbone") -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def dmap(dist): d_map = diffusionmap.DiffusionMap(dist) d_map.run() @@ -53,57 +53,74 @@ def test_eg(dist, dmap): def test_dist_weights(u): - backbone = u.select_atoms('backbone') + backbone = u.select_atoms("backbone") weights_atoms = np.ones(len(backbone.atoms)) - dist = diffusionmap.DistanceMatrix(u, - select='backbone', - weights=weights_atoms) + dist = diffusionmap.DistanceMatrix( + u, select="backbone", weights=weights_atoms + ) dist.run(step=3) dmap = diffusionmap.DiffusionMap(dist) dmap.run() assert_array_almost_equal(dmap.eigenvalues, [1, 1, 1, 1], 4) - assert_array_almost_equal(dmap._eigenvectors, - ([[0, 0, 1, 0], - [0, 0, 0, 1], - [-.707, -.707, 0, 0], - [.707, -.707, 0, 0]]), 2) + assert_array_almost_equal( + dmap._eigenvectors, + ( + [ + [0, 0, 1, 0], + [0, 0, 0, 1], + [-0.707, -0.707, 0, 0], + [0.707, -0.707, 0, 0], + ] + ), + 2, + ) def test_dist_weights_frames(u): - backbone = u.select_atoms('backbone') + backbone = u.select_atoms("backbone") weights_atoms = np.ones(len(backbone.atoms)) - dist = diffusionmap.DistanceMatrix(u, - select='backbone', - weights=weights_atoms) + dist = diffusionmap.DistanceMatrix( + u, select="backbone", weights=weights_atoms + ) frames = np.arange(len(u.trajectory)) dist.run(frames=frames[::3]) dmap = diffusionmap.DiffusionMap(dist) dmap.run() assert_array_almost_equal(dmap.eigenvalues, [1, 1, 1, 1], 4) - assert_array_almost_equal(dmap._eigenvectors, - ([[0, 0, 1, 0], - [0, 0, 0, 1], - [-.707, -.707, 0, 0], - [.707, -.707, 0, 0]]), 2) + assert_array_almost_equal( + dmap._eigenvectors, + ( + [ + [0, 0, 1, 0], + [0, 0, 0, 1], + [-0.707, -0.707, 0, 0], + [0.707, -0.707, 0, 0], + ] + ), + 2, + ) + def test_distvalues_ag_universe(u): - dist_universe = diffusionmap.DistanceMatrix(u, select='backbone').run() - ag = u.select_atoms('backbone') + dist_universe = diffusionmap.DistanceMatrix(u, select="backbone").run() + ag = u.select_atoms("backbone") dist_ag = diffusionmap.DistanceMatrix(ag).run() - assert_allclose(dist_universe.results.dist_matrix, - dist_ag.results.dist_matrix) + assert_allclose( + dist_universe.results.dist_matrix, dist_ag.results.dist_matrix + ) def test_distvalues_ag_select(u): - dist_universe = diffusionmap.DistanceMatrix(u, select='backbone').run() - ag = u.select_atoms('protein') - dist_ag = diffusionmap.DistanceMatrix(ag, select='backbone').run() - assert_allclose(dist_universe.results.dist_matrix, - dist_ag.results.dist_matrix) - + dist_universe = diffusionmap.DistanceMatrix(u, select="backbone").run() + ag = u.select_atoms("protein") + dist_ag = diffusionmap.DistanceMatrix(ag, select="backbone").run() + assert_allclose( + dist_universe.results.dist_matrix, dist_ag.results.dist_matrix + ) + def test_different_steps(u): - dmap = diffusionmap.DiffusionMap(u, select='backbone') + dmap = diffusionmap.DiffusionMap(u, select="backbone") dmap.run(step=3) assert dmap._eigenvectors.shape == (4, 4) @@ -118,7 +135,7 @@ def test_transform(u, dmap): def test_long_traj(u): - with pytest.warns(UserWarning, match='The distance matrix is very large'): + with pytest.warns(UserWarning, match="The distance matrix is very large"): dmap = diffusionmap.DiffusionMap(u) dmap._dist_matrix.run(stop=1) dmap._dist_matrix.n_frames = 5001 @@ -126,20 +143,21 @@ def test_long_traj(u): def test_updating_atomgroup(u): - with pytest.warns(UserWarning, match='U must be a static AtomGroup'): - resid_select = 'around 5 resname ALA' + with pytest.warns(UserWarning, match="U must be a static AtomGroup"): + resid_select = "around 5 resname ALA" ag = u.select_atoms(resid_select, updating=True) dmap = diffusionmap.DiffusionMap(ag) dmap.run() + def test_not_universe_atomgroup_error(u): trj_only = u.trajectory - with pytest.raises(ValueError, match='U is not a Universe or AtomGroup'): + with pytest.raises(ValueError, match="U is not a Universe or AtomGroup"): diffusionmap.DiffusionMap(trj_only) def test_DistanceMatrix_attr_warning(u): - dist = diffusionmap.DistanceMatrix(u, select='backbone').run(step=3) + dist = diffusionmap.DistanceMatrix(u, select="backbone").run(step=3) wmsg = f"The `dist_matrix` attribute was deprecated in MDAnalysis 2.0.0" with pytest.warns(DeprecationWarning, match=wmsg): assert getattr(dist, "dist_matrix") is dist.results.dist_matrix diff --git a/testsuite/MDAnalysisTests/analysis/test_dihedrals.py b/testsuite/MDAnalysisTests/analysis/test_dihedrals.py index 767345bda7f..82625dfb934 100644 --- a/testsuite/MDAnalysisTests/analysis/test_dihedrals.py +++ b/testsuite/MDAnalysisTests/analysis/test_dihedrals.py @@ -26,10 +26,19 @@ import pytest import MDAnalysis as mda -from MDAnalysisTests.datafiles import (GRO, XTC, TPR, DihedralArray, - DihedralsArray, RamaArray, GLYRamaArray, - JaninArray, LYSJaninArray, PDB_rama, - PDB_janin) +from MDAnalysisTests.datafiles import ( + GRO, + XTC, + TPR, + DihedralArray, + DihedralsArray, + RamaArray, + GLYRamaArray, + JaninArray, + LYSJaninArray, + PDB_rama, + PDB_janin, +) import MDAnalysis.analysis.dihedrals from MDAnalysis.analysis.dihedrals import Dihedral, Ramachandran, Janin @@ -39,10 +48,11 @@ class TestDihedral(object): @pytest.fixture() def atomgroup(self): u = mda.Universe(GRO, XTC) - ag = u.select_atoms("(resid 4 and name N CA C) or (resid 5 and name N)") + ag = u.select_atoms( + "(resid 4 and name N CA C) or (resid 5 and name N)" + ) return ag - def test_dihedral(self, atomgroup, client_Dihedral): # client_Dihedral is defined in testsuite/analysis/conftest.py # among with other testing fixtures. During testing, it will @@ -53,25 +63,39 @@ def test_dihedral(self, atomgroup, client_Dihedral): dihedral = Dihedral([atomgroup]).run(**client_Dihedral) test_dihedral = np.load(DihedralArray) - assert_allclose(dihedral.results.angles, test_dihedral, rtol=0, atol=1.5e-5, - err_msg="error: dihedral angles should " - "match test values") + assert_allclose( + dihedral.results.angles, + test_dihedral, + rtol=0, + atol=1.5e-5, + err_msg="error: dihedral angles should " "match test values", + ) def test_dihedral_single_frame(self, atomgroup, client_Dihedral): - dihedral = Dihedral([atomgroup]).run(start=5, stop=6, **client_Dihedral) + dihedral = Dihedral([atomgroup]).run( + start=5, stop=6, **client_Dihedral + ) test_dihedral = [np.load(DihedralArray)[5]] - assert_allclose(dihedral.results.angles, test_dihedral, rtol=0, atol=1.5e-5, - err_msg="error: dihedral angles should " - "match test vales") + assert_allclose( + dihedral.results.angles, + test_dihedral, + rtol=0, + atol=1.5e-5, + err_msg="error: dihedral angles should " "match test vales", + ) def test_atomgroup_list(self, atomgroup, client_Dihedral): dihedral = Dihedral([atomgroup, atomgroup]).run(**client_Dihedral) test_dihedral = np.load(DihedralsArray) - assert_allclose(dihedral.results.angles, test_dihedral, rtol=0, atol=1.5e-5, - err_msg="error: dihedral angles should " - "match test values") + assert_allclose( + dihedral.results.angles, + test_dihedral, + rtol=0, + atol=1.5e-5, + err_msg="error: dihedral angles should " "match test values", + ) def test_enough_atoms(self, atomgroup, client_Dihedral): with pytest.raises(ValueError): @@ -97,42 +121,64 @@ def rama_ref_array(self): def test_ramachandran(self, universe, rama_ref_array, client_Ramachandran): rama = Ramachandran(universe.select_atoms("protein")).run( - **client_Ramachandran) - - assert_allclose(rama.results.angles, rama_ref_array, rtol=0, atol=1.5e-5, - err_msg="error: dihedral angles should " - "match test values") - - def test_ramachandran_single_frame(self, universe, rama_ref_array, client_Ramachandran): + **client_Ramachandran + ) + + assert_allclose( + rama.results.angles, + rama_ref_array, + rtol=0, + atol=1.5e-5, + err_msg="error: dihedral angles should " "match test values", + ) + + def test_ramachandran_single_frame( + self, universe, rama_ref_array, client_Ramachandran + ): rama = Ramachandran(universe.select_atoms("protein")).run( - start=5, stop=6, **client_Ramachandran) - - assert_allclose(rama.results.angles[0], rama_ref_array[5], rtol=0, atol=1.5e-5, - err_msg="error: dihedral angles should " - "match test values") - - def test_ramachandran_residue_selections(self, universe, client_Ramachandran): + start=5, stop=6, **client_Ramachandran + ) + + assert_allclose( + rama.results.angles[0], + rama_ref_array[5], + rtol=0, + atol=1.5e-5, + err_msg="error: dihedral angles should " "match test values", + ) + + def test_ramachandran_residue_selections( + self, universe, client_Ramachandran + ): rama = Ramachandran(universe.select_atoms("resname GLY")).run( - **client_Ramachandran) + **client_Ramachandran + ) test_rama = np.load(GLYRamaArray) - assert_allclose(rama.results.angles, test_rama, rtol=0, atol=1.5e-5, - err_msg="error: dihedral angles should " - "match test values") + assert_allclose( + rama.results.angles, + test_rama, + rtol=0, + atol=1.5e-5, + err_msg="error: dihedral angles should " "match test values", + ) def test_outside_protein_length(self, universe, client_Ramachandran): with pytest.raises(ValueError): - rama = Ramachandran(universe.select_atoms("resid 220"), - check_protein=True).run(**client_Ramachandran) + rama = Ramachandran( + universe.select_atoms("resid 220"), check_protein=True + ).run(**client_Ramachandran) def test_outside_protein_unchecked(self, universe, client_Ramachandran): - rama = Ramachandran(universe.select_atoms("resid 220"), - check_protein=False).run(**client_Ramachandran) + rama = Ramachandran( + universe.select_atoms("resid 220"), check_protein=False + ).run(**client_Ramachandran) def test_protein_ends(self, universe): with pytest.warns(UserWarning) as record: - rama = Ramachandran(universe.select_atoms("protein"), - check_protein=True).run() + rama = Ramachandran( + universe.select_atoms("protein"), check_protein=True + ).run() assert len(record) == 1 def test_None_removal(self): @@ -141,9 +187,14 @@ def test_None_removal(self): rama = Ramachandran(u.select_atoms("protein").residues[1:-1]) def test_plot(self, universe): - ax = Ramachandran(universe.select_atoms("resid 5-10")).run().plot(ref=True) - assert isinstance(ax, matplotlib.axes.Axes), \ - "Ramachandran.plot() did not return and Axes instance" + ax = ( + Ramachandran(universe.select_atoms("resid 5-10")) + .run() + .plot(ref=True) + ) + assert isinstance( + ax, matplotlib.axes.Axes + ), "Ramachandran.plot() did not return and Axes instance" def test_ramachandran_attr_warning(self, universe): rama = Ramachandran(universe.select_atoms("protein")).run(stop=2) @@ -178,24 +229,36 @@ def _test_janin(self, u, ref_array, client_Janin): janin = Janin(u.select_atoms("protein")).run(**client_Janin) # Test precision lowered to account for platform differences with osx - assert_allclose(janin.results.angles, ref_array, rtol=0, atol=1.5e-3, - err_msg="error: dihedral angles should " - "match test values") + assert_allclose( + janin.results.angles, + ref_array, + rtol=0, + atol=1.5e-3, + err_msg="error: dihedral angles should " "match test values", + ) def test_janin_single_frame(self, universe, janin_ref_array): janin = Janin(universe.select_atoms("protein")).run(start=5, stop=6) - assert_allclose(janin.results.angles[0], janin_ref_array[5], rtol=0, atol=1.5e-3, - err_msg="error: dihedral angles should " - "match test values") + assert_allclose( + janin.results.angles[0], + janin_ref_array[5], + rtol=0, + atol=1.5e-3, + err_msg="error: dihedral angles should " "match test values", + ) def test_janin_residue_selections(self, universe, client_Janin): janin = Janin(universe.select_atoms("resname LYS")).run(**client_Janin) test_janin = np.load(LYSJaninArray) - assert_allclose(janin.results.angles, test_janin, rtol=0, atol=1.5e-3, - err_msg="error: dihedral angles should " - "match test values") + assert_allclose( + janin.results.angles, + test_janin, + rtol=0, + atol=1.5e-3, + err_msg="error: dihedral angles should " "match test values", + ) def test_outside_protein_length(self, universe): with pytest.raises(ValueError): @@ -208,13 +271,17 @@ def test_remove_residues(self, universe): def test_atom_selection(self): with pytest.raises(ValueError): u = mda.Universe(PDB_janin) - janin = Janin(u.select_atoms("protein and not resname ALA CYS GLY " - "PRO SER THR VAL")) + janin = Janin( + u.select_atoms( + "protein and not resname ALA CYS GLY " "PRO SER THR VAL" + ) + ) def test_plot(self, universe): ax = Janin(universe.select_atoms("resid 5-10")).run().plot(ref=True) - assert isinstance(ax, matplotlib.axes.Axes), \ - "Ramachandran.plot() did not return and Axes instance" + assert isinstance( + ax, matplotlib.axes.Axes + ), "Ramachandran.plot() did not return and Axes instance" def test_janin_attr_warning(self, universe): janin = Janin(universe.select_atoms("protein")).run(stop=2) @@ -226,13 +293,14 @@ def test_janin_attr_warning(self, universe): # tests for parallelization + @pytest.mark.parametrize( "classname,is_parallelizable", [ (MDAnalysis.analysis.dihedrals.Dihedral, True), (MDAnalysis.analysis.dihedrals.Ramachandran, True), (MDAnalysis.analysis.dihedrals.Janin, True), - ] + ], ) def test_class_is_parallelizable(classname, is_parallelizable): assert classname._analysis_algorithm_is_parallelizable == is_parallelizable @@ -241,13 +309,31 @@ def test_class_is_parallelizable(classname, is_parallelizable): @pytest.mark.parametrize( "classname,backends", [ - (MDAnalysis.analysis.dihedrals.Dihedral, - ('serial', 'multiprocessing', 'dask',)), - (MDAnalysis.analysis.dihedrals.Ramachandran, - ('serial', 'multiprocessing', 'dask',)), - (MDAnalysis.analysis.dihedrals.Janin, - ('serial', 'multiprocessing', 'dask',)), - ] + ( + MDAnalysis.analysis.dihedrals.Dihedral, + ( + "serial", + "multiprocessing", + "dask", + ), + ), + ( + MDAnalysis.analysis.dihedrals.Ramachandran, + ( + "serial", + "multiprocessing", + "dask", + ), + ), + ( + MDAnalysis.analysis.dihedrals.Janin, + ( + "serial", + "multiprocessing", + "dask", + ), + ), + ], ) def test_supported_backends(classname, backends): assert classname.get_supported_backends() == backends diff --git a/testsuite/MDAnalysisTests/analysis/test_distances.py b/testsuite/MDAnalysisTests/analysis/test_distances.py index 8e3a14f8224..90fe8d75e8e 100644 --- a/testsuite/MDAnalysisTests/analysis/test_distances.py +++ b/testsuite/MDAnalysisTests/analysis/test_distances.py @@ -29,8 +29,13 @@ import MDAnalysis.analysis.distances -from numpy.testing import (assert_equal, assert_array_equal, assert_almost_equal, - assert_array_almost_equal,assert_allclose) +from numpy.testing import ( + assert_equal, + assert_array_equal, + assert_almost_equal, + assert_array_almost_equal, + assert_allclose, +) import numpy as np @@ -38,75 +43,92 @@ class TestContactMatrix(object): @staticmethod @pytest.fixture() def coord(): - return np.array([[1, 1, 1], - [5, 5, 5], - [1.1, 1.1, 1.1], - [11, 11, 11], # neighboring image with pbc - [21, 21, 21]], # non neighboring image with pbc - dtype=np.float32) - + return np.array( + [ + [1, 1, 1], + [5, 5, 5], + [1.1, 1.1, 1.1], + [11, 11, 11], # neighboring image with pbc + [21, 21, 21], + ], # non neighboring image with pbc + dtype=np.float32, + ) + @staticmethod @pytest.fixture() def box(): return np.array([10, 10, 10, 90, 90, 90], dtype=np.float32) - + @staticmethod @pytest.fixture() def shape(): return 5, 5 - + @staticmethod @pytest.fixture() def res_no_pbc(): - return np.array([[1, 0, 1, 0, 0], - [0, 1, 0, 0, 0], - [1, 0, 1, 0, 0], - [0, 0, 0, 1, 0], - [0, 0, 0, 0, 1]], dtype=bool) - + return np.array( + [ + [1, 0, 1, 0, 0], + [0, 1, 0, 0, 0], + [1, 0, 1, 0, 0], + [0, 0, 0, 1, 0], + [0, 0, 0, 0, 1], + ], + dtype=bool, + ) + @staticmethod @pytest.fixture() def res_pbc(): - return np.array([[1, 0, 1, 1, 1], - [0, 1, 0, 0, 0], - [1, 0, 1, 1, 1], - [1, 0, 1, 1, 1], - [1, 0, 1, 1, 1]], dtype=bool) + return np.array( + [ + [1, 0, 1, 1, 1], + [0, 1, 0, 0, 0], + [1, 0, 1, 1, 1], + [1, 0, 1, 1, 1], + [1, 0, 1, 1, 1], + ], + dtype=bool, + ) def test_np(self, coord, shape, res_no_pbc): contacts = MDAnalysis.analysis.distances.contact_matrix( coord, cutoff=1, returntype="numpy" ) - assert contacts.shape == shape, \ - "wrong shape (should be {0})".format(shape) + assert contacts.shape == shape, "wrong shape (should be {0})".format( + shape + ) assert_equal(contacts, res_no_pbc) def test_sparse(self, coord, shape, res_no_pbc): contacts = MDAnalysis.analysis.distances.contact_matrix( coord, cutoff=1.5, returntype="sparse" ) - assert contacts.shape == shape, \ - "wrong shape (should be {0})".format(shape) + assert contacts.shape == shape, "wrong shape (should be {0})".format( + shape + ) assert_equal(contacts.toarray(), res_no_pbc) def test_box_numpy(self, coord, box, shape, res_pbc): contacts = MDAnalysis.analysis.distances.contact_matrix( coord, box=box, cutoff=1 ) - assert contacts.shape == shape, \ - "wrong shape (should be {0})".format(shape) + assert contacts.shape == shape, "wrong shape (should be {0})".format( + shape + ) assert_equal(contacts, res_pbc) def test_box_sparse(self, coord, box, shape, res_pbc): contacts = MDAnalysis.analysis.distances.contact_matrix( - coord, box=box, cutoff=1, returntype='sparse' + coord, box=box, cutoff=1, returntype="sparse" + ) + assert contacts.shape == shape, "wrong shape (should be {0})".format( + shape ) - assert contacts.shape == shape, \ - "wrong shape (should be {0})".format(shape) assert_equal(contacts.toarray(), res_pbc) - class TestDist(object): @staticmethod @@ -126,13 +148,13 @@ def ag2(): @pytest.fixture() def box(): return np.array([8, 8, 8, 90, 90, 90], dtype=np.float32) - + @staticmethod @pytest.fixture() def expected(ag, ag2): - - return np.diag(scipy.spatial.distance.cdist( - ag.positions, ag2.positions) + + return np.diag( + scipy.spatial.distance.cdist(ag.positions, ag2.positions) ) @staticmethod @@ -141,38 +163,37 @@ def expected_box(ag, ag2, box): rp = np.abs(ag.positions - ag2.positions) box_2d = box[np.newaxis, 0:3] - rp = np.where(rp > box_2d / 2, box_2d - rp, rp) + rp = np.where(rp > box_2d / 2, box_2d - rp, rp) return np.sqrt(np.square(rp).sum(axis=1)) def test_pairwise_dist(self, ag, ag2, expected): - '''Ensure that pairwise distances between atoms are - correctly calculated.''' + """Ensure that pairwise distances between atoms are + correctly calculated.""" actual = MDAnalysis.analysis.distances.dist(ag, ag2)[2] assert_allclose(actual, expected) def test_pairwise_dist_box(self, ag, ag2, expected_box, box): - '''Ensure that pairwise distances between atoms are - correctly calculated.''' + """Ensure that pairwise distances between atoms are + correctly calculated.""" actual = MDAnalysis.analysis.distances.dist(ag, ag2, 0, box)[2] assert_allclose(actual, expected_box, rtol=1e-05, atol=10) def test_pairwise_dist_offset_effect(self, ag, ag2, expected): - '''Test that feeding in offsets to dist() doesn't alter - pairwise distance matrix.''' - actual = MDAnalysis.analysis.distances.dist( - ag, ag2, offset=229)[2] + """Test that feeding in offsets to dist() doesn't alter + pairwise distance matrix.""" + actual = MDAnalysis.analysis.distances.dist(ag, ag2, offset=229)[2] assert_allclose(actual, expected) def test_offset_calculation(self, ag, ag2): - '''Test that offsets fed to dist() are correctly calculated.''' - actual = MDAnalysis.analysis.distances.dist(ag, ag2, - offset=33)[:2] - assert_equal(actual, np.array([ag.atoms.resids + 33, - ag2.atoms.resids + 33])) + """Test that offsets fed to dist() are correctly calculated.""" + actual = MDAnalysis.analysis.distances.dist(ag, ag2, offset=33)[:2] + assert_equal( + actual, np.array([ag.atoms.resids + 33, ag2.atoms.resids + 33]) + ) def test_mismatch_exception(self, ag, ag2, expected): - '''A ValueError should be raised if the two atomgroups - don't have the same number of atoms.''' + """A ValueError should be raised if the two atomgroups + don't have the same number of atoms.""" with pytest.raises(ValueError): MDAnalysis.analysis.distances.dist(ag[:8], ag2) @@ -202,36 +223,30 @@ def group(u): @pytest.fixture() def expected(self, group, ag, ag2): - distance_matrix_1 = scipy.spatial.distance.cdist(group.positions, - ag.positions) + distance_matrix_1 = scipy.spatial.distance.cdist( + group.positions, ag.positions + ) mask_1 = np.unique(np.where(distance_matrix_1 <= self.distance)[0]) group_filtered = group[mask_1] - distance_matrix_2 = scipy.spatial.distance.cdist(group_filtered.positions, - ag2.positions) + distance_matrix_2 = scipy.spatial.distance.cdist( + group_filtered.positions, ag2.positions + ) mask_2 = np.unique(np.where(distance_matrix_2 <= self.distance)[0]) return group_filtered[mask_2].indices def test_between_simple_case_indices_only(self, group, ag, ag2, expected): - '''Test MDAnalysis.analysis.distances.between() for + """Test MDAnalysis.analysis.distances.between() for a simple input case. Checks atom indices of returned AtomGroup against sorted expected index - values.''' + values.""" actual = MDAnalysis.analysis.distances.between( - group, - ag, - ag2, - self.distance + group, ag, ag2, self.distance ).indices assert_equal(actual, expected) - @pytest.mark.parametrize('dists', [5.9, 0.0]) + @pytest.mark.parametrize("dists", [5.9, 0.0]) def test_between_return_type(self, dists, group, ag, ag2): - '''Test that MDAnalysis.analysis.distances.between() - returns an AtomGroup even when the returned group is empty.''' - actual = MDAnalysis.analysis.distances.between( - group, - ag, - ag2, - dists - ) + """Test that MDAnalysis.analysis.distances.between() + returns an AtomGroup even when the returned group is empty.""" + actual = MDAnalysis.analysis.distances.between(group, ag, ag2, dists) assert isinstance(actual, MDAnalysis.core.groups.AtomGroup) diff --git a/testsuite/MDAnalysisTests/analysis/test_dssp.py b/testsuite/MDAnalysisTests/analysis/test_dssp.py index f7a1e118931..ee43a400dff 100644 --- a/testsuite/MDAnalysisTests/analysis/test_dssp.py +++ b/testsuite/MDAnalysisTests/analysis/test_dssp.py @@ -9,7 +9,9 @@ # Files that match glob pattern '????.pdb.gz' and matching '????.pdb.dssp' files, # containing the secondary structure assignment string, will be tested automatically. -@pytest.mark.parametrize("pdb_filename", glob.glob(f"{DSSP_FOLDER}/?????.pdb.gz")) +@pytest.mark.parametrize( + "pdb_filename", glob.glob(f"{DSSP_FOLDER}/?????.pdb.gz") +) def test_file_guess_hydrogens(pdb_filename, client_DSSP): u = mda.Universe(pdb_filename) with open(f"{pdb_filename.rstrip('.gz')}.dssp", "r") as fin: @@ -27,7 +29,9 @@ def test_trajectory(client_DSSP): last_frame = "".join(run.results.dssp[-1]) avg_frame = "".join(translate(run.results.dssp_ndarray.mean(axis=0))) - assert first_frame[:10] != last_frame[:10] == avg_frame[:10] == "-EEEEEE---" + assert ( + first_frame[:10] != last_frame[:10] == avg_frame[:10] == "-EEEEEE---" + ) protein = mda.Universe(TPR, XTC).select_atoms("protein") run = DSSP(protein).run(**client_DSSP, stop=10) @@ -39,7 +43,9 @@ def test_atomgroup(client_DSSP): last_frame = "".join(run.results.dssp[-1]) avg_frame = "".join(translate(run.results.dssp_ndarray.mean(axis=0))) - assert first_frame[:10] != last_frame[:10] == avg_frame[:10] == "-EEEEEE---" + assert ( + first_frame[:10] != last_frame[:10] == avg_frame[:10] == "-EEEEEE---" + ) def test_trajectory_with_hydrogens(client_DSSP): @@ -49,10 +55,14 @@ def test_trajectory_with_hydrogens(client_DSSP): last_frame = "".join(run.results.dssp[-1]) avg_frame = "".join(translate(run.results.dssp_ndarray.mean(axis=0))) - assert first_frame[:10] == last_frame[:10] == avg_frame[:10] == "-EEEEEE---" + assert ( + first_frame[:10] == last_frame[:10] == avg_frame[:10] == "-EEEEEE---" + ) -@pytest.mark.parametrize("pdb_filename", glob.glob(f"{DSSP_FOLDER}/2xdgA.pdb.gz")) +@pytest.mark.parametrize( + "pdb_filename", glob.glob(f"{DSSP_FOLDER}/2xdgA.pdb.gz") +) def test_trajectory_without_hydrogen_fails(pdb_filename, client_DSSP): u = mda.Universe(pdb_filename) with pytest.raises(ValueError): @@ -62,8 +72,9 @@ def test_trajectory_without_hydrogen_fails(pdb_filename, client_DSSP): @pytest.mark.parametrize( "pdb_filename", glob.glob(f"{DSSP_FOLDER}/1mr1D_failing.pdb.gz") ) -def test_trajectory_with_uneven_number_of_atoms_fails(pdb_filename, - client_DSSP): +def test_trajectory_with_uneven_number_of_atoms_fails( + pdb_filename, client_DSSP +): u = mda.Universe(pdb_filename) with pytest.raises(ValueError): DSSP(u, guess_hydrogens=True).run(**client_DSSP) diff --git a/testsuite/MDAnalysisTests/analysis/test_encore.py b/testsuite/MDAnalysisTests/analysis/test_encore.py index 948575adfff..9204352f480 100644 --- a/testsuite/MDAnalysisTests/analysis/test_encore.py +++ b/testsuite/MDAnalysisTests/analysis/test_encore.py @@ -20,27 +20,25 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -import MDAnalysis as mda -import MDAnalysis.analysis.encore as encore - import importlib -import tempfile -import numpy as np -import sys import os -import warnings import platform +import sys +import tempfile +import warnings from importlib import reload +import MDAnalysis as mda +import MDAnalysis.analysis.align as align +import MDAnalysis.analysis.encore as encore +import MDAnalysis.analysis.encore.confdistmatrix as confdistmatrix +import MDAnalysis.analysis.rms as rms +import numpy as np import pytest -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal -from MDAnalysisTests.datafiles import DCD, DCD2, PSF, TPR, XTC from MDAnalysisTests import block_import - -import MDAnalysis.analysis.rms as rms -import MDAnalysis.analysis.align as align -import MDAnalysis.analysis.encore.confdistmatrix as confdistmatrix +from MDAnalysisTests.datafiles import DCD, DCD2, PSF, TPR, XTC def function(x): @@ -52,14 +50,15 @@ def test_moved_to_mdakit_warning(): with pytest.warns(DeprecationWarning, match=wmsg): reload(encore) + class TestEncore(object): - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def ens1_template(self): template = mda.Universe(PSF, DCD) template.transfer_to_memory(step=5) return template - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def ens2_template(self): template = mda.Universe(PSF, DCD2) template.transfer_to_memory(step=5) @@ -69,21 +68,23 @@ def ens2_template(self): def ens1(self, ens1_template): return mda.Universe( ens1_template.filename, - ens1_template.trajectory.timeseries(order='fac'), - format=mda.coordinates.memory.MemoryReader) + ens1_template.trajectory.timeseries(order="fac"), + format=mda.coordinates.memory.MemoryReader, + ) @pytest.fixture() def ens2(self, ens2_template): return mda.Universe( ens2_template.filename, - ens2_template.trajectory.timeseries(order='fac'), - format=mda.coordinates.memory.MemoryReader) + ens2_template.trajectory.timeseries(order="fac"), + format=mda.coordinates.memory.MemoryReader, + ) def test_triangular_matrix(self): scalar = 2 size = 3 expected_value = 1.984 - filename = tempfile.mktemp()+".npz" + filename = tempfile.mktemp() + ".npz" triangular_matrix = encore.utils.TriangularMatrix(size=size) @@ -91,130 +92,191 @@ def test_triangular_matrix(self): err_msg = ( "Data error in TriangularMatrix: read/write are not consistent" - ) + ) assert_equal(triangular_matrix[0, 1], expected_value, err_msg) - assert_equal(triangular_matrix[0,1], triangular_matrix[1,0], - err_msg="Data error in TriangularMatrix: matrix non symmetrical") + assert_equal( + triangular_matrix[0, 1], + triangular_matrix[1, 0], + err_msg="Data error in TriangularMatrix: matrix non symmetrical", + ) triangular_matrix.savez(filename) - triangular_matrix_2 = encore.utils.TriangularMatrix(size = size, loadfile = filename) - assert_equal(triangular_matrix_2[0,1], expected_value, - err_msg="Data error in TriangularMatrix: loaded matrix non symmetrical") + triangular_matrix_2 = encore.utils.TriangularMatrix( + size=size, loadfile=filename + ) + assert_equal( + triangular_matrix_2[0, 1], + expected_value, + err_msg="Data error in TriangularMatrix: loaded matrix non symmetrical", + ) - triangular_matrix_3 = encore.utils.TriangularMatrix(size = size) + triangular_matrix_3 = encore.utils.TriangularMatrix(size=size) triangular_matrix_3.loadz(filename) - assert_equal(triangular_matrix_3[0,1], expected_value, - err_msg="Data error in TriangularMatrix: loaded matrix non symmetrical") + assert_equal( + triangular_matrix_3[0, 1], + expected_value, + err_msg="Data error in TriangularMatrix: loaded matrix non symmetrical", + ) incremented_triangular_matrix = triangular_matrix + scalar - assert_equal(incremented_triangular_matrix[0,1], expected_value + scalar, - err_msg="Error in TriangularMatrix: addition of scalar gave" - "inconsistent results") + assert_equal( + incremented_triangular_matrix[0, 1], + expected_value + scalar, + err_msg="Error in TriangularMatrix: addition of scalar gave" + "inconsistent results", + ) triangular_matrix += scalar - assert_equal(triangular_matrix[0,1], expected_value + scalar, - err_msg="Error in TriangularMatrix: addition of scalar gave" - "inconsistent results") + assert_equal( + triangular_matrix[0, 1], + expected_value + scalar, + err_msg="Error in TriangularMatrix: addition of scalar gave" + "inconsistent results", + ) multiplied_triangular_matrix_2 = triangular_matrix_2 * scalar - assert_equal(multiplied_triangular_matrix_2[0,1], expected_value * scalar, - err_msg="Error in TriangularMatrix: multiplication by scalar gave" - "inconsistent results") + assert_equal( + multiplied_triangular_matrix_2[0, 1], + expected_value * scalar, + err_msg="Error in TriangularMatrix: multiplication by scalar gave" + "inconsistent results", + ) triangular_matrix_2 *= scalar - assert_equal(triangular_matrix_2[0,1], expected_value * scalar, - err_msg="Error in TriangularMatrix: multiplication by scalar gave\ -inconsistent results") + assert_equal( + triangular_matrix_2[0, 1], + expected_value * scalar, + err_msg="Error in TriangularMatrix: multiplication by scalar gave\ +inconsistent results", + ) - @pytest.mark.xfail(os.name == 'nt', - reason="Not yet supported on Windows.") + @pytest.mark.xfail(os.name == "nt", reason="Not yet supported on Windows.") def test_parallel_calculation(self): - arguments = [tuple([i]) for i in np.arange(0,100)] + arguments = [tuple([i]) for i in np.arange(0, 100)] - parallel_calculation = encore.utils.ParallelCalculation(function=function, - n_jobs=4, - args=arguments) + parallel_calculation = encore.utils.ParallelCalculation( + function=function, n_jobs=4, args=arguments + ) results = parallel_calculation.run() for i, r in enumerate(results): assert_equal( r[1], - arguments[i][0]**2, - err_msg="Unexpected results from ParallelCalculation") + arguments[i][0] ** 2, + err_msg="Unexpected results from ParallelCalculation", + ) def test_rmsd_matrix_with_superimposition(self, ens1): - conf_dist_matrix = encore.confdistmatrix.conformational_distance_matrix( - ens1, - encore.confdistmatrix.set_rmsd_matrix_elements, - select="name CA", - pairwise_align=True, - weights='mass', - n_jobs=1) + conf_dist_matrix = ( + encore.confdistmatrix.conformational_distance_matrix( + ens1, + encore.confdistmatrix.set_rmsd_matrix_elements, + select="name CA", + pairwise_align=True, + weights="mass", + n_jobs=1, + ) + ) reference = rms.RMSD(ens1, select="name CA") reference.run() err_msg = ( "Calculated RMSD values differ from " - "the reference implementation") + "the reference implementation" + ) for i, rmsd in enumerate(reference.results.rmsd): - assert_allclose(conf_dist_matrix[0, i], rmsd[2], rtol=0, atol=1.5e-3, err_msg=err_msg) + assert_allclose( + conf_dist_matrix[0, i], + rmsd[2], + rtol=0, + atol=1.5e-3, + err_msg=err_msg, + ) def test_rmsd_matrix_with_superimposition_custom_weights(self, ens1): - conf_dist_matrix = encore.confdistmatrix.conformational_distance_matrix( - ens1, - encore.confdistmatrix.set_rmsd_matrix_elements, - select="name CA", - pairwise_align=True, - weights='mass', - n_jobs=1) + conf_dist_matrix = ( + encore.confdistmatrix.conformational_distance_matrix( + ens1, + encore.confdistmatrix.set_rmsd_matrix_elements, + select="name CA", + pairwise_align=True, + weights="mass", + n_jobs=1, + ) + ) - conf_dist_matrix_custom = encore.confdistmatrix.conformational_distance_matrix( - ens1, - encore.confdistmatrix.set_rmsd_matrix_elements, - select="name CA", - pairwise_align=True, - weights=(ens1.select_atoms('name CA').masses, ens1.select_atoms('name CA').masses), - n_jobs=1) + conf_dist_matrix_custom = ( + encore.confdistmatrix.conformational_distance_matrix( + ens1, + encore.confdistmatrix.set_rmsd_matrix_elements, + select="name CA", + pairwise_align=True, + weights=( + ens1.select_atoms("name CA").masses, + ens1.select_atoms("name CA").masses, + ), + n_jobs=1, + ) + ) for i in range(conf_dist_matrix_custom.size): - assert_allclose(conf_dist_matrix_custom[0, i], conf_dist_matrix[0, i], rtol=0, atol=1.5e-7) + assert_allclose( + conf_dist_matrix_custom[0, i], + conf_dist_matrix[0, i], + rtol=0, + atol=1.5e-7, + ) def test_rmsd_matrix_without_superimposition(self, ens1): selection_string = "name CA" selection = ens1.select_atoms(selection_string) reference_rmsd = [] - coordinates = ens1.trajectory.timeseries(selection, order='fac') + coordinates = ens1.trajectory.timeseries(selection, order="fac") for coord in coordinates: - reference_rmsd.append(rms.rmsd(coordinates[0], coord, superposition=False)) + reference_rmsd.append( + rms.rmsd(coordinates[0], coord, superposition=False) + ) confdist_matrix = encore.confdistmatrix.conformational_distance_matrix( ens1, encore.confdistmatrix.set_rmsd_matrix_elements, select=selection_string, pairwise_align=False, - weights='mass', - n_jobs=1) + weights="mass", + n_jobs=1, + ) print(repr(confdist_matrix.as_array()[0, :])) - assert_allclose(confdist_matrix.as_array()[0,:], reference_rmsd, rtol=0, atol=1.5e-3, - err_msg="calculated RMSD values differ from reference") + assert_allclose( + confdist_matrix.as_array()[0, :], + reference_rmsd, + rtol=0, + atol=1.5e-3, + err_msg="calculated RMSD values differ from reference", + ) def test_ensemble_superimposition(self): aligned_ensemble1 = mda.Universe(PSF, DCD) - align.AlignTraj(aligned_ensemble1, aligned_ensemble1, - select="name CA", - in_memory=True).run() + align.AlignTraj( + aligned_ensemble1, + aligned_ensemble1, + select="name CA", + in_memory=True, + ).run() aligned_ensemble2 = mda.Universe(PSF, DCD) - align.AlignTraj(aligned_ensemble2, aligned_ensemble2, - select="name *", - in_memory=True).run() - - rmsfs1 = rms.RMSF(aligned_ensemble1.select_atoms('name *')) + align.AlignTraj( + aligned_ensemble2, + aligned_ensemble2, + select="name *", + in_memory=True, + ).run() + + rmsfs1 = rms.RMSF(aligned_ensemble1.select_atoms("name *")) rmsfs1.run() - rmsfs2 = rms.RMSF(aligned_ensemble2.select_atoms('name *')) + rmsfs2 = rms.RMSF(aligned_ensemble2.select_atoms("name *")) rmsfs2.run() assert sum(rmsfs1.results.rmsf) > sum(rmsfs2.results.rmsf), ( @@ -224,18 +286,24 @@ def test_ensemble_superimposition(self): def test_ensemble_superimposition_to_reference_non_weighted(self): aligned_ensemble1 = mda.Universe(PSF, DCD) - align.AlignTraj(aligned_ensemble1, aligned_ensemble1, - select="name CA", - in_memory=True).run() + align.AlignTraj( + aligned_ensemble1, + aligned_ensemble1, + select="name CA", + in_memory=True, + ).run() aligned_ensemble2 = mda.Universe(PSF, DCD) - align.AlignTraj(aligned_ensemble2, aligned_ensemble2, - select="name *", - in_memory=True).run() - - rmsfs1 = rms.RMSF(aligned_ensemble1.select_atoms('name *')) + align.AlignTraj( + aligned_ensemble2, + aligned_ensemble2, + select="name *", + in_memory=True, + ).run() + + rmsfs1 = rms.RMSF(aligned_ensemble1.select_atoms("name *")) rmsfs1.run() - rmsfs2 = rms.RMSF(aligned_ensemble2.select_atoms('name *')) + rmsfs2 = rms.RMSF(aligned_ensemble2.select_atoms("name *")) rmsfs2.run() assert sum(rmsfs1.results.rmsf) > sum(rmsfs2.results.rmsf), ( @@ -244,64 +312,96 @@ def test_ensemble_superimposition_to_reference_non_weighted(self): ) def test_covariance_matrix(self, ens1): + # fmt: off reference_cov = np.array([ - [12.9122,-5.2692,3.9016,10.0663,-5.3309,3.8923,8.5037,-5.2017,2.6941], - [-5.2692,4.1087,-2.4101,-4.5485,3.3954,-2.3245,-3.7343,2.8415,-1.6223], - [3.9016,-2.4101,3.1800,3.4453,-2.6860,2.2438,2.7751,-2.2523,1.6084], - [10.0663,-4.5485,3.4453,8.8608,-4.6727,3.3641,7.0106,-4.4986,2.2604], - [-5.3309,3.3954,-2.6860,-4.6727,4.4627,-2.4233,-3.8304,3.0367,-1.6942], - [3.8923,-2.3245,2.2438,3.3641,-2.4233,2.6193,2.6908,-2.0252,1.5775], - [8.5037,-3.7343,2.7751,7.0106,-3.8304,2.6908,6.2861,-3.7138,1.8701], - [-5.2017,2.8415,-2.2523,-4.4986,3.0367,-2.0252,-3.7138,3.3999,-1.4166], - [2.6941,-1.6223,1.6084,2.2604,-1.6942,1.5775,1.8701,-1.4166,1.4664]]) - - covariance = encore.covariance.covariance_matrix(ens1, - select="name CA and resnum 1:3", - estimator=encore.covariance.shrinkage_covariance_estimator) - assert_allclose(covariance, reference_cov, rtol=0, atol=1.5e-4, - err_msg="Covariance matrix from covariance estimation not as expected") + [12.9122,-5.2692,3.9016,10.0663,-5.3309,3.8923,8.5037,-5.2017,2.6941], + [-5.2692,4.1087,-2.4101,-4.5485,3.3954,-2.3245,-3.7343,2.8415,-1.6223], + [3.9016,-2.4101,3.1800,3.4453,-2.6860,2.2438,2.7751,-2.2523,1.6084], + [10.0663,-4.5485,3.4453,8.8608,-4.6727,3.3641,7.0106,-4.4986,2.2604], + [-5.3309,3.3954,-2.6860,-4.6727,4.4627,-2.4233,-3.8304,3.0367,-1.6942], + [3.8923,-2.3245,2.2438,3.3641,-2.4233,2.6193,2.6908,-2.0252,1.5775], + [8.5037,-3.7343,2.7751,7.0106,-3.8304,2.6908,6.2861,-3.7138,1.8701], + [-5.2017,2.8415,-2.2523,-4.4986,3.0367,-2.0252,-3.7138,3.3999,-1.4166], + [2.6941,-1.6223,1.6084,2.2604,-1.6942,1.5775,1.8701,-1.4166,1.4664] + ]) + # fmt: on + + covariance = encore.covariance.covariance_matrix( + ens1, + select="name CA and resnum 1:3", + estimator=encore.covariance.shrinkage_covariance_estimator, + ) + assert_allclose( + covariance, + reference_cov, + rtol=0, + atol=1.5e-4, + err_msg="Covariance matrix from covariance estimation not as expected", + ) def test_covariance_matrix_with_reference(self, ens1): + # fmt: off reference_cov = np.array([ - [39.0760,-28.5383,29.7761,37.9330,-35.5251,18.9421,30.4334,-31.4829,12.8712], - [-28.5383,24.1827,-25.5676,-29.0183,30.3511,-15.9598,-22.9298,26.1086,-10.8693], - [29.7761,-25.5676,28.9796,30.7607,-32.8739,17.7072,24.1689,-28.3557,12.1190], - [37.9330,-29.0183,30.7607,37.6532,-36.4537,19.2865,29.9841,-32.1404,12.9998], - [-35.5251,30.3511,-32.8739,-36.4537,38.5711,-20.1190,-28.7652,33.2857,-13.6963], - [18.9421,-15.9598,17.7072,19.2865,-20.1190,11.4059,15.1244,-17.2695,7.8205], - [30.4334,-22.9298,24.1689,29.9841,-28.7652,15.1244,24.0514,-25.4106,10.2863], - [-31.4829,26.1086,-28.3557,-32.1404,33.2857,-17.2695,-25.4106,29.1773,-11.7530], - [12.8712,-10.8693,12.1190,12.9998,-13.6963,7.8205,10.2863,-11.7530,5.5058]]) - - covariance = encore.covariance.covariance_matrix(ens1, - select="name CA and resnum 1:3", - estimator=encore.covariance.shrinkage_covariance_estimator, - reference=ens1) + [39.0760,-28.5383,29.7761,37.9330,-35.5251,18.9421,30.4334,-31.4829,12.8712], + [-28.5383,24.1827,-25.5676,-29.0183,30.3511,-15.9598,-22.9298,26.1086,-10.8693], + [29.7761,-25.5676,28.9796,30.7607,-32.8739,17.7072,24.1689,-28.3557,12.1190], + [37.9330,-29.0183,30.7607,37.6532,-36.4537,19.2865,29.9841,-32.1404,12.9998], + [-35.5251,30.3511,-32.8739,-36.4537,38.5711,-20.1190,-28.7652,33.2857,-13.6963], + [18.9421,-15.9598,17.7072,19.2865,-20.1190,11.4059,15.1244,-17.2695,7.8205], + [30.4334,-22.9298,24.1689,29.9841,-28.7652,15.1244,24.0514,-25.4106,10.2863], + [-31.4829,26.1086,-28.3557,-32.1404,33.2857,-17.2695,-25.4106,29.1773,-11.7530], + [12.8712,-10.8693,12.1190,12.9998,-13.6963,7.8205,10.2863,-11.7530,5.5058] + ]) + # fmt: on + + covariance = encore.covariance.covariance_matrix( + ens1, + select="name CA and resnum 1:3", + estimator=encore.covariance.shrinkage_covariance_estimator, + reference=ens1, + ) err_msg = ( - "Covariance matrix from covariance estimation not as expected" - ) - assert_allclose(covariance, reference_cov, rtol=0, atol=1.5e-4, err_msg=err_msg) + "Covariance matrix from covariance estimation not as expected" + ) + assert_allclose( + covariance, reference_cov, rtol=0, atol=1.5e-4, err_msg=err_msg + ) def test_hes_to_self(self, ens1): results, details = encore.hes([ens1, ens1]) result_value = results[0, 1] - expected_value = 0. - assert_allclose(result_value, expected_value, rtol=0, atol=1.5e-7, - err_msg="Harmonic Ensemble Similarity to itself\ - not zero:{0:f}".format(result_value)) + expected_value = 0.0 + assert_allclose( + result_value, + expected_value, + rtol=0, + atol=1.5e-7, + err_msg="Harmonic Ensemble Similarity to itself\ + not zero:{0:f}".format( + result_value + ), + ) def test_hes(self, ens1, ens2): - results, details = encore.hes([ens1, ens2], weights='mass') + results, details = encore.hes([ens1, ens2], weights="mass") result_value = results[0, 1] - min_bound = 1E5 - assert result_value > min_bound, "Unexpected value for Harmonic " \ - "Ensemble Similarity: {0:f}. Expected {1:f}.".format(result_value, min_bound) + min_bound = 1e5 + assert result_value > min_bound, ( + "Unexpected value for Harmonic " + "Ensemble Similarity: {0:f}. Expected {1:f}.".format( + result_value, min_bound + ) + ) def test_hes_custom_weights(self, ens1, ens2): - results, details = encore.hes([ens1, ens2], weights='mass') - results_custom, details_custom = encore.hes([ens1, ens2], - weights=(ens1.select_atoms('name CA').masses, - ens2.select_atoms('name CA').masses)) + results, details = encore.hes([ens1, ens2], weights="mass") + results_custom, details_custom = encore.hes( + [ens1, ens2], + weights=( + ens1.select_atoms("name CA").masses, + ens2.select_atoms("name CA").masses, + ), + ) result_value = results[0, 1] result_value_custom = results_custom[0, 1] assert_allclose(result_value, result_value_custom, rtol=0, atol=1.5e-7) @@ -310,90 +410,154 @@ def test_hes_align(self, ens1, ens2): # This test is massively sensitive! # Get 5260 when masses were float32? results, details = encore.hes([ens1, ens2], align=True) - result_value = results[0,1] + result_value = results[0, 1] expected_value = 2047.05 - assert_allclose(result_value, expected_value, rtol=0, atol=1.5e3, - err_msg="Unexpected value for Harmonic Ensemble Similarity: {0:f}. Expected {1:f}.".format(result_value, expected_value)) + assert_allclose( + result_value, + expected_value, + rtol=0, + atol=1.5e3, + err_msg="Unexpected value for Harmonic Ensemble Similarity: {0:f}. Expected {1:f}.".format( + result_value, expected_value + ), + ) def test_ces_to_self(self, ens1): - results, details = \ - encore.ces([ens1, ens1], - clustering_method=encore.AffinityPropagationNative(preference = -3.0)) - result_value = results[0,1] - expected_value = 0. - assert_allclose(result_value, expected_value, rtol=0, atol=1.5e-7, - err_msg="ClusteringEnsemble Similarity to itself not zero: {0:f}".format(result_value)) + results, details = encore.ces( + [ens1, ens1], + clustering_method=encore.AffinityPropagationNative( + preference=-3.0 + ), + ) + result_value = results[0, 1] + expected_value = 0.0 + assert_allclose( + result_value, + expected_value, + rtol=0, + atol=1.5e-7, + err_msg="ClusteringEnsemble Similarity to itself not zero: {0:f}".format( + result_value + ), + ) def test_ces(self, ens1, ens2): results, details = encore.ces([ens1, ens2]) - result_value = results[0,1] + result_value = results[0, 1] expected_value = 0.51 - assert_allclose(result_value, expected_value, rtol=0, atol=1.5e-2, - err_msg="Unexpected value for Cluster Ensemble Similarity: {0:f}. Expected {1:f}.".format(result_value, expected_value)) + assert_allclose( + result_value, + expected_value, + rtol=0, + atol=1.5e-2, + err_msg="Unexpected value for Cluster Ensemble Similarity: {0:f}. Expected {1:f}.".format( + result_value, expected_value + ), + ) def test_dres_to_self(self, ens1): results, details = encore.dres([ens1, ens1]) - result_value = results[0,1] - expected_value = 0. - assert_allclose(result_value, expected_value, rtol=0, atol=1.5e-2, - err_msg="Dim. Reduction Ensemble Similarity to itself not zero: {0:f}".format(result_value)) + result_value = results[0, 1] + expected_value = 0.0 + assert_allclose( + result_value, + expected_value, + rtol=0, + atol=1.5e-2, + err_msg="Dim. Reduction Ensemble Similarity to itself not zero: {0:f}".format( + result_value + ), + ) def test_dres(self, ens1, ens2): - results, details = encore.dres([ens1, ens2], select="name CA and resnum 1-10") - result_value = results[0,1] + results, details = encore.dres( + [ens1, ens2], select="name CA and resnum 1-10" + ) + result_value = results[0, 1] upper_bound = 0.6 - assert result_value < upper_bound, "Unexpected value for Dim. " \ - "reduction Ensemble Similarity: {0:f}. Expected {1:f}.".format(result_value, upper_bound) + assert result_value < upper_bound, ( + "Unexpected value for Dim. " + "reduction Ensemble Similarity: {0:f}. Expected {1:f}.".format( + result_value, upper_bound + ) + ) @pytest.mark.xfail # sporadically fails, see Issue #2158 def test_dres_without_superimposition(self, ens1, ens2): distance_matrix = encore.get_distance_matrix( - encore.merge_universes([ens1, ens2]), - superimpose=False) - results, details = encore.dres([ens1, ens2], - distance_matrix = distance_matrix) - result_value = results[0,1] + encore.merge_universes([ens1, ens2]), superimpose=False + ) + results, details = encore.dres( + [ens1, ens2], distance_matrix=distance_matrix + ) + result_value = results[0, 1] expected_value = 0.68 - assert_allclose(result_value, expected_value, rtol=0, atol=1.5e-1, - err_msg="Unexpected value for Dim. reduction Ensemble Similarity: {0:f}. Expected {1:f}.".format(result_value, expected_value)) + assert_allclose( + result_value, + expected_value, + rtol=0, + atol=1.5e-1, + err_msg="Unexpected value for Dim. reduction Ensemble Similarity: {0:f}. Expected {1:f}.".format( + result_value, expected_value + ), + ) def test_ces_convergence(self, ens1): - expected_values = [0.3443593, 0.1941854, 0.06857104, 0.] + expected_values = [0.3443593, 0.1941854, 0.06857104, 0.0] results = encore.ces_convergence(ens1, 5) - for i,ev in enumerate(expected_values): - assert_allclose(ev, results[i], rtol=0, atol=1.5e-2, - err_msg="Unexpected value for Clustering Ensemble similarity in convergence estimation") + for i, ev in enumerate(expected_values): + assert_allclose( + ev, + results[i], + rtol=0, + atol=1.5e-2, + err_msg="Unexpected value for Clustering Ensemble similarity in convergence estimation", + ) def test_dres_convergence(self, ens1): # Due to encore.dres_convergence() involving random numbers, the # following assertion is allowed to fail once. This significantly # reduces the probability of a random test failure. - expected_values = [0.3, 0.] + expected_values = [0.3, 0.0] results = encore.dres_convergence(ens1, 10) try: - assert_allclose(results[:,0], expected_values, rtol=0, atol=1.5e-1) + assert_allclose( + results[:, 0], expected_values, rtol=0, atol=1.5e-1 + ) except AssertionError: # Random test failure is very rare, but repeating the failed test # just once would only assert that the test passes with 50% # probability. To be a little safer, we raise a warning and repeat # the test 10 times: - warnings.warn(message="Test 'test_dres_convergence' failed, " - "repeating test 10 times.", - category=RuntimeWarning) + warnings.warn( + message="Test 'test_dres_convergence' failed, " + "repeating test 10 times.", + category=RuntimeWarning, + ) for i in range(10): results = encore.dres_convergence(ens1, 10) - assert_allclose(results[:,0], expected_values, rtol=0, atol=1.5e-1, - err_msg="Unexpected value for Dim. " - "reduction Ensemble similarity in " - "convergence estimation") + assert_allclose( + results[:, 0], + expected_values, + rtol=0, + atol=1.5e-1, + err_msg="Unexpected value for Dim. " + "reduction Ensemble similarity in " + "convergence estimation", + ) @pytest.mark.xfail # sporadically fails, see Issue #2158 def test_hes_error_estimation(self, ens1): expected_average = 10 expected_stdev = 12 - averages, stdevs = encore.hes([ens1, ens1], estimate_error = True, bootstrapping_samples=10, select="name CA and resnum 1-10") - average = averages[0,1] - stdev = stdevs[0,1] + averages, stdevs = encore.hes( + [ens1, ens1], + estimate_error=True, + bootstrapping_samples=10, + select="name CA and resnum 1-10", + ) + average = averages[0, 1] + stdev = stdevs[0, 1] err_msg = ( "Unexpected average value for bootstrapped samples in Harmonic" " Ensemble similarity" @@ -402,69 +566,87 @@ def test_hes_error_estimation(self, ens1): "Unexpected standard deviation for bootstrapped samples in" " Harmonic Ensemble similarity" ) - assert_allclose(average, expected_average, rtol=0, atol=1.5e2, err_msg=err_msg) - assert_allclose(stdev, expected_stdev, rtol=0, atol=1.5e2, err_msg=error_msg) + assert_allclose( + average, expected_average, rtol=0, atol=1.5e2, err_msg=err_msg + ) + assert_allclose( + stdev, expected_stdev, rtol=0, atol=1.5e2, err_msg=error_msg + ) def test_ces_error_estimation(self, ens1): expected_average = 0.03 expected_stdev = 0.31 - averages, stdevs = encore.ces([ens1, ens1], - estimate_error = True, - bootstrapping_samples=10, - clustering_method=encore.AffinityPropagationNative(preference=-2.0), - select="name CA and resnum 1-10") - average = averages[0,1] - stdev = stdevs[0,1] - - assert_allclose(average, expected_average, rtol=0, atol=1.5e-1, - err_msg="Unexpected average value for bootstrapped samples in Clustering Ensemble similarity") - assert_allclose(stdev, expected_stdev, rtol=0, atol=1.5, - err_msg="Unexpected standard deviation for bootstrapped samples in Clustering Ensemble similarity") + averages, stdevs = encore.ces( + [ens1, ens1], + estimate_error=True, + bootstrapping_samples=10, + clustering_method=encore.AffinityPropagationNative( + preference=-2.0 + ), + select="name CA and resnum 1-10", + ) + average = averages[0, 1] + stdev = stdevs[0, 1] + + assert_allclose( + average, + expected_average, + rtol=0, + atol=1.5e-1, + err_msg="Unexpected average value for bootstrapped samples in Clustering Ensemble similarity", + ) + assert_allclose( + stdev, + expected_stdev, + rtol=0, + atol=1.5, + err_msg="Unexpected standard deviation for bootstrapped samples in Clustering Ensemble similarity", + ) def test_ces_error_estimation_ensemble_bootstrap(self, ens1): # Error estimation using a method that does not take a distance # matrix as input, and therefore relies on bootstrapping the ensembles # instead - pytest.importorskip('sklearn') + pytest.importorskip("sklearn") expected_average = 0.03 expected_stdev = 0.02 - averages, stdevs = encore.ces([ens1, ens1], - estimate_error = True, - bootstrapping_samples=10, - clustering_method=encore.KMeans(n_clusters=2), - select="name CA and resnum 1-10") + averages, stdevs = encore.ces( + [ens1, ens1], + estimate_error=True, + bootstrapping_samples=10, + clustering_method=encore.KMeans(n_clusters=2), + select="name CA and resnum 1-10", + ) average = averages[0, 1] stdev = stdevs[0, 1] err_msg = ( "Unexpected average value for bootstrapped samples in" - " Clustering Ensemble similarity") + " Clustering Ensemble similarity" + ) assert_allclose( - average, - expected_average, - rtol = 0, - atol = 1.5e-1, - err_msg=err_msg) + average, expected_average, rtol=0, atol=1.5e-1, err_msg=err_msg + ) error_msg = ( "Unexpected standard deviation for bootstrapped samples in" " Clustering Ensemble similarity" - ) + ) assert_allclose( - stdev, - expected_stdev, - rtol=0, - atol=1.5e-1, - err_msg=error_msg) + stdev, expected_stdev, rtol=0, atol=1.5e-1, err_msg=error_msg + ) def test_dres_error_estimation(self, ens1): average_upper_bound = 0.3 stdev_upper_bound = 0.2 - averages, stdevs = encore.dres([ens1, ens1], estimate_error = True, - bootstrapping_samples=10, - select="name CA and resnum 1-10") - average = averages[0,1] - stdev = stdevs[0,1] + averages, stdevs = encore.dres( + [ens1, ens1], + estimate_error=True, + bootstrapping_samples=10, + select="name CA and resnum 1-10", + ) + average = averages[0, 1] + stdev = stdevs[0, 1] err_msg = ( "Unexpected average value for bootstrapped samples in Dim. " "reduction Ensemble similarity" @@ -478,141 +660,160 @@ def test_dres_error_estimation(self, ens1): class TestEncoreClustering(object): - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def ens1_template(self): template = mda.Universe(PSF, DCD) template.transfer_to_memory(step=5) return template - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def ens2_template(self): template = mda.Universe(PSF, DCD2) template.transfer_to_memory(step=5) return template - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def cc(self): return encore.ClusterCollection([1, 1, 1, 3, 3, 5, 5, 5]) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def cluster(self): return encore.Cluster(elem_list=np.array([0, 1, 2]), centroid=1) @pytest.fixture() def ens1(self, ens1_template): return mda.Universe( - ens1_template.filename, - ens1_template.trajectory.timeseries(order='fac'), - format=mda.coordinates.memory.MemoryReader) + ens1_template.filename, + ens1_template.trajectory.timeseries(order="fac"), + format=mda.coordinates.memory.MemoryReader, + ) @pytest.fixture() def ens2(self, ens2_template): return mda.Universe( ens2_template.filename, - ens2_template.trajectory.timeseries(order='fac'), - format=mda.coordinates.memory.MemoryReader) + ens2_template.trajectory.timeseries(order="fac"), + format=mda.coordinates.memory.MemoryReader, + ) def test_clustering_one_ensemble(self, ens1): cluster_collection = encore.cluster(ens1) expected_value = 7 - assert len(cluster_collection) == expected_value, "Unexpected " \ - "results: {0}".format(cluster_collection) + assert ( + len(cluster_collection) == expected_value + ), "Unexpected " "results: {0}".format(cluster_collection) def test_clustering_two_ensembles(self, ens1, ens2): cluster_collection = encore.cluster([ens1, ens2]) expected_value = 14 - assert len(cluster_collection) == expected_value, "Unexpected " \ - "results: {0}".format(cluster_collection) - - @pytest.mark.xfail(platform.machine() == "arm64" and platform.system() == "Darwin", - reason="see gh-3599") + assert ( + len(cluster_collection) == expected_value + ), "Unexpected " "results: {0}".format(cluster_collection) + + @pytest.mark.xfail( + platform.machine() == "arm64" and platform.system() == "Darwin", + reason="see gh-3599", + ) def test_clustering_three_ensembles_two_identical(self, ens1, ens2): cluster_collection = encore.cluster([ens1, ens2, ens1]) expected_value = 40 - assert len(cluster_collection) == expected_value, "Unexpected result:" \ - " {0}".format(cluster_collection) + assert ( + len(cluster_collection) == expected_value + ), "Unexpected result:" " {0}".format(cluster_collection) def test_clustering_two_methods(self, ens1): cluster_collection = encore.cluster( [ens1], - method=[encore.AffinityPropagationNative(), - encore.AffinityPropagationNative()]) - assert len(cluster_collection[0]) == len(cluster_collection[1]), \ - "Unexpected result: {0}".format(cluster_collection) + method=[ + encore.AffinityPropagationNative(), + encore.AffinityPropagationNative(), + ], + ) + assert len(cluster_collection[0]) == len( + cluster_collection[1] + ), "Unexpected result: {0}".format(cluster_collection) def test_clustering_AffinityPropagationNative_direct(self, ens1): method = encore.AffinityPropagationNative() distance_matrix = encore.get_distance_matrix(ens1) cluster_assignment = method(distance_matrix) expected_value = 7 - assert len(set(cluster_assignment)) == expected_value, \ - "Unexpected result: {0}".format(cluster_assignment) + assert ( + len(set(cluster_assignment)) == expected_value + ), "Unexpected result: {0}".format(cluster_assignment) def test_clustering_AffinityPropagation_direct(self, ens1): - pytest.importorskip('sklearn') + pytest.importorskip("sklearn") method = encore.AffinityPropagation(random_state=0) distance_matrix = encore.get_distance_matrix(ens1) cluster_assignment = method(distance_matrix) expected_value = 7 - assert len(set(cluster_assignment)) == expected_value, \ - "Unexpected result: {0}".format(cluster_assignment) + assert ( + len(set(cluster_assignment)) == expected_value + ), "Unexpected result: {0}".format(cluster_assignment) def test_clustering_KMeans_direct(self, ens1): - pytest.importorskip('sklearn') + pytest.importorskip("sklearn") clusters = 10 method = encore.KMeans(clusters) - coordinates = ens1.trajectory.timeseries(order='fac') - coordinates = np.reshape(coordinates, - (coordinates.shape[0], -1)) + coordinates = ens1.trajectory.timeseries(order="fac") + coordinates = np.reshape(coordinates, (coordinates.shape[0], -1)) cluster_assignment = method(coordinates) - assert len(set(cluster_assignment)) == clusters, \ - "Unexpected result: {0}".format(cluster_assignment) + assert ( + len(set(cluster_assignment)) == clusters + ), "Unexpected result: {0}".format(cluster_assignment) def test_clustering_DBSCAN_direct(self, ens1): - pytest.importorskip('sklearn') + pytest.importorskip("sklearn") method = encore.DBSCAN(eps=0.5, min_samples=2) distance_matrix = encore.get_distance_matrix(ens1) cluster_assignment = method(distance_matrix) expected_value = 2 - assert len(set(cluster_assignment)) == expected_value, \ - "Unexpected result: {0}".format(cluster_assignment) + assert ( + len(set(cluster_assignment)) == expected_value + ), "Unexpected result: {0}".format(cluster_assignment) def test_clustering_two_different_methods(self, ens1): - pytest.importorskip('sklearn') + pytest.importorskip("sklearn") cluster_collection = encore.cluster( [ens1], - method=[encore.AffinityPropagation(preference=-7.5, - random_state=0), - encore.DBSCAN(min_samples=2)]) - assert len(cluster_collection[0]) == len(cluster_collection[1]), \ - "Unexpected result: {0}".format(cluster_collection) + method=[ + encore.AffinityPropagation(preference=-7.5, random_state=0), + encore.DBSCAN(min_samples=2), + ], + ) + assert len(cluster_collection[0]) == len( + cluster_collection[1] + ), "Unexpected result: {0}".format(cluster_collection) def test_clustering_method_w_no_distance_matrix(self, ens1): - pytest.importorskip('sklearn') - cluster_collection = encore.cluster( - [ens1], - method=encore.KMeans(10)) - assert len(cluster_collection) == 10, \ - "Unexpected result: {0}".format(cluster_collection) + pytest.importorskip("sklearn") + cluster_collection = encore.cluster([ens1], method=encore.KMeans(10)) + assert len(cluster_collection) == 10, "Unexpected result: {0}".format( + cluster_collection + ) def test_clustering_two_methods_one_w_no_distance_matrix(self, ens1): - pytest.importorskip('sklearn') + pytest.importorskip("sklearn") cluster_collection = encore.cluster( [ens1], - method=[encore.KMeans(17), - encore.AffinityPropagationNative()]) - assert len(cluster_collection[0]) == len(cluster_collection[0]), \ - "Unexpected result: {0}".format(cluster_collection) + method=[encore.KMeans(17), encore.AffinityPropagationNative()], + ) + assert len(cluster_collection[0]) == len( + cluster_collection[0] + ), "Unexpected result: {0}".format(cluster_collection) def test_sklearn_affinity_propagation(self, ens1): - pytest.importorskip('sklearn') + pytest.importorskip("sklearn") cc1 = encore.cluster([ens1]) - cc2 = encore.cluster([ens1], - method=encore.AffinityPropagation(random_state=0)) - assert len(cc1) == len(cc2), \ - "Native and sklearn implementations of affinity "\ - "propagation don't agree: mismatch in number of "\ - "clusters: {0} {1}".format(len(cc1), len(cc2)) + cc2 = encore.cluster( + [ens1], method=encore.AffinityPropagation(random_state=0) + ) + assert len(cc1) == len(cc2), ( + "Native and sklearn implementations of affinity " + "propagation don't agree: mismatch in number of " + "clusters: {0} {1}".format(len(cc1), len(cc2)) + ) def test_ClusterCollection_init(self, cc): err_msg = "ClusterCollection was not constructed correctly" @@ -631,24 +832,31 @@ def test_ClusterCollection_get_ids(self, cc): assert_equal( cc.get_ids(), [0, 1, 2], - err_msg="ClusterCollection ids aren't as expected") + err_msg="ClusterCollection ids aren't as expected", + ) def test_ClusterCollection_get_centroids(self, cc): assert_equal( - cc.get_centroids(), [1, 3, 5], - err_msg="ClusterCollection centroids aren't as expected") + cc.get_centroids(), + [1, 3, 5], + err_msg="ClusterCollection centroids aren't as expected", + ) + def test_cluster_add_metadata(self, cluster): - metadata = cluster.elements*10 - cluster.add_metadata('test', metadata) + metadata = cluster.elements * 10 + cluster.add_metadata("test", metadata) assert_equal( - cluster.metadata['test'], + cluster.metadata["test"], metadata, - err_msg="Cluster metadata isn't as expected") + err_msg="Cluster metadata isn't as expected", + ) metadata = np.append(metadata, 9) - error_message = ("Size of metadata is not equal to the " - "number of cluster elements") + error_message = ( + "Size of metadata is not equal to the " + "number of cluster elements" + ) with pytest.raises(TypeError, match=error_message): - cluster.add_metadata('test2', metadata) + cluster.add_metadata("test2", metadata) def test_empty_cluster(self): empty_cluster = encore.Cluster() @@ -663,11 +871,14 @@ def test_centroid_not_in_elements(self): encore.Cluster([38, 39, 40, 41, 42, 43], 99) def test_metadata_size_error(self): - error_message = ('Size of metadata having label "label" is ' - 'not equal to the number of cluster elements') + error_message = ( + 'Size of metadata having label "label" is ' + "not equal to the number of cluster elements" + ) with pytest.raises(TypeError, match=error_message): - encore.Cluster(np.array([1, 1, 1]), 1, None, - {"label": [1, 1, 1, 1]}) + encore.Cluster( + np.array([1, 1, 1]), 1, None, {"label": [1, 1, 1, 1]} + ) def test_cluster_iteration(self, cluster): test = [] @@ -676,7 +887,7 @@ def test_cluster_iteration(self, cluster): assert_equal(cluster.elements, test) def test_cluster_len(self, cluster): - assert(cluster.size == len(cluster)) + assert cluster.size == len(cluster) def test_cluster_repr(self): repr_message = "" @@ -685,6 +896,7 @@ def test_cluster_repr(self): repr_message = "" assert_equal(repr(cluster), repr_message) + class TestEncoreClusteringSklearn(object): """The tests in this class were duplicated from the affinity propagation tests in scikit-learn""" @@ -693,75 +905,79 @@ class TestEncoreClusteringSklearn(object): @pytest.fixture() def distance_matrix(self): - X = np.array([[8.73101582, 8.85617874], - [11.61311169, 11.58774351], - [10.86083514, 11.06253959], - [9.45576027, 8.50606967], - [11.30441509, 11.04867001], - [8.63708065, 9.02077816], - [8.34792066, 9.1851129], - [11.06197897, 11.15126501], - [11.24563175, 9.36888267], - [10.83455241, 8.70101808], - [11.49211627, 11.48095194], - [10.6448857, 10.20768141], - [10.491806, 9.38775868], - [11.08330999, 9.39065561], - [10.83872922, 9.48897803], - [11.37890079, 8.93799596], - [11.70562094, 11.16006288], - [10.95871246, 11.1642394], - [11.59763163, 10.91793669], - [11.05761743, 11.5817094], - [8.35444086, 8.91490389], - [8.79613913, 8.82477028], - [11.00420001, 9.7143482], - [11.90790185, 10.41825373], - [11.39149519, 11.89635728], - [8.31749192, 9.78031016], - [11.59530088, 9.75835567], - [11.17754529, 11.13346973], - [11.01830341, 10.92512646], - [11.75326028, 8.46089638], - [11.74702358, 9.36241786], - [10.53075064, 9.77744847], - [8.67474149, 8.30948696], - [11.05076484, 9.16079575], - [8.79567794, 8.52774713], - [11.18626498, 8.38550253], - [10.57169895, 9.42178069], - [8.65168114, 8.76846013], - [11.12522708, 10.6583617], - [8.87537899, 9.02246614], - [9.29163622, 9.05159316], - [11.38003537, 10.93945712], - [8.74627116, 8.85490353], - [10.65550973, 9.76402598], - [8.49888186, 9.31099614], - [8.64181338, 9.154761], - [10.84506927, 10.8790789], - [8.98872711, 9.17133275], - [11.7470232, 10.60908885], - [10.89279865, 9.32098256], - [11.14254656, 9.28262927], - [9.02660689, 9.12098876], - [9.16093666, 8.72607596], - [11.47151183, 8.92803007], - [11.76917681, 9.59220592], - [9.97880407, 11.26144744], - [8.58057881, 8.43199283], - [10.53394006, 9.36033059], - [11.34577448, 10.70313399], - [9.07097046, 8.83928763]]) - - XX = np.einsum('ij,ij->i', X, X)[:, np.newaxis] + X = np.array( + [ + [8.73101582, 8.85617874], + [11.61311169, 11.58774351], + [10.86083514, 11.06253959], + [9.45576027, 8.50606967], + [11.30441509, 11.04867001], + [8.63708065, 9.02077816], + [8.34792066, 9.1851129], + [11.06197897, 11.15126501], + [11.24563175, 9.36888267], + [10.83455241, 8.70101808], + [11.49211627, 11.48095194], + [10.6448857, 10.20768141], + [10.491806, 9.38775868], + [11.08330999, 9.39065561], + [10.83872922, 9.48897803], + [11.37890079, 8.93799596], + [11.70562094, 11.16006288], + [10.95871246, 11.1642394], + [11.59763163, 10.91793669], + [11.05761743, 11.5817094], + [8.35444086, 8.91490389], + [8.79613913, 8.82477028], + [11.00420001, 9.7143482], + [11.90790185, 10.41825373], + [11.39149519, 11.89635728], + [8.31749192, 9.78031016], + [11.59530088, 9.75835567], + [11.17754529, 11.13346973], + [11.01830341, 10.92512646], + [11.75326028, 8.46089638], + [11.74702358, 9.36241786], + [10.53075064, 9.77744847], + [8.67474149, 8.30948696], + [11.05076484, 9.16079575], + [8.79567794, 8.52774713], + [11.18626498, 8.38550253], + [10.57169895, 9.42178069], + [8.65168114, 8.76846013], + [11.12522708, 10.6583617], + [8.87537899, 9.02246614], + [9.29163622, 9.05159316], + [11.38003537, 10.93945712], + [8.74627116, 8.85490353], + [10.65550973, 9.76402598], + [8.49888186, 9.31099614], + [8.64181338, 9.154761], + [10.84506927, 10.8790789], + [8.98872711, 9.17133275], + [11.7470232, 10.60908885], + [10.89279865, 9.32098256], + [11.14254656, 9.28262927], + [9.02660689, 9.12098876], + [9.16093666, 8.72607596], + [11.47151183, 8.92803007], + [11.76917681, 9.59220592], + [9.97880407, 11.26144744], + [8.58057881, 8.43199283], + [10.53394006, 9.36033059], + [11.34577448, 10.70313399], + [9.07097046, 8.83928763], + ] + ) + + XX = np.einsum("ij,ij->i", X, X)[:, np.newaxis] YY = XX.T distances = np.dot(X, X.T) distances *= -2 distances += XX distances += YY np.maximum(distances, 0, out=distances) - distances.flat[::distances.shape[0] + 1] = 0.0 + distances.flat[:: distances.shape[0] + 1] = 0.0 dimension = len(distances) distance_matrix = encore.utils.TriangularMatrix(len(distances)) @@ -771,24 +987,27 @@ def distance_matrix(self): return distance_matrix def test_one(self, distance_matrix): - preference = -float(np.median(distance_matrix.as_array()) * 10.) - clustering_method = encore.AffinityPropagationNative(preference=preference) - ccs = encore.cluster(None, - distance_matrix=distance_matrix, - method=clustering_method) - assert self.n_clusters == len(ccs), \ - "Basic clustering test failed to give the right"\ - "number of clusters: {0} vs {1}".format(self.n_clusters, len(ccs)) + preference = -float(np.median(distance_matrix.as_array()) * 10.0) + clustering_method = encore.AffinityPropagationNative( + preference=preference + ) + ccs = encore.cluster( + None, distance_matrix=distance_matrix, method=clustering_method + ) + assert self.n_clusters == len(ccs), ( + "Basic clustering test failed to give the right" + "number of clusters: {0} vs {1}".format(self.n_clusters, len(ccs)) + ) class TestEncoreDimensionalityReduction(object): - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def ens1_template(self): template = mda.Universe(PSF, DCD) template.transfer_to_memory(step=5) return template - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def ens2_template(self): template = mda.Universe(PSF, DCD2) template.transfer_to_memory(step=5) @@ -798,126 +1017,168 @@ def ens2_template(self): def ens1(self, ens1_template): return mda.Universe( ens1_template.filename, - ens1_template.trajectory.timeseries(order='fac'), - format=mda.coordinates.memory.MemoryReader) + ens1_template.trajectory.timeseries(order="fac"), + format=mda.coordinates.memory.MemoryReader, + ) @pytest.fixture() def ens2(self, ens2_template): return mda.Universe( ens2_template.filename, - ens2_template.trajectory.timeseries(order='fac'), - format=mda.coordinates.memory.MemoryReader) + ens2_template.trajectory.timeseries(order="fac"), + format=mda.coordinates.memory.MemoryReader, + ) def test_dimensionality_reduction_one_ensemble(self, ens1): dimension = 2 coordinates, details = encore.reduce_dimensionality(ens1) - assert_equal(coordinates.shape[0], dimension, - err_msg="Unexpected result in dimensionality reduction: {0}".format(coordinates)) + assert_equal( + coordinates.shape[0], + dimension, + err_msg="Unexpected result in dimensionality reduction: {0}".format( + coordinates + ), + ) def test_dimensionality_reduction_two_ensembles(self, ens1, ens2): dimension = 2 - coordinates, details = \ - encore.reduce_dimensionality([ens1, ens2]) - assert_equal(coordinates.shape[0], dimension, - err_msg="Unexpected result in dimensionality reduction: {0}".format(coordinates)) - - - def test_dimensionality_reduction_three_ensembles_two_identical(self, - ens1, ens2): - coordinates, details = \ - encore.reduce_dimensionality([ens1, ens2, ens1]) - coordinates_ens1 = coordinates[:,np.where(details["ensemble_membership"]==1)] - coordinates_ens3 = coordinates[:,np.where(details["ensemble_membership"]==3)] - assert_allclose(coordinates_ens1, coordinates_ens3, rtol=0, atol=1.5, - err_msg="Unexpected result in dimensionality reduction: {0}".format(coordinates)) + coordinates, details = encore.reduce_dimensionality([ens1, ens2]) + assert_equal( + coordinates.shape[0], + dimension, + err_msg="Unexpected result in dimensionality reduction: {0}".format( + coordinates + ), + ) + def test_dimensionality_reduction_three_ensembles_two_identical( + self, ens1, ens2 + ): + coordinates, details = encore.reduce_dimensionality([ens1, ens2, ens1]) + coordinates_ens1 = coordinates[ + :, np.where(details["ensemble_membership"] == 1) + ] + coordinates_ens3 = coordinates[ + :, np.where(details["ensemble_membership"] == 3) + ] + assert_allclose( + coordinates_ens1, + coordinates_ens3, + rtol=0, + atol=1.5, + err_msg="Unexpected result in dimensionality reduction: {0}".format( + coordinates + ), + ) def test_dimensionality_reduction_specified_dimension(self, ens1, ens2): dimension = 3 coordinates, details = encore.reduce_dimensionality( [ens1, ens2], - method=encore.StochasticProximityEmbeddingNative(dimension=dimension)) - assert_equal(coordinates.shape[0], dimension, - err_msg="Unexpected result in dimensionality reduction: {0}".format(coordinates)) - + method=encore.StochasticProximityEmbeddingNative( + dimension=dimension + ), + ) + assert_equal( + coordinates.shape[0], + dimension, + err_msg="Unexpected result in dimensionality reduction: {0}".format( + coordinates + ), + ) def test_dimensionality_reduction_SPENative_direct(self, ens1): dimension = 2 method = encore.StochasticProximityEmbeddingNative(dimension=dimension) distance_matrix = encore.get_distance_matrix(ens1) coordinates, details = method(distance_matrix) - assert_equal(coordinates.shape[0], dimension, - err_msg="Unexpected result in dimensionality reduction: {0}".format( - coordinates)) + assert_equal( + coordinates.shape[0], + dimension, + err_msg="Unexpected result in dimensionality reduction: {0}".format( + coordinates + ), + ) def test_dimensionality_reduction_PCA_direct(self, ens1): - pytest.importorskip('sklearn') + pytest.importorskip("sklearn") dimension = 2 method = encore.PrincipalComponentAnalysis(dimension=dimension) - coordinates = ens1.trajectory.timeseries(order='fac') - coordinates = np.reshape(coordinates, - (coordinates.shape[0], -1)) + coordinates = ens1.trajectory.timeseries(order="fac") + coordinates = np.reshape(coordinates, (coordinates.shape[0], -1)) coordinates, details = method(coordinates) - assert_equal(coordinates.shape[0], dimension, - err_msg="Unexpected result in dimensionality reduction: {0}".format( - coordinates)) - + assert_equal( + coordinates.shape[0], + dimension, + err_msg="Unexpected result in dimensionality reduction: {0}".format( + coordinates + ), + ) def test_dimensionality_reduction_different_method(self, ens1, ens2): - pytest.importorskip('sklearn') + pytest.importorskip("sklearn") dimension = 3 - coordinates, details = \ - encore.reduce_dimensionality( - [ens1, ens2], - method=encore.PrincipalComponentAnalysis(dimension=dimension)) - assert_equal(coordinates.shape[0], dimension, - err_msg="Unexpected result in dimensionality reduction: {0}".format(coordinates)) - + coordinates, details = encore.reduce_dimensionality( + [ens1, ens2], + method=encore.PrincipalComponentAnalysis(dimension=dimension), + ) + assert_equal( + coordinates.shape[0], + dimension, + err_msg="Unexpected result in dimensionality reduction: {0}".format( + coordinates + ), + ) def test_dimensionality_reduction_two_methods(self, ens1, ens2): - dims = [2,3] - coordinates, details = \ - encore.reduce_dimensionality( - [ens1, ens2], - method=[encore.StochasticProximityEmbeddingNative(dims[0]), - encore.StochasticProximityEmbeddingNative(dims[1])]) + dims = [2, 3] + coordinates, details = encore.reduce_dimensionality( + [ens1, ens2], + method=[ + encore.StochasticProximityEmbeddingNative(dims[0]), + encore.StochasticProximityEmbeddingNative(dims[1]), + ], + ) assert_equal(coordinates[1].shape[0], dims[1]) def test_dimensionality_reduction_two_different_methods(self, ens1, ens2): - pytest.importorskip('sklearn') - dims = [2,3] - coordinates, details = \ - encore.reduce_dimensionality( - [ens1, ens2], - method=[encore.StochasticProximityEmbeddingNative(dims[0]), - encore.PrincipalComponentAnalysis(dims[1])]) + pytest.importorskip("sklearn") + dims = [2, 3] + coordinates, details = encore.reduce_dimensionality( + [ens1, ens2], + method=[ + encore.StochasticProximityEmbeddingNative(dims[0]), + encore.PrincipalComponentAnalysis(dims[1]), + ], + ) assert_equal(coordinates[1].shape[0], dims[1]) class TestEncoreConfDistMatrix(object): def test_get_distance_matrix(self): # Issue #1324 - u = mda.Universe(TPR,XTC) + u = mda.Universe(TPR, XTC) dm = confdistmatrix.get_distance_matrix(u) + class TestEncoreImportWarnings(object): - @block_import('sklearn') + @block_import("sklearn") def _check_sklearn_import_warns(self, package, recwarn): for mod in list(sys.modules): # list as we're changing as we iterate - if 'encore' in mod: + if "encore" in mod: sys.modules.pop(mod, None) - warnings.simplefilter('always') + warnings.simplefilter("always") # assert_warns(ImportWarning, importlib.import_module, package) importlib.import_module(package) assert recwarn.pop(ImportWarning) def test_import_warnings(self, recwarn): for mod in list(sys.modules): # list as we're changing as we iterate - if 'encore' in mod: + if "encore" in mod: sys.modules.pop(mod, None) for pkg in ( - 'MDAnalysis.analysis.encore.dimensionality_reduction.DimensionalityReductionMethod', - 'MDAnalysis.analysis.encore.clustering.ClusteringMethod', + "MDAnalysis.analysis.encore.dimensionality_reduction.DimensionalityReductionMethod", + "MDAnalysis.analysis.encore.clustering.ClusteringMethod", ): self._check_sklearn_import_warns(pkg, recwarn) # This is a quickfix! Convert this to a parametrize call in future. diff --git a/testsuite/MDAnalysisTests/analysis/test_gnm.py b/testsuite/MDAnalysisTests/analysis/test_gnm.py index e69ac7056fe..0283ca66b6b 100644 --- a/testsuite/MDAnalysisTests/analysis/test_gnm.py +++ b/testsuite/MDAnalysisTests/analysis/test_gnm.py @@ -25,10 +25,9 @@ import MDAnalysis as mda import MDAnalysis.analysis.gnm - -from numpy.testing import assert_almost_equal import numpy as np import pytest +from numpy.testing import assert_almost_equal from MDAnalysisTests.datafiles import GRO, XTC @@ -39,16 +38,27 @@ def universe(): def test_gnm(universe, tmpdir, client_GNMAnalysis): - output = os.path.join(str(tmpdir), 'output.txt') + output = os.path.join(str(tmpdir), "output.txt") gnm = mda.analysis.gnm.GNMAnalysis(universe, ReportVector=output) gnm.run(**client_GNMAnalysis) result = gnm.results assert len(result.times) == 10 assert_almost_equal(gnm.results.times, np.arange(0, 1000, 100), decimal=4) - assert_almost_equal(gnm.results.eigenvalues, - [2.0287113e-15, 4.1471575e-15, 1.8539533e-15, 4.3810359e-15, - 3.9607304e-15, 4.1289113e-15, 2.5501084e-15, 4.0498182e-15, - 4.2058769e-15, 3.9839431e-15]) + assert_almost_equal( + gnm.results.eigenvalues, + [ + 2.0287113e-15, + 4.1471575e-15, + 1.8539533e-15, + 4.3810359e-15, + 3.9607304e-15, + 4.1289113e-15, + 2.5501084e-15, + 4.0498182e-15, + 4.2058769e-15, + 3.9839431e-15, + ], + ) def test_gnm_run_step(universe, client_GNMAnalysis): @@ -57,26 +67,34 @@ def test_gnm_run_step(universe, client_GNMAnalysis): result = gnm.results assert len(result.times) == 4 assert_almost_equal(gnm.results.times, np.arange(0, 1200, 300), decimal=4) - assert_almost_equal(gnm.results.eigenvalues, - [2.0287113e-15, 4.3810359e-15, 2.5501084e-15, 3.9839431e-15]) + assert_almost_equal( + gnm.results.eigenvalues, + [2.0287113e-15, 4.3810359e-15, 2.5501084e-15, 3.9839431e-15], + ) def test_generate_kirchoff(universe): gnm = mda.analysis.gnm.GNMAnalysis(universe) gen = gnm.generate_kirchoff() - assert_almost_equal(gen[0], - [7,-1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0,-1,-1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + # fmt: off + assert_almost_equal( + gen[0], + [ + 7,-1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0,-1,-1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ) + # fmt: on def test_gnm_SVD_fail(universe): @@ -93,25 +111,36 @@ def test_closeContactGNMAnalysis(universe, client_GNMAnalysis): gnm.run(stop=2, **client_GNMAnalysis) result = gnm.results assert len(result.times) == 2 - assert_almost_equal(gnm.results.times, (0, 100), decimal=4) - assert_almost_equal(gnm.results.eigenvalues, [0.1502614, 0.1426407]) + assert_almost_equal(gnm.results.times, (0, 100), decimal=4) + assert_almost_equal(gnm.results.eigenvalues, [0.1502614, 0.1426407]) gen = gnm.generate_kirchoff() - assert_almost_equal(gen[0], - [16.326744128018923, -2.716098853586913, -1.94736842105263, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, -0.05263157894736842, 0.0, 0.0, 0.0, -3.3541953679557905, 0.0, -1.4210526315789465, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, -1.0423368771244421, -1.3006649542861801, -0.30779350562554625, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.927172649945531, -0.7509392614826383, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, -2.263157894736841, -0.24333213169614382]) + # fmt: off + assert_almost_equal( + gen[0], + [ + 16.326744128018923, -2.716098853586913, -1.94736842105263, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, -0.05263157894736842, 0.0, 0.0, 0.0, -3.3541953679557905, + 0.0, -1.4210526315789465, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -1.0423368771244421, -1.3006649542861801, + -0.30779350562554625, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + -0.927172649945531, -0.7509392614826383, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + -2.263157894736841, -0.24333213169614382 + ] + ) + # fmt: on def test_closeContactGNMAnalysis_weights_None(universe, client_GNMAnalysis): @@ -120,17 +149,29 @@ def test_closeContactGNMAnalysis_weights_None(universe, client_GNMAnalysis): result = gnm.results assert len(result.times) == 2 assert_almost_equal(gnm.results.times, (0, 100), decimal=4) - assert_almost_equal(gnm.results.eigenvalues, [2.4328739, 2.2967251]) + assert_almost_equal(gnm.results.eigenvalues, [2.4328739, 2.2967251]) gen = gnm.generate_kirchoff() - assert_almost_equal(gen[0], - [303.0, -58.0, -37.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, - 0.0, 0.0, 0.0, -67.0, 0.0, -27.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -17.0, -15.0, - -6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, -14.0, -15.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -43.0, -3.0]) + # fmt: off + assert_almost_equal( + gen[0], + [ + 303.0, -58.0, -37.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0,0.0, 0.0, 0.0, -67.0, 0.0, + -27.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, -17.0, -15.0, -6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, -14.0, -15.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -43.0, -3.0 + ] + ) + # fmt: on diff --git a/testsuite/MDAnalysisTests/analysis/test_helix_analysis.py b/testsuite/MDAnalysisTests/analysis/test_helix_analysis.py index dbde56fde4e..3a291050d46 100644 --- a/testsuite/MDAnalysisTests/analysis/test_helix_analysis.py +++ b/testsuite/MDAnalysisTests/analysis/test_helix_analysis.py @@ -28,10 +28,16 @@ import MDAnalysis as mda from MDAnalysis.analysis import helix_analysis as hel -from MDAnalysisTests.datafiles import (GRO, XTC, PSF, DCD, PDB_small, - HELANAL_BENDING_MATRIX, - HELANAL_BENDING_MATRIX_SUBSET, - XYZ) +from MDAnalysisTests.datafiles import ( + GRO, + XTC, + PSF, + DCD, + PDB_small, + HELANAL_BENDING_MATRIX, + HELANAL_BENDING_MATRIX_SUBSET, + XYZ, +) # reference data from old helix analysis of a single PDB file: # data = MDAnalysis.analysis.helanal.helanal_main(PDB_small, @@ -39,28 +45,69 @@ # keys are renamed and local screw angles now use a different # algorithm HELANAL_SINGLE_DATA = { - 'global_tilts': np.rad2deg(1.3309656332019535), - 'local_heights summary': np.array([1.5286051, 0.19648294, 0.11384312], - dtype=np.float32), - 'local_bends': - np.array([3.44526005, 4.85425806, 4.69548464, 2.39473653, - 3.56172442, 3.97128344, 3.41563916, 1.86140978, - 5.22997046, 5.41381264, 27.49601364, 39.69839478, - 35.05921936, 21.78928566, 9.8632431, 8.80066967, - 5.5344553, 6.14356709, 10.15450764, 11.07686138, - 9.23541832], dtype=np.float32), - 'local_nres_per_turn summary': np.array([3.64864163, 0.152694, - 0.1131402]), - 'local_twists summary': np.array([98.83011627, 4.08171701, 3.07253003], - dtype=np.float32), - 'local_twists': - np.array([97.23709869, 99.09676361, 97.25350952, 101.76019287, - 100.42689514, 97.08784485, 97.07430267, 98.33553314, - 97.86578369, 95.45792389, 97.10089111, 95.26415253, - 87.93136597, 108.38458252, 95.27167511, 104.01931763, - 100.7199707, 101.48034668, 99.64170074, 94.78946686, - 102.50147247, 97.25154877, 104.54204559, 101.42829895], - dtype=np.float32), + "global_tilts": np.rad2deg(1.3309656332019535), + "local_heights summary": np.array( + [1.5286051, 0.19648294, 0.11384312], dtype=np.float32 + ), + "local_bends": np.array( + [ + 3.44526005, + 4.85425806, + 4.69548464, + 2.39473653, + 3.56172442, + 3.97128344, + 3.41563916, + 1.86140978, + 5.22997046, + 5.41381264, + 27.49601364, + 39.69839478, + 35.05921936, + 21.78928566, + 9.8632431, + 8.80066967, + 5.5344553, + 6.14356709, + 10.15450764, + 11.07686138, + 9.23541832, + ], + dtype=np.float32, + ), + "local_nres_per_turn summary": np.array([3.64864163, 0.152694, 0.1131402]), + "local_twists summary": np.array( + [98.83011627, 4.08171701, 3.07253003], dtype=np.float32 + ), + "local_twists": np.array( + [ + 97.23709869, + 99.09676361, + 97.25350952, + 101.76019287, + 100.42689514, + 97.08784485, + 97.07430267, + 98.33553314, + 97.86578369, + 95.45792389, + 97.10089111, + 95.26415253, + 87.93136597, + 108.38458252, + 95.27167511, + 104.01931763, + 100.7199707, + 101.48034668, + 99.64170074, + 94.78946686, + 102.50147247, + 97.25154877, + 104.54204559, + 101.42829895, + ], + dtype=np.float32, + ), } @@ -113,8 +160,9 @@ def test_local_screw_angles_plane_circle(): """ angdeg = np.arange(0, 360, 12, dtype=np.int32) angrad = np.deg2rad(angdeg, dtype=np.float64) - xyz = np.array([[np.cos(a), np.sin(a), 0] for a in angrad], - dtype=np.float64) + xyz = np.array( + [[np.cos(a), np.sin(a), 0] for a in angrad], dtype=np.float64 + ) xyz[15, 1] = 0 # because np.sin(np.deg2rad(180)) = 1e-16 ?! screw = hel.local_screw_angles([1, 0, 0], [0, 1, 0], xyz) correct = np.zeros_like(angdeg) @@ -130,8 +178,9 @@ def test_local_screw_angles_ortho_circle(): """ angdeg = np.arange(0, 360, 12, dtype=np.int32) angrad = np.deg2rad(angdeg, dtype=np.float64) - xyz = np.array([[np.cos(a), np.sin(a), 0] for a in angrad], - dtype=np.float64) + xyz = np.array( + [[np.cos(a), np.sin(a), 0] for a in angrad], dtype=np.float64 + ) xyz[15, 1] = 0 # because np.sin(np.deg2rad(180)) = 1e-16 ?! screw = hel.local_screw_angles([1, 0, 0], [0, 0, 1], xyz) correct = np.zeros_like(angdeg) @@ -197,57 +246,70 @@ def zigzag(): x x x x x """ n_atoms = 100 - u = mda.Universe.empty(100, atom_resindex=np.arange(n_atoms), - trajectory=True) - xyz = np.array(list(zip([1, -1]*(n_atoms//2), # x \in {0, 1} - [0]*n_atoms, # y == 0 - range(n_atoms)))) # z rises continuously + u = mda.Universe.empty( + 100, atom_resindex=np.arange(n_atoms), trajectory=True + ) + xyz = np.array( + list( + zip( + [1, -1] * (n_atoms // 2), # x \in {0, 1} + [0] * n_atoms, # y == 0 + range(n_atoms), + ) + ) + ) # z rises continuously u.load_new(xyz) return u -@pytest.mark.parametrize('ref_axis,screw_angles', [ - # input vectors zigzag between [-1, 0, 0] and [1, 0, 0] - # global axis is z-axis - ([0, 0, 1], [180, 0]), # calculated to x-z plane - ([1, 0, 0], [180, 0]), # calculated to x-z plane - ([-1, 0, 0], [0, 180]), # calculated to x-z plane upside down - ([0, 1, 0], [90, -90]), # calculated to y-z plane - ([0, -1, 0], [-90, 90]), # calculated to x-z plane upside down - # calculated to diagonal xy-z plane rotating around - ([1, 1, 0], [135, -45]), - ([-1, 1, 0], [45, -135]), - ([1, -1, 0], [-135, 45]), - ([-1, -1, 0], [-45, 135]), - # calculated to diagonal xyz-z plane w/o z contribution - ([1, 1, 1], [135, -45]), - ([1, 1, -1], [135, -45]), - ([1, -1, 1], [-135, 45]), - ([-1, 1, 1], [45, -135]), - ([-1, -1, 1], [-45, 135]), - ([-1, -1, -1], [-45, 135]), -]) +@pytest.mark.parametrize( + "ref_axis,screw_angles", + [ + # input vectors zigzag between [-1, 0, 0] and [1, 0, 0] + # global axis is z-axis + ([0, 0, 1], [180, 0]), # calculated to x-z plane + ([1, 0, 0], [180, 0]), # calculated to x-z plane + ([-1, 0, 0], [0, 180]), # calculated to x-z plane upside down + ([0, 1, 0], [90, -90]), # calculated to y-z plane + ([0, -1, 0], [-90, 90]), # calculated to x-z plane upside down + # calculated to diagonal xy-z plane rotating around + ([1, 1, 0], [135, -45]), + ([-1, 1, 0], [45, -135]), + ([1, -1, 0], [-135, 45]), + ([-1, -1, 0], [-45, 135]), + # calculated to diagonal xyz-z plane w/o z contribution + ([1, 1, 1], [135, -45]), + ([1, 1, -1], [135, -45]), + ([1, -1, 1], [-135, 45]), + ([-1, 1, 1], [45, -135]), + ([-1, -1, 1], [-45, 135]), + ([-1, -1, -1], [-45, 135]), + ], +) def test_helix_analysis_zigzag(zigzag, ref_axis, screw_angles): - properties = hel.helix_analysis(zigzag.atoms.positions, - ref_axis=ref_axis) - assert_almost_equal(properties['local_twists'], 180, decimal=4) - assert_almost_equal(properties['local_nres_per_turn'], 2, decimal=4) - assert_almost_equal(properties['global_axis'], - [0, 0, -1], decimal=4) + properties = hel.helix_analysis(zigzag.atoms.positions, ref_axis=ref_axis) + assert_almost_equal(properties["local_twists"], 180, decimal=4) + assert_almost_equal(properties["local_nres_per_turn"], 2, decimal=4) + assert_almost_equal(properties["global_axis"], [0, 0, -1], decimal=4) # all 0 vectors - assert_almost_equal(properties['local_axes'], 0, decimal=4) - assert_almost_equal(properties['local_bends'], 0, decimal=4) - assert_almost_equal(properties['all_bends'], 0, decimal=4) - assert_almost_equal(properties['local_heights'], 0, decimal=4) - assert_almost_equal(properties['local_helix_directions'][0::2], - [[-1, 0, 0]]*49, decimal=4) - assert_almost_equal(properties['local_helix_directions'][1::2], - [[1, 0, 0]]*49, decimal=4) + assert_almost_equal(properties["local_axes"], 0, decimal=4) + assert_almost_equal(properties["local_bends"], 0, decimal=4) + assert_almost_equal(properties["all_bends"], 0, decimal=4) + assert_almost_equal(properties["local_heights"], 0, decimal=4) + assert_almost_equal( + properties["local_helix_directions"][0::2], + [[-1, 0, 0]] * 49, + decimal=4, + ) + assert_almost_equal( + properties["local_helix_directions"][1::2], [[1, 0, 0]] * 49, decimal=4 + ) origins = zigzag.atoms.positions[1:-1].copy() origins[:, 0] = 0 - assert_almost_equal(properties['local_origins'], origins, decimal=4) - assert_almost_equal(properties['local_screw_angles'], - screw_angles*49, decimal=4) + assert_almost_equal(properties["local_origins"], origins, decimal=4) + assert_almost_equal( + properties["local_screw_angles"], screw_angles * 49, decimal=4 + ) def square_oct(n_rep=10): @@ -258,13 +320,20 @@ def square_oct(n_rep=10): x-coordinates increase continuously. """ # square-octagon-square-octagon - sq2 = 0.5 ** 0.5 + sq2 = 0.5**0.5 square = [(1, 0), (0, 1), (-1, 0), (0, -1)] - octagon = [(1, 0), (sq2, sq2), (0, 1), (-sq2, sq2), - (-1, 0), (-sq2, -sq2), (0, -1), (sq2, -sq2)] - shapes = (square+octagon)*n_rep - xyz = np.array(list(zip(np.arange(len(shapes)), - *zip(*shapes)))) + octagon = [ + (1, 0), + (sq2, sq2), + (0, 1), + (-sq2, sq2), + (-1, 0), + (-sq2, -sq2), + (0, -1), + (sq2, -sq2), + ] + shapes = (square + octagon) * n_rep + xyz = np.array(list(zip(np.arange(len(shapes)), *zip(*shapes)))) n_atoms = len(xyz) u = mda.Universe.empty(n_atoms, trajectory=True) u.load_new(xyz) @@ -276,64 +345,82 @@ def test_helix_analysis_square_oct(): u = square_oct(n_rep=n_rep) n_atoms = len(u.atoms) - properties = hel.helix_analysis(u.atoms.positions, - ref_axis=[0, 0, 1]) + properties = hel.helix_analysis(u.atoms.positions, ref_axis=[0, 0, 1]) twist_trans = [102.76438, 32.235607] - twists = ([90]*2 + twist_trans + [45]*6 + twist_trans[::-1]) * n_rep - assert_almost_equal(properties['local_twists'], twists[:n_atoms-3], - decimal=4) + twists = ([90] * 2 + twist_trans + [45] * 6 + twist_trans[::-1]) * n_rep + assert_almost_equal( + properties["local_twists"], twists[: n_atoms - 3], decimal=4 + ) res_trans = [3.503159, 11.167775] - res = ([4]*2 + res_trans + [8]*6 + res_trans[::-1]) * n_rep - assert_almost_equal(properties['local_nres_per_turn'], res[:n_atoms-3], - decimal=4) - assert_almost_equal(properties['global_axis'], - [-1, 0, 0], decimal=3) - assert_almost_equal(properties['local_axes']-[1, 0, 0], 0, decimal=4) - assert_almost_equal(properties['local_bends'], 0, decimal=4) - assert_almost_equal(properties['all_bends'], 0, decimal=4) - assert_almost_equal(properties['local_heights'], 1, decimal=4) - - loc_rot = [[0., 0., 1.], - [0., -1., 0.], - [0., 0., -1.], - [0., 0.97528684, 0.2209424], # the transition square->oct - [0., 0.70710677, 0.70710677], # 0.5 ** 0.5 - [0., 0., 1.], - [0., -0.70710677, 0.70710677], - [0., -1., 0.], - [0., -0.70710677, -0.70710677], - [0., 0., -1.], - [0., 0.70710677, -0.70710677], - [0., 0.97528684, -0.2209424]] * n_rep - assert_almost_equal(properties['local_helix_directions'], - loc_rot[:n_atoms-2], decimal=4) + res = ([4] * 2 + res_trans + [8] * 6 + res_trans[::-1]) * n_rep + assert_almost_equal( + properties["local_nres_per_turn"], res[: n_atoms - 3], decimal=4 + ) + assert_almost_equal(properties["global_axis"], [-1, 0, 0], decimal=3) + assert_almost_equal(properties["local_axes"] - [1, 0, 0], 0, decimal=4) + assert_almost_equal(properties["local_bends"], 0, decimal=4) + assert_almost_equal(properties["all_bends"], 0, decimal=4) + assert_almost_equal(properties["local_heights"], 1, decimal=4) + + loc_rot = [ + [0.0, 0.0, 1.0], + [0.0, -1.0, 0.0], + [0.0, 0.0, -1.0], + [0.0, 0.97528684, 0.2209424], # the transition square->oct + [0.0, 0.70710677, 0.70710677], # 0.5 ** 0.5 + [0.0, 0.0, 1.0], + [0.0, -0.70710677, 0.70710677], + [0.0, -1.0, 0.0], + [0.0, -0.70710677, -0.70710677], + [0.0, 0.0, -1.0], + [0.0, 0.70710677, -0.70710677], + [0.0, 0.97528684, -0.2209424], + ] * n_rep + assert_almost_equal( + properties["local_helix_directions"], loc_rot[: n_atoms - 2], decimal=4 + ) origins = u.atoms.positions.copy()[1:-1] - origins[:, 1:] = ([[0., 0.], # square - [0., 0.], - [0., -0.33318555], # square -> octagon - [-1.7878988, -0.6315732], # square -> octagon - [0., 0.], # octagon - [0., 0.], - [0., 0.], - [0., 0.], - [0., 0.], - [0., 0.], - [-1.3141878, 1.3141878], # octagon -> square - [0.34966463, 0.14732757]]*n_rep)[:len(origins)] - assert_almost_equal(properties['local_origins'], origins, - decimal=4) + origins[:, 1:] = ( + [ + [0.0, 0.0], # square + [0.0, 0.0], + [0.0, -0.33318555], # square -> octagon + [-1.7878988, -0.6315732], # square -> octagon + [0.0, 0.0], # octagon + [0.0, 0.0], + [0.0, 0.0], + [0.0, 0.0], + [0.0, 0.0], + [0.0, 0.0], + [-1.3141878, 1.3141878], # octagon -> square + [0.34966463, 0.14732757], + ] + * n_rep + )[: len(origins)] + assert_almost_equal(properties["local_origins"], origins, decimal=4) # calculated to the x-y plane # all input vectors (loc_rot) are in y-z plane - screw = [0, 90, 180, # square - -77.236, # transition - -45, 0, 45, 90, 135, 180, -135, # octagon - -102.764]*n_rep + screw = [ + 0, + 90, + 180, # square + -77.236, # transition + -45, + 0, + 45, + 90, + 135, + 180, + -135, # octagon + -102.764, + ] * n_rep # not quite 0, comes out as 1.32e-06 - assert_almost_equal(properties['local_screw_angles'], screw[:-2], - decimal=3) + assert_almost_equal( + properties["local_screw_angles"], screw[:-2], decimal=3 + ) class TestHELANAL(object): @@ -341,47 +428,58 @@ class TestHELANAL(object): @pytest.fixture() def psf_ca(self): u = mda.Universe(PSF, DCD) - ag = u.select_atoms('name CA') + ag = u.select_atoms("name CA") return ag @pytest.fixture() def helanal(self, psf_ca): - ha = hel.HELANAL(psf_ca, select='resnum 161-187', - flatten_single_helix=True) + ha = hel.HELANAL( + psf_ca, select="resnum 161-187", flatten_single_helix=True + ) return ha.run(start=10, stop=80) def test_regression_summary(self, helanal): - bends = helanal.results.summary['all_bends'] + bends = helanal.results.summary["all_bends"] old_helanal = read_bending_matrix(HELANAL_BENDING_MATRIX_SUBSET) - assert_almost_equal(np.triu(bends['mean'], 1), old_helanal['Mean'], - decimal=1) - assert_almost_equal(np.triu(bends['sample_sd'], 1), old_helanal['SD'], - decimal=1) - assert_almost_equal(np.triu(bends['abs_dev'], 1), old_helanal['ABDEV'], - decimal=1) + assert_almost_equal( + np.triu(bends["mean"], 1), old_helanal["Mean"], decimal=1 + ) + assert_almost_equal( + np.triu(bends["sample_sd"], 1), old_helanal["SD"], decimal=1 + ) + assert_almost_equal( + np.triu(bends["abs_dev"], 1), old_helanal["ABDEV"], decimal=1 + ) def test_regression_values(self): u = mda.Universe(PDB_small) - ha = hel.HELANAL(u, select='name CA and resnum 161-187', - flatten_single_helix=True) + ha = hel.HELANAL( + u, select="name CA and resnum 161-187", flatten_single_helix=True + ) ha.run() for key, value in HELANAL_SINGLE_DATA.items(): - if 'summary' in key: + if "summary" in key: data = ha.results[key.split()[0]] - calculated = [data.mean(), data.std(ddof=1), - np.fabs(data-data.mean()).mean()] + calculated = [ + data.mean(), + data.std(ddof=1), + np.fabs(data - data.mean()).mean(), + ] else: calculated = ha.results[key][0] - msg = 'Mismatch between calculated and reference {}' - assert_almost_equal(calculated, value, - decimal=4, - err_msg=msg.format(key)) + msg = "Mismatch between calculated and reference {}" + assert_almost_equal( + calculated, value, decimal=4, err_msg=msg.format(key) + ) def test_multiple_selections(self, psf_ca): - ha = hel.HELANAL(psf_ca, flatten_single_helix=True, - select=('resnum 30-40', 'resnum 60-80')) + ha = hel.HELANAL( + psf_ca, + flatten_single_helix=True, + select=("resnum 30-40", "resnum 60-80"), + ) ha.run() n_frames = len(psf_ca.universe.trajectory) assert len(ha.atomgroups) == 2 @@ -393,23 +491,23 @@ def test_multiple_selections(self, psf_ca): def test_universe_from_origins(self, helanal): u = helanal.universe_from_origins() assert isinstance(u, mda.Universe) - assert len(u.atoms) == len(helanal.atomgroups[0])-2 + assert len(u.atoms) == len(helanal.atomgroups[0]) - 2 assert len(u.trajectory) == 70 def test_universe_from_origins_except(self, psf_ca): - ha = hel.HELANAL(psf_ca, select='resnum 161-187') - with pytest.raises(ValueError, match=r'before universe_from_origins'): + ha = hel.HELANAL(psf_ca, select="resnum 161-187") + with pytest.raises(ValueError, match=r"before universe_from_origins"): u = ha.universe_from_origins() def test_multiple_atoms_per_residues(self): u = mda.Universe(XYZ) with pytest.warns(UserWarning) as rec: - ha = hel.HELANAL(u, select='name H') + ha = hel.HELANAL(u, select="name H") assert len(rec) == 1 - assert 'multiple atoms' in rec[0].message.args[0] + assert "multiple atoms" in rec[0].message.args[0] def test_residue_gaps_split(self, psf_ca): - sel = 'resid 6:50 or resid 100:130 or resid 132:148' + sel = "resid 6:50 or resid 100:130 or resid 132:148" with pytest.warns(UserWarning) as rec: ha = hel.HELANAL(psf_ca, select=sel).run() assert len(ha.atomgroups) == 3 @@ -418,52 +516,55 @@ def test_residue_gaps_split(self, psf_ca): assert len(ha.atomgroups[2]) == 17 assert len(rec) == 1 warnmsg = rec[0].message.args[0] - assert 'has gaps in the residues' in warnmsg - assert 'Splitting into 3 helices' in warnmsg + assert "has gaps in the residues" in warnmsg + assert "Splitting into 3 helices" in warnmsg def test_residue_gaps_no_split(self, psf_ca): - sel = 'resid 6:50 or resid 100:130 or resid 132:148' + sel = "resid 6:50 or resid 100:130 or resid 132:148" with pytest.warns(UserWarning) as rec: - ha = hel.HELANAL(psf_ca, select=sel, - split_residue_sequences=False) + ha = hel.HELANAL(psf_ca, select=sel, split_residue_sequences=False) ha.run() assert len(ha.atomgroups) == 1 - assert len(ha.atomgroups[0]) == 45+31+17 + assert len(ha.atomgroups[0]) == 45 + 31 + 17 assert len(rec) == 1 warnmsg = rec[0].message.args[0] - assert 'has gaps in the residues' in warnmsg - assert 'Splitting into' not in warnmsg + assert "has gaps in the residues" in warnmsg + assert "Splitting into" not in warnmsg def test_len_groups_short(self, psf_ca): - sel = 'resnum 161-168' - with pytest.warns(UserWarning, match='Fewer than 9 atoms found'): + sel = "resnum 161-168" + with pytest.warns(UserWarning, match="Fewer than 9 atoms found"): ha = hel.HELANAL(psf_ca, select=sel) assert len(ha.atomgroups) < 9 - @pytest.mark.parametrize('ref_axis,screw_angles', [ - # input vectors zigzag between [-1, 0, 0] and [1, 0, 0] - # global axis is z-axis - ([0, 0, 1], [180, 0]), # calculated to x-z plane - ([1, 0, 0], [180, 0]), # calculated to x-z plane - ([-1, 0, 0], [0, 180]), # calculated to x-z plane upside down - ([0, 1, 0], [90, -90]), # calculated to y-z plane - ([0, -1, 0], [-90, 90]), # calculated to x-z plane upside down - # calculated to diagonal xy-z plane rotating around - ([1, 1, 0], [135, -45]), - ([-1, 1, 0], [45, -135]), - ([1, -1, 0], [-135, 45]), - ([-1, -1, 0], [-45, 135]), - # calculated to diagonal xyz-z plane w/o z contribution - ([1, 1, 1], [135, -45]), - ([1, 1, -1], [135, -45]), - ([1, -1, 1], [-135, 45]), - ([-1, 1, 1], [45, -135]), - ([-1, -1, 1], [-45, 135]), - ([-1, -1, -1], [-45, 135]), - ]) + @pytest.mark.parametrize( + "ref_axis,screw_angles", + [ + # input vectors zigzag between [-1, 0, 0] and [1, 0, 0] + # global axis is z-axis + ([0, 0, 1], [180, 0]), # calculated to x-z plane + ([1, 0, 0], [180, 0]), # calculated to x-z plane + ([-1, 0, 0], [0, 180]), # calculated to x-z plane upside down + ([0, 1, 0], [90, -90]), # calculated to y-z plane + ([0, -1, 0], [-90, 90]), # calculated to x-z plane upside down + # calculated to diagonal xy-z plane rotating around + ([1, 1, 0], [135, -45]), + ([-1, 1, 0], [45, -135]), + ([1, -1, 0], [-135, 45]), + ([-1, -1, 0], [-45, 135]), + # calculated to diagonal xyz-z plane w/o z contribution + ([1, 1, 1], [135, -45]), + ([1, 1, -1], [135, -45]), + ([1, -1, 1], [-135, 45]), + ([-1, 1, 1], [45, -135]), + ([-1, -1, 1], [-45, 135]), + ([-1, -1, -1], [-45, 135]), + ], + ) def test_helanal_zigzag(self, zigzag, ref_axis, screw_angles): - ha = hel.HELANAL(zigzag, select="all", ref_axis=ref_axis, - flatten_single_helix=True).run() + ha = hel.HELANAL( + zigzag, select="all", ref_axis=ref_axis, flatten_single_helix=True + ).run() assert_almost_equal(ha.results.local_twists, 180, decimal=4) assert_almost_equal(ha.results.local_nres_per_turn, 2, decimal=4) assert_almost_equal(ha.results.global_axis, [[0, 0, -1]], decimal=4) @@ -472,21 +573,28 @@ def test_helanal_zigzag(self, zigzag, ref_axis, screw_angles): assert_almost_equal(ha.results.local_bends, 0, decimal=4) assert_almost_equal(ha.results.all_bends, 0, decimal=4) assert_almost_equal(ha.results.local_heights, 0, decimal=4) - assert_almost_equal(ha.results.local_helix_directions[0][0::2], - [[-1, 0, 0]]*49, decimal=4) - assert_almost_equal(ha.results.local_helix_directions[0][1::2], - [[1, 0, 0]]*49, decimal=4) + assert_almost_equal( + ha.results.local_helix_directions[0][0::2], + [[-1, 0, 0]] * 49, + decimal=4, + ) + assert_almost_equal( + ha.results.local_helix_directions[0][1::2], + [[1, 0, 0]] * 49, + decimal=4, + ) origins = zigzag.atoms.positions[1:-1].copy() origins[:, 0] = 0 assert_almost_equal(ha.results.local_origins[0], origins, decimal=4) - assert_almost_equal(ha.results.local_screw_angles[0], - screw_angles*49, decimal=4) + assert_almost_equal( + ha.results.local_screw_angles[0], screw_angles * 49, decimal=4 + ) def test_vector_of_best_fit(): line = np.random.rand(3) unit = line / np.linalg.norm(line) - points = line*np.arange(1000)[:, np.newaxis] + points = line * np.arange(1000)[:, np.newaxis] noise = np.random.normal(size=(1000, 3)) data = points + noise diff --git a/testsuite/MDAnalysisTests/analysis/test_hydrogenbondautocorrel.py b/testsuite/MDAnalysisTests/analysis/test_hydrogenbondautocorrel.py index 6a4970edba1..97eeb6cc016 100644 --- a/testsuite/MDAnalysisTests/analysis/test_hydrogenbondautocorrel.py +++ b/testsuite/MDAnalysisTests/analysis/test_hydrogenbondautocorrel.py @@ -23,8 +23,10 @@ import pytest from MDAnalysisTests.datafiles import ( - TRZ, TRZ_psf, - waterPSF, waterDCD, + TRZ, + TRZ_psf, + waterPSF, + waterDCD, XYZ_mini, ) from numpy.testing import assert_almost_equal @@ -33,8 +35,10 @@ from importlib import reload import MDAnalysis as mda -from MDAnalysis.analysis.hydrogenbonds import (HydrogenBondAutoCorrel as HBAC, - find_hydrogen_donors) +from MDAnalysis.analysis.hydrogenbonds import ( + HydrogenBondAutoCorrel as HBAC, + find_hydrogen_donors, +) class TestHydrogenBondAutocorrel(object): @@ -44,118 +48,166 @@ def u(self): @pytest.fixture() def hydrogens(self, u): - return u.atoms.select_atoms('name Hn') + return u.atoms.select_atoms("name Hn") @pytest.fixture() def nitrogens(self, u): - return u.atoms.select_atoms('name N') + return u.atoms.select_atoms("name N") @pytest.fixture() def oxygens(self, u): - return u.atoms.select_atoms('name O') + return u.atoms.select_atoms("name O") # regression tests for different conditions def test_continuous(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='continuous', - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="continuous", + sample_time=0.06, ) hbond.run() assert_almost_equal( - hbond.solution['results'], - np.array([ 1. , 0.92668623, 0.83137828, - 0.74486804, 0.67741936, 0.60263932], - dtype=np.float32) + hbond.solution["results"], + np.array( + [ + 1.0, + 0.92668623, + 0.83137828, + 0.74486804, + 0.67741936, + 0.60263932, + ], + dtype=np.float32, + ), ) def test_continuous_excl(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='continuous', - exclusions=(np.arange(len(hydrogens)), np.array( - range(len(oxygens)))), - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="continuous", + exclusions=( + np.arange(len(hydrogens)), + np.array(range(len(oxygens))), + ), + sample_time=0.06, ) hbond.run() assert_almost_equal( - hbond.solution['results'], - np.array([ 1. , 0.92668623, 0.83137828, - 0.74486804, 0.67741936, 0.60263932], - dtype=np.float32) + hbond.solution["results"], + np.array( + [ + 1.0, + 0.92668623, + 0.83137828, + 0.74486804, + 0.67741936, + 0.60263932, + ], + dtype=np.float32, + ), ) - def test_intermittent(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='intermittent', - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="intermittent", + sample_time=0.06, ) hbond.run() assert_almost_equal( - hbond.solution['results'], - np.array([ 1. , 0.92668623, 0.84310848, - 0.79325515, 0.76392961, 0.72287393], - dtype=np.float32) + hbond.solution["results"], + np.array( + [ + 1.0, + 0.92668623, + 0.84310848, + 0.79325515, + 0.76392961, + 0.72287393, + ], + dtype=np.float32, + ), ) - def test_intermittent_timecut(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='intermittent', - time_cut=0.01, # time cut at traj.dt == continuous - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="intermittent", + time_cut=0.01, # time cut at traj.dt == continuous + sample_time=0.06, ) hbond.run() assert_almost_equal( - hbond.solution['results'], - np.array([ 1. , 0.92668623, 0.83137828, - 0.74486804, 0.67741936, 0.60263932], - dtype=np.float32) + hbond.solution["results"], + np.array( + [ + 1.0, + 0.92668623, + 0.83137828, + 0.74486804, + 0.67741936, + 0.60263932, + ], + dtype=np.float32, + ), ) def test_intermittent_excl(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='intermittent', - exclusions=(np.arange(len(hydrogens)), np.array( - range(len(oxygens)))), - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="intermittent", + exclusions=( + np.arange(len(hydrogens)), + np.array(range(len(oxygens))), + ), + sample_time=0.06, ) hbond.run() assert_almost_equal( - hbond.solution['results'], - np.array([ 1. , 0.92668623, 0.84310848, - 0.79325515, 0.76392961, 0.72287393], - dtype=np.float32) + hbond.solution["results"], + np.array( + [ + 1.0, + 0.92668623, + 0.84310848, + 0.79325515, + 0.76392961, + 0.72287393, + ], + dtype=np.float32, + ), ) # For `solve` the test trajectories aren't long enough # So spoof the results and check that solver finds solution def test_solve_continuous(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='continuous', - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="continuous", + sample_time=0.06, ) def actual_function_cont(t): @@ -163,24 +215,26 @@ def actual_function_cont(t): A2 = 0.25 tau1 = 0.5 tau2 = 0.1 - return A1 * np.exp(-t/tau1) + A2 * np.exp(-t/tau2) - hbond.solution['time'] = time = np.arange(0, 0.06, 0.001) - hbond.solution['results'] = actual_function_cont(time) + return A1 * np.exp(-t / tau1) + A2 * np.exp(-t / tau2) + + hbond.solution["time"] = time = np.arange(0, 0.06, 0.001) + hbond.solution["results"] = actual_function_cont(time) hbond.solve() assert_almost_equal( - hbond.solution['fit'], + hbond.solution["fit"], np.array([0.75, 0.5, 0.1]), ) def test_solve_intermittent(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='intermittent', - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="intermittent", + sample_time=0.06, ) def actual_function_int(t): @@ -190,69 +244,86 @@ def actual_function_int(t): tau1 = 5 tau2 = 1 tau3 = 0.1 - return A1 * np.exp(-t/tau1) + A2 * np.exp(-t/tau2) + A3 * np.exp(-t/tau3) - hbond.solution['time'] = time = np.arange(0, 6.0, 0.01) - hbond.solution['results'] = actual_function_int(time) + return ( + A1 * np.exp(-t / tau1) + + A2 * np.exp(-t / tau2) + + A3 * np.exp(-t / tau3) + ) + + hbond.solution["time"] = time = np.arange(0, 6.0, 0.01) + hbond.solution["results"] = actual_function_int(time) hbond.solve() assert_almost_equal( - hbond.solution['fit'], + hbond.solution["fit"], np.array([0.33, 0.33, 5, 1, 0.1]), ) # setup errors def test_wronglength_DA(self, u, hydrogens, oxygens, nitrogens): with pytest.raises(ValueError): - HBAC(u, - hydrogens=hydrogens[:-1], - acceptors=oxygens, - donors=nitrogens, - bond_type='intermittent', - exclusions=(np.arange(len(hydrogens)), np.array( - range(len(oxygens)))), - sample_time=0.06, - ) + HBAC( + u, + hydrogens=hydrogens[:-1], + acceptors=oxygens, + donors=nitrogens, + bond_type="intermittent", + exclusions=( + np.arange(len(hydrogens)), + np.array(range(len(oxygens))), + ), + sample_time=0.06, + ) def test_exclusions(self, u, hydrogens, oxygens, nitrogens): - excl_list = (np.array(range(len(hydrogens))), np.array( - range(len(oxygens)))) + excl_list = ( + np.array(range(len(hydrogens))), + np.array(range(len(oxygens))), + ) excl_list2 = excl_list[0], excl_list[1][:-1] with pytest.raises(ValueError): - HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='intermittent', - exclusions=excl_list2, - sample_time=0.06, - ) + HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="intermittent", + exclusions=excl_list2, + sample_time=0.06, + ) def test_bond_type_VE(self, u, hydrogens, oxygens, nitrogens): with pytest.raises(ValueError): - HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='marzipan', - exclusions=(np.arange(len(hydrogens)), np.array(range( - len(oxygens)))), - sample_time=0.06, - ) + HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="marzipan", + exclusions=( + np.arange(len(hydrogens)), + np.array(range(len(oxygens))), + ), + sample_time=0.06, + ) def test_solve_before_run_VE(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='continuous', - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="continuous", + sample_time=0.06, ) with pytest.raises(ValueError): hbond.solve() - @mock.patch('MDAnalysis.coordinates.TRZ.TRZReader._read_frame') - def test_unslicable_traj_VE(self, mock_read, u, hydrogens, oxygens, nitrogens): + @mock.patch("MDAnalysis.coordinates.TRZ.TRZReader._read_frame") + def test_unslicable_traj_VE( + self, mock_read, u, hydrogens, oxygens, nitrogens + ): mock_read.side_effect = TypeError with pytest.raises(ValueError): @@ -261,17 +332,18 @@ def test_unslicable_traj_VE(self, mock_read, u, hydrogens, oxygens, nitrogens): hydrogens=hydrogens, acceptors=oxygens, donors=nitrogens, - bond_type='continuous', - sample_time=0.06 - ) + bond_type="continuous", + sample_time=0.06, + ) def test_repr(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='continuous', - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="continuous", + sample_time=0.06, ) assert isinstance(repr(hbond), str) @@ -279,7 +351,7 @@ def test_repr(self, u, hydrogens, oxygens, nitrogens): def test_find_donors(): u = mda.Universe(waterPSF, waterDCD) - H = u.select_atoms('name H*') + H = u.select_atoms("name H*") D = find_hydrogen_donors(H) diff --git a/testsuite/MDAnalysisTests/analysis/test_hydrogenbondautocorrel_deprecated.py b/testsuite/MDAnalysisTests/analysis/test_hydrogenbondautocorrel_deprecated.py index 7a372d53c54..e1822b62fa0 100644 --- a/testsuite/MDAnalysisTests/analysis/test_hydrogenbondautocorrel_deprecated.py +++ b/testsuite/MDAnalysisTests/analysis/test_hydrogenbondautocorrel_deprecated.py @@ -23,8 +23,10 @@ import pytest from MDAnalysisTests.datafiles import ( - TRZ, TRZ_psf, - waterPSF, waterDCD, + TRZ, + TRZ_psf, + waterPSF, + waterDCD, XYZ_mini, ) from numpy.testing import assert_almost_equal @@ -36,10 +38,12 @@ from MDAnalysis.analysis import hbonds from MDAnalysis.analysis.hbonds import HydrogenBondAutoCorrel as HBAC + @pytest.fixture(scope="module") def u_water(): return mda.Universe(waterPSF, waterDCD) + class TestHydrogenBondAutocorrel(object): @pytest.fixture() def u(self): @@ -47,118 +51,166 @@ def u(self): @pytest.fixture() def hydrogens(self, u): - return u.atoms.select_atoms('name Hn') + return u.atoms.select_atoms("name Hn") @pytest.fixture() def nitrogens(self, u): - return u.atoms.select_atoms('name N') + return u.atoms.select_atoms("name N") @pytest.fixture() def oxygens(self, u): - return u.atoms.select_atoms('name O') + return u.atoms.select_atoms("name O") # regression tests for different conditions def test_continuous(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='continuous', - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="continuous", + sample_time=0.06, ) hbond.run() assert_almost_equal( - hbond.solution['results'], - np.array([ 1. , 0.92668623, 0.83137828, - 0.74486804, 0.67741936, 0.60263932], - dtype=np.float32) + hbond.solution["results"], + np.array( + [ + 1.0, + 0.92668623, + 0.83137828, + 0.74486804, + 0.67741936, + 0.60263932, + ], + dtype=np.float32, + ), ) def test_continuous_excl(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='continuous', - exclusions=(np.arange(len(hydrogens)), np.array( - range(len(oxygens)))), - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="continuous", + exclusions=( + np.arange(len(hydrogens)), + np.array(range(len(oxygens))), + ), + sample_time=0.06, ) hbond.run() assert_almost_equal( - hbond.solution['results'], - np.array([ 1. , 0.92668623, 0.83137828, - 0.74486804, 0.67741936, 0.60263932], - dtype=np.float32) + hbond.solution["results"], + np.array( + [ + 1.0, + 0.92668623, + 0.83137828, + 0.74486804, + 0.67741936, + 0.60263932, + ], + dtype=np.float32, + ), ) - def test_intermittent(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='intermittent', - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="intermittent", + sample_time=0.06, ) hbond.run() assert_almost_equal( - hbond.solution['results'], - np.array([ 1. , 0.92668623, 0.84310848, - 0.79325515, 0.76392961, 0.72287393], - dtype=np.float32) + hbond.solution["results"], + np.array( + [ + 1.0, + 0.92668623, + 0.84310848, + 0.79325515, + 0.76392961, + 0.72287393, + ], + dtype=np.float32, + ), ) - def test_intermittent_timecut(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='intermittent', - time_cut=0.01, # time cut at traj.dt == continuous - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="intermittent", + time_cut=0.01, # time cut at traj.dt == continuous + sample_time=0.06, ) hbond.run() assert_almost_equal( - hbond.solution['results'], - np.array([ 1. , 0.92668623, 0.83137828, - 0.74486804, 0.67741936, 0.60263932], - dtype=np.float32) + hbond.solution["results"], + np.array( + [ + 1.0, + 0.92668623, + 0.83137828, + 0.74486804, + 0.67741936, + 0.60263932, + ], + dtype=np.float32, + ), ) def test_intermittent_excl(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='intermittent', - exclusions=(np.arange(len(hydrogens)), np.array( - range(len(oxygens)))), - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="intermittent", + exclusions=( + np.arange(len(hydrogens)), + np.array(range(len(oxygens))), + ), + sample_time=0.06, ) hbond.run() assert_almost_equal( - hbond.solution['results'], - np.array([ 1. , 0.92668623, 0.84310848, - 0.79325515, 0.76392961, 0.72287393], - dtype=np.float32) + hbond.solution["results"], + np.array( + [ + 1.0, + 0.92668623, + 0.84310848, + 0.79325515, + 0.76392961, + 0.72287393, + ], + dtype=np.float32, + ), ) # For `solve` the test trajectories aren't long enough # So spoof the results and check that solver finds solution def test_solve_continuous(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='continuous', - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="continuous", + sample_time=0.06, ) def actual_function_cont(t): @@ -166,24 +218,26 @@ def actual_function_cont(t): A2 = 0.25 tau1 = 0.5 tau2 = 0.1 - return A1 * np.exp(-t/tau1) + A2 * np.exp(-t/tau2) - hbond.solution['time'] = time = np.arange(0, 0.06, 0.001) - hbond.solution['results'] = actual_function_cont(time) + return A1 * np.exp(-t / tau1) + A2 * np.exp(-t / tau2) + + hbond.solution["time"] = time = np.arange(0, 0.06, 0.001) + hbond.solution["results"] = actual_function_cont(time) hbond.solve() assert_almost_equal( - hbond.solution['fit'], + hbond.solution["fit"], np.array([0.75, 0.5, 0.1]), ) def test_solve_intermittent(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='intermittent', - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="intermittent", + sample_time=0.06, ) def actual_function_int(t): @@ -193,69 +247,86 @@ def actual_function_int(t): tau1 = 5 tau2 = 1 tau3 = 0.1 - return A1 * np.exp(-t/tau1) + A2 * np.exp(-t/tau2) + A3 * np.exp(-t/tau3) - hbond.solution['time'] = time = np.arange(0, 6.0, 0.01) - hbond.solution['results'] = actual_function_int(time) + return ( + A1 * np.exp(-t / tau1) + + A2 * np.exp(-t / tau2) + + A3 * np.exp(-t / tau3) + ) + + hbond.solution["time"] = time = np.arange(0, 6.0, 0.01) + hbond.solution["results"] = actual_function_int(time) hbond.solve() assert_almost_equal( - hbond.solution['fit'], + hbond.solution["fit"], np.array([0.33, 0.33, 5, 1, 0.1]), ) # setup errors def test_wronglength_DA(self, u, hydrogens, oxygens, nitrogens): with pytest.raises(ValueError): - HBAC(u, - hydrogens=hydrogens[:-1], - acceptors=oxygens, - donors=nitrogens, - bond_type='intermittent', - exclusions=(np.arange(len(hydrogens)), np.array( - range(len(oxygens)))), - sample_time=0.06, - ) + HBAC( + u, + hydrogens=hydrogens[:-1], + acceptors=oxygens, + donors=nitrogens, + bond_type="intermittent", + exclusions=( + np.arange(len(hydrogens)), + np.array(range(len(oxygens))), + ), + sample_time=0.06, + ) def test_exclusions(self, u, hydrogens, oxygens, nitrogens): - excl_list = (np.array(range(len(hydrogens))), np.array( - range(len(oxygens)))) + excl_list = ( + np.array(range(len(hydrogens))), + np.array(range(len(oxygens))), + ) excl_list2 = excl_list[0], excl_list[1][:-1] with pytest.raises(ValueError): - HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='intermittent', - exclusions=excl_list2, - sample_time=0.06, - ) + HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="intermittent", + exclusions=excl_list2, + sample_time=0.06, + ) def test_bond_type_VE(self, u, hydrogens, oxygens, nitrogens): with pytest.raises(ValueError): - HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='marzipan', - exclusions=(np.arange(len(hydrogens)), np.array(range( - len(oxygens)))), - sample_time=0.06, - ) + HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="marzipan", + exclusions=( + np.arange(len(hydrogens)), + np.array(range(len(oxygens))), + ), + sample_time=0.06, + ) def test_solve_before_run_VE(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='continuous', - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="continuous", + sample_time=0.06, ) with pytest.raises(ValueError): hbond.solve() - @mock.patch('MDAnalysis.coordinates.TRZ.TRZReader._read_frame') - def test_unslicable_traj_VE(self, mock_read, u, hydrogens, oxygens, nitrogens): + @mock.patch("MDAnalysis.coordinates.TRZ.TRZReader._read_frame") + def test_unslicable_traj_VE( + self, mock_read, u, hydrogens, oxygens, nitrogens + ): mock_read.side_effect = TypeError with pytest.raises(ValueError): @@ -264,38 +335,40 @@ def test_unslicable_traj_VE(self, mock_read, u, hydrogens, oxygens, nitrogens): hydrogens=hydrogens, acceptors=oxygens, donors=nitrogens, - bond_type='continuous', - sample_time=0.06 - ) + bond_type="continuous", + sample_time=0.06, + ) def test_repr(self, u, hydrogens, oxygens, nitrogens): - hbond = HBAC(u, - hydrogens=hydrogens, - acceptors=oxygens, - donors=nitrogens, - bond_type='continuous', - sample_time=0.06, + hbond = HBAC( + u, + hydrogens=hydrogens, + acceptors=oxygens, + donors=nitrogens, + bond_type="continuous", + sample_time=0.06, ) assert isinstance(repr(hbond), str) def test_deprecation_warning(self, u, hydrogens, oxygens, nitrogens): - wmsg = ('`HydrogenBondAutoCorrel` is deprecated!\n' - '`HydrogenBondAutoCorrel` will be removed in release 3.0.0.\n' - 'The class was moved to MDAnalysis.analysis.hbonds.hbond_autocorrel.') + wmsg = ( + "`HydrogenBondAutoCorrel` is deprecated!\n" + "`HydrogenBondAutoCorrel` will be removed in release 3.0.0.\n" + "The class was moved to MDAnalysis.analysis.hbonds.hbond_autocorrel." + ) with pytest.warns(DeprecationWarning, match=wmsg): HBAC( u, hydrogens=hydrogens, acceptors=oxygens, donors=nitrogens, - bond_type='continuous', - sample_time=0.06 + bond_type="continuous", + sample_time=0.06, ) - def test_find_donors(u_water): - H = u_water.select_atoms('name H*') + H = u_water.select_atoms("name H*") D = hbonds.find_hydrogen_donors(H) @@ -313,19 +386,21 @@ def test_donors_nobonds(): def test_find_hydrogen_donors_deprecation_warning(u_water): - H = u_water.select_atoms('name H*') - wmsg = ('`find_hydrogen_donors` is deprecated!\n' - '`find_hydrogen_donors` will be removed in release 3.0.0.\n' - 'The function was moved to MDAnalysis.analysis.hbonds.hbond_autocorrel.') + H = u_water.select_atoms("name H*") + wmsg = ( + "`find_hydrogen_donors` is deprecated!\n" + "`find_hydrogen_donors` will be removed in release 3.0.0.\n" + "The function was moved to MDAnalysis.analysis.hbonds.hbond_autocorrel." + ) with pytest.warns(DeprecationWarning, match=wmsg): hbonds.find_hydrogen_donors(H) def test_moved_module_warning(): - wmsg = ("This module was moved to " - "MDAnalysis.analysis.hydrogenbonds.hbond_autocorrel; " - "hbonds.hbond_autocorrel will be removed in 3.0.0.") + wmsg = ( + "This module was moved to " + "MDAnalysis.analysis.hydrogenbonds.hbond_autocorrel; " + "hbonds.hbond_autocorrel will be removed in 3.0.0." + ) with pytest.warns(DeprecationWarning, match=wmsg): reload(hbonds.hbond_autocorrel) - - diff --git a/testsuite/MDAnalysisTests/analysis/test_hydrogenbonds_analysis.py b/testsuite/MDAnalysisTests/analysis/test_hydrogenbonds_analysis.py index bef6b03331d..a731dd6ec93 100644 --- a/testsuite/MDAnalysisTests/analysis/test_hydrogenbonds_analysis.py +++ b/testsuite/MDAnalysisTests/analysis/test_hydrogenbonds_analysis.py @@ -25,13 +25,18 @@ import pytest import copy -from numpy.testing import (assert_allclose, assert_equal, - assert_array_almost_equal, assert_array_equal, - assert_almost_equal) +from numpy.testing import ( + assert_allclose, + assert_equal, + assert_array_almost_equal, + assert_array_equal, + assert_almost_equal, +) import MDAnalysis from MDAnalysis.analysis.hydrogenbonds.hbond_analysis import ( - HydrogenBondAnalysis) + HydrogenBondAnalysis, +) from MDAnalysis.exceptions import NoDataError from MDAnalysisTests.datafiles import waterPSF, waterDCD @@ -39,20 +44,20 @@ class TestHydrogenBondAnalysisTIP3P(object): @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe(): return MDAnalysis.Universe(waterPSF, waterDCD) kwargs = { - 'donors_sel': 'name OH2', - 'hydrogens_sel': 'name H1 H2', - 'acceptors_sel': 'name OH2', - 'd_h_cutoff': 1.2, - 'd_a_cutoff': 3.0, - 'd_h_a_angle_cutoff': 120.0 + "donors_sel": "name OH2", + "hydrogens_sel": "name H1 H2", + "acceptors_sel": "name OH2", + "d_h_cutoff": 1.2, + "d_a_cutoff": 3.0, + "d_h_a_angle_cutoff": 120.0, } - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def h(self, universe, client_HydrogenBondAnalysis): h = HydrogenBondAnalysis(universe, **self.kwargs) h.run(**client_HydrogenBondAnalysis) @@ -64,18 +69,22 @@ def test_hbond_analysis(self, h): assert len(h.results.hbonds) == 32 reference = { - 'distance': {'mean': 2.7627309, 'std': 0.0905052}, - 'angle': {'mean': 158.9038039, 'std': 12.0362826}, + "distance": {"mean": 2.7627309, "std": 0.0905052}, + "angle": {"mean": 158.9038039, "std": 12.0362826}, } - assert_allclose(np.mean(h.results.hbonds[:, 4]), - reference['distance']['mean']) - assert_allclose(np.std(h.results.hbonds[:, 4]), - reference['distance']['std']) - assert_allclose(np.mean(h.results.hbonds[:, 5]), - reference['angle']['mean']) - assert_allclose(np.std(h.results.hbonds[:, 5]), - reference['angle']['std']) + assert_allclose( + np.mean(h.results.hbonds[:, 4]), reference["distance"]["mean"] + ) + assert_allclose( + np.std(h.results.hbonds[:, 4]), reference["distance"]["std"] + ) + assert_allclose( + np.mean(h.results.hbonds[:, 5]), reference["angle"]["mean"] + ) + assert_allclose( + np.std(h.results.hbonds[:, 5]), reference["angle"]["std"] + ) def test_count_by_time(self, h): @@ -100,7 +109,7 @@ def test_count_by_ids(self, h, universe): unique_hbonds = h.count_by_ids() most_common_hbond_ids = [12, 14, 9] - assert_equal(unique_hbonds[0,:3], most_common_hbond_ids) + assert_equal(unique_hbonds[0, :3], most_common_hbond_ids) # count_by_ids() returns raw counts # convert to fraction of time that bond was observed @@ -117,16 +126,16 @@ def test_hbonds_deprecated_attr(self, h): class TestHydrogenBondAnalysisIdeal(object): kwargs = { - 'donors_sel': 'name O', - 'hydrogens_sel': 'name H1 H2', - 'acceptors_sel': 'name O', - 'd_h_cutoff': 1.2, - 'd_a_cutoff': 3.0, - 'd_h_a_angle_cutoff': 120.0 + "donors_sel": "name O", + "hydrogens_sel": "name H1 H2", + "acceptors_sel": "name O", + "d_h_cutoff": 1.2, + "d_a_cutoff": 3.0, + "d_h_a_angle_cutoff": 120.0, } @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe(): # create two water molecules """ @@ -138,53 +147,56 @@ def universe(): """ n_residues = 2 u = MDAnalysis.Universe.empty( - n_atoms=n_residues*3, + n_atoms=n_residues * 3, n_residues=n_residues, atom_resindex=np.repeat(range(n_residues), 3), residue_segindex=[0] * n_residues, trajectory=True, # necessary for adding coordinates - ) + ) - u.add_TopologyAttr('name', ['O', 'H1', 'H2'] * n_residues) - u.add_TopologyAttr('type', ['O', 'H', 'H'] * n_residues) - u.add_TopologyAttr('resname', ['SOL'] * n_residues) - u.add_TopologyAttr('resid', list(range(1, n_residues + 1))) - u.add_TopologyAttr('id', list(range(1, (n_residues * 3) + 1))) + u.add_TopologyAttr("name", ["O", "H1", "H2"] * n_residues) + u.add_TopologyAttr("type", ["O", "H", "H"] * n_residues) + u.add_TopologyAttr("resname", ["SOL"] * n_residues) + u.add_TopologyAttr("resid", list(range(1, n_residues + 1))) + u.add_TopologyAttr("id", list(range(1, (n_residues * 3) + 1))) # Atomic coordinates with a single hydrogen bond between O1-H2---O2 - pos1 = np.array([[0, 0, 0], # O1 - [-0.249, -0.968, 0], # H1 - [1, 0, 0], # H2 - [2.5, 0, 0], # O2 - [3., 0, 0], # H3 - [2.250, 0.968, 0] # H4 - ]) + pos1 = np.array( + [ + [0, 0, 0], # O1 + [-0.249, -0.968, 0], # H1 + [1, 0, 0], # H2 + [2.5, 0, 0], # O2 + [3.0, 0, 0], # H3 + [2.250, 0.968, 0], # H4 + ] + ) # Atomic coordinates with no hydrogen bonds - pos2 = np.array([[0, 0, 0], # O1 - [-0.249, -0.968, 0], # H1 - [1, 0, 0], # H2 - [4.5, 0, 0], # O2 - [5., 0, 0], # H3 - [4.250, 0.968, 0] # H4 - ]) - - coordinates = np.empty((3, # number of frames - u.atoms.n_atoms, - 3)) + pos2 = np.array( + [ + [0, 0, 0], # O1 + [-0.249, -0.968, 0], # H1 + [1, 0, 0], # H2 + [4.5, 0, 0], # O2 + [5.0, 0, 0], # H3 + [4.250, 0.968, 0], # H4 + ] + ) + + coordinates = np.empty((3, u.atoms.n_atoms, 3)) # number of frames coordinates[0] = pos1 coordinates[1] = pos2 coordinates[2] = pos1 - u.load_new(coordinates, order='fac') + u.load_new(coordinates, order="fac") return u @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def hydrogen_bonds(universe, client_HydrogenBondAnalysis): h = HydrogenBondAnalysis( - universe, - **TestHydrogenBondAnalysisIdeal.kwargs + universe, **TestHydrogenBondAnalysisIdeal.kwargs ) h.run(**client_HydrogenBondAnalysis) return h @@ -200,42 +212,48 @@ def test_count_by_type(self, hydrogen_bonds): def test_no_bond_info_exception(self, universe): kwargs = { - 'donors_sel': None, - 'hydrogens_sel': None, - 'acceptors_sel': None, - 'd_h_cutoff': 1.2, - 'd_a_cutoff': 3.0, - 'd_h_a_angle_cutoff': 120.0 + "donors_sel": None, + "hydrogens_sel": None, + "acceptors_sel": None, + "d_h_cutoff": 1.2, + "d_a_cutoff": 3.0, + "d_h_a_angle_cutoff": 120.0, } u = universe.copy() n_residues = 2 - u.add_TopologyAttr('mass', [15.999, 1.008, 1.008] * n_residues) - u.add_TopologyAttr('charge', [-1.04, 0.52, 0.52] * n_residues) + u.add_TopologyAttr("mass", [15.999, 1.008, 1.008] * n_residues) + u.add_TopologyAttr("charge", [-1.04, 0.52, 0.52] * n_residues) with pytest.raises(NoDataError, match="no bond information"): h = HydrogenBondAnalysis(u, **kwargs) def test_no_bond_donor_sel(self, universe): kwargs = { - 'donors_sel': "type O", - 'hydrogens_sel': None, - 'acceptors_sel': None, - 'd_h_cutoff': 1.2, - 'd_a_cutoff': 3.0, - 'd_h_a_angle_cutoff': 120.0 + "donors_sel": "type O", + "hydrogens_sel": None, + "acceptors_sel": None, + "d_h_cutoff": 1.2, + "d_a_cutoff": 3.0, + "d_h_a_angle_cutoff": 120.0, } u = universe.copy() n_residues = 2 - u.add_TopologyAttr('mass', [15.999, 1.008, 1.008] * n_residues) - u.add_TopologyAttr('charge', [-1.04, 0.52, 0.52] * n_residues) + u.add_TopologyAttr("mass", [15.999, 1.008, 1.008] * n_residues) + u.add_TopologyAttr("charge", [-1.04, 0.52, 0.52] * n_residues) h = HydrogenBondAnalysis(u, **kwargs) donors = u.select_atoms(h.guess_donors()) def test_first_hbond(self, hydrogen_bonds): assert len(hydrogen_bonds.results.hbonds) == 2 - frame_no, donor_index, hydrogen_index, acceptor_index, da_dst, angle =\ - hydrogen_bonds.results.hbonds[0] + ( + frame_no, + donor_index, + hydrogen_index, + acceptor_index, + da_dst, + angle, + ) = hydrogen_bonds.results.hbonds[0] assert_equal(donor_index, 0) assert_equal(hydrogen_index, 2) assert_equal(acceptor_index, 3) @@ -243,7 +261,7 @@ def test_first_hbond(self, hydrogen_bonds): assert_almost_equal(angle, 180) def test_count_by_time(self, hydrogen_bonds): - ref_times = np.array([0, 1, 2]) # u.trajectory.dt is 1 + ref_times = np.array([0, 1, 2]) # u.trajectory.dt is 1 ref_counts = np.array([1, 0, 1]) counts = hydrogen_bonds.count_by_time() @@ -266,8 +284,9 @@ def test_no_attr_hbonds(self, universe): with pytest.raises(NoDataError, match=".hbonds attribute is None"): hbonds.lifetime(tau_max=2, intermittency=1) - def test_logging_step_not_1(self, universe, caplog, - client_HydrogenBondAnalysis): + def test_logging_step_not_1( + self, universe, caplog, client_HydrogenBondAnalysis + ): hbonds = HydrogenBondAnalysis(universe, **self.kwargs) # using step 2 hbonds.run(**client_HydrogenBondAnalysis, step=2) @@ -275,24 +294,25 @@ def test_logging_step_not_1(self, universe, caplog, caplog.set_level(logging.WARNING) hbonds.lifetime(tau_max=2, intermittency=1) - warning = ("Autocorrelation: Hydrogen bonds were computed with " - "step > 1.") + warning = ( + "Autocorrelation: Hydrogen bonds were computed with " "step > 1." + ) assert any(warning in rec.getMessage() for rec in caplog.records) class TestHydrogenBondAnalysisNoRes(TestHydrogenBondAnalysisIdeal): kwargs = { - 'donors_sel': 'type O', -# 'hydrogens_sel': 'type H H', - 'acceptors_sel': 'type O', - 'd_h_cutoff': 1.2, - 'd_a_cutoff': 3.0, - 'd_h_a_angle_cutoff': 120.0 + "donors_sel": "type O", + # 'hydrogens_sel': 'type H H', + "acceptors_sel": "type O", + "d_h_cutoff": 1.2, + "d_a_cutoff": 3.0, + "d_h_a_angle_cutoff": 120.0, } @staticmethod - @pytest.fixture(scope='class', autouse=True) + @pytest.fixture(scope="class", autouse=True) def universe(): # create two water molecules """ @@ -304,52 +324,55 @@ def universe(): """ n_residues = 2 u = MDAnalysis.Universe.empty( - n_atoms=n_residues*3, + n_atoms=n_residues * 3, n_residues=n_residues, atom_resindex=np.repeat(range(n_residues), 3), residue_segindex=[0] * n_residues, trajectory=True, # necessary for adding coordinates - ) + ) - u.add_TopologyAttr('type', ['O', 'H', 'H'] * n_residues) - u.add_TopologyAttr('id', list(range(1, (n_residues * 3) + 1))) - u.add_TopologyAttr('mass', [15.999, 1.008, 1.008] * n_residues) - u.add_TopologyAttr('charge', [-1.04, 0.52, 0.52] * n_residues) + u.add_TopologyAttr("type", ["O", "H", "H"] * n_residues) + u.add_TopologyAttr("id", list(range(1, (n_residues * 3) + 1))) + u.add_TopologyAttr("mass", [15.999, 1.008, 1.008] * n_residues) + u.add_TopologyAttr("charge", [-1.04, 0.52, 0.52] * n_residues) # Atomic coordinates with a single hydrogen bond between O1-H2---O2 - pos1 = np.array([[0, 0, 0], # O1 - [-0.249, -0.968, 0], # H1 - [1, 0, 0], # H2 - [2.5, 0, 0], # O2 - [3., 0, 0], # H3 - [2.250, 0.968, 0] # H4 - ]) + pos1 = np.array( + [ + [0, 0, 0], # O1 + [-0.249, -0.968, 0], # H1 + [1, 0, 0], # H2 + [2.5, 0, 0], # O2 + [3.0, 0, 0], # H3 + [2.250, 0.968, 0], # H4 + ] + ) # Atomic coordinates with no hydrogen bonds - pos2 = np.array([[0, 0, 0], # O1 - [-0.249, -0.968, 0], # H1 - [1, 0, 0], # H2 - [4.5, 0, 0], # O2 - [5., 0, 0], # H3 - [4.250, 0.968, 0] # H4 - ]) - - coordinates = np.empty((3, # number of frames - u.atoms.n_atoms, - 3)) + pos2 = np.array( + [ + [0, 0, 0], # O1 + [-0.249, -0.968, 0], # H1 + [1, 0, 0], # H2 + [4.5, 0, 0], # O2 + [5.0, 0, 0], # H3 + [4.250, 0.968, 0], # H4 + ] + ) + + coordinates = np.empty((3, u.atoms.n_atoms, 3)) # number of frames coordinates[0] = pos1 coordinates[1] = pos2 coordinates[2] = pos1 - u.load_new(coordinates, order='fac') + u.load_new(coordinates, order="fac") return u @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def hydrogen_bonds(universe, client_HydrogenBondAnalysis): h = HydrogenBondAnalysis( - universe, - **TestHydrogenBondAnalysisNoRes.kwargs + universe, **TestHydrogenBondAnalysisNoRes.kwargs ) h.run(**client_HydrogenBondAnalysis) return h @@ -359,26 +382,30 @@ def test_no_hydrogen_bonds(self, universe): tmp_kwargs["d_h_a_angle_cutoff"] = 50 hbonds = HydrogenBondAnalysis(universe, **tmp_kwargs) - with pytest.warns(UserWarning, - match=("No hydrogen bonds were found given angle " - "of 50 between Donor, type O, and Acceptor," - " type O.")): + with pytest.warns( + UserWarning, + match=( + "No hydrogen bonds were found given angle " + "of 50 between Donor, type O, and Acceptor," + " type O." + ), + ): hbonds.run(step=1) class TestHydrogenBondAnalysisBetween(object): kwargs = { - 'donors_sel': 'name O P', - 'hydrogens_sel': 'name H1 H2 PH', - 'acceptors_sel': 'name O P', - 'd_h_cutoff': 1.2, - 'd_a_cutoff': 3.0, - 'd_h_a_angle_cutoff': 120.0 + "donors_sel": "name O P", + "hydrogens_sel": "name H1 H2 PH", + "acceptors_sel": "name O P", + "d_h_cutoff": 1.2, + "d_a_cutoff": 3.0, + "d_h_a_angle_cutoff": 120.0, } @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe(): # create two water molecules and two "protein" molecules # P1-PH1 are the two atoms that comprise the toy protein PROT1 @@ -393,55 +420,52 @@ def universe(): n_sol_residues = 2 n_prot_residues = 2 u = MDAnalysis.Universe.empty( - n_atoms=n_sol_residues*3 + n_prot_residues*2, + n_atoms=n_sol_residues * 3 + n_prot_residues * 2, n_residues=n_residues, atom_resindex=[0, 0, 0, 1, 1, 1, 2, 2, 3, 3], residue_segindex=[0, 0, 1, 1], trajectory=True, # necessary for adding coordinates - ) - - u.add_TopologyAttr( - 'name', - ['O', 'H1', 'H2'] * n_sol_residues + ['P', 'PH'] * n_prot_residues ) + u.add_TopologyAttr( - 'type', - ['O', 'H', 'H'] * n_sol_residues + ['P', 'PH'] * n_prot_residues + "name", + ["O", "H1", "H2"] * n_sol_residues + ["P", "PH"] * n_prot_residues, ) u.add_TopologyAttr( - 'resname', - ['SOL'] * n_sol_residues + ['PROT'] * n_prot_residues + "type", + ["O", "H", "H"] * n_sol_residues + ["P", "PH"] * n_prot_residues, ) u.add_TopologyAttr( - 'resid', - list(range(1, n_residues + 1)) + "resname", ["SOL"] * n_sol_residues + ["PROT"] * n_prot_residues ) + u.add_TopologyAttr("resid", list(range(1, n_residues + 1))) u.add_TopologyAttr( - 'id', - list(range(1, (n_sol_residues * 3 + n_prot_residues * 2) + 1)) + "id", + list(range(1, (n_sol_residues * 3 + n_prot_residues * 2) + 1)), ) # Atomic coordinates with hydrogen bonds between: # O1-H2---O2 # O2-H3---P1 # P1-PH1---P2 - pos = np.array([[0, 0, 0], # O1 - [-0.249, -0.968, 0], # H1 - [1, 0, 0], # H2 - [2.5, 0, 0], # O2 - [3., 0, 0], # H3 - [2.250, 0.968, 0], # H4 - [5.5, 0, 0], # P1 - [6.5, 0, 0], # PH1 - [8.5, 0, 0], # P2 - [9.5, 0, 0], # PH2 - ]) - - coordinates = np.empty((1, # number of frames - u.atoms.n_atoms, - 3)) + pos = np.array( + [ + [0, 0, 0], # O1 + [-0.249, -0.968, 0], # H1 + [1, 0, 0], # H2 + [2.5, 0, 0], # O2 + [3.0, 0, 0], # H3 + [2.250, 0.968, 0], # H4 + [5.5, 0, 0], # P1 + [6.5, 0, 0], # PH1 + [8.5, 0, 0], # P2 + [9.5, 0, 0], # PH2 + ] + ) + + coordinates = np.empty((1, u.atoms.n_atoms, 3)) # number of frames coordinates[0] = pos - u.load_new(coordinates, order='fac') + u.load_new(coordinates, order="fac") return u @@ -454,29 +478,27 @@ def test_between_all(self, universe, client_HydrogenBondAnalysis): expected_hbond_indices = [ [0, 2, 3], # water-water [3, 4, 6], # protein-water - [6, 7, 8] # protein-protein + [6, 7, 8], # protein-protein ] expected_hbond_distances = [2.5, 3.0, 3.0] - assert_array_equal(hbonds.results.hbonds[:, 1:4], - expected_hbond_indices) + assert_array_equal( + hbonds.results.hbonds[:, 1:4], expected_hbond_indices + ) assert_allclose(hbonds.results.hbonds[:, 4], expected_hbond_distances) def test_between_PW(self, universe, client_HydrogenBondAnalysis): # Find only protein-water hydrogen bonds hbonds = HydrogenBondAnalysis( - universe, - between=["resname PROT", "resname SOL"], - **self.kwargs + universe, between=["resname PROT", "resname SOL"], **self.kwargs ) hbonds.run(**client_HydrogenBondAnalysis) # indices of [donor, hydrogen, acceptor] for each hydrogen bond - expected_hbond_indices = [ - [3, 4, 6] # protein-water - ] + expected_hbond_indices = [[3, 4, 6]] # protein-water expected_hbond_distances = [3.0] - assert_array_equal(hbonds.results.hbonds[:, 1:4], - expected_hbond_indices) + assert_array_equal( + hbonds.results.hbonds[:, 1:4], expected_hbond_indices + ) assert_allclose(hbonds.results.hbonds[:, 4], expected_hbond_distances) def test_between_PW_PP(self, universe, client_HydrogenBondAnalysis): @@ -486,42 +508,46 @@ def test_between_PW_PP(self, universe, client_HydrogenBondAnalysis): universe, between=[ ["resname PROT", "resname SOL"], - ["resname PROT", "resname PROT"] + ["resname PROT", "resname PROT"], ], - **self.kwargs + **self.kwargs, ) hbonds.run(**client_HydrogenBondAnalysis) # indices of [donor, hydrogen, acceptor] for each hydrogen bond expected_hbond_indices = [ [3, 4, 6], # protein-water - [6, 7, 8] # protein-protein + [6, 7, 8], # protein-protein ] expected_hbond_distances = [3.0, 3.0] - assert_array_equal(hbonds.results.hbonds[:, 1:4], - expected_hbond_indices) + assert_array_equal( + hbonds.results.hbonds[:, 1:4], expected_hbond_indices + ) assert_allclose(hbonds.results.hbonds[:, 4], expected_hbond_distances) -class TestHydrogenBondAnalysisTIP3P_GuessAcceptors_GuessHydrogens_UseTopology_(TestHydrogenBondAnalysisTIP3P): +class TestHydrogenBondAnalysisTIP3P_GuessAcceptors_GuessHydrogens_UseTopology_( + TestHydrogenBondAnalysisTIP3P +): """Uses the same distance and cutoff hydrogen bond criteria as :class:`TestHydrogenBondAnalysisTIP3P`, so the results are identical, but the hydrogens and acceptors are guessed whilst the donor-hydrogen pairs are determined via the topology. """ + kwargs = { - 'donors_sel': None, - 'hydrogens_sel': None, - 'acceptors_sel': None, - 'd_a_cutoff': 3.0, - 'd_h_a_angle_cutoff': 120.0 + "donors_sel": None, + "hydrogens_sel": None, + "acceptors_sel": None, + "d_a_cutoff": 3.0, + "d_h_a_angle_cutoff": 120.0, } def test_no_hydrogens(self, universe, client_HydrogenBondAnalysis): # If no hydrogens are identified at a given frame, check an # empty donor atom group is created test_kwargs = TestHydrogenBondAnalysisTIP3P.kwargs.copy() - test_kwargs['donors_sel'] = None # use topology to find pairs - test_kwargs['hydrogens_sel'] = "name H" # no atoms have name H + test_kwargs["donors_sel"] = None # use topology to find pairs + test_kwargs["hydrogens_sel"] = "name H" # no atoms have name H h = HydrogenBondAnalysis(universe, **test_kwargs) h.run(**client_HydrogenBondAnalysis) @@ -532,24 +558,23 @@ def test_no_hydrogens(self, universe, client_HydrogenBondAnalysis): class TestHydrogenBondAnalysisTIP3P_GuessDonors_NoTopology(object): - """Guess the donor atoms involved in hydrogen bonds using the partial charges of the atoms. - """ + """Guess the donor atoms involved in hydrogen bonds using the partial charges of the atoms.""" @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe(): return MDAnalysis.Universe(waterPSF, waterDCD) kwargs = { - 'donors_sel': None, - 'hydrogens_sel': None, - 'acceptors_sel': None, - 'd_h_cutoff': 1.2, - 'd_a_cutoff': 3.0, - 'd_h_a_angle_cutoff': 120.0 + "donors_sel": None, + "hydrogens_sel": None, + "acceptors_sel": None, + "d_h_cutoff": 1.2, + "d_a_cutoff": 3.0, + "d_h_a_angle_cutoff": 120.0, } - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def h(self, universe): h = HydrogenBondAnalysis(universe, **self.kwargs) return h @@ -557,7 +582,7 @@ def h(self, universe): def test_guess_donors(self, h): ref_donors = "(resname TIP3 and name OH2)" - donors = h.guess_donors(select='all', max_charge=-0.5) + donors = h.guess_donors(select="all", max_charge=-0.5) assert donors == ref_donors @@ -568,41 +593,40 @@ class TestHydrogenBondAnalysisTIP3P_GuessHydrogens_NoTopology(object): """ @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe(): return MDAnalysis.Universe(waterPSF, waterDCD) kwargs = { - 'donors_sel': None, - 'hydrogens_sel': None, - 'acceptors_sel': None, - 'd_h_cutoff': 1.2, - 'd_a_cutoff': 3.0, - 'd_h_a_angle_cutoff': 120.0 + "donors_sel": None, + "hydrogens_sel": None, + "acceptors_sel": None, + "d_h_cutoff": 1.2, + "d_a_cutoff": 3.0, + "d_h_a_angle_cutoff": 120.0, } - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def h(self, universe): h = HydrogenBondAnalysis(universe, **self.kwargs) return h def test_guess_hydrogens(self, h): - ref_hydrogens = "(resname TIP3 and name H1) or (resname TIP3 and name H2)" - hydrogens = h.guess_hydrogens(select='all') + ref_hydrogens = ( + "(resname TIP3 and name H1) or (resname TIP3 and name H2)" + ) + hydrogens = h.guess_hydrogens(select="all") assert hydrogens == ref_hydrogens pytest.mark.parametrize( "min_mass, max_mass, min_charge", - [ - (1.05, 1.10, 0.30), - (0.90, 0.95, 0.30), - (0.90, 1.10, 1.00) - ] + [(1.05, 1.10, 0.30), (0.90, 0.95, 0.30), (0.90, 1.10, 1.00)], ) + def test_guess_hydrogens_empty_selection(self, h): - hydrogens = h.guess_hydrogens(select='all', min_charge=1.0) + hydrogens = h.guess_hydrogens(select="all", min_charge=1.0) assert hydrogens == "" def test_guess_hydrogens_min_max_mass(self, h): @@ -611,7 +635,8 @@ def test_guess_hydrogens_min_max_mass(self, h): with pytest.raises(ValueError, match=errmsg): - h.guess_hydrogens(select='all', min_mass=1.1, max_mass=0.9) + h.guess_hydrogens(select="all", min_mass=1.1, max_mass=0.9) + class TestHydrogenBondAnalysisTIP3PStartStep(object): """Uses the same distance and cutoff hydrogen bond criteria as :class:`TestHydrogenBondAnalysisTIP3P` but starting @@ -619,20 +644,20 @@ class TestHydrogenBondAnalysisTIP3PStartStep(object): """ @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe(): return MDAnalysis.Universe(waterPSF, waterDCD) kwargs = { - 'donors_sel': 'name OH2', - 'hydrogens_sel': 'name H1 H2', - 'acceptors_sel': 'name OH2', - 'd_h_cutoff': 1.2, - 'd_a_cutoff': 3.0, - 'd_h_a_angle_cutoff': 120.0 + "donors_sel": "name OH2", + "hydrogens_sel": "name H1 H2", + "acceptors_sel": "name OH2", + "d_h_cutoff": 1.2, + "d_a_cutoff": 3.0, + "d_h_a_angle_cutoff": 120.0, } - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def h(self, universe, client_HydrogenBondAnalysis): h = HydrogenBondAnalysis(universe, **self.kwargs) h.run(**client_HydrogenBondAnalysis, start=1, step=2) @@ -644,22 +669,34 @@ def test_hbond_analysis(self, h): assert len(h.results.hbonds) == 15 reference = { - 'distance': {'mean': 2.73942464, 'std': 0.05867924}, - 'angle': {'mean': 157.07768079, 'std': 9.72636682}, + "distance": {"mean": 2.73942464, "std": 0.05867924}, + "angle": {"mean": 157.07768079, "std": 9.72636682}, } - assert_allclose(np.mean(h.results.hbonds[:, 4]), - reference['distance']['mean']) - assert_allclose(np.std(h.results.hbonds[:, 4]), - reference['distance']['std']) - assert_allclose(np.mean(h.results.hbonds[:, 5]), - reference['angle']['mean']) - assert_allclose(np.std(h.results.hbonds[:, 5]), - reference['angle']['std']) + assert_allclose( + np.mean(h.results.hbonds[:, 4]), reference["distance"]["mean"] + ) + assert_allclose( + np.std(h.results.hbonds[:, 4]), reference["distance"]["std"] + ) + assert_allclose( + np.mean(h.results.hbonds[:, 5]), reference["angle"]["mean"] + ) + assert_allclose( + np.std(h.results.hbonds[:, 5]), reference["angle"]["std"] + ) def test_count_by_time(self, h): - ref_times = np.array([0.04, 0.08, 0.12, 0.16, 0.20, ]) + ref_times = np.array( + [ + 0.04, + 0.08, + 0.12, + 0.16, + 0.20, + ] + ) ref_counts = np.array([2, 4, 4, 2, 3]) counts = h.count_by_time() @@ -678,29 +715,32 @@ def test_count_by_type(self, h): class TestHydrogenBondAnalysisEmptySelections: @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe(): return MDAnalysis.Universe(waterPSF, waterDCD) - msg = ("{} is an empty selection string - no hydrogen bonds will " - "be found. This may be intended, but please check your " - "selection." - ) + msg = ( + "{} is an empty selection string - no hydrogen bonds will " + "be found. This may be intended, but please check your " + "selection." + ) - @pytest.mark.parametrize('seltype', - ['donors_sel', 'hydrogens_sel', 'acceptors_sel']) + @pytest.mark.parametrize( + "seltype", ["donors_sel", "hydrogens_sel", "acceptors_sel"] + ) def test_empty_sel(self, universe, seltype): - sel_kwarg = {seltype: ' '} + sel_kwarg = {seltype: " "} with pytest.warns(UserWarning, match=self.msg.format(seltype)): HydrogenBondAnalysis(universe, **sel_kwarg) def test_hbond_analysis(self, universe, client_HydrogenBondAnalysis): - h = HydrogenBondAnalysis(universe, donors_sel=' ', hydrogens_sel=' ', - acceptors_sel=' ') + h = HydrogenBondAnalysis( + universe, donors_sel=" ", hydrogens_sel=" ", acceptors_sel=" " + ) h.run(**client_HydrogenBondAnalysis) - assert h.donors_sel == '' - assert h.hydrogens_sel == '' - assert h.acceptors_sel == '' + assert h.donors_sel == "" + assert h.hydrogens_sel == "" + assert h.acceptors_sel == "" assert h.results.hbonds.size == 0 diff --git a/testsuite/MDAnalysisTests/analysis/test_leaflet.py b/testsuite/MDAnalysisTests/analysis/test_leaflet.py index 0c4839f36b5..e968bbf8c9c 100644 --- a/testsuite/MDAnalysisTests/analysis/test_leaflet.py +++ b/testsuite/MDAnalysisTests/analysis/test_leaflet.py @@ -43,14 +43,14 @@ def lipid_heads(universe): return universe.select_atoms(LIPID_HEAD_STRING) -@pytest.mark.skipif(HAS_NX, reason='networkx is installed') +@pytest.mark.skipif(HAS_NX, reason="networkx is installed") def test_optional_nx(): errmsg = "The LeafletFinder class requires an installation of networkx" with pytest.raises(ImportError, match=errmsg): _ = LeafletFinder(universe, lipid_heads, pbc=True) -@pytest.mark.skipif(not HAS_NX, reason='needs networkx') +@pytest.mark.skipif(not HAS_NX, reason="needs networkx") class TestLeafletFinder: @staticmethod def lines2one(lines): @@ -61,13 +61,22 @@ def test_leaflet_finder(self, universe, lipid_heads): lfls = LeafletFinder(universe, lipid_heads, pbc=True) top_heads, bottom_heads = lfls.groups() # Make top be... on top. - if top_heads.center_of_geometry()[2] < bottom_heads.center_of_geometry()[2]: - top_heads,bottom_heads = (bottom_heads,top_heads) - assert_equal(top_heads.indices, np.arange(1,2150,12), - err_msg="Found wrong leaflet lipids") - assert_equal(bottom_heads.indices, np.arange(2521,4670,12), - err_msg="Found wrong leaflet lipids") - + if ( + top_heads.center_of_geometry()[2] + < bottom_heads.center_of_geometry()[2] + ): + top_heads, bottom_heads = (bottom_heads, top_heads) + assert_equal( + top_heads.indices, + np.arange(1, 2150, 12), + err_msg="Found wrong leaflet lipids", + ) + assert_equal( + bottom_heads.indices, + np.arange(2521, 4670, 12), + err_msg="Found wrong leaflet lipids", + ) + def test_string_vs_atomgroup_proper(self, universe, lipid_heads): lfls_ag = LeafletFinder(universe, lipid_heads, pbc=True) lfls_string = LeafletFinder(universe, LIPID_HEAD_STRING, pbc=True) @@ -75,52 +84,58 @@ def test_string_vs_atomgroup_proper(self, universe, lipid_heads): groups_string = lfls_string.groups() assert_equal(groups_string[0].indices, groups_ag[0].indices) assert_equal(groups_string[1].indices, groups_ag[1].indices) - + def test_optimize_cutoff(self, universe, lipid_heads): cutoff, N = optimize_cutoff(universe, lipid_heads, pbc=True) assert N == 2 assert_almost_equal(cutoff, 10.5, decimal=4) - + def test_pbc_on_off(self, universe, lipid_heads): lfls_pbc_on = LeafletFinder(universe, lipid_heads, pbc=True) lfls_pbc_off = LeafletFinder(universe, lipid_heads, pbc=False) assert lfls_pbc_on.graph.size() > lfls_pbc_off.graph.size() - + def test_pbc_on_off_difference(self, universe, lipid_heads): import networkx lfls_pbc_on = LeafletFinder(universe, lipid_heads, cutoff=7, pbc=True) - lfls_pbc_off = LeafletFinder(universe, lipid_heads, cutoff=7, pbc=False) + lfls_pbc_off = LeafletFinder( + universe, lipid_heads, cutoff=7, pbc=False + ) pbc_on_graph = lfls_pbc_on.graph pbc_off_graph = lfls_pbc_off.graph diff_graph = networkx.difference(pbc_on_graph, pbc_off_graph) - assert_equal(set(diff_graph.edges), {(69, 153), (73, 79), - (206, 317), (313, 319)}) - + assert_equal( + set(diff_graph.edges), + {(69, 153), (73, 79), (206, 317), (313, 319)}, + ) + @pytest.mark.parametrize("sparse", [True, False, None]) def test_sparse_on_off_none(self, universe, lipid_heads, sparse): - lfls_ag = LeafletFinder(universe, lipid_heads, cutoff=15.0, pbc=True, - sparse=sparse) + lfls_ag = LeafletFinder( + universe, lipid_heads, cutoff=15.0, pbc=True, sparse=sparse + ) assert_almost_equal(len(lfls_ag.graph.edges), 1903, decimal=4) - + def test_cutoff_update(self, universe, lipid_heads): lfls_ag = LeafletFinder(universe, lipid_heads, cutoff=15.0, pbc=True) lfls_ag.update(cutoff=1.0) assert_almost_equal(lfls_ag.cutoff, 1.0, decimal=4) assert_almost_equal(len(lfls_ag.groups()), 360, decimal=4) - + def test_cutoff_update_default(self, universe, lipid_heads): lfls_ag = LeafletFinder(universe, lipid_heads, cutoff=15.0, pbc=True) lfls_ag.update() assert_almost_equal(lfls_ag.cutoff, 15.0, decimal=4) assert_almost_equal(len(lfls_ag.groups()), 2, decimal=4) - + def test_write_selection(self, universe, lipid_heads, tmpdir): lfls_ag = LeafletFinder(universe, lipid_heads, cutoff=15.0, pbc=True) with tmpdir.as_cwd(): - filename = lfls_ag.write_selection('leaflet.vmd') - expected_output = self.lines2one([ - """# leaflets based on select= cutoff=15.000000 + filename = lfls_ag.write_selection("leaflet.vmd") + expected_output = self.lines2one( + [ + """# leaflets based on select= cutoff=15.000000 # MDAnalysis VMD selection atomselect macro leaflet_1 {index 1 13 25 37 49 61 73 85 \\ 97 109 121 133 145 157 169 181 \\ @@ -170,10 +185,17 @@ def test_write_selection(self, universe, lipid_heads, tmpdir): 4537 4549 4561 4573 4585 4597 4609 4621 \\ 4633 4645 4657 4669 } - """]) - - assert self.lines2one(open('leaflet.vmd').readlines()) == expected_output - + """ + ] + ) + + assert ( + self.lines2one(open("leaflet.vmd").readlines()) + == expected_output + ) + def test_component_index_is_not_none(self, universe, lipid_heads): lfls_ag = LeafletFinder(universe, lipid_heads, cutoff=15.0, pbc=True) - assert_almost_equal(len(lfls_ag.groups(component_index=0)), 180, decimal=4) + assert_almost_equal( + len(lfls_ag.groups(component_index=0)), 180, decimal=4 + ) diff --git a/testsuite/MDAnalysisTests/analysis/test_lineardensity.py b/testsuite/MDAnalysisTests/analysis/test_lineardensity.py index 2b6ce161cb6..9f5963938bb 100644 --- a/testsuite/MDAnalysisTests/analysis/test_lineardensity.py +++ b/testsuite/MDAnalysisTests/analysis/test_lineardensity.py @@ -35,7 +35,7 @@ def test_invalid_grouping(): """Invalid groupings raise AttributeError""" universe = mda.Universe(waterPSF, waterDCD) - sel_string = 'all' + sel_string = "all" selection = universe.select_atoms(sel_string) with pytest.raises(AttributeError): # centroid is attribute of AtomGroup, but not valid here @@ -44,55 +44,128 @@ def test_invalid_grouping(): # test data for grouping='atoms' -expected_masses_atoms = np.array([15.9994, 1.008, 1.008, 15.9994, 1.008, 1.008, - 15.9994, 1.008, 1.008, 15.9994, 1.008, 1.008, - 15.9994, 1.008, 1.008]) -expected_charges_atoms = np.array([-0.834, 0.417, 0.417, -0.834, 0.417, - 0.417, -0.834, 0.417, 0.417, -0.834, - 0.417, 0.417, -0.834, 0.417, 0.417]) -expected_xmass_atoms = np.array([0., 0., 0., 0.00723323, 0.00473288, 0., - 0., 0., 0., 0.]) -expected_xcharge_atoms = np.array([0., 0., 0., 2.21582311e-05, - -2.21582311e-05, 0., 0., 0., 0., 0.]) +expected_masses_atoms = np.array( + [ + 15.9994, + 1.008, + 1.008, + 15.9994, + 1.008, + 1.008, + 15.9994, + 1.008, + 1.008, + 15.9994, + 1.008, + 1.008, + 15.9994, + 1.008, + 1.008, + ] +) +expected_charges_atoms = np.array( + [ + -0.834, + 0.417, + 0.417, + -0.834, + 0.417, + 0.417, + -0.834, + 0.417, + 0.417, + -0.834, + 0.417, + 0.417, + -0.834, + 0.417, + 0.417, + ] +) +expected_xmass_atoms = np.array( + [0.0, 0.0, 0.0, 0.00723323, 0.00473288, 0.0, 0.0, 0.0, 0.0, 0.0] +) +expected_xcharge_atoms = np.array( + [0.0, 0.0, 0.0, 2.21582311e-05, -2.21582311e-05, 0.0, 0.0, 0.0, 0.0, 0.0] +) # test data for grouping='residues' -expected_masses_residues = np.array([18.0154, 18.0154, 18.0154, 18.0154, - 18.0154]) +expected_masses_residues = np.array( + [18.0154, 18.0154, 18.0154, 18.0154, 18.0154] +) expected_charges_residues = np.array([0, 0, 0, 0, 0]) -expected_xmass_residues = np.array([0., 0., 0., 0.00717967, 0.00478644, - 0., 0., 0., 0., 0.]) -expected_xcharge_residues = np.array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]) +expected_xmass_residues = np.array( + [0.0, 0.0, 0.0, 0.00717967, 0.00478644, 0.0, 0.0, 0.0, 0.0, 0.0] +) +expected_xcharge_residues = np.array( + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] +) # test data for grouping='segments' expected_masses_segments = np.array([90.0770]) expected_charges_segments = np.array([0]) -expected_xmass_segments = np.array([0., 0., 0., 0.01196611, 0., - 0., 0., 0., 0., 0.]) -expected_xcharge_segments = np.array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]) +expected_xmass_segments = np.array( + [0.0, 0.0, 0.0, 0.01196611, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] +) +expected_xcharge_segments = np.array( + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] +) # test data for grouping='fragments' -expected_masses_fragments = np.array([18.0154, 18.0154, 18.0154, 18.0154, - 18.0154]) +expected_masses_fragments = np.array( + [18.0154, 18.0154, 18.0154, 18.0154, 18.0154] +) expected_charges_fragments = np.array([0, 0, 0, 0, 0]) -expected_xmass_fragments = np.array([0., 0., 0., 0.00717967, 0.00478644, - 0., 0., 0., 0., 0.]) -expected_xcharge_fragments = np.array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]) - - -@pytest.mark.parametrize("grouping, expected_masses, expected_charges, expected_xmass, expected_xcharge", [ - ("atoms", expected_masses_atoms, expected_charges_atoms, - expected_xmass_atoms, expected_xcharge_atoms), - ("residues", expected_masses_residues, expected_charges_residues, - expected_xmass_residues, expected_xcharge_residues), - ("segments", expected_masses_segments, expected_charges_segments, - expected_xmass_segments, expected_xcharge_segments), - ("fragments", expected_masses_fragments, expected_charges_fragments, - expected_xmass_fragments, expected_xcharge_fragments) -]) -def test_lineardensity(grouping, expected_masses, expected_charges, - expected_xmass, expected_xcharge): +expected_xmass_fragments = np.array( + [0.0, 0.0, 0.0, 0.00717967, 0.00478644, 0.0, 0.0, 0.0, 0.0, 0.0] +) +expected_xcharge_fragments = np.array( + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] +) + + +@pytest.mark.parametrize( + "grouping, expected_masses, expected_charges, expected_xmass, expected_xcharge", + [ + ( + "atoms", + expected_masses_atoms, + expected_charges_atoms, + expected_xmass_atoms, + expected_xcharge_atoms, + ), + ( + "residues", + expected_masses_residues, + expected_charges_residues, + expected_xmass_residues, + expected_xcharge_residues, + ), + ( + "segments", + expected_masses_segments, + expected_charges_segments, + expected_xmass_segments, + expected_xcharge_segments, + ), + ( + "fragments", + expected_masses_fragments, + expected_charges_fragments, + expected_xmass_fragments, + expected_xcharge_fragments, + ), + ], +) +def test_lineardensity( + grouping, + expected_masses, + expected_charges, + expected_xmass, + expected_xcharge, +): universe = mda.Universe(waterPSF, waterDCD) - sel_string = 'all' + sel_string = "all" selection = universe.select_atoms(sel_string) ld = LinearDensity(selection, grouping, binsize=5).run() assert_allclose(ld.masses, expected_masses) @@ -105,26 +178,30 @@ def test_lineardensity(grouping, expected_masses, expected_charges, @pytest.fixture(scope="module") def testing_Universe(): """Generate a universe for testing whether LinearDensity works with - updating atom groups. Also used for parallel analysis test.""" + updating atom groups. Also used for parallel analysis test.""" n_atoms = 3 - u = mda.Universe.empty(n_atoms=n_atoms, - n_residues=n_atoms, - n_segments=n_atoms, - atom_resindex=np.arange(n_atoms), - residue_segindex=np.arange(n_atoms)) + u = mda.Universe.empty( + n_atoms=n_atoms, + n_residues=n_atoms, + n_segments=n_atoms, + atom_resindex=np.arange(n_atoms), + residue_segindex=np.arange(n_atoms), + ) for attr in ["charges", "masses"]: u.add_TopologyAttr(attr, values=np.ones(n_atoms)) - coords = np.array([ - [[1., 1., 1.], [1., 2., 1.], [2., 1., 1.]], - [[1., 1., 2.], [1., 2., 1.], [2., 1., 1.]], - [[1., 1., 3.], [1., 2., 1.], [2., 1., 1.]], - [[1., 1., 4.], [1., 2., 1.], [2., 1., 1.]], - [[1., 1., 5.], [1., 2., 1.], [2., 1., 1.]] - ]) + coords = np.array( + [ + [[1.0, 1.0, 1.0], [1.0, 2.0, 1.0], [2.0, 1.0, 1.0]], + [[1.0, 1.0, 2.0], [1.0, 2.0, 1.0], [2.0, 1.0, 1.0]], + [[1.0, 1.0, 3.0], [1.0, 2.0, 1.0], [2.0, 1.0, 1.0]], + [[1.0, 1.0, 4.0], [1.0, 2.0, 1.0], [2.0, 1.0, 1.0]], + [[1.0, 1.0, 5.0], [1.0, 2.0, 1.0], [2.0, 1.0, 1.0]], + ] + ) - u.trajectory = get_reader_for(coords)(coords, order='fac', n_atoms=n_atoms) + u.trajectory = get_reader_for(coords)(coords, order="fac", n_atoms=n_atoms) for ts in u.trajectory: ts.dimensions = np.array([2, 2, 6, 90, 90, 90]) @@ -133,7 +210,7 @@ def testing_Universe(): def test_updating_atomgroup(testing_Universe): - expected_z_pos = np.array([0., 0.91329641, 0.08302695, 0., 0., 0.]) + expected_z_pos = np.array([0.0, 0.91329641, 0.08302695, 0.0, 0.0, 0.0]) u = testing_Universe selection = u.select_atoms("prop z < 3", updating=True) ld = LinearDensity(selection, binsize=1).run() @@ -143,27 +220,31 @@ def test_updating_atomgroup(testing_Universe): assert_allclose(ld.results.x.hist_bin_edges, expected_bin_edges) -testdict = {"pos": "mass_density", - "pos_std": "mass_density_stddev", - "char": "charge_density", - "char_std": "charge_density_stddev"} +testdict = { + "pos": "mass_density", + "pos_std": "mass_density_stddev", + "char": "charge_density", + "char_std": "charge_density_stddev", +} # TODO: Remove in 3.0.0 def test_old_name_deprecations(): universe = mda.Universe(waterPSF, waterDCD) - sel_string = 'all' + sel_string = "all" selection = universe.select_atoms(sel_string) ld = LinearDensity(selection, binsize=5).run() with pytest.warns(DeprecationWarning): assert_allclose(ld.results.x.pos, ld.results.x.mass_density) assert_allclose(ld.results.x.pos_std, ld.results.x.mass_density_stddev) assert_allclose(ld.results.x.char, ld.results.x.charge_density) - assert_allclose(ld.results.x.char_std, - ld.results.x.charge_density_stddev) + assert_allclose( + ld.results.x.char_std, ld.results.x.charge_density_stddev + ) for key in testdict.keys(): - assert_allclose(ld.results["x"][key], - ld.results["x"][testdict[key]]) + assert_allclose( + ld.results["x"][key], ld.results["x"][testdict[key]] + ) # Check that no DeprecationWarning is raised with new attributes with no_deprecated_call(): @@ -185,8 +266,13 @@ def test_parallel_analysis(testing_Universe): ld1 = LinearDensity(selection1, binsize=1).run() ld2 = LinearDensity(selection2, binsize=1).run() ld_whole = LinearDensity(selection_whole, binsize=1).run() - with pytest.warns(DeprecationWarning, - match="`_add_other_results` is deprecated!"): + with pytest.warns( + DeprecationWarning, match="`_add_other_results` is deprecated!" + ): ld1._add_other_results(ld2) - assert_allclose(ld1.results.z.mass_density, ld_whole.results.z.mass_density) - assert_allclose(ld1.results.x.mass_density, ld_whole.results.x.mass_density) + assert_allclose( + ld1.results.z.mass_density, ld_whole.results.z.mass_density + ) + assert_allclose( + ld1.results.x.mass_density, ld_whole.results.x.mass_density + ) diff --git a/testsuite/MDAnalysisTests/analysis/test_msd.py b/testsuite/MDAnalysisTests/analysis/test_msd.py index 3b96e40c61a..757fe6552cf 100644 --- a/testsuite/MDAnalysisTests/analysis/test_msd.py +++ b/testsuite/MDAnalysisTests/analysis/test_msd.py @@ -25,7 +25,7 @@ from MDAnalysis.analysis.msd import EinsteinMSD as MSD import MDAnalysis as mda -from numpy.testing import (assert_almost_equal, assert_equal) +from numpy.testing import assert_almost_equal, assert_equal import numpy as np from MDAnalysisTests.datafiles import PSF, DCD, RANDOM_WALK, RANDOM_WALK_TOPO @@ -34,38 +34,38 @@ import pytest -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def SELECTION(): - selection = 'backbone and name CA and resid 1-10' + selection = "backbone and name CA and resid 1-10" return selection -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def u(): return mda.Universe(PSF, DCD) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def NSTEP(): nstep = 5000 return nstep -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def random_walk_u(): # 100x100 return mda.Universe(RANDOM_WALK_TOPO, RANDOM_WALK) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def msd(u, SELECTION): # non fft msd - m = MSD(u, SELECTION, msd_type='xyz', fft=False) + m = MSD(u, SELECTION, msd_type="xyz", fft=False) m.run() return m -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def step_traj(NSTEP): # constant velocity x = np.arange(NSTEP) traj = np.vstack([x, x, x]).T @@ -75,7 +75,7 @@ def step_traj(NSTEP): # constant velocity return u -@block_import('tidynamics') +@block_import("tidynamics") def test_notidynamics(u, SELECTION): with pytest.raises(ImportError, match="tidynamics was not found"): u = mda.Universe(PSF, DCD) @@ -86,7 +86,7 @@ def test_notidynamics(u, SELECTION): def characteristic_poly(n, d): # polynomial that describes unit step traj MSD x = np.arange(0, n) - y = d*x*x + y = d * x * x return y @@ -98,65 +98,86 @@ def test_selection_works(self, msd): def test_ag_accepted(self, u): ag = u.select_atoms("resid 1") - m = MSD(ag, msd_type='xyz', fft=False) + m = MSD(ag, msd_type="xyz", fft=False) def test_updating_ag_rejected(self, u): updating_ag = u.select_atoms("around 3.5 resid 1", updating=True) errmsg = "UpdatingAtomGroups are not valid" with pytest.raises(TypeError, match=errmsg): - m = MSD(updating_ag, msd_type='xyz', fft=False) + m = MSD(updating_ag, msd_type="xyz", fft=False) - @pytest.mark.parametrize('msdtype', ['foo', 'bar', 'yx', 'zyx']) + @pytest.mark.parametrize("msdtype", ["foo", "bar", "yx", "zyx"]) def test_msdtype_error(self, u, SELECTION, msdtype): errmsg = f"invalid msd_type: {msdtype}" with pytest.raises(ValueError, match=errmsg): m = MSD(u, SELECTION, msd_type=msdtype) - @pytest.mark.parametrize("dim, dim_factor", [ - ('xyz', 3), ('xy', 2), ('xz', 2), ('yz', 2), ('x', 1), ('y', 1), - ('z', 1) - ]) - def test_simple_step_traj_all_dims(self, step_traj, NSTEP, dim, - dim_factor): + @pytest.mark.parametrize( + "dim, dim_factor", + [ + ("xyz", 3), + ("xy", 2), + ("xz", 2), + ("yz", 2), + ("x", 1), + ("y", 1), + ("z", 1), + ], + ) + def test_simple_step_traj_all_dims( + self, step_traj, NSTEP, dim, dim_factor + ): # testing the "simple" algorithm on constant velocity trajectory # should fit the polynomial y=dim_factor*x**2 - m_simple = MSD(step_traj, 'all', msd_type=dim, fft=False) + m_simple = MSD(step_traj, "all", msd_type=dim, fft=False) m_simple.run() poly = characteristic_poly(NSTEP, dim_factor) assert_almost_equal(m_simple.results.timeseries, poly, decimal=4) - @pytest.mark.parametrize("dim, dim_factor", [ - ('xyz', 3), ('xy', 2), ('xz', 2), ('yz', 2), ('x', 1), ('y', 1), - ('z', 1) - ]) - def test_simple_start_stop_step_all_dims(self, step_traj, NSTEP, dim, - dim_factor): + @pytest.mark.parametrize( + "dim, dim_factor", + [ + ("xyz", 3), + ("xy", 2), + ("xz", 2), + ("yz", 2), + ("x", 1), + ("y", 1), + ("z", 1), + ], + ) + def test_simple_start_stop_step_all_dims( + self, step_traj, NSTEP, dim, dim_factor + ): # testing the "simple" algorithm on constant velocity trajectory # test start stop step is working correctly - m_simple = MSD(step_traj, 'all', msd_type=dim, fft=False) + m_simple = MSD(step_traj, "all", msd_type=dim, fft=False) m_simple.run(start=10, stop=1000, step=10) poly = characteristic_poly(NSTEP, dim_factor) # polynomial must take offset start into account - assert_almost_equal(m_simple.results.timeseries, poly[0:990:10], - decimal=4) + assert_almost_equal( + m_simple.results.timeseries, poly[0:990:10], decimal=4 + ) def test_random_walk_u_simple(self, random_walk_u): # regress against random_walk test data - msd_rw = MSD(random_walk_u, 'all', msd_type='xyz', fft=False) + msd_rw = MSD(random_walk_u, "all", msd_type="xyz", fft=False) msd_rw.run() norm = np.linalg.norm(msd_rw.results.timeseries) val = 3932.39927487146 assert_almost_equal(norm, val, decimal=5) -@pytest.mark.skipif(import_not_available("tidynamics"), - reason="Test skipped because tidynamics not found") +@pytest.mark.skipif( + import_not_available("tidynamics"), + reason="Test skipped because tidynamics not found", +) class TestMSDFFT(object): - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def msd_fft(self, u, SELECTION): # fft msd - m = MSD(u, SELECTION, msd_type='xyz', fft=True) + m = MSD(u, SELECTION, msd_type="xyz", fft=True) m.run() return m @@ -172,7 +193,7 @@ def test_fft_vs_simple_default_per_particle(self, msd, msd_fft): per_particle_fft = msd_fft.results.msds_by_particle assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) - @pytest.mark.parametrize("dim", ['xyz', 'xy', 'xz', 'yz', 'x', 'y', 'z']) + @pytest.mark.parametrize("dim", ["xyz", "xy", "xz", "yz", "x", "y", "z"]) def test_fft_vs_simple_all_dims(self, u, SELECTION, dim): # check fft and simple give same result for each dimensionality m_simple = MSD(u, SELECTION, msd_type=dim, fft=False) @@ -183,7 +204,7 @@ def test_fft_vs_simple_all_dims(self, u, SELECTION, dim): timeseries_fft = m_fft.results.timeseries assert_almost_equal(timeseries_simple, timeseries_fft, decimal=4) - @pytest.mark.parametrize("dim", ['xyz', 'xy', 'xz', 'yz', 'x', 'y', 'z']) + @pytest.mark.parametrize("dim", ["xyz", "xy", "xz", "yz", "x", "y", "z"]) def test_fft_vs_simple_all_dims_per_particle(self, u, SELECTION, dim): # check fft and simple give same result for each particle in each # dimension @@ -195,40 +216,58 @@ def test_fft_vs_simple_all_dims_per_particle(self, u, SELECTION, dim): per_particle_fft = m_fft.results.msds_by_particle assert_almost_equal(per_particle_simple, per_particle_fft, decimal=4) - @pytest.mark.parametrize("dim, dim_factor", [ - ('xyz', 3), ('xy', 2), ('xz', 2), ('yz', 2), ('x', 1), ('y', 1), - ('z', 1) - ]) + @pytest.mark.parametrize( + "dim, dim_factor", + [ + ("xyz", 3), + ("xy", 2), + ("xz", 2), + ("yz", 2), + ("x", 1), + ("y", 1), + ("z", 1), + ], + ) def test_fft_step_traj_all_dims(self, step_traj, NSTEP, dim, dim_factor): # testing the fft algorithm on constant velocity trajectory # this should fit the polynomial y=dim_factor*x**2 # fft based tests require a slight decrease in expected prescision # primarily due to roundoff in fft(ifft()) calls. # relative accuracy expected to be around ~1e-12 - m_simple = MSD(step_traj, 'all', msd_type=dim, fft=True) + m_simple = MSD(step_traj, "all", msd_type=dim, fft=True) m_simple.run() poly = characteristic_poly(NSTEP, dim_factor) # this was relaxed from decimal=4 for numpy=1.13 test assert_almost_equal(m_simple.results.timeseries, poly, decimal=3) - @pytest.mark.parametrize("dim, dim_factor", [( - 'xyz', 3), ('xy', 2), ('xz', 2), ('yz', 2), ('x', 1), ('y', 1), - ('z', 1) - ]) - def test_fft_start_stop_step_all_dims(self, step_traj, NSTEP, dim, - dim_factor): + @pytest.mark.parametrize( + "dim, dim_factor", + [ + ("xyz", 3), + ("xy", 2), + ("xz", 2), + ("yz", 2), + ("x", 1), + ("y", 1), + ("z", 1), + ], + ) + def test_fft_start_stop_step_all_dims( + self, step_traj, NSTEP, dim, dim_factor + ): # testing the fft algorithm on constant velocity trajectory # test start stop step is working correctly - m_simple = MSD(step_traj, 'all', msd_type=dim, fft=True) + m_simple = MSD(step_traj, "all", msd_type=dim, fft=True) m_simple.run(start=10, stop=1000, step=10) poly = characteristic_poly(NSTEP, dim_factor) # polynomial must take offset start into account - assert_almost_equal(m_simple.results.timeseries, poly[0:990:10], - decimal=3) + assert_almost_equal( + m_simple.results.timeseries, poly[0:990:10], decimal=3 + ) def test_random_walk_u_fft(self, random_walk_u): # regress against random_walk test data - msd_rw = MSD(random_walk_u, 'all', msd_type='xyz', fft=True) + msd_rw = MSD(random_walk_u, "all", msd_type="xyz", fft=True) msd_rw.run() norm = np.linalg.norm(msd_rw.results.timeseries) val = 3932.39927487146 diff --git a/testsuite/MDAnalysisTests/analysis/test_nucleicacids.py b/testsuite/MDAnalysisTests/analysis/test_nucleicacids.py index ce2ae5e4864..e695a605691 100644 --- a/testsuite/MDAnalysisTests/analysis/test_nucleicacids.py +++ b/testsuite/MDAnalysisTests/analysis/test_nucleicacids.py @@ -27,15 +27,19 @@ from pytest import approx -from MDAnalysis.analysis.nucleicacids import (NucPairDist, WatsonCrickDist, - MajorPairDist, MinorPairDist) +from MDAnalysis.analysis.nucleicacids import ( + NucPairDist, + WatsonCrickDist, + MajorPairDist, + MinorPairDist, +) from MDAnalysisTests.datafiles import RNA_PSF, RNA_PDB from MDAnalysis.core.groups import ResidueGroup -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def u(): return mda.Universe(RNA_PSF, RNA_PDB) @@ -51,10 +55,10 @@ def test_empty_ag_error(strand): strand2 = ResidueGroup([strand.residues[1]]) with pytest.raises(ValueError, match="returns an empty AtomGroup"): - NucPairDist.select_strand_atoms(strand1, strand2, 'UNK1', 'O2') + NucPairDist.select_strand_atoms(strand1, strand2, "UNK1", "O2") -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def wc_rna(strand, client_NucPairDist): strand1 = ResidueGroup([strand.residues[0], strand.residues[21]]) strand2 = ResidueGroup([strand.residues[1], strand.residues[22]]) diff --git a/testsuite/MDAnalysisTests/analysis/test_nuclinfo.py b/testsuite/MDAnalysisTests/analysis/test_nuclinfo.py index ea6c3e03fbf..61577599a00 100644 --- a/testsuite/MDAnalysisTests/analysis/test_nuclinfo.py +++ b/testsuite/MDAnalysisTests/analysis/test_nuclinfo.py @@ -30,147 +30,212 @@ ) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def u(): return mda.Universe(RNA_PSF, RNA_PDB) -@pytest.mark.parametrize('i, bp, seg1, seg2, expected_value', ( - ( 1, 2, 'RNAA', 'RNAA', 4.3874702), - (22, 23, 'RNAA', 'RNAA', 4.1716404), -)) +@pytest.mark.parametrize( + "i, bp, seg1, seg2, expected_value", + ( + (1, 2, "RNAA", "RNAA", 4.3874702), + (22, 23, "RNAA", "RNAA", 4.1716404), + ), +) def test_wc_pair(u, i, bp, seg1, seg2, expected_value): val = nuclinfo.wc_pair(u, i, bp, seg1=seg1, seg2=seg2) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('i, bp, seg1, seg2, expected_value', ( - ( 3, 17, 'RNAA', 'RNAA', 15.06506), - (20, 5, 'RNAA', 'RNAA', 3.219116), -)) +@pytest.mark.parametrize( + "i, bp, seg1, seg2, expected_value", + ( + (3, 17, "RNAA", "RNAA", 15.06506), + (20, 5, "RNAA", "RNAA", 3.219116), + ), +) def test_minor_pair(u, i, bp, seg1, seg2, expected_value): val = nuclinfo.minor_pair(u, i, bp, seg1=seg1, seg2=seg2) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('i, bp, seg1, seg2, expected_value', ( - (2, 12, 'RNAA', 'RNAA', 26.884272), - (5, 9, 'RNAA', 'RNAA', 13.578535), -)) +@pytest.mark.parametrize( + "i, bp, seg1, seg2, expected_value", + ( + (2, 12, "RNAA", "RNAA", 26.884272), + (5, 9, "RNAA", "RNAA", 13.578535), + ), +) def test_major_pair(u, i, bp, seg1, seg2, expected_value): val = nuclinfo.major_pair(u, i, bp, seg1=seg1, seg2=seg2) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('seg, i, expected_value', ( - ('RNAA', 9, 3.16497), - ('RNAA', 21, 22.07721), -)) +@pytest.mark.parametrize( + "seg, i, expected_value", + ( + ("RNAA", 9, 3.16497), + ("RNAA", 21, 22.07721), + ), +) def test_phase_cp(u, seg, i, expected_value): val = nuclinfo.phase_cp(u, seg=seg, i=i) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('seg, i, expected_value', ( - ('RNAA', 1, 359.57580), - ('RNAA', 11, 171.71645), -)) +@pytest.mark.parametrize( + "seg, i, expected_value", + ( + ("RNAA", 1, 359.57580), + ("RNAA", 11, 171.71645), + ), +) def test_phase_as(u, seg, i, expected_value): val = nuclinfo.phase_as(u, seg=seg, i=i) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('seg, i, expected_value', ( - ('RNAA', 5, [302.203802, 179.043077, 35.271411, 79.499729, 201.000393, - 282.14321 , 210.709327]), - ('RNAA', 21, [280.388619, 185.12919 , 56.616215, 64.87354 , 187.153367, - 279.340915, 215.332144]), -)) +@pytest.mark.parametrize( + "seg, i, expected_value", + ( + ( + "RNAA", + 5, + [ + 302.203802, + 179.043077, + 35.271411, + 79.499729, + 201.000393, + 282.14321, + 210.709327, + ], + ), + ( + "RNAA", + 21, + [ + 280.388619, + 185.12919, + 56.616215, + 64.87354, + 187.153367, + 279.340915, + 215.332144, + ], + ), + ), +) def test_tors(u, seg, i, expected_value): val = nuclinfo.tors(u, seg=seg, i=i) assert_allclose(val, expected_value, rtol=1e-03) -@pytest.mark.parametrize('seg, i, expected_value', ( - ('RNAA', 6, 279.15103), - ('RNAA', 18, 298.09936), -)) +@pytest.mark.parametrize( + "seg, i, expected_value", + ( + ("RNAA", 6, 279.15103), + ("RNAA", 18, 298.09936), + ), +) def test_tors_alpha(u, seg, i, expected_value): val = nuclinfo.tors_alpha(u, seg=seg, i=i) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('seg, i, expected_value', ( - ('RNAA', 7, 184.20501), - ('RNAA', 15, 169.70042), -)) +@pytest.mark.parametrize( + "seg, i, expected_value", + ( + ("RNAA", 7, 184.20501), + ("RNAA", 15, 169.70042), + ), +) def test_tors_beta(u, seg, i, expected_value): val = nuclinfo.tors_beta(u, seg=seg, i=i) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('seg, i, expected_value', ( - ('RNAA', 7, 52.72022), - ('RNAA', 15, 54.59684), -)) +@pytest.mark.parametrize( + "seg, i, expected_value", + ( + ("RNAA", 7, 52.72022), + ("RNAA", 15, 54.59684), + ), +) def test_tors_gamma(u, seg, i, expected_value): val = nuclinfo.tors_gamma(u, seg=seg, i=i) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('seg, i, expected_value', ( - ('RNAA', 7, 84.80554), - ('RNAA', 15, 82.00043), -)) +@pytest.mark.parametrize( + "seg, i, expected_value", + ( + ("RNAA", 7, 84.80554), + ("RNAA", 15, 82.00043), + ), +) def test_tors_delta(u, seg, i, expected_value): val = nuclinfo.tors_delta(u, seg=seg, i=i) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('seg, i, expected_value', ( - ('RNAA', 7, 200.40990), - ('RNAA', 15, 210.96953), -)) +@pytest.mark.parametrize( + "seg, i, expected_value", + ( + ("RNAA", 7, 200.40990), + ("RNAA", 15, 210.96953), + ), +) def test_tors_eps(u, seg, i, expected_value): val = nuclinfo.tors_eps(u, seg=seg, i=i) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('seg, i, expected_value', ( - ('RNAA', 7, 297.84736), - ('RNAA', 15, 330.24898) -)) +@pytest.mark.parametrize( + "seg, i, expected_value", (("RNAA", 7, 297.84736), ("RNAA", 15, 330.24898)) +) def test_tors_zeta(u, seg, i, expected_value): val = nuclinfo.tors_zeta(u, seg=seg, i=i) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('seg, i, expected_value', ( - ('RNAA', 1, 178.37435), - ('RNAA', 2, 202.03418), - ('RNAA', 7, 200.91674), - ('RNAA', 15, 209.32109), -)) +@pytest.mark.parametrize( + "seg, i, expected_value", + ( + ("RNAA", 1, 178.37435), + ("RNAA", 2, 202.03418), + ("RNAA", 7, 200.91674), + ("RNAA", 15, 209.32109), + ), +) def test_tors_chi(u, seg, i, expected_value): val = nuclinfo.tors_chi(u, seg=seg, i=i) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('seg, i, expected_value', ( - ('RNAA', 20, 103.07024), - ('RNAA', 5, 156.62223), - ('RNAA', 7 , 77.94538), - ('RNAA', 15, 130.18539), -)) +@pytest.mark.parametrize( + "seg, i, expected_value", + ( + ("RNAA", 20, 103.07024), + ("RNAA", 5, 156.62223), + ("RNAA", 7, 77.94538), + ("RNAA", 15, 130.18539), + ), +) def test_hydroxyl(u, seg, i, expected_value): val = nuclinfo.hydroxyl(u, seg=seg, i=i) assert_almost_equal(val, expected_value, decimal=3) -@pytest.mark.parametrize('bp1, bp2, i, seg1, seg2, seg3, expected_value', ( - (16, 2, 3, 'RNAA', 'RNAA', 'RNAA', 314.69804), - (8, 9, 10, 'RNAA', 'RNAA', 'RNAA', 34.50106), -)) -def test_pseudo_dihe_baseflip(u, bp1, bp2, i, seg1, seg2, seg3, expected_value): +@pytest.mark.parametrize( + "bp1, bp2, i, seg1, seg2, seg3, expected_value", + ( + (16, 2, 3, "RNAA", "RNAA", "RNAA", 314.69804), + (8, 9, 10, "RNAA", "RNAA", "RNAA", 34.50106), + ), +) +def test_pseudo_dihe_baseflip( + u, bp1, bp2, i, seg1, seg2, seg3, expected_value +): val = nuclinfo.pseudo_dihe_baseflip(u, bp1, bp2, i, seg1, seg2, seg3) assert_almost_equal(val, expected_value, decimal=3) diff --git a/testsuite/MDAnalysisTests/analysis/test_pca.py b/testsuite/MDAnalysisTests/analysis/test_pca.py index 19dca6cf3b0..663aa5e68ff 100644 --- a/testsuite/MDAnalysisTests/analysis/test_pca.py +++ b/testsuite/MDAnalysisTests/analysis/test_pca.py @@ -24,44 +24,58 @@ import MDAnalysis as mda from MDAnalysis.analysis import align import MDAnalysis.analysis.pca -from MDAnalysis.analysis.pca import (PCA, cosine_content, - rmsip, cumulative_overlap) +from MDAnalysis.analysis.pca import ( + PCA, + cosine_content, + rmsip, + cumulative_overlap, +) -from numpy.testing import (assert_almost_equal, assert_equal, - assert_array_almost_equal, assert_allclose,) +from numpy.testing import ( + assert_almost_equal, + assert_equal, + assert_array_almost_equal, + assert_allclose, +) -from MDAnalysisTests.datafiles import (PSF, DCD, RANDOM_WALK, RANDOM_WALK_TOPO, - waterPSF, waterDCD) +from MDAnalysisTests.datafiles import ( + PSF, + DCD, + RANDOM_WALK, + RANDOM_WALK_TOPO, + waterPSF, + waterDCD, +) import pytest -SELECTION = 'backbone and name CA and resid 1-10' +SELECTION = "backbone and name CA and resid 1-10" -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def u(): return mda.Universe(PSF, DCD) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def u_fresh(): # each test gets a fresh universe return mda.Universe(PSF, DCD) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def u_aligned(): u = mda.Universe(PSF, DCD, in_memory=True) align.AlignTraj(u, u, select=SELECTION).run() return u -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def pca(u): u.transfer_to_memory() return PCA(u, select=SELECTION).run() -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def pca_aligned(u): # run on a copy so positions in u are unchanged u_copy = u.copy() @@ -85,15 +99,17 @@ def test_cum_var(pca): def test_pcs(pca): - assert_equal(pca.results.p_components.shape, (pca._n_atoms * 3, - pca._n_atoms * 3)) + assert_equal( + pca.results.p_components.shape, (pca._n_atoms * 3, pca._n_atoms * 3) + ) def test_pcs_n_components(u): pca = PCA(u, select=SELECTION).run() - assert_equal(pca.n_components, pca._n_atoms*3) - assert_equal(pca.results.p_components.shape, (pca._n_atoms * 3, - pca._n_atoms * 3)) + assert_equal(pca.n_components, pca._n_atoms * 3) + assert_equal( + pca.results.p_components.shape, (pca._n_atoms * 3, pca._n_atoms * 3) + ) pca.n_components = 10 assert_equal(pca.n_components, 10) assert_equal(pca.results.p_components.shape, (pca._n_atoms * 3, 10)) @@ -102,27 +118,27 @@ def test_pcs_n_components(u): def test_different_steps(pca, u): atoms = u.select_atoms(SELECTION) dot = pca.transform(atoms, start=5, stop=7, step=1) - assert_equal(dot.shape, (2, atoms.n_atoms*3)) + assert_equal(dot.shape, (2, atoms.n_atoms * 3)) def test_transform_different_atoms(pca, u): - atoms = u.select_atoms('backbone and name N and resid 1-10') + atoms = u.select_atoms("backbone and name N and resid 1-10") with pytest.warns(UserWarning): pca.transform(atoms, start=5, stop=7, step=1) def test_transform_rerun(u): - atoms = u.select_atoms('bynum 1-10') + atoms = u.select_atoms("bynum 1-10") u.transfer_to_memory() - pca = PCA(u, select='bynum 1-10').run(stop=5) + pca = PCA(u, select="bynum 1-10").run(stop=5) dot = pca.transform(atoms) assert_equal(dot.shape, (98, atoms.n_atoms * 3)) def test_pca_not_run(u): - atoms = u.select_atoms('bynum 1-10') + atoms = u.select_atoms("bynum 1-10") u.transfer_to_memory() - pca = PCA(u, select='bynum 1-10') + pca = PCA(u, select="bynum 1-10") with pytest.raises(ValueError): dot = pca.transform(atoms, stop=5) @@ -137,7 +153,7 @@ def test_no_frames(u): def test_can_run_frames(u): atoms = u.select_atoms(SELECTION) u.transfer_to_memory() - PCA(u, select=SELECTION).run(frames=[0,1]) + PCA(u, select=SELECTION).run(frames=[0, 1]) def test_can_run_frames(u): @@ -168,37 +184,39 @@ def test_project_no_pca_run(u, pca): pca_class = PCA(u, select=SELECTION) with pytest.raises(ValueError) as exc: pca_class.project_single_frame() - assert 'Call run() on the PCA before projecting' in str(exc.value) + assert "Call run() on the PCA before projecting" in str(exc.value) def test_project_none_anchor(u, pca): - group = u.select_atoms('resnum 1') + group = u.select_atoms("resnum 1") with pytest.raises(ValueError) as exc: func = pca.project_single_frame(0, group=group, anchor=None) - assert ("'anchor' cannot be 'None'" + - " if 'group' is not 'None'") in str(exc.value) + assert ("'anchor' cannot be 'None'" + " if 'group' is not 'None'") in str( + exc.value + ) def test_project_more_anchor(u, pca): - group = u.select_atoms('resnum 1') + group = u.select_atoms("resnum 1") with pytest.raises(ValueError) as exc: - project = pca.project_single_frame(0, group=group, anchor='backbone') + project = pca.project_single_frame(0, group=group, anchor="backbone") assert "More than one 'anchor' found in residues" in str(exc.value) def test_project_less_anchor(u, pca): - group = u.select_atoms('all') + group = u.select_atoms("all") with pytest.raises(ValueError) as exc: - project = pca.project_single_frame(0, group=group, anchor='name CB') - assert ("Some residues in 'group'" + - " do not have an 'anchor'") in str(exc.value) + project = pca.project_single_frame(0, group=group, anchor="name CB") + assert ("Some residues in 'group'" + " do not have an 'anchor'") in str( + exc.value + ) def test_project_invalid_anchor(u): - pca = PCA(u, select='name CA').run() - group = u.select_atoms('all') + pca = PCA(u, select="name CA").run() + group = u.select_atoms("all") with pytest.raises(ValueError) as exc: - project = pca.project_single_frame(0, group=group, anchor='name N') + project = pca.project_single_frame(0, group=group, anchor="name N") assert "Some 'anchors' are not part of PCA class" in str(exc.value) @@ -227,8 +245,7 @@ def test_project_reconstruct_whole(u, u_fresh): @pytest.mark.parametrize( - ("n1", "n2"), - [(0, 0), (0, [0]), ([0, 1], [0, 1]), (0, 1), (1, 0)] + ("n1", "n2"), [(0, 0), (0, [0]), ([0, 1], [0, 1]), (0, 1), (1, 0)] ) def test_project_twice_projection(u_fresh, n1, n2): # Two succesive projections are applied. The second projection does nothing @@ -252,17 +269,14 @@ def test_project_twice_projection(u_fresh, n1, n2): def test_project_extrapolate_translation(u_fresh): # when the projection is extended to non-PCA atoms, # non-PCA atoms' coordinates will be conserved relative to the anchor atom - pca = PCA(u_fresh, select='resnum 1 and backbone').run() - sel = 'resnum 1 and name CA CB CG' + pca = PCA(u_fresh, select="resnum 1 and backbone").run() + sel = "resnum 1 and name CA CB CG" group = u_fresh.select_atoms(sel) - project = pca.project_single_frame(0, group=group, - anchor='name CA') + project = pca.project_single_frame(0, group=group, anchor="name CA") - distances_original = ( - mda.lib.distances.self_distance_array(group.positions) - ) - distances_new = ( - mda.lib.distances.self_distance_array(project(group).positions) + distances_original = mda.lib.distances.self_distance_array(group.positions) + distances_new = mda.lib.distances.self_distance_array( + project(group).positions ) assert_allclose(distances_original, distances_new, rtol=1e-05) @@ -273,7 +287,7 @@ def test_cosine_content(): pca_random = PCA(rand).run() dot = pca_random.transform(rand.atoms) content = cosine_content(dot, 0) - assert_almost_equal(content, .99, 1) + assert_almost_equal(content, 0.99, 1) def test_mean_shape(pca_aligned, u): @@ -285,19 +299,17 @@ def test_mean_shape(pca_aligned, u): def test_calculate_mean(pca_aligned, u, u_aligned): ag = u_aligned.select_atoms(SELECTION) coords = u_aligned.trajectory.coordinate_array[:, ag.ix] - assert_almost_equal(pca_aligned.mean, coords.mean( - axis=0), decimal=5) + assert_almost_equal(pca_aligned.mean, coords.mean(axis=0), decimal=5) def test_given_mean(pca, u): - pca = PCA(u, select=SELECTION, align=False, - mean=pca.mean).run() + pca = PCA(u, select=SELECTION, align=False, mean=pca.mean).run() assert_almost_equal(pca.cov, pca.cov, decimal=5) def test_wrong_num_given_mean(u): wrong_mean = [[0, 0, 0], [1, 1, 1]] - with pytest.raises(ValueError, match='Number of atoms in'): + with pytest.raises(ValueError, match="Number of atoms in"): pca = PCA(u, select=SELECTION, mean=wrong_mean).run() @@ -316,22 +328,24 @@ def test_pca_rmsip_self(pca): def test_rmsip_ortho(pca): - value = rmsip(pca.results.p_components[:, :10].T, - pca.results.p_components[:, 10:20].T) + value = rmsip( + pca.results.p_components[:, :10].T, + pca.results.p_components[:, 10:20].T, + ) assert_almost_equal(value, 0.0) def test_pytest_too_many_components(pca): with pytest.raises(ValueError) as exc: pca.rmsip(pca, n_components=(1, 2, 3)) - assert 'Too many values' in str(exc.value) + assert "Too many values" in str(exc.value) def test_asymmetric_rmsip(pca): a = pca.rmsip(pca, n_components=(10, 4)) b = pca.rmsip(pca, n_components=(4, 10)) - assert abs(a-b) > 0.1, 'RMSIP should be asymmetric' + assert abs(a - b) > 0.1, "RMSIP should be asymmetric" assert_almost_equal(b, 1.0) @@ -346,51 +360,47 @@ def test_cumulative_overlap_ortho(pca): assert_almost_equal(value, 0.0) -@pytest.mark.parametrize( - 'method', ['rmsip', - 'cumulative_overlap']) +@pytest.mark.parametrize("method", ["rmsip", "cumulative_overlap"]) def test_compare_not_run_other(u, pca, method): pca2 = PCA(u) func = getattr(pca, method) with pytest.raises(ValueError) as exc: func(pca2) - assert 'Call run()' in str(exc.value) + assert "Call run()" in str(exc.value) -@pytest.mark.parametrize( - 'method', ['rmsip', - 'cumulative_overlap']) +@pytest.mark.parametrize("method", ["rmsip", "cumulative_overlap"]) def test_compare_not_run_self(u, pca, method): pca2 = PCA(u) func = getattr(pca2, method) with pytest.raises(ValueError) as exc: func(pca) - assert 'Call run()' in str(exc.value) + assert "Call run()" in str(exc.value) -@pytest.mark.parametrize( - 'method', ['rmsip', - 'cumulative_overlap']) +@pytest.mark.parametrize("method", ["rmsip", "cumulative_overlap"]) def test_compare_wrong_class(u, pca, method): func = getattr(pca, method) with pytest.raises(ValueError) as exc: func(3) - assert 'must be another PCA class' in str(exc.value) + assert "must be another PCA class" in str(exc.value) -@pytest.mark.parametrize("attr", ("p_components", "variance", - "cumulated_variance")) +@pytest.mark.parametrize( + "attr", ("p_components", "variance", "cumulated_variance") +) def test_pca_attr_warning(u, attr): pca = PCA(u, select=SELECTION).run(stop=2) wmsg = f"The `{attr}` attribute was deprecated in MDAnalysis 2.0.0" with pytest.warns(DeprecationWarning, match=wmsg): getattr(pca, attr) is pca.results[attr] + @pytest.mark.parametrize( "classname,is_parallelizable", [ (MDAnalysis.analysis.pca.PCA, False), - ] + ], ) def test_class_is_parallelizable(classname, is_parallelizable): assert classname._analysis_algorithm_is_parallelizable == is_parallelizable @@ -399,9 +409,8 @@ def test_class_is_parallelizable(classname, is_parallelizable): @pytest.mark.parametrize( "classname,backends", [ - (MDAnalysis.analysis.pca.PCA, ('serial',)), - ] + (MDAnalysis.analysis.pca.PCA, ("serial",)), + ], ) def test_supported_backends(classname, backends): assert classname.get_supported_backends() == backends - diff --git a/testsuite/MDAnalysisTests/analysis/test_persistencelength.py b/testsuite/MDAnalysisTests/analysis/test_persistencelength.py index 02d90f290b3..95b0c882719 100644 --- a/testsuite/MDAnalysisTests/analysis/test_persistencelength.py +++ b/testsuite/MDAnalysisTests/analysis/test_persistencelength.py @@ -46,8 +46,7 @@ def u(): @staticmethod @pytest.fixture() def p(u): - ags = [r.atoms.select_atoms('name C* N*') - for r in u.residues] + ags = [r.atoms.select_atoms("name C* N*") for r in u.residues] p = polymer.PersistenceLength(ags) return p @@ -70,17 +69,19 @@ def test_lb(self, p_run): def test_fit(self, p_run): assert_almost_equal(p_run.results.lp, 6.504, 3) - assert len(p_run.results.fit) == len(p_run.results.bond_autocorrelation) + assert len(p_run.results.fit) == len( + p_run.results.bond_autocorrelation + ) def test_raise_NoDataError(self, p): - #Ensure that a NoDataError is raised if perform_fit() + # Ensure that a NoDataError is raised if perform_fit() # is called before the run() method of AnalysisBase with pytest.raises(NoDataError): p._perform_fit() def test_plot_ax_return(self, p_run): - '''Ensure that a matplotlib axis object is - returned when plot() is called.''' + """Ensure that a matplotlib axis object is + returned when plot() is called.""" actual = p_run.plot() expected = matplotlib.axes.Axes assert isinstance(actual, expected) @@ -126,7 +127,7 @@ def test_fit_noisy(self): class TestSortBackbone(object): @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def u(): return mda.Universe(TRZ_psf, TRZ) @@ -161,12 +162,12 @@ def test_branches(self, u): def test_circular(self): u = mda.Universe.empty(6, trajectory=True) # circular structure - bondlist = [(0, 1), (1, 2), (2, 3), - (3, 4), (4, 5), (5, 0)] + bondlist = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0)] u.add_TopologyAttr(Bonds(bondlist)) with pytest.raises(ValueError) as ex: polymer.sort_backbone(u.atoms) + assert 'cyclical' in str(ex.value) # tests for parallelization @@ -190,3 +191,5 @@ def test_class_is_parallelizable(classname, is_parallelizable): ) def test_supported_backends(classname, backends): assert classname.get_supported_backends() == backends + assert "cyclical" in str(ex.value) + diff --git a/testsuite/MDAnalysisTests/analysis/test_rdf.py b/testsuite/MDAnalysisTests/analysis/test_rdf.py index 90adef77e3a..d3507e56734 100644 --- a/testsuite/MDAnalysisTests/analysis/test_rdf.py +++ b/testsuite/MDAnalysisTests/analysis/test_rdf.py @@ -33,7 +33,7 @@ @pytest.fixture() def u(): u = mda.Universe(two_water_gro, in_memory=True) - u.add_TopologyAttr('chainIDs', u.atoms.resids) + u.add_TopologyAttr("chainIDs", u.atoms.resids) return u @@ -43,8 +43,8 @@ def sels(u): # (NOTE: requires in-memory coordinates to make them permanent) for at, (x, y) in zip(u.atoms, zip([1] * 3 + [2] * 3, [2, 1, 3] * 2)): at.position = x, y, 0.0 - s1 = u.select_atoms('name OW') - s2 = u.select_atoms('name HW1 HW2') + s1 = u.select_atoms("name OW") + s2 = u.select_atoms("name HW1 HW2") return s1, s2 @@ -96,10 +96,9 @@ def test_exclusion(sels): assert rdf.results.count.sum() == 4 -@pytest.mark.parametrize("attr, count", [ - ("residue", 8), - ("segment", 0), - ("chain", 8)]) +@pytest.mark.parametrize( + "attr, count", [("residue", 8), ("segment", 0), ("chain", 8)] +) def test_ignore_same_residues(sels, attr, count): # should see two distances with 4 counts each s1, s2 = sels @@ -110,13 +109,18 @@ def test_ignore_same_residues(sels, attr, count): def test_ignore_same_residues_fails(sels): s1, s2 = sels - with pytest.raises(ValueError, match="The exclude_same argument to InterRDF must be"): + with pytest.raises( + ValueError, match="The exclude_same argument to InterRDF must be" + ): InterRDF(s2, s2, exclude_same="unsupported").run() - with pytest.raises(ValueError, match="The exclude_same argument to InterRDF cannot be used with"): + with pytest.raises( + ValueError, + match="The exclude_same argument to InterRDF cannot be used with", + ): InterRDF(s2, s2, exclude_same="residue", exclusion_block=tuple()).run() - - + + @pytest.mark.parametrize("attr", ("rdf", "bins", "edges", "count")) def test_rdf_attr_warning(sels, attr): s1, s2 = sels @@ -126,18 +130,18 @@ def test_rdf_attr_warning(sels, attr): getattr(rdf, attr) is rdf.results[attr] -@pytest.mark.parametrize("norm, value", [ - ("density", 1.956823), - ("rdf", 244602.88385), - ("none", 4)]) +@pytest.mark.parametrize( + "norm, value", [("density", 1.956823), ("rdf", 244602.88385), ("none", 4)] +) def test_norm(sels, norm, value): s1, s2 = sels rdf = InterRDF(s1, s2, norm=norm).run() assert_allclose(max(rdf.results.rdf), value) -@pytest.mark.parametrize("norm, norm_required", [ - ("Density", "density"), (None, "none")]) +@pytest.mark.parametrize( + "norm, norm_required", [("Density", "density"), (None, "none")] +) def test_norm_values(sels, norm, norm_required): s1, s2 = sels rdf = InterRDF(s1, s2, norm=norm).run() diff --git a/testsuite/MDAnalysisTests/analysis/test_rdf_s.py b/testsuite/MDAnalysisTests/analysis/test_rdf_s.py index 49e2a66bd5b..5c6e8b6031a 100644 --- a/testsuite/MDAnalysisTests/analysis/test_rdf_s.py +++ b/testsuite/MDAnalysisTests/analysis/test_rdf_s.py @@ -30,23 +30,24 @@ from MDAnalysisTests.datafiles import GRO_MEMPROT, XTC_MEMPROT -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def u(): return mda.Universe(GRO_MEMPROT, XTC_MEMPROT) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def sels(u): - s1 = u.select_atoms('name ZND and resid 289') + s1 = u.select_atoms("name ZND and resid 289") s2 = u.select_atoms( - '(name OD1 or name OD2) and resid 51 and sphzone 5.0 (resid 289)') - s3 = u.select_atoms('name ZND and (resid 291 or resid 292)') - s4 = u.select_atoms('(name OD1 or name OD2) and sphzone 5.0 (resid 291)') + "(name OD1 or name OD2) and resid 51 and sphzone 5.0 (resid 289)" + ) + s3 = u.select_atoms("name ZND and (resid 291 or resid 292)") + s4 = u.select_atoms("(name OD1 or name OD2) and sphzone 5.0 (resid 291)") ags = [[s1, s2], [s3, s4]] return ags -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def rdf(u, sels): return InterRDF_s(u, sels).run() @@ -100,22 +101,27 @@ def test_double_run(rdf): def test_cdf(rdf): rdf.get_cdf() - ref = rdf.results.count[0][0][0].sum()/rdf.n_frames + ref = rdf.results.count[0][0][0].sum() / rdf.n_frames assert rdf.results.cdf[0][0][0][-1] == ref -@pytest.mark.parametrize("density, value", [ - (None, 26551.55088100731), # default, like False (no kwarg, see below) - (False, 26551.55088100731), - (True, 0.021915460340071267)]) +@pytest.mark.parametrize( + "density, value", + [ + (None, 26551.55088100731), # default, like False (no kwarg, see below) + (False, 26551.55088100731), + (True, 0.021915460340071267), + ], +) def test_density(u, sels, density, value): - kwargs = {'density': density} if density is not None else {} + kwargs = {"density": density} if density is not None else {} rdf = InterRDF_s(u, sels, **kwargs).run() assert_almost_equal(max(rdf.results.rdf[0][0][0]), value) if not density: - s1 = u.select_atoms('name ZND and resid 289') + s1 = u.select_atoms("name ZND and resid 289") s2 = u.select_atoms( - 'name OD1 and resid 51 and sphzone 5.0 (resid 289)') + "name OD1 and resid 51 and sphzone 5.0 (resid 289)" + ) rdf_ref = InterRDF(s1, s2).run() assert_almost_equal(rdf_ref.results.rdf, rdf.results.rdf[0][0][0]) @@ -125,23 +131,29 @@ def test_overwrite_norm(u, sels): assert rdf.norm == "density" -@pytest.mark.parametrize("norm, value", [ - ("density", 0.021915460340071267), - ("rdf", 26551.55088100731), - ("none", 0.6)]) +@pytest.mark.parametrize( + "norm, value", + [ + ("density", 0.021915460340071267), + ("rdf", 26551.55088100731), + ("none", 0.6), + ], +) def test_norm(u, sels, norm, value): rdf = InterRDF_s(u, sels, norm=norm).run() assert_allclose(max(rdf.results.rdf[0][0][0]), value) if norm == "rdf": - s1 = u.select_atoms('name ZND and resid 289') + s1 = u.select_atoms("name ZND and resid 289") s2 = u.select_atoms( - 'name OD1 and resid 51 and sphzone 5.0 (resid 289)') + "name OD1 and resid 51 and sphzone 5.0 (resid 289)" + ) rdf_ref = InterRDF(s1, s2).run() assert_almost_equal(rdf_ref.results.rdf, rdf.results.rdf[0][0][0]) -@pytest.mark.parametrize("norm, norm_required", [ - ("Density", "density"), (None, "none")]) +@pytest.mark.parametrize( + "norm, norm_required", [("Density", "density"), (None, "none")] +) def test_norm_values(u, sels, norm, norm_required): rdf = InterRDF_s(u, sels, norm=norm).run() assert rdf.norm == norm_required diff --git a/testsuite/MDAnalysisTests/analysis/test_results.py b/testsuite/MDAnalysisTests/analysis/test_results.py index e3d8fa6ca95..adfbc4b5063 100644 --- a/testsuite/MDAnalysisTests/analysis/test_results.py +++ b/testsuite/MDAnalysisTests/analysis/test_results.py @@ -156,7 +156,9 @@ def merger(self): @pytest.mark.parametrize("n", [1, 2, 5, 14]) def test_all_results(self, results_0, results_1, merger, n): - objects = [obj for obj, _ in zip(cycle([results_0, results_1]), range(n))] + objects = [ + obj for obj, _ in zip(cycle([results_0, results_1]), range(n)) + ] arr = [i for _, i in zip(range(n), cycle([0, 1]))] answers = { @@ -169,14 +171,19 @@ def test_all_results(self, results_0, results_1, merger, n): results = merger.merge(objects) for attr, merged_value in results.items(): - assert_equal(merged_value, answers.get(attr), err_msg=f"{attr=}, {merged_value=}, {arr=}, {objects=}") + assert_equal( + merged_value, + answers.get(attr), + err_msg=f"{attr=}, {merged_value=}, {arr=}, {objects=}", + ) def test_missing_aggregator(self, results_0, results_1, merger): original_float_lookup = merger._lookup.get("float") merger._lookup["float"] = None - with pytest.raises(ValueError, - match="No aggregation function for key='float'"): + with pytest.raises( + ValueError, match="No aggregation function for key='float'" + ): merger.merge([results_0, results_1], require_all_aggregators=True) merger._lookup["float"] = original_float_lookup diff --git a/testsuite/MDAnalysisTests/analysis/test_rms.py b/testsuite/MDAnalysisTests/analysis/test_rms.py index deb46885c82..f39baa68f12 100644 --- a/testsuite/MDAnalysisTests/analysis/test_rms.py +++ b/testsuite/MDAnalysisTests/analysis/test_rms.py @@ -61,17 +61,20 @@ def u2(self): @pytest.fixture() def p_first(self, u): u.trajectory[0] - return u.select_atoms('protein') + return u.select_atoms("protein") @pytest.fixture() def p_last(self, u2): u2.trajectory[-1] - return u2.select_atoms('protein') + return u2.select_atoms("protein") # internal test def test_p_frames(self, p_first, p_last): # check that these fixtures are really different - assert p_first.universe.trajectory.ts.frame != p_last.universe.trajectory.ts.frame + assert ( + p_first.universe.trajectory.ts.frame + != p_last.universe.trajectory.ts.frame + ) assert not np.allclose(p_first.positions, p_last.positions) def test_no_center(self, a, b): @@ -83,14 +86,12 @@ def test_center(self, a, b): assert_almost_equal(rmsd, 0.0) def test_list(self, a, b): - rmsd = rms.rmsd(a.tolist(), - b.tolist(), - center=False) + rmsd = rms.rmsd(a.tolist(), b.tolist(), center=False) assert_almost_equal(rmsd, 1.0) - @pytest.mark.parametrize('dtype', [np.float32, np.float64]) + @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_superposition(self, a, b, u, dtype): - bb = u.atoms.select_atoms('backbone') + bb = u.atoms.select_atoms("backbone") a = bb.positions.copy() u.trajectory[-1] b = bb.positions.copy() @@ -110,20 +111,29 @@ def test_weights(self, a, b): def test_weights_and_superposition_1(self, u): weights = np.ones(len(u.trajectory[0])) - weighted = rms.rmsd(u.trajectory[0], u.trajectory[1], - weights=weights, superposition=True) - firstCoords = rms.rmsd(u.trajectory[0], u.trajectory[1], - superposition=True) + weighted = rms.rmsd( + u.trajectory[0], + u.trajectory[1], + weights=weights, + superposition=True, + ) + firstCoords = rms.rmsd( + u.trajectory[0], u.trajectory[1], superposition=True + ) assert_almost_equal(weighted, firstCoords, decimal=5) def test_weights_and_superposition_2(self, u): weights = np.zeros(len(u.trajectory[0])) weights[:100] = 1 - weighted = rms.rmsd(u.trajectory[0], u.trajectory[-1], - weights=weights, superposition=True) - firstCoords = rms.rmsd(u.trajectory[0][:100], - u.trajectory[-1][:100], - superposition=True) + weighted = rms.rmsd( + u.trajectory[0], + u.trajectory[-1], + weights=weights, + superposition=True, + ) + firstCoords = rms.rmsd( + u.trajectory[0][:100], u.trajectory[-1][:100], superposition=True + ) # very close to zero, change significant decimal places to 5 assert_almost_equal(weighted, firstCoords, decimal=5) @@ -162,7 +172,7 @@ def universe(self): @pytest.fixture() def outfile(self, tmpdir): - return os.path.join(str(tmpdir), 'rmsd.txt') + return os.path.join(str(tmpdir), "rmsd.txt") @pytest.fixture() def correct_values(self): @@ -178,13 +188,11 @@ def correct_values_mass_add_ten(self): @pytest.fixture() def correct_values_group(self): - return [[0, 1, 0, 0, 0], - [49, 50, 4.7857, 4.7048, 4.6924]] + return [[0, 1, 0, 0, 0], [49, 50, 4.7857, 4.7048, 4.6924]] @pytest.fixture() def correct_values_backbone_group(self): - return [[0, 1, 0, 0, 0], - [49, 50, 4.6997, 1.9154, 2.7139]] + return [[0, 1, 0, 0, 0], [49, 50, 4.6997, 1.9154, 2.7139]] def test_rmsd(self, universe, correct_values, client_RMSD): # client_RMSD is defined in testsuite/analysis/conftest.py @@ -192,197 +200,276 @@ def test_rmsd(self, universe, correct_values, client_RMSD): # collect all possible backends and reasonable number of workers # for a given AnalysisBase subclass, and extend the tests # to run with all of them. - RMSD = MDAnalysis.analysis.rms.RMSD(universe, select='name CA') + RMSD = MDAnalysis.analysis.rms.RMSD(universe, select="name CA") RMSD.run(step=49, **client_RMSD) - assert_almost_equal(RMSD.results.rmsd, correct_values, 4, - err_msg="error: rmsd profile should match" + - "test values") + assert_almost_equal( + RMSD.results.rmsd, + correct_values, + 4, + err_msg="error: rmsd profile should match" + "test values", + ) def test_rmsd_frames(self, universe, correct_values, client_RMSD): - RMSD = MDAnalysis.analysis.rms.RMSD(universe, select='name CA') + RMSD = MDAnalysis.analysis.rms.RMSD(universe, select="name CA") RMSD.run(frames=[0, 49], **client_RMSD) - assert_almost_equal(RMSD.results.rmsd, correct_values, 4, - err_msg="error: rmsd profile should match" + - "test values") - - def test_rmsd_unicode_selection(self, universe, correct_values, client_RMSD): - RMSD = MDAnalysis.analysis.rms.RMSD(universe, select=u'name CA') + assert_almost_equal( + RMSD.results.rmsd, + correct_values, + 4, + err_msg="error: rmsd profile should match" + "test values", + ) + + def test_rmsd_unicode_selection( + self, universe, correct_values, client_RMSD + ): + RMSD = MDAnalysis.analysis.rms.RMSD(universe, select="name CA") RMSD.run(step=49, **client_RMSD) - assert_almost_equal(RMSD.results.rmsd, correct_values, 4, - err_msg="error: rmsd profile should match" + - "test values") + assert_almost_equal( + RMSD.results.rmsd, + correct_values, + 4, + err_msg="error: rmsd profile should match" + "test values", + ) def test_rmsd_atomgroup_selections(self, universe, client_RMSD): # see Issue #1684 - R1 = MDAnalysis.analysis.rms.RMSD(universe.atoms, - select="resid 1-30").run(**client_RMSD) - R2 = MDAnalysis.analysis.rms.RMSD(universe.atoms.select_atoms("name CA"), - select="resid 1-30").run(**client_RMSD) + R1 = MDAnalysis.analysis.rms.RMSD( + universe.atoms, select="resid 1-30" + ).run(**client_RMSD) + R2 = MDAnalysis.analysis.rms.RMSD( + universe.atoms.select_atoms("name CA"), select="resid 1-30" + ).run(**client_RMSD) assert not np.allclose(R1.results.rmsd[:, 2], R2.results.rmsd[:, 2]) def test_rmsd_single_frame(self, universe, client_RMSD): - RMSD = MDAnalysis.analysis.rms.RMSD(universe, select='name CA', - ).run(start=5, stop=6, **client_RMSD) + RMSD = MDAnalysis.analysis.rms.RMSD( + universe, + select="name CA", + ).run(start=5, stop=6, **client_RMSD) single_frame = [[5, 6, 0.91544906]] - assert_almost_equal(RMSD.results.rmsd, single_frame, 4, - err_msg="error: rmsd profile should match" + - "test values") + assert_almost_equal( + RMSD.results.rmsd, + single_frame, + 4, + err_msg="error: rmsd profile should match" + "test values", + ) def test_mass_weighted(self, universe, correct_values, client_RMSD): # mass weighting the CA should give the same answer as weighing # equally because all CA have the same mass - RMSD = MDAnalysis.analysis.rms.RMSD(universe, select='name CA', - weights='mass').run(step=49, **client_RMSD) + RMSD = MDAnalysis.analysis.rms.RMSD( + universe, select="name CA", weights="mass" + ).run(step=49, **client_RMSD) - assert_almost_equal(RMSD.results.rmsd, correct_values, 4, - err_msg="error: rmsd profile should match" - "test values") + assert_almost_equal( + RMSD.results.rmsd, + correct_values, + 4, + err_msg="error: rmsd profile should match" "test values", + ) def test_custom_weighted(self, universe, correct_values_mass, client_RMSD): - RMSD = MDAnalysis.analysis.rms.RMSD(universe, weights="mass").run(step=49, **client_RMSD) + RMSD = MDAnalysis.analysis.rms.RMSD(universe, weights="mass").run( + step=49, **client_RMSD + ) - assert_almost_equal(RMSD.results.rmsd, correct_values_mass, 4, - err_msg="error: rmsd profile should match" - "test values") + assert_almost_equal( + RMSD.results.rmsd, + correct_values_mass, + 4, + err_msg="error: rmsd profile should match" "test values", + ) def test_weights_mass_is_mass_weighted(self, universe, client_RMSD): - RMSD_mass = MDAnalysis.analysis.rms.RMSD(universe, - weights="mass").run(step=49, **client_RMSD) - RMSD_cust = MDAnalysis.analysis.rms.RMSD(universe, - weights=universe.atoms.masses).run(step=49, **client_RMSD) - assert_almost_equal(RMSD_mass.results.rmsd, RMSD_cust.results.rmsd, 4, - err_msg="error: rmsd profiles should match for 'mass' " - "and universe.atoms.masses") - - def test_custom_weighted_list(self, universe, correct_values_mass, client_RMSD): + RMSD_mass = MDAnalysis.analysis.rms.RMSD(universe, weights="mass").run( + step=49, **client_RMSD + ) + RMSD_cust = MDAnalysis.analysis.rms.RMSD( + universe, weights=universe.atoms.masses + ).run(step=49, **client_RMSD) + assert_almost_equal( + RMSD_mass.results.rmsd, + RMSD_cust.results.rmsd, + 4, + err_msg="error: rmsd profiles should match for 'mass' " + "and universe.atoms.masses", + ) + + def test_custom_weighted_list( + self, universe, correct_values_mass, client_RMSD + ): weights = universe.atoms.masses - RMSD = MDAnalysis.analysis.rms.RMSD(universe, - weights=list(weights)).run(step=49, **client_RMSD) - assert_almost_equal(RMSD.results.rmsd, correct_values_mass, 4, - err_msg="error: rmsd profile should match" + - "test values") - - def test_custom_groupselection_weights_applied_1D_array(self, universe, client_RMSD): - RMSD = MDAnalysis.analysis.rms.RMSD(universe, - select='backbone', - groupselections=['name CA and resid 1-5', 'name CA and resid 1'], - weights=None, - weights_groupselections=[[1, 0, 0, 0, 0], None]).run(step=49, - **client_RMSD - ) - - assert_almost_equal(RMSD.results.rmsd.T[3], RMSD.results.rmsd.T[4], 4, - err_msg="error: rmsd profile should match " - "for applied weight array and selected resid") - - def test_custom_groupselection_weights_applied_mass(self, universe, correct_values_mass, client_RMSD): - RMSD = MDAnalysis.analysis.rms.RMSD(universe, - select='backbone', - groupselections=['all', 'all'], - weights=None, - weights_groupselections=['mass', - universe.atoms.masses]).run(step=49, - **client_RMSD - ) - - assert_almost_equal(RMSD.results.rmsd.T[3], RMSD.results.rmsd.T[4], 4, - err_msg="error: rmsd profile should match " - "between applied mass and universe.atoms.masses") + RMSD = MDAnalysis.analysis.rms.RMSD( + universe, weights=list(weights) + ).run(step=49, **client_RMSD) + assert_almost_equal( + RMSD.results.rmsd, + correct_values_mass, + 4, + err_msg="error: rmsd profile should match" + "test values", + ) + + def test_custom_groupselection_weights_applied_1D_array( + self, universe, client_RMSD + ): + RMSD = MDAnalysis.analysis.rms.RMSD( + universe, + select="backbone", + groupselections=["name CA and resid 1-5", "name CA and resid 1"], + weights=None, + weights_groupselections=[[1, 0, 0, 0, 0], None], + ).run(step=49, **client_RMSD) + + assert_almost_equal( + RMSD.results.rmsd.T[3], + RMSD.results.rmsd.T[4], + 4, + err_msg="error: rmsd profile should match " + "for applied weight array and selected resid", + ) + + def test_custom_groupselection_weights_applied_mass( + self, universe, correct_values_mass, client_RMSD + ): + RMSD = MDAnalysis.analysis.rms.RMSD( + universe, + select="backbone", + groupselections=["all", "all"], + weights=None, + weights_groupselections=["mass", universe.atoms.masses], + ).run(step=49, **client_RMSD) + + assert_almost_equal( + RMSD.results.rmsd.T[3], + RMSD.results.rmsd.T[4], + 4, + err_msg="error: rmsd profile should match " + "between applied mass and universe.atoms.masses", + ) def test_rmsd_scalar_weights_raises_ValueError(self, universe): with pytest.raises(ValueError): - RMSD = MDAnalysis.analysis.rms.RMSD( - universe, weights=42) + RMSD = MDAnalysis.analysis.rms.RMSD(universe, weights=42) def test_rmsd_string_weights_raises_ValueError(self, universe): with pytest.raises(ValueError): - RMSD = MDAnalysis.analysis.rms.RMSD( - universe, weights="Jabberwock") + RMSD = MDAnalysis.analysis.rms.RMSD(universe, weights="Jabberwock") def test_rmsd_mismatched_weights_raises_ValueError(self, universe): with pytest.raises(ValueError): RMSD = MDAnalysis.analysis.rms.RMSD( - universe, weights=universe.atoms.masses[:-1]) + universe, weights=universe.atoms.masses[:-1] + ) - def test_rmsd_misuse_weights_for_groupselection_raises_TypeError(self, universe): + def test_rmsd_misuse_weights_for_groupselection_raises_TypeError( + self, universe + ): with pytest.raises(TypeError): RMSD = MDAnalysis.analysis.rms.RMSD( - universe, groupselections=['all'], - weights=[universe.atoms.masses, universe.atoms.masses[:-1]]) - - def test_rmsd_mismatched_weights_in_groupselection_raises_ValueError(self, universe): + universe, + groupselections=["all"], + weights=[universe.atoms.masses, universe.atoms.masses[:-1]], + ) + + def test_rmsd_mismatched_weights_in_groupselection_raises_ValueError( + self, universe + ): with pytest.raises(ValueError): RMSD = MDAnalysis.analysis.rms.RMSD( - universe, groupselections=['all'], + universe, + groupselections=["all"], weights=universe.atoms.masses, - weights_groupselections = [universe.atoms.masses[:-1]]) + weights_groupselections=[universe.atoms.masses[:-1]], + ) def test_rmsd_list_of_weights_wrong_length(self, universe): with pytest.raises(ValueError): RMSD = MDAnalysis.analysis.rms.RMSD( - universe, groupselections=['backbone', 'name CA'], - weights='mass', - weights_groupselections=[None]) - - def test_rmsd_group_selections(self, universe, correct_values_group, client_RMSD): - RMSD = MDAnalysis.analysis.rms.RMSD(universe, - groupselections=['backbone', 'name CA'] - ).run(step=49, **client_RMSD) - assert_almost_equal(RMSD.results.rmsd, correct_values_group, 4, - err_msg="error: rmsd profile should match" - "test values") - - def test_rmsd_backbone_and_group_selection(self, universe, - correct_values_backbone_group, - client_RMSD): + universe, + groupselections=["backbone", "name CA"], + weights="mass", + weights_groupselections=[None], + ) + + def test_rmsd_group_selections( + self, universe, correct_values_group, client_RMSD + ): + RMSD = MDAnalysis.analysis.rms.RMSD( + universe, groupselections=["backbone", "name CA"] + ).run(step=49, **client_RMSD) + assert_almost_equal( + RMSD.results.rmsd, + correct_values_group, + 4, + err_msg="error: rmsd profile should match" "test values", + ) + + def test_rmsd_backbone_and_group_selection( + self, universe, correct_values_backbone_group, client_RMSD + ): RMSD = MDAnalysis.analysis.rms.RMSD( universe, reference=universe, select="backbone", - groupselections=['backbone and resid 1:10', - 'backbone and resid 10:20']).run(step=49, **client_RMSD) + groupselections=[ + "backbone and resid 1:10", + "backbone and resid 10:20", + ], + ).run(step=49, **client_RMSD) assert_almost_equal( - RMSD.results.rmsd, correct_values_backbone_group, 4, - err_msg="error: rmsd profile should match test values") + RMSD.results.rmsd, + correct_values_backbone_group, + 4, + err_msg="error: rmsd profile should match test values", + ) def test_ref_length_unequal_len(self, universe): reference = MDAnalysis.Universe(PSF, DCD) reference.atoms = reference.atoms[:-1] with pytest.raises(SelectionError): - RMSD = MDAnalysis.analysis.rms.RMSD(universe, - reference=reference) + RMSD = MDAnalysis.analysis.rms.RMSD(universe, reference=reference) def test_mass_mismatches(self, universe): reference = MDAnalysis.Universe(PSF, DCD) reference.atoms.masses = 10 with pytest.raises(SelectionError): - RMSD = MDAnalysis.analysis.rms.RMSD(universe, - reference=reference) + RMSD = MDAnalysis.analysis.rms.RMSD(universe, reference=reference) - def test_ref_mobile_mass_mismapped(self, universe,correct_values_mass_add_ten, client_RMSD): + def test_ref_mobile_mass_mismapped( + self, universe, correct_values_mass_add_ten, client_RMSD + ): reference = MDAnalysis.Universe(PSF, DCD) universe.atoms.masses = universe.atoms.masses + 10 - RMSD = MDAnalysis.analysis.rms.RMSD(universe, - reference=reference, - select='all', - weights='mass', - tol_mass=100) + RMSD = MDAnalysis.analysis.rms.RMSD( + universe, + reference=reference, + select="all", + weights="mass", + tol_mass=100, + ) RMSD.run(step=49, **client_RMSD) - assert_almost_equal(RMSD.results.rmsd, correct_values_mass_add_ten, 4, - err_msg="error: rmsd profile should match " - "between true values and calculated values") + assert_almost_equal( + RMSD.results.rmsd, + correct_values_mass_add_ten, + 4, + err_msg="error: rmsd profile should match " + "between true values and calculated values", + ) def test_group_selections_unequal_len(self, universe): reference = MDAnalysis.Universe(PSF, DCD) - reference.atoms[0].residue.resname = 'NOTMET' + reference.atoms[0].residue.resname = "NOTMET" with pytest.raises(SelectionError): - RMSD = MDAnalysis.analysis.rms.RMSD(universe, - reference=reference, - groupselections=['resname MET', 'type NH3']) + RMSD = MDAnalysis.analysis.rms.RMSD( + universe, + reference=reference, + groupselections=["resname MET", "type NH3"], + ) def test_rmsd_attr_warning(self, universe, client_RMSD): - RMSD = MDAnalysis.analysis.rms.RMSD( - universe, select='name CA').run(stop=2, **client_RMSD) + RMSD = MDAnalysis.analysis.rms.RMSD(universe, select="name CA").run( + stop=2, **client_RMSD + ) wmsg = "The `rmsd` attribute was deprecated in MDAnalysis 2.0.0" with pytest.warns(DeprecationWarning, match=wmsg): @@ -395,23 +482,29 @@ def universe(self): return mda.Universe(GRO, XTC) def test_rmsf(self, universe, client_RMSF): - rmsfs = rms.RMSF(universe.select_atoms('name CA')) + rmsfs = rms.RMSF(universe.select_atoms("name CA")) rmsfs.run(**client_RMSF) test_rmsfs = np.load(rmsfArray) - assert_almost_equal(rmsfs.results.rmsf, test_rmsfs, 5, - err_msg="error: rmsf profile should match test " - "values") + assert_almost_equal( + rmsfs.results.rmsf, + test_rmsfs, + 5, + err_msg="error: rmsf profile should match test " "values", + ) def test_rmsf_single_frame(self, universe, client_RMSF): - rmsfs = rms.RMSF(universe.select_atoms('name CA')).run(start=5, stop=6, **client_RMSF) + rmsfs = rms.RMSF(universe.select_atoms("name CA")).run( + start=5, stop=6, **client_RMSF + ) - assert_almost_equal(rmsfs.results.rmsf, 0, 5, - err_msg="error: rmsfs should all be zero") + assert_almost_equal( + rmsfs.results.rmsf, 0, 5, err_msg="error: rmsfs should all be zero" + ) def test_rmsf_identical_frames(self, universe, tmpdir, client_RMSF): - outfile = os.path.join(str(tmpdir), 'rmsf.xtc') + outfile = os.path.join(str(tmpdir), "rmsf.xtc") # write a dummy trajectory of all the same frame with mda.Writer(outfile, universe.atoms.n_atoms) as W: @@ -419,13 +512,16 @@ def test_rmsf_identical_frames(self, universe, tmpdir, client_RMSF): W.write(universe) universe = mda.Universe(GRO, outfile) - rmsfs = rms.RMSF(universe.select_atoms('name CA')) + rmsfs = rms.RMSF(universe.select_atoms("name CA")) rmsfs.run(**client_RMSF) - assert_almost_equal(rmsfs.results.rmsf, 0, 5, - err_msg="error: rmsfs should all be 0") + assert_almost_equal( + rmsfs.results.rmsf, 0, 5, err_msg="error: rmsfs should all be 0" + ) def test_rmsf_attr_warning(self, universe, client_RMSF): - rmsfs = rms.RMSF(universe.select_atoms('name CA')).run(stop=2, **client_RMSF) + rmsfs = rms.RMSF(universe.select_atoms("name CA")).run( + stop=2, **client_RMSF + ) wmsg = "The `rmsf` attribute was deprecated in MDAnalysis 2.0.0" with pytest.warns(DeprecationWarning, match=wmsg): @@ -437,7 +533,7 @@ def test_rmsf_attr_warning(self, universe, client_RMSF): [ (MDAnalysis.analysis.rms.RMSD, True), (MDAnalysis.analysis.rms.RMSF, False), - ] + ], ) def test_class_is_parallelizable(classname, is_parallelizable): assert classname._analysis_algorithm_is_parallelizable == is_parallelizable @@ -446,9 +542,16 @@ def test_class_is_parallelizable(classname, is_parallelizable): @pytest.mark.parametrize( "classname,backends", [ - (MDAnalysis.analysis.rms.RMSD, ('serial', 'multiprocessing', 'dask',)), - (MDAnalysis.analysis.rms.RMSF, ('serial',)), - ] + ( + MDAnalysis.analysis.rms.RMSD, + ( + "serial", + "multiprocessing", + "dask", + ), + ), + (MDAnalysis.analysis.rms.RMSF, ("serial",)), + ], ) def test_supported_backends(classname, backends): assert classname.get_supported_backends() == backends diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 39fe372409c..2b53d2f1d35 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -2,157 +2,160 @@ from collections import defaultdict from numpy.testing import ( - assert_equal, assert_array_equal,) + assert_equal, + assert_array_equal, +) import pytest import MDAnalysis from MDAnalysis.analysis.hydrogenbonds.wbridge_analysis import ( - WaterBridgeAnalysis, ) + WaterBridgeAnalysis, +) class TestWaterBridgeAnalysis(object): @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe_empty(): - '''A universe with no hydrogen bonds''' - grofile = '''Test gro file + """A universe with no hydrogen bonds""" + 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 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe_DA(): - '''A universe with one hydrogen bond acceptor bonding to a hydrogen bond - donor''' - grofile = '''Test gro file + """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 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') + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @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 + """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 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') + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe_AD(): - '''A universe with one hydrogen bond donor bonding to a hydrogen bond - acceptor''' - grofile = '''Test gro file + """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 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') + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @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 + """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 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') + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @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 + """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 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') + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @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 + """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 1ALA H 2 0.100 0.000 0.000 2SOL OW 3 0.300 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') + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe_AWA(): - '''A universe with two hydrogen bond acceptor are joined by a water''' - grofile = '''Test gro file + """A universe with two hydrogen bond acceptor are joined by a water""" + 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 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @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 + """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 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') + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @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 + """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 2SOL OW 2 0.300 0.000 0.000 @@ -161,16 +164,16 @@ def universe_AWWA(): 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') + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @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 + """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 2SOL OW 2 0.300 0.000 0.000 @@ -181,16 +184,16 @@ def universe_AWWWA(): 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') + 10.0 10.0 10.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @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 + """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 @@ -203,16 +206,16 @@ def universe_AWWWWA(): 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') + 10.0 10.0 10.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @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 + """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 2SOL OW 2 0.300 0.000 0.000 @@ -223,16 +226,16 @@ def universe_branch(): 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') + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @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 + """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 @@ -246,15 +249,15 @@ def universe_AWA_AWWA(): 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') + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") return u @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def wb_multiframe(): - '''A water bridge object with multipley frames''' - grofile = '''Test gro file + """A water bridge object with multipley frames""" + grofile = """Test gro file 13 1ALA O 1 0.000 0.000 0.000 1ALA H 2 0.000 0.000 0.000 @@ -269,113 +272,164 @@ def wb_multiframe(): 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) + 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.results.network = [] wb.results.network.append({(1, 0, 12, None, 2.0, 180.0): None}) wb.results.network.append({(0, None, 12, 13, 2.0, 180.0): None}) - wb.results.network.append({(1, 0, 3, None, 2.0, 180.0): - {(4, 2, 12, None, 2.0, 180.0): None}}) - wb.results.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.results.network.append( + {(1, 0, 3, None, 2.0, 180.0): {(4, 2, 12, None, 2.0, 180.0): None}} + ) + wb.results.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.results.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) + """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.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''' + """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: raise pytest.fail("selection_type aaa should rasie error") def test_distance_type_error(self, universe_DA): - '''Test the case when the wrong selection1_type is given''' - with pytest.raises(ValueError, match="Only 'hydrogen' and 'heavy' are allowed for option `distance_type'"): - WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', - 'protein and (resid 4)', order=0, - selection1_type='aaa', distance_type='aaa') + """Test the case when the wrong selection1_type is given""" + with pytest.raises( + ValueError, + match="Only 'hydrogen' and 'heavy' are allowed for option `distance_type'", + ): + WaterBridgeAnalysis( + universe_DA, + "protein and (resid 1)", + "protein and (resid 4)", + order=0, + selection1_type="aaa", + distance_type="aaa", + ) def test_selection2_type_error(self, universe_DA): - '''Test the case when the wrong selection1_type is given''' - with pytest.raises(ValueError, match="`selection2_type` is not a keyword argument."): - WaterBridgeAnalysis(universe_DA, 'protein and (resid 1)', - 'protein and (resid 4)', order=0, - selection1_type='aaa', selection2_type='aaa') - + """Test the case when the wrong selection1_type is given""" + with pytest.raises( + ValueError, match="`selection2_type` is not a keyword argument." + ): + WaterBridgeAnalysis( + universe_DA, + "protein and (resid 1)", + "protein and (resid 4)", + order=0, + selection1_type="aaa", + selection2_type="aaa", + ) 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) + """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.results.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)') + """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.results.network[0].keys()), 2) - @pytest.mark.parametrize('distance_type', ["hydrogen", "heavy"]) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_donor_accepter(self, universe_DA, distance_type): - '''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, - distance_type=distance_type) + """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, + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) - @pytest.mark.parametrize('distance_type', ["hydrogen", "heavy"]) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_donor_accepter_pbc(self, universe_DA_PBC, distance_type): - '''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, - distance_type=distance_type) + """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, + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) - @pytest.mark.parametrize('distance_type', ["hydrogen", "heavy"]) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_accepter_donor(self, universe_AD, distance_type): - '''Test zeroth order acceptor to donor hydrogen bonding''' - wb = WaterBridgeAnalysis(universe_AD, 'protein and (resid 1)', - 'protein and (resid 4)', order=0, - distance_type=distance_type) + """Test zeroth order acceptor to donor hydrogen bonding""" + wb = WaterBridgeAnalysis( + universe_AD, + "protein and (resid 1)", + "protein and (resid 4)", + order=0, + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 1, 2)) - @pytest.mark.parametrize('distance_type', ["hydrogen", "heavy"]) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_acceptor_water_accepter(self, universe_AWA, distance_type): - '''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)', distance_type=distance_type) + """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)", + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -383,12 +437,16 @@ def test_acceptor_water_accepter(self, universe_AWA, distance_type): assert_equal(list(second.keys())[0][:4], (3, 1, 4, None)) assert_equal(second[list(second.keys())[0]], None) - @pytest.mark.parametrize('distance_type', ["hydrogen", "heavy"]) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_donor_water_accepter(self, universe_DWA, distance_type): - '''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)', distance_type=distance_type) + """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)", + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) @@ -396,12 +454,16 @@ def test_donor_water_accepter(self, universe_DWA, distance_type): assert_equal(list(second.keys())[0][:4], (3, 2, 4, None)) assert_equal(second[list(second.keys())[0]], None) - @pytest.mark.parametrize('distance_type', ["hydrogen", "heavy"]) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_acceptor_water_donor(self, universe_AWD, distance_type): - '''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)', distance_type=distance_type) + """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)", + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -409,12 +471,16 @@ def test_acceptor_water_donor(self, universe_AWD, distance_type): assert_equal(list(second.keys())[0][:4], (1, None, 3, 4)) assert_equal(second[list(second.keys())[0]], None) - @pytest.mark.parametrize('distance_type', ["hydrogen", "heavy"]) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_donor_water_donor(self, universe_DWD, distance_type): - '''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)', distance_type=distance_type) + """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)", + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) @@ -423,38 +489,44 @@ def test_donor_water_donor(self, universe_DWD, distance_type): 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') + """Test case where no water bridge exists""" + wb = WaterBridgeAnalysis(universe_empty, "protein", "protein") wb.run(verbose=False) assert_equal(wb.results.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 = WaterBridgeAnalysis( + universe_DWA, "protein and resid 1", "protein and resid 1" + ) wb.run(verbose=False) assert_equal(wb.results.network[0], defaultdict(dict)) - @pytest.mark.parametrize('distance_type', ["hydrogen", "heavy"]) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_acceptor_2water_accepter(self, universe_AWWA, distance_type): - '''Test case where the hydrogen bond acceptor from selection 1 form second order - water bridge with hydrogen bond acceptor from selection 2''' + """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)', - distance_type=distance_type) + wb = WaterBridgeAnalysis( + universe_AWWA, + "protein and (resid 1)", + "protein and (resid 4)", + distance_type=distance_type, + ) wb.run(verbose=False) assert_equal(wb.results.network[0], defaultdict(dict)) # test second order - wb = WaterBridgeAnalysis(universe_AWWA, - 'protein and (resid 1)', - 'protein and (resid 4)', - order=2, - distance_type=distance_type) + wb = WaterBridgeAnalysis( + universe_AWWA, + "protein and (resid 1)", + "protein and (resid 4)", + order=2, + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -464,9 +536,13 @@ def test_acceptor_2water_accepter(self, universe_AWWA, distance_type): 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, - distance_type=distance_type) + wb = WaterBridgeAnalysis( + universe_AWWA, + "protein and (resid 1)", + "protein and (resid 4)", + order=3, + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -476,19 +552,27 @@ def test_acceptor_2water_accepter(self, universe_AWWA, distance_type): assert_equal(list(third.keys())[0][:4], (5, 4, 6, None)) assert_equal(third[list(third.keys())[0]], None) - @pytest.mark.parametrize('distance_type', ["hydrogen", "heavy"]) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_acceptor_3water_accepter(self, universe_AWWWA, distance_type): - '''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, - distance_type=distance_type) + """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, + distance_type=distance_type, + ) wb.run(verbose=False) assert_equal(wb.results.network[0], defaultdict(dict)) - wb = WaterBridgeAnalysis(universe_AWWWA, 'protein and (resid 1)', - 'protein and (resid 5)', order=3, - distance_type=distance_type) + wb = WaterBridgeAnalysis( + universe_AWWWA, + "protein and (resid 1)", + "protein and (resid 5)", + order=3, + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -500,9 +584,13 @@ def test_acceptor_3water_accepter(self, universe_AWWWA, distance_type): 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, - distance_type=distance_type) + wb = WaterBridgeAnalysis( + universe_AWWWA, + "protein and (resid 1)", + "protein and (resid 5)", + order=4, + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -514,19 +602,27 @@ def test_acceptor_3water_accepter(self, universe_AWWWA, distance_type): assert_equal(list(fourth.keys())[0][:4], (7, 6, 8, None)) assert_equal(fourth[list(fourth.keys())[0]], None) - @pytest.mark.parametrize('distance_type', ["hydrogen", "heavy"]) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_acceptor_4water_accepter(self, universe_AWWWWA, distance_type): - '''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, - distance_type=distance_type) + """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, + distance_type=distance_type, + ) wb.run(verbose=False) assert_equal(wb.results.network[0], defaultdict(dict)) - wb = WaterBridgeAnalysis(universe_AWWWWA, 'protein and (resid 1)', - 'protein and (resid 6)', order=4, - distance_type=distance_type) + wb = WaterBridgeAnalysis( + universe_AWWWWA, + "protein and (resid 1)", + "protein and (resid 6)", + order=4, + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -540,9 +636,13 @@ def test_acceptor_4water_accepter(self, universe_AWWWWA, distance_type): 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, - distance_type=distance_type) + wb = WaterBridgeAnalysis( + universe_AWWWWA, + "protein and (resid 1)", + "protein and (resid 6)", + order=5, + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -556,55 +656,89 @@ def test_acceptor_4water_accepter(self, universe_AWWWWA, distance_type): assert_equal(list(fifth.keys())[0][:4], (9, 8, 10, None)) assert_equal(fifth[list(fifth.keys())[0]], None) - @pytest.mark.parametrize('distance_type', ["hydrogen", "heavy"]) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_acceptor_22water_accepter(self, universe_branch, distance_type): - '''Test case where the hydrogen bond acceptor from selection 1 form a second order + """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, - distance_type=distance_type) + 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, + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] 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, 1, 4, None)) third = second[list(second.keys())[0]] - assert_equal([(5, 4, 7, None), (6, 4, 8, None)], - sorted([key[:4] for key in list(third.keys())])) + assert_equal( + [(5, 4, 7, None), (6, 4, 8, None)], + sorted([key[:4] for key in list(third.keys())]), + ) 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' + """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.results.timeseries[0]) - assert_equal(timeseries[0][:4], (0, 2, ('ALA', 1, 'O'), ('SOL', 2, 'HW1'))) - 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'))) + assert_equal( + timeseries[0][:4], (0, 2, ("ALA", 1, "O"), ("SOL", 2, "HW1")) + ) + 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' + """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.results.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'))) + 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")) + ) - @pytest.mark.parametrize('distance_type', ["hydrogen", "heavy"]) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_acceptor_12water_accepter(self, universe_AWA_AWWA, distance_type): - '''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, - distance_type=distance_type) + """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, + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) @@ -612,174 +746,294 @@ def test_acceptor_12water_accepter(self, universe_AWA_AWWA, distance_type): assert_equal(list(second.keys())[0][:4], (3, 1, 4, None)) assert_equal(second[list(second.keys())[0]], None) network = wb.results.network[0] - wb = WaterBridgeAnalysis(universe_AWA_AWWA, 'protein and (resid 1 or resid 5)', - 'protein and (resid 4 or resid 8)', order=2, - distance_type=distance_type) + wb = WaterBridgeAnalysis( + universe_AWA_AWWA, + "protein and (resid 1 or resid 5)", + "protein and (resid 4 or resid 8)", + order=2, + distance_type=distance_type, + ) wb.run(verbose=False) network = wb.results.network[0] - assert_equal([(0, None, 2, 1), (5, None, 7, 6)], - sorted([key[:4] for key in list(network.keys())])) + 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): - ''' + """ 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.)]) + assert_equal( + wb.count_by_type(), [(1, 4, "ALA", 1, "H", "ALA", 4, "O", 1.0)] + ) 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): - ''' + """ This test tests if count_by_type() works in multiply situations. :return: - ''' - 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]] + """ + 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): - ''' + """ This test tests if modifying analysis_func allows some results to be filtered out in count_by_type(). :return: - ''' + """ + 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_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': + (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, 11, 'ALA', 1, 'O', 'ALA', 6, 'H'), 0.25)] - assert_equal(sorted(wb_multiframe.count_by_type(analysis_func=analysis)), result) + + 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): - ''' + """ This test tests if modifying analysis_func allows some same residue to be merged in count_by_type(). - ''' + """ + 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_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) + (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', 6), 1.0)] - assert_equal(sorted(wb_multiframe.count_by_type(analysis_func=analysis)), result) + + 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): - ''' + """ This test tests if modifying analysis_func allows the order of water bridge to be separated in count_by_type(). :return: - ''' + """ + 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_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) + (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', 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) + + 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): - ''' + """ This test tests if count_by_times() works. :return: - ''' - assert_equal(wb_multiframe.count_by_time(), [(0, 1), (1, 1), (2, 1), (3, 1)]) - + """ + 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): - ''' + """ This test tests if modyfing the analysis_func allows the weight to be changed 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] - atom1, heavy_atom1, sele2_index, sele2_heavy_index, dist, angle = current[-1] + 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) + (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), ]) + 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_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 - assert_equal(wb.count_by_time(analysis_func=analysis), [(0,0), ]) + + assert_equal( + wb.count_by_time(analysis_func=analysis), + [ + (0, 0), + ], + ) def test_generate_table_hba(self, wb_multiframe): - '''Test generate table using hydrogen bond analysis format''' - table = wb_multiframe.generate_table(output_format='donor_acceptor') + """Test generate table using hydrogen bond analysis format""" + table = wb_multiframe.generate_table(output_format="donor_acceptor") assert_array_equal( sorted(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''' - table = wb_multiframe.generate_table(output_format='sele1_sele2') + """Test generate table using hydrogen bond analysis format""" + table = wb_multiframe.generate_table(output_format="sele1_sele2") assert_array_equal( sorted(table.sele1_resid), [1, 1, 1, 1, 2, 2, 3], ) def test_timesteps_by_type(self, wb_multiframe): - '''Test the timesteps_by_type function''' + """Test the timesteps_by_type function""" timesteps = sorted(wb_multiframe.timesteps_by_type()) - assert_array_equal(timesteps[3], [1, 12, 'ALA', 1, 'H', 'ALA', 6, 'O', 0, 2]) + assert_array_equal( + timesteps[3], [1, 12, "ALA", 1, "H", "ALA", 6, "O", 0, 2] + ) def test_duplicate_water(self): - '''A case #3119 where + """A case #3119 where Acceptor···H−O···H-Donor | H···O-H will be recognised as 3rd order water bridge. - ''' - grofile = '''Test gro file + """ + grofile = """Test gro file 7 1LEU O 1 1.876 0.810 1.354 117SOL HW1 2 1.853 0.831 1.162 @@ -788,16 +1042,21 @@ def test_duplicate_water(self): 135SOL OW 5 1.924 0.713 0.845 1LEU H 6 1.997 0.991 1.194 1LEU N 7 2.041 1.030 1.274 - 2.22092 2.22092 2.22092''' - u = MDAnalysis.Universe(StringIO(grofile), format='gro') - wb = WaterBridgeAnalysis(u, 'resname LEU and name O', - 'resname LEU and name N H', order=4) + 2.22092 2.22092 2.22092""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") + wb = WaterBridgeAnalysis( + u, "resname LEU and name O", "resname LEU and name N H", order=4 + ) wb.run() assert len(wb.results.timeseries[0]) == 2 def test_warn_results_deprecated(self, universe_DA): - 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() wmsg = "The `network` attribute was deprecated in MDAnalysis 2.0.0" diff --git a/testsuite/MDAnalysisTests/core/test_atomselections.py b/testsuite/MDAnalysisTests/core/test_atomselections.py index bced4c43bde..51b395f94cb 100644 --- a/testsuite/MDAnalysisTests/core/test_atomselections.py +++ b/testsuite/MDAnalysisTests/core/test_atomselections.py @@ -48,6 +48,8 @@ PDB_helix, PDB_elements, PDB_charges, + waterPSF, + PDB_full, ) from MDAnalysisTests import make_Universe @@ -650,6 +652,38 @@ def test_nucleicsugar(self, universe): assert_equal(rna.n_atoms, rna.n_residues * 5) +class TestSelectionsWater(object): + @pytest.fixture(scope='class') + def universe(self): + return MDAnalysis.Universe(GRO) + + @pytest.fixture(scope='class') + def universe2(self): + return MDAnalysis.Universe(waterPSF) + + @pytest.fixture(scope='class') + def universe3(self): + return MDAnalysis.Universe(PDB_full) + + def test_water_gro(self, universe): + # Test SOL water with 4 atoms + water_gro = universe.select_atoms("water") + assert_equal(water_gro.n_atoms, 44336) + assert_equal(water_gro.n_residues, 11084) + + def test_water_tip3(self, universe2): + # Test TIP3 water with 3 atoms + water_tip3 = universe2.select_atoms('water') + assert_equal(water_tip3.n_atoms, 15) + assert_equal(water_tip3.n_residues, 5) + + def test_water_pdb(self, universe3): + # Test HOH water with 1 atom + water_pdb = universe3.select_atoms("water") + assert_equal(water_pdb.n_residues, 188) + assert_equal(water_pdb.n_atoms, 188) + + class BaseDistanceSelection(object): """Both KDTree and distmat selections on orthogonal system diff --git a/testsuite/MDAnalysisTests/import/fork_called.py b/testsuite/MDAnalysisTests/import/fork_called.py index 823f122a4e5..5bfb6b390f1 100644 --- a/testsuite/MDAnalysisTests/import/fork_called.py +++ b/testsuite/MDAnalysisTests/import/fork_called.py @@ -26,6 +26,7 @@ """Tests whether os.fork() is called as a side effect when importing MDAnalysis. See PR #1794 for details.""" -with mock.patch('os.fork') as os_dot_fork: +with mock.patch("os.fork") as os_dot_fork: import MDAnalysis + assert not os_dot_fork.called diff --git a/testsuite/MDAnalysisTests/import/test_import.py b/testsuite/MDAnalysisTests/import/test_import.py index d8065f4ac18..9794b716ef0 100644 --- a/testsuite/MDAnalysisTests/import/test_import.py +++ b/testsuite/MDAnalysisTests/import/test_import.py @@ -27,8 +27,10 @@ """Test if importing MDAnalysis has unwanted side effects (PR #1794).""" -@pytest.mark.skipif(os.name == 'nt', - reason="fork-related import checks irrelevant on Windows") + +@pytest.mark.skipif( + os.name == "nt", reason="fork-related import checks irrelevant on Windows" +) class TestMDAImport(object): # Tests concerning importing MDAnalysis. def test_os_dot_fork_not_called(self): @@ -37,7 +39,7 @@ def test_os_dot_fork_not_called(self): # no previously imported modules interfere with it. It is therefore # offloaded to the script "fork_called.py". loc = os.path.dirname(os.path.realpath(__file__)) - script = os.path.join(loc, 'fork_called.py') + script = os.path.join(loc, "fork_called.py") encoding = sys.stdout.encoding if encoding is None: encoding = "utf-8" @@ -47,16 +49,17 @@ def test_os_dot_fork_not_called(self): # CalledProcessError. That error's output member then contains the # failed script's stderr and we can print it: try: - out = subprocess.check_output([sys.executable, script], - stderr=subprocess.STDOUT)\ - .decode(encoding) + out = subprocess.check_output( + [sys.executable, script], stderr=subprocess.STDOUT + ).decode(encoding) except subprocess.CalledProcessError as err: print(err.output) - raise(err) + raise (err) def test_os_dot_fork_not_none(self): # In MDAnalysis.core.universe, os.fork is set to None prior to importing # the uuid module and restored afterwards (see PR #1794 for details). # This tests asserts that os.fork has been restored. import MDAnalysis + assert os.fork is not None diff --git a/testsuite/MDAnalysisTests/topology/base.py b/testsuite/MDAnalysisTests/topology/base.py index 6527ab8ae34..e7649d65f86 100644 --- a/testsuite/MDAnalysisTests/topology/base.py +++ b/testsuite/MDAnalysisTests/topology/base.py @@ -25,7 +25,7 @@ import MDAnalysis as mda from MDAnalysis.core.topology import Topology -mandatory_attrs = ['ids', 'resids', 'resnums', 'segids'] +mandatory_attrs = ["ids", "resids", "resnums", "segids"] class ParserBase(object): @@ -57,34 +57,56 @@ def test_mandatory_attributes(self, top): # attributes required as part of the API # ALL parsers must provide these for attr in mandatory_attrs: - assert hasattr(top, attr), 'Missing required attribute: {}'.format(attr) + assert hasattr(top, attr), "Missing required attribute: {}".format( + attr + ) def test_expected_attributes(self, top): # Extra attributes as declared in specific implementations for attr in self.expected_attrs: - assert hasattr(top, attr), 'Missing expected attribute: {}'.format(attr) + assert hasattr(top, attr), "Missing expected attribute: {}".format( + attr + ) def test_no_unexpected_attributes(self, top): - attrs = set(self.expected_attrs - + mandatory_attrs - + ['indices', 'resindices', 'segindices'] + self.guessed_attrs) + attrs = set( + self.expected_attrs + + mandatory_attrs + + ["indices", "resindices", "segindices"] + + self.guessed_attrs + ) for attr in top.attrs: - assert attr.attrname in attrs, 'Unexpected attribute: {}'.format(attr.attrname) + assert attr.attrname in attrs, "Unexpected attribute: {}".format( + attr.attrname + ) def test_size(self, top): """Check that the Topology is correctly sized""" - assert top.n_atoms == self.expected_n_atoms, '{} atoms read, {} expected in {}'.format( - top.n_atoms, self.expected_n_atoms, self.__class__.__name__) - - assert top.n_residues == self.expected_n_residues, '{} residues read, {} expected in {}'.format( - top.n_residues, self.expected_n_residues, self.__class__.__name__) - - assert top.n_segments == self.expected_n_segments, '{} segment read, {} expected in {}'.format( - top.n_segments, self.expected_n_segments, self.__class__.__name__) + assert ( + top.n_atoms == self.expected_n_atoms + ), "{} atoms read, {} expected in {}".format( + top.n_atoms, self.expected_n_atoms, self.__class__.__name__ + ) + + assert ( + top.n_residues == self.expected_n_residues + ), "{} residues read, {} expected in {}".format( + top.n_residues, self.expected_n_residues, self.__class__.__name__ + ) + + assert ( + top.n_segments == self.expected_n_segments + ), "{} segment read, {} expected in {}".format( + top.n_segments, self.expected_n_segments, self.__class__.__name__ + ) def test_tt_size(self, top): """Check that the transtable is appropriately sized""" - assert top.tt.size == (self.expected_n_atoms, self.expected_n_residues, self.expected_n_segments) + assert top.tt.size == ( + self.expected_n_atoms, + self.expected_n_residues, + self.expected_n_segments, + ) def test_creates_universe(self, filename): """Check that Universe works with this Parser""" @@ -95,8 +117,9 @@ def test_guessed_attributes(self, filename): """check that the universe created with certain parser have the same guessed attributes as when it was guessed inside the parser""" u = mda.Universe(filename) - u_guessed_attrs = [attr.attrname for attr - in u._topology.guessed_attributes] + u_guessed_attrs = [ + attr.attrname for attr in u._topology.guessed_attributes + ] for attr in self.guessed_attrs: assert hasattr(u.atoms, attr) assert attr in u_guessed_attrs diff --git a/testsuite/MDAnalysisTests/topology/test_altloc.py b/testsuite/MDAnalysisTests/topology/test_altloc.py index 1007c3e0673..63b43c32139 100644 --- a/testsuite/MDAnalysisTests/topology/test_altloc.py +++ b/testsuite/MDAnalysisTests/topology/test_altloc.py @@ -50,7 +50,7 @@ def test_bonds(u): def test_write_read(u, tmpdir): - outfile = str(tmpdir.join('test.pdb')) + outfile = str(tmpdir.join("test.pdb")) u.select_atoms("all").write(outfile) u2 = Universe(outfile) assert len(u.atoms) == len(u2.atoms) diff --git a/testsuite/MDAnalysisTests/topology/test_crd.py b/testsuite/MDAnalysisTests/topology/test_crd.py index 7c9b0a72419..d7a8794c97b 100644 --- a/testsuite/MDAnalysisTests/topology/test_crd.py +++ b/testsuite/MDAnalysisTests/topology/test_crd.py @@ -33,10 +33,16 @@ class TestCRDParser(ParserBase): parser = mda.topology.CRDParser.CRDParser ref_filename = CRD - expected_attrs = ['ids', 'names', 'tempfactors', - 'resids', 'resnames', 'resnums', - 'segids'] - guessed_attrs = ['masses', 'types'] + expected_attrs = [ + "ids", + "names", + "tempfactors", + "resids", + "resnames", + "resnums", + "segids", + ] + guessed_attrs = ["masses", "types"] expected_n_atoms = 3341 expected_n_residues = 214 @@ -44,10 +50,10 @@ class TestCRDParser(ParserBase): def test_guessed_masses(self, filename): u = mda.Universe(filename) - expected = [14.007, 1.008, 1.008, 1.008, 12.011, 1.008, 12.011] + expected = [14.007, 1.008, 1.008, 1.008, 12.011, 1.008, 12.011] assert_allclose(u.atoms.masses[:7], expected) def test_guessed_types(self, filename): u = mda.Universe(filename) - expected = ['N', 'H', 'H', 'H', 'C', 'H', 'C'] + expected = ["N", "H", "H", "H", "C", "H", "C"] assert (u.atoms.types[:7] == expected).all() diff --git a/testsuite/MDAnalysisTests/topology/test_dlpoly.py b/testsuite/MDAnalysisTests/topology/test_dlpoly.py index da1e871dcdd..9ab731d439a 100644 --- a/testsuite/MDAnalysisTests/topology/test_dlpoly.py +++ b/testsuite/MDAnalysisTests/topology/test_dlpoly.py @@ -34,7 +34,7 @@ DLP_HISTORY_order, DLP_HISTORY_minimal, DLP_HISTORY_minimal_cell, - DLP_HISTORY_classic + DLP_HISTORY_classic, ) @@ -50,8 +50,8 @@ def test_guessed_attributes(self, filename): class DLPBase2(DLPUniverse): - expected_attrs = ['ids', 'names'] - guessed_attrs = ['masses', 'types'] + expected_attrs = ["ids", "names"] + guessed_attrs = ["masses", "types"] expected_n_atoms = 216 expected_n_residues = 1 @@ -64,73 +64,72 @@ def test_guesssed_masses(self, filename): def test_guessed_types(self, filename): u = mda.Universe(filename, topology_format=self.format) - assert u.atoms.types[0] == 'K' - assert u.atoms.types[4] == 'CL' + assert u.atoms.types[0] == "K" + assert u.atoms.types[4] == "CL" def test_names(self, top): - assert top.names.values[0] == 'K+' - assert top.names.values[4] == 'Cl-' + assert top.names.values[0] == "K+" + assert top.names.values[4] == "Cl-" class TestDLPHistoryParser(DLPBase2): parser = mda.topology.DLPolyParser.HistoryParser ref_filename = DLP_HISTORY - format = 'HISTORY' + format = "HISTORY" class TestDLPConfigParser(DLPBase2): parser = mda.topology.DLPolyParser.ConfigParser ref_filename = DLP_CONFIG - format = 'CONFIG' + format = "CONFIG" class DLPBase(DLPUniverse): - expected_attrs = ['ids', 'names'] + expected_attrs = ["ids", "names"] expected_n_atoms = 3 expected_n_residues = 1 expected_n_segments = 1 def test_dlp_names(self, top): - assert_equal(top.names.values, - ['C', 'B', 'A']) + assert_equal(top.names.values, ["C", "B", "A"]) class TestDLPConfigMinimal(DLPBase): parser = mda.topology.DLPolyParser.ConfigParser ref_filename = DLP_CONFIG_minimal - format = 'CONFIG' + format = "CONFIG" class TestDLPConfigOrder(DLPBase): parser = mda.topology.DLPolyParser.ConfigParser ref_filename = DLP_CONFIG_order - format = 'CONFIG' + format = "CONFIG" class TestDLPHistoryMinimal(DLPBase): parser = mda.topology.DLPolyParser.HistoryParser ref_filename = DLP_HISTORY_minimal - format = 'HISTORY' + format = "HISTORY" class TestDLPHistoryMinimal(DLPBase): parser = mda.topology.DLPolyParser.HistoryParser ref_filename = DLP_HISTORY_minimal_cell - format = 'HISTORY' + format = "HISTORY" class TestDLPHistoryOrder(DLPBase): parser = mda.topology.DLPolyParser.HistoryParser ref_filename = DLP_HISTORY_order - format = 'HISTORY' + format = "HISTORY" class TestDLPHistoryClassic(DLPBase): parser = mda.topology.DLPolyParser.HistoryParser ref_filename = DLP_HISTORY_classic - format = 'HISTORY' + format = "HISTORY" def test_HISTORY_EOFError(): with pytest.raises(EOFError): - mda.Universe(DLP_CONFIG, topology_format='HISTORY') + mda.Universe(DLP_CONFIG, topology_format="HISTORY") diff --git a/testsuite/MDAnalysisTests/topology/test_dms.py b/testsuite/MDAnalysisTests/topology/test_dms.py index b1eb8d77b34..0465eb1de5c 100644 --- a/testsuite/MDAnalysisTests/topology/test_dms.py +++ b/testsuite/MDAnalysisTests/topology/test_dms.py @@ -28,10 +28,19 @@ class TestDMSParser(ParserBase): parser = mda.topology.DMSParser.DMSParser ref_filename = DMS_DOMAINS - expected_attrs = ['ids', 'names', 'bonds', 'charges', - 'masses', 'resids', 'resnames', 'segids', - 'chainIDs', 'atomnums'] - guessed_attrs = ['types'] + expected_attrs = [ + "ids", + "names", + "bonds", + "charges", + "masses", + "resids", + "resnames", + "segids", + "chainIDs", + "atomnums", + ] + guessed_attrs = ["types"] expected_n_atoms = 3341 expected_n_residues = 214 expected_n_segments = 3 @@ -62,9 +71,9 @@ def test_atomsels(self, filename): assert len(s5) == 190 def test_guessed_types(self, filename): - u = mda.Universe(filename) - expected = ['N', 'H', 'H', 'H', 'C', 'H', 'C'] - assert (u.atoms.types[:7] == expected).all() + u = mda.Universe(filename) + expected = ["N", "H", "H", "H", "C", "H", "C"] + assert (u.atoms.types[:7] == expected).all() class TestDMSParserNoSegid(TestDMSParser): diff --git a/testsuite/MDAnalysisTests/topology/test_fhiaims.py b/testsuite/MDAnalysisTests/topology/test_fhiaims.py index b8bbc29e46e..4086c107dff 100644 --- a/testsuite/MDAnalysisTests/topology/test_fhiaims.py +++ b/testsuite/MDAnalysisTests/topology/test_fhiaims.py @@ -30,28 +30,25 @@ class TestFHIAIMS(ParserBase): parser = mda.topology.FHIAIMSParser.FHIAIMSParser - expected_attrs = ['names', 'elements'] - guessed_attrs = ['masses', 'types'] + expected_attrs = ["names", "elements"] + guessed_attrs = ["masses", "types"] expected_n_residues = 1 expected_n_segments = 1 expected_n_atoms = 6 ref_filename = FHIAIMS def test_names(self, top): - assert_equal(top.names.values, - ['O', 'H', 'H', 'O', 'H', 'H']) + assert_equal(top.names.values, ["O", "H", "H", "O", "H", "H"]) def test_guessed_types(self, filename): u = mda.Universe(filename) - assert_equal(u.atoms.types, - ['O', 'H', 'H', 'O', 'H', 'H']) + assert_equal(u.atoms.types, ["O", "H", "H", "O", "H", "H"]) def test_guessed_masses(self, filename): u = mda.Universe(filename) - assert_allclose(u.atoms.masses, - [15.999, 1.008, 1.008, 15.999, - 1.008, 1.008]) + assert_allclose( + u.atoms.masses, [15.999, 1.008, 1.008, 15.999, 1.008, 1.008] + ) def test_elements(self, top): - assert_equal(top.elements.values, - ['O', 'H', 'H', 'O', 'H', 'H']) + assert_equal(top.elements.values, ["O", "H", "H", "O", "H", "H"]) diff --git a/testsuite/MDAnalysisTests/topology/test_gms.py b/testsuite/MDAnalysisTests/topology/test_gms.py index 65935c14baf..8becdfda6f8 100644 --- a/testsuite/MDAnalysisTests/topology/test_gms.py +++ b/testsuite/MDAnalysisTests/topology/test_gms.py @@ -34,8 +34,8 @@ class GMSBase(ParserBase): parser = mda.topology.GMSParser.GMSParser - expected_attrs = ['names', 'atomiccharges'] - guessed_attrs = ['masses', 'types'] + expected_attrs = ["names", "atomiccharges"] + guessed_attrs = ["masses", "types"] expected_n_residues = 1 expected_n_segments = 1 @@ -45,12 +45,10 @@ class TestGMSASYMOPT(GMSBase): ref_filename = GMS_ASYMOPT def test_names(self, top): - assert_equal(top.names.values, - ['O', 'H', 'H', 'O', 'H', 'H']) + assert_equal(top.names.values, ["O", "H", "H", "O", "H", "H"]) def test_types(self, top): - assert_equal(top.atomiccharges.values, - [8, 1, 1, 8, 1, 1]) + assert_equal(top.atomiccharges.values, [8, 1, 1, 8, 1, 1]) def test_guessed_masses(self, filename): u = mda.Universe(filename) @@ -59,7 +57,7 @@ def test_guessed_masses(self, filename): def test_guessed_types(self, filename): u = mda.Universe(filename) - expected = ['O', 'H', 'H', 'O', 'H', 'H'] + expected = ["O", "H", "H", "O", "H", "H"] assert (u.atoms.types == expected).all() @@ -68,12 +66,12 @@ class TestGMSSYMOPT(GMSBase): ref_filename = GMS_SYMOPT def test_names(self, top): - assert_equal(top.names.values, - ['CARBON', 'CARBON', 'HYDROGEN', 'HYDROGEN']) + assert_equal( + top.names.values, ["CARBON", "CARBON", "HYDROGEN", "HYDROGEN"] + ) def test_types(self, top): - assert_equal(top.atomiccharges.values, - [6, 6, 1, 1]) + assert_equal(top.atomiccharges.values, [6, 6, 1, 1]) class TestGMSASYMSURF(TestGMSASYMOPT): diff --git a/testsuite/MDAnalysisTests/topology/test_gro.py b/testsuite/MDAnalysisTests/topology/test_gro.py index f9d506fdba5..3bd266b0087 100644 --- a/testsuite/MDAnalysisTests/topology/test_gro.py +++ b/testsuite/MDAnalysisTests/topology/test_gro.py @@ -40,8 +40,8 @@ class TestGROParser(ParserBase): parser = mda.topology.GROParser.GROParser ref_filename = GRO - expected_attrs = ['ids', 'names', 'resids', 'resnames'] - guessed_attrs = ['masses', 'types'] + expected_attrs = ["ids", "names", "resids", "resnames"] + guessed_attrs = ["masses", "types"] expected_n_atoms = 47681 expected_n_residues = 11302 expected_n_segments = 1 @@ -54,17 +54,18 @@ def test_attr_size(self, top): def test_guessed_masses(self, filename): u = mda.Universe(filename) - expected = [14.007, 1.008, 1.008, 1.008, 12.011, 1.008, 12.011] + expected = [14.007, 1.008, 1.008, 1.008, 12.011, 1.008, 12.011] assert_allclose(u.atoms.masses[:7], expected) def test_guessed_types(self, filename): u = mda.Universe(filename) - expected = ['N', 'H', 'H', 'H', 'C', 'H', 'C'] + expected = ["N", "H", "H", "H", "C", "H", "C"] assert_equal(u.atoms.types[:7], expected) class TestGROWideBox(object): """Tests for Issue #548""" + def test_atoms(self): parser = mda.topology.GROParser.GROParser with parser(two_water_gro_widebox) as p: @@ -89,16 +90,23 @@ def test_parse_missing_atomname_IOerror(): class TestGroResidWrapping(object): # resid is 5 digit field, so is limited to 100k # check that parser recognises when resids have wrapped - names = ['MET', 'ARG', 'ILE', 'ILE', 'LEU', 'LEU', 'GLY'] + names = ["MET", "ARG", "ILE", "ILE", "LEU", "LEU", "GLY"] lengths = [19, 24, 19, 19, 19, 19, 7] parser = mda.topology.GROParser.GROParser - @pytest.mark.parametrize('parser, resids', ( - (GRO_residwrap, [1, 99999, 100000, 100001, 199999, 200000, 200001]), - (GRO_residwrap_0base, [0, 99999, 100000, 100001, 199999, 200000, - 200001]) - - )) + @pytest.mark.parametrize( + "parser, resids", + ( + ( + GRO_residwrap, + [1, 99999, 100000, 100001, 199999, 200000, 200001], + ), + ( + GRO_residwrap_0base, + [0, 99999, 100000, 100001, 199999, 200000, 200001], + ), + ), + ) def test_wrapping_resids(self, parser, resids): with self.parser(parser) as p: top = p.parse() @@ -116,7 +124,7 @@ def test_sameresid_diffresname(): with parser(GRO_sameresid_diffresname) as p: top = p.parse() resids = [9, 9] - resnames = ['GLN', 'POPC'] + resnames = ["GLN", "POPC"] for i, (resid, resname) in enumerate(zip(resids, resnames)): assert top.resids.values[i] == resid assert top.resnames.values[i] == resname diff --git a/testsuite/MDAnalysisTests/topology/test_gsd.py b/testsuite/MDAnalysisTests/topology/test_gsd.py index d183642013c..54dbe49288a 100644 --- a/testsuite/MDAnalysisTests/topology/test_gsd.py +++ b/testsuite/MDAnalysisTests/topology/test_gsd.py @@ -31,12 +31,23 @@ import os -@pytest.mark.skipif(not HAS_GSD, reason='gsd not installed') +@pytest.mark.skipif(not HAS_GSD, reason="gsd not installed") class GSDBase(ParserBase): parser = mda.topology.GSDParser.GSDParser - expected_attrs = ['ids', 'names', 'resids', 'resnames', 'masses', - 'charges', 'radii', 'types', - 'bonds', 'angles', 'dihedrals', 'impropers'] + expected_attrs = [ + "ids", + "names", + "resids", + "resnames", + "masses", + "charges", + "radii", + "types", + "bonds", + "angles", + "dihedrals", + "impropers", + ] expected_n_bonds = 0 expected_n_angles = 0 expected_n_dihedrals = 0 @@ -80,7 +91,7 @@ def test_impropers(self, top): assert top.impropers.values == [] -@pytest.mark.skipif(not HAS_GSD, reason='gsd not installed') +@pytest.mark.skipif(not HAS_GSD, reason="gsd not installed") class TestGSDParser(GSDBase): ref_filename = GSD expected_n_atoms = 5832 @@ -88,7 +99,7 @@ class TestGSDParser(GSDBase): expected_n_segments = 1 -@pytest.mark.skipif(not HAS_GSD, reason='gsd not installed') +@pytest.mark.skipif(not HAS_GSD, reason="gsd not installed") class TestGSDParserBonds(GSDBase): ref_filename = GSD_bonds expected_n_atoms = 490 @@ -102,16 +113,16 @@ 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) + 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) + 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) + assert (0, 250, 350, 450) not in vals diff --git a/testsuite/MDAnalysisTests/topology/test_guessers.py b/testsuite/MDAnalysisTests/topology/test_guessers.py index 46581c6c999..69b1046f826 100644 --- a/testsuite/MDAnalysisTests/topology/test_guessers.py +++ b/testsuite/MDAnalysisTests/topology/test_guessers.py @@ -42,31 +42,38 @@ except ImportError: pass -requires_rdkit = pytest.mark.skipif(import_not_available("rdkit"), - reason="requires RDKit") +requires_rdkit = pytest.mark.skipif( + import_not_available("rdkit"), reason="requires RDKit" +) class TestGuessMasses(object): def test_guess_masses(self): - out = guessers.guess_masses(['C', 'C', 'H']) + out = guessers.guess_masses(["C", "C", "H"]) assert isinstance(out, np.ndarray) assert_equal(out, np.array([12.011, 12.011, 1.008])) def test_guess_masses_warn(self): - with pytest.warns(UserWarning, match='Failed to guess the mass'): - guessers.guess_masses(['X']) + with pytest.warns(UserWarning, match="Failed to guess the mass"): + guessers.guess_masses(["X"]) def test_guess_masses_miss(self): - out = guessers.guess_masses(['X', 'Z']) + out = guessers.guess_masses(["X", "Z"]) assert_equal(out, np.array([0.0, 0.0])) - @pytest.mark.parametrize('element, value', (('H', 1.008), ('XYZ', 0.0), )) + @pytest.mark.parametrize( + "element, value", + ( + ("H", 1.008), + ("XYZ", 0.0), + ), + ) def test_get_atom_mass(self, element, value): assert guessers.get_atom_mass(element) == value def test_guess_atom_mass(self): - assert guessers.guess_atom_mass('1H') == 1.008 + assert guessers.guess_atom_mass("1H") == 1.008 class TestGuessTypes(object): @@ -74,50 +81,53 @@ class TestGuessTypes(object): # guess_atom_type # guess_atom_element def test_guess_types(self): - out = guessers.guess_types(['MG2+', 'C12']) + out = guessers.guess_types(["MG2+", "C12"]) assert isinstance(out, np.ndarray) - assert_equal(out, np.array(['MG', 'C'], dtype=object)) + assert_equal(out, np.array(["MG", "C"], dtype=object)) def test_guess_atom_element(self): - assert guessers.guess_atom_element('MG2+') == 'MG' + assert guessers.guess_atom_element("MG2+") == "MG" def test_guess_atom_element_empty(self): - assert guessers.guess_atom_element('') == '' + assert guessers.guess_atom_element("") == "" def test_guess_atom_element_singledigit(self): - assert guessers.guess_atom_element('1') == '1' + assert guessers.guess_atom_element("1") == "1" def test_guess_atom_element_1H(self): - assert guessers.guess_atom_element('1H') == 'H' - assert guessers.guess_atom_element('2H') == 'H' - - @pytest.mark.parametrize('name, element', ( - ('AO5*', 'O'), - ('F-', 'F'), - ('HB1', 'H'), - ('OC2', 'O'), - ('1he2', 'H'), - ('3hg2', 'H'), - ('OH-', 'O'), - ('HO', 'H'), - ('he', 'H'), - ('zn', 'ZN'), - ('Ca2+', 'CA'), - ('CA', 'C'), - ('N0A', 'N'), - ('C0U', 'C'), - ('C0S', 'C'), - ('Na+', 'NA'), - ('Cu2+', 'CU') - )) + assert guessers.guess_atom_element("1H") == "H" + assert guessers.guess_atom_element("2H") == "H" + + @pytest.mark.parametrize( + "name, element", + ( + ("AO5*", "O"), + ("F-", "F"), + ("HB1", "H"), + ("OC2", "O"), + ("1he2", "H"), + ("3hg2", "H"), + ("OH-", "O"), + ("HO", "H"), + ("he", "H"), + ("zn", "ZN"), + ("Ca2+", "CA"), + ("CA", "C"), + ("N0A", "N"), + ("C0U", "C"), + ("C0S", "C"), + ("Na+", "NA"), + ("Cu2+", "CU"), + ), + ) def test_guess_element_from_name(self, name, element): assert guessers.guess_atom_element(name) == element def test_guess_charge(): # this always returns 0.0 - assert guessers.guess_atom_charge('this') == 0.0 + assert guessers.guess_atom_charge("this") == 0.0 def test_guess_bonds_Error(): @@ -141,42 +151,45 @@ def bond_sort(arr): # sort from low to high, also within a tuple # e.g. ([5, 4], [0, 1], [0, 3]) -> ([0, 1], [0, 3], [4, 5]) out = [] - for (i, j) in arr: + for i, j in arr: if i > j: i, j = j, i out.append((i, j)) return sorted(out) + def test_guess_bonds_water(): u = mda.Universe(datafiles.two_water_gro) - bonds = bond_sort(guessers.guess_bonds(u.atoms, u.atoms.positions, u.dimensions)) - assert_equal(bonds, ((0, 1), - (0, 2), - (3, 4), - (3, 5))) + bonds = bond_sort( + guessers.guess_bonds(u.atoms, u.atoms.positions, u.dimensions) + ) + assert_equal(bonds, ((0, 1), (0, 2), (3, 4), (3, 5))) + def test_guess_bonds_adk(): u = mda.Universe(datafiles.PSF, datafiles.DCD) u.atoms.types = guessers.guess_types(u.atoms.names) bonds = bond_sort(guessers.guess_bonds(u.atoms, u.atoms.positions)) - assert_equal(np.sort(u.bonds.indices, axis=0), - np.sort(bonds, axis=0)) + assert_equal(np.sort(u.bonds.indices, axis=0), np.sort(bonds, axis=0)) + def test_guess_bonds_peptide(): u = mda.Universe(datafiles.PSF_NAMD, datafiles.PDB_NAMD) u.atoms.types = guessers.guess_types(u.atoms.names) bonds = bond_sort(guessers.guess_bonds(u.atoms, u.atoms.positions)) - assert_equal(np.sort(u.bonds.indices, axis=0), - np.sort(bonds, axis=0)) - - -@pytest.mark.parametrize("smi", [ - "c1ccccc1", - "C1=CC=CC=C1", - "CCO", - "c1ccccc1Cc1ccccc1", - "CN1C=NC2=C1C(=O)N(C(=O)N2C)C", -]) + assert_equal(np.sort(u.bonds.indices, axis=0), np.sort(bonds, axis=0)) + + +@pytest.mark.parametrize( + "smi", + [ + "c1ccccc1", + "C1=CC=CC=C1", + "CCO", + "c1ccccc1Cc1ccccc1", + "CN1C=NC2=C1C(=O)N(C(=O)N2C)C", + ], +) @requires_rdkit def test_guess_aromaticities(smi): mol = Chem.MolFromSmiles(smi) @@ -187,20 +200,25 @@ def test_guess_aromaticities(smi): assert_equal(values, expected) -@pytest.mark.parametrize("smi", [ - "c1ccccc1", - "C1=CC=CC=C1", - "CCO", - "c1ccccc1Cc1ccccc1", - "CN1C=NC2=C1C(=O)N(C(=O)N2C)C", -]) +@pytest.mark.parametrize( + "smi", + [ + "c1ccccc1", + "C1=CC=CC=C1", + "CCO", + "c1ccccc1Cc1ccccc1", + "CN1C=NC2=C1C(=O)N(C(=O)N2C)C", + ], +) @requires_rdkit def test_guess_gasteiger_charges(smi): mol = Chem.MolFromSmiles(smi) mol = Chem.AddHs(mol) ComputeGasteigerCharges(mol, throwOnParamFailure=True) - expected = np.array([atom.GetDoubleProp("_GasteigerCharge") - for atom in mol.GetAtoms()], dtype=np.float32) + expected = np.array( + [atom.GetDoubleProp("_GasteigerCharge") for atom in mol.GetAtoms()], + dtype=np.float32, + ) u = mda.Universe(mol) values = guessers.guess_gasteiger_charges(u.atoms) assert_equal(values, expected) @@ -208,21 +226,24 @@ def test_guess_gasteiger_charges(smi): class TestDeprecationWarning: wmsg = ( - "MDAnalysis.topology.guessers is deprecated in favour of " - "the new Guessers API. " - "See MDAnalysis.guesser.default_guesser for more details." + "MDAnalysis.topology.guessers is deprecated in favour of " + "the new Guessers API. " + "See MDAnalysis.guesser.default_guesser for more details." ) - @pytest.mark.parametrize('func, arg', [ - [guessers.guess_masses, ['C']], - [guessers.validate_atom_types, ['C']], - [guessers.guess_types, ['CA']], - [guessers.guess_atom_type, 'CA'], - [guessers.guess_atom_element, 'CA'], - [guessers.get_atom_mass, 'C'], - [guessers.guess_atom_mass, 'CA'], - [guessers.guess_atom_charge, 'CA'], - ]) + @pytest.mark.parametrize( + "func, arg", + [ + [guessers.guess_masses, ["C"]], + [guessers.validate_atom_types, ["C"]], + [guessers.guess_types, ["CA"]], + [guessers.guess_atom_type, "CA"], + [guessers.guess_atom_element, "CA"], + [guessers.get_atom_mass, "C"], + [guessers.guess_atom_mass, "CA"], + [guessers.guess_atom_charge, "CA"], + ], + ) def test_mass_type_elements_deprecations(self, func, arg): with pytest.warns(DeprecationWarning, match=self.wmsg): func(arg) @@ -251,7 +272,7 @@ def test_angles_dihedral_deprecations(self): @requires_rdkit def test_rdkit_guessers_deprecations(self): - mol = Chem.MolFromSmiles('c1ccccc1') + mol = Chem.MolFromSmiles("c1ccccc1") mol = Chem.AddHs(mol) u = mda.Universe(mol) diff --git a/testsuite/MDAnalysisTests/topology/test_hoomdxml.py b/testsuite/MDAnalysisTests/topology/test_hoomdxml.py index 759a2aae78d..091662abef4 100644 --- a/testsuite/MDAnalysisTests/topology/test_hoomdxml.py +++ b/testsuite/MDAnalysisTests/topology/test_hoomdxml.py @@ -31,7 +31,14 @@ class TestHoomdXMLParser(ParserBase): parser = mda.topology.HoomdXMLParser.HoomdXMLParser ref_filename = HoomdXMLdata expected_attrs = [ - 'types', 'masses', 'charges', 'radii', 'bonds', 'angles', 'dihedrals', 'impropers' + "types", + "masses", + "charges", + "radii", + "bonds", + "angles", + "dihedrals", + "impropers", ] expected_n_atoms = 769 @@ -62,19 +69,19 @@ 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) + 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) + 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) + assert (0, 250, 350, 450) not in vals def test_read_masses(self, top): assert_almost_equal(top.masses.values, 1.0) diff --git a/testsuite/MDAnalysisTests/topology/test_itp.py b/testsuite/MDAnalysisTests/topology/test_itp.py index 8711ac072a3..5b3cf93411a 100644 --- a/testsuite/MDAnalysisTests/topology/test_itp.py +++ b/testsuite/MDAnalysisTests/topology/test_itp.py @@ -45,13 +45,27 @@ class BaseITP(ParserBase): parser = mda.topology.ITPParser.ITPParser - expected_attrs = ['ids', 'names', 'types', 'masses', - 'charges', 'chargegroups', - 'resids', 'resnames', - 'segids', 'moltypes', 'molnums', - 'bonds', 'angles', 'dihedrals', 'impropers'] - - guessed_attrs = ['elements', ] + expected_attrs = [ + "ids", + "names", + "types", + "masses", + "charges", + "chargegroups", + "resids", + "resnames", + "segids", + "moltypes", + "molnums", + "bonds", + "angles", + "dihedrals", + "impropers", + ] + + guessed_attrs = [ + "elements", + ] expected_n_atoms = 63 expected_n_residues = 10 @@ -146,12 +160,26 @@ def test_impropers_type(self, universe): class TestITPNoMass(ParserBase): parser = mda.topology.ITPParser.ITPParser ref_filename = ITP_nomass - expected_attrs = ['ids', 'names', 'types', - 'charges', 'chargegroups', - 'resids', 'resnames', - 'segids', 'moltypes', 'molnums', - 'bonds', 'angles', 'dihedrals', 'impropers', 'masses', ] - guessed_attrs = ['elements', ] + expected_attrs = [ + "ids", + "names", + "types", + "charges", + "chargegroups", + "resids", + "resnames", + "segids", + "moltypes", + "molnums", + "bonds", + "angles", + "dihedrals", + "impropers", + "masses", + ] + guessed_attrs = [ + "elements", + ] expected_n_atoms = 60 expected_n_residues = 1 @@ -168,11 +196,23 @@ def test_mass_guess(self, universe): class TestITPAtomtypes(ParserBase): parser = mda.topology.ITPParser.ITPParser ref_filename = ITP_atomtypes - expected_attrs = ['ids', 'names', 'types', - 'charges', 'chargegroups', - 'resids', 'resnames', 'masses', - 'segids', 'moltypes', 'molnums', - 'bonds', 'angles', 'dihedrals', 'impropers'] + expected_attrs = [ + "ids", + "names", + "types", + "charges", + "chargegroups", + "resids", + "resnames", + "masses", + "segids", + "moltypes", + "molnums", + "bonds", + "angles", + "dihedrals", + "impropers", + ] expected_n_atoms = 4 expected_n_residues = 1 @@ -186,7 +226,7 @@ def test_charge_parse(self, universe): assert_allclose(universe.atoms[0].charge, 4) assert_allclose(universe.atoms[1].charge, 1.1) assert_allclose(universe.atoms[2].charge, -3.000) - assert_allclose(universe.atoms[3].charge, 1.) + assert_allclose(universe.atoms[3].charge, 1.0) def test_mass_parse_or_guess(self, universe): # read from [ atoms ] section @@ -202,12 +242,26 @@ def test_mass_parse_or_guess(self, universe): class TestITPCharges(ParserBase): parser = mda.topology.ITPParser.ITPParser ref_filename = ITP_charges - expected_attrs = ['ids', 'names', 'types', 'masses', - 'charges', 'chargegroups', - 'resids', 'resnames', - 'segids', 'moltypes', 'molnums', - 'bonds', 'angles', 'dihedrals', 'impropers'] - guessed_attrs = ['elements', ] + expected_attrs = [ + "ids", + "names", + "types", + "masses", + "charges", + "chargegroups", + "resids", + "resnames", + "segids", + "moltypes", + "molnums", + "bonds", + "angles", + "dihedrals", + "impropers", + ] + guessed_attrs = [ + "elements", + ] expected_n_atoms = 9 expected_n_residues = 3 @@ -221,7 +275,7 @@ def test_charge_parse(self, universe): assert_allclose(universe.atoms[0].charge, -1.0) assert_allclose(universe.atoms[1].charge, 0) assert_allclose(universe.atoms[2].charge, 0) - assert_allclose(universe.atoms[3].charge, -1.) + assert_allclose(universe.atoms[3].charge, -1.0) def test_masses_are_read(self, universe): assert_allclose(universe.atoms.masses, [100] * 9) @@ -252,12 +306,27 @@ def test_dihedrals_identity(self, universe): class TestITPNoKeywords(BaseITP): - expected_attrs = ['ids', 'names', 'types', - 'charges', 'chargegroups', - 'resids', 'resnames', - 'segids', 'moltypes', 'molnums', - 'bonds', 'angles', 'dihedrals', 'impropers', 'masses', ] - guessed_attrs = ['elements', 'masses', ] + expected_attrs = [ + "ids", + "names", + "types", + "charges", + "chargegroups", + "resids", + "resnames", + "segids", + "moltypes", + "molnums", + "bonds", + "angles", + "dihedrals", + "impropers", + "masses", + ] + guessed_attrs = [ + "elements", + "masses", + ] """ Test reading ITP files *without* defined keywords. @@ -285,7 +354,7 @@ class TestITPNoKeywords(BaseITP): def test_whether_settles_types(self, universe): for param in list(universe.bonds) + list(universe.angles): - assert param.type == 'settles' + assert param.type == "settles" def test_bonds_values(self, top): vals = top.bonds.values @@ -298,8 +367,9 @@ def test_defines(self, top): def test_guessed_masses(self, filename): u = mda.Universe(filename) - assert_allclose(u.atoms.masses, - [15.999, 15.999, 15.999, 15.999, 15.999]) + assert_allclose( + u.atoms.masses, [15.999, 15.999, 15.999, 15.999, 15.999] + ) class TestITPKeywords(TestITPNoKeywords): @@ -313,14 +383,20 @@ class TestITPKeywords(TestITPNoKeywords): @pytest.fixture def universe(self, filename): - return mda.Universe(filename, FLEXIBLE=True, EXTRA_ATOMS=True, - HW1_CHARGE=1, HW2_CHARGE=3) + return mda.Universe( + filename, + FLEXIBLE=True, + EXTRA_ATOMS=True, + HW1_CHARGE=1, + HW2_CHARGE=3, + ) @pytest.fixture() def top(self, filename): with self.parser(filename) as p: - yield p.parse(FLEXIBLE=True, EXTRA_ATOMS=True, - HW1_CHARGE=1, HW2_CHARGE=3) + yield p.parse( + FLEXIBLE=True, EXTRA_ATOMS=True, HW1_CHARGE=1, HW2_CHARGE=3 + ) def test_whether_settles_types(self, universe): for param in list(universe.bonds) + list(universe.angles): @@ -340,6 +416,7 @@ class TestNestedIfs(BaseITP): """ Test reading ITP files with nested ifdef/ifndef conditions. """ + ref_filename = ITP_spce expected_n_atoms = 7 expected_n_residues = 1 @@ -352,7 +429,9 @@ class TestNestedIfs(BaseITP): @pytest.fixture def universe(self, filename): - return mda.Universe(filename, HEAVY_H=True, EXTRA_ATOMS=True, HEAVY_SIX=True) + return mda.Universe( + filename, HEAVY_H=True, EXTRA_ATOMS=True, HEAVY_SIX=True + ) @pytest.fixture() def top(self, filename): @@ -386,7 +465,9 @@ def top(self, filename): @pytest.fixture() def universe(self, filename): - return mda.Universe(filename, topology_format='ITP', include_dir=GMX_DIR) + return mda.Universe( + filename, topology_format="ITP", include_dir=GMX_DIR + ) def test_output(self, filename): """Testing the call signature""" @@ -395,19 +476,21 @@ def test_output(self, filename): def test_creates_universe(self, filename): """Check that Universe works with this Parser""" - u = mda.Universe(filename, topology_format='ITP', include_dir=GMX_DIR) + u = mda.Universe(filename, topology_format="ITP", include_dir=GMX_DIR) def test_guessed_attributes(self, filename): """check that the universe created with certain parser have the same guessed attributes as when it was guessed inside the parser""" - u = mda.Universe(filename, topology_format='ITP', include_dir=GMX_DIR) + u = mda.Universe(filename, topology_format="ITP", include_dir=GMX_DIR) for attr in self.guessed_attrs: assert hasattr(u.atoms, attr) def test_sequential(self, universe): resids = np.array(list(range(2, 12)) + list(range(13, 23))) assert_equal(universe.residues.resids[:20], resids) - assert_equal(universe.residues.resindices, np.arange(self.expected_n_residues)) + assert_equal( + universe.residues.resindices, np.arange(self.expected_n_residues) + ) assert_equal(universe.atoms.chargegroups[-1], 63) @@ -442,7 +525,7 @@ def test_relstring(self, tmpdir): p2 = tmpdir.mkdir("sub2") p2.chdir() with p2.as_cwd() as pchange: - u = mda.Universe(str("../sub1/test.itp"), format='ITP') + u = mda.Universe(str("../sub1/test.itp"), format="ITP") def test_relpath(self, tmpdir): content = """ @@ -455,7 +538,7 @@ def test_relpath(self, tmpdir): p2.chdir() with p2.as_cwd() as pchange: relpath = Path("../sub1/test.itp") - u = mda.Universe(relpath, format='ITP') + u = mda.Universe(relpath, format="ITP") def test_relative_path(self, tmpdir): test_itp_content = '#include "../atoms.itp"' @@ -485,8 +568,10 @@ def test_missing_elements_no_attribute(): 1) a warning is raised if elements are missing 2) the elements attribute is not set """ - wmsg = ("Element information is missing, elements attribute " - "will not be populated. If needed these can be ") + wmsg = ( + "Element information is missing, elements attribute " + "will not be populated. If needed these can be " + ) with pytest.warns(UserWarning, match=wmsg): u = mda.Universe(ITP_atomtypes) with pytest.raises(AttributeError): diff --git a/testsuite/MDAnalysisTests/topology/test_lammpsdata.py b/testsuite/MDAnalysisTests/topology/test_lammpsdata.py index 7e76c2e7a3d..c5f087b89b2 100644 --- a/testsuite/MDAnalysisTests/topology/test_lammpsdata.py +++ b/testsuite/MDAnalysisTests/topology/test_lammpsdata.py @@ -43,8 +43,16 @@ class LammpsBase(ParserBase): parser = mda.topology.LAMMPSParser.DATAParser expected_n_segments = 1 - expected_attrs = ['types', 'resids', 'masses', 'charges', - 'bonds', 'angles', 'dihedrals', 'impropers'] + expected_attrs = [ + "types", + "resids", + "masses", + "charges", + "bonds", + "angles", + "dihedrals", + "impropers", + ] def test_n_atom_types(self, top): assert_equal(len(set(top.types.values)), self.expected_n_atom_types) @@ -53,7 +61,7 @@ def test_n_bonds(self, top): if self.ref_n_bonds: assert_equal(len(top.bonds.values), self.ref_n_bonds) else: - assert not hasattr(top, 'bonds') + assert not hasattr(top, "bonds") def test_bond_member(self, top): if self.ref_n_bonds: @@ -63,7 +71,7 @@ def test_n_angles(self, top): if self.ref_n_angles: assert_equal(len(top.angles.values), self.ref_n_angles) else: - assert not hasattr(self.top, 'angles') + assert not hasattr(self.top, "angles") def test_angle_member(self, top): if self.ref_n_angles: @@ -73,7 +81,7 @@ def test_n_dihedrals(self, top): if self.ref_n_dihedrals: assert_equal(len(top.dihedrals.values), self.ref_n_dihedrals) else: - assert not hasattr(self.top, 'dihedrals') + assert not hasattr(self.top, "dihedrals") def test_dihedral_member(self, top): if self.ref_n_dihedrals: @@ -83,17 +91,17 @@ def test_n_impropers(self, top): if self.ref_n_impropers: assert_equal(len(top.impropers.values), self.ref_n_impropers) else: - assert not hasattr(self.top, 'impropers') + assert not hasattr(self.top, "impropers") def test_improper_member(self, top): if self.ref_n_impropers: assert self.ref_improper in top.impropers.values def test_creates_universe(self, filename): - u = mda.Universe(filename, format='DATA') + u = mda.Universe(filename, format="DATA") def test_guessed_attributes(self, filename): - u = mda.Universe(filename, format='DATA') + u = mda.Universe(filename, format="DATA") for attr in self.guessed_attrs: assert hasattr(u.atoms, attr) @@ -104,6 +112,7 @@ class TestLammpsData(LammpsBase): The reading of coords and velocities is done separately in test_coordinates """ + ref_filename = LAMMPSdata expected_n_atoms = 18364 expected_n_atom_types = 10 @@ -165,24 +174,32 @@ class TestLAMMPSDeletedAtoms(LammpsBase): def test_atom_ids(self, filename): u = mda.Universe(filename) - assert_equal(u.atoms.ids, - [1, 10, 1002, 2003, 2004, 2005, 2006, 2007, 2008, 2009]) + assert_equal( + u.atoms.ids, + [1, 10, 1002, 2003, 2004, 2005, 2006, 2007, 2008, 2009], + ) def test_traj(self, filename): u = mda.Universe(filename) - assert_equal(u.atoms.positions, - np.array([[11.8998565674, 48.4455718994, 19.0971984863], - [14.5285415649, 50.6892776489, 19.9419136047], - [12.8466796875, 48.1473007202, 18.6461906433], - [11.0093536377, 48.7145767212, 18.5247917175], - [12.4033203125, 49.2582168579, 20.2825050354], - [13.0947723389, 48.8437194824, 21.0175533295], - [11.540184021, 49.6138534546, 20.8459072113], - [13.0085144043, 50.6062469482, 19.9141769409], - [12.9834518433, 51.1562423706, 18.9713554382], - [12.6588821411, 51.4160842896, 20.5548400879]], - dtype=np.float32)) + assert_equal( + u.atoms.positions, + np.array( + [ + [11.8998565674, 48.4455718994, 19.0971984863], + [14.5285415649, 50.6892776489, 19.9419136047], + [12.8466796875, 48.1473007202, 18.6461906433], + [11.0093536377, 48.7145767212, 18.5247917175], + [12.4033203125, 49.2582168579, 20.2825050354], + [13.0947723389, 48.8437194824, 21.0175533295], + [11.540184021, 49.6138534546, 20.8459072113], + [13.0085144043, 50.6062469482, 19.9141769409], + [12.9834518433, 51.1562423706, 18.9713554382], + [12.6588821411, 51.4160842896, 20.5548400879], + ], + dtype=np.float32, + ), + ) class TestLammpsDataPairIJ(LammpsBase): @@ -190,8 +207,15 @@ class TestLammpsDataPairIJ(LammpsBase): PairIJ Coeffs section """ - expected_attrs = ['types', 'resids', 'masses', - 'bonds', 'angles', 'dihedrals', 'impropers'] + expected_attrs = [ + "types", + "resids", + "masses", + "bonds", + "angles", + "dihedrals", + "impropers", + ] ref_filename = LAMMPSdata_PairIJ expected_n_atoms = 800 expected_n_atom_types = 2 @@ -228,47 +252,62 @@ class TestLammpsDataPairIJ(LammpsBase): 1 1 3.7151744275286681e+01 1.8684434743140471e+01 1.9285127961842125e+01 0 0 0 """ + def test_noresid(): - u = mda.Universe(StringIO(LAMMPS_NORESID), format='data', - atom_style='id type x y z') + u = mda.Universe( + StringIO(LAMMPS_NORESID), format="data", atom_style="id type x y z" + ) assert len(u.atoms) == 1 assert_equal(u.atoms[0].mass, 28.0) - assert_equal(u.atoms.positions, - np.array([[3.7151744275286681e+01, - 1.8684434743140471e+01, - 1.9285127961842125e+01]], dtype=np.float32)) + assert_equal( + u.atoms.positions, + np.array( + [ + [ + 3.7151744275286681e01, + 1.8684434743140471e01, + 1.9285127961842125e01, + ] + ], + dtype=np.float32, + ), + ) + def test_noresid_failure(): with pytest.raises( - ValueError, - match='.+?You can supply a description of the atom_style.+?', + ValueError, + match=".+?You can supply a description of the atom_style.+?", ): - u = mda.Universe(StringIO(LAMMPS_NORESID), format='data') + u = mda.Universe(StringIO(LAMMPS_NORESID), format="data") def test_interpret_atom_style(): style = mda.topology.LAMMPSParser.DATAParser._interpret_atom_style( - 'id charge type z y x') + "id charge type z y x" + ) assert isinstance(style, dict) - assert style['id'] == 0 - assert style['type'] == 2 - assert style['charge'] == 1 - assert style['x'] == 5 - assert style['y'] == 4 - assert style['z'] == 3 + assert style["id"] == 0 + assert style["type"] == 2 + assert style["charge"] == 1 + assert style["x"] == 5 + assert style["y"] == 4 + assert style["z"] == 3 def test_interpret_atom_style_missing(): - with pytest.raises(ValueError, - match='atom_style string missing required.+?'): + with pytest.raises( + ValueError, match="atom_style string missing required.+?" + ): style = mda.topology.LAMMPSParser.DATAParser._interpret_atom_style( - 'id charge z y x') + "id charge z y x" + ) class TestDumpParser(ParserBase): - expected_attrs = ['types', 'masses'] + expected_attrs = ["types", "masses"] expected_n_atoms = 24 expected_n_residues = 1 expected_n_segments = 1 @@ -277,7 +316,7 @@ class TestDumpParser(ParserBase): ref_filename = LAMMPSDUMP def test_creates_universe(self): - u = mda.Universe(self.ref_filename, format='LAMMPSDUMP') + u = mda.Universe(self.ref_filename, format="LAMMPSDUMP") assert isinstance(u, mda.Universe) assert len(u.atoms) == 24 @@ -286,30 +325,31 @@ def test_masses_warning(self): # masses are mandatory, but badly guessed # check that user is alerted with self.parser(self.ref_filename) as p: - with pytest.warns(UserWarning, match='Guessed all Masses to 1.0'): + with pytest.warns(UserWarning, match="Guessed all Masses to 1.0"): p.parse() def test_guessed_attributes(self, filename): - u = mda.Universe(filename, format='LAMMPSDUMP') + u = mda.Universe(filename, format="LAMMPSDUMP") for attr in self.guessed_attrs: assert hasattr(u.atoms, attr) def test_id_ordering(self): # ids are nonsequential in file, but should get rearranged - u = mda.Universe(self.ref_filename, format='LAMMPSDUMP') + u = mda.Universe(self.ref_filename, format="LAMMPSDUMP") # the 4th in file has id==13, but should have been sorted assert u.atoms[3].id == 4 def test_guessed_masses(self, filename): - u = mda.Universe(filename, format='LAMMPSDUMP') - expected = [1., 1., 1., 1., 1., 1., 1.] + u = mda.Universe(filename, format="LAMMPSDUMP") + expected = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] assert_allclose(u.atoms.masses[:7], expected) def test_guessed_types(self, filename): - u = mda.Universe(filename, format='LAMMPSDUMP') - expected = ['2', '1', '1', '2', '1', '1', '2'] + u = mda.Universe(filename, format="LAMMPSDUMP") + expected = ["2", "1", "1", "2", "1", "1", "2"] assert (u.atoms.types[:7] == expected).all() + # this tests that topology can still be constructed if non-standard or uneven # column present. class TestDumpParserLong(TestDumpParser): diff --git a/testsuite/MDAnalysisTests/topology/test_minimal.py b/testsuite/MDAnalysisTests/topology/test_minimal.py index 1a1cee1d6a0..2d0e1ffd87d 100644 --- a/testsuite/MDAnalysisTests/topology/test_minimal.py +++ b/testsuite/MDAnalysisTests/topology/test_minimal.py @@ -41,7 +41,8 @@ working_readers = pytest.mark.parametrize( - 'filename,expected_n_atoms', [ + "filename,expected_n_atoms", + [ (DCD, 3341), (INPCRD, 5), (LAMMPSdcd2, 12421), @@ -49,7 +50,9 @@ (TRR, 47681), (XTC, 47681), (np.zeros((1, 10, 3)), 10), # memory reader default - ]) + ], +) + @working_readers def test_minimal_parser(filename, expected_n_atoms): @@ -65,14 +68,18 @@ def test_universe_with_minimal(filename, expected_n_atoms): assert len(u.atoms) == expected_n_atoms -nonworking_readers = pytest.mark.parametrize('filename,n_atoms', [ - (TRJ, 252), - (TRJncdf, 2661), - (TRZ, 8184), -]) +nonworking_readers = pytest.mark.parametrize( + "filename,n_atoms", + [ + (TRJ, 252), + (TRJncdf, 2661), + (TRZ, 8184), + ], +) + @nonworking_readers -def test_minimal_parser_fail(filename,n_atoms): +def test_minimal_parser_fail(filename, n_atoms): with MinimalParser(filename) as p: with pytest.raises(NotImplementedError): p.parse() @@ -89,15 +96,18 @@ def test_minimal_n_atoms_kwarg(filename, n_atoms): def memory_possibilities(): # iterate over all possible shapes for a MemoryReader array # number of frames, atoms and coordinates - n = {'f': 1, 'a': 10, 'c': 3} - for permutation in itertools.permutations('fac', 3): - order = ''.join(permutation) + n = {"f": 1, "a": 10, "c": 3} + for permutation in itertools.permutations("fac", 3): + order = "".join(permutation) array = np.zeros([n[val] for val in permutation]) yield array, order -memory_reader = pytest.mark.parametrize('array,order', list(memory_possibilities())) +memory_reader = pytest.mark.parametrize( + "array,order", list(memory_possibilities()) +) + @memory_reader def test_memory_minimal_parser(array, order): @@ -105,6 +115,7 @@ def test_memory_minimal_parser(array, order): top = p.parse(order=order) assert top.n_atoms == 10 + @memory_reader def test_memory_universe(array, order): u = mda.Universe(array, order=order, to_guess=()) diff --git a/testsuite/MDAnalysisTests/topology/test_mmtf.py b/testsuite/MDAnalysisTests/topology/test_mmtf.py index 9e2a85f7784..7125d23f2d3 100644 --- a/testsuite/MDAnalysisTests/topology/test_mmtf.py +++ b/testsuite/MDAnalysisTests/topology/test_mmtf.py @@ -12,16 +12,28 @@ class MMTFBase(ParserBase): expected_attrs = [ - 'ids', 'names', 'types', 'altLocs', 'tempfactors', 'occupancies', - 'charges', 'names', 'resnames', 'resids', 'resnums', 'icodes', - 'segids', 'bonds', 'models' + "ids", + "names", + "types", + "altLocs", + "tempfactors", + "occupancies", + "charges", + "names", + "resnames", + "resids", + "resnums", + "icodes", + "segids", + "bonds", + "models", ] class TestMMTFParser(MMTFBase): parser = mda.topology.MMTFParser.MMTFParser ref_filename = MMTF - guessed_attrs = ['masses'] + guessed_attrs = ["masses"] expected_n_atoms = 512 expected_n_residues = 124 expected_n_segments = 8 @@ -40,7 +52,7 @@ class TestMMTFSkinny(MMTFBase): # for all attributes often in MMTF, # check that we get expected error on access # (sort so pytest gets reliable order) - guessed_attrs = ['ids', 'masses', 'segids'] + guessed_attrs = ["ids", "masses", "segids"] expected_n_atoms = 660 expected_n_residues = 134 expected_n_segments = 2 @@ -49,7 +61,7 @@ class TestMMTFSkinny(MMTFBase): class TestMMTFSkinny2(MMTFBase): parser = mda.topology.MMTFParser.MMTFParser ref_filename = MMTF_skinny2 - guessed_attrs = ['ids', 'masses', 'segids'] + guessed_attrs = ["ids", "masses", "segids"] expected_n_atoms = 169 expected_n_residues = 44 expected_n_segments = 2 @@ -70,10 +82,10 @@ def test_names(self, u): assert_equal(u.atoms.names[:3], ["O5'", "C5'", "C4'"]) def test_resnames(self, u): - assert_equal(u.residues.resnames[:3], ['DG', 'DA', 'DA']) + assert_equal(u.residues.resnames[:3], ["DG", "DA", "DA"]) def test_segids(self, u): - assert_equal(u.segments[:3].segids, ['A', 'B', 'C']) + assert_equal(u.segments[:3].segids, ["A", "B", "C"]) def test_resids(self, u): assert_equal(u.residues.resids[-3:], [2008, 2009, 2010]) @@ -86,16 +98,16 @@ def test_bfactors(self, u): assert_equal(u.atoms.bfactors[:3], [9.48, 10.88, 10.88]) def test_types(self, u): - assert_equal(u.atoms.types[:3], ['O', 'C', 'C']) + assert_equal(u.atoms.types[:3], ["O", "C", "C"]) def test_models(self, u): assert all(u.atoms.models == 0) def test_icodes(self, u): - assert all(u.atoms.icodes == '') + assert all(u.atoms.icodes == "") def test_altlocs(self, u): - assert all(u.atoms.altLocs[:3] == '') + assert all(u.atoms.altLocs[:3] == "") def test_guessed_masses(self, u): expected = [15.999, 12.011, 12.011, 15.999, 12.011, 15.999, 12.011] @@ -143,33 +155,33 @@ def u(self): return mda.Universe(MMTF_gz) def test_model_selection(self, u): - m1 = u.select_atoms('model 0') - m2 = u.select_atoms('model 1') + m1 = u.select_atoms("model 0") + m2 = u.select_atoms("model 1") assert len(m1) == 570 assert len(m2) == 570 def test_model_multiple(self, u): - m2plus = u.select_atoms('model 1-10') + m2plus = u.select_atoms("model 1-10") assert len(m2plus) == 570 def test_model_multiple_2(self, u): - m2plus = u.select_atoms('model 1:10') + m2plus = u.select_atoms("model 1:10") assert len(m2plus) == 570 def test_model_multiple_3(self, u): - m1and2 = u.select_atoms('model 0-1') + m1and2 = u.select_atoms("model 0-1") assert len(m1and2) == 1140 def test_model_multiple_4(self, u): - m1and2 = u.select_atoms('model 0:1') + m1and2 = u.select_atoms("model 0:1") assert len(m1and2) == 1140 def test_model_multiple_5(self, u): - m1and2 = u.select_atoms('model 0 1') + m1and2 = u.select_atoms("model 0 1") assert len(m1and2) == 1140 diff --git a/testsuite/MDAnalysisTests/topology/test_mol2.py b/testsuite/MDAnalysisTests/topology/test_mol2.py index 604fbe63628..4157e8273a1 100644 --- a/testsuite/MDAnalysisTests/topology/test_mol2.py +++ b/testsuite/MDAnalysisTests/topology/test_mol2.py @@ -176,11 +176,17 @@ class TestMOL2Base(ParserBase): parser = mda.topology.MOL2Parser.MOL2Parser expected_attrs = [ - 'ids', 'names', 'types', 'charges', 'resids', 'resnames', 'bonds', - 'elements', + "ids", + "names", + "types", + "charges", + "resids", + "resnames", + "bonds", + "elements", ] - guessed_attrs = ['masses'] + guessed_attrs = ["masses"] expected_n_atoms = 49 expected_n_residues = 1 expected_n_segments = 1 @@ -204,10 +210,12 @@ def filename(self, request): def test_bond_orders(): - ref_orders = ('am 1 1 2 1 2 1 1 am 1 1 am 2 2 ' - '1 1 1 1 1 1 1 1 1 1 1 1 1 1 ' - 'ar ar ar 1 ar 1 ar 1 ar 1 1 1 ' - '2 1 1 1 1 2 1 1 2 1 1').split() + ref_orders = ( + "am 1 1 2 1 2 1 1 am 1 1 am 2 2 " + "1 1 1 1 1 1 1 1 1 1 1 1 1 1 " + "ar ar ar 1 ar 1 ar 1 ar 1 1 1 " + "2 1 1 1 1 2 1 1 2 1 1" + ).split() u = mda.Universe(mol2_molecule) orders = [bond.order for bond in u.atoms.bonds] assert_equal(orders, ref_orders) @@ -217,8 +225,7 @@ def test_elements(): u = mda.Universe(mol2_molecule) assert_equal( - u.atoms.elements[:5], - np.array(["N", "S", "N", "N", "O"], dtype="U3") + u.atoms.elements[:5], np.array(["N", "S", "N", "N", "O"], dtype="U3") ) @@ -227,22 +234,19 @@ def test_elements_selection(): u = mda.Universe(mol2_molecule) ag = u.select_atoms("element S") - assert_equal( - ag.elements, - np.array(["S", "S"], dtype="U3") - ) + assert_equal(ag.elements, np.array(["S", "S"], dtype="U3")) def test_wrong_elements_warnings(): - with pytest.warns(UserWarning, match='Unknown elements found') as record: - u = mda.Universe(StringIO(mol2_wrong_element), format='MOL2') + with pytest.warns(UserWarning, match="Unknown elements found") as record: + u = mda.Universe(StringIO(mol2_wrong_element), format="MOL2") # One warning from invalid elements, one from masses PendingDeprecationWarning assert len(record) == 3 - expected_elements = np.array(['N', '', ''], dtype=object) + expected_elements = np.array(["N", "", ""], dtype=object) guseed_masses = np.array([14.007, 0.0, 0.0], dtype=float) - gussed_types = np.array(['N.am', 'X.o2', 'XX.am']) + gussed_types = np.array(["N.am", "X.o2", "XX.am"]) assert_equal(u.atoms.elements, expected_elements) assert_equal(u.atoms.types, gussed_types) @@ -250,29 +254,40 @@ def test_wrong_elements_warnings(): def test_all_wrong_elements_warnings(): - with pytest.warns(UserWarning, match='Unknown elements found'): - u = mda.Universe(StringIO(mol2_all_wrong_elements), format='MOL2') + with pytest.warns(UserWarning, match="Unknown elements found"): + u = mda.Universe(StringIO(mol2_all_wrong_elements), format="MOL2") - with pytest.raises(mda.exceptions.NoDataError, - match='This Universe does not contain element ' - 'information'): + with pytest.raises( + mda.exceptions.NoDataError, + match="This Universe does not contain element " "information", + ): u.atoms.elements def test_all_elements(): - with pytest.warns(UserWarning, match='Unknown elements found'): - u = mda.Universe(StringIO(mol2_fake), format='MOL2') - - expected = ["H"] * 2 + [""] + ["C"] * 5 + [""] + ["N"] * 4 + ["O"] * 5 + \ - ["S"] * 6 + ["P"] + ["Cr"] * 2 + ["Co"] + with pytest.warns(UserWarning, match="Unknown elements found"): + u = mda.Universe(StringIO(mol2_fake), format="MOL2") + + expected = ( + ["H"] * 2 + + [""] + + ["C"] * 5 + + [""] + + ["N"] * 4 + + ["O"] * 5 + + ["S"] * 6 + + ["P"] + + ["Cr"] * 2 + + ["Co"] + ) expected = np.array(expected, dtype=object) assert_equal(u.atoms.elements, expected) # Test for Issue #3385 / PR #3598 def test_wo_optional_columns(): - u = mda.Universe(StringIO(mol2_wo_opt_col), format='MOL2') + u = mda.Universe(StringIO(mol2_wo_opt_col), format="MOL2") assert_equal(u.atoms.resids, np.array([1, 1])) with pytest.raises(mda.exceptions.NoDataError): u.atoms.resnames @@ -281,7 +296,7 @@ def test_wo_optional_columns(): def test_partial_optional_columns(): - u = mda.Universe(StringIO(mol2_partial_opt_col), format='MOL2') + u = mda.Universe(StringIO(mol2_partial_opt_col), format="MOL2") assert_equal(u.atoms.resids, np.array([1, 2])) with pytest.raises(mda.exceptions.NoDataError): u.atoms.resnames @@ -290,27 +305,27 @@ def test_partial_optional_columns(): def test_mol2_wo_required_columns(): - with pytest.raises(ValueError, - match='The @ATOM block in mol2 file'): - u = mda.Universe(StringIO(mol2_wo_required_col), format='MOL2') + with pytest.raises( + ValueError, match="The @ATOM block in mol2 file" + ): + u = mda.Universe(StringIO(mol2_wo_required_col), format="MOL2") def test_mol2_no_charges(): - with pytest.raises(ValueError, - match='indicates no charges'): - u = mda.Universe(StringIO(mol2_no_charge_error1), format='MOL2') - with pytest.raises(ValueError, - match='indicates a charge model'): - u = mda.Universe(StringIO(mol2_no_charge_error2), format='MOL2') + with pytest.raises(ValueError, match="indicates no charges"): + u = mda.Universe(StringIO(mol2_no_charge_error1), format="MOL2") + with pytest.raises(ValueError, match="indicates a charge model"): + u = mda.Universe(StringIO(mol2_no_charge_error2), format="MOL2") def test_unformat(): - with pytest.raises(ValueError, - match='Some atoms in the mol2 file'): - u = mda.Universe(StringIO(mol2_resname_unformat), format='MOL2') + with pytest.raises(ValueError, match="Some atoms in the mol2 file"): + u = mda.Universe(StringIO(mol2_resname_unformat), format="MOL2") def test_guessed_masses(): u = mda.Universe(mol2_molecules) - assert_allclose(u.atoms.masses[:7], [14.007, 32.06, - 14.007, 14.007, 15.999, 15.999, 12.011]) + assert_allclose( + u.atoms.masses[:7], + [14.007, 32.06, 14.007, 14.007, 15.999, 15.999, 12.011], + ) diff --git a/testsuite/MDAnalysisTests/topology/test_pdb.py b/testsuite/MDAnalysisTests/topology/test_pdb.py index 51822e96710..146462969af 100644 --- a/testsuite/MDAnalysisTests/topology/test_pdb.py +++ b/testsuite/MDAnalysisTests/topology/test_pdb.py @@ -22,28 +22,28 @@ # from io import StringIO -import pytest -import numpy as np -from numpy.testing import assert_equal, assert_allclose import MDAnalysis as mda +import numpy as np +import pytest +from MDAnalysis import NoDataError +from MDAnalysis.guesser import tables +from MDAnalysis.topology.PDBParser import PDBParser +from numpy.testing import assert_allclose, assert_equal -from MDAnalysisTests.topology.base import ParserBase from MDAnalysisTests.datafiles import ( PDB, PDB_HOLE, - PDB_small, + PDB_chainidnewres, + PDB_charges, PDB_conect, PDB_conect2TER, - PDB_singleconect, - PDB_chainidnewres, - PDB_sameresid_diffresname, - PDB_helix, PDB_elements, - PDB_charges, + PDB_helix, + PDB_sameresid_diffresname, + PDB_singleconect, + PDB_small, ) -from MDAnalysis.topology.PDBParser import PDBParser -from MDAnalysis import NoDataError -from MDAnalysis.guesser import tables +from MDAnalysisTests.topology.base import ParserBase _PDBPARSER = mda.topology.PDBParser.PDBParser @@ -64,24 +64,34 @@ (" 24", 24), (" 645", 645), (" 4951", 4951), - ("10267", 10267) + ("10267", 10267), ] -@pytest.mark.parametrize('hybrid, integer', hybrid36) +@pytest.mark.parametrize("hybrid, integer", hybrid36) def test_hy36decode(hybrid, integer): assert mda.topology.PDBParser.hy36decode(5, hybrid) == integer class PDBBase(ParserBase): - expected_attrs = ['ids', 'names', 'record_types', 'resids', - 'resnames', 'altLocs', 'icodes', 'occupancies', - 'tempfactors', 'chainIDs'] - guessed_attrs = ['types', 'masses'] + expected_attrs = [ + "ids", + "names", + "record_types", + "resids", + "resnames", + "altLocs", + "icodes", + "occupancies", + "tempfactors", + "chainIDs", + ] + guessed_attrs = ["types", "masses"] class TestPDBParser(PDBBase): """This one has neither chainids or segids""" + parser = mda.topology.PDBParser.PDBParser ref_filename = PDB expected_n_atoms = 47681 @@ -91,6 +101,7 @@ class TestPDBParser(PDBBase): class TestPDBParserSegids(PDBBase): """Has segids""" + parser = mda.topology.PDBParser.PDBParser ref_filename = PDB_small expected_n_atoms = 3341 @@ -102,20 +113,24 @@ class TestPDBConect(object): """Testing PDB topology parsing (PDB)""" def test_conect_parser(self): - lines = ("CONECT1233212331", - "CONECT123331233112334", - "CONECT123341233312335", - "CONECT123351233412336", - "CONECT12336123271233012335", - "CONECT12337 7718 84081234012344", - "CONECT1233812339123401234112345") - results = ((12332, [12331]), - (12333, [12331, 12334]), - (12334, [12333, 12335]), - (12335, [12334, 12336]), - (12336, [12327, 12330, 12335]), - (12337, [7718, 8408, 12340, 12344]), - (12338, [12339, 12340, 12341, 12345])) + lines = ( + "CONECT1233212331", + "CONECT123331233112334", + "CONECT123341233312335", + "CONECT123351233412336", + "CONECT12336123271233012335", + "CONECT12337 7718 84081234012344", + "CONECT1233812339123401234112345", + ) + results = ( + (12332, [12331]), + (12333, [12331, 12334]), + (12334, [12333, 12335]), + (12335, [12334, 12336]), + (12336, [12327, 12330, 12335]), + (12337, [7718, 8408, 12340, 12344]), + (12338, [12339, 12340, 12341, 12345]), + ) for line, res in zip(lines, results): bonds = mda.topology.PDBParser._parse_conect(line) assert_equal(bonds[0], res[0]) @@ -124,8 +139,9 @@ def test_conect_parser(self): def test_conect_parser_runtime(self): with pytest.raises(RuntimeError): - mda.topology.PDBParser._parse_conect('CONECT12337 7718 ' - '84081234012344123') + mda.topology.PDBParser._parse_conect( + "CONECT12337 7718 " "84081234012344123" + ) def test_conect_topo_parser(self): """Check that the parser works as intended, @@ -145,7 +161,7 @@ def parse(): with pytest.warns(UserWarning): struc = parse() - assert hasattr(struc, 'bonds') + assert hasattr(struc, "bonds") assert len(struc.bonds.values) == 4 @@ -157,7 +173,7 @@ def parse(): with pytest.warns(UserWarning): struc = parse() - assert hasattr(struc, 'bonds') + assert hasattr(struc, "bonds") assert len(struc.bonds.values) == 2 @@ -169,7 +185,7 @@ def test_new_chainid_new_res(): assert len(u.residues) == 4 assert_equal(u.residues.resids, [1, 2, 3, 3]) assert len(u.segments) == 4 - assert_equal(u.segments.segids, ['A', 'B', 'C', 'D']) + assert_equal(u.segments.segids, ["A", "B", "C", "D"]) assert len(u.segments[0].atoms) == 5 assert len(u.segments[1].atoms) == 5 assert len(u.segments[2].atoms) == 5 @@ -180,7 +196,7 @@ def test_sameresid_diffresname(): with _PDBPARSER(PDB_sameresid_diffresname) as p: top = p.parse() resids = [9, 9] - resnames = ['GLN', 'POPC'] + resnames = ["GLN", "POPC"] for i, (resid, resname) in enumerate(zip(resids, resnames)): assert top.resids.values[i] == resid assert top.resnames.values[i] == resname @@ -189,11 +205,11 @@ def test_sameresid_diffresname(): def test_PDB_record_types(): u = mda.Universe(PDB_HOLE) - assert u.atoms[0].record_type == 'ATOM' - assert u.atoms[132].record_type == 'HETATM' + assert u.atoms[0].record_type == "ATOM" + assert u.atoms[132].record_type == "HETATM" - assert_equal(u.atoms[10:20].record_types, 'ATOM') - assert_equal(u.atoms[271:].record_types, 'HETATM') + assert_equal(u.atoms[10:20].record_types, "ATOM") + assert_equal(u.atoms[271:].record_types, "HETATM") PDB_noresid = """\ @@ -210,7 +226,7 @@ def test_PDB_record_types(): def test_PDB_no_resid(): - u = mda.Universe(StringIO(PDB_noresid), format='PDB') + u = mda.Universe(StringIO(PDB_noresid), format="PDB") assert len(u.atoms) == 4 assert len(u.residues) == 1 @@ -242,7 +258,7 @@ def test_PDB_no_resid(): def test_PDB_hex(): - u = mda.Universe(StringIO(PDB_hex), format='PDB') + u = mda.Universe(StringIO(PDB_hex), format="PDB") assert len(u.atoms) == 5 assert u.atoms[0].id == 1 assert u.atoms[1].id == 100000 @@ -253,7 +269,7 @@ def test_PDB_hex(): @pytest.mark.filterwarnings("error:Failed to guess the mass") def test_PDB_metals(): - u = mda.Universe(StringIO(PDB_metals), format='PDB') + u = mda.Universe(StringIO(PDB_metals), format="PDB") assert len(u.atoms) == 4 assert u.atoms[0].mass == pytest.approx(tables.masses["CU"]) @@ -266,11 +282,17 @@ def test_PDB_elements(): """The test checks whether elements attribute are assigned properly given a PDB file with valid elements record. """ - u = mda.Universe(PDB_elements, format='PDB') - element_list = np.array(['N', 'C', 'C', 'O', 'C', 'C', 'O', 'N', 'H', - 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'Cu', 'Fe', - 'Mg', 'Ca', 'S', 'O', 'C', 'C', 'S', 'O', 'C', - 'C'], dtype=object) + u = mda.Universe(PDB_elements, format="PDB") + # fmt: off + element_list = np.array( + [ + 'N', 'C', 'C', 'O', 'C', 'C', 'O', 'N', 'H', 'H', 'H', 'H', 'H', + 'H', 'H', 'H', 'Cu', 'Fe', 'Mg', 'Ca', 'S', 'O', 'C', 'C', 'S', + 'O', 'C', 'C' + ], + dtype=object + ) + # fmt: on assert_equal(u.atoms.elements, element_list) @@ -280,8 +302,10 @@ def test_missing_elements_noattribute(): 1) a warning is raised if elements are missing 2) the elements attribute is not set """ - wmsg = ("Element information is missing, elements attribute will not be " - "populated") + wmsg = ( + "Element information is missing, elements attribute will not be " + "populated" + ) with pytest.warns(UserWarning, match=wmsg): u = mda.Universe(PDB_small) with pytest.raises(AttributeError): @@ -308,14 +332,21 @@ def test_wrong_elements_warnings(): """The test checks whether there are invalid elements in the elements column which have been parsed and returns an appropriate warning. """ - with pytest.warns(UserWarning, match='Unknown element XX found'): - u = mda.Universe(StringIO(PDB_wrong_ele,), format='PDB') - - expected_elements = np.array(['N', '', 'C', 'O', '', 'Cu', 'Fe', 'Mg'], - dtype=object) - gussed_types = np.array(['N', '', 'C', 'O', 'XX', 'CU', 'Fe', 'MG']) - guseed_masses = np.array([14.007, 0.0, 12.011, 15.999, 0.0, - 63.546, 55.847, 24.305], dtype=float) + with pytest.warns(UserWarning, match="Unknown element XX found"): + u = mda.Universe( + StringIO( + PDB_wrong_ele, + ), + format="PDB", + ) + + expected_elements = np.array( + ["N", "", "C", "O", "", "Cu", "Fe", "Mg"], dtype=object + ) + gussed_types = np.array(["N", "", "C", "O", "XX", "CU", "Fe", "MG"]) + guseed_masses = np.array( + [14.007, 0.0, 12.011, 15.999, 0.0, 63.546, 55.847, 24.305], dtype=float + ) assert_equal(u.atoms.elements, expected_elements) assert_equal(u.atoms.types, gussed_types) @@ -324,12 +355,22 @@ def test_wrong_elements_warnings(): def test_guessed_masses_and_types_values(): """Test that guessed masses and types have the expected values for universe - constructed from PDB file. + constructed from PDB file. """ - u = mda.Universe(PDB, format='PDB') - gussed_types = np.array(['N', 'H', 'H', 'H', 'C', 'H', 'C', 'H', 'H', 'C']) - guseed_masses = [14.007, 1.008, 1.008, 1.008, - 12.011, 1.008, 12.011, 1.008, 1.008, 12.011] + u = mda.Universe(PDB, format="PDB") + gussed_types = np.array(["N", "H", "H", "H", "C", "H", "C", "H", "H", "C"]) + guseed_masses = [ + 14.007, + 1.008, + 1.008, + 1.008, + 12.011, + 1.008, + 12.011, + 1.008, + 1.008, + 12.011, + ] failed_type_guesses = u.atoms.types == "" assert_allclose(u.atoms.masses[:10], guseed_masses) @@ -353,9 +394,15 @@ def test_PDB_charges(): properly given a PDB file with a valid formal charges record. """ u = mda.Universe(PDB_charges) - formal_charges = np.array([0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0], dtype=int) + # fmt: off + formal_charges = np.array( + [ + 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ], + dtype=int + ) + # fmt: on assert_equal(u.atoms.formalcharges, formal_charges) @@ -377,10 +424,10 @@ def test_PDB_charges(): """ -@pytest.mark.parametrize('infile,entry', [ - [PDB_charges_nosign, r'2'], - [PDB_charges_invertsign, r'\+2'] -]) +@pytest.mark.parametrize( + "infile,entry", + [[PDB_charges_nosign, r"2"], [PDB_charges_invertsign, r"\+2"]], +) def test_PDB_bad_charges(infile, entry): """ Test that checks that a warning is raised and formal charges are not set: @@ -389,5 +436,5 @@ def test_PDB_bad_charges(infile, entry): """ wmsg = f"Unknown entry {entry} encountered in formal charge field." with pytest.warns(UserWarning, match=wmsg): - u = mda.Universe(StringIO(infile), format='PDB') - assert not hasattr(u, 'formalcharges') + u = mda.Universe(StringIO(infile), format="PDB") + assert not hasattr(u, "formalcharges") diff --git a/testsuite/MDAnalysisTests/topology/test_pdbqt.py b/testsuite/MDAnalysisTests/topology/test_pdbqt.py index 9578c1e9483..02b326b5f53 100644 --- a/testsuite/MDAnalysisTests/topology/test_pdbqt.py +++ b/testsuite/MDAnalysisTests/topology/test_pdbqt.py @@ -51,15 +51,16 @@ class TestPDBQT(ParserBase): "tempfactors", ] - guessed_attrs = ['masses'] + guessed_attrs = ["masses"] expected_n_atoms = 1805 expected_n_residues = 199 # resids go 2-102 then 2-99 expected_n_segments = 2 # res2-102 are A, 2-99 are B def test_guessed_masses(self, filename): u = mda.Universe(filename) - assert_allclose(u.atoms.masses[:7], [14.007, 0., - 0., 12.011, 12.011, 0., 12.011]) + assert_allclose( + u.atoms.masses[:7], [14.007, 0.0, 0.0, 12.011, 12.011, 0.0, 12.011] + ) def test_footnote(): diff --git a/testsuite/MDAnalysisTests/topology/test_pqr.py b/testsuite/MDAnalysisTests/topology/test_pqr.py index fa35171efe7..df3790b7a75 100644 --- a/testsuite/MDAnalysisTests/topology/test_pqr.py +++ b/testsuite/MDAnalysisTests/topology/test_pqr.py @@ -34,10 +34,18 @@ class TestPQRParser(ParserBase): parser = mda.topology.PQRParser.PQRParser ref_filename = PQR - expected_attrs = ['ids', 'names', 'charges', 'radii', 'record_types', - 'resids', 'resnames', 'icodes', - 'segids'] - guessed_attrs = ['masses', 'types'] + expected_attrs = [ + "ids", + "names", + "charges", + "radii", + "record_types", + "resids", + "resnames", + "icodes", + "segids", + ] + guessed_attrs = ["masses", "types"] expected_n_atoms = 3341 expected_n_residues = 214 expected_n_segments = 1 @@ -51,8 +59,8 @@ def test_attr_size(self, top): assert len(top.resnames) == top.n_residues assert len(top.segids) == top.n_segments - expected_masses = [14.007, 1.008, 1.008, 1.008, 12.011, 1.008, 12.011] - expected_types = ['N', 'H', 'H', 'H', 'C', 'H', 'C'] + expected_masses = [14.007, 1.008, 1.008, 1.008, 12.011, 1.008, 12.011] + expected_types = ["N", "H", "H", "H", "C", "H", "C"] def test_guessed_masses(self, filename): u = mda.Universe(filename) @@ -69,20 +77,20 @@ class TestPQRParser2(TestPQRParser): expected_n_residues = 474 expected_masses = [14.007, 12.011, 12.011, 15.999, 12.011, 12.011, 12.011] - expected_types = ['N', 'C', 'C', 'O', 'C', 'C', 'C'] + expected_types = ["N", "C", "C", "O", "C", "C", "C"] def test_record_types(): u = mda.Universe(PQR_icodes) - assert u.atoms[4052].record_type == 'ATOM' - assert u.atoms[4053].record_type == 'HETATM' + assert u.atoms[4052].record_type == "ATOM" + assert u.atoms[4053].record_type == "HETATM" - assert_equal(u.atoms[:10].record_types, 'ATOM') - assert_equal(u.atoms[4060:4070].record_types, 'HETATM') + assert_equal(u.atoms[:10].record_types, "ATOM") + assert_equal(u.atoms[4060:4070].record_types, "HETATM") -GROMACS_PQR = ''' +GROMACS_PQR = """ REMARK The B-factors in this file hold atomic radii REMARK The occupancy in this file hold atomic charges TITLE system @@ -92,17 +100,19 @@ def test_record_types(): ATOM 1 O ZR 1 15.710 17.670 23.340 -0.67 1.48 O TER ENDMDL -''' +""" def test_gromacs_flavour(): - u = mda.Universe(StringIO(GROMACS_PQR), format='PQR') + u = mda.Universe(StringIO(GROMACS_PQR), format="PQR") assert len(u.atoms) == 1 # topology things - assert u.atoms[0].type == 'O' - assert u.atoms[0].segid == 'SYSTEM' + assert u.atoms[0].type == "O" + assert u.atoms[0].segid == "SYSTEM" assert_almost_equal(u.atoms[0].radius, 1.48, decimal=5) assert_almost_equal(u.atoms[0].charge, -0.67, decimal=5) # coordinatey things - assert_almost_equal(u.atoms[0].position, [15.710, 17.670, 23.340], decimal=4) + assert_almost_equal( + u.atoms[0].position, [15.710, 17.670, 23.340], decimal=4 + ) diff --git a/testsuite/MDAnalysisTests/topology/test_psf.py b/testsuite/MDAnalysisTests/topology/test_psf.py index ccfbb0bddd8..c86e6a781c5 100644 --- a/testsuite/MDAnalysisTests/topology/test_psf.py +++ b/testsuite/MDAnalysisTests/topology/test_psf.py @@ -37,34 +37,45 @@ XYZ, ) + class PSFBase(ParserBase): parser = mda.topology.PSFParser.PSFParser - expected_attrs = ['ids', 'names', 'types', 'masses', - 'charges', - 'resids', 'resnames', - 'segids', - 'bonds', 'angles', 'dihedrals', 'impropers'] + expected_attrs = [ + "ids", + "names", + "types", + "masses", + "charges", + "resids", + "resnames", + "segids", + "bonds", + "angles", + "dihedrals", + "impropers", + ] class TestPSFParser(PSFBase): """ Based on small PDB with AdK (:data:`PDB_small`). """ + ref_filename = PSF expected_n_atoms = 3341 expected_n_residues = 214 expected_n_segments = 1 - @pytest.fixture(params=['uncompressed', 'bz2']) + @pytest.fixture(params=["uncompressed", "bz2"]) def filename(self, request, tmpdir): - if request.param == 'uncompressed': + if request.param == "uncompressed": return self.ref_filename else: - fn = str(tmpdir.join('file.psf.bz2')) - with open(self.ref_filename, 'rb') as f: + fn = str(tmpdir.join("file.psf.bz2")) + with open(self.ref_filename, "rb") as f: stuff = f.read() buf = bz2.compress(stuff) - with open(fn, 'wb') as out: + with open(fn, "wb") as out: out.write(buf) return fn @@ -114,6 +125,7 @@ class TestNAMDPSFParser(PSFBase): https://github.com/MDAnalysis/mdanalysis/issues/107 """ + ref_filename = PSF_NAMD expected_n_atoms = 130 expected_n_residues = 6 @@ -134,6 +146,7 @@ def test_as_universe_resids(self): for seg in u.segments: assert_equal(seg.residues.resids[:4], [380, 381, 382, 383]) + class TestPSFParserNoTop(PSFBase): ref_filename = PSF_notop expected_n_atoms = 3341 @@ -152,6 +165,7 @@ def test_dihedrals_total_counts(self, top): def test_impropers_total_counts(self, top): assert len(top.impropers.values) == 0 + def test_psf_nosegid(): """Issue #121""" u = mda.Universe(PSF_nosegid) @@ -159,7 +173,8 @@ def test_psf_nosegid(): assert u.atoms.n_atoms == 98 assert_equal(u.segments.segids, ["SYSTEM"]) + def test_psf_inscode(): """Issue #2053 and #4189""" u = mda.Universe(PSF_inscode) - assert_equal(u.residues.resids[:3], [1, 1, 1]) \ No newline at end of file + assert_equal(u.residues.resids[:3], [1, 1, 1]) diff --git a/testsuite/MDAnalysisTests/topology/test_tables.py b/testsuite/MDAnalysisTests/topology/test_tables.py index 37246ad1864..6b19c715870 100644 --- a/testsuite/MDAnalysisTests/topology/test_tables.py +++ b/testsuite/MDAnalysisTests/topology/test_tables.py @@ -32,4 +32,3 @@ def test_moved_to_guessers_warning(): wmsg = "has been moved to MDAnalysis.guesser.tables" with pytest.warns(DeprecationWarning, match=wmsg): reload(tables) - diff --git a/testsuite/MDAnalysisTests/topology/test_top.py b/testsuite/MDAnalysisTests/topology/test_top.py index 3a8227227c1..a67c9283b77 100644 --- a/testsuite/MDAnalysisTests/topology/test_top.py +++ b/testsuite/MDAnalysisTests/topology/test_top.py @@ -20,37 +20,48 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -import sys import platform +import sys import warnings + import MDAnalysis as mda -import pytest import numpy as np +import pytest from numpy.testing import assert_equal -from MDAnalysisTests.topology.base import ParserBase + +from MDAnalysisTests.datafiles import PRM # ache.prmtop +from MDAnalysisTests.datafiles import PRM7 # tz2.truncoct.parm7.bz2 +from MDAnalysisTests.datafiles import PRM12 # anti.top +from MDAnalysisTests.datafiles import PRM_chainid_bz2 # multi_anche.prmtop.bz2 from MDAnalysisTests.datafiles import ( - PRM, # ache.prmtop - PRM_chainid_bz2, # multi_anche.prmtop.bz2 - PRM12, # anti.top - PRM7, # tz2.truncoct.parm7.bz2 - PRMpbc, + PRM19SBOPC, PRMNCRST, PRMNEGATIVE, + PRM_UreyBradley, PRMErr1, PRMErr2, PRMErr3, PRMErr4, PRMErr5, - PRM_UreyBradley, - PRM19SBOPC, + PRMpbc, ) +from MDAnalysisTests.topology.base import ParserBase class TOPBase(ParserBase): parser = mda.topology.TOPParser.TOPParser expected_attrs = [ - "names", "types", "type_indices", "charges", "masses", "resnames", - "bonds", "angles", "dihedrals", "impropers", "elements" + "names", + "types", + "type_indices", + "charges", + "masses", + "resnames", + "bonds", + "angles", + "dihedrals", + "impropers", + "elements", ] expected_n_segments = 1 @@ -79,14 +90,18 @@ def test_angles_atom_counts(self, filename): def test_dihedrals_atom_counts(self, filename): u = mda.Universe(filename) assert len(u.atoms[[0]].dihedrals) == self.expected_n_zero_dihedrals - assert len(u.atoms[[self.atom_i]].dihedrals) == \ - self.expected_n_i_dihedrals + assert ( + len(u.atoms[[self.atom_i]].dihedrals) + == self.expected_n_i_dihedrals + ) def test_impropers_atom_counts(self, filename): u = mda.Universe(filename) assert len(u.atoms[[0]].impropers) == self.expected_n_zero_impropers - assert len(u.atoms[[self.atom_i]].impropers) == \ - self.expected_n_i_impropers + assert ( + len(u.atoms[[self.atom_i]].impropers) + == self.expected_n_i_impropers + ) def test_bonds_identity(self, top): vals = top.bonds.values @@ -134,8 +149,12 @@ def test_improper_atoms_bonded(self, top): forward = ((imp[0], imp[2]), (imp[1], imp[2]), (imp[2], imp[3])) backward = ((imp[0], imp[1]), (imp[1], imp[2]), (imp[1], imp[3])) for a, b in zip(forward, backward): - assert ((b in vals) or (b[::-1] in vals) or - (a in vals) or (a[::-1] in vals)) + assert ( + (b in vals) + or (b[::-1] in vals) + or (a in vals) + or (a[::-1] in vals) + ) def test_elements(self, top): """Tests elements attribute. @@ -147,18 +166,29 @@ def test_elements(self, top): if self.expected_elems: for erange, evals in zip(self.elems_ranges, self.expected_elems): - assert_equal(top.elements.values[erange[0]:erange[1]], evals, - "unexpected element match") + assert_equal( + top.elements.values[erange[0] : erange[1]], + evals, + "unexpected element match", + ) else: - assert not hasattr(top, 'elements'), 'Unexpected elements attr' + assert not hasattr(top, "elements"), "Unexpected elements attr" class TestPRMParser(TOPBase): ref_filename = PRM # Does not contain an ATOMIC_NUMBER record, so no elements expected_attrs = [ - "names", "types", "type_indices", "charges", "masses", "resnames", - "bonds", "angles", "dihedrals", "impropers" + "names", + "types", + "type_indices", + "charges", + "masses", + "resnames", + "bonds", + "angles", + "dihedrals", + "impropers", ] expected_n_atoms = 252 expected_n_residues = 14 @@ -177,30 +207,68 @@ class TestPRMParser(TOPBase): expected_n_i_impropers = 4 atom_zero_bond_values = ((0, 4), (0, 1), (0, 2), (0, 3)) atom_i_bond_values = ((79, 80), (79, 83), (77, 79)) - atom_zero_angle_values = ((0, 4, 6), (0, 4, 10), (3, 0, 4), - (2, 0, 3), (2, 0, 4), (1, 0, 2), - (1, 0, 3), (1, 0, 4), (0, 4, 5)) - atom_i_angle_values = ((80, 79, 83), (77, 79, 80), (77, 79, 83), - (74, 77, 79), (79, 80, 81), (79, 80, 82), - (79, 83, 84), (79, 83, 85), (78, 77, 79)) - atom_zero_dihedral_values = ((0, 4, 10, 11), (0, 4, 10, 12), - (3, 0, 4, 5), (3, 0, 4, 6), - (3, 0, 4, 10), (2, 0, 4, 5), - (2, 0, 4, 6), (2, 0, 4, 10), - (1, 0, 4, 5), (1, 0, 4, 6), - (1, 0, 4, 10), (0, 4, 6, 7), - (0, 4, 6, 8), (0, 4, 6, 9)) - atom_i_dihedral_values = ((71, 74, 77, 79), (74, 77, 79, 80), - (74, 77, 79, 83), (75, 74, 77, 79), - (76, 74, 77, 79), (77, 79, 80, 81), - (77, 79, 80, 82), (77, 79, 83, 84), - (77, 79, 83, 85), (78, 77, 79, 80), - (78, 77, 79, 83), (80, 79, 83, 84), - (80, 79, 83, 85), (81, 80, 79, 83), - (82, 80, 79, 83)) + atom_zero_angle_values = ( + (0, 4, 6), + (0, 4, 10), + (3, 0, 4), + (2, 0, 3), + (2, 0, 4), + (1, 0, 2), + (1, 0, 3), + (1, 0, 4), + (0, 4, 5), + ) + atom_i_angle_values = ( + (80, 79, 83), + (77, 79, 80), + (77, 79, 83), + (74, 77, 79), + (79, 80, 81), + (79, 80, 82), + (79, 83, 84), + (79, 83, 85), + (78, 77, 79), + ) + atom_zero_dihedral_values = ( + (0, 4, 10, 11), + (0, 4, 10, 12), + (3, 0, 4, 5), + (3, 0, 4, 6), + (3, 0, 4, 10), + (2, 0, 4, 5), + (2, 0, 4, 6), + (2, 0, 4, 10), + (1, 0, 4, 5), + (1, 0, 4, 6), + (1, 0, 4, 10), + (0, 4, 6, 7), + (0, 4, 6, 8), + (0, 4, 6, 9), + ) + atom_i_dihedral_values = ( + (71, 74, 77, 79), + (74, 77, 79, 80), + (74, 77, 79, 83), + (75, 74, 77, 79), + (76, 74, 77, 79), + (77, 79, 80, 81), + (77, 79, 80, 82), + (77, 79, 83, 84), + (77, 79, 83, 85), + (78, 77, 79, 80), + (78, 77, 79, 83), + (80, 79, 83, 84), + (80, 79, 83, 85), + (81, 80, 79, 83), + (82, 80, 79, 83), + ) atom_zero_improper_values = () - atom_i_improper_values = ((74, 79, 77, 78), (77, 80, 79, 83), - (79, 81, 80, 82), (79, 84, 83, 85)) + atom_i_improper_values = ( + (74, 79, 77, 78), + (77, 80, 79, 83), + (79, 81, 80, 82), + (79, 84, 83, 85), + ) expected_elems = None @@ -305,76 +373,25 @@ class TestPRMChainidParser(TOPBase): expected_elems = [ np.array( - [ - "N", - "H", - "H", - "H", - "C", - "H", - "C", - "H", - "H", - ], + ["N", "H", "H", "H", "C", "H", "C", "H", "H"], dtype=object, ), np.array( - [ - "O", - "O", - "N", - "H", - "H", - "H", - "C", - ], + ["O", "O", "N", "H", "H", "H", "C"], dtype=object, ), np.array(["H", "C", "O", "O", "N", "H", "H", "H"], dtype=object), ] + # fmt: off expected_chainIDs = np.array( [ - "A", - "A", - "A", - "A", - "A", - "A", - "A", - "A", - "A", - "A", - "A", - "A", - "A", - "A", - "B", - "B", - "B", - "B", - "B", - "B", - "B", - "B", - "B", - "B", - "B", - "B", - "B", - "B", - "C", - "C", - "C", - "C", - "C", - "C", - "C", - "C", - "C", - "C", + "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", + "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", + "C", "C", "C", "C", "C", "C", "C", "C", "C", "C", ] ) + # fmt: on def test_chainIDs(self, filename): """Tests chainIDs attribute. @@ -386,7 +403,9 @@ def test_chainIDs(self, filename): u = mda.Universe(filename) if hasattr(self, "expected_chainIDs"): - reschainIDs = [atomchainIDs[0] for atomchainIDs in u.residues.chainIDs] + reschainIDs = [ + atomchainIDs[0] for atomchainIDs in u.residues.chainIDs + ] assert_equal( reschainIDs, self.expected_chainIDs, "unexpected element match" ) @@ -413,54 +432,94 @@ class TestPRM12Parser(TOPBase): atom_i = 335 ref_proteinatoms = 0 atom_zero_bond_values = ((0, 1),) - atom_i_bond_values = ((335, 337), (335, 354), - (334, 335), (335, 336)) + atom_i_bond_values = ((335, 337), (335, 354), (334, 335), (335, 336)) atom_zero_angle_values = ((0, 1, 2),) - atom_i_angle_values = ((337, 335, 354), (335, 337, 338), - (335, 337, 351), (335, 354, 352), - (334, 335, 337), (334, 335, 354), - (332, 334, 335), (336, 335, 337), - (336, 335, 354), (335, 354, 355), - (335, 354, 356), (334, 335, 336)) - atom_zero_dihedral_values = ((0, 1, 2, 3), (0, 1, 2, 4), - (0, 1, 2, 5)) - atom_i_dihedral_values = ((329, 332, 334, 335), (332, 334, 335, 336), - (332, 334, 335, 337), (332, 334, 335, 354), - (332, 352, 354, 335), (333, 332, 334, 335), - (334, 335, 337, 338), (334, 335, 337, 351), - (334, 335, 354, 352), (334, 335, 354, 355), - (334, 335, 354, 356), (335, 334, 332, 352), - (335, 337, 338, 339), (335, 337, 338, 340), - (335, 337, 351, 341), (335, 337, 351, 350), - (335, 354, 352, 353), (335, 354, 352, 357), - (336, 335, 337, 338), (336, 335, 337, 351), - (336, 335, 354, 352), (336, 335, 354, 355), - (336, 335, 354, 356), (337, 335, 354, 352), - (337, 335, 354, 355), (337, 335, 354, 356), - (338, 337, 335, 354), (351, 337, 335, 354)) + atom_i_angle_values = ( + (337, 335, 354), + (335, 337, 338), + (335, 337, 351), + (335, 354, 352), + (334, 335, 337), + (334, 335, 354), + (332, 334, 335), + (336, 335, 337), + (336, 335, 354), + (335, 354, 355), + (335, 354, 356), + (334, 335, 336), + ) + atom_zero_dihedral_values = ((0, 1, 2, 3), (0, 1, 2, 4), (0, 1, 2, 5)) + atom_i_dihedral_values = ( + (329, 332, 334, 335), + (332, 334, 335, 336), + (332, 334, 335, 337), + (332, 334, 335, 354), + (332, 352, 354, 335), + (333, 332, 334, 335), + (334, 335, 337, 338), + (334, 335, 337, 351), + (334, 335, 354, 352), + (334, 335, 354, 355), + (334, 335, 354, 356), + (335, 334, 332, 352), + (335, 337, 338, 339), + (335, 337, 338, 340), + (335, 337, 351, 341), + (335, 337, 351, 350), + (335, 354, 352, 353), + (335, 354, 352, 357), + (336, 335, 337, 338), + (336, 335, 337, 351), + (336, 335, 354, 352), + (336, 335, 354, 355), + (336, 335, 354, 356), + (337, 335, 354, 352), + (337, 335, 354, 355), + (337, 335, 354, 356), + (338, 337, 335, 354), + (351, 337, 335, 354), + ) atom_zero_improper_values = () atom_i_improper_values = ((335, 337, 338, 351),) elems_ranges = [[0, 36], [351, 403]] - expected_elems = [np.array(["H", "O", "C", "H", "H", "C", "H", "O", "C", - "H", "N", "C", "H", "N", "C", "C", "O", "N", - "H", "C", "N", "H", "H", "N", "C", "C", "H", - "C", "H", "H", "O", "P", "O", "O", "O", "C"], - dtype=object), - np.array(["C", "C", "H", "C", "H", "H", "O", "P", "O", - "O", "O", "C", "H", "H", "C", "H", "O", "C", - "H", "N", "C", "H", "N", "C", "C", "O", "N", - "H", "C", "N", "H", "H", "N", "C", "C", "H", - "C", "H", "H", "O", "H", "Na", "Na", "Na", - "Na", "Na", "Na", "Na", "Na", "O", "H", "H"], - dtype=object)] + # fmt: off + expected_elems = [ + np.array( + [ + "H", "O", "C", "H", "H", "C", "H", "O", "C", "H", "N", "C", + "H", "N", "C", "C", "O", "N", "H", "C", "N", "H", "H", "N", + "C", "C", "H", "C", "H", "H", "O", "P", "O", "O", "O","C", + ], + dtype=object, + ), + np.array( + [ + "C", "C", "H", "C", "H", "H", "O", "P", "O", "O", "O", "C", + "H", "H", "C", "H", "O", "C", "H", "N", "C", "H", "N", "C", + "C", "O", "N", "H", "C", "N", "H", "H", "N", "C", "C", "H", + "C", "H", "H", "O", "H", "Na", "Na", "Na", "Na", "Na", "Na", + "Na", "Na", "O", "H", "H", + ], + dtype=object, + ), + ] + # fmt: on class TestParm7Parser(TOPBase): ref_filename = PRM7 # Does not contain an ATOMIC_NUMBER record, so no elements expected_attrs = [ - "names", "types", "type_indices", "charges", "masses", "resnames", - "bonds", "angles", "dihedrals", "impropers" + "names", + "types", + "type_indices", + "charges", + "masses", + "resnames", + "bonds", + "angles", + "dihedrals", + "impropers", ] expected_n_atoms = 5827 expected_n_residues = 1882 @@ -478,39 +537,78 @@ class TestParm7Parser(TOPBase): expected_n_zero_impropers = 0 expected_n_i_impropers = 2 atom_zero_bond_values = ((0, 4), (0, 1), (0, 2), (0, 3)) - atom_i_bond_values = ((135, 137), (135, 155), (133, 135), - (135, 136)) - atom_zero_angle_values = ((0, 4, 6), (0, 4, 11), (3, 0, 4), - (2, 0, 3), (2, 0, 4), (1, 0, 2), - (1, 0, 3), (1, 0, 4), (0, 4, 5)) - atom_i_angle_values = ((131, 133, 135), (137, 135, 155), - (135, 137, 140), (135, 155, 156), - (135, 155, 157), (133, 135, 137), - (133, 135, 155), (136, 135, 137), - (136, 135, 155), (135, 137, 138), - (135, 137, 139), (134, 133, 135), - (133, 135, 136)) - atom_zero_dihedral_values = ((0, 4, 6, 7), (0, 4, 6, 8), - (0, 4, 6, 9), (0, 4, 11, 12), - (0, 4, 11, 13), (1, 0, 4, 5), - (1, 0, 4, 6), (1, 0, 4, 11), - (2, 0, 4, 5), (2, 0, 4, 6), - (2, 0, 4, 11), (3, 0, 4, 5), - (3, 0, 4, 6), (3, 0, 4, 11)) - atom_i_dihedral_values = ((113, 131, 133, 135), (131, 133, 135, 136), - (131, 133, 135, 137), (131, 133, 135, 155), - (132, 131, 133, 135), (133, 135, 137, 138), - (133, 135, 137, 139), (133, 135, 137, 140), - (133, 135, 155, 156), (133, 135, 155, 157), - (134, 133, 135, 136), (134, 133, 135, 137), - (134, 133, 135, 155), (135, 137, 140, 141), - (135, 137, 140, 154), (135, 155, 157, 158), - (135, 155, 157, 159), (136, 135, 137, 138), - (136, 135, 137, 139), (136, 135, 137, 140), - (136, 135, 155, 156), (136, 135, 155, 157), - (137, 135, 155, 156), (137, 135, 155, 157), - (138, 137, 135, 155), (139, 137, 135, 155), - (140, 137, 135, 155)) + atom_i_bond_values = ((135, 137), (135, 155), (133, 135), (135, 136)) + atom_zero_angle_values = ( + (0, 4, 6), + (0, 4, 11), + (3, 0, 4), + (2, 0, 3), + (2, 0, 4), + (1, 0, 2), + (1, 0, 3), + (1, 0, 4), + (0, 4, 5), + ) + atom_i_angle_values = ( + (131, 133, 135), + (137, 135, 155), + (135, 137, 140), + (135, 155, 156), + (135, 155, 157), + (133, 135, 137), + (133, 135, 155), + (136, 135, 137), + (136, 135, 155), + (135, 137, 138), + (135, 137, 139), + (134, 133, 135), + (133, 135, 136), + ) + atom_zero_dihedral_values = ( + (0, 4, 6, 7), + (0, 4, 6, 8), + (0, 4, 6, 9), + (0, 4, 11, 12), + (0, 4, 11, 13), + (1, 0, 4, 5), + (1, 0, 4, 6), + (1, 0, 4, 11), + (2, 0, 4, 5), + (2, 0, 4, 6), + (2, 0, 4, 11), + (3, 0, 4, 5), + (3, 0, 4, 6), + (3, 0, 4, 11), + ) + atom_i_dihedral_values = ( + (113, 131, 133, 135), + (131, 133, 135, 136), + (131, 133, 135, 137), + (131, 133, 135, 155), + (132, 131, 133, 135), + (133, 135, 137, 138), + (133, 135, 137, 139), + (133, 135, 137, 140), + (133, 135, 155, 156), + (133, 135, 155, 157), + (134, 133, 135, 136), + (134, 133, 135, 137), + (134, 133, 135, 155), + (135, 137, 140, 141), + (135, 137, 140, 154), + (135, 155, 157, 158), + (135, 155, 157, 159), + (136, 135, 137, 138), + (136, 135, 137, 139), + (136, 135, 137, 140), + (136, 135, 155, 156), + (136, 135, 155, 157), + (137, 135, 155, 156), + (137, 135, 155, 157), + (138, 137, 135, 155), + (139, 137, 135, 155), + (140, 137, 135, 155), + ) atom_zero_improper_values = () atom_i_improper_values = ((131, 135, 133, 134), (135, 157, 155, 156)) expected_elems = None @@ -520,8 +618,16 @@ class TestPRM2(TOPBase): ref_filename = PRMpbc # Does not contain an ATOMIC_NUMBER record, so no elements expected_attrs = [ - "names", "types", "type_indices", "charges", "masses", "resnames", - "bonds", "angles", "dihedrals", "impropers" + "names", + "types", + "type_indices", + "charges", + "masses", + "resnames", + "bonds", + "angles", + "dihedrals", + "impropers", ] expected_n_atoms = 5071 expected_n_residues = 1686 @@ -542,19 +648,37 @@ class TestPRM2(TOPBase): atom_zero_bond_values = ((0, 1),) atom_i_bond_values = ((14, 15), (14, 16), (8, 14)) atom_zero_angle_values = ((0, 1, 2), (0, 1, 3), (0, 1, 4)) - atom_i_angle_values = ((15, 14, 16), (14, 16, 18), (10, 8, 14), - (8, 14, 15), (8, 14, 16), (6, 8, 14), - (14, 16, 17), (9, 8, 14)) + atom_i_angle_values = ( + (15, 14, 16), + (14, 16, 18), + (10, 8, 14), + (8, 14, 15), + (8, 14, 16), + (6, 8, 14), + (14, 16, 17), + (9, 8, 14), + ) atom_zero_dihedral_values = ((0, 1, 4, 5), (0, 1, 4, 6)) - atom_i_dihedral_values = ((4, 6, 8, 14), (6, 8, 14, 15), - (6, 8, 14, 16), (7, 6, 8, 14), - (8, 14, 16, 17), (8, 14, 16, 18), - (9, 8, 14, 15), (9, 8, 14, 16), - (10, 8, 14, 15), (10, 8, 14, 16), - (11, 10, 8, 14), (12, 10, 8, 14), - (13, 10, 8, 14), (14, 16, 18, 19), - (14, 16, 18, 20), (14, 16, 18, 21), - (15, 14, 16, 17), (15, 14, 16, 18)) + atom_i_dihedral_values = ( + (4, 6, 8, 14), + (6, 8, 14, 15), + (6, 8, 14, 16), + (7, 6, 8, 14), + (8, 14, 16, 17), + (8, 14, 16, 18), + (9, 8, 14, 15), + (9, 8, 14, 16), + (10, 8, 14, 15), + (10, 8, 14, 16), + (11, 10, 8, 14), + (12, 10, 8, 14), + (13, 10, 8, 14), + (14, 16, 18, 19), + (14, 16, 18, 20), + (14, 16, 18, 21), + (15, 14, 16, 17), + (15, 14, 16, 18), + ) atom_zero_improper_values = () atom_i_improper_values = ((8, 16, 14, 15), (14, 18, 16, 17)) expected_elems = None @@ -587,8 +711,12 @@ class TestPRMNCRST(TOPBase): atom_i_dihedral_values = ((0, 1, 4, 5), (2, 1, 4, 5), (3, 1, 4, 5)) atom_zero_improper_values = () atom_i_improper_values = () - elems_ranges = [[0, 6], ] - expected_elems = [np.array(["H", "C", "H", "H", "C", "O"], dtype=object), ] + elems_ranges = [ + [0, 6], + ] + expected_elems = [ + np.array(["H", "C", "H", "H", "C", "O"], dtype=object), + ] class TestPRMNCRST_negative(TOPBase): @@ -618,8 +746,12 @@ class TestPRMNCRST_negative(TOPBase): atom_i_dihedral_values = ((0, 1, 4, 5), (2, 1, 4, 5), (3, 1, 4, 5)) atom_zero_improper_values = () atom_i_improper_values = () - elems_ranges = [[0, 6], ] - expected_elems = [np.array(["H", "", "H", "H", "C", ""], dtype=object), ] + elems_ranges = [ + [0, 6], + ] + expected_elems = [ + np.array(["H", "", "H", "H", "C", ""], dtype=object), + ] class TestPRMEP(TOPBase): @@ -650,17 +782,15 @@ class TestPRMEP(TOPBase): atom_zero_improper_values = () atom_i_improper_values = () elems_ranges = [[0, 8], [20, 28]] - expected_elems = [np.array(["H", "C", "H", "H", "C", "O", "N", "H"], - dtype=object), - np.array(["H", "H", "O", "H", "H", "", "O", "H"], - dtype=object)] + expected_elems = [ + np.array(["H", "C", "H", "H", "C", "O", "N", "H"], dtype=object), + np.array(["H", "H", "O", "H", "H", "", "O", "H"], dtype=object), + ] class TestErrorsAndWarnings(object): - ATOMIC_NUMBER_MSG = ( - "ATOMIC_NUMBER record not found, elements attribute will not be populated" - ) + ATOMIC_NUMBER_MSG = "ATOMIC_NUMBER record not found, elements attribute will not be populated" MISSING_ELEM_MSG = ( "Unknown ATOMIC_NUMBER value found for some atoms, " "these have been given an empty element record" diff --git a/testsuite/MDAnalysisTests/topology/test_topology_base.py b/testsuite/MDAnalysisTests/topology/test_topology_base.py index 0a6099b17de..33d03258f96 100644 --- a/testsuite/MDAnalysisTests/topology/test_topology_base.py +++ b/testsuite/MDAnalysisTests/topology/test_topology_base.py @@ -6,16 +6,18 @@ class TestSquash(object): atom_resids = np.array([2, 2, 1, 1, 5, 5, 4, 4]) - atom_resnames = np.array(['A', 'A', 'B', 'B', 'C', 'C', 'D', 'D'], - dtype=object) + atom_resnames = np.array( + ["A", "A", "B", "B", "C", "C", "D", "D"], dtype=object + ) def test_squash(self): - atom_residx, resids, (resnames, ) = squash_by(self.atom_resids, - self.atom_resnames) + atom_residx, resids, (resnames,) = squash_by( + self.atom_resids, self.atom_resnames + ) assert_equal(atom_residx, np.array([1, 1, 0, 0, 3, 3, 2, 2])) assert_equal(resids, np.array([1, 2, 4, 5])) - assert_equal(resnames, np.array(['B', 'A', 'D', 'C'])) + assert_equal(resnames, np.array(["B", "A", "D", "C"])) class TestChangeSquash(object): @@ -24,21 +26,22 @@ def test_resid_squash(self): # Residues 1 & 2 are Segid A, Residue 3 is Segid B # Resid 2 is repeated twice! Should be detected as 2 distinct residues resids = np.array([2, 2, 3, 3, 2, 2]) - resnames = np.array(['RsA', 'RsA', 'RsB', 'RsB', 'RsC', 'RsC']) - segids = np.array(['A', 'A', 'A', 'A', 'B', 'B']) + resnames = np.array(["RsA", "RsA", "RsB", "RsB", "RsC", "RsC"]) + segids = np.array(["A", "A", "A", "A", "B", "B"]) residx, (new_resids, new_resnames, new_segids) = change_squash( - (resids, ), (resids, resnames, segids)) + (resids,), (resids, resnames, segids) + ) assert_equal(residx, np.array([0, 0, 1, 1, 2, 2])) assert_equal(new_resids, np.array([2, 3, 2])) - assert_equal(new_resnames, np.array(['RsA', 'RsB', 'RsC'])) - assert_equal(new_segids, np.array(['A', 'A', 'B'])) + assert_equal(new_resnames, np.array(["RsA", "RsB", "RsC"])) + assert_equal(new_segids, np.array(["A", "A", "B"])) def test_segid_squash(self): - segids = np.array(['A', 'A', 'B']) + segids = np.array(["A", "A", "B"]) - segidx, (new_segids, ) = change_squash((segids, ), (segids, )) + segidx, (new_segids,) = change_squash((segids,), (segids,)) assert_equal(segidx, np.array([0, 0, 1])) - assert_equal(new_segids, np.array(['A', 'B'])) + assert_equal(new_segids, np.array(["A", "B"])) diff --git a/testsuite/MDAnalysisTests/topology/test_topology_str_types.py b/testsuite/MDAnalysisTests/topology/test_topology_str_types.py index 66bf89b3e09..2827dc4eec8 100644 --- a/testsuite/MDAnalysisTests/topology/test_topology_str_types.py +++ b/testsuite/MDAnalysisTests/topology/test_topology_str_types.py @@ -43,40 +43,47 @@ HoomdXMLdata, XPDB_small, XYZ_mini, - DLP_HISTORY_minimal, ) + DLP_HISTORY_minimal, +) -@pytest.mark.parametrize('prop', [ - 'name', - 'resname', - 'type', - 'segid', - 'moltype', -]) +@pytest.mark.parametrize( + "prop", + [ + "name", + "resname", + "type", + "segid", + "moltype", + ], +) # topology formats curated from values available in # MDAnalysis._PARSERS -@pytest.mark.parametrize( 'top_format, top', [ - ('CONFIG', DLP_CONFIG_minimal), - ('CRD', CRD), - ('DATA', LAMMPSdata), - ('DMS', DMS), - ('GMS', GMS_SYMOPT), - ('GRO', GRO), - ('HISTORY', DLP_HISTORY_minimal), - ('MMTF', MMTF), - ('MOL2', mol2_molecule), - ('PARM7', PRM7), - ('PDB', PDB_small), - ('PDBQT', PDBQT_input), - ('PQR', PQR), - ('PRMTOP', PRM), - ('PSF', PSF), - ('TOP', PRM12), - ('TPR', TPR), - ('XML', HoomdXMLdata), - ('XPDB', XPDB_small), - ('XYZ', XYZ_mini) -]) +@pytest.mark.parametrize( + "top_format, top", + [ + ("CONFIG", DLP_CONFIG_minimal), + ("CRD", CRD), + ("DATA", LAMMPSdata), + ("DMS", DMS), + ("GMS", GMS_SYMOPT), + ("GRO", GRO), + ("HISTORY", DLP_HISTORY_minimal), + ("MMTF", MMTF), + ("MOL2", mol2_molecule), + ("PARM7", PRM7), + ("PDB", PDB_small), + ("PDBQT", PDBQT_input), + ("PQR", PQR), + ("PRMTOP", PRM), + ("PSF", PSF), + ("TOP", PRM12), + ("TPR", TPR), + ("XML", HoomdXMLdata), + ("XPDB", XPDB_small), + ("XYZ", XYZ_mini), + ], +) def test_str_types(top_format, top, prop): # Python 2/3 topology string type checking # Related to Issue #1336 diff --git a/testsuite/MDAnalysisTests/topology/test_tprparser.py b/testsuite/MDAnalysisTests/topology/test_tprparser.py index 208769bd61d..f2b93e0fcbb 100644 --- a/testsuite/MDAnalysisTests/topology/test_tprparser.py +++ b/testsuite/MDAnalysisTests/topology/test_tprparser.py @@ -20,32 +20,38 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -import pytest -from numpy.testing import assert_equal import functools +import MDAnalysis as mda +import MDAnalysis.topology.TPRParser import numpy as np +import pytest +# fmt: off +from MDAnalysis.tests.datafiles import (TPR, TPR400, TPR402, TPR403, TPR404, + TPR405, TPR406, TPR407, TPR450, TPR451, + TPR452, TPR453, TPR454, TPR455, TPR460, + TPR461, TPR502, TPR504, TPR505, TPR510, + TPR2016, TPR2018, TPR2019B3, TPR2020, + TPR2020B2, TPR2021, TPR2022RC1, + TPR2023, TPR2024, TPR2024_4, + TPR_EXTRA_407, TPR_EXTRA_2016, + TPR_EXTRA_2018, TPR_EXTRA_2020, + TPR_EXTRA_2021, TPR_EXTRA_2022RC1, + TPR_EXTRA_2023, TPR_EXTRA_2024, + TPR_EXTRA_2024_4, XTC, TPR334_bonded, + TPR455Double, TPR510_bonded, + TPR2016_bonded, TPR2018_bonded, + TPR2019B3_bonded, TPR2020_bonded, + TPR2020_double_bonded, + TPR2020B2_bonded, TPR2020Double, + TPR2021_bonded, TPR2021_double_bonded, + TPR2021Double, TPR2022RC1_bonded, + TPR2023_bonded, TPR2024_4_bonded, + TPR2024_bonded) +from numpy.testing import assert_equal -from MDAnalysis.tests.datafiles import ( - TPR, - TPR400, TPR402, TPR403, TPR404, TPR405, TPR406, TPR407, - TPR450, TPR451, TPR452, TPR453, TPR454, TPR455, TPR455Double, - TPR460, TPR461, TPR502, TPR504, TPR505, TPR510, TPR510_bonded, - TPR2016, TPR2018, TPR2019B3, TPR2020B2, TPR2020, TPR2020Double, - TPR2021, TPR2021Double, TPR2022RC1, TPR2023, TPR2024, TPR2024_4, - TPR2016_bonded, TPR2018_bonded, TPR2019B3_bonded, - TPR2020B2_bonded, TPR2020_bonded, TPR2020_double_bonded, - TPR2021_bonded, TPR2021_double_bonded, TPR334_bonded, - TPR2022RC1_bonded, TPR2023_bonded, TPR2024_bonded, - TPR2024_4_bonded, - TPR_EXTRA_2021, TPR_EXTRA_2020, TPR_EXTRA_2018, - TPR_EXTRA_2016, TPR_EXTRA_407, TPR_EXTRA_2022RC1, - TPR_EXTRA_2023, TPR_EXTRA_2024, TPR_EXTRA_2024_4, - XTC, -) +# fmt: on from MDAnalysisTests.topology.base import ParserBase -import MDAnalysis.topology.TPRParser -import MDAnalysis as mda BONDED_TPRS = ( TPR510_bonded, @@ -103,18 +109,22 @@ def test_molnums(self, top): def test_chainIDs(self, top): if hasattr(self, "ref_chainIDs"): - assert_equal(self.ref_chainIDs, getattr(top, "chainIDs").name_lookup) + assert_equal( + self.ref_chainIDs, getattr(top, "chainIDs").name_lookup + ) class TestTPR(TPRAttrs): """ this test the data/adk_oplsaa.tpr which is of tpx version 58 """ + expected_n_atoms = 47681 expected_n_residues = 11302 expected_n_segments = 3 - ref_moltypes = np.array(['AKeco'] * 214 + ['SOL'] * 11084 + ['NA+'] * 4, - dtype=object) + ref_moltypes = np.array( + ["AKeco"] * 214 + ["SOL"] * 11084 + ["NA+"] * 4, dtype=object + ) ref_molnums = np.array([0] * 214 + list(range(1, 1 + 11084 + 4))) @pytest.fixture() @@ -129,16 +139,19 @@ class TestTPRGromacsVersions(TPRAttrs): expected_n_atoms = 2263 expected_n_residues = 230 expected_n_segments = 2 - ref_moltypes = np.array(['Protein_A'] * 129 + ['SOL'] * 101, dtype=object) + ref_moltypes = np.array(["Protein_A"] * 129 + ["SOL"] * 101, dtype=object) ref_molnums = np.array([0] * 129 + list(range(1, 1 + 101))) ref_chainIDs = ["Protein_A", "SOL"] - @pytest.fixture(params=[TPR400, TPR402, TPR403, TPR404, TPR405, TPR406, - TPR407, TPR450, TPR451, TPR452, TPR453, TPR454, - TPR455, TPR502, TPR504, TPR505, TPR510, TPR2016, - TPR2018, TPR2019B3, TPR2020, TPR2020Double, - TPR2021, TPR2021Double, TPR2022RC1, TPR2023, - TPR2024, TPR2024_4]) + # fmt: off + @pytest.fixture(params=[ + TPR400, TPR402, TPR403, TPR404, TPR405, TPR406, TPR407, TPR450, + TPR451, TPR452, TPR453, TPR454, TPR455, TPR502, TPR504, TPR505, + TPR510, TPR2016, TPR2018, TPR2019B3, TPR2020, TPR2020Double, + TPR2021, TPR2021Double, TPR2022RC1, TPR2023, TPR2024, TPR2024_4 + ] + ) + # fmt: on def filename(self, request): return request.param @@ -147,10 +160,16 @@ class TestTPRDouble(TPRAttrs): expected_n_atoms = 21692 expected_n_residues = 4352 expected_n_segments = 7 - ref_moltypes = np.array(['DOPC'] * 21 + ['DPPC'] * 10 + ['CHOL'] * 3 - + ['DOPC'] * 21 + ['DPPC'] * 10 + ['CHOL'] * 3 - + ['SOL'] * 4284, - dtype=object) + ref_moltypes = np.array( + ["DOPC"] * 21 + + ["DPPC"] * 10 + + ["CHOL"] * 3 + + ["DOPC"] * 21 + + ["DPPC"] * 10 + + ["CHOL"] * 3 + + ["SOL"] * 4284, + dtype=object, + ) ref_molnums = np.arange(4352) @pytest.fixture() @@ -162,13 +181,25 @@ class TestTPR46x(TPRAttrs): expected_n_atoms = 44052 expected_n_residues = 10712 expected_n_segments = 8 - ref_moltypes = np.array(['Protein_A'] * 27 + ['Protein_B'] * 27 - + ['Protein_C'] * 27 + ['Protein_D'] * 27 - + ['Protein_E'] * 27 - + ['SOL'] * 10530 + ['NA+'] * 26 + ['CL-'] * 21, - dtype=object) - ref_molnums = np.array([0] * 27 + [1] * 27 + [2] * 27 + [3] * 27 + [4] * 27 - + list(range(5, 5 + 10530 + 26 + 21))) + ref_moltypes = np.array( + ["Protein_A"] * 27 + + ["Protein_B"] * 27 + + ["Protein_C"] * 27 + + ["Protein_D"] * 27 + + ["Protein_E"] * 27 + + ["SOL"] * 10530 + + ["NA+"] * 26 + + ["CL-"] * 21, + dtype=object, + ) + ref_molnums = np.array( + [0] * 27 + + [1] * 27 + + [2] * 27 + + [3] * 27 + + [4] * 27 + + list(range(5, 5 + 10530 + 26 + 21)) + ) @pytest.fixture(params=[TPR460, TPR461]) def filename(self, request): @@ -180,7 +211,11 @@ def _test_is_in_topology(name, elements, topology_path, topology_section): Test if an interaction appears as expected in the topology """ post_40_potentials = { - 'RESTRAINTPOT', 'RESTRANGLES', 'RESTRDIHS', 'CBTDIHS', 'PIDIHS', + "RESTRAINTPOT", + "RESTRANGLES", + "RESTRDIHS", + "CBTDIHS", + "PIDIHS", } if name in post_40_potentials and topology_path == TPR_EXTRA_407: # The potential is not yet implemented in this version of gromacs @@ -188,85 +223,102 @@ def _test_is_in_topology(name, elements, topology_path, topology_section): parser = MDAnalysis.topology.TPRParser.TPRParser(topology_path) top = parser.parse() for element in elements: - assert element in getattr(top, topology_section).values, \ - 'Interaction type "{}" not found'.format(name) - - -@pytest.mark.parametrize('topology', BONDED_TPRS) -@pytest.mark.parametrize('bond', ( - ('BONDS', [(0, 1)]), - ('G96BONDS', [(1, 2)]), - ('MORSE', [(2, 3)]), - ('CUBICBONDS', [(3, 4)]), - ('CONNBONDS', [(4, 5)]), - ('HARMONIC', [(5, 6)]), - ('FENEBONDS', [(6, 7)]), - ('RESTRAINTPOT', [(7, 8)]), - ('TABBONDS', [(8, 9)]), - ('TABBONDSNC', [(9, 10)]), - ('CONSTR', [(10, 11)]), - ('CONSTRNC', [(11, 12)]), -)) + assert ( + element in getattr(top, topology_section).values + ), 'Interaction type "{}" not found'.format(name) + + +@pytest.mark.parametrize("topology", BONDED_TPRS) +@pytest.mark.parametrize( + "bond", + ( + ("BONDS", [(0, 1)]), + ("G96BONDS", [(1, 2)]), + ("MORSE", [(2, 3)]), + ("CUBICBONDS", [(3, 4)]), + ("CONNBONDS", [(4, 5)]), + ("HARMONIC", [(5, 6)]), + ("FENEBONDS", [(6, 7)]), + ("RESTRAINTPOT", [(7, 8)]), + ("TABBONDS", [(8, 9)]), + ("TABBONDSNC", [(9, 10)]), + ("CONSTR", [(10, 11)]), + ("CONSTRNC", [(11, 12)]), + ), +) def test_all_bonds(topology, bond): """Test that all bond types are parsed as expected""" - bond_type_in_topology = functools.partial(_test_is_in_topology, - topology_section='bonds') + bond_type_in_topology = functools.partial( + _test_is_in_topology, topology_section="bonds" + ) bond_type, elements = bond bond_type_in_topology(bond_type, elements, topology) -@pytest.mark.parametrize('topology', BONDED_TPRS) -@pytest.mark.parametrize('angle', ( - ('ANGLES', [(0, 1, 2)]), - ('G96ANGLES', [(1, 2, 3)]), - ('CROSS_BOND_BOND', [(2, 3, 4)]), - ('CROSS_BOND_ANGLE', [(3, 4, 5)]), - ('UREY_BRADLEY', [(4, 5, 6)]), - ('QANGLES', [(5, 6, 7)]), - ('RESTRANGLES', [(6, 7, 8)]), - ('TABANGLES', [(7, 8, 9)]), -)) +@pytest.mark.parametrize("topology", BONDED_TPRS) +@pytest.mark.parametrize( + "angle", + ( + ("ANGLES", [(0, 1, 2)]), + ("G96ANGLES", [(1, 2, 3)]), + ("CROSS_BOND_BOND", [(2, 3, 4)]), + ("CROSS_BOND_ANGLE", [(3, 4, 5)]), + ("UREY_BRADLEY", [(4, 5, 6)]), + ("QANGLES", [(5, 6, 7)]), + ("RESTRANGLES", [(6, 7, 8)]), + ("TABANGLES", [(7, 8, 9)]), + ), +) def test_all_angles(topology, angle): - angle_type_in_topology = functools.partial(_test_is_in_topology, - topology_section='angles') + angle_type_in_topology = functools.partial( + _test_is_in_topology, topology_section="angles" + ) angle_type, elements = angle angle_type_in_topology(angle_type, elements, topology) -@pytest.mark.parametrize('topology', BONDED_TPRS) -@pytest.mark.parametrize('dih', ( - ('PDIHS', [(0, 1, 2, 3), (1, 2, 3, 4), (7, 8, 9, 10)]), - ('RBDIHS', [(4, 5, 6, 7)]), - ('RESTRDIHS', [(8, 9, 10, 11)]), - ('CBTDIHS', [(9, 10, 11, 12)]), - ('FOURDIHS', [(6, 7, 8, 9)]), - ('TABDIHS', [(10, 11, 12, 13)]), -)) +@pytest.mark.parametrize("topology", BONDED_TPRS) +@pytest.mark.parametrize( + "dih", + ( + ("PDIHS", [(0, 1, 2, 3), (1, 2, 3, 4), (7, 8, 9, 10)]), + ("RBDIHS", [(4, 5, 6, 7)]), + ("RESTRDIHS", [(8, 9, 10, 11)]), + ("CBTDIHS", [(9, 10, 11, 12)]), + ("FOURDIHS", [(6, 7, 8, 9)]), + ("TABDIHS", [(10, 11, 12, 13)]), + ), +) def test_all_dihedrals(topology, dih): - dih_type_in_topology = functools.partial(_test_is_in_topology, - topology_section='dihedrals') + dih_type_in_topology = functools.partial( + _test_is_in_topology, topology_section="dihedrals" + ) dih_type, elements = dih dih_type_in_topology(dih_type, elements, topology) -@pytest.mark.parametrize('topology', BONDED_TPRS) -@pytest.mark.parametrize('impr', ( - ('IDIHS', [(2, 3, 4, 5), (3, 4, 5, 6)]), - ('PIDIHS', [(5, 6, 7, 8)]) -)) +@pytest.mark.parametrize("topology", BONDED_TPRS) +@pytest.mark.parametrize( + "impr", + (("IDIHS", [(2, 3, 4, 5), (3, 4, 5, 6)]), ("PIDIHS", [(5, 6, 7, 8)])), +) def test_all_impropers(topology, impr): - impr_type_in_topology = functools.partial(_test_is_in_topology, - topology_section='impropers') + impr_type_in_topology = functools.partial( + _test_is_in_topology, topology_section="impropers" + ) impr_type, elements = impr impr_type_in_topology(impr_type, elements, topology) +# fmt: off @pytest.fixture(params=( - TPR400, TPR402, TPR403, TPR404, TPR405, TPR406, TPR407, TPR450, TPR451, - TPR452, TPR453, TPR454, TPR502, TPR504, TPR505, TPR510, TPR2016, TPR2018, - TPR2023, TPR2024, TPR2024_4, -)) + TPR400, TPR402, TPR403, TPR404, TPR405, TPR406, TPR407, TPR450, + TPR451, TPR452, TPR453, TPR454, TPR502, TPR504, TPR505, TPR510, + TPR2016, TPR2018, TPR2023, TPR2024, TPR2024_4, + ) +) +# fmt: on def bonds_water(request): parser = MDAnalysis.topology.TPRParser.TPRParser(request.param).parse() # The index of the first water atom is 1960 @@ -286,19 +338,22 @@ def test_settle(bonds_water): assert bonds_water[-1][1] == 2262 -@pytest.mark.parametrize('tpr_path, expected_exception', ( - (TPR2020B2, IOError), # Gromacs 2020 beta see issue #2428 - (TPR2020B2_bonded, IOError), # Gromacs 2020 beta see issue #2428 - (TPR334_bonded, NotImplementedError), # Too old - (XTC, IOError), # Not a TPR file -)) +@pytest.mark.parametrize( + "tpr_path, expected_exception", + ( + (TPR2020B2, IOError), # Gromacs 2020 beta see issue #2428 + (TPR2020B2_bonded, IOError), # Gromacs 2020 beta see issue #2428 + (TPR334_bonded, NotImplementedError), # Too old + (XTC, IOError), # Not a TPR file + ), +) def test_fail_for_unsupported_files(tpr_path, expected_exception): parser = MDAnalysis.topology.TPRParser.TPRParser(tpr_path) with pytest.raises(expected_exception): parser.parse() -@pytest.mark.parametrize('tpr_path', BONDED_TPRS) +@pytest.mark.parametrize("tpr_path", BONDED_TPRS) def test_no_elements(tpr_path): """ If the TPR does not contain element information, the element topology @@ -314,26 +369,40 @@ def test_elements(): tpr_path = TPR parser = MDAnalysis.topology.TPRParser.TPRParser(tpr_path) topology = parser.parse() - reference = np.array(( - 'H,C,H,H,C,H,H,H,C,H,H,H,C,O,N,H,C,H,C,H,H,C,H,C,H,H,H,C,H,H,' - 'H,C,O,N,H,C,H,H,C,O,O,O,H,H,,O,H,H,,O,H,H,,O,H,H,,O,H,H,,O,H' - ',H,,O,H,H,,O,H,H,,O,H,H,,O,H,H,,O,H,H,,O,H,H,,O,H,H,,O,H,H,,' - 'O,H,H' - ).split(','), dtype=object) + reference = np.array( + ( + "H,C,H,H,C,H,H,H,C,H,H,H,C,O,N,H,C,H,C,H,H,C,H,C,H,H,H,C,H,H," + "H,C,O,N,H,C,H,H,C,O,O,O,H,H,,O,H,H,,O,H,H,,O,H,H,,O,H,H,,O,H" + ",H,,O,H,H,,O,H,H,,O,H,H,,O,H,H,,O,H,H,,O,H,H,,O,H,H,,O,H,H,," + "O,H,H" + ).split(","), + dtype=object, + ) assert_equal(topology.elements.values[3300:3400], reference) - reference = np.array([ - 'O', 'H', 'H', '', 'O', 'H', 'H', '', 'O', 'H', 'H', '', 'O', 'H', - 'H', '', 'Na', 'Na', 'Na', 'Na', - ], dtype=object) + # fmt: off + reference = np.array( + [ + "O", "H", "H", "", "O", "H", "H", "", "O", "H", "H", "", "O", "H", + "H", "", "Na", "Na", "Na", "Na", + ], + dtype=object, + ) + # fmt: on assert_equal(topology.elements.values[-20:], reference) -@pytest.mark.parametrize("resid_from_one,resid_addition", [ - (False, 0), - (True, 1), # status quo for 2.x - ]) +@pytest.mark.parametrize( + "resid_from_one,resid_addition", + [ + (False, 0), + (True, 1), # status quo for 2.x + ], +) def test_resids(resid_from_one, resid_addition): u = mda.Universe(TPR, tpr_resid_from_one=resid_from_one) resids = np.arange(len(u.residues)) + resid_addition - assert_equal(u.residues.resids, resids, - err_msg="tpr_resid_from_one kwarg not switching resids") + assert_equal( + u.residues.resids, + resids, + err_msg="tpr_resid_from_one kwarg not switching resids", + ) diff --git a/testsuite/MDAnalysisTests/topology/test_txyz.py b/testsuite/MDAnalysisTests/topology/test_txyz.py index 06c2e757e0f..5615bdc985a 100644 --- a/testsuite/MDAnalysisTests/topology/test_txyz.py +++ b/testsuite/MDAnalysisTests/topology/test_txyz.py @@ -31,8 +31,8 @@ class TestTXYZParser(ParserBase): parser = mda.topology.TXYZParser.TXYZParser - guessed_attrs = ['masses'] - expected_attrs = ['ids', 'names', 'bonds', 'types', 'elements'] + guessed_attrs = ["masses"] + expected_attrs = ["ids", "names", "bonds", "types", "elements"] expected_n_residues = 1 expected_n_atoms = 9 @@ -55,12 +55,15 @@ def test_atom_type_type(self, top): type_is_str = [isinstance(atom_type, str) for atom_type in types] assert all(type_is_str) + def test_TXYZ_elements(): """The test checks whether elements attribute are assigned properly given a TXYZ file with valid elements record. """ - u = mda.Universe(TXYZ, format='TXYZ') - element_list = np.array(['C', 'H', 'H', 'O', 'H', 'C', 'H', 'H', 'H'], dtype=object) + u = mda.Universe(TXYZ, format="TXYZ") + element_list = np.array( + ["C", "H", "H", "O", "H", "C", "H", "H", "H"], dtype=object + ) assert_equal(u.atoms.elements, element_list) @@ -70,8 +73,10 @@ def test_missing_elements_noattribute(): 1) a warning is raised if elements are missing 2) the elements attribute is not set """ - wmsg = ("Element information is missing, elements attribute " - "will not be populated. If needed these can be ") + wmsg = ( + "Element information is missing, elements attribute " + "will not be populated. If needed these can be " + ) with pytest.warns(UserWarning, match=wmsg): u = mda.Universe(ARC_PBC) with pytest.raises(AttributeError): @@ -80,6 +85,15 @@ def test_missing_elements_noattribute(): def test_guessed_masses(): u = mda.Universe(TXYZ) - expected = [12.011, 1.008, 1.008, 15.999, 1.008, 12.011, - 1.008, 1.008, 1.008] + expected = [ + 12.011, + 1.008, + 1.008, + 15.999, + 1.008, + 12.011, + 1.008, + 1.008, + 1.008, + ] assert_allclose(u.atoms.masses, expected) diff --git a/testsuite/MDAnalysisTests/topology/test_xpdb.py b/testsuite/MDAnalysisTests/topology/test_xpdb.py index 2be72fb8e7e..ba55580acd4 100644 --- a/testsuite/MDAnalysisTests/topology/test_xpdb.py +++ b/testsuite/MDAnalysisTests/topology/test_xpdb.py @@ -33,10 +33,19 @@ class TestXPDBParser(ParserBase): parser = mda.topology.ExtendedPDBParser.ExtendedPDBParser ref_filename = XPDB_small - expected_attrs = ['ids', 'names', 'record_types', 'resids', - 'resnames', 'altLocs', 'icodes', 'occupancies', - 'tempfactors', 'chainIDs'] - guessed_attrs = ['masses', 'types'] + expected_attrs = [ + "ids", + "names", + "record_types", + "resids", + "resnames", + "altLocs", + "icodes", + "occupancies", + "tempfactors", + "chainIDs", + ] + guessed_attrs = ["masses", "types"] expected_n_atoms = 5 expected_n_residues = 5 expected_n_segments = 1 @@ -48,5 +57,5 @@ def test_guessed_masses(self, filename): def test_guessed_types(self, filename): u = mda.Universe(filename) - expected = ['O', 'O', 'O', 'O', 'O'] + expected = ["O", "O", "O", "O", "O"] assert_equal(u.atoms.types, expected) diff --git a/testsuite/MDAnalysisTests/topology/test_xyz.py b/testsuite/MDAnalysisTests/topology/test_xyz.py index 07b0159d6dc..4574e4edb43 100644 --- a/testsuite/MDAnalysisTests/topology/test_xyz.py +++ b/testsuite/MDAnalysisTests/topology/test_xyz.py @@ -37,8 +37,8 @@ class XYZBase(ParserBase): parser = mda.topology.XYZParser.XYZParser expected_n_residues = 1 expected_n_segments = 1 - expected_attrs = ['names', 'elements'] - guessed_attrs = ['masses', 'types'] + expected_attrs = ["names", "elements"] + guessed_attrs = ["masses", "types"] class TestXYZMini(XYZBase): @@ -60,5 +60,5 @@ def test_guessed_masses(self, filename): def test_guessed_types(self, filename): u = mda.Universe(filename) - expected = ['H', 'H', 'H', 'H', 'H', 'H', 'H'] + expected = ["H", "H", "H", "H", "H", "H", "H"] assert_equal(u.atoms.types[:7], expected) diff --git a/testsuite/MDAnalysisTests/utils/test_authors.py b/testsuite/MDAnalysisTests/utils/test_authors.py index 7e1a69960d2..13563dadfdf 100644 --- a/testsuite/MDAnalysisTests/utils/test_authors.py +++ b/testsuite/MDAnalysisTests/utils/test_authors.py @@ -24,5 +24,8 @@ import MDAnalysis + def test_package_authors(): - assert len(MDAnalysis.__authors__) > 0, 'Could not find the list of authors' + assert ( + len(MDAnalysis.__authors__) > 0 + ), "Could not find the list of authors" diff --git a/testsuite/MDAnalysisTests/utils/test_datafiles.py b/testsuite/MDAnalysisTests/utils/test_datafiles.py index 92caf2348f6..7b79bb86404 100644 --- a/testsuite/MDAnalysisTests/utils/test_datafiles.py +++ b/testsuite/MDAnalysisTests/utils/test_datafiles.py @@ -29,10 +29,10 @@ def test_failed_import(monkeypatch): # Putting this test first to avoid datafiles already being loaded errmsg = "MDAnalysisTests package not installed." - monkeypatch.setitem(sys.modules, 'MDAnalysisTests.datafiles', None) + monkeypatch.setitem(sys.modules, "MDAnalysisTests.datafiles", None) - if 'MDAnalysis.tests.datafiles' in sys.modules: - monkeypatch.delitem(sys.modules, 'MDAnalysis.tests.datafiles') + if "MDAnalysis.tests.datafiles" in sys.modules: + monkeypatch.delitem(sys.modules, "MDAnalysis.tests.datafiles") with pytest.raises(ImportError, match=errmsg): import MDAnalysis.tests.datafiles @@ -42,20 +42,35 @@ def test_import(): try: import MDAnalysis.tests.datafiles except ImportError: - pytest.fail("Failed to 'import MDAnalysis.tests.datafiles --- install MDAnalysisTests") + pytest.fail( + "Failed to 'import MDAnalysis.tests.datafiles --- install MDAnalysisTests" + ) def test_all_exports(): import MDAnalysisTests.datafiles - missing = [name for name in dir(MDAnalysisTests.datafiles) - if - not name.startswith('_') and name not in MDAnalysisTests.datafiles.__all__ and name != 'MDAnalysisTests'] + + missing = [ + name + for name in dir(MDAnalysisTests.datafiles) + if not name.startswith("_") + and name not in MDAnalysisTests.datafiles.__all__ + and name != "MDAnalysisTests" + ] assert_equal(missing, [], err_msg="Variables need to be added to __all__.") def test_export_variables(): import MDAnalysisTests.datafiles import MDAnalysis.tests.datafiles - missing = [name for name in MDAnalysisTests.datafiles.__all__ - if name not in dir(MDAnalysis.tests.datafiles)] - assert_equal(missing, [], err_msg="Variables not exported to MDAnalysis.tests.datafiles") + + missing = [ + name + for name in MDAnalysisTests.datafiles.__all__ + if name not in dir(MDAnalysis.tests.datafiles) + ] + assert_equal( + missing, + [], + err_msg="Variables not exported to MDAnalysis.tests.datafiles", + ) diff --git a/testsuite/MDAnalysisTests/utils/test_duecredit.py b/testsuite/MDAnalysisTests/utils/test_duecredit.py index d567d256f5d..f0224a9a73b 100644 --- a/testsuite/MDAnalysisTests/utils/test_duecredit.py +++ b/testsuite/MDAnalysisTests/utils/test_duecredit.py @@ -35,48 +35,68 @@ # duecredit itself is not needed in the name space but this is a # convenient way to skip all tests if duecredit is not installed # (see https://github.com/MDAnalysis/mdanalysis/issues/1906) -pytest.importorskip('duecredit') +pytest.importorskip("duecredit") -@pytest.mark.skipif((os.environ.get('DUECREDIT_ENABLE', 'yes').lower() - in ('no', '0', 'false')), - reason= - "duecredit is explicitly disabled with DUECREDIT_ENABLE=no") + +@pytest.mark.skipif( + ( + os.environ.get("DUECREDIT_ENABLE", "yes").lower() + in ("no", "0", "false") + ), + reason="duecredit is explicitly disabled with DUECREDIT_ENABLE=no", +) class TestDuecredit(object): def test_duecredit_active(self): assert mda.due.active == True - @pytest.mark.parametrize("module,path,citekey", [ - ("MDAnalysis", "MDAnalysis", "10.25080/majora-629e541a-00e"), - ("MDAnalysis", "MDAnalysis", "10.1002/jcc.21787"), - ]) + @pytest.mark.parametrize( + "module,path,citekey", + [ + ("MDAnalysis", "MDAnalysis", "10.25080/majora-629e541a-00e"), + ("MDAnalysis", "MDAnalysis", "10.1002/jcc.21787"), + ], + ) def test_duecredit_collector_primary(self, module, path, citekey): assert mda.due.citations[(path, citekey)].cites_module == True # note: citekeys are *all lower case* - @pytest.mark.parametrize("module,path,citekey", [ - ("MDAnalysis.analysis.psa", - "pathsimanalysis.psa", - "10.1371/journal.pcbi.1004568"), - ("MDAnalysis.analysis.hydrogenbonds.hbond_autocorrel", - "MDAnalysis.analysis.hydrogenbonds.hbond_autocorrel", - "10.1063/1.4922445"), - ("MDAnalysis.analysis.leaflet", - "MDAnalysis.analysis.leaflet", - "10.1002/jcc.21787"), - ("MDAnalysis.lib.qcprot", - "MDAnalysis.lib.qcprot", - "10.1107/s0108767305015266"), - ("MDAnalysis.lib.qcprot", - "MDAnalysis.lib.qcprot", - "qcprot2"), - ("MDAnalysis.analysis.encore", - "MDAnalysis.analysis.encore", - "10.1371/journal.pcbi.1004415"), - ("MDAnalysis.analysis.dssp", - "MDAnalysis.analysis.dssp", - "10.1002/bip.360221211") - ]) + @pytest.mark.parametrize( + "module,path,citekey", + [ + ( + "MDAnalysis.analysis.psa", + "pathsimanalysis.psa", + "10.1371/journal.pcbi.1004568", + ), + ( + "MDAnalysis.analysis.hydrogenbonds.hbond_autocorrel", + "MDAnalysis.analysis.hydrogenbonds.hbond_autocorrel", + "10.1063/1.4922445", + ), + ( + "MDAnalysis.analysis.leaflet", + "MDAnalysis.analysis.leaflet", + "10.1002/jcc.21787", + ), + ( + "MDAnalysis.lib.qcprot", + "MDAnalysis.lib.qcprot", + "10.1107/s0108767305015266", + ), + ("MDAnalysis.lib.qcprot", "MDAnalysis.lib.qcprot", "qcprot2"), + ( + "MDAnalysis.analysis.encore", + "MDAnalysis.analysis.encore", + "10.1371/journal.pcbi.1004415", + ), + ( + "MDAnalysis.analysis.dssp", + "MDAnalysis.analysis.dssp", + "10.1002/bip.360221211", + ), + ], + ) def test_duecredit_collector_analysis_modules(self, module, path, citekey): importlib.import_module(module) assert mda.due.citations[(path, citekey)].cites_module == True @@ -85,17 +105,21 @@ def test_duecredit_mmtf(self): # doesn't trigger on import but on use of either parser or reader u = mda.Universe(MMTF) - assert mda.due.citations[('MDAnalysis.coordinates.MMTF', - '10.1371/journal.pcbi.1005575')].cites_module - assert mda.due.citations[('MDAnalysis.topology.MMTFParser', - '10.1371/journal.pcbi.1005575')].cites_module + assert mda.due.citations[ + ("MDAnalysis.coordinates.MMTF", "10.1371/journal.pcbi.1005575") + ].cites_module + assert mda.due.citations[ + ("MDAnalysis.topology.MMTFParser", "10.1371/journal.pcbi.1005575") + ].cites_module @pytest.mark.skipif(not HAS_H5PY, reason="h5py not installed") def test_duecredit_h5md(self): # doesn't trigger on import but on use of either reader or writer u = mda.Universe(TPR_xvf, H5MD_xvf) - assert mda.due.citations[('MDAnalysis.coordinates.H5MD', - '10.25080/majora-1b6fd038-005')].cites_module - assert mda.due.citations[('MDAnalysis.coordinates.H5MD', - '10.1016/j.cpc.2014.01.018')].cites_module + assert mda.due.citations[ + ("MDAnalysis.coordinates.H5MD", "10.25080/majora-1b6fd038-005") + ].cites_module + assert mda.due.citations[ + ("MDAnalysis.coordinates.H5MD", "10.1016/j.cpc.2014.01.018") + ].cites_module diff --git a/testsuite/MDAnalysisTests/utils/test_failure.py b/testsuite/MDAnalysisTests/utils/test_failure.py index b1ec9f1e869..494e84cdb9f 100644 --- a/testsuite/MDAnalysisTests/utils/test_failure.py +++ b/testsuite/MDAnalysisTests/utils/test_failure.py @@ -24,9 +24,10 @@ def test_failure(): - """Fail if the MDA_FAILURE_TEST environment variable is set. - """ - if u'MDA_FAILURE_TEST' in os.environ: + """Fail if the MDA_FAILURE_TEST environment variable is set.""" + if "MDA_FAILURE_TEST" in os.environ: # Have a file open to trigger an output from the open_files plugin. - f = open('./failure.txt', 'w') - raise AssertionError("the MDA_FAILURE_TEST environment variable is set") + f = open("./failure.txt", "w") + raise AssertionError( + "the MDA_FAILURE_TEST environment variable is set" + ) diff --git a/testsuite/MDAnalysisTests/utils/test_imports.py b/testsuite/MDAnalysisTests/utils/test_imports.py index bd343e2b992..016388e3244 100644 --- a/testsuite/MDAnalysisTests/utils/test_imports.py +++ b/testsuite/MDAnalysisTests/utils/test_imports.py @@ -29,11 +29,11 @@ path_to_testing_modules = MDAnalysisTests.__path__[0] # Exclusion path relative to MDAnalysisTests -exclusions = ['/plugins', '/data'] +exclusions = ["/plugins", "/data"] def is_excluded(path): - leaf = path[len(path_to_testing_modules):] + leaf = path[len(path_to_testing_modules) :] return leaf in exclusions @@ -42,7 +42,7 @@ def get_file_paths(): for dirpath, dirnames, files in os.walk(path_to_testing_modules): if is_excluded(dirpath): continue - for f in filter(lambda x: x.endswith('.py'), files): + for f in filter(lambda x: x.endswith(".py"), files): fpath = os.path.join(dirpath, f) if is_excluded(fpath): continue @@ -50,12 +50,18 @@ def get_file_paths(): return paths -@pytest.mark.parametrize('testing_module', get_file_paths()) +@pytest.mark.parametrize("testing_module", get_file_paths()) def test_relative_import(testing_module): - with open(testing_module, 'r') as test_module_file_object: + with open(testing_module, "r") as test_module_file_object: for lineno, line in enumerate(test_module_file_object, start=1): - if 'from .' in line and 'import' in line \ - and not 'test_imports' in testing_module: + if ( + "from ." in line + and "import" in line + and not "test_imports" in testing_module + ): raise AssertionError( "A relative import statement was found in " - "module {testing_module} at linenumber {lineno}.".format(**vars())) + "module {testing_module} at linenumber {lineno}.".format( + **vars() + ) + ) diff --git a/testsuite/MDAnalysisTests/utils/test_log.py b/testsuite/MDAnalysisTests/utils/test_log.py index 2e95edb39b0..0abaed2795d 100644 --- a/testsuite/MDAnalysisTests/utils/test_log.py +++ b/testsuite/MDAnalysisTests/utils/test_log.py @@ -68,5 +68,8 @@ def buffer(): def _assert_in(output, string): - assert string in output, "Output '{0}' does not match required format '{1}'.".format(output.replace('\r', '\\r'), string.replace('\r', '\\r')) - + assert ( + string in output + ), "Output '{0}' does not match required format '{1}'.".format( + output.replace("\r", "\\r"), string.replace("\r", "\\r") + ) diff --git a/testsuite/MDAnalysisTests/utils/test_meta.py b/testsuite/MDAnalysisTests/utils/test_meta.py index def0a35e740..b6e536ef009 100644 --- a/testsuite/MDAnalysisTests/utils/test_meta.py +++ b/testsuite/MDAnalysisTests/utils/test_meta.py @@ -24,33 +24,47 @@ import MDAnalysisTests + def test_import(): try: import MDAnalysis except ImportError: - raise AssertionError('Failed to import module MDAnalysis. Install MDAnalysis' - 'first to run the tests, e.g. "pip install mdanalysis"') + raise AssertionError( + "Failed to import module MDAnalysis. Install MDAnalysis" + 'first to run the tests, e.g. "pip install mdanalysis"' + ) def test_matching_versions(): import MDAnalysis.version - assert MDAnalysis.version.__version__ == MDAnalysisTests.__version__, \ - "MDAnalysis release {0} must be installed to have meaningful tests, not {1}".format( - MDAnalysisTests.__version__, MDAnalysis.__version__) + + assert ( + MDAnalysis.version.__version__ == MDAnalysisTests.__version__ + ), "MDAnalysis release {0} must be installed to have meaningful tests, not {1}".format( + MDAnalysisTests.__version__, MDAnalysis.__version__ + ) def test_version_format(version=None): if version is None: import MDAnalysis.version + version = MDAnalysis.version.__version__ # see https://github.com/MDAnalysis/mdanalysis/wiki/SemanticVersioning for format definition - m = re.match(r'(?P\d+)\.(?P\d+)\.(?P\d+)(-(?P\w+))?$', - version) - assert m, "version {0} does not match the MAJOR.MINOR.PATCH(-suffix) format".format(version) + m = re.match( + r"(?P\d+)\.(?P\d+)\.(?P\d+)(-(?P\w+))?$", + version, + ) + assert ( + m + ), "version {0} does not match the MAJOR.MINOR.PATCH(-suffix) format".format( + version + ) def test_version_at_packagelevel(): import MDAnalysis + try: version = MDAnalysis.__version__ except: @@ -61,24 +75,24 @@ def test_version_at_packagelevel(): # The following allow testing of the memleak tester plugin. # Keep commented out unless you suspect the plugin # might be misbehaving. Apparently python3 is immune to these leaks!""" -#from numpy.testing import TestCase -#class A(): +# from numpy.testing import TestCase +# class A(): # """This is a small leaky class that won't break anything.""" # def __init__(self): # self.self_ref = self # def __del__(self): # pass # -#def test_that_memleaks(): +# def test_that_memleaks(): # """Test that memleaks (Issue 323)""" # a = A() # -#class TestML1(TestCase): +# class TestML1(TestCase): # def test_that_memleaks(self): # """Test that memleaks (Issue 323)""" # self.a = A() # -#class TestML2(TestCase): +# class TestML2(TestCase): # def setUp(self): # a = A() # def test_that_memleaks(self): diff --git a/testsuite/MDAnalysisTests/utils/test_modelling.py b/testsuite/MDAnalysisTests/utils/test_modelling.py index bae825da3ac..c014c9f4d03 100644 --- a/testsuite/MDAnalysisTests/utils/test_modelling.py +++ b/testsuite/MDAnalysisTests/utils/test_modelling.py @@ -32,7 +32,7 @@ capping_nma, merge_protein, merge_ligand, - merge_water + merge_water, ) import MDAnalysis.core.groups from MDAnalysis.core.groups import AtomGroup @@ -55,22 +55,39 @@ def capping(ref, ace, nma, output): # TODO pick the first residue in the protein (how should we cap the chains?) # TODO consider a case when the protein resid is 1 and all peptide has to be shifted by +1, put that in docs as a # post-processing step - alignto(ace, ref, select={ + alignto( + ace, + ref, + select={ "mobile": "resid {0} and backbone".format(resid_min), - "reference": "resid {0} and backbone".format(resid_min)}, - strict=True) - alignto(nma, ref, select={ - "mobile": "resid {0} and backbone and not (resname NMA NME)".format(resid_max), - "reference": "resid {0} and (backbone or name OT2)".format(resid_max)}, - strict=True) + "reference": "resid {0} and backbone".format(resid_min), + }, + strict=True, + ) + alignto( + nma, + ref, + select={ + "mobile": "resid {0} and backbone and not (resname NMA NME)".format( + resid_max + ), + "reference": "resid {0} and (backbone or name OT2)".format( + resid_max + ), + }, + strict=True, + ) # TODO remove the Hydrogen closest to ACE's oxygen nma.residues.resids = 16 - u = Merge(ace.select_atoms("resname ACE"), - ref.select_atoms( - "not (resid {0} and name HT*) and not (resid {1} and (name HT* OT1))" - "".format(resid_min, resid_max)), - nma.select_atoms("resname NME NMA")) + u = Merge( + ace.select_atoms("resname ACE"), + ref.select_atoms( + "not (resid {0} and name HT*) and not (resid {1} and (name HT* OT1))" + "".format(resid_min, resid_max) + ), + nma.select_atoms("resname NME NMA"), + ) u.trajectory.ts.dimensions = ref.trajectory.ts.dimensions u.atoms.write(output) return u @@ -84,11 +101,13 @@ def test_capping_file(self, tmpdir): ace = MDAnalysis.Universe(capping_ace) nma = MDAnalysis.Universe(capping_nma) - outfile = str(tmpdir.join('test.pdb')) + outfile = str(tmpdir.join("test.pdb")) u = capping(peptide, ace, nma, outfile) - assert_equal(len(u.select_atoms("not name H*")), - len(ref.select_atoms("not name H*"))) + assert_equal( + len(u.select_atoms("not name H*")), + len(ref.select_atoms("not name H*")), + ) u = MDAnalysis.Universe(outfile) @@ -99,8 +118,9 @@ def test_capping_file(self, tmpdir): assert_equal(ace.resids[0], 1) assert_equal(nma.resids[0], 16) - assert_array_equal(peptide.trajectory.ts.dimensions, - u.trajectory.ts.dimensions) + assert_array_equal( + peptide.trajectory.ts.dimensions, u.trajectory.ts.dimensions + ) def test_capping_inmemory(self, tmpdir): peptide = MDAnalysis.Universe(capping_input) @@ -108,10 +128,12 @@ def test_capping_inmemory(self, tmpdir): ace = MDAnalysis.Universe(capping_ace) nma = MDAnalysis.Universe(capping_nma) - outfile = str(tmpdir.join('test.pdb')) + outfile = str(tmpdir.join("test.pdb")) u = capping(peptide, ace, nma, outfile) - assert_equal(len(u.select_atoms("not name H*")), - len(ref.select_atoms("not name H*"))) + assert_equal( + len(u.select_atoms("not name H*")), + len(ref.select_atoms("not name H*")), + ) ace = u.select_atoms("resname ACE") nma = u.select_atoms("resname NMA") @@ -120,8 +142,9 @@ def test_capping_inmemory(self, tmpdir): assert_equal(ace.resids[0], 1) assert_equal(nma.resids[0], 16) - assert_array_equal(peptide.trajectory.ts.dimensions, - u.trajectory.ts.dimensions) + assert_array_equal( + peptide.trajectory.ts.dimensions, u.trajectory.ts.dimensions + ) @pytest.fixture() @@ -138,6 +161,7 @@ def u_ligand(): def u_water(): return MDAnalysis.Universe(merge_water) + @pytest.fixture() def u_without_coords(): return MDAnalysis.Universe(PSF) @@ -145,18 +169,35 @@ def u_without_coords(): class TestMerge(object): def test_merge(self, u_protein, u_ligand, u_water, tmpdir): - ids_before = [a.index for u in [u_protein, u_ligand, u_water] for a in u.atoms] + ids_before = [ + a.index for u in [u_protein, u_ligand, u_water] for a in u.atoms + ] # Do the merge u0 = MDAnalysis.Merge(u_protein.atoms, u_ligand.atoms, u_water.atoms) # Check that the output Universe has the same number of atoms as the # starting AtomGroups - assert_equal(len(u0.atoms), (len(u_protein.atoms) + len(u_ligand.atoms) + len(u_water.atoms))) + assert_equal( + len(u0.atoms), + (len(u_protein.atoms) + len(u_ligand.atoms) + len(u_water.atoms)), + ) # Check that the output Universe has the same number of residues and # segments as the starting AtomGroups - assert_equal(len(u0.residues), (len(u_protein.residues) + len(u_ligand.residues) + - len(u_water.residues))) - assert_equal(len(u0.segments), (len(u_protein.segments) + len(u_ligand.segments) + - len(u_water.segments))) + assert_equal( + len(u0.residues), + ( + len(u_protein.residues) + + len(u_ligand.residues) + + len(u_water.residues) + ), + ) + assert_equal( + len(u0.segments), + ( + len(u_protein.segments) + + len(u_ligand.segments) + + len(u_water.segments) + ), + ) # Make sure that all the atoms in the new universe are assigned to only # one, new Universe @@ -167,15 +208,20 @@ def test_merge(self, u_protein, u_ligand, u_water, tmpdir): # Make sure that the atom ids of the original universes are unchanged, # ie we didn't make the original Universes 'dirty' - ids_after = [a.index for u in [u_protein, u_ligand, u_water] for a in u.atoms] - assert_equal(len(ids_after), (len(u_protein.atoms) + len(u_ligand.atoms) + len(u_water.atoms))) + ids_after = [ + a.index for u in [u_protein, u_ligand, u_water] for a in u.atoms + ] + assert_equal( + len(ids_after), + (len(u_protein.atoms) + len(u_ligand.atoms) + len(u_water.atoms)), + ) assert_equal(ids_before, ids_after) # Test that we have a same number of atoms in a different way ids_new = [a.index for a in u0.atoms] assert_equal(len(ids_new), len(ids_before)) - outfile = str(tmpdir.join('test.pdb')) + outfile = str(tmpdir.join("test.pdb")) u0.atoms.write(outfile) u = MDAnalysis.Universe(outfile) @@ -183,20 +229,28 @@ def test_merge(self, u_protein, u_ligand, u_water, tmpdir): assert_equal(ids_new, ids_new2) def test_merge_same_universe(self, u_protein): - u0 = MDAnalysis.Merge(u_protein.atoms, u_protein.atoms, u_protein.atoms) + u0 = MDAnalysis.Merge( + u_protein.atoms, u_protein.atoms, u_protein.atoms + ) assert_equal(len(u0.atoms), 3 * len(u_protein.atoms)) assert_equal(len(u0.residues), 3 * len(u_protein.residues)) assert_equal(len(u0.segments), 3 * len(u_protein.segments)) def test_residue_references(self, u_protein, u_ligand): m = Merge(u_protein.atoms, u_ligand.atoms) - assert_equal(m.atoms.residues[0].universe, m, - "wrong universe reference for residues after Merge()") + assert_equal( + m.atoms.residues[0].universe, + m, + "wrong universe reference for residues after Merge()", + ) def test_segment_references(self, u_protein, u_ligand): m = Merge(u_protein.atoms, u_ligand.atoms) - assert_equal(m.atoms.segments[0].universe, m, - "wrong universe reference for segments after Merge()") + assert_equal( + m.atoms.segments[0].universe, + m, + "wrong universe reference for segments after Merge()", + ) def test_empty_ValueError(self): with pytest.raises(ValueError): @@ -204,7 +258,7 @@ def test_empty_ValueError(self): def test_nonsense_TypeError(self): with pytest.raises(TypeError): - Merge(['1', 2]) + Merge(["1", 2]) def test_emptyAG_ValueError(self, u_protein): a = AtomGroup([], u_protein) @@ -215,8 +269,8 @@ def test_emptyAG_ValueError(self, u_protein): def test_merge_without_coords(self, u_without_coords): subset = MDAnalysis.Merge(u_without_coords.atoms[:10]) - assert(isinstance(subset, MDAnalysis.Universe)) - assert_equal(len(subset.atoms) , 10) + assert isinstance(subset, MDAnalysis.Universe) + assert_equal(len(subset.atoms), 10) class TestMergeTopology(object): @@ -233,36 +287,45 @@ def test_merge_with_topology(self, u): u_merge = MDAnalysis.Merge(ag1, ag2) - assert(len(u_merge.atoms) == 30) - assert(len(u_merge.atoms.bonds) == 28) - assert(len(u_merge.atoms.angles) == 47) - assert(len(u_merge.atoms.dihedrals) == 53) - assert(len(u_merge.atoms.impropers) == 1) + assert len(u_merge.atoms) == 30 + assert len(u_merge.atoms.bonds) == 28 + assert len(u_merge.atoms.angles) == 47 + assert len(u_merge.atoms.dihedrals) == 53 + assert len(u_merge.atoms.impropers) == 1 # All these bonds are in the merged Universe - assert(len(ag1[0].bonds) == len(u_merge.atoms[0].bonds)) + assert len(ag1[0].bonds) == len(u_merge.atoms[0].bonds) # One of these bonds isn't in the merged Universe - assert(len(ag2[0].bonds) - 1 == len(u_merge.atoms[20].bonds)) + assert len(ag2[0].bonds) - 1 == len(u_merge.atoms[20].bonds) def test_merge_with_topology_from_different_universes(self, u, u_ligand): u_merge = MDAnalysis.Merge(u.atoms[:110], u_ligand.atoms) # merge_protein doesn't contain bond topology, so merged universe # shouldn't have one either - assert not hasattr(u_merge.atoms, 'bonds') + assert not hasattr(u_merge.atoms, "bonds") # PDB reader yields empty Bonds group, which means bonds from # PSF/DCD survive the merge # assert(not hasattr(u_merge.atoms, 'bonds') or len(u_merge.atoms.bonds) == 0) - assert(not hasattr(u_merge.atoms, 'angles') or len(u_merge.atoms.bonds) == 0) - assert(not hasattr(u_merge.atoms, 'dihedrals') or len(u_merge.atoms.bonds) == 0) - assert(not hasattr(u_merge.atoms, 'impropers') or len(u_merge.atoms.bonds) == 0) + assert ( + not hasattr(u_merge.atoms, "angles") + or len(u_merge.atoms.bonds) == 0 + ) + assert ( + not hasattr(u_merge.atoms, "dihedrals") + or len(u_merge.atoms.bonds) == 0 + ) + assert ( + not hasattr(u_merge.atoms, "impropers") + or len(u_merge.atoms.bonds) == 0 + ) def test_merge_without_topology(self, u): # This shouldn't have topology as we merged single atoms u_merge = MDAnalysis.Merge(u.atoms[0:1], u.atoms[10:11]) - assert(len(u_merge.atoms) == 2) - assert(len(u_merge.atoms.bonds) == 0) - assert(len(u_merge.atoms.angles) == 0) - assert(len(u_merge.atoms.dihedrals) == 0) - assert(len(u_merge.atoms.impropers) == 0) + assert len(u_merge.atoms) == 2 + assert len(u_merge.atoms.bonds) == 0 + assert len(u_merge.atoms.angles) == 0 + assert len(u_merge.atoms.dihedrals) == 0 + assert len(u_merge.atoms.impropers) == 0 diff --git a/testsuite/MDAnalysisTests/utils/test_persistence.py b/testsuite/MDAnalysisTests/utils/test_persistence.py index c2c00e7396d..ca9ca19e6e6 100644 --- a/testsuite/MDAnalysisTests/utils/test_persistence.py +++ b/testsuite/MDAnalysisTests/utils/test_persistence.py @@ -24,10 +24,7 @@ import pickle import MDAnalysis as mda -from numpy.testing import ( - TestCase, - assert_equal -) +from numpy.testing import TestCase, assert_equal import gc @@ -89,24 +86,22 @@ def test_pickle_unpickle_empty(self, universe): def test_unpickle_two_ag(self, pickle_str_two_ag): newag, newag2 = pickle.loads(pickle_str_two_ag) - assert newag.universe is newag2.universe, ( - "Two AtomGroups are unpickled to two different Universes" - ) + assert ( + newag.universe is newag2.universe + ), "Two AtomGroups are unpickled to two different Universes" - def test_unpickle_ag_with_universe_f(self, - pickle_str_ag_with_universe_f): + def test_unpickle_ag_with_universe_f(self, pickle_str_ag_with_universe_f): newu, newag = pickle.loads(pickle_str_ag_with_universe_f) assert newag.universe is newu, ( "AtomGroup is not unpickled to the bound Universe" "when Universe is pickled first" ) - def test_unpickle_ag_with_universe(self, - pickle_str_ag_with_universe): + def test_unpickle_ag_with_universe(self, pickle_str_ag_with_universe): newag, newu = pickle.loads(pickle_str_ag_with_universe) assert newag.universe is newu, ( - "AtomGroup is not unpickled to the bound Universe" - "when AtomGroup is pickled first" + "AtomGroup is not unpickled to the bound Universe" + "when AtomGroup is pickled first" ) @@ -119,15 +114,15 @@ def u(): def test_pickling_uag(self, u): ag = u.atoms[:100] - uag = ag.select_atoms('name C', updating=True) + uag = ag.select_atoms("name C", updating=True) pickle_str = pickle.dumps(uag, protocol=pickle.HIGHEST_PROTOCOL) new_uag = pickle.loads(pickle_str) assert_equal(uag.indices, new_uag.indices) def test_pickling_uag_of_uag(self, u): - uag1 = u.select_atoms('name C or name H', updating=True) - uag2 = uag1.select_atoms('name C', updating=True) + uag1 = u.select_atoms("name C or name H", updating=True) + uag2 = uag1.select_atoms("name C", updating=True) pickle_str = pickle.dumps(uag2, protocol=pickle.HIGHEST_PROTOCOL) new_uag2 = pickle.loads(pickle_str) diff --git a/testsuite/MDAnalysisTests/utils/test_pickleio.py b/testsuite/MDAnalysisTests/utils/test_pickleio.py index 64dc6a9a66b..ae6c342cec1 100644 --- a/testsuite/MDAnalysisTests/utils/test_pickleio.py +++ b/testsuite/MDAnalysisTests/utils/test_pickleio.py @@ -36,25 +36,16 @@ bz2_pickle_open, gzip_pickle_open, ) -from MDAnalysis.coordinates.GSD import ( - GSDPicklable, - gsd_pickle_open, - HAS_GSD -) +from MDAnalysis.coordinates.GSD import GSDPicklable, gsd_pickle_open, HAS_GSD from MDAnalysis.coordinates.TRJ import ( NCDFPicklable, ) -from MDAnalysis.coordinates.chemfiles import ( - check_chemfiles_version -) +from MDAnalysis.coordinates.chemfiles import check_chemfiles_version + if check_chemfiles_version(): - from MDAnalysis.coordinates.chemfiles import ( - ChemfilesPicklable - ) + from MDAnalysis.coordinates.chemfiles import ChemfilesPicklable from MDAnalysis.coordinates.H5MD import HAS_H5PY -from MDAnalysis.coordinates.H5MD import ( - H5PYPicklable -) +from MDAnalysis.coordinates.H5MD import H5PYPicklable from MDAnalysis.tests.datafiles import ( PDB, @@ -65,17 +56,19 @@ GSD, NCDF, TPR_xvf, - H5MD_xvf + H5MD_xvf, ) -@pytest.fixture(params=[ - # filename mode - (PDB, 'r'), - (PDB, 'rt'), - (XYZ_bz2, 'rt'), - (GMS_ASYMOPT, 'rt') -]) +@pytest.fixture( + params=[ + # filename mode + (PDB, "r"), + (PDB, "rt"), + (XYZ_bz2, "rt"), + (GMS_ASYMOPT, "rt"), + ] +) def f_text(request): filename, mode = request.param return anyopen(filename, mode) @@ -96,12 +89,14 @@ def test_offset_text_same(f_text): assert_equal(f_text_pickled.tell(), f_text.tell()) -@pytest.fixture(params=[ - # filename mode ref_class - (PDB, 'rb', BufferIOPicklable), - (XYZ_bz2, 'rb', BZ2Picklable), - (MMTF_gz, 'rb', GzipPicklable) -]) +@pytest.fixture( + params=[ + # filename mode ref_class + (PDB, "rb", BufferIOPicklable), + (XYZ_bz2, "rb", BZ2Picklable), + (MMTF_gz, "rb", GzipPicklable), + ] +) def f_byte(request): filename, mode, ref_reader_class = request.param return anyopen(filename, mode), ref_reader_class @@ -136,14 +131,16 @@ def test_fileio_pickle(): assert_equal(raw_io.readlines(), raw_io_pickled.readlines()) -@pytest.fixture(params=[ - # filename mode open_func open_class - ('test.pdb', 'w', pickle_open, FileIOPicklable), - ('test.pdb', 'x', pickle_open, FileIOPicklable), - ('test.pdb', 'a', pickle_open, FileIOPicklable), - ('test.bz2', 'w', bz2_pickle_open, BZ2Picklable), - ('test.gz', 'w', gzip_pickle_open, GzipPicklable), -]) +@pytest.fixture( + params=[ + # filename mode open_func open_class + ("test.pdb", "w", pickle_open, FileIOPicklable), + ("test.pdb", "x", pickle_open, FileIOPicklable), + ("test.pdb", "a", pickle_open, FileIOPicklable), + ("test.bz2", "w", bz2_pickle_open, BZ2Picklable), + ("test.gz", "w", gzip_pickle_open, GzipPicklable), + ] +) def unpicklable_f(request): filename, mode, open_func, open_class = request.param return filename, mode, open_func, open_class @@ -162,26 +159,28 @@ def test_pickle_with_write_mode(unpicklable_f, tmpdir): f_pickled = pickle.loads(pickle.dumps(f_open_by_class)) -@pytest.mark.skipif(not HAS_GSD, reason='gsd not installed') +@pytest.mark.skipif(not HAS_GSD, reason="gsd not installed") def test_GSD_pickle(): - gsd_io = gsd_pickle_open(GSD, mode='r') + gsd_io = gsd_pickle_open(GSD, mode="r") gsd_io_pickled = pickle.loads(pickle.dumps(gsd_io)) - assert_equal(gsd_io[0].particles.position, - gsd_io_pickled[0].particles.position) + assert_equal( + gsd_io[0].particles.position, gsd_io_pickled[0].particles.position + ) -@pytest.mark.skipif(not HAS_GSD, reason='gsd not installed') +@pytest.mark.skipif(not HAS_GSD, reason="gsd not installed") def test_GSD_with_write_mode(tmpdir): with pytest.raises(ValueError, match=r"Only read mode"): - gsd_io = gsd_pickle_open(tmpdir.mkdir("gsd").join('t.gsd'), - mode='w') + gsd_io = gsd_pickle_open(tmpdir.mkdir("gsd").join("t.gsd"), mode="w") def test_NCDF_pickle(): ncdf_io = NCDFPicklable(NCDF, mmap=None) ncdf_io_pickled = pickle.loads(pickle.dumps(ncdf_io)) - assert_equal(ncdf_io.variables['coordinates'][0], - ncdf_io_pickled.variables['coordinates'][0]) + assert_equal( + ncdf_io.variables["coordinates"][0], + ncdf_io_pickled.variables["coordinates"][0], + ) def test_NCDF_mmap_pickle(): @@ -190,8 +189,9 @@ def test_NCDF_mmap_pickle(): assert_equal(ncdf_io_pickled.use_mmap, False) -@pytest.mark.skipif(not check_chemfiles_version(), - reason="Wrong version of chemfiles") +@pytest.mark.skipif( + not check_chemfiles_version(), reason="Wrong version of chemfiles" +) def test_Chemfiles_pickle(): chemfiles_io = ChemfilesPicklable(XYZ) chemfiles_io_pickled = pickle.loads(pickle.dumps(chemfiles_io)) @@ -199,29 +199,34 @@ def test_Chemfiles_pickle(): # As opposed to `chemfiles_io.read().positions) frame = chemfiles_io.read() frame_pickled = chemfiles_io_pickled.read() - assert_equal(frame.positions[:], - frame_pickled.positions[:]) + assert_equal(frame.positions[:], frame_pickled.positions[:]) -@pytest.mark.skipif(not check_chemfiles_version(), - reason="Wrong version of chemfiles") +@pytest.mark.skipif( + not check_chemfiles_version(), reason="Wrong version of chemfiles" +) def test_Chemfiles_with_write_mode(tmpdir): with pytest.raises(ValueError, match=r"Only read mode"): - chemfiles_io = ChemfilesPicklable(tmpdir.mkdir("xyz").join('t.xyz'), - mode='w') + chemfiles_io = ChemfilesPicklable( + tmpdir.mkdir("xyz").join("t.xyz"), mode="w" + ) @pytest.mark.skipif(not HAS_H5PY, reason="h5py not installed") def test_H5MD_pickle(): - h5md_io = H5PYPicklable(H5MD_xvf, 'r') + h5md_io = H5PYPicklable(H5MD_xvf, "r") h5md_io_pickled = pickle.loads(pickle.dumps(h5md_io)) - assert_equal(h5md_io['particles/trajectory/position/value'][0], - h5md_io_pickled['particles/trajectory/position/value'][0]) + assert_equal( + h5md_io["particles/trajectory/position/value"][0], + h5md_io_pickled["particles/trajectory/position/value"][0], + ) @pytest.mark.skipif(not HAS_H5PY, reason="h5py not installed") def test_H5MD_pickle_with_driver(): - h5md_io = H5PYPicklable(H5MD_xvf, 'r', driver='core') + h5md_io = H5PYPicklable(H5MD_xvf, "r", driver="core") h5md_io_pickled = pickle.loads(pickle.dumps(h5md_io)) - assert_equal(h5md_io['particles/trajectory/position/value'][0], - h5md_io_pickled['particles/trajectory/position/value'][0]) + assert_equal( + h5md_io["particles/trajectory/position/value"][0], + h5md_io_pickled["particles/trajectory/position/value"][0], + ) diff --git a/testsuite/MDAnalysisTests/utils/test_qcprot.py b/testsuite/MDAnalysisTests/utils/test_qcprot.py index 484fb78c59e..8ee971c227e 100644 --- a/testsuite/MDAnalysisTests/utils/test_qcprot.py +++ b/testsuite/MDAnalysisTests/utils/test_qcprot.py @@ -47,12 +47,17 @@ @pytest.fixture() def atoms_a(): - return np.array([[1,2,3], [4,5,6], [7,8,9], [10,11,12]], dtype=np.float64) + return np.array( + [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], dtype=np.float64 + ) @pytest.fixture() def atoms_b(): - return np.array([[13,14,15], [16,17,18], [19,20,21], [22,23,24]], dtype=np.float64) + return np.array( + [[13, 14, 15], [16, 17, 18], [19, 20, 21], [22, 23, 24]], + dtype=np.float64, + ) # Calculate rmsd after applying rotation @@ -125,23 +130,35 @@ def test_CalcRMSDRotationalMatrix(): # Calculate rmsd and rotation matrix qcp_rmsd = qcp.CalcRMSDRotationalMatrix(frag_a.T, frag_b.T, N, rot, None) - #print 'qcp rmsd = ',rmsd - #print 'rotation matrix:' - #print rot.reshape((3,3)) + # print 'qcp rmsd = ',rmsd + # print 'rotation matrix:' + # print rot.reshape((3,3)) # rotate frag_b to obtain optimal alignment frag_br = np.dot(frag_b.T, rot.reshape((3, 3))) aligned_rmsd = rmsd(frag_br.T, frag_a) - #print 'rmsd after applying rotation: ',rmsd - - assert_almost_equal(aligned_rmsd, 0.719106, 6, "RMSD between fragments A and B does not match excpected value.") - - expected_rot = np.array([ - [0.72216358, -0.52038257, -0.45572112], - [0.69118937, 0.51700833, 0.50493528], - [-0.0271479, -0.67963547, 0.73304748]]) - assert_almost_equal(rot.reshape((3, 3)), expected_rot, 6, - "Rotation matrix for aliging B to A does not have expected values.") + # print 'rmsd after applying rotation: ',rmsd + + assert_almost_equal( + aligned_rmsd, + 0.719106, + 6, + "RMSD between fragments A and B does not match excpected value.", + ) + + expected_rot = np.array( + [ + [0.72216358, -0.52038257, -0.45572112], + [0.69118937, 0.51700833, 0.50493528], + [-0.0271479, -0.67963547, 0.73304748], + ] + ) + assert_almost_equal( + rot.reshape((3, 3)), + expected_rot, + 6, + "Rotation matrix for aliging B to A does not have expected values.", + ) def test_innerproduct(atoms_a, atoms_b): @@ -158,29 +175,63 @@ def test_innerproduct(atoms_a, atoms_b): def test_RMSDmatrix(atoms_a, atoms_b): number_of_atoms = 4 rotation = np.zeros(9, dtype=np.float64) - rmsd = qcp.CalcRMSDRotationalMatrix(atoms_a, atoms_b, number_of_atoms, rotation, None) # no weights + rmsd = qcp.CalcRMSDRotationalMatrix( + atoms_a, atoms_b, number_of_atoms, rotation, None + ) # no weights rmsd_ref = 20.73219522556076 assert_almost_equal(rmsd_ref, rmsd) - rotation_ref = np.array([0.9977195, 0.02926979, 0.06082009, -.0310942, 0.9990878, 0.02926979, -0.05990789, -.0310942, 0.9977195]) + rotation_ref = np.array( + [ + 0.9977195, + 0.02926979, + 0.06082009, + -0.0310942, + 0.9990878, + 0.02926979, + -0.05990789, + -0.0310942, + 0.9977195, + ] + ) assert_array_almost_equal(rotation, rotation_ref, 6) def test_RMSDmatrix_simple(atoms_a, atoms_b): number_of_atoms = 4 rotation = np.zeros(9, dtype=np.float64) - rmsd = qcp.CalcRMSDRotationalMatrix(atoms_a, atoms_b, number_of_atoms, rotation, None) # no weights + rmsd = qcp.CalcRMSDRotationalMatrix( + atoms_a, atoms_b, number_of_atoms, rotation, None + ) # no weights rmsd_ref = 20.73219522556076 assert_almost_equal(rmsd_ref, rmsd) - rotation_ref = np.array([0.9977195, 0.02926979, 0.06082009, -.0310942, 0.9990878, 0.02926979, -0.05990789, -.0310942, 0.9977195]) + rotation_ref = np.array( + [ + 0.9977195, + 0.02926979, + 0.06082009, + -0.0310942, + 0.9990878, + 0.02926979, + -0.05990789, + -0.0310942, + 0.9977195, + ] + ) assert_array_almost_equal(rotation, rotation_ref, 6) - + def test_rmsd(atoms_a, atoms_b): - rotation_m = np.array([[.9977195, .02926979, .06082009], [-.0310942, .9990878, .02926979], [-.05990789, -.0310942, .9977195]]) + rotation_m = np.array( + [ + [0.9977195, 0.02926979, 0.06082009], + [-0.0310942, 0.9990878, 0.02926979], + [-0.05990789, -0.0310942, 0.9977195], + ] + ) atoms_b_aligned = np.dot(atoms_b, rotation_m) rmsd = rms.rmsd(atoms_b_aligned, atoms_a) rmsd_ref = 20.73219522556076 @@ -189,11 +240,25 @@ def test_rmsd(atoms_a, atoms_b): def test_weights(atoms_a, atoms_b): no_of_atoms = 4 - weights = np.array([1,2,3,4], dtype=np.float64) + weights = np.array([1, 2, 3, 4], dtype=np.float64) rotation = np.zeros(9, dtype=np.float64) - rmsd = qcp.CalcRMSDRotationalMatrix(atoms_a, atoms_b, no_of_atoms, rotation, weights) + rmsd = qcp.CalcRMSDRotationalMatrix( + atoms_a, atoms_b, no_of_atoms, rotation, weights + ) assert_almost_equal(rmsd, 32.798779202159416) - rotation_ref = np.array([0.99861395, .022982, .04735006, -.02409085, .99944556, .022982, -.04679564, -.02409085, .99861395]) + rotation_ref = np.array( + [ + 0.99861395, + 0.022982, + 0.04735006, + -0.02409085, + 0.99944556, + 0.022982, + -0.04679564, + -0.02409085, + 0.99861395, + ] + ) np.testing.assert_almost_equal(rotation_ref, rotation) diff --git a/testsuite/MDAnalysisTests/utils/test_selections.py b/testsuite/MDAnalysisTests/utils/test_selections.py index d271f2f09f6..212bce97dae 100644 --- a/testsuite/MDAnalysisTests/utils/test_selections.py +++ b/testsuite/MDAnalysisTests/utils/test_selections.py @@ -42,7 +42,9 @@ class _SelectionWriter(object): filename = None - max_number = 357 # to keep fixtures smallish, only select CAs up to number 357 + max_number = ( + 357 # to keep fixtures smallish, only select CAs up to number 357 + ) @staticmethod @pytest.fixture() @@ -54,7 +56,9 @@ def namedfile(self): return NamedStream(StringIO(), self.filename) def _selection(self, universe): - return universe.select_atoms("protein and name CA and bynum 1-{0}".format(self.max_number)) + return universe.select_atoms( + "protein and name CA and bynum 1-{0}".format(self.max_number) + ) def _write(self, universe, namedfile, **kwargs): g = self._selection(universe) @@ -72,9 +76,13 @@ def _write_with(self, universe, namedfile, **kwargs): outfile.write(g) return g - def test_write_bad_mode(self, universe, namedfile,): + def test_write_bad_mode( + self, + universe, + namedfile, + ): with pytest.raises(ValueError): - self._write(universe, namedfile, name=self.ref_name, mode='a+') + self._write(universe, namedfile, name=self.ref_name, mode="a+") def test_write(self, universe, namedfile): self._write(universe, namedfile, name=self.ref_name) @@ -105,18 +113,25 @@ class TestSelectionWriter_Gromacs(_SelectionWriter): filename = "CA.ndx" ref_name = "CA_selection" ref_indices = ndx2array( - [ '5 22 46 65 84 103 122 129 141 153 160 170 \n', - '177 199 206 220 237 247 264 284 303 320 335 357 \n', - ] - ) + [ + "5 22 46 65 84 103 122 129 141 153 160 170 \n", + "177 199 206 220 237 247 264 284 303 320 335 357 \n", + ] + ) def _assert_selectionstring(self, namedfile): header = namedfile.readline().strip() - assert_equal(header, "[ {0} ]".format(self.ref_name), - err_msg="NDX file has wrong selection name") + assert_equal( + header, + "[ {0} ]".format(self.ref_name), + err_msg="NDX file has wrong selection name", + ) indices = ndx2array(namedfile.readlines()) - assert_array_equal(indices, self.ref_indices, - err_msg="indices were not written correctly") + assert_array_equal( + indices, + self.ref_indices, + err_msg="indices were not written correctly", + ) class TestSelectionWriter_Charmm(_SelectionWriter): @@ -124,8 +139,9 @@ class TestSelectionWriter_Charmm(_SelectionWriter): writer = MDAnalysis.selections.charmm.SelectionWriter filename = "CA.str" ref_name = "CA_selection" - ref_selectionstring = lines2one([ - """! MDAnalysis CHARMM selection + ref_selectionstring = lines2one( + [ + """! MDAnalysis CHARMM selection DEFINE CA_selection SELECT - BYNUM 5 .or. BYNUM 22 .or. BYNUM 46 .or. BYNUM 65 .or. - BYNUM 84 .or. BYNUM 103 .or. BYNUM 122 .or. BYNUM 129 .or. - @@ -133,12 +149,17 @@ class TestSelectionWriter_Charmm(_SelectionWriter): BYNUM 177 .or. BYNUM 199 .or. BYNUM 206 .or. BYNUM 220 .or. - BYNUM 237 .or. BYNUM 247 .or. BYNUM 264 .or. BYNUM 284 .or. - BYNUM 303 .or. BYNUM 320 .or. BYNUM 335 .or. BYNUM 357 END - """]) + """ + ] + ) def _assert_selectionstring(self, namedfile): selectionstring = lines2one(namedfile.readlines()) - assert_equal(selectionstring, self.ref_selectionstring, - err_msg="Charmm selection was not written correctly") + assert_equal( + selectionstring, + self.ref_selectionstring, + err_msg="Charmm selection was not written correctly", + ) class TestSelectionWriter_PyMOL(_SelectionWriter): @@ -146,18 +167,24 @@ class TestSelectionWriter_PyMOL(_SelectionWriter): writer = MDAnalysis.selections.pymol.SelectionWriter filename = "CA.pml" ref_name = "CA_selection" - ref_selectionstring = lines2one([ - """# MDAnalysis PyMol selection\n select CA_selection, \\ + ref_selectionstring = lines2one( + [ + """# MDAnalysis PyMol selection\n select CA_selection, \\ index 5 | index 22 | index 46 | index 65 | index 84 | index 103 | \\ index 122 | index 129 | index 141 | index 153 | index 160 | index 170 | \\ index 177 | index 199 | index 206 | index 220 | index 237 | index 247 | \\ index 264 | index 284 | index 303 | index 320 | index 335 | index 357 - """]) + """ + ] + ) def _assert_selectionstring(self, namedfile): selectionstring = lines2one(namedfile.readlines()) - assert_equal(selectionstring, self.ref_selectionstring, - err_msg="PyMOL selection was not written correctly") + assert_equal( + selectionstring, + self.ref_selectionstring, + err_msg="PyMOL selection was not written correctly", + ) class TestSelectionWriter_VMD(_SelectionWriter): @@ -165,21 +192,27 @@ class TestSelectionWriter_VMD(_SelectionWriter): writer = MDAnalysis.selections.vmd.SelectionWriter filename = "CA.vmd" ref_name = "CA_selection" - ref_selectionstring = lines2one([ - """# MDAnalysis VMD selection atomselect macro CA_selection {index 4 21 45 64 83 102 121 128 \\ + ref_selectionstring = lines2one( + [ + """# MDAnalysis VMD selection atomselect macro CA_selection {index 4 21 45 64 83 102 121 128 \\ 140 152 159 169 176 198 205 219 \\ 236 246 263 283 302 319 334 356 } - """]) + """ + ] + ) def _assert_selectionstring(self, namedfile): selectionstring = lines2one(namedfile.readlines()) - assert_equal(selectionstring, self.ref_selectionstring, - err_msg="PyMOL selection was not written correctly") + assert_equal( + selectionstring, + self.ref_selectionstring, + err_msg="PyMOL selection was not written correctly", + ) def spt2array(line): """Get name of and convert Jmol SPT definition to integer array""" - match = re.search(r'\@~(\w+) \(\{([\d\s]*)\}\)', line) + match = re.search(r"\@~(\w+) \(\{([\d\s]*)\}\)", line) return match.group(1), np.array(match.group(2).split(), dtype=int) @@ -188,16 +221,22 @@ class TestSelectionWriter_Jmol(_SelectionWriter): writer = MDAnalysis.selections.jmol.SelectionWriter filename = "CA.spt" ref_name, ref_indices = spt2array( - ( '@~ca ({4 21 45 64 83 102 121 128 140 152 159 169 176 198 205 219 236' - ' 246 263 283 302 319 334 356});') + ( + "@~ca ({4 21 45 64 83 102 121 128 140 152 159 169 176 198 205 219 236" + " 246 263 283 302 319 334 356});" ) + ) def _assert_selectionstring(self, namedfile): header, indices = spt2array(namedfile.readline()) - assert_equal(header, self.ref_name, - err_msg="SPT file has wrong selection name") - assert_array_equal(indices, self.ref_indices, - err_msg="SPT indices were not written correctly") + assert_equal( + header, self.ref_name, err_msg="SPT file has wrong selection name" + ) + assert_array_equal( + indices, + self.ref_indices, + err_msg="SPT indices were not written correctly", + ) class TestSelections: diff --git a/testsuite/MDAnalysisTests/utils/test_streamio.py b/testsuite/MDAnalysisTests/utils/test_streamio.py index 53eb1a95c8e..56a421dda7f 100644 --- a/testsuite/MDAnalysisTests/utils/test_streamio.py +++ b/testsuite/MDAnalysisTests/utils/test_streamio.py @@ -20,13 +20,26 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -from os.path import abspath, basename, dirname, expanduser, normpath, relpath, split, splitext +from os.path import ( + abspath, + basename, + dirname, + expanduser, + normpath, + relpath, + split, + splitext, +) from io import StringIO import pytest import numpy as np -from numpy.testing import assert_equal, assert_almost_equal, assert_array_almost_equal +from numpy.testing import ( + assert_equal, + assert_almost_equal, + assert_array_almost_equal, +) import MDAnalysis @@ -77,7 +90,7 @@ class TestNamedStream(object): text = [ "The Jabberwock, with eyes of flame,\n", "Came whiffling through the tulgey wood,\n", - "And burbled as it came!" + "And burbled as it came!", ] textname = "jabberwock.txt" @@ -110,7 +123,7 @@ def test_StringIO_read(self): ns.close(force=True) def test_File_read(self): - obj = open(self.filename, 'r') + obj = open(self.filename, "r") ns = util.NamedStream(obj, self.filename) assert_equal(ns.name, self.filename) assert_equal(str(ns), self.filename) @@ -148,15 +161,17 @@ def test_File_write(self, tmpdir): def test_matryoshka(self): obj = StringIO() - ns = util.NamedStream(obj, 'r') + ns = util.NamedStream(obj, "r") with pytest.warns(RuntimeWarning): - ns2 = util.NamedStream(ns, 'f') + ns2 = util.NamedStream(ns, "f") assert not isinstance(ns2.stream, util.NamedStream) - assert ns2.name == 'f' + assert ns2.name == "f" class TestNamedStream_filename_behavior(object): - textname = os.path.join("~", "stories", "jabberwock.txt") # with tilde ~ to test regular expanduser() + textname = os.path.join( + "~", "stories", "jabberwock.txt" + ) # with tilde ~ to test regular expanduser() # note: no setUp() because classes with generators would run it # *for each generated test* and we need it for the generator method @@ -166,7 +181,9 @@ def create_NamedStream(self, name=None): obj = StringIO() return util.NamedStream(obj, name) - @pytest.mark.parametrize('func', ( + @pytest.mark.parametrize( + "func", + ( abspath, basename, dirname, @@ -174,8 +191,9 @@ def create_NamedStream(self, name=None): normpath, relpath, split, - splitext - )) + splitext, + ), + ) def test_func(self, func): # - "expandvars" gave Segmentation fault (OS X 10.6, Python 2.7.11 -- orbeckst) # - "expanduser" will either return a string if it carried out interpolation @@ -186,9 +204,13 @@ def test_func(self, func): fn = self.textname reference = func(fn) value = func(ns) - assert_equal(value, reference, - err_msg=("os.path.{0}() does not work with " - "NamedStream").format(func.__name__)) + assert_equal( + value, + reference, + err_msg=("os.path.{0}() does not work with " "NamedStream").format( + func.__name__ + ), + ) def test_join(self, tmpdir, funcname="join"): # join not included because of different call signature @@ -198,62 +220,86 @@ def test_join(self, tmpdir, funcname="join"): fn = self.textname reference = str(tmpdir.join(fn)) value = os.path.join(str(tmpdir), ns) - assert_equal(value, reference, - err_msg=("os.path.{0}() does not work with " - "NamedStream").format(funcname)) + assert_equal( + value, + reference, + err_msg=("os.path.{0}() does not work with " "NamedStream").format( + funcname + ), + ) def test_expanduser_noexpansion_returns_NamedStream(self): - ns = self.create_NamedStream("de/zipferlack.txt") # no tilde ~ in name! + ns = self.create_NamedStream( + "de/zipferlack.txt" + ) # no tilde ~ in name! reference = ns.name value = os.path.expanduser(ns) - assert_equal(value, reference, - err_msg=("os.path.expanduser() without '~' did not " - "return NamedStream --- weird!!")) - - @pytest.mark.skipif("HOME" not in os.environ, reason='It is needed') + assert_equal( + value, + reference, + err_msg=( + "os.path.expanduser() without '~' did not " + "return NamedStream --- weird!!" + ), + ) + + @pytest.mark.skipif("HOME" not in os.environ, reason="It is needed") def test_expandvars(self): name = "${HOME}/stories/jabberwock.txt" ns = self.create_NamedStream(name) reference = os.path.expandvars(name) value = os.path.expandvars(ns) - assert_equal(value, reference, - err_msg="os.path.expandvars() did not expand HOME") + assert_equal( + value, + reference, + err_msg="os.path.expandvars() did not expand HOME", + ) def test_expandvars_noexpansion_returns_NamedStream(self): - ns = self.create_NamedStream() # no $VAR constructs + ns = self.create_NamedStream() # no $VAR constructs reference = ns.name value = os.path.expandvars(ns) - assert_equal(value, reference, - err_msg=("os.path.expandvars() without '$VARS' did not " - "return NamedStream --- weird!!")) + assert_equal( + value, + reference, + err_msg=( + "os.path.expandvars() without '$VARS' did not " + "return NamedStream --- weird!!" + ), + ) def test_add(self): ns = self.create_NamedStream() try: assert_equal(ns + "foo", self.textname + "foo") except TypeError: - raise pytest.fail("NamedStream does not support " - "string concatenation, NamedStream + str") + raise pytest.fail( + "NamedStream does not support " + "string concatenation, NamedStream + str" + ) def test_radd(self): ns = self.create_NamedStream() try: assert_equal("foo" + ns, "foo" + self.textname) except TypeError: - raise pytest.fail("NamedStream does not support right " - "string concatenation, str + NamedStream") + raise pytest.fail( + "NamedStream does not support right " + "string concatenation, str + NamedStream" + ) class _StreamData(object): """Data for StreamIO functions.""" + filenames = { - 'PSF': datafiles.PSF, - 'CRD': datafiles.CRD, - 'PDB': datafiles.PDB_small, - 'PQR': datafiles.PQR, - 'GRO': datafiles.GRO_velocity, - 'MOL2': datafiles.mol2_molecules, - 'PDBQT': datafiles.PDBQT_input, + "PSF": datafiles.PSF, + "CRD": datafiles.CRD, + "PDB": datafiles.PDB_small, + "PQR": datafiles.PQR, + "GRO": datafiles.GRO_velocity, + "MOL2": datafiles.mol2_molecules, + "PDBQT": datafiles.PDBQT_input, } def __init__(self): @@ -261,8 +307,10 @@ def __init__(self): for name, fn in self.filenames.items(): with open(fn) as filed: self.buffers[name] = "".join(filed.readlines()) - self.filenames['XYZ_PSF'] = u"bogus/path/mini.psf" - self.buffers['XYZ_PSF'] = u"""\ + self.filenames["XYZ_PSF"] = "bogus/path/mini.psf" + self.buffers[ + "XYZ_PSF" + ] = """\ PSF CMAP 1 !NTITLE @@ -278,8 +326,10 @@ def __init__(self): 7 A 380 THR C C 0.510000 12.0110 0 8 A 380 THR O O -0.510000 15.9990 0 """ - self.filenames['XYZ'] = "bogus/path/mini.xyz" - self.buffers['XYZ'] = """\ + self.filenames["XYZ"] = "bogus/path/mini.xyz" + self.buffers[ + "XYZ" + ] = """\ 8 frame 1 N 0.93100 17.31800 16.42300 @@ -320,7 +370,7 @@ def as_NamedStream(self, name): return util.NamedStream(self.as_StringIO(name), self.filenames[name]) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def streamData(): return _StreamData() @@ -328,72 +378,97 @@ def streamData(): # possibly add tests to individual readers instead? class TestStreamIO(RefAdKSmall): def test_PrimitivePDBReader(self, streamData): - u = MDAnalysis.Universe(streamData.as_NamedStream('PDB')) + u = MDAnalysis.Universe(streamData.as_NamedStream("PDB")) assert_equal(u.atoms.n_atoms, self.ref_n_atoms) def test_PDBReader(self, streamData): try: - u = MDAnalysis.Universe(streamData.as_NamedStream('PDB')) + u = MDAnalysis.Universe(streamData.as_NamedStream("PDB")) except Exception as err: raise pytest.fail("StreamIO not supported:\n>>>>> {0}".format(err)) assert_equal(u.atoms.n_atoms, self.ref_n_atoms) def test_CRDReader(self, streamData): - u = MDAnalysis.Universe(streamData.as_NamedStream('CRD')) + u = MDAnalysis.Universe(streamData.as_NamedStream("CRD")) assert_equal(u.atoms.n_atoms, self.ref_n_atoms) def test_PSFParser(self, streamData): - u = MDAnalysis.Universe(streamData.as_NamedStream('PSF')) + u = MDAnalysis.Universe(streamData.as_NamedStream("PSF")) assert_equal(u.atoms.n_atoms, self.ref_n_atoms) def test_PSF_CRD(self, streamData): - u = MDAnalysis.Universe(streamData.as_NamedStream('PSF'), - streamData.as_NamedStream('CRD')) + u = MDAnalysis.Universe( + streamData.as_NamedStream("PSF"), streamData.as_NamedStream("CRD") + ) assert_equal(u.atoms.n_atoms, self.ref_n_atoms) def test_PQRReader(self, streamData): - u = MDAnalysis.Universe(streamData.as_NamedStream('PQR')) + u = MDAnalysis.Universe(streamData.as_NamedStream("PQR")) assert_equal(u.atoms.n_atoms, self.ref_n_atoms) - assert_almost_equal(u.atoms.total_charge(), self.ref_charmm_totalcharge, 3, - "Total charge (in CHARMM) does not match expected value.") - assert_almost_equal(u.atoms.select_atoms('name H').charges, self.ref_charmm_Hcharges, 3, - "Charges for H atoms do not match.") + assert_almost_equal( + u.atoms.total_charge(), + self.ref_charmm_totalcharge, + 3, + "Total charge (in CHARMM) does not match expected value.", + ) + assert_almost_equal( + u.atoms.select_atoms("name H").charges, + self.ref_charmm_Hcharges, + 3, + "Charges for H atoms do not match.", + ) def test_PDBQTReader(self, streamData): - u = MDAnalysis.Universe(streamData.as_NamedStream('PDBQT')) - sel = u.select_atoms('backbone') + u = MDAnalysis.Universe(streamData.as_NamedStream("PDBQT")) + sel = u.select_atoms("backbone") assert_equal(sel.n_atoms, 796) - sel = u.select_atoms('segid A') + sel = u.select_atoms("segid A") assert_equal(sel.n_atoms, 909, "failed to select segment A") - sel = u.select_atoms('segid B') + sel = u.select_atoms("segid B") assert_equal(sel.n_atoms, 896, "failed to select segment B") def test_GROReader(self, streamData): - u = MDAnalysis.Universe(streamData.as_NamedStream('GRO')) + u = MDAnalysis.Universe(streamData.as_NamedStream("GRO")) assert_equal(u.atoms.n_atoms, 6) - assert_almost_equal(u.atoms[3].position, - 10. * np.array([1.275, 0.053, 0.622]), 3, # manually convert nm -> A - err_msg="wrong coordinates for water 2 OW") - assert_almost_equal(u.atoms[3].velocity, - 10. * np.array([0.2519, 0.3140, -0.1734]), 3, # manually convert nm/ps -> A/ps - err_msg="wrong velocity for water 2 OW") + assert_almost_equal( + u.atoms[3].position, + 10.0 * np.array([1.275, 0.053, 0.622]), + 3, # manually convert nm -> A + err_msg="wrong coordinates for water 2 OW", + ) + assert_almost_equal( + u.atoms[3].velocity, + 10.0 * np.array([0.2519, 0.3140, -0.1734]), + 3, # manually convert nm/ps -> A/ps + err_msg="wrong velocity for water 2 OW", + ) def test_MOL2Reader(self, streamData): - u = MDAnalysis.Universe(streamData.as_NamedStream('MOL2')) + u = MDAnalysis.Universe(streamData.as_NamedStream("MOL2")) assert_equal(len(u.atoms), 49) assert_equal(u.trajectory.n_frames, 200) u.trajectory[199] - assert_array_almost_equal(u.atoms.positions[0], [1.7240, 11.2730, 14.1200]) + assert_array_almost_equal( + u.atoms.positions[0], [1.7240, 11.2730, 14.1200] + ) def test_XYZReader(self, streamData): - u = MDAnalysis.Universe(streamData.as_NamedStream('XYZ_PSF'), - streamData.as_NamedStream('XYZ')) + u = MDAnalysis.Universe( + streamData.as_NamedStream("XYZ_PSF"), + streamData.as_NamedStream("XYZ"), + ) assert_equal(len(u.atoms), 8) assert_equal(u.trajectory.n_frames, 3) - assert_equal(u.trajectory.frame, 0) # weird, something odd with XYZ reader + assert_equal( + u.trajectory.frame, 0 + ) # weird, something odd with XYZ reader u.trajectory.next() # (should really only need one next()... ) assert_equal(u.trajectory.frame, 1) # !!!! ??? u.trajectory.next() # frame 2 assert_equal(u.trajectory.frame, 2) - assert_almost_equal(u.atoms[2].position, np.array([0.45600, 18.48700, 16.26500]), 3, - err_msg="wrong coordinates for atom CA at frame 2") + assert_almost_equal( + u.atoms[2].position, + np.array([0.45600, 18.48700, 16.26500]), + 3, + err_msg="wrong coordinates for atom CA at frame 2", + ) diff --git a/testsuite/MDAnalysisTests/utils/test_transformations.py b/testsuite/MDAnalysisTests/utils/test_transformations.py index 8a3a4baec98..72d6831cd1f 100644 --- a/testsuite/MDAnalysisTests/utils/test_transformations.py +++ b/testsuite/MDAnalysisTests/utils/test_transformations.py @@ -24,8 +24,12 @@ import numpy as np import pytest -from numpy.testing import (assert_allclose, assert_equal, assert_almost_equal, - assert_array_equal) +from numpy.testing import ( + assert_allclose, + assert_equal, + assert_almost_equal, + assert_array_equal, +) from MDAnalysis.lib import transformations as t @@ -50,10 +54,7 @@ _ATOL = 1e-06 -@pytest.mark.parametrize('f', [ - t._py_identity_matrix, - t.identity_matrix -]) +@pytest.mark.parametrize("f", [t._py_identity_matrix, t.identity_matrix]) def test_identity_matrix(f): I = f() assert_allclose(I, np.dot(I, I)) @@ -61,10 +62,13 @@ def test_identity_matrix(f): assert_allclose(I, np.identity(4, dtype=np.float64)) -@pytest.mark.parametrize('f', [ - t._py_translation_matrix, - t.translation_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_translation_matrix, + t.translation_matrix, + ], +) def test_translation_matrix(f): v = np.array([0.2, 0.2, 0.2]) assert_allclose(v, f(v)[:3, 3]) @@ -77,15 +81,18 @@ def test_translation_from_matrix(): assert_allclose(v0, v1) -@pytest.mark.parametrize('f', [ - t._py_reflection_matrix, - t.reflection_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_reflection_matrix, + t.reflection_matrix, + ], +) def test_reflection_matrix(f): v0 = np.array([0.2, 0.2, 0.2, 1.0]) # arbitrary values v1 = np.array([0.4, 0.4, 0.4]) R = f(v0, v1) - assert_allclose(2., np.trace(R)) + assert_allclose(2.0, np.trace(R)) assert_allclose(v0, np.dot(R, v0)) v2 = v0.copy() v2[:3] += v1 @@ -103,13 +110,16 @@ def test_reflection_from_matrix(): assert_equal(t.is_same_transform(M0, M1), True) -@pytest.mark.parametrize('f', [ - t._py_rotation_matrix, - t.rotation_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_rotation_matrix, + t.rotation_matrix, + ], +) def test_rotation_matrix(f): R = f(np.pi / 2.0, [0, 0, 1], [1, 0, 0]) - assert_allclose(np.dot(R, [0, 0, 0, 1]), [1., -1., 0., 1.]) + assert_allclose(np.dot(R, [0, 0, 0, 1]), [1.0, -1.0, 0.0, 1.0]) angle = 0.2 * 2 * np.pi # arbitrary value direc = np.array([0.2, 0.2, 0.2]) point = np.array([0.4, 0.4, 0.4]) @@ -121,7 +131,7 @@ def test_rotation_matrix(f): assert_equal(t.is_same_transform(R0, R1), True) I = np.identity(4, np.float64) assert_allclose(I, f(np.pi * 2, direc), atol=_ATOL) - assert_allclose(2., np.trace(f(np.pi / 2, direc, point))) + assert_allclose(2.0, np.trace(f(np.pi / 2, direc, point))) def test_rotation_from_matrix(): @@ -134,10 +144,13 @@ def test_rotation_from_matrix(): assert_equal(t.is_same_transform(R0, R1), True) -@pytest.mark.parametrize('f', [ - t._py_scale_matrix, - t.scale_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_scale_matrix, + t.scale_matrix, + ], +) def test_scale_matrix(f): v = np.array([14.1, 15.1, 16.1, 1]) S = f(-1.234) @@ -157,10 +170,14 @@ def test_scale_from_matrix(): S1 = t.scale_matrix(factor, origin, direction) assert_equal(t.is_same_transform(S0, S1), True) -@pytest.mark.parametrize('f', [ - t._py_projection_matrix, - t.projection_matrix, -]) + +@pytest.mark.parametrize( + "f", + [ + t._py_projection_matrix, + t.projection_matrix, + ], +) class TestProjectionMatrix(object): def test_projection_matrix_1(self, f): P = f((0, 0, 0), (1, 0, 0)) @@ -187,7 +204,6 @@ def test_projection_matrix_3(self, f): assert_allclose(v1[0], 3.0 - v1[1], atol=_ATOL) - class TestProjectionFromMatrix(object): @staticmethod @pytest.fixture() @@ -215,25 +231,27 @@ def test_projection_from_matrix_2(self, data): def test_projection_from_matrix_3(self, data): point, normal, direct, persp = data P0 = t.projection_matrix( - point, normal, perspective=persp, pseudo=False) + point, normal, perspective=persp, pseudo=False + ) result = t.projection_from_matrix(P0, pseudo=False) P1 = t.projection_matrix(*result) assert_equal(t.is_same_transform(P0, P1), True) def test_projection_from_matrix_4(self, data): point, normal, direct, persp = data - P0 = t.projection_matrix( - point, normal, perspective=persp, pseudo=True) + P0 = t.projection_matrix(point, normal, perspective=persp, pseudo=True) result = t.projection_from_matrix(P0, pseudo=True) P1 = t.projection_matrix(*result) assert_equal(t.is_same_transform(P0, P1), True) - -@pytest.mark.parametrize('f', [ - t._py_clip_matrix, - t.clip_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_clip_matrix, + t.clip_matrix, + ], +) class TestClipMatrix(object): def test_clip_matrix_1(self, f): frustrum = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) # arbitrary values @@ -243,10 +261,12 @@ def test_clip_matrix_1(self, f): M = f(perspective=False, *frustrum) assert_allclose( np.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0]), - np.array([-1., -1., -1., 1.])) + np.array([-1.0, -1.0, -1.0, 1.0]), + ) assert_allclose( np.dot(M, [frustrum[1], frustrum[3], frustrum[5], 1.0]), - np.array([1., 1., 1., 1.])) + np.array([1.0, 1.0, 1.0, 1.0]), + ) def test_clip_matrix_2(self, f): frustrum = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) # arbitrary values @@ -255,33 +275,36 @@ def test_clip_matrix_2(self, f): frustrum[5] += frustrum[4] M = f(perspective=True, *frustrum) v = np.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0]) - assert_allclose(v / v[3], np.array([-1., -1., -1., 1.])) + assert_allclose(v / v[3], np.array([-1.0, -1.0, -1.0, 1.0])) v = np.dot(M, [frustrum[1], frustrum[3], frustrum[4], 1.0]) - assert_allclose(v / v[3], np.array([1., 1., -1., 1.])) + assert_allclose(v / v[3], np.array([1.0, 1.0, -1.0, 1.0])) def test_clip_matrix_frustrum_left_right_bounds(self, f): - '''ValueError should be raised if left > right.''' + """ValueError should be raised if left > right.""" frustrum = np.array([0.4, 0.3, 0.3, 0.7, 0.5, 1.1]) with pytest.raises(ValueError): f(*frustrum) def test_clip_matrix_frustrum_bottom_top_bounds(self, f): - '''ValueError should be raised if bottom > top.''' + """ValueError should be raised if bottom > top.""" frustrum = np.array([0.1, 0.3, 0.71, 0.7, 0.5, 1.1]) with pytest.raises(ValueError): f(*frustrum) def test_clip_matrix_frustrum_near_far_bounds(self, f): - '''ValueError should be raised if near > far.''' + """ValueError should be raised if near > far.""" frustrum = np.array([0.1, 0.3, 0.3, 0.7, 1.5, 1.1]) with pytest.raises(ValueError): f(*frustrum) -@pytest.mark.parametrize('f', [ - t._py_shear_matrix, - t.shear_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_shear_matrix, + t.shear_matrix, + ], +) def test_shear_matrix(f): angle = 0.2 * 4 * np.pi # arbitrary values direct = np.array([0.2, 0.2, 0.2]) @@ -345,13 +368,16 @@ def test_compose_matrix(): assert_equal(t.is_same_transform(M0, M1), True) -@pytest.mark.parametrize('f', [ - t._py_orthogonalization_matrix, - t.orthogonalization_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_orthogonalization_matrix, + t.orthogonalization_matrix, + ], +) class TestOrthogonalizationMatrix(object): def test_orthogonalization_matrix_1(self, f): - O = f((10., 10., 10.), (90., 90., 90.)) + O = f((10.0, 10.0, 10.0), (90.0, 90.0, 90.0)) assert_allclose(O[:3, :3], np.identity(3, float) * 10, atol=_ATOL) def test_orthogonalization_matrix_2(self, f): @@ -359,10 +385,13 @@ def test_orthogonalization_matrix_2(self, f): assert_allclose(np.sum(O), 43.063229, atol=_ATOL) -@pytest.mark.parametrize('f', [ - t._py_superimposition_matrix, - t.superimposition_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_superimposition_matrix, + t.superimposition_matrix, + ], +) def test_superimposition_matrix(f): v0 = np.sin(np.linspace(0, 0.99, 30)).reshape(3, 10) # arbitrary values M = f(v0, v0) @@ -397,13 +426,16 @@ def test_superimposition_matrix(f): assert_allclose(v1, np.dot(M, v[:, :, 0]), atol=_ATOL) -@pytest.mark.parametrize('f', [ - t._py_euler_matrix, - t.euler_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_euler_matrix, + t.euler_matrix, + ], +) class TestEulerMatrix(object): def test_euler_matrix_1(self, f): - R = f(1, 2, 3, 'syxz') + R = f(1, 2, 3, "syxz") assert_allclose(np.sum(R[0]), -1.34786452) def test_euler_matrix_2(self, f): @@ -411,15 +443,18 @@ def test_euler_matrix_2(self, f): assert_allclose(np.sum(R[0]), -0.383436184) -@pytest.mark.parametrize('f', [ - t._py_euler_from_matrix, - t.euler_from_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_euler_from_matrix, + t.euler_from_matrix, + ], +) class TestEulerFromMatrix(object): def test_euler_from_matrix_1(self, f): - R0 = t.euler_matrix(1, 2, 3, 'syxz') - al, be, ga = f(R0, 'syxz') - R1 = t.euler_matrix(al, be, ga, 'syxz') + R0 = t.euler_matrix(1, 2, 3, "syxz") + al, be, ga = f(R0, "syxz") + R1 = t.euler_matrix(al, be, ga, "syxz") assert_allclose(R0, R1) def test_euler_from_matrix_2(self, f): @@ -435,28 +470,37 @@ def test_euler_from_quaternion(): assert_allclose(angles, [0.123, 0, 0], atol=_ATOL) -@pytest.mark.parametrize('f', [ - t._py_quaternion_from_euler, - t.quaternion_from_euler, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_quaternion_from_euler, + t.quaternion_from_euler, + ], +) def test_quaternion_from_euler(f): - q = f(1, 2, 3, 'ryxz') + q = f(1, 2, 3, "ryxz") assert_allclose(q, [0.435953, 0.310622, -0.718287, 0.444435], atol=_ATOL) -@pytest.mark.parametrize('f', [ - t._py_quaternion_about_axis, - t.quaternion_about_axis, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_quaternion_about_axis, + t.quaternion_about_axis, + ], +) def test_quaternion_about_axis(f): q = f(0.123, (1, 0, 0)) assert_allclose(q, [0.99810947, 0.06146124, 0, 0], atol=_ATOL) -@pytest.mark.parametrize('f', [ - t._py_quaternion_matrix, - t.quaternion_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_quaternion_matrix, + t.quaternion_matrix, + ], +) class TestQuaternionMatrix(object): def test_quaternion_matrix_1(self, f): M = f([0.99810947, 0.06146124, 0, 0]) @@ -471,40 +515,53 @@ def test_quaternion_matrix_3(self, f): assert_allclose(M, np.diag([1, -1, -1, 1]), atol=_ATOL) -@pytest.mark.parametrize('f', [ - t._py_quaternion_from_matrix, - t.quaternion_from_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_quaternion_from_matrix, + t.quaternion_from_matrix, + ], +) class TestQuaternionFromMatrix(object): def test_quaternion_from_matrix_1(self, f): q = f(t.identity_matrix(), True) - assert_allclose(q, [1., 0., 0., 0.], atol=_ATOL) + assert_allclose(q, [1.0, 0.0, 0.0, 0.0], atol=_ATOL) def test_quaternion_from_matrix_2(self, f): - q = f(np.diag([1., -1., -1., 1.])) - check = (np.allclose( - q, [0, 1, 0, 0], atol=_ATOL) or np.allclose( - q, [0, -1, 0, 0], atol=_ATOL)) + q = f(np.diag([1.0, -1.0, -1.0, 1.0])) + check = np.allclose(q, [0, 1, 0, 0], atol=_ATOL) or np.allclose( + q, [0, -1, 0, 0], atol=_ATOL + ) assert_equal(check, True) def test_quaternion_from_matrix_3(self, f): R = t.rotation_matrix(0.123, (1, 2, 3)) q = f(R, True) assert_allclose( - q, [0.9981095, 0.0164262, 0.0328524, 0.0492786], atol=_ATOL) + q, [0.9981095, 0.0164262, 0.0328524, 0.0492786], atol=_ATOL + ) def test_quaternion_from_matrix_4(self, f): - R = [[-0.545, 0.797, 0.260, 0], [0.733, 0.603, -0.313, 0], - [-0.407, 0.021, -0.913, 0], [0, 0, 0, 1]] + R = [ + [-0.545, 0.797, 0.260, 0], + [0.733, 0.603, -0.313, 0], + [-0.407, 0.021, -0.913, 0], + [0, 0, 0, 1], + ] q = f(R) assert_allclose(q, [0.19069, 0.43736, 0.87485, -0.083611], atol=_ATOL) def test_quaternion_from_matrix_5(self, f): - R = [[0.395, 0.362, 0.843, 0], [-0.626, 0.796, -0.056, 0], - [-0.677, -0.498, 0.529, 0], [0, 0, 0, 1]] + R = [ + [0.395, 0.362, 0.843, 0], + [-0.626, 0.796, -0.056, 0], + [-0.677, -0.498, 0.529, 0], + [0, 0, 0, 1], + ] q = f(R) assert_allclose( - q, [0.82336615, -0.13610694, 0.46344705, -0.29792603], atol=_ATOL) + q, [0.82336615, -0.13610694, 0.46344705, -0.29792603], atol=_ATOL + ) def test_quaternion_from_matrix_6(self, f): R = t.random_rotation_matrix() @@ -512,19 +569,25 @@ def test_quaternion_from_matrix_6(self, f): assert_equal(t.is_same_transform(R, t.quaternion_matrix(q)), True) -@pytest.mark.parametrize('f', [ - t._py_quaternion_multiply, - t.quaternion_multiply, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_quaternion_multiply, + t.quaternion_multiply, + ], +) def test_quaternion_multiply(f): q = f([4, 1, -2, 3], [8, -5, 6, 7]) assert_allclose(q, [28, -44, -14, 48]) -@pytest.mark.parametrize('f', [ - t._py_quaternion_conjugate, - t.quaternion_conjugate, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_quaternion_conjugate, + t.quaternion_conjugate, + ], +) def test_quaternion_conjugate(f): q0 = t.random_quaternion() q1 = f(q0) @@ -532,10 +595,13 @@ def test_quaternion_conjugate(f): assert_equal(check, True) -@pytest.mark.parametrize('f', [ - t._py_quaternion_inverse, - t.quaternion_inverse, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_quaternion_inverse, + t.quaternion_inverse, + ], +) def test_quaternion_inverse(f): q0 = t.random_quaternion() q1 = f(q0) @@ -550,10 +616,13 @@ def test_quaternion_imag(): assert_allclose(t.quaternion_imag([3.0, 0.0, 1.0, 2.0]), [0.0, 1.0, 2.0]) -@pytest.mark.parametrize('f', [ - t._py_quaternion_slerp, - t.quaternion_slerp, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_quaternion_slerp, + t.quaternion_slerp, + ], +) def test_quaternion_slerp(f): q0 = t.random_quaternion() q1 = t.random_quaternion() @@ -566,16 +635,20 @@ def test_quaternion_slerp(f): q = f(q0, q1, 0.5) angle = np.arccos(np.dot(q0, q)) - check = (np.allclose(2.0, np.arccos(np.dot(q0, q1)) / angle) or - np.allclose(2.0, np.arccos(-np.dot(q0, q1)) / angle)) + check = np.allclose(2.0, np.arccos(np.dot(q0, q1)) / angle) or np.allclose( + 2.0, np.arccos(-np.dot(q0, q1)) / angle + ) assert_equal(check, True) -@pytest.mark.parametrize('f', [ - t._py_random_quaternion, - t.random_quaternion, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_random_quaternion, + t.random_quaternion, + ], +) class TestRandomQuaternion(object): def test_random_quaternion_1(self, f): q = f() @@ -587,21 +660,27 @@ def test_random_quaternion_2(self, f): assert_equal(q.shape[0] == 4, True) -@pytest.mark.parametrize('f', [ - t._py_random_rotation_matrix, - t.random_rotation_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_random_rotation_matrix, + t.random_rotation_matrix, + ], +) def test_random_rotation_matrix(f): R = f() assert_allclose(np.dot(R.T, R), np.identity(4), atol=_ATOL) -@pytest.mark.parametrize('f', [ - t._py_inverse_matrix, - t.inverse_matrix, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_inverse_matrix, + t.inverse_matrix, + ], +) class TestInverseMatrix(object): - @pytest.mark.parametrize('size', list(range(1, 7))) + @pytest.mark.parametrize("size", list(range(1, 7))) def test_inverse(self, size, f): # Create a known random state to generate numbers from # these numbers will then be uncorrelated but deterministic @@ -616,10 +695,13 @@ def test_inverse_matrix(self, f): assert_allclose(M1, np.linalg.inv(M0.T)) -@pytest.mark.parametrize('f', [ - t._py_is_same_transform, - t.is_same_transform, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_is_same_transform, + t.is_same_transform, + ], +) class TestIsSameTransform(object): def test_is_same_transform_1(self, f): assert_equal(f(np.identity(4), np.identity(4)), True) @@ -628,10 +710,13 @@ def test_is_same_transform_2(self, f): assert_equal(f(t.random_rotation_matrix(), np.identity(4)), False) -@pytest.mark.parametrize('f', [ - t._py_random_vector, - t.random_vector, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_random_vector, + t.random_vector, + ], +) class TestRandomVector(object): def test_random_vector_1(self, f): v = f(1000) @@ -644,10 +729,13 @@ def test_random_vector_2(self, f): assert_equal(np.any(v0 == v1), False) -@pytest.mark.parametrize('f', [ - t._py_unit_vector, - t.unit_vector, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_unit_vector, + t.unit_vector, + ], +) class TestUnitVector(object): def test_unit_vector_1(self, f): v0 = np.array([0.2, 0.2, 0.2]) @@ -680,10 +768,13 @@ def test_unit_vector_6(self, f): assert_equal(list(f([1.0])), [1.0]) -@pytest.mark.parametrize('f', [ - t._py_vector_norm, - t.vector_norm, -]) +@pytest.mark.parametrize( + "f", + [ + t._py_vector_norm, + t.vector_norm, + ], +) class TestVectorNorm(object): def test_vector_norm_1(self, f): v = np.array([0.2, 0.2, 0.2]) @@ -743,9 +834,13 @@ def test_rotaxis_equal_vectors(): def test_rotaxis_different_vectors(): # use random coordinate system e = np.eye(3) - r = np.array([[0.69884766, 0.59804425, -0.39237102], - [0.18784672, 0.37585347, 0.90744023], - [0.69016342, -0.7078681, 0.15032367]]) + r = np.array( + [ + [0.69884766, 0.59804425, -0.39237102], + [0.18784672, 0.37585347, 0.90744023], + [0.69016342, -0.7078681, 0.15032367], + ] + ) re = np.dot(r, e) for i, j, l in permutations(range(3)): diff --git a/testsuite/MDAnalysisTests/utils/test_units.py b/testsuite/MDAnalysisTests/utils/test_units.py index 7789df13597..8121188c1c6 100644 --- a/testsuite/MDAnalysisTests/utils/test_units.py +++ b/testsuite/MDAnalysisTests/utils/test_units.py @@ -31,15 +31,17 @@ class TestUnitEncoding(object): def test_unicode(self): try: - assert_equal(units.lengthUnit_factor[u"\u212b"], 1.0) + assert_equal(units.lengthUnit_factor["\u212b"], 1.0) except KeyError: raise AssertionError("Unicode symbol for Angtrom not supported") def test_unicode_encoding_with_symbol(self): try: - assert_equal(units.lengthUnit_factor[u"Å"], 1.0) + assert_equal(units.lengthUnit_factor["Å"], 1.0) except KeyError: - raise AssertionError("UTF-8-encoded symbol for Angtrom not supported") + raise AssertionError( + "UTF-8-encoded symbol for Angtrom not supported" + ) class TestConstants(object): @@ -48,91 +50,111 @@ class TestConstants(object): # Add a reference value to this dict for every entry in # units.constants constants_reference = ( - ('N_Avogadro', 6.02214129e+23), # mol**-1 - ('elementary_charge', 1.602176565e-19), # As - ('calorie', 4.184), # J - ('Boltzmann_constant', 8.314462159e-3), # KJ (mol K)**-1 - ('Boltzman_constant', 8.314462159e-3), # remove in 2.8.0 - ('electric_constant', 5.526350e-3), # As (Angstroms Volts)**-1 + ("N_Avogadro", 6.02214129e23), # mol**-1 + ("elementary_charge", 1.602176565e-19), # As + ("calorie", 4.184), # J + ("Boltzmann_constant", 8.314462159e-3), # KJ (mol K)**-1 + ("Boltzman_constant", 8.314462159e-3), # remove in 2.8.0 + ("electric_constant", 5.526350e-3), # As (Angstroms Volts)**-1 ) - @pytest.mark.parametrize('name, value', constants_reference) + @pytest.mark.parametrize("name, value", constants_reference) def test_constant(self, name, value): assert_almost_equal(units.constants[name], value) def test_boltzmann_typo_deprecation(self): - wmsg = ("Please use 'Boltzmann_constant' henceforth. The key " - "'Boltzman_constant' was a typo and will be removed " - "in MDAnalysis 2.8.0.") + wmsg = ( + "Please use 'Boltzmann_constant' henceforth. The key " + "'Boltzman_constant' was a typo and will be removed " + "in MDAnalysis 2.8.0." + ) with pytest.warns(DeprecationWarning, match=wmsg): - units.constants['Boltzman_constant'] + units.constants["Boltzman_constant"] class TestConversion(object): @staticmethod def _assert_almost_equal_convert(value, u1, u2, ref): val = units.convert(value, u1, u2) - assert_almost_equal(val, ref, - err_msg="Conversion {0} --> {1} failed".format(u1, u2)) + assert_almost_equal( + val, ref, err_msg="Conversion {0} --> {1} failed".format(u1, u2) + ) nm = 12.34567 - A = nm * 10. - @pytest.mark.parametrize('quantity, unit1, unit2, ref', ( - (nm, 'nm', 'A', A), - (A, 'Angstrom', 'nm', nm), - )) + A = nm * 10.0 + + @pytest.mark.parametrize( + "quantity, unit1, unit2, ref", + ( + (nm, "nm", "A", A), + (A, "Angstrom", "nm", nm), + ), + ) def test_length(self, quantity, unit1, unit2, ref): self._assert_almost_equal_convert(quantity, unit1, unit2, ref) - @pytest.mark.parametrize('quantity, unit1, unit2, ref', ( - (1, 'ps', 'AKMA', 20.45482949774598), - (1, 'AKMA', 'ps', 0.04888821), - (1, 'ps', 'ms', 1e-9), - (1, 'ms', 'ps', 1e9), - (1, 'ps', 'us', 1e-6), - (1, 'us', 'ps', 1e6), - )) + @pytest.mark.parametrize( + "quantity, unit1, unit2, ref", + ( + (1, "ps", "AKMA", 20.45482949774598), + (1, "AKMA", "ps", 0.04888821), + (1, "ps", "ms", 1e-9), + (1, "ms", "ps", 1e9), + (1, "ps", "us", 1e-6), + (1, "us", "ps", 1e6), + ), + ) def test_time(self, quantity, unit1, unit2, ref): self._assert_almost_equal_convert(quantity, unit1, unit2, ref) - @pytest.mark.parametrize('quantity, unit1, unit2, ref', ( - (1, 'kcal/mol', 'kJ/mol', 4.184), - (1, 'kcal/mol', 'eV', 0.0433641), - )) + @pytest.mark.parametrize( + "quantity, unit1, unit2, ref", + ( + (1, "kcal/mol", "kJ/mol", 4.184), + (1, "kcal/mol", "eV", 0.0433641), + ), + ) def test_energy(self, quantity, unit1, unit2, ref): self._assert_almost_equal_convert(quantity, unit1, unit2, ref) - @pytest.mark.parametrize('quantity, unit1, unit2, ref', ( - (1, 'kJ/(mol*A)', 'J/m', 1.66053892103219e-11), - (2.5, 'kJ/(mol*nm)', 'kJ/(mol*A)', 0.25), - (1, 'kcal/(mol*Angstrom)', 'kJ/(mol*Angstrom)', 4.184), - )) + @pytest.mark.parametrize( + "quantity, unit1, unit2, ref", + ( + (1, "kJ/(mol*A)", "J/m", 1.66053892103219e-11), + (2.5, "kJ/(mol*nm)", "kJ/(mol*A)", 0.25), + (1, "kcal/(mol*Angstrom)", "kJ/(mol*Angstrom)", 4.184), + ), + ) def test_force(self, quantity, unit1, unit2, ref): self._assert_almost_equal_convert(quantity, unit1, unit2, ref) - @pytest.mark.parametrize('quantity, unit1, unit2, ref', ( - (1, 'A/ps', 'm/s', 1e-10/1e-12), - (1, 'A/ps', 'nm/ps', 0.1), - (1, 'A/ps', 'pm/ps', 1e2), - (1, 'A/ms', 'A/ps', 1e9), - (1, 'A/us', 'A/ps', 1e6), - (1, 'A/fs', 'A/ps', 1e-3), - (1, 'A/AKMA', 'A/ps', 1/4.888821e-2), - )) + @pytest.mark.parametrize( + "quantity, unit1, unit2, ref", + ( + (1, "A/ps", "m/s", 1e-10 / 1e-12), + (1, "A/ps", "nm/ps", 0.1), + (1, "A/ps", "pm/ps", 1e2), + (1, "A/ms", "A/ps", 1e9), + (1, "A/us", "A/ps", 1e6), + (1, "A/fs", "A/ps", 1e-3), + (1, "A/AKMA", "A/ps", 1 / 4.888821e-2), + ), + ) def test_speed(self, quantity, unit1, unit2, ref): self._assert_almost_equal_convert(quantity, unit1, unit2, ref) - @pytest.mark.parametrize('quantity, unit1, unit2', ((nm, 'Stone', 'nm'), - (nm, 'nm', 'Stone'))) + @pytest.mark.parametrize( + "quantity, unit1, unit2", ((nm, "Stone", "nm"), (nm, "nm", "Stone")) + ) def test_unit_unknown(self, quantity, unit1, unit2): with pytest.raises(ValueError): units.convert(quantity, unit1, unit2) def test_unit_unconvertable(self): nm = 12.34567 - A = nm * 10. + A = nm * 10.0 with pytest.raises(ValueError): - units.convert(A, 'A', 'ps') + units.convert(A, "A", "ps") class TestBaseUnits: @@ -141,12 +163,14 @@ class TestBaseUnits: def ref(): # This is a copy of the dictionary we expect. # We want to know if base units are added or altered. - ref = {"length": "A", - "time": "ps", - "energy": "kJ/mol", - "charge": "e", - "force": "kJ/(mol*A)", - "speed": "A/ps"} + ref = { + "length": "A", + "time": "ps", + "energy": "kJ/mol", + "charge": "e", + "force": "kJ/(mol*A)", + "speed": "A/ps", + } return ref def test_MDANALYSIS_BASE_UNITS_correct(self, ref): diff --git a/testsuite/MDAnalysisTests/visualization/test_streamlines.py b/testsuite/MDAnalysisTests/visualization/test_streamlines.py index 767903c74ad..437ccfd97e7 100644 --- a/testsuite/MDAnalysisTests/visualization/test_streamlines.py +++ b/testsuite/MDAnalysisTests/visualization/test_streamlines.py @@ -23,8 +23,7 @@ import numpy as np from numpy.testing import assert_allclose import MDAnalysis -from MDAnalysis.visualization import (streamlines, - streamlines_3D) +from MDAnalysis.visualization import streamlines, streamlines_3D from MDAnalysis.coordinates.XTC import XTCWriter from MDAnalysisTests.datafiles import Martini_membrane_gro import pytest @@ -32,50 +31,69 @@ import matplotlib.pyplot as plt import os + @pytest.fixture(scope="session") def univ(): u = MDAnalysis.Universe(Martini_membrane_gro) return u + @pytest.fixture(scope="session") def membrane_xtc(tmpdir_factory, univ): - x_delta, y_delta, z_delta = 0.5, 0.3, 0.2 - tmp_xtc = tmpdir_factory.mktemp('streamlines').join('dummy.xtc') + x_delta, y_delta, z_delta = 0.5, 0.3, 0.2 + tmp_xtc = tmpdir_factory.mktemp("streamlines").join("dummy.xtc") with XTCWriter(str(tmp_xtc), n_atoms=univ.atoms.n_atoms) as xtc_writer: for i in range(5): - univ.atoms.translate([x_delta, y_delta, z_delta]) - xtc_writer.write(univ.atoms) - x_delta += 0.1 - y_delta += 0.08 - z_delta += 0.02 + univ.atoms.translate([x_delta, y_delta, z_delta]) + xtc_writer.write(univ.atoms) + x_delta += 0.1 + y_delta += 0.08 + z_delta += 0.02 return str(tmp_xtc) + def test_streamplot_2D(membrane_xtc, univ): # regression test the data structures # generated by the 2D streamplot code - u1, v1, avg, std = streamlines.generate_streamlines(topology_file_path=Martini_membrane_gro, - trajectory_file_path=membrane_xtc, - grid_spacing=20, - MDA_selection='name PO4', - start_frame=1, - end_frame=2, - xmin=univ.atoms.positions[...,0].min(), - xmax=univ.atoms.positions[...,0].max(), - ymin=univ.atoms.positions[...,1].min(), - ymax=univ.atoms.positions[...,1].max(), - maximum_delta_magnitude=2.0, - num_cores=1) - assert_allclose(u1, np.array([[0.79999924, 0.79999924, 0.80000687, 0.79999542, 0.79998779], - [0.80000019, 0.79999542, 0.79999924, 0.79999542, 0.80001068], - [0.8000021, 0.79999924, 0.80001068, 0.80000305, 0.79999542], - [0.80000019, 0.79999542, 0.80001068, 0.80000305, 0.80000305], - [0.79999828, 0.80000305, 0.80000305, 0.80000305, 0.79999542]])) - assert_allclose(v1, np.array([[0.53999901, 0.53999996, 0.53999996, 0.53999996, 0.54000092], - [0.5399971, 0.54000092, 0.54000092, 0.54000092, 0.5399971 ], - [0.54000473, 0.54000473, 0.54000092, 0.5399971, 0.54000473], - [0.54000092, 0.53999329, 0.53999329, 0.53999329, 0.54000092], - [0.54000092, 0.53999329, 0.53999329, 0.54000092, 0.53999329]])) + u1, v1, avg, std = streamlines.generate_streamlines( + topology_file_path=Martini_membrane_gro, + trajectory_file_path=membrane_xtc, + grid_spacing=20, + MDA_selection="name PO4", + start_frame=1, + end_frame=2, + xmin=univ.atoms.positions[..., 0].min(), + xmax=univ.atoms.positions[..., 0].max(), + ymin=univ.atoms.positions[..., 1].min(), + ymax=univ.atoms.positions[..., 1].max(), + maximum_delta_magnitude=2.0, + num_cores=1, + ) + assert_allclose( + u1, + np.array( + [ + [0.79999924, 0.79999924, 0.80000687, 0.79999542, 0.79998779], + [0.80000019, 0.79999542, 0.79999924, 0.79999542, 0.80001068], + [0.8000021, 0.79999924, 0.80001068, 0.80000305, 0.79999542], + [0.80000019, 0.79999542, 0.80001068, 0.80000305, 0.80000305], + [0.79999828, 0.80000305, 0.80000305, 0.80000305, 0.79999542], + ] + ), + ) + assert_allclose( + v1, + np.array( + [ + [0.53999901, 0.53999996, 0.53999996, 0.53999996, 0.54000092], + [0.5399971, 0.54000092, 0.54000092, 0.54000092, 0.5399971], + [0.54000473, 0.54000473, 0.54000092, 0.5399971, 0.54000473], + [0.54000092, 0.53999329, 0.53999329, 0.53999329, 0.54000092], + [0.54000092, 0.53999329, 0.53999329, 0.54000092, 0.53999329], + ] + ), + ) assert avg == pytest.approx(0.965194167) assert std == pytest.approx(4.444808820e-06) @@ -84,18 +102,20 @@ def test_streamplot_2D_zero_return(membrane_xtc, univ, tmpdir): # simple roundtrip test to ensure that # zeroed arrays are returned by the 2D streamplot # code when called with an empty selection - u1, v1, avg, std = streamlines.generate_streamlines(topology_file_path=Martini_membrane_gro, - trajectory_file_path=membrane_xtc, - grid_spacing=20, - MDA_selection='name POX', - start_frame=1, - end_frame=2, - xmin=univ.atoms.positions[...,0].min(), - xmax=univ.atoms.positions[...,0].max(), - ymin=univ.atoms.positions[...,1].min(), - ymax=univ.atoms.positions[...,1].max(), - maximum_delta_magnitude=2.0, - num_cores=1) + u1, v1, avg, std = streamlines.generate_streamlines( + topology_file_path=Martini_membrane_gro, + trajectory_file_path=membrane_xtc, + grid_spacing=20, + MDA_selection="name POX", + start_frame=1, + end_frame=2, + xmin=univ.atoms.positions[..., 0].min(), + xmax=univ.atoms.positions[..., 0].max(), + ymin=univ.atoms.positions[..., 1].min(), + ymax=univ.atoms.positions[..., 1].max(), + maximum_delta_magnitude=2.0, + num_cores=1, + ) assert_allclose(u1, np.zeros((5, 5))) assert_allclose(v1, np.zeros((5, 5))) assert avg == approx(0.0) @@ -107,20 +127,22 @@ def test_streamplot_3D(membrane_xtc, univ, tmpdir): # for a roundtrip plotting test, simply # aim to check for sensible values # returned by generate_streamlines_3d - dx, dy, dz = streamlines_3D.generate_streamlines_3d(topology_file_path=Martini_membrane_gro, - trajectory_file_path=membrane_xtc, - grid_spacing=20, - MDA_selection='name PO4', - start_frame=1, - end_frame=2, - xmin=univ.atoms.positions[...,0].min(), - xmax=univ.atoms.positions[...,0].max(), - ymin=univ.atoms.positions[...,1].min(), - ymax=univ.atoms.positions[...,1].max(), - zmin=univ.atoms.positions[...,2].min(), - zmax=univ.atoms.positions[...,2].max(), - maximum_delta_magnitude=2.0, - num_cores=1) + dx, dy, dz = streamlines_3D.generate_streamlines_3d( + topology_file_path=Martini_membrane_gro, + trajectory_file_path=membrane_xtc, + grid_spacing=20, + MDA_selection="name PO4", + start_frame=1, + end_frame=2, + xmin=univ.atoms.positions[..., 0].min(), + xmax=univ.atoms.positions[..., 0].max(), + ymin=univ.atoms.positions[..., 1].min(), + ymax=univ.atoms.positions[..., 1].max(), + zmin=univ.atoms.positions[..., 2].min(), + zmax=univ.atoms.positions[..., 2].max(), + maximum_delta_magnitude=2.0, + num_cores=1, + ) assert dx.shape == (5, 5, 2) assert dy.shape == (5, 5, 2) assert dz.shape == (5, 5, 2) diff --git a/testsuite/pyproject.toml b/testsuite/pyproject.toml index 8c12d164e7f..8e9ed998022 100644 --- a/testsuite/pyproject.toml +++ b/testsuite/pyproject.toml @@ -162,6 +162,8 @@ setup\.py | MDAnalysisTests/auxiliary/.*\.py | MDAnalysisTests/lib/.*\.py | MDAnalysisTests/transformations/.*\.py +| MDAnalysisTests/topology/.*\.py +| MDAnalysisTests/analysis/.*\.py | MDAnalysisTests/guesser/.*\.py | MDAnalysisTests/converters/.*\.py | MDAnalysisTests/coordinates/.*\.py @@ -169,6 +171,9 @@ setup\.py | MDAnalysisTests/formats/.*\.py | MDAnalysisTests/parallelism/.*\.py | MDAnalysisTests/scripts/.*\.py +| MDAnalysisTests/import/.*\.py +| MDAnalysisTests/utils/.*\.py +| MDAnalysisTests/visualization/.*\.py ) ''' extend-exclude = '''