chython 2.19__cp314-cp314-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.cp314-win_amd64.pyd +0 -0
  6. chython/algorithms/_isomorphism.pyx +189 -0
  7. chython/algorithms/_rings.cp314-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.cp314-win_amd64.pyd +0 -0
  79. chython/containers/_pack_v2.pyx +271 -0
  80. chython/containers/_unpack_v0v2.cp314-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.cp314-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
chython/__init__.py ADDED
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2014-2026 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # Copyright 2014-2019 Timur Madzhidov tmadzhidov@gmail.com features and API discussion
5
+ # Copyright 2014-2019 Alexandre Varnek <varnek@unistra.fr> base idea of CGR approach
6
+ # This file is part of chython.
7
+ #
8
+ # chython is free software; you can redistribute it and/or modify
9
+ # it under the terms of the GNU Lesser General Public License as published by
10
+ # the Free Software Foundation; either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Lesser General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public License
19
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
20
+ #
21
+ from os import getenv
22
+ from typing import Literal
23
+ from .algorithms.depict import depict_settings
24
+ from .containers import *
25
+ from .files import *
26
+ from .reactor import *
27
+ from .utils import *
28
+
29
+
30
+ clean2d_engine: Literal['rdkit', 'smilesdrawer', 'cdk', 'obabel', 'indigo'] = 'smilesdrawer'
31
+ conformer_engine: Literal['rdkit', 'cdpkit'] = 'rdkit'
32
+ class_paths = [getenv('CDK_PATH', 'cdk.jar'), getenv('OPSIN_PATH', 'opsin.jar')]
33
+
34
+
35
+ __all__ = []
chython/_functions.py ADDED
@@ -0,0 +1,95 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020-2026 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # This file is part of chython.
5
+ #
6
+ # chython is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation; either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
18
+ #
19
+ from functools import wraps
20
+ from itertools import product
21
+
22
+
23
+ _SENTINEL = object()
24
+
25
+
26
+ def cached_method(func):
27
+ """Cache no-argument method result in instance __dict__. Cleared by flush_cache().
28
+
29
+ Thread-safe for concurrent reads without locking:
30
+ - dict.get/setitem are atomic in CPython 3.14 free-threaded mode
31
+ - Wrapped functions are pure (deterministic, read-only on self)
32
+ - Duplicate computation on cold cache is benign (same result)
33
+ - Mutations must be sequential (caller's responsibility)
34
+ """
35
+ key = f'__cached_method_{func.__name__}'
36
+
37
+ @wraps(func)
38
+ def wrapper(self):
39
+ val = self.__dict__.get(key, _SENTINEL)
40
+ if val is not _SENTINEL:
41
+ return val
42
+ val = func(self)
43
+ self.__dict__[key] = val
44
+ return val
45
+ return wrapper
46
+
47
+
48
+ # lazy itertools.product with diagonal combination precedence
49
+ def lazy_product(*args):
50
+ if len(args) == 1:
51
+ for x in args[0]:
52
+ yield x,
53
+ elif not args:
54
+ yield ()
55
+ else:
56
+ gens = [iter(x) for x in args]
57
+ empty = [False] * len(args)
58
+ pools = [[] for _ in range(len(args))]
59
+ indices = set()
60
+
61
+ reached = 0
62
+ while True:
63
+ out = []
64
+ ind = []
65
+ for n, (p, g, e) in enumerate(zip(pools, gens, empty)):
66
+ if e:
67
+ out.append(p[-1])
68
+ else:
69
+ try:
70
+ x = next(g)
71
+ except StopIteration:
72
+ if not p: # one of gens empty
73
+ return
74
+ reached += 1
75
+ if reached == len(args):
76
+ break
77
+ out.append(p[-1])
78
+ empty[n] = True
79
+ else:
80
+ p.append(x)
81
+ out.append(x)
82
+ ind.append(len(p) - 1)
83
+ else:
84
+ yield tuple(out)
85
+ indices.add(tuple(ind))
86
+ continue
87
+ break
88
+
89
+ for ind in product(*(range(len(p)) for p in pools)):
90
+ if ind in indices:
91
+ continue
92
+ yield tuple(p[x] for x, p in zip(ind, pools))
93
+
94
+
95
+ __all__ = ['cached_method', 'lazy_product']
chython/_java.py ADDED
@@ -0,0 +1,57 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 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 functools import cache
20
+
21
+
22
+ @cache
23
+ def _start_jvm():
24
+ """Start JVM once with all Java dependencies on classpath. Thread-safe via @cache."""
25
+ from jpype import isJVMStarted, startJVM
26
+
27
+ if not isJVMStarted():
28
+ from chython import class_paths
29
+
30
+ startJVM('--enable-native-access=ALL-UNNAMED', classpath=class_paths)
31
+
32
+
33
+ @cache
34
+ def get_cdk():
35
+ """Get CDK Java package. Starts JVM if needed."""
36
+ try:
37
+ from jpype import JPackage
38
+
39
+ _start_jvm()
40
+ return JPackage('org').openscience.cdk
41
+ except (ImportError, AttributeError):
42
+ raise ImportError('Java/JPype/CDK.jar is not installed or broken. make sure CDK_PATH env variable is set')
43
+
44
+
45
+ @cache
46
+ def get_opsin():
47
+ """Get OPSIN NameToStructure instance. Starts JVM if needed."""
48
+ try:
49
+ from jpype import JPackage
50
+
51
+ _start_jvm()
52
+ return JPackage('uk').ac.cam.ch.wwmm.opsin.NameToStructure.getInstance()
53
+ except (ImportError, AttributeError):
54
+ raise ImportError('Java/JPype/OPSIN.jar is not installed or broken. make sure OPSIN_PATH env variable is set')
55
+
56
+
57
+ __all__ = ['get_cdk', 'get_opsin']
@@ -0,0 +1,18 @@
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
+ #
@@ -0,0 +1,189 @@
1
+ # cython: freethreading_compatible=True
2
+ #
3
+ # Copyright 2021-2026 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # Copyright 2021 Aleksandr Sizov <murkyrussian@gmail.com>
5
+ # This file is part of chython.
6
+ #
7
+ # chython is free software; you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published by
9
+ # the Free Software Foundation; either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+ cimport cython
21
+ from cpython.mem cimport PyMem_Malloc, PyMem_Free
22
+ from libc.string cimport memset
23
+
24
+ cdef extern from "Python.h":
25
+ dict _PyDict_NewPresized(Py_ssize_t minused)
26
+
27
+ cdef packed struct atom_t:
28
+ unsigned long long bits1
29
+ unsigned long long bits2
30
+ unsigned long long bits3
31
+ unsigned long long bits4
32
+ unsigned int from_
33
+ unsigned int to_
34
+ unsigned int mapping
35
+
36
+ cdef packed struct bond_t:
37
+ unsigned long long bond
38
+ unsigned int index
39
+
40
+ cdef packed struct molecule_t:
41
+ unsigned int atoms_count
42
+ atom_t *atoms
43
+ bond_t *bonds
44
+
45
+ cdef packed struct q_atom_t:
46
+ unsigned long long mask1
47
+ unsigned long long mask2
48
+ unsigned long long mask3
49
+ unsigned long long mask4
50
+ unsigned int back
51
+ unsigned int closure
52
+ unsigned int from_
53
+ unsigned int to_
54
+ unsigned int mapping
55
+
56
+ cdef packed struct query_t:
57
+ unsigned int atoms_count
58
+ q_atom_t *atoms
59
+ bond_t *bonds
60
+
61
+
62
+ @cython.boundscheck(False)
63
+ @cython.wraparound(False)
64
+ def get_mapping(const unsigned char[::1] q_buffer not None, const unsigned char[::1] m_buffer not None,
65
+ const unsigned int[::1] scope not None):
66
+ # expected less than 2^16 atoms in structure.
67
+ cdef unsigned int stack = 0, path_size = 0, depth, front, q_decrement
68
+ cdef unsigned int n, m, i, j, closures_counter
69
+ cdef unsigned long long c_bond
70
+ cdef dict mapping
71
+ cdef query_t query
72
+ cdef molecule_t molecule
73
+ cdef q_atom_t q_atom
74
+ cdef atom_t n_atom, m_atom
75
+ cdef bond_t i_bond, j_bond
76
+
77
+ query.atoms_count = (<unsigned int*> &q_buffer[0])[0]
78
+ query.atoms = <q_atom_t*> (&q_buffer[0] + 4)
79
+ query.bonds = <bond_t*> (&query.atoms[0] + query.atoms_count)
80
+ q_decrement = query.atoms_count - 1
81
+
82
+ molecule.atoms_count = (<unsigned int*> &m_buffer[0])[0]
83
+ molecule.atoms = <atom_t*> (&m_buffer[0] + 4)
84
+ molecule.bonds = <bond_t*> (&molecule.atoms[0] + molecule.atoms_count)
85
+
86
+ cdef unsigned int *path = <unsigned int *> PyMem_Malloc(q_decrement * sizeof(unsigned int))
87
+ cdef unsigned int *stack_index = <unsigned int *> PyMem_Malloc(2 * molecule.atoms_count * sizeof(unsigned int))
88
+ cdef unsigned int *stack_depth = <unsigned int *> PyMem_Malloc(2 * molecule.atoms_count * sizeof(unsigned int))
89
+ cdef bint *matched = <bint *> PyMem_Malloc(molecule.atoms_count * sizeof(bint))
90
+ cdef unsigned long long *closures = <unsigned long long *> PyMem_Malloc(molecule.atoms_count * sizeof(unsigned long long))
91
+
92
+ if not path or not stack_index or not stack_depth or not matched or not closures:
93
+ raise MemoryError()
94
+
95
+ memset(matched, 0, molecule.atoms_count * sizeof(bint))
96
+ memset(closures, 0, molecule.atoms_count * sizeof(unsigned long long))
97
+
98
+ q_atom = query.atoms[0]
99
+ for n in range(molecule.atoms_count):
100
+ n_atom = molecule.atoms[n]
101
+ if (scope[n] and
102
+ q_atom.mask1 & n_atom.bits1 and # bits1 doesn't contain bond bits.
103
+ q_atom.mask2 & n_atom.bits2 == n_atom.bits2 and
104
+ q_atom.mask3 & n_atom.bits3 == n_atom.bits3 and
105
+ q_atom.mask4 & n_atom.bits4):
106
+
107
+ stack_index[stack] = n
108
+ stack_depth[stack] = 0
109
+ stack += 1
110
+
111
+ try:
112
+ while stack:
113
+ stack -= 1
114
+ depth = stack_depth[stack]
115
+ n = stack_index[stack]
116
+
117
+ if depth == q_decrement:
118
+ mapping = _PyDict_NewPresized(query.atoms_count)
119
+ for i in range(depth):
120
+ mapping[query.atoms[i].mapping] = molecule.atoms[path[i]].mapping
121
+ mapping[query.atoms[depth].mapping] = molecule.atoms[n].mapping
122
+ yield mapping
123
+ else:
124
+ if path_size != depth: # dead end reached
125
+ for i in range(depth, path_size):
126
+ matched[path[i]] = False # mark unmatched
127
+ path_size = depth
128
+
129
+ matched[n] = True
130
+ path[path_size] = n
131
+ path_size += 1
132
+
133
+ front = depth + 1
134
+ # load next query atom
135
+ q_atom = query.atoms[front]
136
+ if q_atom.back != depth: # branch
137
+ n = path[q_atom.back]
138
+
139
+ n_atom = molecule.atoms[n]
140
+ for i in range(n_atom.from_, n_atom.to_):
141
+ i_bond = molecule.bonds[i]
142
+ m = i_bond.index
143
+ m_atom = molecule.atoms[m]
144
+ if (scope[m] and not matched[m] and
145
+ q_atom.mask1 & i_bond.bond == i_bond.bond and # bond order, in ring mark and atom bit should match.
146
+ q_atom.mask2 & m_atom.bits2 == m_atom.bits2 and
147
+ q_atom.mask3 & m_atom.bits3 == m_atom.bits3 and
148
+ q_atom.mask4 & m_atom.bits4):
149
+
150
+ if q_atom.closure: # candidate atom should have same closures.
151
+ closures_counter = 0
152
+ # make a map of closures for o_n atom
153
+ # an index is a neighbor atom and a value is a bond between o_n and the neighbor
154
+ for j in range(m_atom.from_, m_atom.to_):
155
+ j_bond = molecule.bonds[j]
156
+ if j_bond.index != n and matched[j_bond.index]:
157
+ closures[j_bond.index] = j_bond.bond
158
+ closures_counter += 1
159
+
160
+ if closures_counter == q_atom.closure:
161
+ for j in range(q_atom.from_, q_atom.to_):
162
+ j_bond = query.bonds[j]
163
+ c_bond = closures[path[j_bond.index]]
164
+ if not c_bond or j_bond.bond & c_bond != c_bond: # compare order and ring bits
165
+ break
166
+ else:
167
+ stack_index[stack] = m
168
+ stack_depth[stack] = front
169
+ stack += 1
170
+
171
+ # fill an array with nulls
172
+ for j in range(m_atom.from_, m_atom.to_):
173
+ j_bond = molecule.bonds[j]
174
+ closures[j_bond.index] = 0
175
+ else: # candidate atom should not have closures.
176
+ for j in range(m_atom.from_, m_atom.to_):
177
+ j_bond = molecule.bonds[j]
178
+ if j_bond.index != n and matched[j_bond.index]:
179
+ break # found closure
180
+ else:
181
+ stack_index[stack] = m
182
+ stack_depth[stack] = front
183
+ stack += 1
184
+ finally:
185
+ PyMem_Free(path)
186
+ PyMem_Free(matched)
187
+ PyMem_Free(stack_index)
188
+ PyMem_Free(stack_depth)
189
+ PyMem_Free(closures)