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,376 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2022-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 re import compile, match, search
|
|
20
|
+
from ...containers.bonds import QueryBond
|
|
21
|
+
from ...exceptions import IncorrectSmiles, IncorrectSmarts
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# -,= OR bonds supported
|
|
25
|
+
# !: NOT bonds supported
|
|
26
|
+
# ~ ANY bonds supported
|
|
27
|
+
# @ bond not supported
|
|
28
|
+
# @;!: and any other complicated combinations not supported
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# tokens structure:
|
|
32
|
+
# (type: int, value)
|
|
33
|
+
# types:
|
|
34
|
+
# 0: atom
|
|
35
|
+
# 1: bond
|
|
36
|
+
# 2: open chain (
|
|
37
|
+
# 3: close chain )
|
|
38
|
+
# 4: dot bond .
|
|
39
|
+
# 5: in bracket raw data []
|
|
40
|
+
# 6: closure number
|
|
41
|
+
# 7: raw closure number
|
|
42
|
+
# 8: aromatic atom
|
|
43
|
+
# 9: up down bond
|
|
44
|
+
|
|
45
|
+
# 10: query OR bond
|
|
46
|
+
# 11: query NOT bond
|
|
47
|
+
# 12: in ring bond
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
iso_re = compile(r'^[0-9]+')
|
|
51
|
+
chg_re = compile(r'[+-][1-4+-]?')
|
|
52
|
+
mpp_re = compile(r':[1-9][0-9]*$')
|
|
53
|
+
str_re = compile(r'@[@?]?')
|
|
54
|
+
|
|
55
|
+
replace_dict = {'-': 1, '=': 2, '#': 3, ':': 4, '~': 8}
|
|
56
|
+
not_dict = {'-': [2, 3, 4], '=': [1, 3, 4], '#': [1, 2, 4], ':': [1, 2, 3]}
|
|
57
|
+
atom_re = compile(r'([1-9][0-9]{0,2})?([A-IK-PR-Zacnopsbt][a-ik-pr-vy]?)(@@|@)?(H[1-4]?)?([+-][1-4+-]?)?(:[0-9]{1,4})?')
|
|
58
|
+
charge_dict = {'+': 1, '+1': 1, '++': 2, '+2': 2, '+3': 3, '+++': 3, '+4': 4, '++++': 4,
|
|
59
|
+
'-': -1, '-1': -1, '--': -2, '-2': -2, '-3': -3, '---': -3, '-4': -4, '----': -4}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _tokenize(smiles):
|
|
63
|
+
token_type = token = None
|
|
64
|
+
tokens = []
|
|
65
|
+
for s in smiles:
|
|
66
|
+
if token_type == 12: # -;, =;, #; or :; found.
|
|
67
|
+
if s == '!': # not in ring
|
|
68
|
+
if token: # !! case
|
|
69
|
+
raise IncorrectSmarts('Invalid ring bond token')
|
|
70
|
+
token = True
|
|
71
|
+
elif s == '@':
|
|
72
|
+
tokens.append((12, QueryBond(tokens.pop(-1)[1], not token)))
|
|
73
|
+
token_type = token = None # finalize [not]ring bond
|
|
74
|
+
else:
|
|
75
|
+
raise IncorrectSmarts('Invalid ring bond token')
|
|
76
|
+
# [atom block parser]
|
|
77
|
+
elif s == '[': # open complex token
|
|
78
|
+
if token_type == 5: # two opened [
|
|
79
|
+
raise IncorrectSmiles('[..[')
|
|
80
|
+
elif token_type in (10, 11):
|
|
81
|
+
raise IncorrectSmarts('Query bond invalid')
|
|
82
|
+
elif token_type == 7: # empty closure
|
|
83
|
+
raise IncorrectSmiles('invalid closure')
|
|
84
|
+
elif token:
|
|
85
|
+
tokens.append((token_type, token))
|
|
86
|
+
token = []
|
|
87
|
+
token_type = 5
|
|
88
|
+
elif s == ']': # close complex token
|
|
89
|
+
if token_type != 5:
|
|
90
|
+
raise IncorrectSmiles(']..]')
|
|
91
|
+
elif not token:
|
|
92
|
+
raise IncorrectSmiles('empty [] brackets')
|
|
93
|
+
tokens.append((5, ''.join(token)))
|
|
94
|
+
token = None
|
|
95
|
+
token_type = 0 # mark as atom
|
|
96
|
+
elif token_type == 5: # grow token with brackets. skip validation
|
|
97
|
+
token.append(s)
|
|
98
|
+
# closure parser
|
|
99
|
+
elif s.isnumeric(): # closures
|
|
100
|
+
if token_type in (10, 11):
|
|
101
|
+
raise IncorrectSmarts('Query bond invalid')
|
|
102
|
+
elif token_type == 2:
|
|
103
|
+
raise IncorrectSmiles('(1 case invalid')
|
|
104
|
+
elif token_type == 7: # % already found. collect number
|
|
105
|
+
if not token and s == '0':
|
|
106
|
+
raise IncorrectSmiles('number starts with 0')
|
|
107
|
+
token.append(s)
|
|
108
|
+
if len(token) == 2:
|
|
109
|
+
tokens.append((6, int(''.join(token))))
|
|
110
|
+
token = None
|
|
111
|
+
token_type = 6 # mark finished
|
|
112
|
+
else:
|
|
113
|
+
if s == '0':
|
|
114
|
+
raise IncorrectSmiles('number starts with 0')
|
|
115
|
+
elif token:
|
|
116
|
+
tokens.append((token_type, token))
|
|
117
|
+
token = None
|
|
118
|
+
token_type = 6
|
|
119
|
+
tokens.append((6, int(s)))
|
|
120
|
+
elif token_type == 7:
|
|
121
|
+
raise IncorrectSmiles('expected closure number')
|
|
122
|
+
elif s == '%':
|
|
123
|
+
if token_type in (10, 11):
|
|
124
|
+
raise IncorrectSmarts('Query bond invalid')
|
|
125
|
+
elif token_type == 2:
|
|
126
|
+
raise IncorrectSmiles('(%10 case invalid')
|
|
127
|
+
elif token:
|
|
128
|
+
tokens.append((token_type, token))
|
|
129
|
+
token_type = 7
|
|
130
|
+
token = []
|
|
131
|
+
# bonds parser
|
|
132
|
+
elif s in '=#:-~': # bonds found
|
|
133
|
+
if token_type == 10:
|
|
134
|
+
token.append(replace_dict[s])
|
|
135
|
+
tokens.append((10, token))
|
|
136
|
+
token_type = token = None # finalize token
|
|
137
|
+
elif token_type == 11:
|
|
138
|
+
token_type = None
|
|
139
|
+
tokens.append((10, not_dict[s]))
|
|
140
|
+
else:
|
|
141
|
+
if token:
|
|
142
|
+
tokens.append((token_type, token))
|
|
143
|
+
token = None
|
|
144
|
+
token_type = 1
|
|
145
|
+
tokens.append((1, replace_dict[s]))
|
|
146
|
+
elif token_type in (10, 11): # expected bond symbol
|
|
147
|
+
raise IncorrectSmarts('query bond invalid')
|
|
148
|
+
elif s in r'\/':
|
|
149
|
+
if token:
|
|
150
|
+
tokens.append((token_type, token))
|
|
151
|
+
token = None
|
|
152
|
+
token_type = 9
|
|
153
|
+
tokens.append((9, s == '/')) # Up is true
|
|
154
|
+
elif s == '.':
|
|
155
|
+
if token:
|
|
156
|
+
tokens.append((token_type, token))
|
|
157
|
+
token = None
|
|
158
|
+
token_type = 4
|
|
159
|
+
tokens.append((4, None))
|
|
160
|
+
elif s == ';': # ;@ or ;!@ - in ring or not in ring bond
|
|
161
|
+
if token_type is not None and token_type != 1: # start of smiles or bond, list of bonds and not bond.
|
|
162
|
+
raise IncorrectSmarts('Ring bond token invalid')
|
|
163
|
+
token_type = 12
|
|
164
|
+
elif s == ',': # query bond separator
|
|
165
|
+
if token_type != 1:
|
|
166
|
+
raise IncorrectSmarts('Query bond invalid')
|
|
167
|
+
token_type = 10
|
|
168
|
+
token = [tokens.pop(-1)[1]]
|
|
169
|
+
elif s == '!': # query not bond
|
|
170
|
+
if token_type not in (0, 2, 3, 6, 8): # closures, brackets or atoms expected
|
|
171
|
+
raise IncorrectSmarts('Query bond invalid')
|
|
172
|
+
elif token:
|
|
173
|
+
tokens.append((token_type, token))
|
|
174
|
+
token = None
|
|
175
|
+
token_type = 11
|
|
176
|
+
# brackets
|
|
177
|
+
elif s == '(':
|
|
178
|
+
if token_type == 2: # barely opened
|
|
179
|
+
raise IncorrectSmiles('((')
|
|
180
|
+
elif token:
|
|
181
|
+
tokens.append((token_type, token))
|
|
182
|
+
token = None
|
|
183
|
+
token_type = 2
|
|
184
|
+
tokens.append((2, None))
|
|
185
|
+
elif s == ')':
|
|
186
|
+
if token_type == 2: # barely opened
|
|
187
|
+
raise IncorrectSmiles('()')
|
|
188
|
+
elif token:
|
|
189
|
+
tokens.append((token_type, token))
|
|
190
|
+
token = None
|
|
191
|
+
token_type = 3
|
|
192
|
+
tokens.append((3, None))
|
|
193
|
+
# simple atoms
|
|
194
|
+
elif s in 'NOPSFI': # organic atoms
|
|
195
|
+
if token:
|
|
196
|
+
tokens.append((token_type, token))
|
|
197
|
+
token = None
|
|
198
|
+
token_type = 0
|
|
199
|
+
tokens.append((0, s))
|
|
200
|
+
elif s in 'cnopsb': # aromatic ring atom
|
|
201
|
+
if token:
|
|
202
|
+
tokens.append((token_type, token))
|
|
203
|
+
token = None
|
|
204
|
+
token_type = 8
|
|
205
|
+
tokens.append((8, s.upper()))
|
|
206
|
+
elif s in 'CB': # flag possible Cl or Br
|
|
207
|
+
if token:
|
|
208
|
+
tokens.append((token_type, token))
|
|
209
|
+
token_type = 0
|
|
210
|
+
token = s
|
|
211
|
+
elif token_type == 0:
|
|
212
|
+
if s == 'l':
|
|
213
|
+
if token == 'C':
|
|
214
|
+
tokens.append((0, 'Cl'))
|
|
215
|
+
token = None
|
|
216
|
+
else:
|
|
217
|
+
raise IncorrectSmiles('invalid element Bl')
|
|
218
|
+
elif s == 'r':
|
|
219
|
+
if token == 'B':
|
|
220
|
+
tokens.append((0, 'Br'))
|
|
221
|
+
token = None
|
|
222
|
+
else:
|
|
223
|
+
raise IncorrectSmiles('invalid smiles for Cr')
|
|
224
|
+
else:
|
|
225
|
+
raise IncorrectSmiles('invalid smiles')
|
|
226
|
+
else:
|
|
227
|
+
raise IncorrectSmiles('invalid smiles')
|
|
228
|
+
|
|
229
|
+
if token_type == 5:
|
|
230
|
+
raise IncorrectSmiles('atom description has not finished')
|
|
231
|
+
elif token_type == 7:
|
|
232
|
+
if token:
|
|
233
|
+
tokens.append((6, int(token[0])))
|
|
234
|
+
else:
|
|
235
|
+
raise IncorrectSmiles('invalid %closure')
|
|
236
|
+
elif token:
|
|
237
|
+
tokens.append((token_type, token)) # C or B
|
|
238
|
+
return tokens
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _atom_parse(token):
|
|
242
|
+
# [isotope]Element[element][@[@]][H[n]][+-charge][:mapping]
|
|
243
|
+
_match = atom_re.fullmatch(token)
|
|
244
|
+
if _match is None:
|
|
245
|
+
raise IncorrectSmiles(f'atom token invalid {token}')
|
|
246
|
+
isotope, element, stereo, hydrogen, charge, mapping = _match.groups()
|
|
247
|
+
|
|
248
|
+
if isotope:
|
|
249
|
+
isotope = int(isotope)
|
|
250
|
+
|
|
251
|
+
if stereo:
|
|
252
|
+
stereo = stereo == '@'
|
|
253
|
+
|
|
254
|
+
if hydrogen:
|
|
255
|
+
if len(hydrogen) > 1:
|
|
256
|
+
hydrogen = int(hydrogen[1:])
|
|
257
|
+
else:
|
|
258
|
+
hydrogen = 1
|
|
259
|
+
else:
|
|
260
|
+
hydrogen = 0
|
|
261
|
+
|
|
262
|
+
if charge:
|
|
263
|
+
try:
|
|
264
|
+
charge = charge_dict[charge]
|
|
265
|
+
except KeyError:
|
|
266
|
+
raise IncorrectSmiles('charge token invalid')
|
|
267
|
+
else:
|
|
268
|
+
charge = 0
|
|
269
|
+
|
|
270
|
+
if mapping:
|
|
271
|
+
try:
|
|
272
|
+
mapping = int(mapping[1:])
|
|
273
|
+
except ValueError:
|
|
274
|
+
raise IncorrectSmiles('invalid mapping token')
|
|
275
|
+
|
|
276
|
+
if element in ('c', 'n', 'o', 'p', 's', 'as', 'se', 'b', 'te'):
|
|
277
|
+
_type = 8
|
|
278
|
+
element = element.capitalize()
|
|
279
|
+
else:
|
|
280
|
+
_type = 0
|
|
281
|
+
return _type, {'element': element, 'isotope': isotope, 'parsed_mapping': mapping, 'charge': charge,
|
|
282
|
+
'implicit_hydrogens': hydrogen, 'stereo': stereo}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _query_parse(token):
|
|
286
|
+
out = {}
|
|
287
|
+
if isotope := match(iso_re, token):
|
|
288
|
+
token = token[isotope.end():] # remove isotope substring
|
|
289
|
+
out['isotope'] = int(isotope.group())
|
|
290
|
+
if charge := search(chg_re, token):
|
|
291
|
+
token = token[:charge.start()] + token[charge.end():] # remove charge substring
|
|
292
|
+
out['charge'] = charge_dict[charge.group()]
|
|
293
|
+
|
|
294
|
+
if mapping := search(mpp_re, token):
|
|
295
|
+
token = token[:mapping.start()]
|
|
296
|
+
out['parsed_mapping'] = int(mapping.group()[1:])
|
|
297
|
+
|
|
298
|
+
if stereo := search(str_re, token): # drop stereo mark. unsupported
|
|
299
|
+
token = token[:stereo.start()] + token[stereo.end():]
|
|
300
|
+
out['stereo'] = stereo.group() == '@'
|
|
301
|
+
|
|
302
|
+
# supported only <;> and <,> logic. <&> and silent <&> not supported!
|
|
303
|
+
primitives = token.split(';')
|
|
304
|
+
if element := primitives[0]:
|
|
305
|
+
element = [int(x[1:]) if x.startswith('#') else x for x in element.split(',')]
|
|
306
|
+
if len(element) == 1:
|
|
307
|
+
element = element[0]
|
|
308
|
+
else:
|
|
309
|
+
raise IncorrectSmarts('Empty element')
|
|
310
|
+
out['element'] = element
|
|
311
|
+
|
|
312
|
+
for p in primitives[1:]: # parse hydrogens (h), neighbors (D), rings_sizes (r or !R), hybridization == 4 (a)
|
|
313
|
+
if not p:
|
|
314
|
+
continue
|
|
315
|
+
elif p == 'a': # aromatic atom
|
|
316
|
+
out['hybridization'] = 4
|
|
317
|
+
elif p == 'A': # ignore aliphatic mark. Ad-Hoc for Marwin.
|
|
318
|
+
continue
|
|
319
|
+
elif p == '!R':
|
|
320
|
+
out['ring_sizes'] = 0
|
|
321
|
+
elif p == 'M':
|
|
322
|
+
out['masked'] = True
|
|
323
|
+
else:
|
|
324
|
+
p = p.split(',')
|
|
325
|
+
if len(p) != 1 and len({x[0] for x in p}) > 1:
|
|
326
|
+
raise IncorrectSmarts('Unsupported OR statement')
|
|
327
|
+
elif (t := p[0][0]) not in ('D', 'h', 'r', 'x', 'z'):
|
|
328
|
+
raise IncorrectSmarts('Unsupported SMARTS primitive. Use only D, h, r, !R and a.')
|
|
329
|
+
# z and x private chython marks for hybridization and heteroatoms count
|
|
330
|
+
try:
|
|
331
|
+
p = [int(x[1:]) for x in p]
|
|
332
|
+
except ValueError:
|
|
333
|
+
raise IncorrectSmarts('Unsupported SMARTS primitive')
|
|
334
|
+
|
|
335
|
+
if t == 'D':
|
|
336
|
+
out['neighbors'] = p
|
|
337
|
+
elif t == 'h':
|
|
338
|
+
out['implicit_hydrogens'] = p
|
|
339
|
+
elif t == 'r': # r
|
|
340
|
+
out['ring_sizes'] = p
|
|
341
|
+
elif t == 'x':
|
|
342
|
+
out['heteroatoms'] = p
|
|
343
|
+
else: # z
|
|
344
|
+
out['hybridization'] = p
|
|
345
|
+
return 0, out
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def smiles_tokenize(smi):
|
|
349
|
+
tokens = _tokenize(smi)
|
|
350
|
+
out = []
|
|
351
|
+
for token_type, token in tokens:
|
|
352
|
+
if token_type in (0, 8): # simple atom
|
|
353
|
+
out.append((token_type, {'element': token}))
|
|
354
|
+
elif token_type == 5:
|
|
355
|
+
out.append(_atom_parse(token))
|
|
356
|
+
elif token_type == 10:
|
|
357
|
+
raise IncorrectSmiles('SMARTS detected')
|
|
358
|
+
else:
|
|
359
|
+
out.append((token_type, token))
|
|
360
|
+
return out
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def smarts_tokenize(smi):
|
|
364
|
+
tokens = _tokenize(smi)
|
|
365
|
+
out = []
|
|
366
|
+
for token_type, token in tokens:
|
|
367
|
+
if token_type in (0, 8): # simple atom
|
|
368
|
+
out.append((0, {'element': token}))
|
|
369
|
+
elif token_type == 5:
|
|
370
|
+
out.append(_query_parse(token))
|
|
371
|
+
else:
|
|
372
|
+
out.append((token_type, token))
|
|
373
|
+
return out
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
__all__ = ['smiles_tokenize', 'smarts_tokenize']
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2023-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 .wrapper import *
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__all__ = ['inchi']
|
|
Binary file
|