diff --git a/docs/releasehistory.rst b/docs/releasehistory.rst index 3d1ff8f8..8ece06e8 100644 --- a/docs/releasehistory.rst +++ b/docs/releasehistory.rst @@ -17,12 +17,14 @@ New features - `PR #88 `_: New method to retrieve atom degrees with RDKit. - `PR #86 `_: New method to check the input PDB prior building the molecule. - `PR #94 `_: New method for the OPLS OBC parameters. +- `PR #92 `_: New parameter to skip the stereochemistry assignment (and the checking from the OpenFF toolkit). Tests added """"""""""" - `PR #88 `_: Adds tests to validate the atom degrees getter. - `PR #86 `_: Adds tests to validate the PDB check up. - `PR #94 `_: Adds tests to validate the OPLS OBC parameters generator. +- `PR #92 `_: New test to check the behaviour of the allow_undefined_stereo parameter. 1.0.0 - Full compatibility for OPLS2005 and OpenFF dihedrals and rotamer library improvements diff --git a/peleffy/forcefield/parameters.py b/peleffy/forcefield/parameters.py index dfa52d8e..be5de05f 100644 --- a/peleffy/forcefield/parameters.py +++ b/peleffy/forcefield/parameters.py @@ -184,6 +184,27 @@ def name(self): """ return self._name + @staticmethod + def from_impact_template(molecule, impact_template_path): + """ + It returns a parameter wrapper out of an impact template. + + Parameters + ---------- + molecule : a peleffy.topology.Molecule + The peleffy's Molecule object + impact_template_path : str + The path to the impact template from where the parameters + will be fetched + + Returns + ------- + parameters : a BaseParameterWrapper object + The resulting parameters wrapper + """ + + raise NotImplementedError() + class OpenForceFieldParameterWrapper(BaseParameterWrapper): """ diff --git a/peleffy/tests/test_molecule.py b/peleffy/tests/test_molecule.py index c380929b..9c30576c 100644 --- a/peleffy/tests/test_molecule.py +++ b/peleffy/tests/test_molecule.py @@ -287,3 +287,27 @@ def test_PDB_checkup(self): assert output == "Input PDB has no information about the " \ + "connectivity and this could result in an unexpected " \ + "bond assignment\n" + + def test_undefined_stereo(self): + """ + It checks the behaviour when ignoring the stereochemistry + in the Molecule initialization. + """ + from openforcefield.utils.toolkits import UndefinedStereochemistryError + + # This should crash due to an undefined stereochemistry error + with pytest.raises(UndefinedStereochemistryError): + mol = Molecule(smiles='CN(C)CCC=C1c2ccccc2CCc3c1cccc3') + + # This now should work + mol = Molecule(smiles='CN(C)CCC=C1c2ccccc2CCc3c1cccc3', + allow_undefined_stereo=True) + + # And we can parameterize it + mol.parameterize('openff_unconstrained-1.2.1.offxml', + charge_method='gasteiger') + + # Save it + with tempfile.TemporaryDirectory() as tmpdir: + with temporary_cd(tmpdir): + mol.to_pdb_file('molecule.pdb') diff --git a/peleffy/topology/molecule.py b/peleffy/topology/molecule.py index afdb9612..bc101be9 100644 --- a/peleffy/topology/molecule.py +++ b/peleffy/topology/molecule.py @@ -448,7 +448,8 @@ class Molecule(object): def __init__(self, path=None, smiles=None, rotamer_resolution=30, exclude_terminal_rotamers=True, name='', tag='UNK', - connectivity_template=None, core_constraints=[]): + connectivity_template=None, core_constraints=[], + allow_undefined_stereo=False): """ It initializes a Molecule object through a PDB file or a SMILES tag. @@ -477,6 +478,10 @@ def __init__(self, path=None, smiles=None, rotamer_resolution=30, the core will be forced to contain them. Atoms can be specified through integers that match the atom index or strings that match with the atom PDB name + allow_undefined_stereo : bool + Whether to allow a molecule with undefined stereochemistry + to be defined or try to assign the stereochemistry and + raise a complaint if not possible. Default is False Examples -------- @@ -535,6 +540,7 @@ def __init__(self, path=None, smiles=None, rotamer_resolution=30, self._exclude_terminal_rotamers = exclude_terminal_rotamers self._connectivity_template = connectivity_template self._core_constraints = core_constraints + self._allow_undefined_stereo = allow_undefined_stereo if isinstance(path, str): from pathlib import Path @@ -636,9 +642,10 @@ def _initialize_from_pdb(self, path): logger.info(' - Assigning connectivity from template') rdkit_toolkit.assign_connectivity_from_template(self) - # RDKit must generate stereochemistry specifically from 3D coords - logger.info(' - Assigning stereochemistry from 3D coordinates') - rdkit_toolkit.assign_stereochemistry_from_3D(self) + if not self.allow_undefined_stereo: + # RDKit must generate stereochemistry specifically from 3D coords + logger.info(' - Assigning stereochemistry from 3D coordinates') + rdkit_toolkit.assign_stereochemistry_from_3D(self) # Set molecule name according to PDB name if self.name == '': @@ -1202,6 +1209,21 @@ def connectivity_template(self): """ return self._connectivity_template + @property + def allow_undefined_stereo(self): + """ + Whether to allow a molecule with undefined stereochemistry + to be defined or try to assign the stereochemistry and + raise a complaint if not possible + + Returns + ------- + allow_undefined_stereo : bool + The current configuration towards the stereochemistry + behaviour of this molecule + """ + return self._allow_undefined_stereo + @property def core_constraints(self): """ diff --git a/peleffy/utils/output.py b/peleffy/utils/output.py index cf0287bc..0a4d0cca 100644 --- a/peleffy/utils/output.py +++ b/peleffy/utils/output.py @@ -116,6 +116,12 @@ def get_impact_template_path(self, create_missing_folders=True): else: path = os.path.join(self.output_path, self.OPLS_IMPACT_TEMPLATE_PATH) + else: + raise Exception('DataLocal output path for the impact ' + + 'template cannot be inferred because ' + + 'the forcefield for molecule ' + + '\'{}\' '.format(self.molecule) + + 'has not been set yet') else: path = self.output_path diff --git a/peleffy/utils/toolkits.py b/peleffy/utils/toolkits.py index b8aa791a..f336aca7 100644 --- a/peleffy/utils/toolkits.py +++ b/peleffy/utils/toolkits.py @@ -649,7 +649,9 @@ def from_rdkit(self, molecule): from openforcefield.topology.molecule import Molecule rdkit_molecule = molecule.rdkit_molecule - return Molecule.from_rdkit(rdkit_molecule) + return Molecule.from_rdkit( + rdkit_molecule, + allow_undefined_stereo=molecule.allow_undefined_stereo) def get_forcefield(self, forcefield_name): """