Upload folder using huggingface_hub
Browse files- .gitattributes +22 -10
- README.md +234 -0
- __init__.py +1 -0
- configs/evaluate.yml +101 -0
- configs/sampling.yml +61 -0
- configs/training.yml +150 -0
- data/1a46_ligand.sdf +179 -0
- data/1a46_protein_processed.pdb +0 -0
- data/6o5u_ligand.sdf +154 -0
- data/6o5u_protein_processed.pdb +0 -0
- models/__init__.py +34 -0
- models/__pycache__/__init__.cpython-311.pyc +0 -0
- models/__pycache__/aa_model.cpython-310.pyc +0 -0
- models/__pycache__/aa_model.cpython-311.pyc +0 -0
- models/__pycache__/cg_model.cpython-310.pyc +0 -0
- models/__pycache__/cg_model.cpython-311.pyc +0 -0
- models/__pycache__/layers.cpython-311.pyc +0 -0
- models/__pycache__/old_aa_model.cpython-310.pyc +0 -0
- models/__pycache__/old_aa_model.cpython-311.pyc +0 -0
- models/__pycache__/score_wrapper.cpython-310.pyc +0 -0
- models/__pycache__/score_wrapper.cpython-311.pyc +0 -0
- models/__pycache__/tensor_layers.cpython-310.pyc +0 -0
- models/__pycache__/tensor_layers.cpython-311.pyc +0 -0
- models/aa_model.py +721 -0
- models/cg_model.py +1070 -0
- models/layers.py +128 -0
- models/old_aa_model.py +582 -0
- models/score_wrapper.py +219 -0
- models/tensor_layers.py +459 -0
- scripts/__init__.py +1 -0
- scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- scripts/__pycache__/evaluate.cpython-311.pyc +0 -0
- scripts/__pycache__/sample_diffdock.cpython-310.pyc +0 -0
- scripts/__pycache__/train_diffdock.cpython-310.pyc +0 -0
- scripts/__pycache__/train_diffdock.cpython-311.pyc +0 -0
- scripts/evaluate.py +5 -0
- scripts/infer.sh +147 -0
- scripts/infer_slurm.sbatch +26 -0
- scripts/sample_diffdock.py +395 -0
- scripts/train.sh +231 -0
- scripts/train_demo.sh +199 -0
- scripts/train_diffdock.py +404 -0
- scripts/train_slurm.sbatch +26 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,47 @@
|
|
| 1 |
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 4 |
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bin.* filter=lfs diff=lfs merge=lfs -text
|
| 5 |
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 6 |
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 11 |
*.model filter=lfs diff=lfs merge=lfs -text
|
| 12 |
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 13 |
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 14 |
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 15 |
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 16 |
*.pb filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 17 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 18 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 19 |
*.rar filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 20 |
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 21 |
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 22 |
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 23 |
*.tgz filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 24 |
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 25 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
*.zstandard filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.db* filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.ark* filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
**/*ckpt*data* filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
**/*ckpt*.meta filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
**/*ckpt*.index filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*.gguf* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.ggml filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
*.llamafile* filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
*.pt2 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 46 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
frameworks:
|
| 3 |
+
- ""
|
| 4 |
+
language:
|
| 5 |
+
- en
|
| 6 |
+
license: mit
|
| 7 |
+
tags:
|
| 8 |
+
- OneScience
|
| 9 |
+
- protein-ligand molecular docking
|
| 10 |
+
- generative
|
| 11 |
+
---
|
| 12 |
+
<p align="center">
|
| 13 |
+
<strong>
|
| 14 |
+
<span style="font-size: 30px;">DiffDock</span>
|
| 15 |
+
</strong>
|
| 16 |
+
</p>
|
| 17 |
+
|
| 18 |
+
# Model Overview
|
| 19 |
+
|
| 20 |
+
DiffDock is a diffusion model for protein-ligand molecular docking proposed by Corso et al. It supports single-complex molecular docking, batch molecular docking, evaluation, and related workflows.
|
| 21 |
+
|
| 22 |
+
# Model Description
|
| 23 |
+
|
| 24 |
+
DiffDock formulates docking as a generative modeling problem. Given a protein receptor and a small-molecule ligand, it jointly models the ligand's translational, rotational, and torsional degrees of freedom through diffusion processes on SO(3)/SE(3), and generates candidate binding conformations.
|
| 25 |
+
|
| 26 |
+
Weights and datasets are not available at the moment. They will be uploaded to Hugging Face soon, and command-line downloads will be supported later.
|
| 27 |
+
|
| 28 |
+
# Use Cases
|
| 29 |
+
|
| 30 |
+
| Use case | Description |
|
| 31 |
+
| :---: | :---: |
|
| 32 |
+
| Score model training | Takes processed PDBBind or MOAD data as input and outputs a DiffDock score model checkpoint |
|
| 33 |
+
| Single-complex molecular docking | Takes a protein PDB file and a ligand SMILES/SDF/MOL2 input, and outputs candidate ligand binding conformations in SDF format |
|
| 34 |
+
| Batch molecular docking | Takes a CSV file containing proteins, ligands, and complex names, and outputs sampled conformations in batches |
|
| 35 |
+
| Confidence rerank | Uses an additional confidence model to rank sampled conformations |
|
| 36 |
+
| Dataset evaluation | Computes metrics such as RMSD for sampling results on a validation or test set, with optional GNINA integration |
|
| 37 |
+
|
| 38 |
+
# Usage
|
| 39 |
+
|
| 40 |
+
## 1. Using OneCode
|
| 41 |
+
|
| 42 |
+
You can try intelligent one-click AI4S programming through the OneCode online environment:
|
| 43 |
+
|
| 44 |
+
[Try intelligent one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 45 |
+
|
| 46 |
+
## 2. Manual Installation and Usage
|
| 47 |
+
|
| 48 |
+
**Hardware Requirements**
|
| 49 |
+
|
| 50 |
+
- Running on a GPU or DCU is recommended.
|
| 51 |
+
- CPU can be used for connectivity checks, but it is relatively slow.
|
| 52 |
+
- DCU users need to install DTK in advance. DTK 25.04.2 or later is recommended, or the OneScience-recommended version that matches the current cluster.
|
| 53 |
+
|
| 54 |
+
**Software Requirements**
|
| 55 |
+
|
| 56 |
+
Common DiffDock dependencies include PyTorch, PyTorch Geometric, RDKit, OpenBabel, e3nn, torch-scatter, torch-cluster, NumPy, SciPy, tqdm, PyYAML, and others. GNINA energy minimization during dataset evaluation requires installing `gnina` separately.
|
| 57 |
+
|
| 58 |
+
**Environment Checks**
|
| 59 |
+
|
| 60 |
+
- NVIDIA GPU:
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
nvidia-smi
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
- Hygon DCU:
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
hy-smi
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
## Quick Start
|
| 73 |
+
|
| 74 |
+
### 1. Install the Runtime Environment
|
| 75 |
+
|
| 76 |
+
```bash
|
| 77 |
+
conda create -n onescience311 python=3.11 -y
|
| 78 |
+
conda activate onescience311
|
| 79 |
+
pip install onescience[bio] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
If the following code cannot find required libraries at runtime, activate CUDA as shown below.
|
| 83 |
+
|
| 84 |
+
```bash
|
| 85 |
+
source ${ROCM_PATH}/cuda/env.sh
|
| 86 |
+
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib:$LD_LIBRARY_PATH"
|
| 87 |
+
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib/python3.11/site-packages/fastpt/torch/lib:$LD_LIBRARY_PATH"
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
### 2. Download the Model Package and Database
|
| 91 |
+
|
| 92 |
+
```bash
|
| 93 |
+
# If the model ID on the Hugging Face page uses different capitalization, use the actual published name.
|
| 94 |
+
hf download --model OneScience-Sugon/diffdock --local-dir ./diffdock
|
| 95 |
+
cd diffdock
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
### Training Weights and Datasets
|
| 99 |
+
|
| 100 |
+
Weights and datasets are not available at the moment. They will be uploaded to Hugging Face soon, and command-line downloads will be supported later.
|
| 101 |
+
|
| 102 |
+
### 3. Check Files in the Package
|
| 103 |
+
|
| 104 |
+
The current package does not include pretrained weights. The command below should only show source code, configuration files, and example input files:
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
find . -maxdepth 3 -type f
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
If sampling or evaluation is required, first obtain a checkpoint through training, or place external DiffDock score/confidence weights in a local directory. Make sure the weight directory contains `model_parameters.yml`.
|
| 111 |
+
|
| 112 |
+
### 4. Score Model Training
|
| 113 |
+
|
| 114 |
+
Before training, update the data paths in `configs/training.yml` to local paths. Common fields include:
|
| 115 |
+
|
| 116 |
+
- `data.pdbbind_dir`: processed PDBBind data directory.
|
| 117 |
+
- `data.moad_dir`: processed MOAD data directory.
|
| 118 |
+
- `data.split_train`: list of training-set complexes.
|
| 119 |
+
- `data.split_val`: list of validation-set complexes.
|
| 120 |
+
- `runtime.log_dir`: training output directory.
|
| 121 |
+
|
| 122 |
+
Start training:
|
| 123 |
+
|
| 124 |
+
```bash
|
| 125 |
+
cd scripts
|
| 126 |
+
bash train.sh
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
After training succeeds, the output directory usually contains:
|
| 130 |
+
|
| 131 |
+
- `model_parameters.yml`: model architecture and training parameters.
|
| 132 |
+
- `best_model.pt`: weights with the best validation loss.
|
| 133 |
+
- `best_ema_model.pt`: EMA weights.
|
| 134 |
+
- `best_inference_epoch_model.pt` or `best_ema_inference_epoch_model.pt`: weights saved when inference validation is enabled.
|
| 135 |
+
- `last_model.pt`: training state from the final epoch.
|
| 136 |
+
|
| 137 |
+
### 5. Molecular Docking Sampling
|
| 138 |
+
|
| 139 |
+
Sampling requires a trained score model directory, for example:
|
| 140 |
+
|
| 141 |
+
```text
|
| 142 |
+
outputs/train/diffdock_cg_example/
|
| 143 |
+
├── model_parameters.yml
|
| 144 |
+
└── best_model.pt
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
Update the key fields in `configs/sampling.yml` to the actual paths:
|
| 148 |
+
|
| 149 |
+
- `model.model_dir`: score model directory.
|
| 150 |
+
- `model.ckpt`: score checkpoint file name.
|
| 151 |
+
- `confidence.confidence_model_dir`: confidence model directory; set it to `null` when rerank is not used.
|
| 152 |
+
- `input.protein_path`: protein PDB path. You can use `data/6o5u_protein_processed.pdb`.
|
| 153 |
+
- `input.ligand_description`: SMILES string or ligand SDF/MOL2 path. You can use `data/6o5u_ligand.sdf`.
|
| 154 |
+
- `runtime.out_dir`: sampling output directory.
|
| 155 |
+
|
| 156 |
+
Start single-complex sampling:
|
| 157 |
+
|
| 158 |
+
```bash
|
| 159 |
+
cd scripts
|
| 160 |
+
bash infer.sh
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
### 6. CSV Batch Sampling
|
| 164 |
+
|
| 165 |
+
Set `input.protein_ligand_csv` in `configs/sampling.yml` to the CSV path, and set the single-complex fields to `null` as needed. The CSV is recommended to contain the following columns:
|
| 166 |
+
|
| 167 |
+
```text
|
| 168 |
+
complex_name,protein_path,ligand_description,protein_sequence
|
| 169 |
+
```
|
| 170 |
+
|
| 171 |
+
Here, `ligand_description` can be either a SMILES string or an SDF/MOL2 file path.
|
| 172 |
+
|
| 173 |
+
### 7. Dataset Evaluation
|
| 174 |
+
|
| 175 |
+
Before evaluation, update the dataset, model, and output directories in `configs/evaluate.yml` to actual paths:
|
| 176 |
+
|
| 177 |
+
```bash
|
| 178 |
+
python -m scripts.evaluate --config configs/evaluate.yml
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
If `gnina.gnina_minimize=true` is enabled, first make sure the following command can be run directly in the current environment:
|
| 182 |
+
|
| 183 |
+
```bash
|
| 184 |
+
gnina --help
|
| 185 |
+
```
|
| 186 |
+
|
| 187 |
+
### 8. Confidence Model Training
|
| 188 |
+
|
| 189 |
+
The confidence training entry point is provided by the installed OneScience package. This Hugging Face package does not duplicate that module. After completing score model training, run the following in the OneScience environment:
|
| 190 |
+
|
| 191 |
+
```bash
|
| 192 |
+
python -m onescience.confidence.diffdock.confidence_train \
|
| 193 |
+
--original_model_dir outputs/train/diffdock_cg_example \
|
| 194 |
+
--data_dir /path/to/PDBBind_processed \
|
| 195 |
+
--split_train /path/to/splits/timesplit_no_lig_overlap_train \
|
| 196 |
+
--split_val /path/to/splits/timesplit_no_lig_overlap_val
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
# Data Preparation
|
| 200 |
+
|
| 201 |
+
DiffDock training usually requires preprocessed PDBBind or MOAD data. It is recommended to organize the data under a unified root directory, for example:
|
| 202 |
+
|
| 203 |
+
```text
|
| 204 |
+
${ONESCIENCE_DATASETS_DIR}/diffdock/
|
| 205 |
+
├── PDBBind_processed/
|
| 206 |
+
├── MOAD_processed/
|
| 207 |
+
└── splits/
|
| 208 |
+
├── timesplit_no_lig_overlap_train
|
| 209 |
+
├── timesplit_no_lig_overlap_val
|
| 210 |
+
└── timesplit_test
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
Split files should be plain text files, with one complex name per line. Each name must correspond to a subdirectory name in the data directory.
|
| 214 |
+
|
| 215 |
+
# Official OneScience Information
|
| 216 |
+
|
| 217 |
+
| Platform | OneScience main repository | Skills repository |
|
| 218 |
+
| --- | --- | --- |
|
| 219 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 220 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 221 |
+
|
| 222 |
+
# Citation and License
|
| 223 |
+
|
| 224 |
+
- The original DiffDock code is licensed under the MIT License. This repository retains source attribution and is organized for OneScience Hugging Face automated runtime scenarios.
|
| 225 |
+
- If you use DiffDock results in research, we recommend citing the original DiffDock and DiffDock-L papers, as well as relevant OneScience project information. Depending on the actual task, also add citations for datasets or tools such as PDBBind, MOAD, RDKit, OpenBabel, and GNINA.
|
| 226 |
+
|
| 227 |
+
```bibtex
|
| 228 |
+
@inproceedings{corso2023diffdock,
|
| 229 |
+
title={DiffDock: Diffusion Steps, Twists, and Turns for Molecular Docking},
|
| 230 |
+
author={Corso, Gabriele and St{\"a}rk, Hannes and Jing, Bowen and Barzilay, Regina and Jaakkola, Tommi},
|
| 231 |
+
booktitle={International Conference on Learning Representations},
|
| 232 |
+
year={2023}
|
| 233 |
+
}
|
| 234 |
+
```
|
__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
__all__ = []
|
configs/evaluate.yml
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================================
|
| 2 |
+
# DiffDock CGModel Dataset Evaluation Configuration
|
| 3 |
+
#
|
| 4 |
+
# IMPORTANT: Update all filesystem paths below to match your environment.
|
| 5 |
+
#
|
| 6 |
+
# Supported dataset values: pdbbind | moad | generalisation
|
| 7 |
+
#
|
| 8 |
+
# External dependencies (optional):
|
| 9 |
+
# gnina_minimize=true → requires `gnina` executable on PATH
|
| 10 |
+
# RMSD computations → requires `spyrmsd` (pip install spyrmsd)
|
| 11 |
+
# ============================================================================
|
| 12 |
+
|
| 13 |
+
runtime:
|
| 14 |
+
run_name: diffdock_eval_example
|
| 15 |
+
project: diffdock
|
| 16 |
+
device: auto
|
| 17 |
+
out_dir: examples/biosciences/diffdock/outputs/evaluate
|
| 18 |
+
wandb: false
|
| 19 |
+
num_cpu: null
|
| 20 |
+
restrict_cpu: false
|
| 21 |
+
|
| 22 |
+
model:
|
| 23 |
+
model_dir: /public/home/liuyx19/modelscope/diffdock/outputs/train/diffdock_pdbbind_smoke100_val20_cpu
|
| 24 |
+
ckpt: best_model.pt
|
| 25 |
+
old_score_model: false # must stay false; old_* score-model paths are intentionally not migrated
|
| 26 |
+
no_model: false
|
| 27 |
+
force_fixed_center_conv: false
|
| 28 |
+
|
| 29 |
+
confidence:
|
| 30 |
+
confidence_model_dir: null
|
| 31 |
+
confidence_ckpt: best_model.pt
|
| 32 |
+
old_confidence_model: false # must stay false; old confidence-model paths are intentionally not migrated
|
| 33 |
+
|
| 34 |
+
data:
|
| 35 |
+
dataset: pdbbind # supported: pdbbind | moad | generalisation; unsupported: pdbsidechain | distillation
|
| 36 |
+
cache_path: /public/home/liuyx19/modelscope/diffdock/cache
|
| 37 |
+
data_dir: /public/share/sugonhpcapp01/onestore/onedatasets/diffdock/datasets/PDBBind_processed
|
| 38 |
+
split_path: /public/share/sugonhpcapp01/onestore/onedatasets/diffdock/datasets/splits/timesplit_no_lig_overlap_val
|
| 39 |
+
split: val
|
| 40 |
+
limit_complexes: 0
|
| 41 |
+
num_workers: 1
|
| 42 |
+
chain_cutoff: null
|
| 43 |
+
protein_file: protein_processed
|
| 44 |
+
ligand_file: ligand
|
| 45 |
+
esm_embeddings_path: null
|
| 46 |
+
moad_esm_embeddings_sequences_path: null
|
| 47 |
+
not_knn_only_graph: false
|
| 48 |
+
include_miscellaneous_atoms: false
|
| 49 |
+
triple_training: false # unsupported in the current migration; will fail fast if set true
|
| 50 |
+
unroll_clusters: false
|
| 51 |
+
remove_pdbbind: false
|
| 52 |
+
min_ligand_size: 0
|
| 53 |
+
max_receptor_size: null
|
| 54 |
+
remove_promiscuous_targets: null
|
| 55 |
+
matching_popsize: 40
|
| 56 |
+
matching_maxiter: 40
|
| 57 |
+
|
| 58 |
+
sampling:
|
| 59 |
+
batch_size: 20
|
| 60 |
+
inference_steps: 20
|
| 61 |
+
actual_steps: null
|
| 62 |
+
samples_per_complex: 10
|
| 63 |
+
no_random: false
|
| 64 |
+
no_final_step_noise: true
|
| 65 |
+
ode: false
|
| 66 |
+
sigma_schedule: expbeta
|
| 67 |
+
inf_sched_alpha: 1.0
|
| 68 |
+
inf_sched_beta: 1.0
|
| 69 |
+
pocket_knowledge: false
|
| 70 |
+
no_random_pocket: false
|
| 71 |
+
pocket_tr_max: 3.0
|
| 72 |
+
pocket_cutoff: 5.0
|
| 73 |
+
different_schedules: false
|
| 74 |
+
resample_rdkit: false
|
| 75 |
+
skip_matching: false
|
| 76 |
+
initial_noise_std_proportion: -1.0
|
| 77 |
+
choose_residue: false
|
| 78 |
+
limit_failures: 5
|
| 79 |
+
tqdm: true
|
| 80 |
+
temp_sampling_tr: 1.0
|
| 81 |
+
temp_psi_tr: 0.0
|
| 82 |
+
temp_sigma_data_tr: 0.5
|
| 83 |
+
temp_sampling_rot: 1.0
|
| 84 |
+
temp_psi_rot: 0.0
|
| 85 |
+
temp_sigma_data_rot: 0.5
|
| 86 |
+
temp_sampling_tor: 1.0
|
| 87 |
+
temp_psi_tor: 0.0
|
| 88 |
+
temp_sigma_data_tor: 0.5
|
| 89 |
+
|
| 90 |
+
outputs:
|
| 91 |
+
save_visualisation: false
|
| 92 |
+
save_complexes: false
|
| 93 |
+
complexes_save_path: null
|
| 94 |
+
|
| 95 |
+
gnina:
|
| 96 |
+
gnina_minimize: false
|
| 97 |
+
gnina_path: gnina
|
| 98 |
+
gnina_full_dock: false
|
| 99 |
+
save_gnina_metrics: false
|
| 100 |
+
gnina_autobox_add: 4.0
|
| 101 |
+
gnina_poses_to_optimize: 1
|
configs/sampling.yml
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================================
|
| 2 |
+
# DiffDock CGModel Sampling Configuration
|
| 3 |
+
#
|
| 4 |
+
# IMPORTANT: Update all filesystem paths below to match your environment.
|
| 5 |
+
# - model_dir: must contain a CGModel checkpoint + model_parameters.yml
|
| 6 |
+
# - protein_path: path to receptor PDB
|
| 7 |
+
# - ligand_description: SMILES string or path to .sdf/.mol2
|
| 8 |
+
#
|
| 9 |
+
# CSV batch mode:
|
| 10 |
+
# Set protein_ligand_csv instead of the single-complex fields.
|
| 11 |
+
# CSV columns: complex_name, protein_path, ligand_description, protein_sequence
|
| 12 |
+
#
|
| 13 |
+
# Confidence rerank:
|
| 14 |
+
# Set confidence_model_dir to a CGModel-based confidence checkpoint dir.
|
| 15 |
+
# ============================================================================
|
| 16 |
+
|
| 17 |
+
runtime:
|
| 18 |
+
device: auto
|
| 19 |
+
loglevel: INFO
|
| 20 |
+
out_dir: examples/biosciences/diffdock/outputs/sample
|
| 21 |
+
|
| 22 |
+
model:
|
| 23 |
+
model_dir: ${ONESCIENCE_DATASETS_DIR}/diffdock/score_model
|
| 24 |
+
ckpt: best_ema_inference_epoch_model.pt
|
| 25 |
+
old_score_model: false
|
| 26 |
+
|
| 27 |
+
confidence:
|
| 28 |
+
confidence_model_dir: ${ONESCIENCE_DATASETS_DIR}/diffdock/confidence_model
|
| 29 |
+
confidence_ckpt: best_model_epoch75.pt
|
| 30 |
+
old_confidence_model: false
|
| 31 |
+
|
| 32 |
+
input:
|
| 33 |
+
protein_ligand_csv: null
|
| 34 |
+
complex_name: 6o5u_test
|
| 35 |
+
protein_path: examples/biosciences/diffdock/data/6o5u_protein_processed.pdb
|
| 36 |
+
protein_sequence: null
|
| 37 |
+
ligand_description: examples/biosciences/diffdock/data/6o5u_ligand.sdf
|
| 38 |
+
lm_embeddings: null
|
| 39 |
+
|
| 40 |
+
sampling:
|
| 41 |
+
samples_per_complex: 10
|
| 42 |
+
batch_size: 10
|
| 43 |
+
inference_steps: 20
|
| 44 |
+
actual_steps: null
|
| 45 |
+
sigma_schedule: expbeta
|
| 46 |
+
inf_sched_alpha: 1.0
|
| 47 |
+
inf_sched_beta: 1.0
|
| 48 |
+
no_random: false
|
| 49 |
+
no_final_step_noise: true
|
| 50 |
+
ode: false
|
| 51 |
+
choose_residue: false
|
| 52 |
+
initial_noise_std_proportion: 1.0
|
| 53 |
+
temp_sampling_tr: 1.0
|
| 54 |
+
temp_psi_tr: 0.0
|
| 55 |
+
temp_sigma_data_tr: 0.5
|
| 56 |
+
temp_sampling_rot: 1.0
|
| 57 |
+
temp_psi_rot: 0.0
|
| 58 |
+
temp_sigma_data_rot: 0.5
|
| 59 |
+
temp_sampling_tor: 1.0
|
| 60 |
+
temp_psi_tor: 0.0
|
| 61 |
+
temp_sigma_data_tor: 0.5
|
configs/training.yml
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================================
|
| 2 |
+
# DiffDock CGModel Training Configuration
|
| 3 |
+
#
|
| 4 |
+
# IMPORTANT: Update all filesystem paths below to match your environment.
|
| 5 |
+
# - cache_path, pdbbind_dir, moad_dir: dataset directories
|
| 6 |
+
# - split_train, split_val: text files listing complex names (one per line)
|
| 7 |
+
#
|
| 8 |
+
# Supported scope (current migration):
|
| 9 |
+
# dataset: pdbbind | moad | generalisation
|
| 10 |
+
# Unsupported (will fail fast):
|
| 11 |
+
# dataset: pdbsidechain | distillation
|
| 12 |
+
# all_atoms: true (AAModel not migrated)
|
| 13 |
+
# triple_training: true
|
| 14 |
+
# ============================================================================
|
| 15 |
+
|
| 16 |
+
runtime:
|
| 17 |
+
run_name: diffdock_cg_example
|
| 18 |
+
log_dir: examples/biosciences/diffdock/outputs/train
|
| 19 |
+
device: auto
|
| 20 |
+
seed: 0
|
| 21 |
+
cudnn_benchmark: false
|
| 22 |
+
wandb: false
|
| 23 |
+
project: diffdock
|
| 24 |
+
|
| 25 |
+
data:
|
| 26 |
+
dataset: pdbbind # supported: pdbbind | moad | generalisation
|
| 27 |
+
cache_path: ./cache
|
| 28 |
+
pdbbind_dir: ${ONESCIENCE_DATASETS_DIR}/diffdock/datasets/PDBBind_processed
|
| 29 |
+
moad_dir: ${ONESCIENCE_DATASETS_DIR}/diffdock/datasets/BindingMOAD_2020_processed
|
| 30 |
+
split_train: ${ONESCIENCE_DATASETS_DIR}/diffdock/datasets/splits/timesplit_no_lig_overlap_train
|
| 31 |
+
split_val: ${ONESCIENCE_DATASETS_DIR}/diffdock/datasets/splits/timesplit_no_lig_overlap_val
|
| 32 |
+
protein_file: protein_processed
|
| 33 |
+
limit_complexes: null
|
| 34 |
+
num_conformers: 1
|
| 35 |
+
num_workers: 1
|
| 36 |
+
num_dataloader_workers: 0
|
| 37 |
+
batch_size: 4
|
| 38 |
+
pin_memory: false
|
| 39 |
+
dataloader_drop_last: false
|
| 40 |
+
remove_hs: true
|
| 41 |
+
receptor_radius: 30
|
| 42 |
+
c_alpha_max_neighbors: 10
|
| 43 |
+
atom_radius: 5
|
| 44 |
+
atom_max_neighbors: 8
|
| 45 |
+
chain_cutoff: null
|
| 46 |
+
max_lig_size: null
|
| 47 |
+
matching_popsize: 20
|
| 48 |
+
matching_maxiter: 20
|
| 49 |
+
matching_tries: 1
|
| 50 |
+
not_knn_only_graph: false
|
| 51 |
+
include_miscellaneous_atoms: false
|
| 52 |
+
all_atoms: false # must stay false in the current migration; only the CGModel main path is supported
|
| 53 |
+
triple_training: false # unsupported in the current migration; will fail fast if set true
|
| 54 |
+
combined_training: false
|
| 55 |
+
double_val: false
|
| 56 |
+
train_multiplicity: 1
|
| 57 |
+
val_multiplicity: 1
|
| 58 |
+
max_receptor_size: null
|
| 59 |
+
remove_promiscuous_targets: null
|
| 60 |
+
min_ligand_size: 0
|
| 61 |
+
unroll_clusters: false
|
| 62 |
+
enforce_timesplit: false
|
| 63 |
+
crop_beyond: 20
|
| 64 |
+
moad_esm_embeddings_path: null
|
| 65 |
+
pdbbind_esm_embeddings_path: null
|
| 66 |
+
moad_esm_embeddings_sequences_path: null
|
| 67 |
+
esm_embeddings_model: null
|
| 68 |
+
|
| 69 |
+
diffusion:
|
| 70 |
+
no_torsion: false
|
| 71 |
+
tr_sigma_min: 0.1
|
| 72 |
+
tr_sigma_max: 30.0
|
| 73 |
+
rot_sigma_min: 0.1
|
| 74 |
+
rot_sigma_max: 1.65
|
| 75 |
+
tor_sigma_min: 0.0314
|
| 76 |
+
tor_sigma_max: 3.14
|
| 77 |
+
sampling_alpha: 1.0
|
| 78 |
+
sampling_beta: 1.0
|
| 79 |
+
tr_weight: 0.33
|
| 80 |
+
rot_weight: 0.33
|
| 81 |
+
tor_weight: 0.33
|
| 82 |
+
backbone_loss_weight: 0.0
|
| 83 |
+
sidechain_loss_weight: 0.0
|
| 84 |
+
|
| 85 |
+
model:
|
| 86 |
+
# old_score_model is intentionally unsupported in the current migration; training only covers the CGModel main path.
|
| 87 |
+
num_conv_layers: 2
|
| 88 |
+
max_radius: 5.0
|
| 89 |
+
scale_by_sigma: true
|
| 90 |
+
norm_by_sigma: false
|
| 91 |
+
ns: 16
|
| 92 |
+
nv: 4
|
| 93 |
+
distance_embed_dim: 32
|
| 94 |
+
cross_distance_embed_dim: 32
|
| 95 |
+
no_batch_norm: true
|
| 96 |
+
use_second_order_repr: false
|
| 97 |
+
cross_max_distance: 80
|
| 98 |
+
dynamic_max_cross: false
|
| 99 |
+
dropout: 0.0
|
| 100 |
+
smooth_edges: false
|
| 101 |
+
odd_parity: false
|
| 102 |
+
embedding_type: sinusoidal
|
| 103 |
+
sigma_embed_dim: 32
|
| 104 |
+
embedding_scale: 1000
|
| 105 |
+
no_aminoacid_identities: false
|
| 106 |
+
sh_lmax: 2
|
| 107 |
+
no_differentiate_convolutions: false
|
| 108 |
+
tp_weights_layers: 2
|
| 109 |
+
num_prot_emb_layers: 0
|
| 110 |
+
reduce_pseudoscalars: false
|
| 111 |
+
embed_also_ligand: true
|
| 112 |
+
depthwise_convolution: false
|
| 113 |
+
use_old_atom_encoder: false
|
| 114 |
+
|
| 115 |
+
optimization:
|
| 116 |
+
n_epochs: 10
|
| 117 |
+
lr: 0.001
|
| 118 |
+
w_decay: 0.0
|
| 119 |
+
scheduler: null
|
| 120 |
+
scheduler_patience: 20
|
| 121 |
+
lr_start_factor: 0.001
|
| 122 |
+
warmup_dur: 4
|
| 123 |
+
use_ema: true
|
| 124 |
+
ema_rate: 0.999
|
| 125 |
+
restart_dir: null
|
| 126 |
+
restart_ckpt: last_model
|
| 127 |
+
restart_lr: null
|
| 128 |
+
pretrain_dir: null
|
| 129 |
+
pretrain_ckpt: null
|
| 130 |
+
save_model_freq: null
|
| 131 |
+
|
| 132 |
+
validation:
|
| 133 |
+
test_sigma_intervals: false
|
| 134 |
+
inference_samples: 1
|
| 135 |
+
val_inference_freq: null
|
| 136 |
+
train_inference_freq: null
|
| 137 |
+
inference_steps: 20
|
| 138 |
+
num_inference_complexes: 20
|
| 139 |
+
inference_earlystop_metric: valinf_min_rmsds_lt2
|
| 140 |
+
inference_secondary_metric: null
|
| 141 |
+
inference_earlystop_goal: max
|
| 142 |
+
|
| 143 |
+
# ----------------------------------------------------------------------------
|
| 144 |
+
# NOTE: Confidence model training is a separate step after score-model training.
|
| 145 |
+
# Run: python -m onescience.confidence.diffdock.confidence_train
|
| 146 |
+
# --original_model_dir <this_run_dir>
|
| 147 |
+
# --data_dir <pdbbind_dir> --split_train <...> --split_val <...>
|
| 148 |
+
# There is no example-level confidence_train script; the module lives at
|
| 149 |
+
# src/onescience/confidence/diffdock/confidence_train.py
|
| 150 |
+
# ----------------------------------------------------------------------------
|
data/1a46_ligand.sdf
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
1a46_ligand
|
| 2 |
+
-I-interpret-
|
| 3 |
+
|
| 4 |
+
85 88 0 0 0 0 0 0 0 0999 V2000
|
| 5 |
+
17.8330 -13.0420 21.6620 C 0 0 0 0 0
|
| 6 |
+
18.8870 -13.0710 20.5870 C 0 0 0 0 0
|
| 7 |
+
19.8510 -14.2200 21.1170 C 0 0 0 0 0
|
| 8 |
+
19.3270 -16.4440 22.1560 C 0 0 0 0 0
|
| 9 |
+
18.1340 -17.2300 22.7620 C 0 0 0 0 0
|
| 10 |
+
17.2230 -16.3290 23.5970 C 0 0 0 0 0
|
| 11 |
+
17.0320 -14.9230 23.0460 C 0 0 0 0 0
|
| 12 |
+
18.8520 -15.2420 21.4440 N 0 3 0 0 0
|
| 13 |
+
17.7750 -14.5090 22.0480 N 0 0 0 0 0
|
| 14 |
+
15.9850 -14.2900 23.3800 O 0 0 0 0 0
|
| 15 |
+
16.6380 -13.0610 20.7550 C 0 0 0 0 0
|
| 16 |
+
16.4620 -13.9620 19.8370 O 0 0 0 0 0
|
| 17 |
+
15.8090 -16.7300 23.6610 N 0 3 0 0 0
|
| 18 |
+
17.4150 -16.4170 25.1230 C 0 0 0 0 0
|
| 19 |
+
18.7640 -15.9840 25.5820 C 0 0 0 0 0
|
| 20 |
+
19.0510 -14.6340 25.7600 C 0 0 0 0 0
|
| 21 |
+
20.3910 -14.2520 26.0760 C 0 0 0 0 0
|
| 22 |
+
21.4290 -15.1780 26.2150 C 0 0 0 0 0
|
| 23 |
+
21.0990 -16.5480 26.0980 C 0 0 0 0 0
|
| 24 |
+
19.7890 -16.9510 25.7560 C 0 0 0 0 0
|
| 25 |
+
15.6470 -12.0890 20.7690 N 0 0 0 0 0
|
| 26 |
+
14.4940 -11.8920 19.9090 C 0 0 0 0 0
|
| 27 |
+
14.4960 -10.9450 18.7130 C 0 0 0 0 0
|
| 28 |
+
13.3800 -10.6840 18.0770 O 0 0 0 0 0
|
| 29 |
+
13.1950 -11.6150 20.6280 C 0 0 0 0 0
|
| 30 |
+
12.8670 -12.5040 21.7570 C 0 0 0 0 0
|
| 31 |
+
11.5610 -12.2200 22.4370 C 0 0 0 0 0
|
| 32 |
+
11.1700 -13.3510 23.3530 C 0 0 0 0 0
|
| 33 |
+
10.0380 -13.1110 24.2350 N 0 3 0 0 0
|
| 34 |
+
14.8040 -11.9210 16.4570 N 0 0 0 0 0
|
| 35 |
+
15.3450 -11.4350 17.5510 C 0 0 0 0 0
|
| 36 |
+
16.4740 -11.0890 17.7310 O 0 0 0 0 0
|
| 37 |
+
15.6510 -12.3330 15.3350 C 0 0 0 0 0
|
| 38 |
+
16.0390 -13.7960 15.2500 C 0 0 0 0 0
|
| 39 |
+
14.9560 -14.6030 14.5390 C 0 0 0 0 0
|
| 40 |
+
14.5990 -13.9990 13.1800 C 0 0 0 0 0
|
| 41 |
+
14.1680 -12.5610 13.3540 C 0 0 0 0 0
|
| 42 |
+
15.2770 -11.7400 13.9980 C 0 0 0 0 0
|
| 43 |
+
17.9332 -12.2994 22.4536 H 0 0 0 0 0
|
| 44 |
+
19.3882 -12.1140 20.4420 H 0 0 0 0 0
|
| 45 |
+
18.4882 -13.2617 19.5906 H 0 0 0 0 0
|
| 46 |
+
20.4926 -13.9283 21.9484 H 0 0 0 0 0
|
| 47 |
+
20.6127 -14.5392 20.4056 H 0 0 0 0 0
|
| 48 |
+
19.8508 -17.0880 21.4496 H 0 0 0 0 0
|
| 49 |
+
19.9921 -16.1358 22.9627 H 0 0 0 0 0
|
| 50 |
+
18.5327 -18.0092 23.4116 H 0 0 0 0 0
|
| 51 |
+
17.5467 -17.6450 21.9429 H 0 0 0 0 0
|
| 52 |
+
18.5389 -15.7277 20.6035 H 0 0 0 0 0
|
| 53 |
+
15.7428 -17.6818 24.0216 H 0 0 0 0 0
|
| 54 |
+
15.3044 -16.0949 24.2794 H 0 0 0 0 0
|
| 55 |
+
15.4029 -16.6903 22.7262 H 0 0 0 0 0
|
| 56 |
+
17.2937 -17.4623 25.4072 H 0 0 0 0 0
|
| 57 |
+
16.6848 -15.7509 25.5825 H 0 0 0 0 0
|
| 58 |
+
18.2682 -13.8821 25.6602 H 0 0 0 0 0
|
| 59 |
+
20.6133 -13.1939 26.2145 H 0 0 0 0 0
|
| 60 |
+
22.4528 -14.8565 26.4061 H 0 0 0 0 0
|
| 61 |
+
21.8654 -17.3029 26.2740 H 0 0 0 0 0
|
| 62 |
+
19.5640 -18.0094 25.6250 H 0 0 0 0 0
|
| 63 |
+
15.7457 -11.3948 21.5098 H 0 0 0 0 0
|
| 64 |
+
14.5905 -12.8910 19.4839 H 0 0 0 0 0
|
| 65 |
+
14.8425 -10.0689 19.2612 H 0 0 0 0 0
|
| 66 |
+
13.5584 -10.0751 17.3566 H 0 0 0 0 0
|
| 67 |
+
12.4050 -11.7585 19.8909 H 0 0 0 0 0
|
| 68 |
+
13.2901 -10.6141 21.0491 H 0 0 0 0 0
|
| 69 |
+
13.6465 -12.3595 22.5050 H 0 0 0 0 0
|
| 70 |
+
12.7942 -13.5124 21.3496 H 0 0 0 0 0
|
| 71 |
+
10.7892 -12.1043 21.6761 H 0 0 0 0 0
|
| 72 |
+
11.6663 -11.3113 23.0296 H 0 0 0 0 0
|
| 73 |
+
12.0278 -13.5229 24.0031 H 0 0 0 0 0
|
| 74 |
+
10.8774 -14.1769 22.7046 H 0 0 0 0 0
|
| 75 |
+
9.8690 -13.9413 24.8029 H 0 0 0 0 0
|
| 76 |
+
10.2441 -12.3181 24.8427 H 0 0 0 0 0
|
| 77 |
+
9.2101 -12.9059 23.6756 H 0 0 0 0 0
|
| 78 |
+
13.7904 -12.0118 16.3885 H 0 0 0 0 0
|
| 79 |
+
16.5871 -11.8550 15.6237 H 0 0 0 0 0
|
| 80 |
+
16.1623 -14.1864 16.2602 H 0 0 0 0 0
|
| 81 |
+
16.9681 -13.8812 14.6864 H 0 0 0 0 0
|
| 82 |
+
14.0613 -14.5994 15.1616 H 0 0 0 0 0
|
| 83 |
+
15.3317 -15.6133 14.3772 H 0 0 0 0 0
|
| 84 |
+
13.7819 -14.5683 12.7368 H 0 0 0 0 0
|
| 85 |
+
15.4725 -14.0364 12.5291 H 0 0 0 0 0
|
| 86 |
+
13.2893 -12.5323 13.9983 H 0 0 0 0 0
|
| 87 |
+
13.9420 -12.1402 12.3742 H 0 0 0 0 0
|
| 88 |
+
16.1510 -11.7459 13.3467 H 0 0 0 0 0
|
| 89 |
+
14.9268 -10.7183 14.1449 H 0 0 0 0 0
|
| 90 |
+
2 1 1 0 0 0
|
| 91 |
+
1 9 1 0 0 0
|
| 92 |
+
1 11 1 0 0 0
|
| 93 |
+
3 2 1 0 0 0
|
| 94 |
+
8 3 1 0 0 0
|
| 95 |
+
4 5 1 0 0 0
|
| 96 |
+
4 8 1 0 0 0
|
| 97 |
+
5 6 1 0 0 0
|
| 98 |
+
6 7 1 0 0 0
|
| 99 |
+
6 13 1 0 0 0
|
| 100 |
+
6 14 1 0 0 0
|
| 101 |
+
7 9 1 0 0 0
|
| 102 |
+
7 10 2 0 0 0
|
| 103 |
+
8 9 1 0 0 0
|
| 104 |
+
11 12 2 0 0 0
|
| 105 |
+
11 21 1 0 0 0
|
| 106 |
+
14 15 1 0 0 0
|
| 107 |
+
15 16 4 0 0 0
|
| 108 |
+
15 20 4 0 0 0
|
| 109 |
+
16 17 4 0 0 0
|
| 110 |
+
17 18 4 0 0 0
|
| 111 |
+
18 19 4 0 0 0
|
| 112 |
+
19 20 4 0 0 0
|
| 113 |
+
21 22 1 0 0 0
|
| 114 |
+
22 23 1 0 0 0
|
| 115 |
+
22 25 1 0 0 0
|
| 116 |
+
23 24 1 0 0 0
|
| 117 |
+
23 31 1 0 0 0
|
| 118 |
+
25 26 1 0 0 0
|
| 119 |
+
26 27 1 0 0 0
|
| 120 |
+
27 28 1 0 0 0
|
| 121 |
+
28 29 1 0 0 0
|
| 122 |
+
31 30 1 0 0 0
|
| 123 |
+
30 33 1 0 0 0
|
| 124 |
+
31 32 2 0 0 0
|
| 125 |
+
33 34 1 0 0 0
|
| 126 |
+
33 38 1 0 0 0
|
| 127 |
+
34 35 1 0 0 0
|
| 128 |
+
35 36 1 0 0 0
|
| 129 |
+
36 37 1 0 0 0
|
| 130 |
+
37 38 1 0 0 0
|
| 131 |
+
1 39 1 0 0 0
|
| 132 |
+
2 40 1 0 0 0
|
| 133 |
+
2 41 1 0 0 0
|
| 134 |
+
3 42 1 0 0 0
|
| 135 |
+
3 43 1 0 0 0
|
| 136 |
+
4 44 1 0 0 0
|
| 137 |
+
4 45 1 0 0 0
|
| 138 |
+
5 46 1 0 0 0
|
| 139 |
+
5 47 1 0 0 0
|
| 140 |
+
8 48 1 0 0 0
|
| 141 |
+
13 49 1 0 0 0
|
| 142 |
+
13 50 1 0 0 0
|
| 143 |
+
13 51 1 0 0 0
|
| 144 |
+
14 52 1 0 0 0
|
| 145 |
+
14 53 1 0 0 0
|
| 146 |
+
16 54 1 0 0 0
|
| 147 |
+
17 55 1 0 0 0
|
| 148 |
+
18 56 1 0 0 0
|
| 149 |
+
19 57 1 0 0 0
|
| 150 |
+
20 58 1 0 0 0
|
| 151 |
+
21 59 1 0 0 0
|
| 152 |
+
22 60 1 0 0 0
|
| 153 |
+
23 61 1 0 0 0
|
| 154 |
+
24 62 1 0 0 0
|
| 155 |
+
25 63 1 0 0 0
|
| 156 |
+
25 64 1 0 0 0
|
| 157 |
+
26 65 1 0 0 0
|
| 158 |
+
26 66 1 0 0 0
|
| 159 |
+
27 67 1 0 0 0
|
| 160 |
+
27 68 1 0 0 0
|
| 161 |
+
28 69 1 0 0 0
|
| 162 |
+
28 70 1 0 0 0
|
| 163 |
+
29 71 1 0 0 0
|
| 164 |
+
29 72 1 0 0 0
|
| 165 |
+
29 73 1 0 0 0
|
| 166 |
+
30 74 1 0 0 0
|
| 167 |
+
33 75 1 0 0 0
|
| 168 |
+
34 76 1 0 0 0
|
| 169 |
+
34 77 1 0 0 0
|
| 170 |
+
35 78 1 0 0 0
|
| 171 |
+
35 79 1 0 0 0
|
| 172 |
+
36 80 1 0 0 0
|
| 173 |
+
36 81 1 0 0 0
|
| 174 |
+
37 82 1 0 0 0
|
| 175 |
+
37 83 1 0 0 0
|
| 176 |
+
38 84 1 0 0 0
|
| 177 |
+
38 85 1 0 0 0
|
| 178 |
+
M END
|
| 179 |
+
$$$$
|
data/1a46_protein_processed.pdb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/6o5u_ligand.sdf
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
6o5u_ligand
|
| 2 |
+
-I-interpret-
|
| 3 |
+
|
| 4 |
+
73 75 0 0 0 0 0 0 0 0999 V2000
|
| 5 |
+
108.6730 -18.4700 -7.8360 C 0 0 0 0 0
|
| 6 |
+
107.3110 -18.9740 -7.3680 C 0 0 0 0 0
|
| 7 |
+
107.4800 -20.3570 -6.7640 C 0 0 0 0 0
|
| 8 |
+
108.0820 -21.2810 -7.8150 C 0 0 0 0 0
|
| 9 |
+
109.3440 -20.6380 -8.3930 C 0 0 0 0 0
|
| 10 |
+
109.7960 -21.4700 -9.5890 C 0 0 0 0 0
|
| 11 |
+
112.5990 -15.8100 -5.7750 C 0 0 0 0 0
|
| 12 |
+
112.1040 -15.6660 -7.2080 C 0 0 0 0 0
|
| 13 |
+
110.7540 -16.3630 -7.2880 C 0 0 0 0 0
|
| 14 |
+
110.8240 -17.8460 -6.8990 C 0 0 0 0 0
|
| 15 |
+
111.5110 -18.0160 -5.5370 C 0 0 0 0 0
|
| 16 |
+
112.8430 -17.2770 -5.4680 C 0 0 0 0 0
|
| 17 |
+
112.8110 -13.7240 -8.4160 C 0 0 0 0 0
|
| 18 |
+
112.9750 -12.2640 -8.0080 C 0 0 0 0 0
|
| 19 |
+
111.6610 -11.5000 -8.1330 C 0 0 0 0 0
|
| 20 |
+
111.2120 -11.6530 -9.5690 C 0 0 0 0 0
|
| 21 |
+
111.1210 -13.1180 -9.9350 C 0 0 0 0 0
|
| 22 |
+
110.8030 -13.1430 -11.4220 C 0 0 0 0 0
|
| 23 |
+
110.9760 -20.8510 -10.1750 N 0 3 0 0 0
|
| 24 |
+
111.7470 -19.4450 -5.3220 N 0 3 0 0 0
|
| 25 |
+
113.8400 -15.0670 -5.6100 N 0 3 0 0 0
|
| 26 |
+
111.8410 -10.0810 -7.8770 N 0 3 0 0 0
|
| 27 |
+
109.1180 -19.3280 -8.8520 O 0 0 0 0 0
|
| 28 |
+
106.8170 -18.1010 -6.3680 O 0 0 0 0 0
|
| 29 |
+
106.1940 -20.8240 -6.3760 O 0 0 0 0 0
|
| 30 |
+
108.4250 -22.5370 -7.2480 O 0 0 0 0 0
|
| 31 |
+
109.5510 -18.4650 -6.7100 O 0 0 0 0 0
|
| 32 |
+
110.3630 -16.2700 -8.6430 O 0 0 0 0 0
|
| 33 |
+
111.9040 -14.2970 -7.4880 O 0 0 0 0 0
|
| 34 |
+
112.3420 -13.8090 -9.7580 O 0 0 0 0 0
|
| 35 |
+
113.3340 -12.2190 -6.6510 O 0 0 0 0 0
|
| 36 |
+
109.9130 -11.0980 -9.7100 O 0 0 0 0 0
|
| 37 |
+
110.7860 -14.4810 -11.8020 O 0 0 0 0 0
|
| 38 |
+
108.6303 -17.4570 -8.2362 H 0 0 0 0 0
|
| 39 |
+
106.6193 -19.0120 -8.2095 H 0 0 0 0 0
|
| 40 |
+
108.1401 -20.3313 -5.8970 H 0 0 0 0 0
|
| 41 |
+
107.3417 -21.4380 -8.5994 H 0 0 0 0 0
|
| 42 |
+
110.0905 -20.6009 -7.5997 H 0 0 0 0 0
|
| 43 |
+
108.9974 -21.5151 -10.3294 H 0 0 0 0 0
|
| 44 |
+
110.0340 -22.4829 -9.2644 H 0 0 0 0 0
|
| 45 |
+
111.8471 -15.4154 -5.0916 H 0 0 0 0 0
|
| 46 |
+
112.8209 -16.0908 -7.9107 H 0 0 0 0 0
|
| 47 |
+
110.0567 -15.8930 -6.5945 H 0 0 0 0 0
|
| 48 |
+
111.3562 -18.3049 -7.7322 H 0 0 0 0 0
|
| 49 |
+
110.8622 -17.5955 -4.7687 H 0 0 0 0 0
|
| 50 |
+
113.2743 -17.3813 -4.4724 H 0 0 0 0 0
|
| 51 |
+
113.5390 -17.6975 -6.1939 H 0 0 0 0 0
|
| 52 |
+
113.7563 -14.2662 -8.3935 H 0 0 0 0 0
|
| 53 |
+
113.7270 -11.8166 -8.6580 H 0 0 0 0 0
|
| 54 |
+
110.9429 -11.8905 -7.4119 H 0 0 0 0 0
|
| 55 |
+
111.9311 -11.1480 -10.2139 H 0 0 0 0 0
|
| 56 |
+
110.3778 -13.5999 -9.2998 H 0 0 0 0 0
|
| 57 |
+
111.5492 -12.5865 -11.9892 H 0 0 0 0 0
|
| 58 |
+
109.8423 -12.6689 -11.6228 H 0 0 0 0 0
|
| 59 |
+
111.2850 -21.4016 -10.9761 H 0 0 0 0 0
|
| 60 |
+
110.7465 -19.9049 -10.4794 H 0 0 0 0 0
|
| 61 |
+
111.7205 -20.8142 -9.4787 H 0 0 0 0 0
|
| 62 |
+
112.2040 -19.5810 -4.4203 H 0 0 0 0 0
|
| 63 |
+
110.8556 -19.9407 -5.3310 H 0 0 0 0 0
|
| 64 |
+
112.3452 -19.8052 -6.0655 H 0 0 0 0 0
|
| 65 |
+
114.1687 -15.1655 -4.6495 H 0 0 0 0 0
|
| 66 |
+
114.5431 -15.4352 -6.2507 H 0 0 0 0 0
|
| 67 |
+
113.6746 -14.0815 -5.8147 H 0 0 0 0 0
|
| 68 |
+
110.9443 -9.6037 -7.9692 H 0 0 0 0 0
|
| 69 |
+
112.2015 -9.9503 -6.9318 H 0 0 0 0 0
|
| 70 |
+
112.5031 -9.6965 -8.5509 H 0 0 0 0 0
|
| 71 |
+
106.7059 -17.2223 -6.7383 H 0 0 0 0 0
|
| 72 |
+
105.8266 -20.2335 -5.7143 H 0 0 0 0 0
|
| 73 |
+
107.6379 -22.9476 -6.8827 H 0 0 0 0 0
|
| 74 |
+
109.5104 -16.6957 -8.7588 H 0 0 0 0 0
|
| 75 |
+
113.4409 -11.3044 -6.3796 H 0 0 0 0 0
|
| 76 |
+
109.6229 -11.1925 -10.6202 H 0 0 0 0 0
|
| 77 |
+
110.5878 -14.5438 -12.7392 H 0 0 0 0 0
|
| 78 |
+
1 2 1 0 0 0
|
| 79 |
+
1 23 1 0 0 0
|
| 80 |
+
27 1 1 0 0 0
|
| 81 |
+
2 3 1 0 0 0
|
| 82 |
+
2 24 1 0 0 0
|
| 83 |
+
3 4 1 0 0 0
|
| 84 |
+
3 25 1 0 0 0
|
| 85 |
+
5 4 1 0 0 0
|
| 86 |
+
4 26 1 0 0 0
|
| 87 |
+
5 6 1 0 0 0
|
| 88 |
+
23 5 1 0 0 0
|
| 89 |
+
6 19 1 0 0 0
|
| 90 |
+
8 7 1 0 0 0
|
| 91 |
+
7 12 1 0 0 0
|
| 92 |
+
7 21 1 0 0 0
|
| 93 |
+
8 9 1 0 0 0
|
| 94 |
+
29 8 1 0 0 0
|
| 95 |
+
9 10 1 0 0 0
|
| 96 |
+
9 28 1 0 0 0
|
| 97 |
+
10 11 1 0 0 0
|
| 98 |
+
10 27 1 0 0 0
|
| 99 |
+
12 11 1 0 0 0
|
| 100 |
+
11 20 1 0 0 0
|
| 101 |
+
13 14 1 0 0 0
|
| 102 |
+
13 29 1 0 0 0
|
| 103 |
+
13 30 1 0 0 0
|
| 104 |
+
14 15 1 0 0 0
|
| 105 |
+
14 31 1 0 0 0
|
| 106 |
+
15 16 1 0 0 0
|
| 107 |
+
15 22 1 0 0 0
|
| 108 |
+
16 17 1 0 0 0
|
| 109 |
+
16 32 1 0 0 0
|
| 110 |
+
17 18 1 0 0 0
|
| 111 |
+
17 30 1 0 0 0
|
| 112 |
+
18 33 1 0 0 0
|
| 113 |
+
1 34 1 0 0 0
|
| 114 |
+
2 35 1 0 0 0
|
| 115 |
+
3 36 1 0 0 0
|
| 116 |
+
4 37 1 0 0 0
|
| 117 |
+
5 38 1 0 0 0
|
| 118 |
+
6 39 1 0 0 0
|
| 119 |
+
6 40 1 0 0 0
|
| 120 |
+
7 41 1 0 0 0
|
| 121 |
+
8 42 1 0 0 0
|
| 122 |
+
9 43 1 0 0 0
|
| 123 |
+
10 44 1 0 0 0
|
| 124 |
+
11 45 1 0 0 0
|
| 125 |
+
12 46 1 0 0 0
|
| 126 |
+
12 47 1 0 0 0
|
| 127 |
+
13 48 1 0 0 0
|
| 128 |
+
14 49 1 0 0 0
|
| 129 |
+
15 50 1 0 0 0
|
| 130 |
+
16 51 1 0 0 0
|
| 131 |
+
17 52 1 0 0 0
|
| 132 |
+
18 53 1 0 0 0
|
| 133 |
+
18 54 1 0 0 0
|
| 134 |
+
19 55 1 0 0 0
|
| 135 |
+
19 56 1 0 0 0
|
| 136 |
+
19 57 1 0 0 0
|
| 137 |
+
20 58 1 0 0 0
|
| 138 |
+
20 59 1 0 0 0
|
| 139 |
+
20 60 1 0 0 0
|
| 140 |
+
21 61 1 0 0 0
|
| 141 |
+
21 62 1 0 0 0
|
| 142 |
+
21 63 1 0 0 0
|
| 143 |
+
22 64 1 0 0 0
|
| 144 |
+
22 65 1 0 0 0
|
| 145 |
+
22 66 1 0 0 0
|
| 146 |
+
24 67 1 0 0 0
|
| 147 |
+
25 68 1 0 0 0
|
| 148 |
+
26 69 1 0 0 0
|
| 149 |
+
28 70 1 0 0 0
|
| 150 |
+
31 71 1 0 0 0
|
| 151 |
+
32 72 1 0 0 0
|
| 152 |
+
33 73 1 0 0 0
|
| 153 |
+
M END
|
| 154 |
+
$$$$
|
data/6o5u_protein_processed.pdb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
models/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__all__ = [
|
| 2 |
+
"CGModel",
|
| 3 |
+
"build_score_model",
|
| 4 |
+
"load_model_args",
|
| 5 |
+
"load_score_model",
|
| 6 |
+
"model_uses_lm_embeddings",
|
| 7 |
+
]
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def __getattr__(name):
|
| 11 |
+
if name == "CGModel":
|
| 12 |
+
from .cg_model import CGModel
|
| 13 |
+
|
| 14 |
+
return CGModel
|
| 15 |
+
if name in {
|
| 16 |
+
"build_score_model",
|
| 17 |
+
"load_model_args",
|
| 18 |
+
"load_score_model",
|
| 19 |
+
"model_uses_lm_embeddings",
|
| 20 |
+
}:
|
| 21 |
+
from .score_wrapper import (
|
| 22 |
+
build_score_model,
|
| 23 |
+
load_model_args,
|
| 24 |
+
load_score_model,
|
| 25 |
+
model_uses_lm_embeddings,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
return {
|
| 29 |
+
"build_score_model": build_score_model,
|
| 30 |
+
"load_model_args": load_model_args,
|
| 31 |
+
"load_score_model": load_score_model,
|
| 32 |
+
"model_uses_lm_embeddings": model_uses_lm_embeddings,
|
| 33 |
+
}[name]
|
| 34 |
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
models/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (928 Bytes). View file
|
|
|
models/__pycache__/aa_model.cpython-310.pyc
ADDED
|
Binary file (21.3 kB). View file
|
|
|
models/__pycache__/aa_model.cpython-311.pyc
ADDED
|
Binary file (53.4 kB). View file
|
|
|
models/__pycache__/cg_model.cpython-310.pyc
ADDED
|
Binary file (21.8 kB). View file
|
|
|
models/__pycache__/cg_model.cpython-311.pyc
ADDED
|
Binary file (51.9 kB). View file
|
|
|
models/__pycache__/layers.cpython-311.pyc
ADDED
|
Binary file (8.54 kB). View file
|
|
|
models/__pycache__/old_aa_model.cpython-310.pyc
ADDED
|
Binary file (17.7 kB). View file
|
|
|
models/__pycache__/old_aa_model.cpython-311.pyc
ADDED
|
Binary file (45.3 kB). View file
|
|
|
models/__pycache__/score_wrapper.cpython-310.pyc
ADDED
|
Binary file (5.76 kB). View file
|
|
|
models/__pycache__/score_wrapper.cpython-311.pyc
ADDED
|
Binary file (10.4 kB). View file
|
|
|
models/__pycache__/tensor_layers.cpython-310.pyc
ADDED
|
Binary file (10.9 kB). View file
|
|
|
models/__pycache__/tensor_layers.cpython-311.pyc
ADDED
|
Binary file (22.8 kB). View file
|
|
|
models/aa_model.py
ADDED
|
@@ -0,0 +1,721 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from e3nn import o3
|
| 2 |
+
import torch
|
| 3 |
+
from torch import nn
|
| 4 |
+
from torch.nn import functional as F
|
| 5 |
+
from torch_cluster import radius, radius_graph
|
| 6 |
+
from torch_geometric.utils import subgraph
|
| 7 |
+
from torch_scatter import scatter_mean
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
from onescience.datapipes.diffdock.process_mols import lig_feature_dims, rec_residue_feature_dims, rec_atom_feature_dims
|
| 11 |
+
from onescience.utils.diffdock import so3, torus
|
| 12 |
+
|
| 13 |
+
from .layers import GaussianSmearing, AtomEncoder
|
| 14 |
+
from .tensor_layers import get_irrep_seq, TensorProductConvLayer
|
| 15 |
+
|
| 16 |
+
try:
|
| 17 |
+
from esm.pretrained import load_model_and_alphabet
|
| 18 |
+
except ImportError:
|
| 19 |
+
load_model_and_alphabet = None
|
| 20 |
+
|
| 21 |
+
AGGREGATORS = {"mean": lambda x: torch.mean(x, dim=1),
|
| 22 |
+
"max": lambda x: torch.max(x, dim=1)[0],
|
| 23 |
+
"min": lambda x: torch.min(x, dim=1)[0],
|
| 24 |
+
"std": lambda x: torch.std(x, dim=1)}
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class AAModel(torch.nn.Module):
|
| 28 |
+
def __init__(self, t_to_sigma, device, timestep_emb_func, in_lig_edge_features=4, sigma_embed_dim=32, sh_lmax=2,
|
| 29 |
+
ns=16, nv=4, num_conv_layers=2, lig_max_radius=5, rec_max_radius=30, cross_max_distance=250,
|
| 30 |
+
center_max_distance=30, distance_embed_dim=32, cross_distance_embed_dim=32, no_torsion=False,
|
| 31 |
+
scale_by_sigma=True, norm_by_sigma=True, use_second_order_repr=False, batch_norm=True,
|
| 32 |
+
dynamic_max_cross=False, dropout=0.0, smooth_edges=False, odd_parity=False,
|
| 33 |
+
separate_noise_schedule=False, lm_embedding_type=None, confidence_mode=False,
|
| 34 |
+
confidence_dropout=0, confidence_no_batchnorm = False,
|
| 35 |
+
asyncronous_noise_schedule=False, affinity_prediction=False, parallel=1,
|
| 36 |
+
parallel_aggregators="mean max min std", num_confidence_outputs=1, atom_num_confidence_outputs=1, fixed_center_conv=False,
|
| 37 |
+
no_aminoacid_identities=False, include_miscellaneous_atoms=False,
|
| 38 |
+
differentiate_convolutions=True, tp_weights_layers=2, num_prot_emb_layers=0,
|
| 39 |
+
reduce_pseudoscalars=False, embed_also_ligand=False, atom_confidence=False, sidechain_pred=False,
|
| 40 |
+
depthwise_convolution=False, crop_beyond=None):
|
| 41 |
+
super(AAModel, self).__init__()
|
| 42 |
+
assert (not no_aminoacid_identities) or (lm_embedding_type is None), "no language model emb without identities"
|
| 43 |
+
assert not sidechain_pred, "sidechain prediction not implemented/makes sense for all atom model"
|
| 44 |
+
assert not depthwise_convolution, "depthwise convolution not implemented for all atom model"
|
| 45 |
+
if parallel > 1: assert affinity_prediction
|
| 46 |
+
|
| 47 |
+
self.t_to_sigma = t_to_sigma
|
| 48 |
+
self.in_lig_edge_features = in_lig_edge_features
|
| 49 |
+
sigma_embed_dim *= (3 if separate_noise_schedule else 1)
|
| 50 |
+
self.sigma_embed_dim = sigma_embed_dim
|
| 51 |
+
self.lig_max_radius = lig_max_radius
|
| 52 |
+
self.rec_max_radius = rec_max_radius
|
| 53 |
+
self.cross_max_distance = cross_max_distance
|
| 54 |
+
self.dynamic_max_cross = dynamic_max_cross
|
| 55 |
+
self.center_max_distance = center_max_distance
|
| 56 |
+
self.distance_embed_dim = distance_embed_dim
|
| 57 |
+
self.cross_distance_embed_dim = cross_distance_embed_dim
|
| 58 |
+
self.sh_irreps = o3.Irreps.spherical_harmonics(lmax=sh_lmax)
|
| 59 |
+
self.ns, self.nv = ns, nv
|
| 60 |
+
self.scale_by_sigma = scale_by_sigma
|
| 61 |
+
self.norm_by_sigma = norm_by_sigma
|
| 62 |
+
self.device = device
|
| 63 |
+
self.no_torsion = no_torsion
|
| 64 |
+
self.smooth_edges = smooth_edges
|
| 65 |
+
self.odd_parity = odd_parity
|
| 66 |
+
self.num_conv_layers = num_conv_layers
|
| 67 |
+
self.timestep_emb_func = timestep_emb_func
|
| 68 |
+
self.separate_noise_schedule = separate_noise_schedule
|
| 69 |
+
self.confidence_mode = confidence_mode
|
| 70 |
+
self.num_conv_layers = num_conv_layers
|
| 71 |
+
self.num_prot_emb_layers = num_prot_emb_layers
|
| 72 |
+
self.asyncronous_noise_schedule = asyncronous_noise_schedule
|
| 73 |
+
self.affinity_prediction = affinity_prediction
|
| 74 |
+
self.parallel, self.parallel_aggregators = parallel, parallel_aggregators.split(' ')
|
| 75 |
+
self.fixed_center_conv = fixed_center_conv
|
| 76 |
+
self.no_aminoacid_identities = no_aminoacid_identities
|
| 77 |
+
self.differentiate_convolutions = differentiate_convolutions
|
| 78 |
+
self.reduce_pseudoscalars = reduce_pseudoscalars
|
| 79 |
+
self.atom_confidence = atom_confidence
|
| 80 |
+
self.atom_num_confidence_outputs = atom_num_confidence_outputs
|
| 81 |
+
self.crop_beyond = crop_beyond
|
| 82 |
+
|
| 83 |
+
self.lm_embedding_type = lm_embedding_type
|
| 84 |
+
if lm_embedding_type is None:
|
| 85 |
+
lm_embedding_dim = 0
|
| 86 |
+
elif lm_embedding_type == "precomputed":
|
| 87 |
+
lm_embedding_dim=1280
|
| 88 |
+
else:
|
| 89 |
+
if load_model_and_alphabet is None:
|
| 90 |
+
raise ImportError(
|
| 91 |
+
"esm is required when lm_embedding_type is not None and not 'precomputed'."
|
| 92 |
+
)
|
| 93 |
+
lm, alphabet = load_model_and_alphabet(lm_embedding_type)
|
| 94 |
+
self.batch_converter = alphabet.get_batch_converter()
|
| 95 |
+
lm.lm_head = torch.nn.Identity()
|
| 96 |
+
lm.contact_head = torch.nn.Identity()
|
| 97 |
+
lm_embedding_dim = lm.embed_dim
|
| 98 |
+
self.lm = lm
|
| 99 |
+
|
| 100 |
+
# embedding layers
|
| 101 |
+
atom_encoder_class = AtomEncoder
|
| 102 |
+
self.lig_node_embedding = atom_encoder_class(emb_dim=ns, feature_dims=lig_feature_dims, sigma_embed_dim=sigma_embed_dim)
|
| 103 |
+
self.lig_edge_embedding = nn.Sequential(nn.Linear(in_lig_edge_features + sigma_embed_dim + distance_embed_dim, ns),nn.ReLU(),nn.Dropout(dropout),nn.Linear(ns, ns))
|
| 104 |
+
|
| 105 |
+
self.rec_sigma_embedding = nn.Sequential(nn.Linear(sigma_embed_dim, ns), nn.ReLU(), nn.Dropout(dropout), nn.Linear(ns, ns))
|
| 106 |
+
self.rec_node_embedding = atom_encoder_class(emb_dim=ns, feature_dims=rec_residue_feature_dims, sigma_embed_dim=0, lm_embedding_dim=lm_embedding_dim)
|
| 107 |
+
self.rec_edge_embedding = nn.Sequential(nn.Linear(distance_embed_dim, ns), nn.ReLU(), nn.Dropout(dropout), nn.Linear(ns, ns))
|
| 108 |
+
self.atom_node_embedding = atom_encoder_class(emb_dim=ns, feature_dims=rec_atom_feature_dims, sigma_embed_dim=0)
|
| 109 |
+
self.atom_edge_embedding = nn.Sequential(nn.Linear(distance_embed_dim, ns), nn.ReLU(), nn.Dropout(dropout), nn.Linear(ns, ns))
|
| 110 |
+
|
| 111 |
+
self.lr_edge_embedding = nn.Sequential(nn.Linear(sigma_embed_dim + cross_distance_embed_dim, ns), nn.ReLU(), nn.Dropout(dropout),nn.Linear(ns, ns))
|
| 112 |
+
self.ar_edge_embedding = nn.Sequential(nn.Linear(distance_embed_dim, ns), nn.ReLU(), nn.Dropout(dropout),nn.Linear(ns, ns))
|
| 113 |
+
self.la_edge_embedding = nn.Sequential(nn.Linear(sigma_embed_dim + cross_distance_embed_dim, ns), nn.ReLU(), nn.Dropout(dropout),nn.Linear(ns, ns))
|
| 114 |
+
|
| 115 |
+
self.lig_distance_expansion = GaussianSmearing(0.0, lig_max_radius, distance_embed_dim)
|
| 116 |
+
self.rec_distance_expansion = GaussianSmearing(0.0, rec_max_radius, distance_embed_dim)
|
| 117 |
+
self.cross_distance_expansion = GaussianSmearing(0.0, cross_max_distance, cross_distance_embed_dim)
|
| 118 |
+
|
| 119 |
+
irrep_seq = get_irrep_seq(ns, nv, use_second_order_repr, reduce_pseudoscalars)
|
| 120 |
+
assert not include_miscellaneous_atoms, "currently not supported"
|
| 121 |
+
|
| 122 |
+
rec_emb_layers = []
|
| 123 |
+
for i in range(num_prot_emb_layers):
|
| 124 |
+
in_irreps = irrep_seq[min(i, len(irrep_seq) - 1)]
|
| 125 |
+
out_irreps = irrep_seq[min(i + 1, len(irrep_seq) - 1)]
|
| 126 |
+
layer = TensorProductConvLayer(
|
| 127 |
+
in_irreps=in_irreps,
|
| 128 |
+
sh_irreps=self.sh_irreps,
|
| 129 |
+
out_irreps=out_irreps,
|
| 130 |
+
n_edge_features=3 * ns,
|
| 131 |
+
hidden_features=3 * ns,
|
| 132 |
+
residual=True,
|
| 133 |
+
batch_norm=batch_norm,
|
| 134 |
+
dropout=dropout,
|
| 135 |
+
faster=sh_lmax == 1 and not use_second_order_repr,
|
| 136 |
+
tp_weights_layers=tp_weights_layers,
|
| 137 |
+
edge_groups=1 if not differentiate_convolutions else 4,
|
| 138 |
+
)
|
| 139 |
+
rec_emb_layers.append(layer)
|
| 140 |
+
self.rec_emb_layers = nn.ModuleList(rec_emb_layers)
|
| 141 |
+
|
| 142 |
+
self.embed_also_ligand = embed_also_ligand
|
| 143 |
+
if embed_also_ligand:
|
| 144 |
+
lig_emb_layers = []
|
| 145 |
+
for i in range(num_prot_emb_layers):
|
| 146 |
+
in_irreps = irrep_seq[min(i, len(irrep_seq) - 1)]
|
| 147 |
+
out_irreps = irrep_seq[min(i + 1, len(irrep_seq) - 1)]
|
| 148 |
+
layer = TensorProductConvLayer(
|
| 149 |
+
in_irreps=in_irreps,
|
| 150 |
+
sh_irreps=self.sh_irreps,
|
| 151 |
+
out_irreps=out_irreps,
|
| 152 |
+
n_edge_features=3 * ns,
|
| 153 |
+
hidden_features=3 * ns,
|
| 154 |
+
residual=True,
|
| 155 |
+
batch_norm=batch_norm,
|
| 156 |
+
dropout=dropout,
|
| 157 |
+
faster=sh_lmax == 1 and not use_second_order_repr,
|
| 158 |
+
tp_weights_layers=tp_weights_layers,
|
| 159 |
+
edge_groups=1,
|
| 160 |
+
)
|
| 161 |
+
lig_emb_layers.append(layer)
|
| 162 |
+
self.lig_emb_layers = nn.ModuleList(lig_emb_layers)
|
| 163 |
+
|
| 164 |
+
# convolutional layers
|
| 165 |
+
conv_layers = []
|
| 166 |
+
for i in range(num_prot_emb_layers, num_prot_emb_layers + num_conv_layers):
|
| 167 |
+
in_irreps = irrep_seq[min(i, len(irrep_seq) - 1)]
|
| 168 |
+
out_irreps = irrep_seq[min(i + 1, len(irrep_seq) - 1)]
|
| 169 |
+
layer = TensorProductConvLayer(
|
| 170 |
+
in_irreps=in_irreps,
|
| 171 |
+
sh_irreps=self.sh_irreps,
|
| 172 |
+
out_irreps=out_irreps,
|
| 173 |
+
n_edge_features=3 * ns,
|
| 174 |
+
hidden_features=3 * ns,
|
| 175 |
+
residual=True,
|
| 176 |
+
batch_norm=batch_norm,
|
| 177 |
+
dropout=dropout,
|
| 178 |
+
faster=sh_lmax == 1 and not use_second_order_repr,
|
| 179 |
+
tp_weights_layers=tp_weights_layers,
|
| 180 |
+
edge_groups=1 if not differentiate_convolutions else (3 if i == num_prot_emb_layers + num_conv_layers - 1 else 9),
|
| 181 |
+
)
|
| 182 |
+
conv_layers.append(layer)
|
| 183 |
+
self.conv_layers = nn.ModuleList(conv_layers)
|
| 184 |
+
|
| 185 |
+
# confidence and affinity prediction layers
|
| 186 |
+
if self.confidence_mode:
|
| 187 |
+
if self.affinity_prediction:
|
| 188 |
+
if self.parallel > 1:
|
| 189 |
+
output_confidence_dim = 1 + ns
|
| 190 |
+
else:
|
| 191 |
+
output_confidence_dim = num_confidence_outputs + 1
|
| 192 |
+
else:
|
| 193 |
+
output_confidence_dim = num_confidence_outputs
|
| 194 |
+
|
| 195 |
+
input_size = ns + (nv if reduce_pseudoscalars else ns) if num_conv_layers + num_prot_emb_layers >= 3 else ns
|
| 196 |
+
|
| 197 |
+
if self.atom_confidence:
|
| 198 |
+
self.atom_confidence_predictor = nn.Sequential(
|
| 199 |
+
nn.Linear(input_size, ns),
|
| 200 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 201 |
+
nn.ReLU(),
|
| 202 |
+
nn.Dropout(confidence_dropout),
|
| 203 |
+
nn.Linear(ns, ns),
|
| 204 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 205 |
+
nn.ReLU(),
|
| 206 |
+
nn.Dropout(confidence_dropout),
|
| 207 |
+
nn.Linear(ns, atom_num_confidence_outputs + ns)
|
| 208 |
+
)
|
| 209 |
+
input_size = ns
|
| 210 |
+
|
| 211 |
+
self.confidence_predictor = nn.Sequential(
|
| 212 |
+
nn.Linear(input_size, ns),
|
| 213 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 214 |
+
nn.ReLU(),
|
| 215 |
+
nn.Dropout(confidence_dropout),
|
| 216 |
+
nn.Linear(ns, ns),
|
| 217 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 218 |
+
nn.ReLU(),
|
| 219 |
+
nn.Dropout(confidence_dropout),
|
| 220 |
+
nn.Linear(ns, output_confidence_dim)
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
if self.parallel > 1:
|
| 224 |
+
self.affinity_predictor = nn.Sequential(
|
| 225 |
+
nn.Linear(len(self.parallel_aggregators) * ns, ns),
|
| 226 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 227 |
+
nn.ReLU(),
|
| 228 |
+
nn.Dropout(confidence_dropout),
|
| 229 |
+
nn.Linear(ns, ns),
|
| 230 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 231 |
+
nn.ReLU(),
|
| 232 |
+
nn.Dropout(confidence_dropout),
|
| 233 |
+
nn.Linear(ns, 1)
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
else:
|
| 237 |
+
# convolution for translational and rotational scores
|
| 238 |
+
self.center_distance_expansion = GaussianSmearing(0.0, center_max_distance, distance_embed_dim)
|
| 239 |
+
self.center_edge_embedding = nn.Sequential(
|
| 240 |
+
nn.Linear(distance_embed_dim + sigma_embed_dim, ns),
|
| 241 |
+
nn.ReLU(),
|
| 242 |
+
nn.Dropout(dropout),
|
| 243 |
+
nn.Linear(ns, ns)
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
self.final_conv = TensorProductConvLayer(
|
| 247 |
+
in_irreps=self.conv_layers[-1].out_irreps,
|
| 248 |
+
sh_irreps=self.sh_irreps,
|
| 249 |
+
out_irreps=f'2x1o + 2x1e' if not self.odd_parity else '1x1o + 1x1e',
|
| 250 |
+
n_edge_features=2 * ns,
|
| 251 |
+
residual=False,
|
| 252 |
+
dropout=dropout,
|
| 253 |
+
batch_norm=batch_norm
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
self.tr_final_layer = nn.Sequential(nn.Linear(1 + sigma_embed_dim, ns),nn.Dropout(dropout), nn.ReLU(), nn.Linear(ns, 1))
|
| 257 |
+
self.rot_final_layer = nn.Sequential(nn.Linear(1 + sigma_embed_dim, ns),nn.Dropout(dropout), nn.ReLU(), nn.Linear(ns, 1))
|
| 258 |
+
|
| 259 |
+
if not no_torsion:
|
| 260 |
+
# convolution for torsional score
|
| 261 |
+
self.final_edge_embedding = nn.Sequential(
|
| 262 |
+
nn.Linear(distance_embed_dim, ns),
|
| 263 |
+
nn.ReLU(),
|
| 264 |
+
nn.Dropout(dropout),
|
| 265 |
+
nn.Linear(ns, ns)
|
| 266 |
+
)
|
| 267 |
+
self.final_tp_tor = o3.FullTensorProduct(self.sh_irreps, "2e")
|
| 268 |
+
self.tor_bond_conv = TensorProductConvLayer(
|
| 269 |
+
in_irreps=self.conv_layers[-1].out_irreps,
|
| 270 |
+
sh_irreps=self.final_tp_tor.irreps_out,
|
| 271 |
+
out_irreps=f'{ns}x0o + {ns}x0e' if not self.odd_parity else f'{ns}x0o',
|
| 272 |
+
n_edge_features=3 * ns,
|
| 273 |
+
residual=False,
|
| 274 |
+
dropout=dropout,
|
| 275 |
+
batch_norm=batch_norm
|
| 276 |
+
)
|
| 277 |
+
self.tor_final_layer = nn.Sequential(
|
| 278 |
+
nn.Linear(2 * ns if not self.odd_parity else ns, ns, bias=False),
|
| 279 |
+
nn.Tanh(),
|
| 280 |
+
nn.Dropout(dropout),
|
| 281 |
+
nn.Linear(ns, 1, bias=False)
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
@staticmethod
|
| 285 |
+
def _resolve_edge_store(data, primary_key, fallback_key):
|
| 286 |
+
edge_types = getattr(data, "edge_types", ())
|
| 287 |
+
if primary_key in edge_types:
|
| 288 |
+
return data[primary_key]
|
| 289 |
+
if fallback_key in edge_types:
|
| 290 |
+
return data[fallback_key]
|
| 291 |
+
try:
|
| 292 |
+
return data[primary_key]
|
| 293 |
+
except Exception:
|
| 294 |
+
return data[fallback_key]
|
| 295 |
+
|
| 296 |
+
def _ligand_edge_store(self, data):
|
| 297 |
+
return self._resolve_edge_store(
|
| 298 |
+
data,
|
| 299 |
+
("ligand", "ligand"),
|
| 300 |
+
("ligand", "lig_bond", "ligand"),
|
| 301 |
+
)
|
| 302 |
+
|
| 303 |
+
def _receptor_edge_store(self, data):
|
| 304 |
+
return self._resolve_edge_store(
|
| 305 |
+
data,
|
| 306 |
+
("receptor", "receptor"),
|
| 307 |
+
("receptor", "rec_contact", "receptor"),
|
| 308 |
+
)
|
| 309 |
+
|
| 310 |
+
def _atom_edge_store(self, data):
|
| 311 |
+
return self._resolve_edge_store(
|
| 312 |
+
data,
|
| 313 |
+
("atom", "atom"),
|
| 314 |
+
("atom", "atom_contact", "atom"),
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
def _atom_receptor_edge_store(self, data):
|
| 318 |
+
return self._resolve_edge_store(
|
| 319 |
+
data,
|
| 320 |
+
("atom", "receptor"),
|
| 321 |
+
("atom", "atom_rec_contact", "receptor"),
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
+
def embedding(self, data):
|
| 325 |
+
receptor_edge_store = self._receptor_edge_store(data)
|
| 326 |
+
atom_edge_store = self._atom_edge_store(data)
|
| 327 |
+
atom_receptor_edge_store = self._atom_receptor_edge_store(data)
|
| 328 |
+
if not hasattr(data['receptor'], "rec_node_attr"):
|
| 329 |
+
if self.lm_embedding_type not in [None, 'precomputed']:
|
| 330 |
+
sequences = [s for l in data['receptor'].sequence for s in l]
|
| 331 |
+
if isinstance(sequences[0], list):
|
| 332 |
+
sequences = [s for l in sequences for s in l]
|
| 333 |
+
sequences = [(i, s) for i, s in enumerate(sequences)]
|
| 334 |
+
batch_labels, batch_strs, batch_tokens = self.batch_converter(sequences)
|
| 335 |
+
out = self.lm(batch_tokens.to(data['receptor'].x.device), repr_layers=[self.lm.num_layers], return_contacts=False)
|
| 336 |
+
rec_lm_emb = torch.cat([t[:len(sequences[i][1])] for i, t in enumerate(out['representations'][self.lm.num_layers])], dim=0)
|
| 337 |
+
data['receptor'].x = torch.cat([data['receptor'].x, rec_lm_emb], dim=-1)
|
| 338 |
+
|
| 339 |
+
rec_node_attr, rec_edge_attr, rec_edge_sh, rec_edge_weight = self.build_rec_conv_graph(data)
|
| 340 |
+
rec_node_attr = self.rec_node_embedding(rec_node_attr)
|
| 341 |
+
rec_edge_attr = self.rec_edge_embedding(rec_edge_attr)
|
| 342 |
+
|
| 343 |
+
atom_node_attr, atom_edge_attr, atom_edge_sh, atom_edge_weight = self.build_atom_conv_graph(data)
|
| 344 |
+
atom_node_attr = self.atom_node_embedding(atom_node_attr)
|
| 345 |
+
atom_edge_attr = self.atom_edge_embedding(atom_edge_attr)
|
| 346 |
+
|
| 347 |
+
ar_edge_attr, ar_edge_sh, ar_edge_weight = self.build_cross_rec_conv_graph(data)
|
| 348 |
+
ar_edge_attr = self.ar_edge_embedding(ar_edge_attr)
|
| 349 |
+
|
| 350 |
+
rec_edge_index = receptor_edge_store.edge_index.clone()
|
| 351 |
+
atom_edge_index = atom_edge_store.edge_index.clone()
|
| 352 |
+
ar_edge_index = atom_receptor_edge_store.edge_index.clone()
|
| 353 |
+
|
| 354 |
+
node_attr = torch.cat([rec_node_attr, atom_node_attr], dim=0)
|
| 355 |
+
ar_edge_index[0] = ar_edge_index[0] + len(rec_node_attr)
|
| 356 |
+
edge_index = torch.cat([rec_edge_index, ar_edge_index, atom_edge_index + len(rec_node_attr), torch.flip(ar_edge_index, dims=[0])], dim=1)
|
| 357 |
+
edge_attr = torch.cat([rec_edge_attr, ar_edge_attr, atom_edge_attr, ar_edge_attr], dim=0)
|
| 358 |
+
edge_sh = torch.cat([rec_edge_sh, ar_edge_sh, atom_edge_sh, ar_edge_sh], dim=0)
|
| 359 |
+
edge_weight = torch.cat([rec_edge_weight, ar_edge_weight, atom_edge_weight, ar_edge_weight], dim=0) \
|
| 360 |
+
if torch.is_tensor(rec_edge_weight) else torch.ones((len(edge_index[0]), 1), device=edge_index.device)
|
| 361 |
+
s1, s2, s3 = len(rec_edge_index[0]), len(rec_edge_index[0]) + len(ar_edge_index[0]), len(rec_edge_index[0]) + len(ar_edge_index[0]) + len(atom_edge_index[0])
|
| 362 |
+
|
| 363 |
+
for l in range(len(self.rec_emb_layers)):
|
| 364 |
+
edge_attr_ = torch.cat(
|
| 365 |
+
[edge_attr, node_attr[edge_index[0], :self.ns], node_attr[edge_index[1], :self.ns]], -1)
|
| 366 |
+
if self.differentiate_convolutions: edge_attr_ = [edge_attr_[:s1], edge_attr_[s1:s2], edge_attr_[s2:s3], edge_attr_[s3:]]
|
| 367 |
+
node_attr = self.rec_emb_layers[l](node_attr, edge_index, edge_attr_, edge_sh, edge_weight=edge_weight)
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
data['receptor'].rec_node_attr = node_attr[:len(rec_node_attr)]
|
| 371 |
+
receptor_edge_store.rec_edge_attr = rec_edge_attr
|
| 372 |
+
receptor_edge_store.edge_sh = rec_edge_sh
|
| 373 |
+
receptor_edge_store.edge_weight = rec_edge_weight
|
| 374 |
+
|
| 375 |
+
data['atom'].atom_node_attr = node_attr[len(rec_node_attr):]
|
| 376 |
+
atom_edge_store.atom_edge_attr = atom_edge_attr
|
| 377 |
+
atom_edge_store.edge_sh = atom_edge_sh
|
| 378 |
+
atom_edge_store.edge_weight = atom_edge_weight
|
| 379 |
+
|
| 380 |
+
atom_receptor_edge_store.edge_attr = ar_edge_attr
|
| 381 |
+
atom_receptor_edge_store.edge_sh = ar_edge_sh
|
| 382 |
+
atom_receptor_edge_store.edge_weight = ar_edge_weight
|
| 383 |
+
|
| 384 |
+
# receptor embedding
|
| 385 |
+
rec_sigma_emb = self.rec_sigma_embedding(self.timestep_emb_func(data.complex_t['tr']))
|
| 386 |
+
rec_node_attr = data['receptor'].rec_node_attr + 0
|
| 387 |
+
rec_node_attr[:, :self.ns] = rec_node_attr[:, :self.ns] + rec_sigma_emb[data['receptor'].batch]
|
| 388 |
+
rec_edge_attr = receptor_edge_store.rec_edge_attr + rec_sigma_emb[data['receptor'].batch[receptor_edge_store.edge_index[0]]]
|
| 389 |
+
|
| 390 |
+
# atom embedding
|
| 391 |
+
atom_node_attr = data['atom'].atom_node_attr + 0
|
| 392 |
+
atom_node_attr[:, :self.ns] = atom_node_attr[:, :self.ns] + rec_sigma_emb[data['atom'].batch]
|
| 393 |
+
atom_edge_attr = atom_edge_store.atom_edge_attr + rec_sigma_emb[data['atom'].batch[atom_edge_store.edge_index[0]]]
|
| 394 |
+
|
| 395 |
+
# atom-receptor embedding
|
| 396 |
+
ar_edge_attr = atom_receptor_edge_store.edge_attr + rec_sigma_emb[data['atom'].batch[atom_receptor_edge_store.edge_index[0]]]
|
| 397 |
+
|
| 398 |
+
# ligand embedding
|
| 399 |
+
lig_node_attr, lig_edge_index, lig_edge_attr, lig_edge_sh, lig_edge_weight = self.build_lig_conv_graph(data)
|
| 400 |
+
lig_node_attr = self.lig_node_embedding(lig_node_attr)
|
| 401 |
+
lig_edge_attr = self.lig_edge_embedding(lig_edge_attr)
|
| 402 |
+
|
| 403 |
+
if self.embed_also_ligand:
|
| 404 |
+
for l in range(len(self.lig_emb_layers)):
|
| 405 |
+
edge_attr_ = torch.cat([lig_edge_attr, lig_node_attr[lig_edge_index[0], :self.ns], lig_node_attr[lig_edge_index[1], :self.ns]], -1)
|
| 406 |
+
lig_node_attr = self.lig_emb_layers[l](lig_node_attr, lig_edge_index, edge_attr_, lig_edge_sh, edge_weight=lig_edge_weight)
|
| 407 |
+
|
| 408 |
+
else:
|
| 409 |
+
lig_node_attr = F.pad(lig_node_attr, (0, rec_node_attr.shape[-1] - lig_node_attr.shape[-1]))
|
| 410 |
+
|
| 411 |
+
return lig_node_attr, lig_edge_index, lig_edge_attr, lig_edge_sh, lig_edge_weight, \
|
| 412 |
+
rec_node_attr, receptor_edge_store.edge_index, rec_edge_attr, receptor_edge_store.edge_sh, receptor_edge_store.edge_weight, \
|
| 413 |
+
atom_node_attr, atom_edge_store.edge_index, atom_edge_attr, atom_edge_store.edge_sh, atom_edge_store.edge_weight, \
|
| 414 |
+
atom_receptor_edge_store.edge_index, ar_edge_attr, atom_receptor_edge_store.edge_sh, atom_receptor_edge_store.edge_weight
|
| 415 |
+
|
| 416 |
+
def forward(self, data):
|
| 417 |
+
if self.crop_beyond is not None:
|
| 418 |
+
# TODO missing filtering atoms
|
| 419 |
+
raise NotImplementedError
|
| 420 |
+
ligand_pos = data['ligand'].pos
|
| 421 |
+
receptor_pos = data['receptor'].pos
|
| 422 |
+
residues_to_keep = torch.any(torch.sum((ligand_pos.unsqueeze(0) - receptor_pos.unsqueeze(1)) ** 2, -1) < self.crop_beyond ** 2, dim=1)
|
| 423 |
+
|
| 424 |
+
data['receptor'].pos = data['receptor'].pos[residues_to_keep]
|
| 425 |
+
data['receptor'].x = data['receptor'].x[residues_to_keep]
|
| 426 |
+
data['receptor'].side_chain_vecs = data['receptor'].side_chain_vecs[residues_to_keep]
|
| 427 |
+
data['receptor', 'rec_contact', 'receptor'].edge_index = subgraph(residues_to_keep, data['receptor', 'rec_contact', 'receptor'].edge_index, relabel_nodes=True)[0]
|
| 428 |
+
|
| 429 |
+
if self.no_aminoacid_identities:
|
| 430 |
+
data['receptor'].x = data['receptor'].x * 0
|
| 431 |
+
|
| 432 |
+
if not self.confidence_mode:
|
| 433 |
+
tr_sigma, rot_sigma, tor_sigma = self.t_to_sigma(*[data.complex_t[noise_type] for noise_type in ['tr', 'rot', 'tor']])
|
| 434 |
+
else:
|
| 435 |
+
tr_sigma, rot_sigma, tor_sigma = [data.complex_t[noise_type] for noise_type in ['tr', 'rot', 'tor']]
|
| 436 |
+
|
| 437 |
+
lig_node_attr, lig_edge_index, lig_edge_attr, lig_edge_sh, lig_edge_weight, rec_node_attr, \
|
| 438 |
+
rec_edge_index, rec_edge_attr, rec_edge_sh, rec_edge_weight,\
|
| 439 |
+
atom_node_attr, atom_edge_index, atom_edge_attr, atom_edge_sh, atom_edge_weight, \
|
| 440 |
+
ar_edge_index, ar_edge_attr, ar_edge_sh, ar_edge_weight = self.embedding(data)
|
| 441 |
+
|
| 442 |
+
# build lig cross graph
|
| 443 |
+
cross_cutoff = (tr_sigma * 3 + 20).unsqueeze(1) if self.dynamic_max_cross else self.cross_max_distance
|
| 444 |
+
lr_edge_index, lr_edge_attr, lr_edge_sh, lr_edge_weight, la_edge_index, la_edge_attr, \
|
| 445 |
+
la_edge_sh, la_edge_weight = self.build_cross_lig_conv_graph(data, cross_cutoff)
|
| 446 |
+
lr_edge_attr= self.lr_edge_embedding(lr_edge_attr)
|
| 447 |
+
la_edge_attr = self.la_edge_embedding(la_edge_attr)
|
| 448 |
+
|
| 449 |
+
n_lig, n_rec = len(lig_node_attr), len(rec_node_attr)
|
| 450 |
+
|
| 451 |
+
node_attr = torch.cat([lig_node_attr, rec_node_attr, atom_node_attr], dim=0)
|
| 452 |
+
rec_edge_index, atom_edge_index, lr_edge_index, la_edge_index, ar_edge_index = rec_edge_index.clone(), atom_edge_index.clone(), lr_edge_index.clone(), la_edge_index.clone(), ar_edge_index.clone()
|
| 453 |
+
rec_edge_index[0], rec_edge_index[1] = rec_edge_index[0] + n_lig, rec_edge_index[1] + n_lig
|
| 454 |
+
atom_edge_index[0], atom_edge_index[1] = atom_edge_index[0] + n_lig + n_rec, atom_edge_index[1] + n_lig + n_rec
|
| 455 |
+
lr_edge_index[1] = lr_edge_index[1] + n_lig
|
| 456 |
+
la_edge_index[1] = la_edge_index[1] + n_lig + n_rec
|
| 457 |
+
ar_edge_index[0], ar_edge_index[1] = ar_edge_index[0] + n_lig + n_rec, ar_edge_index[1] + n_lig
|
| 458 |
+
|
| 459 |
+
edge_index = torch.cat([lig_edge_index, lr_edge_index, la_edge_index, rec_edge_index,
|
| 460 |
+
torch.flip(lr_edge_index, dims=[0]), torch.flip(ar_edge_index, dims=[0]),
|
| 461 |
+
atom_edge_index, torch.flip(la_edge_index, dims=[0]), ar_edge_index], dim=1)
|
| 462 |
+
edge_attr = torch.cat([lig_edge_attr, lr_edge_attr, la_edge_attr, rec_edge_attr, lr_edge_attr,
|
| 463 |
+
ar_edge_attr, atom_edge_attr, la_edge_attr, ar_edge_attr], dim=0)
|
| 464 |
+
edge_sh = torch.cat([lig_edge_sh, lr_edge_sh, la_edge_sh, rec_edge_sh, lr_edge_sh, ar_edge_sh,
|
| 465 |
+
atom_edge_sh, la_edge_sh, ar_edge_sh], dim=0)
|
| 466 |
+
edge_weight = torch.cat([lig_edge_weight, lr_edge_weight, la_edge_weight, rec_edge_weight, lr_edge_weight,
|
| 467 |
+
ar_edge_weight, atom_edge_weight, la_edge_weight, ar_edge_weight],
|
| 468 |
+
dim=0) if torch.is_tensor(lig_edge_weight) else torch.ones((len(edge_index[0]), 1),
|
| 469 |
+
device=edge_index.device)
|
| 470 |
+
s1, s2, s3, s4, s5, s6, s7, s8, _ = tuple(np.cumsum(list(map(len, [lig_edge_attr, lr_edge_attr, la_edge_attr,
|
| 471 |
+
rec_edge_attr, lr_edge_attr, ar_edge_attr, atom_edge_attr, la_edge_attr, ar_edge_attr]))).tolist())
|
| 472 |
+
|
| 473 |
+
for l in range(len(self.conv_layers)):
|
| 474 |
+
if l < len(self.conv_layers) - 1:
|
| 475 |
+
edge_attr_ = torch.cat([edge_attr, node_attr[edge_index[0], :self.ns], node_attr[edge_index[1], :self.ns]], -1)
|
| 476 |
+
if self.differentiate_convolutions: edge_attr_ = [edge_attr_[:s1], edge_attr_[s1:s2], edge_attr_[s2:s3], edge_attr_[s3:s4],
|
| 477 |
+
edge_attr_[s4:s5], edge_attr_[s5:s6], edge_attr_[s6:s7], edge_attr_[s7:s8], edge_attr_[s8:]]
|
| 478 |
+
node_attr = self.conv_layers[l](node_attr, edge_index, edge_attr_, edge_sh, edge_weight=edge_weight)
|
| 479 |
+
else:
|
| 480 |
+
edge_attr_ = torch.cat([edge_attr[:s3], node_attr[edge_index[0, :s3], :self.ns], node_attr[edge_index[1, :s3], :self.ns]], -1)
|
| 481 |
+
if self.differentiate_convolutions: edge_attr_ = [edge_attr_[:s1], edge_attr_[s1:s2], edge_attr_[s2:s3]]
|
| 482 |
+
node_attr = self.conv_layers[l](node_attr, edge_index[:, :s3], edge_attr_, edge_sh[:s3], edge_weight=edge_weight[:s3])
|
| 483 |
+
|
| 484 |
+
lig_node_attr = node_attr[:len(lig_node_attr)]
|
| 485 |
+
|
| 486 |
+
# confidence and affinity prediction
|
| 487 |
+
if self.confidence_mode:
|
| 488 |
+
scalar_lig_attr = torch.cat([lig_node_attr[:,:self.ns], lig_node_attr[:,-(self.nv if self.reduce_pseudoscalars else self.ns):] ], dim=1) \
|
| 489 |
+
if self.num_conv_layers + self.num_prot_emb_layers >= 3 else lig_node_attr[:,:self.ns]
|
| 490 |
+
|
| 491 |
+
if self.atom_confidence:
|
| 492 |
+
scalar_lig_attr = self.atom_confidence_predictor(scalar_lig_attr)
|
| 493 |
+
atom_confidence = scalar_lig_attr[:, :self.atom_num_confidence_outputs]
|
| 494 |
+
scalar_lig_attr = scalar_lig_attr[:, self.atom_num_confidence_outputs:]
|
| 495 |
+
else:
|
| 496 |
+
atom_confidence = torch.zeros((len(lig_node_attr),), device=lig_node_attr.device)
|
| 497 |
+
|
| 498 |
+
confidence = self.confidence_predictor(scatter_mean(scalar_lig_attr, data['ligand'].batch, dim=0)).squeeze(dim=-1)
|
| 499 |
+
|
| 500 |
+
if self.parallel > 1:
|
| 501 |
+
confidence, affinity = confidence[:, 0], confidence[:, 1:]
|
| 502 |
+
confidence = confidence.reshape(data.num_graphs, self.parallel)
|
| 503 |
+
affinity = affinity.reshape(data.num_graphs, self.parallel, -1)
|
| 504 |
+
affinity = torch.cat([AGGREGATORS[agg](affinity) for agg in self.parallel_aggregators], dim=-1)
|
| 505 |
+
affinity = self.affinity_predictor(affinity).squeeze(dim=-1)
|
| 506 |
+
confidence = confidence, affinity
|
| 507 |
+
return confidence, atom_confidence
|
| 508 |
+
assert self.parallel == 1
|
| 509 |
+
|
| 510 |
+
# compute translational and rotational score vectors
|
| 511 |
+
center_edge_index, center_edge_attr, center_edge_sh = self.build_center_conv_graph(data)
|
| 512 |
+
center_edge_attr = self.center_edge_embedding(center_edge_attr)
|
| 513 |
+
if self.fixed_center_conv:
|
| 514 |
+
center_edge_attr = torch.cat([center_edge_attr, lig_node_attr[center_edge_index[1], :self.ns]], -1)
|
| 515 |
+
else:
|
| 516 |
+
center_edge_attr = torch.cat([center_edge_attr, lig_node_attr[center_edge_index[0], :self.ns]], -1)
|
| 517 |
+
global_pred = self.final_conv(lig_node_attr, center_edge_index, center_edge_attr, center_edge_sh, out_nodes=data.num_graphs)
|
| 518 |
+
|
| 519 |
+
tr_pred = global_pred[:, :3] + (global_pred[:, 6:9] if not self.odd_parity else 0)
|
| 520 |
+
rot_pred = global_pred[:, 3:6] + (global_pred[:, 9:] if not self.odd_parity else 0)
|
| 521 |
+
|
| 522 |
+
if self.separate_noise_schedule:
|
| 523 |
+
data.graph_sigma_emb = torch.cat([self.timestep_emb_func(data.complex_t[noise_type]) for noise_type in ['tr', 'rot', 'tor']], dim=1)
|
| 524 |
+
elif self.asyncronous_noise_schedule:
|
| 525 |
+
data.graph_sigma_emb = self.timestep_emb_func(data.complex_t['t'])
|
| 526 |
+
else: # tr rot and tor noise is all the same in this case
|
| 527 |
+
data.graph_sigma_emb = self.timestep_emb_func(data.complex_t['tr'])
|
| 528 |
+
|
| 529 |
+
# adjust the magniture of the score vectors
|
| 530 |
+
tr_norm = torch.linalg.vector_norm(tr_pred, dim=1).unsqueeze(1)
|
| 531 |
+
tr_pred = tr_pred / tr_norm * self.tr_final_layer(torch.cat([tr_norm, data.graph_sigma_emb], dim=1))
|
| 532 |
+
|
| 533 |
+
rot_norm = torch.linalg.vector_norm(rot_pred, dim=1).unsqueeze(1)
|
| 534 |
+
rot_pred = rot_pred / rot_norm * self.rot_final_layer(torch.cat([rot_norm, data.graph_sigma_emb], dim=1))
|
| 535 |
+
|
| 536 |
+
if self.scale_by_sigma:
|
| 537 |
+
tr_pred = tr_pred / tr_sigma.unsqueeze(1)
|
| 538 |
+
rot_pred = rot_pred * so3.score_norm(rot_sigma.cpu()).unsqueeze(1).to(data['ligand'].x.device)
|
| 539 |
+
|
| 540 |
+
if self.no_torsion or data['ligand'].edge_mask.sum() == 0: return tr_pred, rot_pred, torch.empty(0,device=self.device), None
|
| 541 |
+
|
| 542 |
+
# torsional components
|
| 543 |
+
tor_bonds, tor_edge_index, tor_edge_attr, tor_edge_sh, tor_edge_weight = self.build_bond_conv_graph(data)
|
| 544 |
+
tor_bond_vec = data['ligand'].pos[tor_bonds[1]] - data['ligand'].pos[tor_bonds[0]]
|
| 545 |
+
tor_bond_attr = lig_node_attr[tor_bonds[0]] + lig_node_attr[tor_bonds[1]]
|
| 546 |
+
|
| 547 |
+
tor_bonds_sh = o3.spherical_harmonics("2e", tor_bond_vec, normalize=True, normalization='component')
|
| 548 |
+
tor_edge_sh = self.final_tp_tor(tor_edge_sh, tor_bonds_sh[tor_edge_index[0]])
|
| 549 |
+
|
| 550 |
+
tor_edge_attr = torch.cat([tor_edge_attr, lig_node_attr[tor_edge_index[1], :self.ns],
|
| 551 |
+
tor_bond_attr[tor_edge_index[0], :self.ns]], -1)
|
| 552 |
+
tor_pred = self.tor_bond_conv(lig_node_attr, tor_edge_index, tor_edge_attr, tor_edge_sh,
|
| 553 |
+
out_nodes=data['ligand'].edge_mask.sum(), reduce='mean', edge_weight=tor_edge_weight)
|
| 554 |
+
tor_pred = self.tor_final_layer(tor_pred).squeeze(1)
|
| 555 |
+
ligand_edge_store = self._ligand_edge_store(data)
|
| 556 |
+
edge_sigma = tor_sigma[data['ligand'].batch][ligand_edge_store.edge_index[0]][data['ligand'].edge_mask]
|
| 557 |
+
|
| 558 |
+
if self.scale_by_sigma:
|
| 559 |
+
tor_pred = tor_pred * torch.sqrt(torch.tensor(torus.score_norm(edge_sigma.cpu().numpy())).float()
|
| 560 |
+
.to(data['ligand'].x.device))
|
| 561 |
+
return tr_pred, rot_pred, tor_pred, None
|
| 562 |
+
|
| 563 |
+
def get_edge_weight(self, edge_vec, max_norm):
|
| 564 |
+
if self.smooth_edges:
|
| 565 |
+
normalised_norm = torch.clip(edge_vec.norm(dim=-1) * np.pi / max_norm, max=np.pi)
|
| 566 |
+
return 0.5 * (torch.cos(normalised_norm) + 1.0).unsqueeze(-1)
|
| 567 |
+
return 1.0
|
| 568 |
+
|
| 569 |
+
def build_lig_conv_graph(self, data):
|
| 570 |
+
# build the graph between ligand atoms
|
| 571 |
+
if self.separate_noise_schedule:
|
| 572 |
+
data['ligand'].node_sigma_emb = torch.cat(
|
| 573 |
+
[self.timestep_emb_func(data['ligand'].node_t[noise_type]) for noise_type in ['tr', 'rot', 'tor']],
|
| 574 |
+
dim=1)
|
| 575 |
+
elif self.asyncronous_noise_schedule:
|
| 576 |
+
data['ligand'].node_sigma_emb = self.timestep_emb_func(data['ligand'].node_t['t'])
|
| 577 |
+
else:
|
| 578 |
+
data['ligand'].node_sigma_emb = self.timestep_emb_func(
|
| 579 |
+
data['ligand'].node_t['tr']) # tr rot and tor noise is all the same
|
| 580 |
+
|
| 581 |
+
if self.parallel == 1:
|
| 582 |
+
radius_edges = radius_graph(data['ligand'].pos, self.lig_max_radius, data['ligand'].batch)
|
| 583 |
+
else:
|
| 584 |
+
batches = torch.zeros(data.num_graphs, device=data['ligand'].x.device).long()
|
| 585 |
+
batches = batches.index_add(0, data['ligand'].batch, torch.ones(len(data['ligand'].batch), device=data['ligand'].x.device).long())
|
| 586 |
+
outer_batches = data.num_graphs
|
| 587 |
+
b = [torch.ones(batches[i].item()//self.parallel, device=data['ligand'].x.device).long() * (self.parallel * i + j)
|
| 588 |
+
for i in range(outer_batches) for j in range(self.parallel)]
|
| 589 |
+
data['ligand'].batch_parallel = torch.cat(b)
|
| 590 |
+
radius_edges = radius_graph(data['ligand'].pos, self.lig_max_radius, data['ligand'].batch_parallel)
|
| 591 |
+
ligand_edge_store = self._ligand_edge_store(data)
|
| 592 |
+
edge_index = torch.cat([ligand_edge_store.edge_index, radius_edges], 1).long()
|
| 593 |
+
edge_attr = torch.cat([
|
| 594 |
+
ligand_edge_store.edge_attr,
|
| 595 |
+
torch.zeros(radius_edges.shape[-1], self.in_lig_edge_features, device=data['ligand'].x.device)
|
| 596 |
+
], 0)
|
| 597 |
+
|
| 598 |
+
edge_sigma_emb = data['ligand'].node_sigma_emb[edge_index[0].long()]
|
| 599 |
+
edge_attr = torch.cat([edge_attr, edge_sigma_emb], 1)
|
| 600 |
+
node_attr = torch.cat([data['ligand'].x, data['ligand'].node_sigma_emb], 1)
|
| 601 |
+
|
| 602 |
+
src, dst = edge_index
|
| 603 |
+
edge_vec = data['ligand'].pos[dst.long()] - data['ligand'].pos[src.long()]
|
| 604 |
+
edge_length_emb = self.lig_distance_expansion(edge_vec.norm(dim=-1))
|
| 605 |
+
|
| 606 |
+
edge_attr = torch.cat([edge_attr, edge_length_emb], 1)
|
| 607 |
+
edge_sh = o3.spherical_harmonics(self.sh_irreps, edge_vec, normalize=True, normalization='component')
|
| 608 |
+
edge_weight = self.get_edge_weight(edge_vec, self.lig_max_radius)
|
| 609 |
+
|
| 610 |
+
return node_attr, edge_index, edge_attr, edge_sh, edge_weight
|
| 611 |
+
|
| 612 |
+
def build_rec_conv_graph(self, data):
|
| 613 |
+
# build the graph between receptor residues
|
| 614 |
+
node_attr = data['receptor'].x
|
| 615 |
+
|
| 616 |
+
# this assumes the edges were already created in preprocessing since protein's structure is fixed
|
| 617 |
+
edge_index = self._receptor_edge_store(data).edge_index
|
| 618 |
+
src, dst = edge_index
|
| 619 |
+
edge_vec = data['receptor'].pos[dst.long()] - data['receptor'].pos[src.long()]
|
| 620 |
+
|
| 621 |
+
edge_attr = self.rec_distance_expansion(edge_vec.norm(dim=-1))
|
| 622 |
+
edge_sh = o3.spherical_harmonics(self.sh_irreps, edge_vec, normalize=True, normalization='component')
|
| 623 |
+
edge_weight = self.get_edge_weight(edge_vec, self.rec_max_radius)
|
| 624 |
+
|
| 625 |
+
return node_attr, edge_attr, edge_sh, edge_weight
|
| 626 |
+
|
| 627 |
+
def build_atom_conv_graph(self, data):
|
| 628 |
+
# build the graph between receptor atoms
|
| 629 |
+
node_attr = data['atom'].x
|
| 630 |
+
|
| 631 |
+
# this assumes the edges were already created in preprocessing since protein's structure is fixed
|
| 632 |
+
edge_index = self._atom_edge_store(data).edge_index
|
| 633 |
+
src, dst = edge_index
|
| 634 |
+
edge_vec = data['atom'].pos[dst.long()] - data['atom'].pos[src.long()]
|
| 635 |
+
|
| 636 |
+
edge_attr = self.lig_distance_expansion(edge_vec.norm(dim=-1))
|
| 637 |
+
edge_sh = o3.spherical_harmonics(self.sh_irreps, edge_vec, normalize=True, normalization='component')
|
| 638 |
+
edge_weight = self.get_edge_weight(edge_vec, self.lig_max_radius)
|
| 639 |
+
|
| 640 |
+
return node_attr, edge_attr, edge_sh, edge_weight
|
| 641 |
+
|
| 642 |
+
def build_cross_lig_conv_graph(self, data, lr_cross_distance_cutoff):
|
| 643 |
+
# build the cross edges between ligand atoms and receptor residues + atoms
|
| 644 |
+
|
| 645 |
+
# LIGAND to RECEPTOR
|
| 646 |
+
if torch.is_tensor(lr_cross_distance_cutoff):
|
| 647 |
+
# different cutoff for every graph
|
| 648 |
+
lr_edge_index = radius(data['receptor'].pos / lr_cross_distance_cutoff[data['receptor'].batch],
|
| 649 |
+
data['ligand'].pos / lr_cross_distance_cutoff[data['ligand'].batch], 1,
|
| 650 |
+
data['receptor'].batch, data['ligand'].batch, max_num_neighbors=10000)
|
| 651 |
+
else:
|
| 652 |
+
lr_edge_index = radius(data['receptor'].pos, data['ligand'].pos, lr_cross_distance_cutoff,
|
| 653 |
+
data['receptor'].batch, data['ligand'].batch, max_num_neighbors=10000)
|
| 654 |
+
|
| 655 |
+
lr_edge_vec = data['receptor'].pos[lr_edge_index[1].long()] - data['ligand'].pos[lr_edge_index[0].long()]
|
| 656 |
+
lr_edge_length_emb = self.cross_distance_expansion(lr_edge_vec.norm(dim=-1))
|
| 657 |
+
lr_edge_sigma_emb = data['ligand'].node_sigma_emb[lr_edge_index[0].long()]
|
| 658 |
+
lr_edge_attr = torch.cat([lr_edge_sigma_emb, lr_edge_length_emb], 1)
|
| 659 |
+
lr_edge_sh = o3.spherical_harmonics(self.sh_irreps, lr_edge_vec, normalize=True, normalization='component')
|
| 660 |
+
|
| 661 |
+
cutoff_d = lr_cross_distance_cutoff[data['ligand'].batch[lr_edge_index[0]]].squeeze() \
|
| 662 |
+
if torch.is_tensor(lr_cross_distance_cutoff) else lr_cross_distance_cutoff
|
| 663 |
+
lr_edge_weight = self.get_edge_weight(lr_edge_vec, cutoff_d)
|
| 664 |
+
|
| 665 |
+
# LIGAND to ATOM
|
| 666 |
+
la_edge_index = radius(data['atom'].pos, data['ligand'].pos, self.lig_max_radius,
|
| 667 |
+
data['atom'].batch, data['ligand'].batch, max_num_neighbors=10000)
|
| 668 |
+
|
| 669 |
+
la_edge_vec = data['atom'].pos[la_edge_index[1].long()] - data['ligand'].pos[la_edge_index[0].long()]
|
| 670 |
+
la_edge_length_emb = self.lig_distance_expansion(la_edge_vec.norm(dim=-1))
|
| 671 |
+
la_edge_sigma_emb = data['ligand'].node_sigma_emb[la_edge_index[0].long()]
|
| 672 |
+
la_edge_attr = torch.cat([la_edge_sigma_emb, la_edge_length_emb], 1)
|
| 673 |
+
la_edge_sh = o3.spherical_harmonics(self.sh_irreps, la_edge_vec, normalize=True, normalization='component')
|
| 674 |
+
la_edge_weight = self.get_edge_weight(la_edge_vec, self.lig_max_radius)
|
| 675 |
+
|
| 676 |
+
return lr_edge_index, lr_edge_attr, lr_edge_sh, lr_edge_weight, la_edge_index, la_edge_attr, \
|
| 677 |
+
la_edge_sh, la_edge_weight
|
| 678 |
+
|
| 679 |
+
def build_cross_rec_conv_graph(self, data):
|
| 680 |
+
# build the cross edges between ligan atoms, receptor residues and receptor atoms
|
| 681 |
+
|
| 682 |
+
# ATOM to RECEPTOR
|
| 683 |
+
ar_edge_index = self._atom_receptor_edge_store(data).edge_index
|
| 684 |
+
ar_edge_vec = data['receptor'].pos[ar_edge_index[1].long()] - data['atom'].pos[ar_edge_index[0].long()]
|
| 685 |
+
ar_edge_attr = self.rec_distance_expansion(ar_edge_vec.norm(dim=-1))
|
| 686 |
+
ar_edge_sh = o3.spherical_harmonics(self.sh_irreps, ar_edge_vec, normalize=True, normalization='component')
|
| 687 |
+
ar_edge_weight = 1
|
| 688 |
+
|
| 689 |
+
return ar_edge_attr, ar_edge_sh, ar_edge_weight
|
| 690 |
+
|
| 691 |
+
def build_center_conv_graph(self, data):
|
| 692 |
+
# build the filter for the convolution of the center with the ligand atoms
|
| 693 |
+
# for translational and rotational score
|
| 694 |
+
edge_index = torch.cat([data['ligand'].batch.unsqueeze(0), torch.arange(len(data['ligand'].batch)).to(data['ligand'].x.device).unsqueeze(0)], dim=0)
|
| 695 |
+
|
| 696 |
+
center_pos, count = torch.zeros((data.num_graphs, 3)).to(data['ligand'].x.device), torch.zeros((data.num_graphs, 3)).to(data['ligand'].x.device)
|
| 697 |
+
center_pos.index_add_(0, index=data['ligand'].batch, source=data['ligand'].pos)
|
| 698 |
+
center_pos = center_pos / torch.bincount(data['ligand'].batch).unsqueeze(1)
|
| 699 |
+
|
| 700 |
+
edge_vec = data['ligand'].pos[edge_index[1]] - center_pos[edge_index[0]]
|
| 701 |
+
edge_attr = self.center_distance_expansion(edge_vec.norm(dim=-1))
|
| 702 |
+
edge_sigma_emb = data['ligand'].node_sigma_emb[edge_index[1].long()]
|
| 703 |
+
edge_attr = torch.cat([edge_attr, edge_sigma_emb], 1)
|
| 704 |
+
edge_sh = o3.spherical_harmonics(self.sh_irreps, edge_vec, normalize=True, normalization='component')
|
| 705 |
+
return edge_index, edge_attr, edge_sh
|
| 706 |
+
|
| 707 |
+
def build_bond_conv_graph(self, data):
|
| 708 |
+
# build graph for the pseudotorque layer
|
| 709 |
+
bonds = self._ligand_edge_store(data).edge_index[:, data['ligand'].edge_mask].long()
|
| 710 |
+
bond_pos = (data['ligand'].pos[bonds[0]] + data['ligand'].pos[bonds[1]]) / 2
|
| 711 |
+
bond_batch = data['ligand'].batch[bonds[0]]
|
| 712 |
+
edge_index = radius(data['ligand'].pos, bond_pos, self.lig_max_radius, batch_x=data['ligand'].batch, batch_y=bond_batch)
|
| 713 |
+
|
| 714 |
+
edge_vec = data['ligand'].pos[edge_index[1]] - bond_pos[edge_index[0]]
|
| 715 |
+
edge_attr = self.lig_distance_expansion(edge_vec.norm(dim=-1))
|
| 716 |
+
|
| 717 |
+
edge_attr = self.final_edge_embedding(edge_attr)
|
| 718 |
+
edge_sh = o3.spherical_harmonics(self.sh_irreps, edge_vec, normalize=True, normalization='component')
|
| 719 |
+
edge_weight = self.get_edge_weight(edge_vec, self.lig_max_radius)
|
| 720 |
+
|
| 721 |
+
return bonds, edge_index, edge_attr, edge_sh, edge_weight
|
models/cg_model.py
ADDED
|
@@ -0,0 +1,1070 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from e3nn import o3
|
| 7 |
+
from e3nn.o3 import Linear
|
| 8 |
+
from torch import nn
|
| 9 |
+
from torch.nn import functional as F
|
| 10 |
+
from torch_cluster import radius, radius_graph
|
| 11 |
+
from torch_scatter import scatter, scatter_mean
|
| 12 |
+
|
| 13 |
+
from onescience.datapipes.diffdock.process_mols import (
|
| 14 |
+
lig_feature_dims,
|
| 15 |
+
rec_atom_feature_dims,
|
| 16 |
+
rec_residue_feature_dims,
|
| 17 |
+
)
|
| 18 |
+
from onescience.utils.diffdock import so3, torus
|
| 19 |
+
|
| 20 |
+
from .layers import AtomEncoder, GaussianSmearing
|
| 21 |
+
from .tensor_layers import TensorProductConvLayer, get_irrep_seq
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _env_flag(name):
|
| 25 |
+
return os.environ.get(name, "").lower() in {"1", "true", "yes", "on"}
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
from esm.pretrained import load_model_and_alphabet
|
| 29 |
+
except ImportError:
|
| 30 |
+
load_model_and_alphabet = None
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class CGModel(torch.nn.Module):
|
| 34 |
+
def __init__(
|
| 35 |
+
self,
|
| 36 |
+
t_to_sigma,
|
| 37 |
+
device,
|
| 38 |
+
timestep_emb_func,
|
| 39 |
+
in_lig_edge_features=4,
|
| 40 |
+
sigma_embed_dim=32,
|
| 41 |
+
sh_lmax=2,
|
| 42 |
+
ns=16,
|
| 43 |
+
nv=4,
|
| 44 |
+
num_conv_layers=2,
|
| 45 |
+
lig_max_radius=5,
|
| 46 |
+
rec_max_radius=30,
|
| 47 |
+
cross_max_distance=250,
|
| 48 |
+
center_max_distance=30,
|
| 49 |
+
distance_embed_dim=32,
|
| 50 |
+
cross_distance_embed_dim=32,
|
| 51 |
+
no_torsion=False,
|
| 52 |
+
scale_by_sigma=True,
|
| 53 |
+
norm_by_sigma=True,
|
| 54 |
+
use_second_order_repr=False,
|
| 55 |
+
batch_norm=True,
|
| 56 |
+
dynamic_max_cross=False,
|
| 57 |
+
dropout=0.0,
|
| 58 |
+
smooth_edges=False,
|
| 59 |
+
odd_parity=False,
|
| 60 |
+
separate_noise_schedule=False,
|
| 61 |
+
lm_embedding_type=None,
|
| 62 |
+
confidence_mode=False,
|
| 63 |
+
confidence_dropout=0,
|
| 64 |
+
confidence_no_batchnorm=False,
|
| 65 |
+
asyncronous_noise_schedule=False,
|
| 66 |
+
affinity_prediction=False,
|
| 67 |
+
parallel=1,
|
| 68 |
+
parallel_aggregators="mean max min std",
|
| 69 |
+
num_confidence_outputs=1,
|
| 70 |
+
atom_num_confidence_outputs=1,
|
| 71 |
+
fixed_center_conv=False,
|
| 72 |
+
no_aminoacid_identities=False,
|
| 73 |
+
include_miscellaneous_atoms=False,
|
| 74 |
+
differentiate_convolutions=True,
|
| 75 |
+
tp_weights_layers=2,
|
| 76 |
+
num_prot_emb_layers=0,
|
| 77 |
+
reduce_pseudoscalars=False,
|
| 78 |
+
embed_also_ligand=False,
|
| 79 |
+
atom_confidence=False,
|
| 80 |
+
sidechain_pred=False,
|
| 81 |
+
depthwise_convolution=False,
|
| 82 |
+
):
|
| 83 |
+
super(CGModel, self).__init__()
|
| 84 |
+
assert parallel == 1, "not implemented"
|
| 85 |
+
assert (not no_aminoacid_identities) or (
|
| 86 |
+
lm_embedding_type is None
|
| 87 |
+
), "no language model emb without identities"
|
| 88 |
+
self.t_to_sigma = t_to_sigma
|
| 89 |
+
self.in_lig_edge_features = in_lig_edge_features
|
| 90 |
+
sigma_embed_dim *= 3 if separate_noise_schedule else 1
|
| 91 |
+
self.sigma_embed_dim = sigma_embed_dim
|
| 92 |
+
self.lig_max_radius = lig_max_radius
|
| 93 |
+
self.rec_max_radius = rec_max_radius
|
| 94 |
+
self.include_miscellaneous_atoms = include_miscellaneous_atoms
|
| 95 |
+
self.cross_max_distance = cross_max_distance
|
| 96 |
+
self.dynamic_max_cross = dynamic_max_cross
|
| 97 |
+
self.center_max_distance = center_max_distance
|
| 98 |
+
self.distance_embed_dim = distance_embed_dim
|
| 99 |
+
self.cross_distance_embed_dim = cross_distance_embed_dim
|
| 100 |
+
self.sh_irreps = o3.Irreps.spherical_harmonics(lmax=sh_lmax)
|
| 101 |
+
self.ns, self.nv = ns, nv
|
| 102 |
+
self.scale_by_sigma = scale_by_sigma
|
| 103 |
+
self.norm_by_sigma = norm_by_sigma
|
| 104 |
+
self.device = device
|
| 105 |
+
self.no_torsion = no_torsion
|
| 106 |
+
self.smooth_edges = smooth_edges
|
| 107 |
+
self.odd_parity = odd_parity
|
| 108 |
+
self.timestep_emb_func = timestep_emb_func
|
| 109 |
+
self.separate_noise_schedule = separate_noise_schedule
|
| 110 |
+
self.confidence_mode = confidence_mode
|
| 111 |
+
self.num_conv_layers = num_conv_layers
|
| 112 |
+
self.num_prot_emb_layers = num_prot_emb_layers
|
| 113 |
+
self.asyncronous_noise_schedule = asyncronous_noise_schedule
|
| 114 |
+
self.affinity_prediction = affinity_prediction
|
| 115 |
+
self.fixed_center_conv = fixed_center_conv
|
| 116 |
+
self.no_aminoacid_identities = no_aminoacid_identities
|
| 117 |
+
self.differentiate_convolutions = differentiate_convolutions
|
| 118 |
+
self.reduce_pseudoscalars = reduce_pseudoscalars
|
| 119 |
+
self.atom_confidence = atom_confidence
|
| 120 |
+
self.atom_num_confidence_outputs = atom_num_confidence_outputs
|
| 121 |
+
self.sidechain_pred = sidechain_pred
|
| 122 |
+
|
| 123 |
+
self.lm_embedding_type = lm_embedding_type
|
| 124 |
+
if lm_embedding_type is None:
|
| 125 |
+
lm_embedding_dim = 0
|
| 126 |
+
elif lm_embedding_type == "precomputed":
|
| 127 |
+
lm_embedding_dim = 1280
|
| 128 |
+
else:
|
| 129 |
+
if load_model_and_alphabet is None:
|
| 130 |
+
raise ImportError(
|
| 131 |
+
"esm is required when lm_embedding_type is not None and not 'precomputed'."
|
| 132 |
+
)
|
| 133 |
+
lm, alphabet = load_model_and_alphabet(lm_embedding_type)
|
| 134 |
+
self.batch_converter = alphabet.get_batch_converter()
|
| 135 |
+
lm.lm_head = torch.nn.Identity()
|
| 136 |
+
lm.contact_head = torch.nn.Identity()
|
| 137 |
+
lm_embedding_dim = lm.embed_dim
|
| 138 |
+
self.lm = lm
|
| 139 |
+
|
| 140 |
+
atom_encoder_class = AtomEncoder
|
| 141 |
+
|
| 142 |
+
self.lig_node_embedding = atom_encoder_class(
|
| 143 |
+
emb_dim=ns,
|
| 144 |
+
feature_dims=lig_feature_dims,
|
| 145 |
+
sigma_embed_dim=sigma_embed_dim,
|
| 146 |
+
)
|
| 147 |
+
self.lig_edge_embedding = nn.Sequential(
|
| 148 |
+
nn.Linear(in_lig_edge_features + sigma_embed_dim + distance_embed_dim, ns),
|
| 149 |
+
nn.ReLU(),
|
| 150 |
+
nn.Dropout(dropout),
|
| 151 |
+
nn.Linear(ns, ns),
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
self.rec_node_embedding = atom_encoder_class(
|
| 155 |
+
emb_dim=ns,
|
| 156 |
+
feature_dims=rec_residue_feature_dims,
|
| 157 |
+
sigma_embed_dim=0,
|
| 158 |
+
lm_embedding_dim=lm_embedding_dim,
|
| 159 |
+
)
|
| 160 |
+
self.rec_edge_embedding = nn.Sequential(
|
| 161 |
+
nn.Linear(distance_embed_dim, ns),
|
| 162 |
+
nn.ReLU(),
|
| 163 |
+
nn.Dropout(dropout),
|
| 164 |
+
nn.Linear(ns, ns),
|
| 165 |
+
)
|
| 166 |
+
self.rec_sigma_embedding = nn.Sequential(
|
| 167 |
+
nn.Linear(sigma_embed_dim, ns),
|
| 168 |
+
nn.ReLU(),
|
| 169 |
+
nn.Dropout(dropout),
|
| 170 |
+
nn.Linear(ns, ns),
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
if self.include_miscellaneous_atoms:
|
| 174 |
+
self.misc_atom_node_embedding = atom_encoder_class(
|
| 175 |
+
emb_dim=ns,
|
| 176 |
+
feature_dims=rec_atom_feature_dims,
|
| 177 |
+
sigma_embed_dim=sigma_embed_dim,
|
| 178 |
+
)
|
| 179 |
+
self.misc_atom_edge_embedding = nn.Sequential(
|
| 180 |
+
nn.Linear(sigma_embed_dim + distance_embed_dim, ns),
|
| 181 |
+
nn.ReLU(),
|
| 182 |
+
nn.Dropout(dropout),
|
| 183 |
+
nn.Linear(ns, ns),
|
| 184 |
+
)
|
| 185 |
+
self.ar_edge_embedding = nn.Sequential(
|
| 186 |
+
nn.Linear(sigma_embed_dim + distance_embed_dim, ns),
|
| 187 |
+
nn.ReLU(),
|
| 188 |
+
nn.Dropout(dropout),
|
| 189 |
+
nn.Linear(ns, ns),
|
| 190 |
+
)
|
| 191 |
+
self.la_edge_embedding = nn.Sequential(
|
| 192 |
+
nn.Linear(sigma_embed_dim + cross_distance_embed_dim, ns),
|
| 193 |
+
nn.ReLU(),
|
| 194 |
+
nn.Dropout(dropout),
|
| 195 |
+
nn.Linear(ns, ns),
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
self.cross_edge_embedding = nn.Sequential(
|
| 199 |
+
nn.Linear(sigma_embed_dim + cross_distance_embed_dim, ns),
|
| 200 |
+
nn.ReLU(),
|
| 201 |
+
nn.Dropout(dropout),
|
| 202 |
+
nn.Linear(ns, ns),
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
self.lig_distance_expansion = GaussianSmearing(0.0, lig_max_radius, distance_embed_dim)
|
| 206 |
+
self.rec_distance_expansion = GaussianSmearing(0.0, rec_max_radius, distance_embed_dim)
|
| 207 |
+
self.cross_distance_expansion = GaussianSmearing(
|
| 208 |
+
0.0, cross_max_distance, cross_distance_embed_dim
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
irrep_seq = get_irrep_seq(ns, nv, use_second_order_repr, reduce_pseudoscalars)
|
| 212 |
+
|
| 213 |
+
assert not self.include_miscellaneous_atoms, "currently not supported"
|
| 214 |
+
|
| 215 |
+
rec_emb_layers = []
|
| 216 |
+
for i in range(num_prot_emb_layers):
|
| 217 |
+
in_irreps = irrep_seq[min(i, len(irrep_seq) - 1)]
|
| 218 |
+
out_irreps = irrep_seq[min(i + 1, len(irrep_seq) - 1)]
|
| 219 |
+
layer = TensorProductConvLayer(
|
| 220 |
+
in_irreps=in_irreps,
|
| 221 |
+
sh_irreps=self.sh_irreps,
|
| 222 |
+
out_irreps=out_irreps,
|
| 223 |
+
n_edge_features=3 * ns,
|
| 224 |
+
hidden_features=3 * ns,
|
| 225 |
+
residual=True,
|
| 226 |
+
batch_norm=batch_norm,
|
| 227 |
+
dropout=dropout,
|
| 228 |
+
faster=sh_lmax == 1 and not use_second_order_repr,
|
| 229 |
+
tp_weights_layers=tp_weights_layers,
|
| 230 |
+
edge_groups=1,
|
| 231 |
+
depthwise=depthwise_convolution,
|
| 232 |
+
)
|
| 233 |
+
rec_emb_layers.append(layer)
|
| 234 |
+
self.rec_emb_layers = nn.ModuleList(rec_emb_layers)
|
| 235 |
+
|
| 236 |
+
self.embed_also_ligand = embed_also_ligand
|
| 237 |
+
if embed_also_ligand:
|
| 238 |
+
lig_emb_layers = []
|
| 239 |
+
for i in range(num_prot_emb_layers):
|
| 240 |
+
in_irreps = irrep_seq[min(i, len(irrep_seq) - 1)]
|
| 241 |
+
out_irreps = irrep_seq[min(i + 1, len(irrep_seq) - 1)]
|
| 242 |
+
layer = TensorProductConvLayer(
|
| 243 |
+
in_irreps=in_irreps,
|
| 244 |
+
sh_irreps=self.sh_irreps,
|
| 245 |
+
out_irreps=out_irreps,
|
| 246 |
+
n_edge_features=3 * ns,
|
| 247 |
+
hidden_features=3 * ns,
|
| 248 |
+
residual=True,
|
| 249 |
+
batch_norm=batch_norm,
|
| 250 |
+
dropout=dropout,
|
| 251 |
+
faster=sh_lmax == 1 and not use_second_order_repr,
|
| 252 |
+
tp_weights_layers=tp_weights_layers,
|
| 253 |
+
edge_groups=1,
|
| 254 |
+
depthwise=depthwise_convolution,
|
| 255 |
+
)
|
| 256 |
+
lig_emb_layers.append(layer)
|
| 257 |
+
self.lig_emb_layers = nn.ModuleList(lig_emb_layers)
|
| 258 |
+
|
| 259 |
+
conv_layers = []
|
| 260 |
+
for i in range(num_prot_emb_layers, num_prot_emb_layers + num_conv_layers):
|
| 261 |
+
in_irreps = irrep_seq[min(i, len(irrep_seq) - 1)]
|
| 262 |
+
out_irreps = irrep_seq[min(i + 1, len(irrep_seq) - 1)]
|
| 263 |
+
layer = TensorProductConvLayer(
|
| 264 |
+
in_irreps=in_irreps,
|
| 265 |
+
sh_irreps=self.sh_irreps,
|
| 266 |
+
out_irreps=out_irreps,
|
| 267 |
+
n_edge_features=3 * ns,
|
| 268 |
+
hidden_features=3 * ns,
|
| 269 |
+
residual=True,
|
| 270 |
+
batch_norm=batch_norm,
|
| 271 |
+
dropout=dropout,
|
| 272 |
+
faster=sh_lmax == 1 and not use_second_order_repr,
|
| 273 |
+
tp_weights_layers=tp_weights_layers,
|
| 274 |
+
edge_groups=(
|
| 275 |
+
1
|
| 276 |
+
if not differentiate_convolutions
|
| 277 |
+
else (2 if i == num_prot_emb_layers + num_conv_layers - 1 else 4)
|
| 278 |
+
),
|
| 279 |
+
depthwise=depthwise_convolution,
|
| 280 |
+
)
|
| 281 |
+
conv_layers.append(layer)
|
| 282 |
+
self.conv_layers = nn.ModuleList(conv_layers)
|
| 283 |
+
|
| 284 |
+
if sidechain_pred:
|
| 285 |
+
self.sidechain_predictor = Linear(
|
| 286 |
+
irreps_in=irrep_seq[min(num_prot_emb_layers + num_conv_layers, len(irrep_seq) - 1)],
|
| 287 |
+
irreps_out="4x0e + 2x1e + 4x0o + 2x1o",
|
| 288 |
+
internal_weights=True,
|
| 289 |
+
shared_weights=True,
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
if self.confidence_mode:
|
| 293 |
+
input_size = (
|
| 294 |
+
ns + (nv if reduce_pseudoscalars else ns)
|
| 295 |
+
if num_conv_layers + num_prot_emb_layers >= 3
|
| 296 |
+
else ns
|
| 297 |
+
)
|
| 298 |
+
|
| 299 |
+
if self.atom_confidence:
|
| 300 |
+
self.atom_confidence_predictor = nn.Sequential(
|
| 301 |
+
nn.Linear(input_size, ns),
|
| 302 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 303 |
+
nn.ReLU(),
|
| 304 |
+
nn.Dropout(confidence_dropout),
|
| 305 |
+
nn.Linear(ns, ns),
|
| 306 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 307 |
+
nn.ReLU(),
|
| 308 |
+
nn.Dropout(confidence_dropout),
|
| 309 |
+
nn.Linear(ns, atom_num_confidence_outputs + ns),
|
| 310 |
+
)
|
| 311 |
+
input_size = ns
|
| 312 |
+
|
| 313 |
+
self.confidence_predictor = nn.Sequential(
|
| 314 |
+
nn.Linear(input_size, ns),
|
| 315 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 316 |
+
nn.ReLU(),
|
| 317 |
+
nn.Dropout(confidence_dropout),
|
| 318 |
+
nn.Linear(ns, ns),
|
| 319 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 320 |
+
nn.ReLU(),
|
| 321 |
+
nn.Dropout(confidence_dropout),
|
| 322 |
+
nn.Linear(ns, num_confidence_outputs + (1 if self.affinity_prediction else 0)),
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
else:
|
| 326 |
+
self.center_distance_expansion = GaussianSmearing(
|
| 327 |
+
0.0, center_max_distance, distance_embed_dim
|
| 328 |
+
)
|
| 329 |
+
self.center_edge_embedding = nn.Sequential(
|
| 330 |
+
nn.Linear(distance_embed_dim + sigma_embed_dim, ns),
|
| 331 |
+
nn.ReLU(),
|
| 332 |
+
nn.Dropout(dropout),
|
| 333 |
+
nn.Linear(ns, ns),
|
| 334 |
+
)
|
| 335 |
+
|
| 336 |
+
self.final_conv = TensorProductConvLayer(
|
| 337 |
+
in_irreps=self.conv_layers[-1].out_irreps,
|
| 338 |
+
sh_irreps=self.sh_irreps,
|
| 339 |
+
out_irreps="2x1o + 2x1e" if not self.odd_parity else "1x1o + 1x1e",
|
| 340 |
+
n_edge_features=2 * ns,
|
| 341 |
+
residual=False,
|
| 342 |
+
dropout=dropout,
|
| 343 |
+
batch_norm=batch_norm,
|
| 344 |
+
)
|
| 345 |
+
self.tr_final_layer = nn.Sequential(
|
| 346 |
+
nn.Linear(1 + sigma_embed_dim, ns),
|
| 347 |
+
nn.Dropout(dropout),
|
| 348 |
+
nn.ReLU(),
|
| 349 |
+
nn.Linear(ns, 1),
|
| 350 |
+
)
|
| 351 |
+
self.rot_final_layer = nn.Sequential(
|
| 352 |
+
nn.Linear(1 + sigma_embed_dim, ns),
|
| 353 |
+
nn.Dropout(dropout),
|
| 354 |
+
nn.ReLU(),
|
| 355 |
+
nn.Linear(ns, 1),
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
if not no_torsion:
|
| 359 |
+
self.final_edge_embedding = nn.Sequential(
|
| 360 |
+
nn.Linear(distance_embed_dim, ns),
|
| 361 |
+
nn.ReLU(),
|
| 362 |
+
nn.Dropout(dropout),
|
| 363 |
+
nn.Linear(ns, ns),
|
| 364 |
+
)
|
| 365 |
+
self.final_tp_tor = o3.FullTensorProduct(self.sh_irreps, "2e")
|
| 366 |
+
self.tor_bond_conv = TensorProductConvLayer(
|
| 367 |
+
in_irreps=self.conv_layers[-1].out_irreps,
|
| 368 |
+
sh_irreps=self.final_tp_tor.irreps_out,
|
| 369 |
+
out_irreps=f"{ns}x0o + {ns}x0e" if not self.odd_parity else f"{ns}x0o",
|
| 370 |
+
n_edge_features=3 * ns,
|
| 371 |
+
residual=False,
|
| 372 |
+
dropout=dropout,
|
| 373 |
+
batch_norm=batch_norm,
|
| 374 |
+
)
|
| 375 |
+
self.tor_final_layer = nn.Sequential(
|
| 376 |
+
nn.Linear(2 * ns if not self.odd_parity else ns, ns, bias=False),
|
| 377 |
+
nn.Tanh(),
|
| 378 |
+
nn.Dropout(dropout),
|
| 379 |
+
nn.Linear(ns, 1, bias=False),
|
| 380 |
+
)
|
| 381 |
+
|
| 382 |
+
@staticmethod
|
| 383 |
+
def _resolve_edge_store(data, primary_key, fallback_key):
|
| 384 |
+
edge_types = getattr(data, "edge_types", ())
|
| 385 |
+
if primary_key in edge_types:
|
| 386 |
+
return data[primary_key]
|
| 387 |
+
if fallback_key in edge_types:
|
| 388 |
+
return data[fallback_key]
|
| 389 |
+
try:
|
| 390 |
+
return data[primary_key]
|
| 391 |
+
except Exception:
|
| 392 |
+
return data[fallback_key]
|
| 393 |
+
|
| 394 |
+
def _ligand_edge_store(self, data):
|
| 395 |
+
return self._resolve_edge_store(
|
| 396 |
+
data,
|
| 397 |
+
("ligand", "ligand"),
|
| 398 |
+
("ligand", "lig_bond", "ligand"),
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
def _receptor_edge_store(self, data):
|
| 402 |
+
return self._resolve_edge_store(
|
| 403 |
+
data,
|
| 404 |
+
("receptor", "receptor"),
|
| 405 |
+
("receptor", "rec_contact", "receptor"),
|
| 406 |
+
)
|
| 407 |
+
|
| 408 |
+
def ligand_embedding(self, data):
|
| 409 |
+
lig_node_attr, lig_edge_index, lig_edge_attr, lig_edge_sh, lig_edge_weight = self.build_lig_conv_graph(
|
| 410 |
+
data
|
| 411 |
+
)
|
| 412 |
+
lig_node_attr = self.lig_node_embedding(lig_node_attr)
|
| 413 |
+
lig_edge_attr = self.lig_edge_embedding(lig_edge_attr)
|
| 414 |
+
|
| 415 |
+
assert self.embed_also_ligand, "otherwise reimplement padding"
|
| 416 |
+
for l in range(len(self.lig_emb_layers)):
|
| 417 |
+
edge_attr_ = torch.cat(
|
| 418 |
+
[
|
| 419 |
+
lig_edge_attr,
|
| 420 |
+
lig_node_attr[lig_edge_index[0], : self.ns],
|
| 421 |
+
lig_node_attr[lig_edge_index[1], : self.ns],
|
| 422 |
+
],
|
| 423 |
+
-1,
|
| 424 |
+
)
|
| 425 |
+
lig_node_attr = self.lig_emb_layers[l](
|
| 426 |
+
lig_node_attr,
|
| 427 |
+
lig_edge_index,
|
| 428 |
+
edge_attr_,
|
| 429 |
+
lig_edge_sh,
|
| 430 |
+
edge_weight=lig_edge_weight,
|
| 431 |
+
)
|
| 432 |
+
|
| 433 |
+
return lig_node_attr, lig_edge_index, lig_edge_attr, lig_edge_sh, lig_edge_weight
|
| 434 |
+
|
| 435 |
+
def embedding(self, data):
|
| 436 |
+
receptor_edge_store = self._receptor_edge_store(data)
|
| 437 |
+
if not hasattr(data["receptor"], "rec_node_attr"):
|
| 438 |
+
if self.lm_embedding_type not in [None, "precomputed"]:
|
| 439 |
+
sequences = [s for l in data["receptor"].sequence for s in l]
|
| 440 |
+
if isinstance(sequences[0], list):
|
| 441 |
+
sequences = [s for l in sequences for s in l]
|
| 442 |
+
sequences = [(i, s) for i, s in enumerate(sequences)]
|
| 443 |
+
batch_labels, batch_strs, batch_tokens = self.batch_converter(sequences)
|
| 444 |
+
out = self.lm(
|
| 445 |
+
batch_tokens.to(data["receptor"].x.device),
|
| 446 |
+
repr_layers=[self.lm.num_layers],
|
| 447 |
+
return_contacts=False,
|
| 448 |
+
)
|
| 449 |
+
rec_lm_emb = torch.cat(
|
| 450 |
+
[
|
| 451 |
+
t[: len(sequences[i][1])]
|
| 452 |
+
for i, t in enumerate(out["representations"][self.lm.num_layers])
|
| 453 |
+
],
|
| 454 |
+
dim=0,
|
| 455 |
+
)
|
| 456 |
+
data["receptor"].x = torch.cat([data["receptor"].x, rec_lm_emb], dim=-1)
|
| 457 |
+
|
| 458 |
+
rec_node_attr, rec_edge_attr, rec_edge_sh, rec_edge_weight = self.build_rec_conv_graph(data)
|
| 459 |
+
rec_node_attr = self.rec_node_embedding(rec_node_attr)
|
| 460 |
+
rec_edge_attr = self.rec_edge_embedding(rec_edge_attr)
|
| 461 |
+
|
| 462 |
+
for l in range(len(self.rec_emb_layers)):
|
| 463 |
+
edge_attr_ = torch.cat(
|
| 464 |
+
[
|
| 465 |
+
rec_edge_attr,
|
| 466 |
+
rec_node_attr[receptor_edge_store.edge_index[0], : self.ns],
|
| 467 |
+
rec_node_attr[receptor_edge_store.edge_index[1], : self.ns],
|
| 468 |
+
],
|
| 469 |
+
-1,
|
| 470 |
+
)
|
| 471 |
+
rec_node_attr = self.rec_emb_layers[l](
|
| 472 |
+
rec_node_attr,
|
| 473 |
+
receptor_edge_store.edge_index,
|
| 474 |
+
edge_attr_,
|
| 475 |
+
rec_edge_sh,
|
| 476 |
+
edge_weight=rec_edge_weight,
|
| 477 |
+
)
|
| 478 |
+
|
| 479 |
+
data["receptor"].rec_node_attr = rec_node_attr
|
| 480 |
+
receptor_edge_store.rec_edge_attr = rec_edge_attr
|
| 481 |
+
receptor_edge_store.edge_sh = rec_edge_sh
|
| 482 |
+
receptor_edge_store.edge_weight = rec_edge_weight
|
| 483 |
+
|
| 484 |
+
rec_sigma_emb = self.rec_sigma_embedding(self.timestep_emb_func(data.complex_t["tr"]))
|
| 485 |
+
rec_node_attr = data["receptor"].rec_node_attr + 0
|
| 486 |
+
rec_node_attr[:, : self.ns] = rec_node_attr[:, : self.ns] + rec_sigma_emb[data["receptor"].batch]
|
| 487 |
+
rec_edge_attr = receptor_edge_store.rec_edge_attr + rec_sigma_emb[
|
| 488 |
+
data["receptor"].batch[receptor_edge_store.edge_index[0]]
|
| 489 |
+
]
|
| 490 |
+
|
| 491 |
+
lig_node_attr, lig_edge_index, lig_edge_attr, lig_edge_sh, lig_edge_weight = self.ligand_embedding(
|
| 492 |
+
data
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
return (
|
| 496 |
+
lig_node_attr,
|
| 497 |
+
lig_edge_index,
|
| 498 |
+
lig_edge_attr,
|
| 499 |
+
lig_edge_sh,
|
| 500 |
+
lig_edge_weight,
|
| 501 |
+
rec_node_attr,
|
| 502 |
+
receptor_edge_store.edge_index,
|
| 503 |
+
rec_edge_attr,
|
| 504 |
+
receptor_edge_store.edge_sh,
|
| 505 |
+
receptor_edge_store.edge_weight,
|
| 506 |
+
)
|
| 507 |
+
|
| 508 |
+
def forward(self, data):
|
| 509 |
+
if self.no_aminoacid_identities:
|
| 510 |
+
data["receptor"].x = data["receptor"].x * 0
|
| 511 |
+
|
| 512 |
+
if not self.confidence_mode:
|
| 513 |
+
tr_sigma, rot_sigma, tor_sigma = self.t_to_sigma(
|
| 514 |
+
*[data.complex_t[noise_type] for noise_type in ["tr", "rot", "tor"]]
|
| 515 |
+
)
|
| 516 |
+
else:
|
| 517 |
+
tr_sigma, rot_sigma, tor_sigma = [
|
| 518 |
+
data.complex_t[noise_type] for noise_type in ["tr", "rot", "tor"]
|
| 519 |
+
]
|
| 520 |
+
|
| 521 |
+
(
|
| 522 |
+
lig_node_attr,
|
| 523 |
+
lig_edge_index,
|
| 524 |
+
lig_edge_attr,
|
| 525 |
+
lig_edge_sh,
|
| 526 |
+
lig_edge_weight,
|
| 527 |
+
rec_node_attr,
|
| 528 |
+
rec_edge_index,
|
| 529 |
+
rec_edge_attr,
|
| 530 |
+
rec_edge_sh,
|
| 531 |
+
rec_edge_weight,
|
| 532 |
+
) = self.embedding(data)
|
| 533 |
+
|
| 534 |
+
if self.dynamic_max_cross:
|
| 535 |
+
cross_cutoff = (tr_sigma * 3 + 20).unsqueeze(1)
|
| 536 |
+
else:
|
| 537 |
+
cross_cutoff = self.cross_max_distance
|
| 538 |
+
|
| 539 |
+
lr_edge_index, lr_edge_attr, lr_edge_sh, rev_lr_edge_sh, lr_edge_weight = self.build_cross_conv_graph(
|
| 540 |
+
data, cross_cutoff
|
| 541 |
+
)
|
| 542 |
+
lr_edge_attr = self.cross_edge_embedding(lr_edge_attr)
|
| 543 |
+
|
| 544 |
+
node_attr = torch.cat([lig_node_attr, rec_node_attr], dim=0)
|
| 545 |
+
lr_edge_index[1] = lr_edge_index[1] + len(lig_node_attr)
|
| 546 |
+
edge_index = torch.cat(
|
| 547 |
+
[
|
| 548 |
+
lig_edge_index,
|
| 549 |
+
lr_edge_index,
|
| 550 |
+
rec_edge_index + len(lig_node_attr),
|
| 551 |
+
torch.flip(lr_edge_index, dims=[0]),
|
| 552 |
+
],
|
| 553 |
+
dim=1,
|
| 554 |
+
)
|
| 555 |
+
edge_attr = torch.cat([lig_edge_attr, lr_edge_attr, rec_edge_attr, lr_edge_attr], dim=0)
|
| 556 |
+
edge_sh = torch.cat([lig_edge_sh, lr_edge_sh, rec_edge_sh, rev_lr_edge_sh], dim=0)
|
| 557 |
+
edge_weight = (
|
| 558 |
+
torch.cat([lig_edge_weight, lr_edge_weight, rec_edge_weight, lr_edge_weight], dim=0)
|
| 559 |
+
if torch.is_tensor(lig_edge_weight)
|
| 560 |
+
else torch.ones((len(edge_index[0]), 1), device=edge_index.device)
|
| 561 |
+
)
|
| 562 |
+
s1, s2, s3 = (
|
| 563 |
+
len(lig_edge_index[0]),
|
| 564 |
+
len(lig_edge_index[0]) + len(lr_edge_index[0]),
|
| 565 |
+
len(lig_edge_index[0]) + len(lr_edge_index[0]) + len(rec_edge_index[0]),
|
| 566 |
+
)
|
| 567 |
+
|
| 568 |
+
for l in range(len(self.conv_layers)):
|
| 569 |
+
if l < len(self.conv_layers) - 1:
|
| 570 |
+
edge_attr_ = torch.cat(
|
| 571 |
+
[
|
| 572 |
+
edge_attr,
|
| 573 |
+
node_attr[edge_index[0], : self.ns],
|
| 574 |
+
node_attr[edge_index[1], : self.ns],
|
| 575 |
+
],
|
| 576 |
+
-1,
|
| 577 |
+
)
|
| 578 |
+
if self.differentiate_convolutions:
|
| 579 |
+
edge_attr_ = [edge_attr_[:s1], edge_attr_[s1:s2], edge_attr_[s2:s3], edge_attr_[s3:]]
|
| 580 |
+
node_attr = self.conv_layers[l](
|
| 581 |
+
node_attr,
|
| 582 |
+
edge_index,
|
| 583 |
+
edge_attr_,
|
| 584 |
+
edge_sh,
|
| 585 |
+
edge_weight=edge_weight,
|
| 586 |
+
)
|
| 587 |
+
else:
|
| 588 |
+
edge_attr_ = torch.cat(
|
| 589 |
+
[
|
| 590 |
+
edge_attr[:s2],
|
| 591 |
+
node_attr[edge_index[0, :s2], : self.ns],
|
| 592 |
+
node_attr[edge_index[1, :s2], : self.ns],
|
| 593 |
+
],
|
| 594 |
+
-1,
|
| 595 |
+
)
|
| 596 |
+
if self.differentiate_convolutions:
|
| 597 |
+
edge_attr_ = [edge_attr_[:s1], edge_attr_[s1:s2]]
|
| 598 |
+
node_attr = self.conv_layers[l](
|
| 599 |
+
node_attr,
|
| 600 |
+
edge_index[:, :s2],
|
| 601 |
+
edge_attr_,
|
| 602 |
+
edge_sh[:s2],
|
| 603 |
+
edge_weight=edge_weight[:s2],
|
| 604 |
+
)
|
| 605 |
+
|
| 606 |
+
lig_node_attr = node_attr[: len(lig_node_attr)]
|
| 607 |
+
|
| 608 |
+
if self.confidence_mode:
|
| 609 |
+
scalar_lig_attr = (
|
| 610 |
+
torch.cat(
|
| 611 |
+
[
|
| 612 |
+
lig_node_attr[:, : self.ns],
|
| 613 |
+
lig_node_attr[:, -(self.nv if self.reduce_pseudoscalars else self.ns) :],
|
| 614 |
+
],
|
| 615 |
+
dim=1,
|
| 616 |
+
)
|
| 617 |
+
if self.num_conv_layers + self.num_prot_emb_layers >= 3
|
| 618 |
+
else lig_node_attr[:, : self.ns]
|
| 619 |
+
)
|
| 620 |
+
|
| 621 |
+
if self.atom_confidence:
|
| 622 |
+
scalar_lig_attr = self.atom_confidence_predictor(scalar_lig_attr)
|
| 623 |
+
atom_confidence = scalar_lig_attr[:, : self.atom_num_confidence_outputs]
|
| 624 |
+
scalar_lig_attr = scalar_lig_attr[:, self.atom_num_confidence_outputs :]
|
| 625 |
+
else:
|
| 626 |
+
atom_confidence = torch.zeros((len(lig_node_attr),), device=lig_node_attr.device)
|
| 627 |
+
|
| 628 |
+
confidence = self.confidence_predictor(
|
| 629 |
+
scatter_mean(scalar_lig_attr, data["ligand"].batch, dim=0)
|
| 630 |
+
).squeeze(dim=-1)
|
| 631 |
+
return confidence, atom_confidence
|
| 632 |
+
|
| 633 |
+
center_edge_index, center_edge_attr, center_edge_sh = self.build_center_conv_graph(data)
|
| 634 |
+
center_edge_attr = self.center_edge_embedding(center_edge_attr)
|
| 635 |
+
if self.fixed_center_conv:
|
| 636 |
+
center_edge_attr = torch.cat(
|
| 637 |
+
[center_edge_attr, lig_node_attr[center_edge_index[1], : self.ns]], -1
|
| 638 |
+
)
|
| 639 |
+
else:
|
| 640 |
+
center_edge_attr = torch.cat(
|
| 641 |
+
[center_edge_attr, lig_node_attr[center_edge_index[0], : self.ns]], -1
|
| 642 |
+
)
|
| 643 |
+
global_pred = self.final_conv(
|
| 644 |
+
lig_node_attr,
|
| 645 |
+
center_edge_index,
|
| 646 |
+
center_edge_attr,
|
| 647 |
+
center_edge_sh,
|
| 648 |
+
out_nodes=data.num_graphs,
|
| 649 |
+
)
|
| 650 |
+
|
| 651 |
+
tr_pred = global_pred[:, :3] + (global_pred[:, 6:9] if not self.odd_parity else 0)
|
| 652 |
+
rot_pred = global_pred[:, 3:6] + (global_pred[:, 9:] if not self.odd_parity else 0)
|
| 653 |
+
|
| 654 |
+
if self.separate_noise_schedule:
|
| 655 |
+
data.graph_sigma_emb = torch.cat(
|
| 656 |
+
[self.timestep_emb_func(data.complex_t[noise_type]) for noise_type in ["tr", "rot", "tor"]],
|
| 657 |
+
dim=1,
|
| 658 |
+
)
|
| 659 |
+
elif self.asyncronous_noise_schedule:
|
| 660 |
+
data.graph_sigma_emb = self.timestep_emb_func(data.complex_t["t"])
|
| 661 |
+
else:
|
| 662 |
+
data.graph_sigma_emb = self.timestep_emb_func(data.complex_t["tr"])
|
| 663 |
+
|
| 664 |
+
tr_norm = torch.linalg.vector_norm(tr_pred, dim=1).unsqueeze(1)
|
| 665 |
+
tr_pred = tr_pred / tr_norm * self.tr_final_layer(
|
| 666 |
+
torch.cat([tr_norm, data.graph_sigma_emb], dim=1)
|
| 667 |
+
)
|
| 668 |
+
rot_norm = torch.linalg.vector_norm(rot_pred, dim=1).unsqueeze(1)
|
| 669 |
+
rot_pred = rot_pred / rot_norm * self.rot_final_layer(
|
| 670 |
+
torch.cat([rot_norm, data.graph_sigma_emb], dim=1)
|
| 671 |
+
)
|
| 672 |
+
|
| 673 |
+
if self.scale_by_sigma:
|
| 674 |
+
tr_pred = tr_pred / tr_sigma.unsqueeze(1)
|
| 675 |
+
rot_pred = rot_pred * so3.score_norm(rot_sigma.cpu()).unsqueeze(1).to(
|
| 676 |
+
data["ligand"].x.device
|
| 677 |
+
)
|
| 678 |
+
|
| 679 |
+
sidechain_pred = None
|
| 680 |
+
if self.sidechain_pred:
|
| 681 |
+
rec_node_attr = node_attr[len(lig_node_attr) :]
|
| 682 |
+
sidechain_pred = self.sidechain_predictor(rec_node_attr)
|
| 683 |
+
sidechain_pred = sidechain_pred[:, :10] + sidechain_pred[:, 10:]
|
| 684 |
+
|
| 685 |
+
if self.no_torsion or data["ligand"].edge_mask.sum() == 0:
|
| 686 |
+
return tr_pred, rot_pred, torch.empty(0, device=self.device), sidechain_pred
|
| 687 |
+
|
| 688 |
+
tor_bonds, tor_edge_index, tor_edge_attr, tor_edge_sh, tor_edge_weight = self.build_bond_conv_graph(
|
| 689 |
+
data
|
| 690 |
+
)
|
| 691 |
+
tor_bond_vec = data["ligand"].pos[tor_bonds[1]] - data["ligand"].pos[tor_bonds[0]]
|
| 692 |
+
tor_bond_attr = lig_node_attr[tor_bonds[0]] + lig_node_attr[tor_bonds[1]]
|
| 693 |
+
|
| 694 |
+
tor_bonds_sh = o3.spherical_harmonics(
|
| 695 |
+
"2e", tor_bond_vec, normalize=True, normalization="component"
|
| 696 |
+
)
|
| 697 |
+
tor_edge_sh = self.final_tp_tor(tor_edge_sh, tor_bonds_sh[tor_edge_index[0]])
|
| 698 |
+
|
| 699 |
+
tor_edge_attr = torch.cat(
|
| 700 |
+
[
|
| 701 |
+
tor_edge_attr,
|
| 702 |
+
lig_node_attr[tor_edge_index[1], : self.ns],
|
| 703 |
+
tor_bond_attr[tor_edge_index[0], : self.ns],
|
| 704 |
+
],
|
| 705 |
+
-1,
|
| 706 |
+
)
|
| 707 |
+
tor_pred = self.tor_bond_conv(
|
| 708 |
+
lig_node_attr,
|
| 709 |
+
tor_edge_index,
|
| 710 |
+
tor_edge_attr,
|
| 711 |
+
tor_edge_sh,
|
| 712 |
+
out_nodes=data["ligand"].edge_mask.sum(),
|
| 713 |
+
reduce="mean",
|
| 714 |
+
edge_weight=tor_edge_weight,
|
| 715 |
+
)
|
| 716 |
+
tor_pred = self.tor_final_layer(tor_pred).squeeze(1)
|
| 717 |
+
ligand_edge_store = self._ligand_edge_store(data)
|
| 718 |
+
edge_sigma = tor_sigma[data["ligand"].batch][ligand_edge_store.edge_index[0]][
|
| 719 |
+
data["ligand"].edge_mask
|
| 720 |
+
]
|
| 721 |
+
|
| 722 |
+
if self.scale_by_sigma:
|
| 723 |
+
tor_pred = tor_pred * torch.sqrt(
|
| 724 |
+
torch.tensor(torus.score_norm(edge_sigma.cpu().numpy())).float().to(
|
| 725 |
+
data["ligand"].x.device
|
| 726 |
+
)
|
| 727 |
+
)
|
| 728 |
+
return tr_pred, rot_pred, tor_pred, sidechain_pred
|
| 729 |
+
|
| 730 |
+
def torsional_forward(self, data):
|
| 731 |
+
tor_sigma = self.t_to_sigma(data.complex_t["tor"])
|
| 732 |
+
|
| 733 |
+
lig_node_attr, lig_edge_index, lig_edge_attr, lig_edge_sh, lig_edge_weight = self.ligand_embedding(
|
| 734 |
+
data
|
| 735 |
+
)
|
| 736 |
+
|
| 737 |
+
if self.separate_noise_schedule:
|
| 738 |
+
data.graph_sigma_emb = torch.cat(
|
| 739 |
+
[self.timestep_emb_func(data.complex_t[noise_type]) for noise_type in ["tr", "rot", "tor"]],
|
| 740 |
+
dim=1,
|
| 741 |
+
)
|
| 742 |
+
elif self.asyncronous_noise_schedule:
|
| 743 |
+
data.graph_sigma_emb = self.timestep_emb_func(data.complex_t["t"])
|
| 744 |
+
else:
|
| 745 |
+
data.graph_sigma_emb = self.timestep_emb_func(data.complex_t["tr"])
|
| 746 |
+
|
| 747 |
+
tor_bonds, tor_edge_index, tor_edge_attr, tor_edge_sh, tor_edge_weight = self.build_bond_conv_graph(
|
| 748 |
+
data
|
| 749 |
+
)
|
| 750 |
+
tor_bond_vec = data["ligand"].pos[tor_bonds[1]] - data["ligand"].pos[tor_bonds[0]]
|
| 751 |
+
tor_bond_attr = lig_node_attr[tor_bonds[0]] + lig_node_attr[tor_bonds[1]]
|
| 752 |
+
|
| 753 |
+
tor_bonds_sh = o3.spherical_harmonics(
|
| 754 |
+
"2e", tor_bond_vec, normalize=True, normalization="component"
|
| 755 |
+
)
|
| 756 |
+
tor_edge_sh = self.final_tp_tor(tor_edge_sh, tor_bonds_sh[tor_edge_index[0]])
|
| 757 |
+
|
| 758 |
+
tor_edge_attr = torch.cat(
|
| 759 |
+
[
|
| 760 |
+
tor_edge_attr,
|
| 761 |
+
lig_node_attr[tor_edge_index[1], : self.ns],
|
| 762 |
+
tor_bond_attr[tor_edge_index[0], : self.ns],
|
| 763 |
+
],
|
| 764 |
+
-1,
|
| 765 |
+
)
|
| 766 |
+
tor_pred = self.tor_bond_conv(
|
| 767 |
+
lig_node_attr,
|
| 768 |
+
tor_edge_index,
|
| 769 |
+
tor_edge_attr,
|
| 770 |
+
tor_edge_sh,
|
| 771 |
+
out_nodes=data["ligand"].edge_mask.sum(),
|
| 772 |
+
reduce="mean",
|
| 773 |
+
edge_weight=tor_edge_weight,
|
| 774 |
+
)
|
| 775 |
+
tor_pred = self.tor_final_layer(tor_pred).squeeze(1)
|
| 776 |
+
ligand_edge_store = self._ligand_edge_store(data)
|
| 777 |
+
edge_sigma = tor_sigma[data["ligand"].batch][ligand_edge_store.edge_index[0]][
|
| 778 |
+
data["ligand"].edge_mask
|
| 779 |
+
]
|
| 780 |
+
|
| 781 |
+
if self.scale_by_sigma:
|
| 782 |
+
tor_pred = tor_pred * torch.sqrt(
|
| 783 |
+
torch.tensor(torus.score_norm(edge_sigma.cpu().numpy())).float().to(
|
| 784 |
+
data["ligand"].x.device
|
| 785 |
+
)
|
| 786 |
+
)
|
| 787 |
+
return 0, 0, tor_pred, 0
|
| 788 |
+
|
| 789 |
+
def get_edge_weight(self, edge_vec, max_norm):
|
| 790 |
+
if self.smooth_edges:
|
| 791 |
+
normalised_norm = torch.clip(edge_vec.norm(dim=-1) * np.pi / max_norm, max=np.pi)
|
| 792 |
+
return 0.5 * (torch.cos(normalised_norm) + 1.0).unsqueeze(-1)
|
| 793 |
+
return 1.0
|
| 794 |
+
|
| 795 |
+
def build_lig_conv_graph(self, data):
|
| 796 |
+
if self.separate_noise_schedule:
|
| 797 |
+
data["ligand"].node_sigma_emb = torch.cat(
|
| 798 |
+
[self.timestep_emb_func(data["ligand"].node_t[noise_type]) for noise_type in ["tr", "rot", "tor"]],
|
| 799 |
+
dim=1,
|
| 800 |
+
)
|
| 801 |
+
elif self.asyncronous_noise_schedule:
|
| 802 |
+
data["ligand"].node_sigma_emb = self.timestep_emb_func(data["ligand"].node_t["t"])
|
| 803 |
+
else:
|
| 804 |
+
data["ligand"].node_sigma_emb = self.timestep_emb_func(data["ligand"].node_t["tr"])
|
| 805 |
+
|
| 806 |
+
ligand_edge_store = self._ligand_edge_store(data)
|
| 807 |
+
radius_edges = radius_graph(data["ligand"].pos, self.lig_max_radius, data["ligand"].batch)
|
| 808 |
+
edge_index = torch.cat([ligand_edge_store.edge_index, radius_edges], 1).long()
|
| 809 |
+
edge_attr = torch.cat(
|
| 810 |
+
[
|
| 811 |
+
ligand_edge_store.edge_attr,
|
| 812 |
+
torch.zeros(
|
| 813 |
+
radius_edges.shape[-1],
|
| 814 |
+
self.in_lig_edge_features,
|
| 815 |
+
device=data["ligand"].x.device,
|
| 816 |
+
),
|
| 817 |
+
],
|
| 818 |
+
0,
|
| 819 |
+
)
|
| 820 |
+
|
| 821 |
+
edge_sigma_emb = data["ligand"].node_sigma_emb[edge_index[0].long()]
|
| 822 |
+
edge_attr = torch.cat([edge_attr, edge_sigma_emb], 1)
|
| 823 |
+
node_attr = torch.cat([data["ligand"].x, data["ligand"].node_sigma_emb], 1)
|
| 824 |
+
|
| 825 |
+
src, dst = edge_index
|
| 826 |
+
edge_vec = data["ligand"].pos[dst.long()] - data["ligand"].pos[src.long()]
|
| 827 |
+
edge_length_emb = self.lig_distance_expansion(edge_vec.norm(dim=-1))
|
| 828 |
+
|
| 829 |
+
edge_attr = torch.cat([edge_attr, edge_length_emb], 1)
|
| 830 |
+
edge_sh = o3.spherical_harmonics(
|
| 831 |
+
self.sh_irreps, edge_vec, normalize=True, normalization="component"
|
| 832 |
+
)
|
| 833 |
+
edge_weight = self.get_edge_weight(edge_vec, self.lig_max_radius)
|
| 834 |
+
|
| 835 |
+
return node_attr, edge_index, edge_attr, edge_sh, edge_weight
|
| 836 |
+
|
| 837 |
+
def build_rec_conv_graph(self, data):
|
| 838 |
+
assert not self.separate_noise_schedule or self.asyncronous_noise_schedule, "removed support in this function"
|
| 839 |
+
node_attr = data["receptor"].x
|
| 840 |
+
|
| 841 |
+
receptor_edge_store = self._receptor_edge_store(data)
|
| 842 |
+
edge_index = receptor_edge_store.edge_index
|
| 843 |
+
src, dst = edge_index
|
| 844 |
+
edge_vec = data["receptor"].pos[dst.long()] - data["receptor"].pos[src.long()]
|
| 845 |
+
|
| 846 |
+
edge_length_emb = self.rec_distance_expansion(edge_vec.norm(dim=-1))
|
| 847 |
+
edge_attr = edge_length_emb
|
| 848 |
+
edge_sh = o3.spherical_harmonics(
|
| 849 |
+
self.sh_irreps, edge_vec, normalize=True, normalization="component"
|
| 850 |
+
)
|
| 851 |
+
edge_weight = self.get_edge_weight(edge_vec, self.rec_max_radius)
|
| 852 |
+
|
| 853 |
+
return node_attr, edge_attr, edge_sh, edge_weight
|
| 854 |
+
|
| 855 |
+
def build_misc_atom_conv_graph(self, data):
|
| 856 |
+
if self.separate_noise_schedule:
|
| 857 |
+
data["misc_atom"].node_sigma_emb = torch.cat(
|
| 858 |
+
[self.timestep_emb_func(data["misc_atom"].node_t[noise_type]) for noise_type in ["tr", "rot", "tor"]],
|
| 859 |
+
dim=1,
|
| 860 |
+
)
|
| 861 |
+
elif self.asyncronous_noise_schedule:
|
| 862 |
+
data["misc_atom"].node_sigma_emb = self.timestep_emb_func(data["misc_atom"].node_t["t"])
|
| 863 |
+
else:
|
| 864 |
+
data["misc_atom"].node_sigma_emb = self.timestep_emb_func(data["misc_atom"].node_t["tr"])
|
| 865 |
+
node_attr = torch.cat([data["misc_atom"].x, data["misc_atom"].node_sigma_emb], 1)
|
| 866 |
+
|
| 867 |
+
edge_index = data["misc_atom", "misc_atom"].edge_index
|
| 868 |
+
src, dst = edge_index
|
| 869 |
+
edge_vec = data["misc_atom"].pos[dst.long()] - data["misc_atom"].pos[src.long()]
|
| 870 |
+
|
| 871 |
+
edge_length_emb = self.lig_distance_expansion(edge_vec.norm(dim=-1))
|
| 872 |
+
edge_sigma_emb = data["misc_atom"].node_sigma_emb[edge_index[0].long()]
|
| 873 |
+
edge_attr = torch.cat([edge_sigma_emb, edge_length_emb], 1)
|
| 874 |
+
edge_sh = o3.spherical_harmonics(
|
| 875 |
+
self.sh_irreps, edge_vec, normalize=True, normalization="component"
|
| 876 |
+
)
|
| 877 |
+
edge_weight = self.get_edge_weight(edge_vec, self.lig_max_radius)
|
| 878 |
+
|
| 879 |
+
return node_attr, edge_index, edge_attr, edge_sh, edge_weight
|
| 880 |
+
|
| 881 |
+
def build_cross_conv_graph(self, data, cross_distance_cutoff):
|
| 882 |
+
radius_on_cpu = _env_flag("DIFFDOCK_RADIUS_ON_CPU")
|
| 883 |
+
if torch.is_tensor(cross_distance_cutoff):
|
| 884 |
+
receptor_pos = data["receptor"].pos
|
| 885 |
+
ligand_pos = data["ligand"].pos
|
| 886 |
+
receptor_batch = data["receptor"].batch
|
| 887 |
+
ligand_batch = data["ligand"].batch
|
| 888 |
+
cutoff = cross_distance_cutoff
|
| 889 |
+
if radius_on_cpu:
|
| 890 |
+
receptor_pos = receptor_pos.detach().cpu()
|
| 891 |
+
ligand_pos = ligand_pos.detach().cpu()
|
| 892 |
+
receptor_batch = receptor_batch.detach().cpu()
|
| 893 |
+
ligand_batch = ligand_batch.detach().cpu()
|
| 894 |
+
cutoff = cutoff.detach().cpu()
|
| 895 |
+
edge_index = radius(
|
| 896 |
+
receptor_pos / cutoff[receptor_batch],
|
| 897 |
+
ligand_pos / cutoff[ligand_batch],
|
| 898 |
+
1,
|
| 899 |
+
receptor_batch,
|
| 900 |
+
ligand_batch,
|
| 901 |
+
max_num_neighbors=10000,
|
| 902 |
+
)
|
| 903 |
+
else:
|
| 904 |
+
receptor_pos = data["receptor"].pos
|
| 905 |
+
ligand_pos = data["ligand"].pos
|
| 906 |
+
receptor_batch = data["receptor"].batch
|
| 907 |
+
ligand_batch = data["ligand"].batch
|
| 908 |
+
if radius_on_cpu:
|
| 909 |
+
receptor_pos = receptor_pos.detach().cpu()
|
| 910 |
+
ligand_pos = ligand_pos.detach().cpu()
|
| 911 |
+
receptor_batch = receptor_batch.detach().cpu()
|
| 912 |
+
ligand_batch = ligand_batch.detach().cpu()
|
| 913 |
+
edge_index = radius(
|
| 914 |
+
receptor_pos,
|
| 915 |
+
ligand_pos,
|
| 916 |
+
cross_distance_cutoff,
|
| 917 |
+
receptor_batch,
|
| 918 |
+
ligand_batch,
|
| 919 |
+
max_num_neighbors=10000,
|
| 920 |
+
)
|
| 921 |
+
if radius_on_cpu:
|
| 922 |
+
edge_index = edge_index.to(data["ligand"].pos.device)
|
| 923 |
+
|
| 924 |
+
src, dst = edge_index
|
| 925 |
+
edge_vec = data["receptor"].pos[dst.long()] - data["ligand"].pos[src.long()]
|
| 926 |
+
|
| 927 |
+
edge_length_emb = self.cross_distance_expansion(edge_vec.norm(dim=-1))
|
| 928 |
+
edge_sigma_emb = data["ligand"].node_sigma_emb[src.long()]
|
| 929 |
+
edge_attr = torch.cat([edge_sigma_emb, edge_length_emb], 1)
|
| 930 |
+
edge_sh = o3.spherical_harmonics(
|
| 931 |
+
self.sh_irreps, edge_vec, normalize=True, normalization="component"
|
| 932 |
+
)
|
| 933 |
+
rev_edge_sh = o3.spherical_harmonics(
|
| 934 |
+
self.sh_irreps, -edge_vec, normalize=True, normalization="component"
|
| 935 |
+
)
|
| 936 |
+
|
| 937 |
+
cutoff_d = (
|
| 938 |
+
cross_distance_cutoff[data["ligand"].batch[src]].squeeze()
|
| 939 |
+
if torch.is_tensor(cross_distance_cutoff)
|
| 940 |
+
else cross_distance_cutoff
|
| 941 |
+
)
|
| 942 |
+
edge_weight = self.get_edge_weight(edge_vec, cutoff_d)
|
| 943 |
+
|
| 944 |
+
return edge_index, edge_attr, edge_sh, rev_edge_sh, edge_weight
|
| 945 |
+
|
| 946 |
+
def build_misc_cross_conv_graph(self, data, lr_cross_distance_cutoff):
|
| 947 |
+
if torch.is_tensor(lr_cross_distance_cutoff):
|
| 948 |
+
lr_edge_index = radius(
|
| 949 |
+
data["receptor"].pos / lr_cross_distance_cutoff[data["receptor"].batch],
|
| 950 |
+
data["ligand"].pos / lr_cross_distance_cutoff[data["ligand"].batch],
|
| 951 |
+
1,
|
| 952 |
+
data["receptor"].batch,
|
| 953 |
+
data["ligand"].batch,
|
| 954 |
+
max_num_neighbors=10000,
|
| 955 |
+
)
|
| 956 |
+
else:
|
| 957 |
+
lr_edge_index = radius(
|
| 958 |
+
data["receptor"].pos,
|
| 959 |
+
data["ligand"].pos,
|
| 960 |
+
lr_cross_distance_cutoff,
|
| 961 |
+
data["receptor"].batch,
|
| 962 |
+
data["ligand"].batch,
|
| 963 |
+
max_num_neighbors=10000,
|
| 964 |
+
)
|
| 965 |
+
|
| 966 |
+
lr_edge_vec = data["receptor"].pos[lr_edge_index[1].long()] - data["ligand"].pos[lr_edge_index[0].long()]
|
| 967 |
+
lr_edge_length_emb = self.cross_distance_expansion(lr_edge_vec.norm(dim=-1))
|
| 968 |
+
lr_edge_sigma_emb = data["ligand"].node_sigma_emb[lr_edge_index[0].long()]
|
| 969 |
+
lr_edge_attr = torch.cat([lr_edge_sigma_emb, lr_edge_length_emb], 1)
|
| 970 |
+
lr_edge_sh = o3.spherical_harmonics(
|
| 971 |
+
self.sh_irreps, lr_edge_vec, normalize=True, normalization="component"
|
| 972 |
+
)
|
| 973 |
+
|
| 974 |
+
cutoff_d = (
|
| 975 |
+
lr_cross_distance_cutoff[data["ligand"].batch[lr_edge_index[0]]].squeeze()
|
| 976 |
+
if torch.is_tensor(lr_cross_distance_cutoff)
|
| 977 |
+
else lr_cross_distance_cutoff
|
| 978 |
+
)
|
| 979 |
+
lr_edge_weight = self.get_edge_weight(lr_edge_vec, cutoff_d)
|
| 980 |
+
|
| 981 |
+
la_edge_index = radius(
|
| 982 |
+
data["misc_atom"].pos,
|
| 983 |
+
data["ligand"].pos,
|
| 984 |
+
self.lig_max_radius,
|
| 985 |
+
data["misc_atom"].batch,
|
| 986 |
+
data["ligand"].batch,
|
| 987 |
+
max_num_neighbors=10000,
|
| 988 |
+
)
|
| 989 |
+
|
| 990 |
+
la_edge_vec = data["misc_atom"].pos[la_edge_index[1].long()] - data["ligand"].pos[la_edge_index[0].long()]
|
| 991 |
+
la_edge_length_emb = self.cross_distance_expansion(la_edge_vec.norm(dim=-1))
|
| 992 |
+
la_edge_sigma_emb = data["ligand"].node_sigma_emb[la_edge_index[0].long()]
|
| 993 |
+
la_edge_attr = torch.cat([la_edge_sigma_emb, la_edge_length_emb], 1)
|
| 994 |
+
la_edge_sh = o3.spherical_harmonics(
|
| 995 |
+
self.sh_irreps, la_edge_vec, normalize=True, normalization="component"
|
| 996 |
+
)
|
| 997 |
+
la_edge_weight = self.get_edge_weight(la_edge_vec, self.lig_max_radius)
|
| 998 |
+
|
| 999 |
+
ar_edge_index = data["misc_atom", "receptor"].edge_index
|
| 1000 |
+
ar_edge_vec = data["receptor"].pos[ar_edge_index[1].long()] - data["misc_atom"].pos[ar_edge_index[0].long()]
|
| 1001 |
+
ar_edge_length_emb = self.rec_distance_expansion(ar_edge_vec.norm(dim=-1))
|
| 1002 |
+
ar_edge_sigma_emb = data["misc_atom"].node_sigma_emb[ar_edge_index[0].long()]
|
| 1003 |
+
ar_edge_attr = torch.cat([ar_edge_sigma_emb, ar_edge_length_emb], 1)
|
| 1004 |
+
ar_edge_sh = o3.spherical_harmonics(
|
| 1005 |
+
self.sh_irreps, ar_edge_vec, normalize=True, normalization="component"
|
| 1006 |
+
)
|
| 1007 |
+
ar_edge_weight = 1
|
| 1008 |
+
|
| 1009 |
+
return (
|
| 1010 |
+
lr_edge_index,
|
| 1011 |
+
lr_edge_attr,
|
| 1012 |
+
lr_edge_sh,
|
| 1013 |
+
lr_edge_weight,
|
| 1014 |
+
la_edge_index,
|
| 1015 |
+
la_edge_attr,
|
| 1016 |
+
la_edge_sh,
|
| 1017 |
+
la_edge_weight,
|
| 1018 |
+
ar_edge_index,
|
| 1019 |
+
ar_edge_attr,
|
| 1020 |
+
ar_edge_sh,
|
| 1021 |
+
ar_edge_weight,
|
| 1022 |
+
)
|
| 1023 |
+
|
| 1024 |
+
def build_center_conv_graph(self, data):
|
| 1025 |
+
edge_index = torch.cat(
|
| 1026 |
+
[
|
| 1027 |
+
data["ligand"].batch.unsqueeze(0),
|
| 1028 |
+
torch.arange(len(data["ligand"].batch)).to(data["ligand"].x.device).unsqueeze(0),
|
| 1029 |
+
],
|
| 1030 |
+
dim=0,
|
| 1031 |
+
)
|
| 1032 |
+
|
| 1033 |
+
center_pos, count = torch.zeros((data.num_graphs, 3)).to(data["ligand"].x.device), torch.zeros(
|
| 1034 |
+
(data.num_graphs, 3)
|
| 1035 |
+
).to(data["ligand"].x.device)
|
| 1036 |
+
center_pos.index_add_(0, index=data["ligand"].batch, source=data["ligand"].pos)
|
| 1037 |
+
center_pos = center_pos / torch.bincount(data["ligand"].batch).unsqueeze(1)
|
| 1038 |
+
|
| 1039 |
+
edge_vec = data["ligand"].pos[edge_index[1]] - center_pos[edge_index[0]]
|
| 1040 |
+
edge_attr = self.center_distance_expansion(edge_vec.norm(dim=-1))
|
| 1041 |
+
edge_sigma_emb = data["ligand"].node_sigma_emb[edge_index[1].long()]
|
| 1042 |
+
edge_attr = torch.cat([edge_attr, edge_sigma_emb], 1)
|
| 1043 |
+
edge_sh = o3.spherical_harmonics(
|
| 1044 |
+
self.sh_irreps, edge_vec, normalize=True, normalization="component"
|
| 1045 |
+
)
|
| 1046 |
+
return edge_index, edge_attr, edge_sh
|
| 1047 |
+
|
| 1048 |
+
def build_bond_conv_graph(self, data):
|
| 1049 |
+
ligand_edge_store = self._ligand_edge_store(data)
|
| 1050 |
+
bonds = ligand_edge_store.edge_index[:, data["ligand"].edge_mask].long()
|
| 1051 |
+
bond_pos = (data["ligand"].pos[bonds[0]] + data["ligand"].pos[bonds[1]]) / 2
|
| 1052 |
+
bond_batch = data["ligand"].batch[bonds[0]]
|
| 1053 |
+
edge_index = radius(
|
| 1054 |
+
data["ligand"].pos,
|
| 1055 |
+
bond_pos,
|
| 1056 |
+
self.lig_max_radius,
|
| 1057 |
+
batch_x=data["ligand"].batch,
|
| 1058 |
+
batch_y=bond_batch,
|
| 1059 |
+
)
|
| 1060 |
+
|
| 1061 |
+
edge_vec = data["ligand"].pos[edge_index[1]] - bond_pos[edge_index[0]]
|
| 1062 |
+
edge_attr = self.lig_distance_expansion(edge_vec.norm(dim=-1))
|
| 1063 |
+
|
| 1064 |
+
edge_attr = self.final_edge_embedding(edge_attr)
|
| 1065 |
+
edge_sh = o3.spherical_harmonics(
|
| 1066 |
+
self.sh_irreps, edge_vec, normalize=True, normalization="component"
|
| 1067 |
+
)
|
| 1068 |
+
edge_weight = self.get_edge_weight(edge_vec, self.lig_max_radius)
|
| 1069 |
+
|
| 1070 |
+
return bonds, edge_index, edge_attr, edge_sh, edge_weight
|
models/layers.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
|
| 4 |
+
ACTIVATIONS = {
|
| 5 |
+
"relu": nn.ReLU,
|
| 6 |
+
"silu": nn.SiLU,
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def FCBlock(in_dim, hidden_dim, out_dim, layers, dropout, activation="relu"):
|
| 11 |
+
activation = ACTIVATIONS[activation]
|
| 12 |
+
assert layers >= 2
|
| 13 |
+
sequential = [nn.Linear(in_dim, hidden_dim), activation(), nn.Dropout(dropout)]
|
| 14 |
+
for i in range(layers - 2):
|
| 15 |
+
sequential += [nn.Linear(hidden_dim, hidden_dim), activation(), nn.Dropout(dropout)]
|
| 16 |
+
sequential += [nn.Linear(hidden_dim, out_dim)]
|
| 17 |
+
return nn.Sequential(*sequential)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class GaussianSmearing(torch.nn.Module):
|
| 21 |
+
# used to embed the edge distances
|
| 22 |
+
def __init__(self, start=0.0, stop=5.0, num_gaussians=50):
|
| 23 |
+
super().__init__()
|
| 24 |
+
offset = torch.linspace(start, stop, num_gaussians)
|
| 25 |
+
self.coeff = -0.5 / (offset[1] - offset[0]).item() ** 2
|
| 26 |
+
self.register_buffer("offset", offset)
|
| 27 |
+
|
| 28 |
+
def forward(self, dist):
|
| 29 |
+
dist = dist.view(-1, 1) - self.offset.view(1, -1)
|
| 30 |
+
return torch.exp(self.coeff * torch.pow(dist, 2))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class AtomEncoder(torch.nn.Module):
|
| 34 |
+
def __init__(self, emb_dim, feature_dims, sigma_embed_dim, lm_embedding_dim=0):
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
Parameters
|
| 38 |
+
----------
|
| 39 |
+
emb_dim
|
| 40 |
+
feature_dims
|
| 41 |
+
first element of feature_dims tuple is a list with the length of each categorical feature,
|
| 42 |
+
and the second is the number of scalar features
|
| 43 |
+
sigma_embed_dim
|
| 44 |
+
lm_embedding_dim
|
| 45 |
+
"""
|
| 46 |
+
super(AtomEncoder, self).__init__()
|
| 47 |
+
self.atom_embedding_list = torch.nn.ModuleList()
|
| 48 |
+
self.num_categorical_features = len(feature_dims[0])
|
| 49 |
+
self.additional_features_dim = feature_dims[1] + sigma_embed_dim + lm_embedding_dim
|
| 50 |
+
for i, dim in enumerate(feature_dims[0]):
|
| 51 |
+
emb = torch.nn.Embedding(dim, emb_dim)
|
| 52 |
+
torch.nn.init.xavier_uniform_(emb.weight.data)
|
| 53 |
+
self.atom_embedding_list.append(emb)
|
| 54 |
+
|
| 55 |
+
if self.additional_features_dim > 0:
|
| 56 |
+
self.additional_features_embedder = torch.nn.Linear(
|
| 57 |
+
self.additional_features_dim + emb_dim, emb_dim
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
def forward(self, x):
|
| 61 |
+
x_embedding = 0
|
| 62 |
+
assert x.shape[1] == self.num_categorical_features + self.additional_features_dim
|
| 63 |
+
for i in range(self.num_categorical_features):
|
| 64 |
+
x_embedding += self.atom_embedding_list[i](x[:, i].long())
|
| 65 |
+
|
| 66 |
+
if self.additional_features_dim > 0:
|
| 67 |
+
x_embedding = self.additional_features_embedder(
|
| 68 |
+
torch.cat([x_embedding, x[:, self.num_categorical_features :]], axis=1)
|
| 69 |
+
)
|
| 70 |
+
return x_embedding
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class OldAtomEncoder(torch.nn.Module):
|
| 74 |
+
def __init__(self, emb_dim, feature_dims, sigma_embed_dim, lm_embedding_type=None):
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
Parameters
|
| 78 |
+
----------
|
| 79 |
+
emb_dim
|
| 80 |
+
feature_dims
|
| 81 |
+
first element of feature_dims tuple is a list with the length of each categorical feature,
|
| 82 |
+
and the second is the number of scalar features
|
| 83 |
+
sigma_embed_dim
|
| 84 |
+
lm_embedding_type
|
| 85 |
+
"""
|
| 86 |
+
super(OldAtomEncoder, self).__init__()
|
| 87 |
+
self.atom_embedding_list = torch.nn.ModuleList()
|
| 88 |
+
self.num_categorical_features = len(feature_dims[0])
|
| 89 |
+
self.num_scalar_features = feature_dims[1] + sigma_embed_dim
|
| 90 |
+
self.lm_embedding_type = lm_embedding_type
|
| 91 |
+
for i, dim in enumerate(feature_dims[0]):
|
| 92 |
+
emb = torch.nn.Embedding(dim, emb_dim)
|
| 93 |
+
torch.nn.init.xavier_uniform_(emb.weight.data)
|
| 94 |
+
self.atom_embedding_list.append(emb)
|
| 95 |
+
|
| 96 |
+
if self.num_scalar_features > 0:
|
| 97 |
+
self.linear = torch.nn.Linear(self.num_scalar_features, emb_dim)
|
| 98 |
+
if self.lm_embedding_type is not None:
|
| 99 |
+
if self.lm_embedding_type == "esm":
|
| 100 |
+
self.lm_embedding_dim = 1280
|
| 101 |
+
else:
|
| 102 |
+
raise ValueError(
|
| 103 |
+
"LM Embedding type was not correctly determined. LM embedding type: ",
|
| 104 |
+
self.lm_embedding_type,
|
| 105 |
+
)
|
| 106 |
+
self.lm_embedding_layer = torch.nn.Linear(self.lm_embedding_dim + emb_dim, emb_dim)
|
| 107 |
+
|
| 108 |
+
def forward(self, x):
|
| 109 |
+
x_embedding = 0
|
| 110 |
+
if self.lm_embedding_type is not None:
|
| 111 |
+
assert (
|
| 112 |
+
x.shape[1]
|
| 113 |
+
== self.num_categorical_features + self.num_scalar_features + self.lm_embedding_dim
|
| 114 |
+
)
|
| 115 |
+
else:
|
| 116 |
+
assert x.shape[1] == self.num_categorical_features + self.num_scalar_features
|
| 117 |
+
for i in range(self.num_categorical_features):
|
| 118 |
+
x_embedding += self.atom_embedding_list[i](x[:, i].long())
|
| 119 |
+
|
| 120 |
+
if self.num_scalar_features > 0:
|
| 121 |
+
x_embedding += self.linear(
|
| 122 |
+
x[:, self.num_categorical_features : self.num_categorical_features + self.num_scalar_features]
|
| 123 |
+
)
|
| 124 |
+
if self.lm_embedding_type is not None:
|
| 125 |
+
x_embedding = self.lm_embedding_layer(
|
| 126 |
+
torch.cat([x_embedding, x[:, -self.lm_embedding_dim :]], axis=1)
|
| 127 |
+
)
|
| 128 |
+
return x_embedding
|
models/old_aa_model.py
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from e3nn import o3
|
| 2 |
+
import torch
|
| 3 |
+
from torch import nn
|
| 4 |
+
from torch.nn import functional as F
|
| 5 |
+
from torch_cluster import radius, radius_graph
|
| 6 |
+
from torch_scatter import scatter, scatter_mean
|
| 7 |
+
import numpy as np
|
| 8 |
+
from e3nn.nn import BatchNorm
|
| 9 |
+
|
| 10 |
+
from onescience.datapipes.diffdock.process_mols import lig_feature_dims, rec_residue_feature_dims, rec_atom_feature_dims
|
| 11 |
+
from onescience.utils.diffdock import so3, torus
|
| 12 |
+
|
| 13 |
+
from .layers import GaussianSmearing, OldAtomEncoder, AtomEncoder
|
| 14 |
+
from .tensor_layers import OldTensorProductConvLayer
|
| 15 |
+
|
| 16 |
+
AGGREGATORS = {"mean": lambda x: torch.mean(x, dim=1),
|
| 17 |
+
"max": lambda x: torch.max(x, dim=1)[0],
|
| 18 |
+
"min": lambda x: torch.min(x, dim=1)[0],
|
| 19 |
+
"std": lambda x: torch.std(x, dim=1)}
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class AAOldModel(torch.nn.Module):
|
| 23 |
+
def __init__(self, t_to_sigma, device, timestep_emb_func, in_lig_edge_features=4, sigma_embed_dim=32, sh_lmax=2,
|
| 24 |
+
ns=16, nv=4, num_conv_layers=2, lig_max_radius=5, rec_max_radius=30, cross_max_distance=250,
|
| 25 |
+
center_max_distance=30, distance_embed_dim=32, cross_distance_embed_dim=32, no_torsion=False,
|
| 26 |
+
scale_by_sigma=True, norm_by_sigma=True, use_second_order_repr=False, batch_norm=True,
|
| 27 |
+
dynamic_max_cross=False, dropout=0.0, smooth_edges=False, odd_parity=False,
|
| 28 |
+
separate_noise_schedule=False, lm_embedding_type=None, confidence_mode=False,
|
| 29 |
+
confidence_dropout=0, confidence_no_batchnorm = False,
|
| 30 |
+
asyncronous_noise_schedule=False, affinity_prediction=False, parallel=1,
|
| 31 |
+
parallel_aggregators="mean max min std", num_confidence_outputs=1, fixed_center_conv=False,
|
| 32 |
+
no_aminoacid_identities=False, include_miscellaneous_atoms=False, use_old_atom_encoder=False):
|
| 33 |
+
super(AAOldModel, self).__init__()
|
| 34 |
+
assert (not no_aminoacid_identities) or (lm_embedding_type is None), "no language model emb without identities"
|
| 35 |
+
if parallel > 1: assert affinity_prediction
|
| 36 |
+
self.t_to_sigma = t_to_sigma
|
| 37 |
+
self.in_lig_edge_features = in_lig_edge_features
|
| 38 |
+
sigma_embed_dim *= (3 if separate_noise_schedule else 1)
|
| 39 |
+
self.sigma_embed_dim = sigma_embed_dim
|
| 40 |
+
self.lig_max_radius = lig_max_radius
|
| 41 |
+
self.rec_max_radius = rec_max_radius
|
| 42 |
+
self.cross_max_distance = cross_max_distance
|
| 43 |
+
self.dynamic_max_cross = dynamic_max_cross
|
| 44 |
+
self.center_max_distance = center_max_distance
|
| 45 |
+
self.distance_embed_dim = distance_embed_dim
|
| 46 |
+
self.cross_distance_embed_dim = cross_distance_embed_dim
|
| 47 |
+
self.sh_irreps = o3.Irreps.spherical_harmonics(lmax=sh_lmax)
|
| 48 |
+
self.ns, self.nv = ns, nv
|
| 49 |
+
self.scale_by_sigma = scale_by_sigma
|
| 50 |
+
self.norm_by_sigma = norm_by_sigma
|
| 51 |
+
self.device = device
|
| 52 |
+
self.no_torsion = no_torsion
|
| 53 |
+
self.smooth_edges = smooth_edges
|
| 54 |
+
self.odd_parity = odd_parity
|
| 55 |
+
self.num_conv_layers = num_conv_layers
|
| 56 |
+
self.timestep_emb_func = timestep_emb_func
|
| 57 |
+
self.separate_noise_schedule = separate_noise_schedule
|
| 58 |
+
self.confidence_mode = confidence_mode
|
| 59 |
+
self.num_conv_layers = num_conv_layers
|
| 60 |
+
self.asyncronous_noise_schedule = asyncronous_noise_schedule
|
| 61 |
+
self.affinity_prediction = affinity_prediction
|
| 62 |
+
self.parallel, self.parallel_aggregators = parallel, parallel_aggregators.split(' ')
|
| 63 |
+
self.fixed_center_conv = fixed_center_conv
|
| 64 |
+
self.no_aminoacid_identities = no_aminoacid_identities
|
| 65 |
+
|
| 66 |
+
# embedding layers
|
| 67 |
+
atom_encoder_class = OldAtomEncoder if use_old_atom_encoder else AtomEncoder
|
| 68 |
+
lm_embedding_dim = 0 if lm_embedding_type is None else 1280
|
| 69 |
+
self.lig_node_embedding = atom_encoder_class(emb_dim=ns, feature_dims=lig_feature_dims, sigma_embed_dim=sigma_embed_dim)
|
| 70 |
+
self.lig_edge_embedding = nn.Sequential(nn.Linear(in_lig_edge_features + sigma_embed_dim + distance_embed_dim, ns),nn.ReLU(),nn.Dropout(dropout),nn.Linear(ns, ns))
|
| 71 |
+
|
| 72 |
+
self.rec_node_embedding = (
|
| 73 |
+
atom_encoder_class(
|
| 74 |
+
emb_dim=ns,
|
| 75 |
+
feature_dims=rec_residue_feature_dims,
|
| 76 |
+
sigma_embed_dim=sigma_embed_dim,
|
| 77 |
+
lm_embedding_type=lm_embedding_type,
|
| 78 |
+
)
|
| 79 |
+
if use_old_atom_encoder
|
| 80 |
+
else atom_encoder_class(
|
| 81 |
+
emb_dim=ns,
|
| 82 |
+
feature_dims=rec_residue_feature_dims,
|
| 83 |
+
sigma_embed_dim=sigma_embed_dim,
|
| 84 |
+
lm_embedding_dim=lm_embedding_dim,
|
| 85 |
+
)
|
| 86 |
+
)
|
| 87 |
+
self.rec_edge_embedding = nn.Sequential(nn.Linear(sigma_embed_dim + distance_embed_dim, ns), nn.ReLU(), nn.Dropout(dropout),nn.Linear(ns, ns))
|
| 88 |
+
|
| 89 |
+
self.atom_node_embedding = atom_encoder_class(emb_dim=ns, feature_dims=rec_atom_feature_dims, sigma_embed_dim=sigma_embed_dim)
|
| 90 |
+
self.atom_edge_embedding = nn.Sequential(nn.Linear(sigma_embed_dim + distance_embed_dim, ns), nn.ReLU(), nn.Dropout(dropout),nn.Linear(ns, ns))
|
| 91 |
+
|
| 92 |
+
self.lr_edge_embedding = nn.Sequential(nn.Linear(sigma_embed_dim + cross_distance_embed_dim, ns), nn.ReLU(), nn.Dropout(dropout),nn.Linear(ns, ns))
|
| 93 |
+
self.ar_edge_embedding = nn.Sequential(nn.Linear(sigma_embed_dim + distance_embed_dim, ns), nn.ReLU(), nn.Dropout(dropout),nn.Linear(ns, ns))
|
| 94 |
+
self.la_edge_embedding = nn.Sequential(nn.Linear(sigma_embed_dim + cross_distance_embed_dim, ns), nn.ReLU(), nn.Dropout(dropout),nn.Linear(ns, ns))
|
| 95 |
+
|
| 96 |
+
self.lig_distance_expansion = GaussianSmearing(0.0, lig_max_radius, distance_embed_dim)
|
| 97 |
+
self.rec_distance_expansion = GaussianSmearing(0.0, rec_max_radius, distance_embed_dim)
|
| 98 |
+
self.cross_distance_expansion = GaussianSmearing(0.0, cross_max_distance, cross_distance_embed_dim)
|
| 99 |
+
|
| 100 |
+
if use_second_order_repr:
|
| 101 |
+
irrep_seq = [
|
| 102 |
+
f'{ns}x0e',
|
| 103 |
+
f'{ns}x0e + {nv}x1o + {nv}x2e',
|
| 104 |
+
f'{ns}x0e + {nv}x1o + {nv}x2e + {nv}x1e + {nv}x2o',
|
| 105 |
+
f'{ns}x0e + {nv}x1o + {nv}x2e + {nv}x1e + {nv}x2o + {ns}x0o'
|
| 106 |
+
]
|
| 107 |
+
else:
|
| 108 |
+
irrep_seq = [
|
| 109 |
+
f'{ns}x0e',
|
| 110 |
+
f'{ns}x0e + {nv}x1o',
|
| 111 |
+
f'{ns}x0e + {nv}x1o + {nv}x1e',
|
| 112 |
+
f'{ns}x0e + {nv}x1o + {nv}x1e + {ns}x0o'
|
| 113 |
+
]
|
| 114 |
+
|
| 115 |
+
# convolutional layers
|
| 116 |
+
conv_layers = []
|
| 117 |
+
for i in range(num_conv_layers):
|
| 118 |
+
in_irreps = irrep_seq[min(i, len(irrep_seq) - 1)]
|
| 119 |
+
out_irreps = irrep_seq[min(i + 1, len(irrep_seq) - 1)]
|
| 120 |
+
parameters = {
|
| 121 |
+
'in_irreps': in_irreps,
|
| 122 |
+
'sh_irreps': self.sh_irreps,
|
| 123 |
+
'out_irreps': out_irreps,
|
| 124 |
+
'n_edge_features': 3 * ns,
|
| 125 |
+
'residual': False,
|
| 126 |
+
'batch_norm': batch_norm,
|
| 127 |
+
'dropout': dropout
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
for _ in range(9): # 3 intra & 6 inter per each layer
|
| 131 |
+
conv_layers.append(OldTensorProductConvLayer(**parameters))
|
| 132 |
+
|
| 133 |
+
self.conv_layers = nn.ModuleList(conv_layers)
|
| 134 |
+
|
| 135 |
+
# confidence and affinity prediction layers
|
| 136 |
+
if self.confidence_mode:
|
| 137 |
+
if self.affinity_prediction:
|
| 138 |
+
if self.parallel > 1:
|
| 139 |
+
output_confidence_dim = 1 + ns
|
| 140 |
+
else:
|
| 141 |
+
output_confidence_dim = num_confidence_outputs +1
|
| 142 |
+
else:
|
| 143 |
+
output_confidence_dim = num_confidence_outputs
|
| 144 |
+
|
| 145 |
+
self.confidence_predictor = nn.Sequential(
|
| 146 |
+
nn.Linear(2 * self.ns if num_conv_layers >= 3 else self.ns, ns),
|
| 147 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 148 |
+
nn.ReLU(),
|
| 149 |
+
nn.Dropout(confidence_dropout),
|
| 150 |
+
nn.Linear(ns, ns),
|
| 151 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 152 |
+
nn.ReLU(),
|
| 153 |
+
nn.Dropout(confidence_dropout),
|
| 154 |
+
nn.Linear(ns, output_confidence_dim)
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
if self.parallel > 1:
|
| 158 |
+
self.affinity_predictor = nn.Sequential(
|
| 159 |
+
nn.Linear(len(self.parallel_aggregators) * ns, ns),
|
| 160 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 161 |
+
nn.ReLU(),
|
| 162 |
+
nn.Dropout(confidence_dropout),
|
| 163 |
+
nn.Linear(ns, ns),
|
| 164 |
+
nn.BatchNorm1d(ns) if not confidence_no_batchnorm else nn.Identity(),
|
| 165 |
+
nn.ReLU(),
|
| 166 |
+
nn.Dropout(confidence_dropout),
|
| 167 |
+
nn.Linear(ns, 1)
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
else:
|
| 171 |
+
# convolution for translational and rotational scores
|
| 172 |
+
self.center_distance_expansion = GaussianSmearing(0.0, center_max_distance, distance_embed_dim)
|
| 173 |
+
self.center_edge_embedding = nn.Sequential(
|
| 174 |
+
nn.Linear(distance_embed_dim + sigma_embed_dim, ns),
|
| 175 |
+
nn.ReLU(),
|
| 176 |
+
nn.Dropout(dropout),
|
| 177 |
+
nn.Linear(ns, ns)
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
self.final_conv = OldTensorProductConvLayer(
|
| 181 |
+
in_irreps=self.conv_layers[-1].out_irreps,
|
| 182 |
+
sh_irreps=self.sh_irreps,
|
| 183 |
+
out_irreps=f'2x1o + 2x1e' if not self.odd_parity else '1x1o + 1x1e',
|
| 184 |
+
n_edge_features=2 * ns,
|
| 185 |
+
residual=False,
|
| 186 |
+
dropout=dropout,
|
| 187 |
+
batch_norm=batch_norm
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
self.tr_final_layer = nn.Sequential(nn.Linear(1 + sigma_embed_dim, ns),nn.Dropout(dropout), nn.ReLU(), nn.Linear(ns, 1))
|
| 191 |
+
self.rot_final_layer = nn.Sequential(nn.Linear(1 + sigma_embed_dim, ns),nn.Dropout(dropout), nn.ReLU(), nn.Linear(ns, 1))
|
| 192 |
+
|
| 193 |
+
if not no_torsion:
|
| 194 |
+
# convolution for torsional score
|
| 195 |
+
self.final_edge_embedding = nn.Sequential(
|
| 196 |
+
nn.Linear(distance_embed_dim, ns),
|
| 197 |
+
nn.ReLU(),
|
| 198 |
+
nn.Dropout(dropout),
|
| 199 |
+
nn.Linear(ns, ns)
|
| 200 |
+
)
|
| 201 |
+
self.final_tp_tor = o3.FullTensorProduct(self.sh_irreps, "2e")
|
| 202 |
+
self.tor_bond_conv = OldTensorProductConvLayer(
|
| 203 |
+
in_irreps=self.conv_layers[-1].out_irreps,
|
| 204 |
+
sh_irreps=self.final_tp_tor.irreps_out,
|
| 205 |
+
out_irreps=f'{ns}x0o + {ns}x0e' if not self.odd_parity else f'{ns}x0o',
|
| 206 |
+
n_edge_features=3 * ns,
|
| 207 |
+
residual=False,
|
| 208 |
+
dropout=dropout,
|
| 209 |
+
batch_norm=batch_norm
|
| 210 |
+
)
|
| 211 |
+
self.tor_final_layer = nn.Sequential(
|
| 212 |
+
nn.Linear(2 * ns if not self.odd_parity else ns, ns, bias=False),
|
| 213 |
+
nn.Tanh(),
|
| 214 |
+
nn.Dropout(dropout),
|
| 215 |
+
nn.Linear(ns, 1, bias=False)
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
@staticmethod
|
| 219 |
+
def _resolve_edge_store(data, primary_key, fallback_key):
|
| 220 |
+
edge_types = getattr(data, "edge_types", ())
|
| 221 |
+
if primary_key in edge_types:
|
| 222 |
+
return data[primary_key]
|
| 223 |
+
if fallback_key in edge_types:
|
| 224 |
+
return data[fallback_key]
|
| 225 |
+
try:
|
| 226 |
+
return data[primary_key]
|
| 227 |
+
except Exception:
|
| 228 |
+
return data[fallback_key]
|
| 229 |
+
|
| 230 |
+
def _ligand_edge_store(self, data):
|
| 231 |
+
return self._resolve_edge_store(
|
| 232 |
+
data,
|
| 233 |
+
("ligand", "ligand"),
|
| 234 |
+
("ligand", "lig_bond", "ligand"),
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
def _receptor_edge_store(self, data):
|
| 238 |
+
return self._resolve_edge_store(
|
| 239 |
+
data,
|
| 240 |
+
("receptor", "receptor"),
|
| 241 |
+
("receptor", "rec_contact", "receptor"),
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
def _atom_edge_store(self, data):
|
| 245 |
+
return self._resolve_edge_store(
|
| 246 |
+
data,
|
| 247 |
+
("atom", "atom"),
|
| 248 |
+
("atom", "atom_contact", "atom"),
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
def _atom_receptor_edge_store(self, data):
|
| 252 |
+
return self._resolve_edge_store(
|
| 253 |
+
data,
|
| 254 |
+
("atom", "receptor"),
|
| 255 |
+
("atom", "atom_rec_contact", "receptor"),
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
def forward(self, data):
|
| 259 |
+
if self.no_aminoacid_identities:
|
| 260 |
+
data['receptor'].x = data['receptor'].x * 0
|
| 261 |
+
|
| 262 |
+
if not self.confidence_mode:
|
| 263 |
+
tr_sigma, rot_sigma, tor_sigma = self.t_to_sigma(*[data.complex_t[noise_type] for noise_type in ['tr', 'rot', 'tor']])
|
| 264 |
+
else:
|
| 265 |
+
tr_sigma, rot_sigma, tor_sigma = [data.complex_t[noise_type] for noise_type in ['tr', 'rot', 'tor']]
|
| 266 |
+
|
| 267 |
+
# build ligand graph
|
| 268 |
+
lig_node_attr, lig_edge_index, lig_edge_attr, lig_edge_sh, lig_edge_weight = self.build_lig_conv_graph(data)
|
| 269 |
+
lig_node_attr = self.lig_node_embedding(lig_node_attr)
|
| 270 |
+
lig_edge_attr = self.lig_edge_embedding(lig_edge_attr)
|
| 271 |
+
|
| 272 |
+
# build receptor graph
|
| 273 |
+
rec_node_attr, rec_edge_index, rec_edge_attr, rec_edge_sh, rec_edge_weight = self.build_rec_conv_graph(data)
|
| 274 |
+
rec_node_attr = self.rec_node_embedding(rec_node_attr)
|
| 275 |
+
rec_edge_attr = self.rec_edge_embedding(rec_edge_attr)
|
| 276 |
+
|
| 277 |
+
# build atom graph
|
| 278 |
+
atom_node_attr, atom_edge_index, atom_edge_attr, atom_edge_sh, atom_edge_weight = self.build_atom_conv_graph(data)
|
| 279 |
+
atom_node_attr = self.atom_node_embedding(atom_node_attr)
|
| 280 |
+
atom_edge_attr = self.atom_edge_embedding(atom_edge_attr)
|
| 281 |
+
|
| 282 |
+
# build cross graph
|
| 283 |
+
cross_cutoff = (tr_sigma * 3 + 20).unsqueeze(1) if self.dynamic_max_cross else self.cross_max_distance
|
| 284 |
+
lr_edge_index, lr_edge_attr, lr_edge_sh, lr_edge_weight, la_edge_index, la_edge_attr, \
|
| 285 |
+
la_edge_sh, la_edge_weight, ar_edge_index, ar_edge_attr, ar_edge_sh, ar_edge_weight = \
|
| 286 |
+
self.build_cross_conv_graph(data, cross_cutoff)
|
| 287 |
+
lr_edge_attr= self.lr_edge_embedding(lr_edge_attr)
|
| 288 |
+
la_edge_attr = self.la_edge_embedding(la_edge_attr)
|
| 289 |
+
ar_edge_attr = self.ar_edge_embedding(ar_edge_attr)
|
| 290 |
+
|
| 291 |
+
for l in range(self.num_conv_layers):
|
| 292 |
+
# LIGAND updates
|
| 293 |
+
lig_edge_attr_ = torch.cat([lig_edge_attr, lig_node_attr[lig_edge_index[0], :self.ns], lig_node_attr[lig_edge_index[1], :self.ns]], -1)
|
| 294 |
+
lig_update = self.conv_layers[9*l](lig_node_attr, lig_edge_index, lig_edge_attr_, lig_edge_sh, edge_weight=lig_edge_weight)
|
| 295 |
+
|
| 296 |
+
lr_edge_attr_ = torch.cat([lr_edge_attr, lig_node_attr[lr_edge_index[0], :self.ns], rec_node_attr[lr_edge_index[1], :self.ns]], -1)
|
| 297 |
+
lr_update = self.conv_layers[9*l+1](rec_node_attr, lr_edge_index, lr_edge_attr_, lr_edge_sh,
|
| 298 |
+
out_nodes=lig_node_attr.shape[0], edge_weight=lr_edge_weight)
|
| 299 |
+
|
| 300 |
+
la_edge_attr_ = torch.cat([la_edge_attr, lig_node_attr[la_edge_index[0], :self.ns], atom_node_attr[la_edge_index[1], :self.ns]], -1)
|
| 301 |
+
la_update = self.conv_layers[9*l+2](atom_node_attr, la_edge_index, la_edge_attr_, la_edge_sh,
|
| 302 |
+
out_nodes=lig_node_attr.shape[0], edge_weight=la_edge_weight)
|
| 303 |
+
|
| 304 |
+
if l != self.num_conv_layers-1: # last layer optimisation
|
| 305 |
+
|
| 306 |
+
# ATOM UPDATES
|
| 307 |
+
atom_edge_attr_ = torch.cat([atom_edge_attr, atom_node_attr[atom_edge_index[0], :self.ns], atom_node_attr[atom_edge_index[1], :self.ns]], -1)
|
| 308 |
+
atom_update = self.conv_layers[9*l+3](atom_node_attr, atom_edge_index, atom_edge_attr_, atom_edge_sh, edge_weight=atom_edge_weight)
|
| 309 |
+
|
| 310 |
+
al_edge_attr_ = torch.cat([la_edge_attr, atom_node_attr[la_edge_index[1], :self.ns], lig_node_attr[la_edge_index[0], :self.ns]], -1)
|
| 311 |
+
al_update = self.conv_layers[9*l+4](lig_node_attr, torch.flip(la_edge_index, dims=[0]), al_edge_attr_,
|
| 312 |
+
la_edge_sh, out_nodes=atom_node_attr.shape[0], edge_weight=la_edge_weight)
|
| 313 |
+
|
| 314 |
+
ar_edge_attr_ = torch.cat([ar_edge_attr, atom_node_attr[ar_edge_index[0], :self.ns], rec_node_attr[ar_edge_index[1], :self.ns]],-1)
|
| 315 |
+
ar_update = self.conv_layers[9*l+5](rec_node_attr, ar_edge_index, ar_edge_attr_, ar_edge_sh, out_nodes=atom_node_attr.shape[0], edge_weight=ar_edge_weight)
|
| 316 |
+
|
| 317 |
+
# RECEPTOR updates
|
| 318 |
+
rec_edge_attr_ = torch.cat([rec_edge_attr, rec_node_attr[rec_edge_index[0], :self.ns], rec_node_attr[rec_edge_index[1], :self.ns]], -1)
|
| 319 |
+
rec_update = self.conv_layers[9*l+6](rec_node_attr, rec_edge_index, rec_edge_attr_, rec_edge_sh, edge_weight=rec_edge_weight)
|
| 320 |
+
|
| 321 |
+
rl_edge_attr_ = torch.cat([lr_edge_attr, rec_node_attr[lr_edge_index[1], :self.ns], lig_node_attr[lr_edge_index[0], :self.ns]], -1)
|
| 322 |
+
rl_update = self.conv_layers[9*l+7](lig_node_attr, torch.flip(lr_edge_index, dims=[0]), rl_edge_attr_,
|
| 323 |
+
lr_edge_sh, out_nodes=rec_node_attr.shape[0], edge_weight=lr_edge_weight)
|
| 324 |
+
|
| 325 |
+
ra_edge_attr_ = torch.cat([ar_edge_attr, rec_node_attr[ar_edge_index[1], :self.ns], atom_node_attr[ar_edge_index[0], :self.ns]], -1)
|
| 326 |
+
ra_update = self.conv_layers[9*l+8](atom_node_attr, torch.flip(ar_edge_index, dims=[0]), ra_edge_attr_,
|
| 327 |
+
ar_edge_sh, out_nodes=rec_node_attr.shape[0], edge_weight=ar_edge_weight)
|
| 328 |
+
|
| 329 |
+
# padding original features and update features with residual updates
|
| 330 |
+
lig_node_attr = F.pad(lig_node_attr, (0, lig_update.shape[-1] - lig_node_attr.shape[-1]))
|
| 331 |
+
lig_node_attr = lig_node_attr + lig_update + la_update + lr_update
|
| 332 |
+
|
| 333 |
+
if l != self.num_conv_layers - 1: # last layer optimisation
|
| 334 |
+
atom_node_attr = F.pad(atom_node_attr, (0, atom_update.shape[-1] - atom_node_attr.shape[-1]))
|
| 335 |
+
atom_node_attr = atom_node_attr + atom_update + al_update + ar_update
|
| 336 |
+
rec_node_attr = F.pad(rec_node_attr, (0, rec_update.shape[-1] - rec_node_attr.shape[-1]))
|
| 337 |
+
rec_node_attr = rec_node_attr + rec_update + ra_update + rl_update
|
| 338 |
+
|
| 339 |
+
# confidence and affinity prediction
|
| 340 |
+
if self.confidence_mode:
|
| 341 |
+
scalar_lig_attr = torch.cat([lig_node_attr[:,:self.ns],lig_node_attr[:,-self.ns:]], dim=1) if self.num_conv_layers >= 3 else lig_node_attr[:,:self.ns]
|
| 342 |
+
confidence = self.confidence_predictor(scatter_mean(scalar_lig_attr, data['ligand'].batch if self.parallel == 1 else data['ligand'].batch_parallel, dim=0)).squeeze(dim=-1)
|
| 343 |
+
|
| 344 |
+
if self.parallel > 1:
|
| 345 |
+
confidence, affinity = confidence[:, 0], confidence[:, 1:]
|
| 346 |
+
confidence = confidence.reshape(data.num_graphs, self.parallel)
|
| 347 |
+
affinity = affinity.reshape(data.num_graphs, self.parallel, -1)
|
| 348 |
+
affinity = torch.cat([AGGREGATORS[agg](affinity) for agg in self.parallel_aggregators], dim=-1)
|
| 349 |
+
affinity = self.affinity_predictor(affinity).squeeze(dim=-1)
|
| 350 |
+
confidence = confidence, affinity
|
| 351 |
+
return confidence
|
| 352 |
+
assert self.parallel == 1
|
| 353 |
+
|
| 354 |
+
# compute translational and rotational score vectors
|
| 355 |
+
center_edge_index, center_edge_attr, center_edge_sh = self.build_center_conv_graph(data)
|
| 356 |
+
center_edge_attr = self.center_edge_embedding(center_edge_attr)
|
| 357 |
+
if self.fixed_center_conv:
|
| 358 |
+
center_edge_attr = torch.cat([center_edge_attr, lig_node_attr[center_edge_index[1], :self.ns]], -1)
|
| 359 |
+
else:
|
| 360 |
+
center_edge_attr = torch.cat([center_edge_attr, lig_node_attr[center_edge_index[0], :self.ns]], -1)
|
| 361 |
+
global_pred = self.final_conv(lig_node_attr, center_edge_index, center_edge_attr, center_edge_sh, out_nodes=data.num_graphs)
|
| 362 |
+
|
| 363 |
+
tr_pred = global_pred[:, :3] + (global_pred[:, 6:9] if not self.odd_parity else 0)
|
| 364 |
+
rot_pred = global_pred[:, 3:6] + (global_pred[:, 9:] if not self.odd_parity else 0)
|
| 365 |
+
|
| 366 |
+
if self.separate_noise_schedule:
|
| 367 |
+
data.graph_sigma_emb = torch.cat([self.timestep_emb_func(data.complex_t[noise_type]) for noise_type in ['tr', 'rot', 'tor']], dim=1)
|
| 368 |
+
elif self.asyncronous_noise_schedule:
|
| 369 |
+
data.graph_sigma_emb = self.timestep_emb_func(data.complex_t['t'])
|
| 370 |
+
else: # tr rot and tor noise is all the same in this case
|
| 371 |
+
data.graph_sigma_emb = self.timestep_emb_func(data.complex_t['tr'])
|
| 372 |
+
|
| 373 |
+
# adjust the magniture of the score vectors
|
| 374 |
+
tr_norm = torch.linalg.vector_norm(tr_pred, dim=1).unsqueeze(1)
|
| 375 |
+
tr_pred = tr_pred / tr_norm * self.tr_final_layer(torch.cat([tr_norm, data.graph_sigma_emb], dim=1))
|
| 376 |
+
|
| 377 |
+
rot_norm = torch.linalg.vector_norm(rot_pred, dim=1).unsqueeze(1)
|
| 378 |
+
rot_pred = rot_pred / rot_norm * self.rot_final_layer(torch.cat([rot_norm, data.graph_sigma_emb], dim=1))
|
| 379 |
+
|
| 380 |
+
if self.scale_by_sigma:
|
| 381 |
+
tr_pred = tr_pred / tr_sigma.unsqueeze(1)
|
| 382 |
+
rot_pred = rot_pred * so3.score_norm(rot_sigma.cpu()).unsqueeze(1).to(data['ligand'].x.device)
|
| 383 |
+
|
| 384 |
+
if self.no_torsion or data['ligand'].edge_mask.sum() == 0: return tr_pred, rot_pred, torch.empty(0,device=self.device)
|
| 385 |
+
|
| 386 |
+
# torsional components
|
| 387 |
+
tor_bonds, tor_edge_index, tor_edge_attr, tor_edge_sh, tor_edge_weight = self.build_bond_conv_graph(data)
|
| 388 |
+
tor_bond_vec = data['ligand'].pos[tor_bonds[1]] - data['ligand'].pos[tor_bonds[0]]
|
| 389 |
+
tor_bond_attr = lig_node_attr[tor_bonds[0]] + lig_node_attr[tor_bonds[1]]
|
| 390 |
+
|
| 391 |
+
tor_bonds_sh = o3.spherical_harmonics("2e", tor_bond_vec, normalize=True, normalization='component')
|
| 392 |
+
tor_edge_sh = self.final_tp_tor(tor_edge_sh, tor_bonds_sh[tor_edge_index[0]])
|
| 393 |
+
|
| 394 |
+
tor_edge_attr = torch.cat([tor_edge_attr, lig_node_attr[tor_edge_index[1], :self.ns],
|
| 395 |
+
tor_bond_attr[tor_edge_index[0], :self.ns]], -1)
|
| 396 |
+
tor_pred = self.tor_bond_conv(lig_node_attr, tor_edge_index, tor_edge_attr, tor_edge_sh,
|
| 397 |
+
out_nodes=data['ligand'].edge_mask.sum(), reduce='mean', edge_weight=tor_edge_weight)
|
| 398 |
+
tor_pred = self.tor_final_layer(tor_pred).squeeze(1)
|
| 399 |
+
ligand_edge_store = self._ligand_edge_store(data)
|
| 400 |
+
edge_sigma = tor_sigma[data['ligand'].batch][ligand_edge_store.edge_index[0]][data['ligand'].edge_mask]
|
| 401 |
+
|
| 402 |
+
if self.scale_by_sigma:
|
| 403 |
+
tor_pred = tor_pred * torch.sqrt(torch.tensor(torus.score_norm(edge_sigma.cpu().numpy())).float()
|
| 404 |
+
.to(data['ligand'].x.device))
|
| 405 |
+
return tr_pred, rot_pred, tor_pred
|
| 406 |
+
|
| 407 |
+
def get_edge_weight(self, edge_vec, max_norm):
|
| 408 |
+
if self.smooth_edges:
|
| 409 |
+
normalised_norm = torch.clip(edge_vec.norm(dim=-1) * np.pi / max_norm, max=np.pi)
|
| 410 |
+
return 0.5 * (torch.cos(normalised_norm) + 1.0).unsqueeze(-1)
|
| 411 |
+
return 1.0
|
| 412 |
+
|
| 413 |
+
def build_lig_conv_graph(self, data):
|
| 414 |
+
# build the graph between ligand atoms
|
| 415 |
+
if self.separate_noise_schedule:
|
| 416 |
+
data['ligand'].node_sigma_emb = torch.cat(
|
| 417 |
+
[self.timestep_emb_func(data['ligand'].node_t[noise_type]) for noise_type in ['tr', 'rot', 'tor']],
|
| 418 |
+
dim=1)
|
| 419 |
+
elif self.asyncronous_noise_schedule:
|
| 420 |
+
data['ligand'].node_sigma_emb = self.timestep_emb_func(data['ligand'].node_t['t'])
|
| 421 |
+
else:
|
| 422 |
+
data['ligand'].node_sigma_emb = self.timestep_emb_func(
|
| 423 |
+
data['ligand'].node_t['tr']) # tr rot and tor noise is all the same
|
| 424 |
+
|
| 425 |
+
if self.parallel == 1:
|
| 426 |
+
radius_edges = radius_graph(data['ligand'].pos, self.lig_max_radius, data['ligand'].batch)
|
| 427 |
+
else:
|
| 428 |
+
batches = torch.zeros(data.num_graphs, device=data['ligand'].x.device).long()
|
| 429 |
+
batches = batches.index_add(0, data['ligand'].batch, torch.ones(len(data['ligand'].batch), device=data['ligand'].x.device).long())
|
| 430 |
+
outer_batches = data.num_graphs
|
| 431 |
+
b = [torch.ones(batches[i].item()//self.parallel, device=data['ligand'].x.device).long() * (self.parallel * i + j)
|
| 432 |
+
for i in range(outer_batches) for j in range(self.parallel)]
|
| 433 |
+
data['ligand'].batch_parallel = torch.cat(b)
|
| 434 |
+
radius_edges = radius_graph(data['ligand'].pos, self.lig_max_radius, data['ligand'].batch_parallel)
|
| 435 |
+
ligand_edge_store = self._ligand_edge_store(data)
|
| 436 |
+
edge_index = torch.cat([ligand_edge_store.edge_index, radius_edges], 1).long()
|
| 437 |
+
edge_attr = torch.cat([
|
| 438 |
+
ligand_edge_store.edge_attr,
|
| 439 |
+
torch.zeros(radius_edges.shape[-1], self.in_lig_edge_features, device=data['ligand'].x.device)
|
| 440 |
+
], 0)
|
| 441 |
+
|
| 442 |
+
edge_sigma_emb = data['ligand'].node_sigma_emb[edge_index[0].long()]
|
| 443 |
+
edge_attr = torch.cat([edge_attr, edge_sigma_emb], 1)
|
| 444 |
+
node_attr = torch.cat([data['ligand'].x, data['ligand'].node_sigma_emb], 1)
|
| 445 |
+
|
| 446 |
+
src, dst = edge_index
|
| 447 |
+
edge_vec = data['ligand'].pos[dst.long()] - data['ligand'].pos[src.long()]
|
| 448 |
+
edge_length_emb = self.lig_distance_expansion(edge_vec.norm(dim=-1))
|
| 449 |
+
|
| 450 |
+
edge_attr = torch.cat([edge_attr, edge_length_emb], 1)
|
| 451 |
+
edge_sh = o3.spherical_harmonics(self.sh_irreps, edge_vec, normalize=True, normalization='component')
|
| 452 |
+
edge_weight = self.get_edge_weight(edge_vec, self.lig_max_radius)
|
| 453 |
+
|
| 454 |
+
return node_attr, edge_index, edge_attr, edge_sh, edge_weight
|
| 455 |
+
|
| 456 |
+
def build_rec_conv_graph(self, data):
|
| 457 |
+
# build the graph between receptor residues
|
| 458 |
+
if self.separate_noise_schedule:
|
| 459 |
+
data['receptor'].node_sigma_emb = torch.cat(
|
| 460 |
+
[self.timestep_emb_func(data['receptor'].node_t[noise_type]) for noise_type in ['tr', 'rot', 'tor']],
|
| 461 |
+
dim=1)
|
| 462 |
+
elif self.asyncronous_noise_schedule:
|
| 463 |
+
data['receptor'].node_sigma_emb = self.timestep_emb_func(data['receptor'].node_t['t'])
|
| 464 |
+
else:
|
| 465 |
+
data['receptor'].node_sigma_emb = self.timestep_emb_func(
|
| 466 |
+
data['receptor'].node_t['tr']) # tr rot and tor noise is all the same
|
| 467 |
+
node_attr = torch.cat([data['receptor'].x, data['receptor'].node_sigma_emb], 1)
|
| 468 |
+
|
| 469 |
+
# this assumes the edges were already created in preprocessing since protein's structure is fixed
|
| 470 |
+
edge_index = self._receptor_edge_store(data).edge_index
|
| 471 |
+
src, dst = edge_index
|
| 472 |
+
edge_vec = data['receptor'].pos[dst.long()] - data['receptor'].pos[src.long()]
|
| 473 |
+
#assert torch.all(edge_vec.norm(dim=-1) < self.rec_max_radius)
|
| 474 |
+
|
| 475 |
+
edge_length_emb = self.rec_distance_expansion(edge_vec.norm(dim=-1))
|
| 476 |
+
edge_sigma_emb = data['receptor'].node_sigma_emb[edge_index[0].long()]
|
| 477 |
+
edge_attr = torch.cat([edge_sigma_emb, edge_length_emb], 1)
|
| 478 |
+
edge_sh = o3.spherical_harmonics(self.sh_irreps, edge_vec, normalize=True, normalization='component')
|
| 479 |
+
edge_weight = self.get_edge_weight(edge_vec, self.rec_max_radius)
|
| 480 |
+
|
| 481 |
+
return node_attr, edge_index, edge_attr, edge_sh, edge_weight
|
| 482 |
+
|
| 483 |
+
def build_atom_conv_graph(self, data):
|
| 484 |
+
# build the graph between receptor atoms
|
| 485 |
+
if self.separate_noise_schedule:
|
| 486 |
+
data['atom'].node_sigma_emb = torch.cat([self.timestep_emb_func(data['atom'].node_t[noise_type]) for noise_type in ['tr', 'rot', 'tor']],dim=1)
|
| 487 |
+
elif self.asyncronous_noise_schedule:
|
| 488 |
+
data['atom'].node_sigma_emb = self.timestep_emb_func(data['atom'].node_t['t'])
|
| 489 |
+
else:
|
| 490 |
+
data['atom'].node_sigma_emb = self.timestep_emb_func(data['atom'].node_t['tr']) # tr rot and tor noise is all the same
|
| 491 |
+
node_attr = torch.cat([data['atom'].x, data['atom'].node_sigma_emb], 1)
|
| 492 |
+
|
| 493 |
+
# this assumes the edges were already created in preprocessing since protein's structure is fixed
|
| 494 |
+
edge_index = self._atom_edge_store(data).edge_index
|
| 495 |
+
src, dst = edge_index
|
| 496 |
+
edge_vec = data['atom'].pos[dst.long()] - data['atom'].pos[src.long()]
|
| 497 |
+
|
| 498 |
+
edge_length_emb = self.lig_distance_expansion(edge_vec.norm(dim=-1))
|
| 499 |
+
edge_sigma_emb = data['atom'].node_sigma_emb[edge_index[0].long()]
|
| 500 |
+
edge_attr = torch.cat([edge_sigma_emb, edge_length_emb], 1)
|
| 501 |
+
edge_sh = o3.spherical_harmonics(self.sh_irreps, edge_vec, normalize=True, normalization='component')
|
| 502 |
+
edge_weight = self.get_edge_weight(edge_vec, self.lig_max_radius)
|
| 503 |
+
|
| 504 |
+
return node_attr, edge_index, edge_attr, edge_sh, edge_weight
|
| 505 |
+
|
| 506 |
+
def build_cross_conv_graph(self, data, lr_cross_distance_cutoff):
|
| 507 |
+
# build the cross edges between ligan atoms, receptor residues and receptor atoms
|
| 508 |
+
|
| 509 |
+
# LIGAND to RECEPTOR
|
| 510 |
+
if torch.is_tensor(lr_cross_distance_cutoff):
|
| 511 |
+
# different cutoff for every graph
|
| 512 |
+
lr_edge_index = radius(data['receptor'].pos / lr_cross_distance_cutoff[data['receptor'].batch],
|
| 513 |
+
data['ligand'].pos / lr_cross_distance_cutoff[data['ligand'].batch], 1,
|
| 514 |
+
data['receptor'].batch, data['ligand'].batch, max_num_neighbors=10000)
|
| 515 |
+
else:
|
| 516 |
+
lr_edge_index = radius(data['receptor'].pos, data['ligand'].pos, lr_cross_distance_cutoff,
|
| 517 |
+
data['receptor'].batch, data['ligand'].batch, max_num_neighbors=10000)
|
| 518 |
+
|
| 519 |
+
lr_edge_vec = data['receptor'].pos[lr_edge_index[1].long()] - data['ligand'].pos[lr_edge_index[0].long()]
|
| 520 |
+
lr_edge_length_emb = self.cross_distance_expansion(lr_edge_vec.norm(dim=-1))
|
| 521 |
+
lr_edge_sigma_emb = data['ligand'].node_sigma_emb[lr_edge_index[0].long()]
|
| 522 |
+
lr_edge_attr = torch.cat([lr_edge_sigma_emb, lr_edge_length_emb], 1)
|
| 523 |
+
lr_edge_sh = o3.spherical_harmonics(self.sh_irreps, lr_edge_vec, normalize=True, normalization='component')
|
| 524 |
+
|
| 525 |
+
cutoff_d = lr_cross_distance_cutoff[data['ligand'].batch[lr_edge_index[0]]].squeeze() \
|
| 526 |
+
if torch.is_tensor(lr_cross_distance_cutoff) else lr_cross_distance_cutoff
|
| 527 |
+
lr_edge_weight = self.get_edge_weight(lr_edge_vec, cutoff_d)
|
| 528 |
+
|
| 529 |
+
# LIGAND to ATOM
|
| 530 |
+
la_edge_index = radius(data['atom'].pos, data['ligand'].pos, self.lig_max_radius,
|
| 531 |
+
data['atom'].batch, data['ligand'].batch, max_num_neighbors=10000)
|
| 532 |
+
|
| 533 |
+
la_edge_vec = data['atom'].pos[la_edge_index[1].long()] - data['ligand'].pos[la_edge_index[0].long()]
|
| 534 |
+
la_edge_length_emb = self.cross_distance_expansion(la_edge_vec.norm(dim=-1))
|
| 535 |
+
la_edge_sigma_emb = data['ligand'].node_sigma_emb[la_edge_index[0].long()]
|
| 536 |
+
la_edge_attr = torch.cat([la_edge_sigma_emb, la_edge_length_emb], 1)
|
| 537 |
+
la_edge_sh = o3.spherical_harmonics(self.sh_irreps, la_edge_vec, normalize=True, normalization='component')
|
| 538 |
+
la_edge_weight = self.get_edge_weight(la_edge_vec, self.lig_max_radius)
|
| 539 |
+
|
| 540 |
+
# ATOM to RECEPTOR
|
| 541 |
+
ar_edge_index = self._atom_receptor_edge_store(data).edge_index
|
| 542 |
+
ar_edge_vec = data['receptor'].pos[ar_edge_index[1].long()] - data['atom'].pos[ar_edge_index[0].long()]
|
| 543 |
+
ar_edge_length_emb = self.rec_distance_expansion(ar_edge_vec.norm(dim=-1))
|
| 544 |
+
ar_edge_sigma_emb = data['atom'].node_sigma_emb[ar_edge_index[0].long()]
|
| 545 |
+
ar_edge_attr = torch.cat([ar_edge_sigma_emb, ar_edge_length_emb], 1)
|
| 546 |
+
ar_edge_sh = o3.spherical_harmonics(self.sh_irreps, ar_edge_vec, normalize=True, normalization='component')
|
| 547 |
+
ar_edge_weight = 1
|
| 548 |
+
|
| 549 |
+
return lr_edge_index, lr_edge_attr, lr_edge_sh, lr_edge_weight, la_edge_index, la_edge_attr, \
|
| 550 |
+
la_edge_sh, la_edge_weight, ar_edge_index, ar_edge_attr, ar_edge_sh, ar_edge_weight
|
| 551 |
+
|
| 552 |
+
def build_center_conv_graph(self, data):
|
| 553 |
+
# build the filter for the convolution of the center with the ligand atoms
|
| 554 |
+
# for translational and rotational score
|
| 555 |
+
edge_index = torch.cat([data['ligand'].batch.unsqueeze(0), torch.arange(len(data['ligand'].batch)).to(data['ligand'].x.device).unsqueeze(0)], dim=0)
|
| 556 |
+
|
| 557 |
+
center_pos, count = torch.zeros((data.num_graphs, 3)).to(data['ligand'].x.device), torch.zeros((data.num_graphs, 3)).to(data['ligand'].x.device)
|
| 558 |
+
center_pos.index_add_(0, index=data['ligand'].batch, source=data['ligand'].pos)
|
| 559 |
+
center_pos = center_pos / torch.bincount(data['ligand'].batch).unsqueeze(1)
|
| 560 |
+
|
| 561 |
+
edge_vec = data['ligand'].pos[edge_index[1]] - center_pos[edge_index[0]]
|
| 562 |
+
edge_attr = self.center_distance_expansion(edge_vec.norm(dim=-1))
|
| 563 |
+
edge_sigma_emb = data['ligand'].node_sigma_emb[edge_index[1].long()]
|
| 564 |
+
edge_attr = torch.cat([edge_attr, edge_sigma_emb], 1)
|
| 565 |
+
edge_sh = o3.spherical_harmonics(self.sh_irreps, edge_vec, normalize=True, normalization='component')
|
| 566 |
+
return edge_index, edge_attr, edge_sh
|
| 567 |
+
|
| 568 |
+
def build_bond_conv_graph(self, data):
|
| 569 |
+
# build graph for the pseudotorque layer
|
| 570 |
+
bonds = self._ligand_edge_store(data).edge_index[:, data['ligand'].edge_mask].long()
|
| 571 |
+
bond_pos = (data['ligand'].pos[bonds[0]] + data['ligand'].pos[bonds[1]]) / 2
|
| 572 |
+
bond_batch = data['ligand'].batch[bonds[0]]
|
| 573 |
+
edge_index = radius(data['ligand'].pos, bond_pos, self.lig_max_radius, batch_x=data['ligand'].batch, batch_y=bond_batch)
|
| 574 |
+
|
| 575 |
+
edge_vec = data['ligand'].pos[edge_index[1]] - bond_pos[edge_index[0]]
|
| 576 |
+
edge_attr = self.lig_distance_expansion(edge_vec.norm(dim=-1))
|
| 577 |
+
|
| 578 |
+
edge_attr = self.final_edge_embedding(edge_attr)
|
| 579 |
+
edge_sh = o3.spherical_harmonics(self.sh_irreps, edge_vec, normalize=True, normalization='component')
|
| 580 |
+
edge_weight = self.get_edge_weight(edge_vec, self.lig_max_radius)
|
| 581 |
+
|
| 582 |
+
return bonds, edge_index, edge_attr, edge_sh, edge_weight
|
models/score_wrapper.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from argparse import Namespace
|
| 2 |
+
from functools import partial
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import yaml
|
| 7 |
+
from torch_geometric.nn.data_parallel import DataParallel
|
| 8 |
+
|
| 9 |
+
from onescience.utils.diffdock.diffusion_utils import get_timestep_embedding, t_to_sigma as t_to_sigma_compl
|
| 10 |
+
from onescience.utils.diffdock.utils import ExponentialMovingAverage
|
| 11 |
+
|
| 12 |
+
from .aa_model import AAModel
|
| 13 |
+
from .cg_model import CGModel
|
| 14 |
+
from .old_aa_model import AAOldModel
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
_LM_EMBEDDING_KEYS = (
|
| 18 |
+
"moad_esm_embeddings_path",
|
| 19 |
+
"pdbbind_esm_embeddings_path",
|
| 20 |
+
"pdbsidechain_esm_embeddings_path",
|
| 21 |
+
"esm_embeddings_path",
|
| 22 |
+
"esm_embeddings_model",
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def load_model_args(model_dir):
|
| 27 |
+
model_dir = Path(model_dir)
|
| 28 |
+
config_path = model_dir / "model_parameters.yml"
|
| 29 |
+
with config_path.open("r", encoding="utf-8") as handle:
|
| 30 |
+
config = yaml.full_load(handle) or {}
|
| 31 |
+
return Namespace(**config)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def model_uses_lm_embeddings(model_args):
|
| 35 |
+
return any(getattr(model_args, key, None) is not None for key in _LM_EMBEDDING_KEYS)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _has_arg(args, name):
|
| 39 |
+
try:
|
| 40 |
+
return name in args
|
| 41 |
+
except TypeError:
|
| 42 |
+
return hasattr(args, name)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _get_arg(args, name, default=None):
|
| 46 |
+
if isinstance(args, dict):
|
| 47 |
+
return args.get(name, default)
|
| 48 |
+
if _has_arg(args, name):
|
| 49 |
+
return getattr(args, name)
|
| 50 |
+
return default
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def get_model(args, device, t_to_sigma, no_parallel=False, confidence_mode=False, old=False):
|
| 54 |
+
timestep_emb_func = get_timestep_embedding(
|
| 55 |
+
embedding_type=_get_arg(args, "embedding_type", "sinusoidal"),
|
| 56 |
+
embedding_dim=args.sigma_embed_dim,
|
| 57 |
+
embedding_scale=_get_arg(args, "embedding_scale", 10000),
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
all_atoms = _get_arg(args, "all_atoms", False)
|
| 61 |
+
if old and not all_atoms:
|
| 62 |
+
raise NotImplementedError(
|
| 63 |
+
"The old coarse-grained DiffDock model path is not migrated yet. "
|
| 64 |
+
"Use old=True only with all_atoms=True, or migrate old_cg_model.py first."
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
lm_embedding_type = None
|
| 68 |
+
if (
|
| 69 |
+
_get_arg(args, "moad_esm_embeddings_path") is not None
|
| 70 |
+
or _get_arg(args, "pdbbind_esm_embeddings_path") is not None
|
| 71 |
+
or _get_arg(args, "pdbsidechain_esm_embeddings_path") is not None
|
| 72 |
+
or _get_arg(args, "esm_embeddings_path") is not None
|
| 73 |
+
):
|
| 74 |
+
lm_embedding_type = "precomputed"
|
| 75 |
+
if _get_arg(args, "esm_embeddings_model") is not None:
|
| 76 |
+
lm_embedding_type = args.esm_embeddings_model
|
| 77 |
+
|
| 78 |
+
if old:
|
| 79 |
+
model_class = AAOldModel
|
| 80 |
+
elif all_atoms:
|
| 81 |
+
model_class = AAModel
|
| 82 |
+
else:
|
| 83 |
+
model_class = CGModel
|
| 84 |
+
|
| 85 |
+
model_kwargs = dict(
|
| 86 |
+
t_to_sigma=t_to_sigma,
|
| 87 |
+
device=device,
|
| 88 |
+
no_torsion=args.no_torsion,
|
| 89 |
+
timestep_emb_func=timestep_emb_func,
|
| 90 |
+
num_conv_layers=args.num_conv_layers,
|
| 91 |
+
lig_max_radius=args.max_radius,
|
| 92 |
+
scale_by_sigma=args.scale_by_sigma,
|
| 93 |
+
sigma_embed_dim=args.sigma_embed_dim,
|
| 94 |
+
norm_by_sigma=_get_arg(args, "norm_by_sigma", False),
|
| 95 |
+
ns=args.ns,
|
| 96 |
+
nv=args.nv,
|
| 97 |
+
distance_embed_dim=args.distance_embed_dim,
|
| 98 |
+
cross_distance_embed_dim=args.cross_distance_embed_dim,
|
| 99 |
+
batch_norm=not args.no_batch_norm,
|
| 100 |
+
dropout=args.dropout,
|
| 101 |
+
use_second_order_repr=args.use_second_order_repr,
|
| 102 |
+
cross_max_distance=args.cross_max_distance,
|
| 103 |
+
dynamic_max_cross=args.dynamic_max_cross,
|
| 104 |
+
smooth_edges=_get_arg(args, "smooth_edges", False),
|
| 105 |
+
odd_parity=_get_arg(args, "odd_parity", False),
|
| 106 |
+
lm_embedding_type=lm_embedding_type,
|
| 107 |
+
confidence_mode=confidence_mode,
|
| 108 |
+
confidence_dropout=_get_arg(args, "confidence_dropout", 0.0),
|
| 109 |
+
confidence_no_batchnorm=_get_arg(args, "confidence_no_batchnorm", False),
|
| 110 |
+
affinity_prediction=_get_arg(args, "affinity_prediction", False),
|
| 111 |
+
parallel=_get_arg(args, "parallel", 1),
|
| 112 |
+
num_confidence_outputs=(
|
| 113 |
+
len(args.rmsd_classification_cutoff) + 1
|
| 114 |
+
if isinstance(_get_arg(args, "rmsd_classification_cutoff"), list)
|
| 115 |
+
else 1
|
| 116 |
+
),
|
| 117 |
+
atom_num_confidence_outputs=(
|
| 118 |
+
len(args.atom_rmsd_classification_cutoff) + 1
|
| 119 |
+
if isinstance(_get_arg(args, "atom_rmsd_classification_cutoff"), list)
|
| 120 |
+
else 1
|
| 121 |
+
),
|
| 122 |
+
parallel_aggregators=_get_arg(args, "parallel_aggregators", ""),
|
| 123 |
+
fixed_center_conv=not _get_arg(args, "not_fixed_center_conv", False),
|
| 124 |
+
no_aminoacid_identities=_get_arg(args, "no_aminoacid_identities", False),
|
| 125 |
+
include_miscellaneous_atoms=_get_arg(args, "include_miscellaneous_atoms", False),
|
| 126 |
+
sh_lmax=_get_arg(args, "sh_lmax", 2),
|
| 127 |
+
differentiate_convolutions=not _get_arg(args, "no_differentiate_convolutions", False),
|
| 128 |
+
tp_weights_layers=_get_arg(args, "tp_weights_layers", 2),
|
| 129 |
+
num_prot_emb_layers=_get_arg(args, "num_prot_emb_layers", 0),
|
| 130 |
+
reduce_pseudoscalars=_get_arg(args, "reduce_pseudoscalars", False),
|
| 131 |
+
embed_also_ligand=_get_arg(args, "embed_also_ligand", False),
|
| 132 |
+
atom_confidence=_get_arg(args, "atom_confidence_loss_weight", 0.0) > 0.0,
|
| 133 |
+
sidechain_pred=(
|
| 134 |
+
(_has_arg(args, "sidechain_loss_weight") and args.sidechain_loss_weight > 0)
|
| 135 |
+
or (_has_arg(args, "backbone_loss_weight") and args.backbone_loss_weight > 0)
|
| 136 |
+
),
|
| 137 |
+
depthwise_convolution=_get_arg(args, "depthwise_convolution", False),
|
| 138 |
+
)
|
| 139 |
+
if model_class is AAModel:
|
| 140 |
+
model_kwargs["crop_beyond"] = _get_arg(args, "crop_beyond", None)
|
| 141 |
+
elif model_class is AAOldModel:
|
| 142 |
+
for key in (
|
| 143 |
+
"atom_num_confidence_outputs",
|
| 144 |
+
"differentiate_convolutions",
|
| 145 |
+
"tp_weights_layers",
|
| 146 |
+
"num_prot_emb_layers",
|
| 147 |
+
"reduce_pseudoscalars",
|
| 148 |
+
"embed_also_ligand",
|
| 149 |
+
"atom_confidence",
|
| 150 |
+
"sidechain_pred",
|
| 151 |
+
"depthwise_convolution",
|
| 152 |
+
):
|
| 153 |
+
model_kwargs.pop(key, None)
|
| 154 |
+
model_kwargs["lm_embedding_type"] = (
|
| 155 |
+
"esm" if _get_arg(args, "esm_embeddings_path") is not None else None
|
| 156 |
+
)
|
| 157 |
+
model_kwargs["use_old_atom_encoder"] = _get_arg(args, "use_old_atom_encoder", True)
|
| 158 |
+
|
| 159 |
+
model = model_class(**model_kwargs)
|
| 160 |
+
|
| 161 |
+
if device.type == "cuda" and not no_parallel and _get_arg(args, "dataset") != "torsional":
|
| 162 |
+
model = DataParallel(model)
|
| 163 |
+
model.to(device)
|
| 164 |
+
return model
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def build_score_model(
|
| 168 |
+
model_args,
|
| 169 |
+
device,
|
| 170 |
+
no_parallel=False,
|
| 171 |
+
confidence_mode=False,
|
| 172 |
+
old=False,
|
| 173 |
+
):
|
| 174 |
+
t_to_sigma = partial(t_to_sigma_compl, args=model_args)
|
| 175 |
+
model = get_model(
|
| 176 |
+
model_args,
|
| 177 |
+
device,
|
| 178 |
+
t_to_sigma=t_to_sigma,
|
| 179 |
+
no_parallel=no_parallel,
|
| 180 |
+
confidence_mode=confidence_mode,
|
| 181 |
+
old=old,
|
| 182 |
+
)
|
| 183 |
+
return model, t_to_sigma
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def load_score_model(
|
| 187 |
+
model_dir,
|
| 188 |
+
ckpt,
|
| 189 |
+
device=None,
|
| 190 |
+
no_parallel=True,
|
| 191 |
+
confidence_mode=False,
|
| 192 |
+
old=False,
|
| 193 |
+
strict=True,
|
| 194 |
+
):
|
| 195 |
+
if device is None:
|
| 196 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 197 |
+
|
| 198 |
+
model_args = load_model_args(model_dir)
|
| 199 |
+
model, t_to_sigma = build_score_model(
|
| 200 |
+
model_args=model_args,
|
| 201 |
+
device=device,
|
| 202 |
+
no_parallel=no_parallel,
|
| 203 |
+
confidence_mode=confidence_mode,
|
| 204 |
+
old=old,
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
checkpoint_path = Path(model_dir) / ckpt
|
| 208 |
+
state_dict = torch.load(checkpoint_path, map_location=torch.device("cpu"))
|
| 209 |
+
if isinstance(state_dict, dict) and "model" in state_dict and "optimizer" in state_dict:
|
| 210 |
+
model.load_state_dict(state_dict["model"], strict=strict)
|
| 211 |
+
if "ema_weights" in state_dict and getattr(model_args, "ema_rate", None) is not None:
|
| 212 |
+
ema_weights = ExponentialMovingAverage(model.parameters(), decay=model_args.ema_rate)
|
| 213 |
+
ema_weights.load_state_dict(state_dict["ema_weights"], device=device)
|
| 214 |
+
ema_weights.copy_to(model.parameters())
|
| 215 |
+
else:
|
| 216 |
+
model.load_state_dict(state_dict, strict=strict)
|
| 217 |
+
model = model.to(device)
|
| 218 |
+
model.eval()
|
| 219 |
+
return model, model_args, t_to_sigma
|
models/tensor_layers.py
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
from typing import List, Tuple, Union
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
import torch.nn.functional as F
|
| 9 |
+
from e3nn import o3
|
| 10 |
+
from e3nn.nn import BatchNorm
|
| 11 |
+
from e3nn.o3 import Linear, TensorProduct
|
| 12 |
+
from torch_scatter import scatter, scatter_mean
|
| 13 |
+
|
| 14 |
+
from .layers import FCBlock
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def get_irrep_seq(ns, nv, use_second_order_repr, reduce_pseudoscalars):
|
| 18 |
+
if use_second_order_repr:
|
| 19 |
+
irrep_seq = [
|
| 20 |
+
f"{ns}x0e",
|
| 21 |
+
f"{ns}x0e + {nv}x1o + {nv}x2e",
|
| 22 |
+
f"{ns}x0e + {nv}x1o + {nv}x2e + {nv}x1e + {nv}x2o",
|
| 23 |
+
f"{ns}x0e + {nv}x1o + {nv}x2e + {nv}x1e + {nv}x2o + {nv if reduce_pseudoscalars else ns}x0o",
|
| 24 |
+
]
|
| 25 |
+
else:
|
| 26 |
+
irrep_seq = [
|
| 27 |
+
f"{ns}x0e",
|
| 28 |
+
f"{ns}x0e + {nv}x1o",
|
| 29 |
+
f"{ns}x0e + {nv}x1o + {nv}x1e",
|
| 30 |
+
f"{ns}x0e + {nv}x1o + {nv}x1e + {nv if reduce_pseudoscalars else ns}x0o",
|
| 31 |
+
]
|
| 32 |
+
return irrep_seq
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def irrep_to_size(irrep):
|
| 36 |
+
irreps = irrep.split(" + ")
|
| 37 |
+
size = 0
|
| 38 |
+
for ir in irreps:
|
| 39 |
+
m, (l, p) = ir.split("x")
|
| 40 |
+
size += int(m) * (2 * int(l) + 1)
|
| 41 |
+
return size
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class FasterTensorProduct(torch.nn.Module):
|
| 45 |
+
# Implemented by Bowen Jing
|
| 46 |
+
def __init__(self, in_irreps, sh_irreps, out_irreps, **kwargs):
|
| 47 |
+
super().__init__()
|
| 48 |
+
assert (
|
| 49 |
+
o3.Irreps(sh_irreps) == o3.Irreps("1x0e+1x1o")
|
| 50 |
+
), "sh_irreps don't look like 1st order spherical harmonics"
|
| 51 |
+
self.in_irreps = o3.Irreps(in_irreps)
|
| 52 |
+
self.out_irreps = o3.Irreps(out_irreps)
|
| 53 |
+
|
| 54 |
+
in_muls = {"0e": 0, "1o": 0, "1e": 0, "0o": 0}
|
| 55 |
+
out_muls = {"0e": 0, "1o": 0, "1e": 0, "0o": 0}
|
| 56 |
+
for (m, ir) in self.in_irreps:
|
| 57 |
+
in_muls[str(ir)] = m
|
| 58 |
+
for (m, ir) in self.out_irreps:
|
| 59 |
+
out_muls[str(ir)] = m
|
| 60 |
+
|
| 61 |
+
self.weight_shapes = {
|
| 62 |
+
"0e": (in_muls["0e"] + in_muls["1o"], out_muls["0e"]),
|
| 63 |
+
"1o": (in_muls["0e"] + in_muls["1o"] + in_muls["1e"], out_muls["1o"]),
|
| 64 |
+
"1e": (in_muls["1o"] + in_muls["1e"] + in_muls["0o"], out_muls["1e"]),
|
| 65 |
+
"0o": (in_muls["1e"] + in_muls["0o"], out_muls["0o"]),
|
| 66 |
+
}
|
| 67 |
+
self.weight_numel = sum(a * b for (a, b) in self.weight_shapes.values())
|
| 68 |
+
|
| 69 |
+
def forward(self, in_, sh, weight):
|
| 70 |
+
in_dict, out_dict = {}, {"0e": [], "1o": [], "1e": [], "0o": []}
|
| 71 |
+
for (m, ir), sl in zip(self.in_irreps, self.in_irreps.slices()):
|
| 72 |
+
in_dict[str(ir)] = in_[..., sl]
|
| 73 |
+
if ir[0] == 1:
|
| 74 |
+
in_dict[str(ir)] = in_dict[str(ir)].reshape(
|
| 75 |
+
list(in_dict[str(ir)].shape)[:-1] + [-1, 3]
|
| 76 |
+
)
|
| 77 |
+
sh_0e, sh_1o = sh[..., 0], sh[..., 1:]
|
| 78 |
+
if "0e" in in_dict:
|
| 79 |
+
out_dict["0e"].append(in_dict["0e"] * sh_0e.unsqueeze(-1))
|
| 80 |
+
out_dict["1o"].append(in_dict["0e"].unsqueeze(-1) * sh_1o.unsqueeze(-2))
|
| 81 |
+
if "1o" in in_dict:
|
| 82 |
+
out_dict["0e"].append((in_dict["1o"] * sh_1o.unsqueeze(-2)).sum(-1) / np.sqrt(3))
|
| 83 |
+
out_dict["1o"].append(in_dict["1o"] * sh_0e.unsqueeze(-1).unsqueeze(-1))
|
| 84 |
+
out_dict["1e"].append(
|
| 85 |
+
torch.linalg.cross(in_dict["1o"], sh_1o.unsqueeze(-2), dim=-1) / np.sqrt(2)
|
| 86 |
+
)
|
| 87 |
+
if "1e" in in_dict:
|
| 88 |
+
out_dict["1o"].append(
|
| 89 |
+
torch.linalg.cross(in_dict["1e"], sh_1o.unsqueeze(-2), dim=-1) / np.sqrt(2)
|
| 90 |
+
)
|
| 91 |
+
out_dict["1e"].append(in_dict["1e"] * sh_0e.unsqueeze(-1).unsqueeze(-1))
|
| 92 |
+
out_dict["0o"].append((in_dict["1e"] * sh_1o.unsqueeze(-2)).sum(-1) / np.sqrt(3))
|
| 93 |
+
if "0o" in in_dict:
|
| 94 |
+
out_dict["1e"].append(in_dict["0o"].unsqueeze(-1) * sh_1o.unsqueeze(-2))
|
| 95 |
+
out_dict["0o"].append(in_dict["0o"] * sh_0e.unsqueeze(-1))
|
| 96 |
+
|
| 97 |
+
weight_dict = {}
|
| 98 |
+
start = 0
|
| 99 |
+
for key in self.weight_shapes:
|
| 100 |
+
in_, out = self.weight_shapes[key]
|
| 101 |
+
weight_dict[key] = weight[..., start : start + in_ * out].reshape(
|
| 102 |
+
list(weight.shape)[:-1] + [in_, out]
|
| 103 |
+
) / np.sqrt(in_)
|
| 104 |
+
start += in_ * out
|
| 105 |
+
|
| 106 |
+
if out_dict["0e"]:
|
| 107 |
+
out_dict["0e"] = torch.cat(out_dict["0e"], dim=-1)
|
| 108 |
+
out_dict["0e"] = torch.matmul(
|
| 109 |
+
out_dict["0e"].unsqueeze(-2), weight_dict["0e"]
|
| 110 |
+
).squeeze(-2)
|
| 111 |
+
|
| 112 |
+
if out_dict["1o"]:
|
| 113 |
+
out_dict["1o"] = torch.cat(out_dict["1o"], dim=-2)
|
| 114 |
+
out_dict["1o"] = (
|
| 115 |
+
out_dict["1o"].unsqueeze(-2) * weight_dict["1o"].unsqueeze(-1)
|
| 116 |
+
).sum(-3)
|
| 117 |
+
out_dict["1o"] = out_dict["1o"].reshape(list(out_dict["1o"].shape)[:-2] + [-1])
|
| 118 |
+
|
| 119 |
+
if out_dict["1e"]:
|
| 120 |
+
out_dict["1e"] = torch.cat(out_dict["1e"], dim=-2)
|
| 121 |
+
out_dict["1e"] = (
|
| 122 |
+
out_dict["1e"].unsqueeze(-2) * weight_dict["1e"].unsqueeze(-1)
|
| 123 |
+
).sum(-3)
|
| 124 |
+
out_dict["1e"] = out_dict["1e"].reshape(list(out_dict["1e"].shape)[:-2] + [-1])
|
| 125 |
+
|
| 126 |
+
if out_dict["0o"]:
|
| 127 |
+
out_dict["0o"] = torch.cat(out_dict["0o"], dim=-1)
|
| 128 |
+
out_dict["0o"] = torch.matmul(
|
| 129 |
+
out_dict["0o"].unsqueeze(-2), weight_dict["0o"]
|
| 130 |
+
).squeeze(-2)
|
| 131 |
+
|
| 132 |
+
out = []
|
| 133 |
+
for _, ir in self.out_irreps:
|
| 134 |
+
out.append(out_dict[str(ir)])
|
| 135 |
+
return torch.cat(out, dim=-1)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def tp_scatter_simple(
|
| 139 |
+
tp,
|
| 140 |
+
fc_layer,
|
| 141 |
+
node_attr,
|
| 142 |
+
edge_index,
|
| 143 |
+
edge_attr,
|
| 144 |
+
edge_sh,
|
| 145 |
+
out_nodes=None,
|
| 146 |
+
reduce="mean",
|
| 147 |
+
edge_weight=1.0,
|
| 148 |
+
):
|
| 149 |
+
"""
|
| 150 |
+
Perform TensorProduct + scatter operation, aka graph convolution.
|
| 151 |
+
|
| 152 |
+
This function is only for edge_groups == 1. For multiple edge groups, and for larger graphs,
|
| 153 |
+
use tp_scatter_multigroup instead.
|
| 154 |
+
"""
|
| 155 |
+
|
| 156 |
+
assert isinstance(
|
| 157 |
+
edge_attr, torch.Tensor
|
| 158 |
+
), "This function is only for a single edge group, so edge_attr must be a tensor and not a list."
|
| 159 |
+
|
| 160 |
+
_device = node_attr.device
|
| 161 |
+
_dtype = node_attr.dtype
|
| 162 |
+
edge_src, edge_dst = edge_index
|
| 163 |
+
out_irreps = fc_layer(edge_attr).to(_device).to(_dtype)
|
| 164 |
+
out_irreps.mul_(edge_weight)
|
| 165 |
+
tp = tp(node_attr[edge_dst], edge_sh, out_irreps)
|
| 166 |
+
out_nodes = out_nodes or node_attr.shape[0]
|
| 167 |
+
out = scatter(tp, edge_src, dim=0, dim_size=out_nodes, reduce=reduce)
|
| 168 |
+
return out
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def tp_scatter_multigroup(
|
| 172 |
+
tp: o3.TensorProduct,
|
| 173 |
+
fc_layer: Union[nn.Module, nn.ModuleList],
|
| 174 |
+
node_attr: torch.Tensor,
|
| 175 |
+
edge_index: torch.Tensor,
|
| 176 |
+
edge_attr_groups: List[torch.Tensor],
|
| 177 |
+
edge_sh: torch.Tensor,
|
| 178 |
+
out_nodes=None,
|
| 179 |
+
reduce="mean",
|
| 180 |
+
edge_weight=1.0,
|
| 181 |
+
):
|
| 182 |
+
"""
|
| 183 |
+
Perform TensorProduct + scatter operation, aka graph convolution.
|
| 184 |
+
|
| 185 |
+
To keep the peak memory usage reasonably low, this function does not concatenate the edge_attr_groups.
|
| 186 |
+
Rather, we sum the output of the tensor product for each edge group, and then divide by the number of edges
|
| 187 |
+
"""
|
| 188 |
+
|
| 189 |
+
assert isinstance(edge_attr_groups, list), "This function is only for a list of edge groups"
|
| 190 |
+
assert reduce in {"mean", "sum"}, "Only 'mean' and 'sum' are supported for reduce"
|
| 191 |
+
|
| 192 |
+
_device = node_attr.device
|
| 193 |
+
_dtype = node_attr.dtype
|
| 194 |
+
edge_src, edge_dst = edge_index
|
| 195 |
+
edge_attr_lengths = [_edge_attr.shape[0] for _edge_attr in edge_attr_groups]
|
| 196 |
+
total_rows = sum(edge_attr_lengths)
|
| 197 |
+
assert (
|
| 198 |
+
total_rows == edge_index.shape[1]
|
| 199 |
+
), "Sum of edge_attr_groups must be equal to edge_index.shape[1]"
|
| 200 |
+
num_edge_groups = len(edge_attr_groups)
|
| 201 |
+
edge_weight_is_indexable = hasattr(edge_weight, "__getitem__")
|
| 202 |
+
|
| 203 |
+
out_nodes = out_nodes or node_attr.shape[0]
|
| 204 |
+
total_output_dim = sum([x.dim for x in tp.irreps_out])
|
| 205 |
+
final_out = torch.zeros((out_nodes, total_output_dim), device=_device, dtype=_dtype)
|
| 206 |
+
div_factors = torch.zeros(out_nodes, device=_device, dtype=_dtype)
|
| 207 |
+
|
| 208 |
+
cur_start = 0
|
| 209 |
+
for ii in range(num_edge_groups):
|
| 210 |
+
cur_length = edge_attr_lengths[ii]
|
| 211 |
+
cur_end = cur_start + cur_length
|
| 212 |
+
cur_edge_range = slice(cur_start, cur_end)
|
| 213 |
+
cur_edge_src, cur_edge_dst = edge_src[cur_edge_range], edge_dst[cur_edge_range]
|
| 214 |
+
|
| 215 |
+
cur_fc = fc_layer[ii] if isinstance(fc_layer, nn.ModuleList) else fc_layer
|
| 216 |
+
cur_out_irreps = cur_fc(edge_attr_groups[ii])
|
| 217 |
+
if edge_weight_is_indexable:
|
| 218 |
+
cur_out_irreps.mul_(edge_weight[cur_edge_range])
|
| 219 |
+
else:
|
| 220 |
+
cur_out_irreps.mul_(edge_weight)
|
| 221 |
+
|
| 222 |
+
summand = tp(node_attr[cur_edge_dst, :], edge_sh[cur_edge_range, :], cur_out_irreps)
|
| 223 |
+
final_out += scatter(summand, cur_edge_src, dim=0, dim_size=out_nodes, reduce="sum")
|
| 224 |
+
div_factors += torch.bincount(cur_edge_src, minlength=out_nodes)
|
| 225 |
+
|
| 226 |
+
cur_start = cur_end
|
| 227 |
+
|
| 228 |
+
del cur_out_irreps, summand
|
| 229 |
+
|
| 230 |
+
if reduce == "mean":
|
| 231 |
+
div_factors = torch.clamp(div_factors, torch.finfo(_dtype).eps)
|
| 232 |
+
final_out = final_out / div_factors[:, None]
|
| 233 |
+
|
| 234 |
+
return final_out
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
class TensorProductConvLayer(torch.nn.Module):
|
| 238 |
+
def __init__(
|
| 239 |
+
self,
|
| 240 |
+
in_irreps,
|
| 241 |
+
sh_irreps,
|
| 242 |
+
out_irreps,
|
| 243 |
+
n_edge_features,
|
| 244 |
+
residual=True,
|
| 245 |
+
batch_norm=True,
|
| 246 |
+
dropout=0.0,
|
| 247 |
+
hidden_features=None,
|
| 248 |
+
faster=False,
|
| 249 |
+
edge_groups=1,
|
| 250 |
+
tp_weights_layers=2,
|
| 251 |
+
activation="relu",
|
| 252 |
+
depthwise=False,
|
| 253 |
+
):
|
| 254 |
+
super(TensorProductConvLayer, self).__init__()
|
| 255 |
+
self.in_irreps = in_irreps
|
| 256 |
+
self.out_irreps = out_irreps
|
| 257 |
+
self.sh_irreps = sh_irreps
|
| 258 |
+
self.residual = residual
|
| 259 |
+
self.edge_groups = edge_groups
|
| 260 |
+
self.out_size = irrep_to_size(out_irreps)
|
| 261 |
+
self.depthwise = depthwise
|
| 262 |
+
if hidden_features is None:
|
| 263 |
+
hidden_features = n_edge_features
|
| 264 |
+
|
| 265 |
+
if depthwise:
|
| 266 |
+
in_irreps = o3.Irreps(in_irreps)
|
| 267 |
+
sh_irreps = o3.Irreps(sh_irreps)
|
| 268 |
+
out_irreps = o3.Irreps(out_irreps)
|
| 269 |
+
|
| 270 |
+
irreps_mid = []
|
| 271 |
+
instructions = []
|
| 272 |
+
for i, (mul, ir_in) in enumerate(in_irreps):
|
| 273 |
+
for j, (_, ir_edge) in enumerate(sh_irreps):
|
| 274 |
+
for ir_out in ir_in * ir_edge:
|
| 275 |
+
if ir_out in out_irreps:
|
| 276 |
+
k = len(irreps_mid)
|
| 277 |
+
irreps_mid.append((mul, ir_out))
|
| 278 |
+
instructions.append((i, j, k, "uvu", True))
|
| 279 |
+
|
| 280 |
+
irreps_mid = o3.Irreps(irreps_mid)
|
| 281 |
+
irreps_mid, p, _ = irreps_mid.sort()
|
| 282 |
+
|
| 283 |
+
instructions = [
|
| 284 |
+
(i_in1, i_in2, p[i_out], mode, train)
|
| 285 |
+
for i_in1, i_in2, i_out, mode, train in instructions
|
| 286 |
+
]
|
| 287 |
+
|
| 288 |
+
self.tp = TensorProduct(
|
| 289 |
+
in_irreps,
|
| 290 |
+
sh_irreps,
|
| 291 |
+
irreps_mid,
|
| 292 |
+
instructions,
|
| 293 |
+
shared_weights=False,
|
| 294 |
+
internal_weights=False,
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
self.linear_2 = Linear(
|
| 298 |
+
irreps_in=irreps_mid.simplify(),
|
| 299 |
+
irreps_out=out_irreps,
|
| 300 |
+
internal_weights=True,
|
| 301 |
+
shared_weights=True,
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
else:
|
| 305 |
+
if faster:
|
| 306 |
+
print("Faster Tensor Product")
|
| 307 |
+
self.tp = FasterTensorProduct(in_irreps, sh_irreps, out_irreps)
|
| 308 |
+
else:
|
| 309 |
+
self.tp = o3.FullyConnectedTensorProduct(
|
| 310 |
+
in_irreps, sh_irreps, out_irreps, shared_weights=False
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
if edge_groups == 1:
|
| 314 |
+
self.fc = FCBlock(
|
| 315 |
+
n_edge_features,
|
| 316 |
+
hidden_features,
|
| 317 |
+
self.tp.weight_numel,
|
| 318 |
+
tp_weights_layers,
|
| 319 |
+
dropout,
|
| 320 |
+
activation,
|
| 321 |
+
)
|
| 322 |
+
else:
|
| 323 |
+
self.fc = [
|
| 324 |
+
FCBlock(
|
| 325 |
+
n_edge_features,
|
| 326 |
+
hidden_features,
|
| 327 |
+
self.tp.weight_numel,
|
| 328 |
+
tp_weights_layers,
|
| 329 |
+
dropout,
|
| 330 |
+
activation,
|
| 331 |
+
)
|
| 332 |
+
for _ in range(edge_groups)
|
| 333 |
+
]
|
| 334 |
+
self.fc = nn.ModuleList(self.fc)
|
| 335 |
+
|
| 336 |
+
self.batch_norm = BatchNorm(out_irreps) if batch_norm else None
|
| 337 |
+
|
| 338 |
+
def forward(
|
| 339 |
+
self,
|
| 340 |
+
node_attr,
|
| 341 |
+
edge_index,
|
| 342 |
+
edge_attr,
|
| 343 |
+
edge_sh,
|
| 344 |
+
out_nodes=None,
|
| 345 |
+
reduce="mean",
|
| 346 |
+
edge_weight=1.0,
|
| 347 |
+
):
|
| 348 |
+
if edge_index.shape[1] == 0 and node_attr.shape[0] == 0:
|
| 349 |
+
raise ValueError("No edges and no nodes")
|
| 350 |
+
|
| 351 |
+
_dtype = node_attr.dtype
|
| 352 |
+
if edge_index.shape[1] == 0:
|
| 353 |
+
out = torch.zeros((node_attr.shape[0], self.out_size), dtype=_dtype, device=node_attr.device)
|
| 354 |
+
else:
|
| 355 |
+
if self.edge_groups == 1:
|
| 356 |
+
out = tp_scatter_simple(
|
| 357 |
+
self.tp,
|
| 358 |
+
self.fc,
|
| 359 |
+
node_attr,
|
| 360 |
+
edge_index,
|
| 361 |
+
edge_attr,
|
| 362 |
+
edge_sh,
|
| 363 |
+
out_nodes,
|
| 364 |
+
reduce,
|
| 365 |
+
edge_weight,
|
| 366 |
+
)
|
| 367 |
+
else:
|
| 368 |
+
out = tp_scatter_multigroup(
|
| 369 |
+
self.tp,
|
| 370 |
+
self.fc,
|
| 371 |
+
node_attr,
|
| 372 |
+
edge_index,
|
| 373 |
+
edge_attr,
|
| 374 |
+
edge_sh,
|
| 375 |
+
out_nodes,
|
| 376 |
+
reduce,
|
| 377 |
+
edge_weight,
|
| 378 |
+
)
|
| 379 |
+
|
| 380 |
+
if self.depthwise:
|
| 381 |
+
out = self.linear_2(out)
|
| 382 |
+
|
| 383 |
+
if self.batch_norm:
|
| 384 |
+
out = self.batch_norm(out)
|
| 385 |
+
|
| 386 |
+
if self.residual:
|
| 387 |
+
padded = F.pad(node_attr, (0, out.shape[-1] - node_attr.shape[-1]))
|
| 388 |
+
out = out + padded
|
| 389 |
+
|
| 390 |
+
out = out.to(_dtype)
|
| 391 |
+
return out
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
class OldTensorProductConvLayer(torch.nn.Module):
|
| 395 |
+
def __init__(
|
| 396 |
+
self,
|
| 397 |
+
in_irreps,
|
| 398 |
+
sh_irreps,
|
| 399 |
+
out_irreps,
|
| 400 |
+
n_edge_features,
|
| 401 |
+
residual=True,
|
| 402 |
+
batch_norm=True,
|
| 403 |
+
dropout=0.0,
|
| 404 |
+
hidden_features=None,
|
| 405 |
+
):
|
| 406 |
+
super(OldTensorProductConvLayer, self).__init__()
|
| 407 |
+
self.in_irreps = in_irreps
|
| 408 |
+
self.out_irreps = out_irreps
|
| 409 |
+
self.sh_irreps = sh_irreps
|
| 410 |
+
self.residual = residual
|
| 411 |
+
if hidden_features is None:
|
| 412 |
+
hidden_features = n_edge_features
|
| 413 |
+
|
| 414 |
+
self.tp = tp = o3.FullyConnectedTensorProduct(
|
| 415 |
+
in_irreps, sh_irreps, out_irreps, shared_weights=False
|
| 416 |
+
)
|
| 417 |
+
|
| 418 |
+
self.fc = nn.Sequential(
|
| 419 |
+
nn.Linear(n_edge_features, hidden_features),
|
| 420 |
+
nn.ReLU(),
|
| 421 |
+
nn.Dropout(dropout),
|
| 422 |
+
nn.Linear(hidden_features, tp.weight_numel),
|
| 423 |
+
)
|
| 424 |
+
self.batch_norm = BatchNorm(out_irreps) if batch_norm else None
|
| 425 |
+
|
| 426 |
+
def forward(
|
| 427 |
+
self, node_attr, edge_index, edge_attr, edge_sh, out_nodes=None, reduce="mean", edge_weight=1.0
|
| 428 |
+
):
|
| 429 |
+
edge_chunk_size = 100_000
|
| 430 |
+
num_edges = edge_attr.shape[0]
|
| 431 |
+
num_chunks = (
|
| 432 |
+
(num_edges // edge_chunk_size)
|
| 433 |
+
if num_edges % edge_chunk_size == 0
|
| 434 |
+
else (num_edges // edge_chunk_size) + 1
|
| 435 |
+
)
|
| 436 |
+
edge_ranges = np.array_split(np.arange(num_edges), num_chunks)
|
| 437 |
+
edge_attr_groups = [edge_attr[cur_range] for cur_range in edge_ranges]
|
| 438 |
+
|
| 439 |
+
out = tp_scatter_multigroup(
|
| 440 |
+
self.tp,
|
| 441 |
+
self.fc,
|
| 442 |
+
node_attr,
|
| 443 |
+
edge_index,
|
| 444 |
+
edge_attr_groups,
|
| 445 |
+
edge_sh,
|
| 446 |
+
out_nodes,
|
| 447 |
+
reduce,
|
| 448 |
+
edge_weight,
|
| 449 |
+
)
|
| 450 |
+
|
| 451 |
+
if self.residual:
|
| 452 |
+
padded = F.pad(node_attr, (0, out.shape[-1] - node_attr.shape[-1]))
|
| 453 |
+
out = out + padded
|
| 454 |
+
|
| 455 |
+
if self.batch_norm:
|
| 456 |
+
out = self.batch_norm(out)
|
| 457 |
+
|
| 458 |
+
out = out.to(node_attr.dtype)
|
| 459 |
+
return out
|
scripts/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
__all__ = []
|
scripts/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (178 Bytes). View file
|
|
|
scripts/__pycache__/evaluate.cpython-311.pyc
ADDED
|
Binary file (332 Bytes). View file
|
|
|
scripts/__pycache__/sample_diffdock.cpython-310.pyc
ADDED
|
Binary file (11 kB). View file
|
|
|
scripts/__pycache__/train_diffdock.cpython-310.pyc
ADDED
|
Binary file (11.5 kB). View file
|
|
|
scripts/__pycache__/train_diffdock.cpython-311.pyc
ADDED
|
Binary file (24.5 kB). View file
|
|
|
scripts/evaluate.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from onescience.utils.diffdock.evaluate import main
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
if __name__ == "__main__":
|
| 5 |
+
main()
|
scripts/infer.sh
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib/:$LD_LIBRARY_PATH"
|
| 4 |
+
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib/python3.11/site-packages/fastpt/torch/lib:$LD_LIBRARY_PATH"
|
| 5 |
+
export LD_LIBRARY_PATH=${ROCM_PATH}/opencl/lib:$LD_LIBRARY_PATH
|
| 6 |
+
|
| 7 |
+
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
| 8 |
+
EXAMPLE_DIR=$(cd "${SCRIPT_DIR}/.." && pwd)
|
| 9 |
+
REPO_ROOT=$(cd "${SCRIPT_DIR}/../../../.." && pwd)
|
| 10 |
+
|
| 11 |
+
# source "${REPO_ROOT}/env.sh"
|
| 12 |
+
|
| 13 |
+
if [[ -n "${ROCM_PATH:-}" && -f "${ROCM_PATH}/cuda/env.sh" ]]; then
|
| 14 |
+
source "${ROCM_PATH}/cuda/env.sh"
|
| 15 |
+
fi
|
| 16 |
+
|
| 17 |
+
export PYTHONPATH="${REPO_ROOT}/src:${REPO_ROOT}:${PYTHONPATH:-}"
|
| 18 |
+
export HIP_VISIBLE_DEVICES="${HIP_VISIBLE_DEVICES:-0}"
|
| 19 |
+
export CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES:-${HIP_VISIBLE_DEVICES}}"
|
| 20 |
+
export OMP_NUM_THREADS="${OMP_NUM_THREADS:-4}"
|
| 21 |
+
export DIFFDOCK_RADIUS_ON_CPU="${RADIUS_ON_CPU:-false}"
|
| 22 |
+
|
| 23 |
+
DIFFDOCK_DATA_ROOT="${DIFFDOCK_DATA_ROOT:-${ONESCIENCE_DATASETS_DIR}/diffdock}"
|
| 24 |
+
export TORCH_HOME="${TORCH_HOME:-${DIFFDOCK_DATA_ROOT}/torch_home}"
|
| 25 |
+
|
| 26 |
+
SCORE_MODEL_DIR="${SCORE_MODEL_DIR:-${DIFFDOCK_DATA_ROOT}/score_model}"
|
| 27 |
+
SCORE_CKPT="${SCORE_CKPT:-best_ema_inference_epoch_model.pt}"
|
| 28 |
+
CONFIDENCE_MODEL_DIR="${CONFIDENCE_MODEL_DIR:-${DIFFDOCK_DATA_ROOT}/confidence_model}"
|
| 29 |
+
CONFIDENCE_CKPT="${CONFIDENCE_CKPT:-best_model_epoch75.pt}"
|
| 30 |
+
ENABLE_CONFIDENCE="${ENABLE_CONFIDENCE:-true}"
|
| 31 |
+
OLD_CONFIDENCE_MODEL="${OLD_CONFIDENCE_MODEL:-true}"
|
| 32 |
+
|
| 33 |
+
OUT_DIR="${OUT_DIR:-${EXAMPLE_DIR}/outputs/scnet_inference}"
|
| 34 |
+
CONFIG_PATH="${CONFIG_PATH:-${OUT_DIR}/inference_config.yml}"
|
| 35 |
+
mkdir -p "${OUT_DIR}"
|
| 36 |
+
OUT_DIR=$(cd "${OUT_DIR}" && pwd)
|
| 37 |
+
CONFIG_DIR=$(dirname "${CONFIG_PATH}")
|
| 38 |
+
CONFIG_NAME=$(basename "${CONFIG_PATH}")
|
| 39 |
+
mkdir -p "${CONFIG_DIR}"
|
| 40 |
+
CONFIG_PATH=$(cd "${CONFIG_DIR}" && pwd)/"${CONFIG_NAME}"
|
| 41 |
+
|
| 42 |
+
DEFAULT_SHARED_CSV="${DIFFDOCK_DATA_ROOT}/datasets/inferdata/protein_ligand_example.csv"
|
| 43 |
+
PROTEIN_LIGAND_CSV="${PROTEIN_LIGAND_CSV:-}"
|
| 44 |
+
if [[ -z "${PROTEIN_LIGAND_CSV}" && -f "${DEFAULT_SHARED_CSV}" ]]; then
|
| 45 |
+
PROTEIN_LIGAND_CSV="${DEFAULT_SHARED_CSV}"
|
| 46 |
+
fi
|
| 47 |
+
COMPLEX_NAME="${COMPLEX_NAME:-6o5u_test}"
|
| 48 |
+
PROTEIN_PATH="${PROTEIN_PATH:-${EXAMPLE_DIR}/data/6o5u_protein_processed.pdb}"
|
| 49 |
+
PROTEIN_SEQUENCE="${PROTEIN_SEQUENCE:-}"
|
| 50 |
+
LIGAND_DESCRIPTION="${LIGAND_DESCRIPTION:-${EXAMPLE_DIR}/data/6o5u_ligand.sdf}"
|
| 51 |
+
|
| 52 |
+
DEVICE="${DEVICE:-auto}"
|
| 53 |
+
SAMPLES_PER_COMPLEX="${SAMPLES_PER_COMPLEX:-10}"
|
| 54 |
+
BATCH_SIZE="${BATCH_SIZE:-10}"
|
| 55 |
+
INFERENCE_STEPS="${INFERENCE_STEPS:-20}"
|
| 56 |
+
ACTUAL_STEPS="${ACTUAL_STEPS:-}"
|
| 57 |
+
NO_RANDOM="${NO_RANDOM:-false}"
|
| 58 |
+
NO_FINAL_STEP_NOISE="${NO_FINAL_STEP_NOISE:-true}"
|
| 59 |
+
CROP_BEYOND="${CROP_BEYOND:-}"
|
| 60 |
+
|
| 61 |
+
yaml_value() {
|
| 62 |
+
if [[ -z "${1:-}" || "${1}" == "null" ]]; then
|
| 63 |
+
printf "null"
|
| 64 |
+
else
|
| 65 |
+
local value
|
| 66 |
+
value=$(printf "%s" "$1" | sed "s/'/''/g")
|
| 67 |
+
printf "'%s'" "$value"
|
| 68 |
+
fi
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
yaml_bool() {
|
| 72 |
+
if [[ "${1,,}" == "true" ]]; then
|
| 73 |
+
printf "true"
|
| 74 |
+
else
|
| 75 |
+
printf "false"
|
| 76 |
+
fi
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
if [[ "${ENABLE_CONFIDENCE,,}" == "true" ]]; then
|
| 80 |
+
CONFIDENCE_MODEL_VALUE=$(yaml_value "${CONFIDENCE_MODEL_DIR}")
|
| 81 |
+
else
|
| 82 |
+
CONFIDENCE_MODEL_VALUE="null"
|
| 83 |
+
fi
|
| 84 |
+
|
| 85 |
+
cat > "${CONFIG_PATH}" <<EOF
|
| 86 |
+
runtime:
|
| 87 |
+
device: $(yaml_value "${DEVICE}")
|
| 88 |
+
loglevel: INFO
|
| 89 |
+
out_dir: $(yaml_value "${OUT_DIR}")
|
| 90 |
+
|
| 91 |
+
model:
|
| 92 |
+
model_dir: $(yaml_value "${SCORE_MODEL_DIR}")
|
| 93 |
+
ckpt: $(yaml_value "${SCORE_CKPT}")
|
| 94 |
+
old_score_model: false
|
| 95 |
+
|
| 96 |
+
confidence:
|
| 97 |
+
confidence_model_dir: ${CONFIDENCE_MODEL_VALUE}
|
| 98 |
+
confidence_ckpt: $(yaml_value "${CONFIDENCE_CKPT}")
|
| 99 |
+
old_confidence_model: $(yaml_bool "${OLD_CONFIDENCE_MODEL}")
|
| 100 |
+
|
| 101 |
+
input:
|
| 102 |
+
protein_ligand_csv: $(yaml_value "${PROTEIN_LIGAND_CSV}")
|
| 103 |
+
complex_name: $(yaml_value "${COMPLEX_NAME}")
|
| 104 |
+
protein_path: $(yaml_value "${PROTEIN_PATH}")
|
| 105 |
+
protein_sequence: $(yaml_value "${PROTEIN_SEQUENCE}")
|
| 106 |
+
ligand_description: $(yaml_value "${LIGAND_DESCRIPTION}")
|
| 107 |
+
lm_embeddings: null
|
| 108 |
+
crop_beyond: $(yaml_value "${CROP_BEYOND}")
|
| 109 |
+
|
| 110 |
+
sampling:
|
| 111 |
+
samples_per_complex: ${SAMPLES_PER_COMPLEX}
|
| 112 |
+
batch_size: ${BATCH_SIZE}
|
| 113 |
+
inference_steps: ${INFERENCE_STEPS}
|
| 114 |
+
actual_steps: $(yaml_value "${ACTUAL_STEPS}")
|
| 115 |
+
sigma_schedule: expbeta
|
| 116 |
+
inf_sched_alpha: 1.0
|
| 117 |
+
inf_sched_beta: 1.0
|
| 118 |
+
no_random: $(yaml_bool "${NO_RANDOM}")
|
| 119 |
+
no_final_step_noise: $(yaml_bool "${NO_FINAL_STEP_NOISE}")
|
| 120 |
+
ode: false
|
| 121 |
+
choose_residue: false
|
| 122 |
+
initial_noise_std_proportion: 1.0
|
| 123 |
+
temp_sampling_tr: 1.0
|
| 124 |
+
temp_psi_tr: 0.0
|
| 125 |
+
temp_sigma_data_tr: 0.5
|
| 126 |
+
temp_sampling_rot: 1.0
|
| 127 |
+
temp_psi_rot: 0.0
|
| 128 |
+
temp_sigma_data_rot: 0.5
|
| 129 |
+
temp_sampling_tor: 1.0
|
| 130 |
+
temp_psi_tor: 0.0
|
| 131 |
+
temp_sigma_data_tor: 0.5
|
| 132 |
+
EOF
|
| 133 |
+
|
| 134 |
+
echo "DiffDock inference config: ${CONFIG_PATH}"
|
| 135 |
+
echo "Output directory: ${OUT_DIR}"
|
| 136 |
+
echo "Score model: ${SCORE_MODEL_DIR}/${SCORE_CKPT}"
|
| 137 |
+
echo "Confidence rerank: ${ENABLE_CONFIDENCE}"
|
| 138 |
+
echo "TORCH_HOME: ${TORCH_HOME}"
|
| 139 |
+
echo "Radius on CPU: ${DIFFDOCK_RADIUS_ON_CPU}"
|
| 140 |
+
if [[ -n "${PROTEIN_LIGAND_CSV}" ]]; then
|
| 141 |
+
echo "Input CSV: ${PROTEIN_LIGAND_CSV}"
|
| 142 |
+
else
|
| 143 |
+
echo "Single input: ${PROTEIN_PATH} + ${LIGAND_DESCRIPTION}"
|
| 144 |
+
fi
|
| 145 |
+
|
| 146 |
+
cd "${REPO_ROOT}"
|
| 147 |
+
python "${SCRIPT_DIR}/sample_diffdock.py" --config "${CONFIG_PATH}"
|
scripts/infer_slurm.sbatch
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
#SBATCH -J diffdock_infer
|
| 3 |
+
#SBATCH -p largedev
|
| 4 |
+
#SBATCH -N 1
|
| 5 |
+
#SBATCH --ntasks-per-node=1
|
| 6 |
+
#SBATCH --gres=dcu:1
|
| 7 |
+
#SBATCH --cpus-per-task=8
|
| 8 |
+
#SBATCH --time=02:00:00
|
| 9 |
+
#SBATCH -o slurm-%j.out
|
| 10 |
+
#SBATCH -e slurm-%j.err
|
| 11 |
+
|
| 12 |
+
set -euo pipefail
|
| 13 |
+
|
| 14 |
+
echo "START TIME: $(date)"
|
| 15 |
+
echo "SLURM_JOB_ID: ${SLURM_JOB_ID:-none}"
|
| 16 |
+
echo "HOSTNAME: $(hostname)"
|
| 17 |
+
|
| 18 |
+
module purge || true
|
| 19 |
+
module load sghpc-mpi-gcc/25.8 || true
|
| 20 |
+
|
| 21 |
+
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
| 22 |
+
mkdir -p "${SCRIPT_DIR}/../outputs"
|
| 23 |
+
|
| 24 |
+
srun bash "${SCRIPT_DIR}/infer.sh"
|
| 25 |
+
|
| 26 |
+
echo "END TIME: $(date)"
|
scripts/sample_diffdock.py
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
DIR = Path(__file__).resolve().parent.parent
|
| 5 |
+
sys.path.insert(0, str(DIR))
|
| 6 |
+
import argparse
|
| 7 |
+
import copy
|
| 8 |
+
import csv
|
| 9 |
+
import os
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from types import SimpleNamespace
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch
|
| 15 |
+
import yaml
|
| 16 |
+
from rdkit.Chem import RemoveAllHs
|
| 17 |
+
from torch_geometric.loader import DataLoader
|
| 18 |
+
|
| 19 |
+
from onescience.datapipes.diffdock.process_mols import write_mol_with_coords
|
| 20 |
+
from onescience.utils.diffdock.diffusion_utils import get_t_schedule
|
| 21 |
+
from onescience.utils.diffdock.inference_utils import InferenceDataset, set_nones
|
| 22 |
+
from onescience.utils.diffdock.logging_utils import configure_logger, get_logger
|
| 23 |
+
from onescience.utils.diffdock.sampling import randomize_position, sampling
|
| 24 |
+
from onescience.utils.diffdock.validation import validate_sampling_entrypoint
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
from models.score_wrapper import load_model_args, load_score_model, model_uses_lm_embeddings
|
| 28 |
+
except ImportError:
|
| 29 |
+
from models.score_wrapper import load_model_args, load_score_model, model_uses_lm_embeddings
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def parse_args():
|
| 33 |
+
parser = argparse.ArgumentParser()
|
| 34 |
+
parser.add_argument("--config", required=True, help="Path to the sampling YAML config.")
|
| 35 |
+
return parser.parse_args()
|
| 36 |
+
|
| 37 |
+
def _resolve_env_vars(obj):
|
| 38 |
+
if isinstance(obj, str):
|
| 39 |
+
return os.path.expandvars(obj)
|
| 40 |
+
if isinstance(obj, dict):
|
| 41 |
+
return {k: _resolve_env_vars(v) for k, v in obj.items()}
|
| 42 |
+
if isinstance(obj, list):
|
| 43 |
+
return [_resolve_env_vars(v) for v in obj]
|
| 44 |
+
return obj
|
| 45 |
+
|
| 46 |
+
# def load_config(config_path):
|
| 47 |
+
# with open(config_path, "r", encoding="utf-8") as handle:
|
| 48 |
+
# return yaml.safe_load(handle) or {}
|
| 49 |
+
def load_config(config_path):
|
| 50 |
+
with open(config_path, "r", encoding="utf-8") as handle:
|
| 51 |
+
return _resolve_env_vars(yaml.safe_load(handle) or {})
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def flatten_config(config):
|
| 55 |
+
flat = {}
|
| 56 |
+
for key, value in config.items():
|
| 57 |
+
if isinstance(value, dict):
|
| 58 |
+
flat.update(value)
|
| 59 |
+
else:
|
| 60 |
+
flat[key] = value
|
| 61 |
+
return flat
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def to_namespace(config):
|
| 65 |
+
return SimpleNamespace(**config)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def resolve_device(device_name):
|
| 69 |
+
if device_name in {None, "auto"}:
|
| 70 |
+
return torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 71 |
+
return torch.device(device_name)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def load_inputs(args):
|
| 75 |
+
if args.protein_ligand_csv is not None:
|
| 76 |
+
with open(args.protein_ligand_csv, "r", encoding="utf-8", newline="") as handle:
|
| 77 |
+
rows = list(csv.DictReader(handle))
|
| 78 |
+
complex_names = set_nones([row.get("complex_name") for row in rows])
|
| 79 |
+
protein_paths = set_nones([row.get("protein_path") for row in rows])
|
| 80 |
+
protein_sequences = set_nones([row.get("protein_sequence") for row in rows])
|
| 81 |
+
ligand_descriptions = set_nones([row.get("ligand_description") for row in rows])
|
| 82 |
+
else:
|
| 83 |
+
complex_names = [args.complex_name or "complex_0"]
|
| 84 |
+
protein_paths = [args.protein_path]
|
| 85 |
+
protein_sequences = [args.protein_sequence]
|
| 86 |
+
ligand_descriptions = [args.ligand_description]
|
| 87 |
+
|
| 88 |
+
complex_names = [name if name is not None else f"complex_{idx}" for idx, name in enumerate(complex_names)]
|
| 89 |
+
return complex_names, protein_paths, protein_sequences, ligand_descriptions
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def ensure_output_dirs(out_dir, complex_names):
|
| 93 |
+
os.makedirs(out_dir, exist_ok=True)
|
| 94 |
+
for name in complex_names:
|
| 95 |
+
os.makedirs(os.path.join(out_dir, name), exist_ok=True)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def get_ligand_mol(complex_graph):
|
| 99 |
+
mol = complex_graph.mol
|
| 100 |
+
return mol[0] if isinstance(mol, (list, tuple)) else mol
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def resolve_lm_embeddings_flag(args, model_args):
|
| 104 |
+
lm_embeddings = getattr(args, "lm_embeddings", None)
|
| 105 |
+
if lm_embeddings is None:
|
| 106 |
+
return model_uses_lm_embeddings(model_args)
|
| 107 |
+
return lm_embeddings
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def build_inference_dataset(
|
| 111 |
+
args,
|
| 112 |
+
model_args,
|
| 113 |
+
*,
|
| 114 |
+
complex_names,
|
| 115 |
+
protein_paths,
|
| 116 |
+
protein_sequences,
|
| 117 |
+
ligand_descriptions,
|
| 118 |
+
lm_embeddings,
|
| 119 |
+
):
|
| 120 |
+
return InferenceDataset(
|
| 121 |
+
out_dir=args.out_dir,
|
| 122 |
+
complex_names=complex_names,
|
| 123 |
+
protein_files=protein_paths,
|
| 124 |
+
ligand_descriptions=ligand_descriptions,
|
| 125 |
+
protein_sequences=protein_sequences,
|
| 126 |
+
lm_embeddings=lm_embeddings,
|
| 127 |
+
receptor_radius=model_args.receptor_radius,
|
| 128 |
+
remove_hs=model_args.remove_hs,
|
| 129 |
+
c_alpha_max_neighbors=model_args.c_alpha_max_neighbors,
|
| 130 |
+
all_atoms=model_args.all_atoms,
|
| 131 |
+
atom_radius=model_args.atom_radius,
|
| 132 |
+
atom_max_neighbors=model_args.atom_max_neighbors,
|
| 133 |
+
knn_only_graph=not getattr(model_args, "not_knn_only_graph", False),
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def inference_graph_signature(model_args, lm_embeddings):
|
| 138 |
+
return (
|
| 139 |
+
getattr(model_args, "receptor_radius", None),
|
| 140 |
+
getattr(model_args, "remove_hs", None),
|
| 141 |
+
getattr(model_args, "c_alpha_max_neighbors", None),
|
| 142 |
+
getattr(model_args, "all_atoms", None),
|
| 143 |
+
getattr(model_args, "atom_radius", None),
|
| 144 |
+
getattr(model_args, "atom_max_neighbors", None),
|
| 145 |
+
not getattr(model_args, "not_knn_only_graph", False),
|
| 146 |
+
bool(lm_embeddings),
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def extract_confidence_scores(confidence, confidence_model_args):
|
| 151 |
+
confidence_scores = confidence
|
| 152 |
+
if isinstance(getattr(confidence_model_args, "rmsd_classification_cutoff", None), list):
|
| 153 |
+
confidence_scores = confidence_scores[:, 0]
|
| 154 |
+
return np.asarray(confidence_scores.detach().cpu().numpy()).reshape(-1)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def load_optional_confidence_model(args, device, logger):
|
| 158 |
+
confidence_model_dir = getattr(args, "confidence_model_dir", None)
|
| 159 |
+
if confidence_model_dir is None:
|
| 160 |
+
return None, None
|
| 161 |
+
|
| 162 |
+
confidence_model_dir = Path(confidence_model_dir)
|
| 163 |
+
confidence_checkpoint_path = confidence_model_dir / args.confidence_ckpt
|
| 164 |
+
if not confidence_checkpoint_path.exists():
|
| 165 |
+
raise FileNotFoundError(
|
| 166 |
+
f"Confidence checkpoint not found: {confidence_checkpoint_path}. "
|
| 167 |
+
"This example does not auto-download models."
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
confidence_model_args_preview = load_model_args(confidence_model_dir)
|
| 171 |
+
validate_sampling_entrypoint(
|
| 172 |
+
confidence_model_args_preview,
|
| 173 |
+
context="DiffDock sampling confidence-model checkpoint",
|
| 174 |
+
include_confidence=True,
|
| 175 |
+
confidence_mode=True,
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
confidence_model, confidence_model_args, _ = load_score_model(
|
| 179 |
+
model_dir=confidence_model_dir,
|
| 180 |
+
ckpt=args.confidence_ckpt,
|
| 181 |
+
device=device,
|
| 182 |
+
no_parallel=True,
|
| 183 |
+
confidence_mode=True,
|
| 184 |
+
old=getattr(args, "old_confidence_model", False),
|
| 185 |
+
)
|
| 186 |
+
if hasattr(args, "crop_beyond"):
|
| 187 |
+
confidence_model_args.crop_beyond = args.crop_beyond
|
| 188 |
+
logger.info("Loaded optional confidence model from %s", confidence_model_dir)
|
| 189 |
+
return confidence_model, confidence_model_args
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def main():
|
| 193 |
+
parsed = parse_args()
|
| 194 |
+
raw_config = load_config(parsed.config)
|
| 195 |
+
args = to_namespace(flatten_config(raw_config))
|
| 196 |
+
device = resolve_device(getattr(args, "device", "auto"))
|
| 197 |
+
validate_sampling_entrypoint(
|
| 198 |
+
args,
|
| 199 |
+
include_confidence=(
|
| 200 |
+
getattr(args, "confidence_model_dir", None) is not None
|
| 201 |
+
or getattr(args, "old_confidence_model", False)
|
| 202 |
+
),
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
configure_logger(getattr(args, "loglevel", "INFO"))
|
| 206 |
+
logger = get_logger()
|
| 207 |
+
|
| 208 |
+
model_dir = Path(args.model_dir)
|
| 209 |
+
checkpoint_path = model_dir / args.ckpt
|
| 210 |
+
if not checkpoint_path.exists():
|
| 211 |
+
raise FileNotFoundError(
|
| 212 |
+
f"Checkpoint not found: {checkpoint_path}. This example does not auto-download models."
|
| 213 |
+
)
|
| 214 |
+
score_model_args_preview = load_model_args(model_dir)
|
| 215 |
+
validate_sampling_entrypoint(
|
| 216 |
+
score_model_args_preview,
|
| 217 |
+
context="DiffDock sampling score-model checkpoint",
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
model, score_model_args, t_to_sigma = load_score_model(
|
| 221 |
+
model_dir=model_dir,
|
| 222 |
+
ckpt=args.ckpt,
|
| 223 |
+
device=device,
|
| 224 |
+
no_parallel=True,
|
| 225 |
+
old=getattr(args, "old_score_model", False),
|
| 226 |
+
)
|
| 227 |
+
if hasattr(args, "crop_beyond"):
|
| 228 |
+
score_model_args.crop_beyond = args.crop_beyond
|
| 229 |
+
logger.info("DiffDock sampling will run on %s", device)
|
| 230 |
+
|
| 231 |
+
confidence_model, confidence_model_args = load_optional_confidence_model(args, device, logger)
|
| 232 |
+
|
| 233 |
+
complex_names, protein_paths, protein_sequences, ligand_descriptions = load_inputs(args)
|
| 234 |
+
ensure_output_dirs(args.out_dir, complex_names)
|
| 235 |
+
|
| 236 |
+
score_lm_embeddings = resolve_lm_embeddings_flag(args, score_model_args)
|
| 237 |
+
test_dataset = build_inference_dataset(
|
| 238 |
+
args,
|
| 239 |
+
score_model_args,
|
| 240 |
+
complex_names=complex_names,
|
| 241 |
+
protein_paths=protein_paths,
|
| 242 |
+
protein_sequences=protein_sequences,
|
| 243 |
+
ligand_descriptions=ligand_descriptions,
|
| 244 |
+
lm_embeddings=score_lm_embeddings,
|
| 245 |
+
)
|
| 246 |
+
test_loader = DataLoader(dataset=test_dataset, batch_size=1, shuffle=False)
|
| 247 |
+
|
| 248 |
+
confidence_loader = None
|
| 249 |
+
if confidence_model is not None:
|
| 250 |
+
confidence_lm_embeddings = resolve_lm_embeddings_flag(args, confidence_model_args)
|
| 251 |
+
confidence_needs_independent_graph = (
|
| 252 |
+
inference_graph_signature(score_model_args, score_lm_embeddings)
|
| 253 |
+
!= inference_graph_signature(confidence_model_args, confidence_lm_embeddings)
|
| 254 |
+
)
|
| 255 |
+
if confidence_needs_independent_graph:
|
| 256 |
+
logger.info(
|
| 257 |
+
"Confidence rerank requires independent inference graphs; building a separate confidence dataset."
|
| 258 |
+
)
|
| 259 |
+
confidence_dataset = build_inference_dataset(
|
| 260 |
+
args,
|
| 261 |
+
confidence_model_args,
|
| 262 |
+
complex_names=complex_names,
|
| 263 |
+
protein_paths=protein_paths,
|
| 264 |
+
protein_sequences=protein_sequences,
|
| 265 |
+
ligand_descriptions=ligand_descriptions,
|
| 266 |
+
lm_embeddings=confidence_lm_embeddings,
|
| 267 |
+
)
|
| 268 |
+
confidence_loader = DataLoader(dataset=confidence_dataset, batch_size=1, shuffle=False)
|
| 269 |
+
else:
|
| 270 |
+
logger.info("Confidence rerank will reuse the score-model inference graphs.")
|
| 271 |
+
|
| 272 |
+
tr_schedule = get_t_schedule(
|
| 273 |
+
sigma_schedule=args.sigma_schedule,
|
| 274 |
+
inference_steps=args.inference_steps,
|
| 275 |
+
inf_sched_alpha=args.inf_sched_alpha,
|
| 276 |
+
inf_sched_beta=args.inf_sched_beta,
|
| 277 |
+
)
|
| 278 |
+
|
| 279 |
+
failures = 0
|
| 280 |
+
skipped = 0
|
| 281 |
+
num_samples = args.samples_per_complex
|
| 282 |
+
test_ds_size = len(test_dataset)
|
| 283 |
+
logger.info("Size of test dataset: %s", test_ds_size)
|
| 284 |
+
|
| 285 |
+
if confidence_loader is None:
|
| 286 |
+
loader_iter = ((orig_complex_graph, None) for orig_complex_graph in test_loader)
|
| 287 |
+
else:
|
| 288 |
+
loader_iter = zip(test_loader, confidence_loader)
|
| 289 |
+
|
| 290 |
+
for idx, (orig_complex_graph, confidence_orig_complex_graph) in enumerate(loader_iter):
|
| 291 |
+
if not orig_complex_graph.success[0]:
|
| 292 |
+
skipped += 1
|
| 293 |
+
logger.warning(
|
| 294 |
+
"Skipping %s because preprocessing failed.",
|
| 295 |
+
test_dataset.complex_names[idx],
|
| 296 |
+
)
|
| 297 |
+
continue
|
| 298 |
+
if confidence_orig_complex_graph is not None and not confidence_orig_complex_graph.success[0]:
|
| 299 |
+
skipped += 1
|
| 300 |
+
logger.warning(
|
| 301 |
+
"Skipping %s because confidence preprocessing failed.",
|
| 302 |
+
test_dataset.complex_names[idx],
|
| 303 |
+
)
|
| 304 |
+
continue
|
| 305 |
+
|
| 306 |
+
try:
|
| 307 |
+
data_list = [copy.deepcopy(orig_complex_graph) for _ in range(num_samples)]
|
| 308 |
+
confidence_data_list = None
|
| 309 |
+
if confidence_orig_complex_graph is not None:
|
| 310 |
+
confidence_data_list = [copy.deepcopy(confidence_orig_complex_graph) for _ in range(num_samples)]
|
| 311 |
+
randomize_position(
|
| 312 |
+
data_list,
|
| 313 |
+
score_model_args.no_torsion,
|
| 314 |
+
args.no_random,
|
| 315 |
+
score_model_args.tr_sigma_max,
|
| 316 |
+
initial_noise_std_proportion=args.initial_noise_std_proportion,
|
| 317 |
+
choose_residue=args.choose_residue,
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
ligand = get_ligand_mol(orig_complex_graph)
|
| 321 |
+
data_list, confidence = sampling(
|
| 322 |
+
data_list=data_list,
|
| 323 |
+
model=model,
|
| 324 |
+
inference_steps=args.actual_steps if args.actual_steps is not None else args.inference_steps,
|
| 325 |
+
tr_schedule=tr_schedule,
|
| 326 |
+
rot_schedule=tr_schedule,
|
| 327 |
+
tor_schedule=tr_schedule,
|
| 328 |
+
device=device,
|
| 329 |
+
t_to_sigma=t_to_sigma,
|
| 330 |
+
model_args=score_model_args,
|
| 331 |
+
no_random=args.no_random,
|
| 332 |
+
ode=args.ode,
|
| 333 |
+
confidence_model=confidence_model,
|
| 334 |
+
confidence_data_list=confidence_data_list,
|
| 335 |
+
confidence_model_args=confidence_model_args,
|
| 336 |
+
batch_size=args.batch_size,
|
| 337 |
+
no_final_step_noise=args.no_final_step_noise,
|
| 338 |
+
temp_sampling=[
|
| 339 |
+
args.temp_sampling_tr,
|
| 340 |
+
args.temp_sampling_rot,
|
| 341 |
+
args.temp_sampling_tor,
|
| 342 |
+
],
|
| 343 |
+
temp_psi=[
|
| 344 |
+
args.temp_psi_tr,
|
| 345 |
+
args.temp_psi_rot,
|
| 346 |
+
args.temp_psi_tor,
|
| 347 |
+
],
|
| 348 |
+
temp_sigma_data=[
|
| 349 |
+
args.temp_sigma_data_tr,
|
| 350 |
+
args.temp_sigma_data_rot,
|
| 351 |
+
args.temp_sigma_data_tor,
|
| 352 |
+
],
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
ligand_positions = np.asarray(
|
| 356 |
+
[
|
| 357 |
+
complex_graph["ligand"].pos.cpu().numpy() + orig_complex_graph.original_center.cpu().numpy()
|
| 358 |
+
for complex_graph in data_list
|
| 359 |
+
]
|
| 360 |
+
)
|
| 361 |
+
|
| 362 |
+
rerank_order = np.arange(len(ligand_positions))
|
| 363 |
+
confidence_scores = None
|
| 364 |
+
if confidence is not None:
|
| 365 |
+
confidence_scores = extract_confidence_scores(confidence, confidence_model_args)
|
| 366 |
+
confidence_scores = np.nan_to_num(confidence_scores, nan=-1e-6)
|
| 367 |
+
rerank_order = np.argsort(confidence_scores)[::-1]
|
| 368 |
+
logger.info(
|
| 369 |
+
"Applied confidence rerank for %s. Ranked confidences: %s",
|
| 370 |
+
complex_names[idx],
|
| 371 |
+
np.array2string(confidence_scores[rerank_order], precision=4),
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
+
write_dir = os.path.join(args.out_dir, complex_names[idx])
|
| 375 |
+
for rank, sample_idx in enumerate(rerank_order, start=1):
|
| 376 |
+
pos = ligand_positions[sample_idx]
|
| 377 |
+
mol_pred = copy.deepcopy(ligand)
|
| 378 |
+
if score_model_args.remove_hs:
|
| 379 |
+
mol_pred = RemoveAllHs(mol_pred)
|
| 380 |
+
filename = f"rank{rank}.sdf"
|
| 381 |
+
if confidence_scores is not None:
|
| 382 |
+
filename = f"rank{rank}_conf{confidence_scores[sample_idx]:.4f}.sdf"
|
| 383 |
+
write_mol_with_coords(mol_pred, pos, os.path.join(write_dir, filename))
|
| 384 |
+
|
| 385 |
+
except Exception as exc:
|
| 386 |
+
logger.warning("Failed on %s with error: %s", complex_names[idx], exc)
|
| 387 |
+
failures += 1
|
| 388 |
+
|
| 389 |
+
logger.info("Failed for %s / %s complexes.", failures, test_ds_size)
|
| 390 |
+
logger.info("Skipped %s / %s complexes.", skipped, test_ds_size)
|
| 391 |
+
logger.info("Results saved in %s", args.out_dir)
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
if __name__ == "__main__":
|
| 395 |
+
main()
|
scripts/train.sh
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib/:$LD_LIBRARY_PATH"
|
| 4 |
+
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib/python3.11/site-packages/fastpt/torch/lib:$LD_LIBRARY_PATH"
|
| 5 |
+
export LD_LIBRARY_PATH=${ROCM_PATH}/opencl/lib:$LD_LIBRARY_PATH
|
| 6 |
+
|
| 7 |
+
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
| 8 |
+
EXAMPLE_DIR=$(cd "${SCRIPT_DIR}/.." && pwd)
|
| 9 |
+
REPO_ROOT=$(cd "${SCRIPT_DIR}/../../../.." && pwd)
|
| 10 |
+
|
| 11 |
+
#source "${REPO_ROOT}/env.sh"
|
| 12 |
+
|
| 13 |
+
if [[ -n "${ROCM_PATH:-}" && -f "${ROCM_PATH}/cuda/env.sh" ]]; then
|
| 14 |
+
source "${ROCM_PATH}/cuda/env.sh"
|
| 15 |
+
fi
|
| 16 |
+
|
| 17 |
+
export PYTHONPATH="${REPO_ROOT}/src:${REPO_ROOT}:${PYTHONPATH:-}"
|
| 18 |
+
export HIP_VISIBLE_DEVICES="${HIP_VISIBLE_DEVICES:-0}"
|
| 19 |
+
export CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES:-${HIP_VISIBLE_DEVICES}}"
|
| 20 |
+
export OMP_NUM_THREADS="${OMP_NUM_THREADS:-4}"
|
| 21 |
+
|
| 22 |
+
DIFFDOCK_DATA_ROOT="${DIFFDOCK_DATA_ROOT:-${ONESCIENCE_DATASETS_DIR}/diffdock}"
|
| 23 |
+
DIFFDOCK_DATASETS_DIR="${DIFFDOCK_DATASETS_DIR:-${DIFFDOCK_DATA_ROOT}/datasets}"
|
| 24 |
+
export TORCH_HOME="${TORCH_HOME:-${DIFFDOCK_DATA_ROOT}/torch_home}"
|
| 25 |
+
|
| 26 |
+
DATASET="${DATASET:-pdbbind}"
|
| 27 |
+
RUN_NAME="${RUN_NAME:-diffdock_${DATASET}_scnet}"
|
| 28 |
+
LOG_DIR="${LOG_DIR:-${EXAMPLE_DIR}/outputs/train}"
|
| 29 |
+
CACHE_PATH="${CACHE_PATH:-${EXAMPLE_DIR}/outputs/cache}"
|
| 30 |
+
CONFIG_PATH="${CONFIG_PATH:-${LOG_DIR}/${RUN_NAME}_train_config.yml}"
|
| 31 |
+
mkdir -p "${LOG_DIR}" "${CACHE_PATH}"
|
| 32 |
+
LOG_DIR=$(cd "${LOG_DIR}" && pwd)
|
| 33 |
+
CACHE_PATH=$(cd "${CACHE_PATH}" && pwd)
|
| 34 |
+
CONFIG_DIR=$(dirname "${CONFIG_PATH}")
|
| 35 |
+
CONFIG_NAME=$(basename "${CONFIG_PATH}")
|
| 36 |
+
mkdir -p "${CONFIG_DIR}"
|
| 37 |
+
CONFIG_PATH=$(cd "${CONFIG_DIR}" && pwd)/"${CONFIG_NAME}"
|
| 38 |
+
|
| 39 |
+
PDBBIND_DIR="${PDBBIND_DIR:-${DIFFDOCK_DATASETS_DIR}/PDBBind_processed}"
|
| 40 |
+
MOAD_DIR="${MOAD_DIR:-${DIFFDOCK_DATASETS_DIR}/BindingMOAD_2020_processed}"
|
| 41 |
+
SPLIT_TRAIN="${SPLIT_TRAIN:-${DIFFDOCK_DATASETS_DIR}/splits/timesplit_no_lig_overlap_train}"
|
| 42 |
+
SPLIT_VAL="${SPLIT_VAL:-${DIFFDOCK_DATASETS_DIR}/splits/timesplit_no_lig_overlap_val}"
|
| 43 |
+
|
| 44 |
+
DEVICE="${DEVICE:-auto}"
|
| 45 |
+
if [[ "$DEVICE" == "auto" ]]; then
|
| 46 |
+
if python -c "import torch; print(torch.cuda.is_available())" | grep -q True; then
|
| 47 |
+
DEVICE="cuda"
|
| 48 |
+
else
|
| 49 |
+
DEVICE="cpu"
|
| 50 |
+
fi
|
| 51 |
+
fi
|
| 52 |
+
echo "****** DEVICE ******: $DEVICE"
|
| 53 |
+
|
| 54 |
+
SEED="${SEED:-0}"
|
| 55 |
+
BATCH_SIZE="${BATCH_SIZE:-4}"
|
| 56 |
+
N_EPOCHS="${N_EPOCHS:-10}"
|
| 57 |
+
LR="${LR:-0.001}"
|
| 58 |
+
NUM_WORKERS="${NUM_WORKERS:-1}"
|
| 59 |
+
NUM_DATALOADER_WORKERS="${NUM_DATALOADER_WORKERS:-0}"
|
| 60 |
+
LIMIT_COMPLEXES="${LIMIT_COMPLEXES:-null}"
|
| 61 |
+
SAVE_MODEL_FREQ="${SAVE_MODEL_FREQ:-null}"
|
| 62 |
+
VAL_INFERENCE_FREQ="${VAL_INFERENCE_FREQ:-null}"
|
| 63 |
+
TRAIN_INFERENCE_FREQ="${TRAIN_INFERENCE_FREQ:-null}"
|
| 64 |
+
WANDB="${WANDB:-false}"
|
| 65 |
+
NO_TORSION="${NO_TORSION:-false}"
|
| 66 |
+
NO_BATCH_NORM="${NO_BATCH_NORM:-true}"
|
| 67 |
+
|
| 68 |
+
PDBBIND_ESM_EMBEDDINGS_PATH="${PDBBIND_ESM_EMBEDDINGS_PATH:-}"
|
| 69 |
+
MOAD_ESM_EMBEDDINGS_PATH="${MOAD_ESM_EMBEDDINGS_PATH:-}"
|
| 70 |
+
MOAD_ESM_EMBEDDINGS_SEQUENCES_PATH="${MOAD_ESM_EMBEDDINGS_SEQUENCES_PATH:-}"
|
| 71 |
+
ESM_EMBEDDINGS_MODEL="${ESM_EMBEDDINGS_MODEL:-}"
|
| 72 |
+
|
| 73 |
+
yaml_value() {
|
| 74 |
+
if [[ -z "${1:-}" || "${1}" == "null" ]]; then
|
| 75 |
+
printf "null"
|
| 76 |
+
else
|
| 77 |
+
local value
|
| 78 |
+
value=$(printf "%s" "$1" | sed "s/'/''/g")
|
| 79 |
+
printf "'%s'" "$value"
|
| 80 |
+
fi
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
yaml_bool() {
|
| 84 |
+
if [[ "${1,,}" == "true" ]]; then
|
| 85 |
+
printf "true"
|
| 86 |
+
else
|
| 87 |
+
printf "false"
|
| 88 |
+
fi
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
cat > "${CONFIG_PATH}" <<EOF
|
| 92 |
+
runtime:
|
| 93 |
+
run_name: $(yaml_value "${RUN_NAME}")
|
| 94 |
+
log_dir: $(yaml_value "${LOG_DIR}")
|
| 95 |
+
device: $(yaml_value "${DEVICE}")
|
| 96 |
+
seed: ${SEED}
|
| 97 |
+
cudnn_benchmark: false
|
| 98 |
+
wandb: $(yaml_bool "${WANDB}")
|
| 99 |
+
project: diffdock
|
| 100 |
+
|
| 101 |
+
data:
|
| 102 |
+
dataset: $(yaml_value "${DATASET}")
|
| 103 |
+
cache_path: $(yaml_value "${CACHE_PATH}")
|
| 104 |
+
pdbbind_dir: $(yaml_value "${PDBBIND_DIR}")
|
| 105 |
+
moad_dir: $(yaml_value "${MOAD_DIR}")
|
| 106 |
+
split_train: $(yaml_value "${SPLIT_TRAIN}")
|
| 107 |
+
split_val: $(yaml_value "${SPLIT_VAL}")
|
| 108 |
+
protein_file: protein_processed
|
| 109 |
+
limit_complexes: ${LIMIT_COMPLEXES}
|
| 110 |
+
num_conformers: 1
|
| 111 |
+
num_workers: ${NUM_WORKERS}
|
| 112 |
+
num_dataloader_workers: ${NUM_DATALOADER_WORKERS}
|
| 113 |
+
batch_size: ${BATCH_SIZE}
|
| 114 |
+
pin_memory: false
|
| 115 |
+
dataloader_drop_last: false
|
| 116 |
+
remove_hs: true
|
| 117 |
+
receptor_radius: 30
|
| 118 |
+
c_alpha_max_neighbors: 10
|
| 119 |
+
atom_radius: 5
|
| 120 |
+
atom_max_neighbors: 8
|
| 121 |
+
chain_cutoff: null
|
| 122 |
+
max_lig_size: null
|
| 123 |
+
matching_popsize: 20
|
| 124 |
+
matching_maxiter: 20
|
| 125 |
+
matching_tries: 1
|
| 126 |
+
not_knn_only_graph: false
|
| 127 |
+
include_miscellaneous_atoms: false
|
| 128 |
+
all_atoms: false
|
| 129 |
+
triple_training: false
|
| 130 |
+
combined_training: false
|
| 131 |
+
double_val: false
|
| 132 |
+
train_multiplicity: 1
|
| 133 |
+
val_multiplicity: 1
|
| 134 |
+
max_receptor_size: null
|
| 135 |
+
remove_promiscuous_targets: null
|
| 136 |
+
min_ligand_size: 0
|
| 137 |
+
unroll_clusters: false
|
| 138 |
+
enforce_timesplit: false
|
| 139 |
+
crop_beyond: 20
|
| 140 |
+
moad_esm_embeddings_path: $(yaml_value "${MOAD_ESM_EMBEDDINGS_PATH}")
|
| 141 |
+
pdbbind_esm_embeddings_path: $(yaml_value "${PDBBIND_ESM_EMBEDDINGS_PATH}")
|
| 142 |
+
moad_esm_embeddings_sequences_path: $(yaml_value "${MOAD_ESM_EMBEDDINGS_SEQUENCES_PATH}")
|
| 143 |
+
esm_embeddings_model: $(yaml_value "${ESM_EMBEDDINGS_MODEL}")
|
| 144 |
+
|
| 145 |
+
diffusion:
|
| 146 |
+
no_torsion: $(yaml_bool "${NO_TORSION}")
|
| 147 |
+
tr_sigma_min: 0.1
|
| 148 |
+
tr_sigma_max: 30.0
|
| 149 |
+
rot_sigma_min: 0.1
|
| 150 |
+
rot_sigma_max: 1.65
|
| 151 |
+
tor_sigma_min: 0.0314
|
| 152 |
+
tor_sigma_max: 3.14
|
| 153 |
+
sampling_alpha: 1.0
|
| 154 |
+
sampling_beta: 1.0
|
| 155 |
+
tr_weight: 0.33
|
| 156 |
+
rot_weight: 0.33
|
| 157 |
+
tor_weight: 0.33
|
| 158 |
+
backbone_loss_weight: 0.0
|
| 159 |
+
sidechain_loss_weight: 0.0
|
| 160 |
+
|
| 161 |
+
model:
|
| 162 |
+
num_conv_layers: 2
|
| 163 |
+
max_radius: 5.0
|
| 164 |
+
scale_by_sigma: true
|
| 165 |
+
norm_by_sigma: false
|
| 166 |
+
ns: 16
|
| 167 |
+
nv: 4
|
| 168 |
+
distance_embed_dim: 32
|
| 169 |
+
cross_distance_embed_dim: 32
|
| 170 |
+
no_batch_norm: $(yaml_bool "${NO_BATCH_NORM}")
|
| 171 |
+
use_second_order_repr: false
|
| 172 |
+
cross_max_distance: 80
|
| 173 |
+
dynamic_max_cross: false
|
| 174 |
+
dropout: 0.0
|
| 175 |
+
smooth_edges: false
|
| 176 |
+
odd_parity: false
|
| 177 |
+
embedding_type: sinusoidal
|
| 178 |
+
sigma_embed_dim: 32
|
| 179 |
+
embedding_scale: 1000
|
| 180 |
+
no_aminoacid_identities: false
|
| 181 |
+
sh_lmax: 2
|
| 182 |
+
no_differentiate_convolutions: false
|
| 183 |
+
tp_weights_layers: 2
|
| 184 |
+
num_prot_emb_layers: 0
|
| 185 |
+
reduce_pseudoscalars: false
|
| 186 |
+
embed_also_ligand: true
|
| 187 |
+
depthwise_convolution: false
|
| 188 |
+
use_old_atom_encoder: false
|
| 189 |
+
|
| 190 |
+
optimization:
|
| 191 |
+
n_epochs: ${N_EPOCHS}
|
| 192 |
+
lr: ${LR}
|
| 193 |
+
w_decay: 0.0
|
| 194 |
+
scheduler: null
|
| 195 |
+
scheduler_patience: 20
|
| 196 |
+
lr_start_factor: 0.001
|
| 197 |
+
warmup_dur: 4
|
| 198 |
+
use_ema: true
|
| 199 |
+
ema_rate: 0.999
|
| 200 |
+
restart_dir: null
|
| 201 |
+
restart_ckpt: last_model
|
| 202 |
+
restart_lr: null
|
| 203 |
+
pretrain_dir: null
|
| 204 |
+
pretrain_ckpt: null
|
| 205 |
+
save_model_freq: ${SAVE_MODEL_FREQ}
|
| 206 |
+
|
| 207 |
+
validation:
|
| 208 |
+
test_sigma_intervals: false
|
| 209 |
+
inference_samples: 1
|
| 210 |
+
val_inference_freq: ${VAL_INFERENCE_FREQ}
|
| 211 |
+
train_inference_freq: ${TRAIN_INFERENCE_FREQ}
|
| 212 |
+
inference_steps: 20
|
| 213 |
+
num_inference_complexes: 20
|
| 214 |
+
inference_earlystop_metric: valinf_min_rmsds_lt2
|
| 215 |
+
inference_secondary_metric: null
|
| 216 |
+
inference_earlystop_goal: max
|
| 217 |
+
EOF
|
| 218 |
+
|
| 219 |
+
echo "DiffDock training config: ${CONFIG_PATH}"
|
| 220 |
+
echo "Dataset mode: ${DATASET}"
|
| 221 |
+
echo "PDBBind dir: ${PDBBIND_DIR}"
|
| 222 |
+
echo "MOAD dir: ${MOAD_DIR}"
|
| 223 |
+
echo "Train split: ${SPLIT_TRAIN}"
|
| 224 |
+
echo "Val split: ${SPLIT_VAL}"
|
| 225 |
+
echo "Cache path: ${CACHE_PATH}"
|
| 226 |
+
echo "Run output: ${LOG_DIR}/${RUN_NAME}"
|
| 227 |
+
echo "TORCH_HOME: ${TORCH_HOME}"
|
| 228 |
+
echo "Batch normalization disabled: ${NO_BATCH_NORM}"
|
| 229 |
+
|
| 230 |
+
cd "${REPO_ROOT}"
|
| 231 |
+
python "${SCRIPT_DIR}/train_diffdock.py" --config "${CONFIG_PATH}"
|
scripts/train_demo.sh
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib/:$LD_LIBRARY_PATH"
|
| 4 |
+
export LD_LIBRARY_PATH="$CONDA_PREFIX/lib/python3.11/site-packages/fastpt/torch/lib:$LD_LIBRARY_PATH"
|
| 5 |
+
export LD_LIBRARY_PATH=${ROCM_PATH}/opencl/lib:$LD_LIBRARY_PATH
|
| 6 |
+
|
| 7 |
+
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
| 8 |
+
EXAMPLE_DIR=$(cd "${SCRIPT_DIR}/.." && pwd)
|
| 9 |
+
REPO_ROOT=$(cd "${SCRIPT_DIR}/../../../.." && pwd)
|
| 10 |
+
# echo "*** REPO_ROOT : ${REPO_ROOT}"
|
| 11 |
+
# source "${REPO_ROOT}/env.sh"
|
| 12 |
+
|
| 13 |
+
if [[ -n "${ROCM_PATH:-}" && -f "${ROCM_PATH}/cuda/env.sh" ]]; then
|
| 14 |
+
source "${ROCM_PATH}/cuda/env.sh"
|
| 15 |
+
fi
|
| 16 |
+
|
| 17 |
+
export PYTHONPATH="${REPO_ROOT}/src:${REPO_ROOT}:${PYTHONPATH:-}"
|
| 18 |
+
export HIP_VISIBLE_DEVICES="${HIP_VISIBLE_DEVICES:-0}"
|
| 19 |
+
export CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES:-${HIP_VISIBLE_DEVICES}}"
|
| 20 |
+
export OMP_NUM_THREADS="${OMP_NUM_THREADS:-4}"
|
| 21 |
+
|
| 22 |
+
DIFFDOCK_DATA_ROOT="${DIFFDOCK_DATA_ROOT:-${ONESCIENCE_DATASETS_DIR}/diffdock}"
|
| 23 |
+
DIFFDOCK_DATASETS_DIR="${DIFFDOCK_DATASETS_DIR:-${DIFFDOCK_DATA_ROOT}/datasets}"
|
| 24 |
+
export TORCH_HOME="${TORCH_HOME:-${DIFFDOCK_DATA_ROOT}/torch_home}"
|
| 25 |
+
|
| 26 |
+
RUN_NAME="${RUN_NAME:-diffdock_pdbbind_smoke100_val20_cpu}"
|
| 27 |
+
LOG_DIR="${LOG_DIR:-${EXAMPLE_DIR}/outputs/train}"
|
| 28 |
+
CACHE_PATH="${CACHE_PATH:-${EXAMPLE_DIR}/outputs/cache_pdbbind_smoke100_val20}"
|
| 29 |
+
CONFIG_PATH="${CONFIG_PATH:-${LOG_DIR}/${RUN_NAME}_train_config.yml}"
|
| 30 |
+
|
| 31 |
+
DEVICE="${DEVICE:-cuda:0}"
|
| 32 |
+
BATCH_SIZE="${BATCH_SIZE:-2}"
|
| 33 |
+
N_EPOCHS="${N_EPOCHS:-1}"
|
| 34 |
+
NUM_WORKERS="${NUM_WORKERS:-1}"
|
| 35 |
+
NUM_DATALOADER_WORKERS="${NUM_DATALOADER_WORKERS:-0}"
|
| 36 |
+
LR="${LR:-0.001}"
|
| 37 |
+
SEED="${SEED:-0}"
|
| 38 |
+
|
| 39 |
+
PDBBIND_DIR="${PDBBIND_DIR:-${DIFFDOCK_DATASETS_DIR}/PDBBind_processed}"
|
| 40 |
+
SPLIT_TRAIN="${SPLIT_TRAIN:-${DIFFDOCK_DATASETS_DIR}/splits/timesplit_no_lig_overlap_train_smoke100}"
|
| 41 |
+
SPLIT_VAL="${SPLIT_VAL:-${DIFFDOCK_DATASETS_DIR}/splits/timesplit_no_lig_overlap_val_smoke20}"
|
| 42 |
+
|
| 43 |
+
mkdir -p "${LOG_DIR}" "${CACHE_PATH}"
|
| 44 |
+
LOG_DIR=$(cd "${LOG_DIR}" && pwd)
|
| 45 |
+
CACHE_PATH=$(cd "${CACHE_PATH}" && pwd)
|
| 46 |
+
CONFIG_DIR=$(dirname "${CONFIG_PATH}")
|
| 47 |
+
CONFIG_NAME=$(basename "${CONFIG_PATH}")
|
| 48 |
+
mkdir -p "${CONFIG_DIR}"
|
| 49 |
+
CONFIG_PATH=$(cd "${CONFIG_DIR}" && pwd)/"${CONFIG_NAME}"
|
| 50 |
+
|
| 51 |
+
yaml_value() {
|
| 52 |
+
if [[ -z "${1:-}" || "${1}" == "null" ]]; then
|
| 53 |
+
printf "null"
|
| 54 |
+
else
|
| 55 |
+
local value
|
| 56 |
+
value=$(printf "%s" "$1" | sed "s/'/''/g")
|
| 57 |
+
printf "'%s'" "$value"
|
| 58 |
+
fi
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
cat > "${CONFIG_PATH}" <<EOF
|
| 62 |
+
runtime:
|
| 63 |
+
run_name: $(yaml_value "${RUN_NAME}")
|
| 64 |
+
log_dir: $(yaml_value "${LOG_DIR}")
|
| 65 |
+
device: $(yaml_value "${DEVICE}")
|
| 66 |
+
seed: ${SEED}
|
| 67 |
+
cudnn_benchmark: false
|
| 68 |
+
wandb: false
|
| 69 |
+
project: diffdock
|
| 70 |
+
|
| 71 |
+
data:
|
| 72 |
+
dataset: 'pdbbind'
|
| 73 |
+
cache_path: $(yaml_value "${CACHE_PATH}")
|
| 74 |
+
pdbbind_dir: $(yaml_value "${PDBBIND_DIR}")
|
| 75 |
+
moad_dir: $(yaml_value "${PDBBIND_DIR}")
|
| 76 |
+
split_train: $(yaml_value "${SPLIT_TRAIN}")
|
| 77 |
+
split_val: $(yaml_value "${SPLIT_VAL}")
|
| 78 |
+
protein_file: protein_processed
|
| 79 |
+
limit_complexes: null
|
| 80 |
+
num_conformers: 1
|
| 81 |
+
num_workers: ${NUM_WORKERS}
|
| 82 |
+
num_dataloader_workers: ${NUM_DATALOADER_WORKERS}
|
| 83 |
+
batch_size: ${BATCH_SIZE}
|
| 84 |
+
pin_memory: false
|
| 85 |
+
dataloader_drop_last: false
|
| 86 |
+
remove_hs: true
|
| 87 |
+
receptor_radius: 30
|
| 88 |
+
c_alpha_max_neighbors: 10
|
| 89 |
+
atom_radius: 5
|
| 90 |
+
atom_max_neighbors: 8
|
| 91 |
+
chain_cutoff: null
|
| 92 |
+
max_lig_size: null
|
| 93 |
+
matching_popsize: 20
|
| 94 |
+
matching_maxiter: 20
|
| 95 |
+
matching_tries: 1
|
| 96 |
+
not_knn_only_graph: false
|
| 97 |
+
include_miscellaneous_atoms: false
|
| 98 |
+
all_atoms: false
|
| 99 |
+
triple_training: false
|
| 100 |
+
combined_training: false
|
| 101 |
+
double_val: false
|
| 102 |
+
train_multiplicity: 1
|
| 103 |
+
val_multiplicity: 1
|
| 104 |
+
max_receptor_size: null
|
| 105 |
+
remove_promiscuous_targets: null
|
| 106 |
+
min_ligand_size: 0
|
| 107 |
+
unroll_clusters: false
|
| 108 |
+
enforce_timesplit: false
|
| 109 |
+
crop_beyond: 20
|
| 110 |
+
moad_esm_embeddings_path: null
|
| 111 |
+
pdbbind_esm_embeddings_path: null
|
| 112 |
+
moad_esm_embeddings_sequences_path: null
|
| 113 |
+
esm_embeddings_model: null
|
| 114 |
+
|
| 115 |
+
diffusion:
|
| 116 |
+
no_torsion: false
|
| 117 |
+
tr_sigma_min: 0.1
|
| 118 |
+
tr_sigma_max: 30.0
|
| 119 |
+
rot_sigma_min: 0.1
|
| 120 |
+
rot_sigma_max: 1.65
|
| 121 |
+
tor_sigma_min: 0.0314
|
| 122 |
+
tor_sigma_max: 3.14
|
| 123 |
+
sampling_alpha: 1.0
|
| 124 |
+
sampling_beta: 1.0
|
| 125 |
+
tr_weight: 0.33
|
| 126 |
+
rot_weight: 0.33
|
| 127 |
+
tor_weight: 0.33
|
| 128 |
+
backbone_loss_weight: 0.0
|
| 129 |
+
sidechain_loss_weight: 0.0
|
| 130 |
+
|
| 131 |
+
model:
|
| 132 |
+
num_conv_layers: 2
|
| 133 |
+
max_radius: 5.0
|
| 134 |
+
scale_by_sigma: true
|
| 135 |
+
norm_by_sigma: false
|
| 136 |
+
ns: 16
|
| 137 |
+
nv: 4
|
| 138 |
+
distance_embed_dim: 32
|
| 139 |
+
cross_distance_embed_dim: 32
|
| 140 |
+
no_batch_norm: true
|
| 141 |
+
use_second_order_repr: false
|
| 142 |
+
cross_max_distance: 80
|
| 143 |
+
dynamic_max_cross: false
|
| 144 |
+
dropout: 0.0
|
| 145 |
+
smooth_edges: false
|
| 146 |
+
odd_parity: false
|
| 147 |
+
embedding_type: sinusoidal
|
| 148 |
+
sigma_embed_dim: 32
|
| 149 |
+
embedding_scale: 1000
|
| 150 |
+
no_aminoacid_identities: false
|
| 151 |
+
sh_lmax: 2
|
| 152 |
+
no_differentiate_convolutions: false
|
| 153 |
+
tp_weights_layers: 2
|
| 154 |
+
num_prot_emb_layers: 0
|
| 155 |
+
reduce_pseudoscalars: false
|
| 156 |
+
embed_also_ligand: true
|
| 157 |
+
depthwise_convolution: false
|
| 158 |
+
use_old_atom_encoder: false
|
| 159 |
+
|
| 160 |
+
optimization:
|
| 161 |
+
n_epochs: ${N_EPOCHS}
|
| 162 |
+
lr: ${LR}
|
| 163 |
+
w_decay: 0.0
|
| 164 |
+
scheduler: null
|
| 165 |
+
scheduler_patience: 20
|
| 166 |
+
lr_start_factor: 0.001
|
| 167 |
+
warmup_dur: 4
|
| 168 |
+
use_ema: true
|
| 169 |
+
ema_rate: 0.999
|
| 170 |
+
restart_dir: null
|
| 171 |
+
restart_ckpt: last_model
|
| 172 |
+
restart_lr: null
|
| 173 |
+
pretrain_dir: null
|
| 174 |
+
pretrain_ckpt: null
|
| 175 |
+
save_model_freq: null
|
| 176 |
+
|
| 177 |
+
validation:
|
| 178 |
+
test_sigma_intervals: false
|
| 179 |
+
inference_samples: 1
|
| 180 |
+
val_inference_freq: null
|
| 181 |
+
train_inference_freq: null
|
| 182 |
+
inference_steps: 20
|
| 183 |
+
num_inference_complexes: 20
|
| 184 |
+
inference_earlystop_metric: valinf_min_rmsds_lt2
|
| 185 |
+
inference_secondary_metric: null
|
| 186 |
+
inference_earlystop_goal: max
|
| 187 |
+
EOF
|
| 188 |
+
|
| 189 |
+
echo "DiffDock smoke training config: ${CONFIG_PATH}"
|
| 190 |
+
echo "PDBBind dir: ${PDBBIND_DIR}"
|
| 191 |
+
echo "Train split: ${SPLIT_TRAIN}"
|
| 192 |
+
echo "Val split: ${SPLIT_VAL}"
|
| 193 |
+
echo "Cache path: ${CACHE_PATH}"
|
| 194 |
+
echo "Run output: ${LOG_DIR}/${RUN_NAME}"
|
| 195 |
+
echo "TORCH_HOME: ${TORCH_HOME}"
|
| 196 |
+
|
| 197 |
+
cd "${REPO_ROOT}"
|
| 198 |
+
python "${SCRIPT_DIR}/train_diffdock.py" --config "${CONFIG_PATH}"
|
| 199 |
+
|
scripts/train_diffdock.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
DIR = Path(__file__).resolve().parent.parent
|
| 5 |
+
sys.path.insert(0, str(DIR))
|
| 6 |
+
|
| 7 |
+
import argparse
|
| 8 |
+
import copy
|
| 9 |
+
import math
|
| 10 |
+
import os
|
| 11 |
+
import random
|
| 12 |
+
from functools import partial
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
from types import SimpleNamespace
|
| 15 |
+
|
| 16 |
+
import numpy as np
|
| 17 |
+
import torch
|
| 18 |
+
import yaml
|
| 19 |
+
|
| 20 |
+
from onescience.datapipes.diffdock.loader import construct_loader
|
| 21 |
+
from onescience.utils.diffdock.diffusion_utils import t_to_sigma as t_to_sigma_compl
|
| 22 |
+
from onescience.utils.diffdock.training import (
|
| 23 |
+
inference_epoch_fix,
|
| 24 |
+
loss_function,
|
| 25 |
+
test_epoch,
|
| 26 |
+
train_epoch,
|
| 27 |
+
)
|
| 28 |
+
from onescience.utils.diffdock.utils import (
|
| 29 |
+
ExponentialMovingAverage,
|
| 30 |
+
get_optimizer_and_scheduler,
|
| 31 |
+
save_yaml_file,
|
| 32 |
+
)
|
| 33 |
+
from onescience.utils.diffdock.validation import validate_training_entrypoint
|
| 34 |
+
|
| 35 |
+
try:
|
| 36 |
+
from models.score_wrapper import build_score_model
|
| 37 |
+
except ImportError:
|
| 38 |
+
from models.score_wrapper import build_score_model
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def parse_args():
|
| 42 |
+
parser = argparse.ArgumentParser()
|
| 43 |
+
parser.add_argument("--config", required=True, help="Path to the training YAML config.")
|
| 44 |
+
return parser.parse_args()
|
| 45 |
+
|
| 46 |
+
def _resolve_env_vars(obj):
|
| 47 |
+
if isinstance(obj, str):
|
| 48 |
+
return os.path.expandvars(obj)
|
| 49 |
+
if isinstance(obj, dict):
|
| 50 |
+
return {k: _resolve_env_vars(v) for k, v in obj.items()}
|
| 51 |
+
if isinstance(obj, list):
|
| 52 |
+
return [_resolve_env_vars(v) for v in obj]
|
| 53 |
+
return obj
|
| 54 |
+
|
| 55 |
+
# def load_config(config_path):
|
| 56 |
+
# with open(config_path, "r", encoding="utf-8") as handle:
|
| 57 |
+
# return yaml.safe_load(handle) or {}
|
| 58 |
+
def load_config(config_path):
|
| 59 |
+
with open(config_path, "r", encoding="utf-8") as handle:
|
| 60 |
+
return _resolve_env_vars(yaml.safe_load(handle) or {})
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def flatten_config(config):
|
| 64 |
+
flat = {}
|
| 65 |
+
for key, value in config.items():
|
| 66 |
+
if isinstance(value, dict):
|
| 67 |
+
flat.update(value)
|
| 68 |
+
else:
|
| 69 |
+
flat[key] = value
|
| 70 |
+
return flat
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def to_namespace(config):
|
| 74 |
+
return SimpleNamespace(**config)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def resolve_device(device_name):
|
| 78 |
+
if device_name in {None, "auto"}:
|
| 79 |
+
return torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 80 |
+
return torch.device(device_name)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def state_dict_for_save(model):
|
| 84 |
+
return model.module.state_dict() if hasattr(model, "module") else model.state_dict()
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def model_target(model):
|
| 88 |
+
return model.module if hasattr(model, "module") else model
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def set_seed(seed):
|
| 92 |
+
random.seed(seed)
|
| 93 |
+
np.random.seed(seed)
|
| 94 |
+
torch.manual_seed(seed)
|
| 95 |
+
if torch.cuda.is_available():
|
| 96 |
+
torch.cuda.manual_seed_all(seed)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def maybe_init_wandb(args):
|
| 100 |
+
if not args.wandb:
|
| 101 |
+
return None
|
| 102 |
+
try:
|
| 103 |
+
import wandb
|
| 104 |
+
except ImportError as exc:
|
| 105 |
+
raise ImportError("wandb is enabled in the config but is not installed.") from exc
|
| 106 |
+
wandb.init(project=args.project, name=args.run_name, config=vars(args))
|
| 107 |
+
return wandb
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def maybe_load_restart(args, model, optimizer, ema_weights):
|
| 111 |
+
if args.restart_dir is None:
|
| 112 |
+
return
|
| 113 |
+
|
| 114 |
+
checkpoint_path = Path(args.restart_dir) / f"{args.restart_ckpt}.pt"
|
| 115 |
+
try:
|
| 116 |
+
checkpoint = torch.load(checkpoint_path, map_location=torch.device("cpu"))
|
| 117 |
+
if args.restart_lr is not None:
|
| 118 |
+
checkpoint["optimizer"]["param_groups"][0]["lr"] = args.restart_lr
|
| 119 |
+
optimizer.load_state_dict(checkpoint["optimizer"])
|
| 120 |
+
model_target(model).load_state_dict(checkpoint["model"], strict=True)
|
| 121 |
+
ema_weights.load_state_dict(checkpoint["ema_weights"], device=args.device)
|
| 122 |
+
print("Restarting from epoch", checkpoint["epoch"])
|
| 123 |
+
except Exception as exc:
|
| 124 |
+
print("Exception", exc)
|
| 125 |
+
checkpoint = torch.load(Path(args.restart_dir) / "best_model.pt", map_location=torch.device("cpu"))
|
| 126 |
+
model_target(model).load_state_dict(checkpoint, strict=True)
|
| 127 |
+
print("Due to exception had to take the best epoch and no optimiser")
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def maybe_load_pretrain(args, model):
|
| 131 |
+
if args.pretrain_dir is None:
|
| 132 |
+
return
|
| 133 |
+
checkpoint = torch.load(
|
| 134 |
+
Path(args.pretrain_dir) / f"{args.pretrain_ckpt}.pt",
|
| 135 |
+
map_location=torch.device("cpu"),
|
| 136 |
+
)
|
| 137 |
+
if isinstance(checkpoint, dict) and "model" in checkpoint and "optimizer" in checkpoint:
|
| 138 |
+
checkpoint = checkpoint["model"]
|
| 139 |
+
model_target(model).load_state_dict(checkpoint, strict=True)
|
| 140 |
+
print("Using pretrained model", str(Path(args.pretrain_dir) / f"{args.pretrain_ckpt}.pt"))
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def train(args, model, optimizer, scheduler, ema_weights, train_loader, val_loader, t_to_sigma, run_dir, val_dataset2):
|
| 144 |
+
loss_fn = partial(
|
| 145 |
+
loss_function,
|
| 146 |
+
tr_weight=args.tr_weight,
|
| 147 |
+
rot_weight=args.rot_weight,
|
| 148 |
+
tor_weight=args.tor_weight,
|
| 149 |
+
no_torsion=args.no_torsion,
|
| 150 |
+
backbone_weight=args.backbone_loss_weight,
|
| 151 |
+
sidechain_weight=args.sidechain_loss_weight,
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
best_val_loss = math.inf
|
| 155 |
+
best_val_inference_value = math.inf if args.inference_earlystop_goal == "min" else 0
|
| 156 |
+
best_val_secondary_value = math.inf if args.inference_earlystop_goal == "min" else 0
|
| 157 |
+
best_epoch = 0
|
| 158 |
+
best_val_inference_epoch = 0
|
| 159 |
+
|
| 160 |
+
freeze_params = 0
|
| 161 |
+
scheduler_mode = args.inference_earlystop_goal if args.val_inference_freq is not None else "min"
|
| 162 |
+
if args.scheduler == "layer_linear_warmup":
|
| 163 |
+
freeze_params = args.warmup_dur * (args.num_conv_layers + 2) - 1
|
| 164 |
+
print("Freezing some parameters until epoch {}".format(freeze_params))
|
| 165 |
+
|
| 166 |
+
wandb_run = maybe_init_wandb(args)
|
| 167 |
+
|
| 168 |
+
print("Starting training...")
|
| 169 |
+
for epoch in range(args.n_epochs):
|
| 170 |
+
if epoch % 5 == 0:
|
| 171 |
+
print("Run name:", args.run_name)
|
| 172 |
+
|
| 173 |
+
if args.scheduler == "layer_linear_warmup" and (epoch + 1) % args.warmup_dur == 0:
|
| 174 |
+
step = (epoch + 1) // args.warmup_dur
|
| 175 |
+
if step < args.num_conv_layers + 2:
|
| 176 |
+
print("New unfreezing step")
|
| 177 |
+
optimizer, scheduler = get_optimizer_and_scheduler(
|
| 178 |
+
args,
|
| 179 |
+
model,
|
| 180 |
+
step=step,
|
| 181 |
+
scheduler_mode=scheduler_mode,
|
| 182 |
+
)
|
| 183 |
+
elif step == args.num_conv_layers + 2:
|
| 184 |
+
print("Unfreezing all parameters")
|
| 185 |
+
optimizer, scheduler = get_optimizer_and_scheduler(
|
| 186 |
+
args,
|
| 187 |
+
model,
|
| 188 |
+
step=step,
|
| 189 |
+
scheduler_mode=scheduler_mode,
|
| 190 |
+
)
|
| 191 |
+
ema_weights = ExponentialMovingAverage(model.parameters(), decay=args.ema_rate)
|
| 192 |
+
elif args.scheduler == "linear_warmup" and epoch == args.warmup_dur:
|
| 193 |
+
print("Moving to plateu scheduler")
|
| 194 |
+
optimizer, scheduler = get_optimizer_and_scheduler(
|
| 195 |
+
args,
|
| 196 |
+
model,
|
| 197 |
+
step=1,
|
| 198 |
+
scheduler_mode=scheduler_mode,
|
| 199 |
+
optimizer=optimizer,
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
logs = {}
|
| 203 |
+
train_losses = train_epoch(
|
| 204 |
+
model,
|
| 205 |
+
train_loader,
|
| 206 |
+
optimizer,
|
| 207 |
+
args.device,
|
| 208 |
+
t_to_sigma,
|
| 209 |
+
loss_fn,
|
| 210 |
+
ema_weights if epoch > freeze_params else None,
|
| 211 |
+
)
|
| 212 |
+
print(
|
| 213 |
+
"Epoch {}: Training loss {:.4f} tr {:.4f} rot {:.4f} tor {:.4f} sc {:.4f} lr {:.4f}".format(
|
| 214 |
+
epoch,
|
| 215 |
+
train_losses["loss"],
|
| 216 |
+
train_losses["tr_loss"],
|
| 217 |
+
train_losses["rot_loss"],
|
| 218 |
+
train_losses["tor_loss"],
|
| 219 |
+
train_losses["sidechain_loss"],
|
| 220 |
+
optimizer.param_groups[0]["lr"],
|
| 221 |
+
)
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
if epoch > freeze_params:
|
| 225 |
+
ema_weights.store(model.parameters())
|
| 226 |
+
if args.use_ema:
|
| 227 |
+
ema_weights.copy_to(model.parameters())
|
| 228 |
+
|
| 229 |
+
val_losses = test_epoch(model, val_loader, args.device, t_to_sigma, loss_fn, args.test_sigma_intervals)
|
| 230 |
+
print(
|
| 231 |
+
"Epoch {}: Validation loss {:.4f} tr {:.4f} rot {:.4f} tor {:.4f} sc {:.4f}".format(
|
| 232 |
+
epoch,
|
| 233 |
+
val_losses["loss"],
|
| 234 |
+
val_losses["tr_loss"],
|
| 235 |
+
val_losses["rot_loss"],
|
| 236 |
+
val_losses["tor_loss"],
|
| 237 |
+
val_losses["sidechain_loss"],
|
| 238 |
+
)
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
if args.val_inference_freq is not None and (epoch + 1) % args.val_inference_freq == 0:
|
| 242 |
+
inf_dataset = [
|
| 243 |
+
val_loader.dataset.get(i)
|
| 244 |
+
for i in range(min(args.num_inference_complexes, val_loader.dataset.__len__()))
|
| 245 |
+
]
|
| 246 |
+
inf_metrics = inference_epoch_fix(model, inf_dataset, args.device, t_to_sigma, args)
|
| 247 |
+
print(
|
| 248 |
+
"Epoch {}: Val inference rmsds_lt2 {:.3f} rmsds_lt5 {:.3f} min_rmsds_lt2 {:.3f} min_rmsds_lt5 {:.3f}".format(
|
| 249 |
+
epoch,
|
| 250 |
+
inf_metrics["rmsds_lt2"],
|
| 251 |
+
inf_metrics["rmsds_lt5"],
|
| 252 |
+
inf_metrics["min_rmsds_lt2"],
|
| 253 |
+
inf_metrics["min_rmsds_lt5"],
|
| 254 |
+
)
|
| 255 |
+
)
|
| 256 |
+
logs.update({"valinf_" + k: v for k, v in inf_metrics.items()})
|
| 257 |
+
|
| 258 |
+
if args.double_val and args.val_inference_freq is not None and (epoch + 1) % args.val_inference_freq == 0:
|
| 259 |
+
inf_dataset = [
|
| 260 |
+
val_dataset2.get(i)
|
| 261 |
+
for i in range(min(args.num_inference_complexes, val_dataset2.__len__()))
|
| 262 |
+
]
|
| 263 |
+
inf_metrics2 = inference_epoch_fix(model, inf_dataset, args.device, t_to_sigma, args)
|
| 264 |
+
print(
|
| 265 |
+
"Epoch {}: Val inference on second validation rmsds_lt2 {:.3f} rmsds_lt5 {:.3f} min_rmsds_lt2 {:.3f} min_rmsds_lt5 {:.3f}".format(
|
| 266 |
+
epoch,
|
| 267 |
+
inf_metrics2["rmsds_lt2"],
|
| 268 |
+
inf_metrics2["rmsds_lt5"],
|
| 269 |
+
inf_metrics2["min_rmsds_lt2"],
|
| 270 |
+
inf_metrics2["min_rmsds_lt5"],
|
| 271 |
+
)
|
| 272 |
+
)
|
| 273 |
+
logs.update({"valinf2_" + k: v for k, v in inf_metrics2.items()})
|
| 274 |
+
logs.update({"valinfcomb_" + k: (v + inf_metrics[k]) / 2 for k, v in inf_metrics2.items()})
|
| 275 |
+
|
| 276 |
+
if args.train_inference_freq is not None and (epoch + 1) % args.train_inference_freq == 0:
|
| 277 |
+
inf_dataset = [
|
| 278 |
+
train_loader.dataset.get(i)
|
| 279 |
+
for i in range(min(min(args.num_inference_complexes, 300), train_loader.dataset.__len__()))
|
| 280 |
+
]
|
| 281 |
+
inf_metrics = inference_epoch_fix(model, inf_dataset, args.device, t_to_sigma, args)
|
| 282 |
+
print(
|
| 283 |
+
"Epoch {}: Train inference rmsds_lt2 {:.3f} rmsds_lt5 {:.3f} min_rmsds_lt2 {:.3f} min_rmsds_lt5 {:.3f}".format(
|
| 284 |
+
epoch,
|
| 285 |
+
inf_metrics["rmsds_lt2"],
|
| 286 |
+
inf_metrics["rmsds_lt5"],
|
| 287 |
+
inf_metrics["min_rmsds_lt2"],
|
| 288 |
+
inf_metrics["min_rmsds_lt5"],
|
| 289 |
+
)
|
| 290 |
+
)
|
| 291 |
+
logs.update({"traininf_" + k: v for k, v in inf_metrics.items()})
|
| 292 |
+
|
| 293 |
+
if epoch > freeze_params:
|
| 294 |
+
if not args.use_ema:
|
| 295 |
+
ema_weights.copy_to(model.parameters())
|
| 296 |
+
ema_state_dict = copy.deepcopy(state_dict_for_save(model))
|
| 297 |
+
ema_weights.restore(model.parameters())
|
| 298 |
+
else:
|
| 299 |
+
ema_state_dict = copy.deepcopy(state_dict_for_save(model))
|
| 300 |
+
|
| 301 |
+
if wandb_run is not None:
|
| 302 |
+
logs.update({"train_" + k: v for k, v in train_losses.items()})
|
| 303 |
+
logs.update({"val_" + k: v for k, v in val_losses.items()})
|
| 304 |
+
logs["current_lr"] = optimizer.param_groups[0]["lr"]
|
| 305 |
+
wandb_run.log(logs, step=epoch + 1)
|
| 306 |
+
|
| 307 |
+
model_state_dict = state_dict_for_save(model)
|
| 308 |
+
if args.inference_earlystop_metric in logs and (
|
| 309 |
+
(args.inference_earlystop_goal == "min" and logs[args.inference_earlystop_metric] <= best_val_inference_value)
|
| 310 |
+
or (args.inference_earlystop_goal == "max" and logs[args.inference_earlystop_metric] >= best_val_inference_value)
|
| 311 |
+
):
|
| 312 |
+
best_val_inference_value = logs[args.inference_earlystop_metric]
|
| 313 |
+
best_val_inference_epoch = epoch
|
| 314 |
+
torch.save(model_state_dict, os.path.join(run_dir, "best_inference_epoch_model.pt"))
|
| 315 |
+
if epoch > freeze_params:
|
| 316 |
+
torch.save(ema_state_dict, os.path.join(run_dir, "best_ema_inference_epoch_model.pt"))
|
| 317 |
+
|
| 318 |
+
if args.inference_secondary_metric is not None and args.inference_secondary_metric in logs and (
|
| 319 |
+
(args.inference_earlystop_goal == "min" and logs[args.inference_secondary_metric] <= best_val_secondary_value)
|
| 320 |
+
or (args.inference_earlystop_goal == "max" and logs[args.inference_secondary_metric] >= best_val_secondary_value)
|
| 321 |
+
):
|
| 322 |
+
best_val_secondary_value = logs[args.inference_secondary_metric]
|
| 323 |
+
if epoch > freeze_params:
|
| 324 |
+
torch.save(ema_state_dict, os.path.join(run_dir, "best_ema_secondary_epoch_model.pt"))
|
| 325 |
+
|
| 326 |
+
if val_losses["loss"] <= best_val_loss:
|
| 327 |
+
best_val_loss = val_losses["loss"]
|
| 328 |
+
best_epoch = epoch
|
| 329 |
+
torch.save(model_state_dict, os.path.join(run_dir, "best_model.pt"))
|
| 330 |
+
if epoch > freeze_params:
|
| 331 |
+
torch.save(ema_state_dict, os.path.join(run_dir, "best_ema_model.pt"))
|
| 332 |
+
|
| 333 |
+
if args.save_model_freq is not None and (epoch + 1) % args.save_model_freq == 0:
|
| 334 |
+
best_model_path = os.path.join(run_dir, "best_model.pt")
|
| 335 |
+
if os.path.exists(best_model_path):
|
| 336 |
+
torch.save(torch.load(best_model_path, map_location=torch.device("cpu")), os.path.join(run_dir, f"epoch{epoch + 1}_best_model.pt"))
|
| 337 |
+
|
| 338 |
+
if scheduler:
|
| 339 |
+
if epoch < freeze_params or (args.scheduler == "linear_warmup" and epoch < args.warmup_dur):
|
| 340 |
+
scheduler.step()
|
| 341 |
+
elif args.val_inference_freq is not None:
|
| 342 |
+
scheduler.step(best_val_inference_value)
|
| 343 |
+
else:
|
| 344 |
+
scheduler.step(val_losses["loss"])
|
| 345 |
+
|
| 346 |
+
torch.save(
|
| 347 |
+
{
|
| 348 |
+
"epoch": epoch,
|
| 349 |
+
"model": model_state_dict,
|
| 350 |
+
"optimizer": optimizer.state_dict(),
|
| 351 |
+
"ema_weights": ema_weights.state_dict(),
|
| 352 |
+
},
|
| 353 |
+
os.path.join(run_dir, "last_model.pt"),
|
| 354 |
+
)
|
| 355 |
+
|
| 356 |
+
print("Best Validation Loss {} on Epoch {}".format(best_val_loss, best_epoch))
|
| 357 |
+
print("Best inference metric {} on Epoch {}".format(best_val_inference_value, best_val_inference_epoch))
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
def main():
|
| 361 |
+
parsed = parse_args()
|
| 362 |
+
raw_config = load_config(parsed.config)
|
| 363 |
+
flat_config = flatten_config(raw_config)
|
| 364 |
+
args = to_namespace(flat_config)
|
| 365 |
+
|
| 366 |
+
if getattr(args, "run_name", None) in {None, ""}:
|
| 367 |
+
args.run_name = Path(parsed.config).stem
|
| 368 |
+
|
| 369 |
+
args.device = resolve_device(getattr(args, "device", "auto"))
|
| 370 |
+
if getattr(args, "cudnn_benchmark", False) and args.device.type == "cuda":
|
| 371 |
+
torch.backends.cudnn.benchmark = True
|
| 372 |
+
set_seed(getattr(args, "seed", 0))
|
| 373 |
+
validate_training_entrypoint(args)
|
| 374 |
+
|
| 375 |
+
assert args.inference_earlystop_goal in {"max", "min"}
|
| 376 |
+
if args.val_inference_freq is not None and args.scheduler is not None:
|
| 377 |
+
assert args.scheduler_patience > args.val_inference_freq
|
| 378 |
+
|
| 379 |
+
run_dir = os.path.join(args.log_dir, args.run_name)
|
| 380 |
+
saved_args = vars(args).copy()
|
| 381 |
+
saved_args["device"] = str(args.device)
|
| 382 |
+
save_yaml_file(os.path.join(run_dir, "model_parameters.yml"), saved_args)
|
| 383 |
+
|
| 384 |
+
t_to_sigma = partial(t_to_sigma_compl, args=args)
|
| 385 |
+
train_loader, val_loader, val_dataset2 = construct_loader(args, t_to_sigma, args.device)
|
| 386 |
+
model, _ = build_score_model(args, args.device, no_parallel=False)
|
| 387 |
+
optimizer, scheduler = get_optimizer_and_scheduler(
|
| 388 |
+
args,
|
| 389 |
+
model,
|
| 390 |
+
scheduler_mode=args.inference_earlystop_goal if args.val_inference_freq is not None else "min",
|
| 391 |
+
)
|
| 392 |
+
ema_weights = ExponentialMovingAverage(model.parameters(), decay=args.ema_rate)
|
| 393 |
+
|
| 394 |
+
maybe_load_restart(args, model, optimizer, ema_weights)
|
| 395 |
+
maybe_load_pretrain(args, model)
|
| 396 |
+
|
| 397 |
+
numel = sum(p.numel() for p in model.parameters())
|
| 398 |
+
print("Model with", numel, "parameters")
|
| 399 |
+
|
| 400 |
+
train(args, model, optimizer, scheduler, ema_weights, train_loader, val_loader, t_to_sigma, run_dir, val_dataset2)
|
| 401 |
+
|
| 402 |
+
|
| 403 |
+
if __name__ == "__main__":
|
| 404 |
+
main()
|
scripts/train_slurm.sbatch
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
#SBATCH -J diffdock_train
|
| 3 |
+
#SBATCH -p largedev
|
| 4 |
+
#SBATCH -N 1
|
| 5 |
+
#SBATCH --ntasks-per-node=1
|
| 6 |
+
#SBATCH --gres=dcu:1
|
| 7 |
+
#SBATCH --cpus-per-task=8
|
| 8 |
+
#SBATCH --time=24:00:00
|
| 9 |
+
#SBATCH -o slurm-train-%j.out
|
| 10 |
+
#SBATCH -e slurm-train-%j.err
|
| 11 |
+
|
| 12 |
+
set -euo pipefail
|
| 13 |
+
|
| 14 |
+
echo "START TIME: $(date)"
|
| 15 |
+
echo "SLURM_JOB_ID: ${SLURM_JOB_ID:-none}"
|
| 16 |
+
echo "HOSTNAME: $(hostname)"
|
| 17 |
+
|
| 18 |
+
module purge || true
|
| 19 |
+
module load sghpc-mpi-gcc/25.8 || true
|
| 20 |
+
|
| 21 |
+
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
| 22 |
+
mkdir -p "${SCRIPT_DIR}/../outputs"
|
| 23 |
+
|
| 24 |
+
srun bash "${SCRIPT_DIR}/train.sh"
|
| 25 |
+
|
| 26 |
+
echo "END TIME: $(date)"
|