Source code for gaddlemaps.components._components_top

#    Gaddlemaps python module.
#    Copyright (C) 2019-2021 José Manuel Otero Mato, Hadrián Montes Campos, Luis Miguel Varela Cabo
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""
This module contains MoleculeTop and AtomTop objects which allows to load
atom and molecule information relative to the bonds between atoms.
"""

from io import TextIOWrapper
import os
from itertools import groupby
from typing import Any, Generator, List, Set, Tuple, Union, overload

from ..parsers import read_topology


[docs]class MoleculeTop: """ Loads molecules from a topology file. This class behaves like a list of atoms which has bonds defined. The appropriate parser will be used based on the input file extension. The available parsers are summarized in the class attribute "PARSERS". In this attribute, the keys are the files extensions and the values the corresponding functions that extracts the information from the files with that extensions. These functions should return: - The name of the molecule - A list with tuples with the atoms and residues names in order of appearance in the file. - A list with tuples with atoms index (referred to the atoms_info indexes) that are bonded. Parameters ---------- ftop : string or TextIOWrapper The path to the file (or the opened file) with the molecule name and bonds information. file_format : str, Optional The file extension of ftop. If it is None this will be taken from ftop. Raises ------ ValueError If the file format is not supported. IOError If the input file misses information. """ def __init__(self, ftop: Union[str,TextIOWrapper], file_format: str = None): self.ftop = ftop if isinstance(ftop, str) else ftop.name self.name, atoms_info, atoms_bonds = read_topology(ftop, file_format=file_format) self.atoms: List['AtomTop'] = [] for index, atom in enumerate(atoms_info): self.atoms.append(AtomTop(*atom, index)) for bond in atoms_bonds: self.atoms[bond[0]].connect(self.atoms[bond[1]]) @overload def __getitem__(self, index: int) -> 'AtomTop': ... @overload def __getitem__(self, index: slice) -> List['AtomTop']: ... def __getitem__(self, index): return self.atoms[index] def __len__(self) -> int: return len(self.atoms) def __iter__(self) -> Generator['AtomTop', None, None]: for atom in self.atoms: yield atom def __eq__(self, element: Any) -> bool: if isinstance(element, MoleculeTop): if element.name == self.name and len(self) == len(element): return all(at1 == at2 for at1, at2 in zip(self, element)) # type: ignore return False def __ne__(self, element: Any) -> bool: return not self == element def __str__(self) -> str: return f'MoleculeTop of {self.name}.' __repr__ = __str__ @property def resnames(self) -> List[str]: """ list of str: Residue names of the atoms without consecutive repetitions. To set this property a list with the same length of residues must be passed. """ tot_resnames = ('{:5}{}'.format(atom.resname, atom.resid) for atom in self) # type: ignore return [x[0][:5].strip() for x in groupby(tot_resnames)] @resnames.setter def resnames(self, new_resnames: List[str]): if not isinstance(new_resnames, list): raise ValueError('new_resnames must be a list of strings.') if len(new_resnames) != len(self.resnames): raise ValueError((f'Expected {len(self.resnames)} residue name' f' while {len(new_resnames)} given.')) resname_index = 0 actual_resname = self[0].residname for atom in self: if atom.residname != actual_resname: resname_index += 1 actual_resname = atom.residname atom.resname = new_resnames[resname_index] @property def resids(self) -> List[int]: """ list of str: Residue names of the atoms without consecutive repetitions. To set this property a list with the same length of residues must be passed. """ tot_resids = ('{:5}{}'.format(atom.resname, atom.resid) for atom in self) # type: ignore return [int(x[0][5:].strip()) for x in groupby(tot_resids)] @resids.setter def resids(self, new_resids: List[int]): if not isinstance(new_resids, list): raise ValueError('new_resids must be a list of strings.') if len(new_resids) != len(self.resids): raise ValueError((f'Expected {len(self.resids)} residue name' f' while {len(new_resids)} given.')) resid_index = 0 actual_resid = self[0].residname for atom in self: # type: ignore if atom.residname != actual_resid: resid_index += 1 actual_resid = atom.residname atom.resid = new_resids[resid_index] @property def resname_len_list(self) -> List[Tuple[str, int]]: """ list of tuple(str, int) : [(resname_1, number_of_atoms_with_resname_1), (resname_2, number_of_atoms_with_resname_2), ...] """ tot_resnames = ('{:5}{}'.format(atom.resname, atom.resid) for atom in self) # type: ignore res_len = [] old_resname: List = [] for resname in tot_resnames: if not old_resname: old_resname = [resname, 1] elif resname != old_resname[0]: res_len.append((old_resname[0][:5].strip(), old_resname[1])) old_resname = [resname, 1] else: old_resname[1] += 1 res_len.append((old_resname[0][:5].strip(), old_resname[1])) return res_len
[docs] def index(self, atom: 'AtomTop') -> int: """ Returns the index of the atom in the molecule. Parameters ---------- atom : AtomTop The atom to find the index. Returns ------- index : int The index of the atom in the molecule. """ return self.atoms.index(atom)
[docs] def copy(self) -> 'MoleculeTop': """ Returns a copy of the molecule_top. The atoms forming the copy are not the same objects as the original molecule so you do not have to worry about linked objects. Returns ------- molecule_top : MoleculeTop The copy of the molecule. """ mol = self.__new__(self.__class__) mol.ftop = self.ftop mol.name = self.name mol.atoms = [atom.copy() for atom in self] return mol
[docs]class AtomTop: """ Atom with information of its name, residue name and bonds. It is also needed the index of the atom in the molecule. The bonds are initialized as empty set. Parameters ---------- name : str Atom name resname : str Residue name of the atom. resid : int Residue index of the atom. index : int Atom index in the molecule. Attributes ---------- bonds: set of int A set with the hash of the atoms that are connected to self. """ def __init__(self, name: str, resname: str, resid: int, index: int): self.name = name self.resname = resname self.resid = resid self.index = index self.bonds: Set[int] = set() def __repr__(self) -> str: return f'Itp atom of {self.name} of molecule {self.resname}' __str__ = __repr__ def __hash__(self) -> int: """ Number in the itp line """ return self.index def __eq__(self, atom: Any) -> bool: # type: ignore if isinstance(atom, AtomTop): condition = ( (self.index == atom.index) and (self.resname == atom.resname) and (self.name == atom.name) and (self.bonds == atom.bonds) ) return condition return False @property def residname(self) -> str: """ string: An identifier of the residue (resid+name) """ return '{}{}'.format(self.resid, self.resname)
[docs] def connect(self, atom: 'AtomTop'): """ Connects self with other atom setting the bond. Parameters ---------- atom : AtomTop Atom to connect. """ self.bonds.add(hash(atom)) atom.bonds.add(hash(self))
[docs] def closest_atoms(self, natoms: int = 2) -> List[int]: """ Returns a list with natoms index of bonded atoms to self. If more than natoms are bonded self, the natoms with lower id_num are returned. Parameters ---------- natoms : integer The number of atoms to return. Returns ------- bonded_atoms : list of int The list with the index of natoms atoms bonded self. """ return sorted(self.bonds)[:natoms]
[docs] def copy(self) -> 'AtomTop': """ Returns a copy of the current atom. Returns ------- atom_top : AtomTop The copy of the atom. """ atom = AtomTop(self.name, self.resname, self.resid, self.index) atom.bonds = self.bonds.copy() return atom