anzhi2710gmailcom commited on
Commit
9d6a2a3
·
verified ·
1 Parent(s): 523a7ea

Upload folder using huggingface_hub

Browse files
.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,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ frameworks:
3
+ - ""
4
+ language:
5
+ - en
6
+ license: mit
7
+ tags:
8
+ - OneScience
9
+ - protein-ligand scoring
10
+ - graph neural network
11
+ ---
12
+ <p align="center">
13
+ <strong>
14
+ <span style="font-size: 30px;">GenScore</span>
15
+ </strong>
16
+ </p>
17
+
18
+ # Model Overview
19
+
20
+ GenScore is a **protein-ligand scoring framework** that can predict protein-small molecule binding affinity and evaluate docking pose quality. It demonstrates balanced scoring, ranking, docking, and virtual screening capabilities across multiple datasets.
21
+
22
+ # Model Description
23
+
24
+ GenScore is based on graph neural networks and extends RTMScore.
25
+
26
+ Weights and datasets are not provided 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
+ | Protein-ligand scoring | Predicts binding scores for a given protein, or an extracted binding pocket, and ligand conformation. |
33
+ | Automatic pocket generation | Automatically extracts the binding pocket from a full protein structure based on the reference ligand position. |
34
+ | Contribution analysis | Outputs atom-level and residue-level contributions to the final score for interpretability analysis. |
35
+ | Model training | Trains the GenScore scoring network using preprocessed PDBbind protein-ligand graph data. |
36
+ | CASF-2016 benchmark evaluation | Supports three standard tests: scoring/ranking, docking, and virtual screening. |
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
+ DCU users who want to learn more about adaptation details can contact liubiao@sugon.com.
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
91
+
92
+ ```bash
93
+ # By default, the package is downloaded to the GenScore folder under the current path. To change this, adjust the path after local_dir.
94
+ hf download --model OneScience-Sugon/GenScore --local-dir ./model
95
+
96
+ cd model
97
+ ```
98
+
99
+ ### Training Weights and Datasets
100
+
101
+ Weights and datasets are not provided at the moment. They will be uploaded to Hugging Face soon, and command-line downloads will be supported later.
102
+
103
+ ### 3. Usage Modes
104
+
105
+ #### 1. Protein-Ligand Scoring Inference (`run_genscore.sh`)
106
+
107
+ ```bash
108
+ cd ./scripts
109
+ bash run_genscore.sh
110
+ ```
111
+
112
+ This script runs four inference examples in sequence, covering common use cases:
113
+
114
+ 1. **GT model + automatic pocket generation**: inputs a full protein, a reference ligand, and a decoy ligand, then automatically generates the binding pocket and scores it.
115
+ 2. **GatedGCN model + pre-extracted pocket**: inputs a pre-extracted pocket PDB and a decoy ligand for scoring.
116
+ 3. **Atom contribution analysis**: uses the GatedGCN model to compute each atom's contribution to the score.
117
+ 4. **Residue contribution analysis**: uses the GatedGCN model to compute each residue's contribution to the score.
118
+
119
+ #### 2. Model Training
120
+
121
+ ##### 2.1 Small-Scale Smoke Test (`train_genscore_smoke.sh`)
122
+
123
+ ```bash
124
+ cd ./scripts
125
+ bash train_genscore_smoke.sh
126
+ ```
127
+
128
+ Default configuration:
129
+
130
+ | Parameter | Default value |
131
+ |------|--------|
132
+ | Number of training epochs | 100 |
133
+ | Batch size | 16 |
134
+ | Number of validation samples | 1500 |
135
+ | Encoder | `gatedgcn` |
136
+ | Output model | `genscore_smoke_bs16.pth` |
137
+
138
+ ##### 2.2 Full Training (`train_genscore_full.sh`)
139
+
140
+ ```bash
141
+ cd ./scripts
142
+ bash train_genscore_full.sh
143
+ ```
144
+
145
+ Key parameters can be overridden through environment variables:
146
+
147
+ | Environment variable | Description | Default value |
148
+ |----------|------|--------|
149
+ | `GENSCORE_DATA_DIR` | Training data directory | `${ONESCIENCE_DATASETS_DIR}/GenScore/genscore_data/rtmscore_s` |
150
+ | `GENSCORE_DATA_PREFIX` | Data file prefix | `v2020_train` |
151
+ | `GENSCORE_ENCODER` | Graph encoder type | `gatedgcn` |
152
+ | `GENSCORE_MODEL_PATH` | Output model path | `examples/biosciences/genscore/genscore_${GENSCORE_ENCODER}_full_3000.pth` |
153
+ | `GENSCORE_NUM_EPOCHS` | Number of training epochs | `3000` |
154
+ | `GENSCORE_BATCH_SIZE` | Batch size | `64` |
155
+ | `GENSCORE_NUM_WORKERS` | Number of data loading workers | `8` |
156
+ | `GENSCORE_VALNUM` | Number of validation samples | `1500` |
157
+ | `GENSCORE_PATIENCE` | Early-stopping patience | `70` |
158
+ ---
159
+
160
+ #### 3. CASF-2016 Benchmark Evaluation (`run_genscore_benchmarks.sh`)
161
+
162
+ ```bash
163
+ cd ./scripts
164
+ bash run_genscore_benchmarks.sh all
165
+ ```
166
+
167
+ You can also run a single evaluation task:
168
+
169
+ ```bash
170
+ bash run_genscore_benchmarks.sh scoring
171
+ bash run_genscore_benchmarks.sh docking
172
+ bash run_genscore_benchmarks.sh screening
173
+ ```
174
+
175
+ Evaluation task descriptions:
176
+
177
+ | Task | Description |
178
+ |------|------|
179
+ | `scoring` | Scoring capability evaluation (scoring/ranking) |
180
+ | `docking` | Docking capability evaluation (docking power) |
181
+ | `screening` | Virtual screening capability evaluation (screening power) |
182
+
183
+ ### Data Preprocessing
184
+
185
+ Training requires preprocessed PDBbind graph data, including:
186
+
187
+ ```text
188
+ <data_prefix>_ids.npy
189
+ <data_prefix>_lig.pt
190
+ <data_prefix>_prot.pt
191
+ ```
192
+
193
+ You can preprocess the raw PDBbind data with the following command:
194
+
195
+ ```bash
196
+ cd ./scripts
197
+ export PYTHONPATH=../../../src:$PYTHONPATH
198
+ python preprocess_pdbbind.py \
199
+ --dir /path/to/pdbbind \
200
+ --ref /path/to/pdbbind_2020_general.csv \
201
+ --cutoff 10.0 \
202
+ --outprefix /path/to/preprocessed/pdbbind/v2020_train
203
+ ```
204
+
205
+ Main parameters:
206
+
207
+ | Parameter | Description |
208
+ |------|------|
209
+ | `--dir` | Raw PDBbind data directory |
210
+ | `--ref` | PDBbind index CSV file |
211
+ | `--cutoff` | Protein-ligand distance cutoff, default `10.0` Å |
212
+ | `--outprefix` | Output file prefix |
213
+
214
+ ### Notes
215
+
216
+ - Make sure the `ONESCIENCE_DATASETS_DIR` environment variable is correctly set before running the scripts.
217
+ - The scripts automatically set ROCm/DCU-related `LD_LIBRARY_PATH` values and can run directly on Hygon DCU platforms.
218
+ - Automatic pocket generation depends on OpenBabel and ProDy. If OpenBabel requires explicit data paths, set `BABEL_LIBDIR` and `BABEL_DATADIR` in advance.
219
+ - Training and inference use a single device by default (`HIP_VISIBLE_DEVICES=0` or `CUDA_VISIBLE_DEVICES=0`). For multi-device training, adjust the parallel strategy in the training scripts.
220
+ - The training scripts check whether `<data_prefix>_ids.npy`, `<data_prefix>_lig.pt`, and `<data_prefix>_prot.pt` exist. If any are missing, the scripts will report an error and exit.
221
+ - The `docking` and `screening` tasks in CASF-2016 evaluation require complete CASF-2016 and PDBbind v2020 data.
222
+
223
+ # Official OneScience Information
224
+
225
+ | Platform | OneScience main repository | Skills repository |
226
+ | --- | --- | --- |
227
+ | Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
228
+ | GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
229
+
230
+ # Citation and License
231
+
232
+ If you use GenScore in your research, please cite the original work:
233
+
234
+ ```bibtex
235
+ @article{genScore,
236
+ title={GenScore: a generalized protein-ligand scoring framework},
237
+ author={Shen, Chao and Hu, Yafeng and Wang, Zhe and Zhang, Xujun and Li, Jianxin and Wang, Guisheng and Wang, Tingjun and Chen, Yen-Wei and Pan, Peichen and Hou, Tingjun},
238
+ journal={Journal of Chemical Information and Modeling},
239
+ year={2023}
240
+ }
241
+ ```
242
+
243
+ For more information, see the official GenScore repository: https://github.com/sc8668/GenScore
244
+
245
+ For license information, refer to the description in the [official GenScore repository](https://github.com/sc8668/GenScore).
benchmarks/__pycache__/_common.cpython-311.pyc ADDED
Binary file (5.17 kB). View file
 
benchmarks/_common.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
9
+ import torch as th
10
+ import torch.multiprocessing
11
+ from torch_geometric.loader import DataLoader
12
+
13
+ from onescience.datapipes.genscore.data import PDBbindDataset
14
+ from onescience.metrics.genscore.utils import run_an_eval_epoch
15
+ from models.inference import _build_encoder, scoring
16
+ from models.model.model import GenScore
17
+
18
+ torch.multiprocessing.set_sharing_strategy("file_system")
19
+
20
+
21
+ def add_model_args(parser):
22
+ parser.add_argument("--model-path", required=True, help="Path to a trained GenScore checkpoint.")
23
+ parser.add_argument("--encoder", choices=["gt", "gatedgcn"], default="gatedgcn")
24
+ parser.add_argument("--batch-size", type=int, default=128)
25
+ parser.add_argument("--num-workers", type=int, default=10)
26
+ parser.add_argument("--cutoff", type=float, default=10.0)
27
+ parser.add_argument("--outprefix", default="gatedgcn1x5")
28
+ parser.add_argument("--dist-threhold", type=float, default=5.0)
29
+ parser.add_argument("--hidden-dim0", type=int, default=128)
30
+ parser.add_argument("--hidden-dim", type=int, default=128)
31
+ parser.add_argument("--n-gaussians", type=int, default=10)
32
+ parser.add_argument("--dropout-rate", type=float, default=0.15)
33
+
34
+
35
+ def runtime_kwargs(args):
36
+ return {
37
+ "batch_size": args.batch_size,
38
+ "dist_threhold": args.dist_threhold,
39
+ "device": "cuda" if th.cuda.is_available() else "cpu",
40
+ "num_workers": args.num_workers,
41
+ "num_node_featsp": 41,
42
+ "num_node_featsl": 41,
43
+ "num_edge_featsp": 5,
44
+ "num_edge_featsl": 10,
45
+ "hidden_dim0": args.hidden_dim0,
46
+ "hidden_dim": args.hidden_dim,
47
+ "n_gaussians": args.n_gaussians,
48
+ "dropout_rate": args.dropout_rate,
49
+ }
50
+
51
+
52
+ def score_ligand_file(prot, lig, args, parallel=False):
53
+ return scoring(
54
+ prot=prot,
55
+ lig=lig,
56
+ modpath=args.model_path,
57
+ cut=args.cutoff,
58
+ gen_pocket=False,
59
+ reflig=None,
60
+ encoder=args.encoder,
61
+ explicit_H=False,
62
+ use_chirality=True,
63
+ parallel=parallel,
64
+ **runtime_kwargs(args),
65
+ )
66
+
67
+
68
+ def score_preprocessed(ids, prots, ligs, args):
69
+ kwargs = runtime_kwargs(args)
70
+ data = PDBbindDataset(ids=ids, prots=prots, ligs=ligs)
71
+ loader = DataLoader(
72
+ dataset=data,
73
+ batch_size=kwargs["batch_size"],
74
+ shuffle=False,
75
+ num_workers=kwargs["num_workers"],
76
+ )
77
+
78
+ ligmodel, protmodel = _build_encoder(args.encoder, kwargs)
79
+ model = GenScore(
80
+ ligmodel,
81
+ protmodel,
82
+ in_channels=kwargs["hidden_dim0"],
83
+ hidden_dim=kwargs["hidden_dim"],
84
+ n_gaussians=kwargs["n_gaussians"],
85
+ dropout_rate=kwargs["dropout_rate"],
86
+ dist_threhold=kwargs["dist_threhold"],
87
+ ).to(kwargs["device"])
88
+
89
+ checkpoint = th.load(args.model_path, map_location=th.device(kwargs["device"]))
90
+ model.load_state_dict(checkpoint["model_state_dict"])
91
+ preds = run_an_eval_epoch(
92
+ model,
93
+ loader,
94
+ pred=True,
95
+ dist_threhold=kwargs["dist_threhold"],
96
+ device=kwargs["device"],
97
+ )
98
+ return data.pdbids, preds
99
+
100
+
101
+ def formatter():
102
+ return argparse.ArgumentDefaultsHelpFormatter
benchmarks/casf2016_docking.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ import pickle
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from joblib import Parallel, delayed
8
+
9
+ from _common import add_model_args, formatter, runtime_kwargs, score_ligand_file
10
+
11
+
12
+ def parse_args():
13
+ parser = argparse.ArgumentParser(
14
+ description="Run GenScore CASF-2016 docking benchmark.",
15
+ formatter_class=formatter(),
16
+ )
17
+ add_model_args(parser)
18
+ parser.add_argument("--casf-dir", required=True, help="CASF-2016 root directory.")
19
+ parser.add_argument("--pdbbind-dir", required=True, help="PDBbind root directory.")
20
+ parser.add_argument(
21
+ "--native-ligand-dir",
22
+ default=None,
23
+ help="Directory containing <pdbid>_ligand.mol2 files.",
24
+ )
25
+ parser.add_argument("--outdir", required=True, help="Directory for CASF docking score .dat files.")
26
+ parser.add_argument("--decoys-subdir", default="decoys_docking")
27
+ parser.add_argument("--coreset-subdir", default="coreset")
28
+ parser.add_argument("--refined-subdir", default="v2020-refined")
29
+ parser.add_argument("--other-pl-subdir", default="v2020-other-PL")
30
+ parser.add_argument("--parallel", action="store_true", default=False)
31
+ return parser.parse_args()
32
+
33
+
34
+ def _complex_ids(path):
35
+ return [name for name in os.listdir(path) if os.path.isdir(os.path.join(path, name))]
36
+
37
+
38
+ def _pocket_path(pdbbind_dir, subset, pdbid, cutoff):
39
+ return os.path.join(
40
+ pdbbind_dir,
41
+ subset,
42
+ pdbid,
43
+ f"{pdbid}_prot",
44
+ f"{pdbid}_p_pocket_{cutoff}.pdb",
45
+ )
46
+
47
+
48
+ def _native_ligand_path(pdbbind_dir, native_ligand_dir, subset, pdbid):
49
+ candidates = []
50
+ if native_ligand_dir:
51
+ candidates.append(
52
+ os.path.join(native_ligand_dir, f"{pdbid}_ligand.mol2")
53
+ )
54
+ candidates.extend([
55
+ os.path.join(pdbbind_dir, "mol2", f"{pdbid}_ligand.mol2"),
56
+ os.path.join(
57
+ pdbbind_dir, subset, pdbid, f"{pdbid}_prot",
58
+ f"{pdbid}_l.mol2",
59
+ ),
60
+ os.path.join(
61
+ pdbbind_dir, subset, pdbid, f"{pdbid}_prot",
62
+ f"{pdbid}_l.sdf",
63
+ ),
64
+ ])
65
+ for candidate in candidates:
66
+ if os.path.isfile(candidate):
67
+ return candidate
68
+ raise FileNotFoundError(
69
+ f"No native ligand found for {pdbid}. Checked: "
70
+ + ", ".join(candidates)
71
+ )
72
+
73
+
74
+ def _decoys_path(casf_dir, decoys_subdir, pdbid):
75
+ root = os.path.join(casf_dir, decoys_subdir)
76
+ return _ligand_path(root, f"{pdbid}_decoys")
77
+
78
+
79
+ def _ligand_path(root, stem):
80
+ candidates = [
81
+ os.path.join(root, f"{stem}.{extension}")
82
+ for extension in ("sdf", "mol2")
83
+ ]
84
+ for candidate in candidates:
85
+ if os.path.isfile(candidate):
86
+ return candidate
87
+ raise FileNotFoundError(
88
+ f"No ligand file found for {stem}. Checked: {', '.join(candidates)}"
89
+ )
90
+
91
+
92
+ def score_compound(pdbid, subset, args):
93
+ prot = _pocket_path(args.pdbbind_dir, subset, pdbid, args.cutoff)
94
+ decoys = _decoys_path(args.casf_dir, args.decoys_subdir, pdbid)
95
+ ids_decoy, scores_decoy = score_ligand_file(prot, decoys, args, parallel=True)
96
+
97
+ native = _native_ligand_path(
98
+ args.pdbbind_dir,
99
+ args.native_ligand_dir,
100
+ subset,
101
+ pdbid,
102
+ )
103
+ ids_native, scores_native = score_ligand_file(prot, native, args, parallel=False)
104
+ ids_native = list(ids_native)
105
+ ids_native.pop(-1)
106
+ ids_native.append(f"{pdbid}_ligand")
107
+ return ids_decoy + ids_native, np.append(scores_decoy, scores_native)
108
+
109
+
110
+ def run_for_subset(pdbids, subset, args):
111
+ if args.parallel and runtime_kwargs(args)["device"] == "cpu":
112
+ return Parallel(n_jobs=-1, backend="threading")(
113
+ delayed(score_compound)(pdbid, subset, args) for pdbid in pdbids
114
+ )
115
+ return [score_compound(pdbid, subset, args) for pdbid in pdbids]
116
+
117
+
118
+ def main():
119
+ args = parse_args()
120
+ coreset = _complex_ids(os.path.join(args.casf_dir, args.coreset_subdir))
121
+ refined_ids = set(_complex_ids(os.path.join(args.pdbbind_dir, args.refined_subdir)))
122
+ other_ids = set(_complex_ids(os.path.join(args.pdbbind_dir, args.other_pl_subdir)))
123
+
124
+ ids_refined = [pdbid for pdbid in coreset if pdbid in refined_ids]
125
+ ids_other = [pdbid for pdbid in coreset if pdbid in other_ids]
126
+
127
+ results = run_for_subset(ids_refined, args.refined_subdir, args)
128
+ results += run_for_subset(ids_other, args.other_pl_subdir, args)
129
+
130
+ os.makedirs(args.outdir, exist_ok=True)
131
+ for ids, scores in results:
132
+ pdbid = ids[0].split("_")[0]
133
+ df = pd.DataFrame(zip(ids, scores), columns=["#code", "score"])
134
+ df["#code"] = df["#code"].str.split("-").apply(lambda item: item[0])
135
+ df.to_csv(os.path.join(args.outdir, f"{pdbid}_score.dat"), index=False, sep="\t")
136
+
137
+ with open(f"{args.outprefix}_docking.pkl", "wb") as handle:
138
+ pickle.dump(results, handle)
139
+
140
+
141
+ if __name__ == "__main__":
142
+ main()
benchmarks/casf2016_scoring_ranking.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ from scipy.stats import pearsonr
7
+ from sklearn import linear_model
8
+ from sklearn.metrics import mean_squared_error
9
+
10
+ from _common import add_model_args, formatter, score_preprocessed
11
+
12
+
13
+ def parse_args():
14
+ parser = argparse.ArgumentParser(
15
+ description="Run GenScore CASF-2016 scoring/ranking benchmark.",
16
+ formatter_class=formatter(),
17
+ )
18
+ add_model_args(parser)
19
+ parser.add_argument("--data-dir", required=True, help="Directory containing preprocessed CASF tensors.")
20
+ parser.add_argument("--test-prefix", default="v2020_casf")
21
+ parser.add_argument("--coreset-file", required=True, help="Path to CASF-2016 CoreSet.dat.")
22
+ parser.add_argument("--outdir", required=True, help="Output directory for CASF ranking .dat file.")
23
+ return parser.parse_args()
24
+
25
+
26
+ def obtain_metrics(df):
27
+ regr = linear_model.LinearRegression()
28
+ regr.fit(df.score.values.reshape(-1, 1), df.logKa.values.reshape(-1, 1))
29
+ preds = regr.predict(df.score.values.reshape(-1, 1))
30
+ rp = pearsonr(df.logKa, df.score)[0]
31
+ mse = mean_squared_error(df.logKa, preds)
32
+ num = df.shape[0]
33
+ sd = np.sqrt((mse * num) / (num - 1))
34
+ print("The regression equation: logKa = %.2f + %.2f * Score" % (float(regr.coef_), float(regr.intercept_)))
35
+ print("Number of favorable sample (N): %d" % num)
36
+ print("Pearson correlation coefficient (R): %.3f" % rp)
37
+ print("Standard deviation in fitting (SD): %.2f" % sd)
38
+
39
+
40
+ def main():
41
+ args = parse_args()
42
+ prots = os.path.join(args.data_dir, f"{args.test_prefix}_prot.pt")
43
+ ligs = os.path.join(args.data_dir, f"{args.test_prefix}_lig.pt")
44
+ ids = os.path.join(args.data_dir, f"{args.test_prefix}_ids.npy")
45
+
46
+ _, preds = score_preprocessed(ids, prots, ligs, args)
47
+
48
+ core = pd.read_csv(args.coreset_file, sep=r"[,,\t, ]+", header=0, engine="python")
49
+ df_score = pd.DataFrame(zip(np.load(ids, allow_pickle=True)[0], preds), columns=["#code", "score"])
50
+ testdf = pd.merge(core, df_score, on="#code")
51
+
52
+ os.makedirs(args.outdir, exist_ok=True)
53
+ testdf[["#code", "score"]].to_csv(
54
+ os.path.join(args.outdir, f"{args.outprefix}.dat"),
55
+ index=False,
56
+ sep="\t",
57
+ )
58
+ obtain_metrics(testdf)
59
+
60
+
61
+ if __name__ == "__main__":
62
+ main()
benchmarks/casf2016_screening.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ import pickle
4
+
5
+ import pandas as pd
6
+ from joblib import Parallel, delayed
7
+
8
+ from _common import add_model_args, formatter, runtime_kwargs, score_ligand_file
9
+
10
+
11
+ def parse_args():
12
+ parser = argparse.ArgumentParser(
13
+ description="Run GenScore CASF-2016 screening benchmark.",
14
+ formatter_class=formatter(),
15
+ )
16
+ add_model_args(parser)
17
+ parser.add_argument("--casf-dir", required=True, help="CASF-2016 root directory.")
18
+ parser.add_argument("--pdbbind-dir", required=True, help="PDBbind root directory.")
19
+ parser.add_argument("--outdir", required=True, help="Directory for CASF screening score .dat files.")
20
+ parser.add_argument("--decoys-subdir", default="decoys_screening")
21
+ parser.add_argument("--coreset-subdir", default="coreset")
22
+ parser.add_argument("--refined-subdir", default="v2020-refined")
23
+ parser.add_argument("--other-pl-subdir", default="v2020-other-PL")
24
+ parser.add_argument("--parallel", action="store_true", default=False)
25
+ return parser.parse_args()
26
+
27
+
28
+ def _complex_ids(path):
29
+ return [name for name in os.listdir(path) if os.path.isdir(os.path.join(path, name))]
30
+
31
+
32
+ def _pocket_path(pdbbind_dir, subset, pdbid, cutoff):
33
+ return os.path.join(
34
+ pdbbind_dir,
35
+ subset,
36
+ pdbid,
37
+ f"{pdbid}_prot",
38
+ f"{pdbid}_p_pocket_{cutoff}.pdb",
39
+ )
40
+
41
+
42
+ def _ligand_path(root, stem):
43
+ candidates = [os.path.join(root, f"{stem}.{extension}") for extension in ("mol2", "sdf")]
44
+ for path in candidates:
45
+ if os.path.isfile(path):
46
+ return path
47
+ raise FileNotFoundError(
48
+ f"No ligand file found for {stem}. Checked: {', '.join(candidates)}"
49
+ )
50
+
51
+
52
+ def score_compound(pdbid, ligid, subset, args):
53
+ prot = _pocket_path(args.pdbbind_dir, subset, pdbid, args.cutoff)
54
+ root = os.path.join(args.casf_dir, args.decoys_subdir, pdbid)
55
+ lig = _ligand_path(root, f"{pdbid}_{ligid}")
56
+ return score_ligand_file(prot, lig, args, parallel=True)
57
+
58
+
59
+ def score_target(pdbid, subset, ligids, args):
60
+ print(f"{pdbid} started.....")
61
+ ids_list = []
62
+ scores_list = []
63
+ for ligid in ligids:
64
+ ids, scores = score_compound(pdbid, ligid, subset, args)
65
+ if ids is not None and scores is not None:
66
+ ids_list.extend(ids)
67
+ scores_list.extend(scores)
68
+ print(f"{pdbid} finished.....")
69
+ return pdbid, [ids_list, scores_list]
70
+
71
+
72
+ def run_for_subset(pdbids, subset, ligids, args):
73
+ if args.parallel and runtime_kwargs(args)["device"] == "cpu":
74
+ return Parallel(n_jobs=-1, backend="threading")(
75
+ delayed(score_target)(pdbid, subset, ligids, args) for pdbid in pdbids
76
+ )
77
+ return [score_target(pdbid, subset, ligids, args) for pdbid in pdbids]
78
+
79
+
80
+ def main():
81
+ args = parse_args()
82
+ ligids = _complex_ids(os.path.join(args.casf_dir, args.coreset_subdir))
83
+ pdbids = _complex_ids(os.path.join(args.casf_dir, args.decoys_subdir))
84
+ refined_ids = set(_complex_ids(os.path.join(args.pdbbind_dir, args.refined_subdir)))
85
+ other_ids = set(_complex_ids(os.path.join(args.pdbbind_dir, args.other_pl_subdir)))
86
+
87
+ ids_refined = [pdbid for pdbid in pdbids if pdbid in refined_ids]
88
+ ids_other = [pdbid for pdbid in pdbids if pdbid in other_ids]
89
+
90
+ results = run_for_subset(ids_refined, args.refined_subdir, ligids, args)
91
+ results += run_for_subset(ids_other, args.other_pl_subdir, ligids, args)
92
+
93
+ os.makedirs(args.outdir, exist_ok=True)
94
+ for pdbid, payload in results:
95
+ df = pd.DataFrame(zip(*payload), columns=["#code_ligand_num", "score"])
96
+ df["#code_ligand_num"] = df["#code_ligand_num"].str.split("-").apply(lambda item: item[0])
97
+ df.to_csv(os.path.join(args.outdir, f"{pdbid}_score.dat"), index=False, sep="\t")
98
+
99
+ with open(f"{args.outprefix}_screening.pkl", "wb") as handle:
100
+ pickle.dump(results, handle)
101
+
102
+
103
+ if __name__ == "__main__":
104
+ main()
models/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ """GenScore protein-ligand scoring model integration."""
2
+
models/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (232 Bytes). View file
 
models/__pycache__/inference.cpython-311.pyc ADDED
Binary file (11.4 kB). View file
 
models/__pycache__/train.cpython-311.pyc ADDED
Binary file (9.66 kB). View file
 
models/inference.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+
4
+ import MDAnalysis as mda
5
+ import numpy as np
6
+ import pandas as pd
7
+ import torch as th
8
+ import torch.multiprocessing
9
+ from torch_geometric.loader import DataLoader
10
+
11
+ from onescience.datapipes.genscore.data import VSDataset
12
+ from models.model.model import GatedGCN, GenScore, GraphTransformer
13
+ from onescience.metrics.genscore.utils import run_an_eval_epoch
14
+
15
+ torch.multiprocessing.set_sharing_strategy("file_system")
16
+
17
+
18
+ def _default_model_path() -> str:
19
+ return os.path.join(
20
+ os.path.dirname(__file__),
21
+ "..",
22
+ "..",
23
+ "..",
24
+ "..",
25
+ "examples",
26
+ "biosciences",
27
+ "genscore",
28
+ "trained_models",
29
+ "GT_0.0_1.pth",
30
+ )
31
+
32
+
33
+ def parse_args():
34
+ parser = argparse.ArgumentParser(description="Run GenScore protein-ligand scoring.")
35
+ parser.add_argument("-p", "--prot", required=True, help="Input protein or pocket file (.pdb).")
36
+ parser.add_argument("-l", "--lig", required=True, help="Input ligand file (.sdf/.mol2).")
37
+ parser.add_argument(
38
+ "-m",
39
+ "--model",
40
+ default=_default_model_path(),
41
+ help="Path to a trained GenScore checkpoint.",
42
+ )
43
+ parser.add_argument(
44
+ "-e",
45
+ "--encoder",
46
+ default="gt",
47
+ choices=["gt", "gatedgcn"],
48
+ help="Protein and ligand graph encoder.",
49
+ )
50
+ parser.add_argument("-o", "--outprefix", default="out", help="Output file prefix.")
51
+ parser.add_argument(
52
+ "-gen_pocket",
53
+ "--gen_pocket",
54
+ action="store_true",
55
+ default=False,
56
+ help="Generate a pocket from the input protein.",
57
+ )
58
+ parser.add_argument(
59
+ "-c",
60
+ "--cutoff",
61
+ default=10.0,
62
+ type=float,
63
+ help="Pocket and interaction cutoff distance.",
64
+ )
65
+ parser.add_argument("-rl", "--reflig", default=None, help="Reference ligand for pocket generation.")
66
+ parser.add_argument(
67
+ "-pl",
68
+ "--parallel",
69
+ default=False,
70
+ action="store_true",
71
+ help="Build ligand graphs in parallel.",
72
+ )
73
+ parser.add_argument(
74
+ "-ac",
75
+ "--atom_contribution",
76
+ default=False,
77
+ action="store_true",
78
+ help="Compute atom-level score contributions.",
79
+ )
80
+ parser.add_argument(
81
+ "-rc",
82
+ "--res_contribution",
83
+ default=False,
84
+ action="store_true",
85
+ help="Compute residue-level score contributions.",
86
+ )
87
+ parser.add_argument("--batch_size", type=int, default=128)
88
+ parser.add_argument("--num_workers", type=int, default=10)
89
+ args = parser.parse_args()
90
+
91
+ if args.gen_pocket and args.reflig is None:
92
+ raise ValueError("If pocket generation is enabled, --reflig must be provided.")
93
+ if args.atom_contribution and args.res_contribution:
94
+ raise ValueError("Only one of atom_contribution and res_contribution is supported.")
95
+ return args
96
+
97
+
98
+ def _build_encoder(encoder, kwargs):
99
+ if encoder == "gt":
100
+ ligmodel = GraphTransformer(
101
+ in_channels=kwargs["num_node_featsl"],
102
+ edge_features=kwargs["num_edge_featsl"],
103
+ num_hidden_channels=kwargs["hidden_dim0"],
104
+ activ_fn=th.nn.SiLU(),
105
+ transformer_residual=True,
106
+ num_attention_heads=4,
107
+ norm_to_apply="batch",
108
+ dropout_rate=0.15,
109
+ num_layers=6,
110
+ )
111
+ protmodel = GraphTransformer(
112
+ in_channels=kwargs["num_node_featsp"],
113
+ edge_features=kwargs["num_edge_featsp"],
114
+ num_hidden_channels=kwargs["hidden_dim0"],
115
+ activ_fn=th.nn.SiLU(),
116
+ transformer_residual=True,
117
+ num_attention_heads=4,
118
+ norm_to_apply="batch",
119
+ dropout_rate=0.15,
120
+ num_layers=6,
121
+ )
122
+ else:
123
+ ligmodel = GatedGCN(
124
+ in_channels=kwargs["num_node_featsl"],
125
+ edge_features=kwargs["num_edge_featsl"],
126
+ num_hidden_channels=kwargs["hidden_dim0"],
127
+ residual=True,
128
+ dropout_rate=0.15,
129
+ equivstable_pe=False,
130
+ num_layers=6,
131
+ )
132
+ protmodel = GatedGCN(
133
+ in_channels=kwargs["num_node_featsp"],
134
+ edge_features=kwargs["num_edge_featsp"],
135
+ num_hidden_channels=kwargs["hidden_dim0"],
136
+ residual=True,
137
+ dropout_rate=0.15,
138
+ equivstable_pe=False,
139
+ num_layers=6,
140
+ )
141
+ return ligmodel, protmodel
142
+
143
+
144
+ def scoring(
145
+ prot,
146
+ lig,
147
+ modpath,
148
+ cut=10.0,
149
+ gen_pocket=False,
150
+ reflig=None,
151
+ encoder="gt",
152
+ atom_contribution=False,
153
+ res_contribution=False,
154
+ explicit_H=False,
155
+ use_chirality=True,
156
+ parallel=False,
157
+ **kwargs,
158
+ ):
159
+ data = VSDataset(
160
+ ligs=lig,
161
+ prot=prot,
162
+ cutoff=cut,
163
+ gen_pocket=gen_pocket,
164
+ reflig=reflig,
165
+ explicit_H=explicit_H,
166
+ use_chirality=use_chirality,
167
+ parallel=parallel,
168
+ )
169
+ test_loader = DataLoader(
170
+ dataset=data,
171
+ batch_size=kwargs["batch_size"],
172
+ shuffle=False,
173
+ num_workers=kwargs["num_workers"],
174
+ )
175
+
176
+ ligmodel, protmodel = _build_encoder(encoder, kwargs)
177
+ model = GenScore(
178
+ ligmodel,
179
+ protmodel,
180
+ in_channels=kwargs["hidden_dim0"],
181
+ hidden_dim=kwargs["hidden_dim"],
182
+ n_gaussians=kwargs["n_gaussians"],
183
+ dropout_rate=kwargs["dropout_rate"],
184
+ dist_threhold=kwargs["dist_threhold"],
185
+ ).to(kwargs["device"])
186
+
187
+ checkpoint = th.load(modpath, map_location=th.device(kwargs["device"]))
188
+ model.load_state_dict(checkpoint["model_state_dict"])
189
+
190
+ if atom_contribution:
191
+ preds, at_contrs, _ = run_an_eval_epoch(
192
+ model,
193
+ test_loader,
194
+ pred=True,
195
+ atom_contribution=True,
196
+ res_contribution=False,
197
+ dist_threhold=kwargs["dist_threhold"],
198
+ device=kwargs["device"],
199
+ )
200
+ atids = [f"{a.GetSymbol()}{a.GetIdx()}" for a in data.ligs[0].GetAtoms()]
201
+ return data.ids, preds, atids, at_contrs
202
+
203
+ if res_contribution:
204
+ preds, _, res_contrs = run_an_eval_epoch(
205
+ model,
206
+ test_loader,
207
+ pred=True,
208
+ atom_contribution=False,
209
+ res_contribution=True,
210
+ dist_threhold=kwargs["dist_threhold"],
211
+ device=kwargs["device"],
212
+ )
213
+ universe = mda.Universe(data.prot)
214
+ resids = [
215
+ f"{chain_id}_{resname}{resid}"
216
+ for chain_id, resname, resid in zip(
217
+ universe.residues.chainIDs,
218
+ universe.residues.resnames,
219
+ universe.residues.resids,
220
+ )
221
+ ]
222
+ return data.ids, preds, resids, res_contrs
223
+
224
+ preds = run_an_eval_epoch(
225
+ model,
226
+ test_loader,
227
+ pred=True,
228
+ dist_threhold=kwargs["dist_threhold"],
229
+ device=kwargs["device"],
230
+ )
231
+ return data.ids, preds
232
+
233
+
234
+ def _runtime_args(args):
235
+ return {
236
+ "batch_size": args.batch_size,
237
+ "dist_threhold": 5.0,
238
+ "device": "cuda" if th.cuda.is_available() else "cpu",
239
+ "num_workers": args.num_workers,
240
+ "num_node_featsp": 41,
241
+ "num_node_featsl": 41,
242
+ "num_edge_featsp": 5,
243
+ "num_edge_featsl": 10,
244
+ "hidden_dim0": 128,
245
+ "hidden_dim": 128,
246
+ "n_gaussians": 10,
247
+ "dropout_rate": 0.15,
248
+ }
249
+
250
+
251
+ def main():
252
+ inargs = parse_args()
253
+ runtime = _runtime_args(inargs)
254
+ common = {
255
+ "prot": inargs.prot,
256
+ "lig": inargs.lig,
257
+ "modpath": inargs.model,
258
+ "cut": inargs.cutoff,
259
+ "gen_pocket": inargs.gen_pocket,
260
+ "reflig": inargs.reflig,
261
+ "encoder": inargs.encoder,
262
+ "explicit_H": False,
263
+ "use_chirality": True,
264
+ "parallel": inargs.parallel,
265
+ **runtime,
266
+ }
267
+
268
+ if inargs.atom_contribution:
269
+ ids, scores, atids, at_contrs = scoring(atom_contribution=True, **common)
270
+ df = pd.DataFrame(at_contrs).T
271
+ df.columns = ids
272
+ df.index = atids
273
+ df = df[df.apply(np.sum, axis=1) != 0].T
274
+ dfx = pd.DataFrame(zip(*(ids, scores)), columns=["id", "score"])
275
+ dfx.index = dfx.id
276
+ df = pd.concat([dfx["score"], df], axis=1)
277
+ df.sort_values("score", ascending=False, inplace=True)
278
+ df.to_csv(f"{inargs.outprefix}_at.csv")
279
+ elif inargs.res_contribution:
280
+ ids, scores, resids, res_contrs = scoring(res_contribution=True, **common)
281
+ df = pd.DataFrame(res_contrs).T
282
+ df.columns = ids
283
+ df.index = resids
284
+ df = df[df.apply(np.sum, axis=1) != 0].T
285
+ dfx = pd.DataFrame(zip(*(ids, scores)), columns=["id", "score"])
286
+ dfx.index = dfx.id
287
+ df = pd.concat([dfx["score"], df], axis=1)
288
+ df.sort_values("score", ascending=False, inplace=True)
289
+ df.to_csv(f"{inargs.outprefix}_res.csv")
290
+ else:
291
+ ids, scores = scoring(**common)
292
+ df = pd.DataFrame(zip(*(ids, scores)), columns=["id", "score"])
293
+ df.sort_values("score", ascending=False, inplace=True)
294
+ df.to_csv(f"{inargs.outprefix}.csv", index=False)
295
+
296
+
297
+ if __name__ == "__main__":
298
+ main()
models/model/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
models/model/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (174 Bytes). View file
 
models/model/__pycache__/model.cpython-311.pyc ADDED
Binary file (27.8 kB). View file
 
models/model/layer/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
models/model/layer/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (180 Bytes). View file
 
models/model/layer/__pycache__/gatedgcn_layer.cpython-311.pyc ADDED
Binary file (6.99 kB). View file
 
models/model/layer/gatedgcn_layer.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import torch_geometric.nn as pyg_nn
5
+ from torch_geometric.graphgym.models.layer import LayerConfig
6
+ from torch_scatter import scatter
7
+
8
+ from torch_geometric.graphgym.config import cfg
9
+ from torch_geometric.graphgym.register import register_layer
10
+
11
+
12
+ class GatedGCNLayer(pyg_nn.conv.MessagePassing):
13
+ """
14
+ GatedGCN layer
15
+ Residual Gated Graph ConvNets
16
+ https://arxiv.org/pdf/1711.07553.pdf
17
+ """
18
+ def __init__(self, in_dim, out_dim, dropout, residual,
19
+ equivstable_pe=False, **kwargs):
20
+ super().__init__(**kwargs)
21
+ self.A = pyg_nn.Linear(in_dim, out_dim, bias=True)
22
+ self.B = pyg_nn.Linear(in_dim, out_dim, bias=True)
23
+ self.C = pyg_nn.Linear(in_dim, out_dim, bias=True)
24
+ self.D = pyg_nn.Linear(in_dim, out_dim, bias=True)
25
+ self.E = pyg_nn.Linear(in_dim, out_dim, bias=True)
26
+
27
+ # Handling for Equivariant and Stable PE using LapPE
28
+ # ICLR 2022 https://openreview.net/pdf?id=e95i1IHcWj
29
+ self.EquivStablePE = equivstable_pe
30
+ if self.EquivStablePE:
31
+ self.mlp_r_ij = nn.Sequential(
32
+ nn.Linear(1, out_dim), nn.ReLU(),
33
+ nn.Linear(out_dim, 1),
34
+ nn.Sigmoid())
35
+
36
+ self.bn_node_x = nn.BatchNorm1d(out_dim)
37
+ self.bn_edge_e = nn.BatchNorm1d(out_dim)
38
+ self.dropout = dropout
39
+ self.residual = residual
40
+ self.e = None
41
+
42
+ def forward(self, batch):
43
+ x, e, edge_index = batch.x, batch.edge_attr, batch.edge_index
44
+
45
+ """
46
+ x : [n_nodes, in_dim]
47
+ e : [n_edges, in_dim]
48
+ edge_index : [2, n_edges]
49
+ """
50
+ if self.residual:
51
+ x_in = x
52
+ e_in = e
53
+
54
+ Ax = self.A(x)
55
+ Bx = self.B(x)
56
+ Ce = self.C(e)
57
+ Dx = self.D(x)
58
+ Ex = self.E(x)
59
+
60
+ # Handling for Equivariant and Stable PE using LapPE
61
+ # ICLR 2022 https://openreview.net/pdf?id=e95i1IHcWj
62
+ pe_LapPE = batch.pe_EquivStableLapPE if self.EquivStablePE else None
63
+
64
+ x, e = self.propagate(edge_index,
65
+ Bx=Bx, Dx=Dx, Ex=Ex, Ce=Ce,
66
+ e=e, Ax=Ax,
67
+ PE=pe_LapPE)
68
+
69
+ x = self.bn_node_x(x)
70
+ e = self.bn_edge_e(e)
71
+
72
+ x = F.relu(x)
73
+ e = F.relu(e)
74
+
75
+ x = F.dropout(x, self.dropout, training=self.training)
76
+ e = F.dropout(e, self.dropout, training=self.training)
77
+
78
+ if self.residual:
79
+ x = x_in + x
80
+ e = e_in + e
81
+
82
+ batch.x = x
83
+ batch.edge_attr = e
84
+
85
+ return batch
86
+
87
+ def message(self, Dx_i, Ex_j, PE_i, PE_j, Ce):
88
+ """
89
+ {}x_i : [n_edges, out_dim]
90
+ {}x_j : [n_edges, out_dim]
91
+ {}e : [n_edges, out_dim]
92
+ """
93
+ e_ij = Dx_i + Ex_j + Ce
94
+ sigma_ij = torch.sigmoid(e_ij)
95
+
96
+ # Handling for Equivariant and Stable PE using LapPE
97
+ # ICLR 2022 https://openreview.net/pdf?id=e95i1IHcWj
98
+ if self.EquivStablePE:
99
+ r_ij = ((PE_i - PE_j) ** 2).sum(dim=-1, keepdim=True)
100
+ r_ij = self.mlp_r_ij(r_ij) # the MLP is 1 dim --> hidden_dim --> 1 dim
101
+ sigma_ij = sigma_ij * r_ij
102
+
103
+ self.e = e_ij
104
+ return sigma_ij
105
+
106
+ def aggregate(self, sigma_ij, index, Bx_j, Bx):
107
+ """
108
+ sigma_ij : [n_edges, out_dim] ; is the output from message() function
109
+ index : [n_edges]
110
+ {}x_j : [n_edges, out_dim]
111
+ """
112
+ dim_size = Bx.shape[0] # or None ?? <--- Double check this
113
+
114
+ sum_sigma_x = sigma_ij * Bx_j
115
+ numerator_eta_xj = scatter(sum_sigma_x, index, 0, None, dim_size,
116
+ reduce='sum')
117
+
118
+ sum_sigma = sigma_ij
119
+ denominator_eta_xj = scatter(sum_sigma, index, 0, None, dim_size,
120
+ reduce='sum')
121
+
122
+ out = numerator_eta_xj / (denominator_eta_xj + 1e-6)
123
+ return out
124
+
125
+ def update(self, aggr_out, Ax):
126
+ """
127
+ aggr_out : [n_nodes, out_dim] ; is the output from aggregate() function after the aggregation
128
+ {}x : [n_nodes, out_dim]
129
+ """
130
+ x = Ax + aggr_out
131
+ e_out = self.e
132
+ del self.e
133
+ return x, e_out
134
+
135
+
136
+ @register_layer('gatedgcnconv')
137
+ class GatedGCNGraphGymLayer(nn.Module):
138
+ """GatedGCN layer.
139
+ Residual Gated Graph ConvNets
140
+ https://arxiv.org/pdf/1711.07553.pdf
141
+ """
142
+ def __init__(self, layer_config: LayerConfig, **kwargs):
143
+ super().__init__()
144
+ self.model = GatedGCNLayer(in_dim=layer_config.dim_in,
145
+ out_dim=layer_config.dim_out,
146
+ dropout=0., # Dropout is handled by GraphGym's `GeneralLayer` wrapper
147
+ residual=False, # Residual connections are handled by GraphGym's `GNNStackStage` wrapper
148
+ **kwargs)
149
+
150
+ def forward(self, batch):
151
+ return self.model(batch)
models/model/model.py ADDED
@@ -0,0 +1,563 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch as th
2
+ import torch.nn.functional as F
3
+ import numpy as np
4
+ from torch_scatter import scatter_add
5
+ from torch_geometric.utils import to_dense_batch
6
+ from torch import nn
7
+
8
+
9
+ def glorot_orthogonal(tensor, scale):
10
+ """Initialize a tensor's values according to an orthogonal Glorot initialization scheme."""
11
+ if tensor is not None:
12
+ th.nn.init.orthogonal_(tensor.data)
13
+ scale /= ((tensor.size(-2) + tensor.size(-1)) * tensor.var())
14
+ tensor.data *= scale.sqrt()
15
+
16
+
17
+ class MultiHeadAttentionLayer(nn.Module):
18
+ """Compute attention scores with a DGLGraph's node and edge (geometric) features."""
19
+ def __init__(self, num_input_feats, num_output_feats,
20
+ num_heads, using_bias=False, update_edge_feats=True):
21
+ super(MultiHeadAttentionLayer, self).__init__()
22
+
23
+ # Declare shared variables
24
+ self.num_output_feats = num_output_feats
25
+ self.num_heads = num_heads
26
+ self.using_bias = using_bias
27
+ self.update_edge_feats = update_edge_feats
28
+
29
+ # Define node features' query, key, and value tensors, and define edge features' projection tensors
30
+ self.Q = nn.Linear(num_input_feats, self.num_output_feats * self.num_heads, bias=using_bias)
31
+ self.K = nn.Linear(num_input_feats, self.num_output_feats * self.num_heads, bias=using_bias)
32
+ self.V = nn.Linear(num_input_feats, self.num_output_feats * self.num_heads, bias=using_bias)
33
+ self.edge_feats_projection = nn.Linear(num_input_feats, self.num_output_feats * self.num_heads, bias=using_bias)
34
+
35
+ self.reset_parameters()
36
+
37
+ def reset_parameters(self):
38
+ """Reinitialize learnable parameters."""
39
+ scale = 2.0
40
+ if self.using_bias:
41
+ glorot_orthogonal(self.Q.weight, scale=scale)
42
+ self.Q.bias.data.fill_(0)
43
+
44
+ glorot_orthogonal(self.K.weight, scale=scale)
45
+ self.K.bias.data.fill_(0)
46
+
47
+ glorot_orthogonal(self.V.weight, scale=scale)
48
+ self.V.bias.data.fill_(0)
49
+
50
+ glorot_orthogonal(self.edge_feats_projection.weight, scale=scale)
51
+ self.edge_feats_projection.bias.data.fill_(0)
52
+ else:
53
+ glorot_orthogonal(self.Q.weight, scale=scale)
54
+ glorot_orthogonal(self.K.weight, scale=scale)
55
+ glorot_orthogonal(self.V.weight, scale=scale)
56
+ glorot_orthogonal(self.edge_feats_projection.weight, scale=scale)
57
+
58
+ def propagate_attention(self, edge_index, node_feats_q, node_feats_k, node_feats_v, edge_feats_projection):
59
+ row, col = edge_index
60
+ e_out = None
61
+ # Compute attention scores
62
+ alpha = node_feats_k[row] * node_feats_q[col]
63
+ # Scale and clip attention scores
64
+ alpha = (alpha / np.sqrt(self.num_output_feats)).clamp(-5.0,5.0)
65
+ # Use available edge features to modify the attention scores
66
+ alpha = alpha * edge_feats_projection
67
+ # Copy edge features as e_out to be passed to edge_feats_MLP
68
+ if self.update_edge_feats:
69
+ e_out = alpha
70
+
71
+ # Apply softmax to attention scores, followed by clipping
72
+ alphax = th.exp((alpha.sum(-1, keepdim=True)).clamp(-5.0,5.0))
73
+ # Send weighted values to target nodes
74
+ wV = scatter_add(node_feats_v[row]*alphax, col, dim=0, dim_size=node_feats_q.size(0))
75
+ z = scatter_add(alphax, col, dim=0, dim_size=node_feats_q.size(0))
76
+ return wV, z, e_out
77
+
78
+ def forward(self, x, edge_attr, edge_index):
79
+ node_feats_q = self.Q(x).view(-1, self.num_heads, self.num_output_feats)
80
+ node_feats_k = self.K(x).view(-1, self.num_heads, self.num_output_feats)
81
+ node_feats_v = self.V(x).view(-1, self.num_heads, self.num_output_feats)
82
+ edge_feats_projection = self.edge_feats_projection(edge_attr).view(-1, self.num_heads, self.num_output_feats)
83
+ wV, z, e_out = self.propagate_attention(edge_index, node_feats_q, node_feats_k, node_feats_v, edge_feats_projection)
84
+
85
+ h_out = wV / (z + th.full_like(z, 1e-6))
86
+ return h_out, e_out
87
+
88
+
89
+ class GraphTransformerModule(nn.Module):
90
+ """A Graph Transformer module (equivalent to one layer of graph convolutions)."""
91
+ def __init__(
92
+ self,
93
+ num_hidden_channels,
94
+ activ_fn=nn.SiLU(),
95
+ residual=True,
96
+ num_attention_heads=4,
97
+ norm_to_apply='batch',
98
+ dropout_rate=0.1,
99
+ num_layers=4,
100
+ ):
101
+ super(GraphTransformerModule, self).__init__()
102
+
103
+ # Record parameters given
104
+ self.activ_fn = activ_fn
105
+ self.residual = residual
106
+ self.num_attention_heads = num_attention_heads
107
+ self.norm_to_apply = norm_to_apply
108
+ self.dropout_rate = dropout_rate
109
+ self.num_layers = num_layers
110
+
111
+ # --------------------
112
+ # Transformer Module
113
+ # --------------------
114
+ # Define all modules related to a Geometric Transformer module
115
+ self.apply_layer_norm = 'layer' in self.norm_to_apply.lower()
116
+
117
+ self.num_hidden_channels, self.num_output_feats = num_hidden_channels, num_hidden_channels
118
+ if self.apply_layer_norm:
119
+ self.layer_norm1_node_feats = nn.LayerNorm(self.num_output_feats)
120
+ self.layer_norm1_edge_feats = nn.LayerNorm(self.num_output_feats)
121
+ else: # Otherwise, default to using batch normalization
122
+ self.batch_norm1_node_feats = nn.BatchNorm1d(self.num_output_feats)
123
+ self.batch_norm1_edge_feats = nn.BatchNorm1d(self.num_output_feats)
124
+
125
+ self.mha_module = MultiHeadAttentionLayer(
126
+ self.num_hidden_channels,
127
+ self.num_output_feats // self.num_attention_heads,
128
+ self.num_attention_heads,
129
+ self.num_hidden_channels != self.num_output_feats, # Only use bias if a Linear() has to change sizes
130
+ update_edge_feats=True
131
+ )
132
+
133
+ self.O_node_feats = nn.Linear(self.num_output_feats, self.num_output_feats)
134
+ self.O_edge_feats = nn.Linear(self.num_output_feats, self.num_output_feats)
135
+
136
+ # MLP for node features
137
+ dropout = nn.Dropout(p=self.dropout_rate) if self.dropout_rate > 0.0 else nn.Identity()
138
+ self.node_feats_MLP = nn.ModuleList([
139
+ nn.Linear(self.num_output_feats, self.num_output_feats * 2, bias=False),
140
+ self.activ_fn,
141
+ dropout,
142
+ nn.Linear(self.num_output_feats * 2, self.num_output_feats, bias=False)
143
+ ])
144
+
145
+ if self.apply_layer_norm:
146
+ self.layer_norm2_node_feats = nn.LayerNorm(self.num_output_feats)
147
+ self.layer_norm2_edge_feats = nn.LayerNorm(self.num_output_feats)
148
+ else: # Otherwise, default to using batch normalization
149
+ self.batch_norm2_node_feats = nn.BatchNorm1d(self.num_output_feats)
150
+ self.batch_norm2_edge_feats = nn.BatchNorm1d(self.num_output_feats)
151
+
152
+ # MLP for edge features
153
+ self.edge_feats_MLP = nn.ModuleList([
154
+ nn.Linear(self.num_output_feats, self.num_output_feats * 2, bias=False),
155
+ self.activ_fn,
156
+ dropout,
157
+ nn.Linear(self.num_output_feats * 2, self.num_output_feats, bias=False)
158
+ ])
159
+
160
+ self.reset_parameters()
161
+
162
+ def reset_parameters(self):
163
+ """Reinitialize learnable parameters."""
164
+ scale = 2.0
165
+ glorot_orthogonal(self.O_node_feats.weight, scale=scale)
166
+ self.O_node_feats.bias.data.fill_(0)
167
+ glorot_orthogonal(self.O_edge_feats.weight, scale=scale)
168
+ self.O_edge_feats.bias.data.fill_(0)
169
+
170
+ for layer in self.node_feats_MLP:
171
+ if hasattr(layer, 'weight'): # Skip initialization for activation functions
172
+ glorot_orthogonal(layer.weight, scale=scale)
173
+
174
+ for layer in self.edge_feats_MLP:
175
+ if hasattr(layer, 'weight'):
176
+ glorot_orthogonal(layer.weight, scale=scale)
177
+
178
+ def run_gt_layer(self, data, node_feats, edge_feats):
179
+ """Perform a forward pass of geometric attention using a multi-head attention (MHA) module."""
180
+ node_feats_in1 = node_feats # Cache node representations for first residual connection
181
+ edge_feats_in1 = edge_feats # Cache edge representations for first residual connection
182
+
183
+ # Apply first round of normalization before applying geometric attention, for performance enhancement
184
+ if self.apply_layer_norm:
185
+ node_feats = self.layer_norm1_node_feats(node_feats)
186
+ edge_feats = self.layer_norm1_edge_feats(edge_feats)
187
+ else: # Otherwise, default to using batch normalization
188
+ node_feats = self.batch_norm1_node_feats(node_feats)
189
+ edge_feats = self.batch_norm1_edge_feats(edge_feats)
190
+
191
+ # Get multi-head attention output using provided node and edge representations
192
+ node_attn_out, edge_attn_out = self.mha_module(node_feats, edge_feats, data.edge_index)
193
+
194
+ node_feats = node_attn_out.view(-1, self.num_output_feats)
195
+ edge_feats = edge_attn_out.view(-1, self.num_output_feats)
196
+
197
+ node_feats = F.dropout(node_feats, self.dropout_rate, training=self.training)
198
+ edge_feats = F.dropout(edge_feats, self.dropout_rate, training=self.training)
199
+
200
+ node_feats = self.O_node_feats(node_feats)
201
+ edge_feats = self.O_edge_feats(edge_feats)
202
+
203
+ # Make first residual connection
204
+ if self.residual:
205
+ node_feats = node_feats_in1 + node_feats # Make first node residual connection
206
+ edge_feats = edge_feats_in1 + edge_feats # Make first edge residual connection
207
+
208
+ node_feats_in2 = node_feats # Cache node representations for second residual connection
209
+ edge_feats_in2 = edge_feats # Cache edge representations for second residual connection
210
+
211
+ # Apply second round of normalization after first residual connection has been made
212
+ if self.apply_layer_norm:
213
+ node_feats = self.layer_norm2_node_feats(node_feats)
214
+ edge_feats = self.layer_norm2_edge_feats(edge_feats)
215
+ else: # Otherwise, default to using batch normalization
216
+ node_feats = self.batch_norm2_node_feats(node_feats)
217
+ edge_feats = self.batch_norm2_edge_feats(edge_feats)
218
+
219
+ # Apply MLPs for node and edge features
220
+ for layer in self.node_feats_MLP:
221
+ node_feats = layer(node_feats)
222
+ for layer in self.edge_feats_MLP:
223
+ edge_feats = layer(edge_feats)
224
+
225
+ # Make second residual connection
226
+ if self.residual:
227
+ node_feats = node_feats_in2 + node_feats # Make second node residual connection
228
+ edge_feats = edge_feats_in2 + edge_feats # Make second edge residual connection
229
+
230
+ # Return edge representations along with node representations (for tasks other than interface prediction)
231
+ return node_feats, edge_feats
232
+
233
+ def forward(self, data, node_feats, edge_feats):
234
+ """Perform a forward pass of a Geometric Transformer to get intermediate node and edge representations."""
235
+ node_feats, edge_feats = self.run_gt_layer(data, node_feats, edge_feats)
236
+ return node_feats, edge_feats
237
+
238
+
239
+ class FinalGraphTransformerModule(nn.Module):
240
+ """A (final layer) Graph Transformer module that combines node and edge representations using self-attention."""
241
+ def __init__(self,
242
+ num_hidden_channels,
243
+ activ_fn=nn.SiLU(),
244
+ residual=True,
245
+ num_attention_heads=4,
246
+ norm_to_apply='batch',
247
+ dropout_rate=0.1,
248
+ num_layers=4):
249
+ super(FinalGraphTransformerModule, self).__init__()
250
+
251
+ # Record parameters given
252
+ self.activ_fn = activ_fn
253
+ self.residual = residual
254
+ self.num_attention_heads = num_attention_heads
255
+ self.norm_to_apply = norm_to_apply
256
+ self.dropout_rate = dropout_rate
257
+ self.num_layers = num_layers
258
+
259
+ # --------------------
260
+ # Transformer Module
261
+ # --------------------
262
+ # Define all modules related to a Geometric Transformer module
263
+ self.apply_layer_norm = 'layer' in self.norm_to_apply.lower()
264
+
265
+ self.num_hidden_channels, self.num_output_feats = num_hidden_channels, num_hidden_channels
266
+ if self.apply_layer_norm:
267
+ self.layer_norm1_node_feats = nn.LayerNorm(self.num_output_feats)
268
+ self.layer_norm1_edge_feats = nn.LayerNorm(self.num_output_feats)
269
+ else: # Otherwise, default to using batch normalization
270
+ self.batch_norm1_node_feats = nn.BatchNorm1d(self.num_output_feats)
271
+ self.batch_norm1_edge_feats = nn.BatchNorm1d(self.num_output_feats)
272
+
273
+ self.mha_module = MultiHeadAttentionLayer(
274
+ self.num_hidden_channels,
275
+ self.num_output_feats // self.num_attention_heads,
276
+ self.num_attention_heads,
277
+ self.num_hidden_channels != self.num_output_feats, # Only use bias if a Linear() has to change sizes
278
+ update_edge_feats=False)
279
+
280
+ self.O_node_feats = nn.Linear(self.num_output_feats, self.num_output_feats)
281
+
282
+ # MLP for node features
283
+ dropout = nn.Dropout(p=self.dropout_rate) if self.dropout_rate > 0.0 else nn.Identity()
284
+ self.node_feats_MLP = nn.ModuleList([
285
+ nn.Linear(self.num_output_feats, self.num_output_feats * 2, bias=False),
286
+ self.activ_fn,
287
+ dropout,
288
+ nn.Linear(self.num_output_feats * 2, self.num_output_feats, bias=False)
289
+ ])
290
+
291
+ if self.apply_layer_norm:
292
+ self.layer_norm2_node_feats = nn.LayerNorm(self.num_output_feats)
293
+ else: # Otherwise, default to using batch normalization
294
+ self.batch_norm2_node_feats = nn.BatchNorm1d(self.num_output_feats)
295
+
296
+ self.reset_parameters()
297
+
298
+ def reset_parameters(self):
299
+ """Reinitialize learnable parameters."""
300
+ scale = 2.0
301
+ glorot_orthogonal(self.O_node_feats.weight, scale=scale)
302
+ self.O_node_feats.bias.data.fill_(0)
303
+
304
+ for layer in self.node_feats_MLP:
305
+ if hasattr(layer, 'weight'): # Skip initialization for activation functions
306
+ glorot_orthogonal(layer.weight, scale=scale)
307
+
308
+ #glorot_orthogonal(self.conformation_module.weight, scale=scale)
309
+
310
+ def run_gt_layer(self, data, node_feats, edge_feats):
311
+ """Perform a forward pass of geometric attention using a multi-head attention (MHA) module."""
312
+ node_feats_in1 = node_feats # Cache node representations for first residual connection
313
+ #edge_feats = self.conformation_module(edge_feats)
314
+
315
+ # Apply first round of normalization before applying geometric attention, for performance enhancement
316
+ if self.apply_layer_norm:
317
+ node_feats = self.layer_norm1_node_feats(node_feats)
318
+ edge_feats = self.layer_norm1_edge_feats(edge_feats)
319
+ else: # Otherwise, default to using batch normalization
320
+ node_feats = self.batch_norm1_node_feats(node_feats)
321
+ edge_feats = self.batch_norm1_edge_feats(edge_feats)
322
+
323
+ # Get multi-head attention output using provided node and edge representations
324
+ node_attn_out, _ = self.mha_module(node_feats, edge_feats, data.edge_index)
325
+ node_feats = node_attn_out.view(-1, self.num_output_feats)
326
+ node_feats = F.dropout(node_feats, self.dropout_rate, training=self.training)
327
+ node_feats = self.O_node_feats(node_feats)
328
+
329
+ # Make first residual connection
330
+ if self.residual:
331
+ node_feats = node_feats_in1 + node_feats # Make first node residual connection
332
+
333
+ node_feats_in2 = node_feats # Cache node representations for second residual connection
334
+
335
+ # Apply second round of normalization after first residual connection has been made
336
+ if self.apply_layer_norm:
337
+ node_feats = self.layer_norm2_node_feats(node_feats)
338
+ else: # Otherwise, default to using batch normalization
339
+ node_feats = self.batch_norm2_node_feats(node_feats)
340
+
341
+ # Apply MLP for node features
342
+ for layer in self.node_feats_MLP:
343
+ node_feats = layer(node_feats)
344
+
345
+ # Make second residual connection
346
+ if self.residual:
347
+ node_feats = node_feats_in2 + node_feats # Make second node residual connection
348
+
349
+ # Return node representations
350
+ return node_feats
351
+
352
+ def forward(self, data, node_feats, edge_feats):
353
+ """Perform a forward pass of a Geometric Transformer to get final node representations."""
354
+ node_feats = self.run_gt_layer(data, node_feats, edge_feats)
355
+ return node_feats
356
+
357
+
358
+ class GraphTransformer(nn.Module):
359
+ """A graph transformer
360
+ """
361
+ def __init__(
362
+ self,
363
+ in_channels,
364
+ edge_features=10,
365
+ num_hidden_channels=128,
366
+ activ_fn=nn.SiLU(),
367
+ transformer_residual=True,
368
+ num_attention_heads=4,
369
+ norm_to_apply='batch',
370
+ dropout_rate=0.1,
371
+ num_layers=4,
372
+ **kwargs
373
+ ):
374
+ super(GraphTransformer, self).__init__()
375
+
376
+ # Initialize model parameters
377
+ self.activ_fn = activ_fn
378
+ self.transformer_residual = transformer_residual
379
+ self.num_attention_heads = num_attention_heads
380
+ self.norm_to_apply = norm_to_apply
381
+ self.dropout_rate = dropout_rate
382
+ self.num_layers = num_layers
383
+
384
+ # --------------------
385
+ # Initializer Modules
386
+ # --------------------
387
+ # Define all modules related to edge and node initialization
388
+ self.node_encoder = nn.Linear(in_channels, num_hidden_channels)
389
+ self.edge_encoder = nn.Linear(edge_features, num_hidden_channels)
390
+ # --------------------
391
+ # Transformer Module
392
+ # --------------------
393
+ # Define all modules related to a variable number of Geometric Transformer modules
394
+ num_intermediate_layers = max(0, num_layers - 1)
395
+ gt_block_modules = [GraphTransformerModule(
396
+ num_hidden_channels=num_hidden_channels,
397
+ activ_fn=activ_fn,
398
+ residual=transformer_residual,
399
+ num_attention_heads=num_attention_heads,
400
+ norm_to_apply=norm_to_apply,
401
+ dropout_rate=dropout_rate,
402
+ num_layers=num_layers) for _ in range(num_intermediate_layers)]
403
+ if num_layers > 0:
404
+ gt_block_modules.extend([
405
+ FinalGraphTransformerModule(
406
+ num_hidden_channels=num_hidden_channels,
407
+ activ_fn=activ_fn,
408
+ residual=transformer_residual,
409
+ num_attention_heads=num_attention_heads,
410
+ norm_to_apply=norm_to_apply,
411
+ dropout_rate=dropout_rate,
412
+ num_layers=num_layers)])
413
+ self.gt_block = nn.ModuleList(gt_block_modules)
414
+
415
+ def forward(self, data):
416
+ node_feats = self.node_encoder(data.x)
417
+ edge_feats = self.edge_encoder(data.edge_attr)
418
+
419
+ # Apply a given number of intermediate geometric attention layers to the node and edge features given
420
+ for gt_layer in self.gt_block[:-1]:
421
+ node_feats, edge_feats = gt_layer(data, node_feats, edge_feats)
422
+
423
+ # Apply final layer to update node representations by merging current node and edge representations
424
+ node_feats = self.gt_block[-1](data, node_feats, edge_feats)
425
+ data.x = node_feats
426
+ data.edge_attr = edge_feats
427
+ #return node_feats
428
+ return data
429
+
430
+ #==============================================
431
+ from .layer.gatedgcn_layer import GatedGCNLayer
432
+
433
+
434
+ class GatedGCN(nn.Module):
435
+ """A graph transformer
436
+ """
437
+ def __init__(
438
+ self,
439
+ in_channels,
440
+ edge_features=10,
441
+ num_hidden_channels=128,
442
+ dropout_rate=0.1,
443
+ num_layers=4,
444
+ residual=True,
445
+ equivstable_pe=False,
446
+ **kwargs
447
+ ):
448
+ super(GatedGCN, self).__init__()
449
+
450
+ # Initialize model parameters
451
+ self.residual = residual
452
+ self.dropout_rate = dropout_rate
453
+ self.num_layers = num_layers
454
+
455
+ self.node_encoder = nn.Linear(in_channels, num_hidden_channels)
456
+ self.edge_encoder = nn.Linear(edge_features, num_hidden_channels)
457
+
458
+ gt_block_modules = [GatedGCNLayer(
459
+ num_hidden_channels,
460
+ num_hidden_channels,
461
+ dropout_rate,
462
+ residual,
463
+ equivstable_pe=equivstable_pe) for _ in range(num_layers)]
464
+
465
+ self.gt_block = nn.ModuleList(gt_block_modules)
466
+
467
+ def forward(self, data):
468
+ data.x = self.node_encoder(data.x)
469
+ data.edge_attr = self.edge_encoder(data.edge_attr)
470
+
471
+ # Apply a given number of intermediate geometric attention layers to the node and edge features given
472
+ for gt_layer in self.gt_block:
473
+ data = gt_layer(data)
474
+
475
+ # Apply final layer to update node representations by merging current node and edge representations
476
+ #data.x = node_feats
477
+ #data.edge_attr = edge_feats
478
+ #return node_feats
479
+ return data
480
+
481
+ #==============================================
482
+ class GenScore(nn.Module):
483
+ def __init__(self, ligand_model, target_model, in_channels, hidden_dim, n_gaussians, dropout_rate=0.15,
484
+ dist_threhold=1000):
485
+ super(GenScore, self).__init__()
486
+
487
+ self.ligand_model = ligand_model
488
+ self.target_model = target_model
489
+ self.MLP = nn.Sequential(nn.Linear(in_channels*2, hidden_dim),
490
+ nn.BatchNorm1d(hidden_dim),
491
+ nn.ELU(),
492
+ nn.Dropout(p=dropout_rate))
493
+
494
+ self.z_pi = nn.Linear(hidden_dim, n_gaussians)
495
+ self.z_sigma = nn.Linear(hidden_dim, n_gaussians)
496
+ self.z_mu = nn.Linear(hidden_dim, n_gaussians)
497
+ self.atom_types = nn.Linear(in_channels, 17)
498
+ self.bond_types = nn.Linear(in_channels*2, 4)
499
+
500
+ #self.device = 'cuda' if th.cuda.is_available() else 'cpu'
501
+ self.dist_threhold = dist_threhold
502
+
503
+ def forward(self, data_ligand, data_target):
504
+
505
+ h_l = self.ligand_model(data_ligand)
506
+ h_t = self.target_model(data_target)
507
+
508
+ h_l_x, l_mask = to_dense_batch(h_l.x, h_l.batch, fill_value=0)
509
+ h_t_x, t_mask = to_dense_batch(h_t.x, h_t.batch, fill_value=0)
510
+ h_l_pos, _ = to_dense_batch(h_l.pos, h_l.batch, fill_value=0)
511
+ h_t_pos, _ = to_dense_batch(h_t.pos, h_t.batch, fill_value=0)
512
+
513
+ #assert h_l_x.size(0) == h_t_x.size(0), 'Encountered unequal batch-sizes'
514
+ (B, N_l, C_out), N_t = h_l_x.size(), h_t_x.size(1)
515
+ self.B = B
516
+ self.N_l = N_l
517
+ self.N_t = N_t
518
+
519
+ # Combine and mask
520
+ h_l_x = h_l_x.unsqueeze(-2)
521
+ h_l_x = h_l_x.repeat(1, 1, N_t, 1) # [B, N_l, N_t, C_out]
522
+
523
+ h_t_x = h_t_x.unsqueeze(-3)
524
+ h_t_x = h_t_x.repeat(1, N_l, 1, 1) # [B, N_l, N_t, C_out]
525
+
526
+ C = th.cat((h_l_x, h_t_x), -1)
527
+ self.C_mask = C_mask = l_mask.view(B, N_l, 1) & t_mask.view(B, 1, N_t)
528
+ self.C = C = C[C_mask]
529
+ C = self.MLP(C)
530
+
531
+ # Get batch indexes for ligand-target combined features
532
+ C_batch = th.tensor(range(B)).unsqueeze(-1).unsqueeze(-1)
533
+ # LL 2026 UPDATE
534
+ C_mask = C_mask.to(C_batch.device)
535
+ C_batch = C_batch.repeat(1, N_l, N_t)[C_mask]
536
+
537
+ # Outputs
538
+ pi = F.softmax(self.z_pi(C), -1)
539
+ sigma = F.elu(self.z_sigma(C))+1.1
540
+ mu = F.elu(self.z_mu(C))+1
541
+ atom_types = self.atom_types(h_l.x)
542
+ bond_types = self.bond_types(th.cat([h_l.x[h_l.edge_index[0]], h_l.x[h_l.edge_index[1]]], axis=1))
543
+
544
+
545
+ dist = self.compute_euclidean_distances_matrix(h_l_pos, h_t_pos.view(B,-1,3))[C_mask]
546
+ return pi, sigma, mu, dist.unsqueeze(1).detach(), atom_types, bond_types, C_batch
547
+
548
+ def compute_euclidean_distances_matrix(self, X, Y):
549
+ # Based on: https://medium.com/@souravdey/l2-distance-matrix-vectorization-trick-26aa3247ac6c
550
+ # (X-Y)^2 = X^2 + Y^2 -2XY
551
+ X = X.double()
552
+ Y = Y.double()
553
+
554
+ dists = -2 * th.bmm(X, Y.permute(0, 2, 1)) + th.sum(Y**2, axis=-1).unsqueeze(1) + th.sum(X**2, axis=-1).unsqueeze(-1)
555
+ return th.nan_to_num((dists**0.5).view(self.B, self.N_l,-1,24),10000).min(axis=-1)[0]
556
+
557
+
558
+
559
+
560
+
561
+
562
+
563
+
models/train.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+
3
+ import numpy as np
4
+ import torch as th
5
+ import torch.multiprocessing
6
+ from torch_geometric.loader import DataLoader
7
+
8
+ from onescience.datapipes.genscore.data import PDBbindDataset
9
+ from models.model.model import GatedGCN, GenScore, GraphTransformer
10
+ from onescience.metrics.genscore.utils import (
11
+ EarlyStopping,
12
+ run_a_train_epoch,
13
+ run_an_eval_epoch,
14
+ set_random_seed,
15
+ )
16
+
17
+ torch.multiprocessing.set_sharing_strategy("file_system")
18
+
19
+
20
+ def parse_args():
21
+ parser = argparse.ArgumentParser(description="Train GenScore.")
22
+ parser.add_argument("--num_epochs", type=int, default=5000)
23
+ parser.add_argument("--batch_size", type=int, default=64)
24
+ parser.add_argument("--aux_weight", type=float, default=0.001)
25
+ parser.add_argument("--affi_weight", type=float, default=-0.5)
26
+ parser.add_argument("--patience", type=int, default=70)
27
+ parser.add_argument("--num_workers", type=int, default=8)
28
+ parser.add_argument("--model_path", type=str, default="genscore.pth")
29
+ parser.add_argument("--encoder", type=str, choices=["gt", "gatedgcn"], default="gt")
30
+ parser.add_argument("--mode", type=str, choices=["lower", "higher"], default="lower")
31
+ parser.add_argument("--finetune", action="store_true", default=False)
32
+ parser.add_argument("--original_model_path", type=str, default=None)
33
+ parser.add_argument("--lr", type=int, default=3)
34
+ parser.add_argument("--weight_decay", type=int, default=5)
35
+ parser.add_argument("--data_dir", type=str, required=True)
36
+ parser.add_argument("--data_prefix", type=str, default="v2020_train")
37
+ parser.add_argument("--valnum", type=int, default=1500)
38
+ parser.add_argument("--seeds", type=int, default=126)
39
+ parser.add_argument("--hidden_dim0", type=int, default=128)
40
+ parser.add_argument("--hidden_dim", type=int, default=128)
41
+ parser.add_argument("--n_gaussians", type=int, default=10)
42
+ parser.add_argument("--dropout_rate", type=float, default=0.15)
43
+ parser.add_argument("--dist_threhold", type=float, default=7.0)
44
+ parser.add_argument("--dist_threhold2", type=float, default=5.0)
45
+ return parser.parse_args()
46
+
47
+
48
+ def _build_encoder(args):
49
+ if args.encoder == "gt":
50
+ ligmodel = GraphTransformer(
51
+ in_channels=41,
52
+ edge_features=10,
53
+ num_hidden_channels=args.hidden_dim0,
54
+ activ_fn=th.nn.SiLU(),
55
+ transformer_residual=True,
56
+ num_attention_heads=4,
57
+ norm_to_apply="batch",
58
+ dropout_rate=0.15,
59
+ num_layers=6,
60
+ )
61
+ protmodel = GraphTransformer(
62
+ in_channels=41,
63
+ edge_features=5,
64
+ num_hidden_channels=args.hidden_dim0,
65
+ activ_fn=th.nn.SiLU(),
66
+ transformer_residual=True,
67
+ num_attention_heads=4,
68
+ norm_to_apply="batch",
69
+ dropout_rate=0.15,
70
+ num_layers=6,
71
+ )
72
+ else:
73
+ ligmodel = GatedGCN(
74
+ in_channels=41,
75
+ edge_features=10,
76
+ num_hidden_channels=args.hidden_dim0,
77
+ residual=True,
78
+ dropout_rate=0.15,
79
+ equivstable_pe=False,
80
+ num_layers=6,
81
+ )
82
+ protmodel = GatedGCN(
83
+ in_channels=41,
84
+ edge_features=5,
85
+ num_hidden_channels=args.hidden_dim0,
86
+ residual=True,
87
+ dropout_rate=0.15,
88
+ equivstable_pe=False,
89
+ num_layers=6,
90
+ )
91
+ return ligmodel, protmodel
92
+
93
+
94
+ def main():
95
+ args = parse_args()
96
+ args.device = "cuda" if th.cuda.is_available() else "cpu"
97
+
98
+ data = PDBbindDataset(
99
+ ids=f"{args.data_dir}/{args.data_prefix}_ids.npy",
100
+ ligs=f"{args.data_dir}/{args.data_prefix}_lig.pt",
101
+ prots=f"{args.data_dir}/{args.data_prefix}_prot.pt",
102
+ )
103
+ train_inds, val_inds = data.train_and_test_split(valnum=args.valnum, seed=args.seeds)
104
+ train_data = PDBbindDataset(
105
+ ids=data.pdbids[train_inds],
106
+ ligs=data.gls[train_inds],
107
+ prots=data.gps[train_inds],
108
+ labels=data.labels[train_inds],
109
+ )
110
+ val_data = PDBbindDataset(
111
+ ids=data.pdbids[val_inds],
112
+ ligs=data.gls[val_inds],
113
+ prots=data.gps[val_inds],
114
+ labels=data.labels[val_inds],
115
+ )
116
+
117
+ ligmodel, protmodel = _build_encoder(args)
118
+ model = GenScore(
119
+ ligmodel,
120
+ protmodel,
121
+ in_channels=args.hidden_dim0,
122
+ hidden_dim=args.hidden_dim,
123
+ n_gaussians=args.n_gaussians,
124
+ dropout_rate=args.dropout_rate,
125
+ dist_threhold=args.dist_threhold,
126
+ ).to(args.device)
127
+
128
+ if args.finetune:
129
+ if args.original_model_path is None:
130
+ raise ValueError('--original_model_path is required when --finetune is used.')
131
+ checkpoint = th.load(args.original_model_path, map_location=th.device(args.device))
132
+ model.load_state_dict(checkpoint["model_state_dict"])
133
+
134
+ optimizer = th.optim.Adam(
135
+ model.parameters(),
136
+ lr=10**-args.lr,
137
+ weight_decay=10**-args.weight_decay,
138
+ )
139
+ train_loader = DataLoader(
140
+ dataset=train_data,
141
+ batch_size=args.batch_size,
142
+ shuffle=True,
143
+ num_workers=args.num_workers,
144
+ )
145
+ val_loader = DataLoader(
146
+ dataset=val_data,
147
+ batch_size=args.batch_size,
148
+ shuffle=False,
149
+ num_workers=args.num_workers,
150
+ )
151
+ stopper = EarlyStopping(patience=args.patience, mode=args.mode, filename=args.model_path)
152
+
153
+ set_random_seed(args.seeds)
154
+ for epoch in range(args.num_epochs):
155
+ total_loss_train, mdn_loss_train, affi_loss_train, atom_loss_train, bond_loss_train = run_a_train_epoch(
156
+ epoch,
157
+ model,
158
+ train_loader,
159
+ optimizer,
160
+ affi_weight=args.affi_weight,
161
+ aux_weight=args.aux_weight,
162
+ dist_threhold=args.dist_threhold2,
163
+ device=args.device,
164
+ )
165
+ if np.isinf(mdn_loss_train) or np.isnan(mdn_loss_train):
166
+ print("Inf ERROR")
167
+ break
168
+
169
+ total_loss_val, mdn_loss_val, affi_loss_val, atom_loss_val, bond_loss_val = run_an_eval_epoch(
170
+ model,
171
+ val_loader,
172
+ dist_threhold=args.dist_threhold2,
173
+ affi_weight=args.affi_weight,
174
+ aux_weight=args.aux_weight,
175
+ device=args.device,
176
+ )
177
+ early_stop = stopper.step(total_loss_val, model)
178
+ print(
179
+ "epoch {:d}/{:d}, total_loss_val {:.4f}, mdn_loss_val {:.4f}, "
180
+ "affi_loss_val {:.4f}, atom_loss_val {:.4f}, bond_loss_val {:.4f}, "
181
+ "best validation {:.4f}".format(
182
+ epoch + 1,
183
+ args.num_epochs,
184
+ total_loss_val,
185
+ mdn_loss_val,
186
+ affi_loss_val,
187
+ atom_loss_val,
188
+ bond_loss_val,
189
+ stopper.best_score,
190
+ )
191
+ )
192
+ if early_stop:
193
+ break
194
+
195
+ stopper.load_checkpoint(model)
196
+ train_metrics = run_an_eval_epoch(
197
+ model,
198
+ train_loader,
199
+ dist_threhold=args.dist_threhold2,
200
+ affi_weight=args.affi_weight,
201
+ aux_weight=args.aux_weight,
202
+ device=args.device,
203
+ )
204
+ val_metrics = run_an_eval_epoch(
205
+ model,
206
+ val_loader,
207
+ dist_threhold=args.dist_threhold2,
208
+ affi_weight=args.affi_weight,
209
+ aux_weight=args.aux_weight,
210
+ device=args.device,
211
+ )
212
+ print("train metrics:", train_metrics)
213
+ print("validation metrics:", val_metrics)
214
+
215
+
216
+ if __name__ == "__main__":
217
+ main()
scripts/genscore.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ GENSCORE_DIR = Path(__file__).resolve().parent.parent
5
+ sys.path.insert(0, str(GENSCORE_DIR))
6
+
7
+ from models.inference import main
8
+
9
+
10
+ if __name__ == "__main__":
11
+ main()
scripts/preprocess_pdbbind.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ import torch as th
7
+ from joblib import Parallel, delayed
8
+
9
+ from onescience.datapipes.genscore.feats.mol2graph_rdmda_res import mol_to_graph2
10
+
11
+
12
+ def parse_args():
13
+ parser = argparse.ArgumentParser(description="Preprocess PDBbind complexes for GenScore training.")
14
+ parser.add_argument(
15
+ "-d",
16
+ "--dir",
17
+ default=".",
18
+ help="Directory containing PDBbind-style protein-ligand complex folders.",
19
+ )
20
+ parser.add_argument(
21
+ "-c",
22
+ "--cutoff",
23
+ default=10.0,
24
+ type=float,
25
+ help="Pocket cutoff used in pocket file names and graph construction.",
26
+ )
27
+ parser.add_argument(
28
+ "-o",
29
+ "--outprefix",
30
+ default="out",
31
+ help="Output prefix for generated _ids.npy, _prot.pt, and _lig.pt files.",
32
+ )
33
+ parser.add_argument(
34
+ "-r",
35
+ "--ref",
36
+ default="pdbbind_2020_general.csv",
37
+ help="CSV containing labels indexed by PDB id with a labels column.",
38
+ )
39
+ parser.add_argument(
40
+ "-usH",
41
+ "--useH",
42
+ default=False,
43
+ action="store_true",
44
+ help="Use explicit hydrogen atoms.",
45
+ )
46
+ parser.add_argument(
47
+ "-uschi",
48
+ "--use_chirality",
49
+ default=False,
50
+ action="store_true",
51
+ help="Use chirality features.",
52
+ )
53
+ parser.add_argument(
54
+ "-p",
55
+ "--parallel",
56
+ default=False,
57
+ action="store_true",
58
+ help="Build graphs in parallel.",
59
+ )
60
+ return parser.parse_args()
61
+
62
+
63
+ def _label_query(pdbid, labels):
64
+ return labels.loc[pdbid, "labels"]
65
+
66
+
67
+ def _pdbbind_paths(root_dir, pdbid, cutoff):
68
+ complex_dir = os.path.join(root_dir, pdbid, f"{pdbid}_prot")
69
+ prot_path = os.path.join(complex_dir, f"{pdbid}_p_pocket_{cutoff}.pdb")
70
+ lig_path = os.path.join(complex_dir, f"{pdbid}_l.sdf")
71
+ return prot_path, lig_path
72
+
73
+
74
+ def _pdbbind_handle(pdbid, args, labels):
75
+ prot_path, lig_path = _pdbbind_paths(args.dir, pdbid, args.cutoff)
76
+ try:
77
+ graph_prot, graph_lig = mol_to_graph2(
78
+ prot_path,
79
+ lig_path,
80
+ cutoff=args.cutoff,
81
+ explicit_H=args.useH,
82
+ use_chirality=args.use_chirality,
83
+ )
84
+ except Exception as exc:
85
+ print(f"{pdbid} failed to generate graph: {exc}")
86
+ return None
87
+ return pdbid, graph_prot, graph_lig, _label_query(pdbid, labels)
88
+
89
+
90
+ def main():
91
+ args = parse_args()
92
+ labels = pd.read_csv(args.ref, index_col=0, header=0)
93
+ pdbids = [
94
+ name
95
+ for name in os.listdir(args.dir)
96
+ if os.path.isdir(os.path.join(args.dir, name))
97
+ ]
98
+
99
+ if args.parallel:
100
+ results = Parallel(n_jobs=-1)(
101
+ delayed(_pdbbind_handle)(pdbid, args, labels) for pdbid in pdbids
102
+ )
103
+ else:
104
+ results = [_pdbbind_handle(pdbid, args, labels) for pdbid in pdbids]
105
+
106
+ results = [item for item in results if item is not None]
107
+ if not results:
108
+ raise RuntimeError("No valid PDBbind complexes were converted.")
109
+
110
+ ids, graphs_p, graphs_l, label_values = list(zip(*results))
111
+ np.save(f"{args.outprefix}_ids", (ids, label_values))
112
+ th.save(graphs_p, f"{args.outprefix}_prot.pt")
113
+ th.save(graphs_l, f"{args.outprefix}_lig.pt")
114
+
115
+
116
+ if __name__ == "__main__":
117
+ main()
scripts/run_genscore.sh ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ source ${ROCM_PATH}/cuda/env.sh
4
+ export LD_LIBRARY_PATH="$CONDA_PREFIX/lib:$LD_LIBRARY_PATH"
5
+ export LD_LIBRARY_PATH="$CONDA_PREFIX/lib/python3.11/site-packages/fastpt/torch/lib:$LD_LIBRARY_PATH"
6
+ export LD_LIBRARY_PATH=${ROCM_PATH}/opencl/lib:$LD_LIBRARY_PATH
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ OUTPUT_PREFIX="${SCRIPT_DIR}/out"
10
+ ROOT_DIR="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
11
+
12
+ cd "${ROOT_DIR}"
13
+ source ${ROOT_DIR}/env.sh
14
+
15
+ export PYTHONPATH="${ROOT_DIR}/src:${PYTHONPATH:-}"
16
+
17
+ MODEL_DIR="${GENSCORE_MODEL_DIR:-${ONESCIENCE_DATASETS_DIR}/GenScore/trained_models}"
18
+ DATA_DIR="${GENSCORE_DATA_DIR:-${ONESCIENCE_DATASETS_DIR}/GenScore/genscore_data/inferdata}"
19
+ BATCH_SIZE="${GENSCORE_BATCH_SIZE:-8}"
20
+ NUM_WORKERS="${GENSCORE_NUM_WORKERS:-0}"
21
+
22
+ for input_file in 1qkt_decoys.sdf 1qkt_l.sdf 1qkt_p.pdb 1qkt_p_pocket_10.0.pdb; do
23
+ if [[ ! -f "${DATA_DIR}/${input_file}" ]]; then
24
+ echo "Missing required inference file: ${DATA_DIR}/${input_file}" >&2
25
+ exit 1
26
+ fi
27
+ done
28
+
29
+ run_step() {
30
+ local name="$1"
31
+ local output="$2"
32
+ shift 2
33
+
34
+ echo "[run] ${name}"
35
+ "$@"
36
+ echo "[done] ${name}: ${output}"
37
+ }
38
+
39
+ # input is protein (needs to be converted to pocket)
40
+ run_step "GT scoring with generated pocket" "${OUTPUT_PREFIX}_gt.csv" \
41
+ python "${SCRIPT_DIR}/genscore.py" \
42
+ -p "${DATA_DIR}/1qkt_p.pdb" \
43
+ -l "${DATA_DIR}/1qkt_decoys.sdf" \
44
+ -rl "${DATA_DIR}/1qkt_l.sdf" \
45
+ -gen_pocket \
46
+ -c 10.0 \
47
+ -e gt \
48
+ -m "${MODEL_DIR}/GT_0.0_1.pth" \
49
+ -o "${OUTPUT_PREFIX}" \
50
+ --batch_size "${BATCH_SIZE}" \
51
+ --num_workers "${NUM_WORKERS}"
52
+
53
+ # input is pocket
54
+ run_step "GatedGCN scoring with prepared pocket" "${OUTPUT_PREFIX}_out_gatedgcn.csv" \
55
+ python "${SCRIPT_DIR}/genscore.py" \
56
+ -p "${DATA_DIR}/1qkt_p_pocket_10.0.pdb" \
57
+ -l "${DATA_DIR}/1qkt_decoys.sdf" \
58
+ -e gatedgcn \
59
+ -m "${MODEL_DIR}/GatedGCN_0.5_1.pth" \
60
+ -o "${OUTPUT_PREFIX}" \
61
+ --batch_size "${BATCH_SIZE}" \
62
+ --num_workers "${NUM_WORKERS}"
63
+
64
+ # calculate the atom contributions of the score
65
+ run_step "GatedGCN atom contribution scoring" "${OUTPUT_PREFIX}_out_at.csv" \
66
+ python "${SCRIPT_DIR}/genscore.py" \
67
+ -p "${DATA_DIR}/1qkt_p_pocket_10.0.pdb" \
68
+ -l "${DATA_DIR}/1qkt_decoys.sdf" \
69
+ -e gatedgcn \
70
+ -ac \
71
+ -m "${MODEL_DIR}/GatedGCN_ft_1.0_1.pth" \
72
+ -o "${OUTPUT_PREFIX}" \
73
+ --batch_size "${BATCH_SIZE}" \
74
+ --num_workers "${NUM_WORKERS}"
75
+
76
+ # calculate the residue contributions of the score
77
+ run_step "GatedGCN residue contribution scoring" "${OUTPUT_PREFIX}_out_res.csv" \
78
+ python "${SCRIPT_DIR}/genscore.py" \
79
+ -p "${DATA_DIR}/1qkt_p_pocket_10.0.pdb" \
80
+ -l "${DATA_DIR}/1qkt_decoys.sdf" \
81
+ -e gatedgcn \
82
+ -rc \
83
+ -m "${MODEL_DIR}/GatedGCN_ft_1.0_1.pth" \
84
+ -o "${OUTPUT_PREFIX}" \
85
+ --batch_size "${BATCH_SIZE}" \
86
+ --num_workers "${NUM_WORKERS}"
87
+
88
+ echo "[done] All GenScore inference examples completed."
scripts/run_genscore_benchmarks.sh ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+
6
+ ROOT_DIR="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
7
+ cd "${ROOT_DIR}"
8
+
9
+ if [[ -n "${ROCM_PATH:-}" && -f "${ROCM_PATH}/cuda/env.sh" ]]; then
10
+ source "${ROCM_PATH}/cuda/env.sh"
11
+ fi
12
+
13
+ source "${ROOT_DIR}/env.sh"
14
+ export PYTHONPATH="${ROOT_DIR}/src:${PYTHONPATH:-}"
15
+
16
+ if [[ -n "${CONDA_PREFIX:-}" ]]; then
17
+ export LD_LIBRARY_PATH="${CONDA_PREFIX}/lib:${CONDA_PREFIX}/lib/python3.11/site-packages/torch/lib:${CONDA_PREFIX}/lib/python3.11/site-packages/fastpt/torch/lib:${LD_LIBRARY_PATH:-}"
18
+ fi
19
+ if [[ -n "${ROCM_PATH:-}" && -d "${ROCM_PATH}/opencl/lib" ]]; then
20
+ export LD_LIBRARY_PATH="${ROCM_PATH}/opencl/lib:${LD_LIBRARY_PATH:-}"
21
+ fi
22
+
23
+ TASK="${1:-all}"
24
+
25
+ GENSCORE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
26
+ BENCHMARK_DIR="${GENSCORE_DIR}/benchmarks"
27
+ MODEL_PATH="${MODEL_PATH:-${ONESCIENCE_DATASETS_DIR}/GenScore/trained_models/GatedGCN_ft_1.0_1.pth}"
28
+ OUT_ROOT="${OUT_ROOT:-${GENSCORE_DIR}/benchmark_outputs}"
29
+
30
+ GENSCORE_DATA_ROOT="${GENSCORE_DATA_ROOT:-${ONESCIENCE_DATASETS_DIR}/GenScore/genscore_data}"
31
+ CASF_DIR="${CASF_DIR:-${GENSCORE_DATA_ROOT}/CASF-2016}"
32
+ PDBBIND_DIR="${PDBBIND_DIR:-${GENSCORE_DATA_ROOT}/PDBbind_v2020}"
33
+ PDBBIND_NATIVE_LIGAND_DIR="${PDBBIND_NATIVE_LIGAND_DIR:-${PDBBIND_DIR}/mol2}"
34
+ PDBBIND_REFINED_SUBDIR="${PDBBIND_REFINED_SUBDIR:-refined-set}"
35
+ PDBBIND_OTHER_PL_SUBDIR="${PDBBIND_OTHER_PL_SUBDIR:-v2020-other-PL}"
36
+ CASF_PREPROCESSED_DIR="${CASF_PREPROCESSED_DIR:-${GENSCORE_DATA_ROOT}/rtmscore_s}"
37
+ CASF_TEST_PREFIX="${CASF_TEST_PREFIX:-v2020_casf}"
38
+ CASF_CORESET_FILE="${CASF_CORESET_FILE:-${CASF_PREPROCESSED_DIR}/v2020_casf_coreset.csv}"
39
+
40
+ ENCODER="${ENCODER:-gatedgcn}"
41
+ CUTOFF="${CUTOFF:-10.0}"
42
+ BATCH_SIZE="${BATCH_SIZE:-128}"
43
+ NUM_WORKERS="${NUM_WORKERS:-0}"
44
+ OUTPREFIX="${OUTPREFIX:-gatedgcn1x5}"
45
+
46
+ require_dir() {
47
+ local name="$1"
48
+ local path="$2"
49
+ if [[ ! -d "${path}" ]]; then
50
+ echo "Missing required directory ${name}: ${path}" >&2
51
+ exit 1
52
+ fi
53
+ }
54
+
55
+ require_file() {
56
+ local name="$1"
57
+ local path="$2"
58
+ if [[ ! -f "${path}" ]]; then
59
+ echo "Missing required file ${name}: ${path}" >&2
60
+ exit 1
61
+ fi
62
+ }
63
+
64
+ run_scoring_ranking() {
65
+ require_dir "CASF_PREPROCESSED_DIR" "${CASF_PREPROCESSED_DIR}"
66
+ require_file "CASF_CORESET_FILE" "${CASF_CORESET_FILE}"
67
+ require_file "MODEL_PATH" "${MODEL_PATH}"
68
+
69
+ echo "[run] CASF-2016 scoring/ranking"
70
+ python "${BENCHMARK_DIR}/casf2016_scoring_ranking.py" \
71
+ --data-dir "${CASF_PREPROCESSED_DIR}" \
72
+ --test-prefix "${CASF_TEST_PREFIX}" \
73
+ --coreset-file "${CASF_CORESET_FILE}" \
74
+ --model-path "${MODEL_PATH}" \
75
+ --encoder "${ENCODER}" \
76
+ --cutoff "${CUTOFF}" \
77
+ --batch-size "${BATCH_SIZE}" \
78
+ --num-workers "${NUM_WORKERS}" \
79
+ --outprefix "${OUTPREFIX}" \
80
+ --outdir "${OUT_ROOT}/power_ranking/examples"
81
+ echo "[done] CASF-2016 scoring/ranking"
82
+ }
83
+
84
+ run_docking() {
85
+ require_dir "CASF_DIR" "${CASF_DIR}"
86
+ require_dir "PDBBIND_DIR" "${PDBBIND_DIR}"
87
+ require_dir "PDBBIND_NATIVE_LIGAND_DIR" "${PDBBIND_NATIVE_LIGAND_DIR}"
88
+ require_dir "PDBbind refined subset" "${PDBBIND_DIR}/${PDBBIND_REFINED_SUBDIR}"
89
+ require_dir "PDBbind other-PL subset" "${PDBBIND_DIR}/${PDBBIND_OTHER_PL_SUBDIR}"
90
+ require_file "MODEL_PATH" "${MODEL_PATH}"
91
+
92
+ echo "[run] CASF-2016 docking"
93
+ python "${BENCHMARK_DIR}/casf2016_docking.py" \
94
+ --casf-dir "${CASF_DIR}" \
95
+ --pdbbind-dir "${PDBBIND_DIR}" \
96
+ --native-ligand-dir "${PDBBIND_NATIVE_LIGAND_DIR}" \
97
+ --refined-subdir "${PDBBIND_REFINED_SUBDIR}" \
98
+ --other-pl-subdir "${PDBBIND_OTHER_PL_SUBDIR}" \
99
+ --model-path "${MODEL_PATH}" \
100
+ --encoder "${ENCODER}" \
101
+ --cutoff "${CUTOFF}" \
102
+ --batch-size "${BATCH_SIZE}" \
103
+ --num-workers "${NUM_WORKERS}" \
104
+ --outprefix "${OUTPREFIX}" \
105
+ --outdir "${OUT_ROOT}/power_docking/examples/${OUTPREFIX}"
106
+ echo "[done] CASF-2016 docking"
107
+ }
108
+
109
+ run_screening() {
110
+ require_dir "CASF_DIR" "${CASF_DIR}"
111
+ require_dir "PDBBIND_DIR" "${PDBBIND_DIR}"
112
+ require_dir "PDBbind refined subset" "${PDBBIND_DIR}/${PDBBIND_REFINED_SUBDIR}"
113
+ require_dir "PDBbind other-PL subset" "${PDBBIND_DIR}/${PDBBIND_OTHER_PL_SUBDIR}"
114
+ require_file "MODEL_PATH" "${MODEL_PATH}"
115
+
116
+ echo "[run] CASF-2016 screening"
117
+ python "${BENCHMARK_DIR}/casf2016_screening.py" \
118
+ --casf-dir "${CASF_DIR}" \
119
+ --pdbbind-dir "${PDBBIND_DIR}" \
120
+ --refined-subdir "${PDBBIND_REFINED_SUBDIR}" \
121
+ --other-pl-subdir "${PDBBIND_OTHER_PL_SUBDIR}" \
122
+ --model-path "${MODEL_PATH}" \
123
+ --encoder "${ENCODER}" \
124
+ --cutoff "${CUTOFF}" \
125
+ --batch-size "${BATCH_SIZE}" \
126
+ --num-workers "${NUM_WORKERS}" \
127
+ --outprefix "${OUTPREFIX}" \
128
+ --outdir "${OUT_ROOT}/power_screening/examples/${OUTPREFIX}"
129
+ echo "[done] CASF-2016 screening"
130
+ }
131
+
132
+ case "${TASK}" in
133
+ scoring|scoring_ranking)
134
+ run_scoring_ranking
135
+ ;;
136
+ docking)
137
+ run_docking
138
+ ;;
139
+ screening)
140
+ run_screening
141
+ ;;
142
+ all)
143
+ run_scoring_ranking
144
+ run_docking
145
+ run_screening
146
+ ;;
147
+ *)
148
+ echo "Usage: $0 [scoring|docking|screening|all]" >&2
149
+ exit 1
150
+ ;;
151
+ esac
scripts/train_genscore.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ GENSCORE_DIR = Path(__file__).resolve().parent.parent
5
+ sys.path.insert(0, str(GENSCORE_DIR))
6
+
7
+ from models.train import main
8
+
9
+
10
+ if __name__ == "__main__":
11
+ main()
scripts/train_genscore_full.sh ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+ source ${ROCM_PATH}/cuda/env.sh
5
+ export LD_LIBRARY_PATH=${ROCM_PATH}/opencl/lib:$LD_LIBRARY_PATH
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+
9
+ ROOT_DIR="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
10
+ echo ${ROOT_DIR}
11
+ source ${ROOT_DIR}/env.sh
12
+
13
+ cd "${ROOT_DIR}"
14
+
15
+ export PYTHONPATH="${ROOT_DIR}/src:${PYTHONPATH:-}"
16
+
17
+ if [[ -n "${CONDA_PREFIX:-}" ]]; then
18
+ export LD_LIBRARY_PATH="${CONDA_PREFIX}/lib:${CONDA_PREFIX}/lib/python3.11/site-packages/torch/lib:${CONDA_PREFIX}/lib/python3.11/site-packages/fastpt/torch/lib:/opt/dtk/.hyhal/rocm_smi/lib:${LD_LIBRARY_PATH:-}"
19
+ fi
20
+
21
+ GENSCORE_DATA_DIR="${GENSCORE_DATA_DIR:-${ONESCIENCE_DATASETS_DIR}/GenScore/genscore_data/rtmscore_s}"
22
+ GENSCORE_DATA_PREFIX="${GENSCORE_DATA_PREFIX:-v2020_train}"
23
+ GENSCORE_ENCODER="${GENSCORE_ENCODER:-gatedgcn}"
24
+ GENSCORE_MODEL_PATH="${GENSCORE_MODEL_PATH:-${SCRIPT_DIR}/genscore_${GENSCORE_ENCODER}_full_3000.pth}"
25
+
26
+ GENSCORE_NUM_EPOCHS="${GENSCORE_NUM_EPOCHS:-3000}"
27
+ GENSCORE_BATCH_SIZE="${GENSCORE_BATCH_SIZE:-64}"
28
+ GENSCORE_NUM_WORKERS="${GENSCORE_NUM_WORKERS:-8}"
29
+ GENSCORE_VALNUM="${GENSCORE_VALNUM:-1500}"
30
+ GENSCORE_PATIENCE="${GENSCORE_PATIENCE:-70}"
31
+
32
+ for suffix in ids.npy lig.pt prot.pt; do
33
+ path="${GENSCORE_DATA_DIR}/${GENSCORE_DATA_PREFIX}_${suffix}"
34
+ if [[ ! -f "${path}" ]]; then
35
+ echo "Missing required training file: ${path}" >&2
36
+ exit 1
37
+ fi
38
+ done
39
+
40
+ mkdir -p "$(dirname "${GENSCORE_MODEL_PATH}")"
41
+
42
+ echo "GenScore full training"
43
+ echo " data_dir: ${GENSCORE_DATA_DIR}"
44
+ echo " data_prefix: ${GENSCORE_DATA_PREFIX}"
45
+ echo " model_path: ${GENSCORE_MODEL_PATH}"
46
+ echo " encoder: ${GENSCORE_ENCODER}"
47
+ echo " num_epochs: ${GENSCORE_NUM_EPOCHS}"
48
+ echo " batch_size: ${GENSCORE_BATCH_SIZE}"
49
+ echo " num_workers: ${GENSCORE_NUM_WORKERS}"
50
+ echo " valnum: ${GENSCORE_VALNUM}"
51
+ echo " patience: ${GENSCORE_PATIENCE}"
52
+
53
+ python ${SCRIPT_DIR}/train_genscore.py \
54
+ --data_dir "${GENSCORE_DATA_DIR}" \
55
+ --data_prefix "${GENSCORE_DATA_PREFIX}" \
56
+ --model_path "${GENSCORE_MODEL_PATH}" \
57
+ --num_epochs "${GENSCORE_NUM_EPOCHS}" \
58
+ --batch_size "${GENSCORE_BATCH_SIZE}" \
59
+ --num_workers "${GENSCORE_NUM_WORKERS}" \
60
+ --valnum "${GENSCORE_VALNUM}" \
61
+ --patience "${GENSCORE_PATIENCE}" \
62
+ --encoder "${GENSCORE_ENCODER}"
scripts/train_genscore_smoke.sh ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ source ${ROCM_PATH}/cuda/env.sh
4
+ export LD_LIBRARY_PATH=${ROCM_PATH}/opencl/lib:$LD_LIBRARY_PATH
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+
8
+ ROOT_DIR="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
9
+ echo ${ROOT_DIR}
10
+ source ${ROOT_DIR}/env.sh
11
+ cd "${ROOT_DIR}"
12
+
13
+ export PYTHONPATH="${ROOT_DIR}/src:${PYTHONPATH:-}"
14
+
15
+ if [[ -n "${CONDA_PREFIX:-}" ]]; then
16
+ export LD_LIBRARY_PATH="${CONDA_PREFIX}/lib:${CONDA_PREFIX}/lib/python3.11/site-packages/torch/lib:${CONDA_PREFIX}/lib/python3.11/site-packages/fastpt/torch/lib:/opt/dtk/.hyhal/rocm_smi/lib:${LD_LIBRARY_PATH:-}"
17
+ fi
18
+
19
+ GENSCORE_DATA_DIR="${GENSCORE_DATA_DIR:-${ONESCIENCE_DATASETS_DIR}/GenScore/genscore_data/rtmscore_s}"
20
+ GENSCORE_DATA_PREFIX="${GENSCORE_DATA_PREFIX:-v2020_train}"
21
+ GENSCORE_MODEL_PATH="${GENSCORE_MODEL_PATH:-${SCRIPT_DIR}/genscore_smoke_bs16.pth}"
22
+
23
+ python ${SCRIPT_DIR}/train_genscore.py \
24
+ --data_dir "${GENSCORE_DATA_DIR}" \
25
+ --data_prefix "${GENSCORE_DATA_PREFIX}" \
26
+ --model_path "${GENSCORE_MODEL_PATH}" \
27
+ --num_epochs 100 \
28
+ --batch_size 16 \
29
+ --num_workers 0 \
30
+ --valnum 1500 \
31
+ --encoder "${GENSCORE_ENCODER:-gatedgcn}"