"""Module containing classes and functions for manipulating reactions and reaction rules.""" from typing import Any, Iterator, List, Optional from CGRtools.containers import MoleculeContainer, ReactionContainer from CGRtools.exceptions import InvalidAromaticRing from CGRtools.reactor import Reactor class Reaction(ReactionContainer): """Reaction class used for a general representation of reaction.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def add_small_mols( big_mol: MoleculeContainer, small_molecules: Optional[Any] = None ) -> List[MoleculeContainer]: """Takes a molecule and returns a list of modified molecules where each small molecule has been added to the big molecule. :param big_mol: A molecule. :param small_molecules: A list of small molecules that need to be added to the molecule. :return: Returns a list of molecules. """ if small_molecules: tmp_mol = big_mol.copy() transition_mapping = {} for small_mol in small_molecules: for n, atom in small_mol.atoms(): new_number = tmp_mol.add_atom(atom.atomic_symbol) transition_mapping[n] = new_number for atom, neighbor, bond in small_mol.bonds(): tmp_mol.add_bond( transition_mapping[atom], transition_mapping[neighbor], bond ) transition_mapping = {} return tmp_mol.split() return [big_mol] def apply_reaction_rule( molecule: MoleculeContainer, reaction_rule: Reactor, sort_reactions: bool = False, top_reactions_num: int = 3, validate_products: bool = True, rebuild_with_cgr: bool = False, ) -> Iterator[List[MoleculeContainer,]]: """Applies a reaction rule to a given molecule. :param molecule: A molecule to which reaction rule will be applied. :param reaction_rule: A reaction rule to be applied. :param sort_reactions: :param top_reactions_num: The maximum amount of reactions after the application of reaction rule. :param validate_products: If True, validates the final products. :param rebuild_with_cgr: If True, the products are extracted from CGR decomposition. :return: An iterator yielding the products of reaction rule application. """ reactants = add_small_mols(molecule, small_molecules=False) try: if sort_reactions: unsorted_reactions = list(reaction_rule(reactants)) sorted_reactions = sorted( unsorted_reactions, key=lambda react: len( list(filter(lambda mol: len(mol) > 6, react.products)) ), reverse=True, ) # take top-N reactions from reactor reactions = sorted_reactions[:top_reactions_num] else: reactions = [] for reaction in reaction_rule(reactants): reactions.append(reaction) if len(reactions) == top_reactions_num: break except IndexError: reactions = [] for reaction in reactions: # temporary solution - incorrect leaving groups reactant_atom_nums = [] for i in reaction.reactants: reactant_atom_nums.extend(i.atoms_numbers) product_atom_nums = [] for i in reaction.products: product_atom_nums.extend(i.atoms_numbers) leaving_atom_nums = set(reactant_atom_nums) - set(product_atom_nums) if len(leaving_atom_nums) > len(product_atom_nums): continue # check reaction if rebuild_with_cgr: cgr = reaction.compose() reactants = cgr.decompose()[1].split() else: reactants = reaction.products # reactants are products in retro reaction reactants = [mol for mol in reactants if len(mol) > 0] # validate products if validate_products: for mol in reactants: try: mol.kekule() if mol.check_valence(): yield None mol.thiele() except InvalidAromaticRing: yield None yield reactants