diff --git a/package/MDAnalysis/guesser/base.py b/package/MDAnalysis/guesser/base.py index 0fd7a7e18ea..c7bcc21e823 100644 --- a/package/MDAnalysis/guesser/base.py +++ b/package/MDAnalysis/guesser/base.py @@ -63,10 +63,11 @@ class FooGuesser(GuesserBase): .. versionadded:: 2.8.0 """ + def __init__(cls, name, bases, classdict): type.__init__(type, name, bases, classdict) - _GUESSERS[classdict['context'].upper()] = cls + _GUESSERS[classdict["context"].upper()] = cls class GuesserBase(metaclass=_GuesserMeta): @@ -87,7 +88,8 @@ class GuesserBase(metaclass=_GuesserMeta): .. versionadded:: 2.8.0 """ - context = 'base' + + context = "base" _guesser_methods: Dict = {} def __init__(self, universe=None, **kwargs): @@ -149,8 +151,10 @@ def guess_attr(self, attr_to_guess, force_guess=False): try: guesser_method = self._guesser_methods[attr_to_guess] except KeyError: - raise ValueError(f'{type(self).__name__} cannot guess this ' - f'attribute: {attr_to_guess}') + raise ValueError( + f"{type(self).__name__} cannot guess this " + f"attribute: {attr_to_guess}" + ) # Connection attributes should be just returned as they are always # appended to the Universe. ``force_guess`` handling should happen @@ -161,7 +165,8 @@ def guess_attr(self, attr_to_guess, force_guess=False): # check if the topology already has the attribute to partially guess it if hasattr(self._universe.atoms, attr_to_guess) and not force_guess: attr_values = np.array( - getattr(self._universe.atoms, attr_to_guess, None)) + getattr(self._universe.atoms, attr_to_guess, None) + ) empty_values = top_attr.are_values_missing(attr_values) @@ -175,8 +180,9 @@ def guess_attr(self, attr_to_guess, force_guess=False): else: logger.info( - f'There is no empty {attr_to_guess} values. Guesser did ' - f'not guess any new values for {attr_to_guess} attribute') + f"There is no empty {attr_to_guess} values. Guesser did " + f"not guess any new values for {attr_to_guess} attribute" + ) return None else: return np.array(guesser_method()) diff --git a/package/MDAnalysis/guesser/default_guesser.py b/package/MDAnalysis/guesser/default_guesser.py index ee4ede1d7d0..2ecc64c1e66 100644 --- a/package/MDAnalysis/guesser/default_guesser.py +++ b/package/MDAnalysis/guesser/default_guesser.py @@ -161,7 +161,8 @@ class DefaultGuesser(GuesserBase): .. versionadded:: 2.8.0 """ - context = 'default' + + context = "default" def __init__( self, @@ -170,7 +171,7 @@ def __init__( vdwradii=None, fudge_factor=0.55, lower_bound=0.1, - **kwargs + **kwargs, ): super().__init__( universe, @@ -178,17 +179,17 @@ def __init__( vdwradii=vdwradii, fudge_factor=fudge_factor, lower_bound=lower_bound, - **kwargs + **kwargs, ) self._guesser_methods = { - 'masses': self.guess_masses, - 'types': self.guess_types, - 'elements': self.guess_types, - 'bonds': self.guess_bonds, - 'angles': self.guess_angles, - 'dihedrals': self.guess_dihedrals, - 'impropers': self.guess_improper_dihedrals, - 'aromaticities': self.guess_aromaticities, + "masses": self.guess_masses, + "types": self.guess_types, + "elements": self.guess_types, + "bonds": self.guess_bonds, + "angles": self.guess_angles, + "dihedrals": self.guess_dihedrals, + "impropers": self.guess_improper_dihedrals, + "aromaticities": self.guess_aromaticities, } def guess_masses(self, atom_types=None, indices_to_guess=None): @@ -225,18 +226,21 @@ def guess_masses(self, atom_types=None, indices_to_guess=None): except NoDataError: try: atom_types = self.guess_types( - atom_types=self._universe.atoms.names) + atom_types=self._universe.atoms.names + ) except NoDataError: raise NoDataError( "there is no reference attributes" " (elements, types, or names)" - " in this universe to guess mass from") from None + " in this universe to guess mass from" + ) from None if indices_to_guess is not None: atom_types = atom_types[indices_to_guess] - masses = np.array([self.get_atom_mass(atom) - for atom in atom_types], dtype=np.float64) + masses = np.array( + [self.get_atom_mass(atom) for atom in atom_types], dtype=np.float64 + ) return masses def get_atom_mass(self, element): @@ -256,7 +260,8 @@ def get_atom_mass(self, element): "Unknown masses are set to 0.0 for current version, " "this will be deprecated in version 3.0.0 and replaced by" " Masse's no_value_label (np.nan)", - PendingDeprecationWarning) + PendingDeprecationWarning, + ) return 0.0 def guess_atom_mass(self, atomname): @@ -295,13 +300,16 @@ def guess_types(self, atom_types=None, indices_to_guess=None): except NoDataError: raise NoDataError( "there is no reference attributes in this universe " - "to guess types from") from None + "to guess types from" + ) from None if indices_to_guess is not None: atom_types = atom_types[indices_to_guess] - return np.array([self.guess_atom_element(atom) - for atom in atom_types], dtype=object) + return np.array( + [self.guess_atom_element(atom) for atom in atom_types], + dtype=object, + ) def guess_atom_element(self, atomname): """Guess the element of the atom from the name. @@ -315,7 +323,7 @@ def guess_atom_element(self, atomname): still not found, we iteratively continue to remove the last character or first character until we find a match. If ultimately no match is found, the first character of the stripped name is returned. - + If the input name is an empty string, an empty string is returned. The table comes from CHARMM and AMBER atom @@ -331,16 +339,16 @@ def guess_atom_element(self, atomname): :func:`guess_atom_type` :mod:`MDAnalysis.guesser.tables` """ - NUMBERS = re.compile(r'[0-9]') # match numbers - SYMBOLS = re.compile(r'[*+-]') # match *, +, - - if atomname == '': - return '' + NUMBERS = re.compile(r"[0-9]") # match numbers + SYMBOLS = re.compile(r"[*+-]") # match *, +, - + if atomname == "": + return "" try: return tables.atomelements[atomname.upper()] except KeyError: # strip symbols and numbers - no_symbols = re.sub(SYMBOLS, '', atomname) - name = re.sub(NUMBERS, '', no_symbols).upper() + no_symbols = re.sub(SYMBOLS, "", atomname) + name = re.sub(NUMBERS, "", no_symbols).upper() # just in case if name in tables.atomelements: @@ -393,7 +401,7 @@ def guess_bonds(self, atoms=None, coords=None): Raises ------ - :exc:`ValueError` + :exc:`ValueError` If inputs are malformed or `vdwradii` data is missing. @@ -410,32 +418,37 @@ def guess_bonds(self, atoms=None, coords=None): if len(atoms) != len(coords): raise ValueError("'atoms' and 'coord' must be the same length") - fudge_factor = self._kwargs.get('fudge_factor', 0.55) + fudge_factor = self._kwargs.get("fudge_factor", 0.55) # so I don't permanently change it vdwradii = tables.vdwradii.copy() - user_vdwradii = self._kwargs.get('vdwradii', None) + user_vdwradii = self._kwargs.get("vdwradii", None) # this should make algo use their values over defaults if user_vdwradii: vdwradii.update(user_vdwradii) # Try using types, then elements - if hasattr(atoms, 'types'): + if hasattr(atoms, "types"): atomtypes = atoms.types else: atomtypes = self.guess_types(atom_types=atoms.names) # check that all types have a defined vdw if not all(val in vdwradii for val in set(atomtypes)): - raise ValueError(("vdw radii for types: " + - ", ".join([t for t in set(atomtypes) if - t not in vdwradii]) + - ". These can be defined manually using the" + - f" keyword 'vdwradii'")) + raise ValueError( + ( + "vdw radii for types: " + + ", ".join( + [t for t in set(atomtypes) if t not in vdwradii] + ) + + ". These can be defined manually using the" + + f" keyword 'vdwradii'" + ) + ) - lower_bound = self._kwargs.get('lower_bound', 0.1) + lower_bound = self._kwargs.get("lower_bound", 0.1) - box = self._kwargs.get('box', None) + box = self._kwargs.get("box", None) if box is not None: box = np.asarray(box) @@ -447,14 +460,14 @@ def guess_bonds(self, atoms=None, coords=None): bonds = [] - pairs, dist = distances.self_capped_distance(coords, - max_cutoff=2.0 * max_vdw, - min_cutoff=lower_bound, - box=box) + pairs, dist = distances.self_capped_distance( + coords, max_cutoff=2.0 * max_vdw, min_cutoff=lower_bound, box=box + ) for idx, (i, j) in enumerate(pairs): - d = (vdwradii[atomtypes[i]] + - vdwradii[atomtypes[j]]) * fudge_factor - if (dist[idx] < d): + d = ( + vdwradii[atomtypes[i]] + vdwradii[atomtypes[j]] + ) * fudge_factor + if dist[idx] < d: bonds.append((atoms[i].index, atoms[j].index)) return tuple(bonds) @@ -480,18 +493,21 @@ def guess_angles(self, bonds=None): -------- :meth:`guess_bonds` - """ + """ from ..core.universe import Universe angles_found = set() - + if bonds is None: - if hasattr(self._universe.atoms, 'bonds'): + if hasattr(self._universe.atoms, "bonds"): bonds = self._universe.atoms.bonds else: temp_u = Universe.empty(n_atoms=len(self._universe.atoms)) - temp_u.add_bonds(self.guess_bonds( - self._universe.atoms, self._universe.atoms.positions)) + temp_u.add_bonds( + self.guess_bonds( + self._universe.atoms, self._universe.atoms.positions + ) + ) bonds = temp_u.atoms.bonds for b in bonds: @@ -501,7 +517,8 @@ def guess_angles(self, bonds=None): if other_b != b: # if not the same bond I start as third_a = other_b.partner(atom) desc = tuple( - [other_a.index, atom.index, third_a.index]) + [other_a.index, atom.index, third_a.index] + ) # first index always less than last if desc[0] > desc[-1]: desc = desc[::-1] @@ -530,15 +547,18 @@ def guess_dihedrals(self, angles=None): from ..core.universe import Universe if angles is None: - if hasattr(self._universe.atoms, 'angles'): + if hasattr(self._universe.atoms, "angles"): angles = self._universe.atoms.angles else: temp_u = Universe.empty(n_atoms=len(self._universe.atoms)) - temp_u.add_bonds(self.guess_bonds( - self._universe.atoms, self._universe.atoms.positions)) - + temp_u.add_bonds( + self.guess_bonds( + self._universe.atoms, self._universe.atoms.positions + ) + ) + temp_u.add_angles(self.guess_angles(temp_u.atoms.bonds)) angles = temp_u.atoms.angles @@ -549,8 +569,9 @@ def guess_dihedrals(self, angles=None): a_tup = tuple([a.index for a in b]) # angle as tuple of numbers # if searching with b[0], want tuple of (b[2], b[1], b[0], +new) # search the first and last atom of each angle - for atom, prefix in zip([b.atoms[0], b.atoms[-1]], - [a_tup[::-1], a_tup]): + for atom, prefix in zip( + [b.atoms[0], b.atoms[-1]], [a_tup[::-1], a_tup] + ): for other_b in atom.bonds: if not other_b.partner(atom) in b: third_a = other_b.partner(atom) @@ -580,14 +601,17 @@ def guess_improper_dihedrals(self, angles=None): from ..core.universe import Universe if angles is None: - if hasattr(self._universe.atoms, 'angles'): + if hasattr(self._universe.atoms, "angles"): angles = self._universe.atoms.angles else: temp_u = Universe.empty(n_atoms=len(self._universe.atoms)) - temp_u.add_bonds(self.guess_bonds( - self._universe.atoms, self._universe.atoms.positions)) + temp_u.add_bonds( + self.guess_bonds( + self._universe.atoms, self._universe.atoms.positions + ) + ) temp_u.add_angles(self.guess_angles(temp_u.atoms.bonds)) @@ -652,7 +676,12 @@ def guess_gasteiger_charges(self, atomgroup): mol = atomgroup.convert_to("RDKIT") from rdkit.Chem.rdPartialCharges import ComputeGasteigerCharges + ComputeGasteigerCharges(mol, throwOnParamFailure=True) - return np.array([atom.GetDoubleProp("_GasteigerCharge") - for atom in mol.GetAtoms()], - dtype=np.float32) + return np.array( + [ + atom.GetDoubleProp("_GasteigerCharge") + for atom in mol.GetAtoms() + ], + dtype=np.float32, + ) diff --git a/package/pyproject.toml b/package/pyproject.toml index 74598c1b049..876d2910dbd 100644 --- a/package/pyproject.toml +++ b/package/pyproject.toml @@ -135,6 +135,7 @@ tables\.py | MDAnalysis/visualization/.*\.py | MDAnalysis/lib/.*\.py^ | MDAnalysis/transformations/.*\.py +| MDAnalysis/guesser/.*\.py | MDAnalysis/converters/.*\.py | MDAnalysis/selections/.*\.py ) diff --git a/testsuite/MDAnalysisTests/guesser/test_base.py b/testsuite/MDAnalysisTests/guesser/test_base.py index c44fdc3c591..2c753c9af3e 100644 --- a/testsuite/MDAnalysisTests/guesser/test_base.py +++ b/testsuite/MDAnalysisTests/guesser/test_base.py @@ -20,89 +20,98 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -import pytest -import numpy as np import MDAnalysis as mda -from MDAnalysis.guesser.base import GuesserBase, get_guesser -from MDAnalysis.core.topology import Topology -from MDAnalysis.core.topologyattrs import Masses, Atomnames, Atomtypes import MDAnalysis.tests.datafiles as datafiles +import numpy as np +import pytest +from MDAnalysis import _GUESSERS, _TOPOLOGY_ATTRS +from MDAnalysis.core.topology import Topology +from MDAnalysis.core.topologyattrs import Atomnames, Atomtypes, Masses from MDAnalysis.exceptions import NoDataError +from MDAnalysis.guesser.base import GuesserBase, get_guesser from numpy.testing import assert_allclose, assert_equal -from MDAnalysis import _TOPOLOGY_ATTRS, _GUESSERS - -class TestBaseGuesser(): +class TestBaseGuesser: def test_get_guesser(self): class TestGuesser1(GuesserBase): - context = 'test1' + context = "test1" class TestGuesser2(GuesserBase): - context = 'test2' + context = "test2" - assert get_guesser(TestGuesser1).context == 'test1' - assert get_guesser('test1').context == 'test1' - assert get_guesser(TestGuesser2()).context == 'test2' + assert get_guesser(TestGuesser1).context == "test1" + assert get_guesser("test1").context == "test1" + assert get_guesser(TestGuesser2()).context == "test2" def test_get_guesser_with_universe(self): class TestGuesser1(GuesserBase): - context = 'test1' + context = "test1" u = mda.Universe.empty(n_atoms=5) guesser = get_guesser(TestGuesser1(), u, foo=1) assert len(guesser._universe.atoms) == 5 - assert 'foo' in guesser._kwargs + assert "foo" in guesser._kwargs def test_guess_invalid_attribute(self): - with pytest.raises(ValueError, - match='default guesser can not guess ' - 'the following attribute: foo'): - mda.Universe(datafiles.PDB, to_guess=['foo']) + with pytest.raises( + ValueError, + match="default guesser can not guess " + "the following attribute: foo", + ): + mda.Universe(datafiles.PDB, to_guess=["foo"]) def test_guess_attribute_with_missing_parent_attr(self): - names = Atomnames(np.array(['C', 'HB', 'HA', 'O'], dtype=object)) + names = Atomnames(np.array(["C", "HB", "HA", "O"], dtype=object)) masses = Masses( - np.array([np.nan, np.nan, np.nan, np.nan], dtype=np.float64)) - top = Topology(4, 1, 1, attrs=[names, masses, ]) - u = mda.Universe(top, to_guess=['masses']) - assert_allclose(u.atoms.masses, np.array( - [12.01100, 1.00800, 1.00800, 15.99900]), atol=0) + np.array([np.nan, np.nan, np.nan, np.nan], dtype=np.float64) + ) + top = Topology(4, 1, 1, attrs=[names, masses]) + u = mda.Universe(top, to_guess=["masses"]) + assert_allclose( + u.atoms.masses, + np.array([12.01100, 1.00800, 1.00800, 15.99900]), + atol=0, + ) def test_force_guessing(self): - names = Atomnames(np.array(['C', 'H', 'H', 'O'], dtype=object)) - types = Atomtypes(np.array(['1', '2', '3', '4'], dtype=object)) - top = Topology(4, 1, 1, attrs=[names, types, ]) - u = mda.Universe(top, force_guess=['types']) - assert_equal(u.atoms.types, ['C', 'H', 'H', 'O']) + names = Atomnames(np.array(["C", "H", "H", "O"], dtype=object)) + types = Atomtypes(np.array(["1", "2", "3", "4"], dtype=object)) + top = Topology(4, 1, 1, attrs=[names, types]) + u = mda.Universe(top, force_guess=["types"]) + assert_equal(u.atoms.types, ["C", "H", "H", "O"]) def test_partial_guessing(self): - types = Atomtypes(np.array(['C', 'H', 'H', 'O'], dtype=object)) + types = Atomtypes(np.array(["C", "H", "H", "O"], dtype=object)) masses = Masses(np.array([0, np.nan, np.nan, 0], dtype=np.float64)) - top = Topology(4, 1, 1, attrs=[types, masses, ]) - u = mda.Universe(top, to_guess=['masses']) - assert_allclose(u.atoms.masses, np.array( - [0, 1.00800, 1.00800, 0]), atol=0) + top = Topology(4, 1, 1, attrs=[types, masses]) + u = mda.Universe(top, to_guess=["masses"]) + assert_allclose( + u.atoms.masses, np.array([0, 1.00800, 1.00800, 0]), atol=0 + ) def test_force_guess_priority(self): "check that passing the attribute to force_guess have higher power" - types = Atomtypes(np.array(['C', 'H', 'H', 'O'], dtype=object)) + types = Atomtypes(np.array(["C", "H", "H", "O"], dtype=object)) masses = Masses(np.array([0, np.nan, np.nan, 0], dtype=np.float64)) - top = Topology(4, 1, 1, attrs=[types, masses, ]) - u = mda.Universe(top, to_guess=['masses'], force_guess=['masses']) - assert_allclose(u.atoms.masses, np.array( - [12.01100, 1.00800, 1.00800, 15.99900]), atol=0) + top = Topology(4, 1, 1, attrs=[types, masses]) + u = mda.Universe(top, to_guess=["masses"], force_guess=["masses"]) + assert_allclose( + u.atoms.masses, + np.array([12.01100, 1.00800, 1.00800, 15.99900]), + atol=0, + ) def test_partial_guess_attr_with_unknown_no_value_label(self): "trying to partially guess attribute tha doesn't have declared" "no_value_label should gives no effect" - names = Atomnames(np.array(['C', 'H', 'H', 'O'], dtype=object)) - types = Atomtypes(np.array(['', '', '', ''], dtype=object)) - top = Topology(4, 1, 1, attrs=[names, types, ]) - u = mda.Universe(top, to_guess=['types']) - assert_equal(u.atoms.types, ['', '', '', '']) + names = Atomnames(np.array(["C", "H", "H", "O"], dtype=object)) + types = Atomtypes(np.array(["", "", "", ""], dtype=object)) + top = Topology(4, 1, 1, attrs=[names, types]) + u = mda.Universe(top, to_guess=["types"]) + assert_equal(u.atoms.types, ["", "", "", ""]) def test_guess_topology_objects_existing_read(self): u = mda.Universe(datafiles.CONECT) @@ -166,7 +175,7 @@ def test_guess_topology_objects_out_of_order_init(self): u = mda.Universe( datafiles.PDB_small, to_guess=["dihedrals", "angles", "bonds"], - guess_bonds=False + guess_bonds=False, ) assert len(u.atoms.angles) == 6123 assert len(u.atoms.dihedrals) == 8921 @@ -177,8 +186,7 @@ def test_guess_topology_objects_out_of_order_guess(self): u.atoms.angles u.guess_TopologyAttrs( - "default", - to_guess=["dihedrals", "angles", "bonds"] + "default", to_guess=["dihedrals", "angles", "bonds"] ) assert len(u.atoms.angles) == 6123 assert len(u.atoms.dihedrals) == 8921 @@ -223,43 +231,50 @@ def test_guess_invalid_attribute(self): default_guesser = get_guesser("default") err = "not a recognized MDAnalysis topology attribute" with pytest.raises(KeyError, match=err): - default_guesser.guess_attr('not_an_attribute') + default_guesser.guess_attr("not_an_attribute") def test_guess_unsupported_attribute(self): default_guesser = get_guesser("default") err = "cannot guess this attribute" with pytest.raises(ValueError, match=err): - default_guesser.guess_attr('tempfactors') - + default_guesser.guess_attr("tempfactors") + def test_guess_singular(self): default_guesser = get_guesser("default") u = mda.Universe(datafiles.PDB, to_guess=[]) assert not hasattr(u.atoms, "masses") default_guesser._universe = u - masses = default_guesser.guess_attr('mass') + masses = default_guesser.guess_attr("mass") def test_Universe_guess_bonds_deprecated(): with pytest.warns( - DeprecationWarning, - match='`guess_bonds` keyword is deprecated' + DeprecationWarning, match="`guess_bonds` keyword is deprecated" ): u = mda.Universe(datafiles.PDB_full, guess_bonds=True) @pytest.mark.parametrize( "universe_input", - [datafiles.DCD, datafiles.XTC, np.random.rand(3, 3), datafiles.PDB] + [datafiles.DCD, datafiles.XTC, np.random.rand(3, 3), datafiles.PDB], ) def test_universe_creation_from_coordinates(universe_input): mda.Universe(universe_input) def test_universe_creation_from_specific_array(): - a = np.array([ - [0., 0., 150.], [0., 0., 150.], [200., 0., 150.], - [0., 0., 150.], [100., 100., 150.], [200., 100., 150.], - [0., 200., 150.], [100., 200., 150.], [200., 200., 150.] - ]) + a = np.array( + [ + [0.0, 0.0, 150.0], + [0.0, 0.0, 150.0], + [200.0, 0.0, 150.0], + [0.0, 0.0, 150.0], + [100.0, 100.0, 150.0], + [200.0, 100.0, 150.0], + [0.0, 200.0, 150.0], + [100.0, 200.0, 150.0], + [200.0, 200.0, 150.0], + ] + ) mda.Universe(a, n_atoms=9) diff --git a/testsuite/MDAnalysisTests/guesser/test_default_guesser.py b/testsuite/MDAnalysisTests/guesser/test_default_guesser.py index fe8e012c8c4..92c24593c35 100644 --- a/testsuite/MDAnalysisTests/guesser/test_default_guesser.py +++ b/testsuite/MDAnalysisTests/guesser/test_default_guesser.py @@ -41,8 +41,9 @@ except ImportError: pass -requires_rdkit = pytest.mark.skipif(import_not_available("rdkit"), - reason="requires RDKit") +requires_rdkit = pytest.mark.skipif( + import_not_available("rdkit"), reason="requires RDKit" +) @pytest.fixture @@ -52,68 +53,76 @@ def default_guesser(): class TestGuessMasses(object): def test_guess_masses_from_universe(self): - topology = Topology(3, attrs=[Atomtypes(['C', 'C', 'H'])]) + topology = Topology(3, attrs=[Atomtypes(["C", "C", "H"])]) u = mda.Universe(topology) assert isinstance(u.atoms.masses, np.ndarray) - assert_allclose(u.atoms.masses, np.array( - [12.011, 12.011, 1.008]), atol=0) + assert_allclose( + u.atoms.masses, np.array([12.011, 12.011, 1.008]), atol=0 + ) def test_guess_masses_from_guesser_object(self, default_guesser): - elements = ['H', 'Ca', 'Am'] + elements = ["H", "Ca", "Am"] values = np.array([1.008, 40.08000, 243.0]) - assert_allclose(default_guesser.guess_masses( - elements), values, atol=0) + assert_allclose(default_guesser.guess_masses(elements), values, atol=0) def test_guess_masses_warn(self): - topology = Topology(2, attrs=[Atomtypes(['X', 'Z'])]) + topology = Topology(2, attrs=[Atomtypes(["X", "Z"])]) msg = "Unknown masses are set to 0.0 for current version, " "this will be depracated in version 3.0.0 and replaced by" " Masse's no_value_label (np.nan)" with pytest.warns(PendingDeprecationWarning, match=msg): - u = mda.Universe(topology, to_guess=['masses']) + u = mda.Universe(topology, to_guess=["masses"]) assert_allclose(u.atoms.masses, np.array([0.0, 0.0]), atol=0) - @pytest.mark.parametrize('element, value', (('H', 1.008), ('XYZ', 0.0),)) + @pytest.mark.parametrize( + "element, value", + ( + ("H", 1.008), + ("XYZ", 0.0), + ), + ) def test_get_atom_mass(self, element, value, default_guesser): default_guesser.get_atom_mass(element) == approx(value) def test_guess_atom_mass(self, default_guesser): - assert default_guesser.guess_atom_mass('1H') == approx(1.008) + assert default_guesser.guess_atom_mass("1H") == approx(1.008) def test_guess_masses_with_no_reference_elements(self): u = mda.Universe.empty(3) - with pytest.raises(NoDataError, - match=('there is no reference attributes ')): - u.guess_TopologyAttrs('default', ['masses']) + with pytest.raises( + NoDataError, match=("there is no reference attributes ") + ): + u.guess_TopologyAttrs("default", ["masses"]) class TestGuessTypes(object): def test_guess_types(self): - topology = Topology(2, attrs=[Atomnames(['MG2+', 'C12'])]) - u = mda.Universe(topology, to_guess=['types']) + topology = Topology(2, attrs=[Atomnames(["MG2+", "C12"])]) + u = mda.Universe(topology, to_guess=["types"]) assert isinstance(u.atoms.types, np.ndarray) - assert_equal(u.atoms.types, np.array(['MG', 'C'], dtype=object)) + assert_equal(u.atoms.types, np.array(["MG", "C"], dtype=object)) def test_guess_atom_element(self, default_guesser): - assert default_guesser.guess_atom_element('MG2+') == 'MG' + assert default_guesser.guess_atom_element("MG2+") == "MG" def test_guess_atom_element_empty(self, default_guesser): - assert default_guesser.guess_atom_element('') == '' + assert default_guesser.guess_atom_element("") == "" def test_guess_atom_element_singledigit(self, default_guesser): - assert default_guesser.guess_atom_element('1') == '1' + assert default_guesser.guess_atom_element("1") == "1" def test_guess_atom_element_1H(self, default_guesser): - assert default_guesser.guess_atom_element('1H') == 'H' - assert default_guesser.guess_atom_element('2H') == 'H' + assert default_guesser.guess_atom_element("1H") == "H" + assert default_guesser.guess_atom_element("2H") == "H" def test_partial_guess_elements(self, default_guesser): - names = np.array(['BR123', 'Hk', 'C12'], dtype=object) - elements = np.array(['BR', 'C'], dtype=object) + names = np.array(["BR123", "Hk", "C12"], dtype=object) + elements = np.array(["BR", "C"], dtype=object) guessed_elements = default_guesser.guess_types( - atom_types=names, indices_to_guess=[True, False, True]) + atom_types=names, indices_to_guess=[True, False, True] + ) assert_equal(elements, guessed_elements) def test_guess_elements_from_no_data(self): @@ -123,36 +132,39 @@ def test_guess_elements_from_no_data(self): "universe to guess types from" ) with pytest.warns(UserWarning, match=msg): - mda.Universe(top, to_guess=['types']) - - @pytest.mark.parametrize('name, element', ( - ('AO5*', 'O'), - ('F-', 'F'), - ('HB1', 'H'), - ('OC2', 'O'), - ('1he2', 'H'), - ('3hg2', 'H'), - ('OH-', 'O'), - ('HO', 'H'), - ('he', 'H'), - ('zn', 'ZN'), - ('Ca2+', 'CA'), - ('CA', 'C'), - )) + mda.Universe(top, to_guess=["types"]) + + @pytest.mark.parametrize( + "name, element", + ( + ("AO5*", "O"), + ("F-", "F"), + ("HB1", "H"), + ("OC2", "O"), + ("1he2", "H"), + ("3hg2", "H"), + ("OH-", "O"), + ("HO", "H"), + ("he", "H"), + ("zn", "ZN"), + ("Ca2+", "CA"), + ("CA", "C"), + ), + ) def test_guess_element_from_name(self, name, element, default_guesser): assert default_guesser.guess_atom_element(name) == element def test_guess_charge(default_guesser): # this always returns 0.0 - assert default_guesser.guess_atom_charge('this') == approx(0.0) + assert default_guesser.guess_atom_charge("this") == approx(0.0) def test_guess_bonds_Error(): u = make_Universe(trajectory=True) msg = "This Universe does not contain name information" with pytest.raises(NoDataError, match=msg): - u.guess_TopologyAttrs(to_guess=['bonds']) + u.guess_TopologyAttrs(to_guess=["bonds"]) def test_guess_bond_vdw_error(): @@ -164,16 +176,16 @@ def test_guess_bond_vdw_error(): def test_guess_bond_coord_error(default_guesser): msg = "atoms' and 'coord' must be the same length" with pytest.raises(ValueError, match=msg): - default_guesser.guess_bonds(['N', 'O', 'C'], [[1, 2, 3]]) + default_guesser.guess_bonds(["N", "O", "C"], [[1, 2, 3]]) def test_guess_angles_with_no_bonds(): "Test guessing angles for atoms with no bonds" " information without adding bonds to universe " u = mda.Universe(datafiles.two_water_gro) - u.guess_TopologyAttrs(to_guess=['angles']) - assert hasattr(u, 'angles') - assert not hasattr(u, 'bonds') + u.guess_TopologyAttrs(to_guess=["angles"]) + assert hasattr(u, "angles") + assert not hasattr(u, "bonds") def test_guess_impropers(default_guesser): @@ -188,41 +200,42 @@ def test_guess_impropers(default_guesser): def test_guess_dihedrals_with_no_angles(): - "Test guessing dihedrals for atoms with no angles " + "Test guessing dihedrals for atoms with no angles" "information without adding bonds or angles to universe" u = mda.Universe(datafiles.two_water_gro) - u.guess_TopologyAttrs(to_guess=['dihedrals']) - assert hasattr(u, 'dihedrals') - assert not hasattr(u, 'angles') - assert not hasattr(u, 'bonds') + u.guess_TopologyAttrs(to_guess=["dihedrals"]) + assert hasattr(u, "dihedrals") + assert not hasattr(u, "angles") + assert not hasattr(u, "bonds") def test_guess_impropers_with_angles(): - "Test guessing impropers for atoms with angles " + "Test guessing impropers for atoms with angles" "and bonds information " - u = mda.Universe(datafiles.two_water_gro, - to_guess=['bonds', 'angles', 'impropers']) - u.guess_TopologyAttrs(to_guess=['impropers']) - assert hasattr(u, 'impropers') - assert hasattr(u, 'angles') - assert hasattr(u, 'bonds') + u = mda.Universe( + datafiles.two_water_gro, to_guess=["bonds", "angles", "impropers"] + ) + u.guess_TopologyAttrs(to_guess=["impropers"]) + assert hasattr(u, "impropers") + assert hasattr(u, "angles") + assert hasattr(u, "bonds") def test_guess_impropers_with_no_angles(): - "Test guessing impropers for atoms with no angles " + "Test guessing impropers for atoms with no angles" "information without adding bonds or angles to universe" u = mda.Universe(datafiles.two_water_gro) - u.guess_TopologyAttrs(to_guess=['impropers']) - assert hasattr(u, 'impropers') - assert not hasattr(u, 'angles') - assert not hasattr(u, 'bonds') + u.guess_TopologyAttrs(to_guess=["impropers"]) + assert hasattr(u, "impropers") + assert not hasattr(u, "angles") + assert not hasattr(u, "bonds") def bond_sort(arr): # sort from low to high, also within a tuple # e.g. ([5, 4], [0, 1], [0, 3]) -> ([0, 1], [0, 3], [4, 5]) out = [] - for (i, j) in arr: + for i, j in arr: if i > j: i, j = j, i out.append((i, j)) @@ -231,52 +244,52 @@ def bond_sort(arr): def test_guess_bonds_water(): u = mda.Universe(datafiles.two_water_gro) - bonds = bond_sort(DefaultGuesser( - None, box=u.dimensions).guess_bonds(u.atoms, u.atoms.positions)) - assert_equal(bonds, ((0, 1), - (0, 2), - (3, 4), - (3, 5))) + bonds = bond_sort( + DefaultGuesser(None, box=u.dimensions).guess_bonds( + u.atoms, u.atoms.positions + ) + ) + assert_equal(bonds, ((0, 1), (0, 2), (3, 4), (3, 5))) @pytest.mark.parametrize( - "fudge_factor, n_bonds", - [(0, 0), (0.55, 4), (200, 6)] + "fudge_factor, n_bonds", [(0, 0), (0.55, 4), (200, 6)] ) def test_guess_bonds_water_fudge_factor_passed(fudge_factor, n_bonds): u = mda.Universe( - datafiles.two_water_gro, - fudge_factor=fudge_factor, - to_guess=("types", "bonds") - ) + datafiles.two_water_gro, + fudge_factor=fudge_factor, + to_guess=("types", "bonds"), + ) assert len(u.atoms.bonds) == n_bonds def test_guess_bonds_adk(): u = mda.Universe(datafiles.PSF, datafiles.DCD) - u.guess_TopologyAttrs(force_guess=['types']) + u.guess_TopologyAttrs(force_guess=["types"]) guesser = DefaultGuesser(None) bonds = bond_sort(guesser.guess_bonds(u.atoms, u.atoms.positions)) - assert_equal(np.sort(u.bonds.indices, axis=0), - np.sort(bonds, axis=0)) + assert_equal(np.sort(u.bonds.indices, axis=0), np.sort(bonds, axis=0)) def test_guess_bonds_peptide(): u = mda.Universe(datafiles.PSF_NAMD, datafiles.PDB_NAMD) - u.guess_TopologyAttrs(force_guess=['types']) + u.guess_TopologyAttrs(force_guess=["types"]) guesser = DefaultGuesser(None) bonds = bond_sort(guesser.guess_bonds(u.atoms, u.atoms.positions)) - assert_equal(np.sort(u.bonds.indices, axis=0), - np.sort(bonds, axis=0)) + assert_equal(np.sort(u.bonds.indices, axis=0), np.sort(bonds, axis=0)) -@pytest.mark.parametrize("smi", [ - "c1ccccc1", - "C1=CC=CC=C1", - "CCO", - "c1ccccc1Cc1ccccc1", - "CN1C=NC2=C1C(=O)N(C(=O)N2C)C", -]) +@pytest.mark.parametrize( + "smi", + [ + "c1ccccc1", + "C1=CC=CC=C1", + "CCO", + "c1ccccc1Cc1ccccc1", + "CN1C=NC2=C1C(=O)N(C(=O)N2C)C", + ], +) @requires_rdkit def test_guess_aromaticities(smi): mol = Chem.MolFromSmiles(smi) @@ -285,25 +298,30 @@ def test_guess_aromaticities(smi): u = mda.Universe(mol) guesser = DefaultGuesser(None) values = guesser.guess_aromaticities(u.atoms) - u.guess_TopologyAttrs(to_guess=['aromaticities']) + u.guess_TopologyAttrs(to_guess=["aromaticities"]) assert_equal(values, expected) assert_equal(u.atoms.aromaticities, expected) -@pytest.mark.parametrize("smi", [ - "c1ccccc1", - "C1=CC=CC=C1", - "CCO", - "c1ccccc1Cc1ccccc1", - "CN1C=NC2=C1C(=O)N(C(=O)N2C)C", -]) +@pytest.mark.parametrize( + "smi", + [ + "c1ccccc1", + "C1=CC=CC=C1", + "CCO", + "c1ccccc1Cc1ccccc1", + "CN1C=NC2=C1C(=O)N(C(=O)N2C)C", + ], +) @requires_rdkit def test_guess_gasteiger_charges(smi): mol = Chem.MolFromSmiles(smi) mol = Chem.AddHs(mol) ComputeGasteigerCharges(mol, throwOnParamFailure=True) - expected = np.array([atom.GetDoubleProp("_GasteigerCharge") - for atom in mol.GetAtoms()], dtype=np.float32) + expected = np.array( + [atom.GetDoubleProp("_GasteigerCharge") for atom in mol.GetAtoms()], + dtype=np.float32, + ) u = mda.Universe(mol) guesser = DefaultGuesser(None) values = guesser.guess_gasteiger_charges(u.atoms) @@ -312,7 +330,8 @@ def test_guess_gasteiger_charges(smi): @requires_rdkit def test_aromaticity(): - u = mda.Universe(datafiles.PDB_small, - to_guess=['elements', 'aromaticities']) - c_aromatic = u.select_atoms('resname PHE and name CD1') + u = mda.Universe( + datafiles.PDB_small, to_guess=["elements", "aromaticities"] + ) + c_aromatic = u.select_atoms("resname PHE and name CD1") assert_equal(c_aromatic.aromaticities[0], True) diff --git a/testsuite/pyproject.toml b/testsuite/pyproject.toml index 6be06957d79..1636dc406d0 100644 --- a/testsuite/pyproject.toml +++ b/testsuite/pyproject.toml @@ -162,6 +162,7 @@ setup\.py | MDAnalysisTests/auxiliary/.*\.py | MDAnalysisTests/lib/.*\.py | MDAnalysisTests/transformations/.*\.py +| MDAnalysisTests/guesser/.*\.py | MDAnalysisTests/converters/.*\.py ) '''