chython 2.19__cp313-cp313-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 (175) hide show
  1. chython/__init__.py +35 -0
  2. chython/_functions.py +95 -0
  3. chython/_java.py +57 -0
  4. chython/algorithms/__init__.py +18 -0
  5. chython/algorithms/_isomorphism.cp313-win_amd64.pyd +0 -0
  6. chython/algorithms/_isomorphism.pyx +189 -0
  7. chython/algorithms/_rings.cp313-win_amd64.pyd +0 -0
  8. chython/algorithms/_rings.pyx +478 -0
  9. chython/algorithms/aromatics/__init__.py +27 -0
  10. chython/algorithms/aromatics/_rules.py +124 -0
  11. chython/algorithms/aromatics/kekule.py +532 -0
  12. chython/algorithms/aromatics/test/__init__.py +18 -0
  13. chython/algorithms/aromatics/test/test_kekule.py +160 -0
  14. chython/algorithms/aromatics/test/test_thiele.py +263 -0
  15. chython/algorithms/aromatics/thiele.py +222 -0
  16. chython/algorithms/calculate2d/__init__.py +23 -0
  17. chython/algorithms/calculate2d/clean2d.js +1 -0
  18. chython/algorithms/calculate2d/molecule.py +191 -0
  19. chython/algorithms/calculate2d/reaction.py +75 -0
  20. chython/algorithms/conformers.py +106 -0
  21. chython/algorithms/depict.py +596 -0
  22. chython/algorithms/fingerprints/__init__.py +43 -0
  23. chython/algorithms/fingerprints/linear.py +202 -0
  24. chython/algorithms/fingerprints/morgan.py +132 -0
  25. chython/algorithms/fingerprints/test/__init__.py +18 -0
  26. chython/algorithms/fingerprints/test/test_linear.py +127 -0
  27. chython/algorithms/fingerprints/test/test_morgan.py +79 -0
  28. chython/algorithms/groups/__init__.py +212 -0
  29. chython/algorithms/groups/_functional.py +290 -0
  30. chython/algorithms/groups/_oxidations.py +70 -0
  31. chython/algorithms/groups/_protective.py +964 -0
  32. chython/algorithms/groups/_reactions.py +809 -0
  33. chython/algorithms/groups/_reductions.py +88 -0
  34. chython/algorithms/groups/_transformations.py +278 -0
  35. chython/algorithms/groups/test/__init__.py +18 -0
  36. chython/algorithms/groups/test/test_deprotection.py +108 -0
  37. chython/algorithms/groups/test/test_reactions.py +377 -0
  38. chython/algorithms/isomorphism.py +664 -0
  39. chython/algorithms/mapping/__init__.py +188 -0
  40. chython/algorithms/mapping/_groups.py +94 -0
  41. chython/algorithms/mapping/_reactions.py +72 -0
  42. chython/algorithms/mapping/attention.py +266 -0
  43. chython/algorithms/mapping/reconstruct.py +346 -0
  44. chython/algorithms/mcs.py +201 -0
  45. chython/algorithms/morgan.py +77 -0
  46. chython/algorithms/rings.py +178 -0
  47. chython/algorithms/smiles.py +597 -0
  48. chython/algorithms/standardize/__init__.py +30 -0
  49. chython/algorithms/standardize/_groups.py +958 -0
  50. chython/algorithms/standardize/_isomers.py +330 -0
  51. chython/algorithms/standardize/_metal_organics.py +189 -0
  52. chython/algorithms/standardize/_reagents.py +44 -0
  53. chython/algorithms/standardize/_salts.py +68 -0
  54. chython/algorithms/standardize/molecule.py +475 -0
  55. chython/algorithms/standardize/reaction.py +384 -0
  56. chython/algorithms/standardize/resonance.py +191 -0
  57. chython/algorithms/standardize/salts.py +128 -0
  58. chython/algorithms/standardize/saturation.py +373 -0
  59. chython/algorithms/standardize/test/__init__.py +18 -0
  60. chython/algorithms/standardize/test/test_canonicalize.py +66 -0
  61. chython/algorithms/standardize/test/test_groups.py +146 -0
  62. chython/algorithms/standardize/test/test_isomers.py +198 -0
  63. chython/algorithms/stereo.py +1215 -0
  64. chython/algorithms/tautomers/__init__.py +167 -0
  65. chython/algorithms/tautomers/_acid.py +59 -0
  66. chython/algorithms/tautomers/_base.py +116 -0
  67. chython/algorithms/tautomers/_keto_enol.py +107 -0
  68. chython/algorithms/tautomers/acid_base.py +208 -0
  69. chython/algorithms/tautomers/heteroarenes.py +103 -0
  70. chython/algorithms/tautomers/keto_enol.py +143 -0
  71. chython/algorithms/tautomers/test/__init__.py +18 -0
  72. chython/algorithms/tautomers/test/test_tautomers.py +80 -0
  73. chython/algorithms/test/__init__.py +18 -0
  74. chython/algorithms/test/test_isomorphism.py +58 -0
  75. chython/algorithms/test/test_smiles.py +76 -0
  76. chython/algorithms/x3dom.py +350 -0
  77. chython/containers/__init__.py +48 -0
  78. chython/containers/_pack_v2.cp313-win_amd64.pyd +0 -0
  79. chython/containers/_pack_v2.pyx +271 -0
  80. chython/containers/_unpack_v0v2.cp313-win_amd64.pyd +0 -0
  81. chython/containers/_unpack_v0v2.pyx +302 -0
  82. chython/containers/bonds.py +255 -0
  83. chython/containers/cgr.py +115 -0
  84. chython/containers/chimera.py +63 -0
  85. chython/containers/graph.py +199 -0
  86. chython/containers/molecule.py +817 -0
  87. chython/containers/query.py +71 -0
  88. chython/containers/rdkit.py +183 -0
  89. chython/containers/reaction.py +324 -0
  90. chython/containers/test/__init__.py +18 -0
  91. chython/containers/test/test_molecule.py +117 -0
  92. chython/containers/test/test_pack.py +60 -0
  93. chython/containers/test/test_rdkit.py +70 -0
  94. chython/containers/test/test_reaction.py +40 -0
  95. chython/exceptions.py +114 -0
  96. chython/files/MRVrw.py +532 -0
  97. chython/files/PDBrw.py +255 -0
  98. chython/files/RDFrw.py +309 -0
  99. chython/files/SDFrw.py +221 -0
  100. chython/files/__init__.py +30 -0
  101. chython/files/_convert.py +207 -0
  102. chython/files/_mapping.py +117 -0
  103. chython/files/_xyz.cp313-win_amd64.pyd +0 -0
  104. chython/files/_xyz.pyx +74 -0
  105. chython/files/daylight/__init__.py +24 -0
  106. chython/files/daylight/parser.py +157 -0
  107. chython/files/daylight/smarts.py +107 -0
  108. chython/files/daylight/smiles.py +260 -0
  109. chython/files/daylight/test/__init__.py +18 -0
  110. chython/files/daylight/test/test_daylight_smarts.py +289 -0
  111. chython/files/daylight/test/test_daylight_smiles.py +101 -0
  112. chython/files/daylight/test/test_parser.py +118 -0
  113. chython/files/daylight/test/test_tokenize.py +77 -0
  114. chython/files/daylight/tokenize.py +376 -0
  115. chython/files/libinchi/__init__.py +22 -0
  116. chython/files/libinchi/libinchi.dll +0 -0
  117. chython/files/libinchi/wrapper.py +554 -0
  118. chython/files/mdl/__init__.py +25 -0
  119. chython/files/mdl/emol.py +251 -0
  120. chython/files/mdl/erxn.py +67 -0
  121. chython/files/mdl/mol.py +186 -0
  122. chython/files/mdl/read.py +223 -0
  123. chython/files/mdl/rxn.py +67 -0
  124. chython/files/mdl/stereo.py +63 -0
  125. chython/files/mdl/write.py +201 -0
  126. chython/files/opsin.py +35 -0
  127. chython/files/test/__init__.py +0 -0
  128. chython/files/test/test_inchi.py +27 -0
  129. chython/files/test/test_mrv.py +47 -0
  130. chython/files/test/test_sdf_rdf.py +64 -0
  131. chython/files/test/test_xyz.py +20 -0
  132. chython/files/xyz.py +78 -0
  133. chython/periodictable/__init__.py +69 -0
  134. chython/periodictable/base/__init__.py +25 -0
  135. chython/periodictable/base/dynamic.py +149 -0
  136. chython/periodictable/base/element.py +451 -0
  137. chython/periodictable/base/groups.py +90 -0
  138. chython/periodictable/base/periods.py +46 -0
  139. chython/periodictable/base/query.py +473 -0
  140. chython/periodictable/base/vector.py +119 -0
  141. chython/periodictable/groupI.py +252 -0
  142. chython/periodictable/groupII.py +223 -0
  143. chython/periodictable/groupIII.py +1154 -0
  144. chython/periodictable/groupIV.py +210 -0
  145. chython/periodictable/groupIX.py +196 -0
  146. chython/periodictable/groupV.py +192 -0
  147. chython/periodictable/groupVI.py +183 -0
  148. chython/periodictable/groupVII.py +162 -0
  149. chython/periodictable/groupVIII.py +161 -0
  150. chython/periodictable/groupX.py +166 -0
  151. chython/periodictable/groupXI.py +164 -0
  152. chython/periodictable/groupXII.py +158 -0
  153. chython/periodictable/groupXIII.py +239 -0
  154. chython/periodictable/groupXIV.py +262 -0
  155. chython/periodictable/groupXV.py +276 -0
  156. chython/periodictable/groupXVI.py +457 -0
  157. chython/periodictable/groupXVII.py +324 -0
  158. chython/periodictable/groupXVIII.py +260 -0
  159. chython/periodictable/test/__init__.py +0 -0
  160. chython/periodictable/test/test_element.py +100 -0
  161. chython/reactor/__init__.py +24 -0
  162. chython/reactor/base.py +222 -0
  163. chython/reactor/reactor.py +177 -0
  164. chython/reactor/test/__init__.py +18 -0
  165. chython/reactor/test/test_reactor.py +40 -0
  166. chython/reactor/test/test_transformer.py +78 -0
  167. chython/reactor/transformer.py +72 -0
  168. chython/utils/__init__.py +50 -0
  169. chython/utils/free_wilson.py +114 -0
  170. chython/utils/grid.py +130 -0
  171. chython/utils/retro.py +139 -0
  172. chython-2.19.dist-info/METADATA +105 -0
  173. chython-2.19.dist-info/RECORD +175 -0
  174. chython-2.19.dist-info/WHEEL +4 -0
  175. chython-2.19.dist-info/licenses/LICENSE +165 -0
@@ -0,0 +1,167 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020-2026 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 collections.abc import Iterator
22
+ from .acid_base import *
23
+ from .heteroarenes import *
24
+ from .keto_enol import *
25
+
26
+
27
+ class Tautomers(AcidBase, HeteroArenes, KetoEnol):
28
+ """
29
+ Oxides and sulphides ignored.
30
+ """
31
+ __slots__ = ()
32
+
33
+ def enumerate_tautomers(self, *, prepare_molecules=True, zwitter=True,
34
+ partial=False, increase_aromaticity=True, keep_sugars=True, heteroarenes=True,
35
+ keto_enol=True, limit: int = 1000) -> Iterator['MoleculeContainer']:
36
+ """
37
+ Enumerate all possible tautomeric forms of molecule.
38
+
39
+ :param prepare_molecules: Standardize structures for correct processing
40
+ :param zwitter: Do zwitter-ions enumeration
41
+ :param partial: Allow OC=CC=C>>O=CCC=C or O=CC=CC>>OC=C=CC
42
+ :param increase_aromaticity: prevent aromatic ring destruction
43
+ :param keep_sugars: prevent carbonyl moving in sugars
44
+ :param heteroarenes: enumerate heteroarenes
45
+ :param keto_enol: enumerate keto-enols
46
+ :param limit: Maximum attempts count
47
+ """
48
+ if limit < 1:
49
+ raise ValueError('limit should be greater or equal 1')
50
+ counter = 0
51
+
52
+ copy = self.copy(keep_sssr=True, keep_components=True)
53
+ if prepare_molecules: # transform to kekule form without hydrogens
54
+ copy.kekule()
55
+ copy.implicify_hydrogens()
56
+
57
+ # transform to thiele to prevent duplicates and dearomatization
58
+ thiele = copy.copy(keep_sssr=True, keep_components=True)
59
+ thiele.thiele(fix_tautomers=False)
60
+ yield thiele # return original structure as first tautomer
61
+
62
+ seen = {thiele: None} # value is parent molecule - required for preventing migrations in sugars.
63
+
64
+ # first try to neutralize
65
+ if copy.neutralize(): # found neutral form
66
+ thiele = copy.copy(keep_sssr=True, keep_components=True)
67
+ thiele.thiele(fix_tautomers=False)
68
+ yield thiele
69
+ counter += 1
70
+ seen[thiele] = None
71
+
72
+ # lets iteratively do keto-enol transformations.
73
+ rings_count = len(thiele.aromatic_rings) # increase rings strategy.
74
+ if keto_enol:
75
+ queue = deque([(copy, thiele)])
76
+ else:
77
+ queue = None
78
+ new_queue = [thiele] # new_queue - molecules suitable for hetero-arenes enumeration.
79
+ # store aromatic form to seen. kekule forms not suitable for duplicate checking.
80
+
81
+ while queue:
82
+ current, thiele_current = queue.popleft()
83
+ for mol, ket in current._enumerate_keto_enol_tautomers(partial):
84
+ thiele = mol.copy(keep_sssr=True, keep_components=True)
85
+ thiele.thiele(fix_tautomers=False)
86
+
87
+ if thiele not in seen:
88
+ seen[thiele] = current
89
+ rc = len(thiele.aromatic_rings)
90
+ if increase_aromaticity:
91
+ if rc < rings_count: # skip aromatic rings destruction
92
+ continue
93
+ elif rc > rings_count: # higher aromaticity found. flush old queues.
94
+ rings_count = rc
95
+ queue = deque([(mol, thiele)])
96
+ new_queue = [thiele]
97
+ copy = mol # new entry point.
98
+ yield thiele
99
+ break
100
+ if keep_sugars and current is not copy and ket:
101
+ # prevent carbonyl migration in sugars. skip entry point.
102
+ # search alpha hydroxy ketone inversion
103
+ before = seen[thiele_current]._sugar_groups
104
+ if any((k, e) in before for e, k in mol._sugar_groups):
105
+ continue
106
+
107
+ queue.append((mol, thiele))
108
+ new_queue.append(thiele)
109
+ yield thiele
110
+ counter += 1
111
+ if counter == limit:
112
+ return
113
+
114
+ # heteroarenes tautomers enumeration
115
+ if heteroarenes:
116
+ queue = deque(new_queue)
117
+ while queue:
118
+ current = queue.popleft()
119
+ for mol in current._enumerate_hetero_arene_tautomers():
120
+ if mol not in seen:
121
+ seen[mol] = None
122
+ queue.append(mol)
123
+ new_queue.append(mol) # new hetero-arenes also should be included to this list.
124
+ yield mol
125
+ counter += 1
126
+ if counter == limit:
127
+ return
128
+
129
+ # zwitter-ions enumeration
130
+ if zwitter:
131
+ queue = deque(new_queue)
132
+ while queue:
133
+ current = queue.popleft()
134
+ for mol in current._enumerate_zwitter_tautomers():
135
+ if mol not in seen:
136
+ seen[mol] = None
137
+ queue.append(mol)
138
+ yield mol
139
+ counter += 1
140
+ if counter == limit:
141
+ return
142
+
143
+ def enumerate_charged_tautomers(self, *, prepare_molecules=True, partial=False,
144
+ increase_aromaticity=True, keep_sugars=True, heteroarenes=True,
145
+ keto_enol=True, deep: int = 4, limit: int = 1000) -> Iterator['MoleculeContainer']:
146
+ """
147
+ Enumerate tautomers and protonated-deprotonated forms.
148
+ Better to use on neutralized non-ionic molecules.
149
+
150
+ See `enumerate_tautomers` and `enumerate_charged_forms` params description.
151
+ """
152
+ count = 0
153
+ for t in self.enumerate_tautomers(prepare_molecules=prepare_molecules, zwitter=False, partial=partial,
154
+ increase_aromaticity=increase_aromaticity, keep_sugars=keep_sugars,
155
+ heteroarenes=heteroarenes, keto_enol=keto_enol, limit=limit):
156
+ yield t
157
+ count += 1
158
+ if count == limit:
159
+ return
160
+ for c in t.enumerate_charged_forms(deep=deep, limit=limit):
161
+ yield c
162
+ count += 1
163
+ if count == limit:
164
+ return
165
+
166
+
167
+ __all__ = ['Tautomers']
@@ -0,0 +1,59 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2021-2026 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-2026 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-2026 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,208 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2022-2026 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.abc import Iterator
20
+ from itertools import combinations, product
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
+ class AcidBase:
26
+ __slots__ = ()
27
+
28
+ def neutralize(self, *, keep_charge=True, logging=False) -> bool | list:
29
+ """
30
+ Convert organic salts to neutral form if possible. Only one possible form used for charge unbalanced structures.
31
+
32
+ :param keep_charge: do partial neutralization to keep total charge of molecule.
33
+ :param logging: return changed atoms list.
34
+ """
35
+ try:
36
+ mol, changed = next(self._neutralize(keep_charge))
37
+ except StopIteration:
38
+ if logging:
39
+ return []
40
+ return False
41
+
42
+ self._atoms = mol._atoms
43
+ self.flush_cache(keep_sssr=True, keep_components=True, keep_special_connectivity=True)
44
+ self.fix_stereo()
45
+ if logging:
46
+ return list(changed)
47
+ return True
48
+
49
+ def enumerate_charged_forms(self, *, deep: int = 4, limit: int = 1000) -> Iterator['MoleculeContainer']:
50
+ """
51
+ Enumerate protonated and deprotonated ions. Use on neutralized molecules.
52
+
53
+ :param deep: Maximum amount of added or removed protons.
54
+ :param limit: Maximum amount of generated structures.
55
+ """
56
+ if limit < 1:
57
+ raise ValueError('limit should be greater or equal 1')
58
+
59
+ donors = set()
60
+ acceptors = set()
61
+ for q in acid_rules:
62
+ for mapping in q.get_mapping(self, automorphism_filter=False):
63
+ donors.add(mapping[1])
64
+ for q in base_rules:
65
+ for mapping in q.get_mapping(self, automorphism_filter=False):
66
+ acceptors.add(mapping[1])
67
+
68
+ h_source = [None] * deep + list(donors)
69
+ h_drain = list(acceptors) + [None] * deep
70
+
71
+ seen = set()
72
+ seen_combo = set()
73
+ for r in range(min(len(acceptors), deep), 0, -1): # number of acceptors to protonate
74
+ for ac in combinations(acceptors, r): # acceptors atoms to protonate
75
+ uniq = set()
76
+ for dc in combinations(h_source, r): # H donors
77
+ if dc in uniq: # filter None-containing duplicates
78
+ continue
79
+ uniq.add(dc)
80
+ seen_combo.add((dc, ac))
81
+ mol = self.copy(keep_sssr=True, keep_components=True)
82
+ for n in ac:
83
+ a = mol._atoms[n]
84
+ a._implicit_hydrogens += 1
85
+ a._charge += 1
86
+ for n in dc:
87
+ if n is not None:
88
+ a = mol._atoms[n]
89
+ a._implicit_hydrogens -= 1
90
+ a._charge -= 1
91
+ if mol not in seen:
92
+ seen.add(mol)
93
+ yield mol
94
+ limit -= 1
95
+ if not limit:
96
+ return
97
+
98
+ for r in range(1, min(len(donors), deep) + 1):
99
+ for dc in combinations(donors, r):
100
+ uniq = set()
101
+ for ac in combinations(h_drain, r): # H acceptors
102
+ if ac in uniq:
103
+ continue
104
+ uniq.add(ac)
105
+ if (dc, ac) in seen_combo:
106
+ continue
107
+ mol = self.copy(keep_sssr=True, keep_components=True)
108
+ for n in ac:
109
+ if n is not None:
110
+ a = mol._atoms[n]
111
+ a._implicit_hydrogens += 1
112
+ a._charge += 1
113
+ for n in dc:
114
+ if n is not None:
115
+ a = mol._atoms[n]
116
+ a._implicit_hydrogens -= 1
117
+ a._charge -= 1
118
+ if mol not in seen:
119
+ seen.add(mol)
120
+ yield mol
121
+ limit -= 1
122
+ if not limit:
123
+ return
124
+
125
+ def _neutralize(self, keep_charge=True):
126
+ donors = set()
127
+ acceptors = set()
128
+ for q in stripped_acid_rules:
129
+ for mapping in q.get_mapping(self, automorphism_filter=False):
130
+ donors.add(mapping[1])
131
+ for q in stripped_base_rules:
132
+ for mapping in q.get_mapping(self, automorphism_filter=False):
133
+ acceptors.add(mapping[1])
134
+
135
+ if keep_charge:
136
+ if not donors or not acceptors:
137
+ return # neutralization impossible
138
+ elif len(donors) > len(acceptors):
139
+ copy = self.copy(keep_sssr=True, keep_components=True)
140
+ for n in acceptors:
141
+ a = copy._atoms[n]
142
+ a._implicit_hydrogens += 1
143
+ a._charge += 1
144
+ for c in combinations(donors, len(acceptors)):
145
+ mol = copy.copy(keep_sssr=True, keep_components=True)
146
+ for n in c:
147
+ a = mol._atoms[n]
148
+ a._implicit_hydrogens -= 1
149
+ a._charge -= 1
150
+ yield mol, acceptors.union(c)
151
+ elif len(donors) < len(acceptors):
152
+ copy = self.copy(keep_sssr=True, keep_components=True)
153
+ for n in donors:
154
+ a = copy._atoms[n]
155
+ a._implicit_hydrogens -= 1
156
+ a._charge -= 1
157
+ for c in combinations(acceptors, len(donors)):
158
+ mol = copy.copy(keep_sssr=True, keep_components=True)
159
+ for n in c:
160
+ a = mol._atoms[n]
161
+ a._implicit_hydrogens += 1
162
+ a._charge += 1
163
+ yield mol, donors.union(c)
164
+ else: # balanced!
165
+ mol = self.copy(keep_sssr=True, keep_components=True)
166
+ for n in donors:
167
+ a = mol._atoms[n]
168
+ a._implicit_hydrogens -= 1
169
+ a._charge -= 1
170
+ for n in acceptors:
171
+ a = mol._atoms[n]
172
+ a._implicit_hydrogens += 1
173
+ a._charge += 1
174
+ yield mol, donors | acceptors
175
+ elif donors or acceptors:
176
+ mol = self.copy(keep_sssr=True, keep_components=True)
177
+ for n in donors:
178
+ a = mol._atoms[n]
179
+ a._implicit_hydrogens -= 1
180
+ a._charge -= 1
181
+ for n in acceptors:
182
+ a = mol._atoms[n]
183
+ a._implicit_hydrogens += 1
184
+ a._charge += 1
185
+ yield mol, donors | acceptors
186
+
187
+ def _enumerate_zwitter_tautomers(self):
188
+ donors = set()
189
+ acceptors = set()
190
+ for q in acid_rules:
191
+ for mapping in q.get_mapping(self, automorphism_filter=False):
192
+ donors.add(mapping[1])
193
+ for q in base_rules:
194
+ for mapping in q.get_mapping(self, automorphism_filter=False):
195
+ acceptors.add(mapping[1])
196
+
197
+ for d, a in product(donors, acceptors):
198
+ mol = self.copy(keep_sssr=True, keep_components=True)
199
+ d = mol._atoms[d]
200
+ a = mol._atoms[a]
201
+ d._implicit_hydrogens -= 1
202
+ a._implicit_hydrogens += 1
203
+ d._charge -= 1
204
+ a._charge += 1
205
+ yield mol
206
+
207
+
208
+ __all__ = ['AcidBase']