chython 2.4__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chython/__init__.py +31 -0
- chython/_functions.py +69 -0
- chython/algorithms/__init__.py +18 -0
- chython/algorithms/_isomorphism.cp312-win_amd64.pyd +0 -0
- chython/algorithms/_isomorphism.pyx +189 -0
- chython/algorithms/aromatics/__init__.py +27 -0
- chython/algorithms/aromatics/_rules.py +124 -0
- chython/algorithms/aromatics/kekule.py +530 -0
- chython/algorithms/aromatics/test/__init__.py +18 -0
- chython/algorithms/aromatics/test/test_kekule.py +160 -0
- chython/algorithms/aromatics/test/test_thiele.py +263 -0
- chython/algorithms/aromatics/thiele.py +226 -0
- chython/algorithms/calculate2d/__init__.py +23 -0
- chython/algorithms/calculate2d/clean2d.js +1 -0
- chython/algorithms/calculate2d/molecule.py +149 -0
- chython/algorithms/calculate2d/reaction.py +80 -0
- chython/algorithms/depict.py +523 -0
- chython/algorithms/fingerprints/__init__.py +48 -0
- chython/algorithms/fingerprints/linear.py +208 -0
- chython/algorithms/fingerprints/morgan.py +137 -0
- chython/algorithms/fingerprints/test/__init__.py +18 -0
- chython/algorithms/fingerprints/test/test_linear.py +127 -0
- chython/algorithms/fingerprints/test/test_morgan.py +79 -0
- chython/algorithms/isomorphism.py +667 -0
- chython/algorithms/mapping/__init__.py +28 -0
- chython/algorithms/mapping/_groups.py +94 -0
- chython/algorithms/mapping/_reactions.py +72 -0
- chython/algorithms/mapping/attention.py +216 -0
- chython/algorithms/mapping/fixmapper.py +84 -0
- chython/algorithms/mapping/groups.py +137 -0
- chython/algorithms/mcs.py +202 -0
- chython/algorithms/morgan.py +82 -0
- chython/algorithms/rings.py +551 -0
- chython/algorithms/smiles.py +556 -0
- chython/algorithms/standardize/__init__.py +30 -0
- chython/algorithms/standardize/_charged.py +115 -0
- chython/algorithms/standardize/_groups.py +926 -0
- chython/algorithms/standardize/_metal_organics.py +189 -0
- chython/algorithms/standardize/_reagents.py +44 -0
- chython/algorithms/standardize/_salts.py +68 -0
- chython/algorithms/standardize/molecule.py +479 -0
- chython/algorithms/standardize/reaction.py +429 -0
- chython/algorithms/standardize/resonance.py +196 -0
- chython/algorithms/standardize/salts.py +128 -0
- chython/algorithms/standardize/saturation.py +378 -0
- chython/algorithms/standardize/test/__init__.py +18 -0
- chython/algorithms/standardize/test/test_groups.py +118 -0
- chython/algorithms/stereo.py +1222 -0
- chython/algorithms/tautomers/__init__.py +171 -0
- chython/algorithms/tautomers/_acid.py +59 -0
- chython/algorithms/tautomers/_base.py +116 -0
- chython/algorithms/tautomers/_keto_enol.py +107 -0
- chython/algorithms/tautomers/acid_base.py +214 -0
- chython/algorithms/tautomers/heteroarenes.py +108 -0
- chython/algorithms/tautomers/keto_enol.py +148 -0
- chython/algorithms/tautomers/test/__init__.py +18 -0
- chython/algorithms/tautomers/test/test_tautomers.py +80 -0
- chython/algorithms/test/__init__.py +18 -0
- chython/algorithms/test/test_isomorphism.py +58 -0
- chython/algorithms/test/test_smiles.py +76 -0
- chython/algorithms/x3dom.py +355 -0
- chython/containers/__init__.py +43 -0
- chython/containers/_pack_v2.cp312-win_amd64.pyd +0 -0
- chython/containers/_pack_v2.pyx +271 -0
- chython/containers/_unpack_v0v2.cp312-win_amd64.pyd +0 -0
- chython/containers/_unpack_v0v2.pyx +300 -0
- chython/containers/bonds.py +255 -0
- chython/containers/cgr.py +115 -0
- chython/containers/graph.py +200 -0
- chython/containers/molecule.py +695 -0
- chython/containers/query.py +66 -0
- chython/containers/reaction.py +324 -0
- chython/exceptions.py +114 -0
- chython/files/MRVrw.py +523 -0
- chython/files/PDBrw.py +257 -0
- chython/files/RDFrw.py +298 -0
- chython/files/SDFrw.py +221 -0
- chython/files/__init__.py +29 -0
- chython/files/_convert.py +207 -0
- chython/files/_mapping.py +117 -0
- chython/files/_xyz.cp312-win_amd64.pyd +0 -0
- chython/files/_xyz.pyx +74 -0
- chython/files/daylight/__init__.py +24 -0
- chython/files/daylight/parser.py +157 -0
- chython/files/daylight/smarts.py +107 -0
- chython/files/daylight/smiles.py +245 -0
- chython/files/daylight/test/__init__.py +18 -0
- chython/files/daylight/test/test_daylight_smarts.py +289 -0
- chython/files/daylight/test/test_daylight_smiles.py +101 -0
- chython/files/daylight/test/test_parser.py +118 -0
- chython/files/daylight/test/test_tokenize.py +77 -0
- chython/files/daylight/tokenize.py +376 -0
- chython/files/libinchi/__init__.py +22 -0
- chython/files/libinchi/libinchi.dll +0 -0
- chython/files/libinchi/wrapper.py +555 -0
- chython/files/mdl/__init__.py +25 -0
- chython/files/mdl/emol.py +209 -0
- chython/files/mdl/erxn.py +67 -0
- chython/files/mdl/mol.py +144 -0
- chython/files/mdl/read.py +225 -0
- chython/files/mdl/rxn.py +67 -0
- chython/files/mdl/stereo.py +63 -0
- chython/files/mdl/write.py +165 -0
- chython/files/xyz.py +78 -0
- chython/periodictable/__init__.py +69 -0
- chython/periodictable/base/__init__.py +25 -0
- chython/periodictable/base/dynamic.py +149 -0
- chython/periodictable/base/element.py +452 -0
- chython/periodictable/base/groups.py +90 -0
- chython/periodictable/base/periods.py +46 -0
- chython/periodictable/base/query.py +473 -0
- chython/periodictable/base/vector.py +119 -0
- chython/periodictable/groupI.py +252 -0
- chython/periodictable/groupII.py +223 -0
- chython/periodictable/groupIII.py +1154 -0
- chython/periodictable/groupIV.py +210 -0
- chython/periodictable/groupIX.py +196 -0
- chython/periodictable/groupV.py +192 -0
- chython/periodictable/groupVI.py +183 -0
- chython/periodictable/groupVII.py +162 -0
- chython/periodictable/groupVIII.py +161 -0
- chython/periodictable/groupX.py +166 -0
- chython/periodictable/groupXI.py +164 -0
- chython/periodictable/groupXII.py +158 -0
- chython/periodictable/groupXIII.py +239 -0
- chython/periodictable/groupXIV.py +262 -0
- chython/periodictable/groupXV.py +276 -0
- chython/periodictable/groupXVI.py +457 -0
- chython/periodictable/groupXVII.py +324 -0
- chython/periodictable/groupXVIII.py +260 -0
- chython/reactor/__init__.py +24 -0
- chython/reactor/base.py +212 -0
- chython/reactor/deprotection.py +565 -0
- chython/reactor/groups.py +128 -0
- chython/reactor/reactions/__init__.py +157 -0
- chython/reactor/reactions/_amidation.py +58 -0
- chython/reactor/reactions/_amine_isocyanate.py +52 -0
- chython/reactor/reactions/_buchwald_hartwig.py +50 -0
- chython/reactor/reactions/_esterification.py +49 -0
- chython/reactor/reactions/_macmillan.py +43 -0
- chython/reactor/reactions/_reductive_amination.py +50 -0
- chython/reactor/reactions/_sonogashira.py +49 -0
- chython/reactor/reactions/_sulfonamidation.py +52 -0
- chython/reactor/reactions/_suzuki_miyaura.py +88 -0
- chython/reactor/reactions/ufe.py +99 -0
- chython/reactor/reactor.py +170 -0
- chython/reactor/retro/__init__.py +77 -0
- chython/reactor/retro/_amidation.py +39 -0
- chython/reactor/retro/_aryl_amination.py +32 -0
- chython/reactor/retro/_mitsunobu.py +49 -0
- chython/reactor/retro/_sonogashira.py +49 -0
- chython/reactor/retro/_suzuki_miyaura.py +54 -0
- chython/reactor/scaffold.py +122 -0
- chython/reactor/test/__init__.py +18 -0
- chython/reactor/test/test_deprotection.py +57 -0
- chython/reactor/test/test_reactor.py +40 -0
- chython/reactor/test/test_scaffold.py +57 -0
- chython/reactor/test/test_transformer.py +40 -0
- chython/reactor/transformer.py +62 -0
- chython/utils/__init__.py +57 -0
- chython/utils/free_wilson.py +114 -0
- chython/utils/grid.py +130 -0
- chython/utils/rdkit.py +165 -0
- chython/utils/retro.py +139 -0
- chython/utils/svg.py +57 -0
- chython/utils/test/__init__.py +18 -0
- chython/utils/test/test_rdkit.py +71 -0
- chython-2.4.dist-info/LICENSE +165 -0
- chython-2.4.dist-info/METADATA +101 -0
- chython-2.4.dist-info/RECORD +171 -0
- chython-2.4.dist-info/WHEEL +4 -0
chython/files/mdl/rxn.py
ADDED
|
@@ -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']
|