diff --git a/package/MDAnalysis/core/topologyattrs.py b/package/MDAnalysis/core/topologyattrs.py index 63a637ca97..9207a3b06f 100644 --- a/package/MDAnalysis/core/topologyattrs.py +++ b/package/MDAnalysis/core/topologyattrs.py @@ -247,18 +247,14 @@ 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 original docstring is assumed to be formatted with black # (i.e., *all text* lines are already indented, the first line # is a triple quote followed by newline) @@ -396,9 +392,7 @@ 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 " @@ -476,9 +470,7 @@ 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 @@ -829,9 +821,7 @@ 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] @@ -954,8 +944,7 @@ def phi_selections(residues, c_name="C", n_name="N", ca_name="CA"): 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 + 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 @@ -1146,8 +1135,7 @@ def psi_selections(residues, c_name="C", n_name="N", ca_name="CA"): 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 + 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] @@ -1259,12 +1247,9 @@ 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_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 + 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] @@ -1390,6 +1375,7 @@ class Atomtypes(AtomStringAttr): singular = "type" per_object = "atom" dtype = object + missing_value_label = "" # TODO: update docs to property doc @@ -1399,6 +1385,7 @@ class Elements(AtomStringAttr): attrname = "elements" singular = "element" dtype = object + missing_value_label = "" @staticmethod def _gen_initial_values(na, nr, ns): @@ -1766,13 +1753,9 @@ 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 - ) + 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() + com = (com * group.masses[:, None]).sum(axis=0) / group.masses.sum() if wrap: pos = atomgroup.pack_into_box(inplace=False) - com @@ -1903,9 +1886,7 @@ def _gyration(recenteredpos, masses): atomgroup = group.atoms masses = atomgroup.masses - com = atomgroup.center_of_mass( - wrap=wrap, unwrap=unwrap, compound=compound - ) + com = atomgroup.center_of_mass(wrap=wrap, unwrap=unwrap, compound=compound) if compound == "group": if wrap: @@ -1923,7 +1904,7 @@ def _gyration(recenteredpos, masses): recenteredpos = atomgroup.positions - com eig_vals = _gyration(recenteredpos, masses) else: - (atom_masks, compound_masks, n_compounds) = ( + atom_masks, compound_masks, n_compounds = ( atomgroup._split_by_compound_indices(compound) ) @@ -2040,8 +2021,7 @@ def asphericity(group, wrap=False, unwrap=False, compound="group"): ) else: shape = (3.0 / 2.0) * ( - np.sum((eig_vals - np.mean(eig_vals)) ** 2) - / np.sum(eig_vals) ** 2 + np.sum((eig_vals - np.mean(eig_vals)) ** 2) / np.sum(eig_vals) ** 2 ) return shape @@ -2125,9 +2105,7 @@ def align_principal_axis(group, axis, vector): # print "axis = %r, angle = %f deg" % (ax, angle) return group.rotateby(angle, ax) - transplants[GroupBase].append( - ("align_principal_axis", align_principal_axis) - ) + transplants[GroupBase].append(("align_principal_axis", align_principal_axis)) # TODO: update docs to property doc @@ -2284,9 +2262,7 @@ def total_charge(group, compound="group"): @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:: @@ -2352,9 +2328,7 @@ def dipole_vector( 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 @@ -2380,11 +2354,9 @@ def dipole_vector( ) else: recenteredpos = atomgroup.positions - ref - dipole_vector = np.einsum( - "ij,ij->j", recenteredpos, charges[:, np.newaxis] - ) + dipole_vector = np.einsum("ij,ij->j", recenteredpos, charges[:, np.newaxis]) else: - (atom_masks, compound_masks, n_compounds) = ( + atom_masks, compound_masks, n_compounds = ( atomgroup._split_by_compound_indices(compound) ) @@ -2466,13 +2438,9 @@ 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 @@ -2565,9 +2533,7 @@ 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 @@ -2595,7 +2561,7 @@ def __quadrupole_tensor(recenteredpos, charges): recenteredpos = atomgroup.positions - ref quad_tensor = __quadrupole_tensor(recenteredpos, charges) else: - (atom_masks, compound_masks, n_compounds) = ( + atom_masks, compound_masks, n_compounds = ( atomgroup._split_by_compound_indices(compound) ) @@ -2685,9 +2651,7 @@ 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 @@ -3011,9 +2975,7 @@ def sequence(self, **kwargs): ) ) 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" @@ -3146,14 +3108,13 @@ 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) + 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) + ("{} must be an iterable of tuples with {}" " atom indices").format( + self.attrname, self._n_atoms + ) ) clean = [] for v in values: @@ -3231,9 +3192,7 @@ 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 @@ -3252,9 +3211,7 @@ 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] diff --git a/testsuite/MDAnalysisTests/guesser/test_base.py b/testsuite/MDAnalysisTests/guesser/test_base.py index e890ca9957..25b18e1965 100644 --- a/testsuite/MDAnalysisTests/guesser/test_base.py +++ b/testsuite/MDAnalysisTests/guesser/test_base.py @@ -26,7 +26,7 @@ import pytest from MDAnalysis import _GUESSERS, _TOPOLOGY_ATTRS from MDAnalysis.core.topology import Topology -from MDAnalysis.core.topologyattrs import Atomnames, Atomtypes, Masses +from MDAnalysis.core.topologyattrs import Atomnames, Atomtypes, Elements, Masses from MDAnalysis.exceptions import NoDataError from MDAnalysis.guesser.base import GuesserBase, get_guesser from numpy.testing import assert_allclose, assert_equal @@ -58,16 +58,13 @@ class TestGuesser1(GuesserBase): def test_guess_invalid_attribute(self): with pytest.raises( ValueError, - match="default guesser can not guess " - "the following attribute: foo", + match="default guesser can not guess " "the following attribute: foo", ): mda.Universe(datafiles.PDB_xsmall, to_guess=["foo"]) def test_guess_attribute_with_missing_parent_attr(self): 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) - ) + 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( @@ -88,9 +85,7 @@ def test_partial_guessing(self): 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 - ) + 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" @@ -104,14 +99,21 @@ def test_force_guess_priority(self): 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" + def test_partial_guess_types_with_missing_value_label(self): + """Atomtypes with empty string values are guessed because + missing_value_label is defined as an empty string.""" 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, ["", "", "", ""]) + assert_equal(u.atoms.types, ["C", "H", "H", "O"]) + + def test_partial_guess_elements_with_missing_value_label(self): + names = Atomnames(np.array(["C1", "N1"], dtype=object)) + elements = Elements(np.array(["C", ""], dtype=object)) + top = Topology(2, 1, 1, attrs=[names, elements]) + u = mda.Universe(top, to_guess=["elements"]) + assert_equal(u.atoms.elements, ["C", "N"]) def test_guess_topology_objects_existing_read(self): u = mda.Universe(datafiles.CONECT) @@ -185,9 +187,7 @@ def test_guess_topology_objects_out_of_order_guess(self): with pytest.raises(NoDataError): u.atoms.angles - u.guess_TopologyAttrs( - "default", to_guess=["dihedrals", "angles", "bonds"] - ) + u.guess_TopologyAttrs("default", to_guess=["dihedrals", "angles", "bonds"]) assert len(u.atoms.angles) == 290 assert len(u.atoms.dihedrals) == 411 @@ -249,9 +249,7 @@ def test_guess_singular(self): def test_Universe_guess_bonds_deprecated(): - with pytest.warns( - DeprecationWarning, match="`guess_bonds` keyword is deprecated" - ): + with pytest.warns(DeprecationWarning, match="`guess_bonds` keyword is deprecated"): u = mda.Universe(datafiles.PDB_xsmall, guess_bonds=True)