Source code for schrodinger.application.matsci.rdpattern

"""
Module to generate and evaluate Smiles/SMARTS pattern for both CG and all
atomic structure using RDKIT

Copyright Schrodinger, LLC. All rights reserved.
"""
import re
from collections import OrderedDict

from rdkit import Chem

from schrodinger import adapter
from schrodinger.application.matsci import msutils
from schrodinger.rdkit import coarsegrain
from schrodinger.thirdparty import rdkit_adapter

# NOTE: 03/09/2022: ST: We do not support chirality for coarse-grained system.
# Currently there are several issue before supports. Current mapper for
# coarse-grained system doesn't accept "@" as chiral center.
# Scientifically, Further, what is the application of chiral center in
# coarse-grained.

PERIODIC_TABLE = Chem.rdchem.GetPeriodicTable()
MAX_MATCHES = 1000000000  # See DESMOND-11242 and RB 65689


[docs]def to_smarts(struct, sanitize=True, include_stereo=True, atom_subset=None, check_connectivity=False): """ Get SMARTS for a given structure :param schrodinger.structure.Structure struct: Structure for which patterns need to be selected :param bool sanitize: Whether RDKit sanitization should be performed. This option is not applicable for coarsegrained structures. :type include_stereo: bool :param include_stereo: Whether the stereochemistry of the structure should be translated into the RDKit mol. It also enables inclusion of information about stereochemistry in the SMARTS. Setting to `False` can speed this up substantially. For CG system, include stereo option is permanently False :param bool check_connectivity: Whether to check that all the atoms from the atom_subset (or entire structure if it is None) are from one molecule. Raise ValueError if it is not the case. :param list atom_subset: List of atom indices. If None then SMARTS for full structure is computed :rtype: str :return: SMARTS pattern for the atom ids provided. """ if not struct.atom_total: # Empty structure return '' if atom_subset is not None: atom_subset = sorted(set(atom_subset)) if check_connectivity: nmols = (struct.mol_total if atom_subset is None else struct.extract(atom_subset).mol_total) if nmols > 1: raise ValueError('SMARTS generation only works on connected sets ' 'of atoms.') # See RB 67298 discussion on speed issues when `include_stereo=True` pattern = Pattern(struct, sanitize=sanitize, include_stereo=include_stereo) return pattern.toSmarts(atom_ids=atom_subset, isomeric=include_stereo)
[docs]def to_smiles(struct, sanitize=True, include_stereo=True, atom_ids=None, fall_back=False): """ Get SMILES for a given structure :type struct: `schrodinger.structure.Structure` :param struct: Structure for which patterns need to be selected :type sanitize: bool :param sanitize: Whether RDKit sanitization should be performed. This option is not applicable for coarsegrained structures. :type include_stereo: bool :param include_stereo: Whether the stereochemistry of the structure should be translated into the RDKit mol. It also enable to include information about stereochemistry in the SMILES. For CG system, include stereo option is permanently False :type atom_ids: list :param atom_ids: list of atom indices. If None then SMARTS for full structure is computed :type fall_back: bool :param fall_back: Ignored if sanitize=False. If sanitize=True, will fall back to using a non-sanitized structure if sanitization fails. :rtype: str :return: SMILES pattern for the atom ids provided """ pattern = Pattern(struct, sanitize=sanitize, include_stereo=include_stereo, fall_back=fall_back) return pattern.toSmiles(atom_ids=atom_ids, isomeric=include_stereo)
[docs]def has_query_stereo(query): """ Checks if rdkit molecule has stereo centers. :param rdkit.Chem.rdchem.Mol query: Input molecule :rtype: bool :return: Whether molecule has stereo centers """ for at in query.GetAtoms(): if at.GetChiralTag() != Chem.CHI_UNSPECIFIED: return True for bnd in query.GetBonds(): if bnd.GetStereo() not in (Chem.BondStereo.STEREONONE, Chem.BondStereo.STEREOANY): return True return False
[docs]def evaluate_smarts(struct, smarts, is_cg=None, sanitize=True, uniquify=True, max_matches=MAX_MATCHES): """ Get the list of matches for the passed SMARTS pattern in the reference structure :type struct: `schrodinger.structure.Structure` :param struct: Structure for which patterns need to be selected :type smarts: str :param smarts: SMARTS pattern to find :type is_cg: bool or None :param is_cg: Whether structure is CG. If None, perform a check :type sanitize: bool :param sanitize: Whether RDKit sanitization should be performed. This option is not applicable for coarsegrained structures. :type uniquify: bool :param uniquify: if True, return only unique sets of matching atoms :type max_matches: int :param max_matches: the maximum number of matches to return :rtype: list or None :return: list of list atom/particle indices with matching SMARTS. """ is_cg = (msutils.is_coarse_grain(struct, by_atom=True) if is_cg is None else is_cg) if is_cg: _, mapper = coarsegrain.prepare_cg_for_rdkit(struct) pattern = Pattern.patternTranslate(smarts, mapper) else: pattern = smarts smarts_mol = Chem.MolFromSmarts(pattern) if not smarts_mol: return [] has_stereo = has_query_stereo(smarts_mol) pattern = Pattern(struct, sanitize=sanitize, include_stereo=has_stereo) return pattern._getPatternMatches(smarts_mol, uniquify=uniquify, use_chirality=has_stereo, max_matches=max_matches)
[docs]def evaluate_smarts_by_molecule(struct, smarts, uniquify=True, matches_by_mol=False, molecule_numbers=None): """ Takes a structure and a SMARTS pattern and returns a list of all matching atom indices, where each element in the list is a group of atoms that match the the SMARTS pattern. :param structure.Structure struct: the structure to search :param str smarts: the SMARTS pattern to match :param bool uniquify: if True, return only unique sets of matching atoms :param bool matches_by_mol: if True then rather than returning a list of matches return a dictionary of matches key-ed by molecule number :param set molecule_numbers: set of molecule numbers in the structure to be used instead of the entire structure :rtype: list or dict :return: For the list (if matches_by_mol is False) each value is a list of atom indices matching the SMARTS pattern, for the dict (if matches_by_mol is True) keys are molecule indices and values are lists of matches for that molecule """ is_cg = msutils.is_coarse_grain(struct, by_atom=True) matches = {} # SMARTs patterns are intra-molecular # so we iterate over molecules in struct if molecule_numbers is None: molecules = struct.molecule else: # Using enumerate as structure.molecule[x] is very slow #SHARED-6204 molecules = [ mol for mol in struct.molecule if mol.number in molecule_numbers ] for mol in molecules: new_to_old = dict(zip(range(1, len(mol.atom) + 1), mol.getAtomIndices())) match = evaluate_smarts(mol.extractStructure(), smarts, is_cg=is_cg, uniquify=uniquify, max_matches=MAX_MATCHES) # Remove empty matches and use structure atom indexes # rather than by-molecule atom indexes for a_match in match: if a_match: b_match = [new_to_old[x] for x in a_match] if mol.number not in matches: matches[mol.number] = [] matches[mol.number].append(b_match) if not matches_by_mol: matches = [ match for mol_match in matches.values() for match in mol_match ] return matches
[docs]def get_name_element_mapper_cg(struct, is_cg=None): """ Gets the mapper between schrodinger particle name and rdkit proxy element name if the structure is CG. None if the structure is AA. :type struct: structure.Structure :param struct: Structure used for creating the mapper. :type is_cg: bool or None :param is_cg: Whether structure is CG. If None, perform a check :returns: Internal mapping dict between schrodinger particle name and rdkit proxy element name if the structure is CG. Return None if it is AA system or no structure is passed. :rtype: dict or None :raise RuntimError: If element mapper cannot be generated for the coarse-grained structure """ is_cg = (msutils.is_coarse_grain(struct, by_atom=True) if is_cg is None else is_cg) if is_cg: # This can raise RuntimeError _, mapper = coarsegrain.prepare_cg_for_rdkit(struct) return mapper
[docs]def validate_smarts(smarts, struct=None, is_cg=None): """ Validate smarts. Works both with AA and CG. :param str smarts: SMARTS to validate :type struct: structure.Structure or None :param struct: If None, validate as AA SMARTS. If present, validate either as AA or CG :type is_cg: bool or None :param is_cg: Whether structure is CG. If None, perform a check :rtype: str or None :return: Error message on error, None if SMARTS is valid """ try: name_element_mapper = get_name_element_mapper_cg(struct, is_cg) except RuntimeError as err: return str(err) if isinstance(smarts, str): smarts = [smarts] for pattern in smarts: if name_element_mapper: pattern = Pattern.patternTranslate(pattern, name_element_mapper) try: adapter.validate_smarts(pattern) except ValueError as err: return str(err)
[docs]def has_stereo_smarts(smarts, struct=None, is_cg=None): """ Check if SMARTS requires stereo. Works both with AA and CG. :type: str or list[str] :param smarts: SMARTS to validate :type struct: structure.Structure or None :param struct: If None, validate as AA SMARTS. If present, validate either as AA or CG :type is_cg: bool or None :param is_cg: Whether structure is CG. If None, perform a check :rtype: bool :return: Whether any SMARTS require stereo or not """ try: name_element_mapper = get_name_element_mapper_cg(struct, is_cg) except RuntimeError as err: # No stereo required for a bad structure return False if isinstance(smarts, str): smarts = [smarts] for pattern in smarts: if name_element_mapper: pattern = Pattern.patternTranslate(pattern, name_element_mapper) smarts_mol = Chem.MolFromSmarts(pattern) if smarts_mol is None: # Bad SMARTS does not need stereo return False if has_query_stereo(smarts_mol): return True return False
[docs]def symbol_to_number(symbol): """Transforms chemical symbols into the corresponding atomic numbers. :type symbol: str :param symbol: Chemical symbol :rtype: int/None :return: Atomic numbers corresponding to entered symbol if valid atomic symbol is entered else return None """ with rdkit_adapter.suppress_rdkit_log(): try: atomic_num = PERIODIC_TABLE.GetAtomicNumber(symbol) except RuntimeError: return return atomic_num
[docs]def get_sdgr_atom_index(mol, rdkit_atom_indices): """ Generator that yields Schrodinger atom indices from rdkit indices. :param rdkit.Chem.rdchem.Mol mol: Molecule :param iterable rdkit_atom_indices: Iterable of mol atom indices :yield int: Schrodinger atom index """ for idx in rdkit_atom_indices: yield mol.GetAtomWithIdx(idx).GetIntProp(rdkit_adapter.SDGR_INDEX)
[docs]class DictCache(OrderedDict): """ A first in first out dictionary cache which caches the key and associated value. """
[docs] def __init__(self, maxcount=10000): """ Constructs a new lru cache. :param maxcount: The maximum number of data that the cache can hold :type maxcount: int """ super().__init__() self.maxcount = maxcount
def __setitem__(self, key, value): """ Set the value for the passed key :param key: The key :type key: string or tuple :param value: The value than any dictionary can hold :type value: string """ super().__setitem__(key, value) # Maintain size limit for the cache if len(self) > self.maxcount: self.popitem(last=False)
[docs]class Pattern: """ A class to calculate calculate SMARTS and SMILES pattern for a structure multiple times. The class can be memory intensive to allow for increased speed. """ PROTECTED_PATTERN_BIT = coarsegrain.PROTECTED_PATTERN_BIT
[docs] def __init__(self, struct, *, is_cg=None, sanitize=True, include_stereo=True, fall_back=False): """ Initiate Pattern class :type struct: `schrodinger.structure.Structure` :param struct: Structure for which patterns need to be selected :type is_cg: bool or None :param is_cg: Whether structure is CG. If None, perform a check :type sanitize: bool :param sanitize: Whether RDKit sanitization should be performed. This option is not applicable for coarsegrained structures. :type include_stereo: bool :param include_stereo: Whether the stereochemistry of the structure should be translated into the RDKit mol. Setting to `False` can speed this up substantially. :type fall_back: bool :param fall_back: Ignored if sanitize=False. If sanitize=True, will fall back to using a non-sanitized structure if sanitization fails. """ self.struct = struct # Fallback is required only when sanitization if True. For # sanitization = False, it will be perform same conversion procedure # twice if fallback is not updated. # This will ensure that conversion is tried only once. self.fall_back = fall_back if sanitize else False self.is_coarse_grain = (msutils.is_coarse_grain(struct, by_atom=True) if is_cg is None else is_cg) self.include_stereo = False if self.is_coarse_grain else include_stereo self.sanitize = sanitize self._sanitized = sanitize self.loadMol() # Cached dicts self._smiles = DictCache() self._smarts = DictCache() # RDKIT mol atomic index and schrodinger structure index mappers self.stidx_to_molidx = rdkit_adapter.get_map_sdgr_to_rdk(self._mol) self.molidx_to_stidx = rdkit_adapter.get_map_rdk_to_sdgr(self._mol)
def _getAAMol(self): """ Gets the rdkit mol for all-atom system for the current settings :returns: The rdkit mol for all-atom system :rtype: `rdkit.Chem.rdchem.Mol` """ # include_properties and include_coordinates are set to false in # adapter.evaluate_smarts, match here. If changing please check the # performance (MATSCI-11446). See RB 67298 discussion on speed # issues when `include_stereo=True` mol = rdkit_adapter.to_rdkit(self.struct, include_properties=False, include_coordinates=False, sanitize=self._sanitized, include_stereo=self.include_stereo) return mol
[docs] def loadMol(self): """ Load rdkit mol in the pattern """ if self.is_coarse_grain: self._mol, self._particle_proxy_mapper = \ coarsegrain._coarsegrain_st_to_rdkit(self.struct) self._generateMapper() else: with rdkit_adapter.suppress_rdkit_log(): try: self._mol = self._getAAMol() except ValueError: if self.fall_back: self._sanitized = False self._mol = self._getAAMol() else: raise
def _generateMapper(self): """ Generate mapper for proxy element to particle name """ self._proxy_particle_mapper = {} for pname, ename in self._particle_proxy_mapper.items(): self._proxy_particle_mapper[ename] = pname self._proxy_particle_mapper[str(symbol_to_number(pname))] = ename @property def sanitized(self): """ Get whether the structure was sanitized or not :return: sanitization status of the structure :rtype: bool """ return self._sanitized @property def smiles(self): """ Get the SMILES for the passed structure :returns: SMILES pattern for the passed structure :rtype: str """ return self.toSmiles() @smiles.setter def smiles(self, value): """ Overwrite SMILES setter to not allow user to set it. :param value: SMILES :type value: str :raises AttributeError: Always raise error since setting SMILES is not allowed. """ raise AttributeError('SMILES cannot be set for a structure')
[docs] def getPattern(self, atom_ids=None, is_smiles_requested=False, isomeric=True): """ Get SMILES/SMARTS for full structure or for substructure of given atom ids. :type atom_ids: list :param atom_ids: list of atom indices :type is_smiles_requested: bool :param is_smiles_requested: return Smiles pattern if True else Smarts :type isomeric: bool :param isomeric: include information about stereochemistry in the SMILES/SMARTS :rtype: str :return: SMILES pattern for the atom ids provided """ isomeric = False if self.is_coarse_grain else isomeric atom_ids = tuple(atom_ids) if atom_ids else None dict_cache = self._smiles if is_smiles_requested else self._smarts if atom_ids in dict_cache: return dict_cache[atom_ids] if is_smiles_requested: if atom_ids: pattern = Chem.MolFragmentToSmiles(self._mol, self.toRdIndices(atom_ids), isomericSmiles=isomeric) else: pattern = Chem.MolToSmiles(self._mol, isomericSmiles=isomeric) else: if atom_ids: pattern = Chem.MolFragmentToSmarts(self._mol, self.toRdIndices(atom_ids), isomericSmarts=isomeric) else: pattern = Chem.MolToSmarts(self._mol, isomericSmiles=isomeric) pattern = self.proxyToParticleName(pattern) dict_cache[atom_ids] = pattern return pattern
[docs] def toSmiles(self, atom_ids=None, isomeric=True): """ Get SMILES for subset of atom ids :type atom_ids: iterable :param atom_ids: atom indices :type isomeric: bool :param isomeric: include information about stereochemistry in the SMILES :rtype: str :return: SMILES pattern for the atom ids provided """ return self.getPattern(atom_ids=atom_ids, is_smiles_requested=True, isomeric=isomeric)
@property def smarts(self): """ Get the SMARTS for the passed structure :returns: SMARTS pattern for the passed structure :rtype: str """ return self.toSmarts() @smarts.setter def smarts(self, value): """ Overwrite SMARTS setter to not allow user to set it :param value: SMARTS :type value: str :raises AttributeError: Always raise error since setting SMARTS is not allowed """ raise AttributeError('SMARTS cannot be set for a structure')
[docs] def toSmarts(self, atom_ids=None, isomeric=True): """ Get SMARTS for subset of atom ids :type atom_ids: iterable :param atom_ids: atom indices :type isomeric: bool :param isomeric: include information about stereochemistry in the SMARTS :rtype: str :return: SMARTS pattern for the atom ids provided """ return self.getPattern(atom_ids=atom_ids, is_smiles_requested=False, isomeric=isomeric)
[docs] def validateSmarts(self, smarts): """ Validate the passed smarts pattern :type smarts: str :param smarts: SMARTS to validate :rtype: str or None :return: Error message on error, None if SMARTS is valid """ smarts = self.particleNameToProxy(smarts) try: adapter.validate_smarts(smarts) except ValueError as err: return str(err)
[docs] def evaluateSmiles(self, smiles, uniquify=True, use_chirality=True, max_matches=MAX_MATCHES): """ Get the list of matches for the passed SMILES pattern in the reference structure :type smiles: str :param smiles: SMILES pattern to find :type uniquify: bool :param uniquify: if True, return only unique sets of matching atoms :type use_chirality: bool :param use_chirality: enables the use of stereochemistry in the matching :type max_matches: int :param max_matches: the maximum number of matches to return :rtype: list or None :return: list of list atom indices with matching SMILES. """ smiles = self.particleNameToProxy(smiles) patt = Chem.MolFromSmiles(smiles, sanitize=False) return self._getPatternMatches(patt, uniquify=uniquify, use_chirality=use_chirality, max_matches=max_matches)
[docs] def evaluateSmarts(self, smarts, uniquify=True, use_chirality=True, max_matches=MAX_MATCHES): """ Get the list of matches for the passed SMARTS pattern in the reference structure :type smarts: str :param smarts: SMARTS pattern to find :type uniquify: bool :param uniquify: if True, return only unique sets of matching atoms :type use_chirality: bool :param use_chirality: enables the use of stereochemistry in the matching :type max_matches: int :param max_matches: the maximum number of matches to return :rtype: list or None :return: list of list atom/particle indices with matching SMARTS. """ smarts = self.particleNameToProxy(smarts) mol = Chem.MolFromSmarts(smarts) return self._getPatternMatches(mol, uniquify=uniquify, use_chirality=use_chirality, max_matches=max_matches)
[docs] @staticmethod def patternTranslate(s_pattern, mapper): """ Replace passed SMARTS/SMILES such that the mapper key values are replaced by :param s_pattern: The SMARTS/SMILES pattern to change :type s_pattern: str :param mapper: The mapper used to convert the SMARTS/SMILES pattern :type mapper: dictionary where the key is the element name to find in the pattern and value is the name to replace it with :returns: the converted SMARTS/SMILES pattern :rtype: str """ split_pattern = [x for x in re.split(r'\[|\]', s_pattern) if x] for sidx, smarts_val in enumerate(split_pattern): # Check if name is part of the split pattern, if not continue if smarts_val[0] not in Pattern.PROTECTED_PATTERN_BIT: for mapper_name in mapper.keys(): if mapper_name in smarts_val: break else: continue # Check if pattern matches mapper atom name if smarts_val in mapper.keys(): split_pattern[sidx] = f'[{mapper[smarts_val]}]' continue # Remove # for representing atomic number if smarts_val.startswith('#'): smarts_val = smarts_val[1:] # Get position where the element/atom name string ends search_text = re.search(r'\W+', smarts_val) pos = search_text.start() if search_text else len(smarts_val) alpha_num_str = smarts_val[:pos] non_alpha_str = smarts_val[pos:] # Do not change anything for protected SMARTS pattern alpha_str = ''.join(x for x in alpha_num_str if x.isalpha()) if (alpha_str not in Pattern.PROTECTED_PATTERN_BIT and alpha_str in mapper.keys()): # Replace the element/atom name with the mapper value smarts_val = mapper[alpha_num_str] + non_alpha_str split_pattern[sidx] = f'[{smarts_val}]' converted_smarts = ''.join(split_pattern) return converted_smarts
[docs] def proxyToParticleName(self, smarts): """ If the structure is a coarse-grained structure convert the SMARTS pattern of proxy elements to coarse grain particle name. Does nothing for atomistic structures :param smarts: The SMARTS pattern :type smarts: str :returns: The translated SMARTS pattern :rtype: str """ if not self.is_coarse_grain: return smarts return self.patternTranslate(smarts, self._proxy_particle_mapper)
[docs] def particleNameToProxy(self, smarts): """ If the structure is a coarse grain structure convert the SMARTS pattern of coarse grain particle name to proxy element name. Does nothing for atomistic structures :param smarts: The SMARTS pattern :type smarts: str :returns: The translated SMARTS pattern :rtype: str """ if not self.is_coarse_grain: return smarts return self.patternTranslate(smarts, self._particle_proxy_mapper)
[docs] def toRdIndices(self, particle_indices): """ Convert list of Schrodinger structure particle indices to RDMol atom indices :type particle_indices: tuple :param particle_indices: tuple of Schrodinger structure particle indices :rtype: list :return: list of RDMol atom indices """ return [self.stidx_to_molidx[x] for x in particle_indices]
[docs] def toStIndices(self, particle_indices): """ Convert list of RDMol atom indices to Schrodinger structure particle indices :type particle_indices: tuple :param particle_indices: tuple of RDMol atom indices :rtype: list :return: list of Schrodinger structure particle indices """ return [self.molidx_to_stidx[x] for x in particle_indices]
def _getPatternMatches(self, mol, uniquify=True, use_chirality=True, max_matches=MAX_MATCHES): """ Search for the passed pattern in the current structure :type mol: `rdkit.Chem.rdchem.Mol` :param mol: rdkit molecule :type uniquify: bool :param uniquify: if True, return only unique sets of matching atoms :type use_chirality: bool :param use_chirality: enables the use of stereochemistry in the matching :type max_matches: int :param max_matches: the maximum number of matches to return :rtype: list :returns: The list of atom/particle ids matches which matches the pattern """ # If the mol is none there will be no matches if mol is None: return [] # Changing maxMatches to reflect MATSCI-6657 rd_matches = self._mol.GetSubstructMatches(mol, uniquify=uniquify, useChirality=use_chirality, useQueryQueryMatches=False, maxMatches=max_matches) matches = [] for rd_indices in rd_matches: matches.append(self.toStIndices(rd_indices)) return matches def _getMolecularPattern(self, cache_dict, search_func): """ Gets the SMILES/SMARTS pattern for all molecules :param cache_dict: The cache SMILES or SMARTS dictionary to check if value has already been calculated :type cache_dict: DictCache :param search_func: The rdkit function to calculate pattern :type search_func: function :returns: The dictionary where the key is the molecule number and pattern is the associated SMILES or SMARTS pattern for the molecule :rtype: dict """ mol_patt = {} for mol_frag in Chem.rdmolops.GetMolFrags(self._mol, asMols=True, sanitizeFrags=False): # Get the corresponding atom/particle schrodinger structure indices st_aid = tuple( sorted( x.GetIntProp(rdkit_adapter.SDGR_INDEX) for x in mol_frag.GetAtoms())) # Set pattern if it is not already set in the cache if st_aid in cache_dict: patt_val = cache_dict[st_aid] else: patt_val = search_func(mol_frag) cache_dict[st_aid] = patt_val # Get the schrodinger structure molecule number and set the pattern # value st_mol_num = self.struct.atom[st_aid[0]].molecule_number mol_patt[st_mol_num] = patt_val return mol_patt
[docs] def getMoleculeSmiles(self): """ Get SMILES for each molecule in the structure :returns: The dictionary where the key is the molecule number and the value is the corresponding SMILES pattern :rtype: dict """ return self._getMolecularPattern(self._smiles, Chem.MolToSmiles)
[docs] def getMoleculeSmarts(self): """ Get SMARTS for each molecule in the structure :returns: The dictionary where the key is the molecule number and the value is the corresponding SMARTS pattern :rtype: dict """ return self._getMolecularPattern(self._smiles, Chem.MolToSmarts)
[docs] def getUniqueMolNums(self, use_smarts=False): """ Get the unique representative molecules in the structure. This function can be upto 50 times slower than extracting molecules individually for small molecules like water. :param bool use_smarts: If true the unique molecules will share the same SMARTS pattern. If false the unique molecules will share the same SMILES pattern. :rtype: list :return: list of molecule numbers that are unique """ name_func = (self.getMoleculeSmarts if use_smarts else self.getMoleculeSmiles) name_mol = {y: x for x, y in name_func().items()} return list(name_mol.values())