File size: 4,290 Bytes
72a3513
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""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