Source code for bbprep._internal.generators.torsion_scanner
import itertools as it
import stk
import stko
from rdkit.Chem import AllChem, rdMolTransforms
from bbprep._internal.ensemble.ensemble import Conformer, Ensemble
from .generator import Generator
from .targets import TorsionRange
[docs]
class TorsionScanner(Generator):
"""Generate conformers by scanning target torsions."""
def __init__(
self,
target_torsions: TorsionRange | tuple[TorsionRange],
) -> None:
"""Initialise generator."""
if not isinstance(target_torsions, tuple):
self._target_torsions = (target_torsions,)
else:
self._target_torsions = target_torsions
[docs]
def generate_conformers(self, molecule: stk.BuildingBlock) -> Ensemble:
# Optimise the initial ligand structure.
molecule = stko.MMFF( # type:ignore[assignment]
ignore_inter_interactions=False
).optimize(
mol=molecule,
)
ensemble = Ensemble(base_molecule=molecule)
rdkit_molecule = molecule.to_rdkit_mol()
AllChem.SanitizeMol(rdkit_molecule) # type: ignore[attr-defined]
rdkit_properties = AllChem.MMFFGetMoleculeProperties( # type: ignore[attr-defined]
rdkit_molecule, mmffVariant="MMFF94s"
)
matched_torsions = {}
atoms_to_be_constrained = set()
for target in self._target_torsions:
matches = rdkit_molecule.GetSubstructMatches(
query=AllChem.MolFromSmarts(target.smarts), # type: ignore[attr-defined]
)
for match in matches:
if len(match) != target.expected_num_atoms:
msg = (
f"{len(match)} not as expected ("
f"{target.expected_num_atoms})"
)
raise RuntimeError(msg)
if not any(i in atoms_to_be_constrained for i in match):
for i in match:
atoms_to_be_constrained.add(i)
initial_torsion = rdMolTransforms.GetDihedralDeg(
rdkit_molecule.GetConformer(0),
match[target.scanned_ids[0]],
match[target.scanned_ids[1]],
match[target.scanned_ids[2]],
match[target.scanned_ids[3]],
)
key = tuple(match[i] for i in target.scanned_ids)
matched_torsions[key] = [
round(initial_torsion + angle, 2)
for angle in target.scanned_range
]
test_molecule = molecule.clone()
keys, values = zip(*matched_torsions.items(), strict=False)
permutations_dicts = [
dict(zip(keys, v, strict=False)) for v in it.product(*values)
]
for cid, permutation in enumerate(permutations_dicts):
rdkit_molecule = test_molecule.to_rdkit_mol()
AllChem.SanitizeMol(rdkit_molecule) # type: ignore[attr-defined]
rdkit_properties = AllChem.MMFFGetMoleculeProperties( # type: ignore[attr-defined]
rdkit_molecule
)
ff = AllChem.MMFFGetMoleculeForceField( # type: ignore[attr-defined]
rdkit_molecule,
rdkit_properties,
)
for torsion in permutation:
actual_angle = permutation[torsion]
ff.MMFFAddTorsionConstraint(
torsion[0],
torsion[1],
torsion[2],
torsion[3],
False, # noqa: FBT003
actual_angle - 0.1,
actual_angle + 0.1,
100.0,
)
ff.Minimize(maxIts=500)
pos_mat = rdkit_molecule.GetConformer(-1).GetPositions()
test_molecule = molecule.with_position_matrix(pos_mat)
ensemble.add_conformer(
conformer=Conformer(
molecule=test_molecule,
conformer_id=cid,
source="torsionscan",
permutation=permutation,
),
)
return ensemble