chython 2.4__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. chython/__init__.py +31 -0
  2. chython/_functions.py +69 -0
  3. chython/algorithms/__init__.py +18 -0
  4. chython/algorithms/_isomorphism.cp312-win_amd64.pyd +0 -0
  5. chython/algorithms/_isomorphism.pyx +189 -0
  6. chython/algorithms/aromatics/__init__.py +27 -0
  7. chython/algorithms/aromatics/_rules.py +124 -0
  8. chython/algorithms/aromatics/kekule.py +530 -0
  9. chython/algorithms/aromatics/test/__init__.py +18 -0
  10. chython/algorithms/aromatics/test/test_kekule.py +160 -0
  11. chython/algorithms/aromatics/test/test_thiele.py +263 -0
  12. chython/algorithms/aromatics/thiele.py +226 -0
  13. chython/algorithms/calculate2d/__init__.py +23 -0
  14. chython/algorithms/calculate2d/clean2d.js +1 -0
  15. chython/algorithms/calculate2d/molecule.py +149 -0
  16. chython/algorithms/calculate2d/reaction.py +80 -0
  17. chython/algorithms/depict.py +523 -0
  18. chython/algorithms/fingerprints/__init__.py +48 -0
  19. chython/algorithms/fingerprints/linear.py +208 -0
  20. chython/algorithms/fingerprints/morgan.py +137 -0
  21. chython/algorithms/fingerprints/test/__init__.py +18 -0
  22. chython/algorithms/fingerprints/test/test_linear.py +127 -0
  23. chython/algorithms/fingerprints/test/test_morgan.py +79 -0
  24. chython/algorithms/isomorphism.py +667 -0
  25. chython/algorithms/mapping/__init__.py +28 -0
  26. chython/algorithms/mapping/_groups.py +94 -0
  27. chython/algorithms/mapping/_reactions.py +72 -0
  28. chython/algorithms/mapping/attention.py +216 -0
  29. chython/algorithms/mapping/fixmapper.py +84 -0
  30. chython/algorithms/mapping/groups.py +137 -0
  31. chython/algorithms/mcs.py +202 -0
  32. chython/algorithms/morgan.py +82 -0
  33. chython/algorithms/rings.py +551 -0
  34. chython/algorithms/smiles.py +556 -0
  35. chython/algorithms/standardize/__init__.py +30 -0
  36. chython/algorithms/standardize/_charged.py +115 -0
  37. chython/algorithms/standardize/_groups.py +926 -0
  38. chython/algorithms/standardize/_metal_organics.py +189 -0
  39. chython/algorithms/standardize/_reagents.py +44 -0
  40. chython/algorithms/standardize/_salts.py +68 -0
  41. chython/algorithms/standardize/molecule.py +479 -0
  42. chython/algorithms/standardize/reaction.py +429 -0
  43. chython/algorithms/standardize/resonance.py +196 -0
  44. chython/algorithms/standardize/salts.py +128 -0
  45. chython/algorithms/standardize/saturation.py +378 -0
  46. chython/algorithms/standardize/test/__init__.py +18 -0
  47. chython/algorithms/standardize/test/test_groups.py +118 -0
  48. chython/algorithms/stereo.py +1222 -0
  49. chython/algorithms/tautomers/__init__.py +171 -0
  50. chython/algorithms/tautomers/_acid.py +59 -0
  51. chython/algorithms/tautomers/_base.py +116 -0
  52. chython/algorithms/tautomers/_keto_enol.py +107 -0
  53. chython/algorithms/tautomers/acid_base.py +214 -0
  54. chython/algorithms/tautomers/heteroarenes.py +108 -0
  55. chython/algorithms/tautomers/keto_enol.py +148 -0
  56. chython/algorithms/tautomers/test/__init__.py +18 -0
  57. chython/algorithms/tautomers/test/test_tautomers.py +80 -0
  58. chython/algorithms/test/__init__.py +18 -0
  59. chython/algorithms/test/test_isomorphism.py +58 -0
  60. chython/algorithms/test/test_smiles.py +76 -0
  61. chython/algorithms/x3dom.py +355 -0
  62. chython/containers/__init__.py +43 -0
  63. chython/containers/_pack_v2.cp312-win_amd64.pyd +0 -0
  64. chython/containers/_pack_v2.pyx +271 -0
  65. chython/containers/_unpack_v0v2.cp312-win_amd64.pyd +0 -0
  66. chython/containers/_unpack_v0v2.pyx +300 -0
  67. chython/containers/bonds.py +255 -0
  68. chython/containers/cgr.py +115 -0
  69. chython/containers/graph.py +200 -0
  70. chython/containers/molecule.py +695 -0
  71. chython/containers/query.py +66 -0
  72. chython/containers/reaction.py +324 -0
  73. chython/exceptions.py +114 -0
  74. chython/files/MRVrw.py +523 -0
  75. chython/files/PDBrw.py +257 -0
  76. chython/files/RDFrw.py +298 -0
  77. chython/files/SDFrw.py +221 -0
  78. chython/files/__init__.py +29 -0
  79. chython/files/_convert.py +207 -0
  80. chython/files/_mapping.py +117 -0
  81. chython/files/_xyz.cp312-win_amd64.pyd +0 -0
  82. chython/files/_xyz.pyx +74 -0
  83. chython/files/daylight/__init__.py +24 -0
  84. chython/files/daylight/parser.py +157 -0
  85. chython/files/daylight/smarts.py +107 -0
  86. chython/files/daylight/smiles.py +245 -0
  87. chython/files/daylight/test/__init__.py +18 -0
  88. chython/files/daylight/test/test_daylight_smarts.py +289 -0
  89. chython/files/daylight/test/test_daylight_smiles.py +101 -0
  90. chython/files/daylight/test/test_parser.py +118 -0
  91. chython/files/daylight/test/test_tokenize.py +77 -0
  92. chython/files/daylight/tokenize.py +376 -0
  93. chython/files/libinchi/__init__.py +22 -0
  94. chython/files/libinchi/libinchi.dll +0 -0
  95. chython/files/libinchi/wrapper.py +555 -0
  96. chython/files/mdl/__init__.py +25 -0
  97. chython/files/mdl/emol.py +209 -0
  98. chython/files/mdl/erxn.py +67 -0
  99. chython/files/mdl/mol.py +144 -0
  100. chython/files/mdl/read.py +225 -0
  101. chython/files/mdl/rxn.py +67 -0
  102. chython/files/mdl/stereo.py +63 -0
  103. chython/files/mdl/write.py +165 -0
  104. chython/files/xyz.py +78 -0
  105. chython/periodictable/__init__.py +69 -0
  106. chython/periodictable/base/__init__.py +25 -0
  107. chython/periodictable/base/dynamic.py +149 -0
  108. chython/periodictable/base/element.py +452 -0
  109. chython/periodictable/base/groups.py +90 -0
  110. chython/periodictable/base/periods.py +46 -0
  111. chython/periodictable/base/query.py +473 -0
  112. chython/periodictable/base/vector.py +119 -0
  113. chython/periodictable/groupI.py +252 -0
  114. chython/periodictable/groupII.py +223 -0
  115. chython/periodictable/groupIII.py +1154 -0
  116. chython/periodictable/groupIV.py +210 -0
  117. chython/periodictable/groupIX.py +196 -0
  118. chython/periodictable/groupV.py +192 -0
  119. chython/periodictable/groupVI.py +183 -0
  120. chython/periodictable/groupVII.py +162 -0
  121. chython/periodictable/groupVIII.py +161 -0
  122. chython/periodictable/groupX.py +166 -0
  123. chython/periodictable/groupXI.py +164 -0
  124. chython/periodictable/groupXII.py +158 -0
  125. chython/periodictable/groupXIII.py +239 -0
  126. chython/periodictable/groupXIV.py +262 -0
  127. chython/periodictable/groupXV.py +276 -0
  128. chython/periodictable/groupXVI.py +457 -0
  129. chython/periodictable/groupXVII.py +324 -0
  130. chython/periodictable/groupXVIII.py +260 -0
  131. chython/reactor/__init__.py +24 -0
  132. chython/reactor/base.py +212 -0
  133. chython/reactor/deprotection.py +565 -0
  134. chython/reactor/groups.py +128 -0
  135. chython/reactor/reactions/__init__.py +157 -0
  136. chython/reactor/reactions/_amidation.py +58 -0
  137. chython/reactor/reactions/_amine_isocyanate.py +52 -0
  138. chython/reactor/reactions/_buchwald_hartwig.py +50 -0
  139. chython/reactor/reactions/_esterification.py +49 -0
  140. chython/reactor/reactions/_macmillan.py +43 -0
  141. chython/reactor/reactions/_reductive_amination.py +50 -0
  142. chython/reactor/reactions/_sonogashira.py +49 -0
  143. chython/reactor/reactions/_sulfonamidation.py +52 -0
  144. chython/reactor/reactions/_suzuki_miyaura.py +88 -0
  145. chython/reactor/reactions/ufe.py +99 -0
  146. chython/reactor/reactor.py +170 -0
  147. chython/reactor/retro/__init__.py +77 -0
  148. chython/reactor/retro/_amidation.py +39 -0
  149. chython/reactor/retro/_aryl_amination.py +32 -0
  150. chython/reactor/retro/_mitsunobu.py +49 -0
  151. chython/reactor/retro/_sonogashira.py +49 -0
  152. chython/reactor/retro/_suzuki_miyaura.py +54 -0
  153. chython/reactor/scaffold.py +122 -0
  154. chython/reactor/test/__init__.py +18 -0
  155. chython/reactor/test/test_deprotection.py +57 -0
  156. chython/reactor/test/test_reactor.py +40 -0
  157. chython/reactor/test/test_scaffold.py +57 -0
  158. chython/reactor/test/test_transformer.py +40 -0
  159. chython/reactor/transformer.py +62 -0
  160. chython/utils/__init__.py +57 -0
  161. chython/utils/free_wilson.py +114 -0
  162. chython/utils/grid.py +130 -0
  163. chython/utils/rdkit.py +165 -0
  164. chython/utils/retro.py +139 -0
  165. chython/utils/svg.py +57 -0
  166. chython/utils/test/__init__.py +18 -0
  167. chython/utils/test/test_rdkit.py +71 -0
  168. chython-2.4.dist-info/LICENSE +165 -0
  169. chython-2.4.dist-info/METADATA +101 -0
  170. chython-2.4.dist-info/RECORD +171 -0
  171. chython-2.4.dist-info/WHEEL +4 -0
@@ -0,0 +1,94 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2021-2023 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # This file is part of chython.
5
+ #
6
+ # chython is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation; either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
18
+ #
19
+ from lazy_object_proxy import Proxy
20
+
21
+
22
+ def _xonyl_groups():
23
+ from ... import smarts
24
+
25
+ rules = []
26
+ # atom 1 - axis
27
+ # atoms 2, 3 - swapping
28
+ q = smarts('[N;D3;z2;+]([O;D1;-])=[O;D1]')
29
+ rules.append(q)
30
+
31
+ q = smarts('[N,O,S,Se;D1;z2:3]=[C,P,As,Sb,Bi,S,Se,Te,Po,Cl,Br,I,At:1][N,O,S,Se;D1:2]')
32
+ rules.append(q)
33
+
34
+ q = smarts('[N,O,S,Se;D1;z2:3]=[C,P,As,Sb,Bi,S,Se,Te,Po,Cl,Br,I,At:1][N,O,S,Se;D1;-:2]')
35
+ rules.append(q)
36
+ return rules
37
+
38
+
39
+ def _substituents():
40
+ """
41
+ Rules for switchable functional groups remapping
42
+ """
43
+ from ... import smarts
44
+
45
+ rules = []
46
+
47
+ # nitro addition
48
+ #
49
+ # O [3] [2] O -- *
50
+ # // /:
51
+ # * - N+ >> * = N+
52
+ # \ \
53
+ # O- [2] O- [3]
54
+ #
55
+ q = smarts('[N;D3;z2,z4;+]([O;D1;-])-,:[O;D2]')
56
+ rules.append((q, ((2, 3), (3, 2))))
57
+
58
+ #
59
+ # NH [3] NH2 [3]
60
+ # // /
61
+ # * - X >> * - X
62
+ # \ :
63
+ # NH2 [2] N [2]
64
+ #
65
+ q = smarts('[N;D2;h0;z2,z4:3]=,:[C;D3:1][N:2]')
66
+ rules.append((q, ((2, 3), (3, 2))))
67
+
68
+ #
69
+ # O [3] [3] O - R
70
+ # // /
71
+ # * - X >> * - X
72
+ # \ \\
73
+ # OH [2] O [2]
74
+ #
75
+ q = smarts('[N,O,S,Se;D1;z2:3]=[C,P,As,Sb,Bi,S,Se,Te,Po,Cl,Br,I,At:1][N,O,S,Se;D2:2]')
76
+ rules.append((q, ((2, 3), (3, 2))))
77
+
78
+ #
79
+ # O [3] A
80
+ # // /
81
+ # * - X >> * - X
82
+ # \ \\
83
+ # OH [2] O [2]
84
+ #
85
+ q = smarts('[N,O,S,Se;D1;z2:3]=[C,P,As,Sb,Bi,S,Se,Te,Po,Cl,Br,I,At:1][A:2]')
86
+ rules.append((q, ((3, 2),))) # possible only: (3, 2)
87
+ return rules
88
+
89
+
90
+ xonyl_groups = Proxy(_xonyl_groups)
91
+ substituents_groups = Proxy(_substituents)
92
+
93
+
94
+ __all__ = ['xonyl_groups', 'substituents_groups']
@@ -0,0 +1,72 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2022 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # This file is part of chython.
5
+ #
6
+ # chython is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation; either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
18
+ #
19
+ from lazy_object_proxy import Proxy
20
+
21
+
22
+ def _rules():
23
+ from ... import smiles, smarts
24
+ raw_rules = []
25
+
26
+ # phenol etherification
27
+ r = smiles('[OH:5][cH:1]:[cH2:2].[CH3:4][OH:3]>>[CH3:4][O:3][cH:1]:[cH2:2]')
28
+ m = smarts('[O;D1:5].[O;D1:3]') # reactant restriction.
29
+ raw_rules.append((r, {3: 5}, m))
30
+
31
+ # esterification
32
+ r = smiles('[OH:2][CH:1]=[O:3].[CH3:4][OH:5]>>[CH3:4][O:2][CH:1]=[O:3]')
33
+ m = smarts('[O;D1:2].[O;D1:5]')
34
+ raw_rules.append((r, {2: 5}, m))
35
+
36
+ # acid reduction
37
+ r = smiles('[CH3:2][C:1]([OH:3])=[O:4]>>[CH3:2][CH2:1][OH:3]')
38
+ m = smarts('[O;D1:3]')
39
+ raw_rules.append((r, {3: 4, 4: 3}, m))
40
+
41
+ # ester reduction
42
+ r = smiles('[CH3:5][O:3][C:1]([CH3:2])=[O:4]>>[CH3:2][CH2:1][OH:3]')
43
+ raw_rules.append((r, {3: 4}, None))
44
+
45
+ # ozonolysis
46
+ r = smiles('[O-:4][O+:1]=[O:6].[CH3:3][CH:2]=[CH2:5]>>[CH3:3][CH:2]=[O:1]')
47
+ raw_rules.append((r, {1: 4}, None))
48
+
49
+ # peroxide oxirane
50
+ r = smiles('[CH3:6][O:1][OH:5].[CH3:4][CH:2]=[CH2:3]>>[CH3:4][CH:2]1[CH2:3][O:1]1')
51
+ m = smarts('[O;D1:5]')
52
+ raw_rules.append((r, {1: 5}, m))
53
+
54
+ # claisen rearrangement
55
+ r = smiles('[cH2:4]:[cH:3]:[cH:2][O:1][CH2:5][CH:6]=[CH2:7]>>[OH:1][cH:2]:[c:3](:[cH2:4])[CH2:5][CH:6]=[CH2:7]')
56
+ raw_rules.append((r, {5: 7, 7: 5}, None))
57
+
58
+ # metathesis
59
+ r = smiles('[CH3:1][CH:2]=[CH2:3].[CH3:5][CH:4]=[CH2:6]>>[CH3:5][CH:3]=[CH:2][CH3:1]')
60
+ raw_rules.append((r, {3: 4, 4: 3}, None))
61
+
62
+ rules = []
63
+ for r, f, m in raw_rules:
64
+ c = ~r
65
+ rules.append((c, str(c.substructure(c.center_atoms)), m, f))
66
+ return rules
67
+
68
+
69
+ rules = Proxy(_rules)
70
+
71
+
72
+ __all__ = ['rules']
@@ -0,0 +1,216 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2022-2024 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # Copyright 2024 Philippe Gantzer <p.gantzer@icredd.hokudai.ac.jp>
5
+ # This file is part of chython.
6
+ #
7
+ # chython is free software; you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published by
9
+ # the Free Software Foundation; either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+ from CachedMethods import class_cached_property
21
+ from itertools import chain, count, repeat
22
+ from logging import getLogger, INFO
23
+ from numpy import ix_, unravel_index, argmax, zeros, array, isclose, nonzero, ones, mean
24
+ from typing import TYPE_CHECKING, Union
25
+
26
+
27
+ if TYPE_CHECKING:
28
+ from chython import ReactionContainer
29
+
30
+ logger = getLogger('chython.attention')
31
+ logger.setLevel(INFO)
32
+
33
+
34
+ class Attention:
35
+ __slots__ = ()
36
+ __class_cache__ = {}
37
+
38
+ def reset_mapping(self: Union['ReactionContainer', 'Attention'], *, return_score: bool = False, multiplier=1.75,
39
+ keep_reactants_numbering=False) -> Union[bool, float]:
40
+ """
41
+ Do atom-to-atom mapping. Return True if mapping changed.
42
+ """
43
+ if any(len(bs) > 14 for m in self.molecules() for bs in m._bonds.values()):
44
+ logger.info('atom-to-atom mapping not supported for hypervalent compounds')
45
+ return False
46
+ fixed = self.__fix_collisions()
47
+ equal_atoms, p2r, r2p, r_adj, p_adj, r_map, p_map, pa, rg_map = self.__prepare_remapping()
48
+
49
+ # rxnmapper-inspired algorithm
50
+ am = self.__get_attention()
51
+ # sum of reactants to products attention and vice-versa for equal atom types only
52
+ am = (am[p2r] + am[r2p].T) * equal_atoms
53
+ amc = am.copy()
54
+
55
+ mapping = {}
56
+ scope = zeros(pa, dtype=bool)
57
+ seen = ones(pa, dtype=bool)
58
+ score = []
59
+ for x in range(pa): # iteratively map each product atom to reactant
60
+ # select highest attention
61
+ # todo: optimize
62
+ if not x:
63
+ i, j = unravel_index(argmax(am), am.shape)
64
+ else:
65
+ ams = am[scope]
66
+ if ams.size:
67
+ i, j = unravel_index(argmax(ams), ams.shape)
68
+ i = nonzero(scope)[0][i]
69
+ else:
70
+ i, j = unravel_index(argmax(am), am.shape)
71
+ if isclose(am[i, j], 0.): # no more products atoms in reactants
72
+ # mark as unmapped
73
+ for n in set(p_map).difference(mapping):
74
+ mapping[n] = 0
75
+ break
76
+ else:
77
+ score.append(amc[i, j])
78
+ mapping[p_map[i]] = r_map[j]
79
+ am[ix_(p_adj[i], r_adj[j])] *= multiplier # highlight neighbors
80
+ am[i] = am[:, j] = 0 # mask mapped product and reactant atoms
81
+ seen[i] = False
82
+ scope[i] = False
83
+ scope[p_adj[i] & seen] = True
84
+
85
+ score = float(mean(score)) if score else 0.
86
+ # mapping done.
87
+ if any(n != m for n, m in mapping.items()): # old mapping changed
88
+ if keep_reactants_numbering or fixed:
89
+ r_mapping = {n: n for n in r_map}
90
+ else:
91
+ r_mapping = {m: n for n, m in enumerate(r_map, 1)} # remap reactants to contiguous range
92
+ for m in self.reactants:
93
+ m.remap(r_mapping)
94
+
95
+ p_mapping = {}
96
+ nm = max(r_mapping.values())
97
+ for n, m in mapping.items():
98
+ if m := r_mapping.get(m):
99
+ p_mapping[n] = m
100
+ else: # not found in reactants atoms. set unique numbers.
101
+ nm += 1
102
+ p_mapping[n] = nm
103
+
104
+ for m in self.products:
105
+ m.remap(p_mapping)
106
+
107
+ if not keep_reactants_numbering and not fixed:
108
+ rg_mapping = {m: n for n, m in enumerate(rg_map, nm+1)} # remap reagents to contiguous range without overlapping
109
+ for m in self.reagents:
110
+ m.remap(rg_mapping)
111
+
112
+ self.flush_cache()
113
+ fixed = True
114
+
115
+ if self.fix_groups_mapping(): # fix carboxy etc
116
+ fixed = True
117
+ if self.fix_mapping(): # fix common mistakes in mechanisms
118
+ fixed = True
119
+ if return_score:
120
+ return score
121
+ return fixed
122
+
123
+ def __fix_collisions(self: 'ReactionContainer'):
124
+ r = [n for m in chain(self.reactants, self.reagents) for n in m._atoms]
125
+ p = [n for m in self.products for n in m._atoms]
126
+ c = count(1)
127
+ if len(r) != len(set(r)):
128
+ for m in chain(self.reactants, self.reagents):
129
+ m.remap({n: next(c) for n in m._atoms})
130
+ if len(p) != len(set(p)):
131
+ for m in self.products:
132
+ m.remap({n: next(c) for n in m._atoms})
133
+ if next(c) != 1:
134
+ self.flush_cache()
135
+ return True
136
+ return False
137
+
138
+ def __prepare_remapping(self: 'ReactionContainer'):
139
+ r_map = [n for m in self.reactants for n in m]
140
+ p_map = [n for m in self.products for n in m]
141
+ rg_map = [n for m in self.reagents for n in m]
142
+ ra = len(r_map) # number of reactants atoms
143
+ pa = len(p_map) # number of products atoms
144
+
145
+ ram = [False] # reactants atoms mask
146
+ r_atoms = []
147
+ r_adj = zeros((ra, ra), dtype=bool)
148
+ i = 0
149
+ for m in self.reactants:
150
+ ram.append(False)
151
+ ram.extend(repeat(True, len(m)))
152
+ a = m.adjacency_matrix()
153
+ j = i + len(m)
154
+ r_adj[i:j, i:j] = a
155
+ i = j
156
+ r_atoms.extend(a.atomic_number for _, a in m.atoms())
157
+ r_atoms = array(r_atoms, dtype=int)
158
+
159
+ pam = [False] * len(ram) # products atoms mask
160
+ p_atoms = []
161
+ p_adj = zeros((pa, pa), dtype=bool)
162
+ i = 0
163
+ for m in self.products:
164
+ pam.append(False)
165
+ pam.extend(repeat(True, len(m)))
166
+ a = m.adjacency_matrix()
167
+ j = i + len(m)
168
+ p_adj[i:j, i:j] = a
169
+ i = j
170
+ p_atoms.extend(a.atomic_number for _, a in m.atoms())
171
+ p_atoms = array(p_atoms, dtype=int)
172
+
173
+ ram.extend(repeat(False, len(pam) - len(ram)))
174
+ ram = array(ram, dtype=bool)
175
+ pam = array(pam, dtype=bool)
176
+ return p_atoms[:, None] == r_atoms, ix_(pam, ram), ix_(ram, pam), r_adj, p_adj, r_map, p_map, pa, rg_map
177
+
178
+ @class_cached_property
179
+ def __attention_model(self):
180
+ from chython import torch_device
181
+ from chytorch.zoo.rxnmap import Model
182
+
183
+ return Model().to(torch_device)
184
+
185
+ @class_cached_property
186
+ def __autocast(self):
187
+ from chython import torch_device
188
+
189
+ if torch_device.startswith('cuda'):
190
+ try:
191
+ from torch import autocast
192
+ except ImportError: # torch 1.8 ad-hoc
193
+ from torch.cuda.amp import autocast
194
+
195
+ return autocast()
196
+ else:
197
+ return autocast('cuda')
198
+ return autocast_filler()
199
+
200
+ def __get_attention(self):
201
+ from torch import no_grad
202
+
203
+ with no_grad(), self.__autocast:
204
+ am = self.__attention_model(self).float().cpu().numpy()
205
+ return am
206
+
207
+
208
+ class autocast_filler:
209
+ def __enter__(self):
210
+ return self
211
+
212
+ def __exit__(self, exc_type, exc_val, exc_tb):
213
+ ...
214
+
215
+
216
+ __all__ = ['Attention']
@@ -0,0 +1,84 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2022-2024 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # This file is part of chython.
5
+ #
6
+ # chython is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation; either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
18
+ #
19
+ from collections import ChainMap
20
+ from itertools import count
21
+ from typing import List, Tuple, TYPE_CHECKING, Union
22
+ from ._reactions import rules
23
+
24
+
25
+ if TYPE_CHECKING:
26
+ from chython import ReactionContainer
27
+
28
+
29
+ class FixMapper:
30
+ __slots__ = ()
31
+
32
+ def fix_mapping(self: 'ReactionContainer', *, logging: bool = False) -> \
33
+ Union[bool, List[Tuple[int, str, Tuple[int, ...]]]]:
34
+ """
35
+ Fix mapping by using loaded rules.
36
+ """
37
+ if not self:
38
+ if logging:
39
+ return []
40
+ return False
41
+
42
+ cgr = ~self
43
+ if not cgr.center_atoms:
44
+ if logging:
45
+ return []
46
+ return False
47
+ del self.__dict__['__cached_method_compose']
48
+
49
+ log = []
50
+ free_number = count(max(cgr) + 1)
51
+ components = [(cgr.substructure(c),
52
+ cgr.augmented_substructure(c, 2), # deep DEPENDS on rules!
53
+ c)
54
+ for c in cgr.substructure(cgr.center_atoms).connected_components]
55
+
56
+ r_atoms = ChainMap(*(x._atoms for x in self.reactants))
57
+ for c, ac, cs in components:
58
+ for rule_num, (query, signature, restrict, fix) in enumerate(rules):
59
+ if str(c) == signature:
60
+ for mapping in query.get_mapping(ac, automorphism_filter=False):
61
+ if not cs.issubset(mapping.values()):
62
+ continue
63
+ if restrict is not None and any(a != r_atoms.get(mapping[n]) for n, a in restrict.atoms()):
64
+ continue
65
+ mapping = {mapping[n]: next(free_number) if m is None else mapping[m] for n, m in fix.items()}
66
+ for m in self.products:
67
+ m.remap(mapping)
68
+ log.append((rule_num, signature, tuple(mapping.values())))
69
+ break
70
+ else:
71
+ continue
72
+ break # component remapped!
73
+
74
+ if log:
75
+ self.flush_cache()
76
+ if logging:
77
+ return log
78
+ return True
79
+ elif logging:
80
+ return log
81
+ return False
82
+
83
+
84
+ __all__ = ['FixMapper']
@@ -0,0 +1,137 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2022 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # This file is part of chython.
5
+ #
6
+ # chython is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation; either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
18
+ #
19
+ from itertools import chain, repeat
20
+ from typing import List, Tuple, TYPE_CHECKING, Union
21
+ from ._groups import *
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from chython import ReactionContainer
26
+
27
+
28
+ class GroupsFix:
29
+ __slots__ = ()
30
+
31
+ def fix_groups_mapping(self: 'ReactionContainer', *, logging: bool = False) -> \
32
+ Union[bool, List[Tuple[str, Tuple[int, ...]]]]:
33
+ """
34
+ Fix atom-to-atom mapping of some functional groups. Return True if found AAM errors.
35
+ """
36
+ if not self:
37
+ if logging:
38
+ return []
39
+ return False
40
+
41
+ log = []
42
+ seen = set()
43
+ remap = {}
44
+ pamer = {}
45
+ r_groups = set()
46
+ p_groups = set()
47
+ r_subs = set()
48
+ p_subs = set()
49
+
50
+ # find xonyl groups. any charged-neutral combinations
51
+ for pattern in xonyl_groups:
52
+ for m, g in chain(zip(self.reactants, repeat(r_groups)), zip(self.products, repeat(p_groups))):
53
+ atoms = m._atoms
54
+ for mapping in pattern.get_mapping(m, automorphism_filter=False):
55
+ n1, n2, n3 = mapping[1], mapping[2], mapping[3]
56
+ if (t := atoms[n2].atomic_number) == atoms[n3].atomic_number:
57
+ g.add((n1, n2, n3, atoms[n1].atomic_number, t))
58
+
59
+ for pattern, _map in substituents_groups:
60
+ for m, g in chain(zip(self.reactants, repeat(r_subs)), zip(self.products, repeat(p_subs))):
61
+ atoms = m._atoms
62
+ for mapping in pattern.get_mapping(m, automorphism_filter=False):
63
+ g.add((n := mapping[1], atoms[n].atomic_number,
64
+ tuple((n := mapping[x], y - 2, atoms[n].atomic_number) for x, y in _map), m))
65
+
66
+ r_groups = list(r_groups)
67
+ p_groups = list(p_groups)
68
+
69
+ # find pairs
70
+ if r_groups and p_groups:
71
+ for n1, n2, n3, x1, x2 in r_groups:
72
+ if n1 in seen: # already remapped
73
+ continue
74
+ for i, (m1, m2, m3, y1, y2) in enumerate(p_groups):
75
+ if m1 not in seen and n1 == m1 and x1 == y1 and x2 == y2: # found pair
76
+ if n2 == m3 and n3 == m2: # found switch
77
+ remap[m2] = m3
78
+ remap[m3] = m2
79
+ seen.add(n1)
80
+ break
81
+ else:
82
+ continue
83
+ del p_groups[i]
84
+
85
+ if not p_groups: # optimize
86
+ r_subs.clear()
87
+
88
+ # hydrolysis, etc.
89
+ for (n1, x1, _map, m), g, r in chain(zip(r_subs, repeat(p_groups), repeat(remap)),
90
+ zip(p_subs, repeat(r_groups), repeat(pamer))):
91
+ if n1 in seen:
92
+ continue
93
+ for i, (m1, *m23, y1, y2) in enumerate(g):
94
+ if m1 not in seen and n1 == m1 and x1 == y1: # found center
95
+ if len(_map) == 1: # acids substitutions.
96
+ ni, _, xi = _map[0]
97
+ # second neighbor should be disconnected from central atom.
98
+ if xi == y2 and m23[0] == ni and (m23[1] not in m._atoms or m23[1] not in m._bonds[n1]):
99
+ m2, m3 = m23
100
+ r[m2] = m3
101
+ r[m3] = m2
102
+ seen.add(n1)
103
+ break
104
+ elif all(xi == y2 and m23[mi] == ni for ni, mi, xi in _map):
105
+ m2, m3 = m23
106
+ r[m2] = m3
107
+ r[m3] = m2
108
+ seen.add(n1)
109
+ break
110
+ else:
111
+ continue
112
+ del g[i]
113
+
114
+ if remap:
115
+ seen = set(remap)
116
+ for m in self.products:
117
+ if not seen.isdisjoint(m):
118
+ m.remap(remap)
119
+ log.append(('products groups remapped', tuple(remap)))
120
+ if pamer:
121
+ seen = set(pamer)
122
+ for m in self.reactants:
123
+ if not seen.isdisjoint(m):
124
+ m.remap(pamer)
125
+ log.append(('reactants groups remapped', tuple(pamer)))
126
+
127
+ if log:
128
+ self.flush_cache()
129
+ if logging:
130
+ return log
131
+ return True
132
+ elif logging:
133
+ return []
134
+ return False
135
+
136
+
137
+ __all__ = ['GroupsFix']