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,554 @@
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 ctypes import c_char, c_double, c_short, c_long, c_char_p, c_byte, POINTER, Structure, cdll, byref
20
+ from importlib.resources import files, as_file
21
+ from itertools import count
22
+ from sysconfig import get_platform
23
+ from threading import Lock
24
+ from warnings import warn
25
+ from .._convert import create_molecule
26
+ from ...containers import MoleculeContainer
27
+ from ...containers.bonds import Bond
28
+ from ...exceptions import ValenceError, IsChiral, NotChiral
29
+ from ...periodictable import H as _H
30
+
31
+
32
+ H = 1
33
+
34
+
35
+ def inchi(data, /, *, ignore_stereo: bool = False, _cls=MoleculeContainer) -> MoleculeContainer:
36
+ """
37
+ INCHI string parser
38
+ """
39
+ if lib is None:
40
+ raise ImportError('libINCHI not found')
41
+
42
+ structure = INCHIStructure()
43
+ with _lib_lock:
44
+ if lib.GetStructFromINCHI(byref(InputINCHI(data)), byref(structure)):
45
+ lib.FreeStructFromINCHI(byref(structure))
46
+ raise ValueError('invalid INCHI')
47
+
48
+ try:
49
+ atoms, bonds = [], []
50
+ protium = {}
51
+ deuterium = {}
52
+ tritium = {}
53
+ seen = set()
54
+ for n in range(structure.num_atoms):
55
+ seen.add(n)
56
+ atom = structure.atom[n]
57
+
58
+ atoms.append({'element': atom.atomic_symbol, 'charge': atom.charge, 'x': atom.x, 'y': atom.y,
59
+ 'z': atom.z, 'isotope': atom.isotope, 'is_radical': atom.is_radical,
60
+ 'implicit_hydrogens': atom.implicit_hydrogens, 'delta_isotope': atom.delta_isotope})
61
+ if atom.implicit_protium:
62
+ protium[n] = atom.implicit_protium
63
+ if atom.implicit_deuterium:
64
+ deuterium[n] = atom.implicit_deuterium
65
+ if atom.implicit_tritium:
66
+ tritium[n] = atom.implicit_tritium
67
+
68
+ for k in range(atom.num_bonds):
69
+ m = atom.neighbor[k]
70
+ if m in seen:
71
+ continue
72
+ order = atom.bond_type[k]
73
+ if order:
74
+ bonds.append((n, m, order))
75
+
76
+ stereo_atoms = []
77
+ stereo_allenes = []
78
+ stereo_cumulenes = []
79
+ for i in range(structure.num_stereo0D):
80
+ stereo = structure.stereo0D[i]
81
+ sign = stereo.sign
82
+ if sign is not None:
83
+ if stereo.is_tetrahedral:
84
+ stereo_atoms.append((stereo.central_atom, stereo.neighbors, sign))
85
+ elif stereo.is_allene:
86
+ nn, *_, nm = stereo.neighbors
87
+ stereo_allenes.append((stereo.central_atom, nn, nm, sign))
88
+ elif stereo.is_cumulene:
89
+ nn, n, m, nm = stereo.neighbors
90
+ stereo_cumulenes.append((n, m, nn, nm, sign))
91
+ finally:
92
+ lib.FreeStructFromINCHI(byref(structure))
93
+
94
+ tmp = {'atoms': atoms, 'bonds': bonds, 'stereo_atoms': stereo_atoms, 'stereo_allenes': stereo_allenes,
95
+ 'stereo_cumulenes': stereo_cumulenes, 'mapping': list(range(1, len(atoms) + 1)),
96
+ 'protium': protium, 'deuterium': deuterium, 'tritium': tritium}
97
+ mol = create_molecule(tmp, skip_calc_implicit=True, _cls=_cls)
98
+ postprocess_molecule(mol, tmp, ignore_stereo=ignore_stereo)
99
+ return mol
100
+
101
+
102
+ def postprocess_molecule(molecule, data, *, ignore_stereo=False):
103
+ atoms = molecule._atoms
104
+ bonds = molecule._bonds
105
+
106
+ # set hydrogen atoms. INCHI designed for hydrogens handling. hope correctly.
107
+ free = count(len(atoms) + 1)
108
+ to_add = []
109
+ for n, atom in atoms.items():
110
+ # in chython hydrogens never have implicit H. convert to explicit
111
+ if atom == H and atom.implicit_hydrogens:
112
+ for _ in range(atom.implicit_hydrogens):
113
+ to_add.append((n, next(free), _H(implicit_hydrogens=0)))
114
+ atom._implicit_hydrogens = 0
115
+
116
+ for n, p in data['protium'].items():
117
+ to_add.append((n + 1, next(free), _H(isotope=1, implicit_hydrogens=0)))
118
+ for n, p in data['deuterium'].items():
119
+ to_add.append((n + 1, next(free), _H(isotope=2, implicit_hydrogens=0)))
120
+ for n, p in data['tritium'].items():
121
+ to_add.append((n + 1, next(free), _H(isotope=3, implicit_hydrogens=0)))
122
+
123
+ if to_add:
124
+ for n, m, a in to_add:
125
+ atoms[m] = a
126
+ bonds[n][m] = b = Bond(1)
127
+ bonds[m] = {n: b}
128
+ molecule.calc_labels() # reset labels
129
+
130
+ if ignore_stereo or not data['stereo_atoms'] and not data['stereo_cumulenes'] and not data['stereo_allenes']:
131
+ return
132
+
133
+ st = molecule.stereogenic_tetrahedrons
134
+ sa = molecule.stereogenic_allenes
135
+ ctc = molecule._stereo_cis_trans_counterpart
136
+
137
+ stereo = []
138
+ for n, ngb, s in data['stereo_atoms']:
139
+ n += 1
140
+ if n in st:
141
+ stereo.append((molecule.add_atom_stereo, n, [x + 1 for x in ngb], s))
142
+ for n, nn, mn, s in data['stereo_allenes']:
143
+ n += 1
144
+ if n in sa:
145
+ stereo.append((molecule.add_atom_stereo, n, nn + 1, mn + 1, s))
146
+ for n, m, nn, nm, s in data['stereo_cumulenes']:
147
+ n += 1
148
+ if n in ctc:
149
+ stereo.append((molecule.add_cis_trans_stereo, n, m + 1, nn + 1, nm + 1, s))
150
+
151
+ while stereo:
152
+ fail_stereo = []
153
+ old_stereo = len(stereo)
154
+ for f, *args in stereo:
155
+ try:
156
+ f(*args, clean_cache=False)
157
+ except NotChiral:
158
+ fail_stereo.append((f, *args))
159
+ except IsChiral:
160
+ pass
161
+ except ValenceError:
162
+ if 'chython_parsing_log' not in molecule.meta:
163
+ molecule.meta['chython_parsing_log'] = []
164
+ molecule.meta['chython_parsing_log'].append('structure has errors, stereo data skipped')
165
+ molecule.flush_cache()
166
+ break
167
+ else:
168
+ stereo = fail_stereo
169
+ if len(stereo) == old_stereo:
170
+ break
171
+ molecule.flush_stereo_cache()
172
+ continue
173
+ break
174
+
175
+
176
+ class InputINCHI(Structure):
177
+ def __init__(self, string, options=None):
178
+ if options is None:
179
+ options = b''
180
+ else:
181
+ options = ' '.join(f'{opt_flag}{x}' for x in options).encode()
182
+ super().__init__(string.encode(), options)
183
+
184
+ _fields_ = [('szInChI', c_char_p), # InChI ASCII string to be converted to a strucure
185
+ ('szOptions', c_char_p) # InChI options: space-delimited; each is preceded
186
+ # by '/' or '-' depending on OS and compiler
187
+ ]
188
+
189
+
190
+ class Atom(Structure):
191
+ @property
192
+ def atomic_symbol(self):
193
+ return self.elname.decode()
194
+
195
+ @property
196
+ def isotope(self):
197
+ if 0 < self.isotopic_mass < 9000: # OVER NINE THOUSANDS!
198
+ return self.isotopic_mass
199
+
200
+ @property
201
+ def delta_isotope(self):
202
+ if self.isotopic_mass > 9000:
203
+ return self.isotopic_mass - 10_000
204
+
205
+ @property
206
+ def is_radical(self):
207
+ return bool(self.radical)
208
+
209
+ @property
210
+ def implicit_hydrogens(self):
211
+ return self.num_iso_H[0]
212
+
213
+ @property
214
+ def implicit_protium(self):
215
+ return self.num_iso_H[1]
216
+
217
+ @property
218
+ def implicit_deuterium(self):
219
+ return self.num_iso_H[2]
220
+
221
+ @property
222
+ def implicit_tritium(self):
223
+ return self.num_iso_H[3]
224
+
225
+ _fields_ = [('x', c_double), ('y', c_double), ('z', c_double), # atom coordinates
226
+ ('neighbor', c_short * 20), # adjacency list: ordering numbers of the adjacent atoms, >= 0
227
+ ('bond_type', c_byte * 20), # inchi_BondType
228
+ ('bond_stereo', c_byte * 20), # inchi_BondStereo2D; negative if the sharp end points to opposite atom
229
+ ('elname', c_char * 6), # zero-terminated chemical element name: "H", "Si", etc.
230
+ ('num_bonds', c_short), # number of neighbors, bond types and bond stereo in the adjacency list
231
+ ('num_iso_H', c_byte * 4), # implicit hydrogen atoms
232
+ # [0]: number of implicit non-isotopic H
233
+ # (exception: num_iso_H[0]=-1 means INCHI adds implicit H automatically),
234
+ # [1]: number of implicit isotopic 1H (protium),
235
+ # [2]: number of implicit 2H (deuterium),
236
+ # [3]: number of implicit 3H (tritium)
237
+ ('isotopic_mass', c_short), # 0 => non-isotopic; isotopic mass or 10000 + mass - average atomic mass
238
+ ('radical', c_byte), # inchi_Radical,
239
+ ('charge', c_byte)] # positive or negative; 0 => no charge
240
+
241
+
242
+ class Stereo0D(Structure):
243
+ @property
244
+ def is_tetrahedral(self):
245
+ return self.type == 2
246
+
247
+ @property
248
+ def is_allene(self):
249
+ return self.type == 3
250
+
251
+ @property
252
+ def is_cumulene(self):
253
+ return self.type == 1
254
+
255
+ @property
256
+ def neighbors(self):
257
+ return tuple(self.neighbor)
258
+
259
+ @property
260
+ def sign(self):
261
+ if self.parity == 1:
262
+ return True
263
+ elif self.parity == 2:
264
+ return False
265
+
266
+ _fields_ = [('neighbor', c_short * 4), # 4 atoms always
267
+ ('central_atom', c_short), # central tetrahedral atom or a central atom of allene; otherwise NO_ATOM
268
+ ('type', c_byte), # inchi_StereoType0D
269
+ ('parity', c_byte)] # inchi_StereoParity0D
270
+
271
+
272
+ class INCHIStructure(Structure):
273
+ _fields_ = [('atom', POINTER(Atom)), # array of num_atoms elements
274
+ ('stereo0D', POINTER(Stereo0D)), # array of num_stereo0D 0D stereo elements or NULL
275
+ ('num_atoms', c_short), # number of atoms in the structure
276
+ ('num_stereo0D', c_short), # number of 0D stereo elements
277
+ ('szMessage', c_char_p), # Error/warning ASCII message
278
+ ('szLog', c_char_p), # log-file ASCII string, contains a human-readable list
279
+ # of recognized options and possibly an Error/warn message
280
+ ('WarningFlags', (c_long * 2) * 2)]
281
+
282
+ # copy-pasted from INCHI-API
283
+ # * Notes: 1. Atom ordering numbers (i, k, and atom[i].neighbor[j] below)
284
+ # * start from zero; max. ordering number is (num_atoms-1).
285
+ # * 2. inchi_Atom atom[i] is connected to the atom[atom[i].neighbor[j]]
286
+ # * by a bond that has type atom[i].bond_type[j] and 2D stereo type
287
+ # * atom[i].bond_stereo[j] (in case of no stereo
288
+ # * atom[i].bond_stereo[j] = INCHI_BOND_STEREO_NONE)
289
+ # * Index j is in the range 0 <= j <= (atom[i].num_bonds-1)
290
+ # * 3. Any connection (represented by atom[i].neighbor[j],
291
+ # * atom[i].bond_type[j], and atom[i].bond_stereo[j])
292
+ # * should be present in one or both adjacency list:
293
+ # * if k = atom[i].neighbor[j] then i may or may not be present in
294
+ # * atom[k].neighbor[] list. For example, the adjacency lists may be
295
+ # * populated with only such neighbors that atom[i].neighbor[j] < i
296
+ # * All elements of an adjacency list must be different, that is,
297
+ # * a bond must be specified in an adjacency list only once.
298
+ # * 4. in Molfiles usually
299
+ # * (number of implicit H) = Valence - SUM(bond_type[])
300
+ # * 5. Seemingly illogical order of the inchi_Atom members was
301
+ # * chosen in an attempt to avoid alignment problems when
302
+ # * accessing inchi_Atom from unrelated to C programming
303
+ # * languages such as Visual Basic.
304
+ # *******************************************************************/
305
+ #
306
+ # /*******************************************************************
307
+ # 0D Stereo Parity and Type definitions
308
+ # *******************************************************************
309
+ # Note:
310
+ # =====
311
+ # o Below #A is the ordering number of atom A, starting from 0
312
+ # o See parity values corresponding to 'o', 'e', and 'u' in
313
+ # inchi_StereoParity0D definition below)
314
+ #
315
+ # =============================================
316
+ # stereogenic bond >A=B< or cumulene >A=C=C=B<
317
+ # =============================================
318
+ #
319
+ # neighbor[4] : {#X,#A,#B,#Y} in this order
320
+ # X central_atom : NO_ATOM
321
+ # \ X Y type : INCHI_StereoType_DoubleBond
322
+ # A==B \ /
323
+ # \ A==B
324
+ # Y
325
+ #
326
+ # parity= 'e' parity= 'o' unknown parity = 'u'
327
+ #
328
+ # Limitations:
329
+ # ============
330
+ # o Atoms A and B in cumulenes MUST be connected by a chain of double bonds;
331
+ # atoms A and B in a stereogenic 'double bond' may be connected by a double,
332
+ # single, or alternating bond.
333
+ # o One atom may belong to up to 3 stereogenic bonds (i.g. in a fused
334
+ # aromatic structure).
335
+ # o Multiple stereogenic bonds incident to any given atom should
336
+ # either all except possibly one have (possibly different) defined
337
+ # parities ('o' or 'e') or should all have an unknown parity 'u'.
338
+ #
339
+ # Note on parities of alternating stereobonds
340
+ # ===========================================
341
+ # D--E
342
+ # In large rings (see Fig. 1, all // \\
343
+ # atoms are C) all alternating bonds B--C F--G
344
+ # are treated as stereogenic. // \\
345
+ # To avoid "undefined" bond parities A H
346
+ # for bonds BC, DE, FG, HI, JK, LM, AN \ /
347
+ # it is recommended to mark them with N==M J==I
348
+ # parities. \ /
349
+ # L==K Fig. 1
350
+ # Such a marking will make
351
+ # the stereochemical layer unambiguous
352
+ # and it will be different from the B--C F--G
353
+ # stereochemical layer of the second // \\ // \\
354
+ # structure (Fig. 2). A D--E H
355
+ # \ /
356
+ # N==M J==I
357
+ # By default, double and alternating \ /
358
+ # bonds in 8-member and greater rings L==K Fig. 2
359
+ # are treated by InChI as stereogenic.
360
+ #
361
+ #
362
+ # =============================================
363
+ # tetrahedral atom
364
+ # =============================================
365
+ #
366
+ # 4 neighbors
367
+ #
368
+ # X neighbor[4] : {#W, #X, #Y, #Z}
369
+ # | central_atom: #A
370
+ # W--A--Y type : INCHI_StereoType_Tetrahedral
371
+ # |
372
+ # Z
373
+ # parity: if (X,Y,Z) are clockwize when seen from W then parity is 'e' otherwise 'o'
374
+ # Example (see AXYZW above): if W is above the plane XYZ then parity = 'e'
375
+ #
376
+ # 3 neighbors
377
+ #
378
+ # Y Y neighbor[4] : {#A, #X, #Y, #Z}
379
+ # / / central_atom: #A
380
+ # X--A (e.g. O=S ) type : INCHI_StereoType_Tetrahedral
381
+ # \ \
382
+ # Z Z
383
+ #
384
+ # parity: if (X,Y,Z) are clockwize when seen from A then parity is 'e',
385
+ # otherwise 'o'
386
+ # unknown parity = 'u'
387
+ # Example (see AXYZ above): if A is above the plane XYZ then parity = 'e'
388
+ # This approach may be used also in case of an implicit H attached to A.
389
+ #
390
+ # =============================================
391
+ # allene
392
+ # =============================================
393
+ #
394
+ # X Y neighbor[4] : {#X,#A,#B,#Y}
395
+ # \ / central_atom : #C
396
+ # A=C=B type : INCHI_StereoType_Allene
397
+ #
398
+ # Y X
399
+ # | |
400
+ # when seen from A along A=C=B: X-A Y-A
401
+ #
402
+ # parity: 'e' 'o'
403
+ #
404
+ # parity: if A, B, Y are clockwise when seen from X then parity is 'e',
405
+ # otherwise 'o'
406
+ # unknown parity = 'u'
407
+ # Example (see XACBY above): if X on the diagram is above the plane ABY
408
+ # then parity is 'o'
409
+ #
410
+ # Limitations
411
+ # ===========
412
+ # o Atoms A and B in allenes MUST be connected by a chain of double bonds;
413
+ #
414
+ #
415
+ # How InChI uses 0D parities
416
+ # ==========================
417
+ #
418
+ # 1. 0D parities are used if all atom coordinates are zeroes.
419
+ #
420
+ # In addition to that:
421
+ #
422
+ # 2. 0D parities are used for Stereobonds, Allenes, or Cumulenes if:
423
+ #
424
+ # 2a. A bond to the end-atom is shorter than MIN_BOND_LEN=0.000001
425
+ # 2b. A ratio of two bond lengths to the end-atom is smaller than MIN_SINE=0.03
426
+ # 2c. In case of a linear fragment X-A=B end-atom A is treated as satisfying 2a-b
427
+ #
428
+ # 0D parities are used if 2a or 2b or 2c applies to one or both end-atoms.
429
+ #
430
+ # 3. 0D parities are used for Tetrahedral Atoms if at least one of 3a-c is true:
431
+ #
432
+ # 3a. One of bonds to the central atom is shorter than MIN_BOND_LEN=0.000001
433
+ # 3b. A ratio of two bond lengths to the central atom is smaller than MIN_SINE=0.03
434
+ # 3c. The four neighbors are almost in one plane or the central atom and
435
+ # its only 3 explicit neighbors are almost in one plane
436
+ #
437
+ # Notes on 0D parities and 'undefined' stereogenic elements
438
+ # =========================================================
439
+ #
440
+ # If 0D parity is to be used according to 1-3 but CH3 CH3
441
+ # has not been provided then the corresponding \ /
442
+ # stereogenic element is considered 'undefined'. C=CH
443
+ # /
444
+ # For example, if in the structure (Fig. 3) H
445
+ # the explicit H has been moved so that it Fig. 3
446
+ # has same coordinates as atom >C= (that is,
447
+ # the length of the bond H-C became zero)
448
+ # then the double bond is assigned 'undefined' CH3 CH3
449
+ # parity which by default is omitted from the \ /
450
+ # Identifier. CH=CH
451
+ #
452
+ # However, the structure on Fig. 4 will have double Fig. 4
453
+ # bond parity 'o' and its parity in the Identifier is (-).
454
+ #
455
+ # Notes on 0D parities in structures containing metals
456
+ # ====================================================
457
+ # Since InChI disconnects bonds to metals the 0D parities upon the
458
+ # disconnection may change in several different ways:
459
+ #
460
+ # 1) previously non-stereogenic bond may become stereogenic:
461
+ #
462
+ # \ / \ /
463
+ # CH==CH disconnection CH==CH
464
+ # \ / ======>
465
+ # M M
466
+ #
467
+ # before the disconnection: after the disconnection:
468
+ # atoms C have valence=5 and the double bond may become
469
+ # the double bond is not stereogenic
470
+ # recognized as stereogenic
471
+ #
472
+ # 2) previously stereogenic bond may become non-stereogenic:
473
+ #
474
+ # M M(+)
475
+ # \ / /
476
+ # N==C disconnection (-)N==C
477
+ # \ ======> \
478
+ #
479
+ # 3) Oddball structures, usually resulting from projecting 3D
480
+ # structures on the plane, may contain fragment like that
481
+ # depicted on Fig. 5:
482
+ #
483
+ # M A M A
484
+ # |\ / B / B
485
+ # | X / disconnection / /
486
+ # |/ \ / ======> / /
487
+ # C===C C===C
488
+ # Fig. 5
489
+ # (X stands for bond intersection)
490
+ #
491
+ # A-C=C-B parity is A-C=C-B parity is
492
+ # trans (e) cis (o) or undefined
493
+ # because the bond because C valence = 3,
494
+ # orientation is same not 4.
495
+ # as on Fig, 6 below:
496
+ #
497
+ # A M
498
+ # \ / Removal of M from the structure
499
+ # C===C on Fig. 5 changes the geometry from trans
500
+ # / \ to cis.
501
+ # M' B Removal of M and M' from the structure
502
+ # Fig. 6 on Fig. 6 does not change the A-C=C-B
503
+ # geometry: it is trans.
504
+ #
505
+ # To resolve the problem InChI API accepts the second parity
506
+ # corresponding to the metal-disconnected structure.
507
+ # To store both bond parities use left shift by 3 bits:
508
+ #
509
+ # inchi_Stereo0D::parity = ParityOfConnected | (ParityOfDisconnected<<3)
510
+ #
511
+ # In case when only disconnected structure parity exists set
512
+ # ParityOfConnected = INCHI_PARITY_UNDEFINED.
513
+ # This is the only case when INCHI_PARITY_UNDEFINED parity
514
+ # may be fed to the InChI.
515
+ #
516
+ # In cases when the bond parity in a disconnected structure exists and
517
+ # differs from the parity in the connected structure the atoms A and B
518
+ # should be non-metals.
519
+ #
520
+
521
+
522
+ lib = None
523
+ _lib_lock = Lock()
524
+
525
+ platform = get_platform()
526
+ if platform == 'win-amd64':
527
+ opt_flag = '/'
528
+ libname = 'libinchi.dll'
529
+ elif platform == 'linux-x86_64':
530
+ opt_flag = '-'
531
+ libname = 'libinchi.so'
532
+ elif platform.startswith('macosx') and platform.endswith('x86_64'):
533
+ opt_flag = '-'
534
+ libname = 'libinchi.dynlib'
535
+ elif platform.startswith('macosx'):
536
+ opt_flag = '-'
537
+ libname = 'libinchi_arm64.dylib'
538
+ else:
539
+ warn('unsupported platform for libinchi', ImportWarning)
540
+ libname = None
541
+
542
+ if libname:
543
+ file = files(__package__).joinpath(libname)
544
+ if file.is_file():
545
+ with as_file(file) as f:
546
+ try:
547
+ lib = cdll.LoadLibrary(str(f))
548
+ except OSError:
549
+ warn('libinchi loading problem', ImportWarning)
550
+ else:
551
+ warn('broken package installation. libinchi not found', ImportWarning)
552
+
553
+
554
+ __all__ = ['inchi']
@@ -0,0 +1,25 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2017-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 .mol import parse_mol_v2000
20
+ from .emol import parse_mol_v3000
21
+ from .rxn import parse_rxn_v2000
22
+ from .erxn import parse_rxn_v3000
23
+ from .stereo import postprocess_molecule
24
+ from .read import MDLRead
25
+ from .write import MOLWrite, EMOLWrite