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,103 @@
|
|
|
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 collections import deque, defaultdict
|
|
20
|
+
from itertools import product
|
|
21
|
+
from ..aromatics.kekule import _kekule_component
|
|
22
|
+
from ...exceptions import InvalidAromaticRing
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# atomic number constants
|
|
26
|
+
B = 5
|
|
27
|
+
C = 6
|
|
28
|
+
N = 7
|
|
29
|
+
P = 15
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class HeteroArenes:
|
|
33
|
+
__slots__ = ()
|
|
34
|
+
|
|
35
|
+
def _enumerate_hetero_arene_tautomers(self):
|
|
36
|
+
atoms = self._atoms
|
|
37
|
+
bonds = self._bonds
|
|
38
|
+
|
|
39
|
+
rings = defaultdict(list) # aromatic skeleton
|
|
40
|
+
for n, m_bond in bonds.items():
|
|
41
|
+
for m, bond in m_bond.items():
|
|
42
|
+
if bond == 4:
|
|
43
|
+
rings[n].append(m)
|
|
44
|
+
if not rings:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
acceptors = set()
|
|
48
|
+
donors = set()
|
|
49
|
+
single_bonded = set()
|
|
50
|
+
for n, ms in rings.items():
|
|
51
|
+
a = atoms[n]
|
|
52
|
+
if len(ms) == 2:
|
|
53
|
+
if a in (B, N, P):
|
|
54
|
+
if not a.charge and not a.is_radical:
|
|
55
|
+
# only neutral B, N, P
|
|
56
|
+
if a.implicit_hydrogens: # pyrrole
|
|
57
|
+
donors.add(n)
|
|
58
|
+
elif len(bonds[n]) == 2: # pyridine
|
|
59
|
+
acceptors.add(n)
|
|
60
|
+
else:
|
|
61
|
+
single_bonded.add(n)
|
|
62
|
+
elif a.charge == -1 and a == C: # ferrocene
|
|
63
|
+
single_bonded.add(n)
|
|
64
|
+
elif len(ms) == 3 and a in (B, N, P) and not a.charge and not a.is_radical:
|
|
65
|
+
single_bonded.add(n)
|
|
66
|
+
if not donors or not acceptors:
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
atoms = set(rings)
|
|
70
|
+
components = []
|
|
71
|
+
while atoms:
|
|
72
|
+
start = atoms.pop()
|
|
73
|
+
component = {start: rings[start]}
|
|
74
|
+
queue = deque([start])
|
|
75
|
+
while queue:
|
|
76
|
+
current = queue.popleft()
|
|
77
|
+
for n in rings[current]:
|
|
78
|
+
if n not in component:
|
|
79
|
+
queue.append(n)
|
|
80
|
+
component[n] = rings[n]
|
|
81
|
+
|
|
82
|
+
atoms.difference_update(component)
|
|
83
|
+
if donors.isdisjoint(component) or acceptors.isdisjoint(component):
|
|
84
|
+
continue
|
|
85
|
+
components.append(component)
|
|
86
|
+
|
|
87
|
+
if not components:
|
|
88
|
+
return
|
|
89
|
+
for component in components:
|
|
90
|
+
for d, a in product(component.keys() & donors, component.keys() & acceptors):
|
|
91
|
+
sb = component.keys() & single_bonded
|
|
92
|
+
sb.add(a) # now pyrrole
|
|
93
|
+
try:
|
|
94
|
+
next(_kekule_component(component, sb, (), 0))
|
|
95
|
+
except InvalidAromaticRing:
|
|
96
|
+
continue
|
|
97
|
+
mol = self.copy(keep_sssr=True, keep_components=True)
|
|
98
|
+
mol._atoms[d]._implicit_hydrogens = 0
|
|
99
|
+
mol._atoms[a]._implicit_hydrogens = 1
|
|
100
|
+
yield mol
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
__all__ = ['HeteroArenes']
|
|
@@ -0,0 +1,143 @@
|
|
|
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 collections import defaultdict
|
|
20
|
+
from functools import cached_property
|
|
21
|
+
from itertools import chain, repeat
|
|
22
|
+
from ._keto_enol import *
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# atomic number constants
|
|
26
|
+
C = 6
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class KetoEnol:
|
|
30
|
+
__slots__ = ()
|
|
31
|
+
|
|
32
|
+
def _enumerate_keto_enol_tautomers(self, partial=False):
|
|
33
|
+
for fix, ket in self.__enumerate_bonds(partial):
|
|
34
|
+
if ket:
|
|
35
|
+
a = fix[-1][1]
|
|
36
|
+
d = fix[0][0]
|
|
37
|
+
else:
|
|
38
|
+
a = fix[0][0]
|
|
39
|
+
d = fix[-1][1]
|
|
40
|
+
|
|
41
|
+
mol = self.copy(keep_sssr=True, keep_components=True)
|
|
42
|
+
m_bonds = mol._bonds
|
|
43
|
+
for n, m, b in fix:
|
|
44
|
+
m_bonds[n][m]._order = b
|
|
45
|
+
|
|
46
|
+
a = mol._atoms[a]
|
|
47
|
+
d = mol._atoms[d]
|
|
48
|
+
a._implicit_hydrogens += 1
|
|
49
|
+
d._implicit_hydrogens -= 1
|
|
50
|
+
a._hybridization -= 1 # -C=X>=C-X or -C=C=X>=C-C=X
|
|
51
|
+
d._hybridization += 1
|
|
52
|
+
yield mol, ket
|
|
53
|
+
|
|
54
|
+
@cached_property
|
|
55
|
+
def _sugar_groups(self):
|
|
56
|
+
ek = []
|
|
57
|
+
for mapping in sugar_group.get_mapping(self, automorphism_filter=False):
|
|
58
|
+
e, k = mapping[1], mapping[2]
|
|
59
|
+
ek.append((e, k))
|
|
60
|
+
return ek
|
|
61
|
+
|
|
62
|
+
def __enumerate_bonds(self, partial):
|
|
63
|
+
atoms = self._atoms
|
|
64
|
+
bonds = self._bonds
|
|
65
|
+
rings = self.atoms_rings_sizes
|
|
66
|
+
|
|
67
|
+
# search neutral oxygen and nitrogen
|
|
68
|
+
donors = defaultdict(set)
|
|
69
|
+
acceptors = defaultdict(set)
|
|
70
|
+
for q in enol_rules:
|
|
71
|
+
for mapping in q.get_mapping(self, automorphism_filter=False):
|
|
72
|
+
donors[mapping[1]].add(mapping[2])
|
|
73
|
+
for q in keto_rules:
|
|
74
|
+
for mapping in q.get_mapping(self, automorphism_filter=False):
|
|
75
|
+
acceptors[mapping[1]].add(mapping[2])
|
|
76
|
+
|
|
77
|
+
for (atom, dirs), hydrogen, anti in chain(zip(donors.items(), repeat(True), repeat(acceptors)),
|
|
78
|
+
zip(acceptors.items(), repeat(False), repeat(donors))):
|
|
79
|
+
path = []
|
|
80
|
+
seen = {atom}
|
|
81
|
+
stack = [(atom, n, 2 if hydrogen else 1, 0) for n in dirs]
|
|
82
|
+
while stack:
|
|
83
|
+
last, current, bond, depth = stack.pop()
|
|
84
|
+
|
|
85
|
+
if partial and path and not len(path) % 2 and \
|
|
86
|
+
(hydrogen or # enol > ketone
|
|
87
|
+
atoms[(x := path[-1][1])].implicit_hydrogens and
|
|
88
|
+
(x not in rings or all(x > 7 for x in rings[x]))): # ketone>
|
|
89
|
+
# return partial hops. ignore allenes in small rings.
|
|
90
|
+
yield path, hydrogen
|
|
91
|
+
if len(path) > depth: # fork found
|
|
92
|
+
if not partial and not len(path) % 2 and (hydrogen or atoms[path[-1][1]].implicit_hydrogens):
|
|
93
|
+
# end of path found. return it and start new one.
|
|
94
|
+
yield path, hydrogen
|
|
95
|
+
seen.difference_update(x for _, x, _ in path[depth:])
|
|
96
|
+
path = path[:depth]
|
|
97
|
+
|
|
98
|
+
path.append((last, current, bond))
|
|
99
|
+
|
|
100
|
+
# adding neighbors
|
|
101
|
+
depth += 1
|
|
102
|
+
seen.add(current)
|
|
103
|
+
if bond == 2:
|
|
104
|
+
next_bond = 1
|
|
105
|
+
else:
|
|
106
|
+
next_bond = 2
|
|
107
|
+
|
|
108
|
+
for n, b in bonds[current].items():
|
|
109
|
+
if n == last:
|
|
110
|
+
continue
|
|
111
|
+
elif n in seen: # aromatic ring destruction. pyridine double bonds shift
|
|
112
|
+
continue
|
|
113
|
+
elif n in anti: # enol-ketone switch
|
|
114
|
+
if current in anti[n]: # keton or enol bond
|
|
115
|
+
if hydrogen:
|
|
116
|
+
cp = path.copy()
|
|
117
|
+
cp.append((current, n, 1)) # double to single in keton end
|
|
118
|
+
yield cp, True
|
|
119
|
+
else:
|
|
120
|
+
cp = path.copy()
|
|
121
|
+
cp.append((current, n, 2)) # single to double in enol end
|
|
122
|
+
yield cp, False
|
|
123
|
+
elif b == bond and (a := atoms[n]) == C: # classic keto-enol route
|
|
124
|
+
if a.hybridization == 2: # grow up
|
|
125
|
+
stack.append((current, n, next_bond, depth))
|
|
126
|
+
elif hydrogen:
|
|
127
|
+
if a.hybridization == 3: # OC=CC=C=C case
|
|
128
|
+
cp = path.copy()
|
|
129
|
+
cp.append((current, n, 1))
|
|
130
|
+
yield cp, True # ketone found
|
|
131
|
+
elif a.hybridization == 1 and a.implicit_hydrogens: # ketone >> enol
|
|
132
|
+
cp = path.copy()
|
|
133
|
+
cp.append((current, n, 2))
|
|
134
|
+
yield cp, False
|
|
135
|
+
|
|
136
|
+
if path and not len(path) % 2 and \
|
|
137
|
+
(hydrogen or # enol > ketone
|
|
138
|
+
atoms[(x := path[-1][1])].implicit_hydrogens and
|
|
139
|
+
(x not in rings or all(x > 7 for x in rings[x]))):
|
|
140
|
+
yield path, hydrogen
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
__all__ = ['KetoEnol']
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2021-2026 Ramil Nugmanov <nougmanoff@protonmail.com>
|
|
4
|
+
# This file is part of chython.
|
|
5
|
+
#
|
|
6
|
+
# chython is free software; you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
8
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU Lesser General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
17
|
+
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
#
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2021-2026 Ramil Nugmanov <nougmanoff@protonmail.com>
|
|
4
|
+
# This file is part of chython.
|
|
5
|
+
#
|
|
6
|
+
# chython is free software; you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
8
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU Lesser General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
17
|
+
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
#
|
|
19
|
+
from chython import smiles
|
|
20
|
+
from itertools import zip_longest
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_keto_enol_2h_pyrrole():
|
|
24
|
+
"""
|
|
25
|
+
2H‐pyrrole. [N:1]=1[C:2][C:3]=,:[C:4][C:5]=1
|
|
26
|
+
"""
|
|
27
|
+
for t, v in zip(['C1C=CC=N1', 'C1N=CC2=C1C=CC=C2'], ['C=1C=CNC=1', 'N1C=C2C=CC=CC2=C1']):
|
|
28
|
+
s = smiles(t)
|
|
29
|
+
t = set(s.enumerate_tautomers())
|
|
30
|
+
v = smiles(v)
|
|
31
|
+
s.thiele()
|
|
32
|
+
v.thiele()
|
|
33
|
+
assert len(t) == 2, ' '.join(str(x) for x in t)
|
|
34
|
+
assert t == {s, v}, f'{", ".join(str(x) for x in t)} != {s}, {v}'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_acid_protonated_nitrogen():
|
|
38
|
+
"""
|
|
39
|
+
Ammonia, H-imine,guanidine,amidine. [H][N+]=,-,:
|
|
40
|
+
"""
|
|
41
|
+
for t, v in zip_longest(['C=[NH2+].[Cl-]', 'C=[NH+]C.[Cl-]', 'C[NH3+].[Cl-]', 'C[NH2+]C.[Cl-]', 'C[NH1+](C)C.[Cl-]',
|
|
42
|
+
'[NH4+].[Cl-]', 'C=[N+](C)C.[Cl-]', 'C[N+](C)(C)C.[Cl-]'],
|
|
43
|
+
['C=N.Cl', 'C=NC.Cl', 'CN.Cl', 'CNC.Cl', 'CN(C)C.Cl', 'N.Cl']):
|
|
44
|
+
s = smiles(t)
|
|
45
|
+
t = set(s.enumerate_tautomers())
|
|
46
|
+
if v:
|
|
47
|
+
assert len(t) == 2, ' '.join(str(x) for x in t)
|
|
48
|
+
assert t == {s, smiles(v)}
|
|
49
|
+
else:
|
|
50
|
+
assert len(t) == 1
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_base_nitrogen():
|
|
54
|
+
for t, v in zip_longest(['N1C=CN=N1.Cl', 'NC(N)=N.Cl', 'CN(C)C(=NO)N(C)C.Cl', 'CN(C)C(=NC)N(C)C.Cl',
|
|
55
|
+
'CN(C)C(=NN)N(C)C.Cl', 'COC(N)=N.Cl', 'CSC(N)=N.Cl', 'COC(OC)=N.Cl', 'COC(C)=N.Cl',
|
|
56
|
+
'CNN.Cl', 'CN.Cl',
|
|
57
|
+
'N.Cl', 'N1C=CC=C1.Cl'],
|
|
58
|
+
[('N1N=NC=C1.Cl', 'N1=CC=NN1.Cl', '[NH+]=1NC=CN=1.[Cl-]', 'N1=[NH+]C=CN1.[Cl-]',
|
|
59
|
+
'N1=CC=[NH+]N1.[Cl-]'),
|
|
60
|
+
('NC(N)=N.Cl', 'NC(N)=[NH2+].[Cl-]'),
|
|
61
|
+
('CN(C)C(=NO)N(C)C.Cl', 'CN(C)C(N(C)C)=[NH+]O.[Cl-]'),
|
|
62
|
+
('CN(C)C(=NC)N(C)C.Cl', 'CN(C)C(N(C)C)=[NH+]C.[Cl-]'),
|
|
63
|
+
('CN(C)C(=NN)N(C)C.Cl', 'CN(C)C(N(C)C)=[NH+]N.[Cl-]'),
|
|
64
|
+
('Cl.NC(=N)OC', '[NH2+]=C(N)OC.[Cl-]'), ('Cl.NC(=N)SC', '[NH2+]=C(N)SC.[Cl-]'),
|
|
65
|
+
('COC(OC)=N.Cl', 'COC(OC)=[NH2+].[Cl-]'),
|
|
66
|
+
('COC(C)=N.Cl', 'COC(C)=[NH2+].[Cl-]'),
|
|
67
|
+
('CNN.Cl', 'CN[NH3+].[Cl-]', 'C[NH2+]N.[Cl-]'),
|
|
68
|
+
('CN.Cl', 'C[NH3+].[Cl-]')]):
|
|
69
|
+
s = smiles(t)
|
|
70
|
+
t = set(s.enumerate_tautomers(zwitter=True))
|
|
71
|
+
if v:
|
|
72
|
+
assert len(t) == len(v), ' '.join(str(x) for x in t)
|
|
73
|
+
vs = set()
|
|
74
|
+
for x in v:
|
|
75
|
+
x = smiles(x)
|
|
76
|
+
x.thiele(fix_tautomers=False)
|
|
77
|
+
vs.add(x)
|
|
78
|
+
assert t == vs, ' '.join(str(x) for x in t) + ' != ' + ' '.join(str(x) for x in vs)
|
|
79
|
+
else:
|
|
80
|
+
assert len(t) == 1, ' '.join(str(x) for x in t)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025, 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
|
+
#
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025, 2026 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 import smiles
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_basic():
|
|
24
|
+
# Test basic atom mapping in simple molecules
|
|
25
|
+
mol1 = smiles('CC(=O)O') # acetic acid
|
|
26
|
+
mol2 = smiles('CC(O)=O') # acetic acid
|
|
27
|
+
|
|
28
|
+
assert mol1 <= mol2
|
|
29
|
+
assert mol2 <= mol1
|
|
30
|
+
|
|
31
|
+
mappings = list(mol1.get_mapping(mol2))
|
|
32
|
+
assert len(mappings) == 1
|
|
33
|
+
assert mappings[0] == {1: 1, 2: 2, 3: 4, 4: 3}
|
|
34
|
+
assert not smiles('CC(O)O') <= mol1
|
|
35
|
+
assert smiles('C[O-]') <= smiles('CC[O-]')
|
|
36
|
+
assert not smiles('C[O-]') <= smiles('CCO')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_substructure_mapping():
|
|
40
|
+
# Test mapping of a substructure
|
|
41
|
+
mol = smiles('CCC(=O)OC')
|
|
42
|
+
substructure = smiles('CC(=O)O')
|
|
43
|
+
|
|
44
|
+
assert substructure < mol
|
|
45
|
+
mappings = list(substructure.get_mapping(mol))
|
|
46
|
+
assert len(mappings) == 1
|
|
47
|
+
assert mappings[0] == {1: 2, 2: 3, 3: 4, 4: 5}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_multiple_mappings():
|
|
51
|
+
# Test cases where multiple valid mappings exist
|
|
52
|
+
mol = smiles('CC(=O)OC(=O)C')
|
|
53
|
+
pattern = smiles('CC(=O)O') # acetone pattern
|
|
54
|
+
|
|
55
|
+
mappings = list(pattern.get_mapping(mol))
|
|
56
|
+
assert len(mappings) == 2 # should find multiple matches
|
|
57
|
+
assert {1: 1, 2: 2, 3: 3, 4: 4} in mappings
|
|
58
|
+
assert {1: 7, 2: 5, 3: 6, 4: 4} in mappings
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025, 2026 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 import smiles
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_basic_smiles():
|
|
24
|
+
# Test basic SMILES generation
|
|
25
|
+
mol = smiles('CO') # methanol
|
|
26
|
+
assert str(mol) in ('CO', 'OC')
|
|
27
|
+
|
|
28
|
+
mol = smiles('c1ccccc1') # benzene
|
|
29
|
+
assert str(mol) == 'c1ccccc1'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_format_options():
|
|
33
|
+
# Test different format options
|
|
34
|
+
mol = smiles('C=1C=CC=CC=1')
|
|
35
|
+
|
|
36
|
+
# Test asymmetric closures
|
|
37
|
+
assert str(mol) == 'C=1C=CC=CC=1'
|
|
38
|
+
assert format(mol, 'a') == 'C=1C=CC=CC1'
|
|
39
|
+
|
|
40
|
+
assert format(mol, '!b') == 'C1CCCCC1'
|
|
41
|
+
|
|
42
|
+
# Test disable stereo
|
|
43
|
+
mol = smiles('C[C@H](O)CC')
|
|
44
|
+
assert '@' in str(mol)
|
|
45
|
+
assert '@' not in format(mol, '!s')
|
|
46
|
+
|
|
47
|
+
mol = smiles('c1ccccc1')
|
|
48
|
+
assert format(mol, 'A') == 'C:1:C:C:C:C:C:1'
|
|
49
|
+
assert format(mol, 'Aa') == 'C:1:C:C:C:C:C1'
|
|
50
|
+
assert format(mol, 'm') == '[cH:1]1[cH:2][cH:3][cH:4][cH:5][cH:6]1'
|
|
51
|
+
assert format(mol, 'h') == '[cH]1[cH][cH][cH][cH][cH]1'
|
|
52
|
+
|
|
53
|
+
assert format(mol, 'Ah') == '[CH]:1:[CH]:[CH]:[CH]:[CH]:[CH]:1'
|
|
54
|
+
assert format(mol, 'Ah!b') == '[CH]1[CH][CH][CH][CH][CH]1'
|
|
55
|
+
|
|
56
|
+
mol = smiles('[K+]')
|
|
57
|
+
assert str(mol) == '[K+]'
|
|
58
|
+
assert format(mol, '!z') == '[K]'
|
|
59
|
+
|
|
60
|
+
mol = smiles('[CH3]')
|
|
61
|
+
assert str(mol) == '[CH3] |^1:0|'
|
|
62
|
+
assert format(mol, '!x') == '[CH3]'
|
|
63
|
+
|
|
64
|
+
mol = smiles('CCO')
|
|
65
|
+
assert len({format(mol, 'r') for _ in range(50)}) == 4
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_smiles_comparison():
|
|
69
|
+
# Test SMILES comparison functionality
|
|
70
|
+
mol1 = smiles('CCO')
|
|
71
|
+
mol2 = smiles('OCC')
|
|
72
|
+
mol3 = smiles('CCC')
|
|
73
|
+
|
|
74
|
+
assert mol1 == mol2 # same molecules
|
|
75
|
+
assert mol1 != mol3 # different molecules
|
|
76
|
+
assert hash(mol1) == hash(mol2) # same hash for same molecules
|