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.
- chython/__init__.py +35 -0
- chython/_functions.py +95 -0
- chython/_java.py +57 -0
- chython/algorithms/__init__.py +18 -0
- chython/algorithms/_isomorphism.cp313-win_amd64.pyd +0 -0
- chython/algorithms/_isomorphism.pyx +189 -0
- chython/algorithms/_rings.cp313-win_amd64.pyd +0 -0
- chython/algorithms/_rings.pyx +478 -0
- chython/algorithms/aromatics/__init__.py +27 -0
- chython/algorithms/aromatics/_rules.py +124 -0
- chython/algorithms/aromatics/kekule.py +532 -0
- chython/algorithms/aromatics/test/__init__.py +18 -0
- chython/algorithms/aromatics/test/test_kekule.py +160 -0
- chython/algorithms/aromatics/test/test_thiele.py +263 -0
- chython/algorithms/aromatics/thiele.py +222 -0
- chython/algorithms/calculate2d/__init__.py +23 -0
- chython/algorithms/calculate2d/clean2d.js +1 -0
- chython/algorithms/calculate2d/molecule.py +191 -0
- chython/algorithms/calculate2d/reaction.py +75 -0
- chython/algorithms/conformers.py +106 -0
- chython/algorithms/depict.py +596 -0
- chython/algorithms/fingerprints/__init__.py +43 -0
- chython/algorithms/fingerprints/linear.py +202 -0
- chython/algorithms/fingerprints/morgan.py +132 -0
- chython/algorithms/fingerprints/test/__init__.py +18 -0
- chython/algorithms/fingerprints/test/test_linear.py +127 -0
- chython/algorithms/fingerprints/test/test_morgan.py +79 -0
- chython/algorithms/groups/__init__.py +212 -0
- chython/algorithms/groups/_functional.py +290 -0
- chython/algorithms/groups/_oxidations.py +70 -0
- chython/algorithms/groups/_protective.py +964 -0
- chython/algorithms/groups/_reactions.py +809 -0
- chython/algorithms/groups/_reductions.py +88 -0
- chython/algorithms/groups/_transformations.py +278 -0
- chython/algorithms/groups/test/__init__.py +18 -0
- chython/algorithms/groups/test/test_deprotection.py +108 -0
- chython/algorithms/groups/test/test_reactions.py +377 -0
- chython/algorithms/isomorphism.py +664 -0
- chython/algorithms/mapping/__init__.py +188 -0
- chython/algorithms/mapping/_groups.py +94 -0
- chython/algorithms/mapping/_reactions.py +72 -0
- chython/algorithms/mapping/attention.py +266 -0
- chython/algorithms/mapping/reconstruct.py +346 -0
- chython/algorithms/mcs.py +201 -0
- chython/algorithms/morgan.py +77 -0
- chython/algorithms/rings.py +178 -0
- chython/algorithms/smiles.py +597 -0
- chython/algorithms/standardize/__init__.py +30 -0
- chython/algorithms/standardize/_groups.py +958 -0
- chython/algorithms/standardize/_isomers.py +330 -0
- chython/algorithms/standardize/_metal_organics.py +189 -0
- chython/algorithms/standardize/_reagents.py +44 -0
- chython/algorithms/standardize/_salts.py +68 -0
- chython/algorithms/standardize/molecule.py +475 -0
- chython/algorithms/standardize/reaction.py +384 -0
- chython/algorithms/standardize/resonance.py +191 -0
- chython/algorithms/standardize/salts.py +128 -0
- chython/algorithms/standardize/saturation.py +373 -0
- chython/algorithms/standardize/test/__init__.py +18 -0
- chython/algorithms/standardize/test/test_canonicalize.py +66 -0
- chython/algorithms/standardize/test/test_groups.py +146 -0
- chython/algorithms/standardize/test/test_isomers.py +198 -0
- chython/algorithms/stereo.py +1215 -0
- chython/algorithms/tautomers/__init__.py +167 -0
- chython/algorithms/tautomers/_acid.py +59 -0
- chython/algorithms/tautomers/_base.py +116 -0
- chython/algorithms/tautomers/_keto_enol.py +107 -0
- chython/algorithms/tautomers/acid_base.py +208 -0
- chython/algorithms/tautomers/heteroarenes.py +103 -0
- chython/algorithms/tautomers/keto_enol.py +143 -0
- chython/algorithms/tautomers/test/__init__.py +18 -0
- chython/algorithms/tautomers/test/test_tautomers.py +80 -0
- chython/algorithms/test/__init__.py +18 -0
- chython/algorithms/test/test_isomorphism.py +58 -0
- chython/algorithms/test/test_smiles.py +76 -0
- chython/algorithms/x3dom.py +350 -0
- chython/containers/__init__.py +48 -0
- chython/containers/_pack_v2.cp313-win_amd64.pyd +0 -0
- chython/containers/_pack_v2.pyx +271 -0
- chython/containers/_unpack_v0v2.cp313-win_amd64.pyd +0 -0
- chython/containers/_unpack_v0v2.pyx +302 -0
- chython/containers/bonds.py +255 -0
- chython/containers/cgr.py +115 -0
- chython/containers/chimera.py +63 -0
- chython/containers/graph.py +199 -0
- chython/containers/molecule.py +817 -0
- chython/containers/query.py +71 -0
- chython/containers/rdkit.py +183 -0
- chython/containers/reaction.py +324 -0
- chython/containers/test/__init__.py +18 -0
- chython/containers/test/test_molecule.py +117 -0
- chython/containers/test/test_pack.py +60 -0
- chython/containers/test/test_rdkit.py +70 -0
- chython/containers/test/test_reaction.py +40 -0
- chython/exceptions.py +114 -0
- chython/files/MRVrw.py +532 -0
- chython/files/PDBrw.py +255 -0
- chython/files/RDFrw.py +309 -0
- chython/files/SDFrw.py +221 -0
- chython/files/__init__.py +30 -0
- chython/files/_convert.py +207 -0
- chython/files/_mapping.py +117 -0
- chython/files/_xyz.cp313-win_amd64.pyd +0 -0
- chython/files/_xyz.pyx +74 -0
- chython/files/daylight/__init__.py +24 -0
- chython/files/daylight/parser.py +157 -0
- chython/files/daylight/smarts.py +107 -0
- chython/files/daylight/smiles.py +260 -0
- chython/files/daylight/test/__init__.py +18 -0
- chython/files/daylight/test/test_daylight_smarts.py +289 -0
- chython/files/daylight/test/test_daylight_smiles.py +101 -0
- chython/files/daylight/test/test_parser.py +118 -0
- chython/files/daylight/test/test_tokenize.py +77 -0
- chython/files/daylight/tokenize.py +376 -0
- chython/files/libinchi/__init__.py +22 -0
- chython/files/libinchi/libinchi.dll +0 -0
- chython/files/libinchi/wrapper.py +554 -0
- chython/files/mdl/__init__.py +25 -0
- chython/files/mdl/emol.py +251 -0
- chython/files/mdl/erxn.py +67 -0
- chython/files/mdl/mol.py +186 -0
- chython/files/mdl/read.py +223 -0
- chython/files/mdl/rxn.py +67 -0
- chython/files/mdl/stereo.py +63 -0
- chython/files/mdl/write.py +201 -0
- chython/files/opsin.py +35 -0
- chython/files/test/__init__.py +0 -0
- chython/files/test/test_inchi.py +27 -0
- chython/files/test/test_mrv.py +47 -0
- chython/files/test/test_sdf_rdf.py +64 -0
- chython/files/test/test_xyz.py +20 -0
- chython/files/xyz.py +78 -0
- chython/periodictable/__init__.py +69 -0
- chython/periodictable/base/__init__.py +25 -0
- chython/periodictable/base/dynamic.py +149 -0
- chython/periodictable/base/element.py +451 -0
- chython/periodictable/base/groups.py +90 -0
- chython/periodictable/base/periods.py +46 -0
- chython/periodictable/base/query.py +473 -0
- chython/periodictable/base/vector.py +119 -0
- chython/periodictable/groupI.py +252 -0
- chython/periodictable/groupII.py +223 -0
- chython/periodictable/groupIII.py +1154 -0
- chython/periodictable/groupIV.py +210 -0
- chython/periodictable/groupIX.py +196 -0
- chython/periodictable/groupV.py +192 -0
- chython/periodictable/groupVI.py +183 -0
- chython/periodictable/groupVII.py +162 -0
- chython/periodictable/groupVIII.py +161 -0
- chython/periodictable/groupX.py +166 -0
- chython/periodictable/groupXI.py +164 -0
- chython/periodictable/groupXII.py +158 -0
- chython/periodictable/groupXIII.py +239 -0
- chython/periodictable/groupXIV.py +262 -0
- chython/periodictable/groupXV.py +276 -0
- chython/periodictable/groupXVI.py +457 -0
- chython/periodictable/groupXVII.py +324 -0
- chython/periodictable/groupXVIII.py +260 -0
- chython/periodictable/test/__init__.py +0 -0
- chython/periodictable/test/test_element.py +100 -0
- chython/reactor/__init__.py +24 -0
- chython/reactor/base.py +222 -0
- chython/reactor/reactor.py +177 -0
- chython/reactor/test/__init__.py +18 -0
- chython/reactor/test/test_reactor.py +40 -0
- chython/reactor/test/test_transformer.py +78 -0
- chython/reactor/transformer.py +72 -0
- chython/utils/__init__.py +50 -0
- chython/utils/free_wilson.py +114 -0
- chython/utils/grid.py +130 -0
- chython/utils/retro.py +139 -0
- chython-2.19.dist-info/METADATA +105 -0
- chython-2.19.dist-info/RECORD +175 -0
- chython-2.19.dist-info/WHEEL +4 -0
- 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']
|