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.
Files changed (171) hide show
  1. chython/__init__.py +31 -0
  2. chython/_functions.py +69 -0
  3. chython/algorithms/__init__.py +18 -0
  4. chython/algorithms/_isomorphism.cp312-win_amd64.pyd +0 -0
  5. chython/algorithms/_isomorphism.pyx +189 -0
  6. chython/algorithms/aromatics/__init__.py +27 -0
  7. chython/algorithms/aromatics/_rules.py +124 -0
  8. chython/algorithms/aromatics/kekule.py +530 -0
  9. chython/algorithms/aromatics/test/__init__.py +18 -0
  10. chython/algorithms/aromatics/test/test_kekule.py +160 -0
  11. chython/algorithms/aromatics/test/test_thiele.py +263 -0
  12. chython/algorithms/aromatics/thiele.py +226 -0
  13. chython/algorithms/calculate2d/__init__.py +23 -0
  14. chython/algorithms/calculate2d/clean2d.js +1 -0
  15. chython/algorithms/calculate2d/molecule.py +149 -0
  16. chython/algorithms/calculate2d/reaction.py +80 -0
  17. chython/algorithms/depict.py +523 -0
  18. chython/algorithms/fingerprints/__init__.py +48 -0
  19. chython/algorithms/fingerprints/linear.py +208 -0
  20. chython/algorithms/fingerprints/morgan.py +137 -0
  21. chython/algorithms/fingerprints/test/__init__.py +18 -0
  22. chython/algorithms/fingerprints/test/test_linear.py +127 -0
  23. chython/algorithms/fingerprints/test/test_morgan.py +79 -0
  24. chython/algorithms/isomorphism.py +667 -0
  25. chython/algorithms/mapping/__init__.py +28 -0
  26. chython/algorithms/mapping/_groups.py +94 -0
  27. chython/algorithms/mapping/_reactions.py +72 -0
  28. chython/algorithms/mapping/attention.py +216 -0
  29. chython/algorithms/mapping/fixmapper.py +84 -0
  30. chython/algorithms/mapping/groups.py +137 -0
  31. chython/algorithms/mcs.py +202 -0
  32. chython/algorithms/morgan.py +82 -0
  33. chython/algorithms/rings.py +551 -0
  34. chython/algorithms/smiles.py +556 -0
  35. chython/algorithms/standardize/__init__.py +30 -0
  36. chython/algorithms/standardize/_charged.py +115 -0
  37. chython/algorithms/standardize/_groups.py +926 -0
  38. chython/algorithms/standardize/_metal_organics.py +189 -0
  39. chython/algorithms/standardize/_reagents.py +44 -0
  40. chython/algorithms/standardize/_salts.py +68 -0
  41. chython/algorithms/standardize/molecule.py +479 -0
  42. chython/algorithms/standardize/reaction.py +429 -0
  43. chython/algorithms/standardize/resonance.py +196 -0
  44. chython/algorithms/standardize/salts.py +128 -0
  45. chython/algorithms/standardize/saturation.py +378 -0
  46. chython/algorithms/standardize/test/__init__.py +18 -0
  47. chython/algorithms/standardize/test/test_groups.py +118 -0
  48. chython/algorithms/stereo.py +1222 -0
  49. chython/algorithms/tautomers/__init__.py +171 -0
  50. chython/algorithms/tautomers/_acid.py +59 -0
  51. chython/algorithms/tautomers/_base.py +116 -0
  52. chython/algorithms/tautomers/_keto_enol.py +107 -0
  53. chython/algorithms/tautomers/acid_base.py +214 -0
  54. chython/algorithms/tautomers/heteroarenes.py +108 -0
  55. chython/algorithms/tautomers/keto_enol.py +148 -0
  56. chython/algorithms/tautomers/test/__init__.py +18 -0
  57. chython/algorithms/tautomers/test/test_tautomers.py +80 -0
  58. chython/algorithms/test/__init__.py +18 -0
  59. chython/algorithms/test/test_isomorphism.py +58 -0
  60. chython/algorithms/test/test_smiles.py +76 -0
  61. chython/algorithms/x3dom.py +355 -0
  62. chython/containers/__init__.py +43 -0
  63. chython/containers/_pack_v2.cp312-win_amd64.pyd +0 -0
  64. chython/containers/_pack_v2.pyx +271 -0
  65. chython/containers/_unpack_v0v2.cp312-win_amd64.pyd +0 -0
  66. chython/containers/_unpack_v0v2.pyx +300 -0
  67. chython/containers/bonds.py +255 -0
  68. chython/containers/cgr.py +115 -0
  69. chython/containers/graph.py +200 -0
  70. chython/containers/molecule.py +695 -0
  71. chython/containers/query.py +66 -0
  72. chython/containers/reaction.py +324 -0
  73. chython/exceptions.py +114 -0
  74. chython/files/MRVrw.py +523 -0
  75. chython/files/PDBrw.py +257 -0
  76. chython/files/RDFrw.py +298 -0
  77. chython/files/SDFrw.py +221 -0
  78. chython/files/__init__.py +29 -0
  79. chython/files/_convert.py +207 -0
  80. chython/files/_mapping.py +117 -0
  81. chython/files/_xyz.cp312-win_amd64.pyd +0 -0
  82. chython/files/_xyz.pyx +74 -0
  83. chython/files/daylight/__init__.py +24 -0
  84. chython/files/daylight/parser.py +157 -0
  85. chython/files/daylight/smarts.py +107 -0
  86. chython/files/daylight/smiles.py +245 -0
  87. chython/files/daylight/test/__init__.py +18 -0
  88. chython/files/daylight/test/test_daylight_smarts.py +289 -0
  89. chython/files/daylight/test/test_daylight_smiles.py +101 -0
  90. chython/files/daylight/test/test_parser.py +118 -0
  91. chython/files/daylight/test/test_tokenize.py +77 -0
  92. chython/files/daylight/tokenize.py +376 -0
  93. chython/files/libinchi/__init__.py +22 -0
  94. chython/files/libinchi/libinchi.dll +0 -0
  95. chython/files/libinchi/wrapper.py +555 -0
  96. chython/files/mdl/__init__.py +25 -0
  97. chython/files/mdl/emol.py +209 -0
  98. chython/files/mdl/erxn.py +67 -0
  99. chython/files/mdl/mol.py +144 -0
  100. chython/files/mdl/read.py +225 -0
  101. chython/files/mdl/rxn.py +67 -0
  102. chython/files/mdl/stereo.py +63 -0
  103. chython/files/mdl/write.py +165 -0
  104. chython/files/xyz.py +78 -0
  105. chython/periodictable/__init__.py +69 -0
  106. chython/periodictable/base/__init__.py +25 -0
  107. chython/periodictable/base/dynamic.py +149 -0
  108. chython/periodictable/base/element.py +452 -0
  109. chython/periodictable/base/groups.py +90 -0
  110. chython/periodictable/base/periods.py +46 -0
  111. chython/periodictable/base/query.py +473 -0
  112. chython/periodictable/base/vector.py +119 -0
  113. chython/periodictable/groupI.py +252 -0
  114. chython/periodictable/groupII.py +223 -0
  115. chython/periodictable/groupIII.py +1154 -0
  116. chython/periodictable/groupIV.py +210 -0
  117. chython/periodictable/groupIX.py +196 -0
  118. chython/periodictable/groupV.py +192 -0
  119. chython/periodictable/groupVI.py +183 -0
  120. chython/periodictable/groupVII.py +162 -0
  121. chython/periodictable/groupVIII.py +161 -0
  122. chython/periodictable/groupX.py +166 -0
  123. chython/periodictable/groupXI.py +164 -0
  124. chython/periodictable/groupXII.py +158 -0
  125. chython/periodictable/groupXIII.py +239 -0
  126. chython/periodictable/groupXIV.py +262 -0
  127. chython/periodictable/groupXV.py +276 -0
  128. chython/periodictable/groupXVI.py +457 -0
  129. chython/periodictable/groupXVII.py +324 -0
  130. chython/periodictable/groupXVIII.py +260 -0
  131. chython/reactor/__init__.py +24 -0
  132. chython/reactor/base.py +212 -0
  133. chython/reactor/deprotection.py +565 -0
  134. chython/reactor/groups.py +128 -0
  135. chython/reactor/reactions/__init__.py +157 -0
  136. chython/reactor/reactions/_amidation.py +58 -0
  137. chython/reactor/reactions/_amine_isocyanate.py +52 -0
  138. chython/reactor/reactions/_buchwald_hartwig.py +50 -0
  139. chython/reactor/reactions/_esterification.py +49 -0
  140. chython/reactor/reactions/_macmillan.py +43 -0
  141. chython/reactor/reactions/_reductive_amination.py +50 -0
  142. chython/reactor/reactions/_sonogashira.py +49 -0
  143. chython/reactor/reactions/_sulfonamidation.py +52 -0
  144. chython/reactor/reactions/_suzuki_miyaura.py +88 -0
  145. chython/reactor/reactions/ufe.py +99 -0
  146. chython/reactor/reactor.py +170 -0
  147. chython/reactor/retro/__init__.py +77 -0
  148. chython/reactor/retro/_amidation.py +39 -0
  149. chython/reactor/retro/_aryl_amination.py +32 -0
  150. chython/reactor/retro/_mitsunobu.py +49 -0
  151. chython/reactor/retro/_sonogashira.py +49 -0
  152. chython/reactor/retro/_suzuki_miyaura.py +54 -0
  153. chython/reactor/scaffold.py +122 -0
  154. chython/reactor/test/__init__.py +18 -0
  155. chython/reactor/test/test_deprotection.py +57 -0
  156. chython/reactor/test/test_reactor.py +40 -0
  157. chython/reactor/test/test_scaffold.py +57 -0
  158. chython/reactor/test/test_transformer.py +40 -0
  159. chython/reactor/transformer.py +62 -0
  160. chython/utils/__init__.py +57 -0
  161. chython/utils/free_wilson.py +114 -0
  162. chython/utils/grid.py +130 -0
  163. chython/utils/rdkit.py +165 -0
  164. chython/utils/retro.py +139 -0
  165. chython/utils/svg.py +57 -0
  166. chython/utils/test/__init__.py +18 -0
  167. chython/utils/test/test_rdkit.py +71 -0
  168. chython-2.4.dist-info/LICENSE +165 -0
  169. chython-2.4.dist-info/METADATA +101 -0
  170. chython-2.4.dist-info/RECORD +171 -0
  171. 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'})]