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,64 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from io import StringIO
|
|
3
|
+
from chython import smiles
|
|
4
|
+
from chython.files import SDFRead, SDFWrite, RDFRead, RDFWrite
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_sdf_read():
|
|
8
|
+
with SDFRead('test/implicit.sdf') as f:
|
|
9
|
+
mols = f.read()
|
|
10
|
+
assert len(mols) > 0
|
|
11
|
+
for mol in mols:
|
|
12
|
+
assert len(mol) > 0
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_sdf_read_stereo():
|
|
16
|
+
with SDFRead('test/stereo.sdf') as f:
|
|
17
|
+
mols = f.read(amount=10)
|
|
18
|
+
assert len(mols) == 10
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_sdf_write_read_roundtrip():
|
|
22
|
+
mol = smiles('CCO')
|
|
23
|
+
mol.clean2d()
|
|
24
|
+
mol.meta['name'] = 'ethanol'
|
|
25
|
+
|
|
26
|
+
buf = StringIO()
|
|
27
|
+
with SDFWrite(buf) as w:
|
|
28
|
+
w.write(mol)
|
|
29
|
+
|
|
30
|
+
buf.seek(0)
|
|
31
|
+
with SDFRead(buf) as r:
|
|
32
|
+
mol2 = next(r)
|
|
33
|
+
|
|
34
|
+
assert str(mol) == str(mol2)
|
|
35
|
+
assert mol2.meta.get('name') == 'ethanol'
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_rdf_read():
|
|
39
|
+
from chython import ReactionContainer
|
|
40
|
+
with RDFRead('test/MR.rdf') as f:
|
|
41
|
+
records = f.read()
|
|
42
|
+
assert len(records) > 0
|
|
43
|
+
rxns = [r for r in records if isinstance(r, ReactionContainer)]
|
|
44
|
+
assert len(rxns) > 0
|
|
45
|
+
for rxn in rxns:
|
|
46
|
+
assert len(rxn.reactants) > 0
|
|
47
|
+
assert len(rxn.products) > 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_rdf_write_read_roundtrip():
|
|
51
|
+
rxn = smiles('[CH3:1][OH:2]>>[CH3:1][NH2:3]')
|
|
52
|
+
for m in rxn.molecules():
|
|
53
|
+
m.clean2d()
|
|
54
|
+
|
|
55
|
+
buf = StringIO()
|
|
56
|
+
with RDFWrite(buf) as w:
|
|
57
|
+
w.write(rxn)
|
|
58
|
+
|
|
59
|
+
buf.seek(0)
|
|
60
|
+
with RDFRead(buf) as r:
|
|
61
|
+
rxn2 = next(r)
|
|
62
|
+
|
|
63
|
+
assert len(rxn2.reactants) == 1
|
|
64
|
+
assert len(rxn2.products) == 1
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from chython import xyz
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_xyz_water():
|
|
6
|
+
mol = xyz((('O', 0., 0., 0.), ('H', 1., 0., 0.), ('H', 0., 1., 0.)))
|
|
7
|
+
assert len(mol) == 3
|
|
8
|
+
assert 'O' in {a.atomic_symbol for _, a in mol.atoms()}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_xyz_methane():
|
|
12
|
+
mol = xyz((
|
|
13
|
+
('C', 0., 0., 0.),
|
|
14
|
+
('H', 1.09, 0., 0.),
|
|
15
|
+
('H', -0.36, 1.03, 0.),
|
|
16
|
+
('H', -0.36, -0.51, 0.89),
|
|
17
|
+
('H', -0.36, -0.51, -0.89),
|
|
18
|
+
))
|
|
19
|
+
assert len(mol) == 5
|
|
20
|
+
assert mol.atoms_count == 5
|
chython/files/xyz.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
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 numpy import array
|
|
20
|
+
from typing import Sequence, Tuple, Optional
|
|
21
|
+
from ..containers import MoleculeContainer
|
|
22
|
+
from ..periodictable import Element
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def xyz(matrix: Sequence[Tuple[str, float, float, float]], charge=0, radical=0, radius_multiplier=1.25,
|
|
26
|
+
atom_charge: Optional[Sequence[int]] = None, _cls=MoleculeContainer) -> MoleculeContainer:
|
|
27
|
+
from ._xyz import possible_bonds # windows?
|
|
28
|
+
|
|
29
|
+
if atom_charge and len(atom_charge) != len(matrix):
|
|
30
|
+
raise ValueError('atom_charge should be None or the same size as matrix')
|
|
31
|
+
|
|
32
|
+
mol = _cls()
|
|
33
|
+
conformer = {}
|
|
34
|
+
mol._conformers = [conformer]
|
|
35
|
+
|
|
36
|
+
atoms = mol._atoms
|
|
37
|
+
bonds = mol._bonds
|
|
38
|
+
for n, (a, x, y, z) in enumerate(matrix, 1):
|
|
39
|
+
atoms[n] = Element.from_symbol(a)(x=x, y=y, implicit_hydrogens=0)
|
|
40
|
+
bonds[n] = {}
|
|
41
|
+
conformer[n] = (x, y, z)
|
|
42
|
+
|
|
43
|
+
if atom_charge is not None and None not in atom_charge:
|
|
44
|
+
for n, c in enumerate(atom_charge, 1):
|
|
45
|
+
atoms[n]._charge = c
|
|
46
|
+
charge = sum(atom_charge)
|
|
47
|
+
|
|
48
|
+
mol.calc_labels()
|
|
49
|
+
pb = possible_bonds(array(list(conformer.values())), array([a.atomic_radius for a in atoms.values()]),
|
|
50
|
+
radius_multiplier)
|
|
51
|
+
|
|
52
|
+
log = mol.saturate(pb, expected_charge=charge, expected_radicals_count=radical, logging=True)
|
|
53
|
+
mol.meta['saturation_log'] = log
|
|
54
|
+
return mol
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def xyz_file(data) -> MoleculeContainer:
|
|
58
|
+
data = data.splitlines()
|
|
59
|
+
|
|
60
|
+
size = int(data[0])
|
|
61
|
+
charge = 0
|
|
62
|
+
radical = 0
|
|
63
|
+
|
|
64
|
+
for x in data[1].split():
|
|
65
|
+
if x.startswith('charge='):
|
|
66
|
+
charge = int(x[7:])
|
|
67
|
+
elif x.startswith('radical='):
|
|
68
|
+
radical = int(x[8:])
|
|
69
|
+
|
|
70
|
+
_xyz = []
|
|
71
|
+
for n, line in enumerate(data[2: size + 2]):
|
|
72
|
+
symbol, x, y, z = line.split()
|
|
73
|
+
_xyz.append((symbol, float(x), float(y), float(z)))
|
|
74
|
+
|
|
75
|
+
return xyz(_xyz, charge, radical)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
__all__ = ['xyz', 'xyz_file']
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2018-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 ABCMeta
|
|
20
|
+
from .base import *
|
|
21
|
+
from .base.groups import *
|
|
22
|
+
from .base.periods import *
|
|
23
|
+
from .groupI import *
|
|
24
|
+
from .groupII import *
|
|
25
|
+
from .groupIII import *
|
|
26
|
+
from .groupIV import *
|
|
27
|
+
from .groupV import *
|
|
28
|
+
from .groupVI import *
|
|
29
|
+
from .groupVII import *
|
|
30
|
+
from .groupVIII import *
|
|
31
|
+
from .groupIX import *
|
|
32
|
+
from .groupX import *
|
|
33
|
+
from .groupXI import *
|
|
34
|
+
from .groupXII import *
|
|
35
|
+
from .groupXIII import *
|
|
36
|
+
from .groupXIV import *
|
|
37
|
+
from .groupXV import *
|
|
38
|
+
from .groupXVI import *
|
|
39
|
+
from .groupXVII import *
|
|
40
|
+
from .groupXVIII import *
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
modules = {v.__name__: v for k, v in globals().items() if k.startswith('group') and k != 'groups'}
|
|
44
|
+
elements = {k: v for k, v in globals().items() if isinstance(v, ABCMeta) and k != 'Element' and issubclass(v, Element)}
|
|
45
|
+
|
|
46
|
+
__all__ = ['Element', 'DynamicElement', 'QueryElement', 'AnyElement', 'ListElement', 'AnyMetal']
|
|
47
|
+
__all__.extend(k for k in globals() if k.startswith('Group'))
|
|
48
|
+
__all__.extend(k for k in globals() if k.startswith('Period'))
|
|
49
|
+
__all__.extend(elements)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
for k, v in elements.items():
|
|
53
|
+
name = f'Dynamic{k}'
|
|
54
|
+
globals()[name] = cls = type(name, (DynamicElement,),
|
|
55
|
+
{'__module__': v.__module__, '__slots__': (),
|
|
56
|
+
'atomic_number': v.atomic_number})
|
|
57
|
+
setattr(modules[v.__module__], name, cls)
|
|
58
|
+
modules[v.__module__].__all__.append(name)
|
|
59
|
+
__all__.append(name)
|
|
60
|
+
|
|
61
|
+
for k, v in elements.items():
|
|
62
|
+
name = f'Query{k}'
|
|
63
|
+
globals()[name] = cls = type(name, (QueryElement,),
|
|
64
|
+
{'__module__': v.__module__, '__slots__': (),
|
|
65
|
+
'atomic_number': v.atomic_number,
|
|
66
|
+
'mdl_isotope': v.mdl_isotope})
|
|
67
|
+
setattr(modules[v.__module__], name, cls)
|
|
68
|
+
modules[v.__module__].__all__.append(name)
|
|
69
|
+
__all__.append(name)
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
from .dynamic import *
|
|
20
|
+
from .element import *
|
|
21
|
+
from .query import *
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = ['Element', 'DynamicElement', 'Query', 'ExtendedQuery',
|
|
25
|
+
'QueryElement', 'AnyElement', 'AnyMetal', 'ListElement']
|
|
@@ -0,0 +1,149 @@
|
|
|
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 typing import Type, Optional
|
|
21
|
+
from .element import Element
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DynamicElement(ABC):
|
|
25
|
+
__slots__ = ('_charge', '_is_radical', '_p_charge', '_p_is_radical', '_isotope')
|
|
26
|
+
|
|
27
|
+
def __init__(self, isotope: Optional[int]):
|
|
28
|
+
self._isotope = isotope
|
|
29
|
+
self._charge = self._p_charge = 0
|
|
30
|
+
self._is_radical = self._p_is_radical = False
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def isotope(self):
|
|
34
|
+
return self._isotope
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def atomic_symbol(self) -> str:
|
|
38
|
+
return self.__class__.__name__[7:]
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def atomic_number(self) -> int:
|
|
43
|
+
"""
|
|
44
|
+
Element number
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_symbol(cls, symbol: str) -> Type['DynamicElement']:
|
|
49
|
+
"""
|
|
50
|
+
get DynamicElement class by its symbol
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
element = next(x for x in DynamicElement.__subclasses__() if x.__name__ == f'Dynamic{symbol}')
|
|
54
|
+
except StopIteration:
|
|
55
|
+
raise ValueError(f'DynamicElement with symbol "{symbol}" not found')
|
|
56
|
+
return element
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_atomic_number(cls, number: int) -> Type['DynamicElement']:
|
|
60
|
+
"""
|
|
61
|
+
get DynamicElement class by its number
|
|
62
|
+
"""
|
|
63
|
+
try:
|
|
64
|
+
element = next(x for x in DynamicElement.__subclasses__() if x.atomic_number.fget(None) == number)
|
|
65
|
+
except StopIteration:
|
|
66
|
+
raise ValueError(f'DynamicElement with number "{number}" not found')
|
|
67
|
+
return element
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_atom(cls, atom: 'Element') -> 'DynamicElement':
|
|
71
|
+
"""
|
|
72
|
+
get DynamicElement object from Element object
|
|
73
|
+
"""
|
|
74
|
+
if not isinstance(atom, Element):
|
|
75
|
+
raise TypeError('Element expected')
|
|
76
|
+
dynamic = object.__new__(cls.from_atomic_number(atom.atomic_number))
|
|
77
|
+
dynamic._isotope = atom.isotope
|
|
78
|
+
dynamic._charge = dynamic._p_charge = atom.charge
|
|
79
|
+
dynamic._is_radical = dynamic._p_is_radical = atom.is_radical
|
|
80
|
+
return dynamic
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_atoms(cls, atom1: 'Element', atom2: 'Element') -> 'DynamicElement':
|
|
84
|
+
"""
|
|
85
|
+
get DynamicElement object from pair of Element objects
|
|
86
|
+
"""
|
|
87
|
+
if not isinstance(atom1, Element) or not isinstance(atom2, Element):
|
|
88
|
+
raise TypeError('Element expected')
|
|
89
|
+
if atom1.atomic_number != atom2.atomic_number:
|
|
90
|
+
raise ValueError('elements should be of the same type')
|
|
91
|
+
if atom1.isotope != atom2.isotope:
|
|
92
|
+
raise ValueError('elements should be of the same isotope')
|
|
93
|
+
dynamic = object.__new__(cls.from_atomic_number(atom1.atomic_number))
|
|
94
|
+
dynamic._isotope = atom1.isotope
|
|
95
|
+
dynamic._charge = atom1.charge
|
|
96
|
+
dynamic._p_charge = atom2.charge
|
|
97
|
+
dynamic._is_radical = atom1.is_radical
|
|
98
|
+
dynamic._p_is_radical = atom2.is_radical
|
|
99
|
+
return dynamic
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def charge(self) -> int:
|
|
103
|
+
return self._charge
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def is_radical(self) -> bool:
|
|
107
|
+
return self._is_radical
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def p_charge(self) -> int:
|
|
111
|
+
return self._p_charge
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def p_is_radical(self) -> bool:
|
|
115
|
+
return self._p_is_radical
|
|
116
|
+
|
|
117
|
+
def __eq__(self, other):
|
|
118
|
+
"""
|
|
119
|
+
compare attached to molecules dynamic elements
|
|
120
|
+
"""
|
|
121
|
+
return isinstance(other, DynamicElement) and self.atomic_number == other.atomic_number and \
|
|
122
|
+
self.isotope == other.isotope and self.charge == other.charge and self.is_radical == other.is_radical and \
|
|
123
|
+
self.p_charge == other.p_charge and self.p_is_radical == other.p_is_radical
|
|
124
|
+
|
|
125
|
+
def __hash__(self):
|
|
126
|
+
return hash((self.isotope or 0, self.atomic_number, self.charge, self.p_charge,
|
|
127
|
+
self.is_radical, self.p_is_radical))
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def is_dynamic(self) -> bool:
|
|
131
|
+
"""
|
|
132
|
+
Atom has dynamic features
|
|
133
|
+
"""
|
|
134
|
+
return self.charge != self.p_charge or self.is_radical != self.p_is_radical
|
|
135
|
+
|
|
136
|
+
def copy(self):
|
|
137
|
+
copy = object.__new__(self.__class__)
|
|
138
|
+
copy._isotope = self.isotope
|
|
139
|
+
copy._charge = self.charge
|
|
140
|
+
copy._is_radical = self.is_radical
|
|
141
|
+
copy._p_is_radical = self.p_is_radical
|
|
142
|
+
copy._p_charge = self.p_charge
|
|
143
|
+
return copy
|
|
144
|
+
|
|
145
|
+
def __copy__(self):
|
|
146
|
+
return self.copy()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
__all__ = ['DynamicElement']
|