Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f56b6bc
Add support for ff19SB.
lohedges Mar 14, 2025
b1cb6ba
Fix for issue #397, also adds utility function to get name of node di…
Mar 21, 2025
d32e415
Allow pert file without modifications to dummy bonded terms.
lohedges May 27, 2025
e2dcacf
Merge branch 'devel' into feature_no_ghost_mods
lohedges May 28, 2025
41534e6
Fix for is_lambda1 in Somd1 processes
May 30, 2025
db03a53
Import proper errors in somd config
Jun 2, 2025
5d748ce
Typo. [ci skip]
lohedges Jun 2, 2025
44eeb7c
Amber config typo fix [ci skip]
Jun 3, 2025
580c91b
Fix for AMBER runs on perturbable systems with restraints
Jun 3, 2025
8fe807a
Fix for previous commit - removes old file save [ci skip]
Jun 3, 2025
754d7f0
Return full data frame, not just lambda array.
lohedges Jun 13, 2025
93fc038
Parquet lambda_array metadata is no longer needed.
lohedges Jun 13, 2025
5ce173b
Merge branch 'devel' into feature_ff19sb
lohedges Jun 20, 2025
e4abd56
Merge branch 'devel' into feature_no_ghost_mods
lohedges Jun 20, 2025
2e5f4fa
Test that we can parameterise with ff19SB, which includes CMAP terms.
lohedges Jun 20, 2025
71145d4
Simplify finite difference calculation. [ci skip]
lohedges Jun 21, 2025
e657eab
Add support for OPC water model. [ci skip]
lohedges Jun 25, 2025
59b512f
Don't duplicate the defaults directive. [ci skip]
lohedges Jun 25, 2025
10187b3
Allow user to disable data preprocessing.
lohedges Jun 26, 2025
98b26d3
Distinguish OPC and TIP4P water. [ci skip]
lohedges Jun 26, 2025
e6af7ab
Merge remote-tracking branch 'origin/fix_397' into feature_ff19sb
lohedges Jun 27, 2025
9ad668c
Allow node to be run in a specified working directory.
lohedges Jun 27, 2025
eb9ff4a
Merge branch 'feature_no_ghost_mods' into feature_ff19sb
lohedges Jun 30, 2025
88b02bc
Merge branch 'feature_focused_sampling' into feature_ff19sb
lohedges Jun 30, 2025
5b0c565
Handle different boolean property types.
lohedges Jun 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions python/BioSimSpace/FreeEnergy/_relative.py
Original file line number Diff line number Diff line change
Expand Up @@ -1064,15 +1064,16 @@ def _somd2_extract(parquet_file, T=None, estimator="MBAR"):
lam = float(metadata["lambda"])
except:
raise ValueError("Parquet metadata does not contain 'lambda'.")
try:
lambda_array = metadata["lambda_array"]
except:
raise ValueError("Parquet metadata does not contain 'lambda array'")
if not is_mbar:
try:
lambda_grad = metadata["lambda_grad"]
except:
raise ValueError("Parquet metadata does not contain 'lambda grad'")
else:
try:
lambda_grad = metadata["lambda_grad"]
except:
lambda_grad = []

# Make sure that the temperature is correct.
if not T == temperature:
Expand All @@ -1085,8 +1086,8 @@ def _somd2_extract(parquet_file, T=None, estimator="MBAR"):
df = table.to_pandas()

if is_mbar:
# Extract the columns correspodning to the lambda array.
df = df[[x for x in lambda_array]]
# Extract all columns other than those used for the gradient.
df = df[[x for x in df.columns if x not in lambda_grad]]

# Subtract the potential at the simulated lambda.
df = df.subtract(df[lam], axis=0)
Expand All @@ -1103,13 +1104,13 @@ def _somd2_extract(parquet_file, T=None, estimator="MBAR"):

# Forward difference.
if lam_delta > lam:
double_incr = (lam_delta - lam) * 2
grad = (df[lam_delta] - df[lam]) * 2 / double_incr
incr = lam_delta - lam
grad = (df[lam_delta] - df[lam]) / incr

# Backward difference.
else:
double_incr = (lam - lam_delta) * 2
grad = (df[lam] - df[lam_delta]) * 2 / double_incr
incr = lam - lam_delta
grad = (df[lam] - df[lam_delta]) / incr

# Central difference.
else:
Expand Down Expand Up @@ -1311,10 +1312,21 @@ def _analyse_internal(files, temperatures, lambdas, engine, estimator, **kwargs)

# Preprocess the data.
try:
processed_data = Relative._preprocess_data(data, estimator, **kwargs)
processed_data = _alchemlyb.concat(processed_data)
except:
_warnings.warn("Could not preprocess the data!")
preprocess = kwargs.pop("preprocess", True)
except KeyError:
preprocess = True

if not isinstance(preprocess, bool):
raise TypeError("'preprocess' must be of type 'bool'.")

if preprocess:
try:
processed_data = Relative._preprocess_data(data, estimator, **kwargs)
processed_data = _alchemlyb.concat(processed_data)
except:
_warnings.warn("Could not preprocess the data!")
processed_data = _alchemlyb.concat(data)
else:
processed_data = _alchemlyb.concat(data)

mbar_method = None
Expand Down
91 changes: 61 additions & 30 deletions python/BioSimSpace/Node/_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
# Set the default node directory.
_node_dir = _os.path.dirname(__file__) + "/_nodes"

__all__ = ["list", "help", "run", "setNodeDirectory"]
__all__ = ["list", "help", "run", "setNodeDirectory", "getNodeDirectory"]


def list():
Expand Down Expand Up @@ -90,7 +90,7 @@ def help(name):
print(proc.stdout)


def run(name, args={}):
def run(name, args={}, work_dir=None):
"""
Run a node.

Expand All @@ -103,6 +103,10 @@ def run(name, args={}):
args : dict
A dictionary of arguments to be passed to the node.

work_dir : str, optional
The working directory in which to run the node. If not specified,
the current working directory is used.

Returns
-------

Expand All @@ -112,9 +116,18 @@ def run(name, args={}):

# Validate the input.

if not isinstance(name, str):
raise TypeError("'name' must be of type 'str'.")

if not isinstance(args, dict):
raise TypeError("'args' must be of type 'dict'.")

if work_dir is not None:
if not isinstance(work_dir, str):
raise TypeError("'work_dir' must be of type 'str'.")
else:
work_dir = _os.getcwd()

# Apped the node directory name.
full_name = _node_dir + "/" + name

Expand All @@ -123,45 +136,50 @@ def run(name, args={}):
if not _os.path.isfile(full_name + ".py"):
raise ValueError(
"Cannot find node: '%s'. " % name
+ "in directory '%s'. " % _node_dir
+ "Run 'Node.list()' to see available nodes!"
)
else:
full_name += ".py"

# Write a YAML configuration file for the BioSimSpace node.
if len(args) > 0:
with open("input.yaml", "w") as file:
_yaml.dump(args, file, default_flow_style=False)

# Create the command.
command = "%s/python %s --config input.yaml" % (
_SireBase.getBinDir(),
full_name,
)
with _Utils.chdir(work_dir):
# Write a YAML configuration file for the BioSimSpace node.
if len(args) > 0:
with open("input.yaml", "w") as file:
_yaml.dump(args, file, default_flow_style=False)

# No arguments.
else:
command = "%s/python %s" % (_SireBase.getBinDir(), full_name)
# Create the command.
command = "%s/python %s --config input.yaml" % (
_SireBase.getBinDir(),
full_name,
)

# Run the node as a subprocess.
proc = _subprocess.run(
_Utils.command_split(command), shell=False, text=True, stderr=_subprocess.PIPE
)
# No arguments.
else:
command = "%s/python %s" % (_SireBase.getBinDir(), full_name)

# Run the node as a subprocess.
proc = _subprocess.run(
_Utils.command_split(command),
shell=False,
text=True,
stderr=_subprocess.PIPE,
)

if proc.returncode == 0:
# Read the output YAML file into a dictionary.
with open("output.yaml", "r") as file:
output = _yaml.safe_load(file)
if proc.returncode == 0:
# Read the output YAML file into a dictionary.
with open("output.yaml", "r") as file:
output = _yaml.safe_load(file)

# Delete the redundant YAML files.
_os.remove("input.yaml")
_os.remove("output.yaml")
# Delete the redundant YAML files.
_os.remove("input.yaml")
_os.remove("output.yaml")

return output
return output

else:
# Print the standard error, decoded as UTF-8.
print(proc.stderr)
else:
# Print the standard error, decoded as UTF-8.
print(proc.stderr)


def setNodeDirectory(dir):
Expand All @@ -180,3 +198,16 @@ def setNodeDirectory(dir):

global _node_dir
_node_dir = dir


def getNodeDirectory():
"""
Get the directory of the node library.

Returns
-------

dir : str
The path to the node library.
"""
return _node_dir
6 changes: 2 additions & 4 deletions python/BioSimSpace/Parameters/_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,14 @@

# A dictionary mapping AMBER protein force field names to their pdb2gmx
# compatibility. Note that the names specified below will be used for the
# parameterisation functions, so they should be suitably formatted. Once we
# have CMAP support we should be able to determine the available force fields
# by scanning the AmberTools installation directory, as we do for those from
# OpenFF.
# parameterisation functions, so they should be suitably formatted.
_amber_protein_forcefields = {
"ff03": True,
"ff99": True,
"ff99SB": False,
"ff99SBildn": False,
"ff14SB": False,
"ff19SB": False,
}

from .. import _amber_home, _gmx_exe, _gmx_path, _isVerbose
Expand Down
23 changes: 18 additions & 5 deletions python/BioSimSpace/Process/_amber.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ def __init__(
# Flag to indicate whether the original system has a box.
self._has_box = _AmberConfig.hasBox(self._system, self._property_map)

# Take note of whether the original reference system was None
# This will be used later to avoid duplication
if reference_system is not None:
self._is_real_reference = True
else:
self._is_real_reference = False

# If the path to the executable wasn't specified, then search
# for it in AMBERHOME and the PATH.
if exe is None:
Expand Down Expand Up @@ -333,7 +340,6 @@ def _setup(self, **kwargs):
else:
# Check for perturbable molecules and convert to the chosen end state.
system = self._checkPerturbable(system)
reference_system = self._checkPerturbable(reference_system)

# RST file (coordinates).
try:
Expand All @@ -348,10 +354,17 @@ def _setup(self, **kwargs):

# Reference file for position restraints.
try:
file = _os.path.splitext(self._ref_file)[0]
_IO.saveMolecules(
file, reference_system, "rst7", property_map=self._property_map
)
if self._is_real_reference:
reference_system, _ = _squash(
system, explicit_dummies=self._explicit_dummies
)
reference_system = self._checkPerturbable(reference_system)
file = _os.path.splitext(self._ref_file)[0]
_IO.saveMolecules(
file, reference_system, "rst7", property_map=self._property_map
)
else:
_shutil.copy(self._rst_file, self._ref_file)
except Exception as e:
msg = "Failed to write reference system to 'RST7' format."
if _isVerbose():
Expand Down
5 changes: 4 additions & 1 deletion python/BioSimSpace/Process/_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,10 @@ def _checkPerturbable(self, system):
"in the 'property_map' argument."
)
else:
is_lambda1 = self._property_map["is_lambda1"].value()
try:
is_lambda1 = self._property_map["is_lambda1"].value()
except:
is_lambda1 = self._property_map["is_lambda1"]
self._property_map.pop("is_lambda1")

# Loop over all perturbable molecules in the system and replace them
Expand Down
Loading
Loading