From 4b383aeb0066984ffc51501c293aa1287972f7ff Mon Sep 17 00:00:00 2001 From: IAlibay Date: Sun, 20 Oct 2024 10:20:41 +0100 Subject: [PATCH 01/11] revert old guessers removal --- package/MDAnalysis/topology/guessers.py | 526 ++++++++++++++++++++++++ 1 file changed, 526 insertions(+) create mode 100644 package/MDAnalysis/topology/guessers.py diff --git a/package/MDAnalysis/topology/guessers.py b/package/MDAnalysis/topology/guessers.py new file mode 100644 index 00000000000..a0847036de8 --- /dev/null +++ b/package/MDAnalysis/topology/guessers.py @@ -0,0 +1,526 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# +# MDAnalysis --- https://www.mdanalysis.org +# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors +# (see the file AUTHORS for the full list of names) +# +# Released under the GNU Public Licence, v2 or any higher version +# +# Please cite your use of MDAnalysis in published work: +# +# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, +# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. +# MDAnalysis: A Python package for the rapid analysis of molecular dynamics +# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th +# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy. +# doi: 10.25080/majora-629e541a-00e +# +# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. +# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. +# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 +# +""" +Guessing unknown Topology information --- :mod:`MDAnalysis.topology.guessers` +============================================================================= + +In general `guess_atom_X` returns the guessed value for a single value, +while `guess_Xs` will work on an array of many atoms. + + +Example uses of guessers +------------------------ + +Guessing elements from atom names +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Currently, it is possible to guess elements from atom names using +:func:`guess_atom_element` (or the synonymous :func:`guess_atom_type`). This can +be done in the following manner:: + + import MDAnalysis as mda + from MDAnalysis.topology.guessers import guess_atom_element + from MDAnalysisTests.datafiles import PRM7 + + u = mda.Universe(PRM7) + + print(u.atoms.names[1]) # returns the atom name H1 + + element = guess_atom_element(u.atoms.names[1]) + + print(element) # returns element H + +In the above example, we take an atom named H1 and use +:func:`guess_atom_element` to guess the element hydrogen (i.e. H). It is +important to note that element guessing is not always accurate. Indeed in cases +where the atom type is not recognised, we may end up with the wrong element. +For example:: + + import MDAnalysis as mda + from MDAnalysis.topology.guessers import guess_atom_element + from MDAnalysisTests.datafiles import PRM19SBOPC + + u = mda.Universe(PRM19SBOPC) + + print(u.atoms.names[-1]) # returns the atom name EPW + + element = guess_atom_element(u.atoms.names[-1]) + + print(element) # returns element P + +Here we find that virtual site atom 'EPW' was given the element P, which +would not be an expected result. We therefore always recommend that users +carefully check the outcomes of any guessers. + +In some cases, one may want to guess elements for an entire universe and add +this guess as a topology attribute. This can be done using :func:`guess_types` +in the following manner:: + + import MDAnalysis as mda + from MDAnalysis.topology.guessers import guess_types + from MDAnalysisTests.datafiles import PRM7 + + u = mda.Universe(PRM7) + + guessed_elements = guess_types(u.atoms.names) + + u.add_TopologyAttr('elements', guessed_elements) + + print(u.atoms.elements) # returns an array of guessed elements + +More information on adding topology attributes can found in the `user guide`_. + + +.. Links + +.. _user guide: https://www.mdanalysis.org/UserGuide/examples/constructing_universe.html#Adding-topology-attributes + +""" +import numpy as np +import warnings +import re + +from ..lib import distances +from . import tables + + +def guess_masses(atom_types): + """Guess the mass of many atoms based upon their type + + Parameters + ---------- + atom_types + Type of each atom + + Returns + ------- + atom_masses : np.ndarray dtype float64 + """ + validate_atom_types(atom_types) + masses = np.array([get_atom_mass(atom_t) for atom_t in atom_types], dtype=np.float64) + return masses + + +def validate_atom_types(atom_types): + """Vaildates the atom types based on whether they are available in our tables + + Parameters + ---------- + atom_types + Type of each atom + + Returns + ------- + None + + .. versionchanged:: 0.20.0 + Try uppercase atom type name as well + """ + for atom_type in np.unique(atom_types): + try: + tables.masses[atom_type] + except KeyError: + try: + tables.masses[atom_type.upper()] + except KeyError: + warnings.warn("Failed to guess the mass for the following atom types: {}".format(atom_type)) + + +def guess_types(atom_names): + """Guess the atom type of many atoms based on atom name + + Parameters + ---------- + atom_names + Name of each atom + + Returns + ------- + atom_types : np.ndarray dtype object + """ + return np.array([guess_atom_element(name) for name in atom_names], dtype=object) + + +def guess_atom_type(atomname): + """Guess atom type from the name. + + At the moment, this function simply returns the element, as + guessed by :func:`guess_atom_element`. + + + See Also + -------- + :func:`guess_atom_element` + :mod:`MDAnalysis.topology.tables` + + + """ + return guess_atom_element(atomname) + + +NUMBERS = re.compile(r'[0-9]') # match numbers +SYMBOLS = re.compile(r'[*+-]') # match *, +, - + +def guess_atom_element(atomname): + """Guess the element of the atom from the name. + + Looks in dict to see if element is found, otherwise it uses the first + character in the atomname. The table comes from CHARMM and AMBER atom + types, where the first character is not sufficient to determine the atom + type. Some GROMOS ions have also been added. + + .. Warning: The translation table is incomplete. This will probably result + in some mistakes, but it still better than nothing! + + See Also + -------- + :func:`guess_atom_type` + :mod:`MDAnalysis.topology.tables` + """ + if atomname == '': + return '' + try: + return tables.atomelements[atomname.upper()] + except KeyError: + # strip symbols + no_symbols = re.sub(SYMBOLS, '', atomname) + + # split name by numbers + no_numbers = re.split(NUMBERS, no_symbols) + no_numbers = list(filter(None, no_numbers)) #remove '' + # if no_numbers is not empty, use the first element of no_numbers + name = no_numbers[0].upper() if no_numbers else '' + + # just in case + if name in tables.atomelements: + return tables.atomelements[name] + + while name: + if name in tables.elements: + return name + if name[:-1] in tables.elements: + return name[:-1] + if name[1:] in tables.elements: + return name[1:] + if len(name) <= 2: + return name[0] + name = name[:-1] # probably element is on left not right + + # if it's numbers + return no_symbols + + +def guess_bonds(atoms, coords, box=None, **kwargs): + r"""Guess if bonds exist between two atoms based on their distance. + + Bond between two atoms is created, if the two atoms are within + + .. math:: + + d < f \cdot (R_1 + R_2) + + of each other, where :math:`R_1` and :math:`R_2` are the VdW radii + of the atoms and :math:`f` is an ad-hoc *fudge_factor*. This is + the `same algorithm that VMD uses`_. + + Parameters + ---------- + atoms : AtomGroup + atoms for which bonds should be guessed + coords : array + coordinates of the atoms (i.e., `AtomGroup.positions)`) + fudge_factor : float, optional + The factor by which atoms must overlap eachother to be considered a + bond. Larger values will increase the number of bonds found. [0.55] + vdwradii : dict, optional + To supply custom vdwradii for atoms in the algorithm. Must be a dict + of format {type:radii}. The default table of van der Waals radii is + hard-coded as :data:`MDAnalysis.topology.tables.vdwradii`. Any user + defined vdwradii passed as an argument will supercede the table + values. [``None``] + lower_bound : float, optional + The minimum bond length. All bonds found shorter than this length will + be ignored. This is useful for parsing PDB with altloc records where + atoms with altloc A and B maybe very close together and there should be + no chemical bond between them. [0.1] + box : array_like, optional + Bonds are found using a distance search, if unit cell information is + given, periodic boundary conditions will be considered in the distance + search. [``None``] + + Returns + ------- + list + List of tuples suitable for use in Universe topology building. + + Warnings + -------- + No check is done after the bonds are guessed to see if Lewis + structure is correct. This is wrong and will burn somebody. + + Raises + ------ + :exc:`ValueError` if inputs are malformed or `vdwradii` data is missing. + + + .. _`same algorithm that VMD uses`: + http://www.ks.uiuc.edu/Research/vmd/vmd-1.9.1/ug/node26.html + + .. versionadded:: 0.7.7 + .. versionchanged:: 0.9.0 + Updated method internally to use more :mod:`numpy`, should work + faster. Should also use less memory, previously scaled as + :math:`O(n^2)`. *vdwradii* argument now augments table list + rather than replacing entirely. + """ + # why not just use atom.positions? + if len(atoms) != len(coords): + raise ValueError("'atoms' and 'coord' must be the same length") + + fudge_factor = kwargs.get('fudge_factor', 0.55) + + vdwradii = tables.vdwradii.copy() # so I don't permanently change it + user_vdwradii = kwargs.get('vdwradii', None) + if user_vdwradii: # this should make algo use their values over defaults + vdwradii.update(user_vdwradii) + + # Try using types, then elements + atomtypes = atoms.types + + # 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 + not t in vdwradii]) + + ". These can be defined manually using the" + + " keyword 'vdwradii'")) + + lower_bound = kwargs.get('lower_bound', 0.1) + + if box is not None: + box = np.asarray(box) + + # to speed up checking, calculate what the largest possible bond + # atom that would warrant attention. + # then use this to quickly mask distance results later + max_vdw = max([vdwradii[t] for t in atomtypes]) + + bonds = [] + + 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): + bonds.append((atoms[i].index, atoms[j].index)) + return tuple(bonds) + + +def guess_angles(bonds): + """Given a list of Bonds, find all angles that exist between atoms. + + Works by assuming that if atoms 1 & 2 are bonded, and 2 & 3 are bonded, + then (1,2,3) must be an angle. + + Returns + ------- + list of tuples + List of tuples defining the angles. + Suitable for use in u._topology + + + See Also + -------- + :meth:`guess_bonds` + + + .. versionadded 0.9.0 + """ + angles_found = set() + + for b in bonds: + for atom in b: + other_a = b.partner(atom) # who's my friend currently in Bond + for other_b in atom.bonds: + 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]) + if desc[0] > desc[-1]: # first index always less than last + desc = desc[::-1] + angles_found.add(desc) + + return tuple(angles_found) + + +def guess_dihedrals(angles): + """Given a list of Angles, find all dihedrals that exist between atoms. + + Works by assuming that if (1,2,3) is an angle, and 3 & 4 are bonded, + then (1,2,3,4) must be a dihedral. + + Returns + ------- + list of tuples + List of tuples defining the dihedrals. + Suitable for use in u._topology + + .. versionadded 0.9.0 + """ + dihedrals_found = set() + + for b in angles: + 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 other_b in atom.bonds: + if not other_b.partner(atom) in b: + third_a = other_b.partner(atom) + desc = prefix + (third_a.index,) + if desc[0] > desc[-1]: + desc = desc[::-1] + dihedrals_found.add(desc) + + return tuple(dihedrals_found) + + +def guess_improper_dihedrals(angles): + """Given a list of Angles, find all improper dihedrals that exist between + atoms. + + Works by assuming that if (1,2,3) is an angle, and 2 & 4 are bonded, + then (2, 1, 3, 4) must be an improper dihedral. + ie the improper dihedral is the angle between the planes formed by + (1, 2, 3) and (1, 3, 4) + + Returns + ------- + List of tuples defining the improper dihedrals. + Suitable for use in u._topology + + .. versionadded 0.9.0 + """ + dihedrals_found = set() + + for b in angles: + atom = b[1] # select middle atom in angle + # start of improper tuple + a_tup = tuple([b[a].index for a in [1, 2, 0]]) + # if searching with b[1], want tuple of (b[1], b[2], b[0], +new) + # search the first and last atom of each angle + for other_b in atom.bonds: + other_atom = other_b.partner(atom) + # if this atom isn't in the angle I started with + if not other_atom in b: + desc = a_tup + (other_atom.index,) + if desc[0] > desc[-1]: + desc = desc[::-1] + dihedrals_found.add(desc) + + return tuple(dihedrals_found) + + +def get_atom_mass(element): + """Return the atomic mass in u for *element*. + + Masses are looked up in :data:`MDAnalysis.topology.tables.masses`. + + .. Warning:: Unknown masses are set to 0.0 + + .. versionchanged:: 0.20.0 + Try uppercase atom type name as well + """ + try: + return tables.masses[element] + except KeyError: + try: + return tables.masses[element.upper()] + except KeyError: + return 0.0 + + +def guess_atom_mass(atomname): + """Guess a mass based on the atom name. + + :func:`guess_atom_element` is used to determine the kind of atom. + + .. warning:: Anything not recognized is simply set to 0; if you rely on the + masses you might want to double check. + """ + return get_atom_mass(guess_atom_element(atomname)) + + +def guess_atom_charge(atomname): + """Guess atom charge from the name. + + .. Warning:: Not implemented; simply returns 0. + """ + # TODO: do something slightly smarter, at least use name/element + return 0.0 + + +def guess_aromaticities(atomgroup): + """Guess aromaticity of atoms using RDKit + + Parameters + ---------- + atomgroup : mda.core.groups.AtomGroup + Atoms for which the aromaticity will be guessed + + Returns + ------- + aromaticities : numpy.ndarray + Array of boolean values for the aromaticity of each atom + + + .. versionadded:: 2.0.0 + """ + mol = atomgroup.convert_to("RDKIT") + return np.array([atom.GetIsAromatic() for atom in mol.GetAtoms()]) + + +def guess_gasteiger_charges(atomgroup): + """Guess Gasteiger partial charges using RDKit + + Parameters + ---------- + atomgroup : mda.core.groups.AtomGroup + Atoms for which the charges will be guessed + + Returns + ------- + charges : numpy.ndarray + Array of float values representing the charge of each atom + + + .. versionadded:: 2.0.0 + """ + 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) From d2ee68f7be4cfb3a86c696ffeb24f7c045435a16 Mon Sep 17 00:00:00 2001 From: IAlibay Date: Sun, 20 Oct 2024 10:22:32 +0100 Subject: [PATCH 02/11] Add tests back --- .../MDAnalysisTests/topology/test_guessers.py | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 testsuite/MDAnalysisTests/topology/test_guessers.py diff --git a/testsuite/MDAnalysisTests/topology/test_guessers.py b/testsuite/MDAnalysisTests/topology/test_guessers.py new file mode 100644 index 00000000000..1d946f22c8c --- /dev/null +++ b/testsuite/MDAnalysisTests/topology/test_guessers.py @@ -0,0 +1,205 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 +# +# MDAnalysis --- https://www.mdanalysis.org +# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors +# (see the file AUTHORS for the full list of names) +# +# Released under the GNU Public Licence, v2 or any higher version +# +# Please cite your use of MDAnalysis in published work: +# +# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, +# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. +# MDAnalysis: A Python package for the rapid analysis of molecular dynamics +# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th +# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy. +# doi: 10.25080/majora-629e541a-00e +# +# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. +# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. +# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 +# +import pytest +from numpy.testing import assert_equal +import numpy as np + +import MDAnalysis as mda +from MDAnalysis.topology import guessers +from MDAnalysis.core.topologyattrs import Angles + +from MDAnalysisTests import make_Universe +from MDAnalysisTests.core.test_fragments import make_starshape +import MDAnalysis.tests.datafiles as datafiles + +from MDAnalysisTests.util import import_not_available + + +try: + from rdkit import Chem + from rdkit.Chem.rdPartialCharges import ComputeGasteigerCharges +except ImportError: + pass + +requires_rdkit = pytest.mark.skipif(import_not_available("rdkit"), + reason="requires RDKit") + + +class TestGuessMasses(object): + def test_guess_masses(self): + out = guessers.guess_masses(['C', 'C', 'H']) + + assert isinstance(out, np.ndarray) + assert_equal(out, np.array([12.011, 12.011, 1.008])) + + def test_guess_masses_warn(self): + with pytest.warns(UserWarning): + guessers.guess_masses(['X']) + + def test_guess_masses_miss(self): + out = guessers.guess_masses(['X', 'Z']) + assert_equal(out, np.array([0.0, 0.0])) + + @pytest.mark.parametrize('element, value', (('H', 1.008), ('XYZ', 0.0), )) + def test_get_atom_mass(self, element, value): + assert guessers.get_atom_mass(element) == value + + def test_guess_atom_mass(self): + assert guessers.guess_atom_mass('1H') == 1.008 + + +class TestGuessTypes(object): + # guess_types + # guess_atom_type + # guess_atom_element + def test_guess_types(self): + out = guessers.guess_types(['MG2+', 'C12']) + + assert isinstance(out, np.ndarray) + assert_equal(out, np.array(['MG', 'C'], dtype=object)) + + def test_guess_atom_element(self): + assert guessers.guess_atom_element('MG2+') == 'MG' + + def test_guess_atom_element_empty(self): + assert guessers.guess_atom_element('') == '' + + def test_guess_atom_element_singledigit(self): + assert guessers.guess_atom_element('1') == '1' + + def test_guess_atom_element_1H(self): + assert guessers.guess_atom_element('1H') == 'H' + assert guessers.guess_atom_element('2H') == 'H' + + @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'), + ('N0A', 'N'), + ('C0U', 'C'), + ('C0S', 'C'), + ('Na+', 'NA'), + ('Cu2+', 'CU') + )) + def test_guess_element_from_name(self, name, element): + assert guessers.guess_atom_element(name) == element + + +def test_guess_charge(): + # this always returns 0.0 + assert guessers.guess_atom_charge('this') == 0.0 + + +def test_guess_bonds_Error(): + u = make_Universe(trajectory=True) + with pytest.raises(ValueError): + guessers.guess_bonds(u.atoms[:4], u.atoms.positions[:5]) + + +def test_guess_impropers(): + u = make_starshape() + + ag = u.atoms[:5] + + u.add_TopologyAttr(Angles(guessers.guess_angles(ag.bonds))) + + vals = guessers.guess_improper_dihedrals(ag.angles) + assert_equal(len(vals), 12) + + +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: + if i > j: + i, j = j, i + out.append((i, j)) + return sorted(out) + +def test_guess_bonds_water(): + u = mda.Universe(datafiles.two_water_gro) + bonds = bond_sort(guessers.guess_bonds(u.atoms, u.atoms.positions, u.dimensions)) + assert_equal(bonds, ((0, 1), + (0, 2), + (3, 4), + (3, 5))) + +def test_guess_bonds_adk(): + u = mda.Universe(datafiles.PSF, datafiles.DCD) + u.atoms.types = guessers.guess_types(u.atoms.names) + bonds = bond_sort(guessers.guess_bonds(u.atoms, u.atoms.positions)) + 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.atoms.types = guessers.guess_types(u.atoms.names) + bonds = bond_sort(guessers.guess_bonds(u.atoms, u.atoms.positions)) + 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", +]) +@requires_rdkit +def test_guess_aromaticities(smi): + mol = Chem.MolFromSmiles(smi) + mol = Chem.AddHs(mol) + expected = np.array([atom.GetIsAromatic() for atom in mol.GetAtoms()]) + u = mda.Universe(mol) + values = guessers.guess_aromaticities(u.atoms) + assert_equal(values, expected) + + +@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) + u = mda.Universe(mol) + values = guessers.guess_gasteiger_charges(u.atoms) + assert_equal(values, expected) From e791a008b7cb5ff0a0c57b04374097141b895381 Mon Sep 17 00:00:00 2001 From: IAlibay Date: Sun, 20 Oct 2024 10:43:25 +0100 Subject: [PATCH 03/11] Add tables back --- package/MDAnalysis/topology/tables.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 package/MDAnalysis/topology/tables.py diff --git a/package/MDAnalysis/topology/tables.py b/package/MDAnalysis/topology/tables.py new file mode 100644 index 00000000000..41bdaf47b17 --- /dev/null +++ b/package/MDAnalysis/topology/tables.py @@ -0,0 +1,21 @@ +import warnings +from MDAnalysis.guesser.tables import ( + kv2dict, + TABLE_ATOMELEMENTS, + atomelements, + elements, + TABLE_MASSES, + masses, + TABLE_VDWRADII, + vdwradii, + Z2SYMB, + SYMB2Z, + SYBYL2SYMB, +) + +wmsg = ("Deprecated in version 2.8.0\n" + "MDAnalysis.topology.tables has been moved to " + "MDAnalysis.guesser.tables. This import point " + "will be removed in MDAnalysis version 3.0.0") +warnings.warn(wmsg, category=DeprecationWarning) + From a4b884c3c0de2781a80aa02647674e298d1cd3a4 Mon Sep 17 00:00:00 2001 From: IAlibay Date: Sun, 20 Oct 2024 10:48:39 +0100 Subject: [PATCH 04/11] Black new file --- package/MDAnalysis/topology/tables.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/package/MDAnalysis/topology/tables.py b/package/MDAnalysis/topology/tables.py index 41bdaf47b17..7f4ebfbbe5c 100644 --- a/package/MDAnalysis/topology/tables.py +++ b/package/MDAnalysis/topology/tables.py @@ -13,9 +13,10 @@ SYBYL2SYMB, ) -wmsg = ("Deprecated in version 2.8.0\n" - "MDAnalysis.topology.tables has been moved to " - "MDAnalysis.guesser.tables. This import point " - "will be removed in MDAnalysis version 3.0.0") +wmsg = ( + "Deprecated in version 2.8.0\n" + "MDAnalysis.topology.tables has been moved to " + "MDAnalysis.guesser.tables. This import point " + "will be removed in MDAnalysis version 3.0.0" +) warnings.warn(wmsg, category=DeprecationWarning) - From 15d7f7d1ce7e8fded74b21ddc1577d792a9632aa Mon Sep 17 00:00:00 2001 From: IAlibay Date: Thu, 24 Oct 2024 19:51:49 +0100 Subject: [PATCH 05/11] Add deprecation warning for guessers --- package/MDAnalysis/topology/guessers.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/package/MDAnalysis/topology/guessers.py b/package/MDAnalysis/topology/guessers.py index a0847036de8..9fcf2c69001 100644 --- a/package/MDAnalysis/topology/guessers.py +++ b/package/MDAnalysis/topology/guessers.py @@ -104,6 +104,17 @@ from . import tables +wmsg = ( + "Deprecated in version 2.8.0\n" + "MDAnalysis.topology.guessers is deprecated in favour of " + "the new Guessers API and will be removed in MDAnalysis version 3.0.0. " + "See MDAnalysis.guesser.default_guesser for more details." +) + + +warnings.warn(wmsg, category=DeprecationWarning) + + def guess_masses(atom_types): """Guess the mass of many atoms based upon their type From 032c51363e482054e0ce059fb698d3573d1f17db Mon Sep 17 00:00:00 2001 From: IAlibay Date: Thu, 24 Oct 2024 21:15:53 +0100 Subject: [PATCH 06/11] Add test for tables deprecation --- .../MDAnalysisTests/topology/test_tables.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 testsuite/MDAnalysisTests/topology/test_tables.py diff --git a/testsuite/MDAnalysisTests/topology/test_tables.py b/testsuite/MDAnalysisTests/topology/test_tables.py new file mode 100644 index 00000000000..37246ad1864 --- /dev/null +++ b/testsuite/MDAnalysisTests/topology/test_tables.py @@ -0,0 +1,35 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 +# +# MDAnalysis --- https://www.mdanalysis.org +# Copyright (c) 2006-2024 The MDAnalysis Development Team and contributors +# (see the file AUTHORS for the full list of names) +# +# Released under the Lesser GNU Public Licence, v2.1 or any higher version +# +# Please cite your use of MDAnalysis in published work: +# +# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, +# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. +# MDAnalysis: A Python package for the rapid analysis of molecular dynamics +# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th +# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy. +# doi: 10.25080/majora-629e541a-00e +# +# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. +# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. +# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 +# +import pytest + +from importlib import reload +import pytest + +from MDAnalysis.topology import tables + + +def test_moved_to_guessers_warning(): + wmsg = "has been moved to MDAnalysis.guesser.tables" + with pytest.warns(DeprecationWarning, match=wmsg): + reload(tables) + From 051f22d2c89fa6e88bab9f047580df05dc89feb1 Mon Sep 17 00:00:00 2001 From: IAlibay Date: Thu, 24 Oct 2024 21:21:10 +0100 Subject: [PATCH 07/11] Add test for guessers deprecation --- package/MDAnalysis/topology/guessers.py | 2 +- testsuite/MDAnalysisTests/topology/test_guessers.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/topology/guessers.py b/package/MDAnalysis/topology/guessers.py index 9fcf2c69001..f904d400abd 100644 --- a/package/MDAnalysis/topology/guessers.py +++ b/package/MDAnalysis/topology/guessers.py @@ -101,7 +101,7 @@ import re from ..lib import distances -from . import tables +from MDAnalysis.guesser import tables wmsg = ( diff --git a/testsuite/MDAnalysisTests/topology/test_guessers.py b/testsuite/MDAnalysisTests/topology/test_guessers.py index 1d946f22c8c..7ab62b56eed 100644 --- a/testsuite/MDAnalysisTests/topology/test_guessers.py +++ b/testsuite/MDAnalysisTests/topology/test_guessers.py @@ -20,6 +20,7 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +from importlib import reload import pytest from numpy.testing import assert_equal import numpy as np @@ -45,6 +46,12 @@ reason="requires RDKit") +def test_moved_to_guessers_warning(): + wmsg = "deprecated in favour of the new Guessers API" + with pytest.warns(DeprecationWarning, match=wmsg): + reload(guessers) + + class TestGuessMasses(object): def test_guess_masses(self): out = guessers.guess_masses(['C', 'C', 'H']) From 578046d7b88795ac5b45327cbbf4dd669c0d5d61 Mon Sep 17 00:00:00 2001 From: IAlibay Date: Thu, 24 Oct 2024 21:22:41 +0100 Subject: [PATCH 08/11] Update changelog --- package/CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package/CHANGELOG b/package/CHANGELOG index 85a7208627b..374b64eca2b 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -112,6 +112,10 @@ Changes numpy.testing.assert_allclose #4438) Deprecations + * MDAnalysis.topology.guessers is deprecated in favour of the neww + Guessers API and will be removed in version 3.0 (PR #4752) + * MDAnalysis.topology.tables is deprecated in favour of + MDAnalysis.guesser.tables and will be removed in version 3.0 (PR #4752) * Unknown masses are set to 0.0 for current version, this will be depracated in version 3.0.0 and replaced by :class:`Masses`' no_value_label attribute(np.nan) (PR #3753) From be692afb97449052aea2580396c96f50035be9db Mon Sep 17 00:00:00 2001 From: Irfan Alibay Date: Thu, 24 Oct 2024 23:32:05 +0100 Subject: [PATCH 09/11] Update package/MDAnalysis/topology/guessers.py Co-authored-by: Oliver Beckstein --- package/MDAnalysis/topology/guessers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package/MDAnalysis/topology/guessers.py b/package/MDAnalysis/topology/guessers.py index f904d400abd..f2b07136cf1 100644 --- a/package/MDAnalysis/topology/guessers.py +++ b/package/MDAnalysis/topology/guessers.py @@ -24,6 +24,11 @@ Guessing unknown Topology information --- :mod:`MDAnalysis.topology.guessers` ============================================================================= +.. deprecated:: 2.8.0 + The :mod:`MDAnalysis.topology.guessers` module will be removed in release 3.0.0. + It is deprecated in favor of the new Guessers API. See + :mod:`MDAnalysis.guesser.default_guesser` for more details. + In general `guess_atom_X` returns the guessed value for a single value, while `guess_Xs` will work on an array of many atoms. From ec92012008c5c18a4c9448c819ae6eac361dc77f Mon Sep 17 00:00:00 2001 From: IAlibay Date: Fri, 25 Oct 2024 20:12:38 +0100 Subject: [PATCH 10/11] Add docs back and fix some review comments --- package/CHANGELOG | 2 +- package/MDAnalysis/topology/guessers.py | 2 +- .../doc/sphinx/source/documentation_pages/topology/guessers.rst | 2 ++ .../doc/sphinx/source/documentation_pages/topology/tables.rst | 1 + .../doc/sphinx/source/documentation_pages/topology_modules.rst | 2 ++ 5 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 package/doc/sphinx/source/documentation_pages/topology/guessers.rst create mode 100644 package/doc/sphinx/source/documentation_pages/topology/tables.rst diff --git a/package/CHANGELOG b/package/CHANGELOG index 9e5533e6627..61fe418266a 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -112,7 +112,7 @@ Changes numpy.testing.assert_allclose #4438) Deprecations - * MDAnalysis.topology.guessers is deprecated in favour of the neww + * MDAnalysis.topology.guessers is deprecated in favour of the new Guessers API and will be removed in version 3.0 (PR #4752) * MDAnalysis.topology.tables is deprecated in favour of MDAnalysis.guesser.tables and will be removed in version 3.0 (PR #4752) diff --git a/package/MDAnalysis/topology/guessers.py b/package/MDAnalysis/topology/guessers.py index f2b07136cf1..7d81f239617 100644 --- a/package/MDAnalysis/topology/guessers.py +++ b/package/MDAnalysis/topology/guessers.py @@ -28,7 +28,7 @@ The :mod:`MDAnalysis.topology.guessers` module will be removed in release 3.0.0. It is deprecated in favor of the new Guessers API. See :mod:`MDAnalysis.guesser.default_guesser` for more details. - + In general `guess_atom_X` returns the guessed value for a single value, while `guess_Xs` will work on an array of many atoms. diff --git a/package/doc/sphinx/source/documentation_pages/topology/guessers.rst b/package/doc/sphinx/source/documentation_pages/topology/guessers.rst new file mode 100644 index 00000000000..e6449f5ddc8 --- /dev/null +++ b/package/doc/sphinx/source/documentation_pages/topology/guessers.rst @@ -0,0 +1,2 @@ +.. automodule:: MDAnalysis.topology.guessers + :members: diff --git a/package/doc/sphinx/source/documentation_pages/topology/tables.rst b/package/doc/sphinx/source/documentation_pages/topology/tables.rst new file mode 100644 index 00000000000..f4d579ec9c8 --- /dev/null +++ b/package/doc/sphinx/source/documentation_pages/topology/tables.rst @@ -0,0 +1 @@ +.. automodule:: MDAnalysis.topology.tables diff --git a/package/doc/sphinx/source/documentation_pages/topology_modules.rst b/package/doc/sphinx/source/documentation_pages/topology_modules.rst index 01f3ab32e27..e1f818adabf 100644 --- a/package/doc/sphinx/source/documentation_pages/topology_modules.rst +++ b/package/doc/sphinx/source/documentation_pages/topology_modules.rst @@ -60,3 +60,5 @@ the topology readers. topology/base topology/core topology/tpr_util + topology/guessers + topology/tables From 878aa5cd3cb0dd8245134818fce75bca2058a56a Mon Sep 17 00:00:00 2001 From: IAlibay Date: Fri, 25 Oct 2024 20:29:51 +0100 Subject: [PATCH 11/11] Add tables docs --- package/MDAnalysis/topology/tables.py | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/package/MDAnalysis/topology/tables.py b/package/MDAnalysis/topology/tables.py index 7f4ebfbbe5c..6f88368ace7 100644 --- a/package/MDAnalysis/topology/tables.py +++ b/package/MDAnalysis/topology/tables.py @@ -1,3 +1,33 @@ +""" +MDAnalysis topology tables +========================== + +.. deprecated:: 2.8.0 + The :mod:`MDAnalysis.topology.tables` module has been moved to + :mod:`MDAnalysis.guesser.tables`. This import point will + be removed in release 3.0.0. + +The module contains static lookup tables for atom typing etc. The +tables are dictionaries that are indexed by the element. + +.. autodata:: atomelements +.. autodata:: masses +.. autodata:: vdwradii + +The original raw data are stored as multi-line strings that are +translated into dictionaries with :func:`kv2dict`. In the future, +these tables might be moved into external data files; see +:func:`kv2dict` for explanation of the file format. + +.. autofunction:: kv2dict + +The raw tables are stored in the strings + +.. autodata:: TABLE_ATOMELEMENTS +.. autodata:: TABLE_MASSES +.. autodata:: TABLE_VDWRADII +""" + import warnings from MDAnalysis.guesser.tables import ( kv2dict,