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.
- chython/__init__.py +35 -0
- chython/_functions.py +95 -0
- chython/_java.py +57 -0
- chython/algorithms/__init__.py +18 -0
- chython/algorithms/_isomorphism.cp313-win_amd64.pyd +0 -0
- chython/algorithms/_isomorphism.pyx +189 -0
- chython/algorithms/_rings.cp313-win_amd64.pyd +0 -0
- chython/algorithms/_rings.pyx +478 -0
- chython/algorithms/aromatics/__init__.py +27 -0
- chython/algorithms/aromatics/_rules.py +124 -0
- chython/algorithms/aromatics/kekule.py +532 -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 +222 -0
- chython/algorithms/calculate2d/__init__.py +23 -0
- chython/algorithms/calculate2d/clean2d.js +1 -0
- chython/algorithms/calculate2d/molecule.py +191 -0
- chython/algorithms/calculate2d/reaction.py +75 -0
- chython/algorithms/conformers.py +106 -0
- chython/algorithms/depict.py +596 -0
- chython/algorithms/fingerprints/__init__.py +43 -0
- chython/algorithms/fingerprints/linear.py +202 -0
- chython/algorithms/fingerprints/morgan.py +132 -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/groups/__init__.py +212 -0
- chython/algorithms/groups/_functional.py +290 -0
- chython/algorithms/groups/_oxidations.py +70 -0
- chython/algorithms/groups/_protective.py +964 -0
- chython/algorithms/groups/_reactions.py +809 -0
- chython/algorithms/groups/_reductions.py +88 -0
- chython/algorithms/groups/_transformations.py +278 -0
- chython/algorithms/groups/test/__init__.py +18 -0
- chython/algorithms/groups/test/test_deprotection.py +108 -0
- chython/algorithms/groups/test/test_reactions.py +377 -0
- chython/algorithms/isomorphism.py +664 -0
- chython/algorithms/mapping/__init__.py +188 -0
- chython/algorithms/mapping/_groups.py +94 -0
- chython/algorithms/mapping/_reactions.py +72 -0
- chython/algorithms/mapping/attention.py +266 -0
- chython/algorithms/mapping/reconstruct.py +346 -0
- chython/algorithms/mcs.py +201 -0
- chython/algorithms/morgan.py +77 -0
- chython/algorithms/rings.py +178 -0
- chython/algorithms/smiles.py +597 -0
- chython/algorithms/standardize/__init__.py +30 -0
- chython/algorithms/standardize/_groups.py +958 -0
- chython/algorithms/standardize/_isomers.py +330 -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 +475 -0
- chython/algorithms/standardize/reaction.py +384 -0
- chython/algorithms/standardize/resonance.py +191 -0
- chython/algorithms/standardize/salts.py +128 -0
- chython/algorithms/standardize/saturation.py +373 -0
- chython/algorithms/standardize/test/__init__.py +18 -0
- chython/algorithms/standardize/test/test_canonicalize.py +66 -0
- chython/algorithms/standardize/test/test_groups.py +146 -0
- chython/algorithms/standardize/test/test_isomers.py +198 -0
- chython/algorithms/stereo.py +1215 -0
- chython/algorithms/tautomers/__init__.py +167 -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 +208 -0
- chython/algorithms/tautomers/heteroarenes.py +103 -0
- chython/algorithms/tautomers/keto_enol.py +143 -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 +350 -0
- chython/containers/__init__.py +48 -0
- chython/containers/_pack_v2.cp313-win_amd64.pyd +0 -0
- chython/containers/_pack_v2.pyx +271 -0
- chython/containers/_unpack_v0v2.cp313-win_amd64.pyd +0 -0
- chython/containers/_unpack_v0v2.pyx +302 -0
- chython/containers/bonds.py +255 -0
- chython/containers/cgr.py +115 -0
- chython/containers/chimera.py +63 -0
- chython/containers/graph.py +199 -0
- chython/containers/molecule.py +817 -0
- chython/containers/query.py +71 -0
- chython/containers/rdkit.py +183 -0
- chython/containers/reaction.py +324 -0
- chython/containers/test/__init__.py +18 -0
- chython/containers/test/test_molecule.py +117 -0
- chython/containers/test/test_pack.py +60 -0
- chython/containers/test/test_rdkit.py +70 -0
- chython/containers/test/test_reaction.py +40 -0
- chython/exceptions.py +114 -0
- chython/files/MRVrw.py +532 -0
- chython/files/PDBrw.py +255 -0
- chython/files/RDFrw.py +309 -0
- chython/files/SDFrw.py +221 -0
- chython/files/__init__.py +30 -0
- chython/files/_convert.py +207 -0
- chython/files/_mapping.py +117 -0
- chython/files/_xyz.cp313-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 +260 -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 +554 -0
- chython/files/mdl/__init__.py +25 -0
- chython/files/mdl/emol.py +251 -0
- chython/files/mdl/erxn.py +67 -0
- chython/files/mdl/mol.py +186 -0
- chython/files/mdl/read.py +223 -0
- chython/files/mdl/rxn.py +67 -0
- chython/files/mdl/stereo.py +63 -0
- chython/files/mdl/write.py +201 -0
- chython/files/opsin.py +35 -0
- chython/files/test/__init__.py +0 -0
- chython/files/test/test_inchi.py +27 -0
- chython/files/test/test_mrv.py +47 -0
- chython/files/test/test_sdf_rdf.py +64 -0
- chython/files/test/test_xyz.py +20 -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 +451 -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/periodictable/test/__init__.py +0 -0
- chython/periodictable/test/test_element.py +100 -0
- chython/reactor/__init__.py +24 -0
- chython/reactor/base.py +222 -0
- chython/reactor/reactor.py +177 -0
- chython/reactor/test/__init__.py +18 -0
- chython/reactor/test/test_reactor.py +40 -0
- chython/reactor/test/test_transformer.py +78 -0
- chython/reactor/transformer.py +72 -0
- chython/utils/__init__.py +50 -0
- chython/utils/free_wilson.py +114 -0
- chython/utils/grid.py +130 -0
- chython/utils/retro.py +139 -0
- chython-2.19.dist-info/METADATA +105 -0
- chython-2.19.dist-info/RECORD +175 -0
- chython-2.19.dist-info/WHEEL +4 -0
- 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__ = ()
|