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
chython/reactor/base.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2014-2025 Ramil Nugmanov <nougmanoff@protonmail.com>
|
|
4
|
+
# Copyright 2019 Adelia Fatykhova <adelik21979@gmail.com>
|
|
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 typing import Union
|
|
21
|
+
from ..containers import MoleculeContainer, QueryContainer
|
|
22
|
+
from ..containers.bonds import Bond
|
|
23
|
+
from ..periodictable import Element, AnyElement, QueryElement
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseReactor:
|
|
27
|
+
def __init__(self, pattern, replacement, delete_atoms, fix_rings, fix_tautomers):
|
|
28
|
+
if isinstance(replacement, QueryContainer):
|
|
29
|
+
for n, a in replacement.atoms():
|
|
30
|
+
if not isinstance(a, (AnyElement, QueryElement)):
|
|
31
|
+
raise TypeError('Unsupported query atom type')
|
|
32
|
+
elif len(a.implicit_hydrogens) > 1:
|
|
33
|
+
raise ValueError('Query element in patch has more than one implicit hydrogen clause')
|
|
34
|
+
for *_, b in replacement.bonds():
|
|
35
|
+
if len(b.order) > 1:
|
|
36
|
+
raise ValueError('Variable bond in replacement')
|
|
37
|
+
|
|
38
|
+
self._to_delete = {n for n, a in pattern.atoms() if not a.masked} - set(replacement) if delete_atoms else ()
|
|
39
|
+
self._replacement = replacement
|
|
40
|
+
self._fix_rings = fix_rings
|
|
41
|
+
self._fix_tautomers = fix_tautomers
|
|
42
|
+
|
|
43
|
+
def _get_deleted(self, structure, mapping):
|
|
44
|
+
if not self._to_delete:
|
|
45
|
+
return set()
|
|
46
|
+
|
|
47
|
+
bonds = structure._bonds
|
|
48
|
+
to_delete = {mapping[x] for x in self._to_delete}
|
|
49
|
+
# if deleted atoms have another path to remain fragment, the path is preserved
|
|
50
|
+
remain = set(mapping.values()).difference(to_delete)
|
|
51
|
+
delete, global_seen = set(), set()
|
|
52
|
+
for x in to_delete:
|
|
53
|
+
for n in bonds[x]:
|
|
54
|
+
if n in global_seen or n in remain:
|
|
55
|
+
continue
|
|
56
|
+
seen = {n}
|
|
57
|
+
global_seen.add(n)
|
|
58
|
+
stack = [x for x in bonds[n] if x not in global_seen]
|
|
59
|
+
while stack:
|
|
60
|
+
current = stack.pop()
|
|
61
|
+
if current in remain:
|
|
62
|
+
break
|
|
63
|
+
if current in to_delete:
|
|
64
|
+
continue
|
|
65
|
+
seen.add(current)
|
|
66
|
+
global_seen.add(current)
|
|
67
|
+
stack.extend([x for x in bonds[current] if x not in global_seen])
|
|
68
|
+
else:
|
|
69
|
+
delete.update(seen)
|
|
70
|
+
|
|
71
|
+
to_delete.update(delete)
|
|
72
|
+
return to_delete
|
|
73
|
+
|
|
74
|
+
def _patcher(self, structure: MoleculeContainer, mapping):
|
|
75
|
+
satoms = structure._atoms
|
|
76
|
+
sbonds = structure._bonds
|
|
77
|
+
|
|
78
|
+
to_delete = self._get_deleted(structure, mapping)
|
|
79
|
+
new = structure.__class__()
|
|
80
|
+
natoms = new._atoms
|
|
81
|
+
nbonds = new._bonds
|
|
82
|
+
max_atom = max(satoms)
|
|
83
|
+
stereo_atoms = []
|
|
84
|
+
stereo_bonds = []
|
|
85
|
+
|
|
86
|
+
# let's preserve connectivity order from replacement to keep stereo signs as is.
|
|
87
|
+
# stereo labels from original structure will be recalculated after full molecule construction.
|
|
88
|
+
for n, ra in self._replacement.atoms():
|
|
89
|
+
if isinstance(ra, AnyElement):
|
|
90
|
+
if m := mapping.get(n):
|
|
91
|
+
# keep matched atom type and isotope
|
|
92
|
+
sa = satoms[m]
|
|
93
|
+
a = sa.copy()
|
|
94
|
+
a.charge = ra.charge
|
|
95
|
+
a.is_radical = ra.is_radical
|
|
96
|
+
if ra.stereo is not None: # override stereo
|
|
97
|
+
a._stereo = ra.stereo
|
|
98
|
+
elif sa.stereo is not None: # keep original stereo
|
|
99
|
+
stereo_atoms.append(m) # mark for stereo fix
|
|
100
|
+
else:
|
|
101
|
+
raise ValueError("AnyElement doesn't match to pattern")
|
|
102
|
+
else: # QueryElement or Element
|
|
103
|
+
ra: Union[QueryElement, Element] # typehint
|
|
104
|
+
e = Element.from_atomic_number(ra.atomic_number)
|
|
105
|
+
a = e(ra.isotope, charge=ra.charge, is_radical=ra.is_radical)
|
|
106
|
+
if not (m := mapping.get(n)): # new atom
|
|
107
|
+
m = max_atom + 1
|
|
108
|
+
mapping[n] = max_atom = m
|
|
109
|
+
a._stereo = ra.stereo # keep stereo from patch for new atoms
|
|
110
|
+
if isinstance(ra, Element):
|
|
111
|
+
a._implicit_hydrogens = ra.implicit_hydrogens # keep H count from patch
|
|
112
|
+
a.xy = ra.xy # keep coordinates from patch
|
|
113
|
+
elif ra.implicit_hydrogens: # keep H count from patch
|
|
114
|
+
a._implicit_hydrogens = ra.implicit_hydrogens[0]
|
|
115
|
+
else: # existing atoms
|
|
116
|
+
sa = satoms[m]
|
|
117
|
+
a.xy = sa.xy # preserve existing coordinates
|
|
118
|
+
if ra.stereo is not None:
|
|
119
|
+
a._stereo = ra.stereo
|
|
120
|
+
elif sa.stereo is not None: # keep original stereo
|
|
121
|
+
stereo_atoms.append(m)
|
|
122
|
+
natoms[m] = a
|
|
123
|
+
nbonds[m] = {}
|
|
124
|
+
|
|
125
|
+
# preserve connectivity order
|
|
126
|
+
for n, bs in self._replacement._bonds.items():
|
|
127
|
+
n = mapping[n]
|
|
128
|
+
for m, rb in bs.items():
|
|
129
|
+
m = mapping[m]
|
|
130
|
+
if n in nbonds[m]: # back-link
|
|
131
|
+
nbonds[n][m] = nbonds[m][n]
|
|
132
|
+
else:
|
|
133
|
+
nbonds[n][m] = b = Bond(int(rb))
|
|
134
|
+
if rb.stereo is not None: # override stereo
|
|
135
|
+
b._stereo = rb.stereo
|
|
136
|
+
# check bond exists in source and has stereo label and the same order
|
|
137
|
+
elif (sbn := sbonds.get(n)) is None or (sb := sbn.get(m)) is None or sb.stereo is None or sb != b:
|
|
138
|
+
continue
|
|
139
|
+
else: # original structure has stereo bond
|
|
140
|
+
stereo_bonds.append((n, m))
|
|
141
|
+
|
|
142
|
+
patched_atoms = set(new)
|
|
143
|
+
for n, sa in satoms.items(): # add unmatched or masked atoms
|
|
144
|
+
if n not in patched_atoms and n not in to_delete:
|
|
145
|
+
natoms[n] = a = sa.copy(hydrogens=True)
|
|
146
|
+
nbonds[n] = {}
|
|
147
|
+
if sa.stereo is not None:
|
|
148
|
+
# in case of allenes label can disappear/change, thus, requires recalculation
|
|
149
|
+
# for tetrahedrons label can be stored as is
|
|
150
|
+
if n in structure.stereogenic_tetrahedrons:
|
|
151
|
+
a._stereo = sa.stereo
|
|
152
|
+
else:
|
|
153
|
+
stereo_atoms.append(n)
|
|
154
|
+
|
|
155
|
+
for n, bs in sbonds.items(): # preserve connectivity order for keeping stereo labels as is
|
|
156
|
+
if n in to_delete: # atoms for removing
|
|
157
|
+
continue
|
|
158
|
+
for m, b in bs.items():
|
|
159
|
+
# ignore deleted atoms and patch atoms
|
|
160
|
+
if m in to_delete or n in patched_atoms and m in patched_atoms:
|
|
161
|
+
continue
|
|
162
|
+
elif n in nbonds[m]: # back-link
|
|
163
|
+
nbonds[n][m] = nbonds[m][n]
|
|
164
|
+
else:
|
|
165
|
+
nbonds[n][m] = b.copy()
|
|
166
|
+
if b.stereo is not None:
|
|
167
|
+
# stereo label should be recalculated
|
|
168
|
+
stereo_bonds.append((n, m))
|
|
169
|
+
|
|
170
|
+
for n, a in new.atoms():
|
|
171
|
+
if a.implicit_hydrogens is None:
|
|
172
|
+
new.calc_implicit(n)
|
|
173
|
+
new.calc_labels()
|
|
174
|
+
|
|
175
|
+
# translate stereo sign from old order to new order
|
|
176
|
+
for n in stereo_atoms:
|
|
177
|
+
if n in new.stereogenic_tetrahedrons:
|
|
178
|
+
if sbonds[n].keys() == nbonds[n].keys():
|
|
179
|
+
# flush stereo from reaction center. should be explicitly set in replacement.
|
|
180
|
+
s = new._translate_tetrahedron_sign(n, structure.stereogenic_tetrahedrons[n], satoms[n].stereo)
|
|
181
|
+
natoms[n]._stereo = s
|
|
182
|
+
elif n in new.stereogenic_allenes:
|
|
183
|
+
if set(new.stereogenic_allenes[n]) == set(structure.stereogenic_allenes[n]):
|
|
184
|
+
# flush stereo for changed allene substituents
|
|
185
|
+
s = new._translate_allene_sign(n, *structure.stereogenic_allenes[n][:2], satoms[n].stereo)
|
|
186
|
+
natoms[n]._stereo = s
|
|
187
|
+
# else: ignore label
|
|
188
|
+
|
|
189
|
+
for n, m in stereo_bonds:
|
|
190
|
+
# check if bond is center of cumulene
|
|
191
|
+
if (n12 := new._stereo_cis_trans_terminals.get(n, True)) != new._stereo_cis_trans_terminals.get(m, False):
|
|
192
|
+
continue
|
|
193
|
+
s12 = structure._stereo_cis_trans_terminals[n]
|
|
194
|
+
# check if cumulene terminals are the same
|
|
195
|
+
if set(n12) != set(s12):
|
|
196
|
+
continue
|
|
197
|
+
if set(new.stereogenic_cis_trans[n12]) == set(env := structure.stereogenic_cis_trans[s12]):
|
|
198
|
+
# connected to cumulenes atoms should be the same
|
|
199
|
+
s = new._translate_cis_trans_sign(*n12, *env[:2], sbonds[n][m].stereo)
|
|
200
|
+
nbonds[n][m]._stereo = s
|
|
201
|
+
# else: ignore label
|
|
202
|
+
|
|
203
|
+
if self._fix_rings:
|
|
204
|
+
new.kekule() # keeps stereo as is
|
|
205
|
+
if not new.thiele(fix_tautomers=self._fix_tautomers): # fixes stereo if any ring aromatized
|
|
206
|
+
new.fix_stereo()
|
|
207
|
+
else:
|
|
208
|
+
new.fix_stereo()
|
|
209
|
+
return new
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
__all__ = ['BaseReactor']
|