chython 2.19__cp313-cp313-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 (175) hide show
  1. chython/__init__.py +35 -0
  2. chython/_functions.py +95 -0
  3. chython/_java.py +57 -0
  4. chython/algorithms/__init__.py +18 -0
  5. chython/algorithms/_isomorphism.cp313-win_amd64.pyd +0 -0
  6. chython/algorithms/_isomorphism.pyx +189 -0
  7. chython/algorithms/_rings.cp313-win_amd64.pyd +0 -0
  8. chython/algorithms/_rings.pyx +478 -0
  9. chython/algorithms/aromatics/__init__.py +27 -0
  10. chython/algorithms/aromatics/_rules.py +124 -0
  11. chython/algorithms/aromatics/kekule.py +532 -0
  12. chython/algorithms/aromatics/test/__init__.py +18 -0
  13. chython/algorithms/aromatics/test/test_kekule.py +160 -0
  14. chython/algorithms/aromatics/test/test_thiele.py +263 -0
  15. chython/algorithms/aromatics/thiele.py +222 -0
  16. chython/algorithms/calculate2d/__init__.py +23 -0
  17. chython/algorithms/calculate2d/clean2d.js +1 -0
  18. chython/algorithms/calculate2d/molecule.py +191 -0
  19. chython/algorithms/calculate2d/reaction.py +75 -0
  20. chython/algorithms/conformers.py +106 -0
  21. chython/algorithms/depict.py +596 -0
  22. chython/algorithms/fingerprints/__init__.py +43 -0
  23. chython/algorithms/fingerprints/linear.py +202 -0
  24. chython/algorithms/fingerprints/morgan.py +132 -0
  25. chython/algorithms/fingerprints/test/__init__.py +18 -0
  26. chython/algorithms/fingerprints/test/test_linear.py +127 -0
  27. chython/algorithms/fingerprints/test/test_morgan.py +79 -0
  28. chython/algorithms/groups/__init__.py +212 -0
  29. chython/algorithms/groups/_functional.py +290 -0
  30. chython/algorithms/groups/_oxidations.py +70 -0
  31. chython/algorithms/groups/_protective.py +964 -0
  32. chython/algorithms/groups/_reactions.py +809 -0
  33. chython/algorithms/groups/_reductions.py +88 -0
  34. chython/algorithms/groups/_transformations.py +278 -0
  35. chython/algorithms/groups/test/__init__.py +18 -0
  36. chython/algorithms/groups/test/test_deprotection.py +108 -0
  37. chython/algorithms/groups/test/test_reactions.py +377 -0
  38. chython/algorithms/isomorphism.py +664 -0
  39. chython/algorithms/mapping/__init__.py +188 -0
  40. chython/algorithms/mapping/_groups.py +94 -0
  41. chython/algorithms/mapping/_reactions.py +72 -0
  42. chython/algorithms/mapping/attention.py +266 -0
  43. chython/algorithms/mapping/reconstruct.py +346 -0
  44. chython/algorithms/mcs.py +201 -0
  45. chython/algorithms/morgan.py +77 -0
  46. chython/algorithms/rings.py +178 -0
  47. chython/algorithms/smiles.py +597 -0
  48. chython/algorithms/standardize/__init__.py +30 -0
  49. chython/algorithms/standardize/_groups.py +958 -0
  50. chython/algorithms/standardize/_isomers.py +330 -0
  51. chython/algorithms/standardize/_metal_organics.py +189 -0
  52. chython/algorithms/standardize/_reagents.py +44 -0
  53. chython/algorithms/standardize/_salts.py +68 -0
  54. chython/algorithms/standardize/molecule.py +475 -0
  55. chython/algorithms/standardize/reaction.py +384 -0
  56. chython/algorithms/standardize/resonance.py +191 -0
  57. chython/algorithms/standardize/salts.py +128 -0
  58. chython/algorithms/standardize/saturation.py +373 -0
  59. chython/algorithms/standardize/test/__init__.py +18 -0
  60. chython/algorithms/standardize/test/test_canonicalize.py +66 -0
  61. chython/algorithms/standardize/test/test_groups.py +146 -0
  62. chython/algorithms/standardize/test/test_isomers.py +198 -0
  63. chython/algorithms/stereo.py +1215 -0
  64. chython/algorithms/tautomers/__init__.py +167 -0
  65. chython/algorithms/tautomers/_acid.py +59 -0
  66. chython/algorithms/tautomers/_base.py +116 -0
  67. chython/algorithms/tautomers/_keto_enol.py +107 -0
  68. chython/algorithms/tautomers/acid_base.py +208 -0
  69. chython/algorithms/tautomers/heteroarenes.py +103 -0
  70. chython/algorithms/tautomers/keto_enol.py +143 -0
  71. chython/algorithms/tautomers/test/__init__.py +18 -0
  72. chython/algorithms/tautomers/test/test_tautomers.py +80 -0
  73. chython/algorithms/test/__init__.py +18 -0
  74. chython/algorithms/test/test_isomorphism.py +58 -0
  75. chython/algorithms/test/test_smiles.py +76 -0
  76. chython/algorithms/x3dom.py +350 -0
  77. chython/containers/__init__.py +48 -0
  78. chython/containers/_pack_v2.cp313-win_amd64.pyd +0 -0
  79. chython/containers/_pack_v2.pyx +271 -0
  80. chython/containers/_unpack_v0v2.cp313-win_amd64.pyd +0 -0
  81. chython/containers/_unpack_v0v2.pyx +302 -0
  82. chython/containers/bonds.py +255 -0
  83. chython/containers/cgr.py +115 -0
  84. chython/containers/chimera.py +63 -0
  85. chython/containers/graph.py +199 -0
  86. chython/containers/molecule.py +817 -0
  87. chython/containers/query.py +71 -0
  88. chython/containers/rdkit.py +183 -0
  89. chython/containers/reaction.py +324 -0
  90. chython/containers/test/__init__.py +18 -0
  91. chython/containers/test/test_molecule.py +117 -0
  92. chython/containers/test/test_pack.py +60 -0
  93. chython/containers/test/test_rdkit.py +70 -0
  94. chython/containers/test/test_reaction.py +40 -0
  95. chython/exceptions.py +114 -0
  96. chython/files/MRVrw.py +532 -0
  97. chython/files/PDBrw.py +255 -0
  98. chython/files/RDFrw.py +309 -0
  99. chython/files/SDFrw.py +221 -0
  100. chython/files/__init__.py +30 -0
  101. chython/files/_convert.py +207 -0
  102. chython/files/_mapping.py +117 -0
  103. chython/files/_xyz.cp313-win_amd64.pyd +0 -0
  104. chython/files/_xyz.pyx +74 -0
  105. chython/files/daylight/__init__.py +24 -0
  106. chython/files/daylight/parser.py +157 -0
  107. chython/files/daylight/smarts.py +107 -0
  108. chython/files/daylight/smiles.py +260 -0
  109. chython/files/daylight/test/__init__.py +18 -0
  110. chython/files/daylight/test/test_daylight_smarts.py +289 -0
  111. chython/files/daylight/test/test_daylight_smiles.py +101 -0
  112. chython/files/daylight/test/test_parser.py +118 -0
  113. chython/files/daylight/test/test_tokenize.py +77 -0
  114. chython/files/daylight/tokenize.py +376 -0
  115. chython/files/libinchi/__init__.py +22 -0
  116. chython/files/libinchi/libinchi.dll +0 -0
  117. chython/files/libinchi/wrapper.py +554 -0
  118. chython/files/mdl/__init__.py +25 -0
  119. chython/files/mdl/emol.py +251 -0
  120. chython/files/mdl/erxn.py +67 -0
  121. chython/files/mdl/mol.py +186 -0
  122. chython/files/mdl/read.py +223 -0
  123. chython/files/mdl/rxn.py +67 -0
  124. chython/files/mdl/stereo.py +63 -0
  125. chython/files/mdl/write.py +201 -0
  126. chython/files/opsin.py +35 -0
  127. chython/files/test/__init__.py +0 -0
  128. chython/files/test/test_inchi.py +27 -0
  129. chython/files/test/test_mrv.py +47 -0
  130. chython/files/test/test_sdf_rdf.py +64 -0
  131. chython/files/test/test_xyz.py +20 -0
  132. chython/files/xyz.py +78 -0
  133. chython/periodictable/__init__.py +69 -0
  134. chython/periodictable/base/__init__.py +25 -0
  135. chython/periodictable/base/dynamic.py +149 -0
  136. chython/periodictable/base/element.py +451 -0
  137. chython/periodictable/base/groups.py +90 -0
  138. chython/periodictable/base/periods.py +46 -0
  139. chython/periodictable/base/query.py +473 -0
  140. chython/periodictable/base/vector.py +119 -0
  141. chython/periodictable/groupI.py +252 -0
  142. chython/periodictable/groupII.py +223 -0
  143. chython/periodictable/groupIII.py +1154 -0
  144. chython/periodictable/groupIV.py +210 -0
  145. chython/periodictable/groupIX.py +196 -0
  146. chython/periodictable/groupV.py +192 -0
  147. chython/periodictable/groupVI.py +183 -0
  148. chython/periodictable/groupVII.py +162 -0
  149. chython/periodictable/groupVIII.py +161 -0
  150. chython/periodictable/groupX.py +166 -0
  151. chython/periodictable/groupXI.py +164 -0
  152. chython/periodictable/groupXII.py +158 -0
  153. chython/periodictable/groupXIII.py +239 -0
  154. chython/periodictable/groupXIV.py +262 -0
  155. chython/periodictable/groupXV.py +276 -0
  156. chython/periodictable/groupXVI.py +457 -0
  157. chython/periodictable/groupXVII.py +324 -0
  158. chython/periodictable/groupXVIII.py +260 -0
  159. chython/periodictable/test/__init__.py +0 -0
  160. chython/periodictable/test/test_element.py +100 -0
  161. chython/reactor/__init__.py +24 -0
  162. chython/reactor/base.py +222 -0
  163. chython/reactor/reactor.py +177 -0
  164. chython/reactor/test/__init__.py +18 -0
  165. chython/reactor/test/test_reactor.py +40 -0
  166. chython/reactor/test/test_transformer.py +78 -0
  167. chython/reactor/transformer.py +72 -0
  168. chython/utils/__init__.py +50 -0
  169. chython/utils/free_wilson.py +114 -0
  170. chython/utils/grid.py +130 -0
  171. chython/utils/retro.py +139 -0
  172. chython-2.19.dist-info/METADATA +105 -0
  173. chython-2.19.dist-info/RECORD +175 -0
  174. chython-2.19.dist-info/WHEEL +4 -0
  175. chython-2.19.dist-info/licenses/LICENSE +165 -0
@@ -0,0 +1,478 @@
1
+ # cython: freethreading_compatible=True
2
+ # cython: undeclared_check_usage=error
3
+ # cython: warn.undeclared=True
4
+ # cython: warn.unused=True
5
+ # cython: warn.unused_arg=True
6
+ # cython: warn.maybe_uninitialized=True
7
+ # cython: boundscheck=False
8
+ # cython: wraparound=False
9
+
10
+ #
11
+ # Copyright 2025, 2026 Ramil Nugmanov <nougmanoff@protonmail.com>
12
+ # This file is part of chython.
13
+ #
14
+ # chython is free software; you can redistribute it and/or modify
15
+ # it under the terms of the GNU Lesser General Public License as published by
16
+ # the Free Software Foundation; either version 3 of the License, or
17
+ # (at your option) any later version.
18
+ #
19
+ # This program is distributed in the hope that it will be useful,
20
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
+ # GNU Lesser General Public License for more details.
23
+ #
24
+ # You should have received a copy of the GNU Lesser General Public License
25
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
26
+ #
27
+ from cpython.mem cimport PyMem_Malloc, PyMem_Free, PyMem_Realloc
28
+ from libc.limits cimport USHRT_MAX
29
+ from libc.string cimport memset, memcpy
30
+
31
+
32
+ cdef struct paths_t:
33
+ # store PID units. paths store only non-terminal nodes
34
+ # in the case of distances equals 1 - don't store any paths
35
+ unsigned short distance # bonds count on the shortest path between nodes
36
+ unsigned char num_pid0 # shortest paths count
37
+ unsigned char num_pid1 # shortest+1 paths count
38
+ unsigned short **pid0 # array of pointers to the shortest paths arrays
39
+ unsigned short **pid1 # array of pointers to the shortest+1 paths arrays
40
+
41
+
42
+ cdef struct dist_matrix_t:
43
+ unsigned short n_nodes
44
+ unsigned short *mapping # idx to node label mapping
45
+ paths_t *data
46
+
47
+
48
+ cdef struct ring_t:
49
+ unsigned short n_nodes
50
+ unsigned short *nodes
51
+ unsigned long long *hash
52
+
53
+
54
+ cdef struct rings_t:
55
+ unsigned short n_rings
56
+ unsigned short n_allocated
57
+ unsigned short n_reserved
58
+ unsigned short hash_size
59
+ ring_t *rings
60
+
61
+
62
+ cdef dist_matrix_t *alloc_dist_matrix(unsigned short n_nodes):
63
+ cdef size_t i, total = n_nodes * n_nodes
64
+ cdef dist_matrix_t *matrix = <dist_matrix_t *> PyMem_Malloc(sizeof(dist_matrix_t))
65
+
66
+ matrix.n_nodes = n_nodes
67
+ matrix.mapping = <unsigned short *> PyMem_Malloc(n_nodes * sizeof(unsigned short))
68
+ matrix.data = <paths_t *> PyMem_Malloc(total * sizeof(paths_t))
69
+ # set in paths_t all attrs to 0/NULL
70
+ memset(matrix.data, 0, total * sizeof(paths_t))
71
+
72
+ # reset distances to max
73
+ for i in range(total):
74
+ matrix.data[i].distance = USHRT_MAX
75
+ return matrix
76
+
77
+
78
+ cdef rings_t *alloc_rings(unsigned short n_rings, size_t hash_size):
79
+ cdef size_t i, n1 = n_rings + 1
80
+ cdef rings_t *rings = <rings_t *> PyMem_Malloc(sizeof(rings_t))
81
+
82
+ rings.n_rings = n_rings
83
+ rings.n_allocated = 0
84
+ rings.n_reserved = n1
85
+ rings.hash_size = hash_size
86
+ rings.rings = <ring_t *> PyMem_Malloc(n1 * sizeof(ring_t))
87
+ memset(rings.rings, 0, n1 * sizeof(ring_t))
88
+
89
+ for i in range(n1):
90
+ rings.rings[i].n_nodes = USHRT_MAX
91
+ return rings
92
+
93
+
94
+ cdef void realloc_rings(rings_t *rings, size_t n_rings):
95
+ cdef size_t i
96
+ rings.rings = <ring_t *> PyMem_Realloc(rings.rings, n_rings * sizeof(ring_t))
97
+ memset(rings.rings + rings.n_reserved, 0, (n_rings - rings.n_reserved) * sizeof(ring_t))
98
+
99
+ for i in range(rings.n_reserved, n_rings):
100
+ rings.rings[i].n_nodes = USHRT_MAX
101
+ rings.n_reserved = n_rings
102
+
103
+
104
+ cdef void free_pid0(paths_t *paths):
105
+ cdef size_t i
106
+ if paths.num_pid0 == 0: return
107
+ for i in range(paths.num_pid0):
108
+ PyMem_Free(paths.pid0[i])
109
+ PyMem_Free(paths.pid0)
110
+ paths.num_pid0 = 0
111
+ paths.pid0 = NULL
112
+
113
+
114
+ cdef void free_pid1(paths_t *paths):
115
+ cdef size_t i
116
+ if paths.num_pid1 == 0: return
117
+ for i in range(paths.num_pid1):
118
+ PyMem_Free(paths.pid1[i])
119
+ PyMem_Free(paths.pid1)
120
+ paths.num_pid1 = 0
121
+ paths.pid1 = NULL
122
+
123
+
124
+ cdef void free_pids(paths_t *paths):
125
+ free_pid0(paths)
126
+ free_pid1(paths)
127
+
128
+
129
+ cdef void free_dist_matrix(dist_matrix_t *matrix):
130
+ cdef size_t i, total = matrix.n_nodes * matrix.n_nodes
131
+ for i in range(total):
132
+ free_pids(&matrix.data[i])
133
+ PyMem_Free(matrix.mapping)
134
+ PyMem_Free(matrix.data)
135
+ PyMem_Free(matrix)
136
+
137
+
138
+ cdef void free_ring(ring_t *ring):
139
+ PyMem_Free(ring.hash)
140
+ PyMem_Free(ring.nodes)
141
+ ring.hash = NULL
142
+ ring.nodes = NULL
143
+
144
+
145
+ cdef void free_rings(rings_t *rings):
146
+ cdef size_t i
147
+ for i in range(rings.n_allocated):
148
+ free_ring(&rings.rings[i])
149
+ PyMem_Free(rings.rings)
150
+ PyMem_Free(rings)
151
+
152
+
153
+ cdef void move_paths(paths_t *paths):
154
+ free_pid1(paths)
155
+
156
+ paths.num_pid1 = paths.num_pid0
157
+ paths.pid1 = paths.pid0
158
+ paths.num_pid0 = 0
159
+ paths.pid0 = NULL
160
+
161
+
162
+ cdef unsigned short *concatenate_paths(paths_t *paths_ik, paths_t *paths_kj, unsigned short k):
163
+ cdef unsigned short *path
164
+ cdef size_t size1, size2
165
+
166
+ path = <unsigned short *> PyMem_Malloc((paths_ik.distance + paths_kj.distance - 1) * sizeof(unsigned short))
167
+
168
+ size1 = paths_ik.distance - 1
169
+ size2 = paths_kj.distance - 1
170
+ path[size1] = k # put node k to form continuous i-k-j path excluding i,j terminals
171
+ if size1:
172
+ memcpy(path, paths_ik.pid0[0], size1 * sizeof(unsigned short)) # copy nodes between i-k
173
+ if size2:
174
+ memcpy(path + paths_ik.distance, paths_kj.pid0[0], size2 * sizeof(unsigned short))
175
+ return path
176
+
177
+
178
+ cdef void append_pid0(paths_t *paths_ij, paths_t *paths_ik, paths_t *paths_kj, unsigned short k):
179
+ cdef size_t i = paths_ij.num_pid0
180
+ paths_ij.num_pid0 += 1
181
+ paths_ij.pid0 = <unsigned short **> PyMem_Realloc(paths_ij.pid0, paths_ij.num_pid0 * sizeof(unsigned short *))
182
+ paths_ij.pid0[i] = concatenate_paths(paths_ik, paths_kj, k)
183
+
184
+
185
+ cdef void append_pid1(paths_t *paths_ij, paths_t *paths_ik, paths_t *paths_kj, unsigned short k):
186
+ cdef size_t i = paths_ij.num_pid1
187
+ paths_ij.num_pid1 += 1
188
+ paths_ij.pid1 = <unsigned short **> PyMem_Realloc(paths_ij.pid1, paths_ij.num_pid1 * sizeof(unsigned short *))
189
+ paths_ij.pid1[i] = concatenate_paths(paths_ik, paths_kj, k)
190
+
191
+
192
+ cdef tuple ring_to_tuple(ring_t *ring):
193
+ cdef size_t i
194
+ cdef list result = []
195
+
196
+ for i in range(ring.n_nodes):
197
+ result.append(ring.nodes[i])
198
+ return tuple(result)
199
+
200
+
201
+ cdef int has_not_overlap(paths_t *paths_ik, paths_t *paths_kj, paths_t *paths_ij, unsigned short k, int test_pid1):
202
+ cdef size_t i, j
203
+ cdef unsigned short n1, n2
204
+ cdef unsigned short *path
205
+
206
+ if paths_ik.distance == 1:
207
+ n1 = k
208
+ else:
209
+ n1 = paths_ik.pid0[0][0]
210
+ if paths_kj.distance == 1:
211
+ n2 = k
212
+ else:
213
+ n2 = paths_kj.pid0[0][paths_kj.distance - 2]
214
+
215
+ j = paths_ij.distance - 2
216
+ for i in range(paths_ij.num_pid0):
217
+ path = paths_ij.pid0[i]
218
+ if path[0] == n1 or path[j] == n2:
219
+ return 0
220
+ if test_pid1:
221
+ j = paths_ij.distance - 1
222
+ for i in range(paths_ij.num_pid1):
223
+ path = paths_ij.pid1[i]
224
+ if path[0] == n1 or path[j] == n2:
225
+ return 0
226
+ return 1
227
+
228
+
229
+ cdef unsigned long long *build_hash(unsigned short *path1, unsigned short *path2,
230
+ unsigned short i, unsigned short j, size_t size1, size_t size2, size_t hash_size):
231
+ cdef size_t k
232
+ cdef unsigned short n
233
+ cdef unsigned long long *hash = <unsigned long long *> PyMem_Malloc(hash_size * sizeof(unsigned long long))
234
+
235
+ memset(hash, 0, hash_size * sizeof(unsigned long long))
236
+ hash[i // 64] |= (<unsigned long long> 1 << (i % 64)) # add linker node
237
+ hash[j // 64] |= (<unsigned long long> 1 << (j % 64))
238
+
239
+ for k in range(size1):
240
+ n = path1[k]
241
+ hash[n // 64] |= (<unsigned long long> 1 << (n % 64))
242
+ for k in range(size2):
243
+ n = path2[k]
244
+ hash[n // 64] |= (<unsigned long long> 1 << (n % 64))
245
+ return hash
246
+
247
+
248
+ cdef unsigned short *build_ring(unsigned short *path1, unsigned short *path2,
249
+ unsigned short i, unsigned short j, size_t size1, size_t size2):
250
+ cdef size_t k, s = size1 + 1
251
+ cdef unsigned short *nodes
252
+
253
+ nodes = <unsigned short *> PyMem_Malloc((size1 + size2 + 2) * sizeof(unsigned short))
254
+ nodes[0] = i
255
+ nodes[s] = j
256
+ memcpy(nodes + 1, path1, size1 * sizeof(unsigned short))
257
+
258
+ # inverse second path
259
+ for k in range(size2 - 1, -1, -1):
260
+ s += 1
261
+ nodes[s] = path2[k]
262
+ return nodes
263
+
264
+
265
+ cdef void push_ring(ring_t *ring, rings_t *rings, unsigned short rank):
266
+ cdef size_t i
267
+ cdef unsigned short ext
268
+
269
+ if rings.n_allocated == rings.n_reserved - 1: # almost all slots are used
270
+ ext = rings.n_reserved # double rings storage
271
+ if ext > 1000: ext = 1000 # no more than 1k rings to extend
272
+ realloc_rings(rings, rings.n_reserved + ext)
273
+
274
+ for i in range(rings.n_allocated, rank, -1):
275
+ rings.rings[i] = rings.rings[i - 1]
276
+
277
+ # insert the new ring at the rank position
278
+ rings.rings[rank] = ring[0] # copy dereferenced struct memory
279
+ rings.n_allocated += 1
280
+
281
+
282
+ cdef unsigned short get_rank(ring_t *ring, rings_t *rings):
283
+ cdef size_t i, j
284
+ cdef int hash_match
285
+ cdef ring_t *other
286
+
287
+ for i in range(<size_t> rings.n_allocated + 1):
288
+ other = &rings.rings[i]
289
+ if ring.n_nodes == other.n_nodes:
290
+ hash_match = 1
291
+ for j in range(rings.hash_size):
292
+ if ring.hash[j] != other.hash[j]:
293
+ hash_match = 0
294
+ break
295
+ if hash_match:
296
+ return USHRT_MAX # duplicate found
297
+ elif ring.n_nodes < other.n_nodes: # always true for n_allocated+1
298
+ return i
299
+
300
+
301
+ cdef void build_pid(dist_matrix_t *matrix):
302
+ cdef size_t i, j, k, sk, si
303
+ cdef unsigned short rk
304
+ cdef unsigned int d
305
+ cdef paths_t *paths_kj
306
+ cdef paths_t *paths_ij
307
+ cdef paths_t *paths_ik
308
+
309
+ for k in range(matrix.n_nodes):
310
+ sk = k * matrix.n_nodes
311
+ rk = matrix.mapping[k]
312
+ for i in range(matrix.n_nodes):
313
+ if i == k: continue
314
+ si = i * matrix.n_nodes
315
+ paths_ik = &matrix.data[si + k]
316
+ if paths_ik.distance == USHRT_MAX: continue
317
+
318
+ for j in range(matrix.n_nodes):
319
+ if j == k or j == i: continue
320
+ paths_kj = &matrix.data[sk + j]
321
+ if paths_kj.distance == USHRT_MAX: continue
322
+
323
+ paths_ij = &matrix.data[si + j]
324
+ d = paths_ik.distance + paths_kj.distance
325
+ if d < paths_ij.distance: # shorter pid0 path found
326
+ if d == paths_ij.distance - 1 and has_not_overlap(paths_ik, paths_kj, paths_ij, rk, 0):
327
+ move_paths(paths_ij)
328
+ else: # drop old paths
329
+ free_pids(paths_ij)
330
+
331
+ paths_ij.distance = d
332
+ append_pid0(paths_ij, paths_ik, paths_kj, rk)
333
+ elif d == paths_ij.distance: # new pid0 path
334
+ if has_not_overlap(paths_ik, paths_kj, paths_ij, rk, 0):
335
+ append_pid0(paths_ij, paths_ik, paths_kj, rk)
336
+ elif d == paths_ij.distance + 1: # new pid1 path
337
+ if has_not_overlap(paths_ik, paths_kj, paths_ij, rk, 1):
338
+ append_pid1(paths_ij, paths_ik, paths_kj, rk)
339
+
340
+
341
+ cdef rings_t *build_cset(dist_matrix_t *matrix, size_t n_rings):
342
+ cdef size_t i, j, k, si, n_max = 0
343
+ cdef unsigned short ri, rj, d, rank
344
+ cdef ring_t ring
345
+ cdef paths_t *path
346
+ cdef rings_t *rings
347
+
348
+ for i in range(matrix.n_nodes):
349
+ ri = matrix.mapping[i]
350
+ if ri > n_max: n_max = ri
351
+
352
+ rings = alloc_rings(n_rings, (n_max + 64) // 64)
353
+
354
+ for i in range(matrix.n_nodes):
355
+ si = i * matrix.n_nodes
356
+ ri = matrix.mapping[i]
357
+ for j in range(matrix.n_nodes):
358
+ if i == j: continue
359
+ path = &matrix.data[si + j]
360
+ d = path.distance # make shortcut
361
+ if d == USHRT_MAX: continue # different components
362
+ rj = matrix.mapping[j]
363
+
364
+ if d == 1: # triangles
365
+ if not path.num_pid1: continue
366
+ ring.n_nodes = 3
367
+ for k in range(path.num_pid1):
368
+ ring.hash = build_hash(NULL, path.pid1[k], ri, rj, 0, 1, rings.hash_size)
369
+
370
+ rank = get_rank(&ring, rings)
371
+ if rank != USHRT_MAX:
372
+ ring.nodes = build_ring(path.pid1[k], NULL, ri, rj, 1, 0)
373
+ push_ring(&ring, rings, rank)
374
+ else:
375
+ PyMem_Free(ring.hash)
376
+ elif path.num_pid0 == 1: # is odd?
377
+ if not path.num_pid1: continue
378
+ ring.n_nodes = 2 * d + 1
379
+ for k in range(path.num_pid1):
380
+ ring.hash = build_hash(path.pid0[0], path.pid1[k], ri, rj, d - 1, d, rings.hash_size)
381
+
382
+ rank = get_rank(&ring, rings)
383
+ if rank != USHRT_MAX:
384
+ ring.nodes = build_ring(path.pid0[0], path.pid1[k], ri, rj, d - 1, d)
385
+ push_ring(&ring, rings, rank)
386
+ else:
387
+ PyMem_Free(ring.hash)
388
+ else: # is even
389
+ ring.n_nodes = 2 * d
390
+ d -= 1
391
+ for k in range(1, path.num_pid0):
392
+ ring.hash = build_hash(path.pid0[k - 1], path.pid0[k], ri, rj, d, d, rings.hash_size)
393
+
394
+ rank = get_rank(&ring, rings)
395
+ if rank != USHRT_MAX:
396
+ ring.nodes = build_ring(path.pid0[k - 1], path.pid0[k], ri, rj, d, d)
397
+ push_ring(&ring, rings, rank)
398
+ else:
399
+ PyMem_Free(ring.hash)
400
+ return rings
401
+
402
+
403
+ cdef void filter_rings(rings_t *rings):
404
+ cdef size_t i, j, k, pc = 0
405
+ cdef ring_t *ring
406
+ cdef int is_unique
407
+ cdef unsigned long long *hash
408
+ cdef unsigned short *poplist
409
+
410
+ if rings.n_allocated == rings.n_rings: return
411
+
412
+ hash = <unsigned long long *> PyMem_Malloc(rings.hash_size * sizeof(unsigned long long))
413
+ poplist = <unsigned short *> PyMem_Malloc(rings.n_allocated * sizeof(unsigned short))
414
+ memcpy(hash, rings.rings[0].hash, rings.hash_size * sizeof(unsigned long long))
415
+
416
+ for i in range(1, rings.n_allocated):
417
+ is_unique = 0
418
+ ring = &rings.rings[i]
419
+ for j in range(rings.hash_size):
420
+ if ring.hash[j] & (~hash[j]):
421
+ is_unique = 1
422
+ break
423
+ if is_unique: # extend global hash
424
+ for j in range(rings.hash_size):
425
+ hash[j] |= ring.hash[j]
426
+ else:
427
+ poplist[pc] = i
428
+ pc += 1
429
+
430
+ for i in range(<size_t>rings.n_allocated - 1, <size_t> rings.n_rings - 1, -1):
431
+ pc -= 1
432
+ k = poplist[pc]
433
+
434
+ free_ring(&rings.rings[k]) # drop condensed ring
435
+ for j in range(k, i):
436
+ rings.rings[j] = rings.rings[j + 1]
437
+
438
+ rings.n_allocated = rings.n_rings
439
+ PyMem_Free(poplist)
440
+ PyMem_Free(hash)
441
+
442
+
443
+ def sssr(object graph, size_t n_rings):
444
+ cdef size_t si
445
+ cdef unsigned short i, n, m, n_nodes = len(graph)
446
+ cdef unsigned short [USHRT_MAX] reverse_mapping # 128kb
447
+ cdef rings_t *rings
448
+ cdef dist_matrix_t *matrix
449
+ cdef object mb
450
+ cdef list output = []
451
+
452
+ if not n_rings: return output
453
+
454
+ if n_nodes > 65500: raise ValueError('Too many atoms')
455
+ matrix = alloc_dist_matrix(n_nodes)
456
+ for i, n in enumerate(graph):
457
+ if n > 65500: raise ValueError('Atom index too large')
458
+ matrix.mapping[i] = n
459
+ reverse_mapping[n] = i
460
+
461
+ for n, mb in graph.items():
462
+ si = n_nodes * reverse_mapping[n]
463
+ for m in mb:
464
+ matrix.data[si + reverse_mapping[m]].distance = 1
465
+
466
+ # run PID matrix calculation
467
+ build_pid(matrix)
468
+ # run CSET calculation
469
+ rings = build_cset(matrix, n_rings)
470
+ free_dist_matrix(matrix)
471
+ # filter out condensed rings
472
+ filter_rings(rings)
473
+
474
+ for i in range(rings.n_rings):
475
+ output.append(ring_to_tuple(&rings.rings[i]))
476
+
477
+ free_rings(rings)
478
+ return output
@@ -0,0 +1,27 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2018-2026 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 .kekule import *
20
+ from .thiele import *
21
+
22
+
23
+ class Aromatize(Thiele, Kekule):
24
+ __slots__ = ()
25
+
26
+
27
+ __all__ = ['Aromatize']
@@ -0,0 +1,124 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2021-2026 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 lazy_object_proxy import Proxy
20
+
21
+
22
+ def _rules():
23
+ from ... import smarts
24
+
25
+ rules = []
26
+
27
+ # Aromatic N-Oxide
28
+ #
29
+ # : N : >> : [N+] :
30
+ # \\ \
31
+ # O [O-]
32
+ #
33
+ q = smarts('[N;a;D3:1]=[O;D1:2]')
34
+ atom_fix = {1: 1, 2: -1}
35
+ bonds_fix = ((1, 2, 1),)
36
+ # query, atom fix, bond fix, allow multimatch
37
+ rules.append((q, atom_fix, bonds_fix, False))
38
+
39
+ # Aromatic N-Nitride?
40
+ #
41
+ # : N : >> : [N+] :
42
+ # \\ \
43
+ # N [N-]
44
+ #
45
+ q = smarts('[N;a;D3:1]=[N;D1,D2;z2:2]')
46
+ atom_fix = {1: 1, 2: -1}
47
+ bonds_fix = ((1, 2, 1),)
48
+ rules.append((q, atom_fix, bonds_fix, False))
49
+
50
+ #
51
+ # : [S+] : >> : S :
52
+ # | \\
53
+ # [O-] O
54
+ #
55
+ q = smarts('[S;a;D3;+:1]-[O;D1;-:2]')
56
+ atom_fix = {1: 0, 2: 0}
57
+ bonds_fix = ((1, 2, 2),)
58
+ rules.append((q, atom_fix, bonds_fix, False))
59
+
60
+ #
61
+ # [O-]-N:C:C:[N+]=O
62
+ #
63
+ q = smarts('[N;a;D3;+:1](=[O;D1:2]):[C:3]:[C:4]:[N;D3:5]-[O;D1;-:6]')
64
+ atom_fix = {5: 1, 2: -1}
65
+ bonds_fix = ((1, 2, 1),)
66
+ rules.append((q, atom_fix, bonds_fix, False))
67
+
68
+ #
69
+ # N : A : N - ?
70
+ # : :
71
+ # C # C
72
+ q = smarts('[N;a;D2,D3;r5:3]:1:[C;D2;r5:1]#[C;D2;r5:2]:[N;D2;r5:5]:[C,N;r5:4]:1')
73
+ atom_fix = {}
74
+ bonds_fix = ((1, 2, 4),)
75
+ rules.append((q, atom_fix, bonds_fix, False))
76
+
77
+ #
78
+ # C:[N+]:[C-]
79
+ # \\
80
+ # O
81
+ #
82
+ q = smarts('[N;a;D3;+:1](=[O;D1:2])(:[C;D2,D3;-:3]):[C;D2,D3:4]')
83
+ atom_fix = {2: -1, 3: 0}
84
+ bonds_fix = ((1, 2, 1),)
85
+ rules.append((q, atom_fix, bonds_fix, False))
86
+
87
+ #
88
+ # O=[N+] : C
89
+ # : :
90
+ # O : N : C
91
+ q = smarts('[N;a;D3;r5;+:1]:1(=[O;D1:2]):[O;D2;r5:3]:[N;D2,D3;r5:4]:[C;D2,D3;r5:5]:[C;D2,D3;r5:6]:1')
92
+ atom_fix = {}
93
+ bonds_fix = ((1, 3, 1), (1, 6, 1), (3, 4, 1), (4, 5, 1), (5, 6, 2))
94
+ rules.append((q, atom_fix, bonds_fix, False))
95
+
96
+ # bad complex representation
97
+ # : :
98
+ # N - [M] >> N ... [M]
99
+ # : :
100
+ q = smarts('[N;a;D3:1]-[M:2]')
101
+ atom_fix = {}
102
+ bonds_fix = ((1, 2, 8),)
103
+ rules.append((q, atom_fix, bonds_fix, True))
104
+ return rules
105
+
106
+
107
+ def _freaks():
108
+ from ... import smarts
109
+
110
+ rules = []
111
+
112
+ q = smarts('[N,O,S;D2;r5;z1]1[A;r5]=,:[A;r5][A;r5]:[A;r5]1')
113
+ rules.append(q)
114
+
115
+ q = smarts('[N;D3;r5;z1]1[A;r5]=,:[A;r5][A;r5]:[A;r5]1')
116
+ rules.append(q)
117
+ return rules
118
+
119
+
120
+ rules = Proxy(_rules)
121
+ freak_rules = Proxy(_freaks)
122
+
123
+
124
+ __all__ = ['rules', 'freak_rules']