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.
Files changed (171) hide show
  1. chython/__init__.py +31 -0
  2. chython/_functions.py +69 -0
  3. chython/algorithms/__init__.py +18 -0
  4. chython/algorithms/_isomorphism.cp312-win_amd64.pyd +0 -0
  5. chython/algorithms/_isomorphism.pyx +189 -0
  6. chython/algorithms/aromatics/__init__.py +27 -0
  7. chython/algorithms/aromatics/_rules.py +124 -0
  8. chython/algorithms/aromatics/kekule.py +530 -0
  9. chython/algorithms/aromatics/test/__init__.py +18 -0
  10. chython/algorithms/aromatics/test/test_kekule.py +160 -0
  11. chython/algorithms/aromatics/test/test_thiele.py +263 -0
  12. chython/algorithms/aromatics/thiele.py +226 -0
  13. chython/algorithms/calculate2d/__init__.py +23 -0
  14. chython/algorithms/calculate2d/clean2d.js +1 -0
  15. chython/algorithms/calculate2d/molecule.py +149 -0
  16. chython/algorithms/calculate2d/reaction.py +80 -0
  17. chython/algorithms/depict.py +523 -0
  18. chython/algorithms/fingerprints/__init__.py +48 -0
  19. chython/algorithms/fingerprints/linear.py +208 -0
  20. chython/algorithms/fingerprints/morgan.py +137 -0
  21. chython/algorithms/fingerprints/test/__init__.py +18 -0
  22. chython/algorithms/fingerprints/test/test_linear.py +127 -0
  23. chython/algorithms/fingerprints/test/test_morgan.py +79 -0
  24. chython/algorithms/isomorphism.py +667 -0
  25. chython/algorithms/mapping/__init__.py +28 -0
  26. chython/algorithms/mapping/_groups.py +94 -0
  27. chython/algorithms/mapping/_reactions.py +72 -0
  28. chython/algorithms/mapping/attention.py +216 -0
  29. chython/algorithms/mapping/fixmapper.py +84 -0
  30. chython/algorithms/mapping/groups.py +137 -0
  31. chython/algorithms/mcs.py +202 -0
  32. chython/algorithms/morgan.py +82 -0
  33. chython/algorithms/rings.py +551 -0
  34. chython/algorithms/smiles.py +556 -0
  35. chython/algorithms/standardize/__init__.py +30 -0
  36. chython/algorithms/standardize/_charged.py +115 -0
  37. chython/algorithms/standardize/_groups.py +926 -0
  38. chython/algorithms/standardize/_metal_organics.py +189 -0
  39. chython/algorithms/standardize/_reagents.py +44 -0
  40. chython/algorithms/standardize/_salts.py +68 -0
  41. chython/algorithms/standardize/molecule.py +479 -0
  42. chython/algorithms/standardize/reaction.py +429 -0
  43. chython/algorithms/standardize/resonance.py +196 -0
  44. chython/algorithms/standardize/salts.py +128 -0
  45. chython/algorithms/standardize/saturation.py +378 -0
  46. chython/algorithms/standardize/test/__init__.py +18 -0
  47. chython/algorithms/standardize/test/test_groups.py +118 -0
  48. chython/algorithms/stereo.py +1222 -0
  49. chython/algorithms/tautomers/__init__.py +171 -0
  50. chython/algorithms/tautomers/_acid.py +59 -0
  51. chython/algorithms/tautomers/_base.py +116 -0
  52. chython/algorithms/tautomers/_keto_enol.py +107 -0
  53. chython/algorithms/tautomers/acid_base.py +214 -0
  54. chython/algorithms/tautomers/heteroarenes.py +108 -0
  55. chython/algorithms/tautomers/keto_enol.py +148 -0
  56. chython/algorithms/tautomers/test/__init__.py +18 -0
  57. chython/algorithms/tautomers/test/test_tautomers.py +80 -0
  58. chython/algorithms/test/__init__.py +18 -0
  59. chython/algorithms/test/test_isomorphism.py +58 -0
  60. chython/algorithms/test/test_smiles.py +76 -0
  61. chython/algorithms/x3dom.py +355 -0
  62. chython/containers/__init__.py +43 -0
  63. chython/containers/_pack_v2.cp312-win_amd64.pyd +0 -0
  64. chython/containers/_pack_v2.pyx +271 -0
  65. chython/containers/_unpack_v0v2.cp312-win_amd64.pyd +0 -0
  66. chython/containers/_unpack_v0v2.pyx +300 -0
  67. chython/containers/bonds.py +255 -0
  68. chython/containers/cgr.py +115 -0
  69. chython/containers/graph.py +200 -0
  70. chython/containers/molecule.py +695 -0
  71. chython/containers/query.py +66 -0
  72. chython/containers/reaction.py +324 -0
  73. chython/exceptions.py +114 -0
  74. chython/files/MRVrw.py +523 -0
  75. chython/files/PDBrw.py +257 -0
  76. chython/files/RDFrw.py +298 -0
  77. chython/files/SDFrw.py +221 -0
  78. chython/files/__init__.py +29 -0
  79. chython/files/_convert.py +207 -0
  80. chython/files/_mapping.py +117 -0
  81. chython/files/_xyz.cp312-win_amd64.pyd +0 -0
  82. chython/files/_xyz.pyx +74 -0
  83. chython/files/daylight/__init__.py +24 -0
  84. chython/files/daylight/parser.py +157 -0
  85. chython/files/daylight/smarts.py +107 -0
  86. chython/files/daylight/smiles.py +245 -0
  87. chython/files/daylight/test/__init__.py +18 -0
  88. chython/files/daylight/test/test_daylight_smarts.py +289 -0
  89. chython/files/daylight/test/test_daylight_smiles.py +101 -0
  90. chython/files/daylight/test/test_parser.py +118 -0
  91. chython/files/daylight/test/test_tokenize.py +77 -0
  92. chython/files/daylight/tokenize.py +376 -0
  93. chython/files/libinchi/__init__.py +22 -0
  94. chython/files/libinchi/libinchi.dll +0 -0
  95. chython/files/libinchi/wrapper.py +555 -0
  96. chython/files/mdl/__init__.py +25 -0
  97. chython/files/mdl/emol.py +209 -0
  98. chython/files/mdl/erxn.py +67 -0
  99. chython/files/mdl/mol.py +144 -0
  100. chython/files/mdl/read.py +225 -0
  101. chython/files/mdl/rxn.py +67 -0
  102. chython/files/mdl/stereo.py +63 -0
  103. chython/files/mdl/write.py +165 -0
  104. chython/files/xyz.py +78 -0
  105. chython/periodictable/__init__.py +69 -0
  106. chython/periodictable/base/__init__.py +25 -0
  107. chython/periodictable/base/dynamic.py +149 -0
  108. chython/periodictable/base/element.py +452 -0
  109. chython/periodictable/base/groups.py +90 -0
  110. chython/periodictable/base/periods.py +46 -0
  111. chython/periodictable/base/query.py +473 -0
  112. chython/periodictable/base/vector.py +119 -0
  113. chython/periodictable/groupI.py +252 -0
  114. chython/periodictable/groupII.py +223 -0
  115. chython/periodictable/groupIII.py +1154 -0
  116. chython/periodictable/groupIV.py +210 -0
  117. chython/periodictable/groupIX.py +196 -0
  118. chython/periodictable/groupV.py +192 -0
  119. chython/periodictable/groupVI.py +183 -0
  120. chython/periodictable/groupVII.py +162 -0
  121. chython/periodictable/groupVIII.py +161 -0
  122. chython/periodictable/groupX.py +166 -0
  123. chython/periodictable/groupXI.py +164 -0
  124. chython/periodictable/groupXII.py +158 -0
  125. chython/periodictable/groupXIII.py +239 -0
  126. chython/periodictable/groupXIV.py +262 -0
  127. chython/periodictable/groupXV.py +276 -0
  128. chython/periodictable/groupXVI.py +457 -0
  129. chython/periodictable/groupXVII.py +324 -0
  130. chython/periodictable/groupXVIII.py +260 -0
  131. chython/reactor/__init__.py +24 -0
  132. chython/reactor/base.py +212 -0
  133. chython/reactor/deprotection.py +565 -0
  134. chython/reactor/groups.py +128 -0
  135. chython/reactor/reactions/__init__.py +157 -0
  136. chython/reactor/reactions/_amidation.py +58 -0
  137. chython/reactor/reactions/_amine_isocyanate.py +52 -0
  138. chython/reactor/reactions/_buchwald_hartwig.py +50 -0
  139. chython/reactor/reactions/_esterification.py +49 -0
  140. chython/reactor/reactions/_macmillan.py +43 -0
  141. chython/reactor/reactions/_reductive_amination.py +50 -0
  142. chython/reactor/reactions/_sonogashira.py +49 -0
  143. chython/reactor/reactions/_sulfonamidation.py +52 -0
  144. chython/reactor/reactions/_suzuki_miyaura.py +88 -0
  145. chython/reactor/reactions/ufe.py +99 -0
  146. chython/reactor/reactor.py +170 -0
  147. chython/reactor/retro/__init__.py +77 -0
  148. chython/reactor/retro/_amidation.py +39 -0
  149. chython/reactor/retro/_aryl_amination.py +32 -0
  150. chython/reactor/retro/_mitsunobu.py +49 -0
  151. chython/reactor/retro/_sonogashira.py +49 -0
  152. chython/reactor/retro/_suzuki_miyaura.py +54 -0
  153. chython/reactor/scaffold.py +122 -0
  154. chython/reactor/test/__init__.py +18 -0
  155. chython/reactor/test/test_deprotection.py +57 -0
  156. chython/reactor/test/test_reactor.py +40 -0
  157. chython/reactor/test/test_scaffold.py +57 -0
  158. chython/reactor/test/test_transformer.py +40 -0
  159. chython/reactor/transformer.py +62 -0
  160. chython/utils/__init__.py +57 -0
  161. chython/utils/free_wilson.py +114 -0
  162. chython/utils/grid.py +130 -0
  163. chython/utils/rdkit.py +165 -0
  164. chython/utils/retro.py +139 -0
  165. chython/utils/svg.py +57 -0
  166. chython/utils/test/__init__.py +18 -0
  167. chython/utils/test/test_rdkit.py +71 -0
  168. chython-2.4.dist-info/LICENSE +165 -0
  169. chython-2.4.dist-info/METADATA +101 -0
  170. chython-2.4.dist-info/RECORD +171 -0
  171. chython-2.4.dist-info/WHEEL +4 -0
@@ -0,0 +1,67 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020-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
+ from .mol import parse_mol_v2000
20
+ from ...exceptions import EmptyMolecule, EmptyReaction, InvalidV2000
21
+
22
+
23
+ def parse_rxn_v2000(data, *, ignore=True):
24
+ line = data[4]
25
+ reactants_count = int(line[:3])
26
+ products_count = int(line[3:6]) + reactants_count
27
+ reagents_count = int(line[6:].rstrip() or 0) + products_count
28
+
29
+ if not reagents_count:
30
+ raise EmptyReaction
31
+
32
+ title = data[1].strip() or None
33
+ log = []
34
+ molecules = []
35
+
36
+ start = -1
37
+ for n in range(0, reagents_count):
38
+ try:
39
+ start = next(n for n, x in enumerate(data[start + 6:], start + 7) if x.startswith('$MOL'))
40
+ except StopIteration:
41
+ raise InvalidV2000
42
+
43
+ try:
44
+ molecules.append(parse_mol_v2000(data[start:]))
45
+ except ValueError as e:
46
+ if isinstance(e, EmptyMolecule):
47
+ log.append(f'ignored empty molecule {n}')
48
+ elif ignore:
49
+ log.append(f'ignored molecule {n} with {e}')
50
+ else:
51
+ raise
52
+
53
+ if (lm := len(molecules)) < reactants_count:
54
+ reactants_count -= 1
55
+ products_count -= 1
56
+ reagents_count -= 1
57
+ elif lm < products_count:
58
+ products_count -= 1
59
+ reagents_count -= 1
60
+ else:
61
+ reagents_count -= 1
62
+
63
+ return {'reactants': molecules[:reactants_count], 'products': molecules[reactants_count:products_count],
64
+ 'reagents': molecules[products_count:], 'title': title, 'log': log}
65
+
66
+
67
+ __all__ = ['parse_rxn_v2000']
@@ -0,0 +1,63 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020-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
+ from ...exceptions import NotChiral, IsChiral, ValenceError
20
+
21
+
22
+ def postprocess_molecule(molecule, data, *, ignore_stereo=False, calc_cis_trans=False):
23
+ if ignore_stereo:
24
+ return
25
+ mapping = data['mapping']
26
+ log = []
27
+
28
+ if calc_cis_trans:
29
+ molecule.calculate_cis_trans_from_2d(clean_cache=False)
30
+
31
+ stereo = [(mapping[n], mapping[m], s) for n, m, s in data['stereo']]
32
+ while stereo:
33
+ fail_stereo = []
34
+ old_stereo = len(stereo)
35
+ for n, m, s in stereo:
36
+ try:
37
+ molecule.add_wedge(n, m, s, clean_cache=False)
38
+ except NotChiral:
39
+ fail_stereo.append((n, m, s))
40
+ except IsChiral:
41
+ pass
42
+ except ValenceError:
43
+ log.append('structure has errors, stereo data skipped')
44
+ molecule.flush_cache()
45
+ break
46
+ else:
47
+ stereo = fail_stereo
48
+ if len(stereo) == old_stereo:
49
+ break
50
+ molecule.flush_stereo_cache()
51
+ if calc_cis_trans:
52
+ molecule.calculate_cis_trans_from_2d(clean_cache=False)
53
+ continue
54
+ break
55
+
56
+ if log:
57
+ if 'chython_parsing_log' not in molecule.meta:
58
+ molecule.meta['chython_parsing_log'] = log
59
+ else:
60
+ molecule.meta['chython_parsing_log'].extend(log)
61
+
62
+
63
+ __all__ = ['postprocess_molecule']
@@ -0,0 +1,165 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2021-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
+ from collections import defaultdict
20
+ from io import StringIO, TextIOWrapper
21
+ from pathlib import Path
22
+ from ...containers import MoleculeContainer
23
+
24
+
25
+ charge_map = {-4: ' 0', -3: ' 7', -2: ' 6', -1: ' 5', 0: ' 0', 1: ' 3', 2: ' 2', 3: ' 1', 4: ' 0'}
26
+
27
+
28
+ class IO:
29
+ def __init__(self, file, *, mapping: bool = True, append: bool = False):
30
+ """
31
+ :param mapping: write atom mapping.
32
+ :param append: open file path in append mode.
33
+ """
34
+ self._mapping = mapping
35
+
36
+ if isinstance(file, str):
37
+ self._file = open(file, 'a' if append else 'w')
38
+ self._is_buffer = False
39
+ elif isinstance(file, Path):
40
+ self._file = file.open('a' if append else 'w')
41
+ self._is_buffer = False
42
+ elif isinstance(file, (TextIOWrapper, StringIO)):
43
+ self._file = file
44
+ self._is_buffer = True
45
+ else:
46
+ raise TypeError('invalid file. TextIOWrapper, StringIO subclasses or path to file expected')
47
+
48
+ def close(self, force=False):
49
+ """
50
+ Close opened file
51
+
52
+ :param force: force closing of externally opened file or buffer
53
+ """
54
+ self.write = self.__write_closed
55
+
56
+ if not self._is_buffer or force:
57
+ self._file.close()
58
+
59
+ def __enter__(self):
60
+ return self
61
+
62
+ def __exit__(self, _type, value, traceback):
63
+ self.close()
64
+
65
+ @staticmethod
66
+ def __write_closed(_):
67
+ raise ValueError('I/O operation on closed writer')
68
+
69
+
70
+ class EMOLWrite(IO):
71
+ def _write_molecule(self, g, write3d=None):
72
+ if not isinstance(g, MoleculeContainer):
73
+ raise TypeError('MoleculeContainer expected')
74
+
75
+ if write3d is not None:
76
+ xyz = g._conformers[write3d]
77
+ else:
78
+ z = 0
79
+
80
+ bonds = g._bonds
81
+
82
+ file = self._file
83
+ file.write(f'M V30 BEGIN CTAB\nM V30 COUNTS {g.atoms_count} {g.bonds_count} 0 0 0\nM V30 BEGIN ATOM\n')
84
+
85
+ for n, (m, a) in enumerate(g.atoms(), start=1):
86
+ if write3d is not None:
87
+ x, y, z = xyz[m]
88
+ z = f'{z:.4f}'
89
+ else:
90
+ x, y = a.x, a.y
91
+
92
+ c = f' CHG={a.charge}' if a.charge else ''
93
+ r = ' RAD=2' if a.is_radical else ''
94
+ i = f' MASS={a.isotope}' if a.isotope else ''
95
+
96
+ if not self._mapping:
97
+ m = 0
98
+ file.write(f'M V30 {n} {a.atomic_symbol} {x:.4f} {y:.4f} {z} {m}{c}{r}{i}\n')
99
+
100
+ file.write('M V30 END ATOM\nM V30 BEGIN BOND\n')
101
+
102
+ mapping = {m: n for n, m in enumerate(g, start=1)}
103
+ wedge = defaultdict(set)
104
+ i = 0 # trick for empty wedge_map
105
+ for i, (n, m, s) in enumerate(g._wedge_map, start=1):
106
+ file.write(f'M V30 {i} {bonds[n][m].order} {mapping[n]} {mapping[m]} CFG={s == 1 and "1" or "3"}\n')
107
+ wedge[n].add(m)
108
+ wedge[m].add(n)
109
+
110
+ for n, m, b in g.bonds():
111
+ if m not in wedge[n]:
112
+ i += 1
113
+ file.write(f'M V30 {i} {b.order} {mapping[n]} {mapping[m]}\n')
114
+ file.write('M V30 END BOND\nM V30 END CTAB\n')
115
+
116
+
117
+ class MOLWrite(IO):
118
+ def _write_molecule(self, g, write3d=None):
119
+ if not isinstance(g, MoleculeContainer):
120
+ raise TypeError('MoleculeContainer expected')
121
+ elif max(g) > 999:
122
+ raise ValueError('MOL file support only small molecules')
123
+
124
+ if write3d is not None:
125
+ xyz = g._conformers[write3d]
126
+ else:
127
+ z = 0.
128
+
129
+ bonds = g._bonds
130
+
131
+ file = self._file
132
+ file.write(f'{g.name}\n\n\n{g.atoms_count:3d}{g.bonds_count:3d} 0 0 0 0 999 V2000\n')
133
+
134
+ for n, (m, a) in enumerate(g.atoms(), start=1):
135
+ if write3d is not None:
136
+ x, y, z = xyz[m]
137
+ else:
138
+ x, y = a.x, a.y
139
+
140
+ c = charge_map[a.charge]
141
+ if not self._mapping:
142
+ m = 0
143
+ file.write(f'{x:10.4f}{y:10.4f}{z:10.4f} {a.atomic_symbol:3s} 0{c} 0 0 0 0 0 0 0{m:3d} 0 0\n')
144
+
145
+ atoms = {m: n for n, m in enumerate(g, start=1)}
146
+ wedge = defaultdict(set)
147
+ for n, m, s in g._wedge_map:
148
+ file.write(f'{atoms[n]:3d}{atoms[m]:3d} {bonds[n][m].order} {s == 1 and "1" or "6"} 0 0 0\n')
149
+ wedge[n].add(m)
150
+ wedge[m].add(n)
151
+ for n, m, b in g.bonds():
152
+ if m not in wedge[n]:
153
+ file.write(f'{atoms[n]:3d}{atoms[m]:3d} {b.order} 0 0 0 0\n')
154
+
155
+ for n, (_, a) in enumerate(g.atoms(), start=1):
156
+ if a.isotope:
157
+ file.write(f'M ISO 1 {n:3d} {a.isotope:3d}\n')
158
+ if a.is_radical:
159
+ file.write(f'M RAD 1 {n:3d} 2\n') # invalid for carbenes
160
+ if a.charge in (-4, 4):
161
+ file.write(f'M CHG 1 {n:3d} {a.charge:3d}\n')
162
+ file.write('M END\n')
163
+
164
+
165
+ __all__ = ['MOLWrite', 'EMOLWrite']
chython/files/xyz.py ADDED
@@ -0,0 +1,78 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020-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
+ 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-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
+ 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-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
+ 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-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
+ 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']