chython 2.19__cp313-cp313-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. chython/__init__.py +35 -0
  2. chython/_functions.py +95 -0
  3. chython/_java.py +57 -0
  4. chython/algorithms/__init__.py +18 -0
  5. chython/algorithms/_isomorphism.cp313-win_amd64.pyd +0 -0
  6. chython/algorithms/_isomorphism.pyx +189 -0
  7. chython/algorithms/_rings.cp313-win_amd64.pyd +0 -0
  8. chython/algorithms/_rings.pyx +478 -0
  9. chython/algorithms/aromatics/__init__.py +27 -0
  10. chython/algorithms/aromatics/_rules.py +124 -0
  11. chython/algorithms/aromatics/kekule.py +532 -0
  12. chython/algorithms/aromatics/test/__init__.py +18 -0
  13. chython/algorithms/aromatics/test/test_kekule.py +160 -0
  14. chython/algorithms/aromatics/test/test_thiele.py +263 -0
  15. chython/algorithms/aromatics/thiele.py +222 -0
  16. chython/algorithms/calculate2d/__init__.py +23 -0
  17. chython/algorithms/calculate2d/clean2d.js +1 -0
  18. chython/algorithms/calculate2d/molecule.py +191 -0
  19. chython/algorithms/calculate2d/reaction.py +75 -0
  20. chython/algorithms/conformers.py +106 -0
  21. chython/algorithms/depict.py +596 -0
  22. chython/algorithms/fingerprints/__init__.py +43 -0
  23. chython/algorithms/fingerprints/linear.py +202 -0
  24. chython/algorithms/fingerprints/morgan.py +132 -0
  25. chython/algorithms/fingerprints/test/__init__.py +18 -0
  26. chython/algorithms/fingerprints/test/test_linear.py +127 -0
  27. chython/algorithms/fingerprints/test/test_morgan.py +79 -0
  28. chython/algorithms/groups/__init__.py +212 -0
  29. chython/algorithms/groups/_functional.py +290 -0
  30. chython/algorithms/groups/_oxidations.py +70 -0
  31. chython/algorithms/groups/_protective.py +964 -0
  32. chython/algorithms/groups/_reactions.py +809 -0
  33. chython/algorithms/groups/_reductions.py +88 -0
  34. chython/algorithms/groups/_transformations.py +278 -0
  35. chython/algorithms/groups/test/__init__.py +18 -0
  36. chython/algorithms/groups/test/test_deprotection.py +108 -0
  37. chython/algorithms/groups/test/test_reactions.py +377 -0
  38. chython/algorithms/isomorphism.py +664 -0
  39. chython/algorithms/mapping/__init__.py +188 -0
  40. chython/algorithms/mapping/_groups.py +94 -0
  41. chython/algorithms/mapping/_reactions.py +72 -0
  42. chython/algorithms/mapping/attention.py +266 -0
  43. chython/algorithms/mapping/reconstruct.py +346 -0
  44. chython/algorithms/mcs.py +201 -0
  45. chython/algorithms/morgan.py +77 -0
  46. chython/algorithms/rings.py +178 -0
  47. chython/algorithms/smiles.py +597 -0
  48. chython/algorithms/standardize/__init__.py +30 -0
  49. chython/algorithms/standardize/_groups.py +958 -0
  50. chython/algorithms/standardize/_isomers.py +330 -0
  51. chython/algorithms/standardize/_metal_organics.py +189 -0
  52. chython/algorithms/standardize/_reagents.py +44 -0
  53. chython/algorithms/standardize/_salts.py +68 -0
  54. chython/algorithms/standardize/molecule.py +475 -0
  55. chython/algorithms/standardize/reaction.py +384 -0
  56. chython/algorithms/standardize/resonance.py +191 -0
  57. chython/algorithms/standardize/salts.py +128 -0
  58. chython/algorithms/standardize/saturation.py +373 -0
  59. chython/algorithms/standardize/test/__init__.py +18 -0
  60. chython/algorithms/standardize/test/test_canonicalize.py +66 -0
  61. chython/algorithms/standardize/test/test_groups.py +146 -0
  62. chython/algorithms/standardize/test/test_isomers.py +198 -0
  63. chython/algorithms/stereo.py +1215 -0
  64. chython/algorithms/tautomers/__init__.py +167 -0
  65. chython/algorithms/tautomers/_acid.py +59 -0
  66. chython/algorithms/tautomers/_base.py +116 -0
  67. chython/algorithms/tautomers/_keto_enol.py +107 -0
  68. chython/algorithms/tautomers/acid_base.py +208 -0
  69. chython/algorithms/tautomers/heteroarenes.py +103 -0
  70. chython/algorithms/tautomers/keto_enol.py +143 -0
  71. chython/algorithms/tautomers/test/__init__.py +18 -0
  72. chython/algorithms/tautomers/test/test_tautomers.py +80 -0
  73. chython/algorithms/test/__init__.py +18 -0
  74. chython/algorithms/test/test_isomorphism.py +58 -0
  75. chython/algorithms/test/test_smiles.py +76 -0
  76. chython/algorithms/x3dom.py +350 -0
  77. chython/containers/__init__.py +48 -0
  78. chython/containers/_pack_v2.cp313-win_amd64.pyd +0 -0
  79. chython/containers/_pack_v2.pyx +271 -0
  80. chython/containers/_unpack_v0v2.cp313-win_amd64.pyd +0 -0
  81. chython/containers/_unpack_v0v2.pyx +302 -0
  82. chython/containers/bonds.py +255 -0
  83. chython/containers/cgr.py +115 -0
  84. chython/containers/chimera.py +63 -0
  85. chython/containers/graph.py +199 -0
  86. chython/containers/molecule.py +817 -0
  87. chython/containers/query.py +71 -0
  88. chython/containers/rdkit.py +183 -0
  89. chython/containers/reaction.py +324 -0
  90. chython/containers/test/__init__.py +18 -0
  91. chython/containers/test/test_molecule.py +117 -0
  92. chython/containers/test/test_pack.py +60 -0
  93. chython/containers/test/test_rdkit.py +70 -0
  94. chython/containers/test/test_reaction.py +40 -0
  95. chython/exceptions.py +114 -0
  96. chython/files/MRVrw.py +532 -0
  97. chython/files/PDBrw.py +255 -0
  98. chython/files/RDFrw.py +309 -0
  99. chython/files/SDFrw.py +221 -0
  100. chython/files/__init__.py +30 -0
  101. chython/files/_convert.py +207 -0
  102. chython/files/_mapping.py +117 -0
  103. chython/files/_xyz.cp313-win_amd64.pyd +0 -0
  104. chython/files/_xyz.pyx +74 -0
  105. chython/files/daylight/__init__.py +24 -0
  106. chython/files/daylight/parser.py +157 -0
  107. chython/files/daylight/smarts.py +107 -0
  108. chython/files/daylight/smiles.py +260 -0
  109. chython/files/daylight/test/__init__.py +18 -0
  110. chython/files/daylight/test/test_daylight_smarts.py +289 -0
  111. chython/files/daylight/test/test_daylight_smiles.py +101 -0
  112. chython/files/daylight/test/test_parser.py +118 -0
  113. chython/files/daylight/test/test_tokenize.py +77 -0
  114. chython/files/daylight/tokenize.py +376 -0
  115. chython/files/libinchi/__init__.py +22 -0
  116. chython/files/libinchi/libinchi.dll +0 -0
  117. chython/files/libinchi/wrapper.py +554 -0
  118. chython/files/mdl/__init__.py +25 -0
  119. chython/files/mdl/emol.py +251 -0
  120. chython/files/mdl/erxn.py +67 -0
  121. chython/files/mdl/mol.py +186 -0
  122. chython/files/mdl/read.py +223 -0
  123. chython/files/mdl/rxn.py +67 -0
  124. chython/files/mdl/stereo.py +63 -0
  125. chython/files/mdl/write.py +201 -0
  126. chython/files/opsin.py +35 -0
  127. chython/files/test/__init__.py +0 -0
  128. chython/files/test/test_inchi.py +27 -0
  129. chython/files/test/test_mrv.py +47 -0
  130. chython/files/test/test_sdf_rdf.py +64 -0
  131. chython/files/test/test_xyz.py +20 -0
  132. chython/files/xyz.py +78 -0
  133. chython/periodictable/__init__.py +69 -0
  134. chython/periodictable/base/__init__.py +25 -0
  135. chython/periodictable/base/dynamic.py +149 -0
  136. chython/periodictable/base/element.py +451 -0
  137. chython/periodictable/base/groups.py +90 -0
  138. chython/periodictable/base/periods.py +46 -0
  139. chython/periodictable/base/query.py +473 -0
  140. chython/periodictable/base/vector.py +119 -0
  141. chython/periodictable/groupI.py +252 -0
  142. chython/periodictable/groupII.py +223 -0
  143. chython/periodictable/groupIII.py +1154 -0
  144. chython/periodictable/groupIV.py +210 -0
  145. chython/periodictable/groupIX.py +196 -0
  146. chython/periodictable/groupV.py +192 -0
  147. chython/periodictable/groupVI.py +183 -0
  148. chython/periodictable/groupVII.py +162 -0
  149. chython/periodictable/groupVIII.py +161 -0
  150. chython/periodictable/groupX.py +166 -0
  151. chython/periodictable/groupXI.py +164 -0
  152. chython/periodictable/groupXII.py +158 -0
  153. chython/periodictable/groupXIII.py +239 -0
  154. chython/periodictable/groupXIV.py +262 -0
  155. chython/periodictable/groupXV.py +276 -0
  156. chython/periodictable/groupXVI.py +457 -0
  157. chython/periodictable/groupXVII.py +324 -0
  158. chython/periodictable/groupXVIII.py +260 -0
  159. chython/periodictable/test/__init__.py +0 -0
  160. chython/periodictable/test/test_element.py +100 -0
  161. chython/reactor/__init__.py +24 -0
  162. chython/reactor/base.py +222 -0
  163. chython/reactor/reactor.py +177 -0
  164. chython/reactor/test/__init__.py +18 -0
  165. chython/reactor/test/test_reactor.py +40 -0
  166. chython/reactor/test/test_transformer.py +78 -0
  167. chython/reactor/transformer.py +72 -0
  168. chython/utils/__init__.py +50 -0
  169. chython/utils/free_wilson.py +114 -0
  170. chython/utils/grid.py +130 -0
  171. chython/utils/retro.py +139 -0
  172. chython-2.19.dist-info/METADATA +105 -0
  173. chython-2.19.dist-info/RECORD +175 -0
  174. chython-2.19.dist-info/WHEEL +4 -0
  175. chython-2.19.dist-info/licenses/LICENSE +165 -0
@@ -0,0 +1,451 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020-2026 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # This file is part of chython.
5
+ #
6
+ # chython is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation; either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
18
+ #
19
+ from abc import ABC, abstractmethod
20
+ from collections import defaultdict
21
+ from functools import cache
22
+ from typing import Dict, List, Optional, Set, Tuple, Type
23
+ from .vector import Vector
24
+ from ...exceptions import ValenceError
25
+
26
+
27
+ @cache
28
+ def _compiled_valence_rules(cls):
29
+ elements_classes = {x.__name__: x.atomic_number.fget(None) for x in Element.__subclasses__()}
30
+ common_valences = cls._common_valences.fget(None)
31
+
32
+ rules = defaultdict(list)
33
+ if common_valences[0] and cls.atomic_number.fget(None) != 1: # atom has implicit hydrogens by default except H.
34
+ # only first common valence represents implicit H.
35
+ valence = common_valences[0]
36
+ for h in range(valence + 1):
37
+ rules[(0, False, valence - h)].append((set(), {}, h)) # any atoms and bonds possible
38
+
39
+ for valence in common_valences[1:]:
40
+ rules[(0, False, valence)].append((set(), {}, 0))
41
+ else:
42
+ for valence in common_valences:
43
+ rules[(0, False, valence)].append((set(), {}, 0)) # any atoms and bonds possible
44
+
45
+ for charge, is_radical, implicit, environment in cls._valences_exceptions.fget(None):
46
+ explicit = sum(x for x, _ in environment)
47
+ explicit_dict = defaultdict(int)
48
+ explicit_set = set()
49
+ for b, e in environment:
50
+ be = (b, elements_classes[e])
51
+ explicit_set.add(be)
52
+ explicit_dict[be] += 1
53
+ explicit_dict = dict(explicit_dict)
54
+
55
+ if implicit:
56
+ valence = explicit + implicit
57
+ for h in range(implicit + 1):
58
+ rules[(charge, is_radical, valence - h)].append((explicit_set, explicit_dict, h))
59
+ else:
60
+ rules[(charge, is_radical, explicit)].append((explicit_set, explicit_dict, 0))
61
+ return dict(rules)
62
+
63
+
64
+ @cache
65
+ def _compiled_saturation_rules(cls):
66
+ elements_classes = {x.__name__: x.atomic_number.fget(None) for x in Element.__subclasses__()}
67
+
68
+ rules = []
69
+ for valence in cls._common_valences.fget(None):
70
+ rules.append((0, False, valence, 0, None)) # any atoms and bonds possible
71
+
72
+ for charge, is_radical, implicit, environment in cls._valences_exceptions.fget(None):
73
+ if not environment:
74
+ rules.append((charge, is_radical, implicit, 0, None))
75
+ else:
76
+ explicit_dict = defaultdict(int)
77
+ explicit = 0
78
+ for b, e in environment:
79
+ explicit_dict[(b, elements_classes[e])] += 1
80
+ explicit += b
81
+ rules.append((charge, is_radical, implicit + explicit, implicit, dict(explicit_dict)))
82
+ return rules
83
+
84
+
85
+ @cache
86
+ def _elements_map():
87
+ return {x.atomic_number.fget(None): x for x in Element.__subclasses__()}
88
+
89
+
90
+ class Element(ABC):
91
+ __slots__ = ('_isotope', '_charge', '_is_radical', '_xy', '_implicit_hydrogens',
92
+ '_explicit_hydrogens', '_stereo', '_parsed_mapping',
93
+ '_neighbors', '_heteroatoms', '_hybridization', '_ring_sizes', '_in_ring', '_extended_stereo')
94
+
95
+ def __init__(self, isotope: Optional[int] = None, *,
96
+ charge: int = 0, is_radical: bool = False, x: float = 0., y: float = 0.,
97
+ implicit_hydrogens: Optional[int] = None, stereo: Optional[bool] = None,
98
+ parsed_mapping: Optional[int] = None, delta_isotope: Optional[int] = None,
99
+ extended_stereo: Optional[int] = None):
100
+ """
101
+ Element object with specified isotope
102
+
103
+ :param isotope: Isotope number of element
104
+ """
105
+ if delta_isotope is not None and isotope is None:
106
+ # isotope had precedence over MDL delta value
107
+ isotope = self.mdl_isotope + delta_isotope
108
+
109
+ self.isotope = isotope
110
+ self.charge = charge
111
+ self.is_radical = is_radical
112
+ self._xy = Vector(x, y)
113
+
114
+ self._implicit_hydrogens = implicit_hydrogens
115
+ self._stereo = stereo
116
+ self._parsed_mapping = parsed_mapping
117
+ self._extended_stereo = extended_stereo
118
+
119
+ def __repr__(self):
120
+ if self.isotope:
121
+ return f'{self.__class__.__name__}({self.isotope})'
122
+ return f'{self.__class__.__name__}()'
123
+
124
+ @property
125
+ def atomic_symbol(self) -> str:
126
+ return self.__class__.__name__
127
+
128
+ @property
129
+ @abstractmethod
130
+ def atomic_number(self) -> int:
131
+ """
132
+ Element number
133
+ """
134
+
135
+ @property
136
+ def isotope(self) -> Optional[int]:
137
+ """
138
+ Isotope number
139
+ """
140
+ return self._isotope
141
+
142
+ @isotope.setter
143
+ def isotope(self, value: Optional[int]):
144
+ if isinstance(value, int):
145
+ if value not in self.isotopes_distribution:
146
+ raise ValueError(f'isotope number {value} impossible or not stable for {self.atomic_symbol}')
147
+ elif value is not None:
148
+ raise TypeError('integer isotope number required')
149
+ self._isotope = value
150
+
151
+ @property
152
+ def atomic_mass(self) -> float:
153
+ mass = self.isotopes_masses
154
+ if self.isotope is None:
155
+ return sum(x * mass[i] for i, x in self.isotopes_distribution.items())
156
+ return mass[self.isotope]
157
+
158
+ @property
159
+ @abstractmethod
160
+ def isotopes_distribution(self) -> Dict[int, float]:
161
+ """
162
+ Isotopes distribution in earth
163
+ """
164
+
165
+ @property
166
+ @abstractmethod
167
+ def isotopes_masses(self) -> Dict[int, float]:
168
+ """
169
+ Isotopes masses
170
+ """
171
+
172
+ @property
173
+ @abstractmethod
174
+ def atomic_radius(self) -> float:
175
+ """
176
+ Valence radius of atom
177
+ """
178
+
179
+ @property
180
+ @abstractmethod
181
+ def mdl_isotope(self) -> int:
182
+ """
183
+ MDL MOL common isotope
184
+ """
185
+
186
+ @property
187
+ def is_forming_single_bonds(self) -> bool:
188
+ """
189
+ Atom can form stable covalent single bonds in molecules
190
+ """
191
+ return False
192
+
193
+ @property
194
+ def is_forming_double_bonds(self) -> bool:
195
+ """
196
+ Atom can form stable covalent double bonds in molecules
197
+ """
198
+ return False
199
+
200
+ @property
201
+ def charge(self) -> int:
202
+ """
203
+ Charge of atom
204
+ """
205
+ return self._charge
206
+
207
+ @charge.setter
208
+ def charge(self, value: int):
209
+ """
210
+ Update charge of atom. Make sure to flush cache and recalculate hydrogens count and stereo.
211
+ Or use context manager on molecule:
212
+
213
+ with mol:
214
+ mol.atom(1).charge = 1
215
+ """
216
+ if not isinstance(value, int):
217
+ raise TypeError('formal charge should be int in range [-4, 4]')
218
+ elif value > 4 or value < -4:
219
+ raise ValueError('formal charge should be in range [-4, 4]')
220
+ self._charge = value
221
+
222
+ @property
223
+ def is_radical(self) -> bool:
224
+ """
225
+ Radical state of atoms
226
+ """
227
+ return self._is_radical
228
+
229
+ @is_radical.setter
230
+ def is_radical(self, value: bool):
231
+ """
232
+ Update radical state of atom. Make sure to flush cache and recalculate hydrogens count and stereo.
233
+ Or use context manager on molecule:
234
+
235
+ with mol:
236
+ mol.atom(1).is_radical = True
237
+ """
238
+ if not isinstance(value, bool):
239
+ raise TypeError('bool expected')
240
+ self._is_radical = value
241
+
242
+ @property
243
+ def x(self) -> float:
244
+ """
245
+ X coordinate of atom on 2D plane
246
+ """
247
+ return self._xy.x
248
+
249
+ @x.setter
250
+ def x(self, value: float):
251
+ self._xy.x = value
252
+
253
+ @property
254
+ def y(self) -> float:
255
+ """
256
+ Y coordinate of atom on 2D plane
257
+ """
258
+ return self._xy.y
259
+
260
+ @y.setter
261
+ def y(self, value: float):
262
+ self._xy.y = value
263
+
264
+ @property
265
+ def xy(self) -> Vector:
266
+ """
267
+ (X, Y) coordinates of atom on 2D plane
268
+ """
269
+ return self._xy
270
+
271
+ @xy.setter
272
+ def xy(self, value: Tuple[float, float]):
273
+ self._xy = Vector(*value)
274
+
275
+ @property
276
+ def implicit_hydrogens(self) -> Optional[int]:
277
+ return self._implicit_hydrogens
278
+
279
+ @property
280
+ def explicit_hydrogens(self) -> int:
281
+ return self._explicit_hydrogens
282
+
283
+ @property
284
+ def total_hydrogens(self) -> int:
285
+ if self.implicit_hydrogens is None:
286
+ raise ValenceError
287
+ return self.implicit_hydrogens + self.explicit_hydrogens
288
+
289
+ @property
290
+ def stereo(self) -> Optional[bool]:
291
+ """
292
+ Tetrahedron or allene stereo label
293
+ """
294
+ return self._stereo
295
+
296
+ @property
297
+ def extended_stereo(self) -> Optional[int]:
298
+ """
299
+ Positive int for an AND group id, negative int for an OR group id
300
+ """
301
+ if self._stereo is None:
302
+ return None
303
+ return self._extended_stereo
304
+
305
+ @property
306
+ def heteroatoms(self) -> int:
307
+ return self._heteroatoms
308
+
309
+ @property
310
+ def neighbors(self) -> int:
311
+ """
312
+ Neighbors count of atom
313
+ """
314
+ return self._neighbors
315
+
316
+ @property
317
+ def hybridization(self):
318
+ """
319
+ 1 - if atom has zero or only single bonded neighbors, 2 - if has only one double bonded neighbor and any amount
320
+ of single bonded, 3 - if has one triple bonded and any amount of double and single bonded neighbors or
321
+ two double bonded and any amount of single bonded neighbors, 4 - if atom in aromatic ring.
322
+ """
323
+ return self._hybridization
324
+
325
+ @property
326
+ def ring_sizes(self) -> Set[int]:
327
+ """
328
+ Atom rings sizes.
329
+ """
330
+ return self._ring_sizes
331
+
332
+ @property
333
+ def in_ring(self) -> bool:
334
+ """
335
+ Atom in any ring.
336
+ """
337
+ return self._in_ring
338
+
339
+ def copy(self, full=False, hydrogens=False, stereo=False) -> 'Element':
340
+ """
341
+ Get a copy of the Element object with attribute copy control.
342
+ """
343
+ copy = object.__new__(self.__class__)
344
+ copy._isotope = self.isotope
345
+ copy._charge = self.charge
346
+ copy._is_radical = self.is_radical
347
+ copy._xy = self.xy
348
+ if full:
349
+ copy._implicit_hydrogens = self.implicit_hydrogens
350
+ copy._stereo = self.stereo
351
+ copy._extended_stereo = self.extended_stereo
352
+ copy._explicit_hydrogens = self.explicit_hydrogens
353
+ copy._neighbors = self.neighbors
354
+ copy._heteroatoms = self.heteroatoms
355
+ copy._hybridization = self.hybridization
356
+ copy._ring_sizes = self.ring_sizes.copy()
357
+ copy._in_ring = self.in_ring
358
+ else:
359
+ if hydrogens:
360
+ copy._implicit_hydrogens = self.implicit_hydrogens
361
+ else:
362
+ copy._implicit_hydrogens = None
363
+ if stereo:
364
+ copy._stereo = self.stereo
365
+ copy._extended_stereo = self.extended_stereo
366
+ else:
367
+ copy._stereo = copy._extended_stereo = None
368
+ return copy
369
+
370
+ def __copy__(self):
371
+ return self.copy()
372
+
373
+ @classmethod
374
+ def from_symbol(cls, symbol: str) -> Type['Element']:
375
+ """
376
+ get Element class by its symbol
377
+ """
378
+ try:
379
+ element = next(x for x in Element.__subclasses__() if x.__name__ == symbol)
380
+ except StopIteration:
381
+ raise ValueError(f'Element with symbol "{symbol}" not found')
382
+ return element
383
+
384
+ @classmethod
385
+ def from_atomic_number(cls, number: int) -> Type['Element']:
386
+ """
387
+ get Element class by its number
388
+ """
389
+ try:
390
+ return _elements_map()[number]
391
+ except KeyError:
392
+ raise ValueError(f'Element with number "{number}" not found')
393
+
394
+ def __eq__(self, other):
395
+ """
396
+ compare attached to molecules elements
397
+ """
398
+ if isinstance(other, int):
399
+ return self.atomic_number == other
400
+ elif isinstance(other, str):
401
+ return self.atomic_symbol == other
402
+ return isinstance(other, Element) and self.atomic_number == other.atomic_number and \
403
+ self.isotope == other.isotope and self.charge == other.charge and self.is_radical == other.is_radical
404
+
405
+ def __hash__(self):
406
+ return hash((self.isotope or 0, self.atomic_number, self.charge, self.is_radical,
407
+ self.implicit_hydrogens or 0, self.in_ring))
408
+
409
+ def valence_rules(self, valence: int) -> \
410
+ List[Tuple[Set[Tuple[int, 'Element']], Dict[Tuple[int, 'Element'], int], int]]:
411
+ """
412
+ valence rules for element with specific charge/radical state
413
+ """
414
+ try:
415
+ return _compiled_valence_rules(self.__class__)[(self.charge, self.is_radical, valence)]
416
+ except KeyError:
417
+ raise ValenceError
418
+
419
+ @property
420
+ def saturation_rules(self) -> List[Tuple[int, bool, int, int, Optional[Dict[Tuple[int, int], int]]]]:
421
+ return _compiled_saturation_rules(self.__class__)
422
+
423
+ @property
424
+ @abstractmethod
425
+ def _common_valences(self) -> Tuple[int, ...]:
426
+ """
427
+ common valences of element
428
+ """
429
+
430
+ @property
431
+ @abstractmethod
432
+ def _valences_exceptions(self) -> Tuple[Tuple[int, bool, int, Tuple[Tuple[int, str], ...]], ...]:
433
+ """
434
+ exceptions in charges, radical state, implicit H count, and non H neighbors of element
435
+ examples:
436
+ (-1, False, 1, ()) - anion, not radical, has 1 implicit hydrogen if explicit atom not exists: [OH]- or C[O-]
437
+ (0, True, 1, ()) - neutral, radical, has 1 implicit hydrogen if explicit atom not exists: [OH]* or C[O*]
438
+ number of free electrons calculated as diff of default valence and number of connected atoms (include implicit)
439
+ (0, False, 1, ((1, 'C'),)) - neutral, not radical, has 1 implicit hydrogen and 1 single bonded carbon:
440
+ CO - alcohol or e.g. COC - ether
441
+ (0, False, 0, ((1, 'O'), (2, 'O'))) - can be anion/cation or neutral: HNO2 or [NO2]-.
442
+ state of neighbors atoms don't take into account. order of neighbors atoms don't take into account.
443
+ use both: (0, False, 0, ((2, 'O'),)) and (0, False, 0, ((1, 'O'), (1, 'O'))) if chains possible
444
+ use charge transfer for carbonyles, cyanides etc:
445
+ (-1, False, 0, ((1, 'C'),)) - [M-]-C#[O+]
446
+ user both for cyanates and isocyanates etc complexes:
447
+ (-1, False, 0, ((1, 'O'),)) and (-1, False, 0, ((1, 'N'),))
448
+ """
449
+
450
+
451
+ __all__ = ['Element']
@@ -0,0 +1,90 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2019-2026 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # This file is part of chython.
5
+ #
6
+ # chython is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation; either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
18
+ #
19
+
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-2026 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # This file is part of chython.
5
+ #
6
+ # chython is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation; either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
18
+ #
19
+
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__ = ()