anzhi2710gmailcom commited on
Commit
3967f69
·
verified ·
1 Parent(s): 0f2687f

Upload folder using huggingface_hub

Browse files
Files changed (43) hide show
  1. .gitattributes +22 -10
  2. README.md +234 -0
  3. __init__.py +1 -0
  4. configs/evaluate.yml +101 -0
  5. configs/sampling.yml +61 -0
  6. configs/training.yml +150 -0
  7. data/1a46_ligand.sdf +179 -0
  8. data/1a46_protein_processed.pdb +0 -0
  9. data/6o5u_ligand.sdf +154 -0
  10. data/6o5u_protein_processed.pdb +0 -0
  11. models/__init__.py +34 -0
  12. models/__pycache__/__init__.cpython-311.pyc +0 -0
  13. models/__pycache__/aa_model.cpython-310.pyc +0 -0
  14. models/__pycache__/aa_model.cpython-311.pyc +0 -0
  15. models/__pycache__/cg_model.cpython-310.pyc +0 -0
  16. models/__pycache__/cg_model.cpython-311.pyc +0 -0
  17. models/__pycache__/layers.cpython-311.pyc +0 -0
  18. models/__pycache__/old_aa_model.cpython-310.pyc +0 -0
  19. models/__pycache__/old_aa_model.cpython-311.pyc +0 -0
  20. models/__pycache__/score_wrapper.cpython-310.pyc +0 -0
  21. models/__pycache__/score_wrapper.cpython-311.pyc +0 -0
  22. models/__pycache__/tensor_layers.cpython-310.pyc +0 -0
  23. models/__pycache__/tensor_layers.cpython-311.pyc +0 -0
  24. models/aa_model.py +721 -0
  25. models/cg_model.py +1070 -0
  26. models/layers.py +128 -0
  27. models/old_aa_model.py +582 -0
  28. models/score_wrapper.py +219 -0
  29. models/tensor_layers.py +459 -0
  30. scripts/__init__.py +1 -0
  31. scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  32. scripts/__pycache__/evaluate.cpython-311.pyc +0 -0
  33. scripts/__pycache__/sample_diffdock.cpython-310.pyc +0 -0
  34. scripts/__pycache__/train_diffdock.cpython-310.pyc +0 -0
  35. scripts/__pycache__/train_diffdock.cpython-311.pyc +0 -0
  36. scripts/evaluate.py +5 -0
  37. scripts/infer.sh +147 -0
  38. scripts/infer_slurm.sbatch +26 -0
  39. scripts/sample_diffdock.py +395 -0
  40. scripts/train.sh +231 -0
  41. scripts/train_demo.sh +199 -0
  42. scripts/train_diffdock.py +404 -0
  43. 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)"