A three-stage protein–ligand pipeline:
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.
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/.
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).
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
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
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
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 selects the compute device, default auto (CUDA if available, else CPU):
--device | ExplainBind | Boltz --accelerator |
|---|---|---|
auto | CUDA else CPU | gpu if CUDA else cpu |
cpu | CPU | cpu |
cuda/gpu | CUDA (falls back to CPU if absent) | gpu (falls back to CPU if absent) |
mps | Apple MPS | cpu (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.
--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:
.csv or .json with a fasta column plus smiles
and/or selfies (the missing ligand format is derived automatically), or--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
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
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
outputs/drugclip_out.json as
[{"name": "...", "smiles": "..."}, ...]. ExplainBind picks it up automatically
(SMILES are converted to SELFIES). No code changes needed.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
--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.~/.boltz; ESM-2 650M
(~2.5 GB) + SELFormer from Hugging Face for ExplainBind.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.PIPELINE_PY env var if your env lives
elsewhere.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 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 should provide:
outputs/drugclip_out.json
A three-stage protein–ligand pipeline:
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.
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/.
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).
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
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
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
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 selects the compute device, default auto (CUDA if available, else CPU):
--device | ExplainBind | Boltz --accelerator |
|---|---|---|
auto | CUDA else CPU | gpu if CUDA else cpu |
cpu | CPU | cpu |
cuda/gpu | CUDA (falls back to CPU if absent) | gpu (falls back to CPU if absent) |
mps | Apple MPS | cpu (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.
--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:
.csv or .json with a fasta column plus smiles
and/or selfies (the missing ligand format is derived automatically), or--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
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
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
outputs/drugclip_out.json as
[{"name": "...", "smiles": "..."}, ...]. ExplainBind picks it up automatically
(SMILES are converted to SELFIES). No code changes needed.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
--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.~/.boltz; ESM-2 650M
(~2.5 GB) + SELFormer from Hugging Face for ExplainBind.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.PIPELINE_PY env var if your env lives
elsewhere.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 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 should provide:
outputs/drugclip_out.json