Source code for openff.recharge.conformers._conformers
"""A module for generating conformers for molecules."""
import logging
from typing import TYPE_CHECKING, List, Literal, Optional
import numpy
from openff.units import unit, Quantity
from openff.recharge._pydantic import BaseModel, Field
from openff.recharge.conformers.exceptions import ConformerGenerationError
from openff.utilities.utilities import requires_oe_module
if TYPE_CHECKING:
from openff.toolkit import Molecule
_logger = logging.getLogger()
[docs]class ConformerSettings(BaseModel):
"""The settings to use when generating conformers for a
particular molecule.
"""
method: Literal["omega", "omega-elf10"] = Field(
"omega-elf10", description="The method to use to generate the conformers."
)
sampling_mode: Literal["sparse", "dense"] = Field(
"dense", description="The mode in which to generate the conformers."
)
max_conformers: Optional[int] = Field(
5, description="The maximum number of conformers to generate."
)
[docs]class ConformerGenerator:
"""A class to generate a set of conformers for a molecule according to
a specified set of settings.
"""
@classmethod
@requires_oe_module("oechem")
def _generate_omega_conformers(
cls,
molecule: "Molecule",
settings: ConformerSettings,
) -> List[Quantity]:
oe_molecule = molecule.to_openeye()
from openeye import oeomega, oequacpac
if settings.sampling_mode == "sparse":
omega_options = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_Sparse)
elif settings.sampling_mode == "dense":
omega_options = oeomega.OEOmegaOptions(oeomega.OEOmegaSampling_Dense)
else:
raise NotImplementedError()
omega = oeomega.OEOmega(omega_options)
omega.SetIncludeInput(False)
omega.SetCanonOrder(False)
if not omega(oe_molecule):
raise ConformerGenerationError("Failed to generate conformers using OMEGA")
if settings.method == "omega-elf10":
# Select a subset of the OMEGA generated conformers using the ELF10 method.
oe_elf_options = oequacpac.OEELFOptions()
oe_elf_options.SetElfLimit(10)
oe_elf_options.SetPercent(2.0)
oe_elf = oequacpac.OEELF(oe_elf_options)
if not oe_elf.Select(oe_molecule):
raise ConformerGenerationError("ELF10 conformer selection failed")
conformers = []
for oe_conformer in oe_molecule.GetConfs():
conformer = numpy.zeros((oe_molecule.NumAtoms(), 3))
for atom_index, coordinates in oe_conformer.GetCoords().items():
conformer[atom_index, :] = coordinates
conformers.append(conformer * unit.angstrom)
return conformers
[docs] @classmethod
def generate(
cls,
molecule: "Molecule",
settings: ConformerSettings,
) -> List[Quantity]:
"""Generates a set of conformers for a given molecule.
Notes
-----
* All operations are performed on a copy of the original molecule so that it
will not be mutated by this function.
Parameters
----------
molecule
The molecule to generate conformers for.
settings
The settings to generate the conformers according to.
"""
if "omega" in settings.method:
conformers = cls._generate_omega_conformers(molecule, settings)
else:
raise NotImplementedError()
if settings.max_conformers:
conformers = conformers[0 : min(settings.max_conformers, len(conformers))]
return conformers