From c947d4f812d976c600f8f9982af1fe3bf6e4dc89 Mon Sep 17 00:00:00 2001 From: Rocco Meli Date: Mon, 30 Dec 2024 00:03:35 +0100 Subject: [PATCH] [fmt] core --- package/MDAnalysis/core/__init__.py | 2 +- package/MDAnalysis/core/_get_readers.py | 61 +- package/MDAnalysis/core/accessors.py | 7 +- package/MDAnalysis/core/groups.py | 1044 ++++++++----- package/MDAnalysis/core/topology.py | 127 +- package/MDAnalysis/core/topologyattrs.py | 1127 ++++++++------ package/MDAnalysis/core/topologyobjects.py | 243 +-- package/pyproject.toml | 1 + .../MDAnalysisTests/core/test_accessors.py | 22 +- .../MDAnalysisTests/core/test_accumulate.py | 154 +- testsuite/MDAnalysisTests/core/test_atom.py | 31 +- .../MDAnalysisTests/core/test_atomgroup.py | 1221 +++++++++------ .../core/test_atomselections.py | 1316 ++++++++++------- .../MDAnalysisTests/core/test_copying.py | 117 +- .../MDAnalysisTests/core/test_fragments.py | 49 +- .../core/test_group_traj_access.py | 240 +-- testsuite/MDAnalysisTests/core/test_groups.py | 942 ++++++------ .../MDAnalysisTests/core/test_index_dtype.py | 9 +- .../MDAnalysisTests/core/test_requires.py | 25 +- .../MDAnalysisTests/core/test_residue.py | 3 +- .../MDAnalysisTests/core/test_residuegroup.py | 151 +- .../MDAnalysisTests/core/test_segment.py | 8 +- .../MDAnalysisTests/core/test_segmentgroup.py | 52 +- .../MDAnalysisTests/core/test_topology.py | 294 ++-- .../core/test_topologyattrs.py | 315 ++-- .../core/test_topologyobjects.py | 348 +++-- .../MDAnalysisTests/core/test_universe.py | 901 ++++++----- testsuite/MDAnalysisTests/core/test_unwrap.py | 309 ++-- .../core/test_updating_atomgroup.py | 107 +- testsuite/MDAnalysisTests/core/test_wrap.py | 365 +++-- testsuite/MDAnalysisTests/core/util.py | 364 +++-- testsuite/pyproject.toml | 1 + 32 files changed, 5839 insertions(+), 4117 deletions(-) diff --git a/package/MDAnalysis/core/__init__.py b/package/MDAnalysis/core/__init__.py index 254cc6fead1..5247ae35057 100644 --- a/package/MDAnalysis/core/__init__.py +++ b/package/MDAnalysis/core/__init__.py @@ -50,7 +50,7 @@ and write your own Python code. """ -__all__ = ['AtomGroup', 'Selection'] +__all__ = ["AtomGroup", "Selection"] from .groups import AtomGroup from .selection import Selection diff --git a/package/MDAnalysis/core/_get_readers.py b/package/MDAnalysis/core/_get_readers.py index a1d603965e7..45d61b3cadf 100644 --- a/package/MDAnalysis/core/_get_readers.py +++ b/package/MDAnalysis/core/_get_readers.py @@ -22,9 +22,15 @@ import copy import inspect -from .. import (_READERS, _READER_HINTS, - _PARSERS, _PARSER_HINTS, - _MULTIFRAME_WRITERS, _SINGLEFRAME_WRITERS, _CONVERTERS) +from .. import ( + _READERS, + _READER_HINTS, + _PARSERS, + _PARSER_HINTS, + _MULTIFRAME_WRITERS, + _SINGLEFRAME_WRITERS, + _CONVERTERS, +) from ..lib import util @@ -80,8 +86,8 @@ def get_reader_for(filename, format=None): return format # ChainReader gets returned even if format is specified - if _READER_HINTS['CHAIN'](filename): - format = 'CHAIN' + if _READER_HINTS["CHAIN"](filename): + format = "CHAIN" # Only guess if format is not specified if format is None: for fmt_name, test in _READER_HINTS.items(): @@ -103,7 +109,9 @@ def get_reader_for(filename, format=None): " Use the format keyword to explicitly set the format: 'Universe(...,format=FORMAT)'\n" " For missing formats, raise an issue at " "https://github.com/MDAnalysis/mdanalysis/issues".format( - format, filename, _READERS.keys())) + format, filename, _READERS.keys() + ) + ) raise ValueError(errmsg) from None @@ -158,7 +166,7 @@ def get_writer_for(filename, format=None, multiframe=None): The `filename` argument has been made mandatory. """ if filename is None: - format = 'NULL' + format = "NULL" elif format is None: try: root, ext = util.get_ext(filename) @@ -172,18 +180,24 @@ def get_writer_for(filename, format=None, multiframe=None): else: format = util.check_compressed_format(root, ext) - if format == '': - raise ValueError(( - 'File format could not be guessed from {}, ' - 'resulting in empty string - ' - 'only None or valid formats are supported.' - ).format(filename)) + if format == "": + raise ValueError( + ( + "File format could not be guessed from {}, " + "resulting in empty string - " + "only None or valid formats are supported." + ).format(filename) + ) format = format.upper() if multiframe is None: # Multiframe takes priority, else use singleframe - options = copy.copy(_SINGLEFRAME_WRITERS) # do copy to avoid changing in place - options.update(_MULTIFRAME_WRITERS) # update overwrites existing entries + options = copy.copy( + _SINGLEFRAME_WRITERS + ) # do copy to avoid changing in place + options.update( + _MULTIFRAME_WRITERS + ) # update overwrites existing entries errmsg = "No trajectory or frame writer for format '{0}'" elif multiframe is True: options = _MULTIFRAME_WRITERS @@ -192,9 +206,11 @@ def get_writer_for(filename, format=None, multiframe=None): options = _SINGLEFRAME_WRITERS errmsg = "No single frame writer for format '{0}'" else: - raise ValueError("Unknown value '{0}' for multiframe," - " only True, False, None allowed" - "".format(multiframe)) + raise ValueError( + "Unknown value '{0}' for multiframe," + " only True, False, None allowed" + "".format(multiframe) + ) try: return options[format] @@ -252,10 +268,13 @@ def get_parser_for(filename, format=None): " See https://docs.mdanalysis.org/documentation_pages/topology/init.html#supported-topology-formats\n" " For missing formats, raise an issue at \n" " https://github.com/MDAnalysis/mdanalysis/issues".format( - format, _PARSERS.keys())) + format, _PARSERS.keys() + ) + ) raise ValueError(errmsg) from None else: - return _PARSERS['MINIMAL'] + return _PARSERS["MINIMAL"] + def get_converter_for(format): """Return the appropriate topology converter for ``format``. @@ -276,6 +295,6 @@ def get_converter_for(format): try: writer = _CONVERTERS[format] except KeyError: - errmsg = f'No converter found for {format} format' + errmsg = f"No converter found for {format} format" raise TypeError(errmsg) from None return writer diff --git a/package/MDAnalysis/core/accessors.py b/package/MDAnalysis/core/accessors.py index 3338d009702..1d366ec81fe 100644 --- a/package/MDAnalysis/core/accessors.py +++ b/package/MDAnalysis/core/accessors.py @@ -168,6 +168,7 @@ class ConverterWrapper: be accessed as a method with the name of the package in lowercase, i.e. `convert_to.parmed()` """ + _CONVERTERS = {} def __init__(self, ag): @@ -199,6 +200,8 @@ def __call__(self, package, *args, **kwargs): try: convert = getattr(self, package.lower()) except AttributeError: - raise ValueError(f"No {package!r} converter found. Available: " - f"{' '.join(self._CONVERTERS.keys())}") from None + raise ValueError( + f"No {package!r} converter found. Available: " + f"{' '.join(self._CONVERTERS.keys())}" + ) from None return convert(*args, **kwargs) diff --git a/package/MDAnalysis/core/groups.py b/package/MDAnalysis/core/groups.py index e8bf30ba110..40044be7c80 100644 --- a/package/MDAnalysis/core/groups.py +++ b/package/MDAnalysis/core/groups.py @@ -99,13 +99,20 @@ import contextlib import warnings -from .. import (_CONVERTERS, - _TOPOLOGY_ATTRS, _TOPOLOGY_TRANSPLANTS, _TOPOLOGY_ATTRNAMES) +from .. import ( + _CONVERTERS, + _TOPOLOGY_ATTRS, + _TOPOLOGY_TRANSPLANTS, + _TOPOLOGY_ATTRNAMES, +) from ..lib import util -from ..lib.util import (cached, warn_if_not_unique, - unique_int_1d, unique_int_1d_unsorted, - int_array_is_sorted - ) +from ..lib.util import ( + cached, + warn_if_not_unique, + unique_int_1d, + unique_int_1d_unsorted, + int_array_is_sorted, +) from ..lib import distances from ..lib import transformations from ..lib import mdamath @@ -154,7 +161,8 @@ def make_classes(): bases[cls] = GBase._subclass(is_group=True) # CBase for patching all components CBase = bases[ComponentBase] = _TopologyAttrContainer._subclass( - is_group=False) + is_group=False + ) for cls in components: bases[cls] = CBase._subclass(is_group=False) @@ -180,6 +188,7 @@ class _TopologyAttrContainer(object): The mixed subclasses become the final container classes specific to each :class:`~MDAnalysis.core.universe.Universe`. """ + @classmethod def _subclass(cls, is_group): """Factory method returning :class:`_TopologyAttrContainer` subclasses. @@ -200,16 +209,29 @@ def _subclass(cls, is_group): type A subclass of :class:`_TopologyAttrContainer`, with the same name. """ - newcls = type(cls.__name__, (cls,), {'_is_group': bool(is_group)}) + newcls = type(cls.__name__, (cls,), {"_is_group": bool(is_group)}) if is_group: newcls._SETATTR_WHITELIST = { - 'positions', 'velocities', 'forces', 'dimensions', - 'atoms', 'residue', 'residues', 'segment', 'segments', + "positions", + "velocities", + "forces", + "dimensions", + "atoms", + "residue", + "residues", + "segment", + "segments", } else: newcls._SETATTR_WHITELIST = { - 'position', 'velocity', 'force', 'dimensions', - 'atoms', 'residue', 'residues', 'segment', + "position", + "velocity", + "force", + "dimensions", + "atoms", + "residue", + "residues", + "segment", } return newcls @@ -259,12 +281,18 @@ def setter(self, values): return attr.__setitem__(self, values) if cls._is_group: - setattr(cls, attr.attrname, - property(getter, setter, None, attr.groupdoc)) + setattr( + cls, + attr.attrname, + property(getter, setter, None, attr.groupdoc), + ) cls._SETATTR_WHITELIST.add(attr.attrname) else: - setattr(cls, attr.singular, - property(getter, setter, None, attr.singledoc)) + setattr( + cls, + attr.singular, + property(getter, setter, None, attr.singledoc), + ) cls._SETATTR_WHITELIST.add(attr.singular) @classmethod @@ -285,12 +313,16 @@ def _del_prop(cls, attr): def __setattr__(self, attr, value): # `ag.this = 42` calls setattr(ag, 'this', 42) - if not (attr.startswith('_') or # 'private' allowed - attr in self._SETATTR_WHITELIST or # known attributes allowed - hasattr(self, attr)): # preexisting (eg properties) allowed + if not ( + attr.startswith("_") # 'private' allowed + or attr in self._SETATTR_WHITELIST # known attributes allowed + or hasattr(self, attr) + ): # preexisting (eg properties) allowed raise AttributeError( "Cannot set arbitrary attributes to a {}".format( - 'Group' if self._is_group else 'Component')) + "Group" if self._is_group else "Component" + ) + ) # if it is, we allow the setattr to proceed by deferring to the super # behaviour (ie do it) super(_TopologyAttrContainer, self).__setattr__(attr, value) @@ -310,6 +342,7 @@ class _MutableBase(object): in cache retrieval. """ + def __new__(cls, *args, **kwargs): # This pre-initialization wrapper must be pretty generic to # allow for different initialization schemes of the possible classes. @@ -323,32 +356,37 @@ def __new__(cls, *args, **kwargs): u = args[0][0].universe except (TypeError, IndexError, AttributeError): from .universe import Universe + # Let's be generic and get the first argument that's either a # Universe, a Group, or a Component, and go from there. # This is where the UpdatingAtomGroup args get matched. - for arg in args+tuple(kwargs.values()): - if isinstance(arg, (Universe, GroupBase, - ComponentBase)): + for arg in args + tuple(kwargs.values()): + if isinstance(arg, (Universe, GroupBase, ComponentBase)): u = arg.universe break else: errmsg = ( f"No universe, or universe-containing object " - f"passed to the initialization of {cls.__name__}") + f"passed to the initialization of {cls.__name__}" + ) raise TypeError(errmsg) from None try: return object.__new__(u._classes[cls]) except KeyError: # Cache miss. Let's find which kind of class this is and merge. try: - parent_cls = next(u._class_bases[parent] - for parent in cls.mro() - if parent in u._class_bases) + parent_cls = next( + u._class_bases[parent] + for parent in cls.mro() + if parent in u._class_bases + ) except StopIteration: - errmsg = (f"Attempted to instantiate class '{cls.__name__}' " - f"but none of its parents are known to the universe." - f" Currently possible parent classes are: " - f"{str(sorted(u._class_bases.keys()))}") + errmsg = ( + f"Attempted to instantiate class '{cls.__name__}' " + f"but none of its parents are known to the universe." + f" Currently possible parent classes are: " + f"{str(sorted(u._class_bases.keys()))}" + ) raise TypeError(errmsg) from None newcls = u._classes[cls] = parent_cls._mix(cls) return object.__new__(newcls) @@ -360,37 +398,46 @@ def __getattr__(self, attr): topattr, meth, clstype = _TOPOLOGY_TRANSPLANTS[attr] if isinstance(meth, property): attrname = attr - attrtype = 'property' + attrtype = "property" else: - attrname = attr + '()' - attrtype = 'method' + attrname = attr + "()" + attrtype = "method" # property of wrong group/component if not isinstance(self, clstype): - mname = 'property' if isinstance(meth, property) else 'method' - err = '{attr} is a {method} of {clstype}, not {selfcls}' + mname = "property" if isinstance(meth, property) else "method" + err = "{attr} is a {method} of {clstype}, not {selfcls}" clsname = clstype.__name__ - if clsname == 'GroupBase': - clsname = selfcls + 'Group' - raise AttributeError(err.format(attr=attrname, - method=attrtype, - clstype=clsname, - selfcls=selfcls)) + if clsname == "GroupBase": + clsname = selfcls + "Group" + raise AttributeError( + err.format( + attr=attrname, + method=attrtype, + clstype=clsname, + selfcls=selfcls, + ) + ) # missing required topologyattr else: - err = ('{selfcls}.{attrname} not available; ' - 'this requires {topattr}') - raise NoDataError(err.format(selfcls=selfcls, - attrname=attrname, - topattr=topattr)) + err = ( + "{selfcls}.{attrname} not available; " + "this requires {topattr}" + ) + raise NoDataError( + err.format( + selfcls=selfcls, attrname=attrname, topattr=topattr + ) + ) else: - clean = attr.lower().replace('_', '') - err = '{selfcls} has no attribute {attr}. '.format(selfcls=selfcls, - attr=attr) + clean = attr.lower().replace("_", "") + err = "{selfcls} has no attribute {attr}. ".format( + selfcls=selfcls, attr=attr + ) if clean in _TOPOLOGY_ATTRNAMES: match = _TOPOLOGY_ATTRNAMES[clean] - err += 'Did you mean {match}?'.format(match=match) + err += "Did you mean {match}?".format(match=match) raise AttributeError(err) def get_connections(self, typename, outside=True): @@ -430,9 +477,8 @@ def get_connections(self, typename, outside=True): class _ImmutableBase(object): - """Class used to shortcut :meth:`__new__` to :meth:`object.__new__`. + """Class used to shortcut :meth:`__new__` to :meth:`object.__new__`.""" - """ # When mixed via _TopologyAttrContainer._mix this class has MRO priority. # Setting __new__ like this will avoid having to go through the # cache lookup if the class is reused (as in ag._derived_class(...)). @@ -441,26 +487,32 @@ class _ImmutableBase(object): def _pbc_to_wrap(function): """Raises deprecation warning if 'pbc' is set and assigns value to 'wrap'""" + @functools.wraps(function) def wrapped(group, *args, **kwargs): - if kwargs.get('pbc', None) is not None: - warnings.warn("The 'pbc' kwarg has been deprecated and will be " - "removed in version 3.0., " - "please use 'wrap' instead", - DeprecationWarning) - kwargs['wrap'] = kwargs.pop('pbc') + if kwargs.get("pbc", None) is not None: + warnings.warn( + "The 'pbc' kwarg has been deprecated and will be " + "removed in version 3.0., " + "please use 'wrap' instead", + DeprecationWarning, + ) + kwargs["wrap"] = kwargs.pop("pbc") return function(group, *args, **kwargs) + return wrapped def check_wrap_and_unwrap(function): """Raises ValueError when both 'wrap' and 'unwrap' are set to True""" + @functools.wraps(function) def wrapped(group, *args, **kwargs): - if kwargs.get('wrap') and kwargs.get('unwrap'): + if kwargs.get("wrap") and kwargs.get("unwrap"): raise ValueError("both 'wrap' and 'unwrap' can not be set to true") return function(group, *args, **kwargs) + return wrapped @@ -468,18 +520,25 @@ def _only_same_level(function): @functools.wraps(function) def wrapped(self, other): if not isinstance(other, (ComponentBase, GroupBase)): # sanity check - raise TypeError("Can't perform '{}' between objects:" - " '{}' and '{}'".format( - function.__name__, - type(self).__name__, - type(other).__name__)) + raise TypeError( + "Can't perform '{}' between objects:" + " '{}' and '{}'".format( + function.__name__, + type(self).__name__, + type(other).__name__, + ) + ) if self.level != other.level: - raise TypeError("Can't perform '{}' on different level objects" - "".format(function.__name__)) + raise TypeError( + "Can't perform '{}' on different level objects" + "".format(function.__name__) + ) if self.universe is not other.universe: raise ValueError( - "Can't operate on objects from different Universes") + "Can't operate on objects from different Universes" + ) return function(self, other) + return wrapped @@ -560,19 +619,22 @@ def __init__(self, *args): else: # current/new init method ix, u = args - except (AttributeError, # couldn't find ix/universe - TypeError): # couldn't iterate the object we got + except ( + AttributeError, # couldn't find ix/universe + TypeError, + ): # couldn't iterate the object we got errmsg = ( "Can only initialise a Group from an iterable of Atom/Residue/" "Segment objects eg: AtomGroup([Atom1, Atom2, Atom3]) " "or an iterable of indices and a Universe reference " - "eg: AtomGroup([0, 5, 7, 8], u).") + "eg: AtomGroup([0, 5, 7, 8], u)." + ) raise TypeError(errmsg) from None # indices for the objects I hold ix = np.asarray(ix, dtype=np.intp) if ix.ndim > 1: - raise IndexError('Group index must be 1d') + raise IndexError("Group index must be 1d") self._ix = ix self._u = u self._cache = dict() @@ -592,7 +654,7 @@ def __getitem__(self, item): # it can be sliced by all of these already, # so just return ourselves sliced by the item if item is None: - raise TypeError('None cannot be used to index a group.') + raise TypeError("None cannot be used to index a group.") elif isinstance(item, numbers.Integral): return self.level.singular(self.ix[item], self.universe) else: @@ -611,30 +673,35 @@ def __getattr__(self, attr): if attr in _TOPOLOGY_ATTRS: cls = _TOPOLOGY_ATTRS[attr] if attr == cls.singular and attr != cls.attrname: - err = ('{selfcls} has no attribute {attr}. ' - 'Do you mean {plural}?') - raise AttributeError(err.format(selfcls=selfcls, attr=attr, - plural=cls.attrname)) + err = ( + "{selfcls} has no attribute {attr}. " + "Do you mean {plural}?" + ) + raise AttributeError( + err.format(selfcls=selfcls, attr=attr, plural=cls.attrname) + ) else: - err = 'This Universe does not contain {singular} information' + err = "This Universe does not contain {singular} information" raise NoDataError(err.format(singular=cls.singular)) else: return super(GroupBase, self).__getattr__(attr) def __repr__(self): name = self.level.name - return ("<{}Group with {} {}{}>" - "".format(name.capitalize(), len(self), name, - "s"[len(self) == 1:])) # Shorthand for a conditional plural 's'. + return "<{}Group with {} {}{}>" "".format( + name.capitalize(), len(self), name, "s"[len(self) == 1 :] + ) # Shorthand for a conditional plural 's'. def __str__(self): name = self.level.name if len(self) <= 10: - return '<{}Group {}>'.format(name.capitalize(), repr(list(self))) + return "<{}Group {}>".format(name.capitalize(), repr(list(self))) else: - return '<{}Group {}, ..., {}>'.format(name.capitalize(), - repr(list(self)[:3])[:-1], - repr(list(self)[-3:])[1:]) + return "<{}Group {}, ..., {}>".format( + name.capitalize(), + repr(list(self)[:3])[:-1], + repr(list(self)[-3:])[1:], + ) def __add__(self, other): """Concatenate the Group with another Group or Component of the same @@ -671,9 +738,12 @@ def __radd__(self, other): if other == 0: return self._derived_class(self.ix, self.universe) else: - raise TypeError("unsupported operand type(s) for +:" - " '{}' and '{}'".format(type(self).__name__, - type(other).__name__)) + raise TypeError( + "unsupported operand type(s) for +:" + " '{}' and '{}'".format( + type(self).__name__, type(other).__name__ + ) + ) def __sub__(self, other): return self.difference(other) @@ -754,22 +824,22 @@ def dimensions(self, dimensions): self.universe.trajectory.ts.dimensions = dimensions @property - @cached('sorted_unique') + @cached("sorted_unique") def sorted_unique(self): return self.asunique(sorted=True) @property - @cached('unsorted_unique') + @cached("unsorted_unique") def unsorted_unique(self): return self.asunique(sorted=False) @property - @cached('issorted') + @cached("issorted") def issorted(self): return int_array_is_sorted(self.ix) @property - @cached('isunique') + @cached("isunique") def isunique(self): """Boolean indicating whether all components of the group are unique, i.e., the group contains no duplicates. @@ -809,34 +879,35 @@ def isunique(self): def _asunique(self, group, sorted=False, set_mask=False): try: - name = 'sorted_unique' if sorted else 'unsorted_unique' + name = "sorted_unique" if sorted else "unsorted_unique" return self._cache[name] except KeyError: pass if self.isunique: if not sorted: - self._cache['unsorted_unique'] = self + self._cache["unsorted_unique"] = self return self if self.issorted: - self._cache['unsorted_unique'] = self - self._cache['sorted_unique'] = self + self._cache["unsorted_unique"] = self + self._cache["sorted_unique"] = self return self if sorted: if set_mask: unique_ix, restore_mask = np.unique( - self.ix, return_inverse=True) + self.ix, return_inverse=True + ) self._unique_restore_mask = restore_mask else: unique_ix = unique_int_1d(self.ix) _unique = group[unique_ix] - _unique._cache['isunique'] = True - _unique._cache['issorted'] = True - _unique._cache['sorted_unique'] = _unique - _unique._cache['unsorted_unique'] = _unique - self._cache['sorted_unique'] = _unique + _unique._cache["isunique"] = True + _unique._cache["issorted"] = True + _unique._cache["sorted_unique"] = _unique + _unique._cache["unsorted_unique"] = _unique + self._cache["sorted_unique"] = _unique return _unique indices = unique_int_1d_unsorted(self.ix) @@ -848,45 +919,51 @@ def _asunique(self, group, sorted=False, set_mask=False): self._unique_restore_mask = mask issorted = int_array_is_sorted(indices) - if issorted and 'sorted_unique' in self._cache: - self._cache['unsorted_unique'] = self.sorted_unique + if issorted and "sorted_unique" in self._cache: + self._cache["unsorted_unique"] = self.sorted_unique return self.sorted_unique _unique = group[indices] - _unique._cache['isunique'] = True - _unique._cache['issorted'] = issorted - _unique._cache['unsorted_unique'] = _unique - self._cache['unsorted_unique'] = _unique + _unique._cache["isunique"] = True + _unique._cache["issorted"] = issorted + _unique._cache["unsorted_unique"] = _unique + self._cache["unsorted_unique"] = _unique if issorted: - self._cache['sorted_unique'] = _unique - _unique._cache['sorted_unique'] = _unique + self._cache["sorted_unique"] = _unique + _unique._cache["sorted_unique"] = _unique return _unique def _get_compound_indices(self, compound): - if compound == 'residues': + if compound == "residues": compound_indices = self.atoms.resindices - elif compound == 'segments': + elif compound == "segments": compound_indices = self.atoms.segindices - elif compound == 'molecules': + elif compound == "molecules": try: compound_indices = self.atoms.molnums except AttributeError: - errmsg = ("Cannot use compound='molecules': No molecule " - "information in topology.") + errmsg = ( + "Cannot use compound='molecules': No molecule " + "information in topology." + ) raise NoDataError(errmsg) from None - elif compound == 'fragments': + elif compound == "fragments": try: compound_indices = self.atoms.fragindices except NoDataError: - errmsg = ("Cannot use compound='fragments': No bond " - "information in topology.") + errmsg = ( + "Cannot use compound='fragments': No bond " + "information in topology." + ) raise NoDataError(errmsg) from None - elif compound == 'group': + elif compound == "group": raise ValueError("This method does not accept compound='group'") else: - raise ValueError("Unrecognized compound definition: {}\nPlease use" - " one of 'residues', 'segments', 'molecules'," - " or 'fragments'.".format(compound)) + raise ValueError( + "Unrecognized compound definition: {}\nPlease use" + " one of 'residues', 'segments', 'molecules'," + " or 'fragments'.".format(compound) + ) return compound_indices def _split_by_compound_indices(self, compound, stable_sort=False): @@ -960,7 +1037,7 @@ def _split_by_compound_indices(self, compound, stable_sort=False): # stable sort ensures reproducibility, especially concerning who # gets to be a compound's atom[0] and be a reference for unwrap. if stable_sort: - sort_indices = np.argsort(compound_indices, kind='stable') + sort_indices = np.argsort(compound_indices, kind="stable") else: # Quicksort sort_indices = np.argsort(compound_indices) @@ -972,18 +1049,24 @@ def _split_by_compound_indices(self, compound, stable_sort=False): for compound_size in unique_compound_sizes: compound_masks.append(compound_sizes == compound_size) if needs_sorting: - atom_masks.append(sort_indices[size_per_atom == compound_size] - .reshape(-1, compound_size)) + atom_masks.append( + sort_indices[size_per_atom == compound_size].reshape( + -1, compound_size + ) + ) else: - atom_masks.append(np.where(size_per_atom == compound_size)[0] - .reshape(-1, compound_size)) + atom_masks.append( + np.where(size_per_atom == compound_size)[0].reshape( + -1, compound_size + ) + ) return atom_masks, compound_masks, len(compound_sizes) @warn_if_not_unique @_pbc_to_wrap @check_wrap_and_unwrap - def center(self, weights, wrap=False, unwrap=False, compound='group'): + def center(self, weights, wrap=False, unwrap=False, compound="group"): """Weighted center of (compounds of) the group Computes the weighted center of :class:`Atoms` in the group. @@ -1086,12 +1169,13 @@ def center(self, weights, wrap=False, unwrap=False, compound='group'): dtype = np.float64 comp = compound.lower() - if comp == 'group': + if comp == "group": if wrap: coords = atoms.pack_into_box(inplace=False) elif unwrap: coords = atoms.unwrap( - compound=comp, reference=None, inplace=False) + compound=comp, reference=None, inplace=False + ) else: coords = atoms.positions # If there's no atom, return its (empty) coordinates unchanged. @@ -1103,15 +1187,17 @@ def center(self, weights, wrap=False, unwrap=False, compound='group'): return coords.mean(axis=0) # promote weights to dtype if required: weights = weights.astype(dtype, copy=False) - return np.einsum('ij,ij->j',coords,weights[:, None]) / weights.sum() + return ( + np.einsum("ij,ij->j", coords, weights[:, None]) / weights.sum() + ) # When compound split caching gets implemented it will be clever to # preempt at this point whether or not stable sorting will be needed # later for unwrap (so that we don't split now with non-stable sort, # only to have to re-split with stable sort if unwrap is requested). - (atom_masks, - compound_masks, - n_compounds) = self._split_by_compound_indices(comp) + (atom_masks, compound_masks, n_compounds) = ( + self._split_by_compound_indices(comp) + ) # Unwrap Atoms if unwrap: @@ -1132,7 +1218,9 @@ def center(self, weights, wrap=False, unwrap=False, compound='group'): _centers = _coords.mean(axis=1) else: _weights = weights[atom_mask] - _centers = np.einsum('ijk,ijk->ik',_coords,_weights[:, :, None]) + _centers = np.einsum( + "ijk,ijk->ik", _coords, _weights[:, :, None] + ) _centers /= _weights.sum(axis=1)[:, None] centers[compound_mask] = _centers if wrap: @@ -1142,7 +1230,7 @@ def center(self, weights, wrap=False, unwrap=False, compound='group'): @warn_if_not_unique @_pbc_to_wrap @check_wrap_and_unwrap - def center_of_geometry(self, wrap=False, unwrap=False, compound='group'): + def center_of_geometry(self, wrap=False, unwrap=False, compound="group"): r"""Center of geometry of (compounds of) the group .. math:: @@ -1199,7 +1287,7 @@ def center_of_geometry(self, wrap=False, unwrap=False, compound='group'): centroid = center_of_geometry @warn_if_not_unique - def accumulate(self, attribute, function=np.sum, compound='group'): + def accumulate(self, attribute, function=np.sum, compound="group"): r"""Accumulates the attribute associated with (compounds of) the group. Accumulates the attribute of :class:`Atoms` in the group. @@ -1307,18 +1395,20 @@ def accumulate(self, attribute, function=np.sum, compound='group'): else: attribute_values = np.asarray(attribute) if len(attribute_values) != len(atoms): - raise ValueError("The input array length ({}) does not match " - "the number of atoms ({}) in the group." - "".format(len(attribute_values), len(atoms))) + raise ValueError( + "The input array length ({}) does not match " + "the number of atoms ({}) in the group." + "".format(len(attribute_values), len(atoms)) + ) comp = compound.lower() - if comp == 'group': + if comp == "group": return function(attribute_values, axis=0) - (atom_masks, - compound_masks, - n_compounds) = self._split_by_compound_indices(comp) + (atom_masks, compound_masks, n_compounds) = ( + self._split_by_compound_indices(comp) + ) higher_dims = list(attribute_values.shape[1:]) @@ -1727,15 +1817,19 @@ def wrap(self, compound="atoms", center="com", box=None, inplace=True): if box is None: box = self.dimensions if box is None: - raise ValueError("No dimensions information in Universe. " - " Either use the 'box' argument or" - " set the '.dimensions' attribute") + raise ValueError( + "No dimensions information in Universe. " + " Either use the 'box' argument or" + " set the '.dimensions' attribute" + ) else: box = np.asarray(box, dtype=np.float32) if not np.all(box > 0.0) or box.shape != (6,): - raise ValueError("Invalid box: Box has invalid shape or not all " - "box dimensions are positive. You can specify a " - "valid box using the 'box' argument.") + raise ValueError( + "Invalid box: Box has invalid shape or not all " + "box dimensions are positive. You can specify a " + "valid box using the 'box' argument." + ) # no matter what kind of group we have, we need to work on its (unique) # atoms: @@ -1746,12 +1840,20 @@ def wrap(self, compound="atoms", center="com", box=None, inplace=True): atoms = _atoms comp = compound.lower() - if comp not in ('atoms', 'group', 'segments', 'residues', 'molecules', - 'fragments'): - raise ValueError("Unrecognized compound definition '{}'. " - "Please use one of 'atoms', 'group', 'segments', " - "'residues', 'molecules', or 'fragments'." - "".format(compound)) + if comp not in ( + "atoms", + "group", + "segments", + "residues", + "molecules", + "fragments", + ): + raise ValueError( + "Unrecognized compound definition '{}'. " + "Please use one of 'atoms', 'group', 'segments', " + "'residues', 'molecules', or 'fragments'." + "".format(compound) + ) if len(atoms) == 0: return np.zeros((0, 3), dtype=np.float32) @@ -1760,32 +1862,38 @@ def wrap(self, compound="atoms", center="com", box=None, inplace=True): positions = distances.apply_PBC(atoms.positions, box) else: ctr = center.lower() - if ctr == 'com': + if ctr == "com": # Don't use hasattr(self, 'masses') because that's incredibly # slow for ResidueGroups or SegmentGroups - if not hasattr(self._u._topology, 'masses'): - raise NoDataError("Cannot perform wrap with center='com', " - "this requires masses.") - elif ctr != 'cog': - raise ValueError("Unrecognized center definition '{}'. Please " - "use one of 'com' or 'cog'.".format(center)) + if not hasattr(self._u._topology, "masses"): + raise NoDataError( + "Cannot perform wrap with center='com', " + "this requires masses." + ) + elif ctr != "cog": + raise ValueError( + "Unrecognized center definition '{}'. Please " + "use one of 'com' or 'cog'.".format(center) + ) positions = atoms.positions # compute and apply required shift: - if ctr == 'com': + if ctr == "com": ctrpos = atoms.center_of_mass(wrap=False, compound=comp) if np.any(np.isnan(ctrpos)): - specifier = 'the' if comp == 'group' else 'one of the' - raise ValueError("Cannot use compound='{0}' with " - "center='com' because {1} {0}\'s total " - "mass is zero.".format(comp, specifier)) + specifier = "the" if comp == "group" else "one of the" + raise ValueError( + "Cannot use compound='{0}' with " + "center='com' because {1} {0}'s total " + "mass is zero.".format(comp, specifier) + ) else: # ctr == 'cog' ctrpos = atoms.center_of_geometry(wrap=False, compound=comp) ctrpos = ctrpos.astype(np.float32, copy=False) target = distances.apply_PBC(ctrpos, box) shifts = target - ctrpos - if comp == 'group': + if comp == "group": positions += shifts else: @@ -1805,7 +1913,7 @@ def wrap(self, compound="atoms", center="com", box=None, inplace=True): positions = positions[restore_mask] return positions - def unwrap(self, compound='fragments', reference='com', inplace=True): + def unwrap(self, compound="fragments", reference="com", inplace=True): r"""Move atoms of this group so that bonds within the group's compounds aren't split across periodic boundaries. @@ -1878,7 +1986,7 @@ def unwrap(self, compound='fragments', reference='com', inplace=True): """ atoms = self.atoms # bail out early if no bonds in topology: - if not hasattr(atoms, 'bonds'): + if not hasattr(atoms, "bonds"): raise NoDataError( f"{self.__class__.__name__}.unwrap() not available; this AtomGroup lacks defined bonds. " "To resolve this, you can either:\n" @@ -1891,16 +1999,20 @@ def unwrap(self, compound='fragments', reference='com', inplace=True): if reference is not None: try: reference = reference.lower() - if reference not in ('cog', 'com'): + if reference not in ("cog", "com"): raise ValueError except (AttributeError, ValueError): - raise ValueError("Unrecognized reference '{}'. Please use one " - "of 'com', 'cog', or None.".format(reference)) + raise ValueError( + "Unrecognized reference '{}'. Please use one " + "of 'com', 'cog', or None.".format(reference) + ) # Don't use hasattr(self, 'masses') because that's incredibly slow for # ResidueGroups or SegmentGroups - if reference == 'com' and not hasattr(unique_atoms, 'masses'): - raise NoDataError("Cannot perform unwrap with reference='com', " - "this requires masses.") + if reference == "com" and not hasattr(unique_atoms, "masses"): + raise NoDataError( + "Cannot perform unwrap with reference='com', " + "this requires masses." + ) # Sanity checking of the compound parameter is done downstream in # _split_by_compound_indices @@ -1911,18 +2023,20 @@ def unwrap(self, compound='fragments', reference='com', inplace=True): # case below. Both code paths could be merged, but 'group' can be done # unidimensionally whereas the general multi-compound case involves # more indexing and is therefore slower. Leaving separate for now. - if comp == 'group': + if comp == "group": positions = mdamath.make_whole(unique_atoms, inplace=False) # Apply reference shift if required: if reference is not None and len(positions) > 0: - if reference == 'com': + if reference == "com": masses = unique_atoms.masses total_mass = masses.sum() if np.isclose(total_mass, 0.0): - raise ValueError("Cannot perform unwrap with " - "reference='com' because the total " - "mass of the group is zero.") - refpos = np.einsum('ij,ij->j',positions,masses[:, None]) + raise ValueError( + "Cannot perform unwrap with " + "reference='com' because the total " + "mass of the group is zero." + ) + refpos = np.einsum("ij,ij->j", positions, masses[:, None]) refpos /= total_mass else: # reference == 'cog' refpos = positions.mean(axis=0) @@ -1934,32 +2048,40 @@ def unwrap(self, compound='fragments', reference='com', inplace=True): # When unwrapping and not shifting with a cog/com reference we # need to make sure that the first atom of each compound is stable # regarding sorting. - atom_masks = unique_atoms._split_by_compound_indices(comp, - stable_sort=reference is None)[0] + atom_masks = unique_atoms._split_by_compound_indices( + comp, stable_sort=reference is None + )[0] positions = unique_atoms.positions for atom_mask in atom_masks: for mask in atom_mask: - positions[mask] = mdamath.make_whole(unique_atoms[mask], - inplace=False) + positions[mask] = mdamath.make_whole( + unique_atoms[mask], inplace=False + ) # Apply reference shift if required: if reference is not None: - if reference == 'com': + if reference == "com": masses = unique_atoms.masses[atom_mask] total_mass = masses.sum(axis=1) if np.any(np.isclose(total_mass, 0.0)): - raise ValueError("Cannot perform unwrap with " - "reference='com' because the " - "total mass of at least one of " - "the {} is zero.".format(comp)) - refpos = np.einsum('ijk,ijk->ik',positions[atom_mask], - masses[:, :, None]) + raise ValueError( + "Cannot perform unwrap with " + "reference='com' because the " + "total mass of at least one of " + "the {} is zero.".format(comp) + ) + refpos = np.einsum( + "ijk,ijk->ik", + positions[atom_mask], + masses[:, :, None], + ) refpos /= total_mass[:, None] else: # reference == 'cog' refpos = positions[atom_mask].mean(axis=1) refpos = refpos.astype(np.float32, copy=False) target = distances.apply_PBC(refpos, self.dimensions) - positions[atom_mask] += (target[:, None, :] - - refpos[:, None, :]) + positions[atom_mask] += ( + target[:, None, :] - refpos[:, None, :] + ) if inplace: unique_atoms.positions = positions if not atoms.isunique: @@ -1979,21 +2101,21 @@ def copy(self): def _set_unique_caches_from(self, other): # Try to fill the copied group's uniqueness caches: try: - self._cache['isunique'] = other._cache['isunique'] + self._cache["isunique"] = other._cache["isunique"] except KeyError: pass else: if self.isunique: - self._cache['unsorted_unique'] = self + self._cache["unsorted_unique"] = self try: - self._cache['issorted'] = other._cache['issorted'] + self._cache["issorted"] = other._cache["issorted"] except KeyError: pass else: if self.issorted: - if self._cache.get('isunique'): - self._cache['sorted_unique'] = self + if self._cache.get("isunique"): + self._cache["sorted_unique"] = self def groupby(self, topattrs): """Group together items in this group according to values of *topattr* @@ -2061,7 +2183,7 @@ def groupby(self, topattrs): if isinstance(topattrs, (str, bytes)): attr = topattrs if isinstance(topattrs, bytes): - attr = topattrs.decode('utf-8') + attr = topattrs.decode("utf-8") ta = getattr(self, attr) return {i: self[ta == i] for i in set(ta)} @@ -2119,8 +2241,9 @@ def concatenate(self, other): .. versionadded:: 0.16.0 """ o_ix = other.ix_array - return self._derived_class(np.concatenate([self.ix, o_ix]), - self.universe) + return self._derived_class( + np.concatenate([self.ix, o_ix]), self.universe + ) @_only_same_level def union(self, other): @@ -2207,7 +2330,9 @@ def intersection(self, other): .. versionadded:: 0.16 """ o_ix = other.ix_array - return self._derived_class(np.intersect1d(self.ix, o_ix), self.universe) + return self._derived_class( + np.intersect1d(self.ix, o_ix), self.universe + ) @_only_same_level def subtract(self, other): @@ -2651,10 +2776,10 @@ class AtomGroup(GroupBase): def __getattr__(self, attr): # special-case timestep info - if attr in ('velocities', 'forces'): - raise NoDataError('This Timestep has no ' + attr) - elif attr == 'positions': - raise NoDataError('This Universe has no coordinates') + if attr in ("velocities", "forces"): + raise NoDataError("This Timestep has no " + attr) + elif attr == "positions": + raise NoDataError("This Universe has no coordinates") return super(AtomGroup, self).__getattr__(attr) def __reduce__(self): @@ -2691,10 +2816,10 @@ def residues(self): :class:`Residues` present in the :class:`AtomGroup`. """ rg = self.universe.residues[unique_int_1d(self.resindices)] - rg._cache['isunique'] = True - rg._cache['issorted'] = True - rg._cache['sorted_unique'] = rg - rg._cache['unsorted_unique'] = rg + rg._cache["isunique"] = True + rg._cache["issorted"] = True + rg._cache["sorted_unique"] = rg + rg._cache["unsorted_unique"] = rg return rg @residues.setter @@ -2708,14 +2833,20 @@ def residues(self, new): try: r_ix = [r.resindex for r in new] except AttributeError: - errmsg = ("Can only set AtomGroup residues to Residue " - "or ResidueGroup not {}".format( - ', '.join(type(r) for r in new - if not isinstance(r, Residue)))) + errmsg = ( + "Can only set AtomGroup residues to Residue " + "or ResidueGroup not {}".format( + ", ".join( + type(r) for r in new if not isinstance(r, Residue) + ) + ) + ) raise TypeError(errmsg) from None if not isinstance(r_ix, itertools.cycle) and len(r_ix) != len(self): - raise ValueError("Incorrect size: {} for AtomGroup of size: {}" - "".format(len(new), len(self))) + raise ValueError( + "Incorrect size: {} for AtomGroup of size: {}" + "".format(len(new), len(self)) + ) # Optimisation TODO: # This currently rebuilds the tt len(self) times # Ideally all changes would happen and *afterwards* tables are built @@ -2740,16 +2871,18 @@ def segments(self): :class:`AtomGroup`. """ sg = self.universe.segments[unique_int_1d(self.segindices)] - sg._cache['isunique'] = True - sg._cache['issorted'] = True - sg._cache['sorted_unique'] = sg - sg._cache['unsorted_unique'] = sg + sg._cache["isunique"] = True + sg._cache["issorted"] = True + sg._cache["sorted_unique"] = sg + sg._cache["unsorted_unique"] = sg return sg @segments.setter def segments(self, new): - raise NotImplementedError("Cannot assign Segments to AtomGroup. " - "Segments are assigned to Residues") + raise NotImplementedError( + "Cannot assign Segments to AtomGroup. " + "Segments are assigned to Residues" + ) @property def n_segments(self): @@ -2760,7 +2893,7 @@ def n_segments(self): return len(self.segments) @property - @cached('unique_restore_mask') + @cached("unique_restore_mask") def _unique_restore_mask(self): # The _unique_restore_mask property's cache is populated whenever the # AtomGroup.unique property of a *non-unique* AtomGroup is accessed. @@ -2770,18 +2903,24 @@ def _unique_restore_mask(self): # then be replaced by the __getattr__() error message. To prevent the # message from being overridden, we raise a RuntimeError instead. if self.isunique: - msg = ("{0}._unique_restore_mask is not available if the {0} is " - "unique. ".format(self.__class__.__name__)) + msg = ( + "{0}._unique_restore_mask is not available if the {0} is " + "unique. ".format(self.__class__.__name__) + ) else: - msg = ("{0}._unique_restore_mask is only available after " - "accessing {0}.unique. ".format(self.__class__.__name__)) - msg += ("If you see this error message in an unmodified release " - "version of MDAnalysis, this is almost certainly a bug!") + msg = ( + "{0}._unique_restore_mask is only available after " + "accessing {0}.unique. ".format(self.__class__.__name__) + ) + msg += ( + "If you see this error message in an unmodified release " + "version of MDAnalysis, this is almost certainly a bug!" + ) raise RuntimeError(msg) @_unique_restore_mask.setter def _unique_restore_mask(self, mask): - self._cache['unique_restore_mask'] = mask + self._cache["unique_restore_mask"] = mask @property def unique(self): @@ -2822,10 +2961,10 @@ def unique(self): This function now always returns a copy. """ group = self.sorted_unique[:] - group._cache['isunique'] = True - group._cache['issorted'] = True - group._cache['sorted_unique'] = group - group._cache['unsorted_unique'] = group + group._cache["isunique"] = True + group._cache["issorted"] = True + group._cache["sorted_unique"] = group + group._cache["unsorted_unique"] = group return group def asunique(self, sorted=False): @@ -2871,8 +3010,9 @@ def asunique(self, sorted=False): .. versionadded:: 2.0.0 """ - return self._asunique(sorted=sorted, group=self.universe.atoms, - set_mask=True) + return self._asunique( + sorted=sorted, group=self.universe.atoms, set_mask=True + ) @property def positions(self): @@ -2992,9 +3132,19 @@ def ts(self): # As with universe.select_atoms, needing to fish out specific kwargs # (namely, 'updating') doesn't allow a very clean signature. - def select_atoms(self, sel, *othersel, periodic=True, rtol=1e-05, - atol=1e-08, updating=False, sorted=True, - rdkit_kwargs=None, smarts_kwargs=None, **selgroups): + def select_atoms( + self, + sel, + *othersel, + periodic=True, + rtol=1e-05, + atol=1e-08, + updating=False, + sorted=True, + rdkit_kwargs=None, + smarts_kwargs=None, + **selgroups, + ): """Select atoms from within this Group using a selection string. Returns an :class:`AtomGroup` sorted according to their index in the @@ -3208,7 +3358,7 @@ def select_atoms(self, sel, *othersel, periodic=True, rtol=1e-05, universe = mda.Universe(PSF, DCD) guessed_elements = guess_types(universe.atoms.names) universe.add_TopologyAttr('elements', guessed_elements) - + .. doctest:: AtomGroup.select_atoms.smarts >>> universe.select_atoms("smarts C", smarts_kwargs={"maxMatches": 100}) @@ -3383,31 +3533,46 @@ def select_atoms(self, sel, *othersel, periodic=True, rtol=1e-05, """ if not sel: - warnings.warn("Empty string to select atoms, empty group returned.", - UserWarning) + warnings.warn( + "Empty string to select atoms, empty group returned.", + UserWarning, + ) return self[[]] sel_strs = (sel,) + othersel for group, thing in selgroups.items(): if not isinstance(thing, AtomGroup): - raise TypeError("Passed groups must be AtomGroups. " - "You provided {} for group '{}'".format( - thing.__class__.__name__, group)) - - selections = tuple((selection.Parser.parse(s, selgroups, - periodic=periodic, - atol=atol, rtol=rtol, - sorted=sorted, - rdkit_kwargs=rdkit_kwargs, - smarts_kwargs=smarts_kwargs) - for s in sel_strs)) + raise TypeError( + "Passed groups must be AtomGroups. " + "You provided {} for group '{}'".format( + thing.__class__.__name__, group + ) + ) + + selections = tuple( + ( + selection.Parser.parse( + s, + selgroups, + periodic=periodic, + atol=atol, + rtol=rtol, + sorted=sorted, + rdkit_kwargs=rdkit_kwargs, + smarts_kwargs=smarts_kwargs, + ) + for s in sel_strs + ) + ) if updating: atomgrp = UpdatingAtomGroup(self, selections, sel_strs) else: # Apply the first selection and sum to it - atomgrp = sum([sel.apply(self) for sel in selections[1:]], - selections[0].apply(self)) + atomgrp = sum( + [sel.apply(self) for sel in selections[1:]], + selections[0].apply(self), + ) return atomgrp def split(self, level): @@ -3422,9 +3587,11 @@ def split(self, level): .. versionadded:: 0.9.0 .. versionchanged:: 0.17.0 Added the 'molecule' level. """ - accessors = {'segment': 'segindices', - 'residue': 'resindices', - 'molecule': 'molnums'} + accessors = { + "segment": "segindices", + "residue": "resindices", + "molecule": "molnums", + } if level == "atom": return [self.universe.atoms[[a.ix]] for a in self] @@ -3433,16 +3600,22 @@ def split(self, level): try: levelindices = getattr(self, accessors[level]) except AttributeError: - errmsg = (f'This universe does not have {level} information. Maybe' - f' it is not provided in the topology format in use.') + errmsg = ( + f"This universe does not have {level} information. Maybe" + f" it is not provided in the topology format in use." + ) raise AttributeError(errmsg) from None except KeyError: - errmsg = (f"level = '{level}' not supported, must be one of " - f"{accessors.keys()}") + errmsg = ( + f"level = '{level}' not supported, must be one of " + f"{accessors.keys()}" + ) raise ValueError(errmsg) from None - return [self[levelindices == index] for index in - unique_int_1d(levelindices)] + return [ + self[levelindices == index] + for index in unique_int_1d(levelindices) + ] def guess_bonds(self, vdwradii=None, fudge_factor=0.55, lower_bound=0.1): """Guess bonds, angles, and dihedrals between the atoms in this @@ -3489,21 +3662,24 @@ def get_TopAttr(u, name, cls): return attr # indices of bonds - guesser = DefaultGuesser(None, fudge_factor=fudge_factor, - lower_bound=lower_bound, - box=self.dimensions, - vdwradii=vdwradii) + guesser = DefaultGuesser( + None, + fudge_factor=fudge_factor, + lower_bound=lower_bound, + box=self.dimensions, + vdwradii=vdwradii, + ) b = guesser.guess_bonds(self.atoms, self.atoms.positions) - bondattr = get_TopAttr(self.universe, 'bonds', Bonds) + bondattr = get_TopAttr(self.universe, "bonds", Bonds) bondattr._add_bonds(b, guessed=True) a = guesser.guess_angles(self.bonds) - angleattr = get_TopAttr(self.universe, 'angles', Angles) + angleattr = get_TopAttr(self.universe, "angles", Angles) angleattr._add_bonds(a, guessed=True) d = guesser.guess_dihedrals(self.angles) - diheattr = get_TopAttr(self.universe, 'dihedrals', Dihedrals) + diheattr = get_TopAttr(self.universe, "dihedrals", Dihedrals) diheattr._add_bonds(d) @property @@ -3521,7 +3697,8 @@ def bond(self): """ if len(self) != 2: raise ValueError( - "bond only makes sense for a group with exactly 2 atoms") + "bond only makes sense for a group with exactly 2 atoms" + ) return topologyobjects.Bond(self.ix, self.universe) @property @@ -3539,7 +3716,8 @@ def angle(self): """ if len(self) != 3: raise ValueError( - "angle only makes sense for a group with exactly 3 atoms") + "angle only makes sense for a group with exactly 3 atoms" + ) return topologyobjects.Angle(self.ix, self.universe) @property @@ -3557,7 +3735,8 @@ def dihedral(self): """ if len(self) != 4: raise ValueError( - "dihedral only makes sense for a group with exactly 4 atoms") + "dihedral only makes sense for a group with exactly 4 atoms" + ) return topologyobjects.Dihedral(self.ix, self.universe) @property @@ -3575,7 +3754,8 @@ def improper(self): """ if len(self) != 4: raise ValueError( - "improper only makes sense for a group with exactly 4 atoms") + "improper only makes sense for a group with exactly 4 atoms" + ) return topologyobjects.ImproperDihedral(self.ix, self.universe) @property @@ -3593,7 +3773,8 @@ def ureybradley(self): """ if len(self) != 2: raise ValueError( - "urey bradley only makes sense for a group with exactly 2 atoms") + "urey bradley only makes sense for a group with exactly 2 atoms" + ) return topologyobjects.UreyBradley(self.ix, self.universe) @property @@ -3611,13 +3792,20 @@ def cmap(self): """ if len(self) != 5: raise ValueError( - "cmap only makes sense for a group with exactly 5 atoms") + "cmap only makes sense for a group with exactly 5 atoms" + ) return topologyobjects.CMap(self.ix, self.universe) convert_to = Accessor("convert_to", ConverterWrapper) - def write(self, filename=None, file_format=None, - filenamefmt="{trjname}_{frame}", frames=None, **kwargs): + def write( + self, + filename=None, + file_format=None, + filenamefmt="{trjname}_{frame}", + frames=None, + **kwargs, + ): """Write `AtomGroup` to a file. The output can either be a coordinate file or a selection, depending on @@ -3674,7 +3862,7 @@ def write(self, filename=None, file_format=None, raise IndexError("Cannot write an AtomGroup with 0 atoms") trj = self.universe.trajectory # unified trajectory API - if frames is None or frames == 'all': + if frames is None or frames == "all": trj_frames = trj[::] elif isinstance(frames, numbers.Integral): # We accept everything that indexes a trajectory and returns a @@ -3688,25 +3876,27 @@ def write(self, filename=None, file_format=None, else: if test_trajectory is not trj: raise ValueError( - 'The trajectory of {} provided to the frames keyword ' - 'attribute is different from the trajectory of the ' - 'AtomGroup.'.format(frames) + "The trajectory of {} provided to the frames keyword " + "attribute is different from the trajectory of the " + "AtomGroup.".format(frames) ) trj_frames = frames if filename is None: trjname, ext = os.path.splitext(os.path.basename(trj.filename)) filename = filenamefmt.format(trjname=trjname, frame=trj.frame) - filename = util.filename(filename, - ext=file_format if file_format is not None else 'PDB', - keep=True) + filename = util.filename( + filename, + ext=file_format if file_format is not None else "PDB", + keep=True, + ) # Some writer behave differently when they are given a "multiframe" # argument. It is the case of the PDB writer tht writes models when # "multiframe" is True. # We want to honor what the user provided with the argument if # provided explicitly. If not, then we need to figure out if we write # multiple frames or not. - multiframe = kwargs.pop('multiframe', None) + multiframe = kwargs.pop("multiframe", None) if len(trj_frames) > 1 and multiframe == False: raise ValueError( 'Cannot explicitely set "multiframe" to False and request ' @@ -3725,7 +3915,8 @@ def write(self, filename=None, file_format=None, # Once (and if!) class is selected, use it in with block try: writer = get_writer_for( - filename, format=file_format, multiframe=multiframe) + filename, format=file_format, multiframe=multiframe + ) except (ValueError, TypeError): pass else: @@ -3744,8 +3935,9 @@ def write(self, filename=None, file_format=None, try: # here `file_format` is only used as default, # anything pulled off `filename` will be used preferentially - writer = get_selection_writer_for(filename, - file_format if file_format is not None else 'PDB') + writer = get_selection_writer_for( + filename, file_format if file_format is not None else "PDB" + ) except (TypeError, NotImplementedError): pass else: @@ -3755,7 +3947,7 @@ def write(self, filename=None, file_format=None, raise ValueError("No writer found for format: {}".format(filename)) - def sort(self, key='ix', keyfunc=None): + def sort(self, key="ix", keyfunc=None): """ Returns a sorted ``AtomGroup`` using a specified attribute as the key. @@ -3812,24 +4004,28 @@ def sort(self, key='ix', keyfunc=None): """ idx = getattr(self.atoms, key) if len(idx) != len(self.atoms): - raise ValueError("The array returned by the attribute '{}' " - "must have the same length as the number of " - "atoms in the input AtomGroup".format(key)) + raise ValueError( + "The array returned by the attribute '{}' " + "must have the same length as the number of " + "atoms in the input AtomGroup".format(key) + ) if idx.ndim == 1: - order = np.argsort(idx, kind='stable') + order = np.argsort(idx, kind="stable") elif idx.ndim > 1: if keyfunc is None: - raise NameError("The {} attribute returns a multidimensional " - "array. In order to sort it, a function " - "returning a 1D array (to be used as the sort " - "key) must be passed to the keyfunc argument" - .format(key)) + raise NameError( + "The {} attribute returns a multidimensional " + "array. In order to sort it, a function " + "returning a 1D array (to be used as the sort " + "key) must be passed to the keyfunc argument".format(key) + ) sortkeys = keyfunc(idx) if sortkeys.ndim != 1: - raise ValueError("The function assigned to the argument " - "'keyfunc':{} doesn't return a 1D array." - .format(keyfunc)) - order = np.argsort(sortkeys, kind='stable') + raise ValueError( + "The function assigned to the argument " + "'keyfunc':{} doesn't return a 1D array.".format(keyfunc) + ) + order = np.argsort(sortkeys, kind="stable") return self.atoms[order] @@ -3913,10 +4109,10 @@ def segments(self): the :class:`ResidueGroup`. """ sg = self.universe.segments[unique_int_1d(self.segindices)] - sg._cache['isunique'] = True - sg._cache['issorted'] = True - sg._cache['sorted_unique'] = sg - sg._cache['unsorted_unique'] = sg + sg._cache["isunique"] = True + sg._cache["issorted"] = True + sg._cache["sorted_unique"] = sg + sg._cache["unsorted_unique"] = sg return sg @segments.setter @@ -3930,14 +4126,20 @@ def segments(self, new): try: s_ix = [s.segindex for s in new] except AttributeError: - errmsg = ("Can only set ResidueGroup segments to Segment " - "or SegmentGroup, not {}".format( - ', '.join(type(r) for r in new - if not isinstance(r, Segment)))) + errmsg = ( + "Can only set ResidueGroup segments to Segment " + "or SegmentGroup, not {}".format( + ", ".join( + type(r) for r in new if not isinstance(r, Segment) + ) + ) + ) raise TypeError(errmsg) from None if not isinstance(s_ix, itertools.cycle) and len(s_ix) != len(self): - raise ValueError("Incorrect size: {} for ResidueGroup of size: {}" - "".format(len(new), len(self))) + raise ValueError( + "Incorrect size: {} for ResidueGroup of size: {}" + "".format(len(new), len(self)) + ) # Optimisation TODO: # This currently rebuilds the tt len(self) times # Ideally all changes would happen and *afterwards* tables are built @@ -3992,10 +4194,10 @@ def unique(self): This function now always returns a copy. """ group = self.sorted_unique[:] - group._cache['isunique'] = True - group._cache['issorted'] = True - group._cache['sorted_unique'] = group - group._cache['unsorted_unique'] = group + group._cache["isunique"] = True + group._cache["issorted"] = True + group._cache["sorted_unique"] = group + group._cache["unsorted_unique"] = group return group def asunique(self, sorted=False): @@ -4184,10 +4386,10 @@ def unique(self): This function now always returns a copy. """ group = self.sorted_unique[:] - group._cache['isunique'] = True - group._cache['issorted'] = True - group._cache['sorted_unique'] = group - group._cache['unsorted_unique'] = group + group._cache["isunique"] = True + group._cache["issorted"] = True + group._cache["sorted_unique"] = group + group._cache["unsorted_unique"] = group return group def asunique(self, sorted=False): @@ -4248,7 +4450,9 @@ class ComponentBase(_MutableBase): def __init__(self, ix, u): # index of component if not isinstance(ix, numbers.Integral): - raise IndexError('Component can only be indexed by a single integer') + raise IndexError( + "Component can only be indexed by a single integer" + ) self._ix = ix self._u = u @@ -4258,12 +4462,17 @@ def __getattr__(self, attr): if attr in _TOPOLOGY_ATTRS: cls = _TOPOLOGY_ATTRS[attr] if attr == cls.attrname and attr != cls.singular: - err = ('{selfcls} has no attribute {attr}. ' - 'Do you mean {singular}?') - raise AttributeError(err.format(selfcls=selfcls, attr=attr, - singular=cls.singular)) + err = ( + "{selfcls} has no attribute {attr}. " + "Do you mean {singular}?" + ) + raise AttributeError( + err.format( + selfcls=selfcls, attr=attr, singular=cls.singular + ) + ) else: - err = 'This Universe does not contain {singular} information' + err = "This Universe does not contain {singular} information" raise NoDataError(err.format(singular=cls.singular)) else: return super(ComponentBase, self).__getattr__(attr) @@ -4302,7 +4511,8 @@ def __add__(self, other): o_ix = other.ix_array return self.level.plural( - np.concatenate([self.ix_array, o_ix]), self.universe) + np.concatenate([self.ix_array, o_ix]), self.universe + ) def __radd__(self, other): """Using built-in sum requires supporting 0 + self. If other is @@ -4321,9 +4531,12 @@ def __radd__(self, other): if other == 0: return self.level.plural(self.ix_array, self.universe) else: - raise TypeError("unsupported operand type(s) for +:" - " '{}' and '{}'".format(type(self).__name__, - type(other).__name__)) + raise TypeError( + "unsupported operand type(s) for +:" + " '{}' and '{}'".format( + type(self).__name__, type(other).__name__ + ) + ) @property def universe(self): @@ -4364,31 +4577,31 @@ class Atom(ComponentBase): """ def __repr__(self): - me = '' + me = "" def __reduce__(self): return (_unpickle2, (self.universe, self.ix, Atom)) def __getattr__(self, attr): # special-case timestep info - ts = {'velocity': 'velocities', 'force': 'forces'} + ts = {"velocity": "velocities", "force": "forces"} if attr in ts: - raise NoDataError('This Timestep has no ' + ts[attr]) - elif attr == 'position': - raise NoDataError('This Universe has no coordinates') + raise NoDataError("This Timestep has no " + ts[attr]) + elif attr == "position": + raise NoDataError("This Universe has no coordinates") return super(Atom, self).__getattr__(attr) @property @@ -4398,8 +4611,10 @@ def residue(self): @residue.setter def residue(self, new): if not isinstance(new, Residue): - raise TypeError("Can only set Atom residue to Residue, not {}" - "".format(type(new))) + raise TypeError( + "Can only set Atom residue to Residue, not {}" + "".format(type(new)) + ) self.universe._topology.tt.move_atom(self.ix, new.resindex) @property @@ -4408,8 +4623,9 @@ def segment(self): @segment.setter def segment(self, new): - raise NotImplementedError("Cannot set atom segment. " - "Segments are assigned to Residues") + raise NotImplementedError( + "Cannot set atom segment. " "Segments are assigned to Residues" + ) @property def position(self): @@ -4496,13 +4712,13 @@ class Residue(ComponentBase): """ def __repr__(self): - me = '' + return me + ">" def __reduce__(self): return (_unpickle2, (self.universe, self.ix, Residue)) @@ -4513,23 +4729,24 @@ def atoms(self): :class:`Residue`. """ ag = self.universe.atoms[self.universe._topology.indices[self][0]] - ag._cache['isunique'] = True - ag._cache['issorted'] = True - ag._cache['sorted_unique'] = ag - ag._cache['unsorted_unique'] = ag + ag._cache["isunique"] = True + ag._cache["issorted"] = True + ag._cache["sorted_unique"] = ag + ag._cache["unsorted_unique"] = ag return ag @property def segment(self): - """The :class:`Segment` this :class:`Residue` belongs to. - """ + """The :class:`Segment` this :class:`Residue` belongs to.""" return self.universe.segments[self.universe._topology.segindices[self]] @segment.setter def segment(self, new): if not isinstance(new, Segment): - raise TypeError("Can only set Residue segment to Segment, not {}" - "".format(type(new))) + raise TypeError( + "Can only set Residue segment to Segment, not {}" + "".format(type(new)) + ) self.universe._topology.tt.move_residue(self.ix, new.segindex) @@ -4552,10 +4769,10 @@ class Segment(ComponentBase): """ def __repr__(self): - me = '' + me = "" def __reduce__(self): return (_unpickle2, (self.universe, self.ix, Segment)) @@ -4566,10 +4783,10 @@ def atoms(self): :class:`Segment`. """ ag = self.universe.atoms[self.universe._topology.indices[self][0]] - ag._cache['isunique'] = True - ag._cache['issorted'] = True - ag._cache['sorted_unique'] = ag - ag._cache['unsorted_unique'] = ag + ag._cache["isunique"] = True + ag._cache["issorted"] = True + ag._cache["sorted_unique"] = ag + ag._cache["unsorted_unique"] = ag return ag @property @@ -4577,11 +4794,13 @@ def residues(self): """A :class:`ResidueGroup` of :class:`Residues` present in this :class:`Segment`. """ - rg = self.universe.residues[self.universe._topology.resindices[self][0]] - rg._cache['isunique'] = True - rg._cache['issorted'] = True - rg._cache['sorted_unique'] = rg - rg._cache['unsorted_unique'] = rg + rg = self.universe.residues[ + self.universe._topology.resindices[self][0] + ] + rg._cache["isunique"] = True + rg._cache["issorted"] = True + rg._cache["sorted_unique"] = rg + rg._cache["unsorted_unique"] = rg return rg @@ -4590,10 +4809,15 @@ def residues(self): # here, otherwise we get __getattribute__ infinite loops. _UAG_SHORTCUT_ATTRS = { # Class information of the UAG - "__class__", "_derived_class", + "__class__", + "_derived_class", # Metadata of the UAG - "_base_group", "_selections", "_lastupdate", - "level", "_u", "universe", + "_base_group", + "_selections", + "_lastupdate", + "level", + "_u", + "universe", # Methods of the UAG "_ensure_updated", "is_uptodate", @@ -4613,6 +4837,7 @@ class UpdatingAtomGroup(AtomGroup): .. versionadded:: 0.16.0 """ + # WARNING: This class has __getattribute__ and __getattr__ methods (the # latter inherited from AtomGroup). Because of this bugs introduced in the # class that cause an AttributeError may be very hard to diagnose and @@ -4654,8 +4879,7 @@ def update_selection(self): sels = self._selections if sels: # As with select_atoms, we select the first sel and then sum to it. - ix = sum([sel.apply(bg) for sel in sels[1:]], - sels[0].apply(bg)).ix + ix = sum([sel.apply(bg) for sel in sels[1:]], sels[0].apply(bg)).ix else: ix = np.array([], dtype=np.intp) # Run back through AtomGroup init with this information to remake @@ -4712,7 +4936,7 @@ def __getattribute__(self, name): # ALL attribute access goes through here # If the requested attribute is public (not starting with '_') and # isn't in the shortcut list, update ourselves - if not (name.startswith('_') or name in _UAG_SHORTCUT_ATTRS): + if not (name.startswith("_") or name in _UAG_SHORTCUT_ATTRS): self._ensure_updated() # Going via object.__getattribute__ then bypasses this check stage return object.__getattribute__(self, name) @@ -4722,9 +4946,14 @@ def __reduce__(self): # - unpickle base group # - recreate UAG as created through select_atoms (basegroup and selstrs) # even if base_group is a UAG this will work through recursion - return (_unpickle_uag, - (self._base_group.__reduce__(), self._selections, - self._selection_strings)) + return ( + _unpickle_uag, + ( + self._base_group.__reduce__(), + self._selections, + self._selection_strings, + ), + ) def __repr__(self): basestr = super(UpdatingAtomGroup, self).__repr__() @@ -4739,7 +4968,11 @@ def __repr__(self): basegrp = "another AtomGroup." # With a shorthand to conditionally append the 's' in 'selections'. return "{}, with selection{} {} on {}>".format( - basestr[:-1], "s"[len(self._selection_strings) == 1:], sels, basegrp) + basestr[:-1], + "s"[len(self._selection_strings) == 1 :], + sels, + basegrp, + ) @property def atoms(self): @@ -4803,16 +5036,17 @@ def copy(self): .. versionadded:: 0.19.0 """ - return UpdatingAtomGroup(self._base_group, self._selections, - self._selection_strings) + return UpdatingAtomGroup( + self._base_group, self._selections, self._selection_strings + ) # Define relationships between these classes # with Level objects -_Level = namedtuple('Level', ['name', 'singular', 'plural']) -ATOMLEVEL = _Level('atom', Atom, AtomGroup) -RESIDUELEVEL = _Level('residue', Residue, ResidueGroup) -SEGMENTLEVEL = _Level('segment', Segment, SegmentGroup) +_Level = namedtuple("Level", ["name", "singular", "plural"]) +ATOMLEVEL = _Level("atom", Atom, AtomGroup) +RESIDUELEVEL = _Level("residue", Residue, ResidueGroup) +SEGMENTLEVEL = _Level("segment", Segment, SegmentGroup) Atom.level = ATOMLEVEL AtomGroup.level = ATOMLEVEL @@ -4835,21 +5069,25 @@ def requires(*attrs): def mass_times_charge(atomgroup): return atomgroup.masses * atomgroup.charges """ + def require_dec(func): @functools.wraps(func) def check_args(*args, **kwargs): for a in args: # for each argument if isinstance(a, AtomGroup): # Make list of missing attributes - missing = [attr for attr in attrs - if not hasattr(a, attr)] + missing = [attr for attr in attrs if not hasattr(a, attr)] if missing: raise NoDataError( "{funcname} failed. " "AtomGroup is missing the following required " "attributes: {attrs}".format( funcname=func.__name__, - attrs=', '.join(missing))) + attrs=", ".join(missing), + ) + ) return func(*args, **kwargs) + return check_args + return require_dec diff --git a/package/MDAnalysis/core/topology.py b/package/MDAnalysis/core/topology.py index 899260721c3..dffff294d3a 100644 --- a/package/MDAnalysis/core/topology.py +++ b/package/MDAnalysis/core/topology.py @@ -117,7 +117,7 @@ def make_downshift_arrays(upshift, nparents): # reset nparents to the larger one between input and heuristic from data # This is useful for creating empty Universe where default value is 1. - nparents = np.max([nparents, u_values.max()+1]) + nparents = np.max([nparents, u_values.max() + 1]) residue_indices = np.zeros(nparents, dtype=int) missing_resids = np.sort(np.setdiff1d(np.arange(nparents), u_values)) indices = np.append(indices, upshift_sorted.shape[0]) @@ -128,7 +128,7 @@ def make_downshift_arrays(upshift, nparents): if missing_resid == 0: residue_indices[missing_resid] = 0 else: - residue_indices[missing_resid] = residue_indices[missing_resid-1] + residue_indices[missing_resid] = residue_indices[missing_resid - 1] downshift = np.split(order, residue_indices[:-1]) # Add None to end of array to force it to be of type Object @@ -181,10 +181,15 @@ class TransTable(object): .. versionchanged:: 2.3.0 Lazy building RA and SR. """ - def __init__(self, - n_atoms, n_residues, n_segments, # Size of tables - atom_resindex=None, residue_segindex=None, # Contents of tables - ): + + def __init__( + self, + n_atoms, + n_residues, + n_segments, # Size of tables + atom_resindex=None, + residue_segindex=None, # Contents of tables + ): self.n_atoms = n_atoms self.n_residues = n_residues self.n_segments = n_segments @@ -209,21 +214,24 @@ def __init__(self, def copy(self): """Return a deepcopy of this Transtable""" - return self.__class__(self.n_atoms, self.n_residues, self.n_segments, - atom_resindex=self._AR, residue_segindex=self._RS) + return self.__class__( + self.n_atoms, + self.n_residues, + self.n_segments, + atom_resindex=self._AR, + residue_segindex=self._RS, + ) @property def RA(self): if self._RA is None: - self._RA = make_downshift_arrays(self._AR, - self.n_residues) + self._RA = make_downshift_arrays(self._AR, self.n_residues) return self._RA @property def SR(self): if self._SR is None: - self._SR = make_downshift_arrays(self._RS, - self.n_segments) + self._SR = make_downshift_arrays(self._RS, self.n_segments) return self._SR @property @@ -425,7 +433,6 @@ def add_Residue(self, segidx): self._RS = np.concatenate([self._RS, np.array([segidx])]) self._SR = None - return self.n_residues - 1 def add_Segment(self): @@ -436,8 +443,8 @@ def add_Segment(self): def __getstate__(self): # don't serialize _RA and _SR for performance. attrs = self.__dict__ - attrs['_RA'] = None - attrs['_SR'] = None + attrs["_RA"] = None + attrs["_SR"] = None return attrs @@ -452,10 +459,15 @@ class Topology(object): """ - def __init__(self, n_atoms=1, n_res=1, n_seg=1, - attrs=None, - atom_resindex=None, - residue_segindex=None): + def __init__( + self, + n_atoms=1, + n_res=1, + n_seg=1, + attrs=None, + atom_resindex=None, + residue_segindex=None, + ): """ Parameters ---------- @@ -473,9 +485,13 @@ def __init__(self, n_atoms=1, n_res=1, n_seg=1, 1-D array giving the segindex of each residue in the system """ - self.tt = TransTable(n_atoms, n_res, n_seg, - atom_resindex=atom_resindex, - residue_segindex=residue_segindex) + self.tt = TransTable( + n_atoms, + n_res, + n_seg, + atom_resindex=atom_resindex, + residue_segindex=residue_segindex, + ) if attrs is None: attrs = [] @@ -541,16 +557,26 @@ def del_TopologyAttr(self, topologyattr): @property def guessed_attributes(self): """A list of the guessed attributes in this topology""" - return filter(lambda x: x.is_guessed - if(not isinstance(x.is_guessed, typing.Container)) - else True in x.is_guessed, self.attrs) + return filter( + lambda x: ( + x.is_guessed + if (not isinstance(x.is_guessed, typing.Container)) + else True in x.is_guessed + ), + self.attrs, + ) @property def read_attributes(self): """A list of the attributes read from the topology""" - return filter(lambda x: not x.is_guessed - if(not isinstance(x.is_guessed, typing.Container)) - else False in x.is_guessed, self.attrs) + return filter( + lambda x: ( + not x.is_guessed + if (not isinstance(x.is_guessed, typing.Container)) + else False in x.is_guessed + ), + self.attrs, + ) def add_Residue(self, segment, **new_attrs): """ @@ -569,21 +595,28 @@ def add_Residue(self, segment, **new_attrs): """ # Check that all data is here before making any changes for attr in self.attrs: - if not attr.per_object == 'residue': + if not attr.per_object == "residue": continue if attr.singular not in new_attrs: - missing = (attr.singular for attr in self.attrs - if (attr.per_object == 'residue' and - attr.singular not in new_attrs)) - raise NoDataError("Missing the following attributes for the new" - " Residue: {}".format(', '.join(missing))) + missing = ( + attr.singular + for attr in self.attrs + if ( + attr.per_object == "residue" + and attr.singular not in new_attrs + ) + ) + raise NoDataError( + "Missing the following attributes for the new" + " Residue: {}".format(", ".join(missing)) + ) # Resize topology table residx = self.tt.add_Residue(segment.segindex) # Add new value to each attribute for attr in self.attrs: - if not attr.per_object == 'residue': + if not attr.per_object == "residue": continue newval = new_attrs[attr.singular] attr._add_new(newval) @@ -613,22 +646,28 @@ def add_Segment(self, **new_attrs): Added use of _add_new to resize topology attrs """ for attr in self.attrs: - if attr.per_object == 'segment': + if attr.per_object == "segment": if attr.singular not in new_attrs: - missing = (attr.singular for attr in self.attrs - if (attr.per_object == 'segment' and - attr.singular not in new_attrs)) - raise NoDataError("Missing the following attributes for the" - " new Segment: {}" - "".format(', '.join(missing))) + missing = ( + attr.singular + for attr in self.attrs + if ( + attr.per_object == "segment" + and attr.singular not in new_attrs + ) + ) + raise NoDataError( + "Missing the following attributes for the" + " new Segment: {}" + "".format(", ".join(missing)) + ) segidx = self.tt.add_Segment() for attr in self.attrs: - if not attr.per_object == 'segment': + if not attr.per_object == "segment": continue newval = new_attrs[attr.singular] attr._add_new(newval) return segidx - diff --git a/package/MDAnalysis/core/topologyattrs.py b/package/MDAnalysis/core/topologyattrs.py index d1b103e3410..54451316bef 100644 --- a/package/MDAnalysis/core/topologyattrs.py +++ b/package/MDAnalysis/core/topologyattrs.py @@ -58,16 +58,30 @@ import numpy as np -from ..lib.util import (cached, convert_aa_code, iterable, warn_if_not_unique, - unique_int_1d, check_atomgroup_not_empty) +from ..lib.util import ( + cached, + convert_aa_code, + iterable, + warn_if_not_unique, + unique_int_1d, + check_atomgroup_not_empty, +) from ..lib import transformations, mdamath from ..exceptions import NoDataError, SelectionError from .topologyobjects import TopologyGroup from . import selection -from .groups import (ComponentBase, GroupBase, - Atom, Residue, Segment, - AtomGroup, ResidueGroup, SegmentGroup, - check_wrap_and_unwrap, _pbc_to_wrap) +from .groups import ( + ComponentBase, + GroupBase, + Atom, + Residue, + Segment, + AtomGroup, + ResidueGroup, + SegmentGroup, + check_wrap_and_unwrap, + _pbc_to_wrap, +) from .. import _TOPOLOGY_ATTRS, _TOPOLOGY_TRANSPLANTS, _TOPOLOGY_ATTRNAMES @@ -90,13 +104,17 @@ def set_X(self, group, values): values must be single value OR same length as group """ - _SINGLE_VALUE_ERROR = ("Setting {cls} {attrname} with wrong sized input. " - "Must use single value, length of supplied values: {lenvalues}.") + _SINGLE_VALUE_ERROR = ( + "Setting {cls} {attrname} with wrong sized input. " + "Must use single value, length of supplied values: {lenvalues}." + ) # Eg "Setting Residue resid with wrong sized input. Must use single value, length of supplied # values: 2." - _GROUP_VALUE_ERROR = ("Setting {group} {attrname} with wrong sized array. " - "Length {group}: {lengroup}, length of supplied values: {lenvalues}.") + _GROUP_VALUE_ERROR = ( + "Setting {group} {attrname} with wrong sized array. " + "Length {group}: {lengroup}, length of supplied values: {lenvalues}." + ) # Eg "Setting AtomGroup masses with wrong sized array. Length AtomGroup: 100, length of # supplied values: 50." @@ -116,14 +134,23 @@ def wrapper(attr, group, values): if isinstance(group, ComponentBase): if not val_len == 0: - raise ValueError(_SINGLE_VALUE_ERROR.format( - cls=group.__class__.__name__, attrname=attr.singular, - lenvalues=val_len)) + raise ValueError( + _SINGLE_VALUE_ERROR.format( + cls=group.__class__.__name__, + attrname=attr.singular, + lenvalues=val_len, + ) + ) else: if not (val_len == 0 or val_len == len(group)): - raise ValueError(_GROUP_VALUE_ERROR.format( - group=group.__class__.__name__, attrname=attr.attrname, - lengroup=len(group), lenvalues=val_len)) + raise ValueError( + _GROUP_VALUE_ERROR.format( + group=group.__class__.__name__, + attrname=attr.attrname, + lengroup=len(group), + lenvalues=val_len, + ) + ) # if everything went OK, continue with the function return func(attr, group, values) @@ -153,13 +180,13 @@ def _wronglevel_error(attr, group): # What level to go to before trying to set this attr if isinstance(attr, AtomAttr): - corr_classes = ('atoms', 'atom') + corr_classes = ("atoms", "atom") attr_level = 1 elif isinstance(attr, ResidueAttr): - corr_classes = ('residues', 'residue') + corr_classes = ("residues", "residue") attr_level = 2 elif isinstance(attr, SegmentAttr): - corr_classes = ('segments', 'segment') + corr_classes = ("segments", "segment") attr_level = 3 if isinstance(group, ComponentBase) and (attr_level > group_level): @@ -174,9 +201,13 @@ def _wronglevel_error(attr, group): err_msg = "Cannot set {attr} from {cls}. Use '{cls}.{correct}.{attr} = '" # eg "Cannot set masses from Residue. 'Use Residue.atoms.masses = '" - return NotImplementedError(err_msg.format( - attr=attrname, cls=group.__class__.__name__, correct=correct, - )) + return NotImplementedError( + err_msg.format( + attr=attrname, + cls=group.__class__.__name__, + correct=correct, + ) + ) def _build_stub(method_name, method, attribute_name): @@ -204,10 +235,11 @@ def _build_stub(method_name, method, attribute_name): ------- The stub. """ + def stub_method(self, *args, **kwargs): message = ( - '{class_name}.{method_name}() ' - 'not available; this requires {attribute_name}' + "{class_name}.{method_name}() " + "not available; this requires {attribute_name}" ).format( class_name=self.__class__.__name__, method_name=method_name, @@ -215,21 +247,23 @@ def stub_method(self, *args, **kwargs): ) raise NoDataError(message) - annotation = textwrap.dedent("""\ + annotation = textwrap.dedent( + """\ .. note:: This requires the underlying topology to have {}. Otherwise, a :exc:`~MDAnalysis.exceptions.NoDataError` is raised. - """.format(attribute_name)) + """.format( + attribute_name + ) + ) # The first line of the original docstring is not indented, but the # subsequent lines are. We want to dedent the whole docstring. - first_line, other_lines = method.__doc__.split('\n', 1) + first_line, other_lines = method.__doc__.split("\n", 1) stub_method.__doc__ = ( - first_line + '\n' - + textwrap.dedent(other_lines) - + '\n\n' + annotation + first_line + "\n" + textwrap.dedent(other_lines) + "\n\n" + annotation ) stub_method.__name__ = method_name stub_method.__signature__ = inspect_signature(method) @@ -251,10 +285,11 @@ def _attach_transplant_stubs(attribute_name, topology_attribute_class): """ transplants = topology_attribute_class.transplants for dest_class, methods in transplants.items(): - if dest_class == 'Universe': + if dest_class == "Universe": # Cannot be imported at the top level, it creates issues with # circular imports. from .universe import Universe + dest_class = Universe for method_name, method_callback in methods: # Methods the name of which is prefixed by _ should not be accessed @@ -262,7 +297,7 @@ def _attach_transplant_stubs(attribute_name, topology_attribute_class): # only relevant for user-facing method and properties. Also, # methods _-prefixed can be operator methods, and we do not want # to overwrite these with a stub. - if method_name.startswith('_'): + if method_name.startswith("_"): continue is_property = False @@ -279,11 +314,13 @@ def _attach_transplant_stubs(attribute_name, topology_attribute_class): # TODO: remove bfactors in 3.0 -BFACTOR_WARNING = ("The bfactor topology attribute is only " - "provided as an alias to the tempfactor " - "attribute. It will be removed in " - "3.0. Please use the tempfactor attribute " - "instead.") +BFACTOR_WARNING = ( + "The bfactor topology attribute is only " + "provided as an alias to the tempfactor " + "attribute. It will be removed in " + "3.0. Please use the tempfactor attribute " + "instead." +) def deprecate_bfactor_warning(func): @@ -321,26 +358,26 @@ class _TopologyAttrMeta(type): def __init__(cls, name, bases, classdict): type.__init__(type, name, bases, classdict) - attrname = classdict.get('attrname') - singular = classdict.get('singular', attrname) + attrname = classdict.get("attrname") + singular = classdict.get("singular", attrname) if attrname is None: attrname = singular if singular: _TOPOLOGY_ATTRS[singular] = _TOPOLOGY_ATTRS[attrname] = cls - _singular = singular.lower().replace('_', '') - _attrname = attrname.lower().replace('_', '') + _singular = singular.lower().replace("_", "") + _attrname = attrname.lower().replace("_", "") _TOPOLOGY_ATTRNAMES[_singular] = singular _TOPOLOGY_ATTRNAMES[_attrname] = attrname for clstype, transplants in cls.transplants.items(): for name, method in transplants: _TOPOLOGY_TRANSPLANTS[name] = [attrname, method, clstype] - clean = name.lower().replace('_', '') + clean = name.lower().replace("_", "") _TOPOLOGY_ATTRNAMES[clean] = name - for attr in ['singular', 'attrname']: + for attr in ["singular", "attrname"]: try: attrname = classdict[attr] except KeyError: @@ -362,22 +399,28 @@ def __init__(cls, name, bases, classdict): if dtype is not None: per_obj = classdict.get("per_object", bases[0].per_object) try: - selection.gen_selection_class(singular, attrname, - dtype, per_obj) + selection.gen_selection_class( + singular, attrname, dtype, per_obj + ) except ValueError: - msg = ("A selection keyword could not be " - "automatically generated for the " - f"{singular} attribute. If you need a " - "selection keyword, define it manually " - "by subclassing core.selection.Selection") + msg = ( + "A selection keyword could not be " + "automatically generated for the " + f"{singular} attribute. If you need a " + "selection keyword, define it manually " + "by subclassing core.selection.Selection" + ) warnings.warn(msg) # TODO: remove in 3.0 if attrname == "tempfactors": _TOPOLOGY_ATTRS["bfactor"] = _TOPOLOGY_ATTRS["bfactors"] = cls - selcls = selection.gen_selection_class("bfactor", "bfactors", - classdict.get("dtype"), - per_object="atom") + selcls = selection.gen_selection_class( + "bfactor", + "bfactors", + classdict.get("dtype"), + per_object="atom", + ) selcls.apply = deprecate_bfactor_warning(selcls.apply) @@ -406,8 +449,9 @@ class TopologyAttr(object, metaclass=_TopologyAttrMeta): handle for the Topology object TopologyAttr is associated with """ - attrname = 'topologyattrs' - singular = 'topologyattr' + + attrname = "topologyattrs" + singular = "topologyattr" per_object = None # ie Resids per_object = 'residue' top = None # pointer to Topology object transplants = defaultdict(list) @@ -435,8 +479,9 @@ def _gen_initial_values(n_atoms, n_residues, n_segments): raise NotImplementedError("No default values") @classmethod - def from_blank(cls, n_atoms=None, n_residues=None, n_segments=None, - values=None): + def from_blank( + cls, n_atoms=None, n_residues=None, n_segments=None, values=None + ): """Create a blank version of this TopologyAttribute Parameters @@ -524,13 +569,14 @@ def are_values_missing(cls, values): .. versionadded:: 2.8.0 """ - missing_value_label = getattr(cls, 'missing_value_label', None) + missing_value_label = getattr(cls, "missing_value_label", None) if missing_value_label is np.nan: return np.isnan(values) else: return values == missing_value_label + # core attributes @@ -546,8 +592,9 @@ class Atomindices(TopologyAttr): elements in that group. """ - attrname = 'indices' - singular = 'index' + + attrname = "indices" + singular = "index" target_classes = [AtomGroup, ResidueGroup, SegmentGroup, Atom] dtype = int @@ -579,8 +626,9 @@ class Resindices(TopologyAttr): order of the elements in that group. """ - attrname = 'resindices' - singular = 'resindex' + + attrname = "resindices" + singular = "resindex" target_classes = [AtomGroup, ResidueGroup, SegmentGroup, Atom, Residue] dtype = int @@ -613,11 +661,18 @@ class Segindices(TopologyAttr): order of the elements in that group. """ - attrname = 'segindices' - singular = 'segindex' + + attrname = "segindices" + singular = "segindex" dtype = int - target_classes = [AtomGroup, ResidueGroup, SegmentGroup, - Atom, Residue, Segment] + target_classes = [ + AtomGroup, + ResidueGroup, + SegmentGroup, + Atom, + Residue, + Segment, + ] def __init__(self): self._guessed = False @@ -639,11 +694,10 @@ def set_segments(self, sg, values): class AtomAttr(TopologyAttr): - """Base class for atom attributes. + """Base class for atom attributes.""" - """ - attrname = 'atomattrs' - singular = 'atomattr' + attrname = "atomattrs" + singular = "atomattr" target_classes = [AtomGroup, ResidueGroup, SegmentGroup, Atom] def get_atoms(self, ag): @@ -680,11 +734,11 @@ def set_segments(self, sg, values): # TODO: update docs to property doc class Atomids(AtomAttr): - """ID for each atom. - """ - attrname = 'ids' - singular = 'id' - per_object = 'atom' + """ID for each atom.""" + + attrname = "ids" + singular = "id" + per_object = "atom" dtype = int @staticmethod @@ -778,7 +832,9 @@ def _set_X(self, ag, values): newnames.append(val) newidx[i] = nextidx - self.nmidx[ag.ix] = newidx # newidx either single value or same size array + self.nmidx[ag.ix] = ( + newidx # newidx either single value or same size array + ) if newnames: self.name_lookup = np.concatenate([self.name_lookup, newnames]) self.values = self.name_lookup[self.nmidx] @@ -794,20 +850,20 @@ def set_atoms(self, ag, values): @staticmethod def _gen_initial_values(na, nr, ns): - return np.full(na, '', dtype=object) + return np.full(na, "", dtype=object) # TODO: update docs to property doc class Atomnames(AtomStringAttr): - """Name for each atom. - """ - attrname = 'names' - singular = 'name' - per_object = 'atom' + """Name for each atom.""" + + attrname = "names" + singular = "name" + per_object = "atom" dtype = object transplants = defaultdict(list) - def phi_selection(residue, c_name='C', n_name='N', ca_name='CA'): + def phi_selection(residue, c_name="C", n_name="N", ca_name="CA"): """Select AtomGroup corresponding to the phi protein backbone dihedral C'-N-CA-C. @@ -831,11 +887,11 @@ def phi_selection(residue, c_name='C', n_name='N', ca_name='CA'): faster atom matching with boolean arrays. """ # fnmatch is expensive. try the obv candidate first - prev = residue.universe.residues[residue.ix-1] + prev = residue.universe.residues[residue.ix - 1] sid = residue.segment.segid - rid = residue.resid-1 + rid = residue.resid - 1 if not (prev.segment.segid == sid and prev.resid == rid): - sel = 'segid {} and resid {}'.format(sid, rid) + sel = "segid {} and resid {}".format(sid, rid) try: prev = residue.universe.select_atoms(sel).residues[0] except IndexError: @@ -850,12 +906,12 @@ def phi_selection(residue, c_name='C', n_name='N', ca_name='CA'): if not all(len(ag) == 1 for ag in ncac): return None - sel = c_+sum(ncac) + sel = c_ + sum(ncac) return sel - transplants[Residue].append(('phi_selection', phi_selection)) + transplants[Residue].append(("phi_selection", phi_selection)) - def phi_selections(residues, c_name='C', n_name='N', ca_name='CA'): + def phi_selections(residues, c_name="C", n_name="N", ca_name="CA"): """Select list of AtomGroups corresponding to the phi protein backbone dihedral C'-N-CA-C. @@ -882,11 +938,11 @@ def phi_selections(residues, c_name='C', n_name='N', ca_name='CA'): return [] u = residues[0].universe - prev = u.residues[residues.ix-1] # obv candidates first + prev = u.residues[residues.ix - 1] # obv candidates first rsid = residues.segids - prid = residues.resids-1 + prid = residues.resids - 1 ncac_names = [n_name, ca_name, c_name] - sel = 'segid {} and resid {}' + sel = "segid {} and resid {}" # replace wrong residues wix = np.where((prev.segids != rsid) | (prev.resids != prid))[0] @@ -901,8 +957,10 @@ def phi_selections(residues, c_name='C', n_name='N', ca_name='CA'): prev = sum(prevls) keep_prev = [sum(r.atoms.names == c_name) == 1 for r in prev] - keep_res = [all(sum(r.atoms.names == n) == 1 for n in ncac_names) - for r in residues] + keep_res = [ + all(sum(r.atoms.names == n) == 1 for n in ncac_names) + for r in residues + ] keep = np.array(keep_prev) & np.array(keep_res) keep[invalid] = False results = np.zeros_like(residues, dtype=object) @@ -918,9 +976,9 @@ def phi_selections(residues, c_name='C', n_name='N', ca_name='CA'): results[keepix] = [sum(atoms) for atoms in zip(c_, n, ca, c)] return list(results) - transplants[ResidueGroup].append(('phi_selections', phi_selections)) + transplants[ResidueGroup].append(("phi_selections", phi_selections)) - def psi_selection(residue, c_name='C', n_name='N', ca_name='CA'): + def psi_selection(residue, c_name="C", n_name="N", ca_name="CA"): """Select AtomGroup corresponding to the psi protein backbone dihedral N-CA-C-N'. @@ -947,9 +1005,9 @@ def psi_selection(residue, c_name='C', n_name='N', ca_name='CA'): # fnmatch is expensive. try the obv candidate first _manual_sel = False sid = residue.segment.segid - rid = residue.resid+1 + rid = residue.resid + 1 try: - nxt = residue.universe.residues[residue.ix+1] + nxt = residue.universe.residues[residue.ix + 1] except IndexError: _manual_sel = True else: @@ -957,7 +1015,7 @@ def psi_selection(residue, c_name='C', n_name='N', ca_name='CA'): _manual_sel = True if _manual_sel: - sel = 'segid {} and resid {}'.format(sid, rid) + sel = "segid {} and resid {}".format(sid, rid) try: nxt = residue.universe.select_atoms(sel).residues[0] except IndexError: @@ -975,7 +1033,7 @@ def psi_selection(residue, c_name='C', n_name='N', ca_name='CA'): sel = sum(ncac) + n_ return sel - transplants[Residue].append(('psi_selection', psi_selection)) + transplants[Residue].append(("psi_selection", psi_selection)) def _get_next_residues_by_resid(residues): """Select list of Residues corresponding to the next resid for each @@ -993,19 +1051,19 @@ def _get_next_residues_by_resid(residues): u = residues[0].universe except IndexError: return residues - nxres = np.array([None]*len(residues), dtype=object) + nxres = np.array([None] * len(residues), dtype=object) ix = np.arange(len(residues)) # no guarantee residues is ordered or unique last = max(residues.ix) - if last == len(u.residues)-1: + if last == len(u.residues) - 1: notlast = residues.ix != last ix = ix[notlast] residues = residues[notlast] - nxres[ix] = nxt = u.residues[residues.ix+1] + nxres[ix] = nxt = u.residues[residues.ix + 1] rsid = residues.segids - nrid = residues.resids+1 - sel = 'segid {} and resid {}' + nrid = residues.resids + 1 + sel = "segid {} and resid {}" # replace wrong residues wix = np.where((nxt.segids != rsid) | (nxt.resids != nrid))[0] @@ -1017,8 +1075,9 @@ def _get_next_residues_by_resid(residues): nxres[ix[i]] = None return nxres - transplants[ResidueGroup].append(('_get_next_residues_by_resid', - _get_next_residues_by_resid)) + transplants[ResidueGroup].append( + ("_get_next_residues_by_resid", _get_next_residues_by_resid) + ) def _get_prev_residues_by_resid(residues): """Select list of Residues corresponding to the previous resid for each @@ -1036,11 +1095,11 @@ def _get_prev_residues_by_resid(residues): u = residues[0].universe except IndexError: return residues - pvres = np.array([None]*len(residues)) - pvres[:] = prev = u.residues[residues.ix-1] + pvres = np.array([None] * len(residues)) + pvres[:] = prev = u.residues[residues.ix - 1] rsid = residues.segids - prid = residues.resids-1 - sel = 'segid {} and resid {}' + prid = residues.resids - 1 + sel = "segid {} and resid {}" # replace wrong residues wix = np.where((prev.segids != rsid) | (prev.resids != prid))[0] @@ -1052,10 +1111,11 @@ def _get_prev_residues_by_resid(residues): pvres[i] = None return pvres - transplants[ResidueGroup].append(('_get_prev_residues_by_resid', - _get_prev_residues_by_resid)) + transplants[ResidueGroup].append( + ("_get_prev_residues_by_resid", _get_prev_residues_by_resid) + ) - def psi_selections(residues, c_name='C', n_name='N', ca_name='CA'): + def psi_selections(residues, c_name="C", n_name="N", ca_name="CA"): """Select list of AtomGroups corresponding to the psi protein backbone dihedral N-CA-C-N'. @@ -1081,7 +1141,7 @@ def psi_selections(residues, c_name='C', n_name='N', ca_name='CA'): if not residues: return [] - results = np.array([None]*len(residues), dtype=object) + results = np.array([None] * len(residues), dtype=object) nxtres = residues._get_next_residues_by_resid() rix = np.where(nxtres)[0] nxt = sum(nxtres[rix]) @@ -1089,8 +1149,10 @@ def psi_selections(residues, c_name='C', n_name='N', ca_name='CA'): ncac_names = [n_name, ca_name, c_name] keep_nxt = [sum(r.atoms.names == n_name) == 1 for r in nxt] - keep_res = [all(sum(r.atoms.names == n) == 1 for n in ncac_names) - for r in residues] + keep_res = [ + all(sum(r.atoms.names == n) == 1 for n in ncac_names) + for r in residues + ] keep = np.array(keep_nxt) & np.array(keep_res) nxt = nxt[keep] residues = residues[keep] @@ -1103,9 +1165,9 @@ def psi_selections(residues, c_name='C', n_name='N', ca_name='CA'): results[rix[keepix]] = [sum(atoms) for atoms in zip(n, ca, c, n_)] return list(results) - transplants[ResidueGroup].append(('psi_selections', psi_selections)) + transplants[ResidueGroup].append(("psi_selections", psi_selections)) - def omega_selection(residue, c_name='C', n_name='N', ca_name='CA'): + def omega_selection(residue, c_name="C", n_name="N", ca_name="CA"): """Select AtomGroup corresponding to the omega protein backbone dihedral CA-C-N'-CA'. @@ -1135,9 +1197,9 @@ def omega_selection(residue, c_name='C', n_name='N', ca_name='CA'): # fnmatch is expensive. try the obv candidate first _manual_sel = False sid = residue.segment.segid - rid = residue.resid+1 + rid = residue.resid + 1 try: - nxt = residue.universe.residues[residue.ix+1] + nxt = residue.universe.residues[residue.ix + 1] except IndexError: _manual_sel = True else: @@ -1145,7 +1207,7 @@ def omega_selection(residue, c_name='C', n_name='N', ca_name='CA'): _manual_sel = True if _manual_sel: - sel = 'segid {} and resid {}'.format(sid, rid) + sel = "segid {} and resid {}".format(sid, rid) try: nxt = residue.universe.select_atoms(sel).residues[0] except IndexError: @@ -1159,11 +1221,11 @@ def omega_selection(residue, c_name='C', n_name='N', ca_name='CA'): if not all(len(ag) == 1 for ag in [ca_, n_, ca, c]): return None - return ca+c+n_+ca_ + return ca + c + n_ + ca_ - transplants[Residue].append(('omega_selection', omega_selection)) + transplants[Residue].append(("omega_selection", omega_selection)) - def omega_selections(residues, c_name='C', n_name='N', ca_name='CA'): + def omega_selections(residues, c_name="C", n_name="N", ca_name="CA"): """Select list of AtomGroups corresponding to the omega protein backbone dihedral CA-C-N'-CA'. @@ -1193,7 +1255,7 @@ def omega_selections(residues, c_name='C', n_name='N', ca_name='CA'): if not residues: return [] - results = np.array([None]*len(residues), dtype=object) + results = np.array([None] * len(residues), dtype=object) nxtres = residues._get_next_residues_by_resid() rix = np.where(nxtres)[0] nxt = sum(nxtres[rix]) @@ -1201,10 +1263,13 @@ def omega_selections(residues, c_name='C', n_name='N', ca_name='CA'): nxtatoms = [ca_name, n_name] resatoms = [ca_name, c_name] - keep_nxt = [all(sum(r.atoms.names == n) == 1 for n in nxtatoms) - for r in nxt] - keep_res = [all(sum(r.atoms.names == n) == 1 for n in resatoms) - for r in residues] + keep_nxt = [ + all(sum(r.atoms.names == n) == 1 for n in nxtatoms) for r in nxt + ] + keep_res = [ + all(sum(r.atoms.names == n) == 1 for n in resatoms) + for r in residues + ] keep = np.array(keep_nxt) & np.array(keep_res) nxt = nxt[keep] residues = residues[keep] @@ -1218,10 +1283,15 @@ def omega_selections(residues, c_name='C', n_name='N', ca_name='CA'): results[rix[keepix]] = [sum(atoms) for atoms in zip(ca, c, n_, ca_)] return list(results) - transplants[ResidueGroup].append(('omega_selections', omega_selections)) + transplants[ResidueGroup].append(("omega_selections", omega_selections)) - def chi1_selection(residue, n_name='N', ca_name='CA', cb_name='CB', - cg_name='CG CG1 OG OG1 SG'): + def chi1_selection( + residue, + n_name="N", + ca_name="CA", + cb_name="CB", + cg_name="CG CG1 OG OG1 SG", + ): r"""Select AtomGroup corresponding to the chi1 sidechain dihedral ``N-CA-CB-*G.`` The gamma atom is taken to be the heavy atom in the gamma position. If more than one heavy atom is present (e.g. CG1 and CG2), the one with the lower number is used (CG1). @@ -1264,10 +1334,15 @@ def chi1_selection(residue, n_name='N', ca_name='CA', cb_name='CB', return None return sum(ags) - transplants[Residue].append(('chi1_selection', chi1_selection)) + transplants[Residue].append(("chi1_selection", chi1_selection)) - def chi1_selections(residues, n_name='N', ca_name='CA', cb_name='CB', - cg_name='CG CG1 OG OG1 SG'): + def chi1_selections( + residues, + n_name="N", + ca_name="CA", + cb_name="CB", + cg_name="CG CG1 OG OG1 SG", + ): """Select list of AtomGroups corresponding to the chi1 sidechain dihedral N-CA-CB-CG. @@ -1294,10 +1369,12 @@ def chi1_selections(residues, n_name='N', ca_name='CA', cb_name='CB', if not residues: return [] - results = np.array([None]*len(residues)) + results = np.array([None] * len(residues)) names = [n_name, ca_name, cb_name, cg_name] - keep = [all(sum(np.isin(r.atoms.names, n.split())) == 1 - for n in names) for r in residues] + keep = [ + all(sum(np.isin(r.atoms.names, n.split())) == 1 for n in names) + for r in residues + ] keepix = np.where(keep)[0] residues = residues[keep] @@ -1306,36 +1383,39 @@ def chi1_selections(residues, n_name='N', ca_name='CA', cb_name='CB', results[keepix] = [sum(atoms) for atoms in zip(*ags)] return list(results) - transplants[ResidueGroup].append(('chi1_selections', chi1_selections)) + transplants[ResidueGroup].append(("chi1_selections", chi1_selections)) # TODO: update docs to property doc class Atomtypes(AtomStringAttr): """Type for each atom""" - attrname = 'types' - singular = 'type' - per_object = 'atom' + + attrname = "types" + singular = "type" + per_object = "atom" dtype = object # TODO: update docs to property doc class Elements(AtomStringAttr): """Element for each atom""" - attrname = 'elements' - singular = 'element' + + attrname = "elements" + singular = "element" dtype = object @staticmethod def _gen_initial_values(na, nr, ns): - return np.array(['' for _ in range(na)], dtype=object) + return np.array(["" for _ in range(na)], dtype=object) # TODO: update docs to property doc class Radii(AtomAttr): """Radii for each atom""" - attrname = 'radii' - singular = 'radius' - per_object = 'atom' + + attrname = "radii" + singular = "radius" + per_object = "atom" dtype = float @staticmethod @@ -1351,14 +1431,15 @@ class RecordTypes(AtomStringAttr): .. versionchanged:: 0.20.0 Now stores array of dtype object rather than boolean mapping """ - attrname = 'record_types' - singular = 'record_type' - per_object = 'atom' + + attrname = "record_types" + singular = "record_type" + per_object = "atom" dtype = object @staticmethod def _gen_initial_values(na, nr, ns): - return np.array(['ATOM'] * na, dtype=object) + return np.array(["ATOM"] * na, dtype=object) class ChainIDs(AtomStringAttr): @@ -1368,17 +1449,19 @@ class ChainIDs(AtomStringAttr): ---- This is an attribute of the Atom, not Residue or Segment """ - attrname = 'chainIDs' - singular = 'chainID' - per_object = 'atom' + + attrname = "chainIDs" + singular = "chainID" + per_object = "atom" dtype = object class Tempfactors(AtomAttr): """Tempfactor for atoms""" - attrname = 'tempfactors' - singular = 'tempfactor' - per_object = 'atom' + + attrname = "tempfactors" + singular = "tempfactor" + per_object = "atom" dtype = float transplants = defaultdict(list) @@ -1440,22 +1523,31 @@ def bfactors_setter(self, value): self.universe.atoms[self.atoms.ix].tempfactors = value transplants[Atom].append( - ('bfactor', property(bfactor, bfactor_setter, None, - bfactor.__doc__))) + ("bfactor", property(bfactor, bfactor_setter, None, bfactor.__doc__)) + ) for group in (AtomGroup, Residue, ResidueGroup, Segment, SegmentGroup): transplants[group].append( - ("bfactors", property(bfactors, bfactors_setter, None, - bfactors.__doc__))) + ( + "bfactors", + property(bfactors, bfactors_setter, None, bfactors.__doc__), + ) + ) class Masses(AtomAttr): - attrname = 'masses' - singular = 'mass' - per_object = 'atom' + attrname = "masses" + singular = "mass" + per_object = "atom" missing_value_label = np.nan - target_classes = [AtomGroup, ResidueGroup, SegmentGroup, - Atom, Residue, Segment] + target_classes = [ + AtomGroup, + ResidueGroup, + SegmentGroup, + Atom, + Residue, + Segment, + ] transplants = defaultdict(list) dtype = np.float64 @@ -1503,7 +1595,7 @@ def get_segments(self, sg): @_pbc_to_wrap @check_wrap_and_unwrap @check_atomgroup_not_empty - def center_of_mass(group, wrap=False, unwrap=False, compound='group'): + def center_of_mass(group, wrap=False, unwrap=False, compound="group"): r"""Center of mass of (compounds of) the group .. math:: @@ -1569,15 +1661,15 @@ def center_of_mass(group, wrap=False, unwrap=False, compound='group'): is deprecated and will be removed in version 3.0. """ atoms = group.atoms - return atoms.center(weights=atoms.masses, wrap=wrap, compound=compound, - unwrap=unwrap) + return atoms.center( + weights=atoms.masses, wrap=wrap, compound=compound, unwrap=unwrap + ) - transplants[GroupBase].append( - ('center_of_mass', center_of_mass)) + transplants[GroupBase].append(("center_of_mass", center_of_mass)) @warn_if_not_unique @check_atomgroup_not_empty - def total_mass(group, compound='group'): + def total_mass(group, compound="group"): r"""Total mass of (compounds of) the group. Computes the total mass of :class:`Atoms` in the group. @@ -1609,8 +1701,7 @@ def total_mass(group, compound='group'): """ return group.accumulate("masses", compound=compound) - transplants[GroupBase].append( - ('total_mass', total_mass)) + transplants[GroupBase].append(("total_mass", total_mass)) @warn_if_not_unique @_pbc_to_wrap @@ -1680,10 +1771,12 @@ def moment_of_inertia(group, wrap=False, unwrap=False, compound="group"): atomgroup = group.atoms com = atomgroup.center_of_mass( - wrap=wrap, unwrap=unwrap, compound=compound) - if compound != 'group': - com = (com * group.masses[:, None] - ).sum(axis=0) / group.masses.sum() + wrap=wrap, unwrap=unwrap, compound=compound + ) + if compound != "group": + com = (com * group.masses[:, None]).sum( + axis=0 + ) / group.masses.sum() if wrap: pos = atomgroup.pack_into_box(inplace=False) - com @@ -1706,20 +1799,19 @@ def moment_of_inertia(group, wrap=False, unwrap=False, compound="group"): # xx tens[0][0] = (masses * (pos[:, 1] ** 2 + pos[:, 2] ** 2)).sum() # xy & yx - tens[0][1] = tens[1][0] = - (masses * pos[:, 0] * pos[:, 1]).sum() + tens[0][1] = tens[1][0] = -(masses * pos[:, 0] * pos[:, 1]).sum() # xz & zx - tens[0][2] = tens[2][0] = - (masses * pos[:, 0] * pos[:, 2]).sum() + tens[0][2] = tens[2][0] = -(masses * pos[:, 0] * pos[:, 2]).sum() # yy tens[1][1] = (masses * (pos[:, 0] ** 2 + pos[:, 2] ** 2)).sum() # yz + zy - tens[1][2] = tens[2][1] = - (masses * pos[:, 1] * pos[:, 2]).sum() + tens[1][2] = tens[2][1] = -(masses * pos[:, 1] * pos[:, 2]).sum() # zz tens[2][2] = (masses * (pos[:, 0] ** 2 + pos[:, 1] ** 2)).sum() return tens - transplants[GroupBase].append( - ('moment_of_inertia', moment_of_inertia)) + transplants[GroupBase].append(("moment_of_inertia", moment_of_inertia)) @warn_if_not_unique @_pbc_to_wrap @@ -1749,26 +1841,31 @@ def radius_of_gyration(group, wrap=False, **kwargs): else: recenteredpos = atomgroup.positions - com - rog_sq = np.einsum('i,i->',masses,np.einsum('ij,ij->i', - recenteredpos,recenteredpos))/atomgroup.total_mass() + rog_sq = ( + np.einsum( + "i,i->", + masses, + np.einsum("ij,ij->i", recenteredpos, recenteredpos), + ) + / atomgroup.total_mass() + ) return np.sqrt(rog_sq) - transplants[GroupBase].append( - ('radius_of_gyration', radius_of_gyration)) + transplants[GroupBase].append(("radius_of_gyration", radius_of_gyration)) @warn_if_not_unique @_pbc_to_wrap @check_atomgroup_not_empty - def gyration_moments(group, wrap=False, unwrap=False, compound='group'): + def gyration_moments(group, wrap=False, unwrap=False, compound="group"): r"""Moments of the gyration tensor. The moments are defined as the eigenvalues of the gyration tensor. .. math:: - - \mathsf{T} = \frac{1}{N} \sum_{i=1}^{N} (\mathbf{r}_\mathrm{i} - + + \mathsf{T} = \frac{1}{N} \sum_{i=1}^{N} (\mathbf{r}_\mathrm{i} - \mathbf{r}_\mathrm{COM})(\mathbf{r}_\mathrm{i} - \mathbf{r}_\mathrm{COM}) Where :math:`\mathbf{r}_\mathrm{COM}` is the center of mass. @@ -1800,60 +1897,65 @@ def gyration_moments(group, wrap=False, unwrap=False, compound='group'): def _gyration(recenteredpos, masses): if len(masses.shape) > 1: masses = np.squeeze(masses) - tensor = np.einsum( "ki,kj->ij", - recenteredpos, - np.einsum("ij,i->ij", recenteredpos, masses), - ) - return np.linalg.eigvalsh(tensor/np.sum(masses)) + tensor = np.einsum( + "ki,kj->ij", + recenteredpos, + np.einsum("ij,i->ij", recenteredpos, masses), + ) + return np.linalg.eigvalsh(tensor / np.sum(masses)) atomgroup = group.atoms masses = atomgroup.masses com = atomgroup.center_of_mass( - wrap=wrap, unwrap=unwrap, compound=compound) - - if compound == 'group': - if wrap: - recenteredpos = (atomgroup.pack_into_box(inplace=False) - com) - elif unwrap: - recenteredpos = (atomgroup.unwrap(inplace=False, - compound=compound, - reference=None, - ) - com) - else: - recenteredpos = (atomgroup.positions - com) - eig_vals = _gyration(recenteredpos, masses) + wrap=wrap, unwrap=unwrap, compound=compound + ) + + if compound == "group": + if wrap: + recenteredpos = atomgroup.pack_into_box(inplace=False) - com + elif unwrap: + recenteredpos = ( + atomgroup.unwrap( + inplace=False, + compound=compound, + reference=None, + ) + - com + ) + else: + recenteredpos = atomgroup.positions - com + eig_vals = _gyration(recenteredpos, masses) else: - (atom_masks, - compound_masks, - n_compounds) = atomgroup._split_by_compound_indices(compound) - - if unwrap: - coords = atomgroup.unwrap( - compound=compound, - reference=None, - inplace=False - ) - else: - coords = atomgroup.positions - - eig_vals = np.empty((n_compounds, 3), dtype=np.float64) - for compound_mask, atom_mask in zip(compound_masks, atom_masks): - eig_vals[compound_mask, :] = [_gyration( - coords[mask] - com[compound_mask][i], - masses[mask][:, None] - ) for i, mask in enumerate(atom_mask)] + (atom_masks, compound_masks, n_compounds) = ( + atomgroup._split_by_compound_indices(compound) + ) - return eig_vals + if unwrap: + coords = atomgroup.unwrap( + compound=compound, reference=None, inplace=False + ) + else: + coords = atomgroup.positions - transplants[GroupBase].append( - ('gyration_moments', gyration_moments)) + eig_vals = np.empty((n_compounds, 3), dtype=np.float64) + for compound_mask, atom_mask in zip(compound_masks, atom_masks): + eig_vals[compound_mask, :] = [ + _gyration( + coords[mask] - com[compound_mask][i], + masses[mask][:, None], + ) + for i, mask in enumerate(atom_mask) + ] + + return eig_vals + transplants[GroupBase].append(("gyration_moments", gyration_moments)) @warn_if_not_unique @_pbc_to_wrap @check_atomgroup_not_empty - def shape_parameter(group, wrap=False, unwrap=False, compound='group'): + def shape_parameter(group, wrap=False, unwrap=False, compound="group"): """Shape parameter. See [Dima2004a]_ for background information. @@ -1880,24 +1982,31 @@ def shape_parameter(group, wrap=False, unwrap=False, compound='group'): Added calculation for any `compound` type """ atomgroup = group.atoms - eig_vals = atomgroup.gyration_moments(wrap=wrap, unwrap=unwrap, compound=compound) + eig_vals = atomgroup.gyration_moments( + wrap=wrap, unwrap=unwrap, compound=compound + ) if len(eig_vals.shape) > 1: - shape = 27.0 * np.prod(eig_vals - np.mean(eig_vals, axis=1), axis=1 - ) / np.power(np.sum(eig_vals, axis=1), 3) + shape = ( + 27.0 + * np.prod(eig_vals - np.mean(eig_vals, axis=1), axis=1) + / np.power(np.sum(eig_vals, axis=1), 3) + ) else: - shape = 27.0 * np.prod(eig_vals - np.mean(eig_vals) - ) / np.power(np.sum(eig_vals), 3) + shape = ( + 27.0 + * np.prod(eig_vals - np.mean(eig_vals)) + / np.power(np.sum(eig_vals), 3) + ) return shape - transplants[GroupBase].append( - ('shape_parameter', shape_parameter)) + transplants[GroupBase].append(("shape_parameter", shape_parameter)) @warn_if_not_unique @_pbc_to_wrap @check_wrap_and_unwrap @check_atomgroup_not_empty - def asphericity(group, wrap=False, unwrap=False, compound='group'): + def asphericity(group, wrap=False, unwrap=False, compound="group"): """Asphericity. See [Dima2004b]_ for background information. @@ -1925,18 +2034,23 @@ def asphericity(group, wrap=False, unwrap=False, compound='group'): Added calculation for any `compound` type """ atomgroup = group.atoms - eig_vals = atomgroup.gyration_moments(wrap=wrap, unwrap=unwrap, compound=compound) + eig_vals = atomgroup.gyration_moments( + wrap=wrap, unwrap=unwrap, compound=compound + ) if len(eig_vals.shape) > 1: - shape = (3.0 / 2.0) * (np.sum((eig_vals - np.mean(eig_vals, axis=1))**2, axis=1) / - np.sum(eig_vals, axis=1)**2) + shape = (3.0 / 2.0) * ( + np.sum((eig_vals - np.mean(eig_vals, axis=1)) ** 2, axis=1) + / np.sum(eig_vals, axis=1) ** 2 + ) else: - shape = (3.0 / 2.0) * (np.sum((eig_vals - np.mean(eig_vals))**2) / - np.sum(eig_vals)**2) + shape = (3.0 / 2.0) * ( + np.sum((eig_vals - np.mean(eig_vals)) ** 2) + / np.sum(eig_vals) ** 2 + ) return shape - transplants[GroupBase].append( - ('asphericity', asphericity)) + transplants[GroupBase].append(("asphericity", asphericity)) @warn_if_not_unique @_pbc_to_wrap @@ -1987,8 +2101,7 @@ def principal_axes(group, wrap=False): return e_vec - transplants[GroupBase].append( - ('principal_axes', principal_axes)) + transplants[GroupBase].append(("principal_axes", principal_axes)) def align_principal_axis(group, axis, vector): """Align principal axis with index `axis` with `vector`. @@ -2017,16 +2130,23 @@ def align_principal_axis(group, axis, vector): return group.rotateby(angle, ax) transplants[GroupBase].append( - ('align_principal_axis', align_principal_axis)) + ("align_principal_axis", align_principal_axis) + ) # TODO: update docs to property doc class Charges(AtomAttr): - attrname = 'charges' - singular = 'charge' - per_object = 'atom' - target_classes = [AtomGroup, ResidueGroup, SegmentGroup, - Atom, Residue, Segment] + attrname = "charges" + singular = "charge" + per_object = "atom" + target_classes = [ + AtomGroup, + ResidueGroup, + SegmentGroup, + Atom, + Residue, + Segment, + ] transplants = defaultdict(list) dtype = float @@ -2062,7 +2182,7 @@ def get_segments(self, sg): @_pbc_to_wrap @check_wrap_and_unwrap @check_atomgroup_not_empty - def center_of_charge(group, wrap=False, unwrap=False, compound='group'): + def center_of_charge(group, wrap=False, unwrap=False, compound="group"): r"""Center of (absolute) charge of (compounds of) the group .. math:: @@ -2120,18 +2240,18 @@ def center_of_charge(group, wrap=False, unwrap=False, compound='group'): .. versionadded:: 2.2.0 """ atoms = group.atoms - return atoms.center(weights=atoms.charges.__abs__(), - wrap=wrap, - compound=compound, - unwrap=unwrap) - + return atoms.center( + weights=atoms.charges.__abs__(), + wrap=wrap, + compound=compound, + unwrap=unwrap, + ) - transplants[GroupBase].append( - ('center_of_charge', center_of_charge)) + transplants[GroupBase].append(("center_of_charge", center_of_charge)) @warn_if_not_unique @check_atomgroup_not_empty - def total_charge(group, compound='group'): + def total_charge(group, compound="group"): r"""Total charge of (compounds of) the group. Computes the total charge of :class:`Atoms` in the group. @@ -2163,17 +2283,14 @@ def total_charge(group, compound='group'): """ return group.accumulate("charges", compound=compound) - transplants[GroupBase].append( - ('total_charge', total_charge)) + transplants[GroupBase].append(("total_charge", total_charge)) @warn_if_not_unique @_pbc_to_wrap @check_wrap_and_unwrap - def dipole_vector(group, - wrap=False, - unwrap=False, - compound='group', - center="mass"): + def dipole_vector( + group, wrap=False, unwrap=False, compound="group", center="mass" + ): r"""Dipole vector of the group. .. math:: @@ -2239,54 +2356,61 @@ def dipole_vector(group, if center == "mass": masses = atomgroup.masses - ref = atomgroup.center_of_mass(wrap=wrap, - unwrap=unwrap, - compound=compound) + ref = atomgroup.center_of_mass( + wrap=wrap, unwrap=unwrap, compound=compound + ) elif center == "charge": - ref = atomgroup.center_of_charge(wrap=wrap, - unwrap=unwrap, - compound=compound) + ref = atomgroup.center_of_charge( + wrap=wrap, unwrap=unwrap, compound=compound + ) else: choices = ["mass", "charge"] raise ValueError( f"The dipole center, {center}, is not supported. Choose" - " one of: {choices}") + " one of: {choices}" + ) - if compound == 'group': + if compound == "group": if wrap: - recenteredpos = (atomgroup.pack_into_box(inplace=False) - ref) + recenteredpos = atomgroup.pack_into_box(inplace=False) - ref elif unwrap: - recenteredpos = (atomgroup.unwrap( - inplace=False, - compound=compound, - reference=None, - ) - ref) + recenteredpos = ( + atomgroup.unwrap( + inplace=False, + compound=compound, + reference=None, + ) + - ref + ) else: - recenteredpos = (atomgroup.positions - ref) - dipole_vector = np.einsum('ij,ij->j',recenteredpos, - charges[:, np.newaxis]) + recenteredpos = atomgroup.positions - ref + dipole_vector = np.einsum( + "ij,ij->j", recenteredpos, charges[:, np.newaxis] + ) else: - (atom_masks, compound_masks, - n_compounds) = atomgroup._split_by_compound_indices(compound) + (atom_masks, compound_masks, n_compounds) = ( + atomgroup._split_by_compound_indices(compound) + ) if unwrap: - coords = atomgroup.unwrap(compound=compound, - reference=None, - inplace=False) + coords = atomgroup.unwrap( + compound=compound, reference=None, inplace=False + ) else: coords = atomgroup.positions chgs = atomgroup.charges dipole_vector = np.empty((n_compounds, 3), dtype=np.float64) for compound_mask, atom_mask in zip(compound_masks, atom_masks): - dipole_vector[compound_mask] = np.einsum('ijk,ijk->ik', - (coords[atom_mask]- - ref[compound_mask][:, None, :]), - chgs[atom_mask][:, :, None]) + dipole_vector[compound_mask] = np.einsum( + "ijk,ijk->ik", + (coords[atom_mask] - ref[compound_mask][:, None, :]), + chgs[atom_mask][:, :, None], + ) return dipole_vector - transplants[GroupBase].append(('dipole_vector', dipole_vector)) + transplants[GroupBase].append(("dipole_vector", dipole_vector)) def dipole_moment(group, **kwargs): r"""Dipole moment of the group or compounds in a group. @@ -2346,22 +2470,24 @@ def dipole_moment(group, **kwargs): dipole_vector = atomgroup.dipole_vector(**kwargs) if len(dipole_vector.shape) > 1: - dipole_moment = np.sqrt(np.einsum('ij,ij->i',dipole_vector,dipole_vector)) + dipole_moment = np.sqrt( + np.einsum("ij,ij->i", dipole_vector, dipole_vector) + ) else: - dipole_moment = np.sqrt(np.einsum('i,i->',dipole_vector,dipole_vector)) + dipole_moment = np.sqrt( + np.einsum("i,i->", dipole_vector, dipole_vector) + ) return dipole_moment - transplants[GroupBase].append(('dipole_moment', dipole_moment)) + transplants[GroupBase].append(("dipole_moment", dipole_moment)) @warn_if_not_unique @_pbc_to_wrap @check_wrap_and_unwrap - def quadrupole_tensor(group, - wrap=False, - unwrap=False, - compound='group', - center="mass"): + def quadrupole_tensor( + group, wrap=False, unwrap=False, compound="group", center="mass" + ): r"""Traceless quadrupole tensor of the group or compounds. This tensor is first computed as the outer product of vectors from @@ -2426,8 +2552,7 @@ def quadrupole_tensor(group, """ def __quadrupole_tensor(recenteredpos, charges): - """ Calculate the traceless quadrupole tensor - """ + """Calculate the traceless quadrupole tensor""" if len(charges.shape) > 1: charges = np.squeeze(charges) tensor = np.einsum( @@ -2444,39 +2569,44 @@ def __quadrupole_tensor(recenteredpos, charges): if center == "mass": masses = atomgroup.masses - ref = atomgroup.center_of_mass(wrap=wrap, - unwrap=unwrap, - compound=compound) + ref = atomgroup.center_of_mass( + wrap=wrap, unwrap=unwrap, compound=compound + ) elif center == "charge": - ref = atomgroup.center_of_charge(wrap=wrap, - unwrap=unwrap, - compound=compound) + ref = atomgroup.center_of_charge( + wrap=wrap, unwrap=unwrap, compound=compound + ) else: choices = ["mass", "charge"] raise ValueError( f"The quadrupole center, {center}, is not supported. Choose" - " one of: {choices}") + " one of: {choices}" + ) - if compound == 'group': + if compound == "group": if wrap: - recenteredpos = (atomgroup.pack_into_box(inplace=False) - ref) + recenteredpos = atomgroup.pack_into_box(inplace=False) - ref elif unwrap: - recenteredpos = (atomgroup.unwrap( - inplace=False, - compound=compound, - reference=None, - ) - ref) + recenteredpos = ( + atomgroup.unwrap( + inplace=False, + compound=compound, + reference=None, + ) + - ref + ) else: - recenteredpos = (atomgroup.positions - ref) + recenteredpos = atomgroup.positions - ref quad_tensor = __quadrupole_tensor(recenteredpos, charges) else: - (atom_masks, compound_masks, - n_compounds) = atomgroup._split_by_compound_indices(compound) + (atom_masks, compound_masks, n_compounds) = ( + atomgroup._split_by_compound_indices(compound) + ) if unwrap: - coords = atomgroup.unwrap(compound=compound, - reference=None, - inplace=False) + coords = atomgroup.unwrap( + compound=compound, reference=None, inplace=False + ) else: coords = atomgroup.positions chgs = atomgroup.charges @@ -2484,14 +2614,16 @@ def __quadrupole_tensor(recenteredpos, charges): quad_tensor = np.empty((n_compounds, 3, 3), dtype=np.float64) for compound_mask, atom_mask in zip(compound_masks, atom_masks): quad_tensor[compound_mask, :, :] = [ - __quadrupole_tensor(coords[mask] - ref[compound_mask][i], - chgs[mask][:, None]) + __quadrupole_tensor( + coords[mask] - ref[compound_mask][i], + chgs[mask][:, None], + ) for i, mask in enumerate(atom_mask) ] return quad_tensor - transplants[GroupBase].append(('quadrupole_tensor', quadrupole_tensor)) + transplants[GroupBase].append(("quadrupole_tensor", quadrupole_tensor)) def quadrupole_moment(group, **kwargs): r"""Quadrupole moment of the group according to :footcite:p:`Gray1984`. @@ -2557,18 +2689,21 @@ def __quadrupole_moment(tensor): if len(quad_tensor.shape) == 2: quad_moment = __quadrupole_moment(quad_tensor) else: - quad_moment = np.array([__quadrupole_moment(x) for x in quad_tensor]) + quad_moment = np.array( + [__quadrupole_moment(x) for x in quad_tensor] + ) return quad_moment - transplants[GroupBase].append(('quadrupole_moment', quadrupole_moment)) + transplants[GroupBase].append(("quadrupole_moment", quadrupole_moment)) class FormalCharges(AtomAttr): """Formal charge on each atom""" - attrname = 'formalcharges' - singular = 'formalcharge' - per_object = 'atom' + + attrname = "formalcharges" + singular = "formalcharge" + per_object = "atom" dtype = int @staticmethod @@ -2578,9 +2713,9 @@ def _gen_initial_values(na, nr, ns): # TODO: update docs to property doc class Occupancies(AtomAttr): - attrname = 'occupancies' - singular = 'occupancy' - per_object = 'atom' + attrname = "occupancies" + singular = "occupancy" + per_object = "atom" dtype = float @staticmethod @@ -2591,21 +2726,23 @@ def _gen_initial_values(na, nr, ns): # TODO: update docs to property doc class AltLocs(AtomStringAttr): """AltLocs for each atom""" - attrname = 'altLocs' - singular = 'altLoc' - per_object = 'atom' + + attrname = "altLocs" + singular = "altLoc" + per_object = "atom" dtype = object @staticmethod def _gen_initial_values(na, nr, ns): - return np.array(['' for _ in range(na)], dtype=object) + return np.array(["" for _ in range(na)], dtype=object) class GBScreens(AtomAttr): """Generalized Born screening factor""" - attrname = 'gbscreens' - singular = 'gbscreen' - per_object = 'atom' + + attrname = "gbscreens" + singular = "gbscreen" + per_object = "atom" dtype = float @staticmethod @@ -2615,9 +2752,10 @@ def _gen_initial_values(na, nr, ns): class SolventRadii(AtomAttr): """Intrinsic solvation radius""" - attrname = 'solventradii' - singular = 'solventradius' - per_object = 'atom' + + attrname = "solventradii" + singular = "solventradius" + per_object = "atom" dtype = float @staticmethod @@ -2627,9 +2765,10 @@ def _gen_initial_values(na, nr, ns): class NonbondedIndices(AtomAttr): """Nonbonded index (AMBER)""" - attrname = 'nbindices' - singular = 'nbindex' - per_object = 'atom' + + attrname = "nbindices" + singular = "nbindex" + per_object = "atom" dtype = int @staticmethod @@ -2639,9 +2778,10 @@ def _gen_initial_values(na, nr, ns): class RMins(AtomAttr): """The Rmin/2 LJ parameter""" - attrname = 'rmins' - singular = 'rmin' - per_object = 'atom' + + attrname = "rmins" + singular = "rmin" + per_object = "atom" dtype = float @staticmethod @@ -2651,9 +2791,10 @@ def _gen_initial_values(na, nr, ns): class Epsilons(AtomAttr): """The epsilon LJ parameter""" - attrname = 'epsilons' - singular = 'epsilon' - per_object = 'atom' + + attrname = "epsilons" + singular = "epsilon" + per_object = "atom" dtype = float @staticmethod @@ -2663,9 +2804,10 @@ def _gen_initial_values(na, nr, ns): class RMin14s(AtomAttr): """The Rmin/2 LJ parameter for 1-4 interactions""" - attrname = 'rmin14s' - singular = 'rmin14' - per_object = 'atom' + + attrname = "rmin14s" + singular = "rmin14" + per_object = "atom" dtype = float @staticmethod @@ -2675,9 +2817,10 @@ def _gen_initial_values(na, nr, ns): class Epsilon14s(AtomAttr): """The epsilon LJ parameter for 1-4 interactions""" - attrname = 'epsilon14s' - singular = 'epsilon14' - per_object = 'atom' + + attrname = "epsilon14s" + singular = "epsilon14" + per_object = "atom" dtype = float @staticmethod @@ -2687,6 +2830,7 @@ def _gen_initial_values(na, nr, ns): class Aromaticities(AtomAttr): """Aromaticity""" + attrname = "aromaticities" singular = "aromaticity" per_object = "atom" @@ -2699,16 +2843,17 @@ def _gen_initial_values(na, nr, ns): class RSChirality(AtomAttr): """R/S chirality""" - attrname = 'chiralities' - singular= 'chirality' - dtype = 'U1' + + attrname = "chiralities" + singular = "chirality" + dtype = "U1" class ResidueAttr(TopologyAttr): - attrname = 'residueattrs' - singular = 'residueattr' + attrname = "residueattrs" + singular = "residueattr" target_classes = [AtomGroup, ResidueGroup, SegmentGroup, Atom, Residue] - per_object = 'residue' + per_object = "residue" def get_atoms(self, ag): rix = self.top.tt.atoms2residues(ag.ix) @@ -2747,14 +2892,15 @@ def set_residues(self, ag, values): @staticmethod def _gen_initial_values(na, nr, ns): - return np.full(nr, '', dtype=object) + return np.full(nr, "", dtype=object) # TODO: update docs to property doc class Resids(ResidueAttr): """Residue ID""" - attrname = 'resids' - singular = 'resid' + + attrname = "resids" + singular = "resid" dtype = int @staticmethod @@ -2764,14 +2910,14 @@ def _gen_initial_values(na, nr, ns): # TODO: update docs to property doc class Resnames(ResidueStringAttr): - attrname = 'resnames' - singular = 'resname' + attrname = "resnames" + singular = "resname" transplants = defaultdict(list) dtype = object @staticmethod def _gen_initial_values(na, nr, ns): - return np.array(['' for _ in range(nr)], dtype=object) + return np.array(["" for _ in range(nr)], dtype=object) def sequence(self, **kwargs): """Returns the amino acid sequence. @@ -2852,24 +2998,32 @@ def sequence(self, **kwargs): Biopython is now an optional dependency """ if not HAS_BIOPYTHON: - errmsg = ("The `sequence_alignment` method requires an " - "installation of `Biopython`. Please install " - "`Biopython` to use this method: " - "https://biopython.org/wiki/Download") + errmsg = ( + "The `sequence_alignment` method requires an " + "installation of `Biopython`. Please install " + "`Biopython` to use this method: " + "https://biopython.org/wiki/Download" + ) raise ImportError(errmsg) - formats = ('string', 'Seq', 'SeqRecord') + formats = ("string", "Seq", "SeqRecord") format = kwargs.pop("format", "SeqRecord") if format not in formats: - raise TypeError("Unknown format='{0}': must be one of: {1}".format( - format, ", ".join(formats))) + raise TypeError( + "Unknown format='{0}': must be one of: {1}".format( + format, ", ".join(formats) + ) + ) try: - sequence = "".join([convert_aa_code(r) - for r in self.residues.resnames]) + sequence = "".join( + [convert_aa_code(r) for r in self.residues.resnames] + ) except KeyError as err: - errmsg = (f"AtomGroup contains a residue name '{err.message}' that" - f" does not have a IUPAC protein 1-letter character") + errmsg = ( + f"AtomGroup contains a residue name '{err.message}' that" + f" does not have a IUPAC protein 1-letter character" + ) raise ValueError(errmsg) from None if format == "string": return sequence @@ -2878,14 +3032,13 @@ def sequence(self, **kwargs): return seq return Bio.SeqRecord.SeqRecord(seq, **kwargs) - transplants[ResidueGroup].append( - ('sequence', sequence)) + transplants[ResidueGroup].append(("sequence", sequence)) # TODO: update docs to property doc class Resnums(ResidueAttr): - attrname = 'resnums' - singular = 'resnum' + attrname = "resnums" + singular = "resnum" dtype = int @staticmethod @@ -2895,8 +3048,9 @@ def _gen_initial_values(na, nr, ns): class ICodes(ResidueStringAttr): """Insertion code for Atoms""" - attrname = 'icodes' - singular = 'icode' + + attrname = "icodes" + singular = "icode" dtype = object @@ -2905,16 +3059,17 @@ class Moltypes(ResidueStringAttr): Two molecules that share a molecule type share a common template topology. """ - attrname = 'moltypes' - singular = 'moltype' + + attrname = "moltypes" + singular = "moltype" dtype = object class Molnums(ResidueAttr): - """Index of molecule from 0 - """ - attrname = 'molnums' - singular = 'molnum' + """Index of molecule from 0""" + + attrname = "molnums" + singular = "molnum" dtype = np.intp @@ -2922,14 +3077,19 @@ class Molnums(ResidueAttr): class SegmentAttr(TopologyAttr): - """Base class for segment attributes. - - """ - attrname = 'segmentattrs' - singular = 'segmentattr' - target_classes = [AtomGroup, ResidueGroup, - SegmentGroup, Atom, Residue, Segment] - per_object = 'segment' + """Base class for segment attributes.""" + + attrname = "segmentattrs" + singular = "segmentattr" + target_classes = [ + AtomGroup, + ResidueGroup, + SegmentGroup, + Atom, + Residue, + Segment, + ] + per_object = "segment" def get_atoms(self, ag): six = self.top.tt.atoms2segments(ag.ix) @@ -2963,19 +3123,19 @@ def set_segments(self, ag, values): @staticmethod def _gen_initial_values(na, nr, ns): - return np.full(ns, '', dtype=object) + return np.full(ns, "", dtype=object) # TODO: update docs to property doc class Segids(SegmentStringAttr): - attrname = 'segids' - singular = 'segid' + attrname = "segids" + singular = "segid" transplants = defaultdict(list) dtype = object @staticmethod def _gen_initial_values(na, nr, ns): - return np.array(['' for _ in range(ns)], dtype=object) + return np.array(["" for _ in range(ns)], dtype=object) def _check_connection_values(func): @@ -2991,12 +3151,16 @@ def _check_connection_values(func): @functools.wraps(func) def wrapper(self, values, *args, **kwargs): - if not all(len(x) == self._n_atoms - and all(isinstance(y, (int, np.integer)) for y in x) - for x in values): - raise ValueError(("{} must be an iterable of tuples with {}" - " atom indices").format(self.attrname, - self._n_atoms)) + if not all( + len(x) == self._n_atoms + and all(isinstance(y, (int, np.integer)) for y in x) + for x in values + ): + raise ValueError( + ( + "{} must be an iterable of tuples with {}" " atom indices" + ).format(self.attrname, self._n_atoms) + ) clean = [] for v in values: if v[0] > v[-1]: @@ -3019,13 +3183,12 @@ class _ConnectionTopologyAttrMeta(_TopologyAttrMeta): def __init__(cls, name, bases, classdict): super().__init__(name, bases, classdict) - attrname = classdict.get('attrname') + attrname = classdict.get("attrname") if attrname is not None: def intra_connection(self, ag): - """Get connections only within this AtomGroup - """ + """Get connections only within this AtomGroup""" return ag.get_connections(attrname, outside=False) method = MethodType(intra_connection, cls) @@ -3058,22 +3221,25 @@ def __init__(self, values, types=None, guessed=False, order=None): def copy(self): """Return a deepcopy of this attribute""" - return self.__class__(copy.copy(self.values), - copy.copy(self.types), - copy.copy(self._guessed), - copy.copy(self.order)) + return self.__class__( + copy.copy(self.values), + copy.copy(self.types), + copy.copy(self._guessed), + copy.copy(self.order), + ) def __len__(self): return len(self._bondDict) @property - @cached('bd') + @cached("bd") def _bondDict(self): """Lazily built mapping of atoms:bonds""" bd = defaultdict(list) - for b, t, g, o in zip(self.values, self.types, - self._guessed, self.order): + for b, t, g, o in zip( + self.values, self.types, self._guessed, self.order + ): for a in b: bd[a].append((b, t, g, o)) return bd @@ -3092,8 +3258,9 @@ def get_atoms(self, ag): """ try: - unique_bonds = set(itertools.chain( - *[self._bondDict[a] for a in ag.ix])) + unique_bonds = set( + itertools.chain(*[self._bondDict[a] for a in ag.ix]) + ) except TypeError: # maybe we got passed an Atom unique_bonds = self._bondDict[ag.ix] @@ -3103,11 +3270,9 @@ def get_atoms(self, ag): types = types.ravel() guessed = guessed.ravel() order = order.ravel() - return TopologyGroup(bond_idx, ag.universe, - self.singular[:-1], - types, - guessed, - order) + return TopologyGroup( + bond_idx, ag.universe, self.singular[:-1], types, guessed, order + ) @_check_connection_values def _add_bonds(self, values, types=None, guessed=True, order=None): @@ -3126,7 +3291,7 @@ def _add_bonds(self, values, types=None, guessed=True, order=None): self.order.append(o) # kill the old cache of bond Dict try: - del self._cache['bd'] + del self._cache["bd"] except KeyError: pass @@ -3139,24 +3304,27 @@ def _delete_bonds(self, values): to_check = set(values) self_values = set(self.values) if not to_check.issubset(self_values): - missing = to_check-self_values - indices = ', '.join(map(str, missing)) - raise ValueError(('Cannot delete nonexistent ' - '{attrname} with atom indices:' - '{indices}').format(attrname=self.attrname, - indices=indices)) + missing = to_check - self_values + indices = ", ".join(map(str, missing)) + raise ValueError( + ( + "Cannot delete nonexistent " + "{attrname} with atom indices:" + "{indices}" + ).format(attrname=self.attrname, indices=indices) + ) # allow multiple matches idx = [i for i, x in enumerate(self.values) if x in to_check] for i in sorted(idx, reverse=True): del self.values[i] - for attr in ('types', '_guessed', 'order'): - arr = np.array(getattr(self, attr), dtype='object') + for attr in ("types", "_guessed", "order"): + arr = np.array(getattr(self, attr), dtype="object") new = np.delete(arr, idx) setattr(self, attr, list(new)) # kill the old cache of bond Dict try: - del self._cache['bd'] + del self._cache["bd"] except KeyError: pass @@ -3171,10 +3339,11 @@ class Bonds(_Connection): Also adds the `bonded_atoms`, `fragment` and `fragments` attributes. """ - attrname = 'bonds' + + attrname = "bonds" # Singular is the same because one Atom might have # many bonds, so still asks for "bonds" in the plural - singular = 'bonds' + singular = "bonds" transplants = defaultdict(list) _n_atoms = 2 @@ -3186,8 +3355,11 @@ def bonded_atoms(self): return self.universe.atoms[idx] transplants[Atom].append( - ('bonded_atoms', property(bonded_atoms, None, None, - bonded_atoms.__doc__))) + ( + "bonded_atoms", + property(bonded_atoms, None, None, bonded_atoms.__doc__), + ) + ) def fragindex(self): """The index (ID) of the @@ -3199,7 +3371,7 @@ def fragindex(self): """ return self.universe._fragdict[self.ix].ix - @cached('fragindices', universe_validation=True) + @cached("fragindices", universe_validation=True) def fragindices(self): r"""The :class:`fragment indices` @@ -3233,7 +3405,7 @@ def fragment(self): """ return self.universe._fragdict[self.ix].fragment - @cached('fragments', universe_validation=True) + @cached("fragments", universe_validation=True) def fragments(self): """Read-only :class:`tuple` of :class:`fragments`. @@ -3259,8 +3431,12 @@ def fragments(self): .. versionadded:: 0.9.0 """ fragdict = self.universe._fragdict - return tuple(sorted(set(fragdict[aix].fragment for aix in self.ix), - key=lambda x: x[0].ix)) + return tuple( + sorted( + set(fragdict[aix].fragment for aix in self.ix), + key=lambda x: x[0].ix, + ) + ) def n_fragments(self): """The number of unique @@ -3274,24 +3450,24 @@ def n_fragments(self): return len(unique_int_1d(self.fragindices)) transplants[Atom].append( - ('fragment', property(fragment, None, None, - fragment.__doc__))) + ("fragment", property(fragment, None, None, fragment.__doc__)) + ) transplants[Atom].append( - ('fragindex', property(fragindex, None, None, - fragindex.__doc__))) + ("fragindex", property(fragindex, None, None, fragindex.__doc__)) + ) transplants[AtomGroup].append( - ('fragments', property(fragments, None, None, - fragments.__doc__))) + ("fragments", property(fragments, None, None, fragments.__doc__)) + ) transplants[AtomGroup].append( - ('fragindices', property(fragindices, None, None, - fragindices.__doc__))) + ("fragindices", property(fragindices, None, None, fragindices.__doc__)) + ) transplants[AtomGroup].append( - ('n_fragments', property(n_fragments, None, None, - n_fragments.__doc__))) + ("n_fragments", property(n_fragments, None, None, n_fragments.__doc__)) + ) class UreyBradleys(_Connection): @@ -3303,8 +3479,9 @@ class UreyBradleys(_Connection): .. versionadded:: 1.0.0 """ - attrname = 'ureybradleys' - singular = 'ureybradleys' + + attrname = "ureybradleys" + singular = "ureybradleys" transplants = defaultdict(list) _n_atoms = 2 @@ -3317,24 +3494,27 @@ class Angles(_Connection): These indices refer to the atom indices. """ - attrname = 'angles' - singular = 'angles' + + attrname = "angles" + singular = "angles" transplants = defaultdict(list) _n_atoms = 3 class Dihedrals(_Connection): """A connection between four sequential atoms""" - attrname = 'dihedrals' - singular = 'dihedrals' + + attrname = "dihedrals" + singular = "dihedrals" transplants = defaultdict(list) _n_atoms = 4 class Impropers(_Connection): """An imaginary dihedral between four atoms""" - attrname = 'impropers' - singular = 'impropers' + + attrname = "impropers" + singular = "impropers" transplants = defaultdict(list) _n_atoms = 4 @@ -3344,7 +3524,8 @@ class CMaps(_Connection): A connection between five atoms .. versionadded:: 1.0.0 """ - attrname = 'cmaps' - singular = 'cmaps' + + attrname = "cmaps" + singular = "cmaps" transplants = defaultdict(list) _n_atoms = 5 diff --git a/package/MDAnalysis/core/topologyobjects.py b/package/MDAnalysis/core/topologyobjects.py index 436ecc5dd5d..fcc37be6246 100644 --- a/package/MDAnalysis/core/topologyobjects.py +++ b/package/MDAnalysis/core/topologyobjects.py @@ -40,7 +40,6 @@ @functools.total_ordering class TopologyObject(object): - """Base class for all Topology items. Defines the behaviour by which Bonds/Angles/etc in MDAnalysis should @@ -55,6 +54,7 @@ class TopologyObject(object): .. versionchanged:: 2.6.0 Updated Atom ID representation order to match that of AtomGroup indices """ + __slots__ = ("_ix", "_u", "btype", "_bondtype", "_guessed", "order") def __init__(self, ix, universe, type=None, guessed=False, order=None): @@ -121,9 +121,8 @@ def __repr__(self): """Return representation in same order of AtomGroup indices""" return "<{cname} between: {conts}>".format( cname=self.__class__.__name__, - conts=", ".join([ - "Atom {0}".format(i) - for i in self.indices])) + conts=", ".join(["Atom {0}".format(i) for i in self.indices]), + ) def __contains__(self, other): """Check whether an atom is in this :class:`TopologyObject`""" @@ -133,8 +132,9 @@ def __eq__(self, other): """Check whether two bonds have identical contents""" if not self.universe == other.universe: return False - return (np.array_equal(self.indices, other.indices) or - np.array_equal(self.indices[::-1], other.indices)) + return np.array_equal(self.indices, other.indices) or np.array_equal( + self.indices[::-1], other.indices + ) def __ne__(self, other): return not self == other @@ -154,7 +154,6 @@ def __len__(self): class Bond(TopologyObject): - """A bond between two :class:`~MDAnalysis.core.groups.Atom` instances. Two :class:`Bond` instances can be compared with the ``==`` and @@ -173,7 +172,8 @@ class Bond(TopologyObject): Now a subclass of :class:`TopologyObject`. Changed class to use :attr:`__slots__` and stores atoms in :attr:`atoms` attribute. """ - btype = 'bond' + + btype = "bond" def partner(self, atom): """Bond.partner(Atom) @@ -206,7 +206,6 @@ def length(self, pbc=True): class Angle(TopologyObject): - """An angle between three :class:`~MDAnalysis.core.groups.Atom` instances. Atom 2 is the apex of the angle @@ -215,7 +214,8 @@ class Angle(TopologyObject): Now a subclass of :class:`TopologyObject`; now uses :attr:`__slots__` and stores atoms in :attr:`atoms` attribute """ - btype = 'angle' + + btype = "angle" def angle(self, pbc=True): """Returns the angle in degrees of this Angle. @@ -240,14 +240,16 @@ def angle(self, pbc=True): """ box = self.universe.dimensions if pbc else None - return np.rad2deg(distances.calc_angles( - self[0].position, self[1].position, self[2].position, box)) + return np.rad2deg( + distances.calc_angles( + self[0].position, self[1].position, self[2].position, box + ) + ) value = angle class Dihedral(TopologyObject): - """Dihedral (dihedral angle) between four :class:`~MDAnalysis.core.groups.Atom` instances. @@ -262,8 +264,9 @@ class Dihedral(TopologyObject): Renamed to Dihedral (was Torsion) """ + # http://cbio.bmt.tue.nl/pumma/uploads/Theory/dihedral.png - btype = 'dihedral' + btype = "dihedral" def dihedral(self, pbc=True): """Calculate the dihedral angle in degrees. @@ -290,8 +293,11 @@ def dihedral(self, pbc=True): box = self.universe.dimensions if pbc else None A, B, C, D = self.atoms - return np.rad2deg(distances.calc_dihedrals( - A.position, B.position, C.position, D.position, box)) + return np.rad2deg( + distances.calc_dihedrals( + A.position, B.position, C.position, D.position, box + ) + ) value = dihedral @@ -313,8 +319,9 @@ class ImproperDihedral(Dihedral): .. versionchanged:: 0.11.0 Renamed to ImproperDihedral (was Improper_Torsion) """ + # http://cbio.bmt.tue.nl/pumma/uploads/Theory/improper.png - btype = 'improper' + btype = "improper" def improper(self): """Improper dihedral angle in degrees. @@ -328,7 +335,6 @@ def improper(self): class UreyBradley(TopologyObject): - """A Urey-Bradley angle between two :class:`~MDAnalysis.core.groups.Atom` instances. Two :class:`UreyBradley` instances can be compared with the ``==`` and ``!=`` operators. A UreyBradley angle is equal to another if the same atom @@ -336,7 +342,8 @@ class UreyBradley(TopologyObject): .. versionadded:: 1.0.0 """ - btype = 'ureybradley' + + btype = "ureybradley" def partner(self, atom): """UreyBradley.partner(Atom) @@ -353,8 +360,7 @@ def partner(self, atom): raise ValueError("Unrecognised Atom") def distance(self, pbc=True): - """Distance between the atoms. - """ + """Distance between the atoms.""" box = self.universe.dimensions if pbc else None return distances.calc_bonds(self[0].position, self[1].position, box) @@ -363,16 +369,16 @@ def distance(self, pbc=True): class CMap(TopologyObject): """ - Coupled-torsion correction map term between five + Coupled-torsion correction map term between five :class:`~MDAnalysis.core.groups.Atom` instances. .. versionadded:: 1.0.0 """ - btype = 'cmap' + btype = "cmap" -class TopologyDict(object): +class TopologyDict(object): """A customised dictionary designed for sorting the bonds, angles and dihedrals present in a group of atoms. @@ -490,7 +496,8 @@ def __iter__(self): def __repr__(self): return "".format( - num=len(self), type=self.toptype) + num=len(self), type=self.toptype + ) def __getitem__(self, key): """Returns a TopologyGroup matching the criteria if possible, @@ -517,12 +524,17 @@ def __contains__(self, other): return other in self.dict or other[::-1] in self.dict -_BTYPE_TO_SHAPE = {'bond': 2, 'ureybradley': 2, 'angle': 3, - 'dihedral': 4, 'improper': 4, 'cmap': 5} +_BTYPE_TO_SHAPE = { + "bond": 2, + "ureybradley": 2, + "angle": 3, + "dihedral": 4, + "improper": 4, + "cmap": 5, +} class TopologyGroup(object): - """A container for a groups of bonds. All bonds of a certain types can be retrieved from within the @@ -577,20 +589,30 @@ class TopologyGroup(object): ``type``, ``guessed``, and ``order`` are no longer reshaped to arrays with an extra dimension """ - def __init__(self, bondidx, universe, btype=None, type=None, guessed=None, - order=None): + + def __init__( + self, + bondidx, + universe, + btype=None, + type=None, + guessed=None, + order=None, + ): if btype is None: # guess what I am # difference between dihedral and improper # not really important - self.btype = {2: 'bond', - 3: 'angle', - 4: 'dihedral'}[len(bondidx[0])] + self.btype = {2: "bond", 3: "angle", 4: "dihedral"}[ + len(bondidx[0]) + ] elif btype in _BTYPE_TO_SHAPE: self.btype = btype else: - raise ValueError("Unsupported btype, use one of '{}'" - "".format(', '.join(_BTYPE_TO_SHAPE))) + raise ValueError( + "Unsupported btype, use one of '{}'" + "".format(", ".join(_BTYPE_TO_SHAPE)) + ) bondidx = np.asarray(bondidx) nbonds = len(bondidx) @@ -615,8 +637,10 @@ def __init__(self, bondidx, universe, btype=None, type=None, guessed=None, self._order = order[uniq_idx] # Create vertical AtomGroups - self._ags = [universe.atoms[self._bix[:, i]] - for i in range(self._bix.shape[1])] + self._ags = [ + universe.atoms[self._bix[:, i]] + for i in range(self._bix.shape[1]) + ] else: # Empty TopologyGroup self._bix = np.array([]) @@ -652,7 +676,7 @@ def types(self): return list(self.topDict.keys()) @property - @cached('dict') + @cached("dict") def topDict(self): """ Returns the TopologyDict for this topology group. @@ -689,7 +713,7 @@ def atomgroup_intersection(self, ag, **kwargs): # Strict requires all items in a row to be seen, # otherwise any item in a row - func = np.all if kwargs.get('strict', False) else np.any + func = np.all if kwargs.get("strict", False) else np.any atom_idx = ag.indices # Create a list of boolean arrays, @@ -752,9 +776,11 @@ def __add__(self, other): """ # check addition is sane if not isinstance(other, (TopologyObject, TopologyGroup)): - raise TypeError("Can only combine TopologyObject or " - "TopologyGroup to TopologyGroup, not {0}" - "".format(type(other))) + raise TypeError( + "Can only combine TopologyObject or " + "TopologyGroup to TopologyGroup, not {0}" + "".format(type(other)) + ) # cases where either other or self is empty TG if not other: # adding empty TG to me @@ -762,37 +788,43 @@ def __add__(self, other): if not self: if isinstance(other, TopologyObject): # Reshape indices to be 2d array - return TopologyGroup(other.indices[None, :], - other.universe, - btype=other.btype, - type=np.array([other._bondtype]), - guessed=np.array([other.is_guessed]), - order=np.array([other.order]), - ) + return TopologyGroup( + other.indices[None, :], + other.universe, + btype=other.btype, + type=np.array([other._bondtype]), + guessed=np.array([other.is_guessed]), + order=np.array([other.order]), + ) else: - return TopologyGroup(other.indices, - other.universe, - btype=other.btype, - type=other._bondtypes, - guessed=other._guessed, - order=other._order, - ) + return TopologyGroup( + other.indices, + other.universe, + btype=other.btype, + type=other._bondtypes, + guessed=other._guessed, + order=other._order, + ) else: if not other.btype == self.btype: - raise TypeError("Cannot add different types of " - "TopologyObjects together") + raise TypeError( + "Cannot add different types of " "TopologyObjects together" + ) if isinstance(other, TopologyObject): # add TO to me return TopologyGroup( np.concatenate([self.indices, other.indices[None, :]]), self.universe, btype=self.btype, - type=np.concatenate([self._bondtypes, - np.array([other._bondtype])]), - guessed=np.concatenate([self._guessed, - np.array([other.is_guessed])]), - order=np.concatenate([self._order, - np.array([other.order])]), + type=np.concatenate( + [self._bondtypes, np.array([other._bondtype])] + ), + guessed=np.concatenate( + [self._guessed, np.array([other.is_guessed])] + ), + order=np.concatenate( + [self._order, np.array([other.order])] + ), ) else: # add TG to me @@ -814,25 +846,31 @@ def __getitem__(self, item): """ # Grab a single Item, similar to Atom/AtomGroup relationship if isinstance(item, numbers.Integral): - outclass = {'bond': Bond, - 'angle': Angle, - 'dihedral': Dihedral, - 'improper': ImproperDihedral, - 'ureybradley': UreyBradley, - 'cmap': CMap}[self.btype] - return outclass(self._bix[item], - self._u, - type=self._bondtypes[item], - guessed=self._guessed[item], - order=self._order[item]) + outclass = { + "bond": Bond, + "angle": Angle, + "dihedral": Dihedral, + "improper": ImproperDihedral, + "ureybradley": UreyBradley, + "cmap": CMap, + }[self.btype] + return outclass( + self._bix[item], + self._u, + type=self._bondtypes[item], + guessed=self._guessed[item], + order=self._order[item], + ) else: # Slice my index array with the item - return self.__class__(self._bix[item], - self._u, - btype=self.btype, - type=self._bondtypes[item], - guessed=self._guessed[item], - order=self._order[item],) + return self.__class__( + self._bix[item], + self._u, + btype=self.btype, + type=self._bondtypes[item], + guessed=self._guessed[item], + order=self._order[item], + ) def __contains__(self, item): """Tests if this TopologyGroup contains a bond""" @@ -840,7 +878,8 @@ def __contains__(self, item): def __repr__(self): return "".format( - num=len(self), type=self.btype) + num=len(self), type=self.btype + ) def __eq__(self, other): """Test if contents of TopologyGroups are equal""" @@ -869,8 +908,10 @@ def atom3(self): return self._ags[2] except IndexError: nvert = _BTYPE_TO_SHAPE[self.btype] - errmsg = (f"TopologyGroup of {self.btype}s only has {nvert} " - f"vertical AtomGroups") + errmsg = ( + f"TopologyGroup of {self.btype}s only has {nvert} " + f"vertical AtomGroups" + ) raise IndexError(errmsg) from None @property @@ -880,8 +921,10 @@ def atom4(self): return self._ags[3] except IndexError: nvert = _BTYPE_TO_SHAPE[self.btype] - errmsg = (f"TopologyGroup of {self.btype}s only has {nvert} " - f"vertical AtomGroups") + errmsg = ( + f"TopologyGroup of {self.btype}s only has {nvert} " + f"vertical AtomGroups" + ) raise IndexError(errmsg) from None # Distance calculation methods below @@ -899,13 +942,13 @@ def values(self, **kwargs): .. versionadded:: 0.11.0 """ - if self.btype == 'bond': + if self.btype == "bond": return self.bonds(**kwargs) - elif self.btype == 'angle': + elif self.btype == "angle": return self.angles(**kwargs) - elif self.btype == 'dihedral': + elif self.btype == "dihedral": return self.dihedrals(**kwargs) - elif self.btype == 'improper': + elif self.btype == "improper": return self.dihedrals(**kwargs) def _calc_connection_values(self, func, *btypes, result=None, pbc=False): @@ -931,8 +974,9 @@ def bonds(self, pbc=False, result=None): Uses cython implementation """ - return self._calc_connection_values(distances.calc_bonds, "bond", - pbc=pbc, result=result) + return self._calc_connection_values( + distances.calc_bonds, "bond", pbc=pbc, result=result + ) def angles(self, result=None, pbc=False): """Calculates the angle in radians formed between a bond @@ -956,8 +1000,9 @@ def angles(self, result=None, pbc=False): Added *pbc* option (default ``False``) """ - return self._calc_connection_values(distances.calc_angles, "angle", - pbc=pbc, result=result) + return self._calc_connection_values( + distances.calc_angles, "angle", pbc=pbc, result=result + ) def dihedrals(self, result=None, pbc=False): """Calculate the dihedral angle in radians for this topology @@ -983,6 +1028,10 @@ def dihedrals(self, result=None, pbc=False): .. versionchanged:: 0.9.0 Added *pbc* option (default ``False``) """ - return self._calc_connection_values(distances.calc_dihedrals, - "dihedral", "improper", - pbc=pbc, result=result) + return self._calc_connection_values( + distances.calc_dihedrals, + "dihedral", + "improper", + pbc=pbc, + result=result, + ) diff --git a/package/pyproject.toml b/package/pyproject.toml index 8be16c7f61a..04e76fdbfed 100644 --- a/package/pyproject.toml +++ b/package/pyproject.toml @@ -142,6 +142,7 @@ tables\.py | MDAnalysis/coordinates/.*\.py | MDAnalysis/tests/.*\.py | MDAnalysis/selections/.*\.py +| MDAnalysis/core/.*\.py ) ''' extend-exclude = ''' diff --git a/testsuite/MDAnalysisTests/core/test_accessors.py b/testsuite/MDAnalysisTests/core/test_accessors.py index 9b3d22ffd0e..8084fa105ae 100644 --- a/testsuite/MDAnalysisTests/core/test_accessors.py +++ b/testsuite/MDAnalysisTests/core/test_accessors.py @@ -25,8 +25,9 @@ from MDAnalysisTests.util import import_not_available -requires_rdkit = pytest.mark.skipif(import_not_available("rdkit"), - reason="requires RDKit") +requires_rdkit = pytest.mark.skipif( + import_not_available("rdkit"), reason="requires RDKit" +) @requires_rdkit @@ -52,20 +53,25 @@ def test_convert_to_lib_method_kwargs(self, u): class TestAccessor: def test_access_from_class(self): - assert (mda.core.AtomGroup.convert_to is - mda.core.accessors.ConverterWrapper) + assert ( + mda.core.AtomGroup.convert_to + is mda.core.accessors.ConverterWrapper + ) class TestConverterWrapper: def test_raises_valueerror(self): u = mda.Universe.empty(1) - with pytest.raises(ValueError, - match="No 'mdanalysis' converter found"): + with pytest.raises( + ValueError, match="No 'mdanalysis' converter found" + ): u.atoms.convert_to("mdanalysis") @requires_rdkit def test_single_instance(self): u1 = mda.Universe.from_smiles("C") u2 = mda.Universe.from_smiles("CC") - assert (u1.atoms.convert_to.rdkit.__wrapped__ is - u2.atoms.convert_to.rdkit.__wrapped__) + assert ( + u1.atoms.convert_to.rdkit.__wrapped__ + is u2.atoms.convert_to.rdkit.__wrapped__ + ) diff --git a/testsuite/MDAnalysisTests/core/test_accumulate.py b/testsuite/MDAnalysisTests/core/test_accumulate.py index b93a458d06e..2aa1fd29099 100644 --- a/testsuite/MDAnalysisTests/core/test_accumulate.py +++ b/testsuite/MDAnalysisTests/core/test_accumulate.py @@ -29,7 +29,7 @@ from MDAnalysisTests.core.util import UnWrapUniverse import pytest -levels = ('atoms', 'residues', 'segments') +levels = ("atoms", "residues", "segments") class TestAccumulate(object): @@ -41,18 +41,26 @@ def group(self, request): return getattr(u, request.param) def test_accumulate_str_attribute(self, group): - assert_almost_equal(group.accumulate("masses"), np.sum(group.atoms.masses)) + assert_almost_equal( + group.accumulate("masses"), np.sum(group.atoms.masses) + ) def test_accumulate_different_func(self, group): assert_almost_equal( group.accumulate("masses", function=np.prod), - np.prod(group.atoms.masses)) - - @pytest.mark.parametrize('name, compound', (('resindices', 'residues'), - ('segindices', 'segments'), - ('molnums', 'molecules'), - ('fragindices', 'fragments'))) - @pytest.mark.parametrize('level', levels) + np.prod(group.atoms.masses), + ) + + @pytest.mark.parametrize( + "name, compound", + ( + ("resindices", "residues"), + ("segindices", "segments"), + ("molnums", "molecules"), + ("fragindices", "fragments"), + ), + ) + @pytest.mark.parametrize("level", levels) def test_accumulate_str_attribute_compounds(self, name, compound, level): u = UnWrapUniverse() group = getattr(u, level) @@ -68,13 +76,13 @@ def test_accumulate_wrongcomponent(self, group): with pytest.raises(ValueError): group.accumulate("masses", compound="foo") - @pytest.mark.parametrize('level', levels) + @pytest.mark.parametrize("level", levels) def test_accumulate_nobonds(self, level): group = getattr(mda.Universe(GRO), level) with pytest.raises(NoDataError): group.accumulate("masses", compound="fragments") - @pytest.mark.parametrize('level', levels) + @pytest.mark.parametrize("level", levels) def test_accumulate_nomolnums(self, level): group = getattr(mda.Universe(GRO), level) with pytest.raises(NoDataError): @@ -88,16 +96,29 @@ def test_accumulate_array_attribute_wrongshape(self, group): with pytest.raises(ValueError): group.accumulate(np.ones(len(group.atoms) - 1)) - @pytest.mark.parametrize('name, compound', (('resindices', 'residues'), - ('segindices', 'segments'), - ('molnums', 'molecules'), - ('fragindices', 'fragments'))) - @pytest.mark.parametrize('level', levels) + @pytest.mark.parametrize( + "name, compound", + ( + ("resindices", "residues"), + ("segindices", "segments"), + ("molnums", "molecules"), + ("fragindices", "fragments"), + ), + ) + @pytest.mark.parametrize("level", levels) def test_accumulate_array_attribute_compounds(self, name, compound, level): u = UnWrapUniverse() group = getattr(u, level) - ref = [np.ones((len(a), 2, 5)).sum(axis=0) for a in group.atoms.groupby(name).values()] - assert_equal(group.accumulate(np.ones((len(group.atoms), 2, 5)), compound=compound), ref) + ref = [ + np.ones((len(a), 2, 5)).sum(axis=0) + for a in group.atoms.groupby(name).values() + ] + assert_equal( + group.accumulate( + np.ones((len(group.atoms), 2, 5)), compound=compound + ), + ref, + ) class TestTotals(object): @@ -113,9 +134,14 @@ def group(self, request): def test_total_charge(self, group): assert_almost_equal(group.total_charge(), -4.0, decimal=4) - @pytest.mark.parametrize('name, compound', - (('resids', 'residues'), ('segids', 'segments'), - ('fragindices', 'fragments'))) + @pytest.mark.parametrize( + "name, compound", + ( + ("resids", "residues"), + ("segids", "segments"), + ("fragindices", "fragments"), + ), + ) def test_total_charge_compounds(self, group, name, compound): ref = [sum(a.charges) for a in group.atoms.groupby(name).values()] assert_almost_equal(group.total_charge(compound=compound), ref) @@ -135,9 +161,14 @@ def test_total_charge_duplicates(self, group): def test_total_mass(self, group): assert_almost_equal(group.total_mass(), 23582.043) - @pytest.mark.parametrize('name, compound', - (('resids', 'residues'), ('segids', 'segments'), - ('fragindices', 'fragments'))) + @pytest.mark.parametrize( + "name, compound", + ( + ("resids", "residues"), + ("segids", "segments"), + ("fragindices", "fragments"), + ), + ) def test_total_mass_compounds(self, group, name, compound): ref = [sum(a.masses) for a in group.atoms.groupby(name).values()] assert_almost_equal(group.total_mass(compound=compound), ref) @@ -160,7 +191,7 @@ class TestMultipole(object): and quadrupole_moment. """ - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def u(self): u = mda.Universe(PDB_multipole) u.add_TopologyAttr( @@ -184,20 +215,22 @@ def u(self): 0.037, -0.25, 0.034, - 0.034 - ]) # acetate [12:] + 0.034, + ], + ) # acetate [12:] lx, ly, lz = np.max(u.atoms.positions, axis=0) - np.min( - u.atoms.positions, axis=0) + u.atoms.positions, axis=0 + ) u.dimensions = np.array([lx, ly, lz, 90, 90, 90], dtype=float) return u - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def group(self, u): group = u.select_atoms("all") return group - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def methane(self, u): group = u.select_atoms("resname CH4") return group @@ -209,42 +242,44 @@ def test_dipole_moment_com(self, methane): methane.dipole_moment(wrap=True), methane.dipole_moment(), ] - assert_almost_equal(dipoles, [0., 0.2493469, 0.]) + assert_almost_equal(dipoles, [0.0, 0.2493469, 0.0]) def test_dipole_moment_no_center(self, group): try: group.dipole_moment(unwrap=True, center="not supported") except ValueError as e: - assert 'not supported' in e.args[0] + assert "not supported" in e.args[0] def test_dipole_moment_residues_com_coc(self, group): compound = "residues" (_, _, n_compounds) = group.atoms._split_by_compound_indices(compound) dipoles_com = group.dipole_moment(compound=compound, unwrap=False) - dipoles_coc = group.dipole_moment(compound=compound, - unwrap=False, - center="charge") + dipoles_coc = group.dipole_moment( + compound=compound, unwrap=False, center="charge" + ) - assert_almost_equal(dipoles_com, - np.array([0., 0.0010198, 0.1209898, 0.5681058])) + assert_almost_equal( + dipoles_com, np.array([0.0, 0.0010198, 0.1209898, 0.5681058]) + ) assert_almost_equal(dipoles_com[:3], dipoles_coc[:3]) assert dipoles_com[3] != dipoles_coc[3] assert len(dipoles_com) == n_compounds def test_dipole_moment_segment(self, methane): - compound = 'segments' - (_, _, - n_compounds) = methane.atoms._split_by_compound_indices(compound) + compound = "segments" + (_, _, n_compounds) = methane.atoms._split_by_compound_indices( + compound + ) dipoles = methane.dipole_moment(compound=compound, unwrap=True) - assert_almost_equal(dipoles, [0.]) and len(dipoles) == n_compounds + assert_almost_equal(dipoles, [0.0]) and len(dipoles) == n_compounds def test_dipole_moment_fragments(self, group): - compound = 'fragments' + compound = "fragments" (_, _, n_compounds) = group.atoms._split_by_compound_indices(compound) dipoles = group.dipole_moment(compound=compound, unwrap=False) - assert_almost_equal(dipoles, - np.array([0., 0.0010198, 0.1209898, 0.5681058 - ])) and len(dipoles) == n_compounds + assert_almost_equal( + dipoles, np.array([0.0, 0.0010198, 0.1209898, 0.5681058]) + ) and len(dipoles) == n_compounds # Quadrupole def test_quadrupole_moment_com(self, methane): @@ -253,41 +288,44 @@ def test_quadrupole_moment_com(self, methane): methane.quadrupole_moment(wrap=True), methane.quadrupole_moment(), ] - assert_almost_equal(quadrupoles, [0., 0.4657596, 0.]) + assert_almost_equal(quadrupoles, [0.0, 0.4657596, 0.0]) def test_quadrupole_moment_coc(self, group): assert_almost_equal( group.quadrupole_moment(unwrap=False, center="charge"), - 0.9769951421535777) + 0.9769951421535777, + ) def test_quadrupole_moment_no_center(self, group): try: group.quadrupole_moment(unwrap=True, center="not supported") except ValueError as e: - assert 'not supported' in e.args[0] + assert "not supported" in e.args[0] def test_quadrupole_moment_residues(self, group): compound = "residues" (_, _, n_compounds) = group.atoms._split_by_compound_indices(compound) quadrupoles = group.quadrupole_moment(compound=compound, unwrap=False) - assert_almost_equal(quadrupoles, - np.array([0., 0.0011629, 0.1182701, 0.6891748 - ])) and len(quadrupoles) == n_compounds + assert_almost_equal( + quadrupoles, np.array([0.0, 0.0011629, 0.1182701, 0.6891748]) + ) and len(quadrupoles) == n_compounds def test_quadrupole_moment_segment(self, methane): compound = "segments" - (_, _, - n_compounds) = methane.atoms._split_by_compound_indices(compound) + (_, _, n_compounds) = methane.atoms._split_by_compound_indices( + compound + ) quadrupoles = methane.quadrupole_moment(compound=compound, unwrap=True) - assert_almost_equal(quadrupoles, - [0.]) and len(quadrupoles) == n_compounds + assert_almost_equal(quadrupoles, [0.0]) and len( + quadrupoles + ) == n_compounds def test_quadrupole_moment_fragments(self, group): compound = "fragments" (_, _, n_compounds) = group.atoms._split_by_compound_indices(compound) quadrupoles = group.quadrupole_moment(compound=compound, unwrap=False) - assert_almost_equal(quadrupoles, - np.array([0., 0.0011629, 0.1182701, 0.6891748 - ])) and len(quadrupoles) == n_compounds + assert_almost_equal( + quadrupoles, np.array([0.0, 0.0011629, 0.1182701, 0.6891748]) + ) and len(quadrupoles) == n_compounds diff --git a/testsuite/MDAnalysisTests/core/test_atom.py b/testsuite/MDAnalysisTests/core/test_atom.py index 24479783d91..1d114c5a176 100644 --- a/testsuite/MDAnalysisTests/core/test_atom.py +++ b/testsuite/MDAnalysisTests/core/test_atom.py @@ -27,7 +27,8 @@ import MDAnalysis as mda from MDAnalysis import NoDataError from MDAnalysisTests.datafiles import ( - PSF, DCD, + PSF, + DCD, XYZ_mini, ) from numpy.testing import assert_almost_equal @@ -52,16 +53,16 @@ def atom(universe): def test_attributes_names(self, atom): a = atom - assert a.name == 'CG' - assert a.resname == 'LEU' + assert a.name == "CG" + assert a.resname == "LEU" def test_setting_attribute_name(self, atom): - atom.name = 'AA' - assert atom.name == 'AA' + atom.name = "AA" + assert atom.name == "AA" def test_setting_attribute_type(self, atom): - atom.type = 'Z' - assert atom.type == 'Z' + atom.type = "Z" + assert atom.type == "Z" def test_setting_attribute_mass(self, atom): atom.mass = 13 @@ -72,7 +73,9 @@ def test_setting_attributes_charge(self, atom): assert atom.charge == 6 def test_attributes_positions(self, atom): - known_pos = np.array([3.94543672, -12.4060812, -7.26820087], dtype=np.float32) + known_pos = np.array( + [3.94543672, -12.4060812, -7.26820087], dtype=np.float32 + ) a = atom # new position property (mutable) assert_almost_equal(a.position, known_pos) @@ -81,13 +84,13 @@ def test_attributes_positions(self, atom): assert_almost_equal(a.position, pos) def test_atom_selection(self, universe, atom): - asel = universe.select_atoms('atom 4AKE 67 CG').atoms[0] + asel = universe.select_atoms("atom 4AKE 67 CG").atoms[0] assert atom == asel def test_hierarchy(self, universe, atom): u = universe a = atom - assert a.segment == u.select_atoms('segid 4AKE').segments[0] + assert a.segment == u.select_atoms("segid 4AKE").segments[0] assert a.residue == u.residues[66] def test_bad_add(self, atom): @@ -136,16 +139,16 @@ def a(): def test_velocity_fail(self, a): with pytest.raises(NoDataError): - getattr(a, 'velocity') + getattr(a, "velocity") def test_force_fail(self, a): with pytest.raises(NoDataError): - getattr(a, 'force') + getattr(a, "force") def test_velocity_set_fail(self, a): with pytest.raises(NoDataError): - setattr(a, 'velocity', [1.0, 1.0, 1.0]) + setattr(a, "velocity", [1.0, 1.0, 1.0]) def test_force_set_fail(self, a): with pytest.raises(NoDataError): - setattr(a, 'force', [1.0, 1.0, 1.0]) + setattr(a, "force", [1.0, 1.0, 1.0]) diff --git a/testsuite/MDAnalysisTests/core/test_atomgroup.py b/testsuite/MDAnalysisTests/core/test_atomgroup.py index fdb23b682e4..38432e65c42 100644 --- a/testsuite/MDAnalysisTests/core/test_atomgroup.py +++ b/testsuite/MDAnalysisTests/core/test_atomgroup.py @@ -44,12 +44,16 @@ ) from MDAnalysisTests.datafiles import ( - PSF, DCD, - TRZ_psf, TRZ, + PSF, + DCD, + TRZ_psf, + TRZ, two_water_gro, - TPR_xvf, TRR_xvf, - GRO, GRO_MEMPROT, - TPR + TPR_xvf, + TRR_xvf, + GRO, + GRO_MEMPROT, + TPR, ) from MDAnalysisTests import make_Universe, no_deprecated_call from MDAnalysisTests.core.util import UnWrapUniverse @@ -58,6 +62,7 @@ class TestAtomGroupToTopology(object): """Test the conversion of AtomGroup to TopologyObjects""" + @pytest.fixture() def u(self): return mda.Universe(PSF, DCD) @@ -82,12 +87,9 @@ def test_improper(self, u): imp = ag.improper assert isinstance(imp, ImproperDihedral) - @pytest.mark.parametrize('btype,', [ - 'bond', - 'angle', - 'dihedral', - 'improper' - ]) + @pytest.mark.parametrize( + "btype,", ["bond", "angle", "dihedral", "improper"] + ) def test_VE(self, btype, u): ag = u.atoms[:10] with pytest.raises(ValueError): @@ -103,7 +105,7 @@ def u(self): def test_write_no_args(self, u, tmpdir): with tmpdir.as_cwd(): u.atoms.write() - files = glob('*') + files = glob("*") assert len(files) == 1 name = path.splitext(path.basename(DCD))[0] @@ -112,86 +114,99 @@ def test_write_no_args(self, u, tmpdir): def test_raises_unknown_format(self, u, tmpdir): with tmpdir.as_cwd(): with pytest.raises(ValueError): - u.atoms.write('useless.format123') + u.atoms.write("useless.format123") def test_write_coordinates(self, u, tmpdir): with tmpdir.as_cwd(): u.atoms.write("test.xtc") - @pytest.mark.parametrize('frames', ( - [4], - [2, 3, 3, 1], - slice(2, 6, 1), - )) + @pytest.mark.parametrize( + "frames", + ( + [4], + [2, 3, 3, 1], + slice(2, 6, 1), + ), + ) def test_write_frames(self, u, tmpdir, frames): - destination = str(tmpdir / 'test.dcd') + destination = str(tmpdir / "test.dcd") selection = u.trajectory[frames] ref_positions = np.stack([ts.positions.copy() for ts in selection]) u.atoms.write(destination, frames=frames) u_new = mda.Universe(destination, to_guess=()) - new_positions = np.stack([ts.positions.copy() for ts in u_new.trajectory]) + new_positions = np.stack( + [ts.positions.copy() for ts in u_new.trajectory] + ) assert_array_almost_equal(new_positions, ref_positions) - @pytest.mark.parametrize('frames', ( - [4], - [2, 3, 3, 1], - slice(2, 6, 1), - )) + @pytest.mark.parametrize( + "frames", + ( + [4], + [2, 3, 3, 1], + slice(2, 6, 1), + ), + ) def test_write_frame_iterator(self, u, tmpdir, frames): - destination = str(tmpdir / 'test.dcd') + destination = str(tmpdir / "test.dcd") selection = u.trajectory[frames] ref_positions = np.stack([ts.positions.copy() for ts in selection]) u.atoms.write(destination, frames=selection) u_new = mda.Universe(destination, to_guess=()) - new_positions = np.stack([ts.positions.copy() for ts in u_new.trajectory]) + new_positions = np.stack( + [ts.positions.copy() for ts in u_new.trajectory] + ) assert_array_almost_equal(new_positions, ref_positions) - @pytest.mark.parametrize('extension', ('xtc', 'dcd', 'pdb', 'xyz', 'PDB')) - @pytest.mark.parametrize('compression', ('', '.gz', '.bz2')) + @pytest.mark.parametrize("extension", ("xtc", "dcd", "pdb", "xyz", "PDB")) + @pytest.mark.parametrize("compression", ("", ".gz", ".bz2")) def test_write_frame_none(self, u, tmpdir, extension, compression): - destination = str(tmpdir / 'test.' + extension + compression) + destination = str(tmpdir / "test." + extension + compression) u.atoms.write(destination, frames=None) u_new = mda.Universe(destination, to_guess=()) new_positions = np.stack([ts.positions for ts in u_new.trajectory]) # Most format only save 3 decimals; XTC even has only 2. - assert_array_almost_equal(u.atoms.positions[None, ...], - new_positions, decimal=2) + assert_array_almost_equal( + u.atoms.positions[None, ...], new_positions, decimal=2 + ) - @pytest.mark.parametrize('compression', ('', '.gz', '.bz2')) + @pytest.mark.parametrize("compression", ("", ".gz", ".bz2")) def test_write_frames_all(self, u, tmpdir, compression): - destination = str(tmpdir / 'test.dcd' + compression) - u.atoms.write(destination, frames='all') + destination = str(tmpdir / "test.dcd" + compression) + u.atoms.write(destination, frames="all") u_new = mda.Universe(destination, to_guess=()) ref_positions = np.stack([ts.positions.copy() for ts in u.trajectory]) - new_positions = np.stack([ts.positions.copy() for ts in u_new.trajectory]) + new_positions = np.stack( + [ts.positions.copy() for ts in u_new.trajectory] + ) assert_array_almost_equal(new_positions, ref_positions) - @pytest.mark.parametrize('frames', ('invalid', 8, True, False, 3.2)) + @pytest.mark.parametrize("frames", ("invalid", 8, True, False, 3.2)) def test_write_frames_invalid(self, u, tmpdir, frames): - destination = str(tmpdir / 'test.dcd') + destination = str(tmpdir / "test.dcd") with pytest.raises(TypeError): u.atoms.write(destination, frames=frames) def test_incompatible_arguments(self, u, tmpdir): - destination = str(tmpdir / 'test.dcd') + destination = str(tmpdir / "test.dcd") with pytest.raises(ValueError): u.atoms.write(destination, frames=[0, 1, 2], multiframe=False) def test_incompatible_trajectories(self, tmpdir): - destination = str(tmpdir / 'test.dcd') + destination = str(tmpdir / "test.dcd") u1 = make_Universe(trajectory=True) u2 = make_Universe(trajectory=True) - destination = str(tmpdir / 'test.dcd') + destination = str(tmpdir / "test.dcd") with pytest.raises(ValueError): u1.atoms.write(destination, frames=u2.trajectory) def test_write_no_traj_move(self, u, tmpdir): - destination = str(tmpdir / 'test.dcd') + destination = str(tmpdir / "test.dcd") u.trajectory[10] u.atoms.write(destination, frames=[1, 2, 3]) assert u.trajectory.ts.frame == 10 @@ -204,7 +219,7 @@ def test_bogus_kwarg_pdb(self, u, tmpdir): # test for resolution of Issue 877 with tmpdir.as_cwd(): with pytest.raises(TypeError): - u.atoms.write('dummy.pdb', bogus="what?") + u.atoms.write("dummy.pdb", bogus="what?") class _WriteAtoms(object): @@ -224,63 +239,83 @@ def outfile(self, tmpdir): def universe_from_tmp(self, outfile): return mda.Universe(outfile, convert_units=True) - @pytest.mark.parametrize('compression', ('', '.gz', '.bz2')) + @pytest.mark.parametrize("compression", ("", ".gz", ".bz2")) def test_write_atoms(self, universe, outfile, compression): outname = outfile + compression universe.atoms.write(outname) u2 = self.universe_from_tmp(outname) assert_almost_equal( - universe.atoms.positions, u2.atoms.positions, + universe.atoms.positions, + u2.atoms.positions, self.precision, - err_msg=("atom coordinate mismatch between original and {0!s} " - "file".format(self.ext + compression))) + err_msg=( + "atom coordinate mismatch between original and {0!s} " + "file".format(self.ext + compression) + ), + ) def test_write_empty_atomgroup(self, universe, outfile): - sel = universe.select_atoms('name doesntexist') + sel = universe.select_atoms("name doesntexist") with pytest.raises(IndexError): sel.write(outfile) - @pytest.mark.parametrize('selection', ('name CA', - 'segid 4AKE and resname LEU', - 'segid 4AKE')) + @pytest.mark.parametrize( + "selection", ("name CA", "segid 4AKE and resname LEU", "segid 4AKE") + ) def test_write_selection(self, universe, outfile, selection): sel = universe.select_atoms(selection) sel.write(outfile) u2 = self.universe_from_tmp(outfile) # check EVERYTHING, otherwise we might get false positives! sel2 = u2.atoms - assert len(u2.atoms) == len(sel.atoms), ("written selection does not " - "match original selection") + assert len(u2.atoms) == len(sel.atoms), ( + "written selection does not " "match original selection" + ) assert_almost_equal( - sel2.positions, sel.positions, self.precision, - err_msg="written coordinates do not agree with original") + sel2.positions, + sel.positions, + self.precision, + err_msg="written coordinates do not agree with original", + ) def test_write_Residue(self, universe, outfile): - G = universe.select_atoms('segid 4AKE and resname ARG' - ).residues[-2].atoms # 2nd to last Arg + G = ( + universe.select_atoms("segid 4AKE and resname ARG") + .residues[-2] + .atoms + ) # 2nd to last Arg G.write(outfile) u2 = self.universe_from_tmp(outfile) # check EVERYTHING, otherwise we might get false positives! G2 = u2.atoms - assert len(u2.atoms) == len(G.atoms), ("written R206 Residue does not " - "match original ResidueGroup") + assert len(u2.atoms) == len(G.atoms), ( + "written R206 Residue does not " "match original ResidueGroup" + ) assert_almost_equal( - G2.positions, G.positions, self.precision, + G2.positions, + G.positions, + self.precision, err_msg="written Residue R206 coordinates do not " - "agree with original") + "agree with original", + ) def test_write_Universe(self, universe, outfile): U = universe with mda.Writer(outfile) as W: W.write(U) u2 = self.universe_from_tmp(outfile) - assert len(u2.atoms) == len(U.atoms), ("written 4AKE universe does " - "not match original universe " - "in size") + assert len(u2.atoms) == len(U.atoms), ( + "written 4AKE universe does " + "not match original universe " + "in size" + ) assert_almost_equal( - u2.atoms.positions, U.atoms.positions, self.precision, + u2.atoms.positions, + U.atoms.positions, + self.precision, err_msg="written universe 4AKE coordinates do not " - "agree with original") + "agree with original", + ) class TestWritePDB(_WriteAtoms): @@ -341,16 +376,18 @@ def test_rotate(self, u, coords): ag.positions = vec.copy() res_ag = ag.rotate(R[:3, :3]) assert_equal(ag, res_ag) - assert_almost_equal(ag.positions[0], [np.cos(angle), - np.sin(angle), - 0]) + assert_almost_equal( + ag.positions[0], [np.cos(angle), np.sin(angle), 0] + ) ag.positions = vec.copy() ag.rotate(R[:3, :3], vec[0]) assert_almost_equal(ag.positions[0], vec[0]) - assert_almost_equal(ag.positions[1], [-2*np.cos(angle) + 1, - -2*np.sin(angle), - 0], decimal=6) + assert_almost_equal( + ag.positions[1], + [-2 * np.cos(angle) + 1, -2 * np.sin(angle), 0], + decimal=6, + ) def test_rotateby(self, u, coords): R = np.eye(3) @@ -368,16 +405,17 @@ def test_rotateby(self, u, coords): # needs to be rotated about origin res_ag = ag.rotateby(np.rad2deg(angle), axis) assert_equal(res_ag, ag) - assert_almost_equal(ag.positions[0], [np.cos(angle), - np.sin(angle), - 0]) + assert_almost_equal( + ag.positions[0], [np.cos(angle), np.sin(angle), 0] + ) ag.positions = vec.copy() ag.rotateby(np.rad2deg(angle), axis, point=vec[0]) assert_almost_equal(ag.positions[0], vec[0]) - assert_almost_equal(ag.positions[1], [-2*np.cos(angle) + 1, - -2*np.sin(angle), - 0]) + assert_almost_equal( + ag.positions[1], + [-2 * np.cos(angle) + 1, -2 * np.sin(angle), 0], + ) def test_transform_rotation_only(self, u, coords): R = np.eye(3) @@ -394,9 +432,9 @@ def test_transform_rotation_only(self, u, coords): R = transformations.rotation_matrix(angle, axis) ag.positions = vec.copy() ag.transform(R) - assert_almost_equal(ag.positions[0], [np.cos(angle), - np.sin(angle), - 0]) + assert_almost_equal( + ag.positions[0], [np.cos(angle), np.sin(angle), 0] + ) def test_transform_translation_only(self, u, center_of_geometry): disp = np.ones(3) @@ -419,9 +457,9 @@ def test_transform_translation_and_rotation(self, u): ag.positions = [[1, 0, 0], [-1, 0, 0]] ag.transform(T) - assert_almost_equal(ag.positions[0], [np.cos(angle) + 1, - np.sin(angle) + 1, - 1]) + assert_almost_equal( + ag.positions[0], [np.cos(angle) + 1, np.sin(angle) + 1, 1] + ) class TestCenter(object): @@ -438,12 +476,12 @@ def test_center_1(self, ag): def test_center_2(self, ag): weights = np.zeros(ag.n_atoms) - weights[:4] = 1. / 4. + weights[:4] = 1.0 / 4.0 assert_almost_equal(ag.center(weights), ag.positions[:4].mean(axis=0)) def test_center_duplicates(self, ag): weights = np.ones(ag.n_atoms) - weights[0] = 2. + weights[0] = 2.0 ref = ag.center(weights) ag2 = ag + ag[0] with pytest.warns(DuplicateWarning): @@ -461,28 +499,30 @@ def test_center_wrong_shape(self, ag): with pytest.raises(ValueError): ag.center(weights) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) - @pytest.mark.parametrize('is_triclinic', (False, True)) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) + @pytest.mark.parametrize("is_triclinic", (False, True)) def test_center_unwrap(self, level, compound, is_triclinic): u = UnWrapUniverse(is_triclinic=is_triclinic) # select group appropriate for compound: - if compound == 'group': - group = u.atoms[39:47] # molecule 12 - elif compound == 'segments': - group = u.atoms[23:47] # molecules 10, 11, 12 + if compound == "group": + group = u.atoms[39:47] # molecule 12 + elif compound == "segments": + group = u.atoms[23:47] # molecules 10, 11, 12 else: group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # get the expected results - center = group.center(weights=None, wrap=False, - compound=compound, unwrap=True) + center = group.center( + weights=None, wrap=False, compound=compound, unwrap=True + ) ref_center = u.center(compound=compound) assert_almost_equal(ref_center, center, decimal=4) @@ -492,8 +532,9 @@ def test_center_unwrap_wrap_true_group(self): # select group appropriate for compound: group = u.atoms[39:47] # molecule 12 with pytest.raises(ValueError): - group.center(weights=None, compound='group', - unwrap=True, wrap=True) + group.center( + weights=None, compound="group", unwrap=True, wrap=True + ) class TestSplit(object): @@ -501,11 +542,12 @@ class TestSplit(object): @pytest.fixture() def ag(self): universe = mda.Universe(PSF, DCD) - return universe.select_atoms("resid 1:50 and not resname LYS and " - "name CA CB") + return universe.select_atoms( + "resid 1:50 and not resname LYS and " "name CA CB" + ) def test_split_atoms(self, ag): - sg = ag.split('atom') + sg = ag.split("atom") assert len(sg) == len(ag) for g, ref_atom in zip(sg, ag): atom = g[0] @@ -533,7 +575,7 @@ def test_split_segments(self, ag): def test_split_VE(self, ag): with pytest.raises(ValueError): - ag.split('something') + ag.split("something") class TestAtomGroupProperties(object): @@ -545,47 +587,65 @@ class TestAtomGroupProperties(object): - setting the property on Atom changes AG - _unique_restore_mask works correctly """ + @staticmethod def get_new(att_type): """Return enough values to change the small g""" - if att_type == 'string': - return ['A', 'B', 'C', 'D', 'E', 'F'] - elif att_type == 'float': - return np.array([0.001, 0.002, 0.003, 0.005, 0.012, 0.025], - dtype=np.float32) - elif att_type == 'int': + if att_type == "string": + return ["A", "B", "C", "D", "E", "F"] + elif att_type == "float": + return np.array( + [0.001, 0.002, 0.003, 0.005, 0.012, 0.025], dtype=np.float32 + ) + elif att_type == "int": return [4, 6, 8, 1, 5, 4] @pytest.fixture def ag(self): - u = make_Universe(('names', 'resids', 'segids', 'types', 'altLocs', - 'charges', 'masses', 'radii', 'bfactors', - 'occupancies')) + u = make_Universe( + ( + "names", + "resids", + "segids", + "types", + "altLocs", + "charges", + "masses", + "radii", + "bfactors", + "occupancies", + ) + ) u.atoms.occupancies = 1.0 main = u.atoms idx = [0, 1, 4, 7, 11, 14] return main[idx] - attributes = (('name', 'names', 'string'), - ('type', 'types', 'string'), - ('altLoc', 'altLocs', 'string'), - ('charge', 'charges', 'float'), - ('mass', 'masses', 'float'), - ('radius', 'radii', 'float'), - ('bfactor', 'bfactors', 'float'), - ('occupancy', 'occupancies', 'float')) + attributes = ( + ("name", "names", "string"), + ("type", "types", "string"), + ("altLoc", "altLocs", "string"), + ("charge", "charges", "float"), + ("mass", "masses", "float"), + ("radius", "radii", "float"), + ("bfactor", "bfactors", "float"), + ("occupancy", "occupancies", "float"), + ) - @pytest.mark.parametrize('att, atts, att_type', attributes) + @pytest.mark.parametrize("att, atts, att_type", attributes) def test_ag_matches_atom(self, att, atts, ag, att_type): """Checking Atomgroup property matches Atoms""" # Check that accessing via AtomGroup is identical to doing # a list comprehension over AG ref = [getattr(atom, att) for atom in ag] - assert_equal(ref, getattr(ag, atts), - err_msg="AtomGroup doesn't match Atoms for property: " - "{0}".format(att)) + assert_equal( + ref, + getattr(ag, atts), + err_msg="AtomGroup doesn't match Atoms for property: " + "{0}".format(att), + ) - @pytest.mark.parametrize('att, atts, att_type', attributes) + @pytest.mark.parametrize("att, atts, att_type", attributes) def test_atom_check_ag(self, att, atts, ag, att_type): """Changing Atom, checking AtomGroup matches this""" vals = self.get_new(att_type) @@ -595,21 +655,24 @@ def test_atom_check_ag(self, att, atts, ag, att_type): # Check that AtomGroup returns new values other = getattr(ag, atts) - assert_equal(vals, other, - err_msg="Change to Atoms not reflected in AtomGroup for " - "property: {0}".format(att)) + assert_equal( + vals, + other, + err_msg="Change to Atoms not reflected in AtomGroup for " + "property: {0}".format(att), + ) def test_ag_unique_restore_mask(self, ag): # assert that ag is unique: assert ag.isunique # assert restore mask cache is empty: with pytest.raises(KeyError): - _ = ag._cache['unique_restore_mask'] + _ = ag._cache["unique_restore_mask"] # access unique property: uag = ag.asunique() # assert restore mask cache is still empty since ag is unique: with pytest.raises(KeyError): - _ = ag._cache['unique_restore_mask'] + _ = ag._cache["unique_restore_mask"] # make sure that accessing the restore mask of the unique AtomGroup # raises a RuntimeError: with pytest.raises(RuntimeError): @@ -621,11 +684,11 @@ def test_ag_unique_restore_mask(self, ag): assert not ag.isunique # assert cache is empty: with pytest.raises(KeyError): - _ = ag._cache['unique_restore_mask'] + _ = ag._cache["unique_restore_mask"] # access unique property: uag = ag.unique # check if caching works as expected: - assert ag._cache['unique_restore_mask'] is ag._unique_restore_mask + assert ag._cache["unique_restore_mask"] is ag._unique_restore_mask # assert that restore mask cache of ag.unique hasn't been set: with pytest.raises(RuntimeError): _ = ag.unique._unique_restore_mask @@ -640,6 +703,7 @@ class TestOrphans(object): - should have access to Universe - should be able to use the Reader (coordinates) """ + def test_atom(self): u = mda.Universe(two_water_gro) @@ -669,13 +733,14 @@ def getter(): class TestCrossUniverse(object): """Test behaviour when we mix Universes""" + @pytest.mark.parametrize( # Checks Atom to Atom, Atom to AG, AG to Atom and AG to AG - 'index_u1, index_u2', - itertools.product([0, 1], repeat=2) + "index_u1, index_u2", + itertools.product([0, 1], repeat=2), ) def test_add_mixed_universes(self, index_u1, index_u2): - """ Issue #532 + """Issue #532 Checks that adding objects from different universes doesn't proceed quietly. """ @@ -689,7 +754,7 @@ def test_add_mixed_universes(self, index_u1, index_u2): A[index_u1] + B[index_u2] def test_adding_empty_ags(self): - """ Check that empty AtomGroups don't trip up on the Universe check """ + """Check that empty AtomGroups don't trip up on the Universe check""" u = mda.Universe(two_water_gro) assert len(u.atoms[[]] + u.atoms[:3]) == 3 @@ -700,12 +765,12 @@ class TestDihedralSelections(object): dih_prec = 2 @staticmethod - @pytest.fixture(scope='module') + @pytest.fixture(scope="module") def GRO(): return mda.Universe(GRO) @staticmethod - @pytest.fixture(scope='module') + @pytest.fixture(scope="module") def PSFDCD(): return mda.Universe(PSF, DCD) @@ -720,34 +785,37 @@ def memprot(): return mda.Universe(GRO_MEMPROT) @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def resgroup(GRO): return GRO.segments[0].residues[8:10] def test_phi_selection(self, GRO): phisel = GRO.segments[0].residues[9].phi_selection() - assert_equal(phisel.names, ['C', 'N', 'CA', 'C']) + assert_equal(phisel.names, ["C", "N", "CA", "C"]) assert_equal(phisel.residues.resids, [9, 10]) - assert_equal(phisel.residues.resnames, ['PRO', 'GLY']) + assert_equal(phisel.residues.resnames, ["PRO", "GLY"]) - @pytest.mark.parametrize('kwargs,names', [ - ({'c_name': 'O'}, ['O', 'N', 'CA', 'O']), - ({'n_name': 'O'}, ['C', 'O', 'CA', 'C']), - ({'ca_name': 'O'}, ['C', 'N', 'O', 'C']) - ]) + @pytest.mark.parametrize( + "kwargs,names", + [ + ({"c_name": "O"}, ["O", "N", "CA", "O"]), + ({"n_name": "O"}, ["C", "O", "CA", "C"]), + ({"ca_name": "O"}, ["C", "N", "O", "C"]), + ], + ) def test_phi_selection_name(self, GRO, kwargs, names): phisel = GRO.segments[0].residues[9].phi_selection(**kwargs) assert_equal(phisel.names, names) assert_equal(phisel.residues.resids, [9, 10]) - assert_equal(phisel.residues.resnames, ['PRO', 'GLY']) + assert_equal(phisel.residues.resnames, ["PRO", "GLY"]) def test_phi_selections_single(self, GRO): rgsel = GRO.segments[0].residues[[9]].phi_selections() assert len(rgsel) == 1 phisel = rgsel[0] - assert_equal(phisel.names, ['C', 'N', 'CA', 'C']) + assert_equal(phisel.names, ["C", "N", "CA", "C"]) assert_equal(phisel.residues.resids, [9, 10]) - assert_equal(phisel.residues.resnames, ['PRO', 'GLY']) + assert_equal(phisel.residues.resnames, ["PRO", "GLY"]) def test_phi_selections_empty(self, GRO): rgsel = GRO.segments[0].residues[[]].phi_selections() @@ -758,11 +826,14 @@ def test_phi_selections(self, resgroup): rssel = [r.phi_selection() for r in resgroup] assert_equal(rgsel, rssel) - @pytest.mark.parametrize('kwargs,names', [ - ({'c_name': 'O'}, ['O', 'N', 'CA', 'O']), - ({'n_name': 'O'}, ['C', 'O', 'CA', 'C']), - ({'ca_name': 'O'}, ['C', 'N', 'O', 'C']) - ]) + @pytest.mark.parametrize( + "kwargs,names", + [ + ({"c_name": "O"}, ["O", "N", "CA", "O"]), + ({"n_name": "O"}, ["C", "O", "CA", "C"]), + ({"ca_name": "O"}, ["C", "N", "O", "C"]), + ], + ) def test_phi_selections_name(self, resgroup, kwargs, names): rgsel = resgroup.phi_selections(**kwargs) for ag in rgsel: @@ -770,28 +841,31 @@ def test_phi_selections_name(self, resgroup, kwargs, names): def test_psi_selection(self, GRO): psisel = GRO.segments[0].residues[9].psi_selection() - assert_equal(psisel.names, ['N', 'CA', 'C', 'N']) + assert_equal(psisel.names, ["N", "CA", "C", "N"]) assert_equal(psisel.residues.resids, [10, 11]) - assert_equal(psisel.residues.resnames, ['GLY', 'ALA']) + assert_equal(psisel.residues.resnames, ["GLY", "ALA"]) - @pytest.mark.parametrize('kwargs,names', [ - ({'c_name': 'O'}, ['N', 'CA', 'O', 'N']), - ({'n_name': 'O'}, ['O', 'CA', 'C', 'O']), - ({'ca_name': 'O'}, ['N', 'O', 'C', 'N']), - ]) + @pytest.mark.parametrize( + "kwargs,names", + [ + ({"c_name": "O"}, ["N", "CA", "O", "N"]), + ({"n_name": "O"}, ["O", "CA", "C", "O"]), + ({"ca_name": "O"}, ["N", "O", "C", "N"]), + ], + ) def test_psi_selection_name(self, GRO, kwargs, names): psisel = GRO.segments[0].residues[9].psi_selection(**kwargs) assert_equal(psisel.names, names) assert_equal(psisel.residues.resids, [10, 11]) - assert_equal(psisel.residues.resnames, ['GLY', 'ALA']) + assert_equal(psisel.residues.resnames, ["GLY", "ALA"]) def test_psi_selections_single(self, GRO): rgsel = GRO.segments[0].residues[[9]].psi_selections() assert len(rgsel) == 1 psisel = rgsel[0] - assert_equal(psisel.names, ['N', 'CA', 'C', 'N']) + assert_equal(psisel.names, ["N", "CA", "C", "N"]) assert_equal(psisel.residues.resids, [10, 11]) - assert_equal(psisel.residues.resnames, ['GLY', 'ALA']) + assert_equal(psisel.residues.resnames, ["GLY", "ALA"]) def test_psi_selections_empty(self, GRO): rgsel = GRO.segments[0].residues[[]].psi_selections() @@ -802,11 +876,14 @@ def test_psi_selections(self, resgroup): rssel = [r.psi_selection() for r in resgroup] assert_equal(rgsel, rssel) - @pytest.mark.parametrize('kwargs,names', [ - ({'c_name': 'O'}, ['N', 'CA', 'O', 'N']), - ({'n_name': 'O'}, ['O', 'CA', 'C', 'O']), - ({'ca_name': 'O'}, ['N', 'O', 'C', 'N']), - ]) + @pytest.mark.parametrize( + "kwargs,names", + [ + ({"c_name": "O"}, ["N", "CA", "O", "N"]), + ({"n_name": "O"}, ["O", "CA", "C", "O"]), + ({"ca_name": "O"}, ["N", "O", "C", "N"]), + ], + ) def test_psi_selections_name(self, resgroup, kwargs, names): rgsel = resgroup.psi_selections(**kwargs) for ag in rgsel: @@ -814,20 +891,23 @@ def test_psi_selections_name(self, resgroup, kwargs, names): def test_omega_selection(self, GRO): osel = GRO.segments[0].residues[7].omega_selection() - assert_equal(osel.names, ['CA', 'C', 'N', 'CA']) + assert_equal(osel.names, ["CA", "C", "N", "CA"]) assert_equal(osel.residues.resids, [8, 9]) - assert_equal(osel.residues.resnames, ['ALA', 'PRO']) + assert_equal(osel.residues.resnames, ["ALA", "PRO"]) - @pytest.mark.parametrize('kwargs,names', [ - ({'c_name': 'O'}, ['CA', 'O', 'N', 'CA']), - ({'n_name': 'O'}, ['CA', 'C', 'O', 'CA']), - ({'ca_name': 'O'}, ['O', 'C', 'N', 'O']), - ]) + @pytest.mark.parametrize( + "kwargs,names", + [ + ({"c_name": "O"}, ["CA", "O", "N", "CA"]), + ({"n_name": "O"}, ["CA", "C", "O", "CA"]), + ({"ca_name": "O"}, ["O", "C", "N", "O"]), + ], + ) def test_omega_selection_name(self, GRO, kwargs, names): osel = GRO.segments[0].residues[7].omega_selection(**kwargs) assert_equal(osel.names, names) assert_equal(osel.residues.resids, [8, 9]) - assert_equal(osel.residues.resnames, ['ALA', 'PRO']) + assert_equal(osel.residues.resnames, ["ALA", "PRO"]) def test_omega_selections_empty(self, GRO): rgsel = GRO.segments[0].residues[[]].omega_selections() @@ -837,20 +917,23 @@ def test_omega_selections_single(self, GRO): rgsel = GRO.segments[0].residues[[7]].omega_selections() assert len(rgsel) == 1 osel = rgsel[0] - assert_equal(osel.names, ['CA', 'C', 'N', 'CA']) + assert_equal(osel.names, ["CA", "C", "N", "CA"]) assert_equal(osel.residues.resids, [8, 9]) - assert_equal(osel.residues.resnames, ['ALA', 'PRO']) + assert_equal(osel.residues.resnames, ["ALA", "PRO"]) def test_omega_selections(self, resgroup): rgsel = resgroup.omega_selections() rssel = [r.omega_selection() for r in resgroup] assert_equal(rgsel, rssel) - @pytest.mark.parametrize('kwargs,names', [ - ({'c_name': 'O'}, ['CA', 'O', 'N', 'CA']), - ({'n_name': 'O'}, ['CA', 'C', 'O', 'CA']), - ({'ca_name': 'O'}, ['O', 'C', 'N', 'O']), - ]) + @pytest.mark.parametrize( + "kwargs,names", + [ + ({"c_name": "O"}, ["CA", "O", "N", "CA"]), + ({"n_name": "O"}, ["CA", "C", "O", "CA"]), + ({"ca_name": "O"}, ["O", "C", "N", "O"]), + ], + ) def test_omega_selections_name(self, resgroup, kwargs, names): rgsel = resgroup.omega_selections(**kwargs) for ag in rgsel: @@ -858,29 +941,32 @@ def test_omega_selections_name(self, resgroup, kwargs, names): def test_chi1_selection(self, GRO): sel = GRO.segments[0].residues[12].chi1_selection() # LYS - assert_equal(sel.names, ['N', 'CA', 'CB', 'CG']) + assert_equal(sel.names, ["N", "CA", "CB", "CG"]) assert_equal(sel.residues.resids, [13]) - assert_equal(sel.residues.resnames, ['LYS']) - - @pytest.mark.parametrize('kwargs,names', [ - ({'n_name': 'O'}, ['O', 'CA', 'CB', 'CG']), - ({'ca_name': 'O'}, ['N', 'O', 'CB', 'CG']), - ({'cb_name': 'O'}, ['N', 'CA', 'O', 'CG']), - ({'cg_name': 'O'}, ['N', 'CA', 'CB', 'O']), - ]) + assert_equal(sel.residues.resnames, ["LYS"]) + + @pytest.mark.parametrize( + "kwargs,names", + [ + ({"n_name": "O"}, ["O", "CA", "CB", "CG"]), + ({"ca_name": "O"}, ["N", "O", "CB", "CG"]), + ({"cb_name": "O"}, ["N", "CA", "O", "CG"]), + ({"cg_name": "O"}, ["N", "CA", "CB", "O"]), + ], + ) def test_chi1_selection_name(self, GRO, kwargs, names): sel = GRO.segments[0].residues[12].chi1_selection(**kwargs) # LYS assert_equal(sel.names, names) assert_equal(sel.residues.resids, [13]) - assert_equal(sel.residues.resnames, ['LYS']) + assert_equal(sel.residues.resnames, ["LYS"]) def test_chi1_selections_single(self, GRO): rgsel = GRO.segments[0].residues[[12]].chi1_selections() assert len(rgsel) == 1 sel = rgsel[0] - assert_equal(sel.names, ['N', 'CA', 'CB', 'CG']) + assert_equal(sel.names, ["N", "CA", "CB", "CG"]) assert_equal(sel.residues.resids, [13]) - assert_equal(sel.residues.resnames, ['LYS']) + assert_equal(sel.residues.resnames, ["LYS"]) def test_chi1_selections_empty(self, GRO): rgsel = GRO.segments[0].residues[[]].chi1_selections() @@ -911,10 +997,28 @@ def test_chi1_selection_non_cg_charmm(self, resname, PSFDCD): res = resgroup[len(resgroup) // 2] assert res.chi1_selection() is not None - @pytest.mark.parametrize("resname", ["ARG", "ASP", "CYS", "GLN", "GLU", - "HIS", "ILE", "LEU", "LYS", "MET", - "PHE", "PRO", "SER", "THR", "TRP", - "TYR", "VAL"]) + @pytest.mark.parametrize( + "resname", + [ + "ARG", + "ASP", + "CYS", + "GLN", + "GLU", + "HIS", + "ILE", + "LEU", + "LYS", + "MET", + "PHE", + "PRO", + "SER", + "THR", + "TRP", + "TYR", + "VAL", + ], + ) def test_chi1_selection_all_res(self, resname, memprot): resgroup = memprot.select_atoms(f"resname {resname}").residues # get middle one @@ -1005,56 +1109,79 @@ class TestUnwrapFlag(object): prec = 3 ref_noUnwrap_residues = { - 'center_of_geometry': np.array([[21.356, 28.52, 36.762], - [32.062, 36.16, 27.679], - [27.071, 29.997, 28.506]], - dtype=np.float32), - 'center_of_mass': np.array([[21.286, 28.407, 36.629], - [31.931, 35.814, 27.916], - [26.817, 29.41, 29.05]], - dtype=np.float32), - 'moment_of_inertia': - np.array([[7333.79167791, -211.8997285, -721.50785456], - [-211.8997285, 7059.07470427, -91.32156884], - [-721.50785456, -91.32156884, 6509.31735029]]), - 'asphericity': np.array([0.135, 0.047, 0.094]), - 'shape_parameter': np.array([-0.112, -0.004, 0.02]), + "center_of_geometry": np.array( + [ + [21.356, 28.52, 36.762], + [32.062, 36.16, 27.679], + [27.071, 29.997, 28.506], + ], + dtype=np.float32, + ), + "center_of_mass": np.array( + [ + [21.286, 28.407, 36.629], + [31.931, 35.814, 27.916], + [26.817, 29.41, 29.05], + ], + dtype=np.float32, + ), + "moment_of_inertia": np.array( + [ + [7333.79167791, -211.8997285, -721.50785456], + [-211.8997285, 7059.07470427, -91.32156884], + [-721.50785456, -91.32156884, 6509.31735029], + ] + ), + "asphericity": np.array([0.135, 0.047, 0.094]), + "shape_parameter": np.array([-0.112, -0.004, 0.02]), } ref_Unwrap_residues = { - 'center_of_geometry': np.array([[21.356, 41.685, 40.501], - [44.577, 43.312, 79.039], - [2.204, 27.722, 54.023]], - dtype=np.float32), - 'center_of_mass': np.array([[21.286, 41.664, 40.465], - [44.528, 43.426, 78.671], - [2.111, 27.871, 53.767]], - dtype=np.float32), - 'moment_of_inertia': np.array([[16687.941, -1330.617, 2925.883], - [-1330.617, 19256.178, 3354.832], - [2925.883, 3354.832, 8989.946]]), - 'asphericity': np.array([0.61 , 0.701, 0.381]), - 'shape_parameter': np.array([-0.461, 0.35 , 0.311]), + "center_of_geometry": np.array( + [ + [21.356, 41.685, 40.501], + [44.577, 43.312, 79.039], + [2.204, 27.722, 54.023], + ], + dtype=np.float32, + ), + "center_of_mass": np.array( + [ + [21.286, 41.664, 40.465], + [44.528, 43.426, 78.671], + [2.111, 27.871, 53.767], + ], + dtype=np.float32, + ), + "moment_of_inertia": np.array( + [ + [16687.941, -1330.617, 2925.883], + [-1330.617, 19256.178, 3354.832], + [2925.883, 3354.832, 8989.946], + ] + ), + "asphericity": np.array([0.61, 0.701, 0.381]), + "shape_parameter": np.array([-0.461, 0.35, 0.311]), } ref_noUnwrap = { - 'center_of_geometry': np.array([5.1, 7.5, 7.], dtype=np.float32), - 'center_of_mass': np.array([6.48785, 7.5, 7.0], dtype=np.float32), - 'moment_of_inertia': np.array([[0.0, 0.0, 0.0], - [0.0, 98.6542, 0.0], - [0.0, 0.0, 98.65421327]]), - 'asphericity': 1.0, - 'shape_parameter': 1.0, + "center_of_geometry": np.array([5.1, 7.5, 7.0], dtype=np.float32), + "center_of_mass": np.array([6.48785, 7.5, 7.0], dtype=np.float32), + "moment_of_inertia": np.array( + [[0.0, 0.0, 0.0], [0.0, 98.6542, 0.0], [0.0, 0.0, 98.65421327]] + ), + "asphericity": 1.0, + "shape_parameter": 1.0, } ref_Unwrap = { - 'center_of_geometry': np.array([10.1, 7.5, 7.], dtype=np.float32), - 'center_of_mass': np.array([6.8616, 7.5, 7.], dtype=np.float32), - 'moment_of_inertia': np.array([[0.0, 0.0, 0.0], - [0.0, 132.673, 0.0], - [0.0, 0.0, 132.673]]), - 'asphericity': 1.0, - 'shape_parameter': 1.0, + "center_of_geometry": np.array([10.1, 7.5, 7.0], dtype=np.float32), + "center_of_mass": np.array([6.8616, 7.5, 7.0], dtype=np.float32), + "moment_of_inertia": np.array( + [[0.0, 0.0, 0.0], [0.0, 132.673, 0.0], [0.0, 0.0, 132.673]] + ), + "asphericity": 1.0, + "shape_parameter": 1.0, } @pytest.fixture(params=[False, True]) # params indicate shuffling @@ -1076,28 +1203,41 @@ def unwrap_group(self): group.masses = [100.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] return group - @pytest.mark.parametrize('unwrap, ref', ((True, ref_Unwrap_residues), - (False, ref_noUnwrap_residues))) - @pytest.mark.parametrize('method_name', ('center_of_geometry', - 'center_of_mass', - 'moment_of_inertia', - 'asphericity', - 'shape_parameter')) + @pytest.mark.parametrize( + "unwrap, ref", + ((True, ref_Unwrap_residues), (False, ref_noUnwrap_residues)), + ) + @pytest.mark.parametrize( + "method_name", + ( + "center_of_geometry", + "center_of_mass", + "moment_of_inertia", + "asphericity", + "shape_parameter", + ), + ) def test_residues(self, ag, unwrap, ref, method_name): method = getattr(ag, method_name) if unwrap: - result = method(compound='residues', unwrap=unwrap) + result = method(compound="residues", unwrap=unwrap) else: # We test unwrap=False as the default behavior - result = method(compound='residues') + result = method(compound="residues") assert_almost_equal(result, ref[method_name], self.prec) - @pytest.mark.parametrize('unwrap, ref', ((True, ref_Unwrap), - (False, ref_noUnwrap))) - @pytest.mark.parametrize('method_name', ('center_of_geometry', - 'center_of_mass', - 'moment_of_inertia', - 'asphericity')) + @pytest.mark.parametrize( + "unwrap, ref", ((True, ref_Unwrap), (False, ref_noUnwrap)) + ) + @pytest.mark.parametrize( + "method_name", + ( + "center_of_geometry", + "center_of_mass", + "moment_of_inertia", + "asphericity", + ), + ) def test_group(self, unwrap_group, unwrap, ref, method_name): method = getattr(unwrap_group, method_name) if unwrap: @@ -1113,46 +1253,73 @@ class TestPBCFlag(object): prec = 3 ref_noPBC = { - 'center_of_geometry': np.array([4.23789883, 0.62429816, 2.43123484], - dtype=np.float32), - 'center_of_mass': np.array([4.1673783, 0.70507009, 2.21175832]), - 'radius_of_gyration': 119.30368949900134, - 'shape_parameter': 0.6690026954813445, - 'asphericity': 0.5305456387833748, - 'moment_of_inertia': - np.array([[152117.06620921, 55149.54042136, -26630.46034023], - [55149.54042136, 72869.64061494, 21998.1778074], - [-26630.46034023, 21998.1778074, 162388.70002471]]), - 'bbox': np.array([[-75.74159241, -144.86634827, -94.47974396], - [95.83090973, 115.11561584, 88.09812927]], - dtype=np.float32), - 'bsphere': (173.40482, - np.array([4.23789883, 0.62429816, 2.43123484], - dtype=np.float32)), - 'principal_axes': np.array([[0.78787867, 0.26771575, -0.55459488], - [-0.40611024, -0.45112859, -0.7947059], - [-0.46294889, 0.85135849, -0.24671249]]) + "center_of_geometry": np.array( + [4.23789883, 0.62429816, 2.43123484], dtype=np.float32 + ), + "center_of_mass": np.array([4.1673783, 0.70507009, 2.21175832]), + "radius_of_gyration": 119.30368949900134, + "shape_parameter": 0.6690026954813445, + "asphericity": 0.5305456387833748, + "moment_of_inertia": np.array( + [ + [152117.06620921, 55149.54042136, -26630.46034023], + [55149.54042136, 72869.64061494, 21998.1778074], + [-26630.46034023, 21998.1778074, 162388.70002471], + ] + ), + "bbox": np.array( + [ + [-75.74159241, -144.86634827, -94.47974396], + [95.83090973, 115.11561584, 88.09812927], + ], + dtype=np.float32, + ), + "bsphere": ( + 173.40482, + np.array([4.23789883, 0.62429816, 2.43123484], dtype=np.float32), + ), + "principal_axes": np.array( + [ + [0.78787867, 0.26771575, -0.55459488], + [-0.40611024, -0.45112859, -0.7947059], + [-0.46294889, 0.85135849, -0.24671249], + ] + ), } ref_PBC = { - 'center_of_geometry': np.array([26.82960892, 31.5592289, 30.98238945], - dtype=np.float32), - 'center_of_mass': np.array([26.67781143, 31.2104336, 31.19796289]), - 'radius_of_gyration': 27.713008969174918, - 'shape_parameter': 0.0017390512580463542, - 'asphericity': 0.020601215358731016, - 'moment_of_inertia': - np.array([[7333.79167791, -211.8997285, -721.50785456], - [-211.8997285, 7059.07470427, -91.32156884], - [-721.50785456, -91.32156884, 6509.31735029]]), - 'bbox': np.array([[0.145964116, 0.0185623169, 0.0431785583], - [55.3314018, 55.4227829, 55.4158211]], - dtype=np.float32), - 'bsphere': (47.923367, np.array([26.82960892, 31.5592289, 30.98238945], - dtype=np.float32)), - 'principal_axes': np.array([[0.85911708, -0.19258726, -0.4741603], - [0.07520116, 0.96394227, -0.25526473], - [0.50622389, 0.18364489, 0.84262206]]) + "center_of_geometry": np.array( + [26.82960892, 31.5592289, 30.98238945], dtype=np.float32 + ), + "center_of_mass": np.array([26.67781143, 31.2104336, 31.19796289]), + "radius_of_gyration": 27.713008969174918, + "shape_parameter": 0.0017390512580463542, + "asphericity": 0.020601215358731016, + "moment_of_inertia": np.array( + [ + [7333.79167791, -211.8997285, -721.50785456], + [-211.8997285, 7059.07470427, -91.32156884], + [-721.50785456, -91.32156884, 6509.31735029], + ] + ), + "bbox": np.array( + [ + [0.145964116, 0.0185623169, 0.0431785583], + [55.3314018, 55.4227829, 55.4158211], + ], + dtype=np.float32, + ), + "bsphere": ( + 47.923367, + np.array([26.82960892, 31.5592289, 30.98238945], dtype=np.float32), + ), + "principal_axes": np.array( + [ + [0.85911708, -0.19258726, -0.4741603], + [0.07520116, 0.96394227, -0.25526473], + [0.50622389, 0.18364489, 0.84262206], + ] + ), } @pytest.fixture() @@ -1160,17 +1327,23 @@ def ag(self): universe = mda.Universe(TRZ_psf, TRZ) return universe.residues[0:3] - @pytest.mark.parametrize('wrap, ref', ((True, ref_PBC), - (False, ref_noPBC))) - @pytest.mark.parametrize('method_name', ('center_of_geometry', - 'center_of_mass', - 'radius_of_gyration', - 'shape_parameter', - 'asphericity', - 'moment_of_inertia', - 'bbox', - 'bsphere', - 'principal_axes')) + @pytest.mark.parametrize( + "wrap, ref", ((True, ref_PBC), (False, ref_noPBC)) + ) + @pytest.mark.parametrize( + "method_name", + ( + "center_of_geometry", + "center_of_mass", + "radius_of_gyration", + "shape_parameter", + "asphericity", + "moment_of_inertia", + "bbox", + "bsphere", + "principal_axes", + ), + ) def test_wrap(self, ag, wrap, ref, method_name): method = getattr(ag, method_name) if wrap: @@ -1179,7 +1352,7 @@ def test_wrap(self, ag, wrap, ref, method_name): # Test no-wrap as the default behaviour result = method() - if method_name == 'bsphere': + if method_name == "bsphere": assert_almost_equal(result[0], ref[method_name][0], self.prec) assert_almost_equal(result[1], ref[method_name][1], self.prec) else: @@ -1192,6 +1365,7 @@ class TestAtomGroup(object): These are from before the big topology rework (aka #363) but are still valid. There is likely lots of duplication between here and other tests. """ + dih_prec = 2 @pytest.fixture() @@ -1222,25 +1396,22 @@ def test_getitem_int(self, universe): assert_equal(universe.atoms[0].ix, universe.atoms.ix[0]) def test_getitem_slice(self, universe): - assert_equal(universe.atoms[0:4].ix, - universe.atoms.ix[:4]) + assert_equal(universe.atoms[0:4].ix, universe.atoms.ix[:4]) def test_getitem_slice2(self, universe): - assert_equal(universe.atoms[0:8:2].ix, - universe.atoms.ix[0:8:2]) + assert_equal(universe.atoms[0:8:2].ix, universe.atoms.ix[0:8:2]) def test_getitem_IE(self, universe): - d = {'A': 1} + d = {"A": 1} with pytest.raises(IndexError): universe.atoms.__getitem__(d) def test_bad_make(self): with pytest.raises(TypeError): - mda.core.groups.AtomGroup(['these', 'are', 'not', 'atoms']) + mda.core.groups.AtomGroup(["these", "are", "not", "atoms"]) def test_invalid_index_initialisation(self, universe): - indices = [[1, 2, 3], - [4, 5, 6]] + indices = [[1, 2, 3], [4, 5, 6]] with pytest.raises(IndexError): mda.core.groups.AtomGroup(indices, universe) @@ -1279,15 +1450,22 @@ def test_len(self, ag): assert len(ag) == ag.n_atoms, "len and n_atoms disagree" def test_center_of_geometry(self, ag): - assert_almost_equal(ag.center_of_geometry(), - [-0.04223963, 0.0141824, -0.03505163], decimal=5) + assert_almost_equal( + ag.center_of_geometry(), + [-0.04223963, 0.0141824, -0.03505163], + decimal=5, + ) def test_center_of_mass(self, ag): - assert_almost_equal(ag.center_of_mass(), - [-0.01094035, 0.05727601, -0.12885778], decimal=5) + assert_almost_equal( + ag.center_of_mass(), + [-0.01094035, 0.05727601, -0.12885778], + decimal=5, + ) - @pytest.mark.parametrize('method_name', ('center_of_geometry', - 'center_of_mass')) + @pytest.mark.parametrize( + "method_name", ("center_of_geometry", "center_of_mass") + ) def test_center_duplicates(self, ag, method_name): ag2 = ag + ag[0] ref = getattr(ag, method_name)() @@ -1295,69 +1473,88 @@ def test_center_duplicates(self, ag, method_name): assert not np.allclose(getattr(ag2, method_name)(), ref) assert len(w) == 1 - @pytest.mark.parametrize('method_name', ('center_of_geometry', - 'center_of_mass')) - @pytest.mark.parametrize('name, compound', (('resids', 'residues'), - ('segids', 'segments'))) + @pytest.mark.parametrize( + "method_name", ("center_of_geometry", "center_of_mass") + ) + @pytest.mark.parametrize( + "name, compound", (("resids", "residues"), ("segids", "segments")) + ) def test_center_compounds(self, ag, name, compound, method_name): ref = [getattr(a, method_name)() for a in ag.groupby(name).values()] vals = getattr(ag, method_name)(wrap=False, compound=compound) assert_almost_equal(vals, ref, decimal=5) - @pytest.mark.parametrize('method_name', ('center_of_geometry', - 'center_of_mass')) - @pytest.mark.parametrize('name, compound', (('resids', 'residues'), - ('segids', 'segments'))) - @pytest.mark.parametrize('unwrap', (True, False)) - def test_center_compounds_pbc(self, ag, name, compound, - unwrap, method_name): + @pytest.mark.parametrize( + "method_name", ("center_of_geometry", "center_of_mass") + ) + @pytest.mark.parametrize( + "name, compound", (("resids", "residues"), ("segids", "segments")) + ) + @pytest.mark.parametrize("unwrap", (True, False)) + def test_center_compounds_pbc( + self, ag, name, compound, unwrap, method_name + ): ag.dimensions = [50, 50, 50, 90, 90, 90] - ref = [getattr(a, method_name)(unwrap=unwrap) - for a in ag.groupby(name).values()] - vals = getattr(ag, method_name)(compound=compound, - unwrap=unwrap) + ref = [ + getattr(a, method_name)(unwrap=unwrap) + for a in ag.groupby(name).values() + ] + vals = getattr(ag, method_name)(compound=compound, unwrap=unwrap) assert_almost_equal(vals, ref, decimal=5) - @pytest.mark.parametrize('method_name', ('center_of_geometry', - 'center_of_mass')) - @pytest.mark.parametrize('name, compound', (('molnums', 'molecules'), - ('fragindices', 'fragments'))) - def test_center_compounds_special(self, ag_molfrg, name, - compound, method_name): - ref = [getattr(a, method_name)() - for a in ag_molfrg.groupby(name).values()] + @pytest.mark.parametrize( + "method_name", ("center_of_geometry", "center_of_mass") + ) + @pytest.mark.parametrize( + "name, compound", + (("molnums", "molecules"), ("fragindices", "fragments")), + ) + def test_center_compounds_special( + self, ag_molfrg, name, compound, method_name + ): + ref = [ + getattr(a, method_name)() for a in ag_molfrg.groupby(name).values() + ] vals = getattr(ag_molfrg, method_name)(wrap=False, compound=compound) assert_almost_equal(vals, ref, decimal=5) - @pytest.mark.parametrize('method_name', ('center_of_geometry', - 'center_of_mass')) - @pytest.mark.parametrize('name, compound', (('molnums', 'molecules'), - ('fragindices', 'fragments'))) - @pytest.mark.parametrize('unwrap', (True, False)) - def test_center_compounds_special_pbc(self, ag_molfrg, name, compound, - unwrap, method_name): + @pytest.mark.parametrize( + "method_name", ("center_of_geometry", "center_of_mass") + ) + @pytest.mark.parametrize( + "name, compound", + (("molnums", "molecules"), ("fragindices", "fragments")), + ) + @pytest.mark.parametrize("unwrap", (True, False)) + def test_center_compounds_special_pbc( + self, ag_molfrg, name, compound, unwrap, method_name + ): ag_molfrg.dimensions = [50, 50, 50, 90, 90, 90] - ref = [getattr(a, method_name)(unwrap=unwrap) - for a in ag_molfrg.groupby(name).values()] - vals = getattr(ag_molfrg, method_name)(compound=compound, - unwrap=unwrap) + ref = [ + getattr(a, method_name)(unwrap=unwrap) + for a in ag_molfrg.groupby(name).values() + ] + vals = getattr(ag_molfrg, method_name)( + compound=compound, unwrap=unwrap + ) assert_almost_equal(vals, ref, decimal=5) def test_center_wrong_compound(self, ag): with pytest.raises(ValueError): ag.center(weights=None, compound="foo") - @pytest.mark.parametrize('compound', ('molecules', 'fragments')) + @pytest.mark.parametrize("compound", ("molecules", "fragments")) def test_center_compounds_special_fail(self, ag_no_molfrg, compound): with pytest.raises(NoDataError): ag_no_molfrg.center(weights=None, compound=compound) - @pytest.mark.parametrize('weights', (None, - np.array([0.0]), - np.array([2.0]))) - @pytest.mark.parametrize('compound', ('group', 'residues', 'segments', - 'molecules', 'fragments')) - @pytest.mark.parametrize('wrap', (False, True)) + @pytest.mark.parametrize( + "weights", (None, np.array([0.0]), np.array([2.0])) + ) + @pytest.mark.parametrize( + "compound", ("group", "residues", "segments", "molecules", "fragments") + ) + @pytest.mark.parametrize("wrap", (False, True)) def test_center_compounds_single(self, ag_molfrg, wrap, weights, compound): at = ag_molfrg[0] if weights is None or weights[0] != 0.0: @@ -1367,54 +1564,72 @@ def test_center_compounds_single(self, ag_molfrg, wrap, weights, compound): else: ref = at.position.astype(np.float64) else: - ref = np.full((3,), np.nan,np.float64) - if compound != 'group': + ref = np.full((3,), np.nan, np.float64) + if compound != "group": ref = ref.reshape((1, 3)) ag_s = mda.AtomGroup([at]) assert_equal(ref, ag_s.center(weights, wrap=wrap, compound=compound)) - @pytest.mark.parametrize('wrap', (False, True)) - @pytest.mark.parametrize('weights', (None, np.array([]))) - @pytest.mark.parametrize('compound', ('group', 'residues', 'segments', - 'molecules', 'fragments')) + @pytest.mark.parametrize("wrap", (False, True)) + @pytest.mark.parametrize("weights", (None, np.array([]))) + @pytest.mark.parametrize( + "compound", ("group", "residues", "segments", "molecules", "fragments") + ) def test_center_compounds_empty(self, ag_molfrg, wrap, weights, compound): ref = np.empty((0, 3), dtype=np.float64) ag_e = mda.AtomGroup([], ag_molfrg.universe) assert_equal(ref, ag_e.center(weights, wrap=wrap, compound=compound)) - @pytest.mark.parametrize('wrap', (False, True)) - @pytest.mark.parametrize('name, compound', (('', 'group'), - ('resids', 'residues'), - ('segids', 'segments'), - ('molnums', 'molecules'), - ('fragindices', 'fragments'))) - def test_center_compounds_zero_weights(self, ag_molfrg, wrap, name, - compound): - if compound == 'group': + @pytest.mark.parametrize("wrap", (False, True)) + @pytest.mark.parametrize( + "name, compound", + ( + ("", "group"), + ("resids", "residues"), + ("segids", "segments"), + ("molnums", "molecules"), + ("fragindices", "fragments"), + ), + ) + def test_center_compounds_zero_weights( + self, ag_molfrg, wrap, name, compound + ): + if compound == "group": ref = np.full((3,), np.nan) else: n_compounds = len(ag_molfrg.groupby(name)) ref = np.full((n_compounds, 3), np.nan, dtype=np.float64) weights = np.zeros(len(ag_molfrg)) - assert_equal(ref, ag_molfrg.center(weights, wrap=wrap, - compound=compound)) + assert_equal( + ref, ag_molfrg.center(weights, wrap=wrap, compound=compound) + ) def test_coordinates(self, ag): assert_almost_equal( ag.positions[1000:2000:200], - np.array([[3.94543672, -12.4060812, -7.26820087], - [13.21632767, 5.879035, -14.67914867], - [12.07735443, -9.00604534, 4.09301519], - [11.35541916, 7.0690732, -0.32511973], - [-13.26763439, 4.90658951, 10.6880455]], - dtype=np.float32)) + np.array( + [ + [3.94543672, -12.4060812, -7.26820087], + [13.21632767, 5.879035, -14.67914867], + [12.07735443, -9.00604534, 4.09301519], + [11.35541916, 7.0690732, -0.32511973], + [-13.26763439, 4.90658951, 10.6880455], + ], + dtype=np.float32, + ), + ) def test_principal_axes(self, ag): assert_almost_equal( ag.principal_axes(), - np.array([[1.53389276e-03, 4.41386224e-02, 9.99024239e-01], - [1.20986911e-02, 9.98951474e-01, -4.41539838e-02], - [-9.99925632e-01, 1.21546132e-02, 9.98264877e-04],])) + np.array( + [ + [1.53389276e-03, 4.41386224e-02, 9.99024239e-01], + [1.20986911e-02, 9.98951474e-01, -4.41539838e-02], + [-9.99925632e-01, 1.21546132e-02, 9.98264877e-04], + ] + ), + ) def test_principal_axes_duplicates(self, ag): ag2 = ag + ag[0] @@ -1424,7 +1639,7 @@ def test_principal_axes_duplicates(self, ag): assert len(w) == 1 def test_moment_of_inertia_duplicates(self, universe): - ag = universe.select_atoms('segid 4AKE') + ag = universe.select_atoms("segid 4AKE") ag2 = ag + ag[0] ref = ag.moment_of_inertia() with pytest.warns(DuplicateWarning) as w: @@ -1432,7 +1647,7 @@ def test_moment_of_inertia_duplicates(self, universe): assert len(w) == 1 def test_radius_of_gyration_duplicates(self, universe): - ag = universe.select_atoms('segid 4AKE') + ag = universe.select_atoms("segid 4AKE") ag2 = ag + ag[0] ref = ag.radius_of_gyration() with pytest.warns(DuplicateWarning) as w: @@ -1490,12 +1705,15 @@ def test_charges_ndarray(self, ag): assert isinstance(ag.charges, np.ndarray) def test_charges(self, ag): - assert_almost_equal(ag.charges[1000:2000:200], - np.array([-0.09, 0.09, -0.47, 0.51, 0.09])) + assert_almost_equal( + ag.charges[1000:2000:200], + np.array([-0.09, 0.09, -0.47, 0.51, 0.09]), + ) def test_bad_add_AG(self, ag): def bad_add(): return ag + [1, 2, 3] + with pytest.raises(TypeError): bad_add() @@ -1522,7 +1740,7 @@ def test_set_resnum_single(self, universe): def test_set_resname_single(self, universe): ag = universe.atoms[:3] - new = 'abc' + new = "abc" ag.residues.resnames = new for at in ag: assert_equal(at.resname, new) @@ -1532,7 +1750,7 @@ def test_packintobox_badshape(self, universe): ag = universe.atoms[:10] box = np.zeros(9, dtype=np.float32).reshape(3, 3) with pytest.raises(ValueError): - ag.pack_into_box(box = box) + ag.pack_into_box(box=box) def test_packintobox_noshape(self, universe): ag = universe.atoms[:10] @@ -1550,41 +1768,49 @@ def test_packintobox(self, universe): u = universe ag = u.atoms[1000:2000:200] # Provide arbitrary box - box = np.array([5., 5., 5., 90., 90., 90.], dtype=np.float32) + box = np.array([5.0, 5.0, 5.0, 90.0, 90.0, 90.0], dtype=np.float32) # Expected folded coordinates - packed_coords = np.array([[3.94543672, 2.5939188, 2.73179913], - [3.21632767, 0.879035, 0.32085133], - [2.07735443, 0.99395466, 4.09301519], - [1.35541916, 2.0690732, 4.67488003], - [1.73236561, 4.90658951, 0.6880455]], - dtype=np.float32) + packed_coords = np.array( + [ + [3.94543672, 2.5939188, 2.73179913], + [3.21632767, 0.879035, 0.32085133], + [2.07735443, 0.99395466, 4.09301519], + [1.35541916, 2.0690732, 4.67488003], + [1.73236561, 4.90658951, 0.6880455], + ], + dtype=np.float32, + ) ag.pack_into_box(box=box) assert_almost_equal(ag.positions, packed_coords) # Check with duplicates: ag += ag ag.pack_into_box(box=box) - assert_almost_equal(ag.positions, - np.vstack((packed_coords, packed_coords))) + assert_almost_equal( + ag.positions, np.vstack((packed_coords, packed_coords)) + ) def test_residues(self, universe): u = universe - assert_equal(u.residues[100].atoms.ix, - u.select_atoms('resname ILE and resid 101').atoms.ix, - "Direct selection from residue group does not match " - "expected I101.") + assert_equal( + u.residues[100].atoms.ix, + u.select_atoms("resname ILE and resid 101").atoms.ix, + "Direct selection from residue group does not match " + "expected I101.", + ) def test_index_integer(self, universe): u = universe a = u.atoms[100] - assert isinstance(a, mda.core.groups.Atom), ("integer index did not " - "return Atom") + assert isinstance(a, mda.core.groups.Atom), ( + "integer index did not " "return Atom" + ) def test_index_slice(self, universe): u = universe a = u.atoms[100:200:10] - assert isinstance(a, mda.core.groups.AtomGroup), ("slice index did " - "not return " - "AtomGroup") + assert isinstance(a, mda.core.groups.AtomGroup), ( + "slice index did " "not return " "AtomGroup" + ) def test_index_slice_empty(self, universe): u = universe @@ -1594,20 +1820,20 @@ def test_index_advancedslice(self, universe): u = universe aslice = [0, 10, 20, -1, 10] ag = u.atoms[aslice] - assert isinstance(ag, mda.core.groups.AtomGroup), ("advanced slicing " - "does not produce " - "an AtomGroup") + assert isinstance(ag, mda.core.groups.AtomGroup), ( + "advanced slicing " "does not produce " "an AtomGroup" + ) assert_equal(ag[1], ag[-1], "advanced slicing does not preserve order") def test_2d_indexing_caught(self, universe): u = universe - index_2d = [[1, 2, 3], - [4, 5, 6]] + index_2d = [[1, 2, 3], [4, 5, 6]] with pytest.raises(IndexError): u.atoms[index_2d] - @pytest.mark.parametrize('sel', (np.array([True, False, True]), - [True, False, True])) + @pytest.mark.parametrize( + "sel", (np.array([True, False, True]), [True, False, True]) + ) def test_boolean_indexing_2(self, universe, sel): # index an array with a sequence of bools # issue #282 @@ -1629,62 +1855,90 @@ def test_dihedral_ValueError(self, universe): 4 atoms given""" nodih = universe.select_atoms("resid 3:10") with pytest.raises(ValueError): - getattr(nodih, 'dihedral') + getattr(nodih, "dihedral") nodih = universe.select_atoms("resid 3:5") with pytest.raises(ValueError): - getattr(nodih, 'dihedral') + getattr(nodih, "dihedral") def test_improper(self, universe): u = universe - peptbond = u.select_atoms("atom 4AKE 20 C", "atom 4AKE 21 CA", - "atom 4AKE 21 N", "atom 4AKE 21 HN") - assert_almost_equal(peptbond.improper.value(), 168.52952575683594, - self.dih_prec, - "Peptide bond improper dihedral for M21 " - "calculated wrongly.") + peptbond = u.select_atoms( + "atom 4AKE 20 C", + "atom 4AKE 21 CA", + "atom 4AKE 21 N", + "atom 4AKE 21 HN", + ) + assert_almost_equal( + peptbond.improper.value(), + 168.52952575683594, + self.dih_prec, + "Peptide bond improper dihedral for M21 " "calculated wrongly.", + ) def test_dihedral_equals_improper(self, universe): u = universe - peptbond = u.select_atoms("atom 4AKE 20 C", "atom 4AKE 21 CA", - "atom 4AKE 21 N", "atom 4AKE 21 HN") - assert_equal(peptbond.improper.value(), peptbond.dihedral.value(), - "improper() and proper dihedral() give different results") + peptbond = u.select_atoms( + "atom 4AKE 20 C", + "atom 4AKE 21 CA", + "atom 4AKE 21 N", + "atom 4AKE 21 HN", + ) + assert_equal( + peptbond.improper.value(), + peptbond.dihedral.value(), + "improper() and proper dihedral() give different results", + ) def test_bond(self, universe): - sel2 = universe.select_atoms('segid 4AKE and resid 98' - ).select_atoms("name OE1", "name OE2") - assert_almost_equal(sel2.bond.value(), 2.1210737228393555, 3, - "distance of Glu98 OE1--OE2 wrong") + sel2 = universe.select_atoms("segid 4AKE and resid 98").select_atoms( + "name OE1", "name OE2" + ) + assert_almost_equal( + sel2.bond.value(), + 2.1210737228393555, + 3, + "distance of Glu98 OE1--OE2 wrong", + ) def test_bond_pbc(self, universe): - sel2 = universe.select_atoms('segid 4AKE and resid 98' - ).select_atoms("name OE1", "name OE2") - assert_almost_equal(sel2.bond.value(pbc=True), 2.1210737228393555, 3, - "distance of Glu98 OE1--OE2 wrong") + sel2 = universe.select_atoms("segid 4AKE and resid 98").select_atoms( + "name OE1", "name OE2" + ) + assert_almost_equal( + sel2.bond.value(pbc=True), + 2.1210737228393555, + 3, + "distance of Glu98 OE1--OE2 wrong", + ) def test_bond_ValueError(self, universe): ag = universe.atoms[:4] with pytest.raises(ValueError): - getattr(ag, 'bond') + getattr(ag, "bond") def test_angle(self, universe): - sel3 = universe.select_atoms('segid 4AKE and resid 98').select_atoms( - 'name OE1', 'name CD', 'name OE2') - assert_almost_equal(sel3.angle.value(), 117.46187591552734, 3, - "angle of Glu98 OE1-CD-OE2 wrong") + sel3 = universe.select_atoms("segid 4AKE and resid 98").select_atoms( + "name OE1", "name CD", "name OE2" + ) + assert_almost_equal( + sel3.angle.value(), + 117.46187591552734, + 3, + "angle of Glu98 OE1-CD-OE2 wrong", + ) def test_angle_ValueError(self, universe): ag = universe.atoms[:2] with pytest.raises(ValueError): - getattr(ag, 'angle') + getattr(ag, "angle") def test_shape_parameter(self, universe): - s = universe.select_atoms('segid 4AKE').shape_parameter() + s = universe.select_atoms("segid 4AKE").shape_parameter() assert_almost_equal(s, 0.00240753939086033, 6) def test_shape_parameter_duplicates(self, universe): - ag = universe.select_atoms('segid 4AKE') + ag = universe.select_atoms("segid 4AKE") ag2 = ag + ag[0] ref = ag.shape_parameter() with pytest.warns(DuplicateWarning) as w: @@ -1692,11 +1946,11 @@ def test_shape_parameter_duplicates(self, universe): assert len(w) == 1 def test_asphericity(self, universe): - a = universe.select_atoms('segid 4AKE').asphericity() + a = universe.select_atoms("segid 4AKE").asphericity() assert_almost_equal(a, 0.020227504542775828, 6) def test_asphericity_duplicates(self, universe): - ag = universe.select_atoms('segid 4AKE') + ag = universe.select_atoms("segid 4AKE") ag2 = ag + ag[0] ref = ag.asphericity() with pytest.warns(DuplicateWarning) as w: @@ -1708,9 +1962,11 @@ def test_positions(self, universe): pos = ag.positions + 3.14 ag.positions = pos # should work - assert_almost_equal(ag.positions, pos, - err_msg="failed to update atoms 12:42 position " - "to new position") + assert_almost_equal( + ag.positions, + pos, + err_msg="failed to update atoms 12:42 position " "to new position", + ) rg = np.random.RandomState(121989) # create wrong size array @@ -1720,14 +1976,13 @@ def test_positions(self, universe): def test_set_names(self, universe): ag = universe.atoms[:2] - names = ['One', 'Two'] + names = ["One", "Two"] ag.names = names for a, b in zip(ag, names): assert_equal(a.name, b) def test_atom_order(self, universe): - assert_equal(universe.atoms.indices, - sorted(universe.atoms.indices)) + assert_equal(universe.atoms.indices, sorted(universe.atoms.indices)) class TestAtomGroupTimestep(object): @@ -1740,20 +1995,24 @@ def universe(self): return mda.Universe(TRZ_psf, TRZ) def test_partial_timestep(self, universe): - ag = universe.select_atoms('name Ca') + ag = universe.select_atoms("name Ca") idx = ag.indices assert len(ag.ts._pos) == len(ag) for ts in universe.trajectory[0:20:5]: - assert_almost_equal(ts.positions[idx], - ag.ts.positions, - self.prec, - err_msg="Partial timestep coordinates wrong") - assert_almost_equal(ts.velocities[idx], - ag.ts.velocities, - self.prec, - err_msg="Partial timestep coordinates wrong") + assert_almost_equal( + ts.positions[idx], + ag.ts.positions, + self.prec, + err_msg="Partial timestep coordinates wrong", + ) + assert_almost_equal( + ts.velocities[idx], + ag.ts.velocities, + self.prec, + err_msg="Partial timestep coordinates wrong", + ) class TestAtomGroupSort(object): @@ -1769,16 +2028,16 @@ def universe(self): residue_segindex=np.array([0, 0, 1]), trajectory=True, velocities=True, - forces=True + forces=True, ) attributes = ["id", "charge", "mass", "tempfactor"] - for i in (attributes): + for i in attributes: u.add_TopologyAttr(i, [6, 5, 4, 3, 2, 1, 0]) - u.add_TopologyAttr('resid', [2, 1, 0]) - u.add_TopologyAttr('segid', [1, 0]) - u.add_TopologyAttr('bonds', [(0, 1)]) + u.add_TopologyAttr("resid", [2, 1, 0]) + u.add_TopologyAttr("segid", [1, 0]) + u.add_TopologyAttr("bonds", [(0, 1)]) return u @@ -1789,13 +2048,13 @@ def ag(self, universe): return ag test_ids = [ - "ix", - "ids", - "resids", - "segids", - "charges", - "masses", - "tempfactors" + "ix", + "ids", + "resids", + "segids", + "charges", + "masses", + "tempfactors", ] test_data = [ @@ -1814,8 +2073,9 @@ def test_sort(self, ag, inputs, expected): assert np.array_equal(expected, agsort.ix) def test_sort_bonds(self, ag): - with pytest.raises(ValueError, match=r"The array returned by the " - "attribute"): + with pytest.raises( + ValueError, match=r"The array returned by the " "attribute" + ): ag.sort("bonds") def test_sort_positions_2D(self, ag): @@ -1823,8 +2083,11 @@ def test_sort_positions_2D(self, ag): ag.sort("positions", keyfunc=lambda x: x) def test_sort_position_no_keyfunc(self, ag): - with pytest.raises(NameError, match=r"The .* attribute returns a " - "multidimensional array. In order to sort it, "): + with pytest.raises( + NameError, + match=r"The .* attribute returns a " + "multidimensional array. In order to sort it, ", + ): ag.sort("positions") def test_sort_position(self, ag): diff --git a/testsuite/MDAnalysisTests/core/test_atomselections.py b/testsuite/MDAnalysisTests/core/test_atomselections.py index bced4c43bde..5238553d8a5 100644 --- a/testsuite/MDAnalysisTests/core/test_atomselections.py +++ b/testsuite/MDAnalysisTests/core/test_atomselections.py @@ -26,7 +26,7 @@ import itertools import numpy as np from numpy.lib import NumpyVersion -from numpy.testing import( +from numpy.testing import ( assert_equal, ) @@ -38,11 +38,19 @@ from MDAnalysis import SelectionError, SelectionWarning from MDAnalysis.tests.datafiles import ( - PSF, DCD, - PRMpbc, TRJpbc_bz2, - PSF_NAMD, PDB_NAMD, - GRO, RNA_PSF, NUCLsel, TPR, XTC, - TRZ_psf, TRZ, + PSF, + DCD, + PRMpbc, + TRJpbc_bz2, + PSF_NAMD, + PDB_NAMD, + GRO, + RNA_PSF, + NUCLsel, + TPR, + XTC, + TRZ_psf, + TRZ, PDB_icodes, PDB_HOLE, PDB_helix, @@ -55,7 +63,7 @@ class TestSelectionsCHARMM(object): - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe(self): """Set up the standard AdK system in implicit solvent. Geometry here is orthogonal @@ -67,22 +75,28 @@ def universe_copy(self, universe): return MDAnalysis.Universe(PSF, DCD) def test_segid(self, universe): - sel = universe.select_atoms('segid 4AKE') + sel = universe.select_atoms("segid 4AKE") assert_equal(sel.n_atoms, 3341, "failed to select segment 4AKE") - assert_equal(sorted(sel.indices), - sorted(universe.select_atoms('segid 4AKE').indices), - "selected segment 4AKE is not the same as auto-generated segment s4AKE") + assert_equal( + sorted(sel.indices), + sorted(universe.select_atoms("segid 4AKE").indices), + "selected segment 4AKE is not the same as auto-generated segment s4AKE", + ) def test_protein(self, universe): - sel = universe.select_atoms('protein') + sel = universe.select_atoms("protein") assert_equal(sel.n_atoms, 3341, "failed to select protein") - assert_equal(sorted(sel.indices), - sorted(universe.select_atoms('segid 4AKE').indices), - "selected protein is not the same as auto-generated protein segment s4AKE") + assert_equal( + sorted(sel.indices), + sorted(universe.select_atoms("segid 4AKE").indices), + "selected protein is not the same as auto-generated protein segment s4AKE", + ) - @pytest.mark.parametrize('resname', sorted(MDAnalysis.core.selection.ProteinSelection.prot_res)) + @pytest.mark.parametrize( + "resname", sorted(MDAnalysis.core.selection.ProteinSelection.prot_res) + ) def test_protein_resnames(self, resname): - u = make_Universe(('resnames',)) + u = make_Universe(("resnames",)) # set half the residues' names to the resname we're testing myprot = u.residues[::2] # Windows note: the parametrized test input string objects @@ -90,78 +104,88 @@ def test_protein_resnames(self, resname): # proper is needed for unit test on Windows myprot.resnames = str(resname) # select protein - sel = u.select_atoms('protein') + sel = u.select_atoms("protein") # check that contents (atom indices) are identical afterwards assert_equal(myprot.atoms.ix, sel.ix) def test_backbone(self, universe): - sel = universe.select_atoms('backbone') + sel = universe.select_atoms("backbone") assert_equal(sel.n_atoms, 855) def test_resid_single(self, universe): - sel = universe.select_atoms('resid 100') + sel = universe.select_atoms("resid 100") assert_equal(sel.n_atoms, 7) - assert_equal(sel.residues.resnames, ['GLY']) + assert_equal(sel.residues.resnames, ["GLY"]) def test_resid_range(self, universe): - sel = universe.select_atoms('resid 100:105') + sel = universe.select_atoms("resid 100:105") assert_equal(sel.n_atoms, 89) - assert_equal(sel.residues.resnames, - ['GLY', 'ILE', 'ASN', 'VAL', 'ASP', 'TYR']) + assert_equal( + sel.residues.resnames, ["GLY", "ILE", "ASN", "VAL", "ASP", "TYR"] + ) def test_selgroup(self, universe): - sel = universe.select_atoms('not resid 100') - sel2 = universe.select_atoms('not group notr100', notr100=sel) + sel = universe.select_atoms("not resid 100") + sel2 = universe.select_atoms("not group notr100", notr100=sel) assert_equal(sel2.n_atoms, 7) - assert_equal(sel2.residues.resnames, ['GLY']) + assert_equal(sel2.residues.resnames, ["GLY"]) def test_fullselgroup(self, universe): - sel1 = universe.select_atoms('resid 101') - sel2 = universe.select_atoms('resid 100') - sel3 = sel1.select_atoms('global group r100', r100=sel2) + sel1 = universe.select_atoms("resid 101") + sel2 = universe.select_atoms("resid 100") + sel3 = sel1.select_atoms("global group r100", r100=sel2) assert_equal(sel2.n_atoms, 7) - assert_equal(sel2.residues.resnames, ['GLY']) + assert_equal(sel2.residues.resnames, ["GLY"]) # resnum selections are boring here because we haven't really a mechanism yet # to assign the canonical PDB resnums def test_resnum_single(self, universe): - sel = universe.select_atoms('resnum 100') + sel = universe.select_atoms("resnum 100") assert_equal(sel.n_atoms, 7) assert_equal(sel.residues.resids, [100]) - assert_equal(sel.residues.resnames, ['GLY']) + assert_equal(sel.residues.resnames, ["GLY"]) def test_resnum_range(self, universe): - sel = universe.select_atoms('resnum 100:105') + sel = universe.select_atoms("resnum 100:105") assert_equal(sel.n_atoms, 89) assert_equal(sel.residues.resids, range(100, 106)) - assert_equal(sel.residues.resnames, - ['GLY', 'ILE', 'ASN', 'VAL', 'ASP', 'TYR']) + assert_equal( + sel.residues.resnames, ["GLY", "ILE", "ASN", "VAL", "ASP", "TYR"] + ) def test_resname(self, universe): - sel = universe.select_atoms('resname LEU') - assert_equal(sel.n_atoms, 304, - "Failed to find all 'resname LEU' atoms.") - assert_equal(sel.n_residues, 16, - "Failed to find all 'resname LEU' residues.") - assert_equal(sorted(sel.indices), - sorted(universe.select_atoms('segid 4AKE and resname LEU').indices), - "selected 'resname LEU' atoms are not the same as auto-generated s4AKE.LEU") + sel = universe.select_atoms("resname LEU") + assert_equal( + sel.n_atoms, 304, "Failed to find all 'resname LEU' atoms." + ) + assert_equal( + sel.n_residues, 16, "Failed to find all 'resname LEU' residues." + ) + assert_equal( + sorted(sel.indices), + sorted( + universe.select_atoms("segid 4AKE and resname LEU").indices + ), + "selected 'resname LEU' atoms are not the same as auto-generated s4AKE.LEU", + ) def test_name(self, universe): - sel = universe.select_atoms('name CA') + sel = universe.select_atoms("name CA") assert_equal(sel.n_atoms, 214) def test_atom(self, universe): - sel = universe.select_atoms('atom 4AKE 100 CA') + sel = universe.select_atoms("atom 4AKE 100 CA") assert_equal(len(sel), 1) - assert_equal(sel.resnames, ['GLY']) + assert_equal(sel.resnames, ["GLY"]) assert_equal( sel.positions, - np.array([[20.38685226, -3.44224262, -5.92158318]], - dtype=np.float32)) + np.array( + [[20.38685226, -3.44224262, -5.92158318]], dtype=np.float32 + ), + ) def test_atom_empty(self, universe): - sel = universe.select_atoms('atom 4AKE 100 XX') # Does not exist + sel = universe.select_atoms("atom 4AKE 100 XX") # Does not exist assert_equal(len(sel), 0) def test_type(self, universe): @@ -169,124 +193,126 @@ def test_type(self, universe): assert_equal(len(sel), 253) def test_and(self, universe): - sel = universe.select_atoms('resname GLY and resid 100') + sel = universe.select_atoms("resname GLY and resid 100") assert_equal(len(sel), 7) def test_or(self, universe): - sel = universe.select_atoms('resname LYS or resname ARG') + sel = universe.select_atoms("resname LYS or resname ARG") assert_equal(sel.n_residues, 31) def test_not(self, universe): - sel = universe.select_atoms('not backbone') + sel = universe.select_atoms("not backbone") assert_equal(len(sel), 2486) - @pytest.mark.parametrize('selstr', [ - 'around 4.0 bynum 1943', - 'around 4.0 index 1942' - ]) + @pytest.mark.parametrize( + "selstr", ["around 4.0 bynum 1943", "around 4.0 index 1942"] + ) def test_around(self, universe, selstr): sel = universe.select_atoms(selstr) assert_equal(len(sel), 32) - @pytest.mark.parametrize('selstr', [ - 'sphlayer 4.0 6.0 bynum 1281', - 'sphlayer 4.0 6.0 index 1280' - ]) + @pytest.mark.parametrize( + "selstr", + ["sphlayer 4.0 6.0 bynum 1281", "sphlayer 4.0 6.0 index 1280"], + ) def test_sphlayer(self, universe, selstr): sel = universe.select_atoms(selstr) assert_equal(len(sel), 66) - @pytest.mark.parametrize('selstr', [ - 'isolayer 4.0 6.0 bynum 1281', - 'isolayer 4.0 6.0 index 1280' - ]) + @pytest.mark.parametrize( + "selstr", + ["isolayer 4.0 6.0 bynum 1281", "isolayer 4.0 6.0 index 1280"], + ) def test_isolayer(self, universe, selstr): sel = universe.select_atoms(selstr) assert_equal(len(sel), 66) - @pytest.mark.parametrize('selstr', [ - 'sphzone 6.0 bynum 1281', - 'sphzone 6.0 index 1280' - ]) + @pytest.mark.parametrize( + "selstr", ["sphzone 6.0 bynum 1281", "sphzone 6.0 index 1280"] + ) def test_sphzone(self, universe, selstr): sel = universe.select_atoms(selstr) assert_equal(len(sel), 86) - @pytest.mark.parametrize('selstr', [ - 'cylayer 4.0 6.0 10 -10 bynum 1281', - 'cylayer 4.0 6.0 10 -10 index 1280' - ]) + @pytest.mark.parametrize( + "selstr", + [ + "cylayer 4.0 6.0 10 -10 bynum 1281", + "cylayer 4.0 6.0 10 -10 index 1280", + ], + ) def test_cylayer(self, universe, selstr): sel = universe.select_atoms(selstr) assert_equal(len(sel), 88) def test_empty_cylayer(self, universe): - empty = universe.select_atoms('cylayer 4.0 6.0 10 -10 name NOT_A_NAME') + empty = universe.select_atoms("cylayer 4.0 6.0 10 -10 name NOT_A_NAME") assert_equal(len(empty), 0) - @pytest.mark.parametrize('selstr', [ - 'cyzone 6.0 10 -10 bynum 1281', - 'cyzone 6.0 10 -10 index 1280' - ]) + @pytest.mark.parametrize( + "selstr", + ["cyzone 6.0 10 -10 bynum 1281", "cyzone 6.0 10 -10 index 1280"], + ) def test_cyzone(self, universe, selstr): sel = universe.select_atoms(selstr) assert_equal(len(sel), 166) def test_empty_cyzone(self, universe): - empty = universe.select_atoms('cyzone 6.0 10 -10 name NOT_A_NAME') + empty = universe.select_atoms("cyzone 6.0 10 -10 name NOT_A_NAME") assert_equal(len(empty), 0) def test_point(self, universe): - ag = universe.select_atoms('point 5.0 5.0 5.0 3.5') + ag = universe.select_atoms("point 5.0 5.0 5.0 3.5") - d = distance_array(np.array([[5.0, 5.0, 5.0]], dtype=np.float32), - universe.atoms.positions, - box=universe.dimensions) + d = distance_array( + np.array([[5.0, 5.0, 5.0]], dtype=np.float32), + universe.atoms.positions, + box=universe.dimensions, + ) idx = np.where(d < 3.5)[1] assert_equal(set(ag.indices), set(idx)) def test_prop(self, universe): - sel = universe.select_atoms('prop y <= 16') - sel2 = universe.select_atoms('prop abs z < 8') + sel = universe.select_atoms("prop y <= 16") + sel2 = universe.select_atoms("prop abs z < 8") assert_equal(len(sel), 3194) assert_equal(len(sel2), 2001) def test_bynum(self, universe): "Tests the bynum selection, also from AtomGroup instances (Issue 275)" - sel = universe.select_atoms('bynum 5') + sel = universe.select_atoms("bynum 5") assert_equal(sel[0].index, 4) - sel = universe.select_atoms('bynum 1:10') + sel = universe.select_atoms("bynum 1:10") assert_equal(len(sel), 10) assert_equal(sel[0].index, 0) assert_equal(sel[-1].index, 9) - subsel = sel.select_atoms('bynum 5') + subsel = sel.select_atoms("bynum 5") assert_equal(subsel[0].index, 4) - subsel = sel.select_atoms('bynum 2:5') + subsel = sel.select_atoms("bynum 2:5") assert_equal(len(subsel), 4) assert_equal(subsel[0].index, 1) assert_equal(subsel[-1].index, 4) def test_index(self, universe): "Tests the index selection, also from AtomGroup instances (Issue 275)" - sel = universe.select_atoms('index 4') + sel = universe.select_atoms("index 4") assert_equal(sel[0].index, 4) - sel = universe.select_atoms('index 0:9') + sel = universe.select_atoms("index 0:9") assert_equal(len(sel), 10) assert_equal(sel[0].index, 0) assert_equal(sel[-1].index, 9) - subsel = sel.select_atoms('index 4') + subsel = sel.select_atoms("index 4") assert_equal(subsel[0].index, 4) - subsel = sel.select_atoms('index 1:4') + subsel = sel.select_atoms("index 1:4") assert_equal(len(subsel), 4) assert_equal(subsel[0].index, 1) assert_equal(subsel[-1].index, 4) - @pytest.mark.parametrize('selstr,expected', ( - ['byres bynum 0:5', 19], - ['byres index 0:19', 43] - )) + @pytest.mark.parametrize( + "selstr,expected", (["byres bynum 0:5", 19], ["byres index 0:19", 43]) + ) def test_byres(self, universe, selstr, expected): sel = universe.select_atoms(selstr) assert_equal(len(sel), expected) @@ -294,23 +320,69 @@ def test_byres(self, universe, selstr, expected): def test_same_resname(self, universe): """Test the 'same ... as' construct (Issue 217)""" sel = universe.select_atoms("same resname as resid 10 or resid 11") - assert_equal(len(sel), 331, - ("Found a wrong number of atoms with same resname as " - "resids 10 or 11")) - target_resids = np.array([7, 8, 10, 11, 12, 14, 17, 25, 32, 37, 38, - 42, 46, 49, 55, 56, 66, 73, 80, 85, 93, 95, - 99, 100, 122, 127, 130, 144, 150, 176, 180, - 186, 188, 189, 194, 198, 203, 207, 214]) - assert_equal(sel.residues.resids, target_resids, - ("Found wrong residues with same resname as " - "resids 10 or 11")) + assert_equal( + len(sel), + 331, + ( + "Found a wrong number of atoms with same resname as " + "resids 10 or 11" + ), + ) + target_resids = np.array( + [ + 7, + 8, + 10, + 11, + 12, + 14, + 17, + 25, + 32, + 37, + 38, + 42, + 46, + 49, + 55, + 56, + 66, + 73, + 80, + 85, + 93, + 95, + 99, + 100, + 122, + 127, + 130, + 144, + 150, + 176, + 180, + 186, + 188, + 189, + 194, + 198, + 203, + 207, + 214, + ] + ) + assert_equal( + sel.residues.resids, + target_resids, + ("Found wrong residues with same resname as " "resids 10 or 11"), + ) def test_same_segment(self, universe_copy): """Test the 'same ... as' construct (Issue 217)""" - SNew_A = universe_copy.add_Segment(segid='A') - SNew_B = universe_copy.add_Segment(segid='B') - SNew_C = universe_copy.add_Segment(segid='C') + SNew_A = universe_copy.add_Segment(segid="A") + SNew_B = universe_copy.add_Segment(segid="B") + SNew_C = universe_copy.add_Segment(segid="C") universe_copy.residues[:100].segments = SNew_A universe_copy.residues[100:150].segments = SNew_B @@ -318,103 +390,128 @@ def test_same_segment(self, universe_copy): target_resids = np.arange(100) + 1 sel = universe_copy.select_atoms("same segment as resid 10") - assert_equal(len(sel), 1520, - "Found a wrong number of atoms in the same segment of resid 10") - assert_equal(sel.residues.resids, - target_resids, - "Found wrong residues in the same segment of resid 10") + assert_equal( + len(sel), + 1520, + "Found a wrong number of atoms in the same segment of resid 10", + ) + assert_equal( + sel.residues.resids, + target_resids, + "Found wrong residues in the same segment of resid 10", + ) target_resids = np.arange(100, 150) + 1 sel = universe_copy.select_atoms("same segment as resid 110") - assert_equal(len(sel), 797, - "Found a wrong number of atoms in the same segment of resid 110") - assert_equal(sel.residues.resids, target_resids, - "Found wrong residues in the same segment of resid 110") + assert_equal( + len(sel), + 797, + "Found a wrong number of atoms in the same segment of resid 110", + ) + assert_equal( + sel.residues.resids, + target_resids, + "Found wrong residues in the same segment of resid 110", + ) target_resids = np.arange(150, universe_copy.atoms.n_residues) + 1 sel = universe_copy.select_atoms("same segment as resid 160") - assert_equal(len(sel), 1024, - "Found a wrong number of atoms in the same segment of resid 160") - assert_equal(sel.residues.resids, target_resids, - "Found wrong residues in the same segment of resid 160") + assert_equal( + len(sel), + 1024, + "Found a wrong number of atoms in the same segment of resid 160", + ) + assert_equal( + sel.residues.resids, + target_resids, + "Found wrong residues in the same segment of resid 160", + ) def test_empty_same(self, universe): - ag = universe.select_atoms('resname MET') + ag = universe.select_atoms("resname MET") # No GLY, so 'as resname GLY' is empty - ag2 = ag.select_atoms('same mass as resname GLY') + ag2 = ag.select_atoms("same mass as resname GLY") assert len(ag2) == 0 def test_empty_selection(self, universe): """Test that empty selection can be processed (see Issue 12)""" # no Trp in AdK - assert_equal(len(universe.select_atoms('resname TRP')), 0) + assert_equal(len(universe.select_atoms("resname TRP")), 0) def test_parenthesized_expression(self, universe): - sel = universe.select_atoms( - '( name CA or name CB ) and resname LEU') + sel = universe.select_atoms("( name CA or name CB ) and resname LEU") assert_equal(len(sel), 32) def test_no_space_around_parentheses(self, universe): """Test that no space is needed around parentheses (Issue 43).""" # note: will currently be ERROR because it throws a ParseError - sel = universe.select_atoms('(name CA or name CB) and resname LEU') + sel = universe.select_atoms("(name CA or name CB) and resname LEU") assert_equal(len(sel), 32) # TODO: # test for checking ordering and multiple comma-separated selections def test_concatenated_selection(self, universe): - E151 = universe.select_atoms('segid 4AKE').select_atoms('resid 151') + E151 = universe.select_atoms("segid 4AKE").select_atoms("resid 151") # note that this is not quite phi... HN should be C of prec. residue - phi151 = E151.atoms.select_atoms('name HN', 'name N', 'name CA', - 'name CB') + phi151 = E151.atoms.select_atoms( + "name HN", "name N", "name CA", "name CB" + ) assert_equal(len(phi151), 4) - assert_equal(phi151[0].name, 'HN', - "wrong ordering in selection, should be HN-N-CA-CB") + assert_equal( + phi151[0].name, + "HN", + "wrong ordering in selection, should be HN-N-CA-CB", + ) def test_global(self, universe): """Test the `global` modifier keyword (Issue 268)""" ag = universe.select_atoms("resname LYS and name NZ") # Lys amines within 4 angstrom of the backbone. ag1 = universe.select_atoms( - "resname LYS and name NZ and around 4 backbone") + "resname LYS and name NZ and around 4 backbone" + ) ag2 = ag.select_atoms("around 4 global backbone") assert_equal(ag2.indices, ag1.indices) - @pytest.mark.parametrize('selstring, wildstring', [ - ('resname TYR THR', 'resname T*R'), - ('resname ASN GLN', 'resname *N'), - ('resname ASN ASP', 'resname AS*'), - ('resname TYR THR', 'resname T?R'), - ('resname ASN ASP HSD', 'resname *S?'), - ('resname LEU LYS', 'resname L**'), - ('resname MET', 'resname *M*'), - ('resname GLN GLY', 'resname GL[NY]'), - ('resname GLU', 'resname GL[!NY]'), - ]) + @pytest.mark.parametrize( + "selstring, wildstring", + [ + ("resname TYR THR", "resname T*R"), + ("resname ASN GLN", "resname *N"), + ("resname ASN ASP", "resname AS*"), + ("resname TYR THR", "resname T?R"), + ("resname ASN ASP HSD", "resname *S?"), + ("resname LEU LYS", "resname L**"), + ("resname MET", "resname *M*"), + ("resname GLN GLY", "resname GL[NY]"), + ("resname GLU", "resname GL[!NY]"), + ], + ) def test_wildcard_selection(self, universe, selstring, wildstring): ag = universe.select_atoms(selstring) ag_wild = universe.select_atoms(wildstring) assert ag == ag_wild + class TestSelectionsAMBER(object): @pytest.fixture() def universe(self): return MDAnalysis.Universe(PRMpbc, TRJpbc_bz2) def test_protein(self, universe): - sel = universe.select_atoms('protein') + sel = universe.select_atoms("protein") assert_equal(sel.n_atoms, 22, "failed to select protein") def test_backbone(self, universe): - sel = universe.select_atoms('backbone') + sel = universe.select_atoms("backbone") assert_equal(sel.n_atoms, 7) def test_type(self, universe): - sel = universe.select_atoms('type HC') + sel = universe.select_atoms("type HC") assert_equal(len(sel), 6) - assert_equal(sel.names, ['HH31', 'HH32', 'HH33', 'HB1', 'HB2', 'HB3']) + assert_equal(sel.names, ["HH31", "HH32", "HH33", "HB1", "HB2", "HB3"]) class TestSelectionsNAMD(object): @@ -424,23 +521,24 @@ def universe(self): def test_protein(self, universe): # must include non-standard residues - sel = universe.select_atoms( - 'protein or resname HAO or resname ORT') - assert_equal(sel.n_atoms, universe.atoms.n_atoms, - "failed to select peptide") - assert_equal(sel.n_residues, 6, - "failed to select all peptide residues") + sel = universe.select_atoms("protein or resname HAO or resname ORT") + assert_equal( + sel.n_atoms, universe.atoms.n_atoms, "failed to select peptide" + ) + assert_equal( + sel.n_residues, 6, "failed to select all peptide residues" + ) def test_resid_single(self, universe): - sel = universe.select_atoms('resid 12') + sel = universe.select_atoms("resid 12") assert_equal(sel.n_atoms, 26) - assert_equal(sel.residues.resnames, ['HAO']) + assert_equal(sel.residues.resnames, ["HAO"]) def test_type(self, universe): - sel = universe.select_atoms('type H') + sel = universe.select_atoms("type H") assert_equal(len(sel), 5) # note 4th HH - assert_equal(sel.names, ['HN', 'HN', 'HN', 'HH', 'HN']) + assert_equal(sel.names, ["HN", "HN", "HN", "HH", "HN"]) class TestSelectionsGRO(object): @@ -449,76 +547,84 @@ def universe(self): return MDAnalysis.Universe(GRO) def test_protein(self, universe): - sel = universe.select_atoms('protein') + sel = universe.select_atoms("protein") assert_equal(sel.n_atoms, 3341, "failed to select protein") def test_backbone(self, universe): - sel = universe.select_atoms('backbone') + sel = universe.select_atoms("backbone") assert_equal(sel.n_atoms, 855) def test_resid_single(self, universe): - sel = universe.select_atoms('resid 100') + sel = universe.select_atoms("resid 100") assert_equal(sel.n_atoms, 7) - assert_equal(sel.residues.resnames, ['GLY']) - - @pytest.mark.parametrize('selstr', [ - 'same x as bynum 1 or bynum 10', - 'same x as bynum 1 10', - 'same x as index 0 or index 9', - 'same x as index 0 9' - ]) + assert_equal(sel.residues.resnames, ["GLY"]) + + @pytest.mark.parametrize( + "selstr", + [ + "same x as bynum 1 or bynum 10", + "same x as bynum 1 10", + "same x as index 0 or index 9", + "same x as index 0 9", + ], + ) def test_same_coordinate(self, universe, selstr): """Test the 'same ... as' construct (Issue 217)""" sel = universe.select_atoms(selstr) - errmsg = ("Found a wrong number of atoms with same " - "x as ids 1 or 10") - assert_equal(len(sel), 12, errmsg) - target_ids = np.array([0, 8, 9, 224, 643, 3515, 11210, 14121, - 18430, 25418, 35811, 43618]) - assert_equal(sel.indices, target_ids, - "Found wrong atoms with same x as ids 1 or 10") - - @pytest.mark.parametrize('selstr', [ - 'cylayer 10 20 20 -20 bynum 3554', - 'cylayer 10 20 20 -20 index 3553' - ]) + errmsg = "Found a wrong number of atoms with same " "x as ids 1 or 10" + assert_equal(len(sel), 12, errmsg) + target_ids = np.array( + [0, 8, 9, 224, 643, 3515, 11210, 14121, 18430, 25418, 35811, 43618] + ) + assert_equal( + sel.indices, + target_ids, + "Found wrong atoms with same x as ids 1 or 10", + ) + + @pytest.mark.parametrize( + "selstr", + ["cylayer 10 20 20 -20 bynum 3554", "cylayer 10 20 20 -20 index 3553"], + ) def test_cylayer(self, universe, selstr): """Cylinder layer selections with tricilinic periodicity (Issue 274)""" - atgp = universe.select_atoms('name OW') + atgp = universe.select_atoms("name OW") sel = atgp.select_atoms(selstr) assert_equal(len(sel), 1155) - @pytest.mark.parametrize('selstr', [ - 'cyzone 20 20 -20 bynum 3554', - 'cyzone 20 20 -20 index 3553' - ]) + @pytest.mark.parametrize( + "selstr", + ["cyzone 20 20 -20 bynum 3554", "cyzone 20 20 -20 index 3553"], + ) def test_cyzone(self, universe, selstr): """Cylinder zone selections with tricilinic periodicity (Issue 274)""" - atgp = universe.select_atoms('name OW') + atgp = universe.select_atoms("name OW") sel = atgp.select_atoms(selstr) assert_equal(len(sel), 1556) class TestSelectionsTPR(object): @staticmethod - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe(): return MDAnalysis.Universe(TPR, XTC, tpr_resid_from_one=False) - @pytest.mark.parametrize('selstr', [ - 'same fragment as bynum 1', - 'same fragment as index 0' - ]) + @pytest.mark.parametrize( + "selstr", ["same fragment as bynum 1", "same fragment as index 0"] + ) def test_same_fragment(self, universe, selstr): """Test the 'same ... as' construct (Issue 217)""" # This test comes here because it's a system with solvent, # and thus multiple fragments. sel = universe.select_atoms(selstr) - errmsg = ("Found a wrong number of atoms " - "on the same fragment as id 1") + errmsg = ( + "Found a wrong number of atoms " "on the same fragment as id 1" + ) assert_equal(len(sel), 3341, errmsg) - errmsg = ("Found a differ set of atoms when using the 'same " - "fragment as' construct vs. the .fragment property") + errmsg = ( + "Found a differ set of atoms when using the 'same " + "fragment as' construct vs. the .fragment property" + ) assert_equal(sel.indices, universe.atoms[0].fragment.indices, errmsg) def test_moltype(self, universe): @@ -527,11 +633,27 @@ def test_moltype(self, universe): assert_equal(sel.ids, ref) @pytest.mark.parametrize( - 'selection_string,reference', - (('molnum 1', [3341, 3342, 3343, 3344]), - ('molnum 2:4', [3345, 3346, 3347, 3348, 3349, 3350, - 3351, 3352, 3353, 3354, 3355, 3356]), - ) + "selection_string,reference", + ( + ("molnum 1", [3341, 3342, 3343, 3344]), + ( + "molnum 2:4", + [ + 3345, + 3346, + 3347, + 3348, + 3349, + 3350, + 3351, + 3352, + 3353, + 3354, + 3355, + 3356, + ], + ), + ), ) def test_molnum(self, universe, selection_string, reference): sel = universe.select_atoms(selection_string) @@ -548,8 +670,9 @@ def setup_class(self): @pytest.fixture def u(self): smi = "Cc1cNcc1" - u = MDAnalysis.Universe.from_smiles(smi, addHs=False, - generate_coordinates=False) + u = MDAnalysis.Universe.from_smiles( + smi, addHs=False, generate_coordinates=False + ) return u @pytest.fixture @@ -557,29 +680,35 @@ def u2(self): u = MDAnalysis.Universe.from_smiles("Nc1cc(C[C@H]([O-])C=O)c[nH]1") return u - @pytest.mark.parametrize("sel_str, n_atoms", [ - ("aromatic", 5), - ("not aromatic", 1), - ("type N and aromatic", 1), - ("type C and aromatic", 4), - ]) + @pytest.mark.parametrize( + "sel_str, n_atoms", + [ + ("aromatic", 5), + ("not aromatic", 1), + ("type N and aromatic", 1), + ("type C and aromatic", 4), + ], + ) def test_aromatic_selection(self, u, sel_str, n_atoms): sel = u.select_atoms(sel_str) assert sel.n_atoms == n_atoms - @pytest.mark.parametrize("sel_str, indices", [ - ("smarts n", [10]), - ("smarts [#7]", [0, 10]), - ("smarts a", [1, 2, 3, 9, 10]), - ("smarts c", [1, 2, 3, 9]), - ("smarts [*-]", [6]), - ("smarts [$([!#1]);$([!R][R])]", [0, 4]), - ("smarts [$([C@H](-[CH2])(-[O-])-C=O)]", [5]), - ("smarts [$([C@@H](-[CH2])(-[O-])-C=O)]", []), - ("smarts a and type C", [1, 2, 3, 9]), - ("(smarts a) and (type C)", [1, 2, 3, 9]), - ("smarts a and type N", [10]), - ]) + @pytest.mark.parametrize( + "sel_str, indices", + [ + ("smarts n", [10]), + ("smarts [#7]", [0, 10]), + ("smarts a", [1, 2, 3, 9, 10]), + ("smarts c", [1, 2, 3, 9]), + ("smarts [*-]", [6]), + ("smarts [$([!#1]);$([!R][R])]", [0, 4]), + ("smarts [$([C@H](-[CH2])(-[O-])-C=O)]", [5]), + ("smarts [$([C@@H](-[CH2])(-[O-])-C=O)]", []), + ("smarts a and type C", [1, 2, 3, 9]), + ("(smarts a) and (type C)", [1, 2, 3, 9]), + ("smarts a and type N", [10]), + ], + ) def test_smarts_selection(self, u2, sel_str, indices): sel = u2.select_atoms(sel_str) assert_equal(sel.indices, indices) @@ -597,7 +726,8 @@ def test_passing_max_matches_to_converter(self, u2): with pytest.warns(UserWarning, match="Your smarts-based") as wsmg: sel = u2.select_atoms("smarts C", smarts_kwargs=dict(maxMatches=2)) sel2 = u2.select_atoms( - "smarts C", smarts_kwargs=dict(maxMatches=1000)) + "smarts C", smarts_kwargs=dict(maxMatches=1000) + ) assert sel.n_atoms == 2 assert sel2.n_atoms == 3 @@ -608,27 +738,28 @@ def test_passing_use_chirality_to_converter(self): u = mda.Universe.from_smiles("CC[C@H](C)O") sel3 = u.select_atoms("byres smarts CC[C@@H](C)O") assert sel3.n_atoms == 0 - sel4 = u.select_atoms("byres smarts CC[C@@H](C)O", smarts_kwargs={"useChirality": False}) + sel4 = u.select_atoms( + "byres smarts CC[C@@H](C)O", smarts_kwargs={"useChirality": False} + ) assert sel4.n_atoms == 15 class TestSelectionsNucleicAcids(object): - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe(self): return MDAnalysis.Universe(RNA_PSF) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def universe2(self): return MDAnalysis.Universe(NUCLsel) - def test_nucleic(self, universe): rna = universe.select_atoms("nucleic") assert_equal(rna.n_atoms, 739) assert_equal(rna.n_residues, 23) def test_nucleic_all(self, universe2): - sel = universe2.select_atoms('nucleic') + sel = universe2.select_atoms("nucleic") assert len(sel) == 34 def test_nucleicbackbone(self, universe): @@ -661,9 +792,10 @@ class BaseDistanceSelection(object): Cylindrical methods don't use KDTree """ - @pytest.mark.parametrize('periodic', (True, False)) + + @pytest.mark.parametrize("periodic", (True, False)) def test_around(self, u, periodic): - sel = Parser.parse('around 5.0 resid 1', u.atoms) + sel = Parser.parse("around 5.0 resid 1", u.atoms) if periodic: sel.periodic = True else: @@ -671,21 +803,20 @@ def test_around(self, u, periodic): result = sel.apply(u.atoms) - r1 = u.select_atoms('resid 1') + r1 = u.select_atoms("resid 1") cog = r1.center_of_geometry().reshape(1, 3) box = u.dimensions if periodic else None - d = distance_array(u.atoms.positions, r1.positions, - box=box) + d = distance_array(u.atoms.positions, r1.positions, box=box) ref = set(np.where(d < 5.0)[0]) # Around doesn't include atoms from the reference group ref.difference_update(set(r1.indices)) assert ref == set(result.indices) - @pytest.mark.parametrize('periodic', (True, False)) + @pytest.mark.parametrize("periodic", (True, False)) def test_spherical_layer(self, u, periodic): - sel = Parser.parse('sphlayer 2.4 6.0 resid 1', u.atoms) + sel = Parser.parse("sphlayer 2.4 6.0 resid 1", u.atoms) if periodic: sel.periodic = True else: @@ -693,7 +824,7 @@ def test_spherical_layer(self, u, periodic): result = sel.apply(u.atoms) - r1 = u.select_atoms('resid 1') + r1 = u.select_atoms("resid 1") box = u.dimensions if periodic else None cog = r1.center_of_geometry().reshape(1, 3) d = distance_array(u.atoms.positions, cog, box=box) @@ -701,7 +832,7 @@ def test_spherical_layer(self, u, periodic): assert ref == set(result.indices) - @pytest.mark.parametrize('periodic', (True, False)) + @pytest.mark.parametrize("periodic", (True, False)) def test_isolayer(self, u, periodic): rmin, rmax = 2.4, 3 @@ -710,7 +841,10 @@ def test_isolayer(self, u, periodic): else: r1 = u.select_atoms("resid 1 or resid 2 and type O") - sel = Parser.parse("isolayer {} {} (index {} or index {})".format(rmin, rmax, *r1.ix), u.atoms) + sel = Parser.parse( + "isolayer {} {} (index {} or index {})".format(rmin, rmax, *r1.ix), + u.atoms, + ) if periodic: sel.periodic = True else: @@ -723,15 +857,15 @@ def test_isolayer(self, u, periodic): cog2 = r1[1].position.reshape(1, 3) d2 = distance_array(u.atoms.positions, cog2, box=box) - ref_inner = set(np.where((d1 < rmin) | (d2 < rmin))[0]) - ref_outer = set(np.where((d1 < rmax) | (d2 < rmax))[0]) + ref_inner = set(np.where((d1 < rmin) | (d2 < rmin))[0]) + ref_outer = set(np.where((d1 < rmax) | (d2 < rmax))[0]) ref_outer -= ref_inner assert ref_outer == set(result.indices) and len(list(ref_outer)) > 0 - @pytest.mark.parametrize('periodic', (True, False)) + @pytest.mark.parametrize("periodic", (True, False)) def test_spherical_zone(self, u, periodic): - sel = Parser.parse('sphzone 5.0 resid 1', u.atoms) + sel = Parser.parse("sphzone 5.0 resid 1", u.atoms) if periodic: sel.periodic = True else: @@ -739,7 +873,7 @@ def test_spherical_zone(self, u, periodic): result = sel.apply(u.atoms) - r1 = u.select_atoms('resid 1') + r1 = u.select_atoms("resid 1") box = u.dimensions if periodic else None cog = r1.center_of_geometry().reshape(1, 3) d = distance_array(u.atoms.positions, cog, box=box) @@ -747,9 +881,9 @@ def test_spherical_zone(self, u, periodic): assert ref == set(result.indices) - @pytest.mark.parametrize('periodic', (True, False)) + @pytest.mark.parametrize("periodic", (True, False)) def test_point(self, u, periodic): - sel = Parser.parse('point 5.0 5.0 5.0 3.0', u.atoms) + sel = Parser.parse("point 5.0 5.0 5.0 3.0", u.atoms) if periodic: sel.periodic = True else: @@ -757,9 +891,11 @@ def test_point(self, u, periodic): result = sel.apply(u.atoms) box = u.dimensions if periodic else None - d = distance_array(np.array([[5.0, 5.0, 5.0]], dtype=np.float32), - u.atoms.positions, - box=box) + d = distance_array( + np.array([[5.0, 5.0, 5.0]], dtype=np.float32), + u.atoms.positions, + box=box, + ) ref = set(np.where(d < 3.0)[1]) assert ref == set(result.indices) @@ -770,16 +906,15 @@ class TestOrthogonalDistanceSelections(BaseDistanceSelection): def u(self): return mda.Universe(TRZ_psf, TRZ) - @pytest.mark.parametrize('meth, periodic', [ - ('distmat', True), - ('distmat', False) - ]) + @pytest.mark.parametrize( + "meth, periodic", [("distmat", True), ("distmat", False)] + ) def test_cyzone(self, u, meth, periodic): - sel = Parser.parse('cyzone 5 4 -4 resid 2', u.atoms) + sel = Parser.parse("cyzone 5 4 -4 resid 2", u.atoms) sel.periodic = periodic result = sel.apply(u.atoms) - other = u.select_atoms('resid 2') + other = u.select_atoms("resid 2") pos = other.center_of_geometry() vecs = u.atoms.positions - pos @@ -790,15 +925,15 @@ def test_cyzone(self, u, meth, periodic): mask = (vecs[:, 2] > -4) & (vecs[:, 2] < 4) radii = vecs[:, 0] ** 2 + vecs[:, 1] ** 2 - mask &= radii < 5 ** 2 + mask &= radii < 5**2 ref = set(u.atoms[mask].indices) assert ref == set(result.indices) - @pytest.mark.parametrize('periodic,expected', ([True, 33], [False, 25])) + @pytest.mark.parametrize("periodic,expected", ([True, 33], [False, 25])) def test_sphzone(self, u, periodic, expected): - sel = u.select_atoms('sphzone 5.0 resid 1', periodic=periodic) + sel = u.select_atoms("sphzone 5.0 resid 1", periodic=periodic) assert len(sel) == expected @@ -820,9 +955,9 @@ def u(self): return mda.Universe(GRO) def test_around(self, u): - r1 = u.select_atoms('resid 1') + r1 = u.select_atoms("resid 1") - ag = u.select_atoms('around 5.0 resid 1') + ag = u.select_atoms("around 5.0 resid 1") d = distance_array(u.atoms.positions, r1.positions, box=u.dimensions) idx = set(np.where(d < 5.0)[0]) @@ -832,10 +967,10 @@ def test_around(self, u): assert idx == set(ag.indices) def test_sphlayer(self, u): - r1 = u.select_atoms('resid 1') + r1 = u.select_atoms("resid 1") cog = r1.center_of_geometry().reshape(1, 3) - ag = u.select_atoms('sphlayer 2.4 6.0 resid 1') + ag = u.select_atoms("sphlayer 2.4 6.0 resid 1") d = distance_array(u.atoms.positions, cog, box=u.dimensions) idx = set(np.where((d > 2.4) & (d < 6.0))[0]) @@ -843,18 +978,18 @@ def test_sphlayer(self, u): assert idx == set(ag.indices) def test_empty_sphlayer(self, u): - empty = u.select_atoms('sphlayer 2.4 6.0 name NOT_A_NAME') + empty = u.select_atoms("sphlayer 2.4 6.0 name NOT_A_NAME") assert len(empty) == 0 def test_empty_isolayer(self, u): - empty = u.select_atoms('isolayer 2.4 6.0 name NOT_A_NAME') + empty = u.select_atoms("isolayer 2.4 6.0 name NOT_A_NAME") assert len(empty) == 0 def test_sphzone(self, u): - r1 = u.select_atoms('resid 1') + r1 = u.select_atoms("resid 1") cog = r1.center_of_geometry().reshape(1, 3) - ag = u.select_atoms('sphzone 5.0 resid 1') + ag = u.select_atoms("sphzone 5.0 resid 1") d = distance_array(u.atoms.positions, cog, box=u.dimensions) idx = set(np.where(d < 5.0)[0]) @@ -862,16 +997,18 @@ def test_sphzone(self, u): assert idx == set(ag.indices) def test_empty_sphzone(self, u): - empty = u.select_atoms('sphzone 5.0 name NOT_A_NAME') + empty = u.select_atoms("sphzone 5.0 name NOT_A_NAME") assert len(empty) == 0 def test_point_1(self, u): # The example selection - ag = u.select_atoms('point 5.0 5.0 5.0 3.5') + ag = u.select_atoms("point 5.0 5.0 5.0 3.5") - d = distance_array(np.array([[5.0, 5.0, 5.0]], dtype=np.float32), - u.atoms.positions, - box=u.dimensions) + d = distance_array( + np.array([[5.0, 5.0, 5.0]], dtype=np.float32), + u.atoms.positions, + box=u.dimensions, + ) idx = np.where(d < 3.5)[1] @@ -880,11 +1017,13 @@ def test_point_1(self, u): def test_point_2(self, u): ag1 = u.atoms[:10000] - ag2 = ag1.select_atoms('point 5.0 5.0 5.0 3.5') + ag2 = ag1.select_atoms("point 5.0 5.0 5.0 3.5") - d = distance_array(np.array([[5.0, 5.0, 5.0]], dtype=np.float32), - ag1.positions, - box=u.dimensions) + d = distance_array( + np.array([[5.0, 5.0, 5.0]], dtype=np.float32), + ag1.positions, + box=u.dimensions, + ) idx = np.where(d < 3.5)[1] @@ -901,112 +1040,145 @@ def gen_sel_strings(prop, oper): 'prop x<1.5' """ - for x, y in itertools.product([' ', ''], [' ', '']): - yield 'prop {prop}{spc1}{oper}{spc2}1.5'.format( - prop=prop, spc1=x, oper=oper, spc2=y) + for x, y in itertools.product([" ", ""], [" ", ""]): + yield "prop {prop}{spc1}{oper}{spc2}1.5".format( + prop=prop, spc1=x, oper=oper, spc2=y + ) class TestPropSelection(object): - plurals = {'mass': 'masses', - 'charge': 'charges'} + plurals = {"mass": "masses", "charge": "charges"} op_funcs = { - '<': np.less, - '<=': np.less_equal, - '>': np.greater, - '>=': np.greater_equal, - '==': np.equal, - '!=': np.not_equal + "<": np.less, + "<=": np.less_equal, + ">": np.greater, + ">=": np.greater_equal, + "==": np.equal, + "!=": np.not_equal, } opposites = { - '==': '==', '!=': '!=', - '>': '<=', '<=': '>', - '<': '>=', '>=': '<', + "==": "==", + "!=": "!=", + ">": "<=", + "<=": ">", + "<": ">=", + ">=": "<", } @pytest.fixture(params=[slice(None, None), slice(None, 100)]) def ag(self, request): - u = make_Universe(('masses', 'charges')) + u = make_Universe(("masses", "charges")) u.atoms[::2].masses = 1.5 u.atoms[::2].charges = 1.5 return u.atoms[request.param] - @pytest.mark.parametrize('prop, selstr', [ - (prop, sel) - for prop in ['mass', 'charge'] - for sel in gen_sel_strings(prop, '<') - ]) + @pytest.mark.parametrize( + "prop, selstr", + [ + (prop, sel) + for prop in ["mass", "charge"] + for sel in gen_sel_strings(prop, "<") + ], + ) def test_lt(self, prop, selstr, ag): sel = ag.select_atoms(selstr) - assert_equal(set(sel.indices), - set(ag[getattr(ag, self.plurals[prop]) < 1.5].indices)) - - @pytest.mark.parametrize('prop, selstr', [ - (prop, sel) - for prop in ['mass', 'charge'] - for sel in gen_sel_strings(prop, '<=') - ]) + assert_equal( + set(sel.indices), + set(ag[getattr(ag, self.plurals[prop]) < 1.5].indices), + ) + + @pytest.mark.parametrize( + "prop, selstr", + [ + (prop, sel) + for prop in ["mass", "charge"] + for sel in gen_sel_strings(prop, "<=") + ], + ) def test_le(self, prop, selstr, ag): sel = ag.select_atoms(selstr) - assert_equal(set(sel.indices), - set(ag[getattr(ag, - self.plurals[prop]) <= 1.5].indices)) - - @pytest.mark.parametrize('prop, selstr', [ - (prop, sel) - for prop in ['mass', 'charge'] - for sel in gen_sel_strings(prop, '>') - ]) + assert_equal( + set(sel.indices), + set(ag[getattr(ag, self.plurals[prop]) <= 1.5].indices), + ) + + @pytest.mark.parametrize( + "prop, selstr", + [ + (prop, sel) + for prop in ["mass", "charge"] + for sel in gen_sel_strings(prop, ">") + ], + ) def test_gt(self, prop, selstr, ag): sel = ag.select_atoms(selstr) - assert_equal(set(sel.indices), - set(ag[getattr(ag, self.plurals[prop]) > 1.5].indices)) - - @pytest.mark.parametrize('prop, selstr', [ - (prop, sel) - for prop in ['mass', 'charge'] - for sel in gen_sel_strings(prop, '>=') - ]) + assert_equal( + set(sel.indices), + set(ag[getattr(ag, self.plurals[prop]) > 1.5].indices), + ) + + @pytest.mark.parametrize( + "prop, selstr", + [ + (prop, sel) + for prop in ["mass", "charge"] + for sel in gen_sel_strings(prop, ">=") + ], + ) def test_ge(self, prop, selstr, ag): sel = ag.select_atoms(selstr) - assert_equal(set(sel.indices), - set(ag[getattr(ag, - self.plurals[prop]) >= 1.5].indices)) - - @pytest.mark.parametrize('prop, selstr', [ - (prop, sel) - for prop in ['mass', 'charge'] - for sel in gen_sel_strings(prop, '==') - ]) + assert_equal( + set(sel.indices), + set(ag[getattr(ag, self.plurals[prop]) >= 1.5].indices), + ) + + @pytest.mark.parametrize( + "prop, selstr", + [ + (prop, sel) + for prop in ["mass", "charge"] + for sel in gen_sel_strings(prop, "==") + ], + ) def test_eq(self, prop, selstr, ag): sel = ag.select_atoms(selstr) - assert_equal(set(sel.indices), - set(ag[getattr(ag, - self.plurals[prop]) == 1.5].indices)) - - @pytest.mark.parametrize('prop, selstr', [ - (prop, sel) - for prop in ['mass', 'charge'] - for sel in gen_sel_strings(prop, '!=') - ]) + assert_equal( + set(sel.indices), + set(ag[getattr(ag, self.plurals[prop]) == 1.5].indices), + ) + + @pytest.mark.parametrize( + "prop, selstr", + [ + (prop, sel) + for prop in ["mass", "charge"] + for sel in gen_sel_strings(prop, "!=") + ], + ) def test_ne(self, prop, selstr, ag): sel = ag.select_atoms(selstr) - assert_equal(set(sel.indices), - set(ag[getattr(ag, - self.plurals[prop]) != 1.5].indices)) - - @pytest.mark.parametrize('prop, op', [ - (prop, op) - for prop in ['mass', 'charge'] - for op in ('<', '>', '<=', '>=', '==', '!=') - ]) + assert_equal( + set(sel.indices), + set(ag[getattr(ag, self.plurals[prop]) != 1.5].indices), + ) + + @pytest.mark.parametrize( + "prop, op", + [ + (prop, op) + for prop in ["mass", "charge"] + for op in ("<", ">", "<=", ">=", "==", "!=") + ], + ) def test_flip(self, prop, ag, op): func = self.op_funcs[op] # reference group, doing things forwards ref = ag[func(getattr(ag, self.plurals[prop]), 1.5)] - selstr = 'prop 1.5 {op} {prop}'.format( - op=self.opposites[op], prop=prop) + selstr = "prop 1.5 {op} {prop}".format( + op=self.opposites[op], prop=prop + ) sel = ag.select_atoms(selstr) assert_equal(set(ref.indices), set(sel.indices)) @@ -1018,19 +1190,19 @@ def u(self): return mda.Universe(PSF, DCD) def test_bonded_1(self, u): - ag = u.select_atoms('type 2 and bonded name N') + ag = u.select_atoms("type 2 and bonded name N") assert len(ag) == 3 def test_nobonds_warns(self, u): - u = make_Universe(('names',)) + u = make_Universe(("names",)) # empty bond topology attr batt = mda.core.topologyattrs.Bonds([]) u.add_TopologyAttr(batt) with pytest.warns(UserWarning): - u.select_atoms('bonded name AAA') + u.select_atoms("bonded name AAA") class TestSelectionErrors(object): @@ -1038,36 +1210,40 @@ class TestSelectionErrors(object): @pytest.fixture() def universe(): return make_Universe( - ('names', 'masses', 'resids', 'resnames', 'resnums')) - - @pytest.mark.parametrize('selstr', [ - 'name and H', # string selection - 'name )', - 'resid abcd', # resid arg parsing selection - 'resnum 7a7', # rangeselection arg parsing - 'resid 1-', - 'prop chicken == tasty', - 'prop chicken <= 7.4', - 'prop mass ^^ 12.0', - 'same this as resid 1', # same selection - 'same resid resname mass 5.0', # same / expect - 'name H and', # check all tokens used - 'naem H', # unkonwn (misplet) opertaor - 'resid and name C', # rangesel not finding vals - 'resnum ', - 'bynum or protein', - 'index or protein', - 'prop mass < 4.0 hello', # unused token - 'prop mass > 10. and group this', # missing group - # bad ranges - 'mass 1.0 to', - 'mass to 3.0', - 'mass 1.0:', - 'mass :3.0', - 'mass 1-', - 'chirality ', - 'formalcharge 0.2', - ]) + ("names", "masses", "resids", "resnames", "resnums") + ) + + @pytest.mark.parametrize( + "selstr", + [ + "name and H", # string selection + "name )", + "resid abcd", # resid arg parsing selection + "resnum 7a7", # rangeselection arg parsing + "resid 1-", + "prop chicken == tasty", + "prop chicken <= 7.4", + "prop mass ^^ 12.0", + "same this as resid 1", # same selection + "same resid resname mass 5.0", # same / expect + "name H and", # check all tokens used + "naem H", # unkonwn (misplet) opertaor + "resid and name C", # rangesel not finding vals + "resnum ", + "bynum or protein", + "index or protein", + "prop mass < 4.0 hello", # unused token + "prop mass > 10. and group this", # missing group + # bad ranges + "mass 1.0 to", + "mass to 3.0", + "mass 1.0:", + "mass :3.0", + "mass 1-", + "chirality ", + "formalcharge 0.2", + ], + ) def test_selection_fail(self, selstr, universe): with pytest.raises(SelectionError): universe.select_atoms(selstr) @@ -1078,11 +1254,11 @@ def test_invalid_prop_selection(self, universe): def test_segid_and_resid(): - u = make_Universe(('segids', 'resids')) + u = make_Universe(("segids", "resids")) - ag = u.select_atoms('segid SegB and resid 1-100') + ag = u.select_atoms("segid SegB and resid 1-100") - ref = ag.select_atoms('segid SegB').select_atoms('resid 1-100') + ref = ag.select_atoms("segid SegB").select_atoms("resid 1-100") assert_equal(ag.indices, ref.indices) @@ -1092,7 +1268,8 @@ class TestImplicitOr(object): @pytest.fixture() def universe(): return make_Universe( - ('names', 'types', 'resids', 'resnums', 'resnames', 'segids')) + ("names", "types", "resids", "resnums", "resnames", "segids") + ) def _check_sels(self, ref, sel, universe): ref = universe.select_atoms(ref) @@ -1100,31 +1277,45 @@ def _check_sels(self, ref, sel, universe): assert_equal(ref.indices, sel.indices) - @pytest.mark.parametrize('ref, sel', [ - ('name NameABA or name NameACA or name NameADA', - 'name NameABA NameACA NameADA'), - ('type TypeE or type TypeD or type TypeB', 'type TypeE TypeD TypeB'), - ('resname RsC or resname RsY', 'resname RsC RsY'), - ('name NameAB* or name NameACC', 'name NameAB* NameACC'), - ('segid SegA or segid SegC', 'segid SegA SegC'), - ('(name NameABC or name NameABB) and (resname RsD or resname RsF)', - 'name NameABC NameABB and resname RsD RsF'), - ]) + @pytest.mark.parametrize( + "ref, sel", + [ + ( + "name NameABA or name NameACA or name NameADA", + "name NameABA NameACA NameADA", + ), + ( + "type TypeE or type TypeD or type TypeB", + "type TypeE TypeD TypeB", + ), + ("resname RsC or resname RsY", "resname RsC RsY"), + ("name NameAB* or name NameACC", "name NameAB* NameACC"), + ("segid SegA or segid SegC", "segid SegA SegC"), + ( + "(name NameABC or name NameABB) and (resname RsD or resname RsF)", + "name NameABC NameABB and resname RsD RsF", + ), + ], + ) def test_string_selections(self, ref, sel, universe): self._check_sels(ref, sel, universe) - @pytest.mark.parametrize("seltype", ['resid', 'resnum', 'bynum', 'index']) - @pytest.mark.parametrize('ref, sel', [ - ('{typ} 1 or {typ} 2', '{typ} 1 2'), - ('{typ} 1:10 or {typ} 22', '{typ} 1:10 22'), - ('{typ} 1:10 or {typ} 20:30', '{typ} 1:10 20:30'), - ('{typ} 1-5 or {typ} 7', '{typ} 1-5 7'), - ('{typ} 1-5 or {typ} 7:10 or {typ} 12', '{typ} 1-5 7:10 12'), - ('{typ} 1 or {typ} 3 or {typ} 5:10', '{typ} 1 3 5:10'), - ]) + @pytest.mark.parametrize("seltype", ["resid", "resnum", "bynum", "index"]) + @pytest.mark.parametrize( + "ref, sel", + [ + ("{typ} 1 or {typ} 2", "{typ} 1 2"), + ("{typ} 1:10 or {typ} 22", "{typ} 1:10 22"), + ("{typ} 1:10 or {typ} 20:30", "{typ} 1:10 20:30"), + ("{typ} 1-5 or {typ} 7", "{typ} 1-5 7"), + ("{typ} 1-5 or {typ} 7:10 or {typ} 12", "{typ} 1-5 7:10 12"), + ("{typ} 1 or {typ} 3 or {typ} 5:10", "{typ} 1 3 5:10"), + ], + ) def test_range_selections(self, seltype, ref, sel, universe): - self._check_sels(ref.format(typ=seltype), sel.format(typ=seltype), - universe) + self._check_sels( + ref.format(typ=seltype), sel.format(typ=seltype), universe + ) class TestICodeSelection(object): @@ -1133,13 +1324,13 @@ def u(self): return mda.Universe(PDB_icodes) def test_select_icode(self, u): - ag = u.select_atoms('resid 163A') + ag = u.select_atoms("resid 163A") assert len(ag) == 7 assert_equal(ag.ids, np.arange(7) + 1230) def test_select_resid_implicit_icode(self, u): - ag = u.select_atoms('resid 163') + ag = u.select_atoms("resid 163") assert len(ag) == 6 assert_equal(ag.ids, np.arange(6) + 1224) @@ -1147,11 +1338,11 @@ def test_select_resid_implicit_icode(self, u): def test_select_icode_range_1(self, u): # testing range within a single resid integer value u = u - ag = u.select_atoms('resid 163B-163D') + ag = u.select_atoms("resid 163B-163D") # do it manually without selection language... ref = u.residues[u.residues.resids == 163] - ref = ref[(ref.icodes >= 'B') & (ref.icodes <= 'D')] + ref = ref[(ref.icodes >= "B") & (ref.icodes <= "D")] ref = ref.atoms assert_equal(ag.ids, ref.ids) @@ -1162,17 +1353,17 @@ def test_select_icode_range_1(self, u): def test_select_icode_range_2(self, u): u = u - ag = u.select_atoms('resid 163B-165') + ag = u.select_atoms("resid 163B-165") resids = u.residues.resids start = u.residues[resids == 163] - start = start[start.icodes >= 'B'] + start = start[start.icodes >= "B"] mid = u.residues[resids == 164] end = u.residues[resids == 165] - end = end[end.icodes == ''] + end = end[end.icodes == ""] ref = start.atoms + mid.atoms + end.atoms @@ -1182,15 +1373,15 @@ def test_select_icode_range_3(self, u): # same as #2 but with no "middle" icodes u = u - ag = u.select_atoms('resid 163B-164') + ag = u.select_atoms("resid 163B-164") resids = u.residues.resids start = u.residues[resids == 163] - start = start[start.icodes >= 'B'] + start = start[start.icodes >= "B"] end = u.residues[resids == 164] - end = end[end.icodes == ''] + end = end[end.icodes == ""] ref = start.atoms + end.atoms @@ -1199,17 +1390,17 @@ def test_select_icode_range_3(self, u): def test_select_icode_range_4(self, u): u = u - ag = u.select_atoms('resid 160-163G') + ag = u.select_atoms("resid 160-163G") resids = u.residues.resids start = u.residues[resids == 160] - start = start[start.icodes >= ''] + start = start[start.icodes >= ""] mid = u.residues[(resids == 161) | (resids == 162)] end = u.residues[resids == 163] - end = end[end.icodes <= 'G'] + end = end[end.icodes <= "G"] ref = start.atoms + mid.atoms + end.atoms @@ -1219,15 +1410,15 @@ def test_select_icode_range_5(self, u): # same as #4 but with no "middle" icodes in range u = u - ag = u.select_atoms('resid 162-163G') + ag = u.select_atoms("resid 162-163G") resids = u.residues.resids start = u.residues[resids == 162] - start = start[start.icodes >= ''] + start = start[start.icodes >= ""] end = u.residues[resids == 163] - end = end[end.icodes <= 'G'] + end = end[end.icodes <= "G"] ref = start.atoms + end.atoms @@ -1235,14 +1426,14 @@ def test_select_icode_range_5(self, u): def test_missing_icodes_VE(self, u): # trying a selection with icodes in a Universe without raises VA - u = make_Universe(('resids',)) + u = make_Universe(("resids",)) with pytest.raises(ValueError): - u.select_atoms('resid 10A') + u.select_atoms("resid 10A") def test_missing_icodes_range_VE(self, u): - u = make_Universe(('resids',)) + u = make_Universe(("resids",)) with pytest.raises(ValueError): - u.select_atoms('resid 10A-12') + u.select_atoms("resid 10A-12") @pytest.fixture @@ -1262,10 +1453,10 @@ def u_pdb_icodes(): # See Issues #2308 for a discussion ("same resid as", 72), # Selection using resindices - # For PDBs: + # For PDBs: # residues with different insertion codes have different resindices - ("byres", 11) - ] + ("byres", 11), + ], ) def test_similarity_selection_icodes(u_pdb_icodes, selection, n_atoms): @@ -1274,30 +1465,54 @@ def test_similarity_selection_icodes(u_pdb_icodes, selection, n_atoms): assert len(sel.atoms) == n_atoms -@pytest.mark.parametrize('selection', [ - 'all', 'protein', 'backbone', 'nucleic', 'nucleicbackbone', - 'name O', 'name N*', 'resname stuff', 'resname ALA', 'type O', - 'index 0', 'index 1', 'bynum 1-10', - 'segid SYSTEM', 'resid 163', 'resid 1-10', 'resnum 2', - 'around 10 resid 1', 'point 0 0 0 10', 'sphzone 10 resid 1', - 'sphlayer 0 10 index 1', 'cyzone 15 4 -8 index 0', - 'cylayer 5 10 10 -8 index 1', 'prop abs z <= 100', - 'byres index 0', 'same resid as index 0', -]) + +@pytest.mark.parametrize( + "selection", + [ + "all", + "protein", + "backbone", + "nucleic", + "nucleicbackbone", + "name O", + "name N*", + "resname stuff", + "resname ALA", + "type O", + "index 0", + "index 1", + "bynum 1-10", + "segid SYSTEM", + "resid 163", + "resid 1-10", + "resnum 2", + "around 10 resid 1", + "point 0 0 0 10", + "sphzone 10 resid 1", + "sphlayer 0 10 index 1", + "cyzone 15 4 -8 index 0", + "cylayer 5 10 10 -8 index 1", + "prop abs z <= 100", + "byres index 0", + "same resid as index 0", + ], +) def test_selections_on_empty_group(u_pdb_icodes, selection): ag = u_pdb_icodes.atoms[[]].select_atoms(selection) assert len(ag) == 0 + def test_empty_yet_global(u_pdb_icodes): # slight exception to above test, an empty AG can return something if 'global' used - ag = u_pdb_icodes.atoms[[]].select_atoms('global name O') + ag = u_pdb_icodes.atoms[[]].select_atoms("global name O") assert len(ag) == 185 # len(u_pdb_icodes.select_atoms('name O')) + def test_arbitrary_atom_group_raises_error(): u = make_Universe(trajectory=True) with pytest.raises(TypeError): - u.select_atoms('around 2.0 group this', this=u.atoms[0]) + u.select_atoms("around 2.0 group this", this=u.atoms[0]) def test_empty_sel(): @@ -1311,12 +1526,12 @@ def test_empty_sel(): def test_record_type_sel(): u = mda.Universe(PDB_HOLE) - assert len(u.select_atoms('record_type ATOM')) == 264 - assert len(u.select_atoms('not record_type HETATM')) == 264 - assert len(u.select_atoms('record_type HETATM')) == 8 + assert len(u.select_atoms("record_type ATOM")) == 264 + assert len(u.select_atoms("not record_type HETATM")) == 264 + assert len(u.select_atoms("record_type HETATM")) == 8 - assert len(u.select_atoms('name CA and not record_type HETATM')) == 30 - assert len(u.select_atoms('name CA and record_type HETATM')) == 2 + assert len(u.select_atoms("name CA and not record_type HETATM")) == 30 + assert len(u.select_atoms("name CA and record_type HETATM")) == 2 def test_element_sel(): @@ -1334,65 +1549,74 @@ def test_chain_sel(): @pytest.fixture() def u_fake_masses(): u = mda.Universe(TPR) - u.atoms[-10:].masses = - (np.arange(10) + 1.001) + u.atoms[-10:].masses = -(np.arange(10) + 1.001) u.atoms[:5].masses = 0.1 * 3 # 0.30000000000000004 u.atoms[5:10].masses = 0.30000000000000001 return u -@pytest.mark.parametrize("selstr,n_atoms, selkwargs", [ - ("mass 0.8 to 1.2", 23844, {}), - ("mass 8e-1 to 1200e-3", 23844, {}), - ("mass -5--3", 2, {}), # select -5 to -3 - ("mass -3 : -5", 0, {}), # wrong way around - # regex; spacing, delimiters, and negatives - ("mass -5 --3", 2, {}), - ("mass -5- -3", 2, {}), - ("mass -5 - -3", 2, {}), - ("mass -10:3", 34945, {}), - ("mass -10 :3", 34945, {}), - ("mass -10: 3", 34945, {}), - ("mass -10 : 3", 34945, {}), - ("mass -10 -3", 0, {}), # separate selections, not range - # float equality - ("mass 0.3", 5, {"rtol": 0, "atol": 0}), - ("mass 0.3", 5, {"rtol": 1e-22, "atol": 1e-22}), - # 0.30000000000000001 == 0.3 - ("mass 0.3 - 0.30000000000000004", 10, {}), - ("mass 0.30000000000000004", 5, {"rtol": 0, "atol": 0}), - ("mass 0.3 0.30000000000000001", 5, {"rtol": 0, "atol": 0}), - # float near-equality - ("mass 0.3", 10, {}), - ("mass 0.30000000000000004", 10, {}), - ("mass 0.3 0.30000000000000001", 10, {}), - # prop thingy - ("prop mass == 0.3", 10, {}), - ("prop mass == 0.30000000000000004", 10, {}), - ("prop mass == 0.30000000000000004", 5, {"rtol": 0, "atol": 0}), -]) +@pytest.mark.parametrize( + "selstr,n_atoms, selkwargs", + [ + ("mass 0.8 to 1.2", 23844, {}), + ("mass 8e-1 to 1200e-3", 23844, {}), + ("mass -5--3", 2, {}), # select -5 to -3 + ("mass -3 : -5", 0, {}), # wrong way around + # regex; spacing, delimiters, and negatives + ("mass -5 --3", 2, {}), + ("mass -5- -3", 2, {}), + ("mass -5 - -3", 2, {}), + ("mass -10:3", 34945, {}), + ("mass -10 :3", 34945, {}), + ("mass -10: 3", 34945, {}), + ("mass -10 : 3", 34945, {}), + ("mass -10 -3", 0, {}), # separate selections, not range + # float equality + ("mass 0.3", 5, {"rtol": 0, "atol": 0}), + ("mass 0.3", 5, {"rtol": 1e-22, "atol": 1e-22}), + # 0.30000000000000001 == 0.3 + ("mass 0.3 - 0.30000000000000004", 10, {}), + ("mass 0.30000000000000004", 5, {"rtol": 0, "atol": 0}), + ("mass 0.3 0.30000000000000001", 5, {"rtol": 0, "atol": 0}), + # float near-equality + ("mass 0.3", 10, {}), + ("mass 0.30000000000000004", 10, {}), + ("mass 0.3 0.30000000000000001", 10, {}), + # prop thingy + ("prop mass == 0.3", 10, {}), + ("prop mass == 0.30000000000000004", 10, {}), + ("prop mass == 0.30000000000000004", 5, {"rtol": 0, "atol": 0}), + ], +) def test_mass_sel(u_fake_masses, selstr, n_atoms, selkwargs): # test auto-topattr addition of float (FloatRangeSelection) ag = u_fake_masses.select_atoms(selstr, **selkwargs) assert len(ag) == n_atoms + def test_mass_sel_warning(u_fake_masses): - warn_msg = (r"Using float equality .* is not recommended .* " - r"we recommend using a range .*" - r"'mass -0.6 to 1.4'.*" - r"use the `atol` and `rtol` keywords") + warn_msg = ( + r"Using float equality .* is not recommended .* " + r"we recommend using a range .*" + r"'mass -0.6 to 1.4'.*" + r"use the `atol` and `rtol` keywords" + ) with pytest.warns(SelectionWarning, match=warn_msg): u_fake_masses.select_atoms("mass 0.4") -@pytest.mark.parametrize("selstr,n_res", [ - ("resnum -10 to 3", 13), - ("resnum -5--3", 3), # select -5 to -3 - ("resnum -3 : -5", 0), # wrong way around -]) +@pytest.mark.parametrize( + "selstr,n_res", + [ + ("resnum -10 to 3", 13), + ("resnum -5--3", 3), # select -5 to -3 + ("resnum -3 : -5", 0), # wrong way around + ], +) def test_int_sel(selstr, n_res): # test auto-topattr addition of int (IntRangeSelection) u = mda.Universe(TPR) - u.residues[-10:].resnums = - (np.arange(10) + 1) + u.residues[-10:].resnums = -(np.arange(10) + 1) ag = u.select_atoms(selstr).residues assert len(ag) == n_res @@ -1410,12 +1634,15 @@ def test_negative_resid(): assert len(ag) == 4 -@pytest.mark.parametrize("selstr, n_atoms", [ - ("aromaticity", 5), - ("aromaticity true", 5), - ("not aromaticity", 15), - ("aromaticity False", 15), -]) +@pytest.mark.parametrize( + "selstr, n_atoms", + [ + ("aromaticity", 5), + ("aromaticity true", 5), + ("not aromaticity", 15), + ("aromaticity False", 15), + ], +) def test_bool_sel(selstr, n_atoms): if NumpyVersion(np.__version__) >= "2.0.0": pytest.skip("RDKit does not support NumPy 2") @@ -1437,14 +1664,18 @@ def test_bool_sel_error(): def test_error_selection_for_strange_dtype(): with pytest.raises(ValueError, match="No base class defined for dtype"): - MDAnalysis.core.selection.gen_selection_class("star", "stars", - dict, "atom") + MDAnalysis.core.selection.gen_selection_class( + "star", "stars", dict, "atom" + ) -@pytest.mark.parametrize("sel, ix", [ - ("name N", [5, 335, 451]), - ("resname GLU", [5, 6, 7, 8, 335, 451]), -]) +@pytest.mark.parametrize( + "sel, ix", + [ + ("name N", [5, 335, 451]), + ("resname GLU", [5, 6, 7, 8, 335, 451]), + ], +) def test_default_selection_on_ordered_unique_group(u_pdb_icodes, sel, ix): # manually ordered unique atomgroup => sorted by index base_ag = u_pdb_icodes.atoms[[335, 5, 451, 8, 7, 6]] @@ -1452,12 +1683,15 @@ def test_default_selection_on_ordered_unique_group(u_pdb_icodes, sel, ix): assert_equal(ag.ix, ix) -@pytest.mark.parametrize("sel, sort, ix", [ - ("name N", True, [5, 335, 451]), - ("name N", False, [335, 5, 451]), - ("resname GLU", True, [5, 6, 7, 8, 335, 451]), - ("resname GLU", False, [335, 5, 451, 8, 7, 6]), -]) +@pytest.mark.parametrize( + "sel, sort, ix", + [ + ("name N", True, [5, 335, 451]), + ("name N", False, [335, 5, 451]), + ("resname GLU", True, [5, 6, 7, 8, 335, 451]), + ("resname GLU", False, [335, 5, 451, 8, 7, 6]), + ], +) def test_unique_selection_on_ordered_unique_group(u_pdb_icodes, sel, sort, ix): # manually ordered unique atomgroup base_ag = u_pdb_icodes.atoms[[335, 5, 451, 8, 7, 6]] @@ -1465,12 +1699,15 @@ def test_unique_selection_on_ordered_unique_group(u_pdb_icodes, sel, sort, ix): assert_equal(ag.ix, ix) -@pytest.mark.parametrize("sel, sort, ix", [ - ("name N", True, [5, 335, 451]), - ("name N", False, [335, 5, 451]), - ("resname GLU", True, [5, 6, 7, 8, 335, 451]), - ("resname GLU", False, [335, 5, 451, 8, 7, 6]), -]) +@pytest.mark.parametrize( + "sel, sort, ix", + [ + ("name N", True, [5, 335, 451]), + ("name N", False, [335, 5, 451]), + ("resname GLU", True, [5, 6, 7, 8, 335, 451]), + ("resname GLU", False, [335, 5, 451, 8, 7, 6]), + ], +) def test_unique_selection_on_ordered_group(u_pdb_icodes, sel, sort, ix): # manually ordered duplicate atomgroup base_ag = u_pdb_icodes.atoms[[335, 5, 451, 8, 5, 5, 7, 6, 451]] @@ -1478,47 +1715,64 @@ def test_unique_selection_on_ordered_group(u_pdb_icodes, sel, sort, ix): assert_equal(ag.ix, ix) -@pytest.mark.parametrize('smi,chirality', [ - ('C[C@@H](C(=O)O)N', 'S'), - ('C[C@H](C(=O)O)N', 'R'), -]) +@pytest.mark.parametrize( + "smi,chirality", + [ + ("C[C@@H](C(=O)O)N", "S"), + ("C[C@H](C(=O)O)N", "R"), + ], +) def test_chirality(smi, chirality): - Chem = pytest.importorskip('rdkit.Chem', reason='requires rdkit') + Chem = pytest.importorskip("rdkit.Chem", reason="requires rdkit") m = Chem.MolFromSmiles(smi) u = mda.Universe(m) - assert hasattr(u.atoms, 'chiralities') + assert hasattr(u.atoms, "chiralities") - assert u.atoms[0].chirality == '' + assert u.atoms[0].chirality == "" assert u.atoms[1].chirality == chirality - assert_equal(u.atoms[:3].chiralities, np.array(['', chirality, ''], dtype='U1')) + assert_equal( + u.atoms[:3].chiralities, np.array(["", chirality, ""], dtype="U1") + ) -@pytest.mark.parametrize('sel,size', [ - ('R', 1), ('S', 1), ('R S', 2), ('S R', 2), -]) +@pytest.mark.parametrize( + "sel,size", + [ + ("R", 1), + ("S", 1), + ("R S", 2), + ("S R", 2), + ], +) def test_chirality_selection(sel, size): # 2 centers, one R one S - Chem = pytest.importorskip('rdkit.Chem', reason='requires rdkit') + Chem = pytest.importorskip("rdkit.Chem", reason="requires rdkit") - m = Chem.MolFromSmiles('CC[C@H](C)[C@H](C(=O)O)N') + m = Chem.MolFromSmiles("CC[C@H](C)[C@H](C(=O)O)N") u = mda.Universe(m) - ag = u.select_atoms('chirality {}'.format(sel)) + ag = u.select_atoms("chirality {}".format(sel)) assert len(ag) == size -@pytest.mark.parametrize('sel,size,name', [ - ('1', 1, 'NH2'), ('-1', 1, 'OD2'), ('0', 34, 'N'), ('-1 1', 2, 'OD2'), -]) +@pytest.mark.parametrize( + "sel,size,name", + [ + ("1", 1, "NH2"), + ("-1", 1, "OD2"), + ("0", 34, "N"), + ("-1 1", 2, "OD2"), + ], +) def test_formal_charge_selection(sel, size, name): # 2 charge points, one positive one negative u = mda.Universe(PDB_charges) - ag = u.select_atoms(f'formalcharge {sel}') + ag = u.select_atoms(f"formalcharge {sel}") assert len(ag) == size assert ag.atoms[0].name == name diff --git a/testsuite/MDAnalysisTests/core/test_copying.py b/testsuite/MDAnalysisTests/core/test_copying.py index 9f8d0c7000c..919d6fb08c7 100644 --- a/testsuite/MDAnalysisTests/core/test_copying.py +++ b/testsuite/MDAnalysisTests/core/test_copying.py @@ -24,9 +24,7 @@ from numpy.testing import assert_equal import pytest -from MDAnalysisTests.datafiles import ( - PSF, DCD, PDB_small -) +from MDAnalysisTests.datafiles import PSF, DCD, PDB_small import MDAnalysis as mda from MDAnalysis.core import topology @@ -36,9 +34,11 @@ @pytest.fixture() def refTT(): ref = topology.TransTable( - 9, 6, 3, + 9, + 6, + 3, atom_resindex=np.array([0, 0, 1, 1, 2, 2, 3, 4, 5]), - residue_segindex=np.array([0, 1, 2, 0, 1, 1]) + residue_segindex=np.array([0, 1, 2, 0, 1, 1]), ) return ref @@ -51,13 +51,13 @@ def test_size(self, refTT): assert new.n_segments == refTT.n_segments def test_size_independent(self, refTT): - # check changing + # check changing new = refTT.copy() old = refTT.n_atoms refTT.n_atoms = -10 assert new.n_atoms == old - @pytest.mark.parametrize('attr', ['_AR', 'RA', '_RS', 'SR']) + @pytest.mark.parametrize("attr", ["_AR", "RA", "_RS", "SR"]) def test_AR(self, refTT, attr): new = refTT.copy() ref = getattr(refTT, attr) @@ -66,7 +66,7 @@ def test_AR(self, refTT, attr): for a, b in zip(ref, other): assert_equal(a, b) - @pytest.mark.parametrize('attr', ['_AR', 'RA', '_RS', 'SR']) + @pytest.mark.parametrize("attr", ["_AR", "RA", "_RS", "SR"]) def test_AR_independent(self, refTT, attr): new = refTT.copy() ref = getattr(refTT, attr) @@ -89,38 +89,41 @@ def test_move_residue(self, refTT): TA_FILLER = { - object: np.array(['dave', 'steve', 'hugo'], dtype=object), + object: np.array(["dave", "steve", "hugo"], dtype=object), int: np.array([5, 4, 6]), float: np.array([15.4, 5.7, 22.2]), - 'record': np.array(['ATOM', 'ATOM', 'HETATM'], dtype='object'), - 'bond': [(0, 1), (1, 2), (5, 6)], - 'angles': [(0, 1, 2), (1, 2, 3), (4, 5, 6)], - 'dihe': [(0, 1, 2, 3), (1, 2, 3, 4), (5, 6, 7, 8)], + "record": np.array(["ATOM", "ATOM", "HETATM"], dtype="object"), + "bond": [(0, 1), (1, 2), (5, 6)], + "angles": [(0, 1, 2), (1, 2, 3), (4, 5, 6)], + "dihe": [(0, 1, 2, 3), (1, 2, 3, 4), (5, 6, 7, 8)], } -@pytest.fixture(params=[ - (ta.Atomids, int), - (ta.Atomnames, object), - (ta.Atomtypes, object), - (ta.Elements, object), - (ta.Radii, float), - (ta.RecordTypes, 'record'), - (ta.ChainIDs, object), - (ta.Tempfactors, float), - (ta.Masses, float), - (ta.Charges, float), - (ta.Occupancies, float), - (ta.AltLocs, object), - (ta.Resids, int), - (ta.Resnames, object), - (ta.Resnums, int), - (ta.ICodes, object), - (ta.Segids, object), - (ta.Bonds, 'bond'), - (ta.Angles, 'angles'), - (ta.Dihedrals, 'dihe'), - (ta.Impropers, 'dihe'), -]) + +@pytest.fixture( + params=[ + (ta.Atomids, int), + (ta.Atomnames, object), + (ta.Atomtypes, object), + (ta.Elements, object), + (ta.Radii, float), + (ta.RecordTypes, "record"), + (ta.ChainIDs, object), + (ta.Tempfactors, float), + (ta.Masses, float), + (ta.Charges, float), + (ta.Occupancies, float), + (ta.AltLocs, object), + (ta.Resids, int), + (ta.Resnames, object), + (ta.Resnums, int), + (ta.ICodes, object), + (ta.Segids, object), + (ta.Bonds, "bond"), + (ta.Angles, "angles"), + (ta.Dihedrals, "dihe"), + (ta.Impropers, "dihe"), + ] +) def refTA(request): cls, dt = request.param return cls(TA_FILLER[dt]) @@ -136,38 +139,45 @@ def test_copy_attr(refTA): @pytest.fixture() def refTop(): return topology.Topology( - 3, 2, 2, - attrs = [ + 3, + 2, + 2, + attrs=[ ta.Atomnames(TA_FILLER[object]), ta.Masses(TA_FILLER[float]), ta.Resids(TA_FILLER[int]), - ta.Bonds(TA_FILLER['bond']), + ta.Bonds(TA_FILLER["bond"]), ], atom_resindex=np.array([0, 0, 1]), - residue_segindex=np.array([0, 1]) + residue_segindex=np.array([0, 1]), ) + def test_topology_copy_n_attrs(refTop): new = refTop.copy() assert len(new.attrs) == 7 # 4 + 3 indices -@pytest.mark.parametrize('attr', [ - 'names', - 'masses', - 'resids', - 'bonds', - 'tt', -]) + +@pytest.mark.parametrize( + "attr", + [ + "names", + "masses", + "resids", + "bonds", + "tt", + ], +) def test_topology_copy_unique_attrs(refTop, attr): new = refTop.copy() assert getattr(refTop, attr) is not getattr(new, attr) - -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def refUniverse(): return mda.Universe(PSF, DCD) + class TestCopyUniverse(object): def test_universe_copy(self, refUniverse): new = refUniverse.copy() @@ -177,7 +187,7 @@ def test_universe_copy(self, refUniverse): def test_positions(self, refUniverse): new = refUniverse.copy() - + assert_equal(new.atoms.positions, refUniverse.atoms.positions) def test_change_positions(self, refUniverse): @@ -189,7 +199,7 @@ def test_change_positions(self, refUniverse): assert_equal(new.atoms[0].position, previous) assert_equal(refUniverse.atoms[0].position, [1, 2, 3]) - + def test_topology(self, refUniverse): new = refUniverse.copy() @@ -199,10 +209,11 @@ def test_change_topology(self, refUniverse): new = refUniverse.copy() previous = new.atoms[0].name - refUniverse.atoms[0].name = 'newname' + refUniverse.atoms[0].name = "newname" assert new.atoms[0].name == previous - assert refUniverse.atoms[0].name == 'newname' + assert refUniverse.atoms[0].name == "newname" + def test_pdb_copy(): u = mda.Universe(PDB_small) diff --git a/testsuite/MDAnalysisTests/core/test_fragments.py b/testsuite/MDAnalysisTests/core/test_fragments.py index 02c3bb00ef2..5d9d965a376 100644 --- a/testsuite/MDAnalysisTests/core/test_fragments.py +++ b/testsuite/MDAnalysisTests/core/test_fragments.py @@ -110,10 +110,7 @@ class TestFragments(object): Test ring molecules? """ - @pytest.mark.parametrize('u', ( - case1(), - case2() - )) + @pytest.mark.parametrize("u", (case1(), case2())) def test_total_frags(self, u): fragments = u.atoms.fragments fragindices = u.atoms.fragindices @@ -127,23 +124,17 @@ def test_total_frags(self, u): assert len(np.unique(fragindices)) == len(fragments) # check fragindices dtype: assert fragindices.dtype == np.intp - #check n_fragments + # check n_fragments assert u.atoms.n_fragments == len(fragments) - @pytest.mark.parametrize('u', ( - case1(), - case2() - )) + @pytest.mark.parametrize("u", (case1(), case2())) def test_frag_external_ordering(self, u): # check fragments and fragindices are sorted correctly: for i, frag in enumerate(u.atoms.fragments): assert frag[0].index == i * 25 assert np.unique(frag.fragindices)[0] == i - @pytest.mark.parametrize('u', ( - case1(), - case2() - )) + @pytest.mark.parametrize("u", (case1(), case2())) def test_frag_internal_ordering(self, u): # check atoms are sorted within fragments and have the same fragindex: for i, frag in enumerate(u.atoms.fragments): @@ -151,10 +142,7 @@ def test_frag_internal_ordering(self, u): assert len(np.unique(frag.fragindices)) == 1 assert frag.n_fragments == 1 - @pytest.mark.parametrize('u', ( - case1(), - case2() - )) + @pytest.mark.parametrize("u", (case1(), case2())) def test_atom_access(self, u): # check atom can access fragment and fragindex: for at in (u.atoms[0], u.atoms[76], u.atoms[111]): @@ -167,10 +155,7 @@ def test_atom_access(self, u): with pytest.raises(AttributeError): x = at.n_fragments - @pytest.mark.parametrize('u', ( - case1(), - case2() - )) + @pytest.mark.parametrize("u", (case1(), case2())) def test_atomgroup_access(self, u): # check atomgroup can access fragments # first 60 atoms have 3 fragments, given as tuple @@ -198,36 +183,36 @@ def test_atomgroup_fragments_nobonds_NDE(self): u = make_Universe() ag = u.atoms[:10] with pytest.raises(NoDataError): - getattr(ag, 'fragments') + getattr(ag, "fragments") with pytest.raises(NoDataError): - getattr(ag, 'fragindices') + getattr(ag, "fragindices") with pytest.raises(NoDataError): - getattr(ag, 'n_fragments') + getattr(ag, "n_fragments") def test_atom_fragment_nobonds_NDE(self): # should raise NDE u = make_Universe() with pytest.raises(NoDataError): - getattr(u.atoms[10], 'fragment') + getattr(u.atoms[10], "fragment") with pytest.raises(NoDataError): - getattr(u.atoms[10], 'fragindex') + getattr(u.atoms[10], "fragindex") def test_atomgroup_fragment_cache_invalidation_bond_making(self): u = case1() fgs = u.atoms.fragments - assert fgs is u.atoms._cache['fragments'] - assert u.atoms._cache_key in u._cache['_valid']['fragments'] + assert fgs is u.atoms._cache["fragments"] + assert u.atoms._cache_key in u._cache["_valid"]["fragments"] u.add_bonds((fgs[0][-1] + fgs[1][0],)) # should trigger invalidation - assert 'fragments' not in u._cache['_valid'] + assert "fragments" not in u._cache["_valid"] assert len(fgs) > len(u.atoms.fragments) # recomputed def test_atomgroup_fragment_cache_invalidation_bond_breaking(self): u = case1() fgs = u.atoms.fragments - assert fgs is u.atoms._cache['fragments'] - assert u.atoms._cache_key in u._cache['_valid']['fragments'] + assert fgs is u.atoms._cache["fragments"] + assert u.atoms._cache_key in u._cache["_valid"]["fragments"] u.delete_bonds((u.atoms.bonds[3],)) # should trigger invalidation - assert 'fragments' not in u._cache['_valid'] + assert "fragments" not in u._cache["_valid"] assert len(fgs) < len(u.atoms.fragments) # recomputed diff --git a/testsuite/MDAnalysisTests/core/test_group_traj_access.py b/testsuite/MDAnalysisTests/core/test_group_traj_access.py index bc63c83466d..f7061177f4d 100644 --- a/testsuite/MDAnalysisTests/core/test_group_traj_access.py +++ b/testsuite/MDAnalysisTests/core/test_group_traj_access.py @@ -29,9 +29,13 @@ from MDAnalysisTests import make_Universe from MDAnalysisTests.datafiles import ( - COORDINATES_XYZ, COORDINATES_TRR, - GRO, TRR, - GRO_velocity, PDB_xvf, TRR_xvf + COORDINATES_XYZ, + COORDINATES_TRR, + GRO, + TRR, + GRO_velocity, + PDB_xvf, + TRR_xvf, ) import MDAnalysis @@ -39,7 +43,7 @@ def assert_not_view(arr): - assert arr.flags['OWNDATA'] is True + assert arr.flags["OWNDATA"] is True def assert_correct_errormessage(func, var): @@ -52,12 +56,16 @@ def assert_correct_errormessage(func, var): pytest.fail() -@pytest.mark.parametrize('pos,vel,force', ( - (True, False, False), - (True, True, False), - (True, False, True), - (True, True, True), -), indirect=True) +@pytest.mark.parametrize( + "pos,vel,force", + ( + (True, False, False), + (True, True, False), + (True, False, True), + (True, True, True), + ), + indirect=True, +) class TestAtomGroupTrajAccess(object): """ For AtomGroup and Atom access: @@ -78,6 +86,7 @@ class TestAtomGroupTrajAccess(object): - check value in master Timestep object is updated if not present, check we get proper NoDataError on setting """ + @pytest.fixture() def pos(self, request): return request.param @@ -115,9 +124,10 @@ def test_atomgroup_velocities_access(self, u, vel): assert_equal(ag_vel, u.trajectory.ts.velocities[10:20]) else: with pytest.raises(NoDataError): - getattr(ag, 'velocities') - assert_correct_errormessage((getattr, ag, 'velocities'), - 'velocities') + getattr(ag, "velocities") + assert_correct_errormessage( + (getattr, ag, "velocities"), "velocities" + ) def test_atomgroup_forces_access(self, u, force): ag = u.atoms[10:20] @@ -131,8 +141,8 @@ def test_atomgroup_forces_access(self, u, force): assert_equal(ag_for, u.trajectory.ts.forces[10:20]) else: with pytest.raises(NoDataError): - getattr(ag, 'forces') - assert_correct_errormessage((getattr, ag, 'forces'), 'forces') + getattr(ag, "forces") + assert_correct_errormessage((getattr, ag, "forces"), "forces") def test_atom_position_access(self, u): at = u.atoms[55] @@ -156,9 +166,10 @@ def test_atom_velocity_access(self, u, vel): assert_equal(at_vel, u.trajectory.ts.velocities[55]) else: with pytest.raises(NoDataError): - getattr(at, 'velocity') + getattr(at, "velocity") assert_correct_errormessage( - (getattr, at, 'velocity'), 'velocities') + (getattr, at, "velocity"), "velocities" + ) def test_atom_force_access(self, u, force): at = u.atoms[55] @@ -172,58 +183,68 @@ def test_atom_force_access(self, u, force): assert_equal(at_for, u.trajectory.ts.forces[55]) else: with pytest.raises(NoDataError): - getattr(at, 'force') - assert_correct_errormessage((getattr, at, 'force'), 'forces') + getattr(at, "force") + assert_correct_errormessage((getattr, at, "force"), "forces") def test_atomgroup_positions_setting(self, u): ag = u.atoms[[101, 107, 109]] - new = np.array([[72.4, 64.5, 74.7], - [124.6, 15.6, -1.11], - [25.2, -66.6, 0]]) + new = np.array( + [[72.4, 64.5, 74.7], [124.6, 15.6, -1.11], [25.2, -66.6, 0]] + ) ag.positions = new assert_almost_equal(ag.positions, new, decimal=5) - assert_almost_equal(u.trajectory.ts.positions[[101, 107, 109]], - new, decimal=5) + assert_almost_equal( + u.trajectory.ts.positions[[101, 107, 109]], new, decimal=5 + ) def test_atomgroup_velocities_setting(self, u, vel): ag = u.atoms[[101, 107, 109]] - new = np.array([[72.4, 64.5, 74.7], - [124.6, 15.6, -1.11], - [25.2, -66.6, 0]]) + 0.1 + new = ( + np.array( + [[72.4, 64.5, 74.7], [124.6, 15.6, -1.11], [25.2, -66.6, 0]] + ) + + 0.1 + ) if vel: ag.velocities = new assert_almost_equal(ag.velocities, new, decimal=5) assert_almost_equal( - u.trajectory.ts.velocities[[101, 107, 109]], new, decimal=5) + u.trajectory.ts.velocities[[101, 107, 109]], new, decimal=5 + ) else: with pytest.raises(NoDataError): - setattr(ag, 'velocities', new) - assert_correct_errormessage((setattr, ag, 'velocities', new), - 'velocities') + setattr(ag, "velocities", new) + assert_correct_errormessage( + (setattr, ag, "velocities", new), "velocities" + ) def test_atomgroup_forces_setting(self, u, force): ag = u.atoms[[101, 107, 109]] - new = np.array([[72.4, 64.5, 74.7], - [124.6, 15.6, -1.11], - [25.2, -66.6, 0]]) + 0.2 + new = ( + np.array( + [[72.4, 64.5, 74.7], [124.6, 15.6, -1.11], [25.2, -66.6, 0]] + ) + + 0.2 + ) if force: ag.forces = new assert_almost_equal(ag.forces, new, decimal=5) - assert_almost_equal(u.trajectory.ts.forces[[101, 107, 109]], - new, decimal=5) + assert_almost_equal( + u.trajectory.ts.forces[[101, 107, 109]], new, decimal=5 + ) else: with pytest.raises(NoDataError): - setattr(ag, 'forces', new) - assert_correct_errormessage((setattr, ag, 'forces', new), 'forces') + setattr(ag, "forces", new) + assert_correct_errormessage((setattr, ag, "forces", new), "forces") def test_atom_position_setting(self, u): at = u.atoms[94] @@ -244,13 +265,13 @@ def test_atom_velocity_setting(self, u, vel): at.velocity = new assert_almost_equal(at.velocity, new, decimal=5) - assert_almost_equal(u.trajectory.ts.velocities[94], new, - decimal=5) + assert_almost_equal(u.trajectory.ts.velocities[94], new, decimal=5) else: with pytest.raises(NoDataError): - setattr(at, 'velocity', new) - assert_correct_errormessage((setattr, at, 'velocity', new), - 'velocities') + setattr(at, "velocity", new) + assert_correct_errormessage( + (setattr, at, "velocity", new), "velocities" + ) def test_atom_force_setting(self, u, force): at = u.atoms[94] @@ -261,12 +282,11 @@ def test_atom_force_setting(self, u, force): at.force = new assert_almost_equal(at.force, new, decimal=5) - assert_almost_equal(u.trajectory.ts.forces[94], new, - decimal=5) + assert_almost_equal(u.trajectory.ts.forces[94], new, decimal=5) else: with pytest.raises(NoDataError): - setattr(at, 'force', new) - assert_correct_errormessage((setattr, at, 'force', new), 'forces') + setattr(at, "force", new) + assert_correct_errormessage((setattr, at, "force", new), "forces") class TestAtom_ForceVelocity(object): @@ -329,41 +349,66 @@ class TestGROVelocities(object): @pytest.fixture() def reference_velocities(self): return np.array( - [[-101.227, -0.57999998, 0.43400002], - [8.08500004, 3.19099998, -7.79099989], - [-9.04500008, -26.46899986, 13.17999935], - [2.51899981, 3.1400001, -1.73399997], - [-10.64100075, -11.34899998, 0.257], - [19.42700005, -8.21600056, -0.24399999]], dtype=np.float32) + [ + [-101.227, -0.57999998, 0.43400002], + [8.08500004, 3.19099998, -7.79099989], + [-9.04500008, -26.46899986, 13.17999935], + [2.51899981, 3.1400001, -1.73399997], + [-10.64100075, -11.34899998, 0.257], + [19.42700005, -8.21600056, -0.24399999], + ], + dtype=np.float32, + ) def testParse_velocities(self, reference_velocities): # read the velocities from the GRO_velocity file and compare the AtomGroup and individual Atom velocities # parsed with the reference values: u = MDAnalysis.Universe(GRO_velocity) - all_atoms = u.select_atoms('all') + all_atoms = u.select_atoms("all") # check for read-in and unit conversion for .gro file velocities for the entire AtomGroup: - assert_almost_equal(all_atoms.velocities, reference_velocities, - self.prec, - err_msg="problem reading .gro file velocities") + assert_almost_equal( + all_atoms.velocities, + reference_velocities, + self.prec, + err_msg="problem reading .gro file velocities", + ) # likewise for each individual atom (to be robust--in case someone alters the individual atom property code): - assert_almost_equal(all_atoms[0].velocity, reference_velocities[0], - self.prec, - err_msg="problem reading .gro file velocities") - assert_almost_equal(all_atoms[1].velocity, reference_velocities[1], - self.prec, - err_msg="problem reading .gro file velocities") - assert_almost_equal(all_atoms[2].velocity, reference_velocities[2], - self.prec, - err_msg="problem reading .gro file velocities") - assert_almost_equal(all_atoms[3].velocity, reference_velocities[3], - self.prec, - err_msg="problem reading .gro file velocities") - assert_almost_equal(all_atoms[4].velocity, reference_velocities[4], - self.prec, - err_msg="problem reading .gro file velocities") - assert_almost_equal(all_atoms[5].velocity, reference_velocities[5], - self.prec, - err_msg="problem reading .gro file velocities") + assert_almost_equal( + all_atoms[0].velocity, + reference_velocities[0], + self.prec, + err_msg="problem reading .gro file velocities", + ) + assert_almost_equal( + all_atoms[1].velocity, + reference_velocities[1], + self.prec, + err_msg="problem reading .gro file velocities", + ) + assert_almost_equal( + all_atoms[2].velocity, + reference_velocities[2], + self.prec, + err_msg="problem reading .gro file velocities", + ) + assert_almost_equal( + all_atoms[3].velocity, + reference_velocities[3], + self.prec, + err_msg="problem reading .gro file velocities", + ) + assert_almost_equal( + all_atoms[4].velocity, + reference_velocities[4], + self.prec, + err_msg="problem reading .gro file velocities", + ) + assert_almost_equal( + all_atoms[5].velocity, + reference_velocities[5], + self.prec, + err_msg="problem reading .gro file velocities", + ) class TestTRRForces(object): @@ -377,16 +422,22 @@ def universe(self): def reference_mean_protein_force(self): reference_mean_protein_force_native = np.array( [3.4609879271822823, -0.63302345167392804, -1.0587882545813336], - dtype=np.float32) + dtype=np.float32, + ) return reference_mean_protein_force_native / 10 def testForces(self, universe, reference_mean_protein_force): protein = universe.select_atoms("protein") assert_equal(len(protein), 918) mean_F = np.mean( - [protein.forces.mean(axis=0) for ts in universe.trajectory], axis=0) - assert_almost_equal(mean_F, reference_mean_protein_force, self.prec, - err_msg="mean force on protein over whole trajectory does not match") + [protein.forces.mean(axis=0) for ts in universe.trajectory], axis=0 + ) + assert_almost_equal( + mean_F, + reference_mean_protein_force, + self.prec, + err_msg="mean force on protein over whole trajectory does not match", + ) class TestTRRForcesNativeUnits(TestTRRForces): @@ -398,7 +449,8 @@ def universe(self): def reference_mean_protein_force(self): reference_mean_protein_force_native = np.array( [3.4609879271822823, -0.63302345167392804, -1.0587882545813336], - dtype=np.float32) + dtype=np.float32, + ) return reference_mean_protein_force_native @@ -419,20 +471,25 @@ def test_get_velocities(self, ag): def test_velocities(self, universe): ag = universe.atoms[42:45] - ref_v = np.array([ - [-3.61757946, -4.9867239, 2.46281552], - [2.57792854, 3.25411797, -0.75065529], - [13.91627216, 30.17778587, -12.16669178]]) + ref_v = np.array( + [ + [-3.61757946, -4.9867239, 2.46281552], + [2.57792854, 3.25411797, -0.75065529], + [13.91627216, 30.17778587, -12.16669178], + ] + ) v = ag.velocities - assert_almost_equal(v, ref_v, - err_msg="velocities were not read correctly") + assert_almost_equal( + v, ref_v, err_msg="velocities were not read correctly" + ) def test_set_velocities(self, ag): ag = ag v = ag.velocities - 2.7271 ag.velocities = v - assert_almost_equal(ag.velocities, v, - err_msg="messages were not set to new value") + assert_almost_equal( + ag.velocities, v, err_msg="messages were not set to new value" + ) class TestAtomGroupForces(object): @@ -452,12 +509,13 @@ def test_get_forces(self, ag): def test_forces(self, universe): ag = universe.atoms[1:4] - ref_v = np.arange(9).reshape(3, 3) * .01 + .03 + ref_v = np.arange(9).reshape(3, 3) * 0.01 + 0.03 v = ag.forces assert_almost_equal(v, ref_v, err_msg="forces were not read correctly") def test_set_forces(self, ag): v = ag.forces - 2.7271 ag.forces = v - assert_almost_equal(ag.forces, v, - err_msg="messages were not set to new value") + assert_almost_equal( + ag.forces, v, err_msg="messages were not set to new value" + ) diff --git a/testsuite/MDAnalysisTests/core/test_groups.py b/testsuite/MDAnalysisTests/core/test_groups.py index 6137d2b4244..7b116fa1a4a 100644 --- a/testsuite/MDAnalysisTests/core/test_groups.py +++ b/testsuite/MDAnalysisTests/core/test_groups.py @@ -23,11 +23,7 @@ import itertools import re import numpy as np -from numpy.testing import ( - assert_array_equal, - assert_equal, - assert_almost_equal -) +from numpy.testing import assert_array_equal, assert_equal, assert_almost_equal import pytest import operator import warnings @@ -40,21 +36,17 @@ class TestGroupProperties(object): - """ Test attributes common to all groups - """ + """Test attributes common to all groups""" + @pytest.fixture() def u(self): return make_Universe(trajectory=True) @pytest.fixture() def group_dict(self, u): - return { - 'atom': u.atoms, - 'residue': u.residues, - 'segment': u.segments - } + return {"atom": u.atoms, "residue": u.residues, "segment": u.segments} - uni = make_Universe() # can't use fixtures in @pytest.mark.parametrize + uni = make_Universe() # can't use fixtures in @pytest.mark.parametrize def test_dimensions(self, u, group_dict): dimensions = np.arange(6) @@ -63,60 +55,63 @@ def test_dimensions(self, u, group_dict): group.dimensions = dimensions.copy() assert_array_equal(group.dimensions, dimensions) assert_equal(u.dimensions, group.dimensions) - - @pytest.mark.parametrize('group', (uni.atoms[:2], uni.residues[:2], - uni.segments[:2])) + + @pytest.mark.parametrize( + "group", (uni.atoms[:2], uni.residues[:2], uni.segments[:2]) + ) def test_group_isunique(self, group): assert len(group) == 2 # Initially, cache must be empty: with pytest.raises(KeyError): - _ = group._cache['isunique'] + _ = group._cache["isunique"] # Check for correct value and type: assert group.isunique is True # Check if cache is set correctly: - assert group._cache['isunique'] is True + assert group._cache["isunique"] is True # Add duplicate element to group: group += group[0] assert len(group) == 3 # Cache must be reset since the group changed: with pytest.raises(KeyError): - _ = group._cache['isunique'] + _ = group._cache["isunique"] # Check for correct value and type: assert group.isunique is False # Check if cache is set correctly: - assert group._cache['isunique'] is False + assert group._cache["isunique"] is False - #Check empty group: + # Check empty group: group = group[[]] assert len(group) == 0 # Cache must be empty: with pytest.raises(KeyError): - _ = group._cache['isunique'] + _ = group._cache["isunique"] # Check for correct value and type: assert group.isunique is True # Check if cache is set correctly: - assert group._cache['isunique'] is True + assert group._cache["isunique"] is True - @pytest.mark.parametrize('group', (uni.atoms[:2], uni.residues[:2], - uni.segments[:2])) + @pytest.mark.parametrize( + "group", (uni.atoms[:2], uni.residues[:2], uni.segments[:2]) + ) def test_group_unique_nocache(self, group): # check unique group: assert len(group) == 2 # assert caches are empty: - for attr in ('isunique', 'sorted_unique', 'unsorted_unique'): + for attr in ("isunique", "sorted_unique", "unsorted_unique"): assert attr not in group._cache - @pytest.mark.parametrize('group', (uni.atoms[:2], uni.residues[:2], - uni.segments[:2])) + @pytest.mark.parametrize( + "group", (uni.atoms[:2], uni.residues[:2], uni.segments[:2]) + ) def test_create_unique_group_from_unique(self, group): unique_group = group.asunique(sorted=True) # assert identity and caches assert unique_group is group - assert group._cache['sorted_unique'] is unique_group - assert group._cache['unsorted_unique'] is unique_group - assert group._cache['isunique'] is True - assert group._cache['issorted'] # numpy.bool != bool + assert group._cache["sorted_unique"] is unique_group + assert group._cache["unsorted_unique"] is unique_group + assert group._cache["isunique"] is True + assert group._cache["issorted"] # numpy.bool != bool # assert .unique copies assert group.unique is not group @@ -127,7 +122,7 @@ def test_create_unique_group_from_unique(self, group): assert len(group) == 3 # assert caches are cleared since the group changed: - for attr in ('isunique', 'sorted_unique', 'unsorted_unique'): + for attr in ("isunique", "sorted_unique", "unsorted_unique"): assert attr not in group._cache # now not unique @@ -137,8 +132,8 @@ def test_create_unique_group_from_unique(self, group): assert group.asunique() is not unique_group assert group.asunique() == unique_group # check if caches have been set correctly: - assert group._cache['unsorted_unique'] is group.asunique() - assert group._cache['sorted_unique'] is group.asunique() + assert group._cache["unsorted_unique"] is group.asunique() + assert group._cache["sorted_unique"] is group.asunique() assert group.unique is not group.asunique() # check length and type: @@ -146,17 +141,18 @@ def test_create_unique_group_from_unique(self, group): assert type(group.unique) is type(group) # check if caches of group.sorted_unique have been set correctly: - assert group.sorted_unique._cache['isunique'] is True - assert group.sorted_unique._cache['sorted_unique'] is group.sorted_unique + assert group.sorted_unique._cache["isunique"] is True + assert ( + group.sorted_unique._cache["sorted_unique"] is group.sorted_unique + ) # assert that repeated access yields the same object (not a copy): unique_group = group.sorted_unique assert unique_group is group.sorted_unique - @pytest.mark.parametrize('ugroup', [uni.atoms, uni.residues, uni.segments]) - @pytest.mark.parametrize('ix, unique_ix', [ - ([0, 1], [0, 1]), - ([4, 3, 3, 1], [1, 3, 4]) - ]) + @pytest.mark.parametrize("ugroup", [uni.atoms, uni.residues, uni.segments]) + @pytest.mark.parametrize( + "ix, unique_ix", [([0, 1], [0, 1]), ([4, 3, 3, 1], [1, 3, 4])] + ) def test_group_unique_returns_sorted_copy(self, ugroup, ix, unique_ix): # is copy group = ugroup[ix] @@ -164,26 +160,32 @@ def test_group_unique_returns_sorted_copy(self, ugroup, ix, unique_ix): # sorted assert_equal(group.unique.ix, unique_ix) - @pytest.mark.parametrize('ugroup', [uni.atoms, uni.residues, uni.segments]) - @pytest.mark.parametrize('ix, value', [ - ([4, 3, 3, 1], False), - ([1, 3, 4], True), - ([2, 2, 2, 4], True), - ]) + @pytest.mark.parametrize("ugroup", [uni.atoms, uni.residues, uni.segments]) + @pytest.mark.parametrize( + "ix, value", + [ + ([4, 3, 3, 1], False), + ([1, 3, 4], True), + ([2, 2, 2, 4], True), + ], + ) def test_group_issorted(self, ugroup, ix, value): assert ugroup[ix].issorted == value - @pytest.mark.parametrize('ugroup', [uni.atoms, uni.residues, uni.segments]) - @pytest.mark.parametrize('ix, sort, unique_ix, is_same', [ - ([1, 3, 4], True, [1, 3, 4], True), - ([1, 3, 4], False, [1, 3, 4], True), - ([4, 3, 1], True, [1, 3, 4], False), - ([4, 3, 1], False, [4, 3, 1], True), - ([1, 3, 3, 4], True, [1, 3, 4], False), - ([1, 3, 3, 4], False, [1, 3, 4], False), - ([4, 3, 3, 1], True, [1, 3, 4], False), - ([4, 3, 3, 1], False, [4, 3, 1], False), - ]) + @pytest.mark.parametrize("ugroup", [uni.atoms, uni.residues, uni.segments]) + @pytest.mark.parametrize( + "ix, sort, unique_ix, is_same", + [ + ([1, 3, 4], True, [1, 3, 4], True), + ([1, 3, 4], False, [1, 3, 4], True), + ([4, 3, 1], True, [1, 3, 4], False), + ([4, 3, 1], False, [4, 3, 1], True), + ([1, 3, 3, 4], True, [1, 3, 4], False), + ([1, 3, 3, 4], False, [1, 3, 4], False), + ([4, 3, 3, 1], True, [1, 3, 4], False), + ([4, 3, 3, 1], False, [4, 3, 1], False), + ], + ) def test_group_asunique(self, ugroup, ix, sort, unique_ix, is_same): group = ugroup[ix] unique_group = group.asunique(sorted=sort) @@ -191,71 +193,91 @@ def test_group_asunique(self, ugroup, ix, sort, unique_ix, is_same): if is_same: assert unique_group is group - @pytest.mark.parametrize('ugroup', [uni.atoms, uni.residues, uni.segments]) + @pytest.mark.parametrize("ugroup", [uni.atoms, uni.residues, uni.segments]) def test_group_return_sorted_unsorted_unique(self, ugroup): unsorted_unique = ugroup[[1, 3, 4]].asunique(sorted=False) - assert 'unsorted_unique' in unsorted_unique._cache - assert 'sorted_unique' not in unsorted_unique._cache - assert 'issorted' not in unsorted_unique._cache - assert 'isunique' in unsorted_unique._cache + assert "unsorted_unique" in unsorted_unique._cache + assert "sorted_unique" not in unsorted_unique._cache + assert "issorted" not in unsorted_unique._cache + assert "isunique" in unsorted_unique._cache sorted_unique = unsorted_unique.asunique(sorted=True) assert sorted_unique is unsorted_unique - assert unsorted_unique._cache['issorted'] - assert unsorted_unique._cache['sorted_unique'] is unsorted_unique + assert unsorted_unique._cache["issorted"] + assert unsorted_unique._cache["sorted_unique"] is unsorted_unique - @pytest.mark.parametrize('ugroup', [uni.atoms, uni.residues, uni.segments]) + @pytest.mark.parametrize("ugroup", [uni.atoms, uni.residues, uni.segments]) def test_group_return_unsorted_sorted_unique(self, ugroup): unique = ugroup[[1, 3, 3, 4]] sorted_unique = unique.asunique(sorted=True) - assert unique._cache['sorted_unique'] is sorted_unique - assert 'unsorted_unique' not in unique._cache + assert unique._cache["sorted_unique"] is sorted_unique + assert "unsorted_unique" not in unique._cache unsorted_unique = unique.asunique(sorted=False) assert unsorted_unique is sorted_unique - assert unique._cache['unsorted_unique'] is sorted_unique + assert unique._cache["unsorted_unique"] is sorted_unique class TestEmptyAtomGroup(object): - """ Test empty atom groups - """ + """Test empty atom groups""" + u = mda.Universe(PSF, DCD) - @pytest.mark.parametrize('ag', [u.residues[:1]]) + @pytest.mark.parametrize("ag", [u.residues[:1]]) def test_passive_decorator(self, ag): - assert_almost_equal(ag.center_of_mass(), np.array([10.52567673, 9.49548312, -8.15335145])) + assert_almost_equal( + ag.center_of_mass(), + np.array([10.52567673, 9.49548312, -8.15335145]), + ) assert_almost_equal(ag.total_mass(), 133.209) - assert_almost_equal(ag.moment_of_inertia(), np.array([[ 657.514361 , 104.9446833, 110.4782 ], - [ 104.9446833, 307.4360346, -199.1794289], - [ 110.4782 , -199.1794289, 570.2924896]])) + assert_almost_equal( + ag.moment_of_inertia(), + np.array( + [ + [657.514361, 104.9446833, 110.4782], + [104.9446833, 307.4360346, -199.1794289], + [110.4782, -199.1794289, 570.2924896], + ] + ), + ) assert_almost_equal(ag.radius_of_gyration(), 2.400527938286) assert_almost_equal(ag.shape_parameter(), 0.61460819) assert_almost_equal(ag.asphericity(), 0.4892751412) - assert_almost_equal(ag.principal_axes(), np.array([[ 0.7574113, -0.113481 , 0.643001 ], - [ 0.5896252, 0.5419056, -0.5988993], - [-0.2804821, 0.8327427, 0.4773566]])) - assert_almost_equal(ag.center_of_charge(), np.array([11.0800112, 8.8885659, -8.9886632])) + assert_almost_equal( + ag.principal_axes(), + np.array( + [ + [0.7574113, -0.113481, 0.643001], + [0.5896252, 0.5419056, -0.5988993], + [-0.2804821, 0.8327427, 0.4773566], + ] + ), + ) + assert_almost_equal( + ag.center_of_charge(), + np.array([11.0800112, 8.8885659, -8.9886632]), + ) assert_almost_equal(ag.total_charge(), 1) - @pytest.mark.parametrize('ag', [mda.AtomGroup([],u)]) + @pytest.mark.parametrize("ag", [mda.AtomGroup([], u)]) def test_error_empty_group(self, ag): - with pytest.raises(ValueError, match ="AtomGroup is empty"): + with pytest.raises(ValueError, match="AtomGroup is empty"): ag.center_of_mass() - with pytest.raises(ValueError, match ="AtomGroup is empty"): + with pytest.raises(ValueError, match="AtomGroup is empty"): ag.total_mass() - with pytest.raises(ValueError, match ="AtomGroup is empty"): + with pytest.raises(ValueError, match="AtomGroup is empty"): ag.moment_of_inertia() - with pytest.raises(ValueError, match ="AtomGroup is empty"): + with pytest.raises(ValueError, match="AtomGroup is empty"): ag.radius_of_gyration() - with pytest.raises(ValueError, match ="AtomGroup is empty"): + with pytest.raises(ValueError, match="AtomGroup is empty"): ag.shape_parameter() - with pytest.raises(ValueError, match ="AtomGroup is empty"): + with pytest.raises(ValueError, match="AtomGroup is empty"): ag.asphericity() - with pytest.raises(ValueError, match ="AtomGroup is empty"): + with pytest.raises(ValueError, match="AtomGroup is empty"): ag.principal_axes() - with pytest.raises(ValueError, match ="AtomGroup is empty"): + with pytest.raises(ValueError, match="AtomGroup is empty"): ag.center_of_charge() - with pytest.raises(ValueError, match ="AtomGroup is empty"): + with pytest.raises(ValueError, match="AtomGroup is empty"): ag.total_charge() @@ -266,18 +288,19 @@ class TestGroupSlicing(object): ---- TopologyGroup is technically called group, add this in too! """ + u = make_Universe() # test universe is 5:1 mapping 3 times group_dict = { - 'atom': u.atoms, - 'residue': u.residues, - 'segment': u.segments + "atom": u.atoms, + "residue": u.residues, + "segment": u.segments, } singulars = { - 'atom': groups.Atom, - 'residue': groups.Residue, - 'segment': groups.Segment + "atom": groups.Atom, + "residue": groups.Residue, + "segment": groups.Segment, } slices = ( slice(0, 10), @@ -288,11 +311,9 @@ class TestGroupSlicing(object): slice(5, 1, -1), slice(10, 0, -2), ) - length = {'atom': 125, - 'residue': 25, - 'segment': 5} + length = {"atom": 125, "residue": 25, "segment": 5} - levels = ('atom', 'residue', 'segment') + levels = ("atom", "residue", "segment") @pytest.fixture(params=levels) def level(self, request): @@ -323,7 +344,7 @@ def test_len(self, group, level): ref = self.length[level] assert len(group) == ref - @pytest.mark.parametrize('func', [list, np.array]) + @pytest.mark.parametrize("func", [list, np.array]) def test_boolean_slicing(self, group, func): # func is the container type that will be used to slice group = group[:5] @@ -341,15 +362,21 @@ def test_indexerror(self, group, level): with pytest.raises(IndexError): group.__getitem__(idx) - @pytest.mark.parametrize('sl,func', itertools.product(( - slice(0, 10), - slice(0, 2), - slice(1, 3), - slice(0, 2, 2), - slice(0, -1), - slice(5, 1, -1), - slice(10, 0, -2), - ), [list, lambda x: np.array(x, dtype=np.int64)])) + @pytest.mark.parametrize( + "sl,func", + itertools.product( + ( + slice(0, 10), + slice(0, 2), + slice(1, 3), + slice(0, 2, 2), + slice(0, -1), + slice(5, 1, -1), + slice(10, 0, -2), + ), + [list, lambda x: np.array(x, dtype=np.int64)], + ), + ) def test_slice(self, group, nparray, sl, func): """Check that slicing a np array is identical""" g2 = group[sl] @@ -363,14 +390,14 @@ def test_slice(self, group, nparray, sl, func): else: assert g not in g2 - @pytest.mark.parametrize('idx', [0, 1, -1, -2]) + @pytest.mark.parametrize("idx", [0, 1, -1, -2]) def test_integer_getitem(self, group, nparray, idx, singular): a = group[idx] ref = nparray[idx] assert a.ix == ref assert isinstance(a, singular) - + def test_none_getitem(self, group): with pytest.raises(TypeError): group[None] @@ -378,9 +405,11 @@ def test_none_getitem(self, group): def _yield_groups(group_dict, singles, levels, groupclasses, repeat): for level in levels: - for groups in itertools.product([group_dict[level], singles[level]], - repeat=repeat): - yield list(groups) + [groupclasses[level]] + for groups in itertools.product( + [group_dict[level], singles[level]], repeat=repeat + ): + yield list(groups) + [groupclasses[level]] + class TestGroupAddition(object): """Tests for combining Group objects @@ -397,30 +426,31 @@ class TestGroupAddition(object): Sum() should work on an iterable of many same level Components/Groups Groups contain items "x in y" """ + u = make_Universe() - levels = ['atom', 'residue', 'segment'] + levels = ["atom", "residue", "segment"] group_dict = { - 'atom': u.atoms[:5], - 'residue': u.residues[:5], - 'segment': u.segments[:5], + "atom": u.atoms[:5], + "residue": u.residues[:5], + "segment": u.segments[:5], } singles = { - 'atom': u.atoms[0], - 'residue': u.residues[0], - 'segment': u.segments[0], + "atom": u.atoms[0], + "residue": u.residues[0], + "segment": u.segments[0], } groupclasses = { - 'atom': groups.AtomGroup, - 'residue': groups.ResidueGroup, - 'segment': groups.SegmentGroup, + "atom": groups.AtomGroup, + "residue": groups.ResidueGroup, + "segment": groups.SegmentGroup, } # TODO: actually use this singleclasses = { - 'atom': groups.Atom, - 'residue': groups.Residue, - 'segment': groups.Segment + "atom": groups.Atom, + "residue": groups.Residue, + "segment": groups.Segment, } @pytest.fixture(params=levels) @@ -454,8 +484,8 @@ def itr(x): return x @pytest.mark.parametrize( - 'a, b, refclass', - _yield_groups(group_dict, singles, levels, groupclasses, repeat=2) + "a, b, refclass", + _yield_groups(group_dict, singles, levels, groupclasses, repeat=2), ) def test_addition(self, a, b, refclass): """Combine a and b, check length, returned type and ordering""" @@ -468,23 +498,25 @@ def test_addition(self, a, b, refclass): assert x == y @pytest.mark.parametrize( - 'a, b, c, refclass', - _yield_groups(group_dict, singles, levels, groupclasses, repeat=3) + "a, b, c, refclass", + _yield_groups(group_dict, singles, levels, groupclasses, repeat=3), ) def test_sum(self, a, b, c, refclass): # weird hack in radd allows this summed = sum([a, b, c]) assert isinstance(summed, refclass) - assert_equal(len(summed), - len(self.itr(a)) + len(self.itr(b)) + len(self.itr(c))) - for x, y in zip(summed, - itertools.chain(self.itr(a), self.itr(b), self.itr(c))): + assert_equal( + len(summed), len(self.itr(a)) + len(self.itr(b)) + len(self.itr(c)) + ) + for x, y in zip( + summed, itertools.chain(self.itr(a), self.itr(b), self.itr(c)) + ): assert x == y @pytest.mark.parametrize( - 'a, b, c, refclass', - _yield_groups(group_dict, singles, levels, groupclasses, repeat=3) + "a, b, c, refclass", + _yield_groups(group_dict, singles, levels, groupclasses, repeat=3), ) def test_bad_sum(self, a, b, c, refclass): # sum with bad first argument @@ -498,13 +530,12 @@ def test_contains_false(self, group): assert not group[3] in group[:2] @pytest.mark.parametrize( - 'one_level, other_level', + "one_level, other_level", [ (l1, l2) - for l1, l2 - in itertools.product(levels, repeat=2) + for l1, l2 in itertools.product(levels, repeat=2) if l1 != l2 - ] + ], ) def test_contains_wronglevel(self, one_level, other_level): group = self.group_dict[one_level] @@ -512,15 +543,14 @@ def test_contains_wronglevel(self, one_level, other_level): assert not group[2] in group2 @pytest.mark.parametrize( - 'a, b', + "a, b", [ (typeA[alevel], typeB[blevel]) - for (typeA, typeB), (alevel, blevel) - in itertools.product( + for (typeA, typeB), (alevel, blevel) in itertools.product( itertools.product([singles, group_dict], repeat=2), - itertools.permutations(levels, 2) + itertools.permutations(levels, 2), ) - ] + ], ) def test_crosslevel(self, a, b): with pytest.raises(TypeError): @@ -560,8 +590,8 @@ def test_atomgroup_to_residuegroup(self, u): assert isinstance(res, groups.ResidueGroup) assert res == u.residues assert res is not u.residues - assert res._cache['isunique'] is True - assert res._cache['sorted_unique'] is res + assert res._cache["isunique"] is True + assert res._cache["sorted_unique"] is res def test_atomgroup_to_segmentgroup(self, u): seg = u.atoms.segments @@ -569,8 +599,8 @@ def test_atomgroup_to_segmentgroup(self, u): assert isinstance(seg, groups.SegmentGroup) assert seg == u.segments assert seg is not u.segments - assert seg._cache['isunique'] is True - assert seg._cache['sorted_unique'] is seg + assert seg._cache["isunique"] is True + assert seg._cache["sorted_unique"] is seg def test_residuegroup_to_atomgroup(self, u): res = u.residues @@ -580,22 +610,22 @@ def test_residuegroup_to_atomgroup(self, u): assert atm == u.atoms assert atm is not u.atoms # clear res' uniqueness caches: - if 'sorted_unique' in res._cache.keys(): - del res._cache['sorted_unique'] - if 'isunique' in res._cache.keys(): - del res._cache['isunique'] + if "sorted_unique" in res._cache.keys(): + del res._cache["sorted_unique"] + if "isunique" in res._cache.keys(): + del res._cache["isunique"] atm = res.atoms # assert uniqueness caches of atm are empty: with pytest.raises(KeyError): - _ = atm._cache['isunique'] + _ = atm._cache["isunique"] with pytest.raises(KeyError): - _ = atm._cache['sorted_unique'] + _ = atm._cache["sorted_unique"] # populate uniqueness cache of res: assert res.isunique atm = res.atoms # assert uniqueness caches of atm are set: - assert atm._cache['isunique'] is True - assert atm._cache['unsorted_unique'] is atm + assert atm._cache["isunique"] is True + assert atm._cache["unsorted_unique"] is atm def test_residuegroup_to_residuegroup(self, u): res = u.residues.residues @@ -609,8 +639,8 @@ def test_residuegroup_to_segmentgroup(self, u): assert isinstance(seg, groups.SegmentGroup) assert seg == u.segments assert seg is not u.segments - assert seg._cache['isunique'] is True - assert seg._cache['sorted_unique'] is seg + assert seg._cache["isunique"] is True + assert seg._cache["sorted_unique"] is seg def test_segmentgroup_to_atomgroup(self, u): seg = u.segments @@ -620,22 +650,22 @@ def test_segmentgroup_to_atomgroup(self, u): assert atm == u.atoms assert atm is not u.atoms # clear seg's uniqueness caches: - if 'sorted_unique' in seg._cache.keys(): - del seg._cache['sorted_unique'] - if 'isunique' in seg._cache.keys(): - del seg._cache['isunique'] + if "sorted_unique" in seg._cache.keys(): + del seg._cache["sorted_unique"] + if "isunique" in seg._cache.keys(): + del seg._cache["isunique"] atm = seg.atoms # assert uniqueness caches of atm are empty: with pytest.raises(KeyError): - _ = atm._cache['isunique'] + _ = atm._cache["isunique"] with pytest.raises(KeyError): - _ = atm._cache['sorted_unique'] + _ = atm._cache["sorted_unique"] # populate uniqueness cache of seg: assert seg.isunique atm = seg.atoms # assert uniqueness caches of atm are set: - assert atm._cache['isunique'] is True - assert atm._cache['unsorted_unique'] is atm + assert atm._cache["isunique"] is True + assert atm._cache["unsorted_unique"] is atm def test_segmentgroup_to_residuegroup(self, u): seg = u.segments @@ -645,22 +675,22 @@ def test_segmentgroup_to_residuegroup(self, u): assert res == u.residues assert res is not u.residues # clear seg's uniqueness caches: - if 'sorted_unique' in seg._cache.keys(): - del seg._cache['sorted_unique'] - if 'isunique' in seg._cache.keys(): - del seg._cache['isunique'] + if "sorted_unique" in seg._cache.keys(): + del seg._cache["sorted_unique"] + if "isunique" in seg._cache.keys(): + del seg._cache["isunique"] res = seg.residues # assert uniqueness caches of res are empty: with pytest.raises(KeyError): - _ = res._cache['isunique'] + _ = res._cache["isunique"] with pytest.raises(KeyError): - _ = res._cache['sorted_unique'] + _ = res._cache["sorted_unique"] # populate uniqueness cache of seg: assert seg.isunique res = seg.residues # assert uniqueness caches of res are set: - assert res._cache['isunique'] is True - assert res._cache['unsorted_unique'] is res + assert res._cache["isunique"] is True + assert res._cache["unsorted_unique"] is res def test_segmentgroup_to_segmentgroup(self, u): seg = u.segments.segments @@ -680,10 +710,10 @@ def test_residue_to_atomgroup(self, u): ag = u.residues[0].atoms assert isinstance(ag, groups.AtomGroup) assert len(ag) == 5 - assert ag._cache['isunique'] is True - assert ag._cache['sorted_unique'] is ag - del ag._cache['sorted_unique'] - del ag._cache['isunique'] + assert ag._cache["isunique"] is True + assert ag._cache["sorted_unique"] is ag + del ag._cache["sorted_unique"] + del ag._cache["isunique"] assert ag.isunique def test_residue_to_segment(self, u): @@ -694,42 +724,42 @@ def test_segment_to_atomgroup(self, u): ag = u.segments[0].atoms assert isinstance(ag, groups.AtomGroup) assert len(ag) == 25 - assert ag._cache['isunique'] is True - assert ag._cache['sorted_unique'] is ag - del ag._cache['sorted_unique'] - del ag._cache['isunique'] + assert ag._cache["isunique"] is True + assert ag._cache["sorted_unique"] is ag + del ag._cache["sorted_unique"] + del ag._cache["isunique"] assert ag.isunique def test_segment_to_residuegroup(self, u): rg = u.segments[0].residues assert isinstance(rg, groups.ResidueGroup) assert len(rg) == 5 - assert rg._cache['isunique'] is True - assert rg._cache['sorted_unique'] is rg - del rg._cache['sorted_unique'] - del rg._cache['isunique'] + assert rg._cache["isunique"] is True + assert rg._cache["sorted_unique"] is rg + del rg._cache["sorted_unique"] + del rg._cache["isunique"] assert rg.isunique def test_atomgroup_to_residuegroup_unique(self, u): ag = u.atoms[:5] + u.atoms[10:15] + u.atoms[:5] rg = ag.residues assert len(rg) == 2 - assert rg._cache['isunique'] is True - assert rg._cache['sorted_unique'] is rg + assert rg._cache["isunique"] is True + assert rg._cache["sorted_unique"] is rg def test_atomgroup_to_segmentgroup_unique(self, u): ag = u.atoms[0] + u.atoms[-1] + u.atoms[0] sg = ag.segments assert len(sg) == 2 - assert sg._cache['isunique'] is True - assert sg._cache['sorted_unique'] is sg + assert sg._cache["isunique"] is True + assert sg._cache["sorted_unique"] is sg def test_residuegroup_to_segmentgroup_unique(self, u): rg = u.residues[0] + u.residues[6] + u.residues[1] sg = rg.segments assert len(sg) == 2 - assert sg._cache['isunique'] is True - assert sg._cache['sorted_unique'] is sg + assert sg._cache["isunique"] is True + assert sg._cache["sorted_unique"] is sg def test_residuegroup_to_atomgroup_listcomp(self, u): rg = u.residues[0] + u.residues[0] + u.residues[4] @@ -737,16 +767,16 @@ def test_residuegroup_to_atomgroup_listcomp(self, u): assert len(ag) == 15 # assert uniqueness caches of ag are empty: with pytest.raises(KeyError): - _ = ag._cache['isunique'] + _ = ag._cache["isunique"] with pytest.raises(KeyError): - _ = ag._cache['sorted_unique'] + _ = ag._cache["sorted_unique"] # populate uniqueness cache of rg: assert not rg.isunique ag = rg.atoms # ag uniqueness caches are now from residue - assert not ag._cache['isunique'] + assert not ag._cache["isunique"] with pytest.raises(KeyError): - _ = ag._cache['sorted_unique'] + _ = ag._cache["sorted_unique"] def test_segmentgroup_to_residuegroup_listcomp(self, u): sg = u.segments[0] + u.segments[0] + u.segments[1] @@ -754,16 +784,16 @@ def test_segmentgroup_to_residuegroup_listcomp(self, u): assert len(rg) == 15 # assert uniqueness caches of rg are empty: with pytest.raises(KeyError): - _ = rg._cache['isunique'] + _ = rg._cache["isunique"] with pytest.raises(KeyError): - _ = rg._cache['sorted_unique'] + _ = rg._cache["sorted_unique"] # populate uniqueness cache of sg: assert not sg.isunique rg = sg.residues # assert uniqueness caches of rg are now populated - assert not rg._cache['isunique'] + assert not rg._cache["isunique"] with pytest.raises(KeyError): - _ = rg._cache['sorted_unique'] + _ = rg._cache["sorted_unique"] def test_segmentgroup_to_atomgroup_listcomp(self, u): sg = u.segments[0] + u.segments[0] + u.segments[1] @@ -771,20 +801,21 @@ def test_segmentgroup_to_atomgroup_listcomp(self, u): assert len(ag) == 75 # assert uniqueness caches of ag are empty: with pytest.raises(KeyError): - _ = ag._cache['isunique'] + _ = ag._cache["isunique"] with pytest.raises(KeyError): - _ = ag._cache['sorted_unique'] + _ = ag._cache["sorted_unique"] # populate uniqueness cache of sg: assert not sg.isunique ag = sg.atoms # ag uniqueness caches are now from segment - assert not ag._cache['isunique'] + assert not ag._cache["isunique"] with pytest.raises(KeyError): - _ = ag._cache['sorted_unique'] + _ = ag._cache["sorted_unique"] class TestComponentComparisons(object): """Use of operators (< > == != <= >=) with Atom, Residue, and Segment""" + u = make_Universe() levels = [u.atoms, u.residues, u.segments] @@ -798,7 +829,7 @@ def a(self, abc): return abc[0] @pytest.fixture - def b (self, abc): + def b(self, abc): return abc[1] @pytest.fixture @@ -840,8 +871,8 @@ def test_sorting(self, a, b, c): assert sorted([b, a, c]) == [a, b, c] @pytest.mark.parametrize( - 'x, y', - itertools.permutations((u.atoms[0], u.residues[0], u.segments[0]), 2) + "x, y", + itertools.permutations((u.atoms[0], u.residues[0], u.segments[0]), 2), ) def test_crosslevel_cmp(self, x, y): with pytest.raises(TypeError): @@ -854,8 +885,8 @@ def test_crosslevel_cmp(self, x, y): operator.ge(x, y) @pytest.mark.parametrize( - 'x, y', - itertools.permutations((u.atoms[0], u.residues[0], u.segments[0]), 2) + "x, y", + itertools.permutations((u.atoms[0], u.residues[0], u.segments[0]), 2), ) def test_crosslevel_eq(self, x, y): with pytest.raises(TypeError): @@ -887,10 +918,10 @@ class TestGroupBy(object): # tests for the method 'groupby' @pytest.fixture() def u(self): - return make_Universe(('segids', 'charges', 'resids')) + return make_Universe(("segids", "charges", "resids")) def test_groupby_float(self, u): - gb = u.atoms.groupby('charges') + gb = u.atoms.groupby("charges") for ref in [-1.5, -0.5, 0.0, 0.5, 1.5]: assert ref in gb @@ -898,19 +929,19 @@ def test_groupby_float(self, u): assert all(g.charges == ref) assert len(g) == 25 - @pytest.mark.parametrize('string', ['segids', b'segids', u'segids']) + @pytest.mark.parametrize("string", ["segids", b"segids", "segids"]) def test_groupby_string(self, u, string): gb = u.atoms.groupby(string) assert len(gb) == 5 - for ref in ['SegA', 'SegB', 'SegC', 'SegD', 'SegE']: + for ref in ["SegA", "SegB", "SegC", "SegD", "SegE"]: assert ref in gb g = gb[ref] assert all(g.segids == ref) assert len(g) == 25 def test_groupby_int(self, u): - gb = u.atoms.groupby('resids') + gb = u.atoms.groupby("resids") for g in gb.values(): assert len(g) == 5 @@ -918,20 +949,20 @@ def test_groupby_int(self, u): # tests for multiple attributes as arguments def test_groupby_float_string(self, u): - gb = u.atoms.groupby(['charges', 'segids']) + gb = u.atoms.groupby(["charges", "segids"]) for ref in [-1.5, -0.5, 0.0, 0.5, 1.5]: - for subref in ['SegA','SegB','SegC','SegD','SegE']: + for subref in ["SegA", "SegB", "SegC", "SegD", "SegE"]: assert (ref, subref) in gb.keys() a = gb[(ref, subref)] assert len(a) == 5 assert all(a.charges == ref) - assert all(a.segids == subref) + assert all(a.segids == subref) def test_groupby_int_float(self, u): - gb = u.atoms.groupby(['resids', 'charges']) + gb = u.atoms.groupby(["resids", "charges"]) - uplim=int(len(gb)/5+1) + uplim = int(len(gb) / 5 + 1) for ref in range(1, uplim): for subref in [-1.5, -0.5, 0.0, 0.5, 1.5]: assert (ref, subref) in gb.keys() @@ -941,11 +972,11 @@ def test_groupby_int_float(self, u): assert all(a.charges == subref) def test_groupby_string_int(self, u): - gb = u.atoms.groupby(['segids', 'resids']) + gb = u.atoms.groupby(["segids", "resids"]) assert len(gb) == 25 res = 1 - for ref in ['SegA','SegB','SegC','SegD','SegE']: + for ref in ["SegA", "SegB", "SegC", "SegD", "SegE"]: for subref in range(0, 5): assert (ref, res) in gb.keys() a = gb[(ref, res)] @@ -961,51 +992,59 @@ def u(self): def test_atom_repr(self, u): at = u.atoms[0] - assert repr(at) == '' + assert ( + repr(at) + == "" + ) def test_residue_repr(self, u): res = u.residues[0] - assert repr(res) == '' + assert repr(res) == "" def test_segment_repr(self, u): seg = u.segments[0] - assert repr(seg) == '' + assert repr(seg) == "" def test_atomgroup_repr(self, u): ag = u.atoms[:10] - assert repr(ag) == '' + assert repr(ag) == "" def test_atomgroup_str_short(self, u): ag = u.atoms[:2] - assert str(ag) == ', ]>' + assert ( + str(ag) + == ", ]>" + ) def test_atomgroup_str_long(self, u): ag = u.atoms[:11] - assert str(ag).startswith(']>') + assert str(ag).startswith( + "]>") def test_residuegroup_repr(self, u): rg = u.residues[:10] - assert repr(rg) == '' + assert repr(rg) == "" def test_residuegroup_str_short(self, u): rg = u.residues[:2] - assert str(rg) == ', ]>' + assert str(rg) == ", ]>" def test_residuegroup_str_long(self, u): rg = u.residues[:11] - assert str(rg).startswith(',') - assert '...' in str(rg) - assert str(rg).endswith(', ]>') + assert str(rg).startswith(",") + assert "..." in str(rg) + assert str(rg).endswith(", ]>") def test_segmentgroup_repr(self, u): sg = u.segments[:10] - assert repr(sg) == '' + assert repr(sg) == "" def test_segmentgroup_str(self, u): sg = u.segments[:10] - assert str(sg) == ']>' + assert str(sg) == "]>" def _yield_mix(groups, components): @@ -1014,17 +1053,19 @@ def _yield_mix(groups, components): yield (groups[left], components[right]) yield (components[left], groups[right]) + def _yield_sliced_groups(u, slice_left, slice_right): - for level in ('atoms', 'residues', 'segments'): + for level in ("atoms", "residues", "segments"): yield (getattr(u, level)[slice_left], getattr(u, level)[slice_right]) + class TestGroupBaseOperators(object): u = make_Universe() components = (u.atoms[0], u.residues[0], u.segments[0]) component_groups = (u.atoms, u.residues, u.segments) - @pytest.fixture(params=('atoms', 'residues', 'segments')) + @pytest.fixture(params=("atoms", "residues", "segments")) def level(self, request): return request.param @@ -1066,10 +1107,12 @@ def groups_duplicated_and_scrambled(self, level): e = getattr(u, level)[[6, 5, 7, 7, 6]] return a, b, c, d, e - @pytest.fixture(params=('simple', 'scrambled')) + @pytest.fixture(params=("simple", "scrambled")) def groups(self, request, groups_simple, groups_duplicated_and_scrambled): - return {'simple': groups_simple, - 'scrambled': groups_duplicated_and_scrambled}[request.param] + return { + "simple": groups_simple, + "scrambled": groups_duplicated_and_scrambled, + }[request.param] def test_len(self, groups_simple): a, b, c, d, e = groups_simple @@ -1079,7 +1122,9 @@ def test_len(self, groups_simple): assert_equal(len(d), 0) assert_equal(len(e), 3) - def test_len_duplicated_and_scrambled(self, groups_duplicated_and_scrambled): + def test_len_duplicated_and_scrambled( + self, groups_duplicated_and_scrambled + ): a, b, c, d, e = groups_duplicated_and_scrambled assert_equal(len(a), 7) assert_equal(len(b), 8) @@ -1087,23 +1132,24 @@ def test_len_duplicated_and_scrambled(self, groups_duplicated_and_scrambled): assert_equal(len(d), 0) assert_equal(len(e), 5) - def test_equal(self, groups): a, b, c, d, e = groups assert a == a assert a != b assert not a == b - assert not a[0:1] == a[0], \ - 'Element should not equal single element group.' + assert ( + not a[0:1] == a[0] + ), "Element should not equal single element group." - @pytest.mark.parametrize('group', (u.atoms[:2], u.residues[:2], - u.segments[:2])) + @pytest.mark.parametrize( + "group", (u.atoms[:2], u.residues[:2], u.segments[:2]) + ) def test_copy(self, group): # make sure uniqueness caches of group are empty: with pytest.raises(KeyError): - _ = group._cache['isunique'] + _ = group._cache["isunique"] with pytest.raises(KeyError): - _ = group._cache['sorted_unique'] + _ = group._cache["sorted_unique"] # make a copy: cgroup = group.copy() # check if cgroup is an identical copy of group: @@ -1112,17 +1158,17 @@ def test_copy(self, group): assert cgroup == group # check if the copied group's uniqueness caches are empty: with pytest.raises(KeyError): - _ = cgroup._cache['isunique'] + _ = cgroup._cache["isunique"] with pytest.raises(KeyError): - _ = cgroup._cache['sorted_unique'] + _ = cgroup._cache["sorted_unique"] # populate group's uniqueness caches: assert group.isunique # make a copy: cgroup = group.copy() # check if the copied group's uniqueness caches are set correctly: - assert cgroup._cache['isunique'] is True + assert cgroup._cache["isunique"] is True # assert sorted_unique still doesn't exist - assert 'sorted_unique' not in cgroup._cache + assert "sorted_unique" not in cgroup._cache # add duplicate element to group: group += group[0] # populate group's uniqueness caches: @@ -1130,9 +1176,9 @@ def test_copy(self, group): # make a copy: cgroup = group.copy() # check if the copied group's uniqueness caches are set correctly: - assert cgroup._cache['isunique'] is False + assert cgroup._cache["isunique"] is False with pytest.raises(KeyError): - _ = cgroup._cache['sorted_unique'] + _ = cgroup._cache["sorted_unique"] # assert that duplicates are preserved: assert cgroup == group @@ -1167,16 +1213,16 @@ def test_is_strict_superset(self, groups): def test_concatenate(self, groups): a, b, c, d, e = groups cat_ab = a.concatenate(b) - assert cat_ab[:len(a)] == a - assert cat_ab[len(a):] == b + assert cat_ab[: len(a)] == a + assert cat_ab[len(a) :] == b cat_ba = b.concatenate(a) - assert cat_ba[:len(b)] == b - assert cat_ba[len(b):] == a + assert cat_ba[: len(b)] == b + assert cat_ba[len(b) :] == a cat_aa = a.concatenate(a) - assert cat_aa[:len(a)] == a - assert cat_aa[len(a):] == a + assert cat_aa[: len(a)] == a + assert cat_aa[len(a) :] == a cat_ad = a.concatenate(d) assert cat_ad == a @@ -1236,8 +1282,9 @@ def test_difference(self, groups): def test_symmetric_difference(self, groups): a, b, c, d, e = groups symdiff_ab = a.symmetric_difference(b) - assert_array_equal(symdiff_ab.ix, np.array(list(range(1, 3)) + - list(range(5, 8)))) + assert_array_equal( + symdiff_ab.ix, np.array(list(range(1, 3)) + list(range(5, 8))) + ) assert a.symmetric_difference(b) == b.symmetric_difference(a) assert_array_equal(a.symmetric_difference(e).ix, np.arange(1, 8)) @@ -1249,16 +1296,19 @@ def test_isdisjoint(self, groups): assert d.isdisjoint(a) assert not a.isdisjoint(b) - @pytest.mark.parametrize('left, right', itertools.chain( - # Do inter-levels pairs of groups fail as expected? - itertools.permutations(component_groups, 2), - # Do inter-levels pairs of components - itertools.permutations(components, 2), - # Do inter-levels pairs of components/groups fail as expected? - _yield_mix(component_groups, components), - # Does the function fail with inputs that are not components or groups - ((u.atoms, 'invalid'), ), - )) + @pytest.mark.parametrize( + "left, right", + itertools.chain( + # Do inter-levels pairs of groups fail as expected? + itertools.permutations(component_groups, 2), + # Do inter-levels pairs of components + itertools.permutations(components, 2), + # Do inter-levels pairs of components/groups fail as expected? + _yield_mix(component_groups, components), + # Does the function fail with inputs that are not components or groups + ((u.atoms, "invalid"),), + ), + ) def test_failing_pairs(self, left, right): def dummy(self, other): return True @@ -1266,15 +1316,18 @@ def dummy(self, other): with pytest.raises(TypeError): mda.core.groups._only_same_level(dummy)(left, right) - @pytest.mark.parametrize('left, right', itertools.chain( - # Groups - _yield_sliced_groups(u, slice(0, 2), slice(1, 3)), - # Components - _yield_sliced_groups(u, 0, 1), - # Mixed - _yield_sliced_groups(u, slice(0, 2), 1), - _yield_sliced_groups(u, 1, slice(0, 2)), - )) + @pytest.mark.parametrize( + "left, right", + itertools.chain( + # Groups + _yield_sliced_groups(u, slice(0, 2), slice(1, 3)), + # Components + _yield_sliced_groups(u, 0, 1), + # Mixed + _yield_sliced_groups(u, slice(0, 2), 1), + _yield_sliced_groups(u, 1, slice(0, 2)), + ), + ) def test_succeeding_pairs(self, left, right): def dummy(self, other): return True @@ -1291,11 +1344,16 @@ def dummy(self, other): with pytest.raises(ValueError): _only_same_level(dummy)(u.atoms, u2.atoms) - @pytest.mark.parametrize('op, method', ((operator.add, 'concatenate'), - (operator.sub, 'difference'), - (operator.and_, 'intersection'), - (operator.or_, 'union'), - (operator.xor, 'symmetric_difference'))) + @pytest.mark.parametrize( + "op, method", + ( + (operator.add, "concatenate"), + (operator.sub, "difference"), + (operator.and_, "intersection"), + (operator.or_, "union"), + (operator.xor, "symmetric_difference"), + ), + ) def test_shortcut_overriding(self, op, method, level): def check_operator(op, method, level): left = getattr(u, level)[1:3] @@ -1316,13 +1374,14 @@ class TestGroupHash(object): See issue #1397 """ - levels = ('atoms', 'residues', 'segments') + + levels = ("atoms", "residues", "segments") @pytest.fixture(params=levels) def level(self, request): return request.param - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def u(self): return make_Universe(size=(3, 3, 3)) @@ -1340,8 +1399,9 @@ def test_hash_difference(self, u, level): b = getattr(u, level)[1:] assert hash(a) != hash(b) - @pytest.mark.parametrize('level_a, level_b', - itertools.permutations(levels, 2)) + @pytest.mark.parametrize( + "level_a, level_b", itertools.permutations(levels, 2) + ) def test_hash_difference_cross(self, u, level_a, level_b): a = getattr(u, level_a)[0:-1] b = getattr(u, level_b)[0:-1] @@ -1357,31 +1417,44 @@ def test_hash_diff_cross_universe(self, level, u): class TestAtomGroup(object): def test_PDB_atom_repr(self): - u = make_Universe(extras=('altLocs', 'names', 'types', 'resnames', 'resids', 'segids')) - assert_equal("", u.atoms[0].__repr__()) + u = make_Universe( + extras=( + "altLocs", + "names", + "types", + "resnames", + "resids", + "segids", + ) + ) + assert_equal( + "", + u.atoms[0].__repr__(), + ) @pytest.fixture() def attr_universe(): - return make_Universe(('names', 'resids', 'segids')) + return make_Universe(("names", "resids", "segids")) + class TestAttributeSetting(object): - @pytest.mark.parametrize('groupname', ['atoms', 'residues', 'segments']) + @pytest.mark.parametrize("groupname", ["atoms", "residues", "segments"]) def test_setting_group_fail(self, attr_universe, groupname): group = getattr(attr_universe, groupname) with pytest.raises(AttributeError): - group.this = 'that' + group.this = "that" - @pytest.mark.parametrize('groupname', ['atoms', 'residues', 'segments']) + @pytest.mark.parametrize("groupname", ["atoms", "residues", "segments"]) def test_setting_component_fails(self, attr_universe, groupname): component = getattr(attr_universe, groupname)[0] with pytest.raises(AttributeError): - component.this = 'that' + component.this = "that" - @pytest.mark.parametrize('attr', ['name', 'resid', 'segid']) - @pytest.mark.parametrize('groupname', ['atoms', 'residues', 'segments']) + @pytest.mark.parametrize("attr", ["name", "resid", "segid"]) + @pytest.mark.parametrize("groupname", ["atoms", "residues", "segments"]) def test_group_set_singular(self, attr_universe, attr, groupname): # this should fail as you can't set the 'name' of a 'ResidueGroup' group = getattr(attr_universe, groupname) @@ -1389,8 +1462,8 @@ def test_group_set_singular(self, attr_universe, attr, groupname): setattr(group, attr, 24) def test_atom_set_name(self, attr_universe): - attr_universe.atoms[0].name = 'this' - assert attr_universe.atoms[0].name == 'this' + attr_universe.atoms[0].name = "this" + assert attr_universe.atoms[0].name == "this" def test_atom_set_resid(self, attr_universe): with pytest.raises(NotImplementedError): @@ -1398,11 +1471,11 @@ def test_atom_set_resid(self, attr_universe): def test_atom_set_segid(self, attr_universe): with pytest.raises(NotImplementedError): - attr_universe.atoms[0].segid = 'this' + attr_universe.atoms[0].segid = "this" def test_residue_set_name(self, attr_universe): with pytest.raises(AttributeError): - attr_universe.residues[0].name = 'this' + attr_universe.residues[0].name = "this" def test_residue_set_resid(self, attr_universe): attr_universe.residues[0].resid = 24 @@ -1410,36 +1483,37 @@ def test_residue_set_resid(self, attr_universe): def test_residue_set_segid(self, attr_universe): with pytest.raises(NotImplementedError): - attr_universe.residues[0].segid = 'this' + attr_universe.residues[0].segid = "this" def test_segment_set_name(self, attr_universe): with pytest.raises(AttributeError): - attr_universe.segments[0].name = 'this' + attr_universe.segments[0].name = "this" def test_segment_set_resid(self, attr_universe): with pytest.raises(AttributeError): attr_universe.segments[0].resid = 24 def test_segment_set_segid(self, attr_universe): - attr_universe.segments[0].segid = 'this' - assert attr_universe.segments[0].segid == 'this' + attr_universe.segments[0].segid = "this" + assert attr_universe.segments[0].segid == "this" - @pytest.mark.parametrize('attr', ['names', 'resids', 'segids']) - @pytest.mark.parametrize('groupname', ['atoms', 'residues', 'segments']) + @pytest.mark.parametrize("attr", ["names", "resids", "segids"]) + @pytest.mark.parametrize("groupname", ["atoms", "residues", "segments"]) def test_component_set_plural(self, attr, groupname): # this should fail as you can't set the 'Names' of an 'Atom' - u = make_Universe(('names', 'resids', 'segids')) + u = make_Universe(("names", "resids", "segids")) group = getattr(u, groupname) comp = group[0] with pytest.raises(AttributeError): setattr(comp, attr, 24) + class TestAttributeGetting(object): @staticmethod @pytest.fixture() def universe(): - return make_Universe(extras=('masses', 'altLocs')) + return make_Universe(extras=("masses", "altLocs")) @staticmethod @pytest.fixture() @@ -1447,45 +1521,49 @@ def atoms(): u = make_Universe(extras=("masses",), size=(3, 1, 1)) return u.atoms - @pytest.mark.parametrize('attr', ['masses', 'altLocs']) + @pytest.mark.parametrize("attr", ["masses", "altLocs"]) def test_get_present_topattr_group(self, universe, attr): values = getattr(universe.atoms, attr) assert values is not None - @pytest.mark.parametrize('attr', ['mass', 'altLoc']) + @pytest.mark.parametrize("attr", ["mass", "altLoc"]) def test_get_present_topattr_component(self, universe, attr): value = getattr(universe.atoms[0], attr) assert value is not None - @pytest.mark.parametrize('attr,singular', [ - ('masses', 'mass'), - ('altLocs', 'altLoc')]) + @pytest.mark.parametrize( + "attr,singular", [("masses", "mass"), ("altLocs", "altLoc")] + ) def test_get_plural_topattr_from_component(self, universe, attr, singular): with pytest.raises(AttributeError) as exc: getattr(universe.atoms[0], attr) - assert ('Do you mean ' + singular) in str(exc.value) + assert ("Do you mean " + singular) in str(exc.value) - @pytest.mark.parametrize('attr,singular', [ - ('masses', 'mass'), - ('altLocs', 'altLoc')]) + @pytest.mark.parametrize( + "attr,singular", [("masses", "mass"), ("altLocs", "altLoc")] + ) def test_get_sing_topattr_from_group(self, universe, attr, singular): with pytest.raises(AttributeError) as exc: getattr(universe.atoms, singular) - assert ('Do you mean ' + attr) in str(exc.value) + assert ("Do you mean " + attr) in str(exc.value) - @pytest.mark.parametrize('attr,singular', [ - ('elements', 'element'), - ('tempfactors', 'tempfactor'), - ('bonds', 'bonds')]) + @pytest.mark.parametrize( + "attr,singular", + [ + ("elements", "element"), + ("tempfactors", "tempfactor"), + ("bonds", "bonds"), + ], + ) def test_get_absent_topattr_group(self, universe, attr, singular): with pytest.raises(NoDataError) as exc: getattr(universe.atoms, attr) - assert 'does not contain ' + singular in str(exc.value) + assert "does not contain " + singular in str(exc.value) def test_get_non_topattr(self, universe): with pytest.raises(AttributeError) as exc: universe.atoms.jabberwocky - assert 'has no attribute' in str(exc.value) + assert "has no attribute" in str(exc.value) def test_unwrap_without_bonds(self, universe): expected_message = ( @@ -1496,47 +1574,47 @@ def test_unwrap_without_bonds(self, universe): ) expected_message_pattern = re.escape(expected_message) with pytest.raises(NoDataError, match=expected_message_pattern): - universe.atoms.unwrap() + universe.atoms.unwrap() def test_get_absent_attr_method(self, universe): with pytest.raises(NoDataError) as exc: universe.atoms.total_charge() - err = ('AtomGroup.total_charge() not available; ' - 'this requires charges') + err = ( + "AtomGroup.total_charge() not available; " "this requires charges" + ) assert str(exc.value) == err def test_get_absent_attrprop(self, universe): with pytest.raises(NoDataError) as exc: universe.atoms.fragindices - err = ('AtomGroup.fragindices not available; ' - 'this requires bonds') + err = "AtomGroup.fragindices not available; " "this requires bonds" assert str(exc.value) == err def test_attrprop_wrong_group(self, universe): with pytest.raises(AttributeError) as exc: universe.atoms[0].fragindices - err = ('fragindices is a property of AtomGroup, not Atom') + err = "fragindices is a property of AtomGroup, not Atom" assert str(exc.value) == err def test_attrmethod_wrong_group(self, universe): with pytest.raises(AttributeError) as exc: universe.atoms[0].center_of_mass() - err = ('center_of_mass() is a method of AtomGroup, not Atom') + err = "center_of_mass() is a method of AtomGroup, not Atom" assert str(exc.value) == err - @pytest.mark.parametrize('attr', ['altlocs', 'alt_Locs']) + @pytest.mark.parametrize("attr", ["altlocs", "alt_Locs"]) def test_wrong_name(self, universe, attr): with pytest.raises(AttributeError) as exc: getattr(universe.atoms, attr) - err = ('AtomGroup has no attribute {}. ' - 'Did you mean altLocs?').format(attr) + err = ( + "AtomGroup has no attribute {}. " "Did you mean altLocs?" + ).format(attr) assert str(exc.value) == err + class TestInitGroup(object): @staticmethod - @pytest.fixture( - params=['atoms', 'residues', 'segments'] - ) + @pytest.fixture(params=["atoms", "residues", "segments"]) def components(request): # return list of Component and container class for all three levels u = make_Universe() @@ -1544,9 +1622,9 @@ def components(request): group = getattr(u, request.param) cls = { - 'atoms': mda.AtomGroup, - 'residues': mda.ResidueGroup, - 'segments': mda.SegmentGroup, + "atoms": mda.AtomGroup, + "residues": mda.ResidueGroup, + "segments": mda.SegmentGroup, }[request.param] yield (u, [group[0], group[2], group[4]], cls) @@ -1586,10 +1664,11 @@ class TestDecorator(object): def dummy_funtion(cls, compound="group", wrap=True, unwrap=True): return 0 - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) - @pytest.mark.parametrize('pbc', (True, False)) - @pytest.mark.parametrize('unwrap', (True, False)) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) + @pytest.mark.parametrize("pbc", (True, False)) + @pytest.mark.parametrize("unwrap", (True, False)) def test_wrap_and_unwrap_deprecation(self, compound, pbc, unwrap): if pbc and unwrap: @@ -1605,64 +1684,82 @@ def test_wrap_and_unwrap_deprecation(self, compound, pbc, unwrap): # function's signature. This is done on purpose to test the # deprecation. We need to tell the linter. # pylint: disable-next=unexpected-keyword-arg - assert self.dummy_funtion(compound=compound, pbc=pbc, unwrap=unwrap) == 0 + assert ( + self.dummy_funtion( + compound=compound, pbc=pbc, unwrap=unwrap + ) + == 0 + ) - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) - @pytest.mark.parametrize('wrap', (True, False)) - @pytest.mark.parametrize('unwrap', (True, False)) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) + @pytest.mark.parametrize("wrap", (True, False)) + @pytest.mark.parametrize("unwrap", (True, False)) def test_wrap_and_unwrap(self, compound, wrap, unwrap): if wrap and unwrap: with pytest.raises(ValueError): self.dummy_funtion(compound=compound, wrap=wrap, unwrap=unwrap) else: - assert self.dummy_funtion(compound=compound, wrap=wrap, unwrap=unwrap) == 0 + assert ( + self.dummy_funtion(compound=compound, wrap=wrap, unwrap=unwrap) + == 0 + ) @pytest.fixture() def tpr(): with warnings.catch_warnings(): - warnings.filterwarnings("ignore", - message="No coordinate reader found") + warnings.filterwarnings("ignore", message="No coordinate reader found") return mda.Universe(TPR) + class TestGetConnectionsAtoms(object): """Test Atom and AtomGroup.get_connections""" - @pytest.mark.parametrize("typename", - ["bonds", "angles", "dihedrals", "impropers"]) + @pytest.mark.parametrize( + "typename", ["bonds", "angles", "dihedrals", "impropers"] + ) def test_connection_from_atom_not_outside(self, tpr, typename): cxns = tpr.atoms[1].get_connections(typename, outside=False) assert len(cxns) == 0 - @pytest.mark.parametrize("typename, n_atoms", [ - ("bonds", 1), - ("angles", 3), - ("dihedrals", 4), - ]) + @pytest.mark.parametrize( + "typename, n_atoms", + [ + ("bonds", 1), + ("angles", 3), + ("dihedrals", 4), + ], + ) def test_connection_from_atom_outside(self, tpr, typename, n_atoms): cxns = tpr.atoms[10].get_connections(typename, outside=True) assert len(cxns) == n_atoms - @pytest.mark.parametrize("typename, n_atoms", [ - ("bonds", 9), - ("angles", 15), - ("dihedrals", 12), - ]) - def test_connection_from_atoms_not_outside(self, tpr, typename, - n_atoms): + @pytest.mark.parametrize( + "typename, n_atoms", + [ + ("bonds", 9), + ("angles", 15), + ("dihedrals", 12), + ], + ) + def test_connection_from_atoms_not_outside(self, tpr, typename, n_atoms): ag = tpr.atoms[:10] cxns = ag.get_connections(typename, outside=False) assert len(cxns) == n_atoms indices = np.ravel(cxns.to_indices()) assert np.all(np.isin(indices, ag.indices)) - @pytest.mark.parametrize("typename, n_atoms", [ - ("bonds", 13), - ("angles", 27), - ("dihedrals", 38), - ]) + @pytest.mark.parametrize( + "typename, n_atoms", + [ + ("bonds", 13), + ("angles", 27), + ("dihedrals", 38), + ], + ) def test_connection_from_atoms_outside(self, tpr, typename, n_atoms): ag = tpr.atoms[:10] cxns = ag.get_connections(typename, outside=True) @@ -1687,44 +1784,57 @@ def test_get_empty_group(self, tpr, outside): class TestGetConnectionsResidues(object): """Test Residue and ResidueGroup.get_connections""" - @pytest.mark.parametrize("typename, n_atoms", [ - ("bonds", 9), - ("angles", 14), - ("dihedrals", 9), - ("impropers", 0), - ]) + @pytest.mark.parametrize( + "typename, n_atoms", + [ + ("bonds", 9), + ("angles", 14), + ("dihedrals", 9), + ("impropers", 0), + ], + ) def test_connection_from_res_not_outside(self, tpr, typename, n_atoms): cxns = tpr.residues[10].get_connections(typename, outside=False) assert len(cxns) == n_atoms - @pytest.mark.parametrize("typename, n_atoms", [ - ("bonds", 11), - ("angles", 22), - ("dihedrals", 27), - ("impropers", 0), - ]) + @pytest.mark.parametrize( + "typename, n_atoms", + [ + ("bonds", 11), + ("angles", 22), + ("dihedrals", 27), + ("impropers", 0), + ], + ) def test_connection_from_res_outside(self, tpr, typename, n_atoms): cxns = tpr.residues[10].get_connections(typename, outside=True) assert len(cxns) == n_atoms - @pytest.mark.parametrize("typename, n_atoms", [ - ("bonds", 157), - ("angles", 290), - ("dihedrals", 351), - ]) - def test_connection_from_residues_not_outside(self, tpr, typename, - n_atoms): + @pytest.mark.parametrize( + "typename, n_atoms", + [ + ("bonds", 157), + ("angles", 290), + ("dihedrals", 351), + ], + ) + def test_connection_from_residues_not_outside( + self, tpr, typename, n_atoms + ): ag = tpr.residues[:10] cxns = ag.get_connections(typename, outside=False) assert len(cxns) == n_atoms indices = np.ravel(cxns.to_indices()) assert np.all(np.isin(indices, ag.atoms.indices)) - @pytest.mark.parametrize("typename, n_atoms", [ - ("bonds", 158), - ("angles", 294), - ("dihedrals", 360), - ]) + @pytest.mark.parametrize( + "typename, n_atoms", + [ + ("bonds", 158), + ("angles", 294), + ("dihedrals", 360), + ], + ) def test_connection_from_residues_outside(self, tpr, typename, n_atoms): ag = tpr.residues[:10] cxns = ag.get_connections(typename, outside=True) @@ -1746,11 +1856,14 @@ def test_get_empty_group(self, tpr, outside): assert len(cxns) == 0 -@pytest.mark.parametrize("typename, n_inside", [ - ("intra_bonds", 9), - ("intra_angles", 15), - ("intra_dihedrals", 12), -]) +@pytest.mark.parametrize( + "typename, n_inside", + [ + ("intra_bonds", 9), + ("intra_angles", 15), + ("intra_dihedrals", 12), + ], +) def test_topologygroup_gets_connections_inside(tpr, typename, n_inside): ag = tpr.atoms[:10] cxns = getattr(ag, typename) @@ -1759,11 +1872,14 @@ def test_topologygroup_gets_connections_inside(tpr, typename, n_inside): assert np.all(np.isin(indices, ag.indices)) -@pytest.mark.parametrize("typename, n_outside", [ - ("bonds", 13), - ("angles", 27), - ("dihedrals", 38), -]) +@pytest.mark.parametrize( + "typename, n_outside", + [ + ("bonds", 13), + ("angles", 27), + ("dihedrals", 38), + ], +) def test_topologygroup_gets_connections_outside(tpr, typename, n_outside): ag = tpr.atoms[:10] cxns = getattr(ag, typename) diff --git a/testsuite/MDAnalysisTests/core/test_index_dtype.py b/testsuite/MDAnalysisTests/core/test_index_dtype.py index b9cb0f43a09..1500430f69f 100644 --- a/testsuite/MDAnalysisTests/core/test_index_dtype.py +++ b/testsuite/MDAnalysisTests/core/test_index_dtype.py @@ -75,8 +75,7 @@ def test_atomgroup_segment_upshift(u): def test_residuegroup_atom_downshift(u): # downshift arrays are a list (one for each residue) - assert all((arr.dtype == np.intp) - for arr in u.residues.indices) + assert all((arr.dtype == np.intp) for arr in u.residues.indices) def test_residuegroup_resindices(u): @@ -88,13 +87,11 @@ def test_residuegroup_segment_upshift(u): def test_segmentgroup_atom_downshift(u): - assert all((arr.dtype == np.intp) - for arr in u.segments.indices) + assert all((arr.dtype == np.intp) for arr in u.segments.indices) def test_segmentgroup_residue_downshift(u): - assert all((arr.dtype == np.intp) - for arr in u.segments.resindices) + assert all((arr.dtype == np.intp) for arr in u.segments.resindices) def test_segmentgroup_segindices(u): diff --git a/testsuite/MDAnalysisTests/core/test_requires.py b/testsuite/MDAnalysisTests/core/test_requires.py index d76a1ea8051..1dea033b7eb 100644 --- a/testsuite/MDAnalysisTests/core/test_requires.py +++ b/testsuite/MDAnalysisTests/core/test_requires.py @@ -1,6 +1,7 @@ """Tests for core.groups.requires decorator """ + import numpy as np import pytest @@ -12,31 +13,29 @@ class TestRequires(object): def test_requires_failure_singular(self): - @requires('masses') + @requires("masses") def mass_multiplier(ag1, ag2, scalar): return (ag1.masses + ag2.masses) * scalar - u = make_Universe(('charges',)) + u = make_Universe(("charges",)) with pytest.raises(NoDataError): mass_multiplier(u.atoms[:10], u.atoms[20:30], 4.0) - def test_requires_failure_multiple(self): - @requires('masses', 'charges') + @requires("masses", "charges") def mass_multiplier(ag1, ag2, scalar): return (ag1.masses + ag2.charges) * scalar - - u = make_Universe(('masses', 'types')) + u = make_Universe(("masses", "types")) with pytest.raises(NoDataError): mass_multiplier(u.atoms[:10], u.atoms[20:30], 4.0) def test_requires_success(self): - @requires('masses') + @requires("masses") def mass_multiplier(ag1, ag2, scalar): return (ag1.masses + ag2.masses) * scalar - u = make_Universe(('masses',)) + u = make_Universe(("masses",)) result = mass_multiplier(u.atoms[:10], u.atoms[20:30], 4.0) @@ -45,7 +44,7 @@ def mass_multiplier(ag1, ag2, scalar): def test_failure_errormessage(self): # failures should list all required attributes not # just the first one - @requires('cats', 'dogs', 'frogs') + @requires("cats", "dogs", "frogs") def animal_print(ag): return len(ag.cats), len(ag.dogs), len(ag.frogs) @@ -55,9 +54,9 @@ def animal_print(ag): except NoDataError as e: message = e.args[0] # Test function name gets returned (for debug) - assert 'animal_print' in message - assert 'cats' in message - assert 'dogs' in message - assert 'frogs' in message + assert "animal_print" in message + assert "cats" in message + assert "dogs" in message + assert "frogs" in message else: pytest.fail(msg="Should raise NoDataError") diff --git a/testsuite/MDAnalysisTests/core/test_residue.py b/testsuite/MDAnalysisTests/core/test_residue.py index b2bf9429105..a016673c242 100644 --- a/testsuite/MDAnalysisTests/core/test_residue.py +++ b/testsuite/MDAnalysisTests/core/test_residue.py @@ -52,8 +52,7 @@ def test_index(res): def test_atom_order(res): - assert_equal(res.atoms.indices, - sorted(res.atoms.indices)) + assert_equal(res.atoms.indices, sorted(res.atoms.indices)) def test_residue_pickle(res): diff --git a/testsuite/MDAnalysisTests/core/test_residuegroup.py b/testsuite/MDAnalysisTests/core/test_residuegroup.py index ad5521d20a1..06d0e22e9a0 100644 --- a/testsuite/MDAnalysisTests/core/test_residuegroup.py +++ b/testsuite/MDAnalysisTests/core/test_residuegroup.py @@ -32,13 +32,13 @@ @pytest.mark.skipif(HAS_BIOPYTHON, reason="biopython is installed") def test_sequence_import_error(): - p = mda.Universe(PSF, DCD).select_atoms('protein') + p = mda.Universe(PSF, DCD).select_atoms("protein") errmsg = "The `sequence_alignment` method requires an installation" with pytest.raises(ImportError, match=errmsg): _ = p.residues.sequence(format="string") -@pytest.mark.skipif(not HAS_BIOPYTHON, reason='requires biopython') +@pytest.mark.skipif(not HAS_BIOPYTHON, reason="requires biopython") class TestSequence: # all tests are done with the AdK system (PSF and DCD) sequence: # http://www.uniprot.org/uniprot/P69441.fasta @@ -56,21 +56,28 @@ def u(self): def test_string(self, u): p = u.select_atoms("protein") - assert_equal(p.residues.sequence(format="string"), - self.ref_adk_sequence) + assert_equal( + p.residues.sequence(format="string"), self.ref_adk_sequence + ) def test_SeqRecord(self, u): p = u.select_atoms("protein") - s = p.residues.sequence(format="SeqRecord", - id="P69441", name="KAD_ECOLI Adenylate kinase", - description="EcAdK from pdb 4AKE") + s = p.residues.sequence( + format="SeqRecord", + id="P69441", + name="KAD_ECOLI Adenylate kinase", + description="EcAdK from pdb 4AKE", + ) assert_equal(s.id, "P69441") assert_equal(str(s.seq), self.ref_adk_sequence) def test_SeqRecord_default(self, u): p = u.select_atoms("protein") - s = p.residues.sequence(id="P69441", name="KAD_ECOLI Adenylate kinase", - description="EcAdK from pdb 4AKE") + s = p.residues.sequence( + id="P69441", + name="KAD_ECOLI Adenylate kinase", + description="EcAdK from pdb 4AKE", + ) assert_equal(s.id, "P69441") assert_equal(str(s.seq), self.ref_adk_sequence) @@ -94,7 +101,7 @@ def wrong_res(): def test_format_TE(self, u): with pytest.raises(TypeError): - u.residues.sequence(format='chicken') + u.residues.sequence(format="chicken") class TestResidueGroup(object): @@ -112,8 +119,9 @@ def test_newResidueGroup(self, universe): (Issue 135)""" rg = universe.atoms.residues newrg = rg[10:20:2] - assert isinstance(newrg, mda.core.groups.ResidueGroup), \ - "Failed to make a new ResidueGroup: type mismatch" + assert isinstance( + newrg, mda.core.groups.ResidueGroup + ), "Failed to make a new ResidueGroup: type mismatch" def test_n_atoms(self, rg): assert_equal(rg.n_atoms, 3341) @@ -132,8 +140,7 @@ def test_segids_dim(self, rg): def test_len(self, rg): """testing that len(residuegroup) == residuegroup.n_residues""" - assert_equal(len(rg), rg.n_residues, - "len and n_residues disagree") + assert_equal(len(rg), rg.n_residues, "len and n_residues disagree") def test_set_resids(self, universe): rg = universe.select_atoms("bynum 12:42").residues @@ -141,12 +148,18 @@ def test_set_resids(self, universe): rg.resids = resid # check individual atoms for at in rg.atoms: - assert_equal(at.resid, resid, - err_msg="failed to set_resid atoms 12:42 to same resid") + assert_equal( + at.resid, + resid, + err_msg="failed to set_resid atoms 12:42 to same resid", + ) # check residues - assert_equal(rg.resids, resid * np.ones(rg.n_residues), - err_msg="failed to set_resid of residues belonging to " - "atoms 12:42 to same resid") + assert_equal( + rg.resids, + resid * np.ones(rg.n_residues), + err_msg="failed to set_resid of residues belonging to " + "atoms 12:42 to same resid", + ) def test_set_resids(self, universe): """test_set_resid: set ResidueGroup resids on a per-residue basis""" @@ -156,23 +169,30 @@ def test_set_resids(self, universe): # check individual atoms for r, resid in zip(rg, resids): for at in r.atoms: - assert_equal(at.resid, resid, - err_msg="failed to set_resid residues 10:18 to same " - "resid in residue {0}\n" - "(resids = {1}\nresidues = {2})".format(r, - resids, - rg)) - assert_equal(rg.resids, resids, - err_msg="failed to set_resid of residues belonging to " - "residues 10:18 to new resids") + assert_equal( + at.resid, + resid, + err_msg="failed to set_resid residues 10:18 to same " + "resid in residue {0}\n" + "(resids = {1}\nresidues = {2})".format(r, resids, rg), + ) + assert_equal( + rg.resids, + resids, + err_msg="failed to set_resid of residues belonging to " + "residues 10:18 to new resids", + ) def test_set_resids_updates_self(self, universe): rg = universe.select_atoms("resid 10:18").residues resids = np.array(rg.resids) + 1000 rg.resids = resids - assert_equal(rg.resids, resids, - err_msg="old selection was not changed in place " - "after set_resid") + assert_equal( + rg.resids, + resids, + err_msg="old selection was not changed in place " + "after set_resid", + ) def test_set_resnum_single(self, universe): rg = universe.residues[:3] @@ -196,13 +216,13 @@ def test_set_resnum_ValueError(self, universe): rg = universe.residues[:3] new = [22, 23, 24, 25] with pytest.raises(ValueError): - setattr(rg, 'resnums', new) + setattr(rg, "resnums", new) # INVALID: no `set_resnames` method; use `resnames` property directly @pytest.mark.skip def test_set_resname_single(self, universe): rg = universe.residues[:3] - new = 'newname' + new = "newname" rg.set_resnames(new) assert_equal(all(rg.resnames == new), True) @@ -213,7 +233,7 @@ def test_set_resname_single(self, universe): @pytest.mark.skip def test_set_resname_many(self, universe): rg = universe.residues[:3] - new = ['a', 'b', 'c'] + new = ["a", "b", "c"] rg.set_resnames(new) assert_equal(all(rg.resnames == new), True) @@ -224,7 +244,7 @@ def test_set_resname_many(self, universe): @pytest.mark.skip def test_set_resname_ValueError(self, universe): rg = universe.residues[:3] - new = ['a', 'b', 'c', 'd'] + new = ["a", "b", "c", "d"] with pytest.raises(ValueError): rg.set_resnames(new) @@ -241,21 +261,31 @@ def test_merge_residues(self, universe): nres_new = universe.atoms.n_residues r_merged = universe.select_atoms("resid 12:14").residues natoms_new = universe.select_atoms("resid 12").n_atoms - assert_equal(len(r_merged), 1, err_msg="set_resid failed to merge " - "residues: merged = {0}".format( - r_merged)) - assert_equal(nres_new, nres_old - 2, - err_msg="set_resid failed to merge residues: " - "merged = {0}".format(r_merged)) - assert_equal(natoms_new, natoms_old, err_msg="set_resid lost atoms " - "on merge".format( - r_merged)) - - assert_equal(universe.residues.n_residues, - universe.atoms.n_residues, - err_msg="Universe.residues and Universe.atoms.n_residues " - "do not agree after residue " - "merge.") + assert_equal( + len(r_merged), + 1, + err_msg="set_resid failed to merge " + "residues: merged = {0}".format(r_merged), + ) + assert_equal( + nres_new, + nres_old - 2, + err_msg="set_resid failed to merge residues: " + "merged = {0}".format(r_merged), + ) + assert_equal( + natoms_new, + natoms_old, + err_msg="set_resid lost atoms " "on merge".format(r_merged), + ) + + assert_equal( + universe.residues.n_residues, + universe.atoms.n_residues, + err_msg="Universe.residues and Universe.atoms.n_residues " + "do not agree after residue " + "merge.", + ) # INVALID: no `set_masses` method; use `masses` property directly @pytest.mark.skip @@ -264,20 +294,25 @@ def test_set_masses(self, universe): mass = 2.0 rg.set_masses(mass) # check individual atoms - assert_equal([a.mass for a in rg.atoms], - mass * np.ones(rg.n_atoms), - err_msg="failed to set_mass H* atoms in resid 12:42 to {0}".format( - mass)) + assert_equal( + [a.mass for a in rg.atoms], + mass * np.ones(rg.n_atoms), + err_msg="failed to set_mass H* atoms in resid 12:42 to {0}".format( + mass + ), + ) # VALID def test_atom_order(self, universe): - assert_equal(universe.residues.atoms.indices, - sorted(universe.residues.atoms.indices)) + assert_equal( + universe.residues.atoms.indices, + sorted(universe.residues.atoms.indices), + ) def test_get_next_residue(self, rg): unsorted_rep_res = rg[[0, 1, 8, 3, 4, 0, 3, 1, -1]] next_res = unsorted_rep_res._get_next_residues_by_resid() - resids = list(unsorted_rep_res.resids+1) + resids = list(unsorted_rep_res.resids + 1) resids[-1] = None next_resids = [r.resid if r is not None else None for r in next_res] assert_equal(len(next_res), len(unsorted_rep_res)) @@ -286,7 +321,7 @@ def test_get_next_residue(self, rg): def test_get_prev_residue(self, rg): unsorted_rep_res = rg[[0, 1, 8, 3, 4, 0, 3, 1, -1]] prev_res = unsorted_rep_res._get_prev_residues_by_resid() - resids = list(unsorted_rep_res.resids-1) + resids = list(unsorted_rep_res.resids - 1) resids[0] = resids[5] = None prev_resids = [r.resid if r is not None else None for r in prev_res] assert_equal(len(prev_res), len(unsorted_rep_res)) diff --git a/testsuite/MDAnalysisTests/core/test_segment.py b/testsuite/MDAnalysisTests/core/test_segment.py index 60b167dd882..4b9fe5639b5 100644 --- a/testsuite/MDAnalysisTests/core/test_segment.py +++ b/testsuite/MDAnalysisTests/core/test_segment.py @@ -35,7 +35,7 @@ class TestSegment(object): @pytest.fixture() def universe(self): - return make_Universe(('segids',)) + return make_Universe(("segids",)) @pytest.fixture() def sB(self, universe): @@ -61,8 +61,10 @@ def test_advanced_slicing(self, sB): assert isinstance(res, mda.core.groups.ResidueGroup) def test_atom_order(self, universe): - assert_equal(universe.segments[0].atoms.indices, - sorted(universe.segments[0].atoms.indices)) + assert_equal( + universe.segments[0].atoms.indices, + sorted(universe.segments[0].atoms.indices), + ) @pytest.mark.parametrize("ix", (1, -1)) def test_residue_pickle(self, universe, ix): diff --git a/testsuite/MDAnalysisTests/core/test_segmentgroup.py b/testsuite/MDAnalysisTests/core/test_segmentgroup.py index 11841bb7797..f6f2ed82d79 100644 --- a/testsuite/MDAnalysisTests/core/test_segmentgroup.py +++ b/testsuite/MDAnalysisTests/core/test_segmentgroup.py @@ -39,6 +39,7 @@ def universe(): def g(universe): return universe.atoms.segments + def test_newSegmentGroup(universe): """test that slicing a SegmentGroup returns a new SegmentGroup (Issue 135)""" g = universe.atoms.segments @@ -74,40 +75,53 @@ def test_segids_dim(g): def test_set_segids(universe): - s = universe.select_atoms('all').segments - s.segids = 'ADK' - assert_equal(universe.segments.segids, ['ADK'], - err_msg="failed to set_segid on segments") + s = universe.select_atoms("all").segments + s.segids = "ADK" + assert_equal( + universe.segments.segids, + ["ADK"], + err_msg="failed to set_segid on segments", + ) def test_set_segid_updates_(universe): g = universe.select_atoms("resid 10:18").segments - g.segids = 'ADK' - assert_equal(g.segids, ['ADK'], - err_msg="old selection was not changed in place after set_segid") + g.segids = "ADK" + assert_equal( + g.segids, + ["ADK"], + err_msg="old selection was not changed in place after set_segid", + ) def test_set_segids_many(): - u = mda.Universe.empty(n_atoms=6, n_residues=2, n_segments=2, - atom_resindex=[0, 0, 0, 1, 1, 1], residue_segindex=[0,1]) - u.add_TopologyAttr('segids', ['A', 'B']) + u = mda.Universe.empty( + n_atoms=6, + n_residues=2, + n_segments=2, + atom_resindex=[0, 0, 0, 1, 1, 1], + residue_segindex=[0, 1], + ) + u.add_TopologyAttr("segids", ["A", "B"]) # universe with 2 segments, A and B - u.segments.segids = ['X', 'Y'] + u.segments.segids = ["X", "Y"] - assert u.segments[0].segid == 'X' - assert u.segments[1].segid == 'Y' + assert u.segments[0].segid == "X" + assert u.segments[1].segid == "Y" - assert len(u.select_atoms('segid A')) == 0 - assert len(u.select_atoms('segid B')) == 0 - assert len(u.select_atoms('segid X')) == 3 - assert len(u.select_atoms('segid Y')) == 3 + assert len(u.select_atoms("segid A")) == 0 + assert len(u.select_atoms("segid B")) == 0 + assert len(u.select_atoms("segid X")) == 3 + assert len(u.select_atoms("segid Y")) == 3 def test_atom_order(universe): - assert_equal(universe.segments.atoms.indices, - sorted(universe.segments.atoms.indices)) + assert_equal( + universe.segments.atoms.indices, + sorted(universe.segments.atoms.indices), + ) def test_segmentgroup_pickle(): diff --git a/testsuite/MDAnalysisTests/core/test_topology.py b/testsuite/MDAnalysisTests/core/test_topology.py index 4ed660b25be..d1154f5c4e0 100644 --- a/testsuite/MDAnalysisTests/core/test_topology.py +++ b/testsuite/MDAnalysisTests/core/test_topology.py @@ -3,6 +3,7 @@ Should convert between indices (*ix) Should work with both a single or an array of indices """ + import itertools from numpy.testing import ( assert_equal, @@ -38,30 +39,22 @@ def tt(self): def test_a2r(self, tt): for aix, rix in zip( - [np.array([0, 1, 2]), - np.array([9, 6, 2]), - np.array([3, 3, 3])], - [np.array([0, 0, 2]), - np.array([2, 3, 2]), - np.array([2, 2, 2])] + [np.array([0, 1, 2]), np.array([9, 6, 2]), np.array([3, 3, 3])], + [np.array([0, 0, 2]), np.array([2, 3, 2]), np.array([2, 2, 2])], ): assert_equal(tt.atoms2residues(aix), rix) def test_r2a_1d(self, tt): for rix, aix in zip( - [[0, 1], [1, 1], [3, 1]], - [[0, 1, 4, 5, 8], [4, 5, 8, 4, 5, 8], [6, 7, 4, 5, 8]] + [[0, 1], [1, 1], [3, 1]], + [[0, 1, 4, 5, 8], [4, 5, 8, 4, 5, 8], [6, 7, 4, 5, 8]], ): assert_equal(tt.residues2atoms_1d(rix), aix) def test_r2a_2d(self, tt): for rix, aix in zip( - [[0, 1], - [1, 1], - [3, 1]], - [[[0, 1], [4, 5, 8]], - [[4, 5, 8], [4, 5, 8]], - [[6, 7], [4, 5, 8]]] + [[0, 1], [1, 1], [3, 1]], + [[[0, 1], [4, 5, 8]], [[4, 5, 8], [4, 5, 8]], [[6, 7], [4, 5, 8]]], ): answer = tt.residues2atoms_2d(rix) for a1, a2 in zip(answer, aix): @@ -69,34 +62,22 @@ def test_r2a_2d(self, tt): def test_r2s(self, tt): for rix, sidx in zip( - [np.array([0, 1]), - np.array([2, 1, 0]), - np.array([1, 1, 1])], - [np.array([0, 1]), - np.array([1, 1, 0]), - np.array([1, 1, 1])] + [np.array([0, 1]), np.array([2, 1, 0]), np.array([1, 1, 1])], + [np.array([0, 1]), np.array([1, 1, 0]), np.array([1, 1, 1])], ): assert_equal(tt.residues2segments(rix), sidx) def test_s2r_1d(self, tt): for sidx, rix in zip( - [[0, 1], - [1, 0], - [1, 1]], - [[0, 3, 1, 2], - [1, 2, 0, 3], - [1, 2, 1, 2]] + [[0, 1], [1, 0], [1, 1]], + [[0, 3, 1, 2], [1, 2, 0, 3], [1, 2, 1, 2]], ): assert_equal(tt.segments2residues_1d(sidx), rix) def test_s2r_2d(self, tt): for sidx, rix in zip( - [[0, 1], - [1, 0], - [1, 1]], - [[[0, 3], [1, 2]], - [[1, 2], [0, 3]], - [[1, 2], [1, 2]]] + [[0, 1], [1, 0], [1, 1]], + [[[0, 3], [1, 2]], [[1, 2], [0, 3]], [[1, 2], [1, 2]]], ): answer = tt.segments2residues_2d(sidx) for a1, a2 in zip(answer, rix): @@ -104,23 +85,23 @@ def test_s2r_2d(self, tt): def test_s2a_1d(self, tt): for sidx, aix in zip( - [[0, 1], - [1, 0], - [1, 1]], - [[0, 1, 6, 7, 4, 5, 8, 2, 3, 9], - [4, 5, 8, 2, 3, 9, 0, 1, 6, 7], - [4, 5, 8, 2, 3, 9, 4, 5, 8, 2, 3, 9]], + [[0, 1], [1, 0], [1, 1]], + [ + [0, 1, 6, 7, 4, 5, 8, 2, 3, 9], + [4, 5, 8, 2, 3, 9, 0, 1, 6, 7], + [4, 5, 8, 2, 3, 9, 4, 5, 8, 2, 3, 9], + ], ): assert_equal(tt.segments2atoms_1d(sidx), aix) def test_s2a_2d(self, tt): for sidx, aix in zip( - [[0, 1], - [1, 0], - [1, 1]], - [[[0, 1, 6, 7], [4, 5, 8, 2, 3, 9]], - [[4, 5, 8, 2, 3, 9], [0, 1, 6, 7]], - [[4, 5, 8, 2, 3, 9], [4, 5, 8, 2, 3, 9]]], + [[0, 1], [1, 0], [1, 1]], + [ + [[0, 1, 6, 7], [4, 5, 8, 2, 3, 9]], + [[4, 5, 8, 2, 3, 9], [0, 1, 6, 7]], + [[4, 5, 8, 2, 3, 9], [4, 5, 8, 2, 3, 9]], + ], ): answer = tt.segments2atoms_2d(sidx) for a1, a2 in zip(answer, aix): @@ -153,12 +134,19 @@ def test_move_residue_simple(self, tt): def test_lazy_building_RA(self, tt): assert_equal(tt._RA, None) RA = tt.RA - assert_rows_match(tt.RA, - np.array([np.array([0, 1]), - np.array([4, 5, 8]), - np.array([2, 3, 9]), - np.array([6, 7]), - None], dtype=object)) + assert_rows_match( + tt.RA, + np.array( + [ + np.array([0, 1]), + np.array([4, 5, 8]), + np.array([2, 3, 9]), + np.array([6, 7]), + None, + ], + dtype=object, + ), + ) tt.move_atom(1, 3) assert_equal(tt._RA, None) @@ -166,10 +154,10 @@ def test_lazy_building_RA(self, tt): def test_lazy_building_SR(self, tt): assert_equal(tt._SR, None) SR = tt.SR - assert_rows_match(tt.SR, - np.array([np.array([0, 3]), - np.array([1, 2]), - None], dtype=object)) + assert_rows_match( + tt.SR, + np.array([np.array([0, 3]), np.array([1, 2]), None], dtype=object), + ) tt.move_residue(1, 0) assert_equal(tt._SR, None) @@ -187,18 +175,18 @@ def test_serialization(self, tt): class TestLevelMoves(object): """Tests for moving atoms/residues between residues/segments - + Atoms can move between residues by setting .residue with a Residue Residues can move between segments by setting .segment with a Segment Moves are performed by setting either [res/seg]indices or [res/seg]ids - + """ @pytest.fixture() def u(self): - return make_Universe(('resids', 'resnames', 'segids')) + return make_Universe(("resids", "resnames", "segids")) @staticmethod def assert_atoms_match_residue(atom, residue): @@ -305,48 +293,48 @@ def test_move_atomgroup_residue_list(self, u): # Wrong size argument for these operations def test_move_atom_residuegroup_TE(self, u): with pytest.raises(TypeError): - setattr(u.atoms[0], 'residue', u.atoms[1:3]) + setattr(u.atoms[0], "residue", u.atoms[1:3]) def test_move_atom_residue_list_TE(self, u): dest = [u.residues[1], u.residues[3]] with pytest.raises(TypeError): - setattr(u.atoms[0], 'residue', dest) + setattr(u.atoms[0], "residue", dest) def test_move_atomgroup_residuegroup_VE(self, u): ag = u.atoms[:2] dest = u.residues[5:10] with pytest.raises(ValueError): - setattr(ag, 'residues', dest) + setattr(ag, "residues", dest) def test_move_atomgroup_residue_list_VE(self, u): ag = u.atoms[:2] dest = [u.residues[0], u.residues[10], u.residues[15]] with pytest.raises(ValueError): - setattr(ag, 'residues', dest) + setattr(ag, "residues", dest) # Setting to non-Residue/ResidueGroup raises TE def test_move_atom_TE(self, u): with pytest.raises(TypeError): - setattr(u.atoms[0], 'residue', 14) + setattr(u.atoms[0], "residue", 14) def test_move_atomgroup_TE(self, u): with pytest.raises(TypeError): - setattr(u.atoms[:5], 'residues', 15) + setattr(u.atoms[:5], "residues", 15) def test_move_atomgroup_list_TE(self, u): with pytest.raises(TypeError): - setattr(u.atoms[:5], 'residues', [14, 12]) + setattr(u.atoms[:5], "residues", [14, 12]) # Test illegal moves - Atom.segment can't be changed def test_move_atom_segment_NIE(self, u): with pytest.raises(NotImplementedError): - setattr(u.atoms[0], 'segment', u.segments[1]) + setattr(u.atoms[0], "segment", u.segments[1]) def test_move_atomgroup_segment_NIE(self, u): with pytest.raises(NotImplementedError): - setattr(u.atoms[:3], 'segments', u.segments[1]) + setattr(u.atoms[:3], "segments", u.segments[1]) @staticmethod def assert_residue_matches_segment(res, seg): @@ -446,38 +434,38 @@ def test_move_residuegroup_segment_list(self, u): def test_move_residue_segmentgroup_TE(self, u): with pytest.raises(TypeError): - setattr(u.residues[0], 'segment', u.segments[:4]) + setattr(u.residues[0], "segment", u.segments[:4]) def test_move_residue_list_TE(self, u): dest = [u.segments[3], u.segments[4]] with pytest.raises(TypeError): - setattr(u.residues[0], 'segment', dest) + setattr(u.residues[0], "segment", dest) def test_move_residuegroup_segmentgroup_VE(self, u): rg = u.residues[:3] sg = u.segments[1:] with pytest.raises(ValueError): - setattr(rg, 'segments', sg) + setattr(rg, "segments", sg) def test_move_residuegroup_list_VE(self, u): rg = u.residues[:2] sg = [u.segments[1], u.segments[2], u.segments[3]] with pytest.raises(ValueError): - setattr(rg, 'segments', sg) + setattr(rg, "segments", sg) def test_move_residue_TE(self, u): with pytest.raises(TypeError): - setattr(u.residues[0], 'segment', 1) + setattr(u.residues[0], "segment", 1) def test_move_residuegroup_TE(self, u): with pytest.raises(TypeError): - setattr(u.residues[:3], 'segments', 4) + setattr(u.residues[:3], "segments", 4) def test_move_residuegroup_list_TE(self, u): with pytest.raises(TypeError): - setattr(u.residues[:3], 'segments', [1, 2, 3]) + setattr(u.residues[:3], "segments", [1, 2, 3]) class TestDownshiftArrays(object): @@ -503,8 +491,7 @@ def ragged_size(self): @pytest.fixture() def ragged_result(self): - return np.array([[0, 4, 7], [1, 5, 8], [2, 3, 6, 9]], - dtype=object) + return np.array([[0, 4, 7], [1, 5, 8], [2, 3, 6, 9]], dtype=object) # The array as a whole must be dtype object # While the subarrays must be integers @@ -539,53 +526,87 @@ def test_contents_ragged(self, ragged, ragged_size, ragged_result): assert_rows_match(out, ragged_result) def test_missing_intra_values(self): - out = make_downshift_arrays( - np.array([0, 0, 2, 2, 3, 3]), 4) - assert_rows_match(out, - np.array([np.array([0, 1]), - np.array([], dtype=int), - np.array([2, 3]), - np.array([4, 5]), - None], dtype=object)) + out = make_downshift_arrays(np.array([0, 0, 2, 2, 3, 3]), 4) + assert_rows_match( + out, + np.array( + [ + np.array([0, 1]), + np.array([], dtype=int), + np.array([2, 3]), + np.array([4, 5]), + None, + ], + dtype=object, + ), + ) def test_missing_intra_values_2(self): - out = make_downshift_arrays( - np.array([0, 0, 3, 3, 4, 4]), 5) - assert_rows_match(out, - np.array([np.array([0, 1]), - np.array([], dtype=int), - np.array([], dtype=int), - np.array([2, 3]), - np.array([4, 5]), - None], dtype=object)) + out = make_downshift_arrays(np.array([0, 0, 3, 3, 4, 4]), 5) + assert_rows_match( + out, + np.array( + [ + np.array([0, 1]), + np.array([], dtype=int), + np.array([], dtype=int), + np.array([2, 3]), + np.array([4, 5]), + None, + ], + dtype=object, + ), + ) def test_missing_end_values(self): out = make_downshift_arrays(np.array([0, 0, 1, 1, 2, 2]), 4) - assert_rows_match(out, - np.array([np.array([0, 1]), - np.array([2, 3]), - np.array([4, 5]), - np.array([], dtype=int), - None], dtype=object)) + assert_rows_match( + out, + np.array( + [ + np.array([0, 1]), + np.array([2, 3]), + np.array([4, 5]), + np.array([], dtype=int), + None, + ], + dtype=object, + ), + ) def test_missing_end_values_2(self): out = make_downshift_arrays(np.array([0, 0, 1, 1, 2, 2]), 6) - assert_rows_match(out, - np.array([np.array([0, 1]), - np.array([2, 3]), - np.array([4, 5]), - np.array([], dtype=int), - np.array([], dtype=int), - None], dtype=object)) + assert_rows_match( + out, + np.array( + [ + np.array([0, 1]), + np.array([2, 3]), + np.array([4, 5]), + np.array([], dtype=int), + np.array([], dtype=int), + None, + ], + dtype=object, + ), + ) def test_missing_start_values_2(self): out = make_downshift_arrays(np.array([1, 1, 2, 2, 3, 3]), 4) - assert_rows_match(out, - np.array([np.array([], dtype=int), - np.array([0, 1]), - np.array([2, 3]), - np.array([4, 5]), - None], dtype=object)) + assert_rows_match( + out, + np.array( + [ + np.array([], dtype=int), + np.array([0, 1]), + np.array([2, 3]), + np.array([4, 5]), + None, + ], + dtype=object, + ), + ) + class TestAddingResidues(object): """Tests for adding residues and segments to a Universe @@ -635,74 +656,75 @@ def test_add_Residue_ambiguous_segment_NDE(self): u.add_Residue() def test_add_Residue_missing_attr_NDE(self): - u = make_Universe(('resids',)) + u = make_Universe(("resids",)) with pytest.raises(NoDataError): u.add_Residue(segment=u.segments[0]) def test_add_Residue_NDE_message(self): # check error message asks for missing attr - u = make_Universe(('resnames', 'resids')) + u = make_Universe(("resnames", "resids")) try: u.add_Residue(segment=u.segments[0], resid=42) except NoDataError as e: - assert 'resname' in str(e) + assert "resname" in str(e) else: raise AssertionError def test_add_Residue_NDE_message_2(self): # multiple missing attrs, check all get mentioned in error - u = make_Universe(('resnames', 'resids')) + u = make_Universe(("resnames", "resids")) try: u.add_Residue(segment=u.segments[0]) except NoDataError as e: - assert 'resname' in str(e) - assert 'resid' in str(e) + assert "resname" in str(e) + assert "resid" in str(e) else: raise AssertionError def test_add_Residue_with_attrs(self): - u = make_Universe(('resnames', 'resids')) + u = make_Universe(("resnames", "resids")) - r_new = u.add_Residue(segment=u.segments[0], resid=4321, resname='New') + r_new = u.add_Residue(segment=u.segments[0], resid=4321, resname="New") assert r_new.resid == 4321 - assert r_new.resname == 'New' + assert r_new.resname == "New" def test_missing_attr_NDE_Segment(self): - u = make_Universe(('segids',)) + u = make_Universe(("segids",)) with pytest.raises(NoDataError): u.add_Segment() def test_add_Segment_NDE_message(self): - u = make_Universe(('segids',)) + u = make_Universe(("segids",)) try: u.add_Segment() except NoDataError as e: - assert 'segid' in str(e) + assert "segid" in str(e) else: raise AssertionError def test_add_Segment_with_attr(self): - u = make_Universe(('segids',)) + u = make_Universe(("segids",)) - new_seg = u.add_Segment(segid='New') + new_seg = u.add_Segment(segid="New") - assert new_seg.segid == 'New' + assert new_seg.segid == "New" class TestTopologyGuessed(object): @pytest.fixture() def names(self): - return ta.Atomnames(np.array(['A', 'B', 'C'], dtype=object)) + return ta.Atomnames(np.array(["A", "B", "C"], dtype=object)) @pytest.fixture() def types(self): - return ta.Atomtypes(np.array(['X', 'Y', 'Z'], dtype=object), - guessed=True) + return ta.Atomtypes( + np.array(["X", "Y", "Z"], dtype=object), guessed=True + ) @pytest.fixture() def resids(self): @@ -710,8 +732,7 @@ def resids(self): @pytest.fixture() def resnames(self): - return ta.Resnames(np.array(['ABC'], dtype=object), - guessed=True) + return ta.Resnames(np.array(["ABC"], dtype=object), guessed=True) @pytest.fixture() def bonds(self): @@ -719,8 +740,9 @@ def bonds(self): @pytest.fixture() def top(self, names, types, resids, resnames, bonds): - return Topology(n_atoms=3, n_res=1, - attrs=[names, types, resids, resnames, bonds]) + return Topology( + n_atoms=3, n_res=1, attrs=[names, types, resids, resnames, bonds] + ) def test_guessed(self, names, types, resids, resnames, bonds, top): guessed = top.guessed_attributes @@ -739,12 +761,13 @@ def test_read(self, names, types, resids, resnames, bonds, top): assert not types in read assert not resnames in read + class TestTopologyCreation(object): def test_make_topology_no_attrs(self): # should still make attrs list when attrs=None top = Topology() - assert hasattr(top, 'attrs') + assert hasattr(top, "attrs") assert isinstance(top.attrs, list) def test_resindex_VE(self): @@ -758,5 +781,4 @@ def test_segindex_VE(self): AR = np.arange(5) RS = np.arange(10) with pytest.raises(ValueError): - Topology(n_atoms=5, n_res=5, atom_resindex=AR, - residue_segindex=RS) + Topology(n_atoms=5, n_res=5, atom_resindex=AR, residue_segindex=RS) diff --git a/testsuite/MDAnalysisTests/core/test_topologyattrs.py b/testsuite/MDAnalysisTests/core/test_topologyattrs.py index 3ece107a93d..5155933c2e4 100644 --- a/testsuite/MDAnalysisTests/core/test_topologyattrs.py +++ b/testsuite/MDAnalysisTests/core/test_topologyattrs.py @@ -45,6 +45,7 @@ class DummyGroup(object): initiate with indices, these are then available as ._ix """ + def __init__(self, vals): self._ix = vals @@ -64,17 +65,21 @@ class TopologyAttrMixin(object): 2 segments """ - # Reference data + # Reference data @pytest.fixture() def top(self): Ridx = np.array([0, 0, 2, 2, 1, 1, 3, 3, 1, 2]) Sidx = np.array([0, 1, 1, 0]) - return Topology(10, 4, 2, - attrs=[self.attrclass(self.values.copy())], - atom_resindex=Ridx, - residue_segindex=Sidx) + return Topology( + 10, + 4, + 2, + attrs=[self.attrclass(self.values.copy())], + atom_resindex=Ridx, + residue_segindex=Sidx, + ) @pytest.fixture() def attr(self, top): @@ -89,33 +94,33 @@ def test_len(self, attr): class TestAtomAttr(TopologyAttrMixin): - """Test atom-level TopologyAttrs. + """Test atom-level TopologyAttrs.""" - """ values = np.array([7, 3, 69, 9993, 84, 194, 263, 501, 109, 5873]) single_value = 567 attrclass = tpattrs.AtomAttr def test_set_atom_VE(self): - u = make_Universe(('names',)) + u = make_Universe(("names",)) at = u.atoms[0] with pytest.raises(ValueError): - setattr(at, 'name', ['oopsy', 'daisies']) + setattr(at, "name", ["oopsy", "daisies"]) def test_get_atoms(self, attr): result = attr.get_atoms(DummyGroup([2, 1])) assert len(result) == 2 - assert_equal(result, - self.values[[2, 1]]) + assert_equal(result, self.values[[2, 1]]) def test_set_atoms_singular(self, attr): # set len 2 Group to len 1 value dg = DummyGroup([3, 7]) attr.set_atoms(dg, self.single_value) - assert_equal(attr.get_atoms(dg), - np.array([self.single_value, self.single_value])) + assert_equal( + attr.get_atoms(dg), + np.array([self.single_value, self.single_value]), + ) def test_set_atoms_plural(self, attr): # set len 2 Group to len 2 values @@ -137,8 +142,7 @@ def test_get_residues(self, attr): result = attr.get_residues(DummyGroup([2, 1])) assert len(result) == 2 - assert_equal(result, - [self.values[[2, 3, 9]], self.values[[4, 5, 8]]]) + assert_equal(result, [self.values[[2, 3, 9]], self.values[[4, 5, 8]]]) def test_get_segments(self, attr): """Unless overriden by child class, this should yield values for all @@ -148,8 +152,7 @@ def test_get_segments(self, attr): result = attr.get_segments(DummyGroup([1])) assert len(result) == 1 - assert_equal(result, - [self.values[[4, 5, 8, 2, 3, 9]]]) + assert_equal(result, [self.values[[4, 5, 8, 2, 3, 9]]]) class TestAtomids(TestAtomAttr): @@ -175,9 +178,10 @@ def test_cant_set_segment_indices(self, u): class TestAtomnames(TestAtomAttr): - values = np.array(['O', 'C', 'CA', 'N', 'CB', 'CG', 'CD', 'NA', 'CL', 'OW'], - dtype=object) - single_value = 'Ca2' + values = np.array( + ["O", "C", "CA", "N", "CB", "CG", "CD", "NA", "CL", "OW"], dtype=object + ) + single_value = "Ca2" attrclass = tpattrs.Atomnames @pytest.fixture() @@ -198,39 +202,65 @@ def test_next_emptyresidue(self, u): assert groupsize == len(u.residues[[]].atoms) def test_missing_values(self, attr): - assert_equal(attr.are_values_missing(self.values), np.array( - [False, False, False, False, False, False, - False, False, False, False])) + assert_equal( + attr.are_values_missing(self.values), + np.array( + [ + False, + False, + False, + False, + False, + False, + False, + False, + False, + False, + ] + ), + ) def test_missing_value_label(self): - self.attrclass.missing_value_label = 'FOO' - values = np.array(['NA', 'C', 'N', 'FOO']) - assert_equal(self.attrclass.are_values_missing(values), - np.array([False, False, False, True])) + self.attrclass.missing_value_label = "FOO" + values = np.array(["NA", "C", "N", "FOO"]) + assert_equal( + self.attrclass.are_values_missing(values), + np.array([False, False, False, True]), + ) class AggregationMixin(TestAtomAttr): def test_get_residues(self, attr): - assert_equal(attr.get_residues(DummyGroup([2, 1])), - np.array([self.values[[2, 3, 9]].sum(), - self.values[[4, 5, 8]].sum()])) + assert_equal( + attr.get_residues(DummyGroup([2, 1])), + np.array( + [self.values[[2, 3, 9]].sum(), self.values[[4, 5, 8]].sum()] + ), + ) def test_get_segments(self, attr): - assert_equal(attr.get_segments(DummyGroup([1])), - np.array([self.values[[4, 5, 8, 2, 3, 9]].sum()])) + assert_equal( + attr.get_segments(DummyGroup([1])), + np.array([self.values[[4, 5, 8, 2, 3, 9]].sum()]), + ) def test_get_segment(self, attr): - assert_equal(attr.get_segments(DummyGroup(1)), - np.sum(self.values[[4, 5, 8, 2, 3, 9]])) + assert_equal( + attr.get_segments(DummyGroup(1)), + np.sum(self.values[[4, 5, 8, 2, 3, 9]]), + ) class TestMasses(AggregationMixin): attrclass = tpattrs.Masses def test_missing_masses(self): - values = [1., 2., np.nan, 3.] - assert_equal(self.attrclass.are_values_missing(values), - np.array([False, False, True, False])) + values = [1.0, 2.0, np.nan, 3.0] + assert_equal( + self.attrclass.are_values_missing(values), + np.array([False, False, True, False]), + ) + class TestCharges(AggregationMixin): values = np.array([+2, -1, 0, -1, +1, +2, 0, 0, 0, -1]) @@ -238,9 +268,8 @@ class TestCharges(AggregationMixin): class TestResidueAttr(TopologyAttrMixin): - """Test residue-level TopologyAttrs. + """Test residue-level TopologyAttrs.""" - """ single_value = 2 values = np.array([15.2, 395.6, 0.1, 9.8]) attrclass = tpattrs.ResidueAttr @@ -252,29 +281,34 @@ def test_set_residue_VE(self, universe): setattr(res, self.attrclass.singular, self.values[:2]) def test_get_atoms(self, attr): - assert_equal(attr.get_atoms(DummyGroup([7, 3, 9])), - self.values[[3, 2, 2]]) + assert_equal( + attr.get_atoms(DummyGroup([7, 3, 9])), self.values[[3, 2, 2]] + ) def test_get_atom(self, universe): attr = getattr(universe.atoms[0], self.attrclass.singular) assert_equal(attr, self.values[0]) def test_get_residues(self, attr): - assert_equal(attr.get_residues(DummyGroup([1, 2, 1, 3])), - self.values[[1, 2, 1, 3]]) + assert_equal( + attr.get_residues(DummyGroup([1, 2, 1, 3])), + self.values[[1, 2, 1, 3]], + ) def test_set_residues_singular(self, attr): dg = DummyGroup([3, 0, 1]) attr.set_residues(dg, self.single_value) - assert_equal(attr.get_residues(dg), - np.array([self.single_value]*3, dtype=self.values.dtype)) + assert_equal( + attr.get_residues(dg), + np.array([self.single_value] * 3, dtype=self.values.dtype), + ) def test_set_residues_plural(self, attr): - attr.set_residues(DummyGroup([3, 0, 1]), - np.array([23, 504, 2])) - assert_almost_equal(attr.get_residues(DummyGroup([3, 0, 1])), - np.array([23, 504, 2])) + attr.set_residues(DummyGroup([3, 0, 1]), np.array([23, 504, 2])) + assert_almost_equal( + attr.get_residues(DummyGroup([3, 0, 1])), np.array([23, 504, 2]) + ) def test_set_residues_VE(self, attr): dg = DummyGroup([3, 0, 1]) @@ -287,14 +321,16 @@ def test_get_segments(self, attr): atoms in segments. """ - assert_equal(attr.get_segments(DummyGroup([0, 1, 1])), - [self.values[[0, 3]], self.values[[1, 2]], self.values[[1, 2]]]) + assert_equal( + attr.get_segments(DummyGroup([0, 1, 1])), + [self.values[[0, 3]], self.values[[1, 2]], self.values[[1, 2]]], + ) class TestResnames(TestResidueAttr): attrclass = tpattrs.Resnames - single_value = 'xyz' - values = np.array(['a', 'b', '', 'd'], dtype=object) + single_value = "xyz" + values = np.array(["a", "b", "", "d"], dtype=object) class TestICodes(TestResnames): @@ -307,9 +343,7 @@ class TestResids(TestResidueAttr): @pytest.mark.xfail def test_set_atoms(self, attr): - """Setting the resids of atoms changes their residue membership. - - """ + """Setting the resids of atoms changes their residue membership.""" # moving resids doesn't currently work! assert 1 == 2 @@ -322,40 +356,43 @@ def test_set_atoms(self, attr): attr.set_atoms(DummyGroup([3, 7]), np.array([11, 21])) def test_set_residues(self, attr): - attr.set_residues(DummyGroup([3, 0, 1]), - np.array([23, 504, 27])) - assert_almost_equal(attr.get_residues(DummyGroup([3, 0, 1])), - np.array([23, 504, 27])) + attr.set_residues(DummyGroup([3, 0, 1]), np.array([23, 504, 27])) + assert_almost_equal( + attr.get_residues(DummyGroup([3, 0, 1])), np.array([23, 504, 27]) + ) class TestSegmentAttr(TopologyAttrMixin): - """Test segment-level TopologyAttrs. + """Test segment-level TopologyAttrs.""" - """ values = np.array([-0.19, 500]) attrclass = tpattrs.SegmentAttr def test_set_segment_VE(self): - u = make_Universe(('segids',)) + u = make_Universe(("segids",)) seg = u.segments[0] with pytest.raises(ValueError): - setattr(seg, 'segid', [1, 2, 3]) + setattr(seg, "segid", [1, 2, 3]) def test_get_atoms(self, attr): - assert_equal(attr.get_atoms(DummyGroup([2, 4, 1])), - self.values[[1, 1, 0]]) + assert_equal( + attr.get_atoms(DummyGroup([2, 4, 1])), self.values[[1, 1, 0]] + ) def test_get_residues(self, attr): - assert_equal(attr.get_residues(DummyGroup([1, 2, 1, 3])), - self.values[[1, 1, 1, 0]]) + assert_equal( + attr.get_residues(DummyGroup([1, 2, 1, 3])), + self.values[[1, 1, 1, 0]], + ) def test_get_segments(self, attr): """Unless overriden by child class, this should yield values for all atoms in segments. """ - assert_equal(attr.get_segments(DummyGroup([1, 0, 0])), - self.values[[1, 0, 0]]) + assert_equal( + attr.get_segments(DummyGroup([1, 0, 0])), self.values[[1, 0, 0]] + ) def test_set_segments_singular(self, attr): dg = DummyGroup([0, 1]) @@ -382,10 +419,14 @@ def ag(self): def test_principal_axes(self, ag): assert_almost_equal( ag.principal_axes(), - np.array([ - [1.53389276e-03, 4.41386224e-02, 9.99024239e-01], - [1.20986911e-02, 9.98951474e-01, -4.41539838e-02], - [-9.99925632e-01, 1.21546132e-02, 9.98264877e-04]])) + np.array( + [ + [1.53389276e-03, 4.41386224e-02, 9.99024239e-01], + [1.20986911e-02, 9.98951474e-01, -4.41539838e-02], + [-9.99925632e-01, 1.21546132e-02, 9.98264877e-04], + ] + ), + ) @pytest.fixture() def universe_pa(self): @@ -393,7 +434,9 @@ def universe_pa(self): def test_principal_axes_handedness(self, universe_pa): e_vec = universe_pa.atoms.principal_axes() - assert_almost_equal(np.dot(np.cross(e_vec[0], e_vec[1]), e_vec[2]), 1.0) + assert_almost_equal( + np.dot(np.cross(e_vec[0], e_vec[1]), e_vec[2]), 1.0 + ) def test_align_principal_axes_with_self(self, ag): pa = ag.principal_axes() @@ -422,7 +465,7 @@ class TestCrossLevelAttributeSetting(object): Atom.resid = 4 should fail because resid belongs to Residue not Atom """ - u = make_Universe(('names', 'resids', 'segids')) + u = make_Universe(("names", "resids", "segids")) # component and group in each level atomlevel = (u.atoms[0], u.atoms[:10]) @@ -430,13 +473,13 @@ class TestCrossLevelAttributeSetting(object): segmentlevel = (u.segments[0], u.segments[:2]) levels = {0: atomlevel, 1: residuelevel, 2: segmentlevel} - atomattr = 'names' - residueattr = 'resids' - segmentattr = 'segids' + atomattr = "names" + residueattr = "resids" + segmentattr = "segids" attrs = {0: atomattr, 1: residueattr, 2: segmentattr} - @pytest.mark.parametrize('level_idx, level', levels.items()) - @pytest.mark.parametrize('attr_idx, attr', attrs.items()) + @pytest.mark.parametrize("level_idx, level", levels.items()) + @pytest.mark.parametrize("attr_idx, attr", attrs.items()) def test_set_crosslevel(self, level_idx, level, attr_idx, attr): if level_idx == attr_idx: # if we're on the same level, then this should work @@ -468,33 +511,33 @@ class TestRecordTypes(object): def test_record_types_default(self): u = make_Universe() - u.add_TopologyAttr('record_type') + u.add_TopologyAttr("record_type") - assert u.atoms[0].record_type == 'ATOM' - assert_equal(u.atoms[:10].record_types, 'ATOM') + assert u.atoms[0].record_type == "ATOM" + assert_equal(u.atoms[:10].record_types, "ATOM") @pytest.fixture() def rectype_uni(self): # standard 125/25/5 universe u = make_Universe() - u.add_TopologyAttr('record_type') + u.add_TopologyAttr("record_type") # first 25 atoms are ATOM (first 5 residues, first segment) # 25 to 50th are HETATM (res 5:10, second segment) # all after are ATOM - u.atoms[:25].record_types = 'ATOM' - u.atoms[25:50].record_types = 'HETATM' - u.atoms[50:].record_types = 'ATOM' + u.atoms[:25].record_types = "ATOM" + u.atoms[25:50].record_types = "HETATM" + u.atoms[50:].record_types = "ATOM" return u def test_encoding(self, rectype_uni): ag = rectype_uni.atoms[:10] - ag[0].record_type = 'ATOM' - ag[1:4].record_types = 'HETATM' + ag[0].record_type = "ATOM" + ag[1:4].record_types = "HETATM" - assert ag[0].record_type == 'ATOM' - assert ag[1].record_type == 'HETATM' + assert ag[0].record_type == "ATOM" + assert ag[1].record_type == "HETATM" def test_residue_record_types(self, rectype_uni): rt = rectype_uni.residues.record_types @@ -505,8 +548,8 @@ def test_residue_record_types(self, rectype_uni): # check return type explicitly # some versions of numpy allow bool to str comparison assert not rt[0].dtype == bool - assert (rt[0] == 'ATOM').all() - assert (rt[5] == 'HETATM').all() + assert (rt[0] == "ATOM").all() + assert (rt[5] == "HETATM").all() def test_segment_record_types(self, rectype_uni): rt = rectype_uni.segments.record_types @@ -515,12 +558,12 @@ def test_segment_record_types(self, rectype_uni): assert len(rt) == 5 assert not rt[0].dtype == bool - assert (rt[0] == 'ATOM').all() - assert (rt[1] == 'HETATM').all() + assert (rt[0] == "ATOM").all() + assert (rt[1] == "HETATM").all() def test_static_typing(): - ta = tpattrs.Charges(['1.0', '2.0', '3.0']) + ta = tpattrs.Charges(["1.0", "2.0", "3.0"]) assert isinstance(ta.values, np.ndarray) assert ta.values.dtype == float @@ -529,18 +572,21 @@ def test_static_typing(): def test_static_typing_from_empty(): u = mda.Universe.empty(3) - u.add_TopologyAttr('masses', values=['1.0', '2.0', '3.0']) + u.add_TopologyAttr("masses", values=["1.0", "2.0", "3.0"]) assert isinstance(u._topology.masses.values, np.ndarray) assert isinstance(u.atoms[0].mass, float) -@pytest.mark.parametrize('level, transplant_name', ( - ('atoms', 'center_of_mass'), - ('atoms', 'center_of_charge'), - ('atoms', 'total_charge'), - ('residues', 'total_charge'), -)) +@pytest.mark.parametrize( + "level, transplant_name", + ( + ("atoms", "center_of_mass"), + ("atoms", "center_of_charge"), + ("atoms", "total_charge"), + ("residues", "total_charge"), + ), +) def test_stub_transplant_methods(level, transplant_name): u = mda.Universe.empty(n_atoms=2) group = getattr(u, level) @@ -548,10 +594,13 @@ def test_stub_transplant_methods(level, transplant_name): getattr(group, transplant_name)() -@pytest.mark.parametrize('level, transplant_name', ( - ('universe', 'models'), - ('atoms', 'n_fragments'), -)) +@pytest.mark.parametrize( + "level, transplant_name", + ( + ("universe", "models"), + ("atoms", "n_fragments"), + ), +) def test_stub_transplant_property(level, transplant_name): u = mda.Universe.empty(n_atoms=2) group = getattr(u, level) @@ -563,6 +612,7 @@ def test_warn_selection_for_strange_dtype(): err = "A selection keyword could not be automatically generated" with pytest.warns(UserWarning, match=err): + class Star(tpattrs.TopologyAttr): singular = "star" # turns out test_imports doesn't like emoji attrname = "stars" # :( @@ -612,15 +662,16 @@ class TestStringInterning: # try and trip up the string interning we use for string attributes @pytest.fixture def universe(self): - u = mda.Universe.empty(n_atoms=10, n_residues=2, - atom_resindex=[0]*5 + [1] * 5) - u.add_TopologyAttr('names', values=['A'] * 10) - u.add_TopologyAttr('resnames', values=['ResA', 'ResB']) - u.add_TopologyAttr('segids', values=['SegA']) + u = mda.Universe.empty( + n_atoms=10, n_residues=2, atom_resindex=[0] * 5 + [1] * 5 + ) + u.add_TopologyAttr("names", values=["A"] * 10) + u.add_TopologyAttr("resnames", values=["ResA", "ResB"]) + u.add_TopologyAttr("segids", values=["SegA"]) return u - @pytest.mark.parametrize('newname', ['ResA', 'ResB']) + @pytest.mark.parametrize("newname", ["ResA", "ResB"]) def test_add_residue(self, universe, newname): newres = universe.add_Residue(resname=newname) @@ -631,7 +682,7 @@ def test_add_residue(self, universe, newname): assert ag.resname == newname - @pytest.mark.parametrize('newname', ['SegA', 'SegB']) + @pytest.mark.parametrize("newname", ["SegA", "SegB"]) def test_add_segment(self, universe, newname): newseg = universe.add_Segment(segid=newname) @@ -643,32 +694,34 @@ def test_add_segment(self, universe, newname): assert rg.atoms[0].segid == newname def test_issue3437(self, universe): - newseg = universe.add_Segment(segid='B') + newseg = universe.add_Segment(segid="B") ag = universe.residues[0].atoms ag.residues.segments = newseg - assert 'B' in universe.segments.segids + assert "B" in universe.segments.segids - ag2 = universe.select_atoms('segid B') + ag2 = universe.select_atoms("segid B") assert len(ag2) == 5 assert (ag2.ix == ag.ix).all() -class Testcenter_of_charge(): +class Testcenter_of_charge: - compounds = ['group', 'segments', 'residues', 'molecules', 'fragments'] + compounds = ["group", "segments", "residues", "molecules", "fragments"] @pytest.fixture def u(self): """A universe containing two dimers with a finite dipole moment.""" - universe = mda.Universe.empty(n_atoms=4, - n_residues=2, - n_segments=2, - atom_resindex=[0, 0, 1, 1], - residue_segindex=[0, 1]) + universe = mda.Universe.empty( + n_atoms=4, + n_residues=2, + n_segments=2, + atom_resindex=[0, 0, 1, 1], + residue_segindex=[0, 1], + ) universe.add_TopologyAttr("masses", [1, 0, 0, 1]) universe.add_TopologyAttr("charges", [1, -1, -1, 1]) @@ -678,16 +731,16 @@ def u(self): positions = np.array([[0, 0, 0], [0, 1, 0], [2, 1, 0], [2, 2, 0]]) - universe.trajectory = get_reader_for(positions)(positions, - order='fac', - n_atoms=4) + universe.trajectory = get_reader_for(positions)( + positions, order="fac", n_atoms=4 + ) for ts in universe.trajectory: ts.dimensions = np.array([1, 2, 3, 90, 90, 90]) return universe - @pytest.mark.parametrize('compound', compounds) + @pytest.mark.parametrize("compound", compounds) def test_coc(self, u, compound): coc = u.atoms.center_of_charge(compound=compound) if compound == "group": @@ -696,12 +749,12 @@ def test_coc(self, u, compound): coc_ref = [[0, 0.5, 0], [2, 1.5, 0]] assert_equal(coc, coc_ref) - @pytest.mark.parametrize('compound', compounds) + @pytest.mark.parametrize("compound", compounds) def test_coc_wrap(self, u, compound): coc = u.atoms[:2].center_of_charge(compound=compound, wrap=True) assert_equal(coc.flatten(), [0, 0.5, 0]) - @pytest.mark.parametrize('compound', compounds) + @pytest.mark.parametrize("compound", compounds) def test_coc_unwrap(self, u, compound): u.atoms.wrap coc = u.atoms[:2].center_of_charge(compound=compound, unwrap=True) diff --git a/testsuite/MDAnalysisTests/core/test_topologyobjects.py b/testsuite/MDAnalysisTests/core/test_topologyobjects.py index c4bc05c6a1c..196b91364df 100644 --- a/testsuite/MDAnalysisTests/core/test_topologyobjects.py +++ b/testsuite/MDAnalysisTests/core/test_topologyobjects.py @@ -31,19 +31,25 @@ from MDAnalysis.lib.distances import calc_bonds, calc_angles, calc_dihedrals from MDAnalysisTests.datafiles import LAMMPSdata_many_bonds from MDAnalysis.core.topologyobjects import ( - TopologyGroup, TopologyObject, TopologyDict, + TopologyGroup, + TopologyObject, + TopologyDict, # TODO: the following items are not used - Bond, Angle, Dihedral, ImproperDihedral, + Bond, + Angle, + Dihedral, + ImproperDihedral, ) from MDAnalysisTests.datafiles import PSF, DCD, TRZ_psf, TRZ -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def PSFDCD(): return mda.Universe(PSF, DCD) + class TestTopologyObjects(object): """Test the base TopologyObject funtionality @@ -54,6 +60,7 @@ class TestTopologyObjects(object): iter len """ + precision = 3 # see Issue #271 and #1556 @staticmethod @@ -82,9 +89,9 @@ def b(PSFDCD): return PSFDCD.atoms[12].bonds[0] def test_repr(self, TO1): - assert_equal(repr(TO1), '') + assert_equal(repr(TO1), "") - def test_eq(self, a1 ,TO1, TO2, PSFDCD): + def test_eq(self, a1, TO1, TO2, PSFDCD): TO1_b = TopologyObject(a1.indices, PSFDCD) assert_equal(TO1 == TO1_b, True) @@ -145,7 +152,7 @@ def test_bondlength(self, b): assert_almost_equal(b.length(), 1.7661301556941993, self.precision) def test_bondrepr(self, b): - assert_equal(repr(b), '') + assert_equal(repr(b), "") # Angle class checks def test_angle(self, PSFDCD): @@ -157,16 +164,13 @@ def test_angle(self, PSFDCD): def test_angle_repr(self, PSFDCD): angle = PSFDCD.atoms[[30, 10, 20]].angle - assert_equal(repr(angle), '') + assert_equal(repr(angle), "") def test_angle_180(self): # we edit the coordinates, so make our own universe u = mda.Universe(PSF, DCD) angle = u.atoms[210].angles[0] - coords = np.array([[1, 1, 1], - [2, 1, 1], - [3, 1, 1]], - dtype=np.float32) + coords = np.array([[1, 1, 1], [2, 1, 1], [3, 1, 1]], dtype=np.float32) angle.atoms.positions = coords @@ -182,8 +186,10 @@ def test_dihedral(self, PSFDCD): def test_dihedral_repr(self, PSFDCD): dihedral = PSFDCD.atoms[[4, 7, 8, 1]].dihedral - assert_equal(repr(dihedral), - '') + assert_equal( + repr(dihedral), + "", + ) # Improper_Dihedral class check def test_improper(self, PSFDCD): @@ -197,12 +203,13 @@ def test_improper_repr(self, PSFDCD): assert_equal( repr(imp), - '') + "", + ) def test_ureybradley_repr(self, PSFDCD): ub = PSFDCD.atoms[[30, 10]].ureybradley - assert_equal(repr(ub), '') + assert_equal(repr(ub), "") def test_ureybradley_repr_VE(self, PSFDCD): with pytest.raises(ValueError): @@ -214,19 +221,23 @@ def test_ureybradley_partner(self, PSFDCD): assert ub.partner(PSFDCD.atoms[10]) == PSFDCD.atoms[30] def test_ureybradley_distance(self, b): - assert_almost_equal(b.atoms.ureybradley.distance(), b.length(), self.precision) + assert_almost_equal( + b.atoms.ureybradley.distance(), b.length(), self.precision + ) def test_cmap_repr(self, PSFDCD): cmap = PSFDCD.atoms[[4, 7, 8, 1, 2]].cmap assert_equal( repr(cmap), - '') - + "", + ) + def test_cmap_repr_VE(self, PSFDCD): with pytest.raises(ValueError): cmap = PSFDCD.atoms[[30, 10, 2]].cmap + class TestTopologyGroup(object): """Tests TopologyDict and TopologyGroup classes with psf input""" @@ -271,7 +282,7 @@ def test_td_iter(self, b_td): def test_td_keyerror(self, b_td): with pytest.raises(KeyError): - b_td[('something', 'stupid')] + b_td[("something", "stupid")] def test_td_universe(self, b_td, PSFDCD): assert b_td.universe is PSFDCD @@ -282,15 +293,15 @@ def test_bonds_types(self, PSFDCD, res1): assert len(res1.atoms.bonds.types()) == 12 def test_bonds_contains(self, b_td): - assert ('57', '2') in b_td + assert ("57", "2") in b_td def test_bond_uniqueness(self, PSFDCD): bondtypes = PSFDCD.atoms.bonds.types() # check that a key doesn't appear in reversed format in keylist # have to exclude case of b[::-1] == b as this is false positive - assert not any([b[::-1] in bondtypes - for b in bondtypes if b[::-1] != b]) - + assert not any( + [b[::-1] in bondtypes for b in bondtypes if b[::-1] != b] + ) def test_bond_reversal(self, PSFDCD, b_td): bondtypes = PSFDCD.atoms.bonds.types() @@ -315,12 +326,11 @@ def test_angles_types(self, PSFDCD): assert len(PSFDCD.atoms.angles.types()) == 130 def test_angles_contains(self, a_td): - assert ('23', '73', '1') in a_td + assert ("23", "73", "1") in a_td def test_angles_uniqueness(self, a_td): bondtypes = a_td.keys() - assert not any(b[::-1] in bondtypes - for b in bondtypes if b[::-1] != b) + assert not any(b[::-1] in bondtypes for b in bondtypes if b[::-1] != b) def test_angles_reversal(self, a_td): bondtypes = list(a_td.keys()) @@ -336,7 +346,7 @@ def test_dihedrals_types(self, PSFDCD): assert len(PSFDCD.atoms.dihedrals.types()) == 220 def test_dihedrals_contains(self, t_td): - assert ('30', '29', '20', '70') in t_td + assert ("30", "29", "20", "70") in t_td def test_dihedrals_uniqueness(self, t_td): bondtypes = t_td.keys() @@ -353,26 +363,26 @@ def test_dihedrals_reversal(self, t_td): def test_bad_creation(self): """Test making a TopologyDict out of nonsense""" - inputlist = ['a', 'b', 'c'] + inputlist = ["a", "b", "c"] with pytest.raises(TypeError): TopologyDict(inputlist) def test_bad_creation_TG(self): """Test making a TopologyGroup out of nonsense""" - inputlist = ['a', 'b', 'c'] + inputlist = ["a", "b", "c"] with pytest.raises(TypeError): TopologyGroup(inputlist) def test_tg_creation_bad_btype(self, PSFDCD): vals = np.array([[0, 10], [5, 15]]) with pytest.raises(ValueError): - TopologyGroup(vals, PSFDCD, btype='apple') + TopologyGroup(vals, PSFDCD, btype="apple") def test_bond_tg_creation_notype(self, PSFDCD): vals = np.array([[0, 10], [5, 15]]) tg = TopologyGroup(vals, PSFDCD) - assert tg.btype == 'bond' + assert tg.btype == "bond" assert_equal(tg[0].indices, (0, 10)) assert_equal(tg[1].indices, (5, 15)) @@ -380,7 +390,7 @@ def test_angle_tg_creation_notype(self, PSFDCD): vals = np.array([[0, 5, 10], [5, 10, 15]]) tg = TopologyGroup(vals, PSFDCD) - assert tg.btype == 'angle' + assert tg.btype == "angle" assert_equal(tg[0].indices, (0, 5, 10)) assert_equal(tg[1].indices, (5, 10, 15)) @@ -389,7 +399,7 @@ def test_dihedral_tg_creation_notype(self, PSFDCD): tg = TopologyGroup(vals, PSFDCD) - assert tg.btype == 'dihedral' + assert tg.btype == "dihedral" assert_equal(tg[0].indices, (0, 2, 4, 6)) assert_equal(tg[1].indices, (5, 7, 9, 11)) @@ -410,27 +420,28 @@ def test_TG_equality(self, PSFDCD): * check they're equal * change one very slightly and see if they notice """ - tg = PSFDCD.atoms.bonds.selectBonds(('23', '3')) - tg2 = PSFDCD.atoms.bonds.selectBonds(('23', '3')) + tg = PSFDCD.atoms.bonds.selectBonds(("23", "3")) + tg2 = PSFDCD.atoms.bonds.selectBonds(("23", "3")) assert tg == tg2 - tg3 = PSFDCD.atoms.bonds.selectBonds(('81', '10')) + tg3 = PSFDCD.atoms.bonds.selectBonds(("81", "10")) assert not (tg == tg3) assert tg != tg3 def test_create_TopologyGroup(self, res1, PSFDCD): - res1_tg = res1.atoms.bonds.select_bonds(('23', '3')) # make a tg + res1_tg = res1.atoms.bonds.select_bonds(("23", "3")) # make a tg assert len(res1_tg) == 4 # check size of tg testbond = PSFDCD.atoms[7].bonds[0] assert testbond in res1_tg # check a known bond is present - res1_tg2 = res1.atoms.bonds.select_bonds(('23', '3')) + res1_tg2 = res1.atoms.bonds.select_bonds(("23", "3")) assert res1_tg == res1_tg2 - @pytest.mark.parametrize('attr', - ['bonds', 'angles', 'dihedrals', 'impropers']) + @pytest.mark.parametrize( + "attr", ["bonds", "angles", "dihedrals", "impropers"] + ) def test_TG_loose_intersection(self, PSFDCD, attr): """Pull bonds from a TG which are at least partially in an AG""" ag = PSFDCD.atoms[10:60] @@ -464,28 +475,30 @@ def manual(topg, atomg): # bonds assert check_strict_intersection(PSFDCD.atoms.bonds, testinput) - assert (manual(PSFDCD.atoms.bonds, testinput) == - set(PSFDCD.atoms.bonds.atomgroup_intersection( - testinput, strict=True))) + assert manual(PSFDCD.atoms.bonds, testinput) == set( + PSFDCD.atoms.bonds.atomgroup_intersection(testinput, strict=True) + ) # angles assert check_strict_intersection(PSFDCD.atoms.angles, testinput) - assert (manual(PSFDCD.atoms.angles, testinput) == - set(PSFDCD.atoms.angles.atomgroup_intersection( - testinput, strict=True))) + assert manual(PSFDCD.atoms.angles, testinput) == set( + PSFDCD.atoms.angles.atomgroup_intersection(testinput, strict=True) + ) # dihedrals assert check_strict_intersection(PSFDCD.atoms.dihedrals, testinput) - assert (manual(PSFDCD.atoms.dihedrals, testinput) == - set(PSFDCD.atoms.dihedrals.atomgroup_intersection( - testinput, strict=True))) + assert manual(PSFDCD.atoms.dihedrals, testinput) == set( + PSFDCD.atoms.dihedrals.atomgroup_intersection( + testinput, strict=True + ) + ) def test_add_TopologyGroups(self, res1, res2, PSFDCD): - res1_tg = res1.atoms.bonds.selectBonds(('23', '3')) - res2_tg = res2.atoms.bonds.selectBonds(('23', '3')) + res1_tg = res1.atoms.bonds.selectBonds(("23", "3")) + res2_tg = res2.atoms.bonds.selectBonds(("23", "3")) combined_tg = res1_tg + res2_tg # add tgs together assert len(combined_tg) == 10 - big_tg = PSFDCD.atoms.bonds.selectBonds(('23', '3')) + big_tg = PSFDCD.atoms.bonds.selectBonds(("23", "3")) big_tg += combined_tg # try and add some already included bonds assert len(big_tg) == 494 # check len doesn't change @@ -501,7 +514,7 @@ def test_add_TO_to_empty_TG(self, PSFDCD): to = PSFDCD.bonds[5] tg3 = tg1 + to - assert_equal(tg3.indices, to.indices[None, :]) + assert_equal(tg3.indices, to.indices[None, :]) def test_add_TG_to_empty_TG(self, PSFDCD): tg1 = PSFDCD.bonds[:0] # empty @@ -548,8 +561,7 @@ def test_TG_getitem_fancy(self, PSFDCD): tg = PSFDCD.atoms.bonds[:10] tg2 = tg[[1, 4, 5]] - manual = TopologyGroup(tg.indices[[1, 4, 5]], - tg.universe, tg.btype) + manual = TopologyGroup(tg.indices[[1, 4, 5]], tg.universe, tg.btype) assert list(tg2) == list(manual) @@ -576,7 +588,7 @@ def test_atom1(self, PSFDCD): a1 = tg.atom1 assert len(tg) == len(a1) - for (atom, bond) in zip(a1, tg): + for atom, bond in zip(a1, tg): assert atom == bond[0] def test_atom2(self, PSFDCD): @@ -584,7 +596,7 @@ def test_atom2(self, PSFDCD): a2 = tg.atom2 assert len(tg) == len(a2) - for (atom, bond) in zip(a2, tg): + for atom, bond in zip(a2, tg): assert atom == bond[1] def test_atom3_IE(self, PSFDCD): @@ -596,7 +608,7 @@ def test_atom3(self, PSFDCD): tg = PSFDCD.angles[:5] a3 = tg.atom3 assert len(tg) == len(a3) - for (atom, bond) in zip(a3, tg): + for atom, bond in zip(a3, tg): assert atom == bond[2] def test_atom4_IE(self, PSFDCD): @@ -609,7 +621,7 @@ def test_atom4(self, PSFDCD): a4 = tg.atom4 assert len(tg) == len(a4) - for (atom, bond) in zip(a4, tg): + for atom, bond in zip(a4, tg): assert atom == bond[3] @@ -619,6 +631,7 @@ class TestTopologyGroup_Cython(object): - work (return proper values) - catch errors """ + @staticmethod @pytest.fixture def bgroup(PSFDCD): @@ -646,20 +659,30 @@ def test_wrong_type_bonds(self, agroup, dgroup, igroup): tg.bonds() def test_right_type_bonds(self, bgroup, PSFDCD): - assert_equal(bgroup.bonds(), - calc_bonds(bgroup.atom1.positions, - bgroup.atom2.positions)) - assert_equal(bgroup.bonds(pbc=True), - calc_bonds(bgroup.atom1.positions, - bgroup.atom2.positions, - box=PSFDCD.dimensions)) - assert_equal(bgroup.values(), - calc_bonds(bgroup.atom1.positions, - bgroup.atom2.positions)) - assert_equal(bgroup.values(pbc=True), - calc_bonds(bgroup.atom1.positions, - bgroup.atom2.positions, - box=PSFDCD.dimensions)) + assert_equal( + bgroup.bonds(), + calc_bonds(bgroup.atom1.positions, bgroup.atom2.positions), + ) + assert_equal( + bgroup.bonds(pbc=True), + calc_bonds( + bgroup.atom1.positions, + bgroup.atom2.positions, + box=PSFDCD.dimensions, + ), + ) + assert_equal( + bgroup.values(), + calc_bonds(bgroup.atom1.positions, bgroup.atom2.positions), + ) + assert_equal( + bgroup.values(pbc=True), + calc_bonds( + bgroup.atom1.positions, + bgroup.atom2.positions, + box=PSFDCD.dimensions, + ), + ) # angles def test_wrong_type_angles(self, bgroup, dgroup, igroup): @@ -668,24 +691,40 @@ def test_wrong_type_angles(self, bgroup, dgroup, igroup): tg.angles() def test_right_type_angles(self, agroup, PSFDCD): - assert_equal(agroup.angles(), - calc_angles(agroup.atom1.positions, - agroup.atom2.positions, - agroup.atom3.positions)) - assert_equal(agroup.angles(pbc=True), - calc_angles(agroup.atom1.positions, - agroup.atom2.positions, - agroup.atom3.positions, - box=PSFDCD.dimensions)) - assert_equal(agroup.values(), - calc_angles(agroup.atom1.positions, - agroup.atom2.positions, - agroup.atom3.positions)) - assert_equal(agroup.values(pbc=True), - calc_angles(agroup.atom1.positions, - agroup.atom2.positions, - agroup.atom3.positions, - box=PSFDCD.dimensions)) + assert_equal( + agroup.angles(), + calc_angles( + agroup.atom1.positions, + agroup.atom2.positions, + agroup.atom3.positions, + ), + ) + assert_equal( + agroup.angles(pbc=True), + calc_angles( + agroup.atom1.positions, + agroup.atom2.positions, + agroup.atom3.positions, + box=PSFDCD.dimensions, + ), + ) + assert_equal( + agroup.values(), + calc_angles( + agroup.atom1.positions, + agroup.atom2.positions, + agroup.atom3.positions, + ), + ) + assert_equal( + agroup.values(pbc=True), + calc_angles( + agroup.atom1.positions, + agroup.atom2.positions, + agroup.atom3.positions, + box=PSFDCD.dimensions, + ), + ) # dihedrals & impropers def test_wrong_type_dihedrals(self, bgroup, agroup): @@ -694,52 +733,84 @@ def test_wrong_type_dihedrals(self, bgroup, agroup): tg.dihedrals() def test_right_type_dihedrals(self, dgroup, PSFDCD): - assert_equal(dgroup.dihedrals(), - calc_dihedrals(dgroup.atom1.positions, - dgroup.atom2.positions, - dgroup.atom3.positions, - dgroup.atom4.positions)) - assert_equal(dgroup.dihedrals(pbc=True), - calc_dihedrals(dgroup.atom1.positions, - dgroup.atom2.positions, - dgroup.atom3.positions, - dgroup.atom4.positions, - box=PSFDCD.dimensions)) - assert_equal(dgroup.values(), - calc_dihedrals(dgroup.atom1.positions, - dgroup.atom2.positions, - dgroup.atom3.positions, - dgroup.atom4.positions)) - assert_equal(dgroup.values(pbc=True), - calc_dihedrals(dgroup.atom1.positions, - dgroup.atom2.positions, - dgroup.atom3.positions, - dgroup.atom4.positions, - box=PSFDCD.dimensions)) + assert_equal( + dgroup.dihedrals(), + calc_dihedrals( + dgroup.atom1.positions, + dgroup.atom2.positions, + dgroup.atom3.positions, + dgroup.atom4.positions, + ), + ) + assert_equal( + dgroup.dihedrals(pbc=True), + calc_dihedrals( + dgroup.atom1.positions, + dgroup.atom2.positions, + dgroup.atom3.positions, + dgroup.atom4.positions, + box=PSFDCD.dimensions, + ), + ) + assert_equal( + dgroup.values(), + calc_dihedrals( + dgroup.atom1.positions, + dgroup.atom2.positions, + dgroup.atom3.positions, + dgroup.atom4.positions, + ), + ) + assert_equal( + dgroup.values(pbc=True), + calc_dihedrals( + dgroup.atom1.positions, + dgroup.atom2.positions, + dgroup.atom3.positions, + dgroup.atom4.positions, + box=PSFDCD.dimensions, + ), + ) def test_right_type_impropers(self, igroup, PSFDCD): - assert_equal(igroup.dihedrals(), - calc_dihedrals(igroup.atom1.positions, - igroup.atom2.positions, - igroup.atom3.positions, - igroup.atom4.positions)) - assert_equal(igroup.dihedrals(pbc=True), - calc_dihedrals(igroup.atom1.positions, - igroup.atom2.positions, - igroup.atom3.positions, - igroup.atom4.positions, - box=PSFDCD.dimensions)) - assert_equal(igroup.values(), - calc_dihedrals(igroup.atom1.positions, - igroup.atom2.positions, - igroup.atom3.positions, - igroup.atom4.positions)) - assert_equal(igroup.values(pbc=True), - calc_dihedrals(igroup.atom1.positions, - igroup.atom2.positions, - igroup.atom3.positions, - igroup.atom4.positions, - box=PSFDCD.dimensions)) + assert_equal( + igroup.dihedrals(), + calc_dihedrals( + igroup.atom1.positions, + igroup.atom2.positions, + igroup.atom3.positions, + igroup.atom4.positions, + ), + ) + assert_equal( + igroup.dihedrals(pbc=True), + calc_dihedrals( + igroup.atom1.positions, + igroup.atom2.positions, + igroup.atom3.positions, + igroup.atom4.positions, + box=PSFDCD.dimensions, + ), + ) + assert_equal( + igroup.values(), + calc_dihedrals( + igroup.atom1.positions, + igroup.atom2.positions, + igroup.atom3.positions, + igroup.atom4.positions, + ), + ) + assert_equal( + igroup.values(pbc=True), + calc_dihedrals( + igroup.atom1.positions, + igroup.atom2.positions, + igroup.atom3.positions, + igroup.atom4.positions, + box=PSFDCD.dimensions, + ), + ) def test_bond_length_pbc(): @@ -752,16 +823,18 @@ def test_bond_length_pbc(): assert_almost_equal(ref, u.bonds[0].length(pbc=True), decimal=6) + def test_cross_universe_eq(): u1 = mda.Universe(PSF) u2 = mda.Universe(PSF) assert not (u1.bonds[0] == u2.bonds[0]) + def test_zero_size_TG_indices_bonds(): u = mda.Universe.empty(10) - u.add_TopologyAttr('bonds', values=[(1, 2), (2, 3)]) + u.add_TopologyAttr("bonds", values=[(1, 2), (2, 3)]) ag = u.atoms[[0]] @@ -770,10 +843,11 @@ def test_zero_size_TG_indices_bonds(): assert idx.shape == (0, 2) assert idx.dtype == np.int32 + def test_zero_size_TG_indices_angles(): u = mda.Universe.empty(10) - u.add_TopologyAttr('angles', values=[(1, 2, 3), (2, 3, 4)]) + u.add_TopologyAttr("angles", values=[(1, 2, 3), (2, 3, 4)]) ag = u.atoms[[0]] diff --git a/testsuite/MDAnalysisTests/core/test_universe.py b/testsuite/MDAnalysisTests/core/test_universe.py index d17b9c707a3..99eaec38a2c 100644 --- a/testsuite/MDAnalysisTests/core/test_universe.py +++ b/testsuite/MDAnalysisTests/core/test_universe.py @@ -40,15 +40,21 @@ from MDAnalysisTests import make_Universe from MDAnalysisTests.datafiles import ( - PSF, DCD, + PSF, + DCD, PSF_BAD, PDB_small, PDB_chainidrepeat, - GRO, TRR, - two_water_gro, two_water_gro_nonames, - TRZ, TRZ_psf, - PDB, MMTF, CONECT, - PDB_conect + GRO, + TRR, + two_water_gro, + two_water_gro_nonames, + TRZ, + TRZ_psf, + PDB, + MMTF, + CONECT, + PDB_conect, ) import MDAnalysis as mda @@ -65,6 +71,7 @@ class IOErrorParser(TopologyReaderBase): def parse(self, **kwargs): raise IOError("Useful information") + # This string is not in the `TestUniverseCreation` class or its method because of problems # with whitespace. Extra indentations make the string unreadable. CHOL_GRO = """\ @@ -81,6 +88,7 @@ def parse(self, **kwargs): 10 10 10 """ + class TestUniverseCreation(object): # tests concerning Universe creation and errors encountered def test_load(self): @@ -89,13 +97,25 @@ def test_load(self): assert_equal(len(u.atoms), 3341, "Loading universe failed somehow") def test_load_topology_stringio(self): - u = mda.Universe(StringIO(CHOL_GRO), format='GRO') - assert_equal(len(u.atoms), 8, "Loading universe from StringIO failed somehow") - assert_equal(u.trajectory.ts.positions[0], np.array([65.580002, 29.360001, 40.050003], dtype=np.float32)) + u = mda.Universe(StringIO(CHOL_GRO), format="GRO") + assert_equal( + len(u.atoms), 8, "Loading universe from StringIO failed somehow" + ) + assert_equal( + u.trajectory.ts.positions[0], + np.array([65.580002, 29.360001, 40.050003], dtype=np.float32), + ) def test_load_trajectory_stringio(self): - u = mda.Universe(StringIO(CHOL_GRO), StringIO(CHOL_GRO), format='GRO', topology_format='GRO') - assert_equal(len(u.atoms), 8, "Loading universe from StringIO failed somehow") + u = mda.Universe( + StringIO(CHOL_GRO), + StringIO(CHOL_GRO), + format="GRO", + topology_format="GRO", + ) + assert_equal( + len(u.atoms), 8, "Loading universe from StringIO failed somehow" + ) def test_make_universe_stringio_no_format(self): # Loading from StringIO without format arg should raise TypeError @@ -106,92 +126,104 @@ def test_Universe_no_trajectory_AE(self): # querying trajectory without a trajectory loaded (only topology) u = make_Universe() with pytest.raises(AttributeError): - getattr(u, 'trajectory') + getattr(u, "trajectory") def test_Universe_topology_unrecognizedformat_VE(self): with pytest.raises(ValueError): - mda.Universe('some.file.without.parser_or_coordinate_extension') + mda.Universe("some.file.without.parser_or_coordinate_extension") def test_Universe_topology_unrecognizedformat_VE_msg(self): try: - mda.Universe('some.file.without.parser_or_coordinate_extension') + mda.Universe("some.file.without.parser_or_coordinate_extension") except ValueError as e: - assert 'isn\'t a valid topology format' in e.args[0] + assert "isn't a valid topology format" in e.args[0] else: raise AssertionError def test_Universe_topology_IE(self): with pytest.raises(IOError): - mda.Universe('thisfile', topology_format = IOErrorParser) + mda.Universe("thisfile", topology_format=IOErrorParser) def test_Universe_topology_IE_msg(self): # should get the original error, as well as Universe error try: - mda.Universe('thisfile', topology_format=IOErrorParser) + mda.Universe("thisfile", topology_format=IOErrorParser) except IOError as e: - assert 'Failed to load from the topology file' in e.args[0] - assert 'Useful information' in e.args[0] + assert "Failed to load from the topology file" in e.args[0] + assert "Useful information" in e.args[0] else: raise AssertionError def test_Universe_filename_IE_msg(self): # check for non existent file try: - mda.Universe('thisfile.xml') + mda.Universe("thisfile.xml") except IOError as e: - assert_equal('No such file or directory', e.strerror) + assert_equal("No such file or directory", e.strerror) else: raise AssertionError def test_Universe_invalidfile_IE_msg(self, tmpdir): # check for invalid file (something with the wrong content) with tmpdir.as_cwd(): - with open('invalid.file.tpr', 'w') as temp_file: - temp_file.write('plop') + with open("invalid.file.tpr", "w") as temp_file: + temp_file.write("plop") try: - mda.Universe('invalid.file.tpr') + mda.Universe("invalid.file.tpr") except IOError as e: - assert 'file or cannot be recognized' in e.args[0] + assert "file or cannot be recognized" in e.args[0] else: raise AssertionError - @pytest.mark.skipif(get_userid() == 0, - reason="cannot permisssionerror as root") + @pytest.mark.skipif( + get_userid() == 0, reason="cannot permisssionerror as root" + ) def test_Universe_invalidpermissionfile_IE_msg(self, tmpdir): # check for file with invalid permissions (eg. no read access) with tmpdir.as_cwd(): - temp_file = 'permission.denied.tpr' - with open(temp_file, 'w'): + temp_file = "permission.denied.tpr" + with open(temp_file, "w"): pass - if os.name == 'nt': - subprocess.call("icacls {filename} /deny Users:RX".format(filename=temp_file), - shell=True) + if os.name == "nt": + subprocess.call( + "icacls {filename} /deny Users:RX".format( + filename=temp_file + ), + shell=True, + ) else: os.chmod(temp_file, 0o200) # Issue #3221 match by PermissionError and error number instead with pytest.raises(PermissionError, match=f"Errno {errno.EACCES}"): - mda.Universe('permission.denied.tpr') + mda.Universe("permission.denied.tpr") def test_load_new_VE(self): u = mda.Universe.empty(0) with pytest.raises(TypeError): - u.load_new('thisfile', format = 'soup') + u.load_new("thisfile", format="soup") def test_load_new_memory_reader_success(self): u = mda.Universe(GRO) - prot = u.select_atoms('protein') + prot = u.select_atoms("protein") u2 = mda.Merge(prot) - assert u2.load_new( [ prot.positions ], format=mda.coordinates.memory.MemoryReader) is u2 + assert ( + u2.load_new( + [prot.positions], format=mda.coordinates.memory.MemoryReader + ) + is u2 + ) def test_load_new_memory_reader_fails(self): def load(): u = mda.Universe(GRO) - prot = u.select_atoms('protein') + prot = u.select_atoms("protein") u2 = mda.Merge(prot) - u2.load_new( [[ prot.positions ]], format=mda.coordinates.memory.MemoryReader) + u2.load_new( + [[prot.positions]], format=mda.coordinates.memory.MemoryReader + ) with pytest.raises(TypeError): load() @@ -200,13 +232,12 @@ def test_universe_kwargs(self): u = mda.Universe(PSF, PDB_small, fake_kwarg=True) assert_equal(len(u.atoms), 3341, "Loading universe failed somehow") - assert u.kwargs['fake_kwarg'] + assert u.kwargs["fake_kwarg"] # initialize new universe from pieces of existing one - u2 = mda.Universe(u.filename, u.trajectory.filename, - **u.kwargs) + u2 = mda.Universe(u.filename, u.trajectory.filename, **u.kwargs) - assert u2.kwargs['fake_kwarg'] + assert u2.kwargs["fake_kwarg"] assert_equal(u.kwargs, u2.kwargs) def test_universe_topology_class_with_coords(self): @@ -229,35 +260,37 @@ def setup_class(self): def test_default(self): smi = "CN1C=NC2=C1C(=O)N(C(=O)N2C)C" - u = mda.Universe.from_smiles(smi, format='RDKIT') + u = mda.Universe.from_smiles(smi, format="RDKIT") assert u.atoms.n_atoms == 24 assert len(u.bonds.indices) == 25 def test_from_bad_smiles(self): with pytest.raises(SyntaxError) as e: - u = mda.Universe.from_smiles("J", format='RDKIT') + u = mda.Universe.from_smiles("J", format="RDKIT") assert "Error while parsing SMILES" in str(e.value) def test_no_Hs(self): smi = "CN1C=NC2=C1C(=O)N(C(=O)N2C)C" - u = mda.Universe.from_smiles(smi, addHs=False, - generate_coordinates=False, format='RDKIT') + u = mda.Universe.from_smiles( + smi, addHs=False, generate_coordinates=False, format="RDKIT" + ) assert u.atoms.n_atoms == 14 assert len(u.bonds.indices) == 15 def test_gencoords_without_Hs_error(self): with pytest.raises(ValueError) as e: - u = mda.Universe.from_smiles("CCO", addHs=False, - generate_coordinates=True, format='RDKIT') - assert "requires adding hydrogens" in str (e.value) + u = mda.Universe.from_smiles( + "CCO", addHs=False, generate_coordinates=True, format="RDKIT" + ) + assert "requires adding hydrogens" in str(e.value) def test_generate_coordinates_numConfs(self): with pytest.raises(SyntaxError) as e: - u = mda.Universe.from_smiles("CCO", numConfs=0, format='RDKIT') - assert "non-zero positive integer" in str (e.value) + u = mda.Universe.from_smiles("CCO", numConfs=0, format="RDKIT") + assert "non-zero positive integer" in str(e.value) with pytest.raises(SyntaxError) as e: - u = mda.Universe.from_smiles("CCO", numConfs=2.1, format='RDKIT') - assert "non-zero positive integer" in str (e.value) + u = mda.Universe.from_smiles("CCO", numConfs=2.1, format="RDKIT") + assert "non-zero positive integer" in str(e.value) def test_rdkit_kwargs(self): # test for bad kwarg: @@ -269,17 +302,18 @@ def test_rdkit_kwargs(self): except Exception as e: assert "did not match C++ signature" in str(e) else: - raise AssertionError("RDKit should have raised an ArgumentError " - "from Boost") + raise AssertionError( + "RDKit should have raised an ArgumentError " "from Boost" + ) # good kwarg u1 = mda.Universe.from_smiles("C", rdkit_kwargs=dict(randomSeed=42)) u2 = mda.Universe.from_smiles("C", rdkit_kwargs=dict(randomSeed=51)) with pytest.raises(AssertionError) as e: - assert_equal(u1.trajectory.coordinate_array, - u2.trajectory.coordinate_array) + assert_equal( + u1.trajectory.coordinate_array, u2.trajectory.coordinate_array + ) assert "Mismatched elements: 15 / 15 (100%)" in str(e.value) - def test_coordinates(self): # We manually create the molecule to compare coordinates # coordinate generation is pseudo random across different machines @@ -287,14 +321,16 @@ def test_coordinates(self): # coordinates against. See PR #4640 from rdkit import Chem from rdkit.Chem import AllChem - mol = Chem.MolFromSmiles('C', sanitize=True) + + mol = Chem.MolFromSmiles("C", sanitize=True) mol = Chem.AddHs(mol) AllChem.EmbedMultipleConfs(mol, numConfs=2, randomSeed=42) expected = [c.GetPositions() for c in mol.GetConformers()] # now the mda way - u = mda.Universe.from_smiles("C", numConfs=2, - rdkit_kwargs=dict(randomSeed=42)) + u = mda.Universe.from_smiles( + "C", numConfs=2, rdkit_kwargs=dict(randomSeed=42) + ) assert u.trajectory.n_frames == 2 assert_allclose(u.trajectory.coordinate_array, expected, rtol=1e-7) @@ -328,7 +364,7 @@ def test_load_new_TypeError(self): u = mda.Universe(PSF, DCD) def bad_load(uni): - return uni.load_new('filename.notarealextension') + return uni.load_new("filename.notarealextension") with pytest.raises(TypeError): bad_load(u) @@ -360,8 +396,7 @@ def test_pickle(self): new_u = pickle.loads(s) assert_equal(u.atoms.names, new_u.atoms.names) - - @pytest.mark.parametrize('dtype', (int, np.float32, np.float64)) + @pytest.mark.parametrize("dtype", (int, np.float32, np.float64)) def test_set_dimensions(self, dtype): u = mda.Universe(PSF, DCD) box = np.array([10, 11, 12, 90, 90, 90], dtype=dtype) @@ -371,19 +406,19 @@ def test_set_dimensions(self, dtype): class TestTransformations(object): - """Tests the transformations keyword - """ + """Tests the transformations keyword""" + def test_callable(self): - u = mda.Universe(PSF,DCD, transformations=translate([10,10,10])) - uref = mda.Universe(PSF,DCD) - ref = translate([10,10,10])(uref.trajectory.ts) + u = mda.Universe(PSF, DCD, transformations=translate([10, 10, 10])) + uref = mda.Universe(PSF, DCD) + ref = translate([10, 10, 10])(uref.trajectory.ts) assert_almost_equal(u.trajectory.ts.positions, ref, decimal=6) def test_list(self): - workflow = [translate([10,10,0]), translate([0,0,10])] - u = mda.Universe(PSF,DCD, transformations=workflow) - uref = mda.Universe(PSF,DCD) - ref = translate([10,10,10])(uref.trajectory.ts) + workflow = [translate([10, 10, 0]), translate([0, 0, 10])] + u = mda.Universe(PSF, DCD, transformations=workflow) + uref = mda.Universe(PSF, DCD) + ref = translate([10, 10, 10])(uref.trajectory.ts) assert_almost_equal(u.trajectory.ts.positions, ref, decimal=6) @@ -395,35 +430,35 @@ def test_automatic_type_and_mass_guessing(self): def test_no_type_and_mass_guessing(self): u = mda.Universe(PDB_small, to_guess=()) - assert not hasattr(u.atoms, 'masses') - assert not hasattr(u.atoms, 'types') + assert not hasattr(u.atoms, "masses") + assert not hasattr(u.atoms, "types") def test_invalid_context(self): u = mda.Universe(PDB_small) with pytest.raises(KeyError): - u.guess_TopologyAttrs(context='trash', to_guess=['masses']) + u.guess_TopologyAttrs(context="trash", to_guess=["masses"]) def test_invalid_attributes(self): u = mda.Universe(PDB_small) with pytest.raises(ValueError): - u.guess_TopologyAttrs(to_guess=['trash']) + u.guess_TopologyAttrs(to_guess=["trash"]) def test_guess_masses_before_types(self): - u = mda.Universe(PDB_small, to_guess=('masses', 'types')) + u = mda.Universe(PDB_small, to_guess=("masses", "types")) assert_equal(len(u.atoms.masses), 3341) assert_equal(len(u.atoms.types), 3341) def test_guessing_read_attributes(self): u = mda.Universe(PSF) old_types = u.atoms.types - u.guess_TopologyAttrs(force_guess=['types']) + u.guess_TopologyAttrs(force_guess=["types"]) with pytest.raises(AssertionError): assert_equal(old_types, u.atoms.types) class TestGuessMasses(object): - """Tests the Mass Guesser in topology.guessers - """ + """Tests the Mass Guesser in topology.guessers""" + def test_universe_loading_no_warning(self): assert_nowarns(UserWarning, lambda x: mda.Universe(x), GRO) @@ -438,9 +473,10 @@ class TestGuessBonds(object): - fail properly if not - work again if vdwradii are passed. """ - @pytest.fixture(scope='module') + + @pytest.fixture(scope="module") def vdw(self): - return {'A': 1.4, 'B': 0.5} + return {"A": 1.4, "B": 0.5} def _check_universe(self, u): """Verify that the Universe is created correctly""" @@ -453,13 +489,13 @@ def _check_universe(self, u): assert_equal(len(u.atoms[3].bonds), 2) assert_equal(len(u.atoms[4].bonds), 1) assert_equal(len(u.atoms[5].bonds), 1) - assert 'guess_bonds' in u.kwargs + assert "guess_bonds" in u.kwargs def test_universe_guess_bonds(self): """Test that making a Universe with guess_bonds works""" u = mda.Universe(two_water_gro, guess_bonds=True) self._check_universe(u) - assert u.kwargs['guess_bonds'] + assert u.kwargs["guess_bonds"] def test_universe_guess_bonds_no_vdwradii(self): """Make a Universe that has atoms with unknown vdwradii.""" @@ -468,18 +504,17 @@ def test_universe_guess_bonds_no_vdwradii(self): def test_universe_guess_bonds_with_vdwradii(self, vdw): """Unknown atom types, but with vdw radii here to save the day""" - u = mda.Universe(two_water_gro_nonames, guess_bonds=True, - vdwradii=vdw) + u = mda.Universe(two_water_gro_nonames, guess_bonds=True, vdwradii=vdw) self._check_universe(u) - assert u.kwargs['guess_bonds'] - assert_equal(vdw, u.kwargs['vdwradii']) + assert u.kwargs["guess_bonds"] + assert_equal(vdw, u.kwargs["vdwradii"]) def test_universe_guess_bonds_off(self): u = mda.Universe(two_water_gro_nonames, guess_bonds=False) - for attr in ('bonds', 'angles', 'dihedrals'): + for attr in ("bonds", "angles", "dihedrals"): assert not hasattr(u, attr) - assert not u.kwargs['guess_bonds'] + assert not u.kwargs["guess_bonds"] def test_universe_guess_bonds_arguments(self): """Test if 'fudge_factor', and 'lower_bound' parameters @@ -510,11 +545,8 @@ def _check_atomgroup(self, ag, u): assert_equal(len(u.atoms[5].bonds), 0) @pytest.mark.parametrize( - 'ff, lb, nbonds', - [ - (0.55, 0.1, 2), (0.9, 1.6, 1), - (0.5, 0.2, 2), (0.1, 0.1, 0) - ] + "ff, lb, nbonds", + [(0.55, 0.1, 2), (0.9, 1.6, 1), (0.5, 0.2, 2), (0.1, 0.1, 0)], ) def test_atomgroup_guess_bonds(self, ff, lb, nbonds): """Test an atomgroup doing guess bonds""" @@ -554,8 +586,8 @@ def test_guess_bonds_periodicity(self): def guess_bonds_with_to_guess(self): u = mda.Universe(two_water_gro) - has_bonds = hasattr(u.atoms, 'bonds') - u.guess_TopologyAttrs(to_guess=['bonds']) + has_bonds = hasattr(u.atoms, "bonds") + u.guess_TopologyAttrs(to_guess=["bonds"]) assert not has_bonds assert u.atoms.bonds @@ -567,157 +599,194 @@ def test_guess_read_bonds(self): class TestInMemoryUniverse(object): def test_reader_w_timeseries(self): universe = mda.Universe(PSF, DCD, in_memory=True) - assert_equal(universe.trajectory.timeseries(universe.atoms).shape, - (3341, 98, 3), - err_msg="Unexpected shape of trajectory timeseries") + assert_equal( + universe.trajectory.timeseries(universe.atoms).shape, + (3341, 98, 3), + err_msg="Unexpected shape of trajectory timeseries", + ) def test_reader_wo_timeseries(self): universe = mda.Universe(GRO, TRR, in_memory=True) - assert_equal(universe.trajectory.timeseries(universe.atoms).shape, - (47681, 10, 3), - err_msg="Unexpected shape of trajectory timeseries") + assert_equal( + universe.trajectory.timeseries(universe.atoms).shape, + (47681, 10, 3), + err_msg="Unexpected shape of trajectory timeseries", + ) def test_reader_w_timeseries_frame_interval(self): - universe = mda.Universe(PSF, DCD, in_memory=True, - in_memory_step=10) - assert_equal(universe.trajectory.timeseries(universe.atoms).shape, - (3341, 10, 3), - err_msg="Unexpected shape of trajectory timeseries") + universe = mda.Universe(PSF, DCD, in_memory=True, in_memory_step=10) + assert_equal( + universe.trajectory.timeseries(universe.atoms).shape, + (3341, 10, 3), + err_msg="Unexpected shape of trajectory timeseries", + ) def test_reader_wo_timeseries_frame_interval(self): - universe = mda.Universe(GRO, TRR, in_memory=True, - in_memory_step=3) - assert_equal(universe.trajectory.timeseries(universe.atoms).shape, - (47681, 4, 3), - err_msg="Unexpected shape of trajectory timeseries") + universe = mda.Universe(GRO, TRR, in_memory=True, in_memory_step=3) + assert_equal( + universe.trajectory.timeseries(universe.atoms).shape, + (47681, 4, 3), + err_msg="Unexpected shape of trajectory timeseries", + ) def test_existing_universe(self): universe = mda.Universe(PDB_small, DCD) universe.transfer_to_memory() - assert_equal(universe.trajectory.timeseries(universe.atoms).shape, - (3341, 98, 3), - err_msg="Unexpected shape of trajectory timeseries") + assert_equal( + universe.trajectory.timeseries(universe.atoms).shape, + (3341, 98, 3), + err_msg="Unexpected shape of trajectory timeseries", + ) def test_frame_interval_convention(self): universe1 = mda.Universe(PSF, DCD) array1 = universe1.trajectory.timeseries(step=10) - universe2 = mda.Universe(PSF, DCD, in_memory=True, - in_memory_step=10) + universe2 = mda.Universe(PSF, DCD, in_memory=True, in_memory_step=10) array2 = universe2.trajectory.timeseries() - assert_equal(array1, array2, - err_msg="Unexpected differences between arrays.") + assert_equal( + array1, array2, err_msg="Unexpected differences between arrays." + ) def test_slicing_with_start_stop(self): universe = MDAnalysis.Universe(PDB_small, DCD) # Skip only the last frame universe.transfer_to_memory(start=10, stop=20) - assert_equal(universe.trajectory.timeseries(universe.atoms).shape, - (3341, 10, 3), - err_msg="Unexpected shape of trajectory timeseries") + assert_equal( + universe.trajectory.timeseries(universe.atoms).shape, + (3341, 10, 3), + err_msg="Unexpected shape of trajectory timeseries", + ) def test_slicing_without_start(self): universe = MDAnalysis.Universe(PDB_small, DCD) # Skip only the last frame universe.transfer_to_memory(stop=10) - assert_equal(universe.trajectory.timeseries(universe.atoms).shape, - (3341, 10, 3), - err_msg="Unexpected shape of trajectory timeseries") + assert_equal( + universe.trajectory.timeseries(universe.atoms).shape, + (3341, 10, 3), + err_msg="Unexpected shape of trajectory timeseries", + ) def test_slicing_without_stop(self): universe = MDAnalysis.Universe(PDB_small, DCD) # Skip only the last frame universe.transfer_to_memory(start=10) print(universe.trajectory.timeseries(universe.atoms).shape) - assert_equal(universe.trajectory.timeseries(universe.atoms).shape, - (3341, 88, 3), - err_msg="Unexpected shape of trajectory timeseries") + assert_equal( + universe.trajectory.timeseries(universe.atoms).shape, + (3341, 88, 3), + err_msg="Unexpected shape of trajectory timeseries", + ) def test_slicing_step_without_start_stop(self): universe = MDAnalysis.Universe(PDB_small, DCD) # Skip only the last frame universe.transfer_to_memory(step=2) print(universe.trajectory.timeseries(universe.atoms).shape) - assert_equal(universe.trajectory.timeseries(universe.atoms).shape, - (3341, 49, 3), - err_msg="Unexpected shape of trajectory timeseries") + assert_equal( + universe.trajectory.timeseries(universe.atoms).shape, + (3341, 49, 3), + err_msg="Unexpected shape of trajectory timeseries", + ) def test_slicing_step_with_start_stop(self): universe = MDAnalysis.Universe(PDB_small, DCD) # Skip only the last frame universe.transfer_to_memory(start=10, stop=30, step=2) print(universe.trajectory.timeseries(universe.atoms).shape) - assert_equal(universe.trajectory.timeseries(universe.atoms).shape, - (3341, 10, 3), - err_msg="Unexpected shape of trajectory timeseries") + assert_equal( + universe.trajectory.timeseries(universe.atoms).shape, + (3341, 10, 3), + err_msg="Unexpected shape of trajectory timeseries", + ) def test_slicing_step_dt(self): universe = MDAnalysis.Universe(PDB_small, DCD) dt = universe.trajectory.dt universe.transfer_to_memory(step=2) - assert_almost_equal(dt * 2, universe.trajectory.dt, - err_msg="Unexpected in-memory timestep: " - + "dt not updated with step information") + assert_almost_equal( + dt * 2, + universe.trajectory.dt, + err_msg="Unexpected in-memory timestep: " + + "dt not updated with step information", + ) def test_slicing_negative_start(self): universe = MDAnalysis.Universe(PDB_small, DCD) # Skip only the last frame universe.transfer_to_memory(start=-10) print(universe.trajectory.timeseries(universe.atoms).shape) - assert_equal(universe.trajectory.timeseries(universe.atoms).shape, - (3341, 10, 3), - err_msg="Unexpected shape of trajectory timeseries") + assert_equal( + universe.trajectory.timeseries(universe.atoms).shape, + (3341, 10, 3), + err_msg="Unexpected shape of trajectory timeseries", + ) def test_slicing_negative_stop(self): universe = MDAnalysis.Universe(PDB_small, DCD) # Skip only the last frame universe.transfer_to_memory(stop=-20) print(universe.trajectory.timeseries(universe.atoms).shape) - assert_equal(universe.trajectory.timeseries(universe.atoms).shape, - (3341, 78, 3), - err_msg="Unexpected shape of trajectory timeseries") + assert_equal( + universe.trajectory.timeseries(universe.atoms).shape, + (3341, 78, 3), + err_msg="Unexpected shape of trajectory timeseries", + ) def test_transfer_to_memory_kwargs(self): u = mda.Universe(PSF, DCD) u.transfer_to_memory(example_kwarg=True) - assert(u.trajectory._kwargs['example_kwarg']) + assert u.trajectory._kwargs["example_kwarg"] def test_in_memory_kwargs(self): u = mda.Universe(PSF, DCD, in_memory=True, example_kwarg=True) - assert(u.trajectory._kwargs['example_kwarg']) + assert u.trajectory._kwargs["example_kwarg"] + class TestCustomReaders(object): """ Can pass a reader as kwarg on Universe creation """ + def test_custom_reader(self): # check that reader passing works - u = mda.Universe(TRZ_psf, TRZ, format=MDAnalysis.coordinates.TRZ.TRZReader) + u = mda.Universe( + TRZ_psf, TRZ, format=MDAnalysis.coordinates.TRZ.TRZReader + ) assert_equal(len(u.atoms), 8184) def test_custom_reader_singleframe(self): T = MDAnalysis.topology.GROParser.GROParser R = MDAnalysis.coordinates.GRO.GROReader - u = mda.Universe(two_water_gro, two_water_gro, - topology_format=T, format=R) + u = mda.Universe( + two_water_gro, two_water_gro, topology_format=T, format=R + ) assert_equal(len(u.atoms), 6) def test_custom_reader_singleframe_2(self): # Same as before, but only one argument to Universe T = MDAnalysis.topology.GROParser.GROParser R = MDAnalysis.coordinates.GRO.GROReader - u = mda.Universe(two_water_gro, - topology_format=T, format=R) + u = mda.Universe(two_water_gro, topology_format=T, format=R) assert_equal(len(u.atoms), 6) def test_custom_parser(self): # topology reader passing works - u = mda.Universe(TRZ_psf, TRZ, topology_format=MDAnalysis.topology.PSFParser.PSFParser) + u = mda.Universe( + TRZ_psf, + TRZ, + topology_format=MDAnalysis.topology.PSFParser.PSFParser, + ) assert_equal(len(u.atoms), 8184) def test_custom_both(self): # use custom for both - u = mda.Universe(TRZ_psf, TRZ, format=MDAnalysis.coordinates.TRZ.TRZReader, - topology_format=MDAnalysis.topology.PSFParser.PSFParser) + u = mda.Universe( + TRZ_psf, + TRZ, + format=MDAnalysis.coordinates.TRZ.TRZReader, + topology_format=MDAnalysis.topology.PSFParser.PSFParser, + ) assert_equal(len(u.atoms), 8184) @@ -728,34 +797,50 @@ def universe(self): def test_add_TA_fail(self, universe): with pytest.raises(ValueError): - universe.add_TopologyAttr('silly') + universe.add_TopologyAttr("silly") def test_nodefault_fail(self, universe): with pytest.raises(NotImplementedError): - universe.add_TopologyAttr('bonds') + universe.add_TopologyAttr("bonds") @pytest.mark.parametrize( - 'toadd,attrname,default', ( - ['charge', 'charges', 0.0], ['charges', 'charges', 0.0], - ['name', 'names', ''], ['names', 'names', ''], - ['type', 'types', ''], ['types', 'types', ''], - ['element', 'elements', ''], ['elements', 'elements', ''], - ['radius', 'radii', 0.0], ['radii', 'radii', 0.0], - ['chainID', 'chainIDs', ''], ['chainIDs', 'chainIDs', ''], - ['tempfactor', 'tempfactors', 0.0], - ['tempfactors', 'tempfactors', 0.0], - ['mass', 'masses', 0.0], ['masses', 'masses', 0.0], - ['charge', 'charges', 0.0], ['charges', 'charges', 0.0], - ['bfactor', 'bfactors', 0.0], ['bfactors', 'bfactors', 0.0], - ['occupancy', 'occupancies', 0.0], - ['occupancies', 'occupancies', 0.0], - ['altLoc', 'altLocs', ''], ['altLocs', 'altLocs', ''], - ['resid', 'resids', 1], ['resids', 'resids', 1], - ['resname', 'resnames', ''], ['resnames', 'resnames', ''], - ['resnum', 'resnums', 1], ['resnums', 'resnums', 1], - ['icode', 'icodes', ''], ['icodes', 'icodes', ''], - ['segid', 'segids', ''], ['segids', 'segids', ''], - ) + "toadd,attrname,default", + ( + ["charge", "charges", 0.0], + ["charges", "charges", 0.0], + ["name", "names", ""], + ["names", "names", ""], + ["type", "types", ""], + ["types", "types", ""], + ["element", "elements", ""], + ["elements", "elements", ""], + ["radius", "radii", 0.0], + ["radii", "radii", 0.0], + ["chainID", "chainIDs", ""], + ["chainIDs", "chainIDs", ""], + ["tempfactor", "tempfactors", 0.0], + ["tempfactors", "tempfactors", 0.0], + ["mass", "masses", 0.0], + ["masses", "masses", 0.0], + ["charge", "charges", 0.0], + ["charges", "charges", 0.0], + ["bfactor", "bfactors", 0.0], + ["bfactors", "bfactors", 0.0], + ["occupancy", "occupancies", 0.0], + ["occupancies", "occupancies", 0.0], + ["altLoc", "altLocs", ""], + ["altLocs", "altLocs", ""], + ["resid", "resids", 1], + ["resids", "resids", 1], + ["resname", "resnames", ""], + ["resnames", "resnames", ""], + ["resnum", "resnums", 1], + ["resnums", "resnums", 1], + ["icode", "icodes", ""], + ["icodes", "icodes", ""], + ["segid", "segids", ""], + ["segids", "segids", ""], + ), ) def test_add_charges(self, universe, toadd, attrname, default): universe.add_TopologyAttr(toadd) @@ -764,14 +849,15 @@ def test_add_charges(self, universe, toadd, attrname, default): assert getattr(universe.atoms, attrname)[0] == default @pytest.mark.parametrize( - 'attr,values', ( - ('bonds', [(1, 0), (1, 2)]), - ('bonds', [[1, 0], [1, 2]]), - ('bonds', set([(1, 0), (1, 2)])), - ('angles', [(1, 0, 2), (1, 2, 3), (2, 1, 4)]), - ('dihedrals', [[1, 2, 3, 1], (3, 1, 5, 2)]), - ('impropers', [[1, 2, 3, 1], (3, 1, 5, 2)]), - ) + "attr,values", + ( + ("bonds", [(1, 0), (1, 2)]), + ("bonds", [[1, 0], [1, 2]]), + ("bonds", set([(1, 0), (1, 2)])), + ("angles", [(1, 0, 2), (1, 2, 3), (2, 1, 4)]), + ("dihedrals", [[1, 2, 3, 1], (3, 1, 5, 2)]), + ("impropers", [[1, 2, 3, 1], (3, 1, 5, 2)]), + ), ) def test_add_connection(self, universe, attr, values): universe.add_TopologyAttr(attr, values) @@ -783,16 +869,17 @@ def test_add_connection(self, universe, attr, values): assert ix[0] <= ix[-1] @pytest.mark.parametrize( - 'attr,values', ( - ('bonds', [(1, 0, 0), (1, 2)]), - ('bonds', [['x', 'y'], [1, 2]]), - ('bonds', 'rubbish'), - ('bonds', [[1.01, 2.0]]), - ('angles', [(1, 0), (1, 2)]), - ('angles', 'rubbish'), - ('dihedrals', [[1, 1, 1, 0.1]]), - ('impropers', [(1, 2, 3)]), - ) + "attr,values", + ( + ("bonds", [(1, 0, 0), (1, 2)]), + ("bonds", [["x", "y"], [1, 2]]), + ("bonds", "rubbish"), + ("bonds", [[1.01, 2.0]]), + ("angles", [(1, 0), (1, 2)]), + ("angles", "rubbish"), + ("dihedrals", [[1, 1, 1, 0.1]]), + ("impropers", [(1, 2, 3)]), + ), ) def test_add_connection_error(self, universe, attr, values): with pytest.raises(ValueError): @@ -800,7 +887,9 @@ def test_add_connection_error(self, universe, attr, values): def test_add_attr_length_error(self, universe): with pytest.raises(ValueError): - universe.add_TopologyAttr('masses', np.array([1, 2, 3], dtype=np.float64)) + universe.add_TopologyAttr( + "masses", np.array([1, 2, 3], dtype=np.float64) + ) class TestDelTopologyAttr(object): @@ -812,7 +901,7 @@ def universe(self): def test_del_TA_fail(self, universe): with pytest.raises(ValueError, match="Unrecognised"): - universe.del_TopologyAttr('silly') + universe.del_TopologyAttr("silly") def test_absent_fail(self, universe): with pytest.raises(ValueError, match="not in Universe"): @@ -823,11 +912,12 @@ def test_wrongtype_fail(self, universe): universe.del_TopologyAttr(list) @pytest.mark.parametrize( - 'todel,attrname', [ + "todel,attrname", + [ ("charge", "charges"), ("charges", "charges"), ("bonds", "bonds"), - ] + ], ) def test_del_str(self, universe, todel, attrname): assert hasattr(universe.atoms, attrname) @@ -873,8 +963,7 @@ class RootVegetable(AtomStringAttr): transplants = defaultdict(list) def potatoes(self): - """🥔 - """ + """🥔""" return "potoooooooo" transplants["Universe"].append(("potatoes", potatoes)) @@ -890,22 +979,23 @@ def _a_or_reversed_in_b(a, b): """ Check if smaller array ``a`` or ``a[::-1]`` is in b """ - return (a==b).all(1).any() or (a[::-1]==b).all(1).any() + return (a == b).all(1).any() or (a[::-1] == b).all(1).any() + class TestAddTopologyObjects(object): small_atom_indices = ( - ('bonds', [[0, 1], [2, 3]]), - ('angles', [[0, 1, 2], [3, 4, 5]]), - ('dihedrals', [[8, 22, 1, 3], [4, 5, 6, 7], [11, 2, 3, 13]]), - ('impropers', [[1, 6, 7, 2], [5, 3, 4, 2]]), + ("bonds", [[0, 1], [2, 3]]), + ("angles", [[0, 1, 2], [3, 4, 5]]), + ("dihedrals", [[8, 22, 1, 3], [4, 5, 6, 7], [11, 2, 3, 13]]), + ("impropers", [[1, 6, 7, 2], [5, 3, 4, 2]]), ) large_atom_indices = ( - ('bonds', [[0, 111], [22, 3]]), - ('angles', [[0, 111, 2], [3, 44, 5]]), - ('dihedrals', [[8, 222, 1, 3], [44, 5, 6, 7], [111, 2, 3, 13]]), - ('impropers', [[1, 6, 771, 2], [5, 3, 433, 2]]), + ("bonds", [[0, 111], [22, 3]]), + ("angles", [[0, 111, 2], [3, 44, 5]]), + ("dihedrals", [[8, 222, 1, 3], [44, 5, 6, 7], [111, 2, 3, 13]]), + ("impropers", [[1, 6, 771, 2], [5, 3, 433, 2]]), ) @pytest.fixture() @@ -918,189 +1008,203 @@ def universe(self): def _check_valid_added_to_empty(self, u, attr, values, to_add): assert not hasattr(u, attr) - _add_func = getattr(u, 'add_'+attr) + _add_func = getattr(u, "add_" + attr) _add_func(to_add) u_attr = getattr(u, attr) assert len(u_attr) == len(values) - assert all(_a_or_reversed_in_b(x, u_attr.indices) - for x in values) + assert all(_a_or_reversed_in_b(x, u_attr.indices) for x in values) def _check_valid_added_to_populated(self, u, attr, values, to_add): assert hasattr(u, attr) u_attr = getattr(u, attr) original_length = len(u_attr) - _add_func = getattr(u, 'add_'+attr) + _add_func = getattr(u, "add_" + attr) _add_func(to_add) u_attr = getattr(u, attr) assert len(u_attr) == len(values) + original_length - assert all(_a_or_reversed_in_b(x, u_attr.indices) - for x in values) + assert all(_a_or_reversed_in_b(x, u_attr.indices) for x in values) def _check_invalid_addition(self, u, attr, to_add, err_msg): - _add_func = getattr(u, 'add_'+attr) + _add_func = getattr(u, "add_" + attr) with pytest.raises(ValueError) as excinfo: _add_func(to_add) assert err_msg in str(excinfo.value) - @pytest.mark.parametrize( - 'attr,values', small_atom_indices - ) + @pytest.mark.parametrize("attr,values", small_atom_indices) def test_add_indices_to_empty(self, empty, attr, values): self._check_valid_added_to_empty(empty, attr, values, values) def test_add_reversed_duplicates(self, empty): - assert not hasattr(empty, 'bonds') + assert not hasattr(empty, "bonds") empty.add_bonds([[0, 1], [1, 0]]) assert len(empty.bonds) == 1 assert_array_equal(empty.bonds.indices, np.array([[0, 1]])) - @pytest.mark.parametrize( - 'attr,values', large_atom_indices - ) + @pytest.mark.parametrize("attr,values", large_atom_indices) def test_add_indices_to_populated(self, universe, attr, values): self._check_valid_added_to_populated(universe, attr, values, values) - @pytest.mark.parametrize( - 'attr,values', small_atom_indices - ) + @pytest.mark.parametrize("attr,values", small_atom_indices) def test_add_atomgroup_to_empty(self, empty, attr, values): ag = [empty.atoms[x] for x in values] self._check_valid_added_to_empty(empty, attr, values, ag) - @pytest.mark.parametrize( - 'attr,values', large_atom_indices - ) + @pytest.mark.parametrize("attr,values", large_atom_indices) def test_add_atomgroup_to_populated(self, universe, attr, values): ag = [universe.atoms[x] for x in values] self._check_valid_added_to_populated(universe, attr, values, ag) - @pytest.mark.parametrize( - 'attr,values', small_atom_indices - ) - def test_add_atomgroup_wrong_universe_error(self, universe, empty, attr, values): + @pytest.mark.parametrize("attr,values", small_atom_indices) + def test_add_atomgroup_wrong_universe_error( + self, universe, empty, attr, values + ): ag = [empty.atoms[x] for x in values] - self._check_invalid_addition(universe, attr, ag, 'different Universes') + self._check_invalid_addition(universe, attr, ag, "different Universes") - @pytest.mark.parametrize( - 'attr,values', large_atom_indices - ) + @pytest.mark.parametrize("attr,values", large_atom_indices) def test_add_topologyobjects_to_populated(self, universe, attr, values): - topologyobjects = [getattr(universe.atoms[x], attr[:-1]) for x in values] - self._check_valid_added_to_populated(universe, attr, values, topologyobjects) + topologyobjects = [ + getattr(universe.atoms[x], attr[:-1]) for x in values + ] + self._check_valid_added_to_populated( + universe, attr, values, topologyobjects + ) - @pytest.mark.parametrize( - 'attr,values', small_atom_indices - ) - def test_add_topologyobjects_wrong_universe_error(self, universe, empty, attr, values): + @pytest.mark.parametrize("attr,values", small_atom_indices) + def test_add_topologyobjects_wrong_universe_error( + self, universe, empty, attr, values + ): tobj = [getattr(universe.atoms[x], attr[:-1]) for x in values] - self._check_invalid_addition(empty, attr, tobj, 'different Universes') + self._check_invalid_addition(empty, attr, tobj, "different Universes") - @pytest.mark.parametrize( - 'attr,values', large_atom_indices - ) + @pytest.mark.parametrize("attr,values", large_atom_indices) def test_add_topologygroups_to_populated(self, universe, attr, values): - topologygroup = mda.core.topologyobjects.TopologyGroup(np.array(values), - universe) - self._check_valid_added_to_populated(universe, attr, values, topologygroup) - - @pytest.mark.parametrize( - 'attr,values', small_atom_indices - ) - def test_add_topologygroup_wrong_universe_error(self, universe, empty, attr, values): - tg = mda.core.topologyobjects.TopologyGroup(np.array(values), - universe) - self._check_invalid_addition(empty, attr, tg, 'different Universes') + topologygroup = mda.core.topologyobjects.TopologyGroup( + np.array(values), universe + ) + self._check_valid_added_to_populated( + universe, attr, values, topologygroup + ) - @pytest.mark.parametrize( - 'attr,values', small_atom_indices - ) - def test_add_topologygroup_different_universe(self, universe, empty, attr, values): - tg = mda.core.topologyobjects.TopologyGroup(np.array(values), - universe) + @pytest.mark.parametrize("attr,values", small_atom_indices) + def test_add_topologygroup_wrong_universe_error( + self, universe, empty, attr, values + ): + tg = mda.core.topologyobjects.TopologyGroup(np.array(values), universe) + self._check_invalid_addition(empty, attr, tg, "different Universes") + + @pytest.mark.parametrize("attr,values", small_atom_indices) + def test_add_topologygroup_different_universe( + self, universe, empty, attr, values + ): + tg = mda.core.topologyobjects.TopologyGroup(np.array(values), universe) self._check_valid_added_to_empty(empty, attr, values, tg.to_indices()) @pytest.mark.parametrize( - 'attr,values', ( - ('impropers', [[0, 111], [22, 3]]), - ('dihedrals', [[0, 111, 2], [3, 44, 5]]), - ('angles', [[8, 222, 1, 3], [44, 5, 6, 7], [111, 2, 3, 13]]), - ('bonds', [[1, 6, 771, 2], [5, 3, 433, 2]]), - ) + "attr,values", + ( + ("impropers", [[0, 111], [22, 3]]), + ("dihedrals", [[0, 111, 2], [3, 44, 5]]), + ("angles", [[8, 222, 1, 3], [44, 5, 6, 7], [111, 2, 3, 13]]), + ("bonds", [[1, 6, 771, 2], [5, 3, 433, 2]]), + ), ) def test_add_wrong_topologygroup_error(self, universe, attr, values): arr = np.array(values) tg = mda.core.topologyobjects.TopologyGroup(arr, universe) - self._check_invalid_addition(universe, attr, tg, 'iterable of tuples with') + self._check_invalid_addition( + universe, attr, tg, "iterable of tuples with" + ) @pytest.mark.parametrize( - 'attr,values', ( - ('bonds', [[0, -111], [22, 3]]), - ('angles', [[0, 11111, 2], [3, 44, 5]]), - ('dihedrals', [[8, 222, 28888, 3], [44, 5, 6, 7], [111, 2, 3, 13]]), - ('impropers', [[1, 6, 77133, 2], [5, 3, 433, 2]]), - ) + "attr,values", + ( + ("bonds", [[0, -111], [22, 3]]), + ("angles", [[0, 11111, 2], [3, 44, 5]]), + ( + "dihedrals", + [[8, 222, 28888, 3], [44, 5, 6, 7], [111, 2, 3, 13]], + ), + ("impropers", [[1, 6, 77133, 2], [5, 3, 433, 2]]), + ), ) def test_add_nonexistent_indices_error(self, universe, attr, values): - self._check_invalid_addition(universe, attr, values, 'nonexistent atom indices') + self._check_invalid_addition( + universe, attr, values, "nonexistent atom indices" + ) @pytest.mark.parametrize( - 'attr,n', ( - ('bonds', 2), - ('angles', 3), - ('dihedrals', 4), - ('impropers', 4), - ) + "attr,n", + ( + ("bonds", 2), + ("angles", 3), + ("dihedrals", 4), + ("impropers", 4), + ), ) def test_add_wrong_number_of_atoms_error(self, universe, attr, n): - errmsg = ('{} must be an iterable of ' - 'tuples with {} atom indices').format(attr, n) + errmsg = ( + "{} must be an iterable of " "tuples with {} atom indices" + ).format(attr, n) idx = [(0, 1), (0, 1, 2), (8, 22, 1, 3), (5, 3, 4, 2)] self._check_invalid_addition(universe, attr, idx, errmsg) def test_add_bonds_refresh_fragments(self, empty): with pytest.raises(NoDataError): - getattr(empty.atoms, 'fragments') + getattr(empty.atoms, "fragments") empty.add_bonds([empty.atoms[:2]]) - assert len(empty.atoms.fragments) == len(empty.atoms)-1 + assert len(empty.atoms.fragments) == len(empty.atoms) - 1 empty.add_bonds([empty.atoms[2:4]]) - assert len(empty.atoms.fragments) == len(empty.atoms)-2 + assert len(empty.atoms.fragments) == len(empty.atoms) - 2 - @pytest.mark.parametrize( - 'attr,values', small_atom_indices - ) + @pytest.mark.parametrize("attr,values", small_atom_indices) def test_roundtrip(self, empty, attr, values): - _add_func = getattr(empty, 'add_'+attr) + _add_func = getattr(empty, "add_" + attr) _add_func(values) u_attr = getattr(empty, attr) assert len(u_attr) == len(values) - _delete_func = getattr(empty, 'delete_'+attr) + _delete_func = getattr(empty, "delete_" + attr) _delete_func(values) u_attr = getattr(empty, attr) assert len(u_attr) == 0 + class TestDeleteTopologyObjects(object): - TOP = {'bonds': [(0, 1), (2, 3), (3, 4), (4, 5), (7, 8)], - 'angles': [(0, 1, 2), (3, 4, 5), (8, 2, 4)], - 'dihedrals': [(9, 2, 3, 4), (1, 3, 4, 2), (8, 22, 1, 3), (4, 5, 6, 7), (11, 2, 3, 13)], - 'impropers': [(1, 3, 5, 2), (1, 6, 7, 2), (5, 3, 4, 2)]} + TOP = { + "bonds": [(0, 1), (2, 3), (3, 4), (4, 5), (7, 8)], + "angles": [(0, 1, 2), (3, 4, 5), (8, 2, 4)], + "dihedrals": [ + (9, 2, 3, 4), + (1, 3, 4, 2), + (8, 22, 1, 3), + (4, 5, 6, 7), + (11, 2, 3, 13), + ], + "impropers": [(1, 3, 5, 2), (1, 6, 7, 2), (5, 3, 4, 2)], + } existing_atom_indices = ( - ('bonds', [[0, 1], [2, 3]]), - ('angles', [[0, 1, 2], [3, 4, 5]]), - ('dihedrals', [[8, 22, 1, 3], [4, 5, 6, 7], [11, 2, 3, 13]]), - ('impropers', [[1, 6, 7, 2], [5, 3, 4, 2]]), - ) + ("bonds", [[0, 1], [2, 3]]), + ("angles", [[0, 1, 2], [3, 4, 5]]), + ("dihedrals", [[8, 22, 1, 3], [4, 5, 6, 7], [11, 2, 3, 13]]), + ("impropers", [[1, 6, 7, 2], [5, 3, 4, 2]]), + ) nonexisting_atom_indices = ( - ('bonds', [[2, 3], [7, 8], [0, 4]]), - ('angles', [[0, 1, 2], [8, 2, 8], [1, 1, 1]]), - ('dihedrals', [[0, 0, 0, 0], [1, 1, 1, 1]]), - ('impropers', [[8, 22, 1, 3],]), - ) + ("bonds", [[2, 3], [7, 8], [0, 4]]), + ("angles", [[0, 1, 2], [8, 2, 8], [1, 1, 1]]), + ("dihedrals", [[0, 0, 0, 0], [1, 1, 1, 1]]), + ( + "impropers", + [ + [8, 22, 1, 3], + ], + ), + ) @pytest.fixture() def universe(self): @@ -1121,124 +1225,120 @@ def _check_valid_deleted(self, u, attr, values, to_delete): original_length = len(self.TOP[attr]) assert len(u_attr) == original_length - _delete_func = getattr(u, 'delete_'+attr) + _delete_func = getattr(u, "delete_" + attr) _delete_func(to_delete) u_attr = getattr(u, attr) - assert len(u_attr) == original_length-len(values) + assert len(u_attr) == original_length - len(values) not_deleted = [x for x in self.TOP[attr] if list(x) not in values] - assert all([x in u_attr.indices or x[::-1] in u_attr.indices - for x in not_deleted]) + assert all( + [ + x in u_attr.indices or x[::-1] in u_attr.indices + for x in not_deleted + ] + ) def _check_invalid_deleted(self, u, attr, to_delete, err_msg): u_attr = getattr(u, attr) original_length = len(self.TOP[attr]) assert len(u_attr) == original_length - _delete_func = getattr(u, 'delete_'+attr) + _delete_func = getattr(u, "delete_" + attr) with pytest.raises(ValueError) as excinfo: _delete_func(to_delete) assert err_msg in str(excinfo.value) - @pytest.mark.parametrize( - 'attr,values', existing_atom_indices - ) + @pytest.mark.parametrize("attr,values", existing_atom_indices) def test_delete_valid_indices(self, universe, attr, values): self._check_valid_deleted(universe, attr, values, values) - @pytest.mark.parametrize( - 'attr,values', nonexisting_atom_indices - ) + @pytest.mark.parametrize("attr,values", nonexisting_atom_indices) def test_delete_missing_indices(self, universe, attr, values): - self._check_invalid_deleted(universe, attr, values, 'Cannot delete nonexistent') + self._check_invalid_deleted( + universe, attr, values, "Cannot delete nonexistent" + ) - @pytest.mark.parametrize( - 'attr,values', existing_atom_indices - ) + @pytest.mark.parametrize("attr,values", existing_atom_indices) def test_delete_valid_atomgroup(self, universe, attr, values): ag = [universe.atoms[x] for x in values] self._check_valid_deleted(universe, attr, values, ag) - @pytest.mark.parametrize( - 'attr,values', existing_atom_indices - ) - def test_delete_atomgroup_wrong_universe_error(self, universe, universe2, attr, values): + @pytest.mark.parametrize("attr,values", existing_atom_indices) + def test_delete_atomgroup_wrong_universe_error( + self, universe, universe2, attr, values + ): ag = [universe.atoms[x] for x in values] - self._check_invalid_deleted(universe2, attr, ag, 'different Universes') + self._check_invalid_deleted(universe2, attr, ag, "different Universes") - @pytest.mark.parametrize( - 'attr,values', nonexisting_atom_indices - ) + @pytest.mark.parametrize("attr,values", nonexisting_atom_indices) def test_delete_missing_atomgroup(self, universe, attr, values): ag = [universe.atoms[x] for x in values] - self._check_invalid_deleted(universe, attr, ag, 'Cannot delete nonexistent') + self._check_invalid_deleted( + universe, attr, ag, "Cannot delete nonexistent" + ) - @pytest.mark.parametrize( - 'attr,values', existing_atom_indices - ) + @pytest.mark.parametrize("attr,values", existing_atom_indices) def test_delete_mixed_type(self, universe, attr, values): mixed = [universe.atoms[values[0]]] + values[1:] self._check_valid_deleted(universe, attr, values, mixed) - @pytest.mark.parametrize( - 'attr,values', existing_atom_indices - ) + @pytest.mark.parametrize("attr,values", existing_atom_indices) def test_delete_valid_topologyobjects(self, universe, attr, values): to = [getattr(universe.atoms[x], attr[:-1]) for x in values] self._check_valid_deleted(universe, attr, values, to) - @pytest.mark.parametrize( - 'attr,values', existing_atom_indices - ) - def test_delete_topologyobjects_wrong_universe(self, universe, universe2, attr, values): + @pytest.mark.parametrize("attr,values", existing_atom_indices) + def test_delete_topologyobjects_wrong_universe( + self, universe, universe2, attr, values + ): u1 = [getattr(universe.atoms[x], attr[:-1]) for x in values[:-1]] u2 = [getattr(universe2.atoms[values[-1]], attr[:-1])] - self._check_invalid_deleted(universe, attr, u1+u2, 'different Universes') + self._check_invalid_deleted( + universe, attr, u1 + u2, "different Universes" + ) - @pytest.mark.parametrize( - 'attr,values', existing_atom_indices - ) + @pytest.mark.parametrize("attr,values", existing_atom_indices) def test_delete_valid_topologygroup(self, universe, attr, values): arr = np.array(values) tg = mda.core.topologyobjects.TopologyGroup(arr, universe) self._check_valid_deleted(universe, attr, values, tg) - @pytest.mark.parametrize( - 'attr,values', existing_atom_indices - ) - def test_delete_topologygroup_wrong_universe_error(self, universe, universe2, attr, values): + @pytest.mark.parametrize("attr,values", existing_atom_indices) + def test_delete_topologygroup_wrong_universe_error( + self, universe, universe2, attr, values + ): arr = np.array(values) tg = mda.core.topologyobjects.TopologyGroup(arr, universe2) - self._check_invalid_deleted(universe, attr, tg, 'different Universes') + self._check_invalid_deleted(universe, attr, tg, "different Universes") - @pytest.mark.parametrize( - 'attr,values', existing_atom_indices - ) - def test_delete_topologygroup_different_universe(self, universe, universe2, attr, values): + @pytest.mark.parametrize("attr,values", existing_atom_indices) + def test_delete_topologygroup_different_universe( + self, universe, universe2, attr, values + ): arr = np.array(values) tg = mda.core.topologyobjects.TopologyGroup(arr, universe2) self._check_valid_deleted(universe, attr, values, tg.to_indices()) @pytest.mark.parametrize( - 'attr,n', ( - ('bonds', 2), - ('angles', 3), - ('dihedrals', 4), - ('impropers', 4), - ) + "attr,n", + ( + ("bonds", 2), + ("angles", 3), + ("dihedrals", 4), + ("impropers", 4), + ), ) def test_delete_wrong_number_of_atoms_error(self, universe, attr, n): idx = [(0, 1), (0, 1, 2), (8, 22, 1, 3), (5, 3, 4, 2)] - errmsg = ('{} must be an iterable of ' - 'tuples with {} atom indices').format(attr, n) + errmsg = ( + "{} must be an iterable of " "tuples with {} atom indices" + ).format(attr, n) self._check_invalid_deleted(universe, attr, idx, errmsg) - @pytest.mark.parametrize( - 'attr,values', existing_atom_indices - ) + @pytest.mark.parametrize("attr,values", existing_atom_indices) def test_delete_missing_attr(self, attr, values): u = make_Universe() assert not hasattr(u, attr) - _delete_func = getattr(u, 'delete_'+attr) + _delete_func = getattr(u, "delete_" + attr) with pytest.raises(ValueError) as excinfo: _delete_func(values) assert "There are no" in str(excinfo.value) @@ -1248,30 +1348,27 @@ def test_delete_bonds_refresh_fragments(self, universe): universe.delete_bonds([universe.atoms[[2, 3]]]) assert len(universe.atoms.fragments) == n_fragments + 1 - @pytest.mark.parametrize("filename, n_bonds", [ - (CONECT, 72), - (PDB_conect, 8) - ]) + @pytest.mark.parametrize( + "filename, n_bonds", [(CONECT, 72), (PDB_conect, 8)] + ) def test_delete_all_bonds(self, filename, n_bonds): u = mda.Universe(filename) assert len(u.bonds) == n_bonds u.delete_bonds(u.bonds) assert len(u.bonds) == 0 - @pytest.mark.parametrize( - 'attr,values', existing_atom_indices - ) + @pytest.mark.parametrize("attr,values", existing_atom_indices) def test_roundtrip(self, universe, attr, values): u_attr = getattr(universe, attr) original_length = len(self.TOP[attr]) assert len(u_attr) == original_length - _delete_func = getattr(universe, 'delete_'+attr) + _delete_func = getattr(universe, "delete_" + attr) _delete_func(values) nu_attr = getattr(universe, attr) - assert len(nu_attr) == original_length-len(values) + assert len(nu_attr) == original_length - len(values) - _add_func = getattr(universe, 'add_'+attr) + _add_func = getattr(universe, "add_" + attr) _add_func(values) nu_attr = getattr(universe, attr) assert len(nu_attr) == original_length @@ -1280,36 +1377,39 @@ def test_roundtrip(self, universe, attr, values): class TestAllCoordinatesKwarg(object): - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def u_GRO_TRR(self): return mda.Universe(GRO, TRR) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def u_GRO_TRR_allcoords(self): return mda.Universe(GRO, TRR, all_coordinates=True) - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def u_GRO(self): return mda.Universe(GRO) def test_all_coordinates_length(self, u_GRO_TRR, u_GRO_TRR_allcoords): # length with all_coords should be +1 - assert (len(u_GRO_TRR.trajectory) + 1 == - len(u_GRO_TRR_allcoords.trajectory)) + assert len(u_GRO_TRR.trajectory) + 1 == len( + u_GRO_TRR_allcoords.trajectory + ) def test_all_coordinates_frame(self, u_GRO_TRR_allcoords, u_GRO): # check that first frame in u(GRO, TRR, allcords) # are the coordinates from GRO - assert_array_equal(u_GRO_TRR_allcoords.atoms.positions, - u_GRO.atoms.positions) + assert_array_equal( + u_GRO_TRR_allcoords.atoms.positions, u_GRO.atoms.positions + ) def test_second_frame(self, u_GRO_TRR_allcoords, u_GRO_TRR): # check that second frame in u(GRO, TRR, allcoords) # are the coordinates from TRR[0] u_GRO_TRR_allcoords.trajectory[1] - assert_array_equal(u_GRO_TRR_allcoords.atoms.positions, - u_GRO_TRR.atoms.positions) + assert_array_equal( + u_GRO_TRR_allcoords.atoms.positions, u_GRO_TRR.atoms.positions + ) class TestEmpty(object): @@ -1322,10 +1422,10 @@ def test_empty(self): def test_empty_extra(self): u = mda.Universe.empty( - n_atoms=12, n_residues=3, n_segments=2, - atom_resindex=np.array([0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, - 2, 2]), + n_atoms=12, + n_residues=3, + n_segments=2, + atom_resindex=np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2]), residue_segindex=np.array([0, 0, 1]), ) @@ -1345,12 +1445,12 @@ def test_no_resindex_warning(self): u = mda.Universe.empty(n_atoms=10, n_residues=2, n_segments=1) def test_no_segindex_warning(self): - res = np.array([0, 0, 0, 0, 0, - 1, 1, 1, 1, 1]) + res = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) with pytest.warns(UserWarning): - u = mda.Universe.empty(n_atoms=10, n_residues=2, n_segments=2, - atom_resindex=res) + u = mda.Universe.empty( + n_atoms=10, n_residues=2, n_segments=2, atom_resindex=res + ) def test_no_trivial_warning(self): """ @@ -1376,7 +1476,7 @@ def test_trajectory_iteration(self): u = mda.Universe.empty(10, trajectory=True) assert len(u.trajectory) == 1 - timesteps =[] + timesteps = [] for ts in u.trajectory: timesteps.append(ts.frame) assert len(timesteps) == 1 @@ -1402,7 +1502,7 @@ def test_empty_no_atoms(self): def test_empty_creation_raises_error(self): with pytest.raises(TypeError) as exc: u = mda.Universe() - assert 'Universe.empty' in str(exc.value) + assert "Universe.empty" in str(exc.value) def test_deprecate_b_tempfactors(): @@ -1419,7 +1519,7 @@ def __init__(self, val): class ThingyParser(TopologyReaderBase): - format='THINGY' + format = "THINGY" @staticmethod def _format_hint(thing): @@ -1434,8 +1534,7 @@ def test_only_top(self): # issue 3443 t = Thingy(20) - with pytest.warns(UserWarning, - match="No coordinate reader found for"): + with pytest.warns(UserWarning, match="No coordinate reader found for"): u = mda.Universe(t, to_guess=()) assert len(u.atoms) == 10 diff --git a/testsuite/MDAnalysisTests/core/test_unwrap.py b/testsuite/MDAnalysisTests/core/test_unwrap.py index fe69d945760..e6da223ee2b 100644 --- a/testsuite/MDAnalysisTests/core/test_unwrap.py +++ b/testsuite/MDAnalysisTests/core/test_unwrap.py @@ -22,8 +22,11 @@ # import numpy as np import re -from numpy.testing import (assert_raises, assert_almost_equal, - assert_array_equal) +from numpy.testing import ( + assert_raises, + assert_almost_equal, + assert_array_equal, +) import pytest import MDAnalysis as mda @@ -36,71 +39,79 @@ class TestUnwrap(object): """Tests the functionality of *Group.unwrap() using the UnWrapUniverse, which is specifically designed for wrapping and unwrapping tests. """ + precision = 5 - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) - @pytest.mark.parametrize('reference', ('com', 'cog', None)) - @pytest.mark.parametrize('is_triclinic', (False, True)) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) + @pytest.mark.parametrize("reference", ("com", "cog", None)) + @pytest.mark.parametrize("is_triclinic", (False, True)) def test_unwrap_pass(self, level, compound, reference, is_triclinic): # get a pristine test universe: u = UnWrapUniverse(is_triclinic=is_triclinic) # select group appropriate for compound: - if compound == 'group': - group = u.atoms[39:47] # molecule 12 - elif compound == 'segments': - group = u.atoms[23:47] # molecules 10, 11, 12 + if compound == "group": + group = u.atoms[39:47] # molecule 12 + elif compound == "segments": + group = u.atoms[23:47] # molecules 10, 11, 12 else: group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # store original positions: orig_pos = group.atoms.positions # get the expected result: ref_unwrapped_pos = u.unwrapped_coords(compound, reference) - if compound == 'group': - ref_unwrapped_pos = ref_unwrapped_pos[39:47] # molecule 12 - elif compound == 'segments': - ref_unwrapped_pos = ref_unwrapped_pos[23:47] # molecules 10, 11, 12 + if compound == "group": + ref_unwrapped_pos = ref_unwrapped_pos[39:47] # molecule 12 + elif compound == "segments": + ref_unwrapped_pos = ref_unwrapped_pos[ + 23:47 + ] # molecules 10, 11, 12 # first, do the unwrapping out-of-place: - unwrapped_pos = group.unwrap(compound=compound, reference=reference, - inplace=False) + unwrapped_pos = group.unwrap( + compound=compound, reference=reference, inplace=False + ) # check for correct result: - assert_almost_equal(unwrapped_pos, ref_unwrapped_pos, - decimal=self.precision) + assert_almost_equal( + unwrapped_pos, ref_unwrapped_pos, decimal=self.precision + ) # make sure atom positions are unchanged: assert_array_equal(group.atoms.positions, orig_pos) # now, do the unwrapping inplace: - unwrapped_pos2 = group.unwrap(compound=compound, reference=reference, - inplace=True) + unwrapped_pos2 = group.unwrap( + compound=compound, reference=reference, inplace=True + ) # check that result is the same as for out-of-place computation: assert_array_equal(unwrapped_pos, unwrapped_pos2) # check that unwrapped positions are applied: assert_array_equal(group.atoms.positions, unwrapped_pos) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) - @pytest.mark.parametrize('reference', ('com', 'cog', None)) - @pytest.mark.parametrize('is_triclinic', (False, True)) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) + @pytest.mark.parametrize("reference", ("com", "cog", None)) + @pytest.mark.parametrize("is_triclinic", (False, True)) def test_wrap_unwrap_cycle(self, level, compound, reference, is_triclinic): # get a pristine test universe: u = UnWrapUniverse(is_triclinic=is_triclinic) # select group appropriate for compound: - if compound == 'group': - group = u.atoms[39:47] # molecule 12 - elif compound == 'segments': - group = u.atoms[23:47] # molecules 10, 11, 12 + if compound == "group": + group = u.atoms[39:47] # molecule 12 + elif compound == "segments": + group = u.atoms[23:47] # molecules 10, 11, 12 else: group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # wrap: group.wrap() @@ -111,18 +122,20 @@ def test_wrap_unwrap_cycle(self, level, compound, reference, is_triclinic): # wrap again: group.wrap() # make sure wrapped atom positions are as before: - assert_almost_equal(group.atoms.positions, orig_wrapped_pos, - decimal=self.precision) + assert_almost_equal( + group.atoms.positions, orig_wrapped_pos, decimal=self.precision + ) - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) - @pytest.mark.parametrize('reference', ('com', 'cog', None)) - @pytest.mark.parametrize('is_triclinic', (False, True)) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) + @pytest.mark.parametrize("reference", ("com", "cog", None)) + @pytest.mark.parametrize("is_triclinic", (False, True)) def test_unwrap_partial_frags(self, compound, reference, is_triclinic): # get a pristine test universe: u = UnWrapUniverse(is_triclinic=is_triclinic) # select group with one atom missing - group = u.atoms[39:46] # molecule 12 without its last atom + group = u.atoms[39:46] # molecule 12 without its last atom # store original position of last atom of molecule 12: orig_pos = u.atoms[46].position # get the expected result: @@ -130,35 +143,41 @@ def test_unwrap_partial_frags(self, compound, reference, is_triclinic): # first, do the unwrapping out-of-place: group.unwrap(compound=compound, reference=reference, inplace=True) # check for correct result: - assert_almost_equal(group.positions, ref_unwrapped_pos, - decimal=self.precision) + assert_almost_equal( + group.positions, ref_unwrapped_pos, decimal=self.precision + ) # make sure the position of molecule 12's last atom is unchanged: assert_array_equal(u.atoms[46].position, orig_pos) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) - @pytest.mark.parametrize('reference', ('com', 'cog', None)) - @pytest.mark.parametrize('is_triclinic', (False, True)) - def test_unwrap_empty_group(self, level, compound, reference, is_triclinic): + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) + @pytest.mark.parametrize("reference", ("com", "cog", None)) + @pytest.mark.parametrize("is_triclinic", (False, True)) + def test_unwrap_empty_group( + self, level, compound, reference, is_triclinic + ): # get a pristine test universe: u = UnWrapUniverse(is_triclinic=is_triclinic) - if level == 'atoms': + if level == "atoms": group = mda.AtomGroup([], u) - elif level == 'residues': + elif level == "residues": group = mda.ResidueGroup([], u) - elif level == 'segments': + elif level == "segments": group = mda.SegmentGroup([], u) group.unwrap(compound=compound, reference=reference, inplace=True) # check for correct (empty) result: - assert_array_equal(group.atoms.positions, - np.empty((0, 3), dtype=np.float32)) + assert_array_equal( + group.atoms.positions, np.empty((0, 3), dtype=np.float32) + ) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) - @pytest.mark.parametrize('reference', ('com', 'cog', None)) - @pytest.mark.parametrize('is_triclinic', (False, True)) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) + @pytest.mark.parametrize("reference", ("com", "cog", None)) + @pytest.mark.parametrize("is_triclinic", (False, True)) def test_unwrap_duplicates(self, level, compound, reference, is_triclinic): # get a pristine test universe: u = UnWrapUniverse(is_triclinic=is_triclinic) @@ -167,9 +186,9 @@ def test_unwrap_duplicates(self, level, compound, reference, is_triclinic): # select the rest of the universe's atoms: rest = u.atoms[:39] # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # duplicate the group: group += group @@ -181,14 +200,16 @@ def test_unwrap_duplicates(self, level, compound, reference, is_triclinic): # unwrap: group.unwrap(compound=compound, reference=reference, inplace=True) # check for correct result: - assert_almost_equal(group.atoms.positions, ref_unwrapped_pos, - decimal=self.precision) + assert_almost_equal( + group.atoms.positions, ref_unwrapped_pos, decimal=self.precision + ) # check that the rest of the atoms are kept unmodified: assert_array_equal(rest.positions, orig_rest_pos) - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) - @pytest.mark.parametrize('is_triclinic', (False, True)) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) + @pytest.mark.parametrize("is_triclinic", (False, True)) def test_unwrap_com_cog_difference(self, compound, is_triclinic): # get a pristine test universe: u = UnWrapUniverse(is_triclinic=is_triclinic) @@ -200,140 +221,151 @@ def test_unwrap_com_cog_difference(self, compound, is_triclinic): # the first unit cell in negative x-direction. group.masses = [100.0, 1.0, 1.0] # unwrap with center of geometry as reference: - unwrapped_pos_cog = group.unwrap(compound=compound, reference='cog', - inplace=False) + unwrapped_pos_cog = group.unwrap( + compound=compound, reference="cog", inplace=False + ) # get expected result: - ref_unwrapped_pos = u.unwrapped_coords(compound, 'cog')[6:9] + ref_unwrapped_pos = u.unwrapped_coords(compound, "cog")[6:9] # check for correctness: - assert_almost_equal(unwrapped_pos_cog, ref_unwrapped_pos, - decimal=self.precision) + assert_almost_equal( + unwrapped_pos_cog, ref_unwrapped_pos, decimal=self.precision + ) # unwrap with center of mass as reference: - unwrapped_pos_com = group.unwrap(compound=compound, reference='com', - inplace=False) + unwrapped_pos_com = group.unwrap( + compound=compound, reference="com", inplace=False + ) # assert that the com result is shifted with respect to the cog result # by one box length in the x-direction: shift = np.array([10.0, 0.0, 0.0], dtype=np.float32) - assert_almost_equal(unwrapped_pos_cog, unwrapped_pos_com - shift, - decimal=self.precision) + assert_almost_equal( + unwrapped_pos_cog, + unwrapped_pos_com - shift, + decimal=self.precision, + ) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) def test_unwrap_zero_mass_exception_safety(self, level, compound): # get a pristine test universe: u = UnWrapUniverse() # set masses of molecule 12 to zero: u.atoms[39:47].masses = 0.0 # select group appropriate for compound: - if compound == 'group': - group = u.atoms[39:47] # molecule 12 - elif compound == 'segments': - group = u.atoms[23:47] # molecules 10, 11, 12 + if compound == "group": + group = u.atoms[39:47] # molecule 12 + elif compound == "segments": + group = u.atoms[23:47] # molecules 10, 11, 12 else: group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # store original positions: orig_pos = group.atoms.positions # try to unwrap: with pytest.raises(ValueError): - group.unwrap(compound=compound, reference='com', - inplace=True) + group.unwrap(compound=compound, reference="com", inplace=True) # make sure atom positions are unchanged: assert_array_equal(group.atoms.positions, orig_pos) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) def test_unwrap_wrong_reference_exception_safety(self, level, compound): # get a pristine test universe: u = UnWrapUniverse() # select group appropriate for compound: - if compound == 'group': - group = u.atoms[39:47] # molecule 12 - elif compound == 'segments': - group = u.atoms[23:47] # molecules 10, 11, 12 + if compound == "group": + group = u.atoms[39:47] # molecule 12 + elif compound == "segments": + group = u.atoms[23:47] # molecules 10, 11, 12 else: group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # store original positions: orig_pos = group.atoms.positions # try to unwrap: with pytest.raises(ValueError): - group.unwrap(compound=compound, reference='wrong', inplace=True) + group.unwrap(compound=compound, reference="wrong", inplace=True) # make sure atom positions are unchanged: assert_array_equal(group.atoms.positions, orig_pos) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('reference', ('com', 'cog', None)) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize("reference", ("com", "cog", None)) def test_unwrap_wrong_compound_exception_safety(self, level, reference): # get a pristine test universe: u = UnWrapUniverse() group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # store original positions: orig_pos = group.atoms.positions # try to unwrap: with pytest.raises(ValueError): - group.unwrap(compound='wrong', reference=reference, inplace=True) + group.unwrap(compound="wrong", reference=reference, inplace=True) # make sure atom positions are unchanged: assert_array_equal(group.atoms.positions, orig_pos) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) def test_unwrap_no_masses_exception_safety(self, level, compound): # universe without masses: u = UnWrapUniverse(have_masses=False) # select group appropriate for compound: - if compound == 'group': - group = u.atoms[39:47] # molecule 12 - elif compound == 'segments': - group = u.atoms[23:47] # molecules 10, 11, 12 + if compound == "group": + group = u.atoms[39:47] # molecule 12 + elif compound == "segments": + group = u.atoms[23:47] # molecules 10, 11, 12 else: group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # store original positions: orig_pos = group.atoms.positions # try to unwrap: with pytest.raises(NoDataError): - group.unwrap(compound=compound, reference='com', inplace=True) + group.unwrap(compound=compound, reference="com", inplace=True) # make sure atom positions are unchanged: assert_array_equal(group.atoms.positions, orig_pos) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('fragments', 'molecules', 'residues', - 'group', 'segments')) - @pytest.mark.parametrize('reference', ('com', 'cog', None)) - def test_unwrap_no_bonds_exception_safety(self, level, compound, reference): + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", ("fragments", "molecules", "residues", "group", "segments") + ) + @pytest.mark.parametrize("reference", ("com", "cog", None)) + def test_unwrap_no_bonds_exception_safety( + self, level, compound, reference + ): # universe without bonds: u = UnWrapUniverse(have_bonds=False) # select group appropriate for compound: - if compound == 'group': - group = u.atoms[39:47] # molecule 12 - elif compound == 'segments': - group = u.atoms[23:47] # molecules 10, 11, 12 + if compound == "group": + group = u.atoms[39:47] # molecule 12 + elif compound == "segments": + group = u.atoms[23:47] # molecules 10, 11, 12 else: group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # store original positions: orig_pos = group.atoms.positions @@ -348,30 +380,30 @@ def test_unwrap_no_bonds_exception_safety(self, level, compound, reference): # make sure atom positions are unchanged: assert_array_equal(group.atoms.positions, orig_pos) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('reference', ('com', 'cog', None)) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize("reference", ("com", "cog", None)) def test_unwrap_no_molnums_exception_safety(self, level, reference): # universe without molnums: u = UnWrapUniverse(have_molnums=False) group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # store original positions: orig_pos = group.atoms.positions with pytest.raises(NoDataError): - group.unwrap(compound='molecules', reference=reference, - inplace=True) + group.unwrap( + compound="molecules", reference=reference, inplace=True + ) assert_array_equal(group.atoms.positions, orig_pos) def test_uncontiguous(): - """Real-life case of fragment sparsity that triggers Issue 3352 - """ + """Real-life case of fragment sparsity that triggers Issue 3352""" precision = 5 - displacement_vec = [14.7, 0., 0.] + displacement_vec = [14.7, 0.0, 0.0] u = mda.Universe(CONECT) # This is one of the few residues that has bonds ag = u.residues[66].atoms @@ -380,10 +412,15 @@ def test_uncontiguous(): u.atoms.positions -= displacement_vec u.atoms.pack_into_box() # Let's make sure we really broke the fragment - assert_raises(AssertionError, assert_almost_equal, - ref_pos, ag.positions+displacement_vec, - decimal=precision) + assert_raises( + AssertionError, + assert_almost_equal, + ref_pos, + ag.positions + displacement_vec, + decimal=precision, + ) # Ok, let's make it whole again and check that we're good u.atoms.unwrap() - assert_almost_equal(ref_pos, ag.positions+displacement_vec, - decimal=precision) + assert_almost_equal( + ref_pos, ag.positions + displacement_vec, decimal=precision + ) diff --git a/testsuite/MDAnalysisTests/core/test_updating_atomgroup.py b/testsuite/MDAnalysisTests/core/test_updating_atomgroup.py index 51c3eecf500..fc41670a6ee 100644 --- a/testsuite/MDAnalysisTests/core/test_updating_atomgroup.py +++ b/testsuite/MDAnalysisTests/core/test_updating_atomgroup.py @@ -44,18 +44,19 @@ def ag(self, u): @pytest.fixture() def ag_updating(self, u): - return u.select_atoms("prop x < 5 and prop y < 5 and prop z < 5", - updating=True) + return u.select_atoms( + "prop x < 5 and prop y < 5 and prop z < 5", updating=True + ) @pytest.fixture() def ag_updating_compounded(self, u, ag): - return u.select_atoms("around 2 group sele", - sele=ag, updating=True) + return u.select_atoms("around 2 group sele", sele=ag, updating=True) @pytest.fixture() def ag_updating_chained(self, u, ag_updating): - return u.select_atoms("around 2 group sele", - sele=ag_updating, updating=True) + return u.select_atoms( + "around 2 group sele", sele=ag_updating, updating=True + ) @pytest.fixture() def ag_updating_chained2(self, ag_updating): @@ -63,9 +64,24 @@ def ag_updating_chained2(self, ag_updating): def test_update(self, u, ag, ag_updating): assert_equal(ag_updating.indices, ag.indices) - target_idxs = np.array([4469, 4470, 4472, 6289, 6290, 6291, - 6292, 31313, 31314, 31315, 31316, 34661, - 34663, 34664]) + target_idxs = np.array( + [ + 4469, + 4470, + 4472, + 6289, + 6290, + 6291, + 6292, + 31313, + 31314, + 31315, + 31316, + 34661, + 34663, + 34664, + ] + ) next(u.trajectory) assert_equal(ag_updating._lastupdate, 0) assert ag_updating.is_uptodate is False @@ -75,30 +91,30 @@ def test_update(self, u, ag, ag_updating): assert ag_updating._lastupdate is None def test_compounded_update(self, u, ag_updating_compounded): - target_idxs0 = np.array([3650, 7406, 22703, 31426, 40357, - 40360, 41414]) - target_idxs1 = np.array([3650, 8146, 23469, 23472, 31426, - 31689, 31692, 34326, 41414]) - assert_equal(ag_updating_compounded.indices, - target_idxs0) + target_idxs0 = np.array( + [3650, 7406, 22703, 31426, 40357, 40360, 41414] + ) + target_idxs1 = np.array( + [3650, 8146, 23469, 23472, 31426, 31689, 31692, 34326, 41414] + ) + assert_equal(ag_updating_compounded.indices, target_idxs0) next(u.trajectory) - assert_equal(ag_updating_compounded.indices, - target_idxs1) + assert_equal(ag_updating_compounded.indices, target_idxs1) - def test_chained_update(self, u, ag_updating_chained, - ag_updating_compounded): + def test_chained_update( + self, u, ag_updating_chained, ag_updating_compounded + ): target_idxs = np.array([4471, 7406, 11973, 11975, 34662, 44042]) - assert_equal(ag_updating_chained.indices, - ag_updating_compounded.indices) + assert_equal( + ag_updating_chained.indices, ag_updating_compounded.indices + ) next(u.trajectory) assert_equal(ag_updating_chained.indices, target_idxs) def test_chained_update2(self, u, ag_updating, ag_updating_chained2): - assert_equal(ag_updating_chained2.indices, - ag_updating.indices) + assert_equal(ag_updating_chained2.indices, ag_updating.indices) next(u.trajectory) - assert_equal(ag_updating_chained2.indices, - ag_updating.indices) + assert_equal(ag_updating_chained2.indices, ag_updating.indices) def test_slice_is_static(self, u, ag, ag_updating): ag_static1 = ag_updating[:] @@ -168,7 +184,7 @@ class UAGReader(mda.coordinates.base.ReaderBase): """ def __init__(self, n_atoms): - super(UAGReader, self).__init__('UAGReader') + super(UAGReader, self).__init__("UAGReader") self._auxs = {} self.n_frames = 10 @@ -198,32 +214,34 @@ def _read_frame(self, frame): class TestUAGCallCount(object): # make sure updates are only called when required! - # + # # these tests check that potentially expensive selection operations are only # done when necessary @pytest.fixture() def u(self): - u = make_Universe(('names',)) + u = make_Universe(("names",)) u.trajectory = UAGReader(125) return u - @mock.patch.object(MDAnalysis.core.groups.UpdatingAtomGroup, - 'update_selection', - autospec=True, - # required to make it get self when called - ) + @mock.patch.object( + MDAnalysis.core.groups.UpdatingAtomGroup, + "update_selection", + autospec=True, + # required to make it get self when called + ) def test_updated_when_creating(self, mock_update_selection, u): - uag = u.select_atoms('name XYZ', updating=True) + uag = u.select_atoms("name XYZ", updating=True) assert mock_update_selection.call_count == 1 def test_updated_when_next(self, u): - uag = u.select_atoms('name XYZ', updating=True) + uag = u.select_atoms("name XYZ", updating=True) # Use mock.patch.object to start inspecting the uag update selection method # wraps= keyword makes it still function as normal, just we're spying on it now - with mock.patch.object(uag, 'update_selection', - wraps=uag.update_selection) as mock_update: + with mock.patch.object( + uag, "update_selection", wraps=uag.update_selection + ) as mock_update: next(u.trajectory) assert mock_update.call_count == 0 @@ -237,16 +255,16 @@ def test_updated_when_next(self, u): class TestDynamicUAG(object): @pytest.fixture() def u(self): - u = make_Universe(('names',)) + u = make_Universe(("names",)) u.trajectory = UAGReader(125) return u def test_nested_uags(self, u): bg = u.atoms[[3, 4]] - uag1 = u.select_atoms('around 1.5 group bg', bg=bg, updating=True) + uag1 = u.select_atoms("around 1.5 group bg", bg=bg, updating=True) - uag2 = u.select_atoms('around 1.5 group uag', uag=uag1, updating=True) + uag2 = u.select_atoms("around 1.5 group uag", uag=uag1, updating=True) for ts in u.trajectory: assert_equal(len(bg), 2) @@ -254,7 +272,7 @@ def test_nested_uags(self, u): assert_equal(len(uag2), 4) # doesn't include uag1 def test_driveby(self, u): - uag = u.select_atoms('prop x < 5.5', updating=True) + uag = u.select_atoms("prop x < 5.5", updating=True) n_init = 6 for i, ts in enumerate(u.trajectory): @@ -277,8 +295,9 @@ def test_representations(): rep = repr(ag_updating) assert "1 atom," in rep - ag_updating = u.atoms[:-1].select_atoms("bynum 1", "bynum 2", - updating=True) + ag_updating = u.atoms[:-1].select_atoms( + "bynum 1", "bynum 2", updating=True + ) rep = repr(ag_updating) assert "2 atoms," in rep assert "selections 'bynum 1' + 'bynum 2'" in rep @@ -289,6 +308,6 @@ def test_empty_UAG(): u = make_Universe() # technically possible to make a UAG without any selections.. - uag = mda.core.groups.UpdatingAtomGroup(u.atoms, (), '') + uag = mda.core.groups.UpdatingAtomGroup(u.atoms, (), "") assert isinstance(uag, mda.core.groups.UpdatingAtomGroup) diff --git a/testsuite/MDAnalysisTests/core/test_wrap.py b/testsuite/MDAnalysisTests/core/test_wrap.py index 186ac3dadee..76b0263a8e2 100644 --- a/testsuite/MDAnalysisTests/core/test_wrap.py +++ b/testsuite/MDAnalysisTests/core/test_wrap.py @@ -29,6 +29,7 @@ from MDAnalysisTests.core.util import UnWrapUniverse, assert_in_box from MDAnalysisTests.datafiles import TRZ_psf, TRZ + class TestWrap(object): """Tests the functionality of *Group.wrap() using the UnWrapUniverse, which is specifically designed for wrapping and unwrapping tests. @@ -36,66 +37,74 @@ class TestWrap(object): precision = 5 - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('atoms', 'group', 'segments', - 'residues', 'molecules', 'fragments')) - @pytest.mark.parametrize('center', ('com', 'cog')) - @pytest.mark.parametrize('is_triclinic', (False, True)) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", + ("atoms", "group", "segments", "residues", "molecules", "fragments"), + ) + @pytest.mark.parametrize("center", ("com", "cog")) + @pytest.mark.parametrize("is_triclinic", (False, True)) def test_wrap_pass(self, level, compound, center, is_triclinic): # get a pristine test universe: u = UnWrapUniverse(is_triclinic=is_triclinic) group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # store original positions: orig_pos = group.atoms.positions # get the expected result: ref_wrapped_pos = u.wrapped_coords(compound, center) # first, do the wrapping out-of-place: - wrapped_pos = group.wrap(compound=compound, center=center, - inplace=False) + wrapped_pos = group.wrap( + compound=compound, center=center, inplace=False + ) # check for correct result: - assert_almost_equal(wrapped_pos, ref_wrapped_pos, - decimal=self.precision) + assert_almost_equal( + wrapped_pos, ref_wrapped_pos, decimal=self.precision + ) # make sure atom positions are unchanged: assert_array_equal(group.atoms.positions, orig_pos) # now, do the wrapping inplace: - wrapped_pos2 = group.wrap(compound=compound, center=center, - inplace=True) + wrapped_pos2 = group.wrap( + compound=compound, center=center, inplace=True + ) # check that result is the same as for out-of-place computation: assert_array_equal(wrapped_pos, wrapped_pos2) # check that wrapped positions are applied: assert_array_equal(group.atoms.positions, wrapped_pos) # check that nobody messed with the reference positions, # centers of compounds must lie within the primary unit cell: - if compound == 'atoms': + if compound == "atoms": assert_in_box(group.atoms.positions, group.dimensions) - elif center == 'com': + elif center == "com": compos = group.atoms.center_of_mass(wrap=False, compound=compound) assert_in_box(compos, group.dimensions) else: - cogpos = group.atoms.center_of_geometry(wrap=False, - compound=compound) + cogpos = group.atoms.center_of_geometry( + wrap=False, compound=compound + ) assert_in_box(cogpos, group.dimensions) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('atoms', 'group', 'segments', - 'residues', 'molecules', 'fragments')) - @pytest.mark.parametrize('center', ('com', 'cog')) - @pytest.mark.parametrize('is_triclinic', (False, True)) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", + ("atoms", "group", "segments", "residues", "molecules", "fragments"), + ) + @pytest.mark.parametrize("center", ("com", "cog")) + @pytest.mark.parametrize("is_triclinic", (False, True)) def test_unwrap_wrap_cycle(self, level, compound, center, is_triclinic): # get a pristine test universe: u = UnWrapUniverse(is_triclinic=is_triclinic) # set wrapped reference coordinates: - u.atoms.positions = u.wrapped_coords('atoms', 'com') + u.atoms.positions = u.wrapped_coords("atoms", "com") group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # unwrap: group.unwrap() @@ -106,34 +115,41 @@ def test_unwrap_wrap_cycle(self, level, compound, center, is_triclinic): # unwrap again: group.unwrap() # make sure unwrapped atom positions are as before: - assert_almost_equal(group.atoms.positions, orig_unwrapped_pos, - decimal=self.precision) + assert_almost_equal( + group.atoms.positions, orig_unwrapped_pos, decimal=self.precision + ) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('atoms', 'group', 'segments', - 'residues', 'molecules', 'fragments')) - @pytest.mark.parametrize('center', ('com', 'cog')) - @pytest.mark.parametrize('is_triclinic', (False, True)) - def test_wrap_partial_compound(self, level, compound, center, is_triclinic): + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", + ("atoms", "group", "segments", "residues", "molecules", "fragments"), + ) + @pytest.mark.parametrize("center", ("com", "cog")) + @pytest.mark.parametrize("is_triclinic", (False, True)) + def test_wrap_partial_compound( + self, level, compound, center, is_triclinic + ): # get a pristine test universe: u = UnWrapUniverse(is_triclinic=is_triclinic) group = u.atoms ref_wrapped_pos = u.wrapped_coords(compound, center) # select topology level with one missing item and get expected result: - if level == 'atoms': + if level == "atoms": # first atom of molecule 12 missing missing = group[[-8]] group = group[:-8] + group[-7:] - ref_wrapped_pos = np.concatenate([ref_wrapped_pos[:-8], - ref_wrapped_pos[-7:]]) - elif level == 'residues': + ref_wrapped_pos = np.concatenate( + [ref_wrapped_pos[:-8], ref_wrapped_pos[-7:]] + ) + elif level == "residues": group = group.residues # first residue of molecule 12 missing missing = group[-2] group = group[:-2] + group[[-1]] - ref_wrapped_pos = np.concatenate([ref_wrapped_pos[:-8], - ref_wrapped_pos[-4:]]) - elif level == 'segments': + ref_wrapped_pos = np.concatenate( + [ref_wrapped_pos[:-8], ref_wrapped_pos[-4:]] + ) + elif level == "segments": group = group.segments # molecule 12 missing missing = group[-1] @@ -144,34 +160,40 @@ def test_wrap_partial_compound(self, level, compound, center, is_triclinic): # first, do the wrapping out-of-place: group.wrap(compound=compound, center=center, inplace=True) # check for correct result: - assert_almost_equal(group.atoms.positions, ref_wrapped_pos, - decimal=self.precision) + assert_almost_equal( + group.atoms.positions, ref_wrapped_pos, decimal=self.precision + ) # make sure the positions of the missing item are unchanged: assert_array_equal(missing.atoms.positions, orig_pos) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('atoms', 'group', 'segments', - 'residues', 'molecules', 'fragments')) - @pytest.mark.parametrize('center', ('com', 'cog')) - @pytest.mark.parametrize('is_triclinic', (False, True)) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", + ("atoms", "group", "segments", "residues", "molecules", "fragments"), + ) + @pytest.mark.parametrize("center", ("com", "cog")) + @pytest.mark.parametrize("is_triclinic", (False, True)) def test_wrap_empty_group(self, level, compound, center, is_triclinic): # get a pristine test universe: u = UnWrapUniverse(is_triclinic=is_triclinic) group = u.atoms[[]] - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments group.wrap(compound=compound, center=center, inplace=True) # check for correct (empty) result: - assert_array_equal(group.atoms.positions, - np.empty((0, 3), dtype=np.float32)) + assert_array_equal( + group.atoms.positions, np.empty((0, 3), dtype=np.float32) + ) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('atoms', 'group', 'segments', - 'residues', 'molecules', 'fragments')) - @pytest.mark.parametrize('center', ('com', 'cog')) - @pytest.mark.parametrize('is_triclinic', (False, True)) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", + ("atoms", "group", "segments", "residues", "molecules", "fragments"), + ) + @pytest.mark.parametrize("center", ("com", "cog")) + @pytest.mark.parametrize("is_triclinic", (False, True)) def test_wrap_duplicates(self, level, compound, center, is_triclinic): # get a pristine test universe: u = UnWrapUniverse(is_triclinic=is_triclinic) @@ -180,34 +202,37 @@ def test_wrap_duplicates(self, level, compound, center, is_triclinic): # select the rest of the universe's atoms: rest = u.atoms[:39] # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # duplicate the group: group += group # store original positions of the rest: orig_rest_pos = rest.positions # get the expected result with duplicates: - if compound == 'group': + if compound == "group": # reference positions of UnWrapUniverse are known to be incorrect # for compound='group' if the group is not the entire system, so we # have to correct for that: - ref_wrapped_pos = u.wrapped_coords('segments', center)[39:47] + ref_wrapped_pos = u.wrapped_coords("segments", center)[39:47] else: ref_wrapped_pos = u.wrapped_coords(compound, center)[39:47] ref_wrapped_pos = np.vstack((ref_wrapped_pos, ref_wrapped_pos)) # wrap: group.wrap(compound=compound, center=center, inplace=True) # check for correct result: - assert_almost_equal(group.atoms.positions, ref_wrapped_pos, - decimal=self.precision) + assert_almost_equal( + group.atoms.positions, ref_wrapped_pos, decimal=self.precision + ) # check that the rest of the atoms are kept unmodified: assert_array_equal(rest.positions, orig_rest_pos) - @pytest.mark.parametrize('compound', ('atoms', 'group', 'segments', - 'residues', 'molecules', 'fragments')) - @pytest.mark.parametrize('is_triclinic', (False, True)) + @pytest.mark.parametrize( + "compound", + ("atoms", "group", "segments", "residues", "molecules", "fragments"), + ) + @pytest.mark.parametrize("is_triclinic", (False, True)) def test_wrap_com_cog_difference(self, compound, is_triclinic): # get a pristine test universe: u = UnWrapUniverse(is_triclinic=is_triclinic) @@ -219,145 +244,160 @@ def test_wrap_com_cog_difference(self, compound, is_triclinic): # the first unit cell in negative x-direction. group.masses = [100.0, 1.0, 1.0] # wrap with center='cog': - wrapped_pos_cog = group.wrap(compound=compound, center='cog', - inplace=False) + wrapped_pos_cog = group.wrap( + compound=compound, center="cog", inplace=False + ) # get expected result: - ref_wrapped_pos = u.wrapped_coords(compound, 'cog')[6:9] + ref_wrapped_pos = u.wrapped_coords(compound, "cog")[6:9] # check for correctness: - assert_almost_equal(wrapped_pos_cog, ref_wrapped_pos, - decimal=self.precision) + assert_almost_equal( + wrapped_pos_cog, ref_wrapped_pos, decimal=self.precision + ) # wrap with center='com': - wrapped_pos_com = group.wrap(compound=compound, center='com', - inplace=False) + wrapped_pos_com = group.wrap( + compound=compound, center="com", inplace=False + ) # assert that the com result is shifted with respect to the cog result # by one box length in the x-direction: shift = np.array([10.0, 0.0, 0.0], dtype=np.float32) - if compound == 'atoms': + if compound == "atoms": # center argument must be ignored for compound='atoms': shift[0] = 0.0 - assert_almost_equal(wrapped_pos_cog, wrapped_pos_com - shift, - decimal=self.precision) + assert_almost_equal( + wrapped_pos_cog, wrapped_pos_com - shift, decimal=self.precision + ) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('atoms', 'group', 'segments', - 'residues', 'molecules', 'fragments')) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", + ("atoms", "group", "segments", "residues", "molecules", "fragments"), + ) def test_wrap_zero_mass_exception_safety(self, level, compound): # get a pristine test universe: u = UnWrapUniverse() # set masses of molecule 12 to zero: u.atoms[39:47].masses = 0.0 # select group appropriate for compound: - if compound == 'group': - group = u.atoms[39:47] # molecule 12 + if compound == "group": + group = u.atoms[39:47] # molecule 12 else: group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # store original positions: orig_pos = group.atoms.positions - if compound == 'atoms': + if compound == "atoms": # wrap() must not care about masses if compound == 'atoms': - group.wrap(compound=compound, center='com', inplace=True) - ref_wrapped_pos = u.wrapped_coords(compound, 'com') - assert_almost_equal(group.atoms.positions, ref_wrapped_pos, - decimal=self.precision) + group.wrap(compound=compound, center="com", inplace=True) + ref_wrapped_pos = u.wrapped_coords(compound, "com") + assert_almost_equal( + group.atoms.positions, ref_wrapped_pos, decimal=self.precision + ) else: # try to wrap: with pytest.raises(ValueError): - group.wrap(compound=compound, center='com', inplace=True) + group.wrap(compound=compound, center="com", inplace=True) # make sure atom positions are unchanged: assert_array_equal(group.atoms.positions, orig_pos) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('atoms', 'group', 'segments', - 'residues', 'molecules', 'fragments')) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", + ("atoms", "group", "segments", "residues", "molecules", "fragments"), + ) def test_wrap_wrong_center_exception_safety(self, level, compound): # get a pristine test universe: u = UnWrapUniverse() group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # store original positions: orig_pos = group.atoms.positions - if compound == 'atoms': + if compound == "atoms": # wrap() must ignore the center argument if compound == 'atoms': - group.wrap(compound=compound, center='com', inplace=True) - ref_wrapped_pos = u.wrapped_coords(compound, 'com') - assert_almost_equal(group.atoms.positions, ref_wrapped_pos, - decimal=self.precision) + group.wrap(compound=compound, center="com", inplace=True) + ref_wrapped_pos = u.wrapped_coords(compound, "com") + assert_almost_equal( + group.atoms.positions, ref_wrapped_pos, decimal=self.precision + ) else: # try to wrap: with pytest.raises(ValueError): - group.wrap(compound=compound, center='wrong', inplace=True) + group.wrap(compound=compound, center="wrong", inplace=True) # make sure atom positions are unchanged: assert_array_equal(group.atoms.positions, orig_pos) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('center', ('com', 'cog')) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize("center", ("com", "cog")) def test_wrap_wrong_compound_exception_safety(self, level, center): # get a pristine test universe: u = UnWrapUniverse() group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments # store original positions: orig_pos = group.atoms.positions # try to wrap: with pytest.raises(ValueError): - group.wrap(compound='wrong', center=center, inplace=True) + group.wrap(compound="wrong", center=center, inplace=True) # make sure atom positions are unchanged: assert_array_equal(group.atoms.positions, orig_pos) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('atoms', 'group', 'segments', - 'residues', 'molecules', 'fragments')) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", + ("atoms", "group", "segments", "residues", "molecules", "fragments"), + ) def test_unwrap_no_masses_exception_safety(self, level, compound): # universe without masses: u = UnWrapUniverse(have_masses=False) group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments - if compound == 'atoms': + if compound == "atoms": # wrap() must not care about mass presence if compound == 'atoms': - group.wrap(compound=compound, center='com', inplace=True) - ref_wrapped_pos = u.wrapped_coords(compound, 'com') - assert_almost_equal(group.atoms.positions, ref_wrapped_pos, - decimal=self.precision) + group.wrap(compound=compound, center="com", inplace=True) + ref_wrapped_pos = u.wrapped_coords(compound, "com") + assert_almost_equal( + group.atoms.positions, ref_wrapped_pos, decimal=self.precision + ) else: # store original positions: orig_pos = group.atoms.positions # try to wrap: with pytest.raises(NoDataError): - group.wrap(compound=compound, center='com', inplace=True) + group.wrap(compound=compound, center="com", inplace=True) # make sure atom positions are unchanged: assert_array_equal(group.atoms.positions, orig_pos) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('atoms', 'group', 'segments', - 'residues', 'molecules', 'fragments')) - @pytest.mark.parametrize('center', ('com', 'cog')) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", + ("atoms", "group", "segments", "residues", "molecules", "fragments"), + ) + @pytest.mark.parametrize("center", ("com", "cog")) def test_wrap_no_bonds_exception_safety(self, level, compound, center): # universe without bonds: u = UnWrapUniverse(have_bonds=False) group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments - if compound == 'fragments': + if compound == "fragments": # store original positions: orig_pos = group.atoms.positions # must raise an exception for fragments @@ -369,23 +409,26 @@ def test_wrap_no_bonds_exception_safety(self, level, compound, center): # must not care about bonds if compound != 'fragments' group.wrap(compound=compound, center=center, inplace=True) ref_wrapped_pos = u.wrapped_coords(compound, center) - assert_almost_equal(group.atoms.positions, ref_wrapped_pos, - decimal=self.precision) + assert_almost_equal( + group.atoms.positions, ref_wrapped_pos, decimal=self.precision + ) - @pytest.mark.parametrize('level', ('atoms', 'residues', 'segments')) - @pytest.mark.parametrize('compound', ('atoms', 'group', 'segments', - 'residues', 'molecules', 'fragments')) - @pytest.mark.parametrize('center', ('com', 'cog')) + @pytest.mark.parametrize("level", ("atoms", "residues", "segments")) + @pytest.mark.parametrize( + "compound", + ("atoms", "group", "segments", "residues", "molecules", "fragments"), + ) + @pytest.mark.parametrize("center", ("com", "cog")) def test_wrap_no_molnums_exception_safety(self, level, compound, center): # universe without bonds: u = UnWrapUniverse(have_molnums=False) group = u.atoms # select topology level: - if level == 'residues': + if level == "residues": group = group.residues - elif level == 'segments': + elif level == "segments": group = group.segments - if compound == 'molecules': + if compound == "molecules": # store original positions: orig_pos = group.atoms.positions # must raise an exception for molecules @@ -397,8 +440,10 @@ def test_wrap_no_molnums_exception_safety(self, level, compound, center): # must not care about molnums if compound != 'molecules' group.wrap(compound=compound, center=center, inplace=True) ref_wrapped_pos = u.wrapped_coords(compound, center) - assert_almost_equal(group.atoms.positions, ref_wrapped_pos, - decimal=self.precision) + assert_almost_equal( + group.atoms.positions, ref_wrapped_pos, decimal=self.precision + ) + class TestWrapTRZ(object): """Tests the functionality of AtomGroup.wrap() using a TRZ universe.""" @@ -409,7 +454,7 @@ class TestWrapTRZ(object): def u(self): return mda.Universe(TRZ_psf, TRZ) - @pytest.mark.parametrize('box', (np.array([1, 1]), np.zeros(6))) + @pytest.mark.parametrize("box", (np.array([1, 1]), np.zeros(6))) def test_wrap_box_fail(self, u, box): ag = u.atoms with pytest.raises(ValueError): @@ -431,47 +476,47 @@ def test_wrap_large_box(self, u): def test_wrap_atoms(self, u): ag = u.atoms[100:200] - rescom = ag.wrap(compound='atoms', center='com', inplace=False) + rescom = ag.wrap(compound="atoms", center="com", inplace=False) assert_in_box(rescom, u.dimensions) - rescog = ag.wrap(compound='atoms', center='cog', inplace=False) + rescog = ag.wrap(compound="atoms", center="cog", inplace=False) assert_almost_equal(rescom, rescog, decimal=self.precision) - @pytest.mark.parametrize('center', ('com', 'cog')) + @pytest.mark.parametrize("center", ("com", "cog")) def test_wrap_group(self, u, center): ag = u.atoms[:100] - ag.wrap(compound='group', center=center) - if center == 'com': - ctrpos = ag.center_of_mass(wrap=False, compound='group') - elif center == 'cog': - ctrpos = ag.center_of_geometry(wrap=False, compound='group') + ag.wrap(compound="group", center=center) + if center == "com": + ctrpos = ag.center_of_mass(wrap=False, compound="group") + elif center == "cog": + ctrpos = ag.center_of_geometry(wrap=False, compound="group") assert_in_box(ctrpos, u.dimensions) - @pytest.mark.parametrize('center', ('com', 'cog')) + @pytest.mark.parametrize("center", ("com", "cog")) def test_wrap_residues(self, u, center): ag = u.atoms[300:400].residues - ag.wrap(compound='residues', center=center) - if center == 'com': - ctrpos = ag.center_of_mass(wrap=False, compound='residues') - elif center == 'cog': - ctrpos = ag.center_of_geometry(wrap=False, compound='residues') + ag.wrap(compound="residues", center=center) + if center == "com": + ctrpos = ag.center_of_mass(wrap=False, compound="residues") + elif center == "cog": + ctrpos = ag.center_of_geometry(wrap=False, compound="residues") assert_in_box(ctrpos, u.dimensions) - @pytest.mark.parametrize('center', ('com', 'cog')) + @pytest.mark.parametrize("center", ("com", "cog")) def test_wrap_segments(self, u, center): ag = u.atoms[1000:1200] - ag.wrap(compound='segments', center=center) - if center == 'com': - ctrpos = ag.center_of_mass(wrap=False, compound='segments') - elif center == 'cog': - ctrpos = ag.center_of_geometry(wrap=False, compound='segments') + ag.wrap(compound="segments", center=center) + if center == "com": + ctrpos = ag.center_of_mass(wrap=False, compound="segments") + elif center == "cog": + ctrpos = ag.center_of_geometry(wrap=False, compound="segments") assert_in_box(ctrpos, u.dimensions) - @pytest.mark.parametrize('center', ('com', 'cog')) + @pytest.mark.parametrize("center", ("com", "cog")) def test_wrap_fragments(self, u, center): ag = u.atoms[:250] - ag.wrap(compound='fragments', center=center) - if center == 'com': - ctrpos = ag.center_of_mass(wrap=False, compound='fragments') - elif center == 'cog': - ctrpos = ag.center_of_geometry(wrap=False, compound='fragments') + ag.wrap(compound="fragments", center=center) + if center == "com": + ctrpos = ag.center_of_mass(wrap=False, compound="fragments") + elif center == "cog": + ctrpos = ag.center_of_geometry(wrap=False, compound="fragments") assert_in_box(ctrpos, u.dimensions) diff --git a/testsuite/MDAnalysisTests/core/util.py b/testsuite/MDAnalysisTests/core/util.py index 42f2c3a8ed9..52b83d64d62 100644 --- a/testsuite/MDAnalysisTests/core/util.py +++ b/testsuite/MDAnalysisTests/core/util.py @@ -137,15 +137,21 @@ class UnWrapUniverse(object): y directions. """ - def __new__(cls, have_bonds=True, have_masses=True, have_molnums=True, - have_charges=True, is_triclinic=False): + def __new__( + cls, + have_bonds=True, + have_masses=True, + have_molnums=True, + have_charges=True, + is_triclinic=False, + ): # box: - a = 10.0 # edge length + a = 10.0 # edge length tfac = 0.1 # factor for box vector shift of triclinic boxes (~84°) if is_triclinic: - box = triclinic_box([a, 0.0, 0.0], - [a * tfac, a, 0.0], - [a * tfac, a * tfac, a]) + box = triclinic_box( + [a, 0.0, 0.0], [a * tfac, a, 0.0], [a * tfac, a * tfac, a] + ) else: box = np.array([a, a, a, 90.0, 90.0, 90.0], dtype=np.float32) @@ -163,11 +169,11 @@ def __new__(cls, have_bonds=True, have_masses=True, have_molnums=True, rix += 1 # type B for i in range(3, 15, 3): - residx[i:i+3] = rix + residx[i : i + 3] = rix rix += 1 # type C & D for i in range(15, 47, 4): - residx[i:i+4] = rix + residx[i : i + 4] = rix rix += 1 # segindices: @@ -194,17 +200,17 @@ def __new__(cls, have_bonds=True, have_masses=True, have_molnums=True, ) # resnames: we always want those for selection purposes - resnames = ['A'] * 3 - resnames += ['B'] * 4 - resnames += ['C'] * 2 - resnames += ['D1', 'D2'] * 3 + resnames = ["A"] * 3 + resnames += ["B"] * 4 + resnames += ["C"] * 2 + resnames += ["D1", "D2"] * 3 u.add_TopologyAttr(topologyattrs.Resnames(resnames)) # moltypes: we always want those for selection purposes - moltypes = ['A'] * 3 - moltypes += ['B'] * 4 - moltypes += ['C'] * 2 - moltypes += ['D'] * 6 + moltypes = ["A"] * 3 + moltypes += ["B"] * 4 + moltypes += ["C"] * 2 + moltypes += ["D"] * 6 u.add_TopologyAttr(topologyattrs.Moltypes(moltypes)) # trajectory: @@ -215,56 +221,72 @@ def __new__(cls, have_bonds=True, have_masses=True, have_molnums=True, # positions: relpos = np.empty((n_atoms, 3), dtype=np.float32) # type A - relpos[0:3, :] = np.array([[0.5, 0.5, 0.5], - [1.4, 0.5, 0.5], - [2.1, 0.5, 0.5]], dtype=np.float32) + relpos[0:3, :] = np.array( + [[0.5, 0.5, 0.5], [1.4, 0.5, 0.5], [2.1, 0.5, 0.5]], + dtype=np.float32, + ) # type B - relpos[3:15, :] = np.array([[0.1, 0.1, 0.2], - [0.1, 0.1, 0.1], - [0.2, 0.1, 0.1], - [-0.05, 0.2, 0.05], - [0.05, 0.2, 0.05], - [0.05, 0.2, 0.95], - [-0.2, -0.9, 1.05], - [-0.2, 0.1, -0.05], - [-0.1, 0.1, -0.05], - [0.95, 0.2, 0.25], - [0.95, 0.2, 0.15], - [1.05, 0.2, 0.15]], dtype=np.float32) + relpos[3:15, :] = np.array( + [ + [0.1, 0.1, 0.2], + [0.1, 0.1, 0.1], + [0.2, 0.1, 0.1], + [-0.05, 0.2, 0.05], + [0.05, 0.2, 0.05], + [0.05, 0.2, 0.95], + [-0.2, -0.9, 1.05], + [-0.2, 0.1, -0.05], + [-0.1, 0.1, -0.05], + [0.95, 0.2, 0.25], + [0.95, 0.2, 0.15], + [1.05, 0.2, 0.15], + ], + dtype=np.float32, + ) # type C - relpos[15:23, :] = np.array([[0.4, 0.95, 1.05], - [0.4, 0.95, 0.95], - [0.4, 0.05, 0.95], - [0.4, 0.05, 1.05], - [0.6, 0.05, 0.25], - [0.6, 0.05, 0.15], - [0.6, 0.15, 0.15], - [0.6, 0.15, 0.25]], dtype=np.float32) + relpos[15:23, :] = np.array( + [ + [0.4, 0.95, 1.05], + [0.4, 0.95, 0.95], + [0.4, 0.05, 0.95], + [0.4, 0.05, 1.05], + [0.6, 0.05, 0.25], + [0.6, 0.05, 0.15], + [0.6, 0.15, 0.15], + [0.6, 0.15, 0.25], + ], + dtype=np.float32, + ) # type D - relpos[23:47, :] = np.array([[0.2, 0.7, 0.8], - [0.3, 0.7, 0.8], - [0.4, 0.7, 0.8], - [0.5, 0.7, 0.8], - [0.6, 0.7, 0.8], - [0.7, 0.7, 0.8], - [0.8, 0.7, 0.8], - [0.9, 0.7, 0.8], - [0.66, 0.75, 0.7], - [0.76, 0.75, 0.7], - [0.86, 0.75, 0.7], - [0.96, 0.75, 0.7], - [0.06, 0.75, 0.7], - [0.16, 0.75, 0.7], - [0.26, 0.75, 0.7], - [0.36, 0.75, 0.7], - [1.14, 0.65, -0.4], - [1.04, 0.65, -0.4], - [0.94, 0.65, -0.4], - [0.84, 0.65, -0.4], - [0.74, 0.65, -0.4], - [0.64, 0.65, -0.4], - [0.54, 0.65, -0.4], - [0.44, 0.65, -0.4]], dtype=np.float32) + relpos[23:47, :] = np.array( + [ + [0.2, 0.7, 0.8], + [0.3, 0.7, 0.8], + [0.4, 0.7, 0.8], + [0.5, 0.7, 0.8], + [0.6, 0.7, 0.8], + [0.7, 0.7, 0.8], + [0.8, 0.7, 0.8], + [0.9, 0.7, 0.8], + [0.66, 0.75, 0.7], + [0.76, 0.75, 0.7], + [0.86, 0.75, 0.7], + [0.96, 0.75, 0.7], + [0.06, 0.75, 0.7], + [0.16, 0.75, 0.7], + [0.26, 0.75, 0.7], + [0.36, 0.75, 0.7], + [1.14, 0.65, -0.4], + [1.04, 0.65, -0.4], + [0.94, 0.65, -0.4], + [0.84, 0.65, -0.4], + [0.74, 0.65, -0.4], + [0.64, 0.65, -0.4], + [0.54, 0.65, -0.4], + [0.44, 0.65, -0.4], + ], + dtype=np.float32, + ) # make a copy, we need the original later _relpos = relpos.copy() # apply y- and z-dependent shift of x and y coords for triclinic boxes: @@ -280,7 +302,7 @@ def __new__(cls, have_bonds=True, have_masses=True, have_molnums=True, if have_bonds: bonds = [] # type A has no bonds - #type B + # type B for base in range(3, 15, 3): for i in range(2): bonds.append((base + i, base + i + 1)) @@ -353,12 +375,18 @@ def unwrapped_coords(self, compound, reference): """ if reference is not None: ref = reference.lower() - if ref not in ['com', 'cog']: - raise ValueError("Unknown unwrap reference: {}" - "".format(reference)) + if ref not in ["com", "cog"]: + raise ValueError( + "Unknown unwrap reference: {}" "".format(reference) + ) comp = compound.lower() - if comp not in ['group', 'segments', 'residues', 'molecules', - 'fragments']: + if comp not in [ + "group", + "segments", + "residues", + "molecules", + "fragments", + ]: raise ValueError("Unknown unwrap compound: {}".format(compound)) # get relative positions: @@ -374,67 +402,91 @@ def unwrapped_coords(self, compound, reference): relpos[18, :] = [0.4, 1.05, 1.05] # type D # molecule 11, residue 1 - relpos[35:39, :] = np.array([[1.06, 0.75, 0.7], - [1.16, 0.75, 0.7], - [1.26, 0.75, 0.7], - [1.36, 0.75, 0.7]], dtype=np.float32) + relpos[35:39, :] = np.array( + [ + [1.06, 0.75, 0.7], + [1.16, 0.75, 0.7], + [1.26, 0.75, 0.7], + [1.36, 0.75, 0.7], + ], + dtype=np.float32, + ) # apply image shifts if necessary: if reference is None: - if comp == 'residues': - #second residue of molecule 11 - relpos[35:39, :] = np.array([[0.06, 0.75, 0.7], - [0.16, 0.75, 0.7], - [0.26, 0.75, 0.7], - [0.36, 0.75, 0.7]], - dtype=np.float32) + if comp == "residues": + # second residue of molecule 11 + relpos[35:39, :] = np.array( + [ + [0.06, 0.75, 0.7], + [0.16, 0.75, 0.7], + [0.26, 0.75, 0.7], + [0.36, 0.75, 0.7], + ], + dtype=np.float32, + ) else: # molecule 2 & 3 - relpos[1:3, :] = np.array([[0.4, 0.5, 0.5], - [0.1, 0.5, 0.5]], dtype=np.float32) + relpos[1:3, :] = np.array( + [[0.4, 0.5, 0.5], [0.1, 0.5, 0.5]], dtype=np.float32 + ) # molecule 6 - relpos[9:12, :] = np.array([[0.8, 0.1, 1.05], - [0.8, 0.1, 0.95], - [0.9, 0.1, 0.95]], dtype=np.float32) - #molecule 8 - relpos[15:19, :] = np.array([[0.4, -0.05, 0.05], - [0.4, -0.05, -0.05], - [0.4, 0.05, -0.05], - [0.4, 0.05, 0.05]], dtype=np.float32) - if comp == 'residues': - #molecule 11, residue 1 & molecule 12 - relpos[35:47, :] = np.array([[0.06, 0.75, 0.7], - [0.16, 0.75, 0.7], - [0.26, 0.75, 0.7], - [0.36, 0.75, 0.7], - [1.14, 0.65, 0.6], - [1.04, 0.65, 0.6], - [0.94, 0.65, 0.6], - [0.84, 0.65, 0.6], - [0.74, 0.65, 0.6], - [0.64, 0.65, 0.6], - [0.54, 0.65, 0.6], - [0.44, 0.65, 0.6]], - dtype=np.float32) + relpos[9:12, :] = np.array( + [[0.8, 0.1, 1.05], [0.8, 0.1, 0.95], [0.9, 0.1, 0.95]], + dtype=np.float32, + ) + # molecule 8 + relpos[15:19, :] = np.array( + [ + [0.4, -0.05, 0.05], + [0.4, -0.05, -0.05], + [0.4, 0.05, -0.05], + [0.4, 0.05, 0.05], + ], + dtype=np.float32, + ) + if comp == "residues": + # molecule 11, residue 1 & molecule 12 + relpos[35:47, :] = np.array( + [ + [0.06, 0.75, 0.7], + [0.16, 0.75, 0.7], + [0.26, 0.75, 0.7], + [0.36, 0.75, 0.7], + [1.14, 0.65, 0.6], + [1.04, 0.65, 0.6], + [0.94, 0.65, 0.6], + [0.84, 0.65, 0.6], + [0.74, 0.65, 0.6], + [0.64, 0.65, 0.6], + [0.54, 0.65, 0.6], + [0.44, 0.65, 0.6], + ], + dtype=np.float32, + ) else: - #molecule 11 & 12 - relpos[31:47, :] = np.array([[-0.34, 0.75, 0.7], - [-0.24, 0.75, 0.7], - [-0.14, 0.75, 0.7], - [-0.04, 0.75, 0.7], - [0.06, 0.75, 0.7], - [0.16, 0.75, 0.7], - [0.26, 0.75, 0.7], - [0.36, 0.75, 0.7], - [1.14, 0.65, 0.6], - [1.04, 0.65, 0.6], - [0.94, 0.65, 0.6], - [0.84, 0.65, 0.6], - [0.74, 0.65, 0.6], - [0.64, 0.65, 0.6], - [0.54, 0.65, 0.6], - [0.44, 0.65, 0.6]], - dtype=np.float32) + # molecule 11 & 12 + relpos[31:47, :] = np.array( + [ + [-0.34, 0.75, 0.7], + [-0.24, 0.75, 0.7], + [-0.14, 0.75, 0.7], + [-0.04, 0.75, 0.7], + [0.06, 0.75, 0.7], + [0.16, 0.75, 0.7], + [0.26, 0.75, 0.7], + [0.36, 0.75, 0.7], + [1.14, 0.65, 0.6], + [1.04, 0.65, 0.6], + [0.94, 0.65, 0.6], + [0.84, 0.65, 0.6], + [0.74, 0.65, 0.6], + [0.64, 0.65, 0.6], + [0.54, 0.65, 0.6], + [0.44, 0.65, 0.6], + ], + dtype=np.float32, + ) # apply y- and z-dependent shift of x and y coords for triclinic boxes: if self._is_triclinic: @@ -469,18 +521,24 @@ def wrapped_coords(self, compound, center): identical. """ ctr = center.lower() - if ctr not in ['com', 'cog']: + if ctr not in ["com", "cog"]: raise ValueError("Unknown unwrap reference: {}".format(center)) comp = compound.lower() - if comp not in ['atoms', 'group', 'segments', 'residues', - 'molecules', 'fragments']: + if comp not in [ + "atoms", + "group", + "segments", + "residues", + "molecules", + "fragments", + ]: raise ValueError("Unknown unwrap compound: {}".format(compound)) # wrapped relative positions: relpos = self._relpos.copy() # apply required box shifts: - if comp == 'atoms': + if comp == "atoms": # type A # type A # molecule 2: negative x-shift @@ -505,10 +563,10 @@ def wrapped_coords(self, compound, center): relpos[39:41, 0] -= 1.0 # molecule 12: positive z-shift relpos[39:47, 2] += 1.0 - elif comp == 'group': + elif comp == "group": # com or cog of entire system is within box, so no shift pass - elif comp == 'segments': + elif comp == "segments": # type A # molecules 1-3: negative x-shift relpos[0:3, 0] -= 1.0 @@ -521,13 +579,13 @@ def wrapped_coords(self, compound, center): relpos[1, 0] -= 1.0 # molecule 2: negative double x-shift relpos[2, 0] -= 2.0 - #type B + # type B # molecule 6: positive x- and y-shift relpos[9:12, :2] += 1.0 - #type C + # type C # molecule 8: negative z-shift relpos[15:19, 2] -= 1.0 - #type D + # type D # molecule 12: positive z-shift relpos[39:47, 2] += 1.0 @@ -564,41 +622,46 @@ def center(self, compound): relpos = self.unwrapped_coords(compound, reference=None) comp = compound.lower() - if comp not in ['group', 'segments', 'residues', 'molecules', - 'fragments']: + if comp not in [ + "group", + "segments", + "residues", + "molecules", + "fragments", + ]: raise ValueError("Unknown unwrap compound: {}".format(compound)) pos = 0 - if compound=="residues": + if compound == "residues": center_pos = np.zeros((15, 3), dtype=np.float32) else: center_pos = np.zeros((12, 3), dtype=np.float32) for base in range(3): loc_center = relpos[base, :] - center_pos[pos,:] = loc_center - pos+=1 + center_pos[pos, :] = loc_center + pos += 1 for base in range(3, 15, 3): - loc_center = np.mean(relpos[base:base + 3, :], axis=0) - center_pos[pos,:] = loc_center - pos+=1 + loc_center = np.mean(relpos[base : base + 3, :], axis=0) + center_pos[pos, :] = loc_center + pos += 1 - if compound=="residues": + if compound == "residues": for base in range(15, 47, 4): - loc_center = np.mean(relpos[base:base + 4, :], axis=0) - center_pos[pos,:] = loc_center - pos+=1 + loc_center = np.mean(relpos[base : base + 4, :], axis=0) + center_pos[pos, :] = loc_center + pos += 1 else: for base in range(15, 23, 4): - loc_center = np.mean(relpos[base:base + 4, :], axis=0) - center_pos[pos,:] = loc_center - pos+=1 + loc_center = np.mean(relpos[base : base + 4, :], axis=0) + center_pos[pos, :] = loc_center + pos += 1 for base in range(23, 47, 8): - loc_center = np.mean(relpos[base:base + 8, :], axis=0) - center_pos[pos,:] = loc_center - pos+=1 + loc_center = np.mean(relpos[base : base + 8, :], axis=0) + center_pos[pos, :] = loc_center + pos += 1 if compound == "group": center_pos = center_pos[11] @@ -606,4 +669,3 @@ def center(self, compound): center_pos = center_pos[9:] return center_pos - diff --git a/testsuite/pyproject.toml b/testsuite/pyproject.toml index 63450efa749..3f400fb179e 100644 --- a/testsuite/pyproject.toml +++ b/testsuite/pyproject.toml @@ -171,6 +171,7 @@ setup\.py | MDAnalysisTests/formats/.*\.py | MDAnalysisTests/parallelism/.*\.py | MDAnalysisTests/scripts/.*\.py +| MDAnalysisTests/core/.*\.py ) ''' extend-exclude = '''