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,289 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025 Ramil Nugmanov <nougmanoff@protonmail.com>
|
|
4
|
+
# Copyright 2025 Tagir Akhmetshin <tagirshin@gmail.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
|
+
import pytest
|
|
21
|
+
from chython.exceptions import IncorrectSmiles
|
|
22
|
+
from chython.files.daylight.smarts import smarts
|
|
23
|
+
from chython import smiles
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_daylight_smarts_basic():
|
|
27
|
+
# Test basic atomic symbols
|
|
28
|
+
pattern = smarts('[C]') # aliphatic carbon
|
|
29
|
+
assert len(pattern) == 1
|
|
30
|
+
|
|
31
|
+
pattern = smarts('[N]') # nitrogen
|
|
32
|
+
assert len(pattern) == 1
|
|
33
|
+
|
|
34
|
+
pattern = smarts('[O]') # oxygen
|
|
35
|
+
assert len(pattern) == 1
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_daylight_smarts_empty():
|
|
39
|
+
# Test empty SMARTS
|
|
40
|
+
with pytest.raises(ValueError): # smarts() splits input and expects at least 1 value
|
|
41
|
+
smarts('')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_daylight_smarts_invalid():
|
|
45
|
+
# Test invalid SMARTS
|
|
46
|
+
with pytest.raises(IncorrectSmiles):
|
|
47
|
+
smarts('[') # unclosed bracket
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_daylight_smarts_bonds():
|
|
51
|
+
# Test bond primitives
|
|
52
|
+
pattern = smarts('CC') # single bond
|
|
53
|
+
assert len(pattern) == 2
|
|
54
|
+
|
|
55
|
+
pattern = smarts('C=C') # double bond
|
|
56
|
+
assert len(pattern) == 2
|
|
57
|
+
|
|
58
|
+
pattern = smarts('C#C') # triple bond
|
|
59
|
+
assert len(pattern) == 2
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_daylight_smarts_aromatic():
|
|
63
|
+
# Test aromatic specifications
|
|
64
|
+
pattern = smarts('CC=CC=C') # conjugated system
|
|
65
|
+
assert len(pattern) == 5
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_daylight_smarts_complex():
|
|
69
|
+
# Test complex patterns
|
|
70
|
+
pattern = smarts('CC(=O)O') # carboxylic acid
|
|
71
|
+
assert len(pattern) == 4
|
|
72
|
+
|
|
73
|
+
pattern = smarts('CN(C)C') # tertiary amine
|
|
74
|
+
assert len(pattern) == 4
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_daylight_smarts_functional_groups():
|
|
78
|
+
# Test common functional group patterns
|
|
79
|
+
# Create test molecules
|
|
80
|
+
ethanol = smiles('CCO')
|
|
81
|
+
benzene = smiles('c1ccccc1')
|
|
82
|
+
acetic_acid = smiles('CC(=O)O')
|
|
83
|
+
dimethylamine = smiles('CNC')
|
|
84
|
+
phenol = smiles('c1ccccc1O')
|
|
85
|
+
|
|
86
|
+
# Test alcohol pattern
|
|
87
|
+
alcohol_pattern = smarts('CO')
|
|
88
|
+
assert alcohol_pattern.is_substructure(ethanol) # should match ethanol
|
|
89
|
+
assert not alcohol_pattern.is_substructure(benzene) # should not match benzene
|
|
90
|
+
|
|
91
|
+
# Test carboxylic acid pattern
|
|
92
|
+
acid_pattern = smarts('CC(=O)O')
|
|
93
|
+
assert acid_pattern.is_substructure(acetic_acid)
|
|
94
|
+
assert not acid_pattern.is_substructure(ethanol)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_daylight_smarts_multiple_matches():
|
|
98
|
+
# Test patterns that can have multiple matches in a molecule
|
|
99
|
+
# Create test molecules
|
|
100
|
+
propanediol = smiles('OCCO') # 1,2-propanediol
|
|
101
|
+
diethylether = smiles('CCOCC') # diethyl ether
|
|
102
|
+
|
|
103
|
+
# Test alcohol pattern (should find two matches in propanediol)
|
|
104
|
+
alcohol_pattern = smarts('CO')
|
|
105
|
+
matches = list(alcohol_pattern.get_mapping(propanediol))
|
|
106
|
+
assert len(matches) == 2 # should find two alcohol groups
|
|
107
|
+
|
|
108
|
+
# Test ether pattern (should find one match in diethylether)
|
|
109
|
+
ether_pattern = smarts('COC')
|
|
110
|
+
matches = list(ether_pattern.get_mapping(diethylether))
|
|
111
|
+
assert len(matches) == 1 # should find one ether group
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_daylight_smarts_heterocycles():
|
|
115
|
+
# Test complex heterocyclic systems
|
|
116
|
+
# Create test molecules with sophisticated ring systems
|
|
117
|
+
isoxazole = smiles('c1noc1') # five-membered N-O ring
|
|
118
|
+
indole = smiles('c1ccc2ccnc2c1') # bicyclic with N
|
|
119
|
+
benzofuran = smiles('c1ccc2ccoc2c1') # bicyclic with O
|
|
120
|
+
|
|
121
|
+
# Test basic ring patterns
|
|
122
|
+
# Test individual atoms instead of full ring pattern
|
|
123
|
+
n_pattern = smarts('n') # aromatic nitrogen
|
|
124
|
+
assert n_pattern.is_substructure(isoxazole)
|
|
125
|
+
|
|
126
|
+
o_pattern = smarts('o') # aromatic oxygen
|
|
127
|
+
assert o_pattern.is_substructure(isoxazole)
|
|
128
|
+
|
|
129
|
+
# Test aromatic carbons
|
|
130
|
+
c_pattern = smarts('c') # aromatic carbon
|
|
131
|
+
assert c_pattern.is_substructure(indole)
|
|
132
|
+
assert c_pattern.is_substructure(benzofuran)
|
|
133
|
+
|
|
134
|
+
# Test aromatic ring fragments
|
|
135
|
+
c1c_pattern = smarts('c:c') # aromatic C-C bond with explicit aromatic bond
|
|
136
|
+
assert c1c_pattern.is_substructure(indole)
|
|
137
|
+
assert c1c_pattern.is_substructure(benzofuran)
|
|
138
|
+
|
|
139
|
+
# Test heteroatom connections
|
|
140
|
+
cn_pattern = smarts('c:n') # aromatic C-N bond
|
|
141
|
+
assert cn_pattern.is_substructure(indole)
|
|
142
|
+
|
|
143
|
+
co_pattern = smarts('c:o') # aromatic C-O bond
|
|
144
|
+
assert co_pattern.is_substructure(benzofuran)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_daylight_smarts_charged_aromatics():
|
|
148
|
+
# Test charged aromatic ring systems
|
|
149
|
+
pyridinium = smiles('c1cc[nH+]cc1') # protonated pyridine
|
|
150
|
+
n_oxide = smiles('c1cc[n+]([O-])cc1') # pyridine N-oxide
|
|
151
|
+
|
|
152
|
+
# Test patterns for charged systems
|
|
153
|
+
charged_n = smarts('[N+]') # positively charged nitrogen
|
|
154
|
+
assert charged_n.is_substructure(pyridinium)
|
|
155
|
+
assert charged_n.is_substructure(n_oxide)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_daylight_smarts_drug_patterns():
|
|
159
|
+
# Test drug-like substructure patterns
|
|
160
|
+
# Create test molecules representing drug-like structures
|
|
161
|
+
aspirin = smiles('CC(=O)OC1=CC=CC=C1C(=O)O') # aspirin
|
|
162
|
+
caffeine = smiles('CN1C=NC2=C1C(=O)N(C(=O)N2C)C') # caffeine
|
|
163
|
+
|
|
164
|
+
# Test common drug substructures
|
|
165
|
+
ester = smarts('CC(=O)O') # ester group
|
|
166
|
+
assert ester.is_substructure(aspirin)
|
|
167
|
+
|
|
168
|
+
amide = smarts('NC(=O)') # amide group
|
|
169
|
+
assert amide.is_substructure(caffeine)
|
|
170
|
+
|
|
171
|
+
carboxyl = smarts('C(=O)O') # carboxylic acid
|
|
172
|
+
assert carboxyl.is_substructure(aspirin)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_daylight_smarts_complex_rings():
|
|
176
|
+
# Test complex ring systems with multiple heteroatoms
|
|
177
|
+
tetrazole = smiles('c1nnnn1') # five-membered ring with 4 nitrogens
|
|
178
|
+
oxadiazole = smiles('c1nnoc1') # five-membered ring with O and 2 N
|
|
179
|
+
thiazole = smiles('c1scnc1') # five-membered ring with S and N
|
|
180
|
+
|
|
181
|
+
# Test patterns for heterocyclic systems
|
|
182
|
+
# Test individual atoms and bonds instead of full rings
|
|
183
|
+
n_pattern = smarts('n') # aromatic nitrogen
|
|
184
|
+
assert n_pattern.is_substructure(tetrazole)
|
|
185
|
+
assert n_pattern.is_substructure(oxadiazole)
|
|
186
|
+
|
|
187
|
+
o_pattern = smarts('o') # aromatic oxygen
|
|
188
|
+
assert o_pattern.is_substructure(oxadiazole)
|
|
189
|
+
|
|
190
|
+
s_pattern = smarts('s') # aromatic sulfur
|
|
191
|
+
assert s_pattern.is_substructure(thiazole)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_daylight_smarts_electronic_patterns():
|
|
195
|
+
# Test patterns for electron-withdrawing groups and electronic effects
|
|
196
|
+
|
|
197
|
+
# Create test molecules
|
|
198
|
+
acetamide = smiles('CC(=O)N') # amide
|
|
199
|
+
acetate = smiles('CC(=O)O') # ester
|
|
200
|
+
acetic_acid = smiles('CC(=O)O') # carboxylic acid
|
|
201
|
+
acetyl_chloride = smiles('CC(=O)Cl') # acid chloride
|
|
202
|
+
nitromethane = smiles('CN(=O)=O') # nitro compound
|
|
203
|
+
|
|
204
|
+
# Test carbonyl-adjacent patterns
|
|
205
|
+
carbonyl_pattern = smarts('CC(=O)') # basic carbonyl group
|
|
206
|
+
assert carbonyl_pattern.is_substructure(acetamide)
|
|
207
|
+
assert carbonyl_pattern.is_substructure(acetate)
|
|
208
|
+
assert carbonyl_pattern.is_substructure(acetic_acid)
|
|
209
|
+
|
|
210
|
+
# Test carbonyl with heteroatom patterns
|
|
211
|
+
carbonyl_n = smarts('C(=O)N') # C=O with adjacent N
|
|
212
|
+
assert carbonyl_n.is_substructure(acetamide)
|
|
213
|
+
|
|
214
|
+
carbonyl_o = smarts('C(=O)O') # C=O with adjacent O
|
|
215
|
+
assert carbonyl_o.is_substructure(acetate)
|
|
216
|
+
assert carbonyl_o.is_substructure(acetic_acid)
|
|
217
|
+
|
|
218
|
+
# Test multiple bond patterns
|
|
219
|
+
double_bond_o = smarts('C=O') # C=O double bond
|
|
220
|
+
assert double_bond_o.is_substructure(acetamide)
|
|
221
|
+
assert double_bond_o.is_substructure(acetate)
|
|
222
|
+
assert double_bond_o.is_substructure(acetic_acid)
|
|
223
|
+
|
|
224
|
+
# Test halogen patterns
|
|
225
|
+
carbonyl_halogen = smarts('C(=O)Cl') # acid chloride
|
|
226
|
+
assert carbonyl_halogen.is_substructure(acetyl_chloride)
|
|
227
|
+
|
|
228
|
+
# Test nitro group pattern
|
|
229
|
+
nitro_pattern = smarts('N(=O)=O') # nitro group
|
|
230
|
+
assert nitro_pattern.is_substructure(nitromethane)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_daylight_smarts_conjugated_systems():
|
|
234
|
+
# Test patterns for conjugated systems
|
|
235
|
+
|
|
236
|
+
# Create test molecules with conjugated systems
|
|
237
|
+
acrolein = smiles('C=CC=O') # conjugated alkene-carbonyl
|
|
238
|
+
cinnamaldehyde = smiles('c1ccccc1C=CC=O') # conjugated arene-alkene-carbonyl
|
|
239
|
+
acetophenone = smiles('CC(=O)c1ccccc1') # conjugated arene-carbonyl
|
|
240
|
+
|
|
241
|
+
# Test conjugated patterns
|
|
242
|
+
alkene_carbonyl = smarts('C=CC=O') # conjugated alkene-carbonyl
|
|
243
|
+
assert alkene_carbonyl.is_substructure(acrolein)
|
|
244
|
+
assert alkene_carbonyl.is_substructure(cinnamaldehyde)
|
|
245
|
+
|
|
246
|
+
# Test parts of the molecule separately
|
|
247
|
+
# Test aromatic carbons and bonds
|
|
248
|
+
c_pattern = smarts('c') # aromatic carbon
|
|
249
|
+
assert c_pattern.is_substructure(acetophenone)
|
|
250
|
+
|
|
251
|
+
cc_bond = smarts('cc') # aromatic C-C bond
|
|
252
|
+
assert cc_bond.is_substructure(acetophenone)
|
|
253
|
+
|
|
254
|
+
# Test carbonyl group
|
|
255
|
+
carbonyl = smarts('CC(=O)') # carbonyl group
|
|
256
|
+
assert carbonyl.is_substructure(acetophenone)
|
|
257
|
+
|
|
258
|
+
# Test extended conjugation
|
|
259
|
+
conj_bond = smarts('C=CC=O') # conjugated double bonds
|
|
260
|
+
assert conj_bond.is_substructure(cinnamaldehyde)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def test_daylight_smarts_complex_bicyclic():
|
|
264
|
+
# Test complex bicyclic pattern with N and S atoms
|
|
265
|
+
# Create test molecule - a simpler bicyclic system with N-S-N bridge
|
|
266
|
+
bicyclic = smiles('C1=CC=C2N=SN=C2C1') # simplified bicyclic molecule without methyl group
|
|
267
|
+
|
|
268
|
+
# Complex SMARTS pattern for bicyclic system
|
|
269
|
+
# Pattern matches: C1=C-C=C2-N=S-N=C2-C1
|
|
270
|
+
pattern = smarts('[#6]1=[#6]-[#6]=[#6]2-[#7]=[#16]-[#7]=[#6]2-[#6]1')
|
|
271
|
+
assert pattern.is_substructure(bicyclic)
|
|
272
|
+
|
|
273
|
+
# Test non-matching molecule
|
|
274
|
+
benzene = smiles('c1ccccc1')
|
|
275
|
+
assert not pattern.is_substructure(benzene)
|
|
276
|
+
|
|
277
|
+
# Test parts of the pattern
|
|
278
|
+
six_ring = smarts('[#6]1=[#6]-[#6]=[#6]-[#6]-[#6]1') # matches C1=C-C=C-C-C1
|
|
279
|
+
assert six_ring.is_substructure(bicyclic)
|
|
280
|
+
|
|
281
|
+
five_ring = smarts('[#6]1-[#7]=[#16]-[#7]=[#6]1') # matches C1-N=S-N=C1
|
|
282
|
+
assert five_ring.is_substructure(bicyclic)
|
|
283
|
+
|
|
284
|
+
# Test individual bonds
|
|
285
|
+
cn_double = smarts('[#6]=[#7]') # C=N double bond
|
|
286
|
+
assert cn_double.is_substructure(bicyclic)
|
|
287
|
+
|
|
288
|
+
ns_double = smarts('[#7]=[#16]') # N=S double bond
|
|
289
|
+
assert ns_double.is_substructure(bicyclic)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025 Ramil Nugmanov <nougmanoff@protonmail.com>
|
|
4
|
+
# Copyright 2025 Tagir Akhmetshin <tagirshin@gmail.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
|
+
import pytest
|
|
21
|
+
from chython.exceptions import IncorrectSmiles
|
|
22
|
+
from chython.files.daylight.parser import parser
|
|
23
|
+
from chython.files.daylight.tokenize import smiles_tokenize
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_daylight_smiles_basic():
|
|
27
|
+
# Test basic SMILES
|
|
28
|
+
result = parser(list(smiles_tokenize('CC')), True)
|
|
29
|
+
assert len(result['atoms']) == 2
|
|
30
|
+
assert len(result['bonds']) == 1
|
|
31
|
+
|
|
32
|
+
result = parser(list(smiles_tokenize('O')), True)
|
|
33
|
+
assert len(result['atoms']) == 1
|
|
34
|
+
assert not result['bonds']
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_daylight_smiles_empty():
|
|
38
|
+
# Test empty SMILES
|
|
39
|
+
tokens = list(smiles_tokenize(''))
|
|
40
|
+
assert len(tokens) == 0 # empty string should produce empty token list
|
|
41
|
+
|
|
42
|
+
# Empty token list should raise error when parsing
|
|
43
|
+
with pytest.raises(IndexError):
|
|
44
|
+
parser(tokens, True)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_daylight_smiles_invalid():
|
|
48
|
+
# Test invalid SMILES
|
|
49
|
+
with pytest.raises(IncorrectSmiles):
|
|
50
|
+
parser(list(smiles_tokenize('C1CC')), True) # unclosed cycle
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_daylight_smiles_complex():
|
|
54
|
+
# Test complex SMILES
|
|
55
|
+
result = parser(list(smiles_tokenize('C1=CC=CC=C1')), True)
|
|
56
|
+
assert len(result['atoms']) == 6
|
|
57
|
+
assert len(result['bonds']) == 6
|
|
58
|
+
|
|
59
|
+
result = parser(list(smiles_tokenize('C(=O)O')), True)
|
|
60
|
+
assert len(result['atoms']) == 3
|
|
61
|
+
assert len(result['bonds']) == 2
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_daylight_smiles_charged():
|
|
65
|
+
# Test charged species
|
|
66
|
+
result = parser(list(smiles_tokenize('[NH4+]')), True)
|
|
67
|
+
assert len(result['atoms']) == 1
|
|
68
|
+
|
|
69
|
+
result = parser(list(smiles_tokenize('[OH-]')), True)
|
|
70
|
+
assert len(result['atoms']) == 1
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_daylight_smiles_aromatic():
|
|
74
|
+
# Test aromatic SMILES
|
|
75
|
+
result = parser(list(smiles_tokenize('c1ccccc1')), True)
|
|
76
|
+
assert len(result['atoms']) == 6
|
|
77
|
+
assert len(result['bonds']) == 6
|
|
78
|
+
|
|
79
|
+
result = parser(list(smiles_tokenize('n1cccc1')), True)
|
|
80
|
+
assert len(result['atoms']) == 5
|
|
81
|
+
assert len(result['bonds']) == 5
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_daylight_smiles_isotopes():
|
|
85
|
+
# Test isotope labels
|
|
86
|
+
result = parser(list(smiles_tokenize('[2H]O')), True)
|
|
87
|
+
assert len(result['atoms']) == 2
|
|
88
|
+
assert result['atoms'][0]['isotope'] == 2
|
|
89
|
+
|
|
90
|
+
result = parser(list(smiles_tokenize('[13C]')), True)
|
|
91
|
+
assert len(result['atoms']) == 1
|
|
92
|
+
assert result['atoms'][0]['isotope'] == 13
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_daylight_smiles_stereo():
|
|
96
|
+
# Test stereochemistry
|
|
97
|
+
result = parser(list(smiles_tokenize('C/C=C/C')), True)
|
|
98
|
+
assert result['stereo_bonds']
|
|
99
|
+
|
|
100
|
+
result = parser(list(smiles_tokenize('C/C=C\\C')), True)
|
|
101
|
+
assert result['stereo_bonds']
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025 Ramil Nugmanov <nougmanoff@protonmail.com>
|
|
4
|
+
# Copyright 2025 Tagir Akhmetshin <tagirshin@gmail.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
|
+
import pytest
|
|
21
|
+
from chython.exceptions import IncorrectSmiles, IncorrectSmarts
|
|
22
|
+
from chython.files.daylight.parser import parser
|
|
23
|
+
from chython.files.daylight.tokenize import smiles_tokenize, smarts_tokenize
|
|
24
|
+
from chython.files.daylight.smarts import smarts
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_parser_basic():
|
|
28
|
+
# Test basic parsing
|
|
29
|
+
result = parser(list(smiles_tokenize('CC')), True)
|
|
30
|
+
assert len(result['atoms']) == 2
|
|
31
|
+
assert len(result['bonds']) == 1
|
|
32
|
+
|
|
33
|
+
result = parser(list(smiles_tokenize('O')), True)
|
|
34
|
+
assert len(result['atoms']) == 1
|
|
35
|
+
assert not result['bonds']
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_parser_cycles():
|
|
39
|
+
# Test cycle notation
|
|
40
|
+
result = parser(list(smiles_tokenize('C1CCC1')), True)
|
|
41
|
+
assert len(result['atoms']) == 4
|
|
42
|
+
assert len(result['bonds']) == 4
|
|
43
|
+
|
|
44
|
+
# Test multiple cycles
|
|
45
|
+
result = parser(list(smiles_tokenize('C1CC2CCC12')), True)
|
|
46
|
+
assert len(result['atoms']) == 6
|
|
47
|
+
assert len(result['bonds']) == 7
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_parser_branches():
|
|
51
|
+
# Test branched structures
|
|
52
|
+
result = parser(list(smiles_tokenize('CC(C)C')), True)
|
|
53
|
+
assert len(result['atoms']) == 4
|
|
54
|
+
assert len(result['bonds']) == 3
|
|
55
|
+
|
|
56
|
+
# Test nested branches
|
|
57
|
+
result = parser(list(smiles_tokenize('CC(C(C)C)C')), True)
|
|
58
|
+
assert len(result['atoms']) == 6
|
|
59
|
+
assert len(result['bonds']) == 5
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_parser_errors():
|
|
63
|
+
# Test invalid structures
|
|
64
|
+
with pytest.raises(IncorrectSmiles):
|
|
65
|
+
parser(list(smiles_tokenize('C1CC')), True) # unclosed cycle
|
|
66
|
+
|
|
67
|
+
with pytest.raises(IncorrectSmiles):
|
|
68
|
+
parser(list(smiles_tokenize('C((C)')), True) # unmatched parentheses
|
|
69
|
+
|
|
70
|
+
with pytest.raises(IncorrectSmiles):
|
|
71
|
+
parser(list(smiles_tokenize('C1CC2')), True) # unclosed cycles
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_parser_stereo():
|
|
75
|
+
# Test stereo bonds
|
|
76
|
+
result = parser(list(smiles_tokenize('C/C=C/C')), True)
|
|
77
|
+
assert result['stereo_bonds']
|
|
78
|
+
|
|
79
|
+
result = parser(list(smiles_tokenize('C/C=C\\C')), True)
|
|
80
|
+
assert result['stereo_bonds']
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_parser_aromatic():
|
|
84
|
+
# Test aromatic structures
|
|
85
|
+
result = parser(list(smiles_tokenize('c1ccccc1')), True)
|
|
86
|
+
assert len(result['atoms']) == 6
|
|
87
|
+
assert len(result['bonds']) == 6
|
|
88
|
+
|
|
89
|
+
result = parser(list(smiles_tokenize('n1ccccc1')), True)
|
|
90
|
+
assert len(result['atoms']) == 6
|
|
91
|
+
assert len(result['bonds']) == 6
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_parser_smarts():
|
|
95
|
+
# Test SMARTS parsing with supported primitives
|
|
96
|
+
result = parser(list(smarts_tokenize('[D2]')), False)
|
|
97
|
+
assert len(result['atoms']) == 1
|
|
98
|
+
|
|
99
|
+
result = parser(list(smarts_tokenize('[h1]')), False)
|
|
100
|
+
assert len(result['atoms']) == 1
|
|
101
|
+
|
|
102
|
+
result = parser(list(smarts_tokenize('[!R]')), False)
|
|
103
|
+
assert len(result['atoms']) == 1
|
|
104
|
+
|
|
105
|
+
result = parser(list(smarts_tokenize('[r5]')), False)
|
|
106
|
+
assert len(result['atoms']) == 1
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_parser_smarts_errors():
|
|
110
|
+
# Test invalid SMARTS
|
|
111
|
+
with pytest.raises(IncorrectSmiles):
|
|
112
|
+
parser(list(smarts_tokenize('[')), False) # unclosed bracket
|
|
113
|
+
|
|
114
|
+
with pytest.raises(IncorrectSmiles):
|
|
115
|
+
parser(list(smarts_tokenize('[C')), False) # unclosed bracket
|
|
116
|
+
|
|
117
|
+
with pytest.raises(IncorrectSmiles):
|
|
118
|
+
parser(list(smarts_tokenize('C1C')), False) # unclosed ring
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025 Ramil Nugmanov <nougmanoff@protonmail.com>
|
|
4
|
+
# Copyright 2025 Tagir Akhmetshin <tagirshin@gmail.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
|
+
from chython.files.daylight.tokenize import smiles_tokenize, smarts_tokenize
|
|
21
|
+
from chython.containers import QueryBond
|
|
22
|
+
from chython.exceptions import IncorrectSmiles
|
|
23
|
+
from pytest import raises
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_smiles_tokenize():
|
|
27
|
+
assert smiles_tokenize('C') == [(0, {'element': 'C'})]
|
|
28
|
+
assert smiles_tokenize('CC') == [(0, {'element': 'C'}), (0, {'element': 'C'})]
|
|
29
|
+
assert smiles_tokenize('C=O') == [(0, {'element': 'C'}), (1, 2), (0, {'element': 'O'})]
|
|
30
|
+
assert smiles_tokenize('C(O)N') == [(0, {'element': 'C'}), (2, None), (0, {'element': 'O'}),
|
|
31
|
+
(3, None), (0, {'element': 'N'})]
|
|
32
|
+
assert smiles_tokenize('C2CC2') == [(0, {'element': 'C'}), (6, 2), (0, {'element': 'C'}),
|
|
33
|
+
(0, {'element': 'C'}), (6, 2)]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_smiles_tokenize_atom():
|
|
37
|
+
assert smiles_tokenize('[NH4+]') == [(0, {'element': 'N', 'isotope': None, 'parsed_mapping': None, 'charge': 1,
|
|
38
|
+
'implicit_hydrogens': 4, 'stereo': None})]
|
|
39
|
+
assert smiles_tokenize('[14N]') == [(0, {'element': 'N', 'isotope': 14, 'parsed_mapping': None, 'charge': 0,
|
|
40
|
+
'implicit_hydrogens': 0, 'stereo': None})]
|
|
41
|
+
assert smiles_tokenize('[N@H]') == [(0, {'element': 'N', 'isotope': None, 'parsed_mapping': None, 'charge': 0,
|
|
42
|
+
'implicit_hydrogens': 1, 'stereo': True})]
|
|
43
|
+
assert smiles_tokenize('[N@@H--]') == [(0, {'element': 'N', 'isotope': None, 'parsed_mapping': None, 'charge': -2,
|
|
44
|
+
'implicit_hydrogens': 1, 'stereo': False})]
|
|
45
|
+
assert smiles_tokenize('[N@+3]') == [(0, {'element': 'N', 'isotope': None, 'parsed_mapping': None, 'charge': 3,
|
|
46
|
+
'implicit_hydrogens': 0, 'stereo': True})]
|
|
47
|
+
assert smiles_tokenize('[CH2:2]') == [(0, {'element': 'C', 'isotope': None, 'parsed_mapping': 2, 'charge': 0,
|
|
48
|
+
'implicit_hydrogens': 2, 'stereo': None})]
|
|
49
|
+
with raises(IncorrectSmiles):
|
|
50
|
+
smiles_tokenize('[@N]')
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_smarts_tokenize_atom():
|
|
54
|
+
# Test basic SMARTS tokenization
|
|
55
|
+
assert smarts_tokenize('[C]') == [(0, {'element': 'C'})]
|
|
56
|
+
assert smarts_tokenize('[C,N]') == [(0, {'element': ['C', 'N']})]
|
|
57
|
+
assert smarts_tokenize('[C+]') == [(0, {'charge': 1, 'element': 'C'})]
|
|
58
|
+
assert smarts_tokenize('[#1]') == [(0, {'element': 1})]
|
|
59
|
+
assert smarts_tokenize('[C;h1;@]') == [(0, {'element': 'C', 'implicit_hydrogens': [1], 'stereo': True})]
|
|
60
|
+
assert smarts_tokenize('[O;z1,z2;x1]') == [(0, {'element': 'O', 'heteroatoms': [1], 'hybridization': [1, 2]})]
|
|
61
|
+
assert smarts_tokenize('[Se;a;D1,D2;r4,r7:3]') == [(0, {'parsed_mapping': 3, 'element': 'Se', 'hybridization': 4, 'neighbors': [1, 2], 'ring_sizes': [4, 7]})]
|
|
62
|
+
assert smarts_tokenize('[Cl;M]') == [(0, {'element': 'Cl', 'masked': True})]
|
|
63
|
+
assert smarts_tokenize('[A:1]') == [(0, {'parsed_mapping': 1, 'element': 'A'})]
|
|
64
|
+
assert smarts_tokenize('[M]') == [(0, {'element': 'M'})]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_smarts_tokenize_bonds():
|
|
68
|
+
assert smarts_tokenize('[C][C]') == [(0, {'element': 'C'}), (0, {'element': 'C'})]
|
|
69
|
+
assert smarts_tokenize('[C]-[C]') == [(0, {'element': 'C'}), (1, 1), (0, {'element': 'C'})]
|
|
70
|
+
assert smarts_tokenize('[C]~[C]') == [(0, {'element': 'C'}), (1, 8), (0, {'element': 'C'})]
|
|
71
|
+
assert smarts_tokenize('[C]!:[C]') == [(0, {'element': 'C'}), (10, [1, 2, 3]), (0, {'element': 'C'})]
|
|
72
|
+
assert smarts_tokenize('[C]-,=[C]') == [(0, {'element': 'C'}), (10, [1, 2]), (0, {'element': 'C'})]
|
|
73
|
+
assert smarts_tokenize('[C]-;@[C]') == [(0, {'element': 'C'}), (12, QueryBond(1, True)), (0, {'element': 'C'})]
|
|
74
|
+
assert smarts_tokenize('[C]!-;!@[C]') == [(0, {'element': 'C'}), (12, QueryBond((2, 3, 4), False)),
|
|
75
|
+
(0, {'element': 'C'})]
|
|
76
|
+
assert smarts_tokenize('[C]-,=;!@[C]') == [(0, {'element': 'C'}), (12, QueryBond((1, 2), False)),
|
|
77
|
+
(0, {'element': 'C'})]
|