Skip to content

Commit 98f4484

Browse files
authored
Merge pull request #61 from Exscientia/feat__sync
Sync fix from OBS
2 parents ff2466d + f94a993 commit 98f4484

12 files changed

Lines changed: 185 additions & 17 deletions

File tree

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
`BioSimSpace <https://biosimspace.openbiosim.org>`__
22
====================================================
33

4-
.. image:: https://github.com/openbiosim/biosimspace/workflows/Build/badge.svg
5-
:target: https://github.com/openbiosim/biosimspace/actions?query=workflow%3ABuild)
4+
.. image:: https://github.com/openbiosim/biosimspace/actions/workflows/devel.yaml/badge.svg
5+
:target: https://github.com/openbiosim/biosimspace/actions?query=workflow%3ARelease-Devel
66
:alt: Build status
77

88
.. image:: https://anaconda.org/openbiosim/biosimspace/badges/downloads.svg

doc/source/tutorials/alchemical_transfer.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,9 @@ more choices to be made before the system can be prepared. The most important of
8888
these is the choice of displacement vector, which defines the direction and
8989
distance at which the free ligand will be placed relative to the bound ligand.
9090
It is generally recommended that this displacement be at least 3 layers of water
91-
molecules (> 10 Å) thick. If no displacement is provided a default choice of
92-
[20Å, 20Å, 20Å] will be used.
91+
molecules (> 10 Å) thick. If no displacement is provided BioSimSpace will find a
92+
best guess translation vector based on the relative positions of the protein and
93+
bound ligands and translate the free ligand 20 Å along this vector.
9394

9495
This is also the point at which a custom set of atoms can be chosen to define the
9596
centre of mass of both the ligands and the protein. In the majority of cases it
-12.6 KB
Loading

python/BioSimSpace/FreeEnergy/_relative.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,13 @@ def _analyse_amber(work_dir=None, estimator="MBAR", method="alchemlyb", **kwargs
14361436
if "lambda" in part:
14371437
lambdas.append(float(part.split("_")[-1]))
14381438

1439+
if len(lambdas) == 0:
1440+
raise ValueError(
1441+
"No lambda windows were detected from the output directory names! "
1442+
"Ensure that the directory being analysed contains sub-directories"
1443+
"name e.g `lambda_0`, `lambda_1` containing the output files."
1444+
)
1445+
14391446
# Find the temperature for each lambda window.
14401447
temperatures = []
14411448
for file, lambda_ in zip(files, lambdas):
@@ -1517,6 +1524,13 @@ def _analyse_gromacs(work_dir=None, estimator="MBAR", method="alchemlyb", **kwar
15171524
if "lambda" in part:
15181525
lambdas.append(float(part.split("_")[-1]))
15191526

1527+
if len(lambdas) == 0:
1528+
raise ValueError(
1529+
"No lambda windows were detected from the output directory names! "
1530+
"Ensure that the directory being analysed contains sub-directories"
1531+
"name e.g `lambda_0`, `lambda_1` containing the output files."
1532+
)
1533+
15201534
# Find the temperature at each lambda window
15211535
temperatures = []
15221536
for file in files:
@@ -1686,6 +1700,13 @@ def _analyse_somd(work_dir=None, estimator="MBAR", method="alchemlyb", **kwargs)
16861700
if "lambda" in part:
16871701
lambdas.append(float(part.split("_")[-1]))
16881702

1703+
if len(lambdas) == 0:
1704+
raise ValueError(
1705+
"No lambda windows were detected from the output directory names! "
1706+
"Ensure that the directory being analysed contains sub-directories"
1707+
"name e.g `lambda_0`, `lambda_1` containing the output files."
1708+
)
1709+
16891710
temperatures = []
16901711
for file in files:
16911712
found_temperature = False
@@ -1883,6 +1904,13 @@ def _analyse_somd2(work_dir=None, estimator="MBAR", method="alchemlyb", **kwargs
18831904
temperatures = [x for _, x in sorted(zip(lambdas, temperatures))]
18841905
lambdas = sorted(lambdas)
18851906

1907+
if len(lambdas) == 0:
1908+
raise ValueError(
1909+
"No lambda windows were detected from the output directory names! "
1910+
"Ensure that the directory being analysed contains sub-directories"
1911+
"name e.g `lambda_0`, `lambda_1` containing the output files."
1912+
)
1913+
18861914
# Check that the temperatures at the end states match.
18871915
if temperatures[0] != temperatures[-1]:
18881916
raise ValueError("The temperatures at the endstates don't match!")

python/BioSimSpace/Parameters/_Protocol/_amber.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,7 @@ def __init__(
841841
net_charge=None,
842842
ensure_compatible=True,
843843
property_map={},
844+
**kwargs,
844845
):
845846
"""
846847
Constructor.
@@ -873,6 +874,10 @@ def __init__(
873874
A dictionary that maps system "properties" to their user defined
874875
values. This allows the user to refer to properties with their
875876
own naming scheme, e.g. { "charge" : "my-charge" }
877+
878+
**kwargs: dict
879+
Additional keyword arguments. These can be used to pass custom
880+
parameters to the Antechamber program.
876881
"""
877882

878883
if type(version) is not int:
@@ -912,6 +917,11 @@ def __init__(
912917
"'net_charge' must be of type 'int', or `BioSimSpace.Types.Charge'"
913918
)
914919

920+
# Check the kwargs to see whether acdoctor is enabled.
921+
self._acdoctor = kwargs.get("acdoctor", True)
922+
if not isinstance(self._acdoctor, bool):
923+
raise TypeError("'acdoctor' must be of type 'bool'")
924+
915925
# Set the version.
916926
self._version = version
917927

@@ -1086,6 +1096,10 @@ def run(self, molecule, work_dir=None, queue=None):
10861096
charge,
10871097
)
10881098

1099+
# Disable acdoctor if requested.
1100+
if not self._acdoctor:
1101+
command += " -dr no"
1102+
10891103
with open(_os.path.join(str(work_dir), "README.txt"), "w") as file:
10901104
# Write the command to file.
10911105
file.write("# Antechamber was run with the following command:\n")

python/BioSimSpace/Parameters/_parameters.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ def gaff(
362362
charge_method=charge_method,
363363
ensure_compatible=ensure_compatible,
364364
property_map=property_map,
365+
**kwargs,
365366
)
366367

367368
# Run the parameterisation protocol in the background and return
@@ -460,6 +461,7 @@ def gaff2(
460461
charge_method=charge_method,
461462
ensure_compatible=ensure_compatible,
462463
property_map=property_map,
464+
**kwargs,
463465
)
464466

465467
# Run the parameterisation protocol in the background and return

python/BioSimSpace/Sandpit/Exscientia/FreeEnergy/_restraint_search.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,14 @@ def analyse(
590590
f"traj {type(traj)} must be of type 'BioSimSpace.Trajectory._trajectory.Trajectory'"
591591
)
592592

593+
n_frames = traj.nFrames()
594+
if not n_frames >= 50:
595+
_warnings.warn(
596+
f"The trajectory for restraint selection has less than 50 frames ({n_frames} frames). "
597+
"This may result in poor restraint selection with excessively high force constants. "
598+
"Consider running a longer simulation or saving more frames."
599+
)
600+
593601
if not isinstance(temperature, _Temperature):
594602
raise ValueError(
595603
f"temperature {type(temperature)} must be of type 'BioSimSpace.Types.Temperature'"
@@ -667,6 +675,18 @@ def analyse(
667675
decoupled_mol = _system.getDecoupledMolecules()[0]
668676
decoupled_resname = decoupled_mol.getResidues()[0].name()
669677

678+
# Warn the user if the decoupled molecule is water or a macromoelcule
679+
if decoupled_mol.isWater():
680+
_warnings.warn(
681+
"The decoupled molecule is water. Ensure that this is intended. Using constrained water hydrogens as anchor "
682+
"points may produce instabilities."
683+
)
684+
685+
if decoupled_mol.nResidues() > 1:
686+
_warnings.warn(
687+
"The decoupled molecule has more than one residue and is likely a macromolecule. Ensure that this is intended."
688+
)
689+
670690
ligand_selection_str = f"((resname {decoupled_resname}) and (not name H*))"
671691
if append_to_ligand_selection:
672692
ligand_selection_str += " and "
@@ -1012,6 +1032,20 @@ def _findOrderedPairs(u, ligand_selection_str, receptor_selection_str, cutoff):
10121032
"""
10131033

10141034
lig_selection = u.select_atoms(ligand_selection_str)
1035+
1036+
# Ensure that there are no shared atoms between the ligand selection and all possible receptor selections.
1037+
all_possible_receptor_selection = u.select_atoms(receptor_selection_str)
1038+
shared_atoms = [
1039+
atom for atom in lig_selection if atom in all_possible_receptor_selection
1040+
]
1041+
if shared_atoms:
1042+
raise _AnalysisError(
1043+
"Shared atoms between ligand and receptor selections detected. "
1044+
"Please ensure that you are decoupling the intended molecule and that "
1045+
"the ligand and receptor selections are mutually exclusive. "
1046+
f"Shared atoms: {shared_atoms}"
1047+
)
1048+
10151049
pair_variance_dict = {}
10161050

10171051
# Get all receptor atoms within specified distance of cutoff
@@ -1488,7 +1522,9 @@ def _findOrderedBoresch(
14881522
dtheta = abs(val - circmean)
14891523
corrected_values.append(min(dtheta, 2 * _np.pi - dtheta))
14901524
corrected_values = _np.array(corrected_values)
1491-
boresch_dof_data[pair][dof]["var"] = corrected_values.var()
1525+
boresch_dof_data[pair][dof]["var"] = _np.mean(
1526+
corrected_values**2
1527+
)
14921528

14931529
# Assume Gaussian distributions and calculate force constants for harmonic potentials
14941530
# so as to reproduce these distributions at 298 K

python/BioSimSpace/Sandpit/Exscientia/Parameters/_Protocol/_amber.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,7 @@ def __init__(
841841
net_charge=None,
842842
ensure_compatible=True,
843843
property_map={},
844+
**kwargs,
844845
):
845846
"""
846847
Constructor.
@@ -873,6 +874,10 @@ def __init__(
873874
A dictionary that maps system "properties" to their user defined
874875
values. This allows the user to refer to properties with their
875876
own naming scheme, e.g. { "charge" : "my-charge" }
877+
878+
**kwargs: dict
879+
Additional keyword arguments. These can be used to pass custom
880+
parameters to the Antechamber program.
876881
"""
877882

878883
if type(version) is not int:
@@ -912,6 +917,11 @@ def __init__(
912917
"'net_charge' must be of type 'int', or `BioSimSpace.Types.Charge'"
913918
)
914919

920+
# Check the kwargs to see whether acdoctor is enabled.
921+
self._acdoctor = kwargs.get("acdoctor", True)
922+
if not isinstance(self._acdoctor, bool):
923+
raise TypeError("'acdoctor' must be of type 'bool'")
924+
915925
# Set the version.
916926
self._version = version
917927

@@ -1086,6 +1096,10 @@ def run(self, molecule, work_dir=None, queue=None):
10861096
charge,
10871097
)
10881098

1099+
# Disable acdoctor if requested.
1100+
if not self._acdoctor:
1101+
command += " -dr no"
1102+
10891103
with open(_os.path.join(str(work_dir), "README.txt"), "w") as file:
10901104
# Write the command to file.
10911105
file.write("# Antechamber was run with the following command:\n")

python/BioSimSpace/Sandpit/Exscientia/Parameters/_parameters.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ def gaff(
362362
charge_method=charge_method,
363363
ensure_compatible=ensure_compatible,
364364
property_map=property_map,
365+
**kwargs,
365366
)
366367

367368
# Run the parameterisation protocol in the background and return
@@ -460,6 +461,7 @@ def gaff2(
460461
charge_method=charge_method,
461462
ensure_compatible=ensure_compatible,
462463
property_map=property_map,
464+
**kwargs,
463465
)
464466

465467
# Run the parameterisation protocol in the background and return

tests/Parameters/test_parameters.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,24 @@ def test_smiles_stereo():
167167

168168
# Make sure the SMILES strings are the same.
169169
assert rdmol0_smiles == rdmol1_smiles
170+
171+
172+
@pytest.mark.skipif(
173+
has_antechamber is False or has_tleap is False,
174+
reason="Requires AmberTools/antechamber and tLEaP to be installed.",
175+
)
176+
def test_acdoctor():
177+
"""
178+
Test that parameterising negatively charged molecules works when acdoctor
179+
is disabled.
180+
"""
181+
182+
# Load the molecule.
183+
mol = BSS.IO.readMolecules(f"{url}/negative_charge.sdf")[0]
184+
185+
# Make sure parameterisation fails when acdoctor is enabled.
186+
with pytest.raises(BSS._Exceptions.ParameterisationError):
187+
BSS.Parameters.gaff(mol).getMolecule()
188+
189+
# Make sure parameterisation works when acdoctor is disabled.
190+
mol = BSS.Parameters.gaff(mol, acdoctor=False).getMolecule()

0 commit comments

Comments
 (0)