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,523 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2018-2024 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # Copyright 2019-2020 Dinar Batyrshin <batyrshin-dinar@mail.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 CachedMethods import cached_method
21
+ from collections import defaultdict
22
+ from math import atan2, sin, cos, hypot
23
+ from typing import Tuple, TYPE_CHECKING, Union
24
+ from uuid import uuid4
25
+
26
+
27
+ if TYPE_CHECKING:
28
+ from chython import ReactionContainer, MoleculeContainer
29
+
30
+ cpk = tuple('''
31
+ #909090 #D9FFFF
32
+ #CC80FF #C2FF00 #FFB5B5 #101010 #3050F8 #FF0D0D #90E050 #B3E3F5
33
+ #AB5CF2 #8AFF00 #BFA6A6 #F0C8A0 #FF8000 #C6C600 #1FF01F #80D1E3
34
+ #8F40D4 #3DFF00 #E6E6E6 #BFC2C7 #A6A6AB #8A99C7 #9C7AC7
35
+ #E06633 #F090A0 #50D050 #C88033 #7D80B0 #C28F8F #668F8F #BD80E3 #FFA100 #A62929 #5CB8D1
36
+ #702EB0 #00FF00 #94FFFF #94E0E0 #73C2C9 #54B5B5 #3B9E9E
37
+ #248F8F #0A7D8C #006985 #C0C0C0 #FFD98F #A67573 #668080 #9E63B5 #D47A00 #940094 #429EB0
38
+ #57178F #00C900 #70D4FF
39
+ #FFFFC7 #D9FFC7 #C7FFC7 #A3FFC7 #8FFFC7 #61FFC7 #45FFC7
40
+ #30FFC7 #1FFFC7 #00FF9C #00E675 #00D452 #00BF38 #00AB24
41
+ #4DC2FF #4DA6FF #2194D6 #267DAB
42
+ #266696 #175487 #D0D0E0 #FFD123 #B8B8D0 #A6544D #575961 #9E4FB5 #AB5C00 #754F45 #428296
43
+ #420066 #007D00 #70ABFA
44
+ #00BAFF #00A1FF #008FFF #0080FF #006BFF #545CF2 #785CE3
45
+ #8A4FE3 #A136D4 #B31FD4 #B31FBA #B30DA6 #BD0D87 #C70066
46
+ #CC0059 #D1004F #D90045 #E00038
47
+ #E6002E #EB0026 #EB0026 #EB0026 #EB0026 #EB0026 #EB0026 #EB0026 #EB0026 #EB0026 #EB0026
48
+ '''.split())
49
+ _render_charge = {-4: '4-', -3: '3-', -2: '2-', -1: '-', 1: '+', 2: '2+', 3: '3+', 4: '4+'}
50
+ _render_config = {'carbon': False, 'dashes': (.2, .1), 'span_dy': .15, 'mapping': True, 'font_size': .5,
51
+ 'span_size': .35, 'other_size': 0.3, 'monochrome': False, 'bond_color': 'black', 'bond_width': .04,
52
+ 'other_color': 'black', 'bond_radius': .02, 'atom_radius': -.2, 'mapping_size': .25,
53
+ 'atoms_colors': cpk, 'triple_space': .13, 'double_space': .06, 'mapping_color': '#0305A7',
54
+ 'aromatic_space': .14, 'aromatic_dashes': (.15, .05), 'dx_m': .05, 'dy_m': .2,
55
+ 'other_font_style': 'monospace', 'dx_ci': .05, 'dy_ci': 0.2, 'symbols_font_style': 'sans-serif',
56
+ 'mapping_font_style': 'monospace', 'wedge_space': .08}
57
+
58
+
59
+ def _rotate_vector(x1, y1, x2, y2):
60
+ """
61
+ rotate x,y vector over x2-x1, y2-y1 angle
62
+ """
63
+ angle = atan2(y2, x2)
64
+ cos_rad = cos(angle)
65
+ sin_rad = sin(angle)
66
+ return cos_rad * x1 - sin_rad * y1, sin_rad * x1 + cos_rad * y1
67
+
68
+
69
+ def _graph_svg(atoms, bonds, define, masks, uid, viewbox_x, viewbox_y, width, height):
70
+ svg = [f' <g id="{uid}-molecule">\n <defs>']
71
+ svg.extend(define)
72
+ if bonds:
73
+ if masks:
74
+ svg.append(f' <mask id="{uid}-mask">\n'
75
+ f' <rect x="{viewbox_x:.2f}" y="{viewbox_y:.2f}" '
76
+ f'width="{width:.2f}" height="{height:.2f}" fill="white"/>')
77
+ svg.extend(masks)
78
+ svg.append(' </mask>\n </defs>\n'
79
+ f' <g fill="none" stroke="{_render_config["bond_color"]}" '
80
+ f'stroke-width="{_render_config["bond_width"]:.2f}" mask="url(#{uid}-mask)">')
81
+ if len(bonds) == 1: # SVG BUG adhoc
82
+ svg.append(f' <line x1="{viewbox_x:.2f}" y1="{viewbox_y:.2f}" '
83
+ f'x2="{viewbox_x + width:.2f}" y2="{viewbox_y:.2f}" stroke="none"/>')
84
+ else:
85
+ svg.append(f' </defs>\n <g fill="none" stroke="{_render_config["bond_color"]}" '
86
+ f'stroke-width="{_render_config["bond_width"]:.2f}">')
87
+ svg.extend(bonds)
88
+ svg.append(' </g>')
89
+ else:
90
+ svg.append(' </defs>')
91
+
92
+ svg.extend(atoms)
93
+ svg.append(' </g>')
94
+ return svg
95
+
96
+
97
+ def _render_aromatic_bond(n_x, n_y, m_x, m_y, c_x, c_y):
98
+ aromatic_space = _render_config['aromatic_space']
99
+ dash3, dash4 = _render_config['aromatic_dashes']
100
+ # n aligned xy
101
+ mn_x, mn_y, cn_x, cn_y = m_x - n_x, m_y - n_y, c_x - n_x, c_y - n_y
102
+
103
+ # nm reoriented xy
104
+ mr_x, mr_y = hypot(mn_x, mn_y), 0
105
+ cr_x, cr_y = _rotate_vector(cn_x, cn_y, mn_x, -mn_y)
106
+
107
+ if cr_y and aromatic_space / cr_y < .65:
108
+ if cr_y > 0:
109
+ r_y = aromatic_space
110
+ else:
111
+ r_y = -aromatic_space
112
+ cr_y = -cr_y
113
+
114
+ ar_x = aromatic_space * cr_x / cr_y
115
+ br_x = mr_x - aromatic_space * (mr_x - cr_x) / cr_y
116
+
117
+ # backward reorienting
118
+ an_x, an_y = _rotate_vector(ar_x, r_y, mn_x, mn_y)
119
+ bn_x, bn_y = _rotate_vector(br_x, r_y, mn_x, mn_y)
120
+ a_x, a_y = n_x + an_x, n_y + an_y
121
+ b_x, b_y = n_x + bn_x, n_y + bn_y
122
+
123
+ return f' <line x1="{a_x:.2f}" y1="{-a_y:.2f}" x2="{b_x:.2f}" y2="{-b_y:.2f}" ' \
124
+ f'stroke-dasharray="{dash3:.2f} {dash4:.2f}"/>'
125
+
126
+
127
+ def depict_settings(*, carbon: bool = False, aam: bool = True, monochrome: bool = False,
128
+ bond_color: str = 'black', aam_color: str = '#0305A7', atoms_colors: tuple = cpk,
129
+ bond_width: float = .04, wedge_space: float = .08, dashes: Tuple[float, float] = (.2, .1),
130
+ aromatic_dashes: Tuple[float, float] = (.15, .05), dx_ci: float = .05, dy_ci: float = .2,
131
+ dx_m: float = .05, dy_m: float = .2, span_dy: float = .15, double_space: float = .06,
132
+ triple_space: float = .13, aromatic_space: float = .14, atom_radius: float = .2, bond_radius=.02,
133
+ font_size: float = .5, other_size: float = .3, span_size: float = .35, aam_size: float = .25,
134
+ symbols_font_style: str = 'sans-serif', other_font_style: str = 'monospace',
135
+ other_color: str = 'black', mapping_font_style: str = 'monospace'):
136
+ """
137
+ Settings for depict of chemical structures
138
+
139
+ :param carbon: if True, depict atom C
140
+ :param font_size: font size
141
+ :param aam_size: atom-to-atom mapping font size
142
+ :param span_size: font size for hydrogen count
143
+ :param other_size: isotope, radical, charges, neighbors and hybridization symbols size
144
+ :param bond_width: bond width
145
+ :param bond_color: color of bonds
146
+ :param aam_color: atom-to-atom mapping color
147
+ :param atoms_colors: atom colors where key is atomic number - 1, value is atom color (str)
148
+ :param other_color: color for charges, radicals, isotopes
149
+ :param symbols_font_style: font style for atom symbols
150
+ :param other_font_style: font style for charges, radicals, isotopes, hybridization and neighbors
151
+ :param aam: if True, depict mapping
152
+ :param monochrome: if True, colors of items in molecule not used
153
+ :param dashes: first value is long of visible line, second is long of invisible line
154
+ :param aromatic_space: space between simple and aromatic bonds
155
+ :param triple_space: space between simple and triple bonds
156
+ :param double_space: space between simple and double bonds
157
+ :param aromatic_dashes: first value is long of visible line, second is long of invisible line
158
+ :param atom_radius: radius of atoms spheres in depict3d. if negative is multiplier to covalent radii
159
+ :param bond_radius: radius of bonds spheres in depict3d
160
+ :param dx_ci: x-axis offset relative to the center of the atom symbol for radical, charges, isotope
161
+ :param dy_ci: y-axis offset relative to the center of the atom symbol for radical, charges, isotope
162
+ :param dx_m: x-axis offset relative to the center of the atom symbol for atom-to-atom mapping
163
+ :param dy_m: y-axis offset relative to the center of the atom symbol for atom-to-atom mapping
164
+ :param span_dy: y-axis offset relative to the center of the atom symbol for hydrogen count
165
+ :param mapping_font_style: font style for mapping
166
+ :param wedge_space: wedge bond width
167
+ """
168
+ _render_config['carbon'] = carbon
169
+ _render_config['dashes'] = dashes
170
+ _render_config['span_dy'] = span_dy
171
+ _render_config['mapping'] = aam
172
+ _render_config['font_size'] = font_size
173
+ _render_config['span_size'] = span_size
174
+ _render_config['other_size'] = other_size
175
+ _render_config['monochrome'] = monochrome
176
+ _render_config['bond_color'] = bond_color
177
+ _render_config['bond_width'] = bond_width
178
+ _render_config['other_color'] = other_color
179
+ _render_config['bond_radius'] = bond_radius
180
+ _render_config['atom_radius'] = -atom_radius
181
+ _render_config['mapping_size'] = aam_size
182
+ _render_config['atoms_colors'] = atoms_colors
183
+ _render_config['triple_space'] = triple_space
184
+ _render_config['double_space'] = double_space
185
+ _render_config['mapping_color'] = aam_color
186
+ _render_config['aromatic_space'] = aromatic_space
187
+ _render_config['aromatic_dashes'] = aromatic_dashes
188
+ _render_config['dx_m'], _render_config['dy_m'] = dx_m, dy_m
189
+ _render_config['other_font_style'] = other_font_style
190
+ _render_config['dx_ci'], _render_config['dy_ci'] = dx_ci, dy_ci
191
+ _render_config['symbols_font_style'] = symbols_font_style
192
+ _render_config['mapping_font_style'] = mapping_font_style
193
+ _render_config['wedge_space'] = wedge_space
194
+
195
+
196
+ class DepictMolecule:
197
+ __slots__ = ()
198
+
199
+ def depict(self: Union['MoleculeContainer', 'DepictMolecule'], *, width=None, height=None, clean2d: bool = True,
200
+ _embedding=False) -> str:
201
+ """
202
+ Depict molecule in SVG format.
203
+
204
+ :param width: set svg width param. by default auto-calculated.
205
+ :param height: set svg height param. by default auto-calculated.
206
+ :param clean2d: calculate coordinates if necessary.
207
+ """
208
+ uid = str(uuid4())
209
+ min_x = min(a.x for _, a in self.atoms())
210
+ max_x = max(a.x for _, a in self.atoms())
211
+ min_y = min(a.y for _, a in self.atoms())
212
+ max_y = max(a.y for _, a in self.atoms())
213
+ if clean2d and len(self) > 1 and max_y - min_y < .01 and max_x - min_x < 0.01:
214
+ self.clean2d()
215
+ min_x = min(a.x for _, a in self.atoms())
216
+ max_x = max(a.x for _, a in self.atoms())
217
+ min_y = min(a.y for _, a in self.atoms())
218
+ max_y = max(a.y for _, a in self.atoms())
219
+
220
+ bonds = self.__render_bonds()
221
+ atoms, define, masks = self.__render_atoms(uid)
222
+ if _embedding:
223
+ return atoms, bonds, define, masks, uid, min_x, min_y, max_x, max_y
224
+
225
+ font_size = _render_config['font_size']
226
+ font125 = 1.25 * font_size
227
+ _width = max_x - min_x + 4.0 * font_size
228
+ _height = max_y - min_y + 2.5 * font_size
229
+ viewbox_x = min_x - font125
230
+ viewbox_y = -max_y - font125
231
+
232
+ if width is None:
233
+ width = f'{_width:.2f}cm'
234
+ if height is None:
235
+ height = f'{_height:.2f}cm'
236
+
237
+ svg = [f'<svg width="{width}" height="{height}" '
238
+ f'viewBox="{viewbox_x:.2f} {viewbox_y:.2f} {_width:.2f} {_height:.2f}" '
239
+ 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">']
240
+ svg.extend(_graph_svg(atoms, bonds, define, masks, uid, viewbox_x, viewbox_y, _width, _height))
241
+ svg.append('</svg>')
242
+ return '\n'.join(svg)
243
+
244
+ @cached_method
245
+ def _repr_svg_(self):
246
+ return self.depict()
247
+
248
+ def __render_bonds(self: Union['MoleculeContainer', 'DepictMolecule']):
249
+ atoms = self._atoms
250
+ svg = []
251
+ double_space = _render_config['double_space']
252
+ triple_space = _render_config['triple_space']
253
+ wedge_space = _render_config['wedge_space']
254
+ dash1, dash2 = _render_config['dashes']
255
+ color = f' fill="{_render_config["bond_color"]}"'
256
+
257
+ wedge = defaultdict(set)
258
+ for n, m, s in self._wedge_map:
259
+ wedge[n].add(m)
260
+ wedge[m].add(n)
261
+
262
+ nx, ny = atoms[n].xy
263
+ mx, my = atoms[m].xy
264
+ ny, my = -ny, -my
265
+ dx, dy = _rotate_vector(0, wedge_space, mx - nx, ny - my)
266
+
267
+ svg.append(f' <path d="M{nx:.2f} {ny:.2f} L{mx + dx:.2f} {my + dy:.2f} '
268
+ f'L{mx - dx:.2f} {my - dy:.2f} Z"{s == 1 and color or ""}/>')
269
+
270
+ for n, m, bond in self.bonds():
271
+ if m in wedge[n]:
272
+ continue
273
+ nx, ny = atoms[n].xy
274
+ mx, my = atoms[m].xy
275
+ ny, my = -ny, -my
276
+ if bond in (1, 4):
277
+ svg.append(f' <line x1="{nx:.2f}" y1="{ny:.2f}" x2="{mx:.2f}" y2="{my:.2f}"/>')
278
+ elif bond == 2:
279
+ dx, dy = _rotate_vector(0, double_space, mx - nx, ny - my)
280
+ svg.append(f' <line x1="{nx + dx:.2f}" y1="{ny - dy:.2f}" x2="{mx + dx:.2f}" y2="{my - dy:.2f}"/>')
281
+ svg.append(f' <line x1="{nx - dx:.2f}" y1="{ny + dy:.2f}" x2="{mx - dx:.2f}" y2="{my + dy:.2f}"/>')
282
+ elif bond == 3:
283
+ dx, dy = _rotate_vector(0, triple_space, mx - nx, ny - my)
284
+ svg.append(f' <line x1="{nx + dx:.2f}" y1="{ny - dy:.2f}" x2="{mx + dx:.2f}" y2="{my - dy:.2f}"/>')
285
+ svg.append(f' <line x1="{nx:.2f}" y1="{ny:.2f}" x2="{mx:.2f}" y2="{my:.2f}"/>')
286
+ svg.append(f' <line x1="{nx - dx:.2f}" y1="{ny + dy:.2f}" x2="{mx - dx:.2f}" y2="{my + dy:.2f}"/>')
287
+ else:
288
+ svg.append(f' <line x1="{nx:.2f}" y1="{ny:.2f}" x2="{mx:.2f}" y2="{my:.2f}" '
289
+ f'stroke-dasharray="{dash1:.2f} {dash2:.2f}"/>')
290
+
291
+ for ring in self.aromatic_rings:
292
+ cx = sum(atoms[n].x for n in ring) / len(ring)
293
+ cy = sum(atoms[n].y for n in ring) / len(ring)
294
+
295
+ for n, m in zip(ring, ring[1:]):
296
+ nx, ny = atoms[n].xy
297
+ mx, my = atoms[m].xy
298
+ aromatic = _render_aromatic_bond(nx, ny, mx, my, cx, cy)
299
+ if aromatic:
300
+ svg.append(aromatic)
301
+
302
+ nx, ny = atoms[ring[-1]].xy
303
+ mx, my = atoms[ring[0]].xy
304
+ aromatic = _render_aromatic_bond(nx, ny, mx, my, cx, cy)
305
+ if aromatic:
306
+ svg.append(aromatic)
307
+ return svg
308
+
309
+ def __render_atoms(self: 'MoleculeContainer', uid):
310
+ bonds = self._bonds
311
+
312
+ carbon = _render_config['carbon']
313
+ mapping = _render_config['mapping']
314
+ span_size = _render_config['span_size']
315
+ font_size = _render_config['font_size']
316
+ monochrome = _render_config['monochrome']
317
+ other_size = _render_config['other_size']
318
+ atoms_colors = _render_config['atoms_colors']
319
+ mapping_size = _render_config['mapping_size']
320
+ dx_m, dy_m = _render_config['dx_m'], _render_config['dy_m']
321
+ dx_ci, dy_ci = _render_config['dx_ci'], _render_config['dy_ci']
322
+ symbols_font_style = _render_config['symbols_font_style']
323
+ span_dy = _render_config['span_dy']
324
+ other_font_style = _render_config['other_font_style']
325
+ mapping_font_style = _render_config['mapping_font_style']
326
+
327
+ if monochrome:
328
+ map_fill = other_fill = 'black'
329
+ else:
330
+ map_fill = _render_config['mapping_color']
331
+ other_fill = _render_config['other_color']
332
+
333
+ font2 = .2 * font_size
334
+ font3 = .3 * font_size
335
+ font4 = .4 * font_size
336
+ font5 = .5 * font_size
337
+ font6 = .6 * font_size
338
+ font7 = .7 * font_size
339
+ font15 = .15 * font_size
340
+ font25 = .25 * font_size
341
+ stroke_width_s = font_size * .1
342
+ stroke_width_o = other_size * .1
343
+ stroke_width_m = mapping_size * .1
344
+
345
+ svg = []
346
+ maps = []
347
+ symbols = []
348
+ fill_zone = []
349
+ others = []
350
+ define = []
351
+ mask = []
352
+
353
+ for n, atom in self.atoms():
354
+ x, y = atom.x, -atom.y
355
+ symbol = atom.atomic_symbol
356
+ if (symbol != 'C' or atom.charge or atom.is_radical or atom.isotope or carbon
357
+ or not bonds[n] or sum(b == 2 for b in bonds[n].values()) == 2):
358
+ if atom.charge:
359
+ others.append(f' <text x="{x:.2f}" y="{y:.2f}" dx="{dx_ci:.2f}" dy="-{dy_ci:.2f}">'
360
+ f'{_render_charge[atom.charge]}{"↑" if atom.is_radical else ""}</text>')
361
+ elif atom.is_radical:
362
+ others.append(f' <text x="{x:.2f}" y="{y:.2f}" dx="{dx_ci:.2f}" dy="-{dy_ci:.2f}">↑</text>')
363
+ if atom.isotope:
364
+ others.append(f' <text x="{x:.2f}" y="{y:.2f}" dx="-{dx_ci:.2f}" dy="-{dy_ci:.2f}" '
365
+ f'text-anchor="end">{atom.isotope}</text>')
366
+
367
+ if len(symbol) > 1:
368
+ dx = font7
369
+ dx_mm = dx_m + font5
370
+ if symbol[-1] in ('l', 'i', 'r', 't'):
371
+ rx = font6
372
+ ax = font25
373
+ else:
374
+ rx = font7
375
+ ax = font15
376
+ fill_zone.append(f' <ellipse cx="{x - ax:.2f}" cy="{y:.2f}" rx="{rx}" ry="{font4}"/>')
377
+ else:
378
+ if symbol == 'I':
379
+ dx = font15
380
+ dx_mm = dx_m
381
+ else:
382
+ dx = font4
383
+ dx_mm = dx_m + font2
384
+ fill_zone.append(f' <circle cx="{x:.2f}" cy="{y:.2f}" r="{font4:.2f}"/>')
385
+
386
+ h = atom.implicit_hydrogens
387
+ if h == 1:
388
+ h = 'H'
389
+ elif h:
390
+ h = f'H<tspan dy="{span_dy:.2f}" font-size="{span_size:.2f}">{h}</tspan>'
391
+ else:
392
+ h = ''
393
+ symbols.append(f' <text id="{uid}-{n}" x="{x:.2f}" y="{y:.2f}" dx="-{dx:.2f}" dy="{font4:.2f}">'
394
+ f'{symbol}{h}</text>')
395
+
396
+ svg.append(f' <use xlink:href="#{uid}-{n}" '
397
+ f'fill="{"black" if monochrome else atoms_colors[atom.atomic_number - 1]}"/>')
398
+
399
+ if mapping:
400
+ maps.append(f' <text x="{x:.2f}" y="{y:.2f}" dx="-{dx_mm:.2f}" '
401
+ f'dy="{dy_m + font3:.2f}">{n}</text>')
402
+ elif mapping:
403
+ maps.append(f' <text x="{x:.2f}" y="{y:.2f}" dx="-{dx_m:.2f}" dy="{dy_m:.2f}">{n}</text>')
404
+
405
+ if svg: # group atoms symbols
406
+ if fill_zone:
407
+ mask.append(' <g>')
408
+ mask.extend(fill_zone)
409
+ mask.append(' </g>')
410
+
411
+ svg.insert(0, f' <g font-size="{font_size:.2f}" font-family="{symbols_font_style}">')
412
+ svg.append(' </g>')
413
+ define.append(f' <g id="{uid}-symbols" font-size="{font_size:.2f}" '
414
+ f'font-family="{symbols_font_style}">')
415
+ define.extend(symbols)
416
+ define.append(' </g>')
417
+ mask.append(' <g stroke="black">\n '
418
+ f'<use xlink:href="#{uid}-symbols" stroke-width="{stroke_width_s:.2f}"/>')
419
+
420
+ if others:
421
+ define.append(f' <g id="{uid}-attrs" font-size="{other_size:.2f}" '
422
+ f'font-family="{other_font_style}">')
423
+ define.extend(others)
424
+ define.append(' </g>')
425
+ svg.append(f' <use xlink:href="#{uid}-attrs" fill="{other_fill}"/>')
426
+ mask.append(f' <use xlink:href="#{uid}-attrs" stroke-width="{stroke_width_o:.2f}"/>')
427
+ if maps:
428
+ if not svg: # no atoms but maps
429
+ mask.append(' <g stroke="black">')
430
+ define.append(f' <g id="{uid}-mapping" font-size="{mapping_size:.2f}" '
431
+ f'font-family="{mapping_font_style}" text-anchor="end">')
432
+ define.extend(maps)
433
+ define.append(' </g>')
434
+ svg.append(f' <use xlink:href="#{uid}-mapping" fill="{map_fill}"/>')
435
+ mask.append(f' <use xlink:href="#{uid}-mapping" stroke-width="{stroke_width_m:.2f}"/>\n'
436
+ ' </g>')
437
+ elif svg: # no maps but atoms
438
+ mask.append(' </g>')
439
+ return svg, define, mask
440
+
441
+
442
+ class DepictReaction:
443
+ __slots__ = ()
444
+
445
+ def depict(self: 'ReactionContainer', *, width=None, height=None, clean2d: bool = True) -> str:
446
+ """
447
+ Depict reaction in SVG format.
448
+
449
+ :param width: set svg width param. by default auto-calculated.
450
+ :param height: set svg height param. by default auto-calculated.
451
+ :param clean2d: calculate coordinates if necessary.
452
+ """
453
+ if not self._arrow:
454
+ if clean2d:
455
+ for m in self.molecules():
456
+ if len(m) > 1:
457
+ min_x = min(a.x for _, a in m.atoms())
458
+ max_x = max(a.x for _, a in m.atoms())
459
+ min_y = min(a.y for _, a in m.atoms())
460
+ max_y = max(a.y for _, a in m.atoms())
461
+ if max_y - min_y < .01 and max_x - min_x < 0.01:
462
+ m.clean2d()
463
+ self.fix_positions()
464
+
465
+ r_atoms = []
466
+ r_bonds = []
467
+ r_defines = []
468
+ r_masks = []
469
+ r_uids = []
470
+ r_max_x = r_max_y = r_min_y = 0
471
+ for m in self.molecules():
472
+ atoms, bonds, define, masks, uid, min_x, min_y, max_x, max_y = m.depict(clean2d=False, _embedding=True)
473
+ r_atoms.append(atoms)
474
+ r_bonds.append(bonds)
475
+ r_defines.append(define)
476
+ r_masks.append(masks)
477
+ r_uids.append(uid)
478
+ if max_x > r_max_x:
479
+ r_max_x = max_x
480
+ if max_y > r_max_y:
481
+ r_max_y = max_y
482
+ if min_y < r_min_y:
483
+ r_min_y = min_y
484
+
485
+ font_size = _render_config['font_size']
486
+ font125 = 1.25 * font_size
487
+ _width = r_max_x + 4.0 * font_size
488
+ _height = r_max_y - r_min_y + 2.5 * font_size
489
+ viewbox_x = -font125
490
+ viewbox_y = -r_max_y - font125
491
+
492
+ if width is None:
493
+ width = f'{_width:.2f}cm'
494
+ if height is None:
495
+ height = f'{_height:.2f}cm'
496
+
497
+ svg = [f'<svg width="{width}" height="{height}" '
498
+ f'viewBox="{viewbox_x:.2f} {viewbox_y:.2f} {_width:.2f} {_height:.2f}" '
499
+ 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">\n'
500
+ ' <defs>\n <marker id="arrow" markerWidth="10" markerHeight="10" '
501
+ 'refX="0" refY="3" orient="auto">\n <path d="M0,0 L0,6 L9,3"/>\n </marker>\n </defs>\n'
502
+ f' <line x1="{self._arrow[0]:.2f}" y1="0" x2="{self._arrow[1]:.2f}" y2="0" '
503
+ 'fill="none" stroke="black" stroke-width=".04" marker-end="url(#arrow)"/>']
504
+
505
+ sings_plus = self._signs
506
+ if sings_plus:
507
+ svg.append(f' <g fill="none" stroke="black" stroke-width=".04">')
508
+ for x in sings_plus:
509
+ svg.append(f' <line x1="{x + .35:.2f}" y1="0" x2="{x + .65:.2f}" y2="0"/>')
510
+ svg.append(f' <line x1="{x + .5:.2f}" y1="0.15" x2="{x + .5:.2f}" y2="-0.15"/>')
511
+ svg.append(' </g>')
512
+
513
+ for atoms, bonds, define, masks, uid in zip(r_atoms, r_bonds, r_defines, r_masks, r_uids):
514
+ svg.extend(_graph_svg(atoms, bonds, define, masks, uid, viewbox_x, viewbox_y, _width, _height))
515
+ svg.append('</svg>')
516
+ return '\n'.join(svg)
517
+
518
+ @cached_method
519
+ def _repr_svg_(self):
520
+ return self.depict()
521
+
522
+
523
+ __all__ = ['DepictMolecule', 'DepictReaction', 'depict_settings']
@@ -0,0 +1,48 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2021-2023 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # Copyright 2021 Aleksandr Sizov <murkyrussian@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 TYPE_CHECKING
21
+ from .linear import *
22
+ from .morgan import *
23
+
24
+
25
+ if TYPE_CHECKING:
26
+ from chython import MoleculeContainer, CGRContainer
27
+
28
+
29
+ class Fingerprints(LinearFingerprint, MorganFingerprint):
30
+ __slots__ = ()
31
+
32
+ @property
33
+ def _atom_identifiers(self: 'MoleculeContainer'):
34
+ return {idx: hash((atom.isotope or 0, atom.atomic_number, atom.charge, atom.is_radical))
35
+ for idx, atom in self.atoms()}
36
+
37
+
38
+ class FingerprintsCGR(LinearFingerprint, MorganFingerprint):
39
+ __slots__ = ()
40
+
41
+ @property
42
+ def _atom_identifiers(self: 'CGRContainer'):
43
+ return {idx: hash((atom.isotope or 0, atom.atomic_number, atom.charge, atom.p_charge,
44
+ atom.is_radical, atom.p_is_radical))
45
+ for idx, atom in self._atoms.items()}
46
+
47
+
48
+ __all__ = ['Fingerprints', 'FingerprintsCGR']