Skip to content

Commit 8d57d27

Browse files
Milestone: Add feature to lock-in phenyl rings
1 parent 9194147 commit 8d57d27

File tree

5 files changed

+82
-26
lines changed

5 files changed

+82
-26
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PYTHON_BIN_DIR = /usr/local/python35/bin
22

33
test_tautomers:
4-
-@rm graphs/benzene* graphs/ethanal* graphs/propadiene*.pdf graphs/cyclooctyne*.pdf graphs/methylimidazole*.pdf graphs/porphyrin*.pdf
4+
-@rm graphs/*tautomer*.pdf
55
python3 test_tautomers.py
66
.PHONY: test_tautomers.py
77

helpers/molecule.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from fragment_capping.helpers.types_helpers import Fragment, ATB_Molid, Atom, FRAGMENT_CAPPING_DIR, Bond, ATOM_INDEX, MIN, MAX
1313
from fragment_capping.helpers.parameters import FULL_VALENCES, POSSIBLE_CHARGES, Capping_Strategy, possible_bond_order_for_atom_pair, coordinates_n_angstroms_away_from, possible_charge_for_atom, ALL_ELEMENTS, electronegativity_spread, ELECTRONEGATIVITIES, VALENCE_ELECTRONS, MIN_ABSOLUTE_CHARGE, MAX_ABSOLUTE_CHARGE, MIN_BOND_ORDER, MAX_BOND_ORDER, MUST_BE_INT, MAX_NONBONDED_ELECTRONS, NO_CAP, ELECTRONS_PER_BOND, ALL_CAPPING_OPTIONS
1414
from fragment_capping.helpers.babel import energy_minimised_pdb
15-
from fragment_capping.helpers.rings import bonds_for_ring
15+
from fragment_capping.helpers.rings import bonds_for_ring, atom_indices_in_phenyl_rings_for
1616
from fragment_capping.helpers.graphs import unique_molecules
1717
from fragment_capping.helpers.tautomers import get_all_tautomers, get_all_tautomers_naive
1818
from fragment_capping.helpers.capping import get_best_capped_molecule_with_ILP, get_best_capped_molecule
@@ -67,6 +67,7 @@ def __init__(
6767
self.previously_uncapped = set()
6868
self.aromatic_bonds, self.aromatic_rings = (None, None)
6969
self.non_bonded_electrons = None
70+
self.phenyl_atoms = set()
7071

7172
def atom_desc(self, atom: Atom):
7273
if self.use_neighbour_valences:
@@ -924,6 +925,12 @@ def renumber_atoms(self) -> None:
924925
for bond in self.aromatic_bonds
925926
}
926927

928+
if self.phenyl_atoms is not None:
929+
self.phenyl_atoms = {
930+
remap_atom(atom_index)
931+
for atom_index in self.phenyl_atoms
932+
}
933+
927934
return None
928935

929936
def remove_atoms_with_predicate(self, predicate: Callable[[Atom], bool]) -> None:
@@ -979,6 +986,9 @@ def remove_atoms_with_predicate(self, predicate: Callable[[Atom], bool]) -> None
979986
return self.renumber_atoms()
980987

981988
def remove_all_hydrogens(self, mark_all_uncapped: bool = False) -> None:
989+
# Store phenyl rings atoms before removing their hydrogens
990+
self.phenyl_atoms = atom_indices_in_phenyl_rings_for(self)
991+
982992
self.remove_atoms_with_predicate(
983993
lambda atom: atom.element == 'H',
984994
)

helpers/rings.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def bonds_for_ring(ring: List[ATOM_INDEX]) -> List[Bond]:
1111
for atom_indices in zip(ring, ring[1:] + (ring[0],))
1212
]
1313

14-
def atoms_in_small_rings_for(molecule: 'Molecule') -> Set[Atom]:
14+
def atoms_in_small_rings_for(molecule: 'Molecule') -> List[Atom]:
1515
return [
1616
molecule.atoms[atom_index]
1717
for atom_index in reduce(
@@ -27,3 +27,23 @@ def bonds_in_small_rings_for(molecule: 'Molecule') -> Set[Bond]:
2727
[bonds_for_ring(ring) for ring in molecule.rings() if 0 < len(ring) < SMALL_RING],
2828
set(),
2929
)
30+
31+
def atom_indices_in_phenyl_rings_for(molecule: 'Molecule') -> Set[ATOM_INDEX]:
32+
return reduce(
33+
lambda acc, e: acc | set(e),
34+
[
35+
ring
36+
for ring in molecule.rings()
37+
if (
38+
len(ring) == 6
39+
and
40+
all(
41+
len([bond for bond in molecule.bonds if atom_index in bond]) == 3
42+
and
43+
molecule.atoms[atom_index].element == 'C'
44+
for atom_index in ring
45+
)
46+
)
47+
],
48+
set(),
49+
)

helpers/tautomers.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,19 @@ def get_all_tautomers(
237237
max_tautomers: Optional[int] = 2000,
238238
disallow_triple_bond_in_small_rings: bool = True,
239239
disallow_allenes_in_small_rings: bool = True,
240-
use_gurobi: bool = True,
240+
lock_phenyl_rings: bool = True,
241+
use_gurobi: bool = False,
241242
debug: Optional[TextIO] = None,
242243
) -> 'Molecule':
244+
'''
245+
Args:
246+
``disallow_triple_bond_in_small_rings``: Disallow triple bonds in small rings (rings with size <= ``SMALL_RING``)
247+
``disallow_allenes_in_small_rings``: Disallow allenes (=C=) in small rings (rings with size <= ``SMALL_RING``)
248+
``lock_phenyl_rings``: Prevent phenyl rings (6 membered rings with all sp3 carbons) from being hydrogenised.
249+
250+
Returns:
251+
A list of tautomers (Molecule).
252+
'''
243253
if len([1 for atom in molecule.atoms.values() if atom.element == 'H']) > 0:
244254
molecule.remove_all_hydrogens(mark_all_uncapped=True)
245255

@@ -254,31 +264,18 @@ def keep_capping_strategy_for_atom(capping_strategy: Capping_Strategy, atom: Ato
254264
else:
255265
return min_valence_for(atom) <= neighbour_counts[atom.index] + new_atom_for_capping_strategy(capping_strategy) <= max_valence_for(atom)
256266

257-
def possible_capping_strategies_for_atom(atom: Atom) -> List[Capping_Strategy]:
258-
return [NO_CAP, H_CAP, H2_CAP, H3_CAP] + ([H4_CAP] if False else [])
259-
# if debug is not None:
260-
# write_to_debug(debug, '')
261-
# write_to_debug(debug, atom)
262-
# write_to_debug(debug, 'capping_strategy, new_atom_for_capping_strategy(), keep_capping_strategy_for_atom()')
263-
# for capping_strategy in ALL_CAPPING_OPTIONS[molecule.atom_desc(atom)]:
264-
# write_to_debug(
265-
# debug,
266-
# capping_strategy,
267-
# new_atom_for_capping_strategy(capping_strategy),
268-
# keep_capping_strategy_for_atom(capping_strategy, atom)
269-
# )
270-
# return [
271-
# capping_strategy
272-
# for capping_strategy in ALL_CAPPING_OPTIONS[molecule.atom_desc(atom)]
273-
# if keep_capping_strategy_for_atom(capping_strategy, atom)
274-
# ]
267+
def possible_capping_strategies_for_atom(atom: Atom, is_phenyl_atom: bool = False) -> List[Capping_Strategy]:
268+
if is_phenyl_atom and lock_phenyl_rings:
269+
return [NO_CAP, H_CAP]
270+
else:
271+
return [NO_CAP, H_CAP, H2_CAP, H3_CAP] + ([H4_CAP] if False else [])
275272

276273
atoms_need_capping = [atom for atom in molecule.sorted_atoms() if not atom.capped]
277274

278275
write_to_debug(
279276
debug,
280277
[
281-
possible_capping_strategies_for_atom(atom)
278+
possible_capping_strategies_for_atom(atom, is_phenyl_atom=(atom.index in molecule.phenyl_atoms))
282279
for atom in atoms_need_capping
283280
],
284281
)
@@ -291,7 +288,7 @@ def possible_capping_strategies_for_atom(atom: Atom) -> List[Capping_Strategy]:
291288
capping_atoms_for = {}
292289
new_bonds_sets = {}
293290
for uncapped_atom in atoms_need_capping:
294-
possible_capping_strategies = possible_capping_strategies_for_atom(uncapped_atom)
291+
possible_capping_strategies = possible_capping_strategies_for_atom(uncapped_atom, is_phenyl_atom=(uncapped_atom.index in molecule.phenyl_atoms))
295292
if len(possible_capping_strategies) == 0 or len(possible_capping_strategies) == 1 and possible_capping_strategies[0] == NO_CAP:
296293
pass
297294
else:

test_tautomers.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,35 @@
55
from fragment_capping.helpers.molecule import Molecule, molecule_from_pdb_str
66

77
class Test_Capping(unittest.TestCase):
8+
def test_warfarin(self, use_ILP: bool = True) -> None:
9+
with open('pdbs/warfarin.pdb') as fh:
10+
input_molecule = molecule_from_pdb_str(
11+
fh.read(),
12+
name='warfarin',
13+
)
14+
input_molecule.remove_all_hydrogens(mark_all_uncapped=True)
15+
input_molecule.write_graph(
16+
'input',
17+
output_size=(1200, 1200),
18+
graph_kwargs={'include_atom_index': False},
19+
)
20+
tautomers = input_molecule.get_all_tautomers(
21+
net_charge=0,
22+
total_number_hydrogens=16,
23+
enforce_octet_rule=True,
24+
allow_radicals=False,
25+
)
26+
27+
assert len(tautomers) == 52, tautomers
28+
29+
for (n, molecule) in enumerate(tautomers, start=1):
30+
molecule.name = input_molecule.name
31+
molecule.write_graph(
32+
'_tautomer_{0}'.format(n),
33+
output_size=(1200, 1200),
34+
graph_kwargs={'include_atom_index': False},
35+
)
36+
837
def test_porphyrin(self, use_ILP: bool = True) -> None:
938
with open('pdbs/tetraphenylporphyrin.pdb') as fh:
1039
input_molecule = molecule_from_pdb_str(
@@ -24,7 +53,7 @@ def test_porphyrin(self, use_ILP: bool = True) -> None:
2453
allow_radicals=False,
2554
)
2655

27-
assert len(tautomers) == 46, tautomers
56+
assert len(tautomers) == 21, tautomers
2857

2958
for (n, molecule) in enumerate(tautomers, start=1):
3059
molecule.name = input_molecule.name
@@ -178,4 +207,4 @@ def test_cyclooctyne(self):
178207

179208
if __name__ == '__main__':
180209
print('Note: This test is very long (~15 minutes)')
181-
unittest.main(warnings='ignore')
210+
unittest.main(warnings='ignore', verbosity=2)

0 commit comments

Comments
 (0)