diff --git a/cookbook/exploring_rbfe_settings.ipynb b/cookbook/exploring_rbfe_settings.ipynb
new file mode 100644
index 0000000..f8dfd72
--- /dev/null
+++ b/cookbook/exploring_rbfe_settings.ipynb
@@ -0,0 +1,1180 @@
+{
+ "cells": [
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "# Exploring Free Energy Protocol settings\n",
+ "\n",
+ "The free energy protocols implemented in **openfe** expose a number of simulation settings that can affect the convergence properties, accuracy and precision of an alchemical transformation. In this notebook, we will explore changes to common settings, advising when you might want to consider changing them and how to change them in practice. For a detailed explanation of the individual protocols and their limitations see the [protocol documentation](https://docs.openfree.energy/en/latest/guide/protocols/) and references therein.\n"
+ ],
+ "id": "a3f965b58be21979"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "id": "a1b2c3d4e5f60010",
+ "source": [
+ "## Sampling and performance settings\n",
+ "\n",
+ "This section covers the settings that affect sampling, computational cost and statistical precision.\n",
+ "\n",
+ "### Inspecting the default settings\n",
+ "\n",
+ "The default settings for each of the protocols are designed to be a starting point for most alchemical transformations with the goal of obtaining reliable estimates with sufficient precision for decision-making at a reasonable computational cost. However, depending on the specific system and transformation being studied, you may want to adjust these settings to improve convergence or precision of the estimation.\n",
+ "\n",
+ "The default settings for the `RelativeHybridTopologyProtocol` can be inspected by:"
+ ]
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:54:59.569668Z",
+ "start_time": "2026-10-26T16:54:45.868611Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "from openfe.protocols.openmm_rfe import RelativeHybridTopologyProtocol\n",
+ "\n",
+ "settings = RelativeHybridTopologyProtocol.default_settings()"
+ ],
+ "id": "5690f07a5cd27245",
+ "outputs": [],
+ "execution_count": 1
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "
\n",
+ "\n",
+ "\n",
+ "The main parameters that determine the amount of sampling performed and the associated seeing for the `RelativeHybridTopologyProtocol` are:\n",
+ "\n",
+ "| Setting | Description | Setting |\n",
+ "|---------------------------------|--------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|\n",
+ "| **Lambda windows** | The number of discrete alchemical states simulated during the transformation. | `lambda_settings.lambda_windows` |\n",
+ "| **Production length** | The length of the production simulation performed at each lambda window. | `simulation_settings.production_length` |\n",
+ "| **Equilibration length** | The length of the equilibration simulation performed at each lambda window using the selected multisate sampling method. | `simulation_settings.equilibration_length` | |\n",
+ "| **Protocol repeats** | The number of independent executions of the protocol. | `protocol_repeats` |\n",
+ "\n",
+ "The appropriate changes to these settings are transformation dependent with no single setting guaranteeing sufficient convergence or accuracy in general. These settings provide a good starting point to investigate the affect of additional sampling on the transformation under study however, but will also increase the computational cost of the simulation. The following example retrieves them from the default settings:"
+ ],
+ "id": "94c914fc75cd8fcb"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:54:59.598767Z",
+ "start_time": "2026-10-26T16:54:59.585534Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "n_repeats = settings.protocol_repeats\n",
+ "n_windows = settings.lambda_settings.lambda_windows\n",
+ "production_length = settings.simulation_settings.production_length\n",
+ "equilibration_length = settings.simulation_settings.equilibration_length\n",
+ "\n",
+ "print(f\"Lambda windows: {n_windows}\")\n",
+ "print(f\"Production length per state: {production_length.m} ns\")\n",
+ "print(f\"Equilibration length per state: {equilibration_length.m} ns\")\n",
+ "print(f\"Independent repeats: {n_repeats}\")"
+ ],
+ "id": "3c2f7354aadac1e8",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Lambda windows: 11\n",
+ "Production length per state: 5.0 ns\n",
+ "Equilibration length per state: 1.0 ns\n",
+ "Independent repeats: 3\n"
+ ]
+ }
+ ],
+ "execution_count": 2
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Estimating the computational cost of a protocol\n",
+ "\n",
+ "A useful first approximation of the computational cost of a protocol execution is the total simulation time in nanoseconds across all lambda windows and repeats. This can be calculated as:\n",
+ "\n",
+ "$$T_{total} = N_{repeats} \\times N_{\\lambda windows} \\times (T_{production} + T_{equilibration})$$\n",
+ "\n",
+ "So for the default settings the total simulation time is:"
+ ],
+ "id": "704d5dc6e6f895f5"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:54:59.633570Z",
+ "start_time": "2026-10-26T16:54:59.618645Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "def estimate_rbfe_cost(settings):\n",
+ " n_repeats = settings.protocol_repeats\n",
+ " n_windows = settings.lambda_settings.lambda_windows\n",
+ " production_length = settings.simulation_settings.production_length\n",
+ " equilibration_length = settings.simulation_settings.equilibration_length\n",
+ "\n",
+ " total_sim_time = n_repeats * n_windows * (production_length + equilibration_length)\n",
+ " return total_sim_time\n",
+ "total_sim_time_per_leg = estimate_rbfe_cost(settings)\n",
+ "print(f\"Total simulation time: {total_sim_time_per_leg.m} ns\")"
+ ],
+ "id": "fd81e13e2c34684d",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Total simulation time: 198.0 ns\n"
+ ]
+ }
+ ],
+ "execution_count": 3
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "The `RelativeHybridTopologyProtocol` simulates the complex and solvent legs of a transformation independently, so if both use the same settings the total cost of the protocol is double the above estimate. It is useful to consider this estimate when deciding how to adjust the settings for a given transformation."
+ ],
+ "id": "3ab10c2ecebbd3df"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Increasing simulation length\n",
+ "\n",
+ "This section describes how to increase the amount of production simulation performed at each lambda window and estimate the increase in computational cost.\n",
+ "\n",
+ "\n",
+ "Warning: Extending an existing simulation is not currently supported. Increasing the simulation length after running with default settings means the protocol must be run again from the start with the new settings, so the full cost of the longer simulation is incurred.\n",
+ "
\n",
+ "\n",
+ "#### When to increase simulation length\n",
+ "\n",
+ "Consider increasing the simulation length **before** running the simulation when:\n",
+ "\n",
+ "- The transformation involves large structural changes due to for example changes in formal charge. In this case you may also need to consider increasing the number of lambda windows see the next section for more information.\n",
+ "\n",
+ "Consider increasing the simulation length **after** running the simulation when:\n",
+ "\n",
+ "- Free-energy estimates differ substantially between independent repeats of the protocol.\n",
+ "- Forward and reverse free-energy estimates disagree by more than 1kbT at over 50% of the simulation length and their uncertainties do not overlap.[[1]](https://livecomsjournal.org/index.php/livecoms/article/view/v2i1e18378)\n",
+ "- A large number of samples are discarded during analysis due to the automated equilibration and decorrelation methods.\n",
+ "- Replica exchange or MBAR analysis indicates that alchemical overlap or replica mixing is poor between lambda windows, you may also need to consider increasing the number of lambda windows see the next section for more information.\n",
+ "\n",
+ "This information is available through the automated analysis performed by the protocols, see the list of available [analysis metrics](https://docs.openfree.energy/en/latest/guide/protocols/relativehybridtopology.html#analysis) for the `RelativeHybridTopologyProtocol` for more information. It is recommended to review the analysis metrics before deciding to increase the simulation length.\n",
+ "\n",
+ "#### When not to increase simulation length\n",
+ "\n",
+ "Increasing the simulation length is not always the best solution. For example changes to the number of lambda windows or the spacing between them can sometimes be more effective when you have poor overlap between neighboring lambda windows and in some cases a combination of both maybe required.\n",
+ "\n",
+ "#### Changing the production length\n",
+ "\n",
+ "The following example shows how to double the production simulation length for the `RelativeHybridTopologyProtocol` and then calculates the new total simulation time and compares it to the default settings.\n"
+ ],
+ "id": "771ba6b9"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:54:59.664664Z",
+ "start_time": "2026-10-26T16:54:59.647568Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "from openff.units import unit\n",
+ "\n",
+ "long_settings = RelativeHybridTopologyProtocol.default_settings()\n",
+ "\n",
+ "# Increase the production length to 10 ns\n",
+ "long_settings.simulation_settings.production_length = 10 * unit.nanoseconds\n",
+ "\n",
+ "# Create a new protocol object with the updated settings\n",
+ "protocol = RelativeHybridTopologyProtocol(settings=long_settings)\n",
+ "\n",
+ "print(f\"Updated production length: {protocol.settings.simulation_settings.production_length.m} ns\")\n",
+ "\n",
+ "# Calculate the new total simulation time\n",
+ "new_total_sim_time_per_leg = estimate_rbfe_cost(protocol.settings)\n",
+ "print(f\"New total simulation time: {new_total_sim_time_per_leg.m} ns\")\n",
+ "\n",
+ "# Print the percentage increase in total simulation time\n",
+ "percentage_increase = ((new_total_sim_time_per_leg - total_sim_time_per_leg) / total_sim_time_per_leg) * 100\n",
+ "print(f\"Percentage increase in total simulation time: {percentage_increase.m:.2f}%\")"
+ ],
+ "id": "2cf9b3c7030df36f",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Updated production length: 10 ns\n",
+ "New total simulation time: 363.0 ns\n",
+ "Percentage increase in total simulation time: 83.33%\n"
+ ]
+ }
+ ],
+ "execution_count": 4
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Increasing the number of lambda windows\n",
+ "\n",
+ "Increasing the number of lambda windows introduces additional intermediate alchemical states along the transformation which can make the pathway easier to traverse and improve the overlap between neighboring states. This section describes how to increase the number of lambda windows and estimate the increase in computational cost.\n",
+ "\n",
+ "#### When to increase the number of lambda windows\n",
+ "\n",
+ "Consider increasing the number of lambda windows **before** running the simulation when:\n",
+ "\n",
+ "- The transformation involves a change in net formal charge or substantial redistribution of charge, you may also need to consider increasing the simulation length see the previous section for more information.\n",
+ "\n",
+ "Consider increasing the number of lambda windows **after** running the simulation when:\n",
+ "\n",
+ "- Neighboring lambda windows have poor phase-space overlap or replica mixing statistics.\n",
+ "\n",
+ "#### When not to increase the number of lambda windows\n",
+ "\n",
+ "Increasing the number of lambda windows is not always the best solution, slow changes in ligand conformation or binding site rearrangements may require longer simulation lengths to achieve convergence.\n",
+ "\n",
+ "#### Changing the number of lambda windows\n",
+ "\n",
+ "The following example shows how to double the number of lambda windows for the `RelativeHybridTopologyProtocol` to `22` and calculates the new total simulation time. Note the `lambda_windows` setting only accepts integer values and generates a uniform spacing of lambda values between `0` and `1`.\n"
+ ],
+ "id": "3d6bb208a91ce6d0"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:54:59.695800Z",
+ "start_time": "2026-10-26T16:54:59.679630Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "more_window_settings = RelativeHybridTopologyProtocol.default_settings()\n",
+ "\n",
+ "# Increase the number of lambda windows to 22\n",
+ "more_window_settings.lambda_settings.lambda_windows = 22\n",
+ "# The number of replicas must be increased to match the number of lambda windows\n",
+ "more_window_settings.simulation_settings.n_replicas = 22\n",
+ "\n",
+ "# Create a new protocol object with the updated settings\n",
+ "protocol = RelativeHybridTopologyProtocol(settings=more_window_settings)\n",
+ "\n",
+ "print(f\"Updated number of lambda windows: {protocol.settings.lambda_settings.lambda_windows}\")\n",
+ "\n",
+ "# Calculate the new total simulation time\n",
+ "new_total_sim_time_per_leg = estimate_rbfe_cost(protocol.settings)\n",
+ "print(f\"New total simulation time: {new_total_sim_time_per_leg.m} ns\")\n",
+ "\n",
+ "# Print the percentage increase in total simulation time\n",
+ "percentage_increase = ((new_total_sim_time_per_leg - total_sim_time_per_leg) / total_sim_time_per_leg) * 100\n",
+ "print(f\"Percentage increase in total simulation time: {percentage_increase.m:.2f}%\")"
+ ],
+ "id": "a8a0a2b5ba644eb4",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Updated number of lambda windows: 22\n",
+ "New total simulation time: 396.0 ns\n",
+ "Percentage increase in total simulation time: 100.00%\n"
+ ]
+ }
+ ],
+ "execution_count": 5
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Increasing the number of independent repeats\n",
+ "\n",
+ "Independent repeats run the same protocol from the same input structures but different initial velocities. This is the default method used to quantify the uncertainty of a protocol estimate in **openfe** and is useful to assess the reproducibility of the result.\n",
+ "\n",
+ "#### When to run additional repeats\n",
+ "\n",
+ "Consider running additional repeats when:\n",
+ "\n",
+ "- You want to quantify the uncertainty of the free energy estimate beyond the estimator uncertainty provided by MBAR bootstrap resampling.\n",
+ "\n",
+ "#### When not to run additional repeats\n",
+ "\n",
+ "If the uncertainty of the free energy estimate is already sufficiently small for your application, additional repeats may not be necessary. The default number of repeats is designed to provide a reasonable estimate of uncertainty for most transformations.\n",
+ "\n",
+ "If the uncertainty is very large, it may be more efficient to investigate the cause of the variation between repeats which might be better effected by increasing the simulation length or number of lambda windows.\n",
+ "\n",
+ "#### Changing the number of repeats\n",
+ "\n",
+ "The following example shows how to increase the number of independent repeats per single execution of the `RelativeHybridTopologyProtocol` to `4`. Note that you can also execute the protocol multiple times with a single repeat per execution which may be more efficient, see the [quickrun guide](https://docs.openfree.energy/en/stable/guide/execution/quickrun_execution.html#parallel-execution-of-repeats-with-quickrun) for more information."
+ ],
+ "id": "26c868fb24073c7e"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:56:58.685724Z",
+ "start_time": "2026-10-26T16:56:58.647979Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "more_repeats_settings = RelativeHybridTopologyProtocol.default_settings()\n",
+ "\n",
+ "# Increase the number of independent repeats to 4\n",
+ "more_repeats_settings.protocol_repeats = 4\n",
+ "\n",
+ "# Create a new protocol object with the updated settings\n",
+ "protocol = RelativeHybridTopologyProtocol(settings=more_repeats_settings)\n",
+ "\n",
+ "print(f\"Updated number of independent repeats: {protocol.settings.protocol_repeats}\")\n",
+ "\n",
+ "# Calculate the new total simulation time\n",
+ "new_total_sim_time_per_leg = estimate_rbfe_cost(protocol.settings)\n",
+ "print(f\"New total simulation time: {new_total_sim_time_per_leg.m} ns\")\n",
+ "\n",
+ "# Print the percentage increase in total simulation time\n",
+ "percentage_increase = ((new_total_sim_time_per_leg - total_sim_time_per_leg) / total_sim_time_per_leg) * 100\n",
+ "print(f\"Percentage increase in total simulation time: {percentage_increase.m:.2f}%\")"
+ ],
+ "id": "8a8f3e35757daeb3",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Updated number of independent repeats: 4\n",
+ "New total simulation time: 264.0 ns\n",
+ "Percentage increase in total simulation time: 33.33%\n"
+ ]
+ }
+ ],
+ "execution_count": 19
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Increasing the equilibration length\n",
+ "\n",
+ "The equilibration simulation uses the selected multisate sampling method to relax the system at each lambda window before production sampling begins. No samples are collected during this phase for the final free energy estimate.\n",
+ "\n",
+ "#### When to increase the equilibration length\n",
+ "\n",
+ "Consider increasing the equilibration length **before** running the simulation when:\n",
+ "\n",
+ "- The input structures have large steric clashes or are far from equilibrium and the system is not expected to equilibrate quickly.\n",
+ "\n",
+ "#### When not to increase the equilibration length\n",
+ "\n",
+ "If the system is expected to require a long equilibration time, such those with membrane bound proteins, it may be more efficient to perform a longer equilibration simulation outside the protocol.\n",
+ "\n",
+ "#### Changing the equilibration length\n",
+ "\n",
+ "The following example shows how to double the equilibration simulation length for the `RelativeHybridTopologyProtocol` and calculates the new total simulation time. Note that the equilibration length is set per lambda window, so increasing it will increase the total simulation time linearly with the number of windows see increasing production length for more information on the associated cost."
+ ],
+ "id": "1c95943803da691e"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:56:13.313640Z",
+ "start_time": "2026-10-26T16:56:13.242224Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "more_equilibration_settings = RelativeHybridTopologyProtocol.default_settings()\n",
+ "\n",
+ "# Increase the equilibration length to 2 ns\n",
+ "more_equilibration_settings.simulation_settings.equilibration_length = 2 * unit.nanoseconds\n",
+ "\n",
+ "# Create a new protocol object with the updated settings\n",
+ "protocol = RelativeHybridTopologyProtocol(settings=more_equilibration_settings)\n",
+ "\n",
+ "print(f\"Updated equilibration length: {protocol.settings.simulation_settings.equilibration_length.m} ns\")\n",
+ "\n",
+ "# Calculate the new total simulation time\n",
+ "new_total_sim_time_per_leg = estimate_rbfe_cost(protocol.settings)\n",
+ "print(f\"New total simulation time: {new_total_sim_time_per_leg.m} ns\")\n",
+ "\n",
+ "# Print the percentage increase in total simulation time\n",
+ "percentage_increase = ((new_total_sim_time_per_leg - total_sim_time_per_leg) / total_sim_time_per_leg) * 100\n",
+ "print(f\"Percentage increase in total simulation time: {percentage_increase.m:.2f}%\")"
+ ],
+ "id": "5a8fdf02930d79e3",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Updated equilibration length: 2 ns\n",
+ "New total simulation time: 231.0 ns\n",
+ "Percentage increase in total simulation time: 16.67%\n"
+ ]
+ }
+ ],
+ "execution_count": 18
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Other protocols\n",
+ "\n",
+ "The above concepts apply to most free energy protocols, but the settings are located differently. Here we will briefly show how to inspect, and change them for the following protocols:\n",
+ " - `AbsoluteBindingProtocol`\n",
+ " - `SepTopProtocol`\n",
+ "\n",
+ "As these protocols execute both the `complex` and `solvent` legs within a single transformation the settings are exposed with `complex_*` or `solvent_*` prefix. In addition, each leg has a `*_equil_simulation_settings` object controlling an initial MD simulation used to determine the restraint atoms in the complex legs and seed the solvent legs, which is described in the cost estimate for the `AbsoluteBindingProtocol`."
+ ],
+ "id": "c1d2e3f4a5b60001"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### AbsoluteBindingProtocol\n",
+ "\n",
+ "#### Inspecting the default settings"
+ ],
+ "id": "c1d2e3f4a5b60002"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:55:01.234321Z",
+ "start_time": "2026-10-26T16:54:59.797756Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "from openfe.protocols.openmm_afe import AbsoluteBindingProtocol\n",
+ "\n",
+ "abfe_default_settings = AbsoluteBindingProtocol.default_settings()\n",
+ "\n",
+ "print(f\"Independent repeats: {abfe_default_settings.protocol_repeats}\")\n",
+ "for leg in [\"complex\", \"solvent\"]:\n",
+ " equil = getattr(abfe_default_settings, f\"{leg}_equil_simulation_settings\")\n",
+ " sampling = getattr(abfe_default_settings, f\"{leg}_simulation_settings\")\n",
+ "\n",
+ " print(f\"{leg}:\")\n",
+ " print(f\" Lambda windows: {sampling.n_replicas}\")\n",
+ " print(f\" Production length per state: {sampling.production_length.m} ns\")\n",
+ " print(f\" Equilibration length per state: {sampling.equilibration_length.m} ns\")\n",
+ " print(f\" Pre-equilibration MD length: {(equil.equilibration_length_nvt + equil.equilibration_length + equil.production_length).m} ns\")"
+ ],
+ "id": "a1b2c3d4e5f60002",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Independent repeats: 3\n",
+ "complex:\n",
+ " Lambda windows: 30\n",
+ " Production length per state: 10.0 ns\n",
+ " Equilibration length per state: 1 ns\n",
+ " Pre-equilibration MD length: 5.75 ns\n",
+ "solvent:\n",
+ " Lambda windows: 14\n",
+ " Production length per state: 10.0 ns\n",
+ " Equilibration length per state: 1.0 ns\n",
+ " Pre-equilibration MD length: 0.8 ns\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/Users/joshua/mambaforge/envs/openfe_dev/lib/python3.12/site-packages/Bio/Application/__init__.py:39: BiopythonDeprecationWarning: The Bio.Application modules and modules relying on it have been deprecated.\n",
+ "\n",
+ "Due to the on going maintenance burden of keeping command line application\n",
+ "wrappers up to date, we have decided to deprecate and eventually remove these\n",
+ "modules.\n",
+ "\n",
+ "We instead now recommend building your command line and invoking it directly\n",
+ "with the subprocess module.\n",
+ " warnings.warn(\n"
+ ]
+ }
+ ],
+ "execution_count": 8
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### Estimating the computational cost\n",
+ "\n",
+ "The `AbsoluteBindingProtocol` and `SepTopProtocol` have a different cost profile to the `RelativeHybridTopologyProtocol`. Each leg runs a single, longer MD equilibration (NVT equilibration, NPT equilibration and a production run). This simulation is run once per leg and repeat, **not** once per lambda window, so the total simulation time becomes:\n",
+ "\n",
+ "$$T_{total} = N_{repeats} \\times \\sum_{legs} \\left[ T_{MD} + N_{\\lambda windows} \\times (T_{production} + T_{equilibration}) \\right]$$\n",
+ "\n",
+ "where $T_{MD}$ is the sum of the NVT equilibration. The equilibration and production lengths of the leg's are set by `*_equil_simulation_settings`, and the remaining terms come from its `*_simulation_settings`."
+ ],
+ "id": "c1d2e3f4a5b60003"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:55:01.256057Z",
+ "start_time": "2026-10-26T16:55:01.237276Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "def estimate_abfe_septop_cost(settings):\n",
+ " abfe_total_sim_time = 0 * unit.nanoseconds\n",
+ " for leg in [\"complex\", \"solvent\"]:\n",
+ " equil = getattr(settings, f\"{leg}_equil_simulation_settings\")\n",
+ " sampling = getattr(settings, f\"{leg}_simulation_settings\")\n",
+ "\n",
+ " md_time = equil.equilibration_length_nvt + equil.equilibration_length + equil.production_length\n",
+ " replica_exchange_time = sampling.n_replicas * (sampling.production_length + sampling.equilibration_length)\n",
+ " leg_time = md_time + replica_exchange_time\n",
+ " print(f\"{leg}: {md_time.m} ns MD + {replica_exchange_time.m} ns replica exchange = {leg_time.m} ns\")\n",
+ " abfe_total_sim_time += leg_time\n",
+ "\n",
+ " abfe_total_sim_time *= settings.protocol_repeats\n",
+ " print(f\"Total simulation time: {abfe_total_sim_time.m:.2f} ns\")\n",
+ " return abfe_total_sim_time\n",
+ "\n",
+ "default_abfe_cost = estimate_abfe_septop_cost(abfe_default_settings)"
+ ],
+ "id": "0f8fb861",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "complex: 5.75 ns MD + 330.0 ns replica exchange = 335.75 ns\n",
+ "solvent: 0.8 ns MD + 154.0 ns replica exchange = 154.8 ns\n",
+ "Total simulation time: 1471.65 ns\n"
+ ]
+ }
+ ],
+ "execution_count": 9
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### Increasing simulation length\n",
+ "\n",
+ "The production length is set per leg, so each leg to be extended must be changed individually."
+ ],
+ "id": "c1d2e3f4a5b60004"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:55:01.289159Z",
+ "start_time": "2026-10-26T16:55:01.268956Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "from openfe.protocols.openmm_afe import AbsoluteBindingProtocol\n",
+ "\n",
+ "abfe_long_settings = AbsoluteBindingProtocol.default_settings()\n",
+ "\n",
+ "# Settings are per leg, so extend both legs\n",
+ "abfe_long_settings.complex_simulation_settings.production_length = 20 * unit.nanoseconds\n",
+ "abfe_long_settings.solvent_simulation_settings.production_length = 20 * unit.nanoseconds\n",
+ "\n",
+ "# Create a new protocol object with the updated settings\n",
+ "abfe_protocol = AbsoluteBindingProtocol(settings=abfe_long_settings)\n",
+ "\n",
+ "print(f\"Updated complex production length: {abfe_protocol.settings.complex_simulation_settings.production_length.m} ns\")\n",
+ "print(f\"Updated solvent production length: {abfe_protocol.settings.solvent_simulation_settings.production_length.m} ns\")\n",
+ "\n",
+ "# Calculate the new total simulation time\n",
+ "new_abfe_cost = estimate_abfe_septop_cost(abfe_long_settings)\n",
+ "\n",
+ "# Print the percentage increase in total simulation time\n",
+ "percentage_increase = ((new_abfe_cost - default_abfe_cost) / default_abfe_cost) * 100\n",
+ "print(f\"Percentage increase in total simulation time: {percentage_increase.m:.2f}%\")"
+ ],
+ "id": "e1a2b3c4d5f60718",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Updated complex production length: 20 ns\n",
+ "Updated solvent production length: 20 ns\n",
+ "complex: 5.75 ns MD + 630 ns replica exchange = 635.75 ns\n",
+ "solvent: 0.8 ns MD + 294.0 ns replica exchange = 294.8 ns\n",
+ "Total simulation time: 2791.65 ns\n",
+ "Percentage increase in total simulation time: 89.70%\n"
+ ]
+ }
+ ],
+ "execution_count": 10
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### Changing the number of lambda windows\n",
+ "\n",
+ "The `AbsoluteBindingProtocol` and `SepTopProtocol` both allow for exact control of the lambda schedule of each leg requiring an explicitly defined list of values, one entry per window. All of the lists for a leg must have the same length, and `n_replicas` in the matching `*_simulation_settings` must be set to the number of windows.\n",
+ "\n",
+ "The schedule is defined by `lambda_elec`, `lambda_vdw` and `lambda_restraints`."
+ ],
+ "id": "c1d2e3f4a5b60005"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:55:01.322404Z",
+ "start_time": "2026-10-26T16:55:01.304106Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "abfe_window_settings = AbsoluteBindingProtocol.default_settings()\n",
+ "\n",
+ "# Define the new lambda schedule for the complex leg by adding 3 more charge windows\n",
+ "abfe_window_settings.complex_lambda_settings.lambda_elec = [0.0] * 6 + [0.1, 0.2, 0.3, 0.4, 0.5, 0.55, 0.6, 0.65, 0.7, 0.8, 0.85, 0.9] + [1.0] * 15\n",
+ "# Extra vdW windows are added to match the length of the new charge schedule\n",
+ "abfe_window_settings.complex_lambda_settings.lambda_vdw = [0.0] * 19 + [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1.0]\n",
+ "# Extra restraint windows are added to match the length of the new charge schedule\n",
+ "abfe_window_settings.complex_lambda_settings.lambda_restraints = [0.0, 0.2, 0.4, 0.6, 0.8] + [1.0] * 28\n",
+ "\n",
+ "# The number of replicas must match the number of lambda windows\n",
+ "abfe_window_settings.complex_simulation_settings.n_replicas = len(abfe_window_settings.complex_lambda_settings.lambda_vdw)\n",
+ "\n",
+ "# Create a new protocol object with the updated settings\n",
+ "abfe_protocol = AbsoluteBindingProtocol(settings=abfe_window_settings)\n",
+ "\n",
+ "# Calculate the new total simulation time\n",
+ "new_abfe_cost = estimate_abfe_septop_cost(abfe_window_settings)\n",
+ "\n",
+ "# Print the percentage increase in total simulation time\n",
+ "percentage_increase = ((new_abfe_cost - default_abfe_cost) / default_abfe_cost) * 100\n",
+ "print(f\"Percentage increase in total simulation time: {percentage_increase.m:.2f}%\")"
+ ],
+ "id": "b1c2d3e4f5a60002",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "complex: 5.75 ns MD + 363.0 ns replica exchange = 368.75 ns\n",
+ "solvent: 0.8 ns MD + 154.0 ns replica exchange = 154.8 ns\n",
+ "Total simulation time: 1570.65 ns\n",
+ "Percentage increase in total simulation time: 6.73%\n"
+ ]
+ }
+ ],
+ "execution_count": 11
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "\n",
+ "Tip: Changing the number of lambda windows or the lambda schedule for the AbsoluteBindingProtocol and SepTopProtocol by hand can be error prone. The protocols provide a validation method to check that the lambda schedules are valid for the leg in question, use them to validate your changes and catch issues before execution. See the example below.\n",
+ "
"
+ ],
+ "id": "2290219ca360bbd3"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:55:01.357944Z",
+ "start_time": "2026-10-26T16:55:01.325925Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# Validate the lambda schedules for the complex leg\n",
+ "AbsoluteBindingProtocol._validate_lambda_schedule(\n",
+ " abfe_window_settings.complex_lambda_settings,\n",
+ " abfe_window_settings.complex_simulation_settings\n",
+ ")"
+ ],
+ "id": "6c9de9ae4d246d9c",
+ "outputs": [],
+ "execution_count": 12
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### Increasing the number of independent repeats\n",
+ "\n",
+ "The following example shows how to increase the number of independent repeats per single execution of the `AbsoluteBindingProtocol` to `4`. The number of repeats is shared by both legs, and every repeat runs the pre-equilibration MD simulation for each leg again, so the cost increases linearly with the number of repeats. As with the `RelativeHybridTopologyProtocol`, you can also execute the protocol multiple times with a single repeat per execution, see the [quickrun guide](https://docs.openfree.energy/en/stable/guide/execution/quickrun_execution.html#parallel-execution-of-repeats-with-quickrun) for more information."
+ ],
+ "id": "d1e2f3a4b5c6a01"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-27T09:21:28.528751Z",
+ "start_time": "2026-10-27T09:21:28.078100Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "abfe_repeats_settings = AbsoluteBindingProtocol.default_settings()\n",
+ "\n",
+ "# Increase the number of independent repeats to 4\n",
+ "abfe_repeats_settings.protocol_repeats = 4\n",
+ "\n",
+ "# Create a new protocol object with the updated settings\n",
+ "abfe_repeats_protocol = AbsoluteBindingProtocol(settings=abfe_repeats_settings)\n",
+ "\n",
+ "print(f\"Updated number of independent repeats: {abfe_repeats_protocol.settings.protocol_repeats}\")\n",
+ "\n",
+ "# Calculate the new total simulation time\n",
+ "new_abfe_cost = estimate_abfe_septop_cost(abfe_repeats_settings)\n",
+ "\n",
+ "# Print the percentage increase in total simulation time\n",
+ "percentage_increase = ((new_abfe_cost - default_abfe_cost) / default_abfe_cost) * 100\n",
+ "print(f\"Percentage increase in total simulation time: {percentage_increase.m:.2f}%\")"
+ ],
+ "id": "d1e2f3a4b5c6a02",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Updated number of independent repeats: 4\n",
+ "complex: 5.75 ns MD + 330.0 ns replica exchange = 335.75 ns\n",
+ "solvent: 0.8 ns MD + 154.0 ns replica exchange = 154.8 ns\n",
+ "Total simulation time: 1962.20 ns\n",
+ "Percentage increase in total simulation time: 33.33%\n"
+ ]
+ }
+ ],
+ "execution_count": 20
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### Increasing the equilibration length\n",
+ "\n",
+ "The `AbsoluteBindingProtocol` has two equilibration stages per leg: the initial MD simulation controlled by `*_equil_simulation_settings`, which is used to select the restraint atoms, and the equilibration performed at each lambda window before production sampling, controlled by `*_simulation_settings.equilibration_length`. The following example doubles the equilibration length at each lambda window for both legs. As the length is set per lambda window, the cost increases linearly with the number of windows."
+ ],
+ "id": "d1e2f3a4b5c6a03"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-27T09:22:18.217432Z",
+ "start_time": "2026-10-27T09:22:18.145871Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "abfe_equilibration_settings = AbsoluteBindingProtocol.default_settings()\n",
+ "\n",
+ "# Increase the equilibration length at each lambda window to 2 ns for both legs\n",
+ "abfe_equilibration_settings.complex_simulation_settings.equilibration_length = 2 * unit.nanoseconds\n",
+ "abfe_equilibration_settings.solvent_simulation_settings.equilibration_length = 2 * unit.nanoseconds\n",
+ "\n",
+ "# Create a new protocol object with the updated settings\n",
+ "abfe_equilibration_protocol = AbsoluteBindingProtocol(settings=abfe_equilibration_settings)\n",
+ "\n",
+ "print(f\"Updated complex equilibration length: {abfe_equilibration_protocol.settings.complex_simulation_settings.equilibration_length.m} ns\")\n",
+ "print(f\"Updated solvent equilibration length: {abfe_equilibration_protocol.settings.solvent_simulation_settings.equilibration_length.m} ns\")\n",
+ "\n",
+ "# Calculate the new total simulation time\n",
+ "new_abfe_cost = estimate_abfe_septop_cost(abfe_equilibration_settings)\n",
+ "\n",
+ "# Print the percentage increase in total simulation time\n",
+ "percentage_increase = ((new_abfe_cost - default_abfe_cost) / default_abfe_cost) * 100\n",
+ "print(f\"Percentage increase in total simulation time: {percentage_increase.m:.2f}%\")"
+ ],
+ "id": "d1e2f3a4b5c6a04",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Updated complex equilibration length: 2 ns\n",
+ "Updated solvent equilibration length: 2 ns\n",
+ "complex: 5.75 ns MD + 360.0 ns replica exchange = 365.75 ns\n",
+ "solvent: 0.8 ns MD + 168.0 ns replica exchange = 168.8 ns\n",
+ "Total simulation time: 1603.65 ns\n",
+ "Percentage increase in total simulation time: 8.97%\n"
+ ]
+ }
+ ],
+ "execution_count": 21
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### SepTopProtocol\n",
+ "\n",
+ "#### Inspecting the default settings"
+ ],
+ "id": "c1d2e3f4a5b60006"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:55:01.409452Z",
+ "start_time": "2026-10-26T16:55:01.359409Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "from openfe.protocols.openmm_septop import SepTopProtocol\n",
+ "\n",
+ "septop_default_settings = SepTopProtocol.default_settings()\n",
+ "\n",
+ "print(f\"Independent repeats: {septop_default_settings.protocol_repeats}\")\n",
+ "for leg in [\"complex\", \"solvent\"]:\n",
+ " equil = getattr(septop_default_settings, f\"{leg}_equil_simulation_settings\")\n",
+ " sampling = getattr(septop_default_settings, f\"{leg}_simulation_settings\")\n",
+ "\n",
+ " print(f\"{leg}:\")\n",
+ " print(f\" Lambda windows: {sampling.n_replicas}\")\n",
+ " print(f\" Production length per state: {sampling.production_length.m} ns\")\n",
+ " print(f\" Equilibration length per state: {sampling.equilibration_length.m} ns\")\n",
+ " print(f\" Pre-equilibration MD length: {(equil.equilibration_length_nvt + equil.equilibration_length + equil.production_length).m} ns\")"
+ ],
+ "id": "a1b2c3d4e5f60004",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Independent repeats: 3\n",
+ "complex:\n",
+ " Lambda windows: 19\n",
+ " Production length per state: 10.0 ns\n",
+ " Equilibration length per state: 1.0 ns\n",
+ " Pre-equilibration MD length: 2.2 ns\n",
+ "solvent:\n",
+ " Lambda windows: 27\n",
+ " Production length per state: 10.0 ns\n",
+ " Equilibration length per state: 1.0 ns\n",
+ " Pre-equilibration MD length: 2.2 ns\n"
+ ]
+ }
+ ],
+ "execution_count": 13
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### Estimating the computational cost\n",
+ "\n",
+ "The `SepTopProtocol` has the same cost profile as the `AbsoluteBindingProtocol`, so the `estimate_abfe_septop_cost` function defined above can be reused."
+ ],
+ "id": "c1d2e3f4a5b60007"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-27T09:22:33.285791Z",
+ "start_time": "2026-10-27T09:22:33.245289Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "default_septop_cost = estimate_abfe_septop_cost(septop_default_settings)",
+ "id": "85ed4076",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "complex: 2.2 ns MD + 209.0 ns replica exchange = 211.2 ns\n",
+ "solvent: 2.2 ns MD + 297.0 ns replica exchange = 299.2 ns\n",
+ "Total simulation time: 1531.20 ns\n"
+ ]
+ }
+ ],
+ "execution_count": 22
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### Increasing simulation length\n",
+ "\n",
+ "The production length is changed per leg in the same way as for the `AbsoluteBindingProtocol`."
+ ],
+ "id": "c1d2e3f4a5b60008"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-26T16:55:01.460079Z",
+ "start_time": "2026-10-26T16:55:01.438377Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "from openfe.protocols.openmm_septop import SepTopProtocol\n",
+ "\n",
+ "septop_long_settings = SepTopProtocol.default_settings()\n",
+ "\n",
+ "septop_long_settings.complex_simulation_settings.production_length = 20 * unit.nanoseconds\n",
+ "septop_long_settings.solvent_simulation_settings.production_length = 20 * unit.nanoseconds\n",
+ "\n",
+ "# Create a new protocol object with the updated settings\n",
+ "septop_protocol = SepTopProtocol(settings=septop_long_settings)\n",
+ "\n",
+ "print(f\"Updated complex production length: {septop_protocol.settings.complex_simulation_settings.production_length.m} ns\")\n",
+ "print(f\"Updated solvent production length: {septop_protocol.settings.solvent_simulation_settings.production_length.m} ns\")\n",
+ "\n",
+ "# Calculate the new total simulation time\n",
+ "new_septop_cost = estimate_abfe_septop_cost(septop_long_settings)\n",
+ "\n",
+ "# Print the percentage increase in total simulation time\n",
+ "percentage_increase = ((new_septop_cost - default_septop_cost) / default_septop_cost) * 100\n",
+ "print(f\"Percentage increase in total simulation time: {percentage_increase.m:.2f}%\")"
+ ],
+ "id": "a3c4d5e6f7b82930",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Updated complex production length: 20 ns\n",
+ "Updated solvent production length: 20 ns\n",
+ "complex: 2.2 ns MD + 399.0 ns replica exchange = 401.2 ns\n",
+ "solvent: 2.2 ns MD + 567.0 ns replica exchange = 569.2 ns\n",
+ "Total simulation time: 2911.20 ns\n",
+ "Percentage increase in total simulation time: 90.13%\n"
+ ]
+ }
+ ],
+ "execution_count": 15
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### Changing the number of lambda windows\n",
+ "\n",
+ "The schedule is defined by the `A` and `B` states of each component: `lambda_elec_A`, `lambda_elec_B`, `lambda_vdw_A`, `lambda_vdw_B`, `lambda_restraints_A` and `lambda_restraints_B`. Like the `AbsoluteBindingProtocol` all the lambda value lists for a leg must be the same length."
+ ],
+ "id": "c1d2e3f4a5b60009"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-27T09:22:52.631446Z",
+ "start_time": "2026-10-27T09:22:52.537454Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "septop_window_settings = SepTopProtocol.default_settings()\n",
+ "\n",
+ "# Define the new lambda schedule for the complex leg adding 3 more charge windows for A and B\n",
+ "septop_window_settings.complex_lambda_settings.lambda_elec_A = [0.0] * 8 + [0.25, 0.4, 0.5, 0.6, 0.75, 0.8] + [1.0] * 8\n",
+ "septop_window_settings.complex_lambda_settings.lambda_elec_B = [1.0] * 8 + [0.8, 0.75, 0.6, 0.5, 0.4, 0.25] + [0.0] * 8\n",
+ "septop_window_settings.complex_lambda_settings.lambda_vdw_A = [0.0] * 15 + [0.14, 0.28, 0.42, 0.57, 0.71, 0.85, 1.0]\n",
+ "septop_window_settings.complex_lambda_settings.lambda_vdw_B = [1.0, 0.85, 0.71, 0.57, 0.42, 0.28, 0.14] + [0.0] * 15\n",
+ "septop_window_settings.complex_lambda_settings.lambda_restraints_A = [0.0, 0.05, 0.1, 0.3, 0.5, 0.75] + [1.0] * 16\n",
+ "septop_window_settings.complex_lambda_settings.lambda_restraints_B = [1.0] * 16 + [0.75, 0.5, 0.3, 0.1, 0.05, 0.0]\n",
+ "\n",
+ "# The number of replicas must match the number of lambda windows\n",
+ "septop_window_settings.complex_simulation_settings.n_replicas = len(septop_window_settings.complex_lambda_settings.lambda_vdw_A)\n",
+ "\n",
+ "# Create a new protocol object with the updated settings\n",
+ "septop_protocol = SepTopProtocol(settings=septop_window_settings)\n",
+ "\n",
+ "# Calculate the new total simulation time\n",
+ "new_septop_cost = estimate_abfe_septop_cost(septop_window_settings)\n",
+ "\n",
+ "# Print the percentage increase in total simulation time\n",
+ "percentage_increase = ((new_septop_cost - default_septop_cost) / default_septop_cost) * 100\n",
+ "print(f\"Percentage increase in total simulation time: {percentage_increase.m:.2f}%\")"
+ ],
+ "id": "b1c2d3e4f5a60004",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "complex: 2.2 ns MD + 242.0 ns replica exchange = 244.2 ns\n",
+ "solvent: 2.2 ns MD + 297.0 ns replica exchange = 299.2 ns\n",
+ "Total simulation time: 1630.20 ns\n",
+ "Percentage increase in total simulation time: 6.47%\n"
+ ]
+ }
+ ],
+ "execution_count": 23
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-27T09:22:53.372160Z",
+ "start_time": "2026-10-27T09:22:53.350053Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# Validate the lambda schedules for the complex leg\n",
+ "SepTopProtocol._validate_lambda_schedule(\n",
+ " septop_window_settings.complex_lambda_settings,\n",
+ " septop_window_settings.complex_simulation_settings\n",
+ ")"
+ ],
+ "id": "32e3f17b3a1e849",
+ "outputs": [],
+ "execution_count": 24
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### Increasing the number of independent repeats\n",
+ "\n",
+ "The following example shows how to increase the number of independent repeats per single execution of the `SepTopProtocol` to `4`. The number of repeats is shared by both legs, and every repeat runs the pre-equilibration MD simulation for each leg again, so the cost increases linearly with the number of repeats. As with the `RelativeHybridTopologyProtocol`, you can also execute the protocol multiple times with a single repeat per execution, see the [quickrun guide](https://docs.openfree.energy/en/stable/guide/execution/quickrun_execution.html#parallel-execution-of-repeats-with-quickrun) for more information."
+ ],
+ "id": "d1e2f3a4b5c6b01"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-27T09:23:15.668621Z",
+ "start_time": "2026-10-27T09:23:15.596538Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "septop_repeats_settings = SepTopProtocol.default_settings()\n",
+ "\n",
+ "# Increase the number of independent repeats to 4\n",
+ "septop_repeats_settings.protocol_repeats = 4\n",
+ "\n",
+ "# Create a new protocol object with the updated settings\n",
+ "septop_repeats_protocol = SepTopProtocol(settings=septop_repeats_settings)\n",
+ "\n",
+ "print(f\"Updated number of independent repeats: {septop_repeats_protocol.settings.protocol_repeats}\")\n",
+ "\n",
+ "# Calculate the new total simulation time\n",
+ "new_septop_cost = estimate_abfe_septop_cost(septop_repeats_settings)\n",
+ "\n",
+ "# Print the percentage increase in total simulation time\n",
+ "percentage_increase = ((new_septop_cost - default_septop_cost) / default_septop_cost) * 100\n",
+ "print(f\"Percentage increase in total simulation time: {percentage_increase.m:.2f}%\")"
+ ],
+ "id": "d1e2f3a4b5c6b02",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Updated number of independent repeats: 4\n",
+ "complex: 2.2 ns MD + 209.0 ns replica exchange = 211.2 ns\n",
+ "solvent: 2.2 ns MD + 297.0 ns replica exchange = 299.2 ns\n",
+ "Total simulation time: 2041.60 ns\n",
+ "Percentage increase in total simulation time: 33.33%\n"
+ ]
+ }
+ ],
+ "execution_count": 25
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "#### Increasing the equilibration length\n",
+ "\n",
+ "The `SepTopProtocol` has two equilibration stages per leg: the initial MD simulation controlled by `*_equil_simulation_settings`, which is used to select the restraint atoms, and the equilibration performed at each lambda window before production sampling, controlled by `*_simulation_settings.equilibration_length`. The following example doubles the equilibration length at each lambda window for both legs. As the length is set per lambda window, the cost increases linearly with the number of windows."
+ ],
+ "id": "d1e2f3a4b5c6b03"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-10-27T09:23:20.742926Z",
+ "start_time": "2026-10-27T09:23:20.671284Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "septop_equilibration_settings = SepTopProtocol.default_settings()\n",
+ "\n",
+ "# Increase the equilibration length at each lambda window to 2 ns for both legs\n",
+ "septop_equilibration_settings.complex_simulation_settings.equilibration_length = 2 * unit.nanoseconds\n",
+ "septop_equilibration_settings.solvent_simulation_settings.equilibration_length = 2 * unit.nanoseconds\n",
+ "\n",
+ "# Create a new protocol object with the updated settings\n",
+ "septop_equilibration_protocol = SepTopProtocol(settings=septop_equilibration_settings)\n",
+ "\n",
+ "print(f\"Updated complex equilibration length: {septop_equilibration_protocol.settings.complex_simulation_settings.equilibration_length.m} ns\")\n",
+ "print(f\"Updated solvent equilibration length: {septop_equilibration_protocol.settings.solvent_simulation_settings.equilibration_length.m} ns\")\n",
+ "\n",
+ "# Calculate the new total simulation time\n",
+ "new_septop_cost = estimate_abfe_septop_cost(septop_equilibration_settings)\n",
+ "\n",
+ "# Print the percentage increase in total simulation time\n",
+ "percentage_increase = ((new_septop_cost - default_septop_cost) / default_septop_cost) * 100\n",
+ "print(f\"Percentage increase in total simulation time: {percentage_increase.m:.2f}%\")"
+ ],
+ "id": "d1e2f3a4b5c6b04",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Updated complex equilibration length: 2 ns\n",
+ "Updated solvent equilibration length: 2 ns\n",
+ "complex: 2.2 ns MD + 228.0 ns replica exchange = 230.2 ns\n",
+ "solvent: 2.2 ns MD + 324.0 ns replica exchange = 326.2 ns\n",
+ "Total simulation time: 1669.20 ns\n",
+ "Percentage increase in total simulation time: 9.01%\n"
+ ]
+ }
+ ],
+ "execution_count": 26
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 2
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython2",
+ "version": "2.7.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}