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,171 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020-2024 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # Copyright 2020 Nail Samikaev <samikaevn@yandex.ru>
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 collections import deque
21
+ from typing import TYPE_CHECKING, Iterator, Union
22
+ from .acid_base import *
23
+ from .heteroarenes import *
24
+ from .keto_enol import *
25
+
26
+
27
+ if TYPE_CHECKING:
28
+ from chython import MoleculeContainer
29
+
30
+
31
+ class Tautomers(AcidBase, HeteroArenes, KetoEnol):
32
+ """
33
+ Oxides and sulphides ignored.
34
+ """
35
+ __slots__ = ()
36
+
37
+ def enumerate_tautomers(self: Union['MoleculeContainer', 'Tautomers'], *, prepare_molecules=True, zwitter=True,
38
+ partial=False, increase_aromaticity=True, keep_sugars=True, heteroarenes=True,
39
+ keto_enol=True, limit: int = 1000) -> Iterator['MoleculeContainer']:
40
+ """
41
+ Enumerate all possible tautomeric forms of molecule.
42
+
43
+ :param prepare_molecules: Standardize structures for correct processing
44
+ :param zwitter: Do zwitter-ions enumeration
45
+ :param partial: Allow OC=CC=C>>O=CCC=C or O=CC=CC>>OC=C=CC
46
+ :param increase_aromaticity: prevent aromatic ring destruction
47
+ :param keep_sugars: prevent carbonyl moving in sugars
48
+ :param heteroarenes: enumerate heteroarenes
49
+ :param keto_enol: enumerate keto-enols
50
+ :param limit: Maximum attempts count
51
+ """
52
+ if limit < 1:
53
+ raise ValueError('limit should be greater or equal 1')
54
+ counter = 0
55
+
56
+ copy = self.copy(keep_sssr=True, keep_components=True)
57
+ if prepare_molecules: # transform to kekule form without hydrogens
58
+ copy.kekule()
59
+ copy.implicify_hydrogens(_fix_stereo=False)
60
+
61
+ # transform to thiele to prevent duplicates and dearomatization
62
+ thiele = copy.copy(keep_sssr=True, keep_components=True)
63
+ thiele.thiele(fix_tautomers=False)
64
+ yield thiele # return original structure as first tautomer
65
+
66
+ seen = {thiele: None} # value is parent molecule - required for preventing migrations in sugars.
67
+
68
+ # first try to neutralize
69
+ if copy.neutralize(_fix_stereo=False): # found neutral form
70
+ thiele = copy.copy(keep_sssr=True, keep_components=True)
71
+ thiele.thiele(fix_tautomers=False)
72
+ yield thiele
73
+ counter += 1
74
+ seen[thiele] = None
75
+
76
+ # lets iteratively do keto-enol transformations.
77
+ rings_count = len(thiele.aromatic_rings) # increase rings strategy.
78
+ if keto_enol:
79
+ queue = deque([(copy, thiele)])
80
+ else:
81
+ queue = None
82
+ new_queue = [thiele] # new_queue - molecules suitable for hetero-arenes enumeration.
83
+ # store aromatic form to seen. kekule forms not suitable for duplicate checking.
84
+
85
+ while queue:
86
+ current, thiele_current = queue.popleft()
87
+ for mol, ket in current._enumerate_keto_enol_tautomers(partial):
88
+ thiele = mol.copy(keep_sssr=True, keep_components=True)
89
+ thiele.thiele(fix_tautomers=False)
90
+
91
+ if thiele not in seen:
92
+ seen[thiele] = current
93
+ rc = len(thiele.aromatic_rings)
94
+ if increase_aromaticity:
95
+ if rc < rings_count: # skip aromatic rings destruction
96
+ continue
97
+ elif rc > rings_count: # higher aromaticity found. flush old queues.
98
+ rings_count = rc
99
+ queue = deque([(mol, thiele)])
100
+ new_queue = [thiele]
101
+ copy = mol # new entry point.
102
+ yield thiele
103
+ break
104
+ if keep_sugars and current is not copy and ket:
105
+ # prevent carbonyl migration in sugars. skip entry point.
106
+ # search alpha hydroxy ketone inversion
107
+ before = seen[thiele_current]._sugar_groups
108
+ if any((k, e) in before for e, k in mol._sugar_groups):
109
+ continue
110
+
111
+ queue.append((mol, thiele))
112
+ new_queue.append(thiele)
113
+ yield thiele
114
+ counter += 1
115
+ if counter == limit:
116
+ return
117
+
118
+ # heteroarenes tautomers enumeration
119
+ if heteroarenes:
120
+ queue = deque(new_queue)
121
+ while queue:
122
+ current = queue.popleft()
123
+ for mol in current._enumerate_hetero_arene_tautomers():
124
+ if mol not in seen:
125
+ seen[mol] = None
126
+ queue.append(mol)
127
+ new_queue.append(mol) # new hetero-arenes also should be included to this list.
128
+ yield mol
129
+ counter += 1
130
+ if counter == limit:
131
+ return
132
+
133
+ # zwitter-ions enumeration
134
+ if zwitter:
135
+ queue = deque(new_queue)
136
+ while queue:
137
+ current = queue.popleft()
138
+ for mol in current._enumerate_zwitter_tautomers():
139
+ if mol not in seen:
140
+ seen[mol] = None
141
+ queue.append(mol)
142
+ yield mol
143
+ counter += 1
144
+ if counter == limit:
145
+ return
146
+
147
+ def enumerate_charged_tautomers(self: 'MoleculeContainer', *, prepare_molecules=True, partial=False,
148
+ increase_aromaticity=True, keep_sugars=True, heteroarenes=True,
149
+ keto_enol=True, deep: int = 4, limit: int = 1000):
150
+ """
151
+ Enumerate tautomers and protonated-deprotonated forms.
152
+ Better to use on neutralized non-ionic molecules.
153
+
154
+ See `enumerate_tautomers` and `enumerate_charged_forms` params description.
155
+ """
156
+ count = 0
157
+ for t in self.enumerate_tautomers(prepare_molecules=prepare_molecules, zwitter=False, partial=partial,
158
+ increase_aromaticity=increase_aromaticity, keep_sugars=keep_sugars,
159
+ heteroarenes=heteroarenes, keto_enol=keto_enol, limit=limit):
160
+ yield t
161
+ count += 1
162
+ if count == limit:
163
+ return
164
+ for c in t.enumerate_charged_forms(deep=deep, limit=limit):
165
+ yield c
166
+ count += 1
167
+ if count == limit:
168
+ return
169
+
170
+
171
+ __all__ = ['Tautomers']
@@ -0,0 +1,59 @@
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 _stripped_rules():
23
+ from ... import smarts
24
+
25
+ rules = []
26
+
27
+ # Ammonia, H-imine,guanidine,amidine. [H][N+]=,-,:
28
+ q = smarts('[N;h1,h2,h3,h4;+]')
29
+ rules.append(q)
30
+ return rules
31
+
32
+
33
+ def _rules():
34
+ from ... import smarts
35
+
36
+ rules = _stripped_rules()
37
+
38
+ # Phenoles
39
+ q = smarts('[O,S,Se;D1;z1][C,N;a]')
40
+ rules.append(q)
41
+
42
+ # Oxo-acids
43
+ q = smarts('[O,S,Se;D1;z1][C,N,P,S,Se,Cl,Br,I]=O')
44
+ rules.append(q)
45
+
46
+ # Nitro acid
47
+ q = smarts('[N;D3;z2;+]([O;D1:1])([O-])=O')
48
+ rules.append(q)
49
+
50
+ q = smarts('[F,Cl,Br,I;D0]')
51
+ rules.append(q)
52
+ return rules
53
+
54
+
55
+ stripped_rules = Proxy(_stripped_rules)
56
+ rules = Proxy(_rules)
57
+
58
+
59
+ __all__ = ['stripped_rules', 'rules']
@@ -0,0 +1,116 @@
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 _stripped_rules():
23
+ from ... import smarts
24
+
25
+ rules = []
26
+
27
+ # Oxo-acid salts
28
+ q = smarts('[O,S,Se;D1;z1;-][C,Si,N,P,S,Se,Cl,Br,I]=O')
29
+ rules.append(q)
30
+
31
+ # Thiophosphate salts
32
+ q = smarts('P([O,S,Se;D1;-:1])=[S,Se]')
33
+ rules.append(q)
34
+
35
+ # Phenole salts, alcoholates
36
+ q = smarts('[O,S,Se;D1;z1;-][C,N]')
37
+ rules.append(q)
38
+
39
+ # Nitrate
40
+ q = smarts('[N;D3;z2;+]([O;-:1])([O-])=O')
41
+ rules.append(q)
42
+
43
+ # imide
44
+ q = smarts('[N;D1,D2;z1;-]')
45
+ rules.append(q)
46
+
47
+ # ions
48
+ q = smarts('[O,S,F,Cl,Br,I;D0;-]')
49
+ rules.append(q)
50
+ return rules
51
+
52
+
53
+ def _rules():
54
+ from ... import smarts
55
+
56
+ rules = _stripped_rules()
57
+
58
+ # Guanidine
59
+ q = smarts('[N;x0;z2]=C(N)N')
60
+ rules.append(q)
61
+
62
+ # Oxo-guanidine, Amino-guanidine
63
+ q = smarts('[N;D2;x1;z2]([O,N])=C(N)N')
64
+ rules.append(q)
65
+
66
+ # O-alkyl-isourea, S-alkyl-isothiaurea
67
+ q = smarts('[N;x0;z2]=C([O,S;D2;x0;z1])N')
68
+ rules.append(q)
69
+
70
+ # Dialkyl imidocarbonate
71
+ q = smarts('[N;x0;z2]=C([O;D2;x0])[O;D2;x0]')
72
+ rules.append(q)
73
+
74
+ # Amidine
75
+ q = smarts('[N;x0;z2]=[C;x2]N')
76
+ rules.append(q)
77
+
78
+ # O-alkyl-imidate (oxazoline)
79
+ q = smarts('[N;x0;z2]=[C;x2][O;D2;x0]')
80
+ rules.append(q)
81
+
82
+ # Amidoxime. O-N=C([C,H])N
83
+ q = smarts('[N;D2;x1;z2](O)=[C;x2]N')
84
+ rules.append(q)
85
+
86
+ # Oxime, Hydrazone. [O,N]-N=C([C,H])[C,H]
87
+ q = smarts('[N;D2;x1;z2]([N,O])=[C;x1;z2]')
88
+ rules.append(q)
89
+
90
+ # Imine
91
+ q = smarts('[N;x0;z2]=[C;x1;z2]')
92
+ rules.append(q)
93
+
94
+ # Alkyl amine, Hydroxylamine, Hydrazine
95
+ q = smarts('[N;D1;z1][C,N,O;x1;z1]')
96
+ rules.append(q)
97
+
98
+ # Dialkyl amine, Alkyl hydroxylamine, Alkyl hydrazine
99
+ q = smarts('[N;D2;z1]([C,N,O;x1;z1])[C;x1;z1]')
100
+ rules.append(q)
101
+
102
+ # Trialkyl amine, Dialkyl-hydroxylamine, Dialkyl-hydrazine
103
+ q = smarts('[N;D3;z1]([C,N,O;x1;z1])([C;x1;z1])[C;x1;z1]')
104
+ rules.append(q)
105
+
106
+ # Pyridine. Imidazole. Triazole. :N:
107
+ q = smarts('[N;a;h0;D2]')
108
+ rules.append(q)
109
+ return rules
110
+
111
+
112
+ stripped_rules = Proxy(_stripped_rules)
113
+ rules = Proxy(_rules)
114
+
115
+
116
+ __all__ = ['stripped_rules', 'rules']
@@ -0,0 +1,107 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2022, 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 _sugar_group():
23
+ from ... import smarts
24
+
25
+ return smarts('[N,O;D1,D2:1][C;z1][C;z2]=[N,O:2]')
26
+
27
+
28
+ def _keto_rules():
29
+ from ... import smarts
30
+
31
+ rules = []
32
+ # first atom is H-acceptor
33
+ # second is direction
34
+
35
+ # C-C=[O,S,NH]
36
+ q = smarts('[N,O,S;D1;z2]=[C;D2,D3;x1;z2]')
37
+ rules.append(q)
38
+
39
+ # C-C=N-[C,N,O]
40
+ q = smarts('[N;D2;z2](=[C;D2,D3;x1;z2])[C,N,O]')
41
+ rules.append(q)
42
+
43
+ # [C,H]-N=N-C
44
+ q = smarts('[N;D1,D2;x1;z2]=N[C;x1]')
45
+ rules.append(q)
46
+
47
+ # [S,O,NR;H]-C=N
48
+ q = smarts('[N;z2]=C[N,O,S;h1]')
49
+ rules.append(q)
50
+
51
+ # [NH2]-C=N-R
52
+ q = smarts('[N;D2;z2]=C[N;D1]')
53
+ rules.append(q)
54
+
55
+ # S=C-[N,O;H]
56
+ q = smarts('[S;D1;z2]=C[N,O;h1,h2]')
57
+ rules.append(q)
58
+
59
+ # O=C-[N,S;H]
60
+ q = smarts('[O;D1;z2]=C[N,S;h1,h2]')
61
+ rules.append(q)
62
+ return rules
63
+
64
+
65
+ def _enol_rules():
66
+ from ... import smarts
67
+
68
+ rules = []
69
+ # first atom is H-donor
70
+ # second is direction
71
+
72
+ # C=C-[OH,SH,NH2]
73
+ q = smarts('[N,O,S;D1;z1][C;D2,D3;x1;z2]')
74
+ rules.append(q)
75
+
76
+ # C=C-[NH]-[C,N,O]
77
+ q = smarts('[N;D2;z1]([C;D2,D3;x1;z2])[C,N,O]')
78
+ rules.append(q)
79
+
80
+ # [C,H]-[NH]-N=C
81
+ q = smarts('[N;D1,D2;x1;z1][N;z2]=[C;x1]')
82
+ rules.append(q)
83
+
84
+ # [S,O,NR;H]-C=N
85
+ q = smarts('[N,O,S;h1;z1][C;z2]=N')
86
+ rules.append(q)
87
+
88
+ # [NH2]-C=N-R
89
+ q = smarts('[N;D1;z1][C;z2]=[N;D2]')
90
+ rules.append(q)
91
+
92
+ # S=C-[N,O;H]
93
+ q = smarts('[N,O;h1,h2;z1][C;z2]=[S;D1]')
94
+ rules.append(q)
95
+
96
+ # O=C-[N,S;H]
97
+ q = smarts('[N,S;h1,h2;z1][C;z2]=O')
98
+ rules.append(q)
99
+ return rules
100
+
101
+
102
+ keto_rules = Proxy(_keto_rules)
103
+ enol_rules = Proxy(_enol_rules)
104
+ sugar_group = Proxy(_sugar_group)
105
+
106
+
107
+ __all__ = ['keto_rules', 'enol_rules', 'sugar_group']
@@ -0,0 +1,214 @@
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 itertools import combinations, product
20
+ from typing import TYPE_CHECKING, Union, List
21
+ from ._acid import rules as acid_rules, stripped_rules as stripped_acid_rules
22
+ from ._base import rules as base_rules, stripped_rules as stripped_base_rules
23
+
24
+
25
+ if TYPE_CHECKING:
26
+ from chython import MoleculeContainer
27
+
28
+
29
+ class AcidBase:
30
+ __slots__ = ()
31
+
32
+ def neutralize(self: 'MoleculeContainer', *, keep_charge=True, logging=False,
33
+ _fix_stereo=True) -> Union[bool, List[int]]:
34
+ """
35
+ Convert organic salts to neutral form if possible. Only one possible form used for charge unbalanced structures.
36
+
37
+ :param keep_charge: do partial neutralization to keep total charge of molecule.
38
+ :param logging: return changed atoms list.
39
+ """
40
+ try:
41
+ mol, changed = next(self._neutralize(keep_charge))
42
+ except StopIteration:
43
+ if logging:
44
+ return []
45
+ return False
46
+
47
+ self._atoms = mol._atoms
48
+ self.flush_cache(keep_sssr=True, keep_components=True)
49
+ if _fix_stereo:
50
+ self.fix_stereo()
51
+ if logging:
52
+ return list(changed)
53
+ return True
54
+
55
+ def enumerate_charged_forms(self: 'MoleculeContainer', *, deep: int = 4, limit: int = 1000):
56
+ """
57
+ Enumerate protonated and deprotonated ions. Use on neutralized molecules.
58
+
59
+ :param deep: Maximum amount of added or removed protons.
60
+ :param limit: Maximum amount of generated structures.
61
+ """
62
+ if limit < 1:
63
+ raise ValueError('limit should be greater or equal 1')
64
+
65
+ donors = set()
66
+ acceptors = set()
67
+ for q in acid_rules:
68
+ for mapping in q.get_mapping(self, automorphism_filter=False):
69
+ donors.add(mapping[1])
70
+ for q in base_rules:
71
+ for mapping in q.get_mapping(self, automorphism_filter=False):
72
+ acceptors.add(mapping[1])
73
+
74
+ h_source = [None] * deep + list(donors)
75
+ h_drain = list(acceptors) + [None] * deep
76
+
77
+ seen = set()
78
+ seen_combo = set()
79
+ for r in range(min(len(acceptors), deep), 0, -1): # number of acceptors to protonate
80
+ for ac in combinations(acceptors, r): # acceptors atoms to protonate
81
+ uniq = set()
82
+ for dc in combinations(h_source, r): # H donors
83
+ if dc in uniq: # filter None-containing duplicates
84
+ continue
85
+ uniq.add(dc)
86
+ seen_combo.add((dc, ac))
87
+ mol = self.copy(keep_sssr=True, keep_components=True)
88
+ for n in ac:
89
+ a = mol._atoms[n]
90
+ a._implicit_hydrogens += 1
91
+ a._charge += 1
92
+ for n in dc:
93
+ if n is not None:
94
+ a = mol._atoms[n]
95
+ a._implicit_hydrogens -= 1
96
+ a._charge -= 1
97
+ if mol not in seen:
98
+ seen.add(mol)
99
+ yield mol
100
+ limit -= 1
101
+ if not limit:
102
+ return
103
+
104
+ for r in range(1, min(len(donors), deep) + 1):
105
+ for dc in combinations(donors, r):
106
+ uniq = set()
107
+ for ac in combinations(h_drain, r): # H acceptors
108
+ if ac in uniq:
109
+ continue
110
+ uniq.add(ac)
111
+ if (dc, ac) in seen_combo:
112
+ continue
113
+ mol = self.copy(keep_sssr=True, keep_components=True)
114
+ for n in ac:
115
+ if n is not None:
116
+ a = mol._atoms[n]
117
+ a._implicit_hydrogens += 1
118
+ a._charge += 1
119
+ for n in dc:
120
+ if n is not None:
121
+ a = mol._atoms[n]
122
+ a._implicit_hydrogens -= 1
123
+ a._charge -= 1
124
+ if mol not in seen:
125
+ seen.add(mol)
126
+ yield mol
127
+ limit -= 1
128
+ if not limit:
129
+ return
130
+
131
+ def _neutralize(self: 'MoleculeContainer', keep_charge=True):
132
+ donors = set()
133
+ acceptors = set()
134
+ for q in stripped_acid_rules:
135
+ for mapping in q.get_mapping(self, automorphism_filter=False):
136
+ donors.add(mapping[1])
137
+ for q in stripped_base_rules:
138
+ for mapping in q.get_mapping(self, automorphism_filter=False):
139
+ acceptors.add(mapping[1])
140
+
141
+ if keep_charge:
142
+ if not donors or not acceptors:
143
+ return # neutralization impossible
144
+ elif len(donors) > len(acceptors):
145
+ copy = self.copy(keep_sssr=True, keep_components=True)
146
+ for n in acceptors:
147
+ a = copy._atoms[n]
148
+ a._implicit_hydrogens += 1
149
+ a._charge += 1
150
+ for c in combinations(donors, len(acceptors)):
151
+ mol = copy.copy(keep_sssr=True, keep_components=True)
152
+ for n in c:
153
+ a = mol._atoms[n]
154
+ a._implicit_hydrogens -= 1
155
+ a._charge -= 1
156
+ yield mol, acceptors.union(c)
157
+ elif len(donors) < len(acceptors):
158
+ copy = self.copy(keep_sssr=True, keep_components=True)
159
+ for n in donors:
160
+ a = copy._atoms[n]
161
+ a._implicit_hydrogens -= 1
162
+ a._charge -= 1
163
+ for c in combinations(acceptors, len(donors)):
164
+ mol = copy.copy(keep_sssr=True, keep_components=True)
165
+ for n in c:
166
+ a = mol._atoms[n]
167
+ a._implicit_hydrogens += 1
168
+ a._charge += 1
169
+ yield mol, donors.union(c)
170
+ else: # balanced!
171
+ mol = self.copy(keep_sssr=True, keep_components=True)
172
+ for n in donors:
173
+ a = mol._atoms[n]
174
+ a._implicit_hydrogens -= 1
175
+ a._charge -= 1
176
+ for n in acceptors:
177
+ a = mol._atoms[n]
178
+ a._implicit_hydrogens += 1
179
+ a._charge += 1
180
+ yield mol, donors | acceptors
181
+ elif donors or acceptors:
182
+ mol = self.copy(keep_sssr=True, keep_components=True)
183
+ for n in donors:
184
+ a = mol._atoms[n]
185
+ a._implicit_hydrogens -= 1
186
+ a._charge -= 1
187
+ for n in acceptors:
188
+ a = mol._atoms[n]
189
+ a._implicit_hydrogens += 1
190
+ a._charge += 1
191
+ yield mol, donors | acceptors
192
+
193
+ def _enumerate_zwitter_tautomers(self: 'MoleculeContainer'):
194
+ donors = set()
195
+ acceptors = set()
196
+ for q in acid_rules:
197
+ for mapping in q.get_mapping(self, automorphism_filter=False):
198
+ donors.add(mapping[1])
199
+ for q in base_rules:
200
+ for mapping in q.get_mapping(self, automorphism_filter=False):
201
+ acceptors.add(mapping[1])
202
+
203
+ for d, a in product(donors, acceptors):
204
+ mol = self.copy(keep_sssr=True, keep_components=True)
205
+ d = mol._atoms[d]
206
+ a = mol._atoms[a]
207
+ d._implicit_hydrogens -= 1
208
+ a._implicit_hydrogens += 1
209
+ d._charge -= 1
210
+ a._charge += 1
211
+ yield mol
212
+
213
+
214
+ __all__ = ['AcidBase']