chython 2.4__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. chython/__init__.py +31 -0
  2. chython/_functions.py +69 -0
  3. chython/algorithms/__init__.py +18 -0
  4. chython/algorithms/_isomorphism.cp312-win_amd64.pyd +0 -0
  5. chython/algorithms/_isomorphism.pyx +189 -0
  6. chython/algorithms/aromatics/__init__.py +27 -0
  7. chython/algorithms/aromatics/_rules.py +124 -0
  8. chython/algorithms/aromatics/kekule.py +530 -0
  9. chython/algorithms/aromatics/test/__init__.py +18 -0
  10. chython/algorithms/aromatics/test/test_kekule.py +160 -0
  11. chython/algorithms/aromatics/test/test_thiele.py +263 -0
  12. chython/algorithms/aromatics/thiele.py +226 -0
  13. chython/algorithms/calculate2d/__init__.py +23 -0
  14. chython/algorithms/calculate2d/clean2d.js +1 -0
  15. chython/algorithms/calculate2d/molecule.py +149 -0
  16. chython/algorithms/calculate2d/reaction.py +80 -0
  17. chython/algorithms/depict.py +523 -0
  18. chython/algorithms/fingerprints/__init__.py +48 -0
  19. chython/algorithms/fingerprints/linear.py +208 -0
  20. chython/algorithms/fingerprints/morgan.py +137 -0
  21. chython/algorithms/fingerprints/test/__init__.py +18 -0
  22. chython/algorithms/fingerprints/test/test_linear.py +127 -0
  23. chython/algorithms/fingerprints/test/test_morgan.py +79 -0
  24. chython/algorithms/isomorphism.py +667 -0
  25. chython/algorithms/mapping/__init__.py +28 -0
  26. chython/algorithms/mapping/_groups.py +94 -0
  27. chython/algorithms/mapping/_reactions.py +72 -0
  28. chython/algorithms/mapping/attention.py +216 -0
  29. chython/algorithms/mapping/fixmapper.py +84 -0
  30. chython/algorithms/mapping/groups.py +137 -0
  31. chython/algorithms/mcs.py +202 -0
  32. chython/algorithms/morgan.py +82 -0
  33. chython/algorithms/rings.py +551 -0
  34. chython/algorithms/smiles.py +556 -0
  35. chython/algorithms/standardize/__init__.py +30 -0
  36. chython/algorithms/standardize/_charged.py +115 -0
  37. chython/algorithms/standardize/_groups.py +926 -0
  38. chython/algorithms/standardize/_metal_organics.py +189 -0
  39. chython/algorithms/standardize/_reagents.py +44 -0
  40. chython/algorithms/standardize/_salts.py +68 -0
  41. chython/algorithms/standardize/molecule.py +479 -0
  42. chython/algorithms/standardize/reaction.py +429 -0
  43. chython/algorithms/standardize/resonance.py +196 -0
  44. chython/algorithms/standardize/salts.py +128 -0
  45. chython/algorithms/standardize/saturation.py +378 -0
  46. chython/algorithms/standardize/test/__init__.py +18 -0
  47. chython/algorithms/standardize/test/test_groups.py +118 -0
  48. chython/algorithms/stereo.py +1222 -0
  49. chython/algorithms/tautomers/__init__.py +171 -0
  50. chython/algorithms/tautomers/_acid.py +59 -0
  51. chython/algorithms/tautomers/_base.py +116 -0
  52. chython/algorithms/tautomers/_keto_enol.py +107 -0
  53. chython/algorithms/tautomers/acid_base.py +214 -0
  54. chython/algorithms/tautomers/heteroarenes.py +108 -0
  55. chython/algorithms/tautomers/keto_enol.py +148 -0
  56. chython/algorithms/tautomers/test/__init__.py +18 -0
  57. chython/algorithms/tautomers/test/test_tautomers.py +80 -0
  58. chython/algorithms/test/__init__.py +18 -0
  59. chython/algorithms/test/test_isomorphism.py +58 -0
  60. chython/algorithms/test/test_smiles.py +76 -0
  61. chython/algorithms/x3dom.py +355 -0
  62. chython/containers/__init__.py +43 -0
  63. chython/containers/_pack_v2.cp312-win_amd64.pyd +0 -0
  64. chython/containers/_pack_v2.pyx +271 -0
  65. chython/containers/_unpack_v0v2.cp312-win_amd64.pyd +0 -0
  66. chython/containers/_unpack_v0v2.pyx +300 -0
  67. chython/containers/bonds.py +255 -0
  68. chython/containers/cgr.py +115 -0
  69. chython/containers/graph.py +200 -0
  70. chython/containers/molecule.py +695 -0
  71. chython/containers/query.py +66 -0
  72. chython/containers/reaction.py +324 -0
  73. chython/exceptions.py +114 -0
  74. chython/files/MRVrw.py +523 -0
  75. chython/files/PDBrw.py +257 -0
  76. chython/files/RDFrw.py +298 -0
  77. chython/files/SDFrw.py +221 -0
  78. chython/files/__init__.py +29 -0
  79. chython/files/_convert.py +207 -0
  80. chython/files/_mapping.py +117 -0
  81. chython/files/_xyz.cp312-win_amd64.pyd +0 -0
  82. chython/files/_xyz.pyx +74 -0
  83. chython/files/daylight/__init__.py +24 -0
  84. chython/files/daylight/parser.py +157 -0
  85. chython/files/daylight/smarts.py +107 -0
  86. chython/files/daylight/smiles.py +245 -0
  87. chython/files/daylight/test/__init__.py +18 -0
  88. chython/files/daylight/test/test_daylight_smarts.py +289 -0
  89. chython/files/daylight/test/test_daylight_smiles.py +101 -0
  90. chython/files/daylight/test/test_parser.py +118 -0
  91. chython/files/daylight/test/test_tokenize.py +77 -0
  92. chython/files/daylight/tokenize.py +376 -0
  93. chython/files/libinchi/__init__.py +22 -0
  94. chython/files/libinchi/libinchi.dll +0 -0
  95. chython/files/libinchi/wrapper.py +555 -0
  96. chython/files/mdl/__init__.py +25 -0
  97. chython/files/mdl/emol.py +209 -0
  98. chython/files/mdl/erxn.py +67 -0
  99. chython/files/mdl/mol.py +144 -0
  100. chython/files/mdl/read.py +225 -0
  101. chython/files/mdl/rxn.py +67 -0
  102. chython/files/mdl/stereo.py +63 -0
  103. chython/files/mdl/write.py +165 -0
  104. chython/files/xyz.py +78 -0
  105. chython/periodictable/__init__.py +69 -0
  106. chython/periodictable/base/__init__.py +25 -0
  107. chython/periodictable/base/dynamic.py +149 -0
  108. chython/periodictable/base/element.py +452 -0
  109. chython/periodictable/base/groups.py +90 -0
  110. chython/periodictable/base/periods.py +46 -0
  111. chython/periodictable/base/query.py +473 -0
  112. chython/periodictable/base/vector.py +119 -0
  113. chython/periodictable/groupI.py +252 -0
  114. chython/periodictable/groupII.py +223 -0
  115. chython/periodictable/groupIII.py +1154 -0
  116. chython/periodictable/groupIV.py +210 -0
  117. chython/periodictable/groupIX.py +196 -0
  118. chython/periodictable/groupV.py +192 -0
  119. chython/periodictable/groupVI.py +183 -0
  120. chython/periodictable/groupVII.py +162 -0
  121. chython/periodictable/groupVIII.py +161 -0
  122. chython/periodictable/groupX.py +166 -0
  123. chython/periodictable/groupXI.py +164 -0
  124. chython/periodictable/groupXII.py +158 -0
  125. chython/periodictable/groupXIII.py +239 -0
  126. chython/periodictable/groupXIV.py +262 -0
  127. chython/periodictable/groupXV.py +276 -0
  128. chython/periodictable/groupXVI.py +457 -0
  129. chython/periodictable/groupXVII.py +324 -0
  130. chython/periodictable/groupXVIII.py +260 -0
  131. chython/reactor/__init__.py +24 -0
  132. chython/reactor/base.py +212 -0
  133. chython/reactor/deprotection.py +565 -0
  134. chython/reactor/groups.py +128 -0
  135. chython/reactor/reactions/__init__.py +157 -0
  136. chython/reactor/reactions/_amidation.py +58 -0
  137. chython/reactor/reactions/_amine_isocyanate.py +52 -0
  138. chython/reactor/reactions/_buchwald_hartwig.py +50 -0
  139. chython/reactor/reactions/_esterification.py +49 -0
  140. chython/reactor/reactions/_macmillan.py +43 -0
  141. chython/reactor/reactions/_reductive_amination.py +50 -0
  142. chython/reactor/reactions/_sonogashira.py +49 -0
  143. chython/reactor/reactions/_sulfonamidation.py +52 -0
  144. chython/reactor/reactions/_suzuki_miyaura.py +88 -0
  145. chython/reactor/reactions/ufe.py +99 -0
  146. chython/reactor/reactor.py +170 -0
  147. chython/reactor/retro/__init__.py +77 -0
  148. chython/reactor/retro/_amidation.py +39 -0
  149. chython/reactor/retro/_aryl_amination.py +32 -0
  150. chython/reactor/retro/_mitsunobu.py +49 -0
  151. chython/reactor/retro/_sonogashira.py +49 -0
  152. chython/reactor/retro/_suzuki_miyaura.py +54 -0
  153. chython/reactor/scaffold.py +122 -0
  154. chython/reactor/test/__init__.py +18 -0
  155. chython/reactor/test/test_deprotection.py +57 -0
  156. chython/reactor/test/test_reactor.py +40 -0
  157. chython/reactor/test/test_scaffold.py +57 -0
  158. chython/reactor/test/test_transformer.py +40 -0
  159. chython/reactor/transformer.py +62 -0
  160. chython/utils/__init__.py +57 -0
  161. chython/utils/free_wilson.py +114 -0
  162. chython/utils/grid.py +130 -0
  163. chython/utils/rdkit.py +165 -0
  164. chython/utils/retro.py +139 -0
  165. chython/utils/svg.py +57 -0
  166. chython/utils/test/__init__.py +18 -0
  167. chython/utils/test/test_rdkit.py +71 -0
  168. chython-2.4.dist-info/LICENSE +165 -0
  169. chython-2.4.dist-info/METADATA +101 -0
  170. chython-2.4.dist-info/RECORD +171 -0
  171. chython-2.4.dist-info/WHEEL +4 -0
@@ -0,0 +1,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']