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,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__ = ()
|