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.
Files changed (52) hide show
  1. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/PKG-INFO +43 -9
  2. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/README.md +35 -3
  3. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/alignment.py +6 -0
  4. mdeepfri-1.3.0/mDeepFRI/benchmark.py +348 -0
  5. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/cli.py +236 -82
  6. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/contact_map_utils.pyx +1 -0
  7. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/mmseqs.py +49 -21
  8. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/pdb.py +14 -9
  9. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/pipeline.py +330 -148
  10. mdeepfri-1.3.0/mDeepFRI/tests/__init__.py +22 -0
  11. mdeepfri-1.3.0/mDeepFRI/tests/data/AF-A0A3B4WVX2-F1-model_v6.cif +3708 -0
  12. mdeepfri-1.3.0/mDeepFRI/tests/data/GCA_000731455.1.proteins.fa.gz +0 -0
  13. mdeepfri-1.3.0/mDeepFRI/tests/data/small_mapping.tsv +5 -0
  14. mdeepfri-1.3.0/mDeepFRI/tests/data/small_query.faa +23 -0
  15. mdeepfri-1.3.0/mDeepFRI/tests/test_benchmark.py +68 -0
  16. mdeepfri-1.3.0/mDeepFRI/tests/test_cli.py +88 -0
  17. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_mmseqs.py +39 -0
  18. mdeepfri-1.3.0/mDeepFRI/tests/test_pdb.py +66 -0
  19. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_pipeline_regression.py +46 -1
  20. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/utils.py +1 -1
  21. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI.egg-info/PKG-INFO +43 -9
  22. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI.egg-info/SOURCES.txt +7 -1
  23. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI.egg-info/requires.txt +4 -4
  24. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/pyproject.toml +18 -7
  25. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/setup.py +21 -13
  26. mdeepfri-1.2.0/mDeepFRI/tests/__init__.py +0 -13
  27. mdeepfri-1.2.0/mDeepFRI/tests/test_cli.py +0 -32
  28. mdeepfri-1.2.0/mDeepFRI/tests/test_pdb.py +0 -12
  29. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/LICENSE +0 -0
  30. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/__init__.py +0 -0
  31. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/assets/go2cog_USECLO_ALL.tsv +0 -0
  32. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/assets/highquality_clust30_error_ids.pkl +0 -0
  33. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/bio_utils.py +0 -0
  34. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/contact_map.py +0 -0
  35. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/contact_map_utils.pyi +0 -0
  36. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/database.py +0 -0
  37. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/go_propagation.py +0 -0
  38. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/predict.pyi +0 -0
  39. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/predict.pyx +0 -0
  40. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_alignment.py +0 -0
  41. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_bio_utils.py +0 -0
  42. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_conctact_map.py +0 -0
  43. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_contact_map_utils.py +0 -0
  44. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_go2cog_mapping.py +0 -0
  45. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_install.py +0 -0
  46. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_predict.py +0 -0
  47. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_utils.py +0 -0
  48. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI/tests/test_vocabulary_mismatch.py +0 -0
  49. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI.egg-info/dependency_links.txt +0 -0
  50. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI.egg-info/entry_points.txt +0 -0
  51. {mdeepfri-1.2.0 → mdeepfri-1.3.0}/mDeepFRI.egg-info/top_level.txt +0 -0
  52. {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.2.0
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: <3.13,>=3.11
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>=1.3
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>=0.3
33
- Requires-Dist: networkx>=2.6
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, < 3.13 (tested with 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. Installation might take a few minutes
107
- due to download of MMseqs2 binaries.
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, < 3.13 (tested with 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. Installation might take a few minutes
68
- due to download of MMseqs2 binaries.
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.")