Source code for openff.evaluator.substances.components

"""
An API for defining and creating substances.
"""

from enum import Enum

from openff.evaluator.attributes import UNDEFINED, Attribute, AttributeClass


[docs]class Component(AttributeClass): """Defines a single component in a chemical system, as well as it's role within the system (if any). """
[docs] class Role(Enum): """An enum which describes the role of a component in the system, such as whether the component is a solvent, a solute, a receptor etc. These roles are mainly used by workflow to identify the correct species in a system, such as when doing docking or performing solvation free energy calculations. """ Solvent = "solv" Solute = "sol" Ligand = "lig" Receptor = "rec"
smiles = Attribute( docstring="The SMILES pattern which describes this component.", type_hint=str, read_only=True, ) role = Attribute( docstring="The role of this component in the system.", type_hint=Role, default_value=Role.Solvent, read_only=True, ) @property def identifier(self): """str: A unique identifier for this component.""" return f"{self.smiles}{{{self.role.value}}}"
[docs] def __init__(self, smiles=UNDEFINED, role=Role.Solvent): """Constructs a new Component object with either a label or a smiles string, but not both. Notes ----- The `label` and `smiles` arguments are mutually exclusive, and only one can be passed while the other should be `None`. Parameters ---------- smiles: str A SMILES descriptor of the component role: Component.Role The role of this component in the system. """ if smiles != UNDEFINED: smiles = self._standardize_smiles(smiles) self._set_value("smiles", smiles) self._set_value("role", role)
@staticmethod def _standardize_smiles(smiles): """Standardizes a SMILES pattern to be canonical (but not necessarily isomeric) using the OpenFF Toolkit. Parameters ---------- smiles: str The SMILES pattern to standardize. Returns ------- The standardized SMILES pattern. """ from openff.toolkit.topology import Molecule from openff.toolkit.utils.rdkit_wrapper import RDKitToolkitWrapper from openff.toolkit.utils.toolkit_registry import ToolkitRegistry # This parsing was previously done with `cmiles.utils.load_molecule`, which # * did NOT enforce stereochemistry while parsing SMILES and # * implicitly used the same toolkit to write the SMILES back from an object # This is hard-coded to keep test results consistent across OpenEye status # and compared to older versions; if desired this could be relaxed rdkit_registry = ToolkitRegistry(toolkit_precedence=[RDKitToolkitWrapper()]) molecule = Molecule.from_smiles( smiles, toolkit_registry=rdkit_registry, allow_undefined_stereo=True, ) try: # Try to make the smiles isomeric. smiles = molecule.to_smiles( isomeric=True, explicit_hydrogens=False, mapped=False, toolkit_registry=rdkit_registry, ) except ValueError: # Fall-back to non-isomeric. smiles = molecule.to_smiles( isomeric=False, explicit_hydrogens=False, mapped=False, toolkit_registry=rdkit_registry, ) return smiles def __str__(self): return self.identifier def __repr__(self): return f"<{self.__class__.__name__} {str(self)}>" def __hash__(self): return hash(self.identifier) def __eq__(self, other): return type(self) is type(other) and self.identifier == other.identifier def __ne__(self, other): return not (self == other) def __setstate__(self, state): # Make sure the smiles pattern is standardized. state["smiles"] = Component._standardize_smiles(state["smiles"]) super(Component, self).__setstate__(state)