-
Notifications
You must be signed in to change notification settings - Fork 824
Description
Expected behavior
It is possible to add bonds to a bond-less Universe (or replace bonds in a bonded Universe) with u.add_TopologyAttr in a very specific way. I would expect errors or warnings to pop up when u.add_TopologyAttris passed something it can't work with. I would also not expect lists of indices to act so differently from tuples of indices.
Actual behavior
No warnings or errors, until the u.bonds attribute is accessed and either returns an AttributeError or an empty TopologyGroup.
Code to reproduce the behavior
I was trying to make a Universe of water.
>>> import MDAnalysis as mda
>>> import numpy as np
>>> sol = mda.Universe.empty(3000,
n_residues=1000,
atom_resindex=np.repeat(range(1000), 3),
residue_segindex=[0]*1000,
trajectory=True)
>>> sol
<Universe with 3000 atoms>
>>> sol.add_TopologyAttr('name', ['O', 'H1', 'H2']*n_residues)
>>> sol.atoms.names
array(['O', 'H1', 'H2', ..., 'O', 'H1', 'H2'], dtype=object)Attempts
1. Trying a list of lists of indices (bonds attribute is available but broken)
>>> bonds = []
>>> for o in range(0, n_atoms, 3):
... bonds.extend([[o, o+1], [o, o+2]])
...
>>> sol.add_TopologyAttr('bonds', bonds)
>>> sol.bonds
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
MDAnalysis/core/topologyattrs.py in get_atoms(self, ag)
1692 unique_bonds = set(itertools.chain(
-> 1693 *[self._bondDict[a] for a in ag.ix]))
1694 except TypeError:
TypeError: unhashable type: 'list'
During handling of the above exception, another exception occurred:
TypeError Traceback (most recent call last)
<ipython-input-239-076c3c5d8d4d> in <module>
----> 1 sol.bonds
MDAnalysis/core/universe.py in bonds(self)
702 def bonds(self):
703 """Bonds between atoms"""
--> 704 return self.atoms.bonds
705
706 @property
MDAnalysis/core/groups.py in getter(self)
257 """
258 def getter(self):
--> 259 return attr.__getitem__(self)
260
261 def setter(self, values):
MDAnalysis/core/topologyattrs.py in __getitem__(self, group)
270 """Accepts an AtomGroup, ResidueGroup or SegmentGroup"""
271 if isinstance(group, (Atom, AtomGroup)):
--> 272 return self.get_atoms(group)
273 elif isinstance(group, (Residue, ResidueGroup)):
274 return self.get_residues(group)
MDAnalysis/core/topologyattrs.py in get_atoms(self, ag)
1694 except TypeError:
1695 # maybe we got passed an Atom
-> 1696 unique_bonds = self._bondDict[ag.ix]
1697 bond_idx, types, guessed, order = np.hsplit(
1698 np.array(sorted(unique_bonds)), 4)
TypeError: unhashable type: 'numpy.ndarray'2. Trying a TopologyGroup (doesn't add bonds)
>>> bondgroup = mda.core.topologyobjects.TopologyGroup(np.array(bonds), sol)
>>> bondgroup
<TopologyGroup containing 2000 bonds>
>>> sol.add_TopologyAttr('bonds', bondgroup)
>>> sol.bonds
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
MDAnalysis/core/universe.py in __getattr__(self, key)
509 try:
--> 510 segment = self._instant_selectors[key]
511 except KeyError:
KeyError: 'bonds'
During handling of the above exception, another exception occurred:
AttributeError Traceback (most recent call last)
<ipython-input-243-076c3c5d8d4d> in <module>
----> 1 sol.bonds
MDAnalysis/core/universe.py in __getattr__(self, key)
510 segment = self._instant_selectors[key]
511 except KeyError:
--> 512 raise AttributeError('No attribute "{}".'.format(key))
513 else:
514 warnings.warn("Instant selector Universe.<segid> "
AttributeError: No attribute "bonds".3. Trying an atom-wise list of lists of tuples of indices (adds empty group)
>>> bonds = []
>>> for o in range(0, n_atoms, 3):
... bonds.extend([[(o, o+1), (o, o+2)],
... [(o, o+1)],
... [(o, o+2)]])
...
>>> sol.add_TopologyAttr('bonds', bonds)
>>> sol.bonds
<TopologyGroup containing 0 bonds>4. Trying a list of tuples of indices (works!)
>>> bonds = []
>>> for o in range(0, n_atoms, 3):
... bonds.extend([(o, o+1), (o, o+2)])
...
>>> sol.add_TopologyAttr('bonds', bonds)
>>> sol.bonds
<TopologyGroup containing 2000 bonds>🌟Bonus 5. Trying to raise any kind of error🌟
Turns out add_TopologyAttr will silently accept any iterable, and produce empty TopologyGroups for any iterable of something subscriptable.
>>> sol.add_TopologyAttr('bonds', 'invalid bond')
>>> sol.bonds
<TopologyGroup containing 0 bonds>
>>> sol.add_TopologyAttr('bonds', ['0'] * 10)
>>> sol.bonds
<TopologyGroup containing 0 bonds>Again, errors only pop up when sol.bonds is called, when I pass an iterable of something not subscriptable.
Comments
Approach 1 and 4 seem very similar to me. Would it be possible to add another except clause that tries to convert it to a tuple here:
mdanalysis/package/MDAnalysis/core/topologyattrs.py
Lines 1690 to 1696 in 9372d09
| def get_atoms(self, ag): | |
| try: | |
| 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] |
Otherwise it would be preferable to raise errors at the add_TopologyAttr stage rather than when the bonds attribute is called. (In the case of Approaches 3 and 5, no errors are ever raised).
Currently version of MDAnalysis
- Which version are you using? (run
python -c "import MDAnalysis as mda; print(mda.__version__)") 0.20.1 - Which version of Python (
python -V)? 3.7.x - Which operating system? MacOS Mojave