Skip to content

Commit 138ff3a

Browse files
authored
Merge pull request #404 from OpenBioSim/backport_403
Backport fixes from PRs #400 and #403
2 parents a846b70 + f26feb1 commit 138ff3a

10 files changed

Lines changed: 195 additions & 133 deletions

File tree

python/BioSimSpace/Parameters/_parameters.py

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -534,57 +534,6 @@ def _parameterise_openff(
534534
"must be in your PATH."
535535
) from None
536536

537-
# Check the Antechamber version. Open Force Field requires Antechamber >= 22.0.
538-
try:
539-
# Antechamber returns an exit code of 1 when requesting version information.
540-
# As such, we wrap the call within a try-except block in case it fails.
541-
542-
import shlex as _shlex
543-
import subprocess as _subprocess
544-
545-
# Generate the command-line string. (Antechamber must be in the PATH,
546-
# so no need to use AMBERHOME.
547-
command = "antechamber -v"
548-
549-
# Run the command as a subprocess.
550-
proc = _subprocess.run(
551-
_Utils.command_split(command),
552-
shell=False,
553-
text=True,
554-
stdout=_subprocess.PIPE,
555-
stderr=_subprocess.STDOUT,
556-
)
557-
558-
# Get stdout and split into lines.
559-
lines = proc.stdout.split("\n")
560-
561-
# If present, version information is on line 1.
562-
string = lines[1]
563-
564-
# Delete the welcome message.
565-
string = string.replace("Welcome to antechamber", "")
566-
567-
# Extract the version and convert to float.
568-
version = float(string.split(":")[0])
569-
570-
# The version is okay, enable Open Force Field support.
571-
if version >= 22:
572-
is_compatible = True
573-
# Disable Open Force Field support.
574-
else:
575-
is_compatible = False
576-
577-
del _shlex
578-
del _subprocess
579-
580-
# Something went wrong, disable Open Force Field support.
581-
except:
582-
is_compatible = False
583-
raise
584-
585-
if not is_compatible:
586-
raise _IncompatibleError(f"'{forcefield}' requires Antechamber >= 22.0")
587-
588537
# Validate arguments.
589538

590539
if not isinstance(molecule, (_Molecule, str)):

python/BioSimSpace/Process/_amber.py

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -732,31 +732,58 @@ def getFrame(self, index):
732732
# Create a copy of the existing system object.
733733
old_system = self._system.copy()
734734

735-
# Update the coordinates and velocities and return a mapping between
736-
# the molecule indices in the two systems.
737-
sire_system, mapping = _SireIO.updateCoordinatesAndVelocities(
738-
old_system._sire_object,
739-
new_system._sire_object,
740-
self._mapping,
741-
is_lambda1,
742-
self._property_map,
743-
self._property_map,
744-
)
735+
if isinstance(self._protocol, _Protocol._FreeEnergyMixin):
736+
# Udpate the coordinates and velocities and return a mapping between
737+
# the molecule indices in the two systems.
738+
mapping = {
739+
_SireMol.MolIdx(x): _SireMol.MolIdx(x)
740+
for x in range(0, self._squashed_system.nMolecules())
741+
}
742+
(
743+
self._squashed_system._sire_object,
744+
_,
745+
) = _SireIO.updateCoordinatesAndVelocities(
746+
self._squashed_system._sire_object,
747+
new_system._sire_object,
748+
mapping,
749+
is_lambda1,
750+
self._property_map,
751+
self._property_map,
752+
)
753+
754+
# Update the unsquashed system based on the updated squashed system.
755+
old_system = _unsquash(
756+
old_system,
757+
self._squashed_system,
758+
self._mapping,
759+
explicit_dummies=self._explicit_dummies,
760+
)
761+
762+
else:
763+
# Update the coordinates and velocities and return a mapping between
764+
# the molecule indices in the two systems.
765+
sire_system, mapping = _SireIO.updateCoordinatesAndVelocities(
766+
old_system._sire_object,
767+
new_system._sire_object,
768+
self._mapping,
769+
is_lambda1,
770+
self._property_map,
771+
self._property_map,
772+
)
745773

746-
# Update the underlying Sire object.
747-
old_system._sire_object = sire_system
774+
# Update the underlying Sire object.
775+
old_system._sire_object = sire_system
748776

749-
# Store the mapping between the MolIdx in both systems so we don't
750-
# need to recompute it next time.
751-
self._mapping = mapping
777+
# Store the mapping between the MolIdx in both systems so we don't
778+
# need to recompute it next time.
779+
self._mapping = mapping
752780

753781
# Update the box information in the original system.
754-
if self._has_box:
755-
if "space" in new_system._sire_object.propertyKeys():
756-
box = new_system._sire_object.property("space")
757-
old_system._sire_object.setProperty(
758-
self._property_map.get("space", "space"), box
759-
)
782+
if "space" in new_system._sire_object.propertyKeys():
783+
box = new_system._sire_object.property("space")
784+
old_system._sire_object.setProperty(
785+
self._property_map.get("space", "space"), box
786+
)
760787

761788
return old_system
762789

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

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -534,57 +534,6 @@ def _parameterise_openff(
534534
"must be in your PATH."
535535
) from None
536536

537-
# Check the Antechamber version. Open Force Field requires Antechamber >= 22.0.
538-
try:
539-
# Antechamber returns an exit code of 1 when requesting version information.
540-
# As such, we wrap the call within a try-except block in case it fails.
541-
542-
import shlex as _shlex
543-
import subprocess as _subprocess
544-
545-
# Generate the command-line string. (Antechamber must be in the PATH,
546-
# so no need to use AMBERHOME.
547-
command = "antechamber -v"
548-
549-
# Run the command as a subprocess.
550-
proc = _subprocess.run(
551-
_Utils.command_split(command),
552-
shell=False,
553-
text=True,
554-
stdout=_subprocess.PIPE,
555-
stderr=_subprocess.STDOUT,
556-
)
557-
558-
# Get stdout and split into lines.
559-
lines = proc.stdout.split("\n")
560-
561-
# If present, version information is on line 1.
562-
string = lines[1]
563-
564-
# Delete the welcome message.
565-
string = string.replace("Welcome to antechamber", "")
566-
567-
# Extract the version and convert to float.
568-
version = float(string.split(":")[0])
569-
570-
# The version is okay, enable Open Force Field support.
571-
if version >= 22:
572-
is_compatible = True
573-
# Disable Open Force Field support.
574-
else:
575-
is_compatible = False
576-
577-
del _shlex
578-
del _subprocess
579-
580-
# Something went wrong, disable Open Force Field support.
581-
except:
582-
is_compatible = False
583-
raise
584-
585-
if not is_compatible:
586-
raise _IncompatibleError(f"'{forcefield}' requires Antechamber >= 22.0")
587-
588537
# Validate arguments.
589538

590539
if not isinstance(molecule, (_Molecule, str)):

python/BioSimSpace/Sandpit/Exscientia/Process/_gromacs.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1930,6 +1930,29 @@ def getCurrentPressure(self, time_series=False):
19301930
"""
19311931
return self.getPressure(time_series, block=False)
19321932

1933+
def getDensity(self, time_series=False, block="AUTO"):
1934+
"""
1935+
Get the Density.
1936+
1937+
Parameters
1938+
----------
1939+
1940+
time_series : bool
1941+
Whether to return a list of time series records.
1942+
1943+
block : bool
1944+
Whether to block until the process has finished running.
1945+
1946+
Returns
1947+
-------
1948+
1949+
density : :class:`GeneralUnit <BioSimSpace.Units.Mass.kilogram / BioSimSpace.Units.Volume.meter3>`
1950+
The Density.
1951+
"""
1952+
return self.getRecord(
1953+
"DENSITY", time_series, _Units.Mass.kilogram / _Units.Volume.meter3, block
1954+
)
1955+
19331956
def getPressureDC(self, time_series=False, block="AUTO"):
19341957
"""
19351958
Get the DC pressure.
@@ -2489,7 +2512,7 @@ def _parse_energy_units(text):
24892512
elif unit == "nm/ps":
24902513
units.append(_Units.Length.nanometer / _Units.Time.picosecond)
24912514
elif unit == "kg/m^3":
2492-
units.append(_Types._GeneralUnit("kg/m3"))
2515+
units.append(_Units.Mass.kilogram / _Units.Volume.meter3)
24932516
else:
24942517
units.append(1.0)
24952518
_warnings.warn(
@@ -2935,6 +2958,11 @@ def _saveMetric(
29352958
_Units.Temperature.kelvin,
29362959
"getTemperature",
29372960
),
2961+
(
2962+
"Density (g/cm^3)",
2963+
_Units.Mass.gram / _Units.Volume.centimeter3,
2964+
"getDensity",
2965+
),
29382966
]
29392967
)
29402968
df = self._convert_datadict_keys(datadict_keys)

python/BioSimSpace/Sandpit/Exscientia/Process/_process.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import traceback
3333

3434
import pandas as pd
35+
from loguru import logger
3536

3637
from .._Utils import _try_import
3738

@@ -53,6 +54,7 @@
5354
from ..Protocol._protocol import Protocol as _Protocol
5455
from .._SireWrappers import System as _System
5556
from ..Types._type import Type as _Type
57+
from ..Types import Time as _Time
5658
from .. import Units as _Units
5759
from .. import _Utils
5860
from ..FreeEnergy._restraint import Restraint as _Restraint
@@ -898,7 +900,7 @@ def setSeed(self, seed):
898900
else:
899901
self._seed = seed
900902

901-
def wait(self, max_time=None):
903+
def wait(self, max_time=None, inactivity_timeout: None | _Time = None):
902904
"""
903905
Wait for the process to finish.
904906
@@ -939,11 +941,52 @@ def wait(self, max_time=None):
939941
self._process.wait(max_time)
940942

941943
else:
942-
# Wait for the process to finish.
943-
self._process.wait()
944+
if inactivity_timeout is None:
945+
# Wait for the process to finish.
946+
self._process.wait()
944947

945-
# Store the final run time.
946-
self.runTime()
948+
# Store the final run time.
949+
self.runTime()
950+
else:
951+
inactivity_timeout = int(inactivity_timeout.milliseconds().value())
952+
last_time = self._getLastTime()
953+
if last_time is None:
954+
# Wait for the process to finish.
955+
self._process.wait()
956+
957+
# Store the final run time.
958+
self.runTime()
959+
else:
960+
while self.isRunning():
961+
self._process.wait(inactivity_timeout)
962+
if self.isRunning():
963+
current_time = self._getLastTime()
964+
if current_time > last_time:
965+
logger.info(
966+
f"Current simulation time ({current_time})."
967+
)
968+
last_time = current_time
969+
else:
970+
logger.warning(
971+
f"Current simulation time ({current_time}) has not advanced compared "
972+
f"to the last time ({last_time}). The process "
973+
f"might have hung and will be killed."
974+
)
975+
with open(
976+
f"{self.workDir()}/{self._name}.out", "a+"
977+
) as f:
978+
f.write("Process Hung. Killed.")
979+
self.kill()
980+
981+
def _getLastTime(self) -> float | None:
982+
"""This is the base method in the Process base class.
983+
Each subclass, such as AMBER or GROMACS, is expected to override this method
984+
to provide their own implementation for returning the current time.
985+
986+
If this method is not overridden, it will return None,
987+
and the `inactivity_timeout` feature will be skipped.
988+
"""
989+
return None
947990

948991
def isQueued(self):
949992
"""

python/BioSimSpace/Sandpit/Exscientia/Units/Volume/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@
2424
__author__ = "Lester Hedges"
2525
__email__ = "lester.hedges@gmail.com"
2626

27-
__all__ = ["meter3", "nanometer3", "angstrom3", "picometer3"]
27+
__all__ = ["meter3", "nanometer3", "angstrom3", "picometer3", "centimeter3"]
2828

2929
from ...Types import Volume as _Volume
3030

3131
meter3 = _Volume(1, "meter3")
3232
nanometer3 = _Volume(1, "nanometer3")
3333
angstrom3 = _Volume(1, "angstrom3")
3434
picometer3 = _Volume(1, "picometer3")
35+
centimeter3 = _Volume(1e-6, "meter3")

tests/Parameters/test_parameters.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,11 @@ def test_smiles_stereo():
169169
assert rdmol0_smiles == rdmol1_smiles
170170

171171

172+
# This test is currently skipped since it fails with AnteChamber verssion
173+
# 24.0 and above and there is no way to query the version number from
174+
# the command-line. (The version output has been removed.)
172175
@pytest.mark.skipif(
173-
has_antechamber is False or has_tleap is False,
176+
True or has_antechamber is False or has_tleap is False,
174177
reason="Requires AmberTools/antechamber and tLEaP to be installed.",
175178
)
176179
def test_acdoctor():

tests/Sandpit/Exscientia/Parameters/test_parameters.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,11 @@ def test_smiles_stereo():
174174
assert rdmol0_smiles == rdmol1_smiles
175175

176176

177+
# This test is currently skipped since it fails with AnteChamber verssion
178+
# 24.0 and above and there is no way to query the version number from
179+
# the command-line. (The version output has been removed.)
177180
@pytest.mark.skipif(
178-
has_antechamber is False or has_tleap is False,
181+
True or has_antechamber is False or has_tleap is False,
179182
reason="Requires AmberTools/antechamber and tLEaP to be installed.",
180183
)
181184
def test_acdoctor():

tests/Sandpit/Exscientia/Process/test_gromacs.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
from BioSimSpace.Sandpit.Exscientia.Units.Angle import radian
1313
from BioSimSpace.Sandpit.Exscientia.Units.Energy import kcal_per_mol, kj_per_mol
1414
from BioSimSpace.Sandpit.Exscientia.Units.Length import angstrom
15+
from BioSimSpace.Sandpit.Exscientia.Units.Mass import gram
1516
from BioSimSpace.Sandpit.Exscientia.Units.Pressure import bar
1617
from BioSimSpace.Sandpit.Exscientia.Units.Temperature import kelvin
1718
from BioSimSpace.Sandpit.Exscientia.Units.Time import picosecond
18-
from BioSimSpace.Sandpit.Exscientia.Units.Volume import nanometer3
19+
from BioSimSpace.Sandpit.Exscientia.Units.Volume import centimeter3, nanometer3
1920
from tests.Sandpit.Exscientia.conftest import (
2021
has_alchemtest,
2122
has_amber,
@@ -359,6 +360,8 @@ def setup(perturbable_system):
359360
("getPressureDC", False, -215.590363, bar),
360361
("getVolume", True, 44.679958, nanometer3),
361362
("getVolume", False, 44.523510, nanometer3),
363+
("getDensity", False, 1.027221558, gram / centimeter3),
364+
("getDensity", True, 1.023624695, gram / centimeter3),
362365
],
363366
)
364367
def test_get(self, setup, func, time_series, value, unit):
@@ -378,6 +381,7 @@ def test_metric_parquet(self, setup):
378381
assert np.isclose(df["Volume (nm^3)"][0.0], 44.679958)
379382
assert np.isclose(df["Pressure (bar)"][0.0], 119.490417)
380383
assert np.isclose(df["Temperature (kelvin)"][0.0], 306.766907)
384+
assert np.isclose(df["Density (g/cm^3)"][0.0], 1.023624695)
381385

382386
def test_dhdl_parquet_exist(self, setup):
383387
assert Path(f"{setup.workDir()}/dHdl.parquet").exists()

0 commit comments

Comments
 (0)