Skip to content

Commit 7b31b3f

Browse files
Merge pull request PolusAI#229 from misterbrandonwalker/patch_diffdock_sanitize
add timeout for rdkit conformer generation
2 parents f65b543 + d39c0e0 commit 7b31b3f

File tree

1 file changed

+31
-3
lines changed

1 file changed

+31
-3
lines changed

examples/diffdock/sanitize_ligand.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
# mypy: disable-error-code="attr-defined"
12
# pylint: disable=E0401,E1101,I1101
23
"""Filter molecules with kekulization errors.
34
Handle molecules with sanitization errors by assign formal charge based on valence."""
45
# https://depth-first.com/articles/2020/02/10/a-comprehensive-treatment-of-aromaticity-in-the-smiles-language/
56
import argparse
67
from pathlib import Path
7-
from typing import Dict, Tuple
8+
from typing import Dict, Tuple, Optional
9+
from types import FrameType
10+
import signal # only unix supported
811

912
import rdkit
1013
from rdkit import Chem
@@ -16,6 +19,24 @@
1619
input_small_mol_ligand = Path(args.input_small_mol_ligand).name
1720

1821

22+
class TimeoutException(Exception):
23+
pass
24+
25+
26+
def timeout(seconds: int, error_message: str = 'Timeout') -> None:
27+
"""Time out for conformer generation, only works for unix. For windows have to ignore mypy error.
28+
29+
Args:
30+
seconds (int): The time in seconds to wait before timing out
31+
error_message (str, optional): The error message to display. Defaults to 'Timeout'.
32+
"""
33+
def handle_timeout(signum: int, frame: Optional[FrameType]) -> None:
34+
raise TimeoutException(error_message)
35+
36+
signal.signal(signal.SIGALRM, handle_timeout)
37+
signal.alarm(seconds)
38+
39+
1940
def adjust_formal_charges(molecule: Chem.SDMolSupplier) -> Chem.SDMolSupplier:
2041
"""Sometimes input structures do not have correct formal charges corresponding
2142
to bond order topology. So choose to trust bond orders assigned and generate formal
@@ -59,14 +80,19 @@ def adjust_formal_charges(molecule: Chem.SDMolSupplier) -> Chem.SDMolSupplier:
5980
atom.SetFormalCharge(chg)
6081
return molecule
6182

83+
# for some ligands can take up to minutes to generate conformer!!
84+
6285

63-
def generate_conformer(molecule: Chem.SDMolSupplier) -> None:
86+
def generate_conformer(molecule: Chem.SDMolSupplier, timeout_value: int = 5) -> None:
6487
""" Generate conformer for molecule. Sometimes rdkit embedding can fail,
6588
so use random coordinates if failed at first.
6689
6790
Args:
68-
molecule (Chem.SDMolSupplier): _description_
91+
molecule (Chem.SDMolSupplier): The rdkit molecule object
92+
timeout_value (float): The time in seconds to wait before timing out
6993
"""
94+
# Set up the signal handler for the timeout
95+
timeout(timeout_value)
7096
ps = AllChem.ETKDGv2()
7197
confid = AllChem.EmbedMolecule(molecule, ps)
7298
if confid == -1:
@@ -75,6 +101,8 @@ def generate_conformer(molecule: Chem.SDMolSupplier) -> None:
75101
# only want to catch error Bad Conformation Id, dont need to spend time optimizing
76102
AllChem.MMFFOptimizeMolecule(molecule, confId=0, maxIters=1)
77103

104+
signal.alarm(0)
105+
78106

79107
def is_valid_ligand(molecule: Chem.SDMolSupplier) -> Tuple[bool, bool, Chem.SDMolSupplier]:
80108
"""Check for sanitization errors, attempt to fix formal charges/valence consistency errors.

0 commit comments

Comments
 (0)