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.
- chython/__init__.py +31 -0
- chython/_functions.py +69 -0
- chython/algorithms/__init__.py +18 -0
- chython/algorithms/_isomorphism.cp312-win_amd64.pyd +0 -0
- chython/algorithms/_isomorphism.pyx +189 -0
- chython/algorithms/aromatics/__init__.py +27 -0
- chython/algorithms/aromatics/_rules.py +124 -0
- chython/algorithms/aromatics/kekule.py +530 -0
- chython/algorithms/aromatics/test/__init__.py +18 -0
- chython/algorithms/aromatics/test/test_kekule.py +160 -0
- chython/algorithms/aromatics/test/test_thiele.py +263 -0
- chython/algorithms/aromatics/thiele.py +226 -0
- chython/algorithms/calculate2d/__init__.py +23 -0
- chython/algorithms/calculate2d/clean2d.js +1 -0
- chython/algorithms/calculate2d/molecule.py +149 -0
- chython/algorithms/calculate2d/reaction.py +80 -0
- chython/algorithms/depict.py +523 -0
- chython/algorithms/fingerprints/__init__.py +48 -0
- chython/algorithms/fingerprints/linear.py +208 -0
- chython/algorithms/fingerprints/morgan.py +137 -0
- chython/algorithms/fingerprints/test/__init__.py +18 -0
- chython/algorithms/fingerprints/test/test_linear.py +127 -0
- chython/algorithms/fingerprints/test/test_morgan.py +79 -0
- chython/algorithms/isomorphism.py +667 -0
- chython/algorithms/mapping/__init__.py +28 -0
- chython/algorithms/mapping/_groups.py +94 -0
- chython/algorithms/mapping/_reactions.py +72 -0
- chython/algorithms/mapping/attention.py +216 -0
- chython/algorithms/mapping/fixmapper.py +84 -0
- chython/algorithms/mapping/groups.py +137 -0
- chython/algorithms/mcs.py +202 -0
- chython/algorithms/morgan.py +82 -0
- chython/algorithms/rings.py +551 -0
- chython/algorithms/smiles.py +556 -0
- chython/algorithms/standardize/__init__.py +30 -0
- chython/algorithms/standardize/_charged.py +115 -0
- chython/algorithms/standardize/_groups.py +926 -0
- chython/algorithms/standardize/_metal_organics.py +189 -0
- chython/algorithms/standardize/_reagents.py +44 -0
- chython/algorithms/standardize/_salts.py +68 -0
- chython/algorithms/standardize/molecule.py +479 -0
- chython/algorithms/standardize/reaction.py +429 -0
- chython/algorithms/standardize/resonance.py +196 -0
- chython/algorithms/standardize/salts.py +128 -0
- chython/algorithms/standardize/saturation.py +378 -0
- chython/algorithms/standardize/test/__init__.py +18 -0
- chython/algorithms/standardize/test/test_groups.py +118 -0
- chython/algorithms/stereo.py +1222 -0
- chython/algorithms/tautomers/__init__.py +171 -0
- chython/algorithms/tautomers/_acid.py +59 -0
- chython/algorithms/tautomers/_base.py +116 -0
- chython/algorithms/tautomers/_keto_enol.py +107 -0
- chython/algorithms/tautomers/acid_base.py +214 -0
- chython/algorithms/tautomers/heteroarenes.py +108 -0
- chython/algorithms/tautomers/keto_enol.py +148 -0
- chython/algorithms/tautomers/test/__init__.py +18 -0
- chython/algorithms/tautomers/test/test_tautomers.py +80 -0
- chython/algorithms/test/__init__.py +18 -0
- chython/algorithms/test/test_isomorphism.py +58 -0
- chython/algorithms/test/test_smiles.py +76 -0
- chython/algorithms/x3dom.py +355 -0
- chython/containers/__init__.py +43 -0
- chython/containers/_pack_v2.cp312-win_amd64.pyd +0 -0
- chython/containers/_pack_v2.pyx +271 -0
- chython/containers/_unpack_v0v2.cp312-win_amd64.pyd +0 -0
- chython/containers/_unpack_v0v2.pyx +300 -0
- chython/containers/bonds.py +255 -0
- chython/containers/cgr.py +115 -0
- chython/containers/graph.py +200 -0
- chython/containers/molecule.py +695 -0
- chython/containers/query.py +66 -0
- chython/containers/reaction.py +324 -0
- chython/exceptions.py +114 -0
- chython/files/MRVrw.py +523 -0
- chython/files/PDBrw.py +257 -0
- chython/files/RDFrw.py +298 -0
- chython/files/SDFrw.py +221 -0
- chython/files/__init__.py +29 -0
- chython/files/_convert.py +207 -0
- chython/files/_mapping.py +117 -0
- chython/files/_xyz.cp312-win_amd64.pyd +0 -0
- chython/files/_xyz.pyx +74 -0
- chython/files/daylight/__init__.py +24 -0
- chython/files/daylight/parser.py +157 -0
- chython/files/daylight/smarts.py +107 -0
- chython/files/daylight/smiles.py +245 -0
- chython/files/daylight/test/__init__.py +18 -0
- chython/files/daylight/test/test_daylight_smarts.py +289 -0
- chython/files/daylight/test/test_daylight_smiles.py +101 -0
- chython/files/daylight/test/test_parser.py +118 -0
- chython/files/daylight/test/test_tokenize.py +77 -0
- chython/files/daylight/tokenize.py +376 -0
- chython/files/libinchi/__init__.py +22 -0
- chython/files/libinchi/libinchi.dll +0 -0
- chython/files/libinchi/wrapper.py +555 -0
- chython/files/mdl/__init__.py +25 -0
- chython/files/mdl/emol.py +209 -0
- chython/files/mdl/erxn.py +67 -0
- chython/files/mdl/mol.py +144 -0
- chython/files/mdl/read.py +225 -0
- chython/files/mdl/rxn.py +67 -0
- chython/files/mdl/stereo.py +63 -0
- chython/files/mdl/write.py +165 -0
- chython/files/xyz.py +78 -0
- chython/periodictable/__init__.py +69 -0
- chython/periodictable/base/__init__.py +25 -0
- chython/periodictable/base/dynamic.py +149 -0
- chython/periodictable/base/element.py +452 -0
- chython/periodictable/base/groups.py +90 -0
- chython/periodictable/base/periods.py +46 -0
- chython/periodictable/base/query.py +473 -0
- chython/periodictable/base/vector.py +119 -0
- chython/periodictable/groupI.py +252 -0
- chython/periodictable/groupII.py +223 -0
- chython/periodictable/groupIII.py +1154 -0
- chython/periodictable/groupIV.py +210 -0
- chython/periodictable/groupIX.py +196 -0
- chython/periodictable/groupV.py +192 -0
- chython/periodictable/groupVI.py +183 -0
- chython/periodictable/groupVII.py +162 -0
- chython/periodictable/groupVIII.py +161 -0
- chython/periodictable/groupX.py +166 -0
- chython/periodictable/groupXI.py +164 -0
- chython/periodictable/groupXII.py +158 -0
- chython/periodictable/groupXIII.py +239 -0
- chython/periodictable/groupXIV.py +262 -0
- chython/periodictable/groupXV.py +276 -0
- chython/periodictable/groupXVI.py +457 -0
- chython/periodictable/groupXVII.py +324 -0
- chython/periodictable/groupXVIII.py +260 -0
- chython/reactor/__init__.py +24 -0
- chython/reactor/base.py +212 -0
- chython/reactor/deprotection.py +565 -0
- chython/reactor/groups.py +128 -0
- chython/reactor/reactions/__init__.py +157 -0
- chython/reactor/reactions/_amidation.py +58 -0
- chython/reactor/reactions/_amine_isocyanate.py +52 -0
- chython/reactor/reactions/_buchwald_hartwig.py +50 -0
- chython/reactor/reactions/_esterification.py +49 -0
- chython/reactor/reactions/_macmillan.py +43 -0
- chython/reactor/reactions/_reductive_amination.py +50 -0
- chython/reactor/reactions/_sonogashira.py +49 -0
- chython/reactor/reactions/_sulfonamidation.py +52 -0
- chython/reactor/reactions/_suzuki_miyaura.py +88 -0
- chython/reactor/reactions/ufe.py +99 -0
- chython/reactor/reactor.py +170 -0
- chython/reactor/retro/__init__.py +77 -0
- chython/reactor/retro/_amidation.py +39 -0
- chython/reactor/retro/_aryl_amination.py +32 -0
- chython/reactor/retro/_mitsunobu.py +49 -0
- chython/reactor/retro/_sonogashira.py +49 -0
- chython/reactor/retro/_suzuki_miyaura.py +54 -0
- chython/reactor/scaffold.py +122 -0
- chython/reactor/test/__init__.py +18 -0
- chython/reactor/test/test_deprotection.py +57 -0
- chython/reactor/test/test_reactor.py +40 -0
- chython/reactor/test/test_scaffold.py +57 -0
- chython/reactor/test/test_transformer.py +40 -0
- chython/reactor/transformer.py +62 -0
- chython/utils/__init__.py +57 -0
- chython/utils/free_wilson.py +114 -0
- chython/utils/grid.py +130 -0
- chython/utils/rdkit.py +165 -0
- chython/utils/retro.py +139 -0
- chython/utils/svg.py +57 -0
- chython/utils/test/__init__.py +18 -0
- chython/utils/test/test_rdkit.py +71 -0
- chython-2.4.dist-info/LICENSE +165 -0
- chython-2.4.dist-info/METADATA +101 -0
- chython-2.4.dist-info/RECORD +171 -0
- 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']
|