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.
- chython/__init__.py +31 -0
- chython/_functions.py +69 -0
- chython/algorithms/__init__.py +18 -0
- chython/algorithms/_isomorphism.cp312-win_amd64.pyd +0 -0
- chython/algorithms/_isomorphism.pyx +189 -0
- chython/algorithms/aromatics/__init__.py +27 -0
- chython/algorithms/aromatics/_rules.py +124 -0
- chython/algorithms/aromatics/kekule.py +530 -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 +226 -0
- chython/algorithms/calculate2d/__init__.py +23 -0
- chython/algorithms/calculate2d/clean2d.js +1 -0
- chython/algorithms/calculate2d/molecule.py +149 -0
- chython/algorithms/calculate2d/reaction.py +80 -0
- chython/algorithms/depict.py +523 -0
- chython/algorithms/fingerprints/__init__.py +48 -0
- chython/algorithms/fingerprints/linear.py +208 -0
- chython/algorithms/fingerprints/morgan.py +137 -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/isomorphism.py +667 -0
- chython/algorithms/mapping/__init__.py +28 -0
- chython/algorithms/mapping/_groups.py +94 -0
- chython/algorithms/mapping/_reactions.py +72 -0
- chython/algorithms/mapping/attention.py +216 -0
- chython/algorithms/mapping/fixmapper.py +84 -0
- chython/algorithms/mapping/groups.py +137 -0
- chython/algorithms/mcs.py +202 -0
- chython/algorithms/morgan.py +82 -0
- chython/algorithms/rings.py +551 -0
- chython/algorithms/smiles.py +556 -0
- chython/algorithms/standardize/__init__.py +30 -0
- chython/algorithms/standardize/_charged.py +115 -0
- chython/algorithms/standardize/_groups.py +926 -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 +479 -0
- chython/algorithms/standardize/reaction.py +429 -0
- chython/algorithms/standardize/resonance.py +196 -0
- chython/algorithms/standardize/salts.py +128 -0
- chython/algorithms/standardize/saturation.py +378 -0
- chython/algorithms/standardize/test/__init__.py +18 -0
- chython/algorithms/standardize/test/test_groups.py +118 -0
- chython/algorithms/stereo.py +1222 -0
- chython/algorithms/tautomers/__init__.py +171 -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 +214 -0
- chython/algorithms/tautomers/heteroarenes.py +108 -0
- chython/algorithms/tautomers/keto_enol.py +148 -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 +355 -0
- chython/containers/__init__.py +43 -0
- chython/containers/_pack_v2.cp312-win_amd64.pyd +0 -0
- chython/containers/_pack_v2.pyx +271 -0
- chython/containers/_unpack_v0v2.cp312-win_amd64.pyd +0 -0
- chython/containers/_unpack_v0v2.pyx +300 -0
- chython/containers/bonds.py +255 -0
- chython/containers/cgr.py +115 -0
- chython/containers/graph.py +200 -0
- chython/containers/molecule.py +695 -0
- chython/containers/query.py +66 -0
- chython/containers/reaction.py +324 -0
- chython/exceptions.py +114 -0
- chython/files/MRVrw.py +523 -0
- chython/files/PDBrw.py +257 -0
- chython/files/RDFrw.py +298 -0
- chython/files/SDFrw.py +221 -0
- chython/files/__init__.py +29 -0
- chython/files/_convert.py +207 -0
- chython/files/_mapping.py +117 -0
- chython/files/_xyz.cp312-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 +245 -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 +555 -0
- chython/files/mdl/__init__.py +25 -0
- chython/files/mdl/emol.py +209 -0
- chython/files/mdl/erxn.py +67 -0
- chython/files/mdl/mol.py +144 -0
- chython/files/mdl/read.py +225 -0
- chython/files/mdl/rxn.py +67 -0
- chython/files/mdl/stereo.py +63 -0
- chython/files/mdl/write.py +165 -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 +452 -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/reactor/__init__.py +24 -0
- chython/reactor/base.py +212 -0
- chython/reactor/deprotection.py +565 -0
- chython/reactor/groups.py +128 -0
- chython/reactor/reactions/__init__.py +157 -0
- chython/reactor/reactions/_amidation.py +58 -0
- chython/reactor/reactions/_amine_isocyanate.py +52 -0
- chython/reactor/reactions/_buchwald_hartwig.py +50 -0
- chython/reactor/reactions/_esterification.py +49 -0
- chython/reactor/reactions/_macmillan.py +43 -0
- chython/reactor/reactions/_reductive_amination.py +50 -0
- chython/reactor/reactions/_sonogashira.py +49 -0
- chython/reactor/reactions/_sulfonamidation.py +52 -0
- chython/reactor/reactions/_suzuki_miyaura.py +88 -0
- chython/reactor/reactions/ufe.py +99 -0
- chython/reactor/reactor.py +170 -0
- chython/reactor/retro/__init__.py +77 -0
- chython/reactor/retro/_amidation.py +39 -0
- chython/reactor/retro/_aryl_amination.py +32 -0
- chython/reactor/retro/_mitsunobu.py +49 -0
- chython/reactor/retro/_sonogashira.py +49 -0
- chython/reactor/retro/_suzuki_miyaura.py +54 -0
- chython/reactor/scaffold.py +122 -0
- chython/reactor/test/__init__.py +18 -0
- chython/reactor/test/test_deprotection.py +57 -0
- chython/reactor/test/test_reactor.py +40 -0
- chython/reactor/test/test_scaffold.py +57 -0
- chython/reactor/test/test_transformer.py +40 -0
- chython/reactor/transformer.py +62 -0
- chython/utils/__init__.py +57 -0
- chython/utils/free_wilson.py +114 -0
- chython/utils/grid.py +130 -0
- chython/utils/rdkit.py +165 -0
- chython/utils/retro.py +139 -0
- chython/utils/svg.py +57 -0
- chython/utils/test/__init__.py +18 -0
- chython/utils/test/test_rdkit.py +71 -0
- chython-2.4.dist-info/LICENSE +165 -0
- chython-2.4.dist-info/METADATA +101 -0
- chython-2.4.dist-info/RECORD +171 -0
- 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']
|