From 0875ae001b834387783dc4fef3b3cff7cb67b045 Mon Sep 17 00:00:00 2001 From: Lily Wang Date: Tue, 21 Jan 2020 08:34:58 +1100 Subject: [PATCH] added Universe check and more tests --- package/MDAnalysis/core/universe.py | 123 ++++-- .../MDAnalysisTests/core/test_universe.py | 388 +++++++++--------- 2 files changed, 283 insertions(+), 228 deletions(-) diff --git a/package/MDAnalysis/core/universe.py b/package/MDAnalysis/core/universe.py index 62fb0db7dde..6bc96327ad0 100644 --- a/package/MDAnalysis/core/universe.py +++ b/package/MDAnalysis/core/universe.py @@ -1002,10 +1002,12 @@ def _add_topology_objects(self, object_type, values, types=None, guessed=False, ---------- object_type : {'bonds', 'angles', 'dihedrals', 'impropers'} The type of TopologyObject to add. - values : iterable of tuples, AtomGroups, or TopologyObjects + values : TopologyGroup or 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. + If AtomGroups or TopologyObjects are passed, they *must* be from the same + Universe. 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) @@ -1024,15 +1026,22 @@ def _add_topology_objects(self, object_type, values, types=None, guessed=False, 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] + indices = [] + for x in values: + if isinstance(x, (AtomGroup, TopologyObject)): + if x.universe is not self: + err_msg = 'Cannot add {} from different Universes.' + raise ValueError(err_msg.format(object_type)) + indices.append(x.indices) + else: + indices.append(x) - all_indices = set([i for obj in values for i in obj]) + all_indices = set([i for obj in indices for i in obj]) nonexistent = all_indices - set(self.atoms.indices) if nonexistent: istr = ', '.join(map(str, nonexistent)) - raise ValueError(('Cannot add {} for nonexistent' - ' atom indices: {}').format(object_type, istr)) + err_msg = 'Cannot add {} for nonexistent atom indices: {}' + raise ValueError(err_msg.format(object_type, istr)) try: attr = getattr(self._topology, object_type) @@ -1041,17 +1050,19 @@ def _add_topology_objects(self, object_type, values, types=None, guessed=False, attr = getattr(self._topology, object_type) - attr._add_bonds(values, types=types, guessed=guessed, order=order) + attr._add_bonds(indices, 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 + values : iterable of tuples, AtomGroups, or Bonds; or TopologyGroup 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. + If AtomGroups, Bonds, or a TopologyGroup are passed, + they *must* be from the same Universe. 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) @@ -1063,16 +1074,26 @@ def add_bonds(self, values, types=None, guessed=False, order=None): Example ------- - Adding TIP4P water bonds with a list of AtomGroups: + 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) - >>> 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) + You can only add bonds from the same Universe. If you would like to add + AtomGroups, Bonds, or a TopologyGroup from a different Universe, convert + them to indices first. :: + + from MDAnalysis.tests.datafiles import PSF + u2 = mda.Universe(PSF) + + # assuming you have already added bonds to u + u2.add_bonds(u.bonds.to_indices()) .. versionadded:: 0.21.0 @@ -1086,10 +1107,12 @@ def add_angles(self, values, types=None, guessed=False): Parameters ---------- - values : iterable of tuples, AtomGroups, or Angles + values : iterable of tuples, AtomGroups, or Angles; or TopologyGroup 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. + If AtomGroups, Angles, or a TopologyGroup are passed, + they *must* be from the same Universe. 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) @@ -1105,10 +1128,12 @@ def add_dihedrals(self, values, types=None, guessed=False): Parameters ---------- - values : iterable of tuples, AtomGroups, or Dihedrals + values : iterable of tuples, AtomGroups, or Dihedrals; or TopologyGroup 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. + If AtomGroups, Dihedrals, or a TopologyGroup are passed, + they *must* be from the same Universe. 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) @@ -1125,10 +1150,12 @@ def add_impropers(self, values, types=None, guessed=False): Parameters ---------- - values : iterable of tuples, AtomGroups, or Impropers + values : iterable of tuples, AtomGroups, or Impropers; or TopologyGroup 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. + If AtomGroups, Impropers, or a TopologyGroup are passed, + they *must* be from the same Universe. 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) @@ -1147,30 +1174,64 @@ def _delete_topology_objects(self, object_type, values): ---------- object_type : {'bonds', 'angles', 'dihedrals', 'impropers'} The type of TopologyObject to add. - values : iterable of tuples, AtomGroups, or TopologyObjects + values : iterable of tuples, AtomGroups, or TopologyObjects; or TopologyGroup An iterable of: tuples of atom indices, or AtomGroups, or TopologyObjects. + If AtomGroups, TopologyObjects, or a TopologyGroup are passed, + they *must* be from the same Universe. .. versionadded:: 0.21.0 """ - values = [x.indices if isinstance(x, (AtomGroup, TopologyObject)) - else x for x in values] + indices = [] + for x in values: + if isinstance(x, (AtomGroup, TopologyObject)): + if x.universe is not self: + err_msg = 'Cannot delete {} from different Universes.' + raise ValueError(err_msg.format(object_type)) + indices.append(x.indices) + else: + indices.append(x) try: attr = getattr(self._topology, object_type) except AttributeError: raise ValueError('There are no {} to delete'.format(object_type)) - attr._delete_bonds(values) + attr._delete_bonds(indices) def delete_bonds(self, values): """Delete Bonds from this Universe. Parameters ---------- - values : iterable of tuples, AtomGroups, or Bonds + values : iterable of tuples, AtomGroups, or Bonds; or TopologyGroup An iterable of: tuples of 2 atom indices, or AtomGroups with 2 atoms, or Bonds. + If AtomGroups, Bonds, or a TopologyGroup are passed, + they *must* be from the same Universe. + + + Example + ------- + + Deleting bonds from a Universe:: + + import MDAnalysis as mda + from MDAnalysis.tests.datafiles import PSF + u = mda.Universe(PSF) + + # delete first 5 bonds + u.delete_bonds(u.bonds[:5]) + + + If you are deleting bonds in the form of AtomGroups, Bonds, or a + TopologyGroup, they must come from the same Universe. If you want to + delete bonds from another Universe, convert them to indices first. :: + + from MDAnalysis.tests.datafiles import PDB + u2 = mda.Universe(PDB) + + u.delete_bonds(u2.bonds.to_indices()) .. versionadded:: 0.21.0 @@ -1183,9 +1244,11 @@ def delete_angles(self, values): Parameters ---------- - values : iterable of tuples, AtomGroups, or Angles + values : iterable of tuples, AtomGroups, or Angles; or TopologyGroup An iterable of: tuples of 3 atom indices, or AtomGroups with 3 atoms, or Angles. + If AtomGroups, Angles, or a TopologyGroup are passed, + they *must* be from the same Universe. .. versionadded:: 0.21.0 @@ -1197,9 +1260,11 @@ def delete_dihedrals(self, values): Parameters ---------- - values : iterable of tuples, AtomGroups, or Dihedrals + values : iterable of tuples, AtomGroups, or Dihedrals; or TopologyGroup An iterable of: tuples of 4 atom indices, or AtomGroups with 4 atoms, or Dihedrals. + If AtomGroups, Dihedrals, or a TopologyGroup are passed, + they *must* be from the same Universe. .. versionadded:: 0.21.0 @@ -1211,9 +1276,11 @@ def delete_impropers(self, values): Parameters ---------- - values : iterable of tuples, AtomGroups, or Impropers + values : iterable of tuples, AtomGroups, or Impropers; or TopologyGroup An iterable of: tuples of 4 atom indices, or AtomGroups with 4 atoms, or Impropers. + If AtomGroups, Angles, or a TopologyGroup are passed, + they *must* be from the same Universe. .. versionadded:: 0.21.0 diff --git a/testsuite/MDAnalysisTests/core/test_universe.py b/testsuite/MDAnalysisTests/core/test_universe.py index a49748fca32..9d980f89baf 100644 --- a/testsuite/MDAnalysisTests/core/test_universe.py +++ b/testsuite/MDAnalysisTests/core/test_universe.py @@ -673,6 +673,21 @@ def _a_or_reversed_in_b(a, b): 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]]), + ) + + 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]]), + ) + @pytest.fixture() def empty(self): return make_Universe() @@ -680,23 +695,39 @@ def empty(self): @pytest.fixture() def universe(self): return mda.Universe(PSF) + + 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(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) + + 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(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) + + def _check_invalid_addition(self, u, attr, to_add, err_msg): + _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', ( - ('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]]), - ) + 'attr,values', small_atom_indices ) def test_add_indices_to_empty(self, empty, attr, values): - assert not hasattr(empty, attr) - _add_func = getattr(empty, 'add_'+attr) - _add_func(values) - u_attr = getattr(empty, attr) - assert len(u_attr) == len(values) - assert all(_a_or_reversed_in_b(x, u_attr.indices) - for x in values) + self._check_valid_added_to_empty(empty, attr, values, values) def test_add_reversed_duplicates(self, empty): assert not hasattr(empty, 'bonds') @@ -705,106 +736,69 @@ def test_add_reversed_duplicates(self, empty): assert_array_equal(empty.bonds.indices, np.array([[0, 1]])) @pytest.mark.parametrize( - 'attr,values', ( - ('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]]), - ) + 'attr,values', large_atom_indices ) def test_add_indices_to_populated(self, universe, attr, values): - assert hasattr(universe, attr) - u_attr = getattr(universe, attr) - original_length = len(u_attr) - - _add_func = getattr(universe, 'add_'+attr) - _add_func(values) - u_attr = getattr(universe, attr) - assert len(u_attr) == len(values) + original_length - assert all(_a_or_reversed_in_b(x, u_attr.indices) - for x in values) + self._check_valid_added_to_populated(universe, attr, values, values) @pytest.mark.parametrize( - 'attr,values', ( - ('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]]), - ) + 'attr,values', small_atom_indices ) def test_add_atomgroup_to_empty(self, empty, attr, values): - assert not hasattr(empty, attr) ag = [empty.atoms[x] for x in values] - _add_func = getattr(empty, 'add_'+attr) - _add_func(ag) - u_attr = getattr(empty, attr) - assert len(u_attr) == len(values) - assert all(_a_or_reversed_in_b(x, u_attr.indices) - for x in values) + self._check_valid_added_to_empty(empty, attr, values, ag) @pytest.mark.parametrize( - 'attr,values', ( - ('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]]), - ) + 'attr,values', large_atom_indices ) def test_add_atomgroup_to_populated(self, universe, attr, values): - assert hasattr(universe, attr) - u_attr = getattr(universe, attr) - original_length = len(u_attr) + ag = [universe.atoms[x] for x in values] + self._check_valid_added_to_populated(universe, attr, values, ag) - _add_func = getattr(universe, 'add_'+attr) - atomgroup = [universe.atoms[x] for x in values] - _add_func(atomgroup) - u_attr = getattr(universe, attr) - assert len(u_attr) == len(values) + original_length - assert all(_a_or_reversed_in_b(x, u_attr.indices) - for x in 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') @pytest.mark.parametrize( - 'attr,values', ( - ('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]]), - ) + 'attr,values', large_atom_indices ) def test_add_topologyobjects_to_populated(self, universe, attr, values): - assert hasattr(universe, attr) - u_attr = getattr(universe, attr) - original_length = len(u_attr) - - _add_func = getattr(universe, 'add_'+attr) topologyobjects = [getattr(universe.atoms[x], attr[:-1]) for x in values] - _add_func(topologyobjects) - u_attr = getattr(universe, attr) - assert len(u_attr) == len(values) + original_length - assert all(_a_or_reversed_in_b(x, u_attr.indices) - for x in values) + self._check_valid_added_to_populated(universe, attr, values, topologyobjects) @pytest.mark.parametrize( - 'attr,values', ( - ('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]]), - ) + 'attr,values', small_atom_indices ) - def test_add_topologygroups_to_populated(self, universe, attr, values): - assert hasattr(universe, attr) - u_attr = getattr(universe, attr) - original_length = len(u_attr) + 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') - _add_func = getattr(universe, 'add_'+attr) + @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) - _add_func(topologygroup) - u_attr = getattr(universe, attr) - assert len(u_attr) == len(values) + original_length - assert all(_a_or_reversed_in_b(x, u_attr.indices) - for x in values) + 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') + + @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', ( @@ -817,10 +811,7 @@ def test_add_topologygroups_to_populated(self, universe, attr, values): def test_add_wrong_topologygroup_error(self, universe, attr, values): arr = np.array(values) tg = mda.core.topologyobjects.TopologyGroup(arr, universe) - _add_func = getattr(universe, 'add_'+attr) - - with pytest.raises(ValueError): - _add_func(tg) + self._check_invalid_addition(universe, attr, tg, 'iterable of tuples with') @pytest.mark.parametrize( 'attr,values', ( @@ -831,10 +822,7 @@ def test_add_wrong_topologygroup_error(self, universe, attr, values): ) ) def test_add_nonexistent_indices_error(self, universe, attr, values): - _add_func = getattr(universe, 'add_'+attr) - with pytest.raises(ValueError) as excinfo: - _add_func(values) - assert 'nonexistent atom indices' in str(excinfo.value) + self._check_invalid_addition(universe, attr, values, 'nonexistent atom indices') @pytest.mark.parametrize( 'attr,n', ( @@ -845,12 +833,10 @@ def test_add_nonexistent_indices_error(self, universe, attr, values): ) ) def test_add_wrong_number_of_atoms_error(self, universe, attr, n): - _add_func = getattr(universe, 'add_'+attr) - with pytest.raises(ValueError) as excinfo: - _add_func([(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) - assert errmsg in str(excinfo.value) + 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): @@ -863,12 +849,7 @@ def test_add_bonds_refresh_fragments(self, empty): assert len(empty.atoms.fragments) == len(empty.atoms)-2 @pytest.mark.parametrize( - 'attr,values', ( - ('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]]), - ) + 'attr,values', small_atom_indices ) def test_roundtrip(self, empty, attr, values): _add_func = getattr(empty, 'add_'+attr) @@ -888,6 +869,19 @@ class TestDeleteTopologyObjects(object): '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]]), + ) + 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],]), + ) + @pytest.fixture() def universe(self): u = make_Universe(size=(125, 25, 5)) @@ -895,108 +889,114 @@ def universe(self): u._add_topology_objects(attr, values) return u - @pytest.mark.parametrize( - 'attr,values', ( - ('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]]), - ) - ) - def test_delete_valid_indices(self, universe, attr, values): - u_attr = getattr(universe, attr) + @pytest.fixture() + def universe2(self): + u = make_Universe(size=(125, 25, 5)) + for attr, values in self.TOP.items(): + u._add_topology_objects(attr, values) + return u + + def _check_valid_deleted(self, u, attr, values, to_delete): + u_attr = getattr(u, attr) original_length = len(self.TOP[attr]) assert len(u_attr) == original_length - _delete_func = getattr(universe, 'delete_'+attr) - _delete_func(values) - u_attr = getattr(universe, attr) + _delete_func = getattr(u, 'delete_'+attr) + _delete_func(to_delete) + u_attr = getattr(u, attr) 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]) - @pytest.mark.parametrize( - 'attr,values', ( - ('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],]), - ) - ) - def test_delete_missing_indices(self, universe, attr, values): - u_attr = getattr(universe, attr) + 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(universe, 'delete_'+attr) + _delete_func = getattr(u, 'delete_'+attr) with pytest.raises(ValueError) as excinfo: - _delete_func(values) - assert 'Cannot delete nonexistent' in str(excinfo.value) + _delete_func(to_delete) + assert err_msg in str(excinfo.value) @pytest.mark.parametrize( - 'attr,values', ( - ('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]]), - ) + 'attr,values', existing_atom_indices ) - def test_delete_valid_atomgroup(self, universe, attr, values): - u_attr = getattr(universe, attr) - original_length = len(self.TOP[attr]) - assert len(u_attr) == original_length + def test_delete_valid_indices(self, universe, attr, values): + self._check_valid_deleted(universe, attr, values, values) - _delete_func = getattr(universe, 'delete_'+attr) - atomgroups = [universe.atoms[x] for x in values] - _delete_func(atomgroups) - u_attr = getattr(universe, attr) - assert len(u_attr) == original_length-len(values) + @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') - not_deleted = [x for x in self.TOP[attr] if list(x) not in values] - assert all(_a_or_reversed_in_b(x, u_attr.indices) - for x in not_deleted) + @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', ( - ('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],]), - ) + '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') + + @pytest.mark.parametrize( + 'attr,values', nonexisting_atom_indices ) def test_delete_missing_atomgroup(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) - atomgroups = [universe.atoms[x] for x in values] - with pytest.raises(ValueError) as excinfo: - _delete_func(atomgroups) - assert 'Cannot delete nonexistent' in str(excinfo.value) + ag = [universe.atoms[x] for x in values] + self._check_invalid_deleted(universe, attr, ag, 'Cannot delete nonexistent') @pytest.mark.parametrize( - 'attr,values', ( - ('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]]), - ) + 'attr,values', existing_atom_indices ) - def test_delete_mixed_topologyobjects_type(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) + def test_delete_mixed_type(self, universe, attr, values): mixed = [universe.atoms[values[0]]] + values[1:] - _delete_func(mixed) - u_attr = getattr(universe, attr) - assert len(u_attr) == original_length-len(values) + self._check_valid_deleted(universe, attr, values, mixed) - not_deleted = [x for x in self.TOP[attr] if list(x) not in values] - assert all(_a_or_reversed_in_b(x, u_attr.indices) - for x in not_deleted) + @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): + 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') + + @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): + arr = np.array(values) + tg = mda.core.topologyobjects.TopologyGroup(arr, universe2) + 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): + 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', ( @@ -1007,20 +1007,13 @@ def test_delete_mixed_topologyobjects_type(self, universe, attr, values): ) ) def test_delete_wrong_number_of_atoms_error(self, universe, attr, n): - _delete_func = getattr(universe, 'delete_'+attr) - with pytest.raises(ValueError) as excinfo: - _delete_func([(0, 1), (0, 1, 2), (8, 22, 1, 3), (5, 3, 4, 2)]) + 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) - assert errmsg in str(excinfo.value) + self._check_invalid_deleted(universe, attr, idx, errmsg) @pytest.mark.parametrize( - 'attr,values', ( - ('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]]), - ) + 'attr,values', existing_atom_indices ) def test_delete_missing_attr(self, attr, values): u = make_Universe() @@ -1036,12 +1029,7 @@ def test_delete_bonds_refresh_fragments(self, universe): assert len(universe.atoms.fragments) == n_fragments + 1 @pytest.mark.parametrize( - 'attr,values', ( - ('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]]), - ) + 'attr,values', existing_atom_indices ) def test_roundtrip(self, universe, attr, values): u_attr = getattr(universe, attr)