From 11710a7f68dce91de4fe8bafd97d09418692f042 Mon Sep 17 00:00:00 2001 From: IAlibay Date: Mon, 16 Feb 2026 02:04:02 +0000 Subject: [PATCH 01/28] Modify quickrun to allow resuming --- environment.yml | 2 +- src/openfecli/commands/quickrun.py | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/environment.yml b/environment.yml index c45d8102a..f74301b03 100644 --- a/environment.yml +++ b/environment.yml @@ -53,7 +53,7 @@ dependencies: # Control blas/openmp threads - threadpoolctl - pip: - - git+https://github.com/OpenFreeEnergy/gufe@main + - git+https://github.com/OpenFreeEnergy/gufe@restart_execute - run_constrained: # drop this pin when handled upstream in espaloma-feedstock - smirnoff99frosst>=1.1.0.1 #https://github.com/openforcefield/smirnoff99Frosst/issues/109 diff --git a/src/openfecli/commands/quickrun.py b/src/openfecli/commands/quickrun.py index f34410d69..443e20a7d 100644 --- a/src/openfecli/commands/quickrun.py +++ b/src/openfecli/commands/quickrun.py @@ -49,9 +49,11 @@ def quickrun(transformation, work_dir, output): for each repeat of the sampling process (by default 3). """ import logging + from json import JSONDecodeError import os import sys + from gufe import ProtocolDAG from gufe.protocols.protocoldag import execute_DAG from gufe.tokenization import JSON_HANDLER from gufe.transformations.transformation import Transformation @@ -94,13 +96,26 @@ def quickrun(transformation, work_dir, output): else: output.parent.mkdir(exist_ok=True, parents=True) - write("Planning simulations for this edge...") - dag = trans.create() + # Attempt to either deserialize or freshly create DAG + if (work_dir / "protocol_dag.json").is_file(): + write("Attempting to recover edge simulations from file") + try: + dag = ProtocolDAG.from_json(work_dir / "protocol_dag.json") + except JSONDecodeError: + errmsg = "Recovery failed, please clean workdir before continuing" + raise click.ClickException(errmsg) + else: + # Create the DAG instead and then serialize for later resuming + write("Planning simulations for this edge...") + dag = trans.create() + dag.to_json(work_dir / "protocol_dag.json") + write("Starting the simulations for this edge...") dagresult = execute_DAG( dag, shared_basedir=work_dir, scratch_basedir=work_dir, + unitresults_basedir=work_dir, keep_shared=True, raise_error=False, n_retries=2, From 322bc23858bae0b187b1b38131fe526259723a87 Mon Sep 17 00:00:00 2001 From: IAlibay Date: Mon, 16 Feb 2026 12:16:24 +0000 Subject: [PATCH 02/28] fix the gather tests --- .../protocols/openmm_abfe/test_abfe_protocol_results.py | 6 +++--- .../protocols/openmm_ahfe/test_ahfe_protocol_results.py | 6 +++--- .../tests/protocols/openmm_md/test_plain_md_protocol.py | 6 +++--- .../tests/protocols/openmm_rfe/test_hybrid_top_protocol.py | 6 +++--- .../tests/protocols/openmm_septop/test_septop_protocol.py | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/openfe/tests/protocols/openmm_abfe/test_abfe_protocol_results.py b/src/openfe/tests/protocols/openmm_abfe/test_abfe_protocol_results.py index 5d815c713..53574f972 100644 --- a/src/openfe/tests/protocols/openmm_abfe/test_abfe_protocol_results.py +++ b/src/openfe/tests/protocols/openmm_abfe/test_abfe_protocol_results.py @@ -79,12 +79,12 @@ def patcher(): yield -def test_gather(benzene_complex_dag, patcher, tmpdir): +def test_gather(benzene_complex_dag, patcher, tmp_path): # check that .gather behaves as expected dagres = gufe.protocols.execute_DAG( benzene_complex_dag, - shared_basedir=tmpdir, - scratch_basedir=tmpdir, + shared_basedir=tmp_path, + scratch_basedir=tmp_path, keep_shared=True, ) diff --git a/src/openfe/tests/protocols/openmm_ahfe/test_ahfe_protocol_results.py b/src/openfe/tests/protocols/openmm_ahfe/test_ahfe_protocol_results.py index 0cb2d2d25..619b199d1 100644 --- a/src/openfe/tests/protocols/openmm_ahfe/test_ahfe_protocol_results.py +++ b/src/openfe/tests/protocols/openmm_ahfe/test_ahfe_protocol_results.py @@ -99,12 +99,12 @@ def patcher(): yield -def test_gather(benzene_solvation_dag, patcher, tmpdir): +def test_gather(benzene_solvation_dag, patcher, tmp_path): # check that .gather behaves as expected dagres = gufe.protocols.execute_DAG( benzene_solvation_dag, - shared_basedir=tmpdir, - scratch_basedir=tmpdir, + shared_basedir=tmp_path, + scratch_basedir=tmp_path, keep_shared=True, ) diff --git a/src/openfe/tests/protocols/openmm_md/test_plain_md_protocol.py b/src/openfe/tests/protocols/openmm_md/test_plain_md_protocol.py index 60c7e8c47..b8e3153ff 100644 --- a/src/openfe/tests/protocols/openmm_md/test_plain_md_protocol.py +++ b/src/openfe/tests/protocols/openmm_md/test_plain_md_protocol.py @@ -508,7 +508,7 @@ def test_unit_tagging(solvent_protocol_dag, tmpdir): assert len(repeats) == 3 -def test_gather(solvent_protocol_dag, tmpdir): +def test_gather(solvent_protocol_dag, tmp_path): # check .gather behaves as expected with mock.patch( "openfe.protocols.openmm_md.plain_md_methods.PlainMDProtocolUnit.run", @@ -519,8 +519,8 @@ def test_gather(solvent_protocol_dag, tmpdir): ): dagres = gufe.protocols.execute_DAG( solvent_protocol_dag, - shared_basedir=tmpdir, - scratch_basedir=tmpdir, + shared_basedir=tmp_path, + scratch_basedir=tmp_path, keep_shared=True, ) diff --git a/src/openfe/tests/protocols/openmm_rfe/test_hybrid_top_protocol.py b/src/openfe/tests/protocols/openmm_rfe/test_hybrid_top_protocol.py index bd7a1f72f..c632505ea 100644 --- a/src/openfe/tests/protocols/openmm_rfe/test_hybrid_top_protocol.py +++ b/src/openfe/tests/protocols/openmm_rfe/test_hybrid_top_protocol.py @@ -1225,7 +1225,7 @@ def test_unit_tagging(solvent_protocol_dag, tmpdir): assert len(setup_results) == len(sim_results) == len(analysis_results) == 3 -def test_gather(solvent_protocol_dag, tmpdir): +def test_gather(solvent_protocol_dag, tmp_path): # check .gather behaves as expected with ( mock.patch( @@ -1263,8 +1263,8 @@ def test_gather(solvent_protocol_dag, tmpdir): ): dagres = gufe.protocols.execute_DAG( solvent_protocol_dag, - shared_basedir=tmpdir, - scratch_basedir=tmpdir, + shared_basedir=tmp_path, + scratch_basedir=tmp_path, keep_shared=True, ) diff --git a/src/openfe/tests/protocols/openmm_septop/test_septop_protocol.py b/src/openfe/tests/protocols/openmm_septop/test_septop_protocol.py index e5e4a9f91..4a22aebcf 100644 --- a/src/openfe/tests/protocols/openmm_septop/test_septop_protocol.py +++ b/src/openfe/tests/protocols/openmm_septop/test_septop_protocol.py @@ -1295,7 +1295,7 @@ def test_unit_tagging(benzene_toluene_dag, tmpdir): assert len(complex_repeats) == len(solv_repeats) == 2 -def test_gather(benzene_toluene_dag, tmpdir): +def test_gather(benzene_toluene_dag, tmp_path): # check that .gather behaves as expected with ( mock.patch( @@ -1339,8 +1339,8 @@ def test_gather(benzene_toluene_dag, tmpdir): ): dagres = gufe.protocols.execute_DAG( benzene_toluene_dag, - shared_basedir=tmpdir, - scratch_basedir=tmpdir, + shared_basedir=tmp_path, + scratch_basedir=tmp_path, keep_shared=True, ) From a61598e79bfdabbdd12215b03fad2cdc1b3d8f72 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:02:17 +0000 Subject: [PATCH 03/28] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/openfecli/commands/quickrun.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openfecli/commands/quickrun.py b/src/openfecli/commands/quickrun.py index 443e20a7d..cf809605c 100644 --- a/src/openfecli/commands/quickrun.py +++ b/src/openfecli/commands/quickrun.py @@ -49,9 +49,9 @@ def quickrun(transformation, work_dir, output): for each repeat of the sampling process (by default 3). """ import logging - from json import JSONDecodeError import os import sys + from json import JSONDecodeError from gufe import ProtocolDAG from gufe.protocols.protocoldag import execute_DAG From 0f43f6fcb1659c30b5e44f30d279e282902d6393 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 11 Mar 2026 14:04:29 -0700 Subject: [PATCH 04/28] add check for protocol_dag.json --- src/openfecli/tests/commands/test_quickrun.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index 86fe00b26..2bab4c53b 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -34,6 +34,8 @@ def test_quickrun(extra_args, json_file): assert result.exit_code == 0 assert "Here is the result" in result.output + assert pathlib.Path(extra_args.get("-d", ""), "protocol_dag.json").exists() + if outfile := extra_args.get("-o"): assert pathlib.Path(outfile).exists() with open(outfile, mode="r") as outf: From 5e1a21c7bce95ff6e268113eede2722b8f3b321c Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 11 Mar 2026 14:26:44 -0700 Subject: [PATCH 05/28] add basic test --- src/openfecli/tests/commands/test_quickrun.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index 2bab4c53b..64b487821 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -5,6 +5,7 @@ import click import pytest from click.testing import CliRunner +from gufe import Transformation from gufe.tokenization import JSON_HANDLER from openfecli.commands.quickrun import quickrun @@ -94,3 +95,16 @@ def test_quickrun_unit_error(): # to be stored in JSON # not sure whether that means we should always be storing all # protocol dag results maybe? + + +def test_quickrun_resume(json_file): + trans = Transformation.from_json(json_file) + dag = trans.create() + + runner = CliRunner() + with runner.isolated_filesystem(): + dag.to_json("protocol_dag.json") + result = runner.invoke(quickrun, [json_file]) + + assert result.exit_code == 0 + assert "Attempting to recover edge simulations" in result.output From 182562fed57ed38f3946eabe3cc9edeb5ddbf915 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 11 Mar 2026 14:45:17 -0700 Subject: [PATCH 06/28] clearer language, hopefully --- src/openfecli/commands/quickrun.py | 7 ++++--- src/openfecli/tests/commands/test_quickrun.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/openfecli/commands/quickrun.py b/src/openfecli/commands/quickrun.py index cf809605c..3169c6d94 100644 --- a/src/openfecli/commands/quickrun.py +++ b/src/openfecli/commands/quickrun.py @@ -97,12 +97,13 @@ def quickrun(transformation, work_dir, output): output.parent.mkdir(exist_ok=True, parents=True) # Attempt to either deserialize or freshly create DAG - if (work_dir / "protocol_dag.json").is_file(): - write("Attempting to recover edge simulations from file") + dag_json = work_dir / "protocol_dag.json" + if dag_json.is_file(): + write(f"Attempting to resume execution using existing edges from '{dag_json}'") try: dag = ProtocolDAG.from_json(work_dir / "protocol_dag.json") except JSONDecodeError: - errmsg = "Recovery failed, please clean workdir before continuing" + errmsg = f"Recovery failed, please remove {dag_json} and any results from your working directory before continuing to create a new protocol." raise click.ClickException(errmsg) else: # Create the DAG instead and then serialize for later resuming diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index 64b487821..b7e89625e 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -107,4 +107,4 @@ def test_quickrun_resume(json_file): result = runner.invoke(quickrun, [json_file]) assert result.exit_code == 0 - assert "Attempting to recover edge simulations" in result.output + assert "Attempting to resume" in result.output From 3180b8c98848003625f6e2929a0da978f47c55e2 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 11 Mar 2026 15:30:08 -0700 Subject: [PATCH 07/28] store protocol dag using transformation key --- src/openfecli/commands/quickrun.py | 13 +++++++------ src/openfecli/tests/commands/test_quickrun.py | 10 +++++++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/openfecli/commands/quickrun.py b/src/openfecli/commands/quickrun.py index 3169c6d94..308d8f7b0 100644 --- a/src/openfecli/commands/quickrun.py +++ b/src/openfecli/commands/quickrun.py @@ -97,19 +97,20 @@ def quickrun(transformation, work_dir, output): output.parent.mkdir(exist_ok=True, parents=True) # Attempt to either deserialize or freshly create DAG - dag_json = work_dir / "protocol_dag.json" - if dag_json.is_file(): - write(f"Attempting to resume execution using existing edges from '{dag_json}'") + trans_DAG_json = work_dir / f"Transformation-{trans.key}-protocolDAG.json" + + if trans_DAG_json.is_file(): + write(f"Attempting to resume execution using existing edges from '{trans_DAG_json}'") try: - dag = ProtocolDAG.from_json(work_dir / "protocol_dag.json") + dag = ProtocolDAG.from_json(trans_DAG_json) except JSONDecodeError: - errmsg = f"Recovery failed, please remove {dag_json} and any results from your working directory before continuing to create a new protocol." + errmsg = f"Recovery failed, please remove {trans_DAG_json} and any results from your working directory before continuing to create a new protocol." raise click.ClickException(errmsg) else: # Create the DAG instead and then serialize for later resuming write("Planning simulations for this edge...") dag = trans.create() - dag.to_json(work_dir / "protocol_dag.json") + dag.to_json(trans_DAG_json) write("Starting the simulations for this edge...") dagresult = execute_DAG( diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index b7e89625e..6a290cbac 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -10,6 +10,8 @@ from openfecli.commands.quickrun import quickrun +# from ..utils import assert_click_success + @pytest.fixture def json_file(): @@ -34,8 +36,10 @@ def test_quickrun(extra_args, json_file): result = runner.invoke(quickrun, [json_file] + extras) assert result.exit_code == 0 assert "Here is the result" in result.output - - assert pathlib.Path(extra_args.get("-d", ""), "protocol_dag.json").exists() + trans = Transformation.from_json(json_file) + assert pathlib.Path( + extra_args.get("-d", ""), f"Transformation-{trans.key}-protocolDAG.json" + ).exists() if outfile := extra_args.get("-o"): assert pathlib.Path(outfile).exists() @@ -103,7 +107,7 @@ def test_quickrun_resume(json_file): runner = CliRunner() with runner.isolated_filesystem(): - dag.to_json("protocol_dag.json") + dag.to_json(f"Transformation-{trans.key}-protocolDAG.json") result = runner.invoke(quickrun, [json_file]) assert result.exit_code == 0 From 1c0fdf79c0aa066f4ea6aab97b3fe65ff943053c Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Thu, 12 Mar 2026 07:58:17 -0700 Subject: [PATCH 08/28] another tmpdir -> tmp_path fix --- .../tests/protocols/openmm_rfe/test_hybrid_top_protocol.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/openfe/tests/protocols/openmm_rfe/test_hybrid_top_protocol.py b/src/openfe/tests/protocols/openmm_rfe/test_hybrid_top_protocol.py index 7e257e865..83b795eb2 100644 --- a/src/openfe/tests/protocols/openmm_rfe/test_hybrid_top_protocol.py +++ b/src/openfe/tests/protocols/openmm_rfe/test_hybrid_top_protocol.py @@ -1257,12 +1257,12 @@ def test_unit_tagging(solvent_protocol_dag, unit_mock_patcher, tmpdir): assert len(setup_results) == len(sim_results) == len(analysis_results) == 3 -def test_gather(solvent_protocol_dag, unit_mock_patcher, tmpdir): +def test_gather(solvent_protocol_dag, unit_mock_patcher, tmp_path): # check .gather behaves as expected dagres = gufe.protocols.execute_DAG( solvent_protocol_dag, - shared_basedir=tmpdir, - scratch_basedir=tmpdir, + shared_basedir=tmp_path, + scratch_basedir=tmp_path, keep_shared=True, ) From 156320b2d6b263092290ca22adb38a9ba022e5fe Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Thu, 12 Mar 2026 16:21:40 -0700 Subject: [PATCH 09/28] add error handling check --- src/openfecli/commands/quickrun.py | 2 +- src/openfecli/tests/commands/test_quickrun.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/openfecli/commands/quickrun.py b/src/openfecli/commands/quickrun.py index 308d8f7b0..c747bdc20 100644 --- a/src/openfecli/commands/quickrun.py +++ b/src/openfecli/commands/quickrun.py @@ -97,7 +97,7 @@ def quickrun(transformation, work_dir, output): output.parent.mkdir(exist_ok=True, parents=True) # Attempt to either deserialize or freshly create DAG - trans_DAG_json = work_dir / f"Transformation-{trans.key}-protocolDAG.json" + trans_DAG_json = work_dir / f"{trans.key}-protocolDAG.json" if trans_DAG_json.is_file(): write(f"Attempting to resume execution using existing edges from '{trans_DAG_json}'") diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index 6a290cbac..9dda94f50 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -107,8 +107,22 @@ def test_quickrun_resume(json_file): runner = CliRunner() with runner.isolated_filesystem(): - dag.to_json(f"Transformation-{trans.key}-protocolDAG.json") + dag.to_json(f"{trans.key}-protocolDAG.json") result = runner.invoke(quickrun, [json_file]) assert result.exit_code == 0 assert "Attempting to resume" in result.output + + +def test_quickrun_resume_json_invalid(json_file): + """Fail if the output file doesn't load properly.""" + trans = Transformation.from_json(json_file) + + runner = CliRunner() + with runner.isolated_filesystem(): + pathlib.Path(f"{trans.key}-protocolDAG.json").touch() + result = runner.invoke(quickrun, [json_file]) + + assert result.exit_code == 1 + assert "Attempting to resume" in result.output + assert "Recovery failed" in result.stderr From 360882a7ded6fdc6b241e7aa3074d55addfb04b4 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Thu, 12 Mar 2026 17:09:07 -0700 Subject: [PATCH 10/28] fix naming in test --- src/openfecli/tests/commands/test_quickrun.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index 9dda94f50..b4bfe1eab 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -37,9 +37,7 @@ def test_quickrun(extra_args, json_file): assert result.exit_code == 0 assert "Here is the result" in result.output trans = Transformation.from_json(json_file) - assert pathlib.Path( - extra_args.get("-d", ""), f"Transformation-{trans.key}-protocolDAG.json" - ).exists() + assert pathlib.Path(extra_args.get("-d", ""), f"{trans.key}-protocolDAG.json").exists() if outfile := extra_args.get("-o"): assert pathlib.Path(outfile).exists() From 04d47bbcf31cc3bbaf0e56e1a392708bc865ee21 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Thu, 12 Mar 2026 17:12:57 -0700 Subject: [PATCH 11/28] add news item --- news/quickrun_resume.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/quickrun_resume.rst diff --git a/news/quickrun_resume.rst b/news/quickrun_resume.rst new file mode 100644 index 000000000..789c20df5 --- /dev/null +++ b/news/quickrun_resume.rst @@ -0,0 +1,23 @@ +**Added:** + +* ``openfe quickrun`` now stores ``protocolDAG`` information for each transformation, and will attempt to load this file to resume execution of an incomplete transformation. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 1055c1b47b26054f2a72672d7777752ea6c0d816 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Fri, 13 Mar 2026 09:12:10 -0700 Subject: [PATCH 12/28] use assert_click_success --- src/openfecli/tests/commands/test_quickrun.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index b4bfe1eab..942c18068 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -10,7 +10,7 @@ from openfecli.commands.quickrun import quickrun -# from ..utils import assert_click_success +from ..utils import assert_click_success @pytest.fixture @@ -34,7 +34,8 @@ def test_quickrun(extra_args, json_file): runner = CliRunner() with runner.isolated_filesystem(): result = runner.invoke(quickrun, [json_file] + extras) - assert result.exit_code == 0 + + assert_click_success(result) assert "Here is the result" in result.output trans = Transformation.from_json(json_file) assert pathlib.Path(extra_args.get("-d", ""), f"{trans.key}-protocolDAG.json").exists() @@ -70,7 +71,7 @@ def test_quickrun_output_file_in_nonexistent_directory(json_file): with runner.isolated_filesystem(): outfile = pathlib.Path("not_dir/foo.json") result = runner.invoke(quickrun, [json_file, "-o", outfile]) - assert result.exit_code == 0 + assert_click_success(result) assert outfile.parent.is_dir() @@ -81,7 +82,7 @@ def test_quickrun_dir_created_at_runtime(json_file): outdir = "not_dir" outfile = outdir + "foo.json" result = runner.invoke(quickrun, [json_file, "-d", outdir, "-o", outfile]) - assert result.exit_code == 0 + assert_click_success(result) def test_quickrun_unit_error(): @@ -108,7 +109,7 @@ def test_quickrun_resume(json_file): dag.to_json(f"{trans.key}-protocolDAG.json") result = runner.invoke(quickrun, [json_file]) - assert result.exit_code == 0 + assert_click_success(result) assert "Attempting to resume" in result.output From c8a03d8c0ae2f1637c3f2fef23edd9a5686e793d Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 18 Mar 2026 10:10:12 -0700 Subject: [PATCH 13/28] add test for interrupted job --- src/openfecli/tests/commands/test_quickrun.py | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index 942c18068..6943e2be1 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -1,8 +1,8 @@ import json import pathlib from importlib import resources +from unittest import mock -import click import pytest from click.testing import CliRunner from gufe import Transformation @@ -21,13 +21,7 @@ def json_file(): return json_file -@pytest.mark.parametrize( - "extra_args", - [ - {}, - {"-d": "foo_dir", "-o": "foo.json"}, - ], -) +@pytest.mark.parametrize("extra_args", [{}, {"-d": "foo_dir", "-o": "foo.json"}]) def test_quickrun(extra_args, json_file): extras = sum([list(kv) for kv in extra_args.items()], []) @@ -55,6 +49,21 @@ def test_quickrun(extra_args, json_file): # assert len(list(dirpath.iterdir())) > 0 +@pytest.mark.parametrize("extra_args", [{}, {"-d": "foo_dir", "-o": "foo.json"}]) +def test_quickrun_interrupted(extra_args, json_file): + """If a quickrun is unable to complete, the protocolDAG.json checkpoint should exist.""" + extras = sum([list(kv) for kv in extra_args.items()], []) + + runner = CliRunner() + with runner.isolated_filesystem(): + with mock.patch("gufe.protocols.protocoldag.execute_DAG", side_effect=RuntimeError): + result = runner.invoke(quickrun, [json_file] + extras) + + assert "Here is the result" not in result.output + trans = Transformation.from_json(json_file) + assert pathlib.Path(extra_args.get("-d", ""), f"{trans.key}-protocolDAG.json").exists() + + def test_quickrun_output_file_exists(json_file): """Fail if the output file already exists.""" runner = CliRunner() From d735c7fcaecc9b8efbcc6ea166e6d52547431fcb Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 18 Mar 2026 10:11:43 -0700 Subject: [PATCH 14/28] remove checkpoint when a job has completed successfully --- src/openfecli/commands/quickrun.py | 3 +++ src/openfecli/tests/commands/test_quickrun.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/openfecli/commands/quickrun.py b/src/openfecli/commands/quickrun.py index c747bdc20..b469d3cd8 100644 --- a/src/openfecli/commands/quickrun.py +++ b/src/openfecli/commands/quickrun.py @@ -143,6 +143,9 @@ def quickrun(transformation, work_dir, output): with open(output, mode="w") as outf: json.dump(out_dict, outf, cls=JSON_HANDLER.encoder) + # remove the checkpoint since the job has completed + os.remove(trans_DAG_json) + write(f"Here is the result:\n\tdG = {estimate} ± {uncertainty}\n") write("") diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index 6943e2be1..9ca9f2557 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -32,7 +32,8 @@ def test_quickrun(extra_args, json_file): assert_click_success(result) assert "Here is the result" in result.output trans = Transformation.from_json(json_file) - assert pathlib.Path(extra_args.get("-d", ""), f"{trans.key}-protocolDAG.json").exists() + # checkpoint should be deleted when job is complete + assert not pathlib.Path(extra_args.get("-d", ""), f"{trans.key}-protocolDAG.json").exists() if outfile := extra_args.get("-o"): assert pathlib.Path(outfile).exists() From 6518499a16c211b82b9718a4feef1e047fdf463f Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 18 Mar 2026 11:28:57 -0700 Subject: [PATCH 15/28] add handling for checkpoint error handling without --resume --- src/openfecli/commands/quickrun.py | 10 ++++++--- src/openfecli/tests/commands/test_quickrun.py | 22 ++++++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/openfecli/commands/quickrun.py b/src/openfecli/commands/quickrun.py index b469d3cd8..8ef5bc7ce 100644 --- a/src/openfecli/commands/quickrun.py +++ b/src/openfecli/commands/quickrun.py @@ -30,8 +30,9 @@ def _format_exception(exception) -> str: type=click.Path(dir_okay=False, file_okay=False, path_type=pathlib.Path), help="Filepath at which to create and write the JSON-formatted results.", ) # fmt: skip +@click.option("--resume", is_flag=True, default=False, help=("")) # TODO: add help msg @print_duration -def quickrun(transformation, work_dir, output): +def quickrun(transformation, work_dir, output, resume): """Run the transformation (edge) in the given JSON file. Simulation JSON files can be created with the @@ -99,13 +100,16 @@ def quickrun(transformation, work_dir, output): # Attempt to either deserialize or freshly create DAG trans_DAG_json = work_dir / f"{trans.key}-protocolDAG.json" - if trans_DAG_json.is_file(): + if resume and trans_DAG_json.is_file(): write(f"Attempting to resume execution using existing edges from '{trans_DAG_json}'") try: dag = ProtocolDAG.from_json(trans_DAG_json) except JSONDecodeError: - errmsg = f"Recovery failed, please remove {trans_DAG_json} and any results from your working directory before continuing to create a new protocol." + errmsg = f"Recovery failed, please remove {trans_DAG_json} and any results from your working directory before continuing to create a new protocol, or run without `--resume`." raise click.ClickException(errmsg) + elif not resume and trans_DAG_json.is_file(): + errmsg = f"Transformation has been started but is incomplete. Please remove {trans_DAG_json} and rerun, or resume execution using the ``--resume`` flag." + raise RuntimeError(errmsg) else: # Create the DAG instead and then serialize for later resuming write("Planning simulations for this edge...") diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index 9ca9f2557..59bcb0582 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -110,7 +110,8 @@ def test_quickrun_unit_error(): # protocol dag results maybe? -def test_quickrun_resume(json_file): +def test_quickrun_existing_checkpoint(json_file): + """In the default case where resume=False, if the checkpoint exists, quickrun should error out and not attempt to execute.""" trans = Transformation.from_json(json_file) dag = trans.create() @@ -118,20 +119,35 @@ def test_quickrun_resume(json_file): with runner.isolated_filesystem(): dag.to_json(f"{trans.key}-protocolDAG.json") result = runner.invoke(quickrun, [json_file]) + assert isinstance(result.exception, RuntimeError) + assert "Attempting to resume" not in result.output + + +def test_quickrun_resume_from_checkpoint(json_file): + trans = Transformation.from_json(json_file) + dag = trans.create() + + runner = CliRunner() + with runner.isolated_filesystem(): + dag.to_json(f"{trans.key}-protocolDAG.json") + result = runner.invoke(quickrun, [json_file, "--resume"]) assert_click_success(result) assert "Attempting to resume" in result.output -def test_quickrun_resume_json_invalid(json_file): +def test_quickrun_resume_invalid_checkpoint(json_file): """Fail if the output file doesn't load properly.""" trans = Transformation.from_json(json_file) runner = CliRunner() with runner.isolated_filesystem(): pathlib.Path(f"{trans.key}-protocolDAG.json").touch() - result = runner.invoke(quickrun, [json_file]) + result = runner.invoke(quickrun, [json_file, "--resume"]) assert result.exit_code == 1 assert "Attempting to resume" in result.output assert "Recovery failed" in result.stderr + + +# def test_quickrun_resume_missing_checkpoint(json_file): From 5cd437ed4c76f51741947de4e74b5cdf37f9641c Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 18 Mar 2026 11:42:38 -0700 Subject: [PATCH 16/28] clean up logic --- src/openfecli/commands/quickrun.py | 28 ++++++++++++------- src/openfecli/tests/commands/test_quickrun.py | 9 +++++- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/openfecli/commands/quickrun.py b/src/openfecli/commands/quickrun.py index 8ef5bc7ce..d6c828e07 100644 --- a/src/openfecli/commands/quickrun.py +++ b/src/openfecli/commands/quickrun.py @@ -3,6 +3,7 @@ import json import pathlib +import warnings import click @@ -100,17 +101,24 @@ def quickrun(transformation, work_dir, output, resume): # Attempt to either deserialize or freshly create DAG trans_DAG_json = work_dir / f"{trans.key}-protocolDAG.json" - if resume and trans_DAG_json.is_file(): - write(f"Attempting to resume execution using existing edges from '{trans_DAG_json}'") - try: - dag = ProtocolDAG.from_json(trans_DAG_json) - except JSONDecodeError: - errmsg = f"Recovery failed, please remove {trans_DAG_json} and any results from your working directory before continuing to create a new protocol, or run without `--resume`." - raise click.ClickException(errmsg) - elif not resume and trans_DAG_json.is_file(): - errmsg = f"Transformation has been started but is incomplete. Please remove {trans_DAG_json} and rerun, or resume execution using the ``--resume`` flag." - raise RuntimeError(errmsg) + if trans_DAG_json.is_file(): + if resume: + write(f"Attempting to resume execution using existing edges from '{trans_DAG_json}'") + try: + dag = ProtocolDAG.from_json(trans_DAG_json) + except JSONDecodeError: + errmsg = f"Recovery failed, please remove {trans_DAG_json} and any results from your working directory before continuing to create a new protocol, or run without `--resume`." + raise click.ClickException(errmsg) + else: + errmsg = f"Transformation has been started but is incomplete. Please remove {trans_DAG_json} and rerun, or resume execution using the ``--resume`` flag." + raise RuntimeError(errmsg) + else: + if resume: + warnings.warn( + f"No checkpoint found at {trans_DAG_json}! Starting new execution." + ) # TODO: make this clearer + # Create the DAG instead and then serialize for later resuming write("Planning simulations for this edge...") dag = trans.create() diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index 59bcb0582..0dba3f8f5 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -150,4 +150,11 @@ def test_quickrun_resume_invalid_checkpoint(json_file): assert "Recovery failed" in result.stderr -# def test_quickrun_resume_missing_checkpoint(json_file): +def test_quickrun_resume_missing_checkpoint(json_file): + """If --resume is passed but there's not checkpoint, just warn and keep going""" + runner = CliRunner() + with runner.isolated_filesystem(): + result = runner.invoke(quickrun, [json_file, "--resume"]) + + assert result.exit_code == 0 + # TODO: check for warning From 61a97b63a2b45c5f0c5c5fed0416a2c5aefb2f76 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 18 Mar 2026 12:08:40 -0700 Subject: [PATCH 17/28] check for warning --- src/openfecli/commands/quickrun.py | 5 ++--- src/openfecli/tests/commands/test_quickrun.py | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/openfecli/commands/quickrun.py b/src/openfecli/commands/quickrun.py index d6c828e07..a47d7c82b 100644 --- a/src/openfecli/commands/quickrun.py +++ b/src/openfecli/commands/quickrun.py @@ -115,9 +115,8 @@ def quickrun(transformation, work_dir, output, resume): else: if resume: - warnings.warn( - f"No checkpoint found at {trans_DAG_json}! Starting new execution." - ) # TODO: make this clearer + # TODO: make this message clearer + warnings.warn(f"No checkpoint found at {trans_DAG_json}! Starting new execution.") # Create the DAG instead and then serialize for later resuming write("Planning simulations for this edge...") diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index 0dba3f8f5..7b9391d48 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -154,7 +154,6 @@ def test_quickrun_resume_missing_checkpoint(json_file): """If --resume is passed but there's not checkpoint, just warn and keep going""" runner = CliRunner() with runner.isolated_filesystem(): - result = runner.invoke(quickrun, [json_file, "--resume"]) - - assert result.exit_code == 0 - # TODO: check for warning + with pytest.warns(): + result = runner.invoke(quickrun, [json_file, "--resume"]) + assert result.exit_code == 0 From 48ab9c8f9c9c4dc4252a1ecac0b77c4d4626bd8c Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 18 Mar 2026 15:15:23 -0700 Subject: [PATCH 18/28] add docs --- news/quickrun_resume.rst | 3 ++- src/openfecli/commands/quickrun.py | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/news/quickrun_resume.rst b/news/quickrun_resume.rst index 789c20df5..e53e52738 100644 --- a/news/quickrun_resume.rst +++ b/news/quickrun_resume.rst @@ -1,6 +1,7 @@ **Added:** -* ``openfe quickrun`` now stores ``protocolDAG`` information for each transformation, and will attempt to load this file to resume execution of an incomplete transformation. +* Added ``--resume`` flag to ``openfe quickrun``. + Quickrun now temporarily caches ``protocolDAG`` information and when used with the ``--resume`` flag, quickrun will attempt resume execution of an incomplete transformation. **Changed:** diff --git a/src/openfecli/commands/quickrun.py b/src/openfecli/commands/quickrun.py index a47d7c82b..7b8d692f4 100644 --- a/src/openfecli/commands/quickrun.py +++ b/src/openfecli/commands/quickrun.py @@ -31,7 +31,12 @@ def _format_exception(exception) -> str: type=click.Path(dir_okay=False, file_okay=False, path_type=pathlib.Path), help="Filepath at which to create and write the JSON-formatted results.", ) # fmt: skip -@click.option("--resume", is_flag=True, default=False, help=("")) # TODO: add help msg +@click.option( + "--resume", + is_flag=True, + default=False, + help=("Attempt to resume this transformation's execution using the cache."), +) @print_duration def quickrun(transformation, work_dir, output, resume): """Run the transformation (edge) in the given JSON file. @@ -115,7 +120,6 @@ def quickrun(transformation, work_dir, output, resume): else: if resume: - # TODO: make this message clearer warnings.warn(f"No checkpoint found at {trans_DAG_json}! Starting new execution.") # Create the DAG instead and then serialize for later resuming From 31a6589d5d747ffe83d87ff7a69e1ee2b039c15f Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 18 Mar 2026 15:34:24 -0700 Subject: [PATCH 19/28] make a cache dir --- src/openfecli/commands/quickrun.py | 3 ++- src/openfecli/tests/commands/test_quickrun.py | 20 +++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/openfecli/commands/quickrun.py b/src/openfecli/commands/quickrun.py index 7b8d692f4..15e099237 100644 --- a/src/openfecli/commands/quickrun.py +++ b/src/openfecli/commands/quickrun.py @@ -104,7 +104,7 @@ def quickrun(transformation, work_dir, output, resume): output.parent.mkdir(exist_ok=True, parents=True) # Attempt to either deserialize or freshly create DAG - trans_DAG_json = work_dir / f"{trans.key}-protocolDAG.json" + trans_DAG_json = work_dir / "quickrun_cache" / f"{trans.key}-protocolDAG.json" if trans_DAG_json.is_file(): if resume: @@ -125,6 +125,7 @@ def quickrun(transformation, work_dir, output, resume): # Create the DAG instead and then serialize for later resuming write("Planning simulations for this edge...") dag = trans.create() + pathlib.Path(work_dir, "quickrun_cache").mkdir(exist_ok=True) dag.to_json(trans_DAG_json) write("Starting the simulations for this edge...") diff --git a/src/openfecli/tests/commands/test_quickrun.py b/src/openfecli/tests/commands/test_quickrun.py index 7b9391d48..786e3a484 100644 --- a/src/openfecli/tests/commands/test_quickrun.py +++ b/src/openfecli/tests/commands/test_quickrun.py @@ -1,4 +1,5 @@ import json +import os import pathlib from importlib import resources from unittest import mock @@ -33,7 +34,9 @@ def test_quickrun(extra_args, json_file): assert "Here is the result" in result.output trans = Transformation.from_json(json_file) # checkpoint should be deleted when job is complete - assert not pathlib.Path(extra_args.get("-d", ""), f"{trans.key}-protocolDAG.json").exists() + assert not pathlib.Path( + extra_args.get("-d", ""), "quickrun_cache", f"{trans.key}-protocolDAG.json" + ).exists() if outfile := extra_args.get("-o"): assert pathlib.Path(outfile).exists() @@ -62,7 +65,9 @@ def test_quickrun_interrupted(extra_args, json_file): assert "Here is the result" not in result.output trans = Transformation.from_json(json_file) - assert pathlib.Path(extra_args.get("-d", ""), f"{trans.key}-protocolDAG.json").exists() + assert pathlib.Path( + extra_args.get("-d", ""), "quickrun_cache", f"{trans.key}-protocolDAG.json" + ).exists() def test_quickrun_output_file_exists(json_file): @@ -117,7 +122,8 @@ def test_quickrun_existing_checkpoint(json_file): runner = CliRunner() with runner.isolated_filesystem(): - dag.to_json(f"{trans.key}-protocolDAG.json") + pathlib.Path("quickrun_cache").mkdir() + dag.to_json(pathlib.Path("quickrun_cache", f"{trans.key}-protocolDAG.json")) result = runner.invoke(quickrun, [json_file]) assert isinstance(result.exception, RuntimeError) assert "Attempting to resume" not in result.output @@ -129,7 +135,8 @@ def test_quickrun_resume_from_checkpoint(json_file): runner = CliRunner() with runner.isolated_filesystem(): - dag.to_json(f"{trans.key}-protocolDAG.json") + pathlib.Path("quickrun_cache").mkdir() + dag.to_json(pathlib.Path("quickrun_cache", f"{trans.key}-protocolDAG.json")) result = runner.invoke(quickrun, [json_file, "--resume"]) assert_click_success(result) @@ -142,7 +149,8 @@ def test_quickrun_resume_invalid_checkpoint(json_file): runner = CliRunner() with runner.isolated_filesystem(): - pathlib.Path(f"{trans.key}-protocolDAG.json").touch() + pathlib.Path("quickrun_cache").mkdir() + pathlib.Path("quickrun_cache", f"{trans.key}-protocolDAG.json").touch() result = runner.invoke(quickrun, [json_file, "--resume"]) assert result.exit_code == 1 @@ -151,7 +159,7 @@ def test_quickrun_resume_invalid_checkpoint(json_file): def test_quickrun_resume_missing_checkpoint(json_file): - """If --resume is passed but there's not checkpoint, just warn and keep going""" + """If --resume is passed but there's not checkpoint, just warn and keep going.""" runner = CliRunner() with runner.isolated_filesystem(): with pytest.warns(): From f8ecc730240d63a397cfffa59d01b0ec02b6233f Mon Sep 17 00:00:00 2001 From: IAlibay Date: Thu, 19 Mar 2026 12:13:51 +0000 Subject: [PATCH 20/28] Update the CLI quickrun help info --- docs/guide/cli/cli_basics.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/guide/cli/cli_basics.rst b/docs/guide/cli/cli_basics.rst index 15e3faa6a..31a600d08 100644 --- a/docs/guide/cli/cli_basics.rst +++ b/docs/guide/cli/cli_basics.rst @@ -69,6 +69,8 @@ the subcommand name, e.g., ``openfe quickrun --help``, which returns exist, it will be created at runtime. -o PATH Filepath at which to create and write the JSON- formatted results. + --resume Attempt to resume this transformation's execution + using the cache. -h, --help Show this message and exit. For more details on various commands, see the :ref:`cli-reference`. From 10b42df2638886587e8991dbf95e8c4a7447b4c7 Mon Sep 17 00:00:00 2001 From: IAlibay Date: Thu, 19 Mar 2026 12:55:06 +0000 Subject: [PATCH 21/28] Add userguide documentation on how to use quickrun --- docs/guide/cli/index.rst | 1 + docs/guide/cli/quickrun.rst | 78 +++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 docs/guide/cli/quickrun.rst diff --git a/docs/guide/cli/index.rst b/docs/guide/cli/index.rst index 481492eb4..e3a565740 100644 --- a/docs/guide/cli/index.rst +++ b/docs/guide/cli/index.rst @@ -13,3 +13,4 @@ into non-Python workflows. .. toctree:: cli_basics cli_yaml + quickrun diff --git a/docs/guide/cli/quickrun.rst b/docs/guide/cli/quickrun.rst new file mode 100644 index 000000000..bf590d795 --- /dev/null +++ b/docs/guide/cli/quickrun.rst @@ -0,0 +1,78 @@ +.. _userguide_cli_quickrun: + +Using Quickrun +============== + +The ``openfe quickrun`` command executes a single alchemical Transformation. +This is currrently the primary way to execute Transformations after they +have been created during network planning. + + +Basic Usage +----------- + +To run a Transformation (``transformation.json``) and save results to ``results.json``: + +.. code:: none + + openfe quickrun transformation.json -d workdir/ -o workdir/results.json + +The ``-d`` / ``--work-dir`` flag controls where working files (checkpoints, +trajectory data, etc...) are written. If it is ommited, the current directory +will be used. + +The ``-o`` flag controls where the results file will be written. If it is omitted, +results are written to a file named ``_results.json`` in the working directory. + + +Resuming a halted Job +--------------------- + +When ``openfe quickrun`` starts, it saves a plan of the simulation to a +cache file before execution begins: + +.. code:: none + + /quickrun_cache/-protocolDAG.json + +This cache is automatically removed once the job completes successfully. + +If a job is interrupted (e.g. due to a wall-time limit, node failure, or +manual cancellation), you can resume the interrupted job by passing the ``--resume`` flag: + +.. code:: none + + openfe quickrun transformation.json -d workdir/ -o workdir/results.json --resume + +The planned simulation cache will be used to identify where in the simulation +process it is and, if supported by the Transformation Protocol, how to resume. + +**Note:** the same ``-d`` / ``--work-dir`` used in the original run +must be specified so that ``quickrun`` can locate the cache file. + +If you pass ``--resume`` but no cache file is found (e.g. the job never +started), the following warning is printed and a fresh execution begins: + +.. note:: + + If the cache file is corrupted (e.g. due to an incomplete write at + the moment of interruption), ``quickrun --resume`` will raise a + ``ClickException`` with instructions to remove the cache file and either + start fresh or rerun without ``--resume``. + + +If you do not pass the ``--resume`` flag, the code will detect the partially +complete transformation and prevent you from accidentally starting a duplicate +run. The following error will be raised: + +.. code:: none + + RuntimeError: Transformation has been started but is incomplete. Please + remove /quickrun_cache/-protocolDAG.json and rerun, or resume + execution using the ``--resume`` flag. + +See Also +-------- + +- :ref:`cli-reference` - full CLI reference for ``openfe quickrun`` +- :ref:`rbfe-cli-tutorial` - a tutorial on how to use the CLI to run hybrid topology relative binding free energy calculations. From 17aad5c6b8ca957d06a973745ff47e2e43396088 Mon Sep 17 00:00:00 2001 From: IAlibay Date: Thu, 19 Mar 2026 13:03:42 +0000 Subject: [PATCH 22/28] this reference instead? --- docs/guide/cli/quickrun.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/cli/quickrun.rst b/docs/guide/cli/quickrun.rst index bf590d795..e7b21891c 100644 --- a/docs/guide/cli/quickrun.rst +++ b/docs/guide/cli/quickrun.rst @@ -75,4 +75,4 @@ See Also -------- - :ref:`cli-reference` - full CLI reference for ``openfe quickrun`` -- :ref:`rbfe-cli-tutorial` - a tutorial on how to use the CLI to run hybrid topology relative binding free energy calculations. +- :ref:`rbfe_cli_tutorial` - a tutorial on how to use the CLI to run hybrid topology relative binding free energy calculations. From 7d870dc83849089bd3722066f91a71107dc297f4 Mon Sep 17 00:00:00 2001 From: IAlibay Date: Thu, 19 Mar 2026 13:50:05 +0000 Subject: [PATCH 23/28] fix things a little bit --- docs/guide/cli/quickrun.rst | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/guide/cli/quickrun.rst b/docs/guide/cli/quickrun.rst index e7b21891c..1b2312ff2 100644 --- a/docs/guide/cli/quickrun.rst +++ b/docs/guide/cli/quickrun.rst @@ -47,19 +47,28 @@ manual cancellation), you can resume the interrupted job by passing the ``--resu The planned simulation cache will be used to identify where in the simulation process it is and, if supported by the Transformation Protocol, how to resume. -**Note:** the same ``-d`` / ``--work-dir`` used in the original run -must be specified so that ``quickrun`` can locate the cache file. +.. note:: + + The same ``-d`` / ``--work-dir`` used in the original run + must be specified so that ``quickrun`` can locate the cache file. If you pass ``--resume`` but no cache file is found (e.g. the job never started), the following warning is printed and a fresh execution begins: -.. note:: +.. code:: none - If the cache file is corrupted (e.g. due to an incomplete write at - the moment of interruption), ``quickrun --resume`` will raise a - ``ClickException`` with instructions to remove the cache file and either - start fresh or rerun without ``--resume``. + No checkpoint found at /quickrun_cache/-protocolDAG.json! + Starting new execution. + +If the cache file is corrupted (e.g. due to an incomplete write at +the moment of interruption), ``quickrun --resume`` will raise a +``ClickException`` with instructions to remove the cache file and either +start fresh or rerun without ``--resume``: + +.. code:: none + Recovery failed, please remove /quickrun_cache/-protocolDAG.json + and any results from your working directory before continuing to create a new protocol, or run without `--resume`. If you do not pass the ``--resume`` flag, the code will detect the partially complete transformation and prevent you from accidentally starting a duplicate From 99ae3990ba3422dc7216bd441f96671fb6f4e78f Mon Sep 17 00:00:00 2001 From: IAlibay Date: Thu, 19 Mar 2026 13:55:50 +0000 Subject: [PATCH 24/28] update the title --- docs/guide/cli/quickrun.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/cli/quickrun.rst b/docs/guide/cli/quickrun.rst index 1b2312ff2..0ae607025 100644 --- a/docs/guide/cli/quickrun.rst +++ b/docs/guide/cli/quickrun.rst @@ -1,7 +1,7 @@ .. _userguide_cli_quickrun: -Using Quickrun -============== +Using Quickrun to execute Transformations +========================================= The ``openfe quickrun`` command executes a single alchemical Transformation. This is currrently the primary way to execute Transformations after they From 050103397e8c0f303088a3dfe44b07b86a6c2d25 Mon Sep 17 00:00:00 2001 From: Irfan Alibay Date: Mon, 23 Mar 2026 13:27:23 +0000 Subject: [PATCH 25/28] Update environment.yml --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index b4fdbe9c1..5b23e9065 100644 --- a/environment.yml +++ b/environment.yml @@ -53,7 +53,7 @@ dependencies: # Control blas/openmp threads - threadpoolctl - pip: - - git+https://github.com/OpenFreeEnergy/gufe@restart_execute + - git+https://github.com/OpenFreeEnergy/gufe@main - run_constrained: # drop this pin when handled upstream in espaloma-feedstock - smirnoff99frosst>=1.1.0.1 #https://github.com/openforcefield/smirnoff99Frosst/issues/109 From 9cf25a527694fc52c415c74b326790621a451a81 Mon Sep 17 00:00:00 2001 From: Irfan Alibay Date: Mon, 23 Mar 2026 13:28:16 +0000 Subject: [PATCH 26/28] Update docs/guide/cli/quickrun.rst Co-authored-by: Alyssa Travitz <31974495+atravitz@users.noreply.github.com> --- docs/guide/cli/quickrun.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/guide/cli/quickrun.rst b/docs/guide/cli/quickrun.rst index 0ae607025..02b72245f 100644 --- a/docs/guide/cli/quickrun.rst +++ b/docs/guide/cli/quickrun.rst @@ -61,9 +61,7 @@ started), the following warning is printed and a fresh execution begins: Starting new execution. If the cache file is corrupted (e.g. due to an incomplete write at -the moment of interruption), ``quickrun --resume`` will raise a -``ClickException`` with instructions to remove the cache file and either -start fresh or rerun without ``--resume``: +the moment of interruption), ``quickrun --resume`` will raise an error with instructions to rerun the simulation: .. code:: none From 3cd71214b1a9e04c9b760c5be92ffa41232dff1a Mon Sep 17 00:00:00 2001 From: Irfan Alibay Date: Mon, 23 Mar 2026 13:31:35 +0000 Subject: [PATCH 27/28] Update docs/guide/cli/quickrun.rst Co-authored-by: Alyssa Travitz <31974495+atravitz@users.noreply.github.com> --- docs/guide/cli/quickrun.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/cli/quickrun.rst b/docs/guide/cli/quickrun.rst index 02b72245f..ac915aeae 100644 --- a/docs/guide/cli/quickrun.rst +++ b/docs/guide/cli/quickrun.rst @@ -22,7 +22,7 @@ trajectory data, etc...) are written. If it is ommited, the current directory will be used. The ``-o`` flag controls where the results file will be written. If it is omitted, -results are written to a file named ``_results.json`` in the working directory. +results are written to a file named ``_results.json`` in the working directory, where `` is a unique identifier. Resuming a halted Job From 7690ac3defc83a3d14ad61b61035f441fe25e130 Mon Sep 17 00:00:00 2001 From: IAlibay Date: Mon, 23 Mar 2026 13:34:52 +0000 Subject: [PATCH 28/28] update for new name for now --- docs/guide/cli/quickrun.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/cli/quickrun.rst b/docs/guide/cli/quickrun.rst index 02b72245f..f42fb8409 100644 --- a/docs/guide/cli/quickrun.rst +++ b/docs/guide/cli/quickrun.rst @@ -33,7 +33,7 @@ cache file before execution begins: .. code:: none - /quickrun_cache/-protocolDAG.json + /quickrun_cache/-ProtocolDAG.json This cache is automatically removed once the job completes successfully.