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,66 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2018-2025 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 typing import Tuple, Union
|
|
20
|
+
from .bonds import Bond, QueryBond
|
|
21
|
+
from .graph import Graph
|
|
22
|
+
from ..algorithms.isomorphism import QueryIsomorphism
|
|
23
|
+
from ..periodictable import Element, QueryElement
|
|
24
|
+
from ..periodictable.base import Query
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class QueryContainer(Graph[Query, QueryBond], QueryIsomorphism):
|
|
28
|
+
__slots__ = ('_smarts',)
|
|
29
|
+
|
|
30
|
+
def __init__(self, smarts: str):
|
|
31
|
+
super().__init__()
|
|
32
|
+
self._smarts = smarts
|
|
33
|
+
|
|
34
|
+
def __str__(self):
|
|
35
|
+
return self._smarts
|
|
36
|
+
|
|
37
|
+
def __repr__(self):
|
|
38
|
+
return f'smarts({self._smarts})'
|
|
39
|
+
|
|
40
|
+
def add_atom(self, atom: Union[Query, Element, int, str], *args, **kwargs):
|
|
41
|
+
if not isinstance(atom, Query):
|
|
42
|
+
# set only basic labels: charge, radical, isotope. use Query object directly for the full control.
|
|
43
|
+
if isinstance(atom, Element):
|
|
44
|
+
atom = QueryElement.from_atom(atom)
|
|
45
|
+
elif isinstance(atom, str):
|
|
46
|
+
atom = QueryElement.from_symbol(atom)()
|
|
47
|
+
elif isinstance(atom, int):
|
|
48
|
+
atom = QueryElement.from_atomic_number(atom)()
|
|
49
|
+
else:
|
|
50
|
+
raise TypeError('QueryElement object expected')
|
|
51
|
+
return super().add_atom(atom, *args, **kwargs)
|
|
52
|
+
|
|
53
|
+
def add_bond(self, n, m, bond: Union[QueryBond, Bond, int, Tuple[int, ...]]):
|
|
54
|
+
if isinstance(bond, Bond):
|
|
55
|
+
bond = QueryBond.from_bond(bond)
|
|
56
|
+
elif not isinstance(bond, QueryBond):
|
|
57
|
+
bond = QueryBond(bond)
|
|
58
|
+
super().add_bond(n, m, bond)
|
|
59
|
+
|
|
60
|
+
def union(self, other: 'QueryContainer', *, remap: bool = False, copy: bool = True) -> 'QueryContainer':
|
|
61
|
+
if not isinstance(other, QueryContainer):
|
|
62
|
+
raise TypeError('QueryContainer expected')
|
|
63
|
+
return super().union(other, remap=remap, copy=copy)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
__all__ = ['QueryContainer']
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2017-2025 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 CachedMethods import cached_method
|
|
20
|
+
from functools import reduce
|
|
21
|
+
from itertools import chain
|
|
22
|
+
from math import ceil
|
|
23
|
+
from operator import itemgetter, or_
|
|
24
|
+
from typing import Dict, Iterator, Optional, Tuple, List, Sequence
|
|
25
|
+
from zlib import compress, decompress
|
|
26
|
+
from .cgr import CGRContainer
|
|
27
|
+
from .molecule import MoleculeContainer
|
|
28
|
+
from ..algorithms.calculate2d import Calculate2DReaction
|
|
29
|
+
from ..algorithms.depict import DepictReaction
|
|
30
|
+
from ..algorithms.mapping import Mapping
|
|
31
|
+
from ..algorithms.standardize import StandardizeReaction
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ReactionContainer(StandardizeReaction, Mapping, Calculate2DReaction, DepictReaction):
|
|
35
|
+
"""
|
|
36
|
+
Reaction storage. Contains reactants, products and reagents lists.
|
|
37
|
+
|
|
38
|
+
Reaction storage hashable and comparable. based on reaction unique signature (SMILES).
|
|
39
|
+
"""
|
|
40
|
+
__slots__ = ('_reactants', '_products', '_reagents', '_meta', '_name', '_arrow', '_signs', '__dict__')
|
|
41
|
+
|
|
42
|
+
def __init__(self, reactants: Sequence[MoleculeContainer] = (), products: Sequence[MoleculeContainer] = (),
|
|
43
|
+
reagents: Sequence[MoleculeContainer] = (), meta: Optional[Dict] = None, name: Optional[str] = None):
|
|
44
|
+
"""
|
|
45
|
+
New reaction object creation
|
|
46
|
+
|
|
47
|
+
:param reactants: list of MoleculeContainers in left side of reaction
|
|
48
|
+
:param products: right side of reaction. see reactants
|
|
49
|
+
:param reagents: middle side of reaction: solvents, catalysts, etc. see reactants
|
|
50
|
+
:param meta: dictionary of metadata. like DTYPE-DATUM in RDF
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
reactants = tuple(reactants)
|
|
54
|
+
products = tuple(products)
|
|
55
|
+
reagents = tuple(reagents)
|
|
56
|
+
if not reactants and not products and not reagents:
|
|
57
|
+
raise ValueError('At least one graph object required')
|
|
58
|
+
elif not all(isinstance(x, MoleculeContainer) for x in chain(reactants, products, reagents)):
|
|
59
|
+
raise TypeError('MoleculeContainers expected')
|
|
60
|
+
|
|
61
|
+
self._reactants = reactants
|
|
62
|
+
self._products = products
|
|
63
|
+
self._reagents = reagents
|
|
64
|
+
if meta is None:
|
|
65
|
+
self._meta = None
|
|
66
|
+
else:
|
|
67
|
+
self._meta = dict(meta)
|
|
68
|
+
if name is None:
|
|
69
|
+
self._name = None
|
|
70
|
+
else:
|
|
71
|
+
self._name = name
|
|
72
|
+
self._arrow = None
|
|
73
|
+
self._signs = None
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def reactants(self) -> Tuple[MoleculeContainer, ...]:
|
|
77
|
+
return self._reactants
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def reagents(self) -> Tuple[MoleculeContainer, ...]:
|
|
81
|
+
return self._reagents
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def products(self) -> Tuple[MoleculeContainer, ...]:
|
|
85
|
+
return self._products
|
|
86
|
+
|
|
87
|
+
def molecules(self) -> Iterator[MoleculeContainer]:
|
|
88
|
+
"""
|
|
89
|
+
Iterator of all reaction molecules
|
|
90
|
+
"""
|
|
91
|
+
return chain(self.reactants, self.reagents, self.products)
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def meta(self) -> Dict:
|
|
95
|
+
"""
|
|
96
|
+
Dictionary of metadata.
|
|
97
|
+
Like DTYPE-DATUM in RDF
|
|
98
|
+
"""
|
|
99
|
+
if self._meta is None:
|
|
100
|
+
self._meta = {} # lazy
|
|
101
|
+
return self._meta
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def name(self) -> str:
|
|
105
|
+
return self._name or ''
|
|
106
|
+
|
|
107
|
+
@name.setter
|
|
108
|
+
def name(self, name: str):
|
|
109
|
+
if not isinstance(name, str):
|
|
110
|
+
raise TypeError('name should be string up to 80 symbols')
|
|
111
|
+
self._name = name
|
|
112
|
+
|
|
113
|
+
def copy(self) -> 'ReactionContainer':
|
|
114
|
+
"""
|
|
115
|
+
Get copy of object
|
|
116
|
+
"""
|
|
117
|
+
copy = object.__new__(self.__class__)
|
|
118
|
+
copy._reactants = tuple(x.copy() for x in self.reactants)
|
|
119
|
+
copy._products = tuple(x.copy() for x in self.products)
|
|
120
|
+
copy._reagents = tuple(x.copy() for x in self.reagents)
|
|
121
|
+
copy._name = self._name
|
|
122
|
+
if self._meta is None:
|
|
123
|
+
copy._meta = None
|
|
124
|
+
else:
|
|
125
|
+
copy._meta = self._meta.copy()
|
|
126
|
+
copy._arrow = self._arrow
|
|
127
|
+
copy._signs = self._signs
|
|
128
|
+
return copy
|
|
129
|
+
|
|
130
|
+
@cached_method
|
|
131
|
+
def compose(self) -> CGRContainer:
|
|
132
|
+
"""
|
|
133
|
+
Get CGR of reaction
|
|
134
|
+
|
|
135
|
+
Reagents will be presented as unchanged molecules
|
|
136
|
+
:return: CGRContainer
|
|
137
|
+
"""
|
|
138
|
+
rr = self.reagents + self.reactants
|
|
139
|
+
if rr:
|
|
140
|
+
r = reduce(or_, rr)
|
|
141
|
+
else:
|
|
142
|
+
r = MoleculeContainer()
|
|
143
|
+
if self.products:
|
|
144
|
+
p = reduce(or_, self.products)
|
|
145
|
+
else:
|
|
146
|
+
p = MoleculeContainer()
|
|
147
|
+
return r ^ p
|
|
148
|
+
|
|
149
|
+
def flush_cache(self, keep_molecule_cache=False, **kwargs):
|
|
150
|
+
self.__dict__.clear()
|
|
151
|
+
if not keep_molecule_cache:
|
|
152
|
+
for m in self.molecules():
|
|
153
|
+
m.flush_cache(**kwargs)
|
|
154
|
+
|
|
155
|
+
def pack(self, *, compressed=True, check=True) -> bytes:
|
|
156
|
+
"""
|
|
157
|
+
Pack into compressed bytes.
|
|
158
|
+
|
|
159
|
+
Note:
|
|
160
|
+
* Same restrictions as in molecules pack.
|
|
161
|
+
* reactants, reagents nad products should contain less than 256 molecules.
|
|
162
|
+
|
|
163
|
+
Format specification:
|
|
164
|
+
Big endian bytes order
|
|
165
|
+
8 bit - header byte = 0x01 (current format specification)
|
|
166
|
+
8 bit - reactants count
|
|
167
|
+
8 bit - reagents count
|
|
168
|
+
8 bit - products count
|
|
169
|
+
x bit - concatenated molecules packs
|
|
170
|
+
|
|
171
|
+
:param compressed: return zlib-compressed pack.
|
|
172
|
+
:param check: check molecules for format restrictions.
|
|
173
|
+
"""
|
|
174
|
+
data = b''.join((bytearray((1, len(self.reactants), len(self.reagents), len(self.products))),
|
|
175
|
+
*(m.pack(compressed=False, check=check) for m in self.molecules())))
|
|
176
|
+
if compressed:
|
|
177
|
+
return compress(data, 9)
|
|
178
|
+
return data
|
|
179
|
+
|
|
180
|
+
def pach(self, *, compressed=True, check=True) -> bytes:
|
|
181
|
+
"""
|
|
182
|
+
Pack into compressed bytes.
|
|
183
|
+
"""
|
|
184
|
+
return self.pack(compressed=compressed, check=check)
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def pack_len(cls, data: bytes, /, *, compressed=True) -> Tuple[List[int], List[int], List[int]]:
|
|
188
|
+
"""
|
|
189
|
+
Returns reactants, reagents, products molecules atoms count in reaction pack.
|
|
190
|
+
"""
|
|
191
|
+
if compressed:
|
|
192
|
+
data = decompress(data)
|
|
193
|
+
data = memoryview(data)
|
|
194
|
+
if data[0] != 1:
|
|
195
|
+
raise ValueError('invalid pack header')
|
|
196
|
+
reactants, reagents, products = data[1], data[2], data[3]
|
|
197
|
+
|
|
198
|
+
v = data[4] # mol pack version
|
|
199
|
+
shift = 5 # RH+RC+RC+PC+MH
|
|
200
|
+
molecules = []
|
|
201
|
+
for _ in range(reactants + reagents + products - 1):
|
|
202
|
+
acs = int.from_bytes(data[shift: shift + 3], 'big')
|
|
203
|
+
neighbors = 0
|
|
204
|
+
ac = acs >> 12
|
|
205
|
+
shift += 4 # AC+CC+AN
|
|
206
|
+
for _ in range(ac):
|
|
207
|
+
neighbors += data[shift] & 0x0f
|
|
208
|
+
shift += 9
|
|
209
|
+
neighbors //= 2
|
|
210
|
+
if v == 2:
|
|
211
|
+
shift += 3 * neighbors + ceil(neighbors * 3 / 8) + (acs & 0x0fff) * 4
|
|
212
|
+
elif v == 0:
|
|
213
|
+
shift += 3 * neighbors + ceil(neighbors / 5) * 2 + (acs & 0x0fff) * 4
|
|
214
|
+
molecules.append(ac)
|
|
215
|
+
if reactants or reagents or products:
|
|
216
|
+
molecules.append(int.from_bytes(data[shift: shift + 3], 'big') >> 12)
|
|
217
|
+
return molecules[:reactants], molecules[reactants: -products], molecules[-products:]
|
|
218
|
+
|
|
219
|
+
@classmethod
|
|
220
|
+
def unpack(cls, data: bytes, /, *, compressed=True) -> 'ReactionContainer':
|
|
221
|
+
"""
|
|
222
|
+
Unpack from compressed bytes.
|
|
223
|
+
|
|
224
|
+
:param compressed: decompress data before processing.
|
|
225
|
+
"""
|
|
226
|
+
if compressed:
|
|
227
|
+
data = decompress(data)
|
|
228
|
+
data = memoryview(data)
|
|
229
|
+
if data[0] != 1:
|
|
230
|
+
raise ValueError('invalid pack header')
|
|
231
|
+
|
|
232
|
+
reactants, reagents, products = data[1], data[2], data[3]
|
|
233
|
+
molecules: List[MoleculeContainer] = []
|
|
234
|
+
shift = 4
|
|
235
|
+
for _ in range(reactants + reagents + products):
|
|
236
|
+
m, pl = MoleculeContainer.unpack(data[shift:], compressed=False, _return_pack_length=True)
|
|
237
|
+
molecules.append(m)
|
|
238
|
+
shift += pl
|
|
239
|
+
return cls(molecules[:reactants], molecules[-products:], molecules[reactants: -products])
|
|
240
|
+
|
|
241
|
+
@classmethod
|
|
242
|
+
def unpach(cls, data: bytes, /, *, compressed=True) -> 'ReactionContainer':
|
|
243
|
+
"""
|
|
244
|
+
Unpack from compressed bytes.
|
|
245
|
+
"""
|
|
246
|
+
return cls.unpack(data, compressed=compressed)
|
|
247
|
+
|
|
248
|
+
def __bytes__(self):
|
|
249
|
+
return self.pack()
|
|
250
|
+
|
|
251
|
+
def __invert__(self) -> CGRContainer:
|
|
252
|
+
"""
|
|
253
|
+
Get CGR of reaction
|
|
254
|
+
"""
|
|
255
|
+
return self.compose()
|
|
256
|
+
|
|
257
|
+
def __eq__(self, other):
|
|
258
|
+
return isinstance(other, ReactionContainer) and str(self) == str(other)
|
|
259
|
+
|
|
260
|
+
@cached_method
|
|
261
|
+
def __hash__(self):
|
|
262
|
+
return hash(str(self))
|
|
263
|
+
|
|
264
|
+
def __bool__(self):
|
|
265
|
+
"""
|
|
266
|
+
Exists both reactants and products
|
|
267
|
+
"""
|
|
268
|
+
return bool(self.reactants and self.products)
|
|
269
|
+
|
|
270
|
+
@cached_method
|
|
271
|
+
def __str__(self):
|
|
272
|
+
return format(self)
|
|
273
|
+
|
|
274
|
+
def __format__(self, format_spec):
|
|
275
|
+
"""
|
|
276
|
+
:param format_spec:
|
|
277
|
+
!c - Keep nested containers order.
|
|
278
|
+
a - Generate asymmetric closures.
|
|
279
|
+
!s - Disable stereo marks.
|
|
280
|
+
A - Use aromatic bonds instead aromatic atoms.
|
|
281
|
+
m - Set atom mapping.
|
|
282
|
+
r - Generate random-ordered smiles.
|
|
283
|
+
h - Show implicit hydrogens.
|
|
284
|
+
!b - Disable bonds tokens.
|
|
285
|
+
!x - Disable CXSMILES extension.
|
|
286
|
+
!z - Disable charge representation.
|
|
287
|
+
"""
|
|
288
|
+
sig = []
|
|
289
|
+
count = 0
|
|
290
|
+
contract = []
|
|
291
|
+
radicals = []
|
|
292
|
+
|
|
293
|
+
for ml in (self.reactants, self.reagents, self.products):
|
|
294
|
+
mso = [(m, *m.__format__(format_spec, _return_order=True)) for m in ml]
|
|
295
|
+
if not format_spec or '!c' not in format_spec:
|
|
296
|
+
mso.sort(key=itemgetter(1))
|
|
297
|
+
|
|
298
|
+
ss = []
|
|
299
|
+
for m, s, o in mso:
|
|
300
|
+
if m.connected_components_count > 1:
|
|
301
|
+
contract.append([str(x + count) for x in range(m.connected_components_count)])
|
|
302
|
+
count += m.connected_components_count
|
|
303
|
+
else:
|
|
304
|
+
count += 1
|
|
305
|
+
|
|
306
|
+
radicals.extend(m.atom(n).is_radical for n in o)
|
|
307
|
+
ss.append(s)
|
|
308
|
+
sig.append('.'.join(ss))
|
|
309
|
+
|
|
310
|
+
if not format_spec or '!x' not in format_spec:
|
|
311
|
+
cx = []
|
|
312
|
+
if r := ','.join(str(n) for n, r in enumerate(radicals) if r):
|
|
313
|
+
cx.append(f'^1:{r}')
|
|
314
|
+
if contract:
|
|
315
|
+
cx.append(f"f:{','.join('.'.join(x) for x in contract)}")
|
|
316
|
+
if cx:
|
|
317
|
+
return f"{'>'.join(sig)} |{','.join(cx)}|"
|
|
318
|
+
return '>'.join(sig)
|
|
319
|
+
|
|
320
|
+
def __len__(self):
|
|
321
|
+
return len(self.reactants) + len(self.products) + len(self.reagents)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
__all__ = ['ReactionContainer']
|
chython/exceptions.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2017-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
|
+
|
|
20
|
+
|
|
21
|
+
class EmptyMolecule(ValueError):
|
|
22
|
+
"""
|
|
23
|
+
Molecule without atoms
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class EmptyReaction(ValueError):
|
|
28
|
+
"""
|
|
29
|
+
Reaction without molecules
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MappingError(ValueError):
|
|
34
|
+
"""
|
|
35
|
+
Atom-to-Atom mapping invalid
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AtomNotFound(KeyError):
|
|
40
|
+
"""
|
|
41
|
+
Bad atom number
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class BondNotFound(KeyError):
|
|
46
|
+
"""
|
|
47
|
+
Bad atoms numbers
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class NotChiral(KeyError):
|
|
52
|
+
"""
|
|
53
|
+
Atom not chiral
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class IsChiral(KeyError):
|
|
58
|
+
"""
|
|
59
|
+
Atom already chiral
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class InvalidAromaticRing(ValueError):
|
|
64
|
+
"""
|
|
65
|
+
Aromatic ring has impossible Kekule structure
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ValenceError(Exception):
|
|
70
|
+
"""
|
|
71
|
+
Atom has valence error
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class IncorrectSmiles(ValueError):
|
|
76
|
+
"""
|
|
77
|
+
SMILES string invalid
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class IncorrectSmarts(IncorrectSmiles):
|
|
82
|
+
"""
|
|
83
|
+
SMARTS string invalid or unsupported
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ImplementationError(Exception):
|
|
88
|
+
"""
|
|
89
|
+
Algorithm has errors. Please send example of structure to author for analyze.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class BufferOverflow(BufferError):
|
|
94
|
+
"""
|
|
95
|
+
Parser buffer overflow
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class InvalidV2000(ValueError):
|
|
100
|
+
"""
|
|
101
|
+
Invalid V2000
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class InvalidCharge(ValueError):
|
|
106
|
+
"""
|
|
107
|
+
Invalid MDL charge
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class InvalidMolBlock(ValueError):
|
|
112
|
+
"""
|
|
113
|
+
Invalid MDL MOL
|
|
114
|
+
"""
|