Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
43d6007
Migrate validation to Protocol._validate
IAlibay Dec 5, 2025
2cd56ba
some fixes
IAlibay Dec 5, 2025
70e6d7a
Merge branch 'main' into validate-rfe
IAlibay Dec 5, 2025
f330562
move some things around
IAlibay Dec 8, 2025
95b92b3
Merge branch 'main' into validate-rfe
IAlibay Dec 15, 2025
1e0153e
add validate endstate tests
IAlibay Dec 15, 2025
fe2b879
Merge branch 'validate-rfe' of github.com:OpenFreeEnergy/openfe into …
IAlibay Dec 15, 2025
fbc4554
validate mapping tests
IAlibay Dec 15, 2025
c2f49d2
net charge validation tests
IAlibay Dec 15, 2025
c50f99c
more stuff
IAlibay Dec 22, 2025
9e0d29b
remove old tests
IAlibay Dec 24, 2025
2fe8ff9
make hybrid samplers not rely on htf
IAlibay Dec 24, 2025
4a0bd26
fix up test
IAlibay Dec 24, 2025
5848adc
fix up some slow tests
IAlibay Dec 24, 2025
1aaef87
Merge branch 'main' into multistate-nohtf
IAlibay Dec 24, 2025
b6d5ecd
Fix up the one test
IAlibay Dec 26, 2025
0605d11
fix a few things
IAlibay Dec 26, 2025
48106a2
fix the remaining tests
IAlibay Dec 26, 2025
5af66e8
cleanup imports
IAlibay Dec 26, 2025
ad0b5fb
Merge branch 'validate-rfe' into move-rfe-protocol
IAlibay Dec 26, 2025
45e004c
Merge branch 'multistate-nohtf' into move-rfe-protocol
IAlibay Dec 26, 2025
58dd71c
Migrate protocol, units, and results for the hybridtop protocol
IAlibay Dec 26, 2025
792996e
Add news item
IAlibay Dec 26, 2025
91f1788
Merge branch 'validate-rfe' into move-rfe-protocol
IAlibay Dec 26, 2025
527b870
Merge branch 'main' into validate-rfe
IAlibay Dec 26, 2025
7d17998
fix redefine
IAlibay Dec 27, 2025
43eb947
start modularising everything
IAlibay Dec 27, 2025
d1bd736
Add charge validation for smcs when dealing with ismorphic molecules
IAlibay Dec 27, 2025
51a6de1
break down the rfe units into bits
IAlibay Dec 29, 2025
6a5a76a
more broadly disallow oechem as a backend when creating systems
IAlibay Dec 29, 2025
cdd3da0
fix issue with nc being undefined
IAlibay Dec 29, 2025
b580de5
Make structural analysis not use the CLI anymore
IAlibay Dec 29, 2025
e0a8e2a
Merge branch 'validate-rfe' into move-rfe-protocol
IAlibay Dec 29, 2025
a0ef737
Merge branch 'move-rfe-protocol' into breakdown-rfe-protocolunit
IAlibay Dec 29, 2025
0d02ba8
Merge branch 'breakdown-rfe-protocolunit' into non-cli-analysis
IAlibay Dec 29, 2025
b826803
Fix missing import
IAlibay Dec 29, 2025
42ddbcf
Merge branch 'move-rfe-protocol' into breakdown-rfe-protocolunit
IAlibay Dec 29, 2025
e93b40e
Merge branch 'breakdown-rfe-protocolunit' into non-cli-analysis
IAlibay Dec 29, 2025
063e8ce
Fix comp getter
IAlibay Dec 29, 2025
3844bb5
Merge branch 'move-rfe-protocol' into breakdown-rfe-protocolunit
IAlibay Dec 29, 2025
a9ca6c6
Merge branch 'breakdown-rfe-protocolunit' into non-cli-analysis
IAlibay Dec 29, 2025
8d4cc14
Remove hardcoded file paths
IAlibay Dec 30, 2025
a98c799
update module name
IAlibay Dec 30, 2025
5d0bc7e
Merge branch 'move-rfe-protocol' into breakdown-rfe-protocolunit
IAlibay Dec 30, 2025
e52713a
Merge branch 'breakdown-rfe-protocolunit' into non-cli-analysis
IAlibay Dec 30, 2025
7c915ed
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 3, 2026
0632b1e
Merge branch 'breakdown-rfe-protocolunit' into non-cli-analysis
IAlibay Jan 3, 2026
951ac15
move a few things around to make life easier
IAlibay Jan 3, 2026
9787e80
Merge branch 'breakdown-rfe-protocolunit' into non-cli-analysis
IAlibay Jan 3, 2026
2c7fe6d
Merge branch 'main' into non-cli-analysis
IAlibay Jan 7, 2026
e10ee53
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 7, 2026
57928a2
fix mypy issue
IAlibay Jan 7, 2026
4304a79
make mypy happy
IAlibay Jan 7, 2026
e94a348
address review comments
IAlibay Jan 8, 2026
5702ac7
Merge branch 'main' into non-cli-analysis
IAlibay Jan 8, 2026
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
78 changes: 51 additions & 27 deletions openfe/protocols/openmm_rfe/hybridtop_units.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
the Perses toolkit (https://github.com/choderalab/perses).
"""

import json
import logging
import os
import pathlib
Expand Down Expand Up @@ -1229,42 +1228,62 @@ def run(
}

@staticmethod
def structural_analysis(scratch, shared) -> dict:
# don't put energy analysis in here, it uses the open file reporter
# whereas structural stuff requires that the file handle is closed
# TODO: we should just make openfe_analysis write an npz instead!
analysis_out = scratch / "structural_analysis.json"

ret = subprocess.run(
[
"openfe_analysis", # CLI entry point
"RFE_analysis", # CLI option
str(shared), # Where the simulation.nc fille
str(analysis_out), # Where the analysis json file is written
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if ret.returncode:
return {"structural_analysis_error": ret.stderr}
def structural_analysis(
scratch: pathlib.Path,
shared: pathlib.Path,
pdb_filename: str,
trj_filename: str,
) -> dict[str, str | pathlib.Path]:
"""
Run structural analysis using ``openfe-analysis``.

Parameters
----------
scratch : pathlib.Path
Path to the scratch directory.
shared : pathlib.Path
Path to the shared directory.
pdb_filename : str
The PDB file name.
trj_filename : str
The trajectory file name.

with open(analysis_out, "rb") as f:
data = json.load(f)
Returns
-------
dict[str, str | pathlib.Path]
Dictionary containing either the path to the NPZ
file with the structural data, or the analysis error.

savedir = pathlib.Path(shared)
Notes
-----
Don't put energy analysis here, it uses the open file reporter
whereas structural stuff requires the file handle to be closed.
"""
from openfe_analysis import rmsd

pdb_file = shared / pdb_filename
trj_file = shared / trj_filename

try:
data = rmsd.gather_rms_data(pdb_file, trj_file)
# TODO: change this to more specific exception types
except Exception as e:
return {"structural_analysis_error": str(e)}

# Generate plots
if d := data["protein_2D_RMSD"]:
fig = plotting.plot_2D_rmsd(d)
fig.savefig(savedir / "protein_2D_RMSD.png")
fig.savefig(shared / "protein_2D_RMSD.png")
plt.close(fig)
f2 = plotting.plot_ligand_COM_drift(data["time(ps)"], data["ligand_wander"])
f2.savefig(savedir / "ligand_COM_drift.png")
f2.savefig(shared / "ligand_COM_drift.png")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm probably missing something, but why is the plotting of the ligand COM drift here dependent on the presence of the protein 2D RMSD data?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, nvm, this probably just makes sure there's a protein present...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's not the best check in the world, but I'm essentially keeping the same behaviour as we had before. Depending on how openfe-analysis updates, we should further update things here, probably so that we dont use gather_rms_data but instead individual analysis methods or something like that.

plt.close(f2)

f3 = plotting.plot_ligand_RMSD(data["time(ps)"], data["ligand_RMSD"])
f3.savefig(savedir / "ligand_RMSD.png")
f3.savefig(shared / "ligand_RMSD.png")
plt.close(f3)

# Save to numpy compressed format (~ 6x more space efficient than JSON)
# Write out NPZ with the analyzed data
np.savez_compressed(
shared / "structural_analysis.npz",
protein_RMSD=np.asarray(data["protein_RMSD"], dtype=np.float32),
Expand All @@ -1285,7 +1304,12 @@ def _execute(

outputs = self.run(scratch_basepath=ctx.scratch, shared_basepath=ctx.shared)

structural_analysis_outputs = self.structural_analysis(ctx.scratch, ctx.shared)
structural_analysis_outputs = self.structural_analysis(
scratch=ctx.scratch,
shared=ctx.shared,
pdb_filename=self._inputs["protocol"].settings.output_settings.output_structure,
trj_filename=self._inputs["protocol"].settings.output_settings.output_filename,
)

return {
"repeat_id": self._inputs["repeat_id"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1981,7 +1981,7 @@ def test_dry_run_complex_alchemwater_totcharge(
def test_structural_analysis_error(tmpdir):
with tmpdir.as_cwd():
ret = openmm_rfe.RelativeHybridTopologyProtocolUnit.structural_analysis(
Path("."), Path(".")
Path("."), Path("."), "foo", "bar"
)

assert "structural_analysis_error" in ret
Expand Down
Loading