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,300 @@
1
+ # -*- coding: utf-8 -*-
2
+ # cython: language_level=3
3
+ #
4
+ # Copyright 2021-2025 Ramil Nugmanov <nougmanoff@protonmail.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
+ cimport cython
21
+ from cpython.mem cimport PyMem_Malloc, PyMem_Free
22
+ from libc.math cimport ldexp
23
+
24
+ from chython.containers import MoleculeContainer
25
+ from chython.containers.bonds import Bond
26
+ from chython.periodictable import (H, He, Li, Be, B, C, N, O, F, Ne, Na, Mg, Al, Si, P, S, Cl, Ar, K, Ca, Sc, Ti, V, Cr,
27
+ Mn, Fe, Co, Ni, Cu, Zn, Ga, Ge, As, Se, Br, Kr, Rb, Sr, Y, Zr, Nb, Mo, Tc, Ru, Rh,
28
+ Pd, Ag, Cd, In, Sn, Sb, Te, I, Xe, Cs, Ba, La, Ce, Pr, Nd, Pm, Sm, Eu, Gd, Tb, Dy,
29
+ Ho, Er, Tm, Yb, Lu, Hf, Ta, W, Re, Os, Ir, Pt, Au, Hg, Tl, Pb, Bi, Po, At, Rn, Fr,
30
+ Ra, Ac, Th, Pa, U, Np, Pu, Am, Cm, Bk, Cf, Es, Fm, Md, No, Lr, Rf, Db, Sg, Bh, Hs,
31
+ Mt, Ds, Rg, Cn, Nh, Fl, Mc, Lv, Ts, Og)
32
+ from chython.periodictable.base.vector import Vector
33
+
34
+
35
+ # Format specification::
36
+ #
37
+ # Big endian bytes order
38
+ # 8 bit - 0x02 (current format specification)
39
+ # 12 bit - number of atoms
40
+ # 12 bit - cis/trans stereo block size
41
+ #
42
+ # Atom block 9 bytes (repeated):
43
+ # 12 bit - atom number
44
+ # 4 bit - number of neighbors
45
+ # 2 bit tetrahedron sign (00 - not stereo, 10 or 11 - has stereo)
46
+ # 2 bit - allene sign
47
+ # 5 bit - isotope (00000 - not specified, over = isotope - common_isotope + 16)
48
+ # 7 bit - atomic number (<=118)
49
+ # 32 bit - XY float16 coordinates
50
+ # 3 bit - hydrogens (0-7). Note: 7 == None
51
+ # 4 bit - charge (charge + 4. possible range -4 - 4)
52
+ # 1 bit - radical state
53
+ # Connection table: flatten list of neighbors. neighbors count stored in atom block.
54
+ # For example CC(=O)O - {1: [2], 2: [1, 3, 4], 3: [2], 4: [2]} >> [2, 1, 3, 4, 2, 2].
55
+ # Repeated block (equal to bonds count).
56
+ # 24 bit - paired 12 bit numbers.
57
+ # Bonds order block 3 bit per bond zero-padded to full byte at the end.
58
+ # Cis/trans data block (repeated):
59
+ # 24 bit - atoms pair
60
+ # 7 bit - zero padding. in future can be used for extra bond-level stereo, like atropoisomers.
61
+ # 1 bit - sign
62
+
63
+ @cython.nonecheck(False)
64
+ @cython.boundscheck(False)
65
+ @cython.cdivision(True)
66
+ @cython.wraparound(False)
67
+ def unpack(const unsigned char[::1] data not None):
68
+ cdef unsigned char a, b, c, d, isotope, atomic_number, neighbors_count, s = 0, version, stereo, hydrogens
69
+ cdef unsigned char *neighbors, *orders
70
+ cdef unsigned short atoms_count, bonds_count = 0, cis_trans_count, order_count
71
+ cdef unsigned short i, j, k = 0, n, m, buffer_b, shift = 0
72
+ cdef unsigned short *mapping, *connections
73
+ cdef unsigned int size, atoms_shift = 4, bonds_shift, order_shift, cis_trans_shift
74
+ cdef unsigned char[4096] seen
75
+
76
+ cdef object py_mol, py_bond, py_n, py_m, py_atom, py_nan_bool, py_vector
77
+ cdef dict py_atoms, py_bonds, py_ngb
78
+ cdef list py_cis_trans
79
+
80
+ # read header
81
+ version = data[0]
82
+ a, b, c = data[1], data[2], data[3]
83
+ atoms_count = a << 4| b >> 4
84
+ cis_trans_count = (b & 0x0f) << 8 | c
85
+
86
+ # allocate memory
87
+ neighbors = <unsigned char*> PyMem_Malloc(atoms_count * sizeof(unsigned char))
88
+ mapping = <unsigned short*> PyMem_Malloc(atoms_count * sizeof(unsigned short))
89
+
90
+ if not neighbors or not mapping:
91
+ raise MemoryError()
92
+
93
+ py_mol = MoleculeContainer()
94
+ py_atoms = py_mol._atoms
95
+ py_bonds = py_mol._bonds
96
+ py_cis_trans = []
97
+
98
+ # unpack atom block to separate attributes arrays
99
+ for i in range(atoms_count):
100
+ a, b = data[atoms_shift], data[atoms_shift + 1]
101
+ mapping[i] = n = a << 4 | b >> 4
102
+ seen[n] = 0 # erase random value
103
+ neighbors[i] = neighbors_count = b & 0x0f
104
+ bonds_count += neighbors_count
105
+
106
+ a, b = data[atoms_shift + 2], data[atoms_shift + 3]
107
+ stereo = a >> 4
108
+ if stereo == 0:
109
+ py_nan_bool = None
110
+ elif stereo == 0b0010:
111
+ py_nan_bool = False
112
+ elif stereo == 0b0011:
113
+ py_nan_bool = True
114
+ elif stereo == 0b1000:
115
+ py_nan_bool = False
116
+ else: # if stereo == 0b1100:
117
+ py_nan_bool = True
118
+
119
+ atomic_number = b & 0x7f
120
+ py_atom = object.__new__(elements[atomic_number])
121
+ py_n = n
122
+ py_atoms[py_n] = py_atom
123
+ py_bonds[py_n] = {}
124
+
125
+ py_atom._stereo = py_nan_bool
126
+
127
+ isotope = (a & 0x0f) << 1 | b >> 7
128
+ if isotope:
129
+ py_atom._isotope = common_isotopes[atomic_number] + isotope
130
+ else:
131
+ py_atom._isotope = None
132
+
133
+ py_vector = object.__new__(Vector)
134
+ a, b = data[atoms_shift + 4], data[atoms_shift + 5]
135
+ py_vector.x = double_from_bytes(a, b)
136
+ a, b = data[atoms_shift + 6], data[atoms_shift + 7]
137
+ py_vector.y = double_from_bytes(a, b)
138
+ py_atom._xy = py_vector
139
+
140
+ a = data[atoms_shift + 8]
141
+ hydrogens = a >> 5
142
+ if hydrogens == 7:
143
+ py_atom._implicit_hydrogens = None
144
+ else:
145
+ py_atom._implicit_hydrogens = hydrogens
146
+
147
+ py_atom._charge = ((a >> 1) & 0x0f) - 4
148
+ if a & 0x01:
149
+ py_atom._is_radical = True
150
+ else:
151
+ py_atom._is_radical = False
152
+ atoms_shift += 9
153
+
154
+ # calculate bonds count and pack sections
155
+ bonds_count /= 2
156
+
157
+ if version == 2:
158
+ order_count = bonds_count * 3
159
+ if order_count % 8:
160
+ order_count = order_count / 8 + 1
161
+ else:
162
+ order_count /= 8
163
+ else: # if version == 0:
164
+ order_count = bonds_count / 5
165
+ if bonds_count % 5:
166
+ order_count += 1
167
+ order_count *= 2
168
+
169
+ bonds_shift = atoms_shift
170
+ order_shift = bonds_shift + 3 * bonds_count
171
+ cis_trans_shift = order_count + order_shift
172
+ size = cis_trans_shift + 4 * cis_trans_count
173
+
174
+ if bonds_count:
175
+ # keep 4 extra cells for padding
176
+ orders = <unsigned char*> PyMem_Malloc((bonds_count + 4) * sizeof(unsigned char))
177
+ connections = <unsigned short*> PyMem_Malloc(2 * bonds_count * sizeof(unsigned short))
178
+
179
+ # connection table is bidirected
180
+ for i in range(0, 2 * bonds_count, 2):
181
+ a, b, c = data[bonds_shift], data[bonds_shift + 1], data[bonds_shift + 2]
182
+ connections[i] = a << 4| b >> 4
183
+ connections[i + 1] = (b & 0x0f) << 8 | c
184
+ bonds_shift += 3
185
+
186
+ # collect flat bond order list
187
+ i = 0
188
+ if version == 2:
189
+ for j in range(order_shift, cis_trans_shift):
190
+ # 3 3 2 | 1 3 3 1 | 2 3 3
191
+ a = data[j]
192
+ if s == 1:
193
+ orders[i] = buffer_b | a >> 7
194
+ orders[i + 1] = (a >> 4) & 0x7
195
+ orders[i + 2] = (a >> 1) & 0x7
196
+ buffer_b = (a & 1) << 2
197
+ s = 2
198
+ i += 3
199
+ elif s == 2:
200
+ orders[i] = buffer_b | a >> 6
201
+ orders[i + 1] = (a >> 3) & 0x7
202
+ orders[i + 2] = a & 0x7
203
+ i += 3
204
+ s = 0
205
+ else:
206
+ orders[i] = a >> 5
207
+ orders[i + 1] = (a >> 2) & 0x7
208
+ buffer_b = (a & 0x3) << 1
209
+ s = 1
210
+ i += 2
211
+ else: # if version == 0:
212
+ for j in range(order_shift, cis_trans_shift, 2):
213
+ # 0 3 3 1 | 2 3 3
214
+ a, b = data[j], data[j + 1]
215
+ orders[i] = a >> 4
216
+ orders[i + 1] = (a >> 1) & 0x7
217
+ orders[i + 2] = (a & 0x1) << 2 | b >> 6
218
+ orders[i + 3] = (b >> 3) & 0x7
219
+ orders[i + 4] = b & 0x7
220
+ i += 5
221
+
222
+ for i in range(atoms_count):
223
+ n = mapping[i]
224
+ py_n = n # shared py int obj
225
+
226
+ py_ngb = py_bonds[py_n]
227
+ seen[n] = 1
228
+
229
+ neighbors_count = neighbors[i]
230
+ for j in range(shift, shift + neighbors_count):
231
+ m = connections[j]
232
+ py_m = m
233
+ if seen[m]: # bond partially exists. need back-connection.
234
+ py_ngb[py_m] = py_bonds[py_m][py_n]
235
+ else:
236
+ py_bond = object.__new__(Bond)
237
+ py_bond._order = orders[k] + 1
238
+ py_bond._stereo = None
239
+ py_ngb[py_m] = py_bond
240
+ k += 1
241
+ shift += neighbors_count
242
+
243
+ PyMem_Free(orders)
244
+ PyMem_Free(connections)
245
+
246
+ if cis_trans_count:
247
+ for i in range(cis_trans_count):
248
+ a, b = data[cis_trans_shift], data[cis_trans_shift + 1]
249
+ c, d = data[cis_trans_shift + 2], data[cis_trans_shift + 3]
250
+ py_n = a << 4 | b >> 4
251
+ py_m = (b & 0x0f) << 8 | c
252
+ if d:
253
+ py_cis_trans.append((py_n, py_m, True))
254
+ else:
255
+ py_cis_trans.append((py_n, py_m, False))
256
+ cis_trans_shift += 4
257
+
258
+ PyMem_Free(neighbors)
259
+ PyMem_Free(mapping)
260
+ return py_mol, py_cis_trans, size
261
+
262
+
263
+ cdef short[119] common_isotopes
264
+ common_isotopes[:] = [0, -15, -12, -9, -7, -5, -4, -2, 0, 3, 4, 7, 8, 11, 12, 15, 16, 19, 24, 23, 24, 29,
265
+ 32, 35, 36, 39, 40, 43, 43, 48, 49, 54, 57, 59, 63, 64, 68, 69, 72, 73, 75, 77,
266
+ 80, 82, 85, 87, 90, 92, 96, 99, 103, 106, 112, 111, 115, 117, 121, 123, 124, 125,
267
+ 128, 129, 134, 136, 141, 143, 147, 149, 151, 153, 157, 159, 162, 165, 168, 170,
268
+ 174, 176, 179, 181, 185, 188, 191, 193, 193, 194, 206, 207, 210, 211, 216, 215,
269
+ 222, 221, 228, 227, 231, 231, 235, 236, 241, 242, 243, 244, 245, 254, 253, 254,
270
+ 254, 262, 265, 265, 269, 262, 273, 273, 277, 281, 278]
271
+
272
+ cdef list elements
273
+ elements = [None, H, He, Li, Be, B, C, N, O, F, Ne, Na, Mg, Al, Si, P, S, Cl, Ar, K, Ca, Sc, Ti, V, Cr, Mn, Fe, Co,
274
+ Ni, Cu, Zn, Ga, Ge, As, Se, Br, Kr, Rb, Sr, Y, Zr, Nb, Mo, Tc, Ru, Rh, Pd, Ag, Cd, In, Sn, Sb, Te, I, Xe,
275
+ Cs, Ba, La, Ce, Pr, Nd, Pm, Sm, Eu, Gd, Tb, Dy, Ho, Er, Tm, Yb, Lu, Hf, Ta, W, Re, Os, Ir, Pt, Au, Hg,
276
+ Tl, Pb, Bi, Po, At, Rn, Fr, Ra, Ac, Th, Pa, U, Np, Pu, Am, Cm, Bk, Cf, Es, Fm, Md, No, Lr, Rf, Db, Sg,
277
+ Bh, Hs, Mt, Ds, Rg, Cn, Nh, Fl, Mc, Lv, Ts, Og]
278
+
279
+
280
+ cdef double double_from_bytes(unsigned char a, unsigned char b):
281
+ cdef bint sign
282
+ cdef int e
283
+ cdef unsigned int f
284
+ cdef double x
285
+
286
+ sign = a >> 7
287
+ e = (a >> 2) & 0x1f
288
+ f = ((a & 0x03) << 8) | b
289
+
290
+ x = f / 1024.
291
+ if e:
292
+ x += 1.
293
+ e -= 15
294
+ else:
295
+ e = -14
296
+
297
+ x = ldexp(x, e)
298
+ if sign:
299
+ return -x
300
+ return x
@@ -0,0 +1,255 @@
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 typing import Optional, Tuple, Union, List, Set
20
+
21
+
22
+ class Bond:
23
+ __slots__ = ('_order', '_in_ring', '_stereo')
24
+
25
+ def __init__(self, order: int, *, stereo: Optional[bool] = None):
26
+ if not isinstance(order, int):
27
+ raise TypeError('invalid order value')
28
+ elif order not in (1, 4, 2, 3, 8):
29
+ raise ValueError('order should be from [1, 2, 3, 4, 8]')
30
+ self._order = order
31
+ self._stereo = stereo
32
+
33
+ def __eq__(self, other):
34
+ if isinstance(other, int):
35
+ return self.order == other
36
+ elif isinstance(other, Bond):
37
+ return self.order == other.order
38
+ return False
39
+
40
+ def __repr__(self):
41
+ return f'{self.__class__.__name__}({self.order})'
42
+
43
+ def __int__(self):
44
+ """
45
+ Bond order.
46
+ """
47
+ return self.order
48
+
49
+ def __hash__(self):
50
+ """
51
+ Bond order. Used in Morgan atoms ordering.
52
+ """
53
+ return self.order
54
+
55
+ @property
56
+ def order(self) -> int:
57
+ return self._order
58
+
59
+ @property
60
+ def stereo(self) -> Optional[bool]:
61
+ return self._stereo
62
+
63
+ @property
64
+ def in_ring(self) -> bool:
65
+ return self._in_ring
66
+
67
+ def copy(self, full=False, stereo=False) -> 'Bond':
68
+ copy = object.__new__(self.__class__)
69
+ copy._order = self.order
70
+ if full:
71
+ copy._stereo = self.stereo
72
+ copy._in_ring = self.in_ring
73
+ else:
74
+ if stereo:
75
+ copy._stereo = self.stereo
76
+ else:
77
+ copy._stereo = None
78
+ return copy
79
+
80
+ def __copy__(self):
81
+ return self.copy()
82
+
83
+
84
+ class DynamicBond:
85
+ __slots__ = ('_order', '_p_order')
86
+
87
+ def __init__(self, order=None, p_order=None):
88
+ if order is None:
89
+ if not isinstance(p_order, int):
90
+ raise TypeError('p_order should be int type')
91
+ elif not isinstance(order, int):
92
+ raise TypeError('order should be int type or None')
93
+ elif p_order is not None and not isinstance(p_order, int):
94
+ raise TypeError('p_order should be int type or None')
95
+
96
+ if order not in (1, 4, 2, 3, None, 8) or p_order not in (1, 4, 2, 3, None, 8):
97
+ raise ValueError('order or p_order should be from [1, 2, 3, 4, 8]')
98
+
99
+ self._order = order
100
+ self._p_order = p_order
101
+
102
+ def __eq__(self, other):
103
+ if isinstance(other, DynamicBond):
104
+ return self.order == other.order and self.p_order == other.p_order
105
+ return False
106
+
107
+ def __repr__(self):
108
+ return f'{self.__class__.__name__}({self.order}, {self.p_order})'
109
+
110
+ def __int__(self):
111
+ """
112
+ Hash of bond orders.
113
+ """
114
+ return hash(self)
115
+
116
+ def __hash__(self):
117
+ """
118
+ Hash of bond orders.
119
+ """
120
+ return hash((self.order or 0, self.p_order or 0))
121
+
122
+ @property
123
+ def is_dynamic(self) -> bool:
124
+ """
125
+ Bond has dynamic features
126
+ """
127
+ return self.order != self.p_order
128
+
129
+ @property
130
+ def order(self) -> Optional[int]:
131
+ return self._order
132
+
133
+ @property
134
+ def p_order(self) -> Optional[int]:
135
+ return self._p_order
136
+
137
+ def copy(self) -> 'DynamicBond':
138
+ copy = object.__new__(self.__class__)
139
+ copy._order = self.order
140
+ copy._p_order = self.p_order
141
+ return copy
142
+
143
+ def __copy__(self):
144
+ return self.copy()
145
+
146
+ @classmethod
147
+ def from_bond(cls, bond: 'Bond') -> 'DynamicBond':
148
+ if not isinstance(bond, Bond):
149
+ raise TypeError('Bond expected')
150
+ copy = object.__new__(cls)
151
+ copy._order = copy._p_order = bond.order
152
+ return copy
153
+
154
+
155
+ class QueryBond:
156
+ __slots__ = ('_order', '_in_ring', '_stereo')
157
+
158
+ def __init__(self, order: Union[int, List[int], Set[int], Tuple[int, ...]],
159
+ in_ring: Optional[bool] = None, stereo: Optional[bool] = None):
160
+ if isinstance(order, (list, tuple, set)):
161
+ if not all(isinstance(x, int) for x in order):
162
+ raise TypeError('invalid order value')
163
+ if any(x not in (1, 4, 2, 3, 8) for x in order):
164
+ raise ValueError('order should be from [1, 2, 3, 4, 8]')
165
+ order = tuple(sorted(set(order)))
166
+ elif isinstance(order, int):
167
+ if order not in (1, 4, 2, 3, 8):
168
+ raise ValueError('order should be from [1, 2, 3, 4, 8]')
169
+ order = (order,)
170
+ else:
171
+ raise TypeError('invalid order value')
172
+ if in_ring is not None and not isinstance(in_ring, bool):
173
+ raise TypeError('in_ring mark should be boolean or None')
174
+ self._order = order
175
+ self._in_ring = in_ring
176
+ self.stereo = stereo
177
+
178
+ def __eq__(self, other):
179
+ if isinstance(other, Bond):
180
+ if self.in_ring is not None:
181
+ if self.in_ring != other.in_ring:
182
+ return False
183
+ return other.order in self.order
184
+ elif isinstance(other, QueryBond):
185
+ return self.order == other.order and self.in_ring == other.in_ring
186
+ elif isinstance(other, int):
187
+ return other in self.order
188
+ return False
189
+
190
+ def __repr__(self):
191
+ return f'{self.__class__.__name__}({self.order}, {self.in_ring})'
192
+
193
+ def __int__(self):
194
+ """
195
+ Simple bond order or hash of sorted tuple of orders.
196
+ """
197
+ if len(self.order) == 1:
198
+ return self.order[0]
199
+ return hash(self.order)
200
+
201
+ def __hash__(self):
202
+ """
203
+ Hash of orders and cycle mark. Used in Morgan atoms ordering.
204
+ """
205
+ return hash((self.order, self.in_ring))
206
+
207
+ @property
208
+ def order(self) -> Tuple[int, ...]:
209
+ return self._order
210
+
211
+ @property
212
+ def in_ring(self) -> Optional[bool]:
213
+ return self._in_ring
214
+
215
+ @property
216
+ def stereo(self) -> Optional[bool]:
217
+ return self._stereo
218
+
219
+ @stereo.setter
220
+ def stereo(self, value):
221
+ if value is not None and not isinstance(value, bool):
222
+ raise TypeError('stereo mark should be boolean or None')
223
+ self._stereo = value
224
+
225
+ def copy(self, full=False) -> 'QueryBond':
226
+ copy = object.__new__(self.__class__)
227
+ copy._order = self.order
228
+ if full:
229
+ copy._in_ring = self.in_ring
230
+ copy._stereo = self.stereo
231
+ else:
232
+ copy._in_ring = copy._stereo = None
233
+ return copy
234
+
235
+ def __copy__(self):
236
+ return self.copy()
237
+
238
+ @classmethod
239
+ def from_bond(cls, bond: 'Bond', stereo=False, in_ring=False) -> 'QueryBond':
240
+ if not isinstance(bond, Bond):
241
+ raise TypeError('Bond expected')
242
+ copy = object.__new__(cls)
243
+ copy._order = (bond.order,)
244
+ if in_ring:
245
+ copy._in_ring = bond.in_ring
246
+ else:
247
+ copy._in_ring = None
248
+ if stereo:
249
+ copy._stereo = bond.stereo
250
+ else:
251
+ copy._stereo = None
252
+ return copy
253
+
254
+
255
+ __all__ = ['Bond', 'DynamicBond', 'QueryBond']
@@ -0,0 +1,115 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2017-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 functools import cached_property
20
+ from typing import Dict, Iterator, Tuple, Optional, Collection
21
+ from .bonds import DynamicBond
22
+ from ..algorithms.fingerprints import FingerprintsCGR
23
+ from ..algorithms.isomorphism import Isomorphism
24
+ from ..algorithms.morgan import Morgan
25
+ from ..algorithms.rings import Rings
26
+ from ..algorithms.smiles import CGRSmiles
27
+ from ..periodictable import DynamicElement
28
+
29
+
30
+ class CGRContainer(CGRSmiles, Morgan, Rings, Isomorphism, FingerprintsCGR):
31
+ __slots__ = ('_atoms', '_bonds', '__dict__')
32
+ _atoms: Dict[int, DynamicElement]
33
+ _bonds: Dict[int, Dict[int, DynamicBond]]
34
+
35
+ def __init__(self):
36
+ self._atoms = {}
37
+ self._bonds = {}
38
+
39
+ def atoms(self) -> Iterator[Tuple[int, DynamicElement]]:
40
+ return iter(self._atoms.items())
41
+
42
+ def bonds(self) -> Iterator[Tuple[int, int, DynamicBond]]:
43
+ """
44
+ Iterate other all bonds
45
+ """
46
+ seen = set()
47
+ for n, m_bond in self._bonds.items():
48
+ seen.add(n)
49
+ for m, bond in m_bond.items():
50
+ if m not in seen:
51
+ yield n, m, bond
52
+
53
+ @cached_property
54
+ def center_atoms(self) -> Tuple[int, ...]:
55
+ """ Get list of atoms of reaction center (atoms with dynamic: bonds, charges, radicals).
56
+ """
57
+ center = {n for n, a in self._atoms.items() if a.is_dynamic}
58
+ center.update(n for n, m_bond in self._bonds.items() if any(bond.is_dynamic for bond in m_bond.values()))
59
+ return tuple(center)
60
+
61
+ def substructure(self, atoms) -> 'CGRContainer':
62
+ """
63
+ Create substructure containing atoms from atoms list
64
+
65
+ :param atoms: list of atoms numbers of substructure
66
+ """
67
+ atoms = set(atoms)
68
+ sa = self._atoms
69
+ sb = self._bonds
70
+
71
+ sub = object.__new__(self.__class__)
72
+ sub._atoms = {n: sa[n].copy() for n in atoms}
73
+
74
+ sub._bonds = cb = {}
75
+ for n in atoms:
76
+ cb[n] = cbn = {}
77
+ for m, bond in sb[n].items():
78
+ if m in cb: # bond partially exists. need back-connection.
79
+ cbn[m] = cb[m][n]
80
+ elif m in atoms:
81
+ cbn[m] = bond.copy()
82
+ return sub
83
+
84
+ def augmented_substructure(self, atoms, deep: int = 1):
85
+ atoms = set(atoms)
86
+ bonds = self._bonds
87
+
88
+ for _ in range(deep):
89
+ n = {y for x in atoms for y in bonds[x]} | atoms
90
+ if n == atoms:
91
+ break
92
+ atoms = n
93
+ return self.substructure(atoms)
94
+
95
+ def get_mapping(self, other: 'CGRContainer', /, *, automorphism_filter: bool = True,
96
+ searching_scope: Optional[Collection[int]] = None):
97
+ """
98
+ Get self to other CGR substructure mapping generator.
99
+
100
+ :param other: CGR
101
+ :param automorphism_filter: skip matches to the same atoms.
102
+ :param searching_scope: substructure atoms list to localize isomorphism.
103
+ """
104
+ if isinstance(other, CGRContainer):
105
+ return self._get_mapping(other, automorphism_filter=automorphism_filter, searching_scope=searching_scope)
106
+ raise TypeError('CGRContainer expected')
107
+
108
+ def __iter__(self):
109
+ return iter(self._atoms)
110
+
111
+ def __len__(self):
112
+ return len(self._atoms)
113
+
114
+
115
+ __all__ = ['CGRContainer']