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,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']