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,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
+ """