aubreyagub/protein-ligand-screening-pipeline

Python

0

16 commits

updated Jun 12, 2026

See the code

README

protein-ligand-screening-pipeline

Ligand Pipeline: DrugCLIP → ExplainBind → Boltz-2

A three-stage protein–ligand pipeline:

  1. DrugCLIP (optional, owned by teammates) — virtual-screens a library and shortlists candidate ligands for a target.
  2. ExplainBind — scores each candidate against the target protein and predicts the binding probability and the binding-site residues (the pocket).
  3. Boltz-2 — folds each ligand into the ExplainBind-defined pocket (as a Boltz pocket constraint) and predicts the binding affinity.

Stages run as subprocesses from src/main.py and hand off via JSON files under ./outputs/:

outputs/drugclip_out.json   (optional, from DrugCLIP)   ->  ExplainBind
outputs/eb_out.json         (ExplainBind -> Boltz: binding prob + pocket residues)

The InteractBind dataset is pulled automatically from Hugging Face (Zhaohan-Meng/InteractBind) on first run — no manual download needed.


Repository layout

src/
  main.py            # orchestrator: runs the 3 stages, --smoke for a fast test
  explain_bind.py    # ExplainBind stage -> outputs/eb_out.json
  boltz.py           # Boltz stage      -> reads eb_out.json, predicts affinity
outputs/             # all generated artifacts (created on demand)

The three model code bases are third-party clones (not committed — see Setup): boltz/, Uni-Core/, ExplainBind_demo/, DrugCLIP_screen_pipeline/.


Setup

All three tools share a single conda env named pipeline (Python 3.10).

Python version matters: use Python 3.10–3.12. Boltz pins >=3.10,<3.13, so a 3.13+ env cannot install the Boltz stage (ExplainBind alone will still import, which makes the mismatch easy to miss until Boltz fails).

Prerequisites

This project expects a Conda-compatible environment manager. On macOS, Miniforge is recommended.

Check whether Conda is available:

conda --version

### 1. Clone the model repositories (into the project root)

```bash
# Boltz-2
git clone https://github.com/jwohlwend/boltz.git

# Uni-Core (DrugCLIP backbone)
git clone https://github.com/dptech-corp/Uni-Core.git

# DrugCLIP (teammates' stage)
git clone https://github.com/THU-ATOM/DrugCLIP_screen_pipeline.git

# ExplainBind demo (model code + trained checkpoint)
GIT_LFS_SKIP_SMUDGE=1 git clone \
  https://huggingface.co/spaces/Zhaohan-Meng/ExplainBind ExplainBind_demo

2. Create the env and install (order matters — Boltz pins first)

conda create -y -n pipeline python=3.10
conda activate pipeline

# Boltz first (it has the strictest version pins)
pip install -e ./boltz

# ExplainBind stack
pip install "transformers==4.51.3" selfies matplotlib datasets \
            accelerate sentencepiece lmdb "numpy<2"

# DrugCLIP extras + Uni-Core (CPU build)
pip install ml_collections tensorboardX ipython "numpy<2"
KMP_DUPLICATE_LIB_OK=TRUE pip install ./Uni-Core --no-build-isolation

# macOS: rdkit and torch both ship libomp; make the workaround persistent
conda env config vars set -n pipeline KMP_DUPLICATE_LIB_OK=TRUE
conda activate pipeline   # re-activate so the env var takes effect

3. Download the trained ExplainBind checkpoint

The GIT_LFS_SKIP_SMUDGE clone leaves the checkpoint as a pointer; fetch the real file (works without git-lfs):

python - <<'PY'
import shutil, os
from huggingface_hub import hf_hub_download
p = hf_hub_download(repo_id="Zhaohan-Meng/ExplainBind", repo_type="space",
                    filename="save_model_ckp/fasta_selfies/best_model.ckpt")
dst = "ExplainBind_demo/save_model_ckp/fasta_selfies/best_model.ckpt"
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy(p, dst)
print("checkpoint ->", dst)
PY

Usage

conda activate pipeline

python src/main.py --smoke              # tiny target + 1 ligand, fast end-to-end check
python src/main.py                      # default dataset (InteractBind), device auto
python src/main.py --device cuda        # force GPU (see "Device" below)
python src/main.py --dataset my.csv     # your own dataset (see "Choosing the dataset")

Device (CPU / GPU)

--device selects the compute device, default auto (CUDA if available, else CPU):

--deviceExplainBindBoltz --accelerator
autoCUDA else CPUgpu if CUDA else cpu
cpuCPUcpu
cuda/gpuCUDA (falls back to CPU if absent)gpu (falls back to CPU if absent)
mpsApple MPScpu (Boltz has no MPS backend)

On a CUDA machine just use the default. On CPU, Boltz is slow for large proteins (hours) — keep the protein short or use --smoke for local tests.

Choosing the dataset

--dataset selects where the target protein + candidate ligands come from (it feeds the ExplainBind stage; Boltz receives everything via eb_out.json). It accepts either:

  • a local file — .csv or .json with a fasta column plus smiles and/or selfies (the missing ligand format is derived automatically), or
  • a HuggingFace dataset id — add --dataset-config <name> for a specific config (e.g. InteractBind's affinity).
python src/main.py --dataset data/my_ligands.csv
python src/main.py --dataset some/hf-dataset --dataset-config affinity

The target protein is the first row's fasta (or set EB_TARGET); the first EB_N_CANDIDATES (default 4) ligands are scored.

Run a single stage on its own:

python src/explain_bind.py   # writes outputs/eb_out.json
python src/boltz.py          # reads it, folds into the pocket, ranks affinity

Troubleshooting

All stages run as subprocesses through one interpreter: PIPELINE_PY if that env var is set, otherwise the interpreter running main.py (sys.executable). Most setup failures are really "stages ran in the wrong env":

  • ModuleNotFoundError: No module named 'selfies' (or any other stage import) — the active interpreter isn't the fully-installed pipeline env. Run conda activate pipeline and launch main.py from it.

  • `boltz` not found on PATH — same cause: the stage interpreter has the Boltz Python package but not the boltz console script, or is a Python 3.13+ env where Boltz can't be installed at all. Confirm with python -c "import sys; print(sys.version)" (must be 3.10–3.12) and which boltz.

  • A leftover PIPELINE_PY overrides the active env. If you exported it to a different env earlier, stages silently use that one. Check with echo $PIPELINE_PY and unset PIPELINE_PY (or point it at the pipeline env's bin/python) before running.

  • conda: command not found Conda is not installed or not initialised in the shell. Install Miniforge or initialise it:

~/miniforge3/bin/conda init zsh
source ~/.zshrc

With or without DrugCLIP

  • The pipeline can run in two modes.

  • Without DrugCLIP : By default, if outputs/drugclip_out.json is not present, ExplainBind samples candidate ligands from the selected dataset, such as InteractBind.

python src/main.py
  • With: write DrugCLIP's shortlist to outputs/drugclip_out.json as [{"name": "...", "smiles": "..."}, ...]. ExplainBind picks it up automatically (SMILES are converted to SELFIES). No code changes needed.

Outputs

Everything lands under ./outputs/:

outputs/
  drugclip_out.json            # optional DrugCLIP ligand shortlist -> ExplainBind
  eb_out.json                  # ExplainBind -> Boltz handoff; binding probability + pocket contacts
  boltz_run/inputs/*.yaml      # Boltz inputs with ExplainBind-derived pocket constraints
  boltz_run/outputs/...        # Boltz structures + affinity predictions

Notes & caveats

  • Device: runs on CPU, CUDA GPU, or Apple MPS (see --device above). On a CUDA machine the default uses the GPU. On CPU, Boltz structure prediction scales hard with protein length — fine for short proteins / smoke tests, but hours for a ~250+ residue target, so use a GPU for real targets.
  • First-run downloads: Boltz weights + CCD (~a few GB) to ~/.boltz; ESM-2 650M (~2.5 GB) + SELFormer from Hugging Face for ExplainBind.
  • ExplainBind weights come from the demo Space checkpoint; predictions are only meaningful with it in place (step 3 above).
  • rdkit: DrugCLIP documents rdkit==2022.9.5, but Boltz requires rdkit>=2024.3.2. The combined env uses the modern rdkit; DrugCLIP's conformer generation was verified to work on it.
  • Override the stage interpreter with the PIPELINE_PY env var if your env lives elsewhere.
  • Version-control notes

The third-party model repositories and large generated files should not normally be committed.

Recommended ignored files/directories:

boltz/
Uni-Core/
ExplainBind_demo/
DrugCLIP_screen_pipeline/
*.lmdb
*.pt
*.ckpt
data/emb/
outputs/boltz_run/outputs/

DrugCLIP


DrugCLIP is used as the optional first-stage virtual screening tool. Its role in this integrated pipeline is to produce a shortlist of candidate ligands for ExplainBind.

Only the first three DrugCLIP steps are required for integration:

1. Convert the ligand library SDF to LMDB.
2. Prepare holo protein pockets as an LMDB.
3. Run DrugCLIP retrieval to rank ligands against the target pocket.

The downstream DrugCLIP-only steps are not required for the ExplainBind → Boltz-2 integration:

4. Novelty filtering and clustering.
5. Glide docking and scoring.
6. Pocket recycling.
7. Final candidate filtering.

Those steps belong to the original DrugCLIP post-screening workflow and are optional. In this repository, the handoff to ExplainBind happens immediately after DrugCLIP retrieval.

DrugCLIP expected handoff

DrugCLIP should provide:

outputs/drugclip_out.json

aubreyagub/protein-ligand-screening-pipeline

Python

0

16 commits

updated Jun 12, 2026

See the code

README

protein-ligand-screening-pipeline

Ligand Pipeline: DrugCLIP → ExplainBind → Boltz-2

A three-stage protein–ligand pipeline:

  1. DrugCLIP (optional, owned by teammates) — virtual-screens a library and shortlists candidate ligands for a target.
  2. ExplainBind — scores each candidate against the target protein and predicts the binding probability and the binding-site residues (the pocket).
  3. Boltz-2 — folds each ligand into the ExplainBind-defined pocket (as a Boltz pocket constraint) and predicts the binding affinity.

Stages run as subprocesses from src/main.py and hand off via JSON files under ./outputs/:

outputs/drugclip_out.json   (optional, from DrugCLIP)   ->  ExplainBind
outputs/eb_out.json         (ExplainBind -> Boltz: binding prob + pocket residues)

The InteractBind dataset is pulled automatically from Hugging Face (Zhaohan-Meng/InteractBind) on first run — no manual download needed.


Repository layout

src/
  main.py            # orchestrator: runs the 3 stages, --smoke for a fast test
  explain_bind.py    # ExplainBind stage -> outputs/eb_out.json
  boltz.py           # Boltz stage      -> reads eb_out.json, predicts affinity
outputs/             # all generated artifacts (created on demand)

The three model code bases are third-party clones (not committed — see Setup): boltz/, Uni-Core/, ExplainBind_demo/, DrugCLIP_screen_pipeline/.


Setup

All three tools share a single conda env named pipeline (Python 3.10).

Python version matters: use Python 3.10–3.12. Boltz pins >=3.10,<3.13, so a 3.13+ env cannot install the Boltz stage (ExplainBind alone will still import, which makes the mismatch easy to miss until Boltz fails).

Prerequisites

This project expects a Conda-compatible environment manager. On macOS, Miniforge is recommended.

Check whether Conda is available:

conda --version

### 1. Clone the model repositories (into the project root)

```bash
# Boltz-2
git clone https://github.com/jwohlwend/boltz.git

# Uni-Core (DrugCLIP backbone)
git clone https://github.com/dptech-corp/Uni-Core.git

# DrugCLIP (teammates' stage)
git clone https://github.com/THU-ATOM/DrugCLIP_screen_pipeline.git

# ExplainBind demo (model code + trained checkpoint)
GIT_LFS_SKIP_SMUDGE=1 git clone \
  https://huggingface.co/spaces/Zhaohan-Meng/ExplainBind ExplainBind_demo

2. Create the env and install (order matters — Boltz pins first)

conda create -y -n pipeline python=3.10
conda activate pipeline

# Boltz first (it has the strictest version pins)
pip install -e ./boltz

# ExplainBind stack
pip install "transformers==4.51.3" selfies matplotlib datasets \
            accelerate sentencepiece lmdb "numpy<2"

# DrugCLIP extras + Uni-Core (CPU build)
pip install ml_collections tensorboardX ipython "numpy<2"
KMP_DUPLICATE_LIB_OK=TRUE pip install ./Uni-Core --no-build-isolation

# macOS: rdkit and torch both ship libomp; make the workaround persistent
conda env config vars set -n pipeline KMP_DUPLICATE_LIB_OK=TRUE
conda activate pipeline   # re-activate so the env var takes effect

3. Download the trained ExplainBind checkpoint

The GIT_LFS_SKIP_SMUDGE clone leaves the checkpoint as a pointer; fetch the real file (works without git-lfs):

python - <<'PY'
import shutil, os
from huggingface_hub import hf_hub_download
p = hf_hub_download(repo_id="Zhaohan-Meng/ExplainBind", repo_type="space",
                    filename="save_model_ckp/fasta_selfies/best_model.ckpt")
dst = "ExplainBind_demo/save_model_ckp/fasta_selfies/best_model.ckpt"
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy(p, dst)
print("checkpoint ->", dst)
PY

Usage

conda activate pipeline

python src/main.py --smoke              # tiny target + 1 ligand, fast end-to-end check
python src/main.py                      # default dataset (InteractBind), device auto
python src/main.py --device cuda        # force GPU (see "Device" below)
python src/main.py --dataset my.csv     # your own dataset (see "Choosing the dataset")

Device (CPU / GPU)

--device selects the compute device, default auto (CUDA if available, else CPU):

--deviceExplainBindBoltz --accelerator
autoCUDA else CPUgpu if CUDA else cpu
cpuCPUcpu
cuda/gpuCUDA (falls back to CPU if absent)gpu (falls back to CPU if absent)
mpsApple MPScpu (Boltz has no MPS backend)

On a CUDA machine just use the default. On CPU, Boltz is slow for large proteins (hours) — keep the protein short or use --smoke for local tests.

Choosing the dataset

--dataset selects where the target protein + candidate ligands come from (it feeds the ExplainBind stage; Boltz receives everything via eb_out.json). It accepts either:

  • a local file — .csv or .json with a fasta column plus smiles and/or selfies (the missing ligand format is derived automatically), or
  • a HuggingFace dataset id — add --dataset-config <name> for a specific config (e.g. InteractBind's affinity).
python src/main.py --dataset data/my_ligands.csv
python src/main.py --dataset some/hf-dataset --dataset-config affinity

The target protein is the first row's fasta (or set EB_TARGET); the first EB_N_CANDIDATES (default 4) ligands are scored.

Run a single stage on its own:

python src/explain_bind.py   # writes outputs/eb_out.json
python src/boltz.py          # reads it, folds into the pocket, ranks affinity

Troubleshooting

All stages run as subprocesses through one interpreter: PIPELINE_PY if that env var is set, otherwise the interpreter running main.py (sys.executable). Most setup failures are really "stages ran in the wrong env":

  • ModuleNotFoundError: No module named 'selfies' (or any other stage import) — the active interpreter isn't the fully-installed pipeline env. Run conda activate pipeline and launch main.py from it.

  • `boltz` not found on PATH — same cause: the stage interpreter has the Boltz Python package but not the boltz console script, or is a Python 3.13+ env where Boltz can't be installed at all. Confirm with python -c "import sys; print(sys.version)" (must be 3.10–3.12) and which boltz.

  • A leftover PIPELINE_PY overrides the active env. If you exported it to a different env earlier, stages silently use that one. Check with echo $PIPELINE_PY and unset PIPELINE_PY (or point it at the pipeline env's bin/python) before running.

  • conda: command not found Conda is not installed or not initialised in the shell. Install Miniforge or initialise it:

~/miniforge3/bin/conda init zsh
source ~/.zshrc

With or without DrugCLIP

  • The pipeline can run in two modes.

  • Without DrugCLIP : By default, if outputs/drugclip_out.json is not present, ExplainBind samples candidate ligands from the selected dataset, such as InteractBind.

python src/main.py
  • With: write DrugCLIP's shortlist to outputs/drugclip_out.json as [{"name": "...", "smiles": "..."}, ...]. ExplainBind picks it up automatically (SMILES are converted to SELFIES). No code changes needed.

Outputs

Everything lands under ./outputs/:

outputs/
  drugclip_out.json            # optional DrugCLIP ligand shortlist -> ExplainBind
  eb_out.json                  # ExplainBind -> Boltz handoff; binding probability + pocket contacts
  boltz_run/inputs/*.yaml      # Boltz inputs with ExplainBind-derived pocket constraints
  boltz_run/outputs/...        # Boltz structures + affinity predictions

Notes & caveats

  • Device: runs on CPU, CUDA GPU, or Apple MPS (see --device above). On a CUDA machine the default uses the GPU. On CPU, Boltz structure prediction scales hard with protein length — fine for short proteins / smoke tests, but hours for a ~250+ residue target, so use a GPU for real targets.
  • First-run downloads: Boltz weights + CCD (~a few GB) to ~/.boltz; ESM-2 650M (~2.5 GB) + SELFormer from Hugging Face for ExplainBind.
  • ExplainBind weights come from the demo Space checkpoint; predictions are only meaningful with it in place (step 3 above).
  • rdkit: DrugCLIP documents rdkit==2022.9.5, but Boltz requires rdkit>=2024.3.2. The combined env uses the modern rdkit; DrugCLIP's conformer generation was verified to work on it.
  • Override the stage interpreter with the PIPELINE_PY env var if your env lives elsewhere.
  • Version-control notes

The third-party model repositories and large generated files should not normally be committed.

Recommended ignored files/directories:

boltz/
Uni-Core/
ExplainBind_demo/
DrugCLIP_screen_pipeline/
*.lmdb
*.pt
*.ckpt
data/emb/
outputs/boltz_run/outputs/

DrugCLIP


DrugCLIP is used as the optional first-stage virtual screening tool. Its role in this integrated pipeline is to produce a shortlist of candidate ligands for ExplainBind.

Only the first three DrugCLIP steps are required for integration:

1. Convert the ligand library SDF to LMDB.
2. Prepare holo protein pockets as an LMDB.
3. Run DrugCLIP retrieval to rank ligands against the target pocket.

The downstream DrugCLIP-only steps are not required for the ExplainBind → Boltz-2 integration:

4. Novelty filtering and clustering.
5. Glide docking and scoring.
6. Pocket recycling.
7. Final candidate filtering.

Those steps belong to the original DrugCLIP post-screening workflow and are optional. In this repository, the handoff to ExplainBind happens immediately after DrugCLIP retrieval.

DrugCLIP expected handoff

DrugCLIP should provide:

outputs/drugclip_out.json