All workflows

Drug discovery

DrugFlow + Boltz-2 Affinity on HPC or Any Cloud

Generate pocket ligands with DrugFlow, score each with Boltz-2 affinity, rank by ΔG. The GPU stage runs remotely; prep and ranking stay local.

DrugFlowBoltz-2SingularitySlurmRDKitBiopythonuv

What this workflow does

This workflow takes a target protein in PDB format and a reference ligand. The reference ligand defines the binding pocket. DrugFlow generates novel drug-like molecules for that pocket. Boltz-2 then scores every generated molecule for binding affinity. The workflow returns a ΔG table ranked best first.

It is a full de-novo design loop: propose, then score. The generation step is structure-based, so the molecules are built for the pocket you give it, not pulled from a library.

The ΔG column is an approximation. Boltz-2 reports affinity_pred_value as about log10(IC50) with IC50 in µM. Treating IC50 as a Kd proxy at 298 K gives ΔG ≈ 1.364 · (affinity_pred_value − 6) in kcal/mol. Use it for ranking candidates, not as an exact free energy.

The compute problem

The pipeline has six stages and they do not want the same hardware.

Two setup stages only fetch data. One downloads the DrugFlow checkpoint, about 170 MB, from Zenodo. The other clones the DrugFlow source at a pinned commit. DrugFlow is not published as a package, so the code has to come from a checkout even when the environment is already built. Both stages need one CPU and a network connection, and both are skipped once their output exists.

The generation stage wants a GPU. It runs DrugFlow's generate.py over the pocket, and it is the stage that defines the hardware bill.

The next three stages are CPU work. One extracts the target sequence from the PDB and writes one Boltz affinity YAML per molecule. One runs boltz predict over the whole input folder. One parses the affinity JSON files, converts each to ΔG and sorts the table.

The dependency sets also conflict. DrugFlow needs its own environment, and its conda recipe has no osx-arm64 solution: pyg=2.5.1, ProDy=2.4.0 and pytorch=2.2.1 have no arm64 builds. The prep stage needs RDKit and Biopython. The predict stage needs boltz. The ranking stage needs nothing but a stdlib python3. Putting all of that in one environment is a solve that does not resolve.

How Horus solves it

Horus assigns an executor and a target to each stage. The generation stage uses the singularity executor on a slurm target, so the container runs inside an sbatch job with gres: gpu:1. The other five stages run locally. The prep and predict stages each get their own uv environment, so RDKit, Biopython and boltz never have to coexist.

The .sif image is built once, off-workflow, from the published DrugFlow Docker image. The executor has no build or pull step, so the image must already exist on the target. Its path is declared in the artifacts: block as drugflow_sif, next to the protein and the reference ligand, and the generation task reads it as ${drugflow_sif}. The default is relative, so it resolves in the run directory. Point it at an absolute path to share one build across runs. Every path an operator has to set lives in one block.

Singularity does no path translation. A bound host path is visible inside the container at the same location, so every ${...} in the command is valid on both sides and needs no rewriting. The executor auto-binds each input and output artifact's parent directory, which is how the DrugFlow checkout becomes visible to the container that supplies only its dependencies.

To move a stage to another machine, change the executor: field. The runtime.command string does not change. Horus moves the data across each boundary for you.

Pipeline

download_checkpoint (local, CPU)   Zenodo                 ──► drugflow.ckpt
fetch_source        (local, CPU)   pinned commit          ──► drugflow_src/
generate            (GPU, .sif)    protein + ref ligand   ──► results/samples.sdf
prepare_inputs      (local, CPU)   RDKit + Biopython      ──► boltz_inputs/ + smiles.json
predict             (local, CPU)   boltz predict          ──► predictions/affinity_*.json
rank                (local, CPU)   affinity → ΔG, sort    ──► results/deltaG_table.csv

Inputs and outputs

Inputs

  • examples/kras.pdb: the target protein structure. The prep stage takes the sequence of its longest protein chain.
  • examples/kras_ref_ligand.sdf: the reference ligand. Its binding site is the pocket DrugFlow designs into.

Outputs land in horus_workflow_results/:

  • drugflow.ckpt: the checkpoint the workflow downloaded.
  • drugflow_src/: the DrugFlow source at the pinned commit.
  • results/samples.sdf: the generated molecules.
  • results/boltz_inputs/: one Boltz affinity YAML per molecule.
  • results/smiles.json: a {molecule_name: canonical_smiles} map.
  • results/predictions/: one affinity_*.json per molecule.
  • results/deltaG_table.csv: columns molecule, smiles, affinity_pred_value, binding_probability, deltaG_kcal_per_mol, sorted by ascending ΔG.

A lower ΔG value means stronger predicted binding.

Each molecule's YAML is named after the molecule, because the YAML stem becomes the Boltz prediction name and is the key the ranking stage joins against.

Run the workflow

Install the horus-runtime and the plugins one time:

uv sync

If you do not have uv, install it first:

curl -LsSf https://astral.sh/uv/install.sh | sh

You can also install the packages with pip:

pip install horus-runtime horus-environments horus-singularity horus-slurm

Build the .sif image once on the cluster, then run the workflow from a login node where sbatch and singularity (or apptainer) are reachable:

horus run workflow.yaml

To set the number of molecules, edit the command: of the generate stage. --n_samples defaults to 10 and --batch_size to 32. The --pocket_distance_cutoff value defaults to 8.0 ångström. Add --molecule_size for a maximum atom count or a range, --n_steps to override the denoising default, and --filter to resample until the quality filters pass.

For a CPU-only generation run, set the executor's nv: false and --device cpu. The two have to agree, or the container either cannot see the driver stack or does not ask for it.

The predict stage defaults to --accelerator cpu so it runs anywhere. It also passes --use_msa_server, which is the public ColabFold server, so the machine running that stage needs outbound network access and is subject to that server's rate limits. Both the generation and the prediction stages are slow on CPU.

References

Run this workflow

The workflow is open source. Clone the pantheon repository and run it with the horus-runtime engine. To run it on managed compute without a cluster of your own, open it in Temple Compute OS.