mDeepFRI 1.2.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/PKG-INFO +43 -9
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/README.md +35 -3
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/alignment.py +6 -0
- mdeepfri-1.3.0/mDeepFRI/benchmark.py +348 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/cli.py +236 -82
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/contact_map_utils.pyx +1 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/mmseqs.py +49 -21
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/pdb.py +14 -9
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/pipeline.py +330 -148
- mdeepfri-1.3.0/mDeepFRI/tests/__init__.py +22 -0
- mdeepfri-1.3.0/mDeepFRI/tests/data/AF-A0A3B4WVX2-F1-model_v6.cif +3708 -0
- mdeepfri-1.3.0/mDeepFRI/tests/data/GCA_000731455.1.proteins.fa.gz +0 -0
- mdeepfri-1.3.0/mDeepFRI/tests/data/small_mapping.tsv +5 -0
- mdeepfri-1.3.0/mDeepFRI/tests/data/small_query.faa +23 -0
- mdeepfri-1.3.0/mDeepFRI/tests/test_benchmark.py +68 -0
- mdeepfri-1.3.0/mDeepFRI/tests/test_cli.py +88 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_mmseqs.py +39 -0
- mdeepfri-1.3.0/mDeepFRI/tests/test_pdb.py +66 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_pipeline_regression.py +46 -1
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/utils.py +1 -1
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI.egg-info/PKG-INFO +43 -9
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI.egg-info/SOURCES.txt +7 -1
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI.egg-info/requires.txt +4 -4
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/pyproject.toml +18 -7
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/setup.py +21 -13
- mdeepfri-1.2.0/mDeepFRI/tests/__init__.py +0 -13
- mdeepfri-1.2.0/mDeepFRI/tests/test_cli.py +0 -32
- mdeepfri-1.2.0/mDeepFRI/tests/test_pdb.py +0 -12
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/LICENSE +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/__init__.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/assets/go2cog_USECLO_ALL.tsv +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/assets/highquality_clust30_error_ids.pkl +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/bio_utils.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/contact_map.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/contact_map_utils.pyi +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/database.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/go_propagation.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/predict.pyi +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/predict.pyx +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_alignment.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_bio_utils.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_conctact_map.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_contact_map_utils.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_go2cog_mapping.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_install.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_predict.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_utils.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_vocabulary_mismatch.py +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI.egg-info/dependency_links.txt +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI.egg-info/entry_points.txt +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI.egg-info/top_level.txt +0 -0
- {mdeepfri-1.2.0 → mdeepfri-1.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mDeepFRI
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Pipeline for searching and aligning contact maps for proteins, and function prediction with DeepFRI.
|
|
5
5
|
Author-email: Valentyn Bezshapkin <valentyn.bezshapkin@micro.biol.ethz.ch>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -14,23 +14,25 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
14
14
|
Classifier: Programming Language :: Cython
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
19
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
20
|
Classifier: Operating System :: MacOS
|
|
19
21
|
Classifier: Operating System :: POSIX :: Linux
|
|
20
22
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
21
|
-
Requires-Python:
|
|
23
|
+
Requires-Python: >=3.11
|
|
22
24
|
Description-Content-Type: text/markdown
|
|
23
25
|
License-File: LICENSE
|
|
24
26
|
Requires-Dist: click~=8.0
|
|
25
27
|
Requires-Dist: numpy~=2.3
|
|
26
|
-
Requires-Dist: biotite
|
|
28
|
+
Requires-Dist: biotite<3,>=1.3
|
|
27
29
|
Requires-Dist: pysam>=0.18
|
|
28
30
|
Requires-Dist: pyopal~=0.7
|
|
29
|
-
Requires-Dist: foldcomp
|
|
31
|
+
Requires-Dist: foldcomp<2,>=0.1
|
|
30
32
|
Requires-Dist: tqdm
|
|
31
33
|
Requires-Dist: onnxruntime~=1.26.0
|
|
32
|
-
Requires-Dist: obonet
|
|
33
|
-
Requires-Dist: networkx
|
|
34
|
+
Requires-Dist: obonet<2,>=0.3
|
|
35
|
+
Requires-Dist: networkx<4,>=2.6
|
|
34
36
|
Provides-Extra: dev
|
|
35
37
|
Requires-Dist: pre-commit; extra == "dev"
|
|
36
38
|
Provides-Extra: gpu
|
|
@@ -98,13 +100,14 @@ with `MMseqs2`.
|
|
|
98
100
|
|
|
99
101
|
## 📦 Requirements
|
|
100
102
|
|
|
101
|
-
- **Python:** >= 3.11,
|
|
103
|
+
- **Python:** >= 3.11, tested with 3.11 - 3.14 on Linux x86-64 and macOS (Apple Silicon)
|
|
102
104
|
- **Dependencies:** Automatically installed via pip
|
|
103
105
|
|
|
104
106
|
## 🔧 Installation
|
|
105
107
|
|
|
106
|
-
1\. Install from PyPI.
|
|
107
|
-
|
|
108
|
+
1\. Install from PyPI (Python 3.11 - 3.14). Wheels are available for Linux x86-64
|
|
109
|
+
and macOS 14+ on Apple Silicon. On Intel Macs pip builds from source, which needs
|
|
110
|
+
a C++ compiler with OpenMP (`brew install libomp`).
|
|
108
111
|
|
|
109
112
|
```{code-block} bash
|
|
110
113
|
pip install mdeepfri
|
|
@@ -367,6 +370,37 @@ paths to `LD_LIBRARY_PATH`:
|
|
|
367
370
|
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(python -c 'import os, nvidia.cudnn, nvidia.cublas, nvidia.cuda_runtime; libs=[nvidia.cudnn, nvidia.cublas, nvidia.cuda_runtime]; print(":".join([os.path.join(m.__path__[0], "lib") for m in libs]))')
|
|
368
371
|
```
|
|
369
372
|
|
|
373
|
+
### 6. Structural template filtering
|
|
374
|
+
|
|
375
|
+
Templates whose global alignment to the query falls below
|
|
376
|
+
`--alignment-min-identity` or `--alignment-min-coverage` are discarded,
|
|
377
|
+
and the query falls back to the next database or the CNN.
|
|
378
|
+
Both filters are off by default.
|
|
379
|
+
|
|
380
|
+
### 7. Benchmarking
|
|
381
|
+
|
|
382
|
+
`mDeepFRI benchmark` measures how the similarity of the structural template
|
|
383
|
+
affects contact maps and predictions. Every database is searched once with
|
|
384
|
+
every query. For each `--identity-bin LOW HIGH` (MMseqs2 identity, `HIGH`
|
|
385
|
+
exclusive) one hit is kept per query and database - the best by bitscore,
|
|
386
|
+
or a random one with `--hit-selection random --seed 42` - and hits of a
|
|
387
|
+
query to itself are dropped unless `--keep-self-hits` is set. Contact maps
|
|
388
|
+
are built for every `--generate-contacts` value and, if weights are given,
|
|
389
|
+
GO terms are predicted. `--search-results` reuses the `database_search`
|
|
390
|
+
folder of a previous run.
|
|
391
|
+
|
|
392
|
+
```{code-block} bash
|
|
393
|
+
mDeepFRI benchmark -i /path/to/protein/sequences \
|
|
394
|
+
-d /path/to/foldcomp/database/ -w /path/to/deepfri/weights/folder \
|
|
395
|
+
-o /output_path -p mf -p bp -p cc \
|
|
396
|
+
--identity-bin 0 0.5 --identity-bin 0 0.9 --identity-bin 0 1.01
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
The output contains `database_search/` with all MMseqs2 hits, `timing.tsv`
|
|
400
|
+
with the wall time of each stage and, per identity bin, the selected `hits/`,
|
|
401
|
+
their global `alignments/` with gapped sequences and one
|
|
402
|
+
`generate_contacts_<value>/` folder with the `predict-function` outputs.
|
|
403
|
+
|
|
370
404
|
## 🔖 Citations
|
|
371
405
|
|
|
372
406
|
Metagenomic-DeepFRI is a scientific software. If you use it
|
|
@@ -59,13 +59,14 @@ with `MMseqs2`.
|
|
|
59
59
|
|
|
60
60
|
## 📦 Requirements
|
|
61
61
|
|
|
62
|
-
- **Python:** >= 3.11,
|
|
62
|
+
- **Python:** >= 3.11, tested with 3.11 - 3.14 on Linux x86-64 and macOS (Apple Silicon)
|
|
63
63
|
- **Dependencies:** Automatically installed via pip
|
|
64
64
|
|
|
65
65
|
## 🔧 Installation
|
|
66
66
|
|
|
67
|
-
1\. Install from PyPI.
|
|
68
|
-
|
|
67
|
+
1\. Install from PyPI (Python 3.11 - 3.14). Wheels are available for Linux x86-64
|
|
68
|
+
and macOS 14+ on Apple Silicon. On Intel Macs pip builds from source, which needs
|
|
69
|
+
a C++ compiler with OpenMP (`brew install libomp`).
|
|
69
70
|
|
|
70
71
|
```{code-block} bash
|
|
71
72
|
pip install mdeepfri
|
|
@@ -328,6 +329,37 @@ paths to `LD_LIBRARY_PATH`:
|
|
|
328
329
|
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(python -c 'import os, nvidia.cudnn, nvidia.cublas, nvidia.cuda_runtime; libs=[nvidia.cudnn, nvidia.cublas, nvidia.cuda_runtime]; print(":".join([os.path.join(m.__path__[0], "lib") for m in libs]))')
|
|
329
330
|
```
|
|
330
331
|
|
|
332
|
+
### 6. Structural template filtering
|
|
333
|
+
|
|
334
|
+
Templates whose global alignment to the query falls below
|
|
335
|
+
`--alignment-min-identity` or `--alignment-min-coverage` are discarded,
|
|
336
|
+
and the query falls back to the next database or the CNN.
|
|
337
|
+
Both filters are off by default.
|
|
338
|
+
|
|
339
|
+
### 7. Benchmarking
|
|
340
|
+
|
|
341
|
+
`mDeepFRI benchmark` measures how the similarity of the structural template
|
|
342
|
+
affects contact maps and predictions. Every database is searched once with
|
|
343
|
+
every query. For each `--identity-bin LOW HIGH` (MMseqs2 identity, `HIGH`
|
|
344
|
+
exclusive) one hit is kept per query and database - the best by bitscore,
|
|
345
|
+
or a random one with `--hit-selection random --seed 42` - and hits of a
|
|
346
|
+
query to itself are dropped unless `--keep-self-hits` is set. Contact maps
|
|
347
|
+
are built for every `--generate-contacts` value and, if weights are given,
|
|
348
|
+
GO terms are predicted. `--search-results` reuses the `database_search`
|
|
349
|
+
folder of a previous run.
|
|
350
|
+
|
|
351
|
+
```{code-block} bash
|
|
352
|
+
mDeepFRI benchmark -i /path/to/protein/sequences \
|
|
353
|
+
-d /path/to/foldcomp/database/ -w /path/to/deepfri/weights/folder \
|
|
354
|
+
-o /output_path -p mf -p bp -p cc \
|
|
355
|
+
--identity-bin 0 0.5 --identity-bin 0 0.9 --identity-bin 0 1.01
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
The output contains `database_search/` with all MMseqs2 hits, `timing.tsv`
|
|
359
|
+
with the wall time of each stage and, per identity bin, the selected `hits/`,
|
|
360
|
+
their global `alignments/` with gapped sequences and one
|
|
361
|
+
`generate_contacts_<value>/` folder with the `predict-function` outputs.
|
|
362
|
+
|
|
331
363
|
## 🔖 Citations
|
|
332
364
|
|
|
333
365
|
Metagenomic-DeepFRI is a scientific software. If you use it
|
|
@@ -19,6 +19,7 @@ Functions:
|
|
|
19
19
|
align_mmseqs_results: Align all MMseqs2 hits to their targets.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
import platform
|
|
22
23
|
import warnings
|
|
23
24
|
from functools import partial
|
|
24
25
|
from multiprocessing import Pool
|
|
@@ -34,6 +35,11 @@ from mDeepFRI.utils import (load_fasta_as_dict, retrieve_fasta_entries_as_dict,
|
|
|
34
35
|
|
|
35
36
|
warnings.showwarning = stdout_warn
|
|
36
37
|
|
|
38
|
+
# PyOpal's macOS arm64 wheels are cross-compiled on x86-64 runners and
|
|
39
|
+
# do not enable their NEON backend at runtime.
|
|
40
|
+
if platform.machine() == "arm64" and pyopal.lib._NEON_BUILD_SUPPORT:
|
|
41
|
+
pyopal.lib._NEON_RUNTIME_SUPPORT = True
|
|
42
|
+
|
|
37
43
|
|
|
38
44
|
def insert_gaps(sequence: str, reference: str,
|
|
39
45
|
alignment_string: str) -> Tuple[str, str]:
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Benchmarking of structure retrieval, contact map transfer and prediction.
|
|
3
|
+
|
|
4
|
+
Every database is searched once with every query. For each identity bin one
|
|
5
|
+
MMseqs2 hit is kept per query and database, aligned globally and used as a
|
|
6
|
+
structural template. Contact maps are built for each ``generate_contacts``
|
|
7
|
+
value and, if weights are given, GO terms are predicted.
|
|
8
|
+
|
|
9
|
+
Output layout::
|
|
10
|
+
|
|
11
|
+
database_search/<db>_results.tsv all MMseqs2 hits
|
|
12
|
+
<bin>/hits/<db>.tsv selected hit per query
|
|
13
|
+
<bin>/alignments/<db>.tsv global alignments of the hits
|
|
14
|
+
<bin>/structures/<db>/ template structures (optional)
|
|
15
|
+
<bin>/generate_contacts_<gc>/ templates, contact maps, predictions
|
|
16
|
+
timing.tsv wall time of each stage
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import csv
|
|
20
|
+
import dataclasses
|
|
21
|
+
import logging
|
|
22
|
+
import re
|
|
23
|
+
import time
|
|
24
|
+
from contextlib import contextmanager
|
|
25
|
+
from functools import partial
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Iterable, List, Literal, Optional, Tuple
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
30
|
+
import numpy.lib.recfunctions as rfn
|
|
31
|
+
|
|
32
|
+
from mDeepFRI.alignment import AlignmentResult
|
|
33
|
+
from mDeepFRI.mmseqs import MMSEQS_COLUMNS, MMseqsResult, QueryFile
|
|
34
|
+
from mDeepFRI.pipeline import (_initialize_processing_modes,
|
|
35
|
+
align_database_hits, build_contact_maps,
|
|
36
|
+
load_databases, predict_from_contact_maps,
|
|
37
|
+
save_contact_maps, select_templates,
|
|
38
|
+
write_alignment_summary)
|
|
39
|
+
from mDeepFRI.utils import load_deepfri_config
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
ALIGNMENTS_HEADER = [
|
|
44
|
+
"query_id", "target_id", "query_identity", "query_coverage",
|
|
45
|
+
"target_coverage", "query_alignment", "target_alignment"
|
|
46
|
+
]
|
|
47
|
+
TIMING_HEADER = [
|
|
48
|
+
"stage", "identity_bin", "database", "generate_contacts", "seconds"
|
|
49
|
+
]
|
|
50
|
+
MODEL_VERSION = re.compile(r"-model_v\d+$")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def is_self_hit(query: str, target: str) -> bool:
|
|
54
|
+
"""
|
|
55
|
+
Check whether a hit is the query itself, ignoring AlphaFold model versions.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
query (str): Query identifier, e.g. `P12345` or `AF-P12345-F1-model_v6`.
|
|
59
|
+
target (str): Target identifier, e.g. `AF-P12345-F1-model_v4`.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
bool: True for a self-hit.
|
|
63
|
+
"""
|
|
64
|
+
return (MODEL_VERSION.sub("", query) == MODEL_VERSION.sub("", target)
|
|
65
|
+
or target.startswith(f"AF-{query}-"))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def select_hits(hits: np.ndarray,
|
|
69
|
+
identity_bin: Tuple[float, float],
|
|
70
|
+
selection: Literal["bitscore", "random"] = "bitscore",
|
|
71
|
+
drop_self_hits: bool = True,
|
|
72
|
+
seed: int = 0) -> np.ndarray:
|
|
73
|
+
"""
|
|
74
|
+
Keep one MMseqs2 hit per query within an identity bin.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
hits (np.ndarray): MMseqs2 hits.
|
|
78
|
+
identity_bin (Tuple[float, float]): Inclusive lower and exclusive
|
|
79
|
+
upper bound of the MMseqs2 identity.
|
|
80
|
+
selection (str, optional): Keep the hit with the highest bitscore,
|
|
81
|
+
ties broken by identity, or a random hit. Defaults to "bitscore".
|
|
82
|
+
drop_self_hits (bool, optional): Discard hits to the query itself.
|
|
83
|
+
Defaults to True.
|
|
84
|
+
seed (int, optional): Random seed for random selection.
|
|
85
|
+
Defaults to 0.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
np.ndarray: One hit per query, sorted by query.
|
|
89
|
+
"""
|
|
90
|
+
low, high = identity_bin
|
|
91
|
+
candidates = hits[(hits["fident"] >= low) & (hits["fident"] < high)]
|
|
92
|
+
if drop_self_hits:
|
|
93
|
+
self_hits = [
|
|
94
|
+
is_self_hit(query, target)
|
|
95
|
+
for query, target in zip(candidates["query"], candidates["target"])
|
|
96
|
+
]
|
|
97
|
+
candidates = candidates[~np.array(self_hits, dtype=bool)]
|
|
98
|
+
|
|
99
|
+
if selection == "random":
|
|
100
|
+
order = np.argsort(candidates["query"], kind="stable")
|
|
101
|
+
_, starts, counts = np.unique(candidates["query"][order],
|
|
102
|
+
return_index=True,
|
|
103
|
+
return_counts=True)
|
|
104
|
+
rng = np.random.default_rng(seed)
|
|
105
|
+
picks = [
|
|
106
|
+
start + rng.integers(0, count)
|
|
107
|
+
for start, count in zip(starts, counts)
|
|
108
|
+
]
|
|
109
|
+
return candidates[order[picks]]
|
|
110
|
+
|
|
111
|
+
order = np.argsort(candidates, order=["query", "bits", "fident"])[::-1]
|
|
112
|
+
_, first = np.unique(candidates["query"][order], return_index=True)
|
|
113
|
+
return candidates[order[first]]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def write_alignments(path: Path, alignments: List[AlignmentResult]) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Write global alignments with their gapped sequences.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
path (Path): Output TSV file.
|
|
122
|
+
alignments (List[AlignmentResult]): Alignments to write.
|
|
123
|
+
"""
|
|
124
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
with open(path, "w", encoding="utf-8", newline="") as f:
|
|
126
|
+
writer = csv.writer(f, delimiter="\t")
|
|
127
|
+
writer.writerow(ALIGNMENTS_HEADER)
|
|
128
|
+
for aln in alignments:
|
|
129
|
+
writer.writerow([
|
|
130
|
+
aln.query_name, aln.target_name, aln.query_identity,
|
|
131
|
+
aln.query_coverage, aln.target_coverage, aln.gapped_sequence,
|
|
132
|
+
aln.gapped_target
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@contextmanager
|
|
137
|
+
def _timed(timing_file: Path,
|
|
138
|
+
stage: str,
|
|
139
|
+
identity_bin: Optional[str] = None,
|
|
140
|
+
database: Optional[str] = None,
|
|
141
|
+
generate_contacts: Optional[int] = None):
|
|
142
|
+
start = time.perf_counter()
|
|
143
|
+
yield
|
|
144
|
+
seconds = round(time.perf_counter() - start, 2)
|
|
145
|
+
logger.info("%s finished in %.2f s.", stage, seconds)
|
|
146
|
+
with open(timing_file, "a", encoding="utf-8", newline="") as f:
|
|
147
|
+
csv.writer(f, delimiter="\t").writerow(
|
|
148
|
+
[stage, identity_bin, database, generate_contacts, seconds])
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def run_benchmark(query_file: QueryFile,
|
|
152
|
+
output_path: Path,
|
|
153
|
+
databases: Iterable[str] = (),
|
|
154
|
+
identity_bins: Iterable[Tuple[float,
|
|
155
|
+
float]] = ((0.0, 1.01), ),
|
|
156
|
+
generate_contacts: Iterable[int] = (2, ),
|
|
157
|
+
weights: Optional[str] = None,
|
|
158
|
+
deepfri_processing_modes: Iterable[str] = ("bp", "cc", "ec",
|
|
159
|
+
"mf"),
|
|
160
|
+
search_results: Optional[Path] = None,
|
|
161
|
+
skip_pdb: bool = False,
|
|
162
|
+
overwrite: bool = False,
|
|
163
|
+
mmseqs_sensitivity: float = 5.7,
|
|
164
|
+
min_bits: float = 0,
|
|
165
|
+
max_eval: float = 1e-3,
|
|
166
|
+
min_ident: float = 0.0,
|
|
167
|
+
min_coverage: float = 0.9,
|
|
168
|
+
selection: Literal["bitscore", "random"] = "bitscore",
|
|
169
|
+
drop_self_hits: bool = True,
|
|
170
|
+
seed: int = 0,
|
|
171
|
+
alignment_gap_open: int = 10,
|
|
172
|
+
alignment_gap_extend: int = 1,
|
|
173
|
+
scoring_matrix: str = "VTML80",
|
|
174
|
+
alignment_min_identity: float = 0.0,
|
|
175
|
+
alignment_min_coverage: float = 0.0,
|
|
176
|
+
angstrom_contact_threshold: float = 6,
|
|
177
|
+
save_structures: bool = False,
|
|
178
|
+
save_cmaps: bool = False,
|
|
179
|
+
skip_matrix: bool = False,
|
|
180
|
+
tmpdir: Optional[Path] = None,
|
|
181
|
+
threads: int = 1) -> None:
|
|
182
|
+
"""
|
|
183
|
+
Run the pipeline for every combination of identity bin and
|
|
184
|
+
`generate_contacts` value, reusing one database search.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
query_file (QueryFile): Query sequences.
|
|
188
|
+
output_path (Path): Output directory.
|
|
189
|
+
databases (Iterable[str], optional): FoldComp databases searched
|
|
190
|
+
after PDB100, in order of preference.
|
|
191
|
+
identity_bins (Iterable[Tuple[float, float]], optional): Bounds of
|
|
192
|
+
the MMseqs2 identity, lower inclusive and upper exclusive.
|
|
193
|
+
Defaults to all hits.
|
|
194
|
+
generate_contacts (Iterable[int], optional): Numbers of neighbours
|
|
195
|
+
connected to residues inserted in the query. Defaults to (2,).
|
|
196
|
+
weights (str, optional): DeepFRI weights folder. Without weights,
|
|
197
|
+
only contact maps are built.
|
|
198
|
+
deepfri_processing_modes (Iterable[str], optional): Modes to predict.
|
|
199
|
+
search_results (Path, optional): Directory with `<db>_results.tsv`
|
|
200
|
+
files of a previous search, used instead of searching.
|
|
201
|
+
skip_pdb (bool, optional): Skip PDB100. Defaults to False.
|
|
202
|
+
overwrite (bool, optional): Rebuild database files. Defaults to False.
|
|
203
|
+
mmseqs_sensitivity (float, optional): MMseqs2 sensitivity.
|
|
204
|
+
min_bits (float, optional): Minimum MMseqs2 bitscore.
|
|
205
|
+
max_eval (float, optional): Maximum MMseqs2 e-value.
|
|
206
|
+
min_ident (float, optional): Minimum MMseqs2 identity.
|
|
207
|
+
min_coverage (float, optional): Minimum MMseqs2 query and target
|
|
208
|
+
coverage.
|
|
209
|
+
selection (str, optional): Per-query hit selection, see
|
|
210
|
+
`select_hits`. Defaults to "bitscore".
|
|
211
|
+
drop_self_hits (bool, optional): Discard hits to the query itself.
|
|
212
|
+
Defaults to True.
|
|
213
|
+
seed (int, optional): Random seed for random selection.
|
|
214
|
+
alignment_gap_open (int, optional): Gap open penalty.
|
|
215
|
+
alignment_gap_extend (int, optional): Gap extension penalty.
|
|
216
|
+
scoring_matrix (str, optional): Alignment scoring matrix.
|
|
217
|
+
alignment_min_identity (float, optional): Minimum identity of the
|
|
218
|
+
global alignment for a template. Defaults to 0.0.
|
|
219
|
+
alignment_min_coverage (float, optional): Minimum query coverage of
|
|
220
|
+
the global alignment for a template. Defaults to 0.0.
|
|
221
|
+
angstrom_contact_threshold (float, optional): Contact distance
|
|
222
|
+
threshold. Defaults to 6.
|
|
223
|
+
save_structures (bool, optional): Save template structures.
|
|
224
|
+
save_cmaps (bool, optional): Save contact maps.
|
|
225
|
+
skip_matrix (bool, optional): Skip writing prediction matrices.
|
|
226
|
+
tmpdir (Path, optional): Temporary directory for MMseqs2.
|
|
227
|
+
threads (int, optional): Number of threads. Defaults to 1.
|
|
228
|
+
"""
|
|
229
|
+
output_path = Path(output_path)
|
|
230
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
231
|
+
timing_file = output_path / "timing.tsv"
|
|
232
|
+
timing_file.write_text("\t".join(TIMING_HEADER) + "\n")
|
|
233
|
+
timed = partial(_timed, timing_file)
|
|
234
|
+
|
|
235
|
+
if weights:
|
|
236
|
+
deepfri_models_config = load_deepfri_config(weights)
|
|
237
|
+
deepfri_processing_modes = _initialize_processing_modes(
|
|
238
|
+
deepfri_processing_modes, deepfri_models_config)
|
|
239
|
+
|
|
240
|
+
dbs = load_databases(databases=databases,
|
|
241
|
+
skip_pdb=skip_pdb,
|
|
242
|
+
overwrite=overwrite,
|
|
243
|
+
threads=threads)
|
|
244
|
+
|
|
245
|
+
for db in dbs:
|
|
246
|
+
if search_results:
|
|
247
|
+
db.mmseqs_result = Path(search_results) / f"{db.name}_results.tsv"
|
|
248
|
+
if not db.mmseqs_result.exists():
|
|
249
|
+
raise FileNotFoundError(
|
|
250
|
+
f"No search results for {db.name}: {db.mmseqs_result}")
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
search_path = output_path / "database_search"
|
|
254
|
+
search_path.mkdir(exist_ok=True)
|
|
255
|
+
db.mmseqs_result = search_path / f"{db.name}_results.tsv"
|
|
256
|
+
with timed("search", database=db.name):
|
|
257
|
+
hits = query_file.search(db.mmseqs_db,
|
|
258
|
+
mmseqs_sensitivity=mmseqs_sensitivity,
|
|
259
|
+
eval=max_eval,
|
|
260
|
+
tmpdir=tmpdir,
|
|
261
|
+
threads=threads)
|
|
262
|
+
hits = hits.apply_filters(min_cov=min_coverage,
|
|
263
|
+
min_bits=min_bits,
|
|
264
|
+
min_ident=min_ident)
|
|
265
|
+
hits.find_best_matches(k=None).save(db.mmseqs_result)
|
|
266
|
+
|
|
267
|
+
identity_bins = list(identity_bins)
|
|
268
|
+
bin_names = [
|
|
269
|
+
f"identity_bin_{low:.2f}-{high:.2f}" for low, high in identity_bins
|
|
270
|
+
]
|
|
271
|
+
for db in dbs:
|
|
272
|
+
with timed("hit_selection", database=db.name):
|
|
273
|
+
hits = MMseqsResult.from_best_matches(db.mmseqs_result).result_arr
|
|
274
|
+
for bin_name, identity_bin in zip(bin_names, identity_bins):
|
|
275
|
+
selected = select_hits(hits,
|
|
276
|
+
identity_bin,
|
|
277
|
+
selection=selection,
|
|
278
|
+
drop_self_hits=drop_self_hits,
|
|
279
|
+
seed=seed)
|
|
280
|
+
hits_path = output_path / bin_name / "hits" / f"{db.name}.tsv"
|
|
281
|
+
hits_path.parent.mkdir(parents=True, exist_ok=True)
|
|
282
|
+
MMseqsResult(rfn.repack_fields(selected[MMSEQS_COLUMNS]),
|
|
283
|
+
query_file.filepath, db.mmseqs_db).save(hits_path)
|
|
284
|
+
|
|
285
|
+
for bin_name in bin_names:
|
|
286
|
+
bin_path = output_path / bin_name
|
|
287
|
+
logger.info("Processing %s.", bin_name)
|
|
288
|
+
bin_dbs = [
|
|
289
|
+
dataclasses.replace(db,
|
|
290
|
+
mmseqs_result=bin_path / "hits" /
|
|
291
|
+
f"{db.name}.tsv") for db in dbs
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
with timed("alignment", identity_bin=bin_name):
|
|
295
|
+
alignments = align_database_hits(
|
|
296
|
+
bin_dbs,
|
|
297
|
+
alignment_gap_open=alignment_gap_open,
|
|
298
|
+
alignment_gap_extend=alignment_gap_extend,
|
|
299
|
+
scoring_matrix=scoring_matrix,
|
|
300
|
+
threads=threads)
|
|
301
|
+
for db in bin_dbs:
|
|
302
|
+
write_alignments(bin_path / "alignments" / f"{db.name}.tsv",
|
|
303
|
+
alignments[db.name])
|
|
304
|
+
|
|
305
|
+
with timed("templates", identity_bin=bin_name):
|
|
306
|
+
templates = select_templates(
|
|
307
|
+
query_file,
|
|
308
|
+
bin_dbs,
|
|
309
|
+
alignments,
|
|
310
|
+
min_identity=alignment_min_identity,
|
|
311
|
+
min_coverage=alignment_min_coverage,
|
|
312
|
+
structures_path=(bin_path /
|
|
313
|
+
"structures" if save_structures else None),
|
|
314
|
+
threads=threads)
|
|
315
|
+
|
|
316
|
+
for gc in generate_contacts:
|
|
317
|
+
gc_path = bin_path / f"generate_contacts_{gc}"
|
|
318
|
+
gc_path.mkdir(parents=True, exist_ok=True)
|
|
319
|
+
with timed("contact_maps",
|
|
320
|
+
identity_bin=bin_name,
|
|
321
|
+
generate_contacts=gc):
|
|
322
|
+
aligned_cmaps = build_contact_maps(
|
|
323
|
+
templates,
|
|
324
|
+
angstrom_contact_threshold=angstrom_contact_threshold,
|
|
325
|
+
generate_contacts=gc,
|
|
326
|
+
threads=threads)
|
|
327
|
+
if save_cmaps:
|
|
328
|
+
save_contact_maps(aligned_cmaps, gc_path / "contact_maps")
|
|
329
|
+
|
|
330
|
+
if not weights:
|
|
331
|
+
aligned = {aln.query_name for aln, _ in aligned_cmaps}
|
|
332
|
+
write_alignment_summary(
|
|
333
|
+
gc_path / "alignment_summary.tsv", aligned_cmaps,
|
|
334
|
+
[q for q in query_file.sequences if q not in aligned])
|
|
335
|
+
continue
|
|
336
|
+
|
|
337
|
+
with timed("prediction",
|
|
338
|
+
identity_bin=bin_name,
|
|
339
|
+
generate_contacts=gc):
|
|
340
|
+
predict_from_contact_maps(query_file,
|
|
341
|
+
aligned_cmaps,
|
|
342
|
+
deepfri_models_config,
|
|
343
|
+
deepfri_processing_modes,
|
|
344
|
+
gc_path,
|
|
345
|
+
threads=threads,
|
|
346
|
+
skip_matrix=skip_matrix)
|
|
347
|
+
|
|
348
|
+
logger.info("Benchmark finished successfully.")
|