rdchiral-plus 0.5.0__py3-none-any.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.
- rdchiral/__init__.py +29 -0
- rdchiral/bonds.py +609 -0
- rdchiral/chiral.py +162 -0
- rdchiral/clean.py +142 -0
- rdchiral/initialization.py +687 -0
- rdchiral/logging_config.py +173 -0
- rdchiral/main.py +1406 -0
- rdchiral/template_extractor.py +1892 -0
- rdchiral/utils.py +273 -0
- rdchiral_plus-0.5.0.dist-info/METADATA +133 -0
- rdchiral_plus-0.5.0.dist-info/RECORD +14 -0
- rdchiral_plus-0.5.0.dist-info/WHEEL +5 -0
- rdchiral_plus-0.5.0.dist-info/licenses/LICENSE +21 -0
- rdchiral_plus-0.5.0.dist-info/top_level.txt +1 -0
rdchiral/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
|
+
|
|
3
|
+
from rdkit import RDLogger
|
|
4
|
+
|
|
5
|
+
from rdchiral.initialization import rdchiralReactants, rdchiralReaction
|
|
6
|
+
from rdchiral.main import rdchiralRun, rdchiralRunText
|
|
7
|
+
from rdchiral.template_extractor import (
|
|
8
|
+
DEFAULT_EXTRACTED_TEMPLATE,
|
|
9
|
+
extract_from_reaction,
|
|
10
|
+
extract_from_reaction_smiles,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
lg = RDLogger.logger()
|
|
14
|
+
lg.setLevel(RDLogger.CRITICAL)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"DEFAULT_EXTRACTED_TEMPLATE",
|
|
18
|
+
"extract_from_reaction",
|
|
19
|
+
"extract_from_reaction_smiles",
|
|
20
|
+
"rdchiralReactants",
|
|
21
|
+
"rdchiralReaction",
|
|
22
|
+
"rdchiralRun",
|
|
23
|
+
"rdchiralRunText",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
__version__ = version("rdchiral_plus")
|
|
28
|
+
except PackageNotFoundError: # pragma: no cover
|
|
29
|
+
__version__ = "unknown"
|
rdchiral/bonds.py
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
from typing import Dict, Iterator, List, Optional, Set, Tuple
|
|
2
|
+
|
|
3
|
+
from rdkit import Chem
|
|
4
|
+
from rdkit.Chem.rdchem import BondDir, BondType
|
|
5
|
+
|
|
6
|
+
BondDirOpposite = {
|
|
7
|
+
BondDir.ENDUPRIGHT: BondDir.ENDDOWNRIGHT,
|
|
8
|
+
BondDir.ENDDOWNRIGHT: BondDir.ENDUPRIGHT,
|
|
9
|
+
BondDir.NONE: BondDir.NONE,
|
|
10
|
+
}
|
|
11
|
+
BondDirLabel = {BondDir.ENDUPRIGHT: "\\", BondDir.ENDDOWNRIGHT: "/"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def bond_dirs_by_mapnum(mol: Chem.Mol) -> Dict[Tuple[int, int], BondDir]:
|
|
15
|
+
"""
|
|
16
|
+
Return bond directions keyed by atom-map numbers for a mapped RDKit molecule.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
mol (Chem.Mol): RDKit molecule whose bonds will be inspected.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Dict[Tuple[int, int], BondDir]: Mapping from `(mapnum1, mapnum2)` to the
|
|
23
|
+
bond direction for the bond from atom `mapnum1` to atom `mapnum2`.
|
|
24
|
+
|
|
25
|
+
Note:
|
|
26
|
+
Bonds with `BondDir.NONE` are ignored.
|
|
27
|
+
Bonds are also ignored if either endpoint atom has atom-map number 0
|
|
28
|
+
(i.e., is unmapped).
|
|
29
|
+
For each directed entry `(a, b) -> d`, an opposite-direction entry
|
|
30
|
+
`(b, a) -> BondDirOpposite[d]` is also added.
|
|
31
|
+
"""
|
|
32
|
+
bond_dirs_by_mapnum: Dict[Tuple[int, int], BondDir] = {}
|
|
33
|
+
for b in mol.GetBonds():
|
|
34
|
+
bond_dir = b.GetBondDir()
|
|
35
|
+
if bond_dir == BondDir.NONE:
|
|
36
|
+
continue
|
|
37
|
+
begin_atom_map_num = b.GetBeginAtom().GetAtomMapNum()
|
|
38
|
+
if not begin_atom_map_num:
|
|
39
|
+
continue
|
|
40
|
+
end_atom_map_num = b.GetEndAtom().GetAtomMapNum()
|
|
41
|
+
if not end_atom_map_num:
|
|
42
|
+
continue
|
|
43
|
+
bond_dirs_by_mapnum[(begin_atom_map_num, end_atom_map_num)] = bond_dir
|
|
44
|
+
bond_dirs_by_mapnum[(end_atom_map_num, begin_atom_map_num)] = BondDirOpposite[
|
|
45
|
+
bond_dir
|
|
46
|
+
]
|
|
47
|
+
return bond_dirs_by_mapnum
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def enumerate_possible_cistrans_defs(
|
|
51
|
+
template_r: Chem.Mol,
|
|
52
|
+
) -> Tuple[
|
|
53
|
+
Dict[Tuple[int, int, int, int], Tuple[BondDir, BondDir]], Set[Tuple[int, int]]
|
|
54
|
+
]:
|
|
55
|
+
"""
|
|
56
|
+
This function is meant to take a reactant template and fully enumerate
|
|
57
|
+
all the ways in which different double-bonds can have their cis/trans
|
|
58
|
+
chirality specified. This is necessary because double-bond chirality cannot
|
|
59
|
+
be specified using cis/trans (global properties), but must be done using
|
|
60
|
+
ENDUPRIGHT and ENDDOWNRIGHT for the attached single bonds (local properties).
|
|
61
|
+
Now, the next issue is that on each side of the double bond, only one of
|
|
62
|
+
the single bond directions must be specified, and that direction can be
|
|
63
|
+
using either atom order. e.g.,
|
|
64
|
+
|
|
65
|
+
A1 B1
|
|
66
|
+
\\ /
|
|
67
|
+
C = C
|
|
68
|
+
/ \
|
|
69
|
+
A2 B2
|
|
70
|
+
|
|
71
|
+
Can be specified by:
|
|
72
|
+
A1-C is an ENDDOWNRIGHT, C-B1 is an ENDUPRIGHT
|
|
73
|
+
A1-C is an ENDDOWNRIGHT, C-B2 is an ENDDOWNRIGHT
|
|
74
|
+
A1-C is an ENDDOWNRIGHT, B1-C is an ENDDOWNRIGHT
|
|
75
|
+
A1-C is an ENDDOWNRIGHT, B2-C is an ENDUPRIGHT
|
|
76
|
+
...and twelve more definitions using different A1/A2 specs.
|
|
77
|
+
|
|
78
|
+
ALSO - we can think about horizontally reflecting this bond entirely,
|
|
79
|
+
which gets us even more definitions.
|
|
80
|
+
|
|
81
|
+
So, the point of this function is to fully enumerate *all* of the ways
|
|
82
|
+
in which chirality could have been specified. That way, we can take a
|
|
83
|
+
reactant atom and check if its chirality is within the list of acceptable
|
|
84
|
+
definitions to determine if a match was made.
|
|
85
|
+
|
|
86
|
+
The way we do this is by first defining the *local* chirality of a double
|
|
87
|
+
bond, which weights side chains based purely on the unique mapnum numbering.
|
|
88
|
+
Once we have a local cis/trans definition for a double bond, we can enumerate
|
|
89
|
+
the sixteen possible ways that a reactant could match it.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
template_r (Chem.Mol): Reactant template molecule with atom-map numbers.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Tuple[Dict[Tuple[int, int, int, int], Tuple[BondDir, BondDir]], Set[Tuple[int, int]]]:
|
|
96
|
+
- First element (required_bond_defs): Mapping from
|
|
97
|
+
(a1, a2, a3, a4) atom-map-number tuples to (BondDir, BondDir) pairs,
|
|
98
|
+
enumerating all valid ways to specify the cis/trans chirality of
|
|
99
|
+
double bonds in the template.
|
|
100
|
+
- Second element (required_bond_defs_coreatoms): Set of
|
|
101
|
+
(mapnum1, mapnum2) pairs identifying the core double-bond atoms
|
|
102
|
+
for which cis/trans specification was possible.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
required_bond_defs: Dict[Tuple[int, int, int, int], Tuple[BondDir, BondDir]] = {}
|
|
106
|
+
required_bond_defs_coreatoms: Set[Tuple[int, int]] = set()
|
|
107
|
+
|
|
108
|
+
for b in template_r.GetBonds():
|
|
109
|
+
if b.GetBondType() != BondType.DOUBLE:
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
# Define begin and end atoms of the double bond
|
|
113
|
+
ba = b.GetBeginAtom()
|
|
114
|
+
bb = b.GetEndAtom()
|
|
115
|
+
|
|
116
|
+
# Now check if it is even possible to specify
|
|
117
|
+
if ba.GetDegree() == 1 or bb.GetDegree() == 1:
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
ba_label = ba.GetAtomMapNum()
|
|
121
|
+
bb_label = bb.GetAtomMapNum()
|
|
122
|
+
|
|
123
|
+
# Save core atoms so we know that cis/trans was POSSIBLE to specify
|
|
124
|
+
required_bond_defs_coreatoms.add((ba_label, bb_label))
|
|
125
|
+
required_bond_defs_coreatoms.add((bb_label, ba_label))
|
|
126
|
+
|
|
127
|
+
# Define heaviest mapnum neighbor for each atom, excluding the other side of the double bond
|
|
128
|
+
ba_neighbor_labels: List[int] = [a.GetAtomMapNum() for a in ba.GetNeighbors()]
|
|
129
|
+
ba_neighbor_labels.remove(bb_label) # remove other side of =
|
|
130
|
+
ba_neighbor_labels_max = max(ba_neighbor_labels)
|
|
131
|
+
bb_neighbor_labels: List[int] = [a.GetAtomMapNum() for a in bb.GetNeighbors()]
|
|
132
|
+
bb_neighbor_labels.remove(ba_label) # remove other side of =
|
|
133
|
+
bb_neighbor_labels_max = max(bb_neighbor_labels)
|
|
134
|
+
|
|
135
|
+
# The direction of the bond being observed might need to be flipped,
|
|
136
|
+
# based on
|
|
137
|
+
# (a) if it is the heaviest atom on this side, and
|
|
138
|
+
# (b) if the begin/end atoms for the directional bond are
|
|
139
|
+
# in the wrong order (i.e., if the double-bonded atom
|
|
140
|
+
# is the begin atom)
|
|
141
|
+
front_spec = None
|
|
142
|
+
back_spec = None
|
|
143
|
+
for bab in ba.GetBonds():
|
|
144
|
+
if bab.GetBondDir() != BondDir.NONE:
|
|
145
|
+
if bab.GetBeginAtom().GetAtomMapNum() == ba_label:
|
|
146
|
+
# Bond is in wrong order - flip
|
|
147
|
+
if bab.GetEndAtom().GetAtomMapNum() != ba_neighbor_labels_max:
|
|
148
|
+
# Defined atom is not the heaviest - flip
|
|
149
|
+
front_spec = bab.GetBondDir()
|
|
150
|
+
break
|
|
151
|
+
front_spec = BondDirOpposite[bab.GetBondDir()]
|
|
152
|
+
break
|
|
153
|
+
if bab.GetBeginAtom().GetAtomMapNum() != ba_neighbor_labels_max:
|
|
154
|
+
# Defined atom is not heaviest
|
|
155
|
+
front_spec = BondDirOpposite[bab.GetBondDir()]
|
|
156
|
+
break
|
|
157
|
+
front_spec = bab.GetBondDir()
|
|
158
|
+
break
|
|
159
|
+
if front_spec is not None:
|
|
160
|
+
for bbb in bb.GetBonds():
|
|
161
|
+
if bbb.GetBondDir() != BondDir.NONE:
|
|
162
|
+
# For the "back" specification, the double-bonded atom *should* be the BeginAtom
|
|
163
|
+
if bbb.GetEndAtom().GetAtomMapNum() == bb_label:
|
|
164
|
+
# Bond is in wrong order - flip
|
|
165
|
+
if bbb.GetBeginAtom().GetAtomMapNum() != bb_neighbor_labels_max:
|
|
166
|
+
# Defined atom is not the heaviest - flip
|
|
167
|
+
back_spec = bbb.GetBondDir()
|
|
168
|
+
break
|
|
169
|
+
back_spec = BondDirOpposite[bbb.GetBondDir()]
|
|
170
|
+
break
|
|
171
|
+
if bbb.GetEndAtom().GetAtomMapNum() != bb_neighbor_labels_max:
|
|
172
|
+
# Defined atom is not heaviest - flip
|
|
173
|
+
back_spec = BondDirOpposite[bbb.GetBondDir()]
|
|
174
|
+
break
|
|
175
|
+
back_spec = bbb.GetBondDir()
|
|
176
|
+
break
|
|
177
|
+
|
|
178
|
+
# Is this an overall unspecified bond? Put it in the dictionary anyway,
|
|
179
|
+
# so there is something to match
|
|
180
|
+
if front_spec is None or back_spec is None:
|
|
181
|
+
# Create a definition over this bond so that reactant MUST be unspecified, too
|
|
182
|
+
for start_atom in ba_neighbor_labels:
|
|
183
|
+
for end_atom in bb_neighbor_labels:
|
|
184
|
+
required_bond_defs[(start_atom, ba_label, bb_label, end_atom)] = (
|
|
185
|
+
BondDir.NONE,
|
|
186
|
+
BondDir.NONE,
|
|
187
|
+
)
|
|
188
|
+
required_bond_defs[(ba_label, start_atom, bb_label, end_atom)] = (
|
|
189
|
+
BondDir.NONE,
|
|
190
|
+
BondDir.NONE,
|
|
191
|
+
)
|
|
192
|
+
required_bond_defs[(start_atom, ba_label, end_atom, bb_label)] = (
|
|
193
|
+
BondDir.NONE,
|
|
194
|
+
BondDir.NONE,
|
|
195
|
+
)
|
|
196
|
+
required_bond_defs[(ba_label, start_atom, end_atom, bb_label)] = (
|
|
197
|
+
BondDir.NONE,
|
|
198
|
+
BondDir.NONE,
|
|
199
|
+
)
|
|
200
|
+
required_bond_defs[(bb_label, end_atom, start_atom, ba_label)] = (
|
|
201
|
+
BondDir.NONE,
|
|
202
|
+
BondDir.NONE,
|
|
203
|
+
)
|
|
204
|
+
required_bond_defs[(end_atom, bb_label, start_atom, ba_label)] = (
|
|
205
|
+
BondDir.NONE,
|
|
206
|
+
BondDir.NONE,
|
|
207
|
+
)
|
|
208
|
+
required_bond_defs[(bb_label, end_atom, ba_label, start_atom)] = (
|
|
209
|
+
BondDir.NONE,
|
|
210
|
+
BondDir.NONE,
|
|
211
|
+
)
|
|
212
|
+
required_bond_defs[(end_atom, bb_label, ba_label, start_atom)] = (
|
|
213
|
+
BondDir.NONE,
|
|
214
|
+
BondDir.NONE,
|
|
215
|
+
)
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
if front_spec == back_spec:
|
|
219
|
+
b.SetProp("localChirality", "trans")
|
|
220
|
+
else:
|
|
221
|
+
b.SetProp("localChirality", "cis")
|
|
222
|
+
|
|
223
|
+
possible_defs = {}
|
|
224
|
+
for start_atom in ba_neighbor_labels:
|
|
225
|
+
for end_atom in bb_neighbor_labels:
|
|
226
|
+
needs_inversion = (start_atom != ba_neighbor_labels_max) != (
|
|
227
|
+
end_atom != bb_neighbor_labels_max
|
|
228
|
+
)
|
|
229
|
+
for start_atom_dir in [BondDir.ENDUPRIGHT, BondDir.ENDDOWNRIGHT]:
|
|
230
|
+
# When locally trans, BondDir of start shold be same as end,
|
|
231
|
+
# unless we need inversion
|
|
232
|
+
if (front_spec != back_spec) != needs_inversion:
|
|
233
|
+
# locally cis and does not need inversion (True, False)
|
|
234
|
+
# or locally trans and does need inversion (False, True)
|
|
235
|
+
end_atom_dir = BondDirOpposite[start_atom_dir]
|
|
236
|
+
else:
|
|
237
|
+
# locally cis and does need inversion (True, True)
|
|
238
|
+
# or locally trans and does not need inversion (False, False)
|
|
239
|
+
end_atom_dir = start_atom_dir
|
|
240
|
+
|
|
241
|
+
# Enumerate all combinations of atom orders...
|
|
242
|
+
possible_defs[(start_atom, ba_label, bb_label, end_atom)] = (
|
|
243
|
+
start_atom_dir,
|
|
244
|
+
end_atom_dir,
|
|
245
|
+
)
|
|
246
|
+
possible_defs[(ba_label, start_atom, bb_label, end_atom)] = (
|
|
247
|
+
BondDirOpposite[start_atom_dir],
|
|
248
|
+
end_atom_dir,
|
|
249
|
+
)
|
|
250
|
+
possible_defs[(start_atom, ba_label, end_atom, bb_label)] = (
|
|
251
|
+
start_atom_dir,
|
|
252
|
+
BondDirOpposite[end_atom_dir],
|
|
253
|
+
)
|
|
254
|
+
possible_defs[(ba_label, start_atom, end_atom, bb_label)] = (
|
|
255
|
+
BondDirOpposite[start_atom_dir],
|
|
256
|
+
BondDirOpposite[end_atom_dir],
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
possible_defs[(bb_label, end_atom, start_atom, ba_label)] = (
|
|
260
|
+
end_atom_dir,
|
|
261
|
+
start_atom_dir,
|
|
262
|
+
)
|
|
263
|
+
possible_defs[(bb_label, end_atom, ba_label, start_atom)] = (
|
|
264
|
+
end_atom_dir,
|
|
265
|
+
BondDirOpposite[start_atom_dir],
|
|
266
|
+
)
|
|
267
|
+
possible_defs[(end_atom, bb_label, start_atom, ba_label)] = (
|
|
268
|
+
BondDirOpposite[end_atom_dir],
|
|
269
|
+
start_atom_dir,
|
|
270
|
+
)
|
|
271
|
+
possible_defs[(end_atom, bb_label, ba_label, start_atom)] = (
|
|
272
|
+
BondDirOpposite[end_atom_dir],
|
|
273
|
+
BondDirOpposite[start_atom_dir],
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# Save to the definition of this bond (in either direction)
|
|
277
|
+
required_bond_defs.update(possible_defs)
|
|
278
|
+
|
|
279
|
+
return required_bond_defs, required_bond_defs_coreatoms
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def get_atoms_across_double_bonds(
|
|
283
|
+
mol: Chem.Mol,
|
|
284
|
+
) -> List[Tuple[Tuple[int, int, int, int], Tuple[BondDir, BondDir], bool]]:
|
|
285
|
+
"""
|
|
286
|
+
This function takes a molecule and returns a list of cis/trans specifications
|
|
287
|
+
according to the following:
|
|
288
|
+
|
|
289
|
+
(mapnums, dirs)
|
|
290
|
+
|
|
291
|
+
where atoms = (a1, a2, a3, a4) and dirs = (d1, d2)
|
|
292
|
+
and (a1, a2) defines the ENDUPRIGHT/ENDDOWNRIGHT direction of the "front"
|
|
293
|
+
of the bond using d1, and (a3, a4) defines the direction of the "back" of
|
|
294
|
+
the bond using d2.
|
|
295
|
+
|
|
296
|
+
This is used to initialize reactants with a SINGLE definition constraining
|
|
297
|
+
the chirality. Templates have their chirality fully enumerated, so we can
|
|
298
|
+
match this specific definition to the full set of possible definitions
|
|
299
|
+
when determining if a match should be made.
|
|
300
|
+
|
|
301
|
+
NOTE: the atom mapnums are returned. This is so we can later use them
|
|
302
|
+
to get the old_mapno property from the corresponding product atom, which is
|
|
303
|
+
an outcome-specific assignment
|
|
304
|
+
|
|
305
|
+
We also include implicit chirality here based on ring membership, but keep
|
|
306
|
+
track of that separately
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
mol (rdkit.Chem.rdchem.Mol): RDKit molecule
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
List[Tuple[Tuple[int, int, int, int], Tuple[BondDir, BondDir], bool]]:
|
|
313
|
+
A list of cis/trans specifications, where each element is a tuple of
|
|
314
|
+
(mapnums, dirs, is_implicit):
|
|
315
|
+
- mapnums (Tuple[int, int, int, int]): Atom-map numbers (a1, a2, a3, a4)
|
|
316
|
+
where (a1, a2) defines the front single-bond direction and (a3, a4)
|
|
317
|
+
defines the back single-bond direction.
|
|
318
|
+
- dirs (Tuple[BondDir, BondDir]): Bond directions for the front and back.
|
|
319
|
+
- is_implicit (bool): True if the cis/trans specification was inferred
|
|
320
|
+
from ring membership rather than explicitly set.
|
|
321
|
+
"""
|
|
322
|
+
atoms_across_double_bonds: List[
|
|
323
|
+
Tuple[Tuple[int, int, int, int], Tuple[BondDir, BondDir], bool]
|
|
324
|
+
] = []
|
|
325
|
+
ring_info: Chem.RingInfo = mol.GetRingInfo()
|
|
326
|
+
atomrings = ring_info.AtomRings()
|
|
327
|
+
|
|
328
|
+
for b in mol.GetBonds():
|
|
329
|
+
if b.GetBondType() != BondType.DOUBLE:
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
# Define begin and end atoms of the double bond
|
|
333
|
+
ba = b.GetBeginAtom()
|
|
334
|
+
bb = b.GetEndAtom()
|
|
335
|
+
|
|
336
|
+
# Now check if it is even possible to specify
|
|
337
|
+
if ba.GetDegree() == 1 or bb.GetDegree() == 1:
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
ba_label = ba.GetAtomMapNum()
|
|
341
|
+
bb_label = bb.GetAtomMapNum()
|
|
342
|
+
|
|
343
|
+
# Try to specify front and back direction separately
|
|
344
|
+
front_mapnums: Optional[Tuple[int, int]] = None
|
|
345
|
+
front_dir: Optional[BondDir] = None
|
|
346
|
+
back_mapnums: Optional[Tuple[int, int]] = None
|
|
347
|
+
back_dir: Optional[BondDir] = None
|
|
348
|
+
is_implicit = False
|
|
349
|
+
bab = None
|
|
350
|
+
bbb = None
|
|
351
|
+
|
|
352
|
+
def _bab_generator(atom: Chem.Atom) -> Iterator[Chem.Bond]:
|
|
353
|
+
for z in atom.GetBonds():
|
|
354
|
+
if z.GetBondType() != BondType.DOUBLE:
|
|
355
|
+
yield z
|
|
356
|
+
|
|
357
|
+
for bab in _bab_generator(ba):
|
|
358
|
+
if bab.GetBondDir() != BondDir.NONE:
|
|
359
|
+
front_mapnums = (
|
|
360
|
+
bab.GetBeginAtom().GetAtomMapNum(),
|
|
361
|
+
bab.GetEndAtom().GetAtomMapNum(),
|
|
362
|
+
)
|
|
363
|
+
front_dir = bab.GetBondDir()
|
|
364
|
+
break
|
|
365
|
+
|
|
366
|
+
def _bbb_generator(atom: Chem.Atom) -> Iterator[Chem.Bond]:
|
|
367
|
+
for z in atom.GetBonds():
|
|
368
|
+
if z.GetBondType() != BondType.DOUBLE:
|
|
369
|
+
yield z
|
|
370
|
+
|
|
371
|
+
for bbb in _bbb_generator(bb):
|
|
372
|
+
if bbb.GetBondDir() != BondDir.NONE:
|
|
373
|
+
back_mapnums = (
|
|
374
|
+
bbb.GetBeginAtom().GetAtomMapNum(),
|
|
375
|
+
bbb.GetEndAtom().GetAtomMapNum(),
|
|
376
|
+
)
|
|
377
|
+
back_dir = bbb.GetBondDir()
|
|
378
|
+
break
|
|
379
|
+
|
|
380
|
+
# If impossible to spec, just continue
|
|
381
|
+
if bab is None or bbb is None:
|
|
382
|
+
continue
|
|
383
|
+
|
|
384
|
+
# Did we actually get a specification out?
|
|
385
|
+
if front_dir is None or back_dir is None:
|
|
386
|
+
if b.IsInRing():
|
|
387
|
+
# Implicit cis! Now to figure out right definitions...
|
|
388
|
+
for atomring in atomrings:
|
|
389
|
+
if ba.GetIdx() in atomring and bb.GetIdx() in atomring:
|
|
390
|
+
front_mapnums = (bab.GetOtherAtom(ba).GetAtomMapNum(), ba_label)
|
|
391
|
+
back_mapnums = (bb_label, bbb.GetOtherAtom(bb).GetAtomMapNum())
|
|
392
|
+
if (bab.GetOtherAtomIdx(ba.GetIdx()) in atomring) != (
|
|
393
|
+
bbb.GetOtherAtomIdx(bb.GetIdx()) in atomring
|
|
394
|
+
):
|
|
395
|
+
# one of these atoms are in the ring, one is outside -> trans
|
|
396
|
+
front_dir = BondDir.ENDUPRIGHT
|
|
397
|
+
back_dir = BondDir.ENDUPRIGHT
|
|
398
|
+
else:
|
|
399
|
+
front_dir = BondDir.ENDUPRIGHT
|
|
400
|
+
back_dir = BondDir.ENDDOWNRIGHT
|
|
401
|
+
is_implicit = True
|
|
402
|
+
break
|
|
403
|
+
|
|
404
|
+
else:
|
|
405
|
+
# Okay no, actually unspecified
|
|
406
|
+
# Specify direction as BondDir.NONE using whatever bab and bbb were at the end fo the loop
|
|
407
|
+
# note: this is why we use "for bab in ___generator___", so that we know the current
|
|
408
|
+
# value of bab and bbb correspond to a single bond we can def. by
|
|
409
|
+
front_mapnums = (
|
|
410
|
+
bab.GetBeginAtom().GetAtomMapNum(),
|
|
411
|
+
bab.GetEndAtom().GetAtomMapNum(),
|
|
412
|
+
)
|
|
413
|
+
front_dir = BondDir.NONE
|
|
414
|
+
back_mapnums = (
|
|
415
|
+
bbb.GetBeginAtom().GetAtomMapNum(),
|
|
416
|
+
bbb.GetEndAtom().GetAtomMapNum(),
|
|
417
|
+
)
|
|
418
|
+
back_dir = BondDir.NONE
|
|
419
|
+
|
|
420
|
+
if front_mapnums is None or back_mapnums is None:
|
|
421
|
+
raise ValueError("Could not find mapnums for double bond")
|
|
422
|
+
if front_dir is None or back_dir is None:
|
|
423
|
+
raise ValueError("Could not find bond directions for double bond")
|
|
424
|
+
# Save this (a1, a2, a3, a4) -> (d1, d2) spec
|
|
425
|
+
atoms_across_double_bonds.append(
|
|
426
|
+
(
|
|
427
|
+
front_mapnums + back_mapnums,
|
|
428
|
+
(front_dir, back_dir),
|
|
429
|
+
is_implicit,
|
|
430
|
+
)
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
return atoms_across_double_bonds
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def restore_bond_stereo_to_sp2_atom(
|
|
437
|
+
a: Chem.Atom, bond_dirs_by_mapnum: Dict[Tuple[int, int], BondDir]
|
|
438
|
+
) -> bool:
|
|
439
|
+
r"""
|
|
440
|
+
Copy over single-bond directions (ENDUPRIGHT, ENDDOWNRIGHT) to
|
|
441
|
+
the single bonds attached to some double-bonded atom, a
|
|
442
|
+
|
|
443
|
+
In some cases, like C=C/O>>C=C/Br, we should assume that stereochem was
|
|
444
|
+
preserved, even though mapnums won't match. There might be some reactions
|
|
445
|
+
where the chirality is inverted (like C=C/O >> C=C\Br), but let's not
|
|
446
|
+
worry about those for now...
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
a (rdkit.Chem.rdchem.Atom): RDKit atom with double bond
|
|
450
|
+
bond_dirs_by_mapnum (Dict[Tuple[int, int], BondDir]): Dictionary of
|
|
451
|
+
(begin_mapnum, end_mapnum) -> bond_dir that defines if a bond should
|
|
452
|
+
be ENDUPRIGHT or ENDDOWNRIGHT. The reverse key is also included with
|
|
453
|
+
the reverse bond direction. If the source molecule did not have a
|
|
454
|
+
specified chirality at this double bond, then the mapnum tuples will
|
|
455
|
+
be missing from the dict.
|
|
456
|
+
Returns:
|
|
457
|
+
bool: Returns True if a bond direction was copied
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
for bond_to_spec in a.GetBonds():
|
|
461
|
+
if (
|
|
462
|
+
bond_to_spec.GetOtherAtom(a).GetAtomMapNum(),
|
|
463
|
+
a.GetAtomMapNum(),
|
|
464
|
+
) in bond_dirs_by_mapnum:
|
|
465
|
+
bond_to_spec.SetBondDir(
|
|
466
|
+
bond_dirs_by_mapnum[
|
|
467
|
+
(
|
|
468
|
+
bond_to_spec.GetBeginAtom().GetAtomMapNum(),
|
|
469
|
+
bond_to_spec.GetEndAtom().GetAtomMapNum(),
|
|
470
|
+
)
|
|
471
|
+
]
|
|
472
|
+
)
|
|
473
|
+
return True
|
|
474
|
+
|
|
475
|
+
if a.GetDegree() == 2:
|
|
476
|
+
# Either the branch used to define was replaced with H (deg 3 -> deg 2)
|
|
477
|
+
# or the branch used to define was reacted (deg 2 -> deg 2)
|
|
478
|
+
for bond_to_spec in a.GetBonds():
|
|
479
|
+
if bond_to_spec.GetBondType() == BondType.DOUBLE:
|
|
480
|
+
continue
|
|
481
|
+
if not bond_to_spec.GetOtherAtom(a).HasProp("old_mapno"):
|
|
482
|
+
# new atom, deg2->deg2, assume direction preserved
|
|
483
|
+
needs_inversion = False
|
|
484
|
+
else:
|
|
485
|
+
# old atom, just was not used in chirality definition - set opposite
|
|
486
|
+
needs_inversion = True
|
|
487
|
+
|
|
488
|
+
for (i, j), bond_dir in bond_dirs_by_mapnum.items():
|
|
489
|
+
if (
|
|
490
|
+
bond_dir != BondDir.NONE
|
|
491
|
+
and i == bond_to_spec.GetBeginAtom().GetAtomMapNum()
|
|
492
|
+
):
|
|
493
|
+
if needs_inversion:
|
|
494
|
+
bond_to_spec.SetBondDir(BondDirOpposite[bond_dir])
|
|
495
|
+
else:
|
|
496
|
+
bond_to_spec.SetBondDir(bond_dir)
|
|
497
|
+
return True
|
|
498
|
+
|
|
499
|
+
elif a.GetDegree() == 3:
|
|
500
|
+
# If we lost the branch defining stereochem, it must have been replaced
|
|
501
|
+
for bond_to_spec in a.GetBonds():
|
|
502
|
+
if bond_to_spec.GetBondType() == BondType.DOUBLE:
|
|
503
|
+
continue
|
|
504
|
+
oa = bond_to_spec.GetOtherAtom(a)
|
|
505
|
+
if oa.HasProp("old_mapno") or oa.HasProp("react_atom_idx"):
|
|
506
|
+
# looking at an old atom, which should have opposite direction as removed atom
|
|
507
|
+
needs_inversion = True
|
|
508
|
+
else:
|
|
509
|
+
# looking at a new atom, assume same as removed atom
|
|
510
|
+
needs_inversion = False
|
|
511
|
+
|
|
512
|
+
for (i, j), bond_dir in bond_dirs_by_mapnum.items():
|
|
513
|
+
if (
|
|
514
|
+
bond_dir != BondDir.NONE
|
|
515
|
+
and i == bond_to_spec.GetBeginAtom().GetAtomMapNum()
|
|
516
|
+
):
|
|
517
|
+
if needs_inversion:
|
|
518
|
+
bond_to_spec.SetBondDir(BondDirOpposite[bond_dir])
|
|
519
|
+
else:
|
|
520
|
+
bond_to_spec.SetBondDir(bond_dir)
|
|
521
|
+
return True
|
|
522
|
+
|
|
523
|
+
return False
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def correct_conjugated(
|
|
527
|
+
initial_bond_dirs: Dict[Tuple[int, int], BondDir], outcome: Chem.Mol
|
|
528
|
+
) -> bool:
|
|
529
|
+
"""
|
|
530
|
+
Checks whether the copying over of single-bond directions (ENDUPRIGHT, ENDDOWNRIGHT) was
|
|
531
|
+
corrupted for a conjugated system, where parts of the directions were specified by the template
|
|
532
|
+
and parts were copied from the reactants.
|
|
533
|
+
Args:
|
|
534
|
+
initial_bond_dirs (Dict[Tuple[int, int], BondDir]): Dictionary of
|
|
535
|
+
(begin_mapnum, end_mapnum) -> bond_dir that defines if a bond is
|
|
536
|
+
ENDUPRIGHT or ENDDOWNRIGHT. The reverse key is also included with
|
|
537
|
+
the reverse bond direction. If the source molecule did not have a
|
|
538
|
+
specified chirality at this double bond, then the mapnum tuples
|
|
539
|
+
will be missing from the dict.
|
|
540
|
+
outcome (rdkit.Chem.rdchem.Mol): RDKit molecule
|
|
541
|
+
Returns:
|
|
542
|
+
bool: Returns True if a conjugated system was corrected
|
|
543
|
+
"""
|
|
544
|
+
|
|
545
|
+
if not initial_bond_dirs:
|
|
546
|
+
return False
|
|
547
|
+
|
|
548
|
+
conjugated: List[Tuple[int, int]] = []
|
|
549
|
+
for b in outcome.GetBonds():
|
|
550
|
+
if b.GetIsConjugated():
|
|
551
|
+
conjugated.append(
|
|
552
|
+
(b.GetBeginAtom().GetAtomMapNum(), b.GetEndAtom().GetAtomMapNum())
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
if not conjugated:
|
|
556
|
+
return False
|
|
557
|
+
|
|
558
|
+
final_bond_dirs = bond_dirs_by_mapnum(outcome)
|
|
559
|
+
|
|
560
|
+
# connectivity of conjugated systems
|
|
561
|
+
# DFS may be better for large systems
|
|
562
|
+
isolated_conjugated: List[Set[int]] = []
|
|
563
|
+
for i, j in conjugated:
|
|
564
|
+
found = False
|
|
565
|
+
for c in isolated_conjugated:
|
|
566
|
+
if i in c or j in c:
|
|
567
|
+
found = True
|
|
568
|
+
c.add(i)
|
|
569
|
+
c.add(j)
|
|
570
|
+
break
|
|
571
|
+
if not found:
|
|
572
|
+
isolated_conjugated.append({i, j})
|
|
573
|
+
|
|
574
|
+
need_to_change_dirs = {}
|
|
575
|
+
inverted_dirs = []
|
|
576
|
+
new_dirs = []
|
|
577
|
+
for pair in final_bond_dirs:
|
|
578
|
+
if pair in initial_bond_dirs:
|
|
579
|
+
if final_bond_dirs[pair] != initial_bond_dirs[pair]:
|
|
580
|
+
need_to_change_dirs[pair] = initial_bond_dirs[pair]
|
|
581
|
+
inverted_dirs.append(pair)
|
|
582
|
+
else:
|
|
583
|
+
new_dirs.append(pair)
|
|
584
|
+
|
|
585
|
+
if not inverted_dirs:
|
|
586
|
+
return False
|
|
587
|
+
|
|
588
|
+
isolated_conjugated_need_fix = [False] * len(isolated_conjugated)
|
|
589
|
+
for i, conj in enumerate(isolated_conjugated):
|
|
590
|
+
for pair in inverted_dirs:
|
|
591
|
+
if pair[0] in conj or pair[1] in conj:
|
|
592
|
+
isolated_conjugated_need_fix[i] = True
|
|
593
|
+
break
|
|
594
|
+
|
|
595
|
+
for pair in new_dirs:
|
|
596
|
+
for need_fix, conj in zip(isolated_conjugated_need_fix, isolated_conjugated):
|
|
597
|
+
if need_fix and (pair[0] in conj or pair[1] in conj):
|
|
598
|
+
need_to_change_dirs[pair] = BondDirOpposite[final_bond_dirs[pair]]
|
|
599
|
+
break
|
|
600
|
+
|
|
601
|
+
changed = False
|
|
602
|
+
for b in outcome.GetBonds():
|
|
603
|
+
bam = b.GetBeginAtom().GetAtomMapNum()
|
|
604
|
+
bbm = b.GetEndAtom().GetAtomMapNum()
|
|
605
|
+
if (bam, bbm) in need_to_change_dirs:
|
|
606
|
+
b.SetBondDir(need_to_change_dirs[(bam, bbm)])
|
|
607
|
+
changed = True
|
|
608
|
+
|
|
609
|
+
return changed
|