Skip to content

Commit 0f3d159

Browse files
authored
Merge pull request #410 from OpenBioSim/release_2025.1.0
Release 2025.1.0
2 parents 138ff3a + bf3f5b0 commit 0f3d159

File tree

20 files changed

+593
-156
lines changed

20 files changed

+593
-156
lines changed

README.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@
1717
:target: https://joss.theoj.org/papers/4ba84ad443693b5dded90e35bf5f8225
1818
:alt: Paper
1919

20+
Survey
21+
------
22+
23+
We are currently conducting a
24+
`survey <https://docs.google.com/forms/d/1zY0i3lLR9MhmohKjcu0wJp_CXJvBwcWoj6iG4p9LNKk/edit?ts=6836f566>`__
25+
to help us understand how BioSimSpace is being used and how we can improve it.
26+
The survey explores your molecular simulation tools and practices, identifies workflow challenges, and
27+
gathers feedback on the BioSimSpace simulation framework to guide its future development. If you have
28+
a few minutes, please fill it out!
29+
2030
About
2131
-----
2232

doc/source/changelog.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ company supporting open-source development of fostering academic/industrial coll
99
within the biomolecular simulation community. Our software is hosted via the `OpenBioSim`
1010
`GitHub <https://github.com/OpenBioSim/biosimspace>`__ organisation.
1111

12+
`2025.1.0 <https://github.com/openbiosim/biosimspace/compare/2024.4.1...2025.1.0>`_ - Jul 01 2025
13+
-------------------------------------------------------------------------------------------------
14+
15+
* Improved robustness of formal charge inference when reading molecules from PDB or SDF files (`#393 <https://github.com/OpenBioSim/biosimspace/pull/393>`__).
16+
* Make sure the system extracted from AMBER trajectory frames during free-energy perturbation simulations are in the original, unsquashed format (`#403 <https://github.com/OpenBioSim/biosimspace/pull/403>`__).
17+
* Add support for the ``ff19SB`` force field and OPC water (`#406 <https://github.com/OpenBioSim/biosimspace/pull/406>`__).
18+
* Allow creation of ``SOMD`` perturbation files without modification to ghost atom bonded terms (`#407 <https://github.com/OpenBioSim/biosimspace/pull/407>`__).
19+
* Support analysis of ``SOMD2`` energy trajectories with time varying lambda sampling (`#408 <https://github.com/OpenBioSim/biosimspace/pull/408>`__).
20+
1221
`2024.4.1 <https://github.com/openbiosim/biosimspace/compare/2024.4.0...2024.4.1>`_ - Feb 14 2025
1322
-------------------------------------------------------------------------------------------------
1423

python/BioSimSpace/FreeEnergy/_relative.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,15 +1064,16 @@ def _somd2_extract(parquet_file, T=None, estimator="MBAR"):
10641064
lam = float(metadata["lambda"])
10651065
except:
10661066
raise ValueError("Parquet metadata does not contain 'lambda'.")
1067-
try:
1068-
lambda_array = metadata["lambda_array"]
1069-
except:
1070-
raise ValueError("Parquet metadata does not contain 'lambda array'")
10711067
if not is_mbar:
10721068
try:
10731069
lambda_grad = metadata["lambda_grad"]
10741070
except:
10751071
raise ValueError("Parquet metadata does not contain 'lambda grad'")
1072+
else:
1073+
try:
1074+
lambda_grad = metadata["lambda_grad"]
1075+
except:
1076+
lambda_grad = []
10761077

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

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

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

11041105
# Forward difference.
11051106
if lam_delta > lam:
1106-
double_incr = (lam_delta - lam) * 2
1107-
grad = (df[lam_delta] - df[lam]) * 2 / double_incr
1107+
incr = lam_delta - lam
1108+
grad = (df[lam_delta] - df[lam]) / incr
11081109

11091110
# Backward difference.
11101111
else:
1111-
double_incr = (lam - lam_delta) * 2
1112-
grad = (df[lam] - df[lam_delta]) * 2 / double_incr
1112+
incr = lam - lam_delta
1113+
grad = (df[lam] - df[lam_delta]) / incr
11131114

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

13121313
# Preprocess the data.
13131314
try:
1314-
processed_data = Relative._preprocess_data(data, estimator, **kwargs)
1315-
processed_data = _alchemlyb.concat(processed_data)
1316-
except:
1317-
_warnings.warn("Could not preprocess the data!")
1315+
preprocess = kwargs.pop("preprocess", True)
1316+
except KeyError:
1317+
preprocess = True
1318+
1319+
if not isinstance(preprocess, bool):
1320+
raise TypeError("'preprocess' must be of type 'bool'.")
1321+
1322+
if preprocess:
1323+
try:
1324+
processed_data = Relative._preprocess_data(data, estimator, **kwargs)
1325+
processed_data = _alchemlyb.concat(processed_data)
1326+
except:
1327+
_warnings.warn("Could not preprocess the data!")
1328+
processed_data = _alchemlyb.concat(data)
1329+
else:
13181330
processed_data = _alchemlyb.concat(data)
13191331

13201332
mbar_method = None

python/BioSimSpace/Node/_node.py

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
# Set the default node directory.
3737
_node_dir = _os.path.dirname(__file__) + "/_nodes"
3838

39-
__all__ = ["list", "help", "run", "setNodeDirectory"]
39+
__all__ = ["list", "help", "run", "setNodeDirectory", "getNodeDirectory"]
4040

4141

4242
def list():
@@ -90,7 +90,7 @@ def help(name):
9090
print(proc.stdout)
9191

9292

93-
def run(name, args={}):
93+
def run(name, args={}, work_dir=None):
9494
"""
9595
Run a node.
9696
@@ -103,6 +103,10 @@ def run(name, args={}):
103103
args : dict
104104
A dictionary of arguments to be passed to the node.
105105
106+
work_dir : str, optional
107+
The working directory in which to run the node. If not specified,
108+
the current working directory is used.
109+
106110
Returns
107111
-------
108112
@@ -112,9 +116,18 @@ def run(name, args={}):
112116

113117
# Validate the input.
114118

119+
if not isinstance(name, str):
120+
raise TypeError("'name' must be of type 'str'.")
121+
115122
if not isinstance(args, dict):
116123
raise TypeError("'args' must be of type 'dict'.")
117124

125+
if work_dir is not None:
126+
if not isinstance(work_dir, str):
127+
raise TypeError("'work_dir' must be of type 'str'.")
128+
else:
129+
work_dir = _os.getcwd()
130+
118131
# Apped the node directory name.
119132
full_name = _node_dir + "/" + name
120133

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

131-
# Write a YAML configuration file for the BioSimSpace node.
132-
if len(args) > 0:
133-
with open("input.yaml", "w") as file:
134-
_yaml.dump(args, file, default_flow_style=False)
135-
136-
# Create the command.
137-
command = "%s/python %s --config input.yaml" % (
138-
_SireBase.getBinDir(),
139-
full_name,
140-
)
145+
with _Utils.chdir(work_dir):
146+
# Write a YAML configuration file for the BioSimSpace node.
147+
if len(args) > 0:
148+
with open("input.yaml", "w") as file:
149+
_yaml.dump(args, file, default_flow_style=False)
141150

142-
# No arguments.
143-
else:
144-
command = "%s/python %s" % (_SireBase.getBinDir(), full_name)
151+
# Create the command.
152+
command = "%s/python %s --config input.yaml" % (
153+
_SireBase.getBinDir(),
154+
full_name,
155+
)
145156

146-
# Run the node as a subprocess.
147-
proc = _subprocess.run(
148-
_Utils.command_split(command), shell=False, text=True, stderr=_subprocess.PIPE
149-
)
157+
# No arguments.
158+
else:
159+
command = "%s/python %s" % (_SireBase.getBinDir(), full_name)
160+
161+
# Run the node as a subprocess.
162+
proc = _subprocess.run(
163+
_Utils.command_split(command),
164+
shell=False,
165+
text=True,
166+
stderr=_subprocess.PIPE,
167+
)
150168

151-
if proc.returncode == 0:
152-
# Read the output YAML file into a dictionary.
153-
with open("output.yaml", "r") as file:
154-
output = _yaml.safe_load(file)
169+
if proc.returncode == 0:
170+
# Read the output YAML file into a dictionary.
171+
with open("output.yaml", "r") as file:
172+
output = _yaml.safe_load(file)
155173

156-
# Delete the redundant YAML files.
157-
_os.remove("input.yaml")
158-
_os.remove("output.yaml")
174+
# Delete the redundant YAML files.
175+
_os.remove("input.yaml")
176+
_os.remove("output.yaml")
159177

160-
return output
178+
return output
161179

162-
else:
163-
# Print the standard error, decoded as UTF-8.
164-
print(proc.stderr)
180+
else:
181+
# Print the standard error, decoded as UTF-8.
182+
print(proc.stderr)
165183

166184

167185
def setNodeDirectory(dir):
@@ -180,3 +198,16 @@ def setNodeDirectory(dir):
180198

181199
global _node_dir
182200
_node_dir = dir
201+
202+
203+
def getNodeDirectory():
204+
"""
205+
Get the directory of the node library.
206+
207+
Returns
208+
-------
209+
210+
dir : str
211+
The path to the node library.
212+
"""
213+
return _node_dir

python/BioSimSpace/Parameters/_parameters.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,14 @@
3737

3838
# A dictionary mapping AMBER protein force field names to their pdb2gmx
3939
# compatibility. Note that the names specified below will be used for the
40-
# parameterisation functions, so they should be suitably formatted. Once we
41-
# have CMAP support we should be able to determine the available force fields
42-
# by scanning the AmberTools installation directory, as we do for those from
43-
# OpenFF.
40+
# parameterisation functions, so they should be suitably formatted.
4441
_amber_protein_forcefields = {
4542
"ff03": True,
4643
"ff99": True,
4744
"ff99SB": False,
4845
"ff99SBildn": False,
4946
"ff14SB": False,
47+
"ff19SB": False,
5048
}
5149

5250
from .. import _amber_home, _gmx_exe, _gmx_path, _isVerbose

python/BioSimSpace/Process/_amber.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,13 @@ def __init__(
172172
# Flag to indicate whether the original system has a box.
173173
self._has_box = _AmberConfig.hasBox(self._system, self._property_map)
174174

175+
# Take note of whether the original reference system was None
176+
# This will be used later to avoid duplication
177+
if reference_system is not None:
178+
self._is_real_reference = True
179+
else:
180+
self._is_real_reference = False
181+
175182
# If the path to the executable wasn't specified, then search
176183
# for it in AMBERHOME and the PATH.
177184
if exe is None:
@@ -333,7 +340,6 @@ def _setup(self, **kwargs):
333340
else:
334341
# Check for perturbable molecules and convert to the chosen end state.
335342
system = self._checkPerturbable(system)
336-
reference_system = self._checkPerturbable(reference_system)
337343

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

349355
# Reference file for position restraints.
350356
try:
351-
file = _os.path.splitext(self._ref_file)[0]
352-
_IO.saveMolecules(
353-
file, reference_system, "rst7", property_map=self._property_map
354-
)
357+
if self._is_real_reference:
358+
reference_system, _ = _squash(
359+
system, explicit_dummies=self._explicit_dummies
360+
)
361+
reference_system = self._checkPerturbable(reference_system)
362+
file = _os.path.splitext(self._ref_file)[0]
363+
_IO.saveMolecules(
364+
file, reference_system, "rst7", property_map=self._property_map
365+
)
366+
else:
367+
_shutil.copy(self._rst_file, self._ref_file)
355368
except Exception as e:
356369
msg = "Failed to write reference system to 'RST7' format."
357370
if _isVerbose():

python/BioSimSpace/Process/_process.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,10 @@ def _checkPerturbable(self, system):
731731
"in the 'property_map' argument."
732732
)
733733
else:
734-
is_lambda1 = self._property_map["is_lambda1"].value()
734+
try:
735+
is_lambda1 = self._property_map["is_lambda1"].value()
736+
except:
737+
is_lambda1 = self._property_map["is_lambda1"]
735738
self._property_map.pop("is_lambda1")
736739

737740
# Loop over all perturbable molecules in the system and replace them

0 commit comments

Comments
 (0)