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,452 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020-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 abc import ABC, abstractmethod
20
+ from CachedMethods import class_cached_property
21
+ from collections import defaultdict
22
+ from typing import Dict, List, Optional, Set, Tuple, Type
23
+ from .vector import Vector
24
+ from ...exceptions import ValenceError
25
+
26
+
27
+ class Element(ABC):
28
+ __slots__ = ('_isotope', '_charge', '_is_radical', '_xy', '_implicit_hydrogens',
29
+ '_explicit_hydrogens', '_stereo', '_parsed_mapping',
30
+ '_neighbors', '_heteroatoms', '_hybridization', '_ring_sizes', '_in_ring')
31
+ __class_cache__ = {}
32
+
33
+ def __init__(self, isotope: Optional[int] = None, *,
34
+ charge: int = 0, is_radical: bool = False, x: float = 0., y: float = 0.,
35
+ implicit_hydrogens: Optional[int] = None, stereo: Optional[bool] = None,
36
+ parsed_mapping: Optional[int] = None, delta_isotope: Optional[int] = None):
37
+ """
38
+ Element object with specified isotope
39
+
40
+ :param isotope: Isotope number of element
41
+ """
42
+ if delta_isotope is not None:
43
+ assert isotope is None, 'isotope absolute value and delta value provided'
44
+ isotope = self.mdl_isotope + delta_isotope
45
+
46
+ self.isotope = isotope
47
+ self.charge = charge
48
+ self.is_radical = is_radical
49
+ self._xy = Vector(x, y)
50
+
51
+ self._implicit_hydrogens = implicit_hydrogens
52
+ self._stereo = stereo
53
+ self._parsed_mapping = parsed_mapping
54
+
55
+ def __repr__(self):
56
+ if self.isotope:
57
+ return f'{self.__class__.__name__}({self.isotope})'
58
+ return f'{self.__class__.__name__}()'
59
+
60
+ @property
61
+ def atomic_symbol(self) -> str:
62
+ return self.__class__.__name__
63
+
64
+ @property
65
+ @abstractmethod
66
+ def atomic_number(self) -> int:
67
+ """
68
+ Element number
69
+ """
70
+
71
+ @property
72
+ def isotope(self) -> Optional[int]:
73
+ """
74
+ Isotope number
75
+ """
76
+ return self._isotope
77
+
78
+ @isotope.setter
79
+ def isotope(self, value: Optional[int]):
80
+ if isinstance(value, int):
81
+ if value not in self.isotopes_distribution:
82
+ raise ValueError(f'isotope number {value} impossible or not stable for {self.atomic_symbol}')
83
+ elif value is not None:
84
+ raise TypeError('integer isotope number required')
85
+ self._isotope = value
86
+
87
+ @property
88
+ def atomic_mass(self) -> float:
89
+ mass = self.isotopes_masses
90
+ if self.isotope is None:
91
+ return sum(x * mass[i] for i, x in self.isotopes_distribution.items())
92
+ return mass[self.isotope]
93
+
94
+ @property
95
+ @abstractmethod
96
+ def isotopes_distribution(self) -> Dict[int, float]:
97
+ """
98
+ Isotopes distribution in earth
99
+ """
100
+
101
+ @property
102
+ @abstractmethod
103
+ def isotopes_masses(self) -> Dict[int, float]:
104
+ """
105
+ Isotopes masses
106
+ """
107
+
108
+ @property
109
+ @abstractmethod
110
+ def atomic_radius(self) -> float:
111
+ """
112
+ Valence radius of atom
113
+ """
114
+
115
+ @property
116
+ @abstractmethod
117
+ def mdl_isotope(self) -> int:
118
+ """
119
+ MDL MOL common isotope
120
+ """
121
+
122
+ @property
123
+ def is_forming_single_bonds(self) -> bool:
124
+ """
125
+ Atom can form stable covalent single bonds in molecules
126
+ """
127
+ return False
128
+
129
+ @property
130
+ def is_forming_double_bonds(self) -> bool:
131
+ """
132
+ Atom can form stable covalent double bonds in molecules
133
+ """
134
+ return False
135
+
136
+ @property
137
+ def charge(self) -> int:
138
+ """
139
+ Charge of atom
140
+ """
141
+ return self._charge
142
+
143
+ @charge.setter
144
+ def charge(self, value: int):
145
+ """
146
+ Update charge of atom. Make sure to flush cache and recalculate hydrogens count and stereo.
147
+ Or use context manager on molecule:
148
+
149
+ with mol:
150
+ mol.atom(1).charge = 1
151
+ """
152
+ if not isinstance(value, int):
153
+ raise TypeError('formal charge should be int in range [-4, 4]')
154
+ elif value > 4 or value < -4:
155
+ raise ValueError('formal charge should be in range [-4, 4]')
156
+ self._charge = value
157
+
158
+ @property
159
+ def is_radical(self) -> bool:
160
+ """
161
+ Radical state of atoms
162
+ """
163
+ return self._is_radical
164
+
165
+ @is_radical.setter
166
+ def is_radical(self, value: bool):
167
+ """
168
+ Update radical state of atom. Make sure to flush cache and recalculate hydrogens count and stereo.
169
+ Or use context manager on molecule:
170
+
171
+ with mol:
172
+ mol.atom(1).is_radical = True
173
+ """
174
+ if not isinstance(value, bool):
175
+ raise TypeError('bool expected')
176
+ self._is_radical = value
177
+
178
+ @property
179
+ def x(self) -> float:
180
+ """
181
+ X coordinate of atom on 2D plane
182
+ """
183
+ return self._xy.x
184
+
185
+ @x.setter
186
+ def x(self, value: float):
187
+ self._xy.x = value
188
+
189
+ @property
190
+ def y(self) -> float:
191
+ """
192
+ Y coordinate of atom on 2D plane
193
+ """
194
+ return self._xy.y
195
+
196
+ @y.setter
197
+ def y(self, value: float):
198
+ self._xy.y = value
199
+
200
+ @property
201
+ def xy(self) -> Vector:
202
+ """
203
+ (X, Y) coordinates of atom on 2D plane
204
+ """
205
+ return self._xy
206
+
207
+ @xy.setter
208
+ def xy(self, value: Tuple[float, float]):
209
+ self._xy = Vector(*value)
210
+
211
+ @property
212
+ def implicit_hydrogens(self) -> Optional[int]:
213
+ return self._implicit_hydrogens
214
+
215
+ @property
216
+ def explicit_hydrogens(self) -> int:
217
+ return self._explicit_hydrogens
218
+
219
+ @property
220
+ def total_hydrogens(self) -> int:
221
+ if self.implicit_hydrogens is None:
222
+ raise ValenceError
223
+ return self.implicit_hydrogens + self.explicit_hydrogens
224
+
225
+ @property
226
+ def stereo(self) -> Optional[bool]:
227
+ """
228
+ Tetrahedron or allene stereo label
229
+ """
230
+ return self._stereo
231
+
232
+ @property
233
+ def heteroatoms(self) -> int:
234
+ return self._heteroatoms
235
+
236
+ @property
237
+ def neighbors(self) -> int:
238
+ """
239
+ Neighbors count of atom
240
+ """
241
+ return self._neighbors
242
+
243
+ @property
244
+ def hybridization(self):
245
+ """
246
+ 1 - if atom has zero or only single bonded neighbors, 2 - if has only one double bonded neighbor and any amount
247
+ of single bonded, 3 - if has one triple bonded and any amount of double and single bonded neighbors or
248
+ two double bonded and any amount of single bonded neighbors, 4 - if atom in aromatic ring.
249
+ """
250
+ return self._hybridization
251
+
252
+ @property
253
+ def ring_sizes(self) -> Set[int]:
254
+ """
255
+ Atom rings sizes.
256
+ """
257
+ return self._ring_sizes
258
+
259
+ @property
260
+ def in_ring(self) -> bool:
261
+ """
262
+ Atom in any ring.
263
+ """
264
+ return self._in_ring
265
+
266
+ def copy(self, full=False, hydrogens=False, stereo=False) -> 'Element':
267
+ """
268
+ Get a copy of the Element object with attribute copy control.
269
+ """
270
+ copy = object.__new__(self.__class__)
271
+ copy._isotope = self.isotope
272
+ copy._charge = self.charge
273
+ copy._is_radical = self.is_radical
274
+ copy._xy = self.xy
275
+ if full:
276
+ copy._implicit_hydrogens = self.implicit_hydrogens
277
+ copy._stereo = self.stereo
278
+ copy._explicit_hydrogens = self.explicit_hydrogens
279
+ copy._neighbors = self.neighbors
280
+ copy._heteroatoms = self.heteroatoms
281
+ copy._hybridization = self.hybridization
282
+ copy._ring_sizes = self.ring_sizes.copy()
283
+ copy._in_ring = self.in_ring
284
+ else:
285
+ if hydrogens:
286
+ copy._implicit_hydrogens = self.implicit_hydrogens
287
+ else:
288
+ copy._implicit_hydrogens = None
289
+ if stereo:
290
+ copy._stereo = self.stereo
291
+ else:
292
+ copy._stereo = None
293
+ return copy
294
+
295
+ def __copy__(self):
296
+ return self.copy()
297
+
298
+ @classmethod
299
+ def from_symbol(cls, symbol: str) -> Type['Element']:
300
+ """
301
+ get Element class by its symbol
302
+ """
303
+ try:
304
+ element = next(x for x in Element.__subclasses__() if x.__name__ == symbol)
305
+ except StopIteration:
306
+ raise ValueError(f'Element with symbol "{symbol}" not found')
307
+ return element
308
+
309
+ @classmethod
310
+ def from_atomic_number(cls, number: int) -> Type['Element']:
311
+ """
312
+ get Element class by its number
313
+ """
314
+ try:
315
+ elements = cls.__class_cache__['elements']
316
+ except KeyError:
317
+ elements = {x.atomic_number.fget(None): x for x in Element.__subclasses__()}
318
+ cls.__class_cache__['elements'] = elements
319
+ try:
320
+ return elements[number]
321
+ except KeyError:
322
+ raise ValueError(f'Element with number "{number}" not found')
323
+
324
+ def __eq__(self, other):
325
+ """
326
+ compare attached to molecules elements
327
+ """
328
+ if isinstance(other, int):
329
+ return self.atomic_number == other
330
+ elif isinstance(other, str):
331
+ return self.atomic_symbol == other
332
+ return isinstance(other, Element) and self.atomic_number == other.atomic_number and \
333
+ self.isotope == other.isotope and self.charge == other.charge and self.is_radical == other.is_radical
334
+
335
+ def __hash__(self):
336
+ return hash((self.isotope or 0, self.atomic_number, self.charge, self.is_radical,
337
+ self.implicit_hydrogens or 0, self.in_ring))
338
+
339
+ def valence_rules(self, valence: int) -> \
340
+ List[Tuple[Set[Tuple[int, 'Element']], Dict[Tuple[int, 'Element'], int], int]]:
341
+ """
342
+ valence rules for element with specific charge/radical state
343
+ """
344
+ try:
345
+ return self._compiled_valence_rules[(self.charge, self.is_radical, valence)]
346
+ except KeyError:
347
+ raise ValenceError
348
+
349
+ @property
350
+ @abstractmethod
351
+ def _common_valences(self) -> Tuple[int, ...]:
352
+ """
353
+ common valences of element
354
+ """
355
+
356
+ @property
357
+ @abstractmethod
358
+ def _valences_exceptions(self) -> Tuple[Tuple[int, bool, int, Tuple[Tuple[int, str], ...]], ...]:
359
+ """
360
+ exceptions in charges, radical state, implicit H count, and non H neighbors of element
361
+ examples:
362
+ (-1, False, 1, ()) - anion, not radical, has 1 implicit hydrogen if explicit atom not exists: [OH]- or C[O-]
363
+ (0, True, 1, ()) - neutral, radical, has 1 implicit hydrogen if explicit atom not exists: [OH]* or C[O*]
364
+ number of free electrons calculated as diff of default valence and number of connected atoms (include implicit)
365
+ (0, False, 1, ((1, 'C'),)) - neutral, not radical, has 1 implicit hydrogen and 1 single bonded carbon:
366
+ CO - alcohol or e.g. COC - ether
367
+ (0, False, 0, ((1, 'O'), (2, 'O'))) - can be anion/cation or neutral: HNO2 or [NO2]-.
368
+ state of neighbors atoms don't take into account. order of neighbors atoms don't take into account.
369
+ use both: (0, False, 0, ((2, 'O'),)) and (0, False, 0, ((1, 'O'), (1, 'O'))) if chains possible
370
+ use charge transfer for carbonyles, cyanides etc:
371
+ (-1, False, 0, ((1, 'C'),)) - [M-]-C#[O+]
372
+ user both for cyanates and isocyanates etc complexes:
373
+ (-1, False, 0, ((1, 'O'),)) and (-1, False, 0, ((1, 'N'),))
374
+ """
375
+
376
+ @class_cached_property
377
+ def _compiled_charge_radical(self) -> Set[Tuple[int, bool]]:
378
+ """
379
+ exceptions in charges, radical state
380
+ examples:
381
+ (-1, False) - anion, not radical
382
+ (0, True) - neutral radical
383
+ """
384
+ return {(c, r) for c, r, *_ in self._valences_exceptions}
385
+
386
+ @class_cached_property
387
+ def _compiled_valence_rules(self) -> \
388
+ Dict[Tuple[int, bool, int], List[Tuple[Set[Tuple[int, int]], Dict[Tuple[int, int], int], int]]]:
389
+ """
390
+ dictionary with key = (charge, is_radical, sum_of_bonds) and
391
+ value = list of possible neighbors and implicit H count
392
+ """
393
+ elements_classes = {x.__name__: x.atomic_number.fget(None) for x in Element.__subclasses__()}
394
+
395
+ rules = defaultdict(list)
396
+ if self._common_valences[0] and self.atomic_number != 1: # atom has implicit hydrogens by default except H.
397
+ # only first common valence represents implicit H.
398
+ valence = self._common_valences[0]
399
+ for h in range(valence + 1):
400
+ rules[(0, False, valence - h)].append((set(), {}, h)) # any atoms and bonds possible
401
+
402
+ for valence in self._common_valences[1:]:
403
+ rules[(0, False, valence)].append((set(), {}, 0))
404
+ else:
405
+ for valence in self._common_valences:
406
+ rules[(0, False, valence)].append((set(), {}, 0)) # any atoms and bonds possible
407
+
408
+ for charge, is_radical, implicit, environment in self._valences_exceptions:
409
+ explicit = sum(x for x, _ in environment)
410
+ explicit_dict = defaultdict(int)
411
+ explicit_set = set()
412
+ for b, e in environment:
413
+ be = (b, elements_classes[e])
414
+ explicit_set.add(be)
415
+ explicit_dict[be] += 1
416
+ explicit_dict = dict(explicit_dict)
417
+
418
+ if implicit:
419
+ valence = explicit + implicit
420
+
421
+ for h in range(implicit + 1):
422
+ rules[(charge, is_radical, valence - h)].append((explicit_set, explicit_dict, h))
423
+ else:
424
+ rules[(charge, is_radical, explicit)].append((explicit_set, explicit_dict, 0))
425
+ return dict(rules)
426
+
427
+ @class_cached_property
428
+ def _compiled_saturation_rules(self) -> List[Tuple[int, bool, int, int, Optional[Dict[Tuple[int, int], int]]]]:
429
+ """
430
+ dictionary with key = (charge, is_radical, sum_of_bonds) and
431
+ value = list of possible neighbors
432
+ """
433
+ elements_classes = {x.__name__: x.atomic_number.fget(None) for x in Element.__subclasses__()}
434
+
435
+ rules = []
436
+ for valence in self._common_valences:
437
+ rules.append((0, False, valence, 0, None)) # any atoms and bonds possible
438
+
439
+ for charge, is_radical, implicit, environment in self._valences_exceptions:
440
+ if not environment:
441
+ rules.append((charge, is_radical, implicit, 0, None))
442
+ else:
443
+ explicit_dict = defaultdict(int)
444
+ explicit = 0
445
+ for b, e in environment:
446
+ explicit_dict[(b, elements_classes[e])] += 1
447
+ explicit += b
448
+ rules.append((charge, is_radical, implicit + explicit, implicit, dict(explicit_dict)))
449
+ return rules
450
+
451
+
452
+ __all__ = ['Element']
@@ -0,0 +1,90 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2019-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 GroupI:
22
+ __slots__ = ()
23
+
24
+
25
+ class GroupII:
26
+ __slots__ = ()
27
+
28
+
29
+ class GroupIII:
30
+ __slots__ = ()
31
+
32
+
33
+ class GroupIV:
34
+ __slots__ = ()
35
+
36
+
37
+ class GroupV:
38
+ __slots__ = ()
39
+
40
+
41
+ class GroupVI:
42
+ __slots__ = ()
43
+
44
+
45
+ class GroupVII:
46
+ __slots__ = ()
47
+
48
+
49
+ class GroupVIII:
50
+ __slots__ = ()
51
+
52
+
53
+ class GroupIX:
54
+ __slots__ = ()
55
+
56
+
57
+ class GroupX:
58
+ __slots__ = ()
59
+
60
+
61
+ class GroupXI:
62
+ __slots__ = ()
63
+
64
+
65
+ class GroupXII:
66
+ __slots__ = ()
67
+
68
+
69
+ class GroupXIII:
70
+ __slots__ = ()
71
+
72
+
73
+ class GroupXIV:
74
+ __slots__ = ()
75
+
76
+
77
+ class GroupXV:
78
+ __slots__ = ()
79
+
80
+
81
+ class GroupXVI:
82
+ __slots__ = ()
83
+
84
+
85
+ class GroupXVII:
86
+ __slots__ = ()
87
+
88
+
89
+ class GroupXVIII:
90
+ __slots__ = ()
@@ -0,0 +1,46 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2019-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 PeriodI:
22
+ __slots__ = ()
23
+
24
+
25
+ class PeriodII:
26
+ __slots__ = ()
27
+
28
+
29
+ class PeriodIII:
30
+ __slots__ = ()
31
+
32
+
33
+ class PeriodIV:
34
+ __slots__ = ()
35
+
36
+
37
+ class PeriodV:
38
+ __slots__ = ()
39
+
40
+
41
+ class PeriodVI:
42
+ __slots__ = ()
43
+
44
+
45
+ class PeriodVII:
46
+ __slots__ = ()