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,94 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2021-2023 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 lazy_object_proxy import Proxy
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _xonyl_groups():
|
|
23
|
+
from ... import smarts
|
|
24
|
+
|
|
25
|
+
rules = []
|
|
26
|
+
# atom 1 - axis
|
|
27
|
+
# atoms 2, 3 - swapping
|
|
28
|
+
q = smarts('[N;D3;z2;+]([O;D1;-])=[O;D1]')
|
|
29
|
+
rules.append(q)
|
|
30
|
+
|
|
31
|
+
q = smarts('[N,O,S,Se;D1;z2:3]=[C,P,As,Sb,Bi,S,Se,Te,Po,Cl,Br,I,At:1][N,O,S,Se;D1:2]')
|
|
32
|
+
rules.append(q)
|
|
33
|
+
|
|
34
|
+
q = smarts('[N,O,S,Se;D1;z2:3]=[C,P,As,Sb,Bi,S,Se,Te,Po,Cl,Br,I,At:1][N,O,S,Se;D1;-:2]')
|
|
35
|
+
rules.append(q)
|
|
36
|
+
return rules
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _substituents():
|
|
40
|
+
"""
|
|
41
|
+
Rules for switchable functional groups remapping
|
|
42
|
+
"""
|
|
43
|
+
from ... import smarts
|
|
44
|
+
|
|
45
|
+
rules = []
|
|
46
|
+
|
|
47
|
+
# nitro addition
|
|
48
|
+
#
|
|
49
|
+
# O [3] [2] O -- *
|
|
50
|
+
# // /:
|
|
51
|
+
# * - N+ >> * = N+
|
|
52
|
+
# \ \
|
|
53
|
+
# O- [2] O- [3]
|
|
54
|
+
#
|
|
55
|
+
q = smarts('[N;D3;z2,z4;+]([O;D1;-])-,:[O;D2]')
|
|
56
|
+
rules.append((q, ((2, 3), (3, 2))))
|
|
57
|
+
|
|
58
|
+
#
|
|
59
|
+
# NH [3] NH2 [3]
|
|
60
|
+
# // /
|
|
61
|
+
# * - X >> * - X
|
|
62
|
+
# \ :
|
|
63
|
+
# NH2 [2] N [2]
|
|
64
|
+
#
|
|
65
|
+
q = smarts('[N;D2;h0;z2,z4:3]=,:[C;D3:1][N:2]')
|
|
66
|
+
rules.append((q, ((2, 3), (3, 2))))
|
|
67
|
+
|
|
68
|
+
#
|
|
69
|
+
# O [3] [3] O - R
|
|
70
|
+
# // /
|
|
71
|
+
# * - X >> * - X
|
|
72
|
+
# \ \\
|
|
73
|
+
# OH [2] O [2]
|
|
74
|
+
#
|
|
75
|
+
q = smarts('[N,O,S,Se;D1;z2:3]=[C,P,As,Sb,Bi,S,Se,Te,Po,Cl,Br,I,At:1][N,O,S,Se;D2:2]')
|
|
76
|
+
rules.append((q, ((2, 3), (3, 2))))
|
|
77
|
+
|
|
78
|
+
#
|
|
79
|
+
# O [3] A
|
|
80
|
+
# // /
|
|
81
|
+
# * - X >> * - X
|
|
82
|
+
# \ \\
|
|
83
|
+
# OH [2] O [2]
|
|
84
|
+
#
|
|
85
|
+
q = smarts('[N,O,S,Se;D1;z2:3]=[C,P,As,Sb,Bi,S,Se,Te,Po,Cl,Br,I,At:1][A:2]')
|
|
86
|
+
rules.append((q, ((3, 2),))) # possible only: (3, 2)
|
|
87
|
+
return rules
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
xonyl_groups = Proxy(_xonyl_groups)
|
|
91
|
+
substituents_groups = Proxy(_substituents)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
__all__ = ['xonyl_groups', 'substituents_groups']
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2022 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 lazy_object_proxy import Proxy
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _rules():
|
|
23
|
+
from ... import smiles, smarts
|
|
24
|
+
raw_rules = []
|
|
25
|
+
|
|
26
|
+
# phenol etherification
|
|
27
|
+
r = smiles('[OH:5][cH:1]:[cH2:2].[CH3:4][OH:3]>>[CH3:4][O:3][cH:1]:[cH2:2]')
|
|
28
|
+
m = smarts('[O;D1:5].[O;D1:3]') # reactant restriction.
|
|
29
|
+
raw_rules.append((r, {3: 5}, m))
|
|
30
|
+
|
|
31
|
+
# esterification
|
|
32
|
+
r = smiles('[OH:2][CH:1]=[O:3].[CH3:4][OH:5]>>[CH3:4][O:2][CH:1]=[O:3]')
|
|
33
|
+
m = smarts('[O;D1:2].[O;D1:5]')
|
|
34
|
+
raw_rules.append((r, {2: 5}, m))
|
|
35
|
+
|
|
36
|
+
# acid reduction
|
|
37
|
+
r = smiles('[CH3:2][C:1]([OH:3])=[O:4]>>[CH3:2][CH2:1][OH:3]')
|
|
38
|
+
m = smarts('[O;D1:3]')
|
|
39
|
+
raw_rules.append((r, {3: 4, 4: 3}, m))
|
|
40
|
+
|
|
41
|
+
# ester reduction
|
|
42
|
+
r = smiles('[CH3:5][O:3][C:1]([CH3:2])=[O:4]>>[CH3:2][CH2:1][OH:3]')
|
|
43
|
+
raw_rules.append((r, {3: 4}, None))
|
|
44
|
+
|
|
45
|
+
# ozonolysis
|
|
46
|
+
r = smiles('[O-:4][O+:1]=[O:6].[CH3:3][CH:2]=[CH2:5]>>[CH3:3][CH:2]=[O:1]')
|
|
47
|
+
raw_rules.append((r, {1: 4}, None))
|
|
48
|
+
|
|
49
|
+
# peroxide oxirane
|
|
50
|
+
r = smiles('[CH3:6][O:1][OH:5].[CH3:4][CH:2]=[CH2:3]>>[CH3:4][CH:2]1[CH2:3][O:1]1')
|
|
51
|
+
m = smarts('[O;D1:5]')
|
|
52
|
+
raw_rules.append((r, {1: 5}, m))
|
|
53
|
+
|
|
54
|
+
# claisen rearrangement
|
|
55
|
+
r = smiles('[cH2:4]:[cH:3]:[cH:2][O:1][CH2:5][CH:6]=[CH2:7]>>[OH:1][cH:2]:[c:3](:[cH2:4])[CH2:5][CH:6]=[CH2:7]')
|
|
56
|
+
raw_rules.append((r, {5: 7, 7: 5}, None))
|
|
57
|
+
|
|
58
|
+
# metathesis
|
|
59
|
+
r = smiles('[CH3:1][CH:2]=[CH2:3].[CH3:5][CH:4]=[CH2:6]>>[CH3:5][CH:3]=[CH:2][CH3:1]')
|
|
60
|
+
raw_rules.append((r, {3: 4, 4: 3}, None))
|
|
61
|
+
|
|
62
|
+
rules = []
|
|
63
|
+
for r, f, m in raw_rules:
|
|
64
|
+
c = ~r
|
|
65
|
+
rules.append((c, str(c.substructure(c.center_atoms)), m, f))
|
|
66
|
+
return rules
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
rules = Proxy(_rules)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
__all__ = ['rules']
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2022-2024 Ramil Nugmanov <nougmanoff@protonmail.com>
|
|
4
|
+
# Copyright 2024 Philippe Gantzer <p.gantzer@icredd.hokudai.ac.jp>
|
|
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 CachedMethods import class_cached_property
|
|
21
|
+
from itertools import chain, count, repeat
|
|
22
|
+
from logging import getLogger, INFO
|
|
23
|
+
from numpy import ix_, unravel_index, argmax, zeros, array, isclose, nonzero, ones, mean
|
|
24
|
+
from typing import TYPE_CHECKING, Union
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from chython import ReactionContainer
|
|
29
|
+
|
|
30
|
+
logger = getLogger('chython.attention')
|
|
31
|
+
logger.setLevel(INFO)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Attention:
|
|
35
|
+
__slots__ = ()
|
|
36
|
+
__class_cache__ = {}
|
|
37
|
+
|
|
38
|
+
def reset_mapping(self: Union['ReactionContainer', 'Attention'], *, return_score: bool = False, multiplier=1.75,
|
|
39
|
+
keep_reactants_numbering=False) -> Union[bool, float]:
|
|
40
|
+
"""
|
|
41
|
+
Do atom-to-atom mapping. Return True if mapping changed.
|
|
42
|
+
"""
|
|
43
|
+
if any(len(bs) > 14 for m in self.molecules() for bs in m._bonds.values()):
|
|
44
|
+
logger.info('atom-to-atom mapping not supported for hypervalent compounds')
|
|
45
|
+
return False
|
|
46
|
+
fixed = self.__fix_collisions()
|
|
47
|
+
equal_atoms, p2r, r2p, r_adj, p_adj, r_map, p_map, pa, rg_map = self.__prepare_remapping()
|
|
48
|
+
|
|
49
|
+
# rxnmapper-inspired algorithm
|
|
50
|
+
am = self.__get_attention()
|
|
51
|
+
# sum of reactants to products attention and vice-versa for equal atom types only
|
|
52
|
+
am = (am[p2r] + am[r2p].T) * equal_atoms
|
|
53
|
+
amc = am.copy()
|
|
54
|
+
|
|
55
|
+
mapping = {}
|
|
56
|
+
scope = zeros(pa, dtype=bool)
|
|
57
|
+
seen = ones(pa, dtype=bool)
|
|
58
|
+
score = []
|
|
59
|
+
for x in range(pa): # iteratively map each product atom to reactant
|
|
60
|
+
# select highest attention
|
|
61
|
+
# todo: optimize
|
|
62
|
+
if not x:
|
|
63
|
+
i, j = unravel_index(argmax(am), am.shape)
|
|
64
|
+
else:
|
|
65
|
+
ams = am[scope]
|
|
66
|
+
if ams.size:
|
|
67
|
+
i, j = unravel_index(argmax(ams), ams.shape)
|
|
68
|
+
i = nonzero(scope)[0][i]
|
|
69
|
+
else:
|
|
70
|
+
i, j = unravel_index(argmax(am), am.shape)
|
|
71
|
+
if isclose(am[i, j], 0.): # no more products atoms in reactants
|
|
72
|
+
# mark as unmapped
|
|
73
|
+
for n in set(p_map).difference(mapping):
|
|
74
|
+
mapping[n] = 0
|
|
75
|
+
break
|
|
76
|
+
else:
|
|
77
|
+
score.append(amc[i, j])
|
|
78
|
+
mapping[p_map[i]] = r_map[j]
|
|
79
|
+
am[ix_(p_adj[i], r_adj[j])] *= multiplier # highlight neighbors
|
|
80
|
+
am[i] = am[:, j] = 0 # mask mapped product and reactant atoms
|
|
81
|
+
seen[i] = False
|
|
82
|
+
scope[i] = False
|
|
83
|
+
scope[p_adj[i] & seen] = True
|
|
84
|
+
|
|
85
|
+
score = float(mean(score)) if score else 0.
|
|
86
|
+
# mapping done.
|
|
87
|
+
if any(n != m for n, m in mapping.items()): # old mapping changed
|
|
88
|
+
if keep_reactants_numbering or fixed:
|
|
89
|
+
r_mapping = {n: n for n in r_map}
|
|
90
|
+
else:
|
|
91
|
+
r_mapping = {m: n for n, m in enumerate(r_map, 1)} # remap reactants to contiguous range
|
|
92
|
+
for m in self.reactants:
|
|
93
|
+
m.remap(r_mapping)
|
|
94
|
+
|
|
95
|
+
p_mapping = {}
|
|
96
|
+
nm = max(r_mapping.values())
|
|
97
|
+
for n, m in mapping.items():
|
|
98
|
+
if m := r_mapping.get(m):
|
|
99
|
+
p_mapping[n] = m
|
|
100
|
+
else: # not found in reactants atoms. set unique numbers.
|
|
101
|
+
nm += 1
|
|
102
|
+
p_mapping[n] = nm
|
|
103
|
+
|
|
104
|
+
for m in self.products:
|
|
105
|
+
m.remap(p_mapping)
|
|
106
|
+
|
|
107
|
+
if not keep_reactants_numbering and not fixed:
|
|
108
|
+
rg_mapping = {m: n for n, m in enumerate(rg_map, nm+1)} # remap reagents to contiguous range without overlapping
|
|
109
|
+
for m in self.reagents:
|
|
110
|
+
m.remap(rg_mapping)
|
|
111
|
+
|
|
112
|
+
self.flush_cache()
|
|
113
|
+
fixed = True
|
|
114
|
+
|
|
115
|
+
if self.fix_groups_mapping(): # fix carboxy etc
|
|
116
|
+
fixed = True
|
|
117
|
+
if self.fix_mapping(): # fix common mistakes in mechanisms
|
|
118
|
+
fixed = True
|
|
119
|
+
if return_score:
|
|
120
|
+
return score
|
|
121
|
+
return fixed
|
|
122
|
+
|
|
123
|
+
def __fix_collisions(self: 'ReactionContainer'):
|
|
124
|
+
r = [n for m in chain(self.reactants, self.reagents) for n in m._atoms]
|
|
125
|
+
p = [n for m in self.products for n in m._atoms]
|
|
126
|
+
c = count(1)
|
|
127
|
+
if len(r) != len(set(r)):
|
|
128
|
+
for m in chain(self.reactants, self.reagents):
|
|
129
|
+
m.remap({n: next(c) for n in m._atoms})
|
|
130
|
+
if len(p) != len(set(p)):
|
|
131
|
+
for m in self.products:
|
|
132
|
+
m.remap({n: next(c) for n in m._atoms})
|
|
133
|
+
if next(c) != 1:
|
|
134
|
+
self.flush_cache()
|
|
135
|
+
return True
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
def __prepare_remapping(self: 'ReactionContainer'):
|
|
139
|
+
r_map = [n for m in self.reactants for n in m]
|
|
140
|
+
p_map = [n for m in self.products for n in m]
|
|
141
|
+
rg_map = [n for m in self.reagents for n in m]
|
|
142
|
+
ra = len(r_map) # number of reactants atoms
|
|
143
|
+
pa = len(p_map) # number of products atoms
|
|
144
|
+
|
|
145
|
+
ram = [False] # reactants atoms mask
|
|
146
|
+
r_atoms = []
|
|
147
|
+
r_adj = zeros((ra, ra), dtype=bool)
|
|
148
|
+
i = 0
|
|
149
|
+
for m in self.reactants:
|
|
150
|
+
ram.append(False)
|
|
151
|
+
ram.extend(repeat(True, len(m)))
|
|
152
|
+
a = m.adjacency_matrix()
|
|
153
|
+
j = i + len(m)
|
|
154
|
+
r_adj[i:j, i:j] = a
|
|
155
|
+
i = j
|
|
156
|
+
r_atoms.extend(a.atomic_number for _, a in m.atoms())
|
|
157
|
+
r_atoms = array(r_atoms, dtype=int)
|
|
158
|
+
|
|
159
|
+
pam = [False] * len(ram) # products atoms mask
|
|
160
|
+
p_atoms = []
|
|
161
|
+
p_adj = zeros((pa, pa), dtype=bool)
|
|
162
|
+
i = 0
|
|
163
|
+
for m in self.products:
|
|
164
|
+
pam.append(False)
|
|
165
|
+
pam.extend(repeat(True, len(m)))
|
|
166
|
+
a = m.adjacency_matrix()
|
|
167
|
+
j = i + len(m)
|
|
168
|
+
p_adj[i:j, i:j] = a
|
|
169
|
+
i = j
|
|
170
|
+
p_atoms.extend(a.atomic_number for _, a in m.atoms())
|
|
171
|
+
p_atoms = array(p_atoms, dtype=int)
|
|
172
|
+
|
|
173
|
+
ram.extend(repeat(False, len(pam) - len(ram)))
|
|
174
|
+
ram = array(ram, dtype=bool)
|
|
175
|
+
pam = array(pam, dtype=bool)
|
|
176
|
+
return p_atoms[:, None] == r_atoms, ix_(pam, ram), ix_(ram, pam), r_adj, p_adj, r_map, p_map, pa, rg_map
|
|
177
|
+
|
|
178
|
+
@class_cached_property
|
|
179
|
+
def __attention_model(self):
|
|
180
|
+
from chython import torch_device
|
|
181
|
+
from chytorch.zoo.rxnmap import Model
|
|
182
|
+
|
|
183
|
+
return Model().to(torch_device)
|
|
184
|
+
|
|
185
|
+
@class_cached_property
|
|
186
|
+
def __autocast(self):
|
|
187
|
+
from chython import torch_device
|
|
188
|
+
|
|
189
|
+
if torch_device.startswith('cuda'):
|
|
190
|
+
try:
|
|
191
|
+
from torch import autocast
|
|
192
|
+
except ImportError: # torch 1.8 ad-hoc
|
|
193
|
+
from torch.cuda.amp import autocast
|
|
194
|
+
|
|
195
|
+
return autocast()
|
|
196
|
+
else:
|
|
197
|
+
return autocast('cuda')
|
|
198
|
+
return autocast_filler()
|
|
199
|
+
|
|
200
|
+
def __get_attention(self):
|
|
201
|
+
from torch import no_grad
|
|
202
|
+
|
|
203
|
+
with no_grad(), self.__autocast:
|
|
204
|
+
am = self.__attention_model(self).float().cpu().numpy()
|
|
205
|
+
return am
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class autocast_filler:
|
|
209
|
+
def __enter__(self):
|
|
210
|
+
return self
|
|
211
|
+
|
|
212
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
213
|
+
...
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
__all__ = ['Attention']
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2022-2024 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 ChainMap
|
|
20
|
+
from itertools import count
|
|
21
|
+
from typing import List, Tuple, TYPE_CHECKING, Union
|
|
22
|
+
from ._reactions import rules
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from chython import ReactionContainer
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FixMapper:
|
|
30
|
+
__slots__ = ()
|
|
31
|
+
|
|
32
|
+
def fix_mapping(self: 'ReactionContainer', *, logging: bool = False) -> \
|
|
33
|
+
Union[bool, List[Tuple[int, str, Tuple[int, ...]]]]:
|
|
34
|
+
"""
|
|
35
|
+
Fix mapping by using loaded rules.
|
|
36
|
+
"""
|
|
37
|
+
if not self:
|
|
38
|
+
if logging:
|
|
39
|
+
return []
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
cgr = ~self
|
|
43
|
+
if not cgr.center_atoms:
|
|
44
|
+
if logging:
|
|
45
|
+
return []
|
|
46
|
+
return False
|
|
47
|
+
del self.__dict__['__cached_method_compose']
|
|
48
|
+
|
|
49
|
+
log = []
|
|
50
|
+
free_number = count(max(cgr) + 1)
|
|
51
|
+
components = [(cgr.substructure(c),
|
|
52
|
+
cgr.augmented_substructure(c, 2), # deep DEPENDS on rules!
|
|
53
|
+
c)
|
|
54
|
+
for c in cgr.substructure(cgr.center_atoms).connected_components]
|
|
55
|
+
|
|
56
|
+
r_atoms = ChainMap(*(x._atoms for x in self.reactants))
|
|
57
|
+
for c, ac, cs in components:
|
|
58
|
+
for rule_num, (query, signature, restrict, fix) in enumerate(rules):
|
|
59
|
+
if str(c) == signature:
|
|
60
|
+
for mapping in query.get_mapping(ac, automorphism_filter=False):
|
|
61
|
+
if not cs.issubset(mapping.values()):
|
|
62
|
+
continue
|
|
63
|
+
if restrict is not None and any(a != r_atoms.get(mapping[n]) for n, a in restrict.atoms()):
|
|
64
|
+
continue
|
|
65
|
+
mapping = {mapping[n]: next(free_number) if m is None else mapping[m] for n, m in fix.items()}
|
|
66
|
+
for m in self.products:
|
|
67
|
+
m.remap(mapping)
|
|
68
|
+
log.append((rule_num, signature, tuple(mapping.values())))
|
|
69
|
+
break
|
|
70
|
+
else:
|
|
71
|
+
continue
|
|
72
|
+
break # component remapped!
|
|
73
|
+
|
|
74
|
+
if log:
|
|
75
|
+
self.flush_cache()
|
|
76
|
+
if logging:
|
|
77
|
+
return log
|
|
78
|
+
return True
|
|
79
|
+
elif logging:
|
|
80
|
+
return log
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
__all__ = ['FixMapper']
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2022 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 itertools import chain, repeat
|
|
20
|
+
from typing import List, Tuple, TYPE_CHECKING, Union
|
|
21
|
+
from ._groups import *
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from chython import ReactionContainer
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GroupsFix:
|
|
29
|
+
__slots__ = ()
|
|
30
|
+
|
|
31
|
+
def fix_groups_mapping(self: 'ReactionContainer', *, logging: bool = False) -> \
|
|
32
|
+
Union[bool, List[Tuple[str, Tuple[int, ...]]]]:
|
|
33
|
+
"""
|
|
34
|
+
Fix atom-to-atom mapping of some functional groups. Return True if found AAM errors.
|
|
35
|
+
"""
|
|
36
|
+
if not self:
|
|
37
|
+
if logging:
|
|
38
|
+
return []
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
log = []
|
|
42
|
+
seen = set()
|
|
43
|
+
remap = {}
|
|
44
|
+
pamer = {}
|
|
45
|
+
r_groups = set()
|
|
46
|
+
p_groups = set()
|
|
47
|
+
r_subs = set()
|
|
48
|
+
p_subs = set()
|
|
49
|
+
|
|
50
|
+
# find xonyl groups. any charged-neutral combinations
|
|
51
|
+
for pattern in xonyl_groups:
|
|
52
|
+
for m, g in chain(zip(self.reactants, repeat(r_groups)), zip(self.products, repeat(p_groups))):
|
|
53
|
+
atoms = m._atoms
|
|
54
|
+
for mapping in pattern.get_mapping(m, automorphism_filter=False):
|
|
55
|
+
n1, n2, n3 = mapping[1], mapping[2], mapping[3]
|
|
56
|
+
if (t := atoms[n2].atomic_number) == atoms[n3].atomic_number:
|
|
57
|
+
g.add((n1, n2, n3, atoms[n1].atomic_number, t))
|
|
58
|
+
|
|
59
|
+
for pattern, _map in substituents_groups:
|
|
60
|
+
for m, g in chain(zip(self.reactants, repeat(r_subs)), zip(self.products, repeat(p_subs))):
|
|
61
|
+
atoms = m._atoms
|
|
62
|
+
for mapping in pattern.get_mapping(m, automorphism_filter=False):
|
|
63
|
+
g.add((n := mapping[1], atoms[n].atomic_number,
|
|
64
|
+
tuple((n := mapping[x], y - 2, atoms[n].atomic_number) for x, y in _map), m))
|
|
65
|
+
|
|
66
|
+
r_groups = list(r_groups)
|
|
67
|
+
p_groups = list(p_groups)
|
|
68
|
+
|
|
69
|
+
# find pairs
|
|
70
|
+
if r_groups and p_groups:
|
|
71
|
+
for n1, n2, n3, x1, x2 in r_groups:
|
|
72
|
+
if n1 in seen: # already remapped
|
|
73
|
+
continue
|
|
74
|
+
for i, (m1, m2, m3, y1, y2) in enumerate(p_groups):
|
|
75
|
+
if m1 not in seen and n1 == m1 and x1 == y1 and x2 == y2: # found pair
|
|
76
|
+
if n2 == m3 and n3 == m2: # found switch
|
|
77
|
+
remap[m2] = m3
|
|
78
|
+
remap[m3] = m2
|
|
79
|
+
seen.add(n1)
|
|
80
|
+
break
|
|
81
|
+
else:
|
|
82
|
+
continue
|
|
83
|
+
del p_groups[i]
|
|
84
|
+
|
|
85
|
+
if not p_groups: # optimize
|
|
86
|
+
r_subs.clear()
|
|
87
|
+
|
|
88
|
+
# hydrolysis, etc.
|
|
89
|
+
for (n1, x1, _map, m), g, r in chain(zip(r_subs, repeat(p_groups), repeat(remap)),
|
|
90
|
+
zip(p_subs, repeat(r_groups), repeat(pamer))):
|
|
91
|
+
if n1 in seen:
|
|
92
|
+
continue
|
|
93
|
+
for i, (m1, *m23, y1, y2) in enumerate(g):
|
|
94
|
+
if m1 not in seen and n1 == m1 and x1 == y1: # found center
|
|
95
|
+
if len(_map) == 1: # acids substitutions.
|
|
96
|
+
ni, _, xi = _map[0]
|
|
97
|
+
# second neighbor should be disconnected from central atom.
|
|
98
|
+
if xi == y2 and m23[0] == ni and (m23[1] not in m._atoms or m23[1] not in m._bonds[n1]):
|
|
99
|
+
m2, m3 = m23
|
|
100
|
+
r[m2] = m3
|
|
101
|
+
r[m3] = m2
|
|
102
|
+
seen.add(n1)
|
|
103
|
+
break
|
|
104
|
+
elif all(xi == y2 and m23[mi] == ni for ni, mi, xi in _map):
|
|
105
|
+
m2, m3 = m23
|
|
106
|
+
r[m2] = m3
|
|
107
|
+
r[m3] = m2
|
|
108
|
+
seen.add(n1)
|
|
109
|
+
break
|
|
110
|
+
else:
|
|
111
|
+
continue
|
|
112
|
+
del g[i]
|
|
113
|
+
|
|
114
|
+
if remap:
|
|
115
|
+
seen = set(remap)
|
|
116
|
+
for m in self.products:
|
|
117
|
+
if not seen.isdisjoint(m):
|
|
118
|
+
m.remap(remap)
|
|
119
|
+
log.append(('products groups remapped', tuple(remap)))
|
|
120
|
+
if pamer:
|
|
121
|
+
seen = set(pamer)
|
|
122
|
+
for m in self.reactants:
|
|
123
|
+
if not seen.isdisjoint(m):
|
|
124
|
+
m.remap(pamer)
|
|
125
|
+
log.append(('reactants groups remapped', tuple(pamer)))
|
|
126
|
+
|
|
127
|
+
if log:
|
|
128
|
+
self.flush_cache()
|
|
129
|
+
if logging:
|
|
130
|
+
return log
|
|
131
|
+
return True
|
|
132
|
+
elif logging:
|
|
133
|
+
return []
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
__all__ = ['GroupsFix']
|