Skip to content

Commit

Permalink
added add_TopologyObjects
Browse files Browse the repository at this point in the history
added delete_TopologyObjects
  • Loading branch information
Lily Wang authored and richardjgowers committed Jan 14, 2020
1 parent 4adbd6c commit 206652b
Show file tree
Hide file tree
Showing 8 changed files with 429 additions and 29 deletions.
4 changes: 4 additions & 0 deletions package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ Enhancements
* Added wrap/unwrap transformations (PR #2038)
* Read TPR files from Gromacs 2020 (Issue #2412)

* Added add_TopologyObject/s and delete_TopologyObject/s (PR #2382)
* Added _add_TopologyObjects, _delete_TopologyObjects, and public convenience
methods to Universe. Added type and order checking to Bonds/Angles/Dihedrals
and type checking to Impropers. (PR #2382)

Deprecations
* analysis.hbonds.HydrogenBondAnalysis will be deprecated in 1.0
Expand Down
6 changes: 3 additions & 3 deletions package/MDAnalysis/core/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -2950,15 +2950,15 @@ def get_TopAttr(u, name, cls):
box = self.dimensions if self.dimensions.all() else None
b = guess_bonds(self.atoms, self.atoms.positions, vdwradii=vdwradii, box=box)
bondattr = get_TopAttr(self.universe, 'bonds', Bonds)
bondattr.add_bonds(b, guessed=True)
bondattr._add_bonds(b, guessed=True)

a = guess_angles(self.bonds)
angleattr = get_TopAttr(self.universe, 'angles', Angles)
angleattr.add_bonds(a, guessed=True)
angleattr._add_bonds(a, guessed=True)

d = guess_dihedrals(self.angles)
diheattr = get_TopAttr(self.universe, 'dihedrals', Dihedrals)
diheattr.add_bonds(d)
diheattr._add_bonds(d)

@property
def bond(self):
Expand Down
71 changes: 55 additions & 16 deletions package/MDAnalysis/core/topologyattrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1644,17 +1644,41 @@ def _get_named_segment(group, segid):
transplants[SegmentGroup].append(
('_get_named_segment', _get_named_segment))

def _check_connection_values(func):
"""
Checks values passed to _Connection methods for appropriate number of
atom indices and coerces them to tuples of ints.
class _Connection(AtomAttr):
"""Base class for connectivity between atoms"""
def __init__(self, values, types=None, guessed=False, order=None):
values = [tuple(x) for x in values]
.. versionadded:: 0.21.0
"""
@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):
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))
" atom indices").format(self.attrname,
self._n_atoms))
clean = []
for v in values:
if v[0] > v[-1]:
v = v[::-1]
clean.append(tuple(v))

return func(self, clean, *args, **kwargs)
return wrapper

class _Connection(AtomAttr):
"""Base class for connectivity between atoms
.. versionchanged:: 0.21.0
Added type checking to atom index values.
"""

@_check_connection_values
def __init__(self, values, types=None, guessed=False, order=None):
self.values = values
if types is None:
types = [None] * len(values)
Expand Down Expand Up @@ -1687,12 +1711,6 @@ def _bondDict(self):

for b, t, g, o in zip(self.values, self.types,
self._guessed, self.order):
# We always want the first index
# to be less than the last
# eg (0, 1) not (1, 0)
# and (4, 10, 8) not (8, 10, 4)
if b[0] > b[-1]:
b = b[::-1]
for a in b:
bd[a].append((b, t, g, o))
return bd
Expand All @@ -1719,7 +1737,8 @@ def get_atoms(self, ag):
guessed,
order)

def add_bonds(self, values, types=None, guessed=True, order=None):
@_check_connection_values
def _add_bonds(self, values, types=None, guessed=True, order=None):
if types is None:
types = itertools.cycle((None,))
if guessed in (True, False):
Expand All @@ -1739,7 +1758,26 @@ def add_bonds(self, values, types=None, guessed=True, order=None):
del self._cache['bd']
except KeyError:
pass


@_check_connection_values
def _delete_bonds(self, values):
"""
.. versionadded:: 0.21.0
"""
to_check = set(values) & set(self.values)
idx = [self.values.index(v) for v 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')
new = np.delete(arr, idx)
setattr(self, attr, list(new))
# kill the old cache of bond Dict
try:
del self._cache['bd']
except KeyError:
pass

class Bonds(_Connection):
"""Bonds between two atoms
Expand Down Expand Up @@ -1922,3 +1960,4 @@ class Impropers(_Connection):
singular = 'impropers'
transplants = defaultdict(list)
_n_atoms = 4

1 change: 1 addition & 0 deletions package/MDAnalysis/core/topologyobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ def __init__(self, bondidx, universe, btype=None, type=None, guessed=None,
raise ValueError("Unsupported btype, use one of '{}'"
"".format(', '.join(_BTYPE_TO_SHAPE)))

bondidx = np.asarray(bondidx)
nbonds = len(bondidx)
# remove duplicate bonds
if type is None:
Expand Down
236 changes: 236 additions & 0 deletions package/MDAnalysis/core/universe.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
AtomGroup, ResidueGroup, SegmentGroup)
from .topology import Topology
from .topologyattrs import AtomAttr, ResidueAttr, SegmentAttr
from .topologyobjects import TopologyObject

logger = logging.getLogger("MDAnalysis.core.universe")

Expand Down Expand Up @@ -992,6 +993,241 @@ def add_Segment(self, **attrs):
self.segments = SegmentGroup(np.arange(self._topology.n_segments), self)
# return the new segment
return self.segments[segidx]

def _add_topology_objects(self, object_type, values, types=None, guessed=False,
order=None):
"""Add new TopologyObjects to this Universe
Parameters
----------
object_type : {'bonds', 'angles', 'dihedrals', 'impropers'}
The type of TopologyObject to add.
values : iterable of tuples, AtomGroups, or TopologyObjects
An iterable of: tuples of atom indices, or AtomGroups,
or TopologyObjects. If every value is a TopologyObject, all
keywords are ignored.
types : iterable (optional, default None)
None, or an iterable of hashable values with the same length as ``values``
guessed : bool or iterable (optional, default False)
bool, or an iterable of hashable values with the same length as ``values``
order : iterable (optional, default None)
None, or an iterable of hashable values with the same length as ``values``
.. versionadded:: 0.21.0
"""
if all(isinstance(x, TopologyObject) for x in values):
try:
types = [t.type for t in values]
except AttributeError:
types = None
guessed = [t.is_guessed for t in values]
order = [t.order for t in values]

values = [x.indices if isinstance(x, (AtomGroup, TopologyObject))
else x for x in values]

try:
attr = getattr(self._topology, object_type)
except AttributeError:
self.add_TopologyAttr(object_type, [])
attr = getattr(self._topology, object_type)

attr._add_bonds(values, types=types, guessed=guessed, order=order)

def add_bonds(self, values, types=None, guessed=False, order=None):
"""Add new Bonds to this Universe.
Parameters
----------
values : iterable of tuples, AtomGroups, or Bonds
An iterable of: tuples of 2 atom indices, or AtomGroups with 2 atoms,
or Bonds. If every value is a Bond, all
keywords are ignored.
types : iterable (optional, default None)
None, or an iterable of hashable values with the same length as ``values``
guessed : bool or iterable (optional, default False)
bool, or an iterable of hashable values with the same length as ``values``
order : iterable (optional, default None)
None, or an iterable of hashable values with the same length as ``values``
Example
-------
Adding TIP4P water bonds with a list of AtomGroups:
>>> import MDAnalysis as mda
>>> from MDAnalysis.tests.datafiles import GRO
>>> u = mda.Universe(GRO)
>>> sol = u.select_atoms('resname SOL')
>>> ow_hw1 = sol.select_atoms('name OW or name HW1').split('residue')
>>> ow_hw2 = sol.select_atoms('name OW or name HW2').split('residue')
>>> ow_mw = sol.select_atoms('name OW or name MW').split('residue')
>>> u.add_bonds(ow_hw1 + ow_hw2 + ow_mw)
.. versionadded:: 0.21.0
"""
self._add_topology_objects('bonds', values, types=types,
guessed=guessed, order=order)
self._cache.pop('fragments', None)

def add_angles(self, values, types=None, guessed=False):
"""Add new Angles to this Universe.
Parameters
----------
values : iterable of tuples, AtomGroups, or Angles
An iterable of: tuples of 3 atom indices, or AtomGroups with 3 atoms,
or Angles. If every value is a Angle, all
keywords are ignored.
types : iterable (optional, default None)
None, or an iterable of hashable values with the same length as ``values``
guessed : bool or iterable (optional, default False)
bool, or an iterable of hashable values with the same length as ``values``
.. versionadded:: 0.21.0
"""
self._add_topology_objects('angles', values, types=types,
guessed=guessed)

def add_dihedrals(self, values, types=None, guessed=False):
"""Add new Dihedrals to this Universe.
Parameters
----------
values : iterable of tuples, AtomGroups, or Dihedrals
An iterable of: tuples of 4 atom indices, or AtomGroups with 4 atoms,
or Dihedrals. If every value is a Dihedral, all
keywords are ignored.
types : iterable (optional, default None)
None, or an iterable of hashable values with the same length as ``values``
guessed : bool or iterable (optional, default False)
bool, or an iterable of hashable values with the same length as ``values``
.. versionadded:: 0.21.0
"""
self._add_topology_objects('dihedrals', values, types=types,
guessed=guessed)

def add_impropers(self, values, types=None, guessed=False):
"""Add new Impropers to this Universe.
Parameters
----------
values : iterable of tuples, AtomGroups, or Impropers
An iterable of: tuples of 4 atom indices, or AtomGroups with 4 atoms,
or Impropers. If every value is an Improper, all
keywords are ignored.
types : iterable (optional, default None)
None, or an iterable of hashable values with the same length as ``values``
guessed : bool or iterable (optional, default False)
bool, or an iterable of hashable values with the same length as ``values``
.. versionadded:: 0.21.0
"""
self._add_topology_objects('impropers', values, types=types,
guessed=guessed)




def _delete_topology_objects(self, object_type, values):
"""Delete TopologyObjects from this Universe
Parameters
----------
object_type : {'bonds', 'angles', 'dihedrals', 'impropers'}
The type of TopologyObject to add.
values : iterable of tuples, AtomGroups, or TopologyObjects
An iterable of: tuples of atom indices, or AtomGroups,
or TopologyObjects.
.. versionadded:: 0.21.0
"""
values = [x.indices if isinstance(x, (AtomGroup, TopologyObject))
else x for x in values]

try:
attr = getattr(self._topology, object_type)
except AttributeError:
raise ValueError('There are no {} to delete'.format(object_type))

attr._delete_bonds(values)

def delete_bonds(self, values):
"""Delete Bonds from this Universe.
Parameters
----------
values : iterable of tuples, AtomGroups, or Bonds
An iterable of: tuples of 2 atom indices, or AtomGroups with 2 atoms,
or Bonds.
.. versionadded:: 0.21.0
"""
self._delete_topology_objects('bonds', values)
self._cache.pop('fragments', None)

def delete_angles(self, values):
"""Delete Angles from this Universe.
Parameters
----------
values : iterable of tuples, AtomGroups, or Angles
An iterable of: tuples of 3 atom indices, or AtomGroups with 3 atoms,
or Angles.
.. versionadded:: 0.21.0
"""
self._delete_topology_objects('angles', values)

def delete_dihedrals(self, values):
"""Delete Dihedrals from this Universe.
Parameters
----------
values : iterable of tuples, AtomGroups, or Dihedrals
An iterable of: tuples of 4 atom indices, or AtomGroups with 4 atoms,
or Dihedrals.
.. versionadded:: 0.21.0
"""
self._delete_topology_objects('dihedrals', values)

def delete_impropers(self, values):
"""Delete Impropers from this Universe.
Parameters
----------
values : iterable of tuples, AtomGroups, or Impropers
An iterable of: tuples of 4 atom indices, or AtomGroups with 4 atoms,
or Impropers.
.. versionadded:: 0.21.0
"""
self._delete_topology_objects('impropers', values)


# TODO: Maybe put this as a Bond attribute transplant
# Problems: Can we transplant onto Universe?
Expand Down
Loading

0 comments on commit 206652b

Please sign in to comment.