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,122 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 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 .. import smarts, MoleculeContainer
|
|
20
|
+
from .transformer import Transformer
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
Predefined transformers for common reactive groups cleavage.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
_alcohol = (
|
|
27
|
+
('[O;D1;z1;x0][C;z1;x1:1]', '[A:1]', # rule
|
|
28
|
+
'CCO', 'CC', # match test
|
|
29
|
+
'c1ccccc1O'), # false-match test
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
_phenol = (
|
|
33
|
+
('[O;D1;z1;x0][C;a:1]', '[A:1]', 'c1ccccc1O', 'c1ccccc1', 'CCO'),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
_chloro_aryl = (
|
|
37
|
+
('[Cl;D1;z1;x0][C;a:1]', '[A:1]', 'c1ccccc1Cl', 'c1ccccc1', 'c1ccccc1Br', 'CCCl'),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
_bromo_aryl = (
|
|
41
|
+
('[Br;D1;z1;x0][C;a:1]', '[A:1]', 'c1ccccc1Br', 'c1ccccc1', 'c1ccccc1I', 'CCBr'),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
_iodo_aryl = (
|
|
45
|
+
('[I;D1;z1;x0][C;a:1]', '[A:1]', 'c1ccccc1I', 'c1ccccc1', 'c1ccccc1Cl', 'CCI'),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
_chloro_alkyl = (
|
|
49
|
+
('[Cl;D1;z1;x0][C;x1;z1:1]', '[A:1]', 'CCCl', 'CC', 'c1ccccc1Cl'),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
_bromo_alkyl = (
|
|
53
|
+
('[Br;D1;z1;x0][C;x1;z1:1]', '[A:1]', 'CCBr', 'CC', 'c1ccccc1Br'),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
_iodo_alkyl = (
|
|
57
|
+
('[I;D1;z1;x0][C;x1;z1:1]', '[A:1]', 'CCI', 'CC', 'c1ccccc1I'),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
_carboxy = (
|
|
61
|
+
('[O;D1;z1;x0][C;D3;!R;x2;z2](=[O;D1])[C:1]', '[A:1]', 'CCC(=O)O', 'CC'),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
_chloro_anhydride = (
|
|
65
|
+
('[Cl;D1;z1;x0][C;D3;!R;x2;z2](=[O;D1])[C:1]', '[A:1]', 'CCC(=O)Cl', 'CC'),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
_amine_primary = (
|
|
69
|
+
('[N;D1;z1;x0][C;x1;z1:1]', '[A:1]', 'CCN', 'CC', 'CNC'),
|
|
70
|
+
('[N;D1;z1;x0][C;a:1]', '[A:1]', 'c1ccccc1N', 'c1ccccc1', 'c1ccccc1NC'),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
#################
|
|
74
|
+
# Magic Factory #
|
|
75
|
+
#################
|
|
76
|
+
|
|
77
|
+
_groups = [k[1:] for k, v in globals().items() if k.startswith('_') and isinstance(v, tuple) and v]
|
|
78
|
+
__all__ = ['apply_all'] + _groups
|
|
79
|
+
_cache = {}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _prepare_reactor(rules, name):
|
|
83
|
+
rxn = [Transformer(smarts(r), smarts(p)) for r, p, *_ in rules]
|
|
84
|
+
|
|
85
|
+
def w(molecule: MoleculeContainer, /) -> MoleculeContainer:
|
|
86
|
+
"""
|
|
87
|
+
Remove reactive groups from the given molecule if applicable.
|
|
88
|
+
"""
|
|
89
|
+
for r in rxn:
|
|
90
|
+
while True:
|
|
91
|
+
try:
|
|
92
|
+
molecule = next(r(molecule))
|
|
93
|
+
except StopIteration:
|
|
94
|
+
break
|
|
95
|
+
return molecule
|
|
96
|
+
|
|
97
|
+
w.__module__ = __name__
|
|
98
|
+
w.__qualname__ = w.__name__ = name
|
|
99
|
+
return w
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def apply_all(molecule: MoleculeContainer, /) -> MoleculeContainer:
|
|
103
|
+
"""
|
|
104
|
+
Remove all found reactive groups from the given molecule.
|
|
105
|
+
"""
|
|
106
|
+
for name in _groups:
|
|
107
|
+
molecule = __getattr__(name)(molecule)
|
|
108
|
+
return molecule
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def __getattr__(name):
|
|
112
|
+
try:
|
|
113
|
+
return _cache[name]
|
|
114
|
+
except KeyError:
|
|
115
|
+
if name in _groups:
|
|
116
|
+
_cache[name] = t = _prepare_reactor(globals()[f'_{name}'], name)
|
|
117
|
+
return t
|
|
118
|
+
raise AttributeError
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def __dir__():
|
|
122
|
+
return __all__
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2022 Ramil Nugmanov <nougmanoff@protonmail.com>
|
|
4
|
+
# This file is part of chython.
|
|
5
|
+
#
|
|
6
|
+
# chython is free software; you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
8
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU Lesser General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
17
|
+
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
#
|
|
@@ -0,0 +1,57 @@
|
|
|
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 chython import smiles, smarts, Transformer
|
|
20
|
+
from chython.reactor import deprotection
|
|
21
|
+
from itertools import product
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_deprotection():
|
|
25
|
+
qs = set()
|
|
26
|
+
ts = set()
|
|
27
|
+
for x in dir(deprotection):
|
|
28
|
+
if x == 'apply_all':
|
|
29
|
+
continue
|
|
30
|
+
for r in getattr(deprotection, '_' + x):
|
|
31
|
+
if len(r) > 2: # has test
|
|
32
|
+
q, p, t, a, *bs = r
|
|
33
|
+
t = smiles(t)
|
|
34
|
+
t.canonicalize()
|
|
35
|
+
q = smarts(q)
|
|
36
|
+
qs.add(q)
|
|
37
|
+
ts.add(t)
|
|
38
|
+
a = smiles(a)
|
|
39
|
+
a.canonicalize()
|
|
40
|
+
# test match
|
|
41
|
+
assert q < t, f'{x}: {q} !< {t}'
|
|
42
|
+
o = next(Transformer(q, smarts(p))(t))
|
|
43
|
+
assert o == a, f'{x}: {o} != {a}'
|
|
44
|
+
for b in bs:
|
|
45
|
+
b = smiles(b)
|
|
46
|
+
b.canonicalize()
|
|
47
|
+
assert not q < b, f'{x}: {q} < {b}'
|
|
48
|
+
|
|
49
|
+
# test rule-test is unique pair
|
|
50
|
+
assert len(qs) == len(ts)
|
|
51
|
+
|
|
52
|
+
m = 0
|
|
53
|
+
for q, t in product(qs, ts):
|
|
54
|
+
m += q < t
|
|
55
|
+
|
|
56
|
+
# test selectivity of rules
|
|
57
|
+
assert len(qs) == m
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025 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 chython import smiles, smarts, Reactor
|
|
20
|
+
from pytest import mark
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
data = [
|
|
24
|
+
(('[B;D3;x2;z1:4]([O:5])([O:6])-[C;@@;h1:3]1([O;M][C;M]1)', '[Cl,Br,I;D1:1]-[C;a:2]'), ('[A;@:3]-[A:2]',),
|
|
25
|
+
('CC1O[C@@H]1B(O)O', 'Brc1ccccc1'), ('CC1O[C@H]1c1ccccc1',)), # inverse stereo check
|
|
26
|
+
(('[B;D3;x2;z1:4]([O:5])([O:6])-[C;@@;h1:3]1([O;M][C;M]1)', '[Cl,Br,I;D1:1]-[C;a:2]'), ('[A;@@:3]-[A:2]',),
|
|
27
|
+
('CC1O[C@@H]1B(O)O', 'Brc1ccccc1'), ('CC1O[C@@H]1c1ccccc1',)), # keep stereo on RC
|
|
28
|
+
(('[B;D3;x2;z1:4]([O:5])([O:6])-[C;@@;h1:3]1([O;M][C;M]1)', '[Cl,Br,I;D1:1]-[C;a:2]'), ('[A:3]-[A:2]',),
|
|
29
|
+
('CC1O[C@@H]1B(O)O', 'Brc1ccccc1'), ('CC1OC1c1ccccc1',)), # drop stereo on RC
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@mark.parametrize('patterns, products, source, result', data)
|
|
34
|
+
def test_transformer(patterns, products, source, result):
|
|
35
|
+
for q, m in zip(patterns, source):
|
|
36
|
+
assert smarts(q) <= smiles(m)
|
|
37
|
+
|
|
38
|
+
reactor = Reactor([smarts(x) for x in patterns], [smarts(x) for x in products])
|
|
39
|
+
out = {format(smiles(x), 'h') for x in result}
|
|
40
|
+
assert {format(x, 'h') for x in next(reactor(*(smiles(x) for x in source))).products} == out
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 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 chython import smiles, smarts, Transformer
|
|
20
|
+
from chython.reactor import scaffold
|
|
21
|
+
from itertools import product
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_scaffold():
|
|
25
|
+
qs = set()
|
|
26
|
+
ts = set()
|
|
27
|
+
for x in dir(scaffold):
|
|
28
|
+
if x == 'apply_all':
|
|
29
|
+
continue
|
|
30
|
+
for r in getattr(scaffold, '_' + x):
|
|
31
|
+
if len(r) > 2: # has test
|
|
32
|
+
q, p, t, a, *bs = r
|
|
33
|
+
t = smiles(t)
|
|
34
|
+
t.canonicalize()
|
|
35
|
+
q = smarts(q)
|
|
36
|
+
qs.add(q)
|
|
37
|
+
ts.add(t)
|
|
38
|
+
a = smiles(a)
|
|
39
|
+
a.canonicalize()
|
|
40
|
+
# test match
|
|
41
|
+
assert q < t, f'{x}: {q} !< {t}'
|
|
42
|
+
o = next(Transformer(q, smarts(p))(t))
|
|
43
|
+
assert o == a, f'{x}: {o} != {a}'
|
|
44
|
+
for b in bs:
|
|
45
|
+
b = smiles(b)
|
|
46
|
+
b.canonicalize()
|
|
47
|
+
assert not q < b, f'{x}: {q} < {b}'
|
|
48
|
+
|
|
49
|
+
# test rule-test is unique pair
|
|
50
|
+
assert len(qs) == len(ts)
|
|
51
|
+
|
|
52
|
+
m = 0
|
|
53
|
+
for q, t in product(qs, ts):
|
|
54
|
+
m += q < t
|
|
55
|
+
|
|
56
|
+
# test selectivity of rules
|
|
57
|
+
assert len(qs) == m
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025 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 chython import smiles, smarts, Transformer
|
|
20
|
+
from pytest import mark
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
data = [
|
|
24
|
+
('[C:1]Br', '[A:1][O;M]', 'C[C@H](OC)CBr', 'C[C@H](OC)CO'), # keep stereo out of match
|
|
25
|
+
('[C:2][C:1]Br', '[A:2][A:1][O;M]', 'C[C@H](OC)CBr', 'C[C@H](OC)CO'), # keep stereo inside match
|
|
26
|
+
('[C;M][C;@;h1:1]([O;M])[N;M]', '[A;@@:1]', 'CC[C@H](O)N', 'CC[C@@H](O)N'), # inversion of stereo
|
|
27
|
+
('[C:1]Br', '[A:1][O;M]', 'C/C=C/CBr', 'C/C=C/CO'), # keep stereo out of match
|
|
28
|
+
('[C:1]Br', '[A:1][O;M]', 'CC=[C@]=CCBr', 'CC=[C@]=CCO'), # keep
|
|
29
|
+
('[C:1]Br', '[A:1][O;M]', 'CC=[C@]=CBr', 'CC=C=CO'), # drop stereo on RC
|
|
30
|
+
('[C:1]Br', '[A:1][O;M]', 'C/C=C/Br', 'CC=CO'), # drop stereo on RC
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@mark.parametrize('pattern, replacement, source, result', data)
|
|
35
|
+
def test_transformer(pattern, replacement, source, result):
|
|
36
|
+
transformer = Transformer(smarts(pattern), smarts(replacement))
|
|
37
|
+
|
|
38
|
+
mol = smiles(source)
|
|
39
|
+
out = {format(smiles(x), 'h') for x in ([result] if isinstance(result, str) else result)}
|
|
40
|
+
assert {format(x, 'h') for x in transformer(mol)} == out
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2014-2024 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 .base import BaseReactor
|
|
22
|
+
from ..containers import QueryContainer, MoleculeContainer
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Transformer(BaseReactor):
|
|
26
|
+
"""
|
|
27
|
+
Editor for molecules.
|
|
28
|
+
generates modified molecules from input molecule using template.
|
|
29
|
+
Transformer calling returns generator of all possible replacements.
|
|
30
|
+
"""
|
|
31
|
+
def __init__(self, pattern: QueryContainer, replacement: Union[MoleculeContainer, QueryContainer],
|
|
32
|
+
delete_atoms: bool = True, automorphism_filter: bool = True, fix_aromatic_rings: bool = True,
|
|
33
|
+
fix_tautomers: bool = True, copy_metadata: bool = False):
|
|
34
|
+
"""
|
|
35
|
+
:param pattern: Search pattern.
|
|
36
|
+
:param replacement: Resulted structure.
|
|
37
|
+
:param delete_atoms: If True atoms exists in reactants but not exists in products will be removed.
|
|
38
|
+
:param fix_aromatic_rings: Proceed kekule and thiele on products.
|
|
39
|
+
:param fix_tautomers: See `thiele()` docs.
|
|
40
|
+
:param automorphism_filter: Skip matches to same atoms.
|
|
41
|
+
:param copy_metadata: Copy metadata from structure to transformed
|
|
42
|
+
"""
|
|
43
|
+
if not isinstance(pattern, QueryContainer) or not isinstance(replacement, (MoleculeContainer, QueryContainer)):
|
|
44
|
+
raise TypeError('invalid params')
|
|
45
|
+
|
|
46
|
+
self._pattern = pattern
|
|
47
|
+
self._automorphism_filter = automorphism_filter
|
|
48
|
+
self._copy_metadata = copy_metadata
|
|
49
|
+
super().__init__(pattern, replacement, delete_atoms, fix_aromatic_rings, fix_tautomers)
|
|
50
|
+
|
|
51
|
+
def __call__(self, structure: MoleculeContainer):
|
|
52
|
+
if not isinstance(structure, MoleculeContainer):
|
|
53
|
+
raise TypeError('only Molecules possible')
|
|
54
|
+
|
|
55
|
+
for mapping in self._pattern.get_mapping(structure, automorphism_filter=self._automorphism_filter):
|
|
56
|
+
transformed = self._patcher(structure, mapping)
|
|
57
|
+
if self._copy_metadata:
|
|
58
|
+
transformed.meta.update(structure.meta)
|
|
59
|
+
yield transformed
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
__all__ = ['Transformer']
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2019-2025 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 importlib.util import find_spec
|
|
20
|
+
from .free_wilson import *
|
|
21
|
+
from .grid import *
|
|
22
|
+
from .retro import *
|
|
23
|
+
from .svg import *
|
|
24
|
+
from ..containers.graph import Graph
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_patched = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def patch_pandas():
|
|
31
|
+
"""
|
|
32
|
+
Fix pandas molecules representation.
|
|
33
|
+
"""
|
|
34
|
+
# prevent recursive patching
|
|
35
|
+
global _patched
|
|
36
|
+
if _patched:
|
|
37
|
+
return
|
|
38
|
+
_patched = True
|
|
39
|
+
|
|
40
|
+
from pandas.io.formats import printing
|
|
41
|
+
from pandas.io.formats.printing import is_sequence
|
|
42
|
+
|
|
43
|
+
def w(obj):
|
|
44
|
+
if isinstance(obj, Graph):
|
|
45
|
+
return False
|
|
46
|
+
return is_sequence(obj)
|
|
47
|
+
|
|
48
|
+
printing.is_sequence = w
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = ['fw_prepare_groups', 'fw_decomposition_tree',
|
|
52
|
+
'grid_depict', 'GridDepict', 'retro_depict', 'RetroDepict', 'svg2png', 'patch_pandas']
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if find_spec('rdkit'):
|
|
56
|
+
from .rdkit import *
|
|
57
|
+
__all__.extend(['from_rdkit_molecule', 'to_rdkit_molecule'])
|
|
@@ -0,0 +1,114 @@
|
|
|
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 product
|
|
20
|
+
from typing import Union, List
|
|
21
|
+
from .retro import Tree
|
|
22
|
+
from ..containers import MoleculeContainer, QueryContainer
|
|
23
|
+
from ..periodictable import H
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def fw_prepare_groups(core: Union[MoleculeContainer, QueryContainer], molecule: MoleculeContainer) -> \
|
|
27
|
+
List[MoleculeContainer]:
|
|
28
|
+
"""
|
|
29
|
+
Prepare list of core with connected groups. Hydrogens added to groups for marking connection point.
|
|
30
|
+
Hydrogens have isotope marks equal to mapping of core atoms.
|
|
31
|
+
Groups connected multiple times (rings) - contains multiple hydrogens.
|
|
32
|
+
|
|
33
|
+
:param core: core structure for searching
|
|
34
|
+
:param molecule: target structure
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
core_map = next(core.get_mapping(molecule))
|
|
38
|
+
except StopIteration:
|
|
39
|
+
return []
|
|
40
|
+
|
|
41
|
+
reverse = {v: k for k, v in core_map.items()}
|
|
42
|
+
cs = set(core_map.values())
|
|
43
|
+
groups = molecule.substructure(molecule._atoms.keys() - cs, recalculate_hydrogens=False)
|
|
44
|
+
gs = set(groups)
|
|
45
|
+
|
|
46
|
+
cf = molecule.substructure(cs, recalculate_hydrogens=False)
|
|
47
|
+
|
|
48
|
+
for n, m, b in molecule.bonds():
|
|
49
|
+
if n in cs:
|
|
50
|
+
if m in gs:
|
|
51
|
+
a = molecule.atom(n)
|
|
52
|
+
h = H(x=a.x, y=a.y)
|
|
53
|
+
h._isotope = reverse[n] # mark mapping to isotope
|
|
54
|
+
groups.add_bond(groups.add_atom(h, _skip_calculation=True), m, b.copy(), _skip_calculation=True)
|
|
55
|
+
|
|
56
|
+
a = molecule.atom(m)
|
|
57
|
+
h = H(x=a.x, y=a.y)
|
|
58
|
+
h._isotope = reverse[n] # mark mapping to isotope
|
|
59
|
+
cf.add_bond(cf.add_atom(h, _skip_calculation=True), n, b.copy(), _skip_calculation=True)
|
|
60
|
+
elif m in cs and n in gs:
|
|
61
|
+
a = molecule.atom(m)
|
|
62
|
+
h = H(x=a.x, y=a.y)
|
|
63
|
+
h._isotope = reverse[m]
|
|
64
|
+
groups.add_bond(groups.add_atom(h, _skip_calculation=True), n, b.copy(), _skip_calculation=True)
|
|
65
|
+
|
|
66
|
+
a = molecule.atom(n)
|
|
67
|
+
h = H(x=a.x, y=a.y)
|
|
68
|
+
h._isotope = reverse[m] # mark mapping to isotope
|
|
69
|
+
cf.add_bond(cf.add_atom(h.copy(), _skip_calculation=True), n, b.copy(), _skip_calculation=True)
|
|
70
|
+
groups = groups.split()
|
|
71
|
+
groups.insert(0, cf)
|
|
72
|
+
return groups
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def fw_decomposition_tree(groups: List[MoleculeContainer]) -> Tree:
|
|
76
|
+
assert len(groups) == len(set(groups))
|
|
77
|
+
|
|
78
|
+
pred = {} # directed graph from substructures to superstructures
|
|
79
|
+
succ = {}
|
|
80
|
+
for m in groups:
|
|
81
|
+
pred[m] = set()
|
|
82
|
+
succ[m] = set()
|
|
83
|
+
|
|
84
|
+
for m in groups:
|
|
85
|
+
for n in groups:
|
|
86
|
+
if m < n:
|
|
87
|
+
pred[n].add(m)
|
|
88
|
+
succ[m].add(n)
|
|
89
|
+
|
|
90
|
+
# break triangles
|
|
91
|
+
scope = {m for m, ns in succ.items() if len(ns) > 1}
|
|
92
|
+
while scope:
|
|
93
|
+
m = sorted(scope, key=lambda x: len(pred[x]))[0]
|
|
94
|
+
s = succ[m]
|
|
95
|
+
scope.discard(m)
|
|
96
|
+
while True:
|
|
97
|
+
for x, y in product((x for x in s if succ[x]), (x for x in s if len(pred[x]) > 1)):
|
|
98
|
+
if y in succ[x]:
|
|
99
|
+
s.discard(y)
|
|
100
|
+
pred[y].discard(m)
|
|
101
|
+
break
|
|
102
|
+
else:
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
def _rec_tree(x):
|
|
106
|
+
return x, [_rec_tree(y) for y in succ[x]]
|
|
107
|
+
|
|
108
|
+
m = MoleculeContainer()
|
|
109
|
+
m.add_atom('H')
|
|
110
|
+
|
|
111
|
+
return m, [_rec_tree(x) for x, p in pred.items() if not p]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
__all__ = ['fw_prepare_groups', 'fw_decomposition_tree']
|