RBFE¶
This document describes how to run a relative binding free energy (RBFE) workflow using Deep Origin tools.
RBFE compares two ligands bound to the same protein and reports the difference in their binding free energies (ΔΔG). Run it across a series of related ligands to rank them relative to one another before committing to synthesis.
With the RBFE class, you choose what to run by what you pass in; the
workflow steps are inferred for you:
| You pass | Inferred steps |
Use when |
|---|---|---|
ligands=[...] (+ protein) |
["konnektor", "system-prep", "rbfe"] |
You have a congeneric series (each ligand must already have a docked pose in the binding site — see Docking) and want the workflow to plan the pairwise network for you |
pairs=[(a, b), ...] (+ protein) |
["system-prep", "rbfe"] |
You already know which ligand pairs you want to compare |
prepared_systems=[...] |
["rbfe"] |
You've already prepared systems with SystemPrep and only want the free energy perturbation (FEP) calculation |
Add exp_abfe and/or fep_abfe anchors to any of the above to also append a
cycle-closure step, which converts pairwise ΔΔG into per-ligand absolute dG.
You may pass exactly one of ligands, pairs, or prepared_systems.
Prerequisites¶
We assume you have a protein and two or more ligands you want to compare. In this tutorial we use a protein and ligands from the BRD4 example dataset.
from deeporigin.drug_discovery import (
BRD_DATA_DIR,
Ligand,
Protein,
RBFE,
)
protein = Protein.from_file(BRD_DATA_DIR / "brd.pdb")
protein.sync()
ligand1 = Ligand.from_sdf(BRD_DATA_DIR / "brd-2.sdf")
ligand1.sync()
ligand2 = Ligand.from_sdf(BRD_DATA_DIR / "brd-3.sdf")
ligand2.sync()
sync() uploads each structure and registers it on the data platform. This is
required: RBFE references proteins and ligands by their platform IDs and file
paths. For more details, see Getting Started.
Ways of running¶
There are three ways to run RBFE, one for each input you can pass (see the table above). Pick the one that matches what you already have.
Comparing specific pairs¶
If you already know which ligands to compare, pass them as pairs. Each tuple is
one ΔΔG calculation. The workflow prepares each pair and runs FEP:
rbfe = RBFE(
protein=protein,
pairs=[(ligand1, ligand2)],
)
rbfe.start(quote=True)
rbfe.estimate
Add more tuples to compare more pairs in a single execution (up to 20 pairs).
Building a network automatically¶
For a congeneric series, pass the whole ligand set and let the workflow plan the pairwise edges for you with Konnektor, then prep and run FEP on each edge:
from deeporigin.drug_discovery import LigandSet
ligands = LigandSet.from_dir(BRD_DATA_DIR)
for ligand in ligands:
ligand.sync()
rbfe = RBFE(
protein=protein,
ligands=ligands,
network_type="mst",
)
rbfe.start(quote=True)
rbfe.estimate
network_type controls how pairs are chosen:
"mst"(default) — a minimum spanning tree: the fewest edges needed to connect every ligand. Cheapest, but no redundancy."star"— every ligand connected to one central reference."cyclic"— adds redundant edges so you can check consistency around cycles (see cycle closure).
To preview the network before submitting, or to run Konnektor on its own, see RBFE tools.
Reusing prepared systems¶
If you already prepared systems (for example with SystemPrep, or from a prior
run), skip prep and submit FEP only:
rbfe = RBFE(
prepared_systems=[prepared_system_1, prepared_system_2],
)
rbfe.start(quote=True)
rbfe.estimate
Prepare a single pair separately with SystemPrep(protein=..., ligand1=...,
ligand2=...).run() — see SystemPrep.
Converting to absolute binding free energy¶
RBFE gives you relative values (ΔΔG between pairs). To turn these into
absolute per-ligand values (dG), anchor the network with at least one known
absolute value and append cycle closure. Provide the anchor as fep_abfe
(computed by absolute binding free energy (ABFE)) and/or exp_abfe
(measured experimentally):
anchor = [{"ligand_id": ligand1.id, "dG": -10.0}]
rbfe = RBFE(
protein=protein,
ligands=ligands,
network_type="mst",
fep_abfe=anchor,
)
assert rbfe.steps[-1] == "cycle-closure"
Passing an anchor appends cycle-closure to steps automatically. Each anchor
entry needs a string ligand_id and a numeric dG (in kcal/mol). Read the
per-ligand results back with get_cycle_closure_results() (see
Results).
Estimating costs¶
Before starting, quote the run to see the estimated cost in USD. start(quote=True)
works for any of the entry points above:
rbfe.start(quote=True)
rbfe.estimate
Cost scales with the number of pairs (or network edges) and the simulation length, so quoting is especially worthwhile for large networks or production runs.
Starting a run¶
Confirm the quoted price to start the execution:
rbfe.confirm()
Then monitor progress. In a notebook:
task = await rbfe.watch()
You will see a live progress widget similar to this. RBFE runs as a workflow, so
the widget shows a tree of steps rather than a single bar: the network is planned
(konnektor), then each pair is prepared and simulated, and finally the results
are combined. As the run progresses, more steps appear and turn green:
To reconnect to a run in a later session, rehydrate it with
RBFE.from_id("<executionId>") or RBFE.from_last_run().
Parameters¶
RBFE simulation parameters are controlled via the RBFEParams dataclass. Defaults
are tuned for production runs; most users only adjust a few fields.
RBFEParams shares the same fields as ABFEParams, with
lower window defaults suited to relative calculations (binding_n_windows=24 and
solvation_n_windows=24). The field you are most likely to change:
repeats— number of independent repeats; more repeats tighten error estimates.
RBFEParams is a frozen dataclass. Use dataclasses.replace() to build a
modified copy and pass it to the constructor:
from dataclasses import replace
from deeporigin.drug_discovery import RBFEParams
params = replace(RBFEParams(), repeats=3, temperature=300)
params
Fields changed from their defaults are marked with an asterisk (*) when printed.
Changing parameters may lead to simulation failures
Some parameters, such as dt, are constrained to specific ranges. Runs with
out-of-range values are rejected, and moving parameters away from their
defaults may cause simulations to fail.
Results¶
After a run completes, fetch the pairwise ΔΔG summary:
df = rbfe.get_results()
df
This returns one row per pair:
| protein_id | ligand1_id | ligand2_id | ddG |
|---|---|---|---|
| prot-... | lig-... | lig-... | -1.23 kcal/mol |
If you ran cycle closure, also fetch the per-ligand absolute dG:
rbfe.get_cycle_closure_results()
| ligand_id | dG | unit | cluster |
|---|---|---|---|
| lig-... | -9.9 | kcal/mol | 0 |
To inspect a prepared system or plan/visualize a network, see RBFE tools.
Additional resources¶
- RBFE tools — inspecting networks, prepared systems, and results
- RBFE reference — generated API docs
- ABFE tutorial — absolute binding free energy, used to anchor cycle closure