wuxing0105 commited on
Commit
84bcf84
·
verified ·
1 Parent(s): b7b760f

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. flax_model/alphafold3/_tools/hmmer/share/man/man1/esl-alimask.1 +618 -0
  2. flax_model/alphafold3/_tools/hmmer/share/man/man1/hmmalign.1 +185 -0
  3. flax_model/alphafold3/_tools/hmmer/share/man/man1/hmmpgmd_shard.1 +162 -0
  4. flax_model/alphafold3/_tools/hmmer/share/man/man1/hmmstat.1 +133 -0
  5. flax_model/alphafold3/_tools/hmmer/share/man/man1/makehmmerdb.1 +133 -0
  6. flax_model/alphafold3/_tools/hmmer/share/man/man1/nhmmer.1 +687 -0
  7. flax_model/alphafold3/_tools/hmmer/share/man/man1/nhmmscan.1 +472 -0
  8. flax_model/alphafold3/data/cpp/__init__.py +0 -0
  9. flax_model/alphafold3/data/cpp/msa_profile_pybind.cc +79 -0
  10. flax_model/alphafold3/data/msa.py +348 -0
  11. flax_model/alphafold3/data/msa_store.py +58 -0
  12. flax_model/alphafold3/data/template_realign.py +160 -0
  13. flax_model/alphafold3/data/templates.py +978 -0
  14. flax_model/alphafold3/data/tools/__init__.py +0 -0
  15. flax_model/alphafold3/data/tools/hmmbuild.py +136 -0
  16. flax_model/alphafold3/data/tools/hmmsearch.py +141 -0
  17. flax_model/alphafold3/data/tools/jackhmmer.py +326 -0
  18. flax_model/alphafold3/data/tools/mmseqs.py +303 -0
  19. flax_model/alphafold3/data/tools/rdkit_utils.py +535 -0
  20. flax_model/alphafold3/data/tools/shards.py +94 -0
  21. flax_model/alphafold3/data/tools/subprocess_utils.py +112 -0
  22. flax_model/alphafold3/jax/__init__.py +0 -0
  23. flax_model/alphafold3/jax/attention/__init__.py +0 -0
  24. flax_model/alphafold3/jax/attention/attention_base.py +355 -0
  25. flax_model/alphafold3/jax/attention/flash_attention.py +697 -0
  26. flax_model/alphafold3/jax/attention/xla_attention.py +132 -0
  27. flax_model/alphafold3/jax/common/__init__.py +0 -0
  28. flax_model/alphafold3/jax/common/triton_utils.py +118 -0
  29. flax_model/alphafold3/jax/gated_linear_unit/block.py +118 -0
  30. flax_model/alphafold3/jax/gated_linear_unit/gated_linear_unit.py +116 -0
  31. flax_model/alphafold3/jax/gated_linear_unit/matmul_config.py +70 -0
  32. flax_model/alphafold3/jax/geometry/__init__.py +20 -0
  33. flax_model/alphafold3/test_data/miniature_databases/nt_rna_2023_02_23_clust_seq_id_90_cov_80_rep_seq__subsampled_1000.fasta +0 -0
  34. scripts/infer_jackhmmer.sh +53 -0
  35. scripts/preflight.py +89 -0
  36. scripts/run_alphafold.py +1194 -0
  37. weight/_dep/abseil-cpp/.github/ISSUE_TEMPLATE/config.yml +5 -0
  38. weight/_dep/abseil-cpp/.github/PULL_REQUEST_TEMPLATE.md +8 -0
  39. weight/_dep/abseil-cpp/BUILD.bazel +25 -0
  40. weight/_dep/abseil-cpp/CMake/Googletest/CMakeLists.txt.in +14 -0
  41. weight/_dep/abseil-cpp/CMake/Googletest/DownloadGTest.cmake +41 -0
  42. weight/_dep/abseil-cpp/CMake/README.md +188 -0
  43. weight/_dep/abseil-cpp/CMake/install_test_project/CMakeLists.txt +25 -0
  44. weight/_dep/abseil-cpp/CMake/install_test_project/simple.cc +32 -0
  45. weight/_dep/abseil-cpp/CMake/install_test_project/test.sh +112 -0
  46. weight/_dep/abseil-cpp/CMakeLists.txt +270 -0
  47. weight/_dep/abseil-cpp/CONTRIBUTING.md +141 -0
  48. weight/_dep/abseil-cpp/FAQ.md +167 -0
  49. weight/_dep/abseil-cpp/MODULE.bazel +39 -0
  50. weight/_dep/abseil-cpp/PrivacyInfo.xcprivacy +14 -0
flax_model/alphafold3/_tools/hmmer/share/man/man1/esl-alimask.1 ADDED
@@ -0,0 +1,618 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .TH "esl\-alimask" 1 "Aug 2023" "Easel 0.49" "Easel Manual"
2
+
3
+ .SH NAME
4
+ esl\-alimask \- remove columns from a multiple sequence alignment
5
+
6
+ .SH SYNOPSIS
7
+
8
+ .nf
9
+ \fBesl\-alimask \fR[\fIoptions\fR] \fImsafile maskfile\fR
10
+ (remove columns based on a mask in an input file)
11
+
12
+ \fBesl\-alimask \-t \fR[\fIoptions\fR] \fImsafile coords\fR
13
+ (remove a contiguous set of columns at the start and end of an alignment)
14
+
15
+ \fBesl\-alimask \-g \fR[\fIoptions\fR] \fImsafile\fR
16
+ (remove columns based on their frequency of gaps)
17
+
18
+ \fBesl\-alimask \-p \fR[\fIoptions\fR] \fImsafile\fR
19
+ (remove columns based on their posterior probability annotation)
20
+
21
+ \fBesl\-alimask \-\-rf\-is\-mask \fR[\fIoptions\fR] \fImsafile\fR
22
+ (only remove columns that are gaps in the RF annotation)
23
+
24
+ The \fB\-g\fR and \fB\-p\fR options may be used in combination.
25
+ .fi
26
+
27
+
28
+ .SH DESCRIPTION
29
+
30
+ .B esl\-alimask
31
+ reads a single input alignment, removes some columns from it
32
+ (i.e. masks it), and outputs the masked alignment.
33
+
34
+ .PP
35
+ .B esl\-alimask
36
+ can be run in several different modes.
37
+
38
+ .PP
39
+ .B esl\-alimask
40
+ runs in "mask file mode" by default when two
41
+ command-line arguments (\fImsafile\fR and \fImaskfile\fR)
42
+ are supplied. In this mode, a bit-vector mask in the
43
+ .I maskfile
44
+ defines which columns to keep/remove. The mask is a string that may
45
+ only contain the characters '0' and '1'. A '0' at position x of the
46
+ mask indicates that column x is excluded by the mask and should be
47
+ removed during masking. A '1' at position x of the mask indicates
48
+ that column x is included by the mask and should not be removed during
49
+ masking. All lines in the
50
+ .I maskfile
51
+ that begin with '#' are considered comment lines and are ignored. All
52
+ non-whitespace characters in non-comment lines are considered to be
53
+ part of the mask. The length of the mask must equal either the total
54
+ number of columns in the (first) alignment in
55
+ .I msafile,
56
+ or the number of columns that are not gaps in the RF annotation of that
57
+ alignment. The latter case is only valid if
58
+ .I msafile
59
+ is in Stockholm format and contains '#=GC RF' annotation.
60
+ If the mask length is equal to the non-gap RF length, all gap
61
+ RF columns will automatically be removed.
62
+
63
+ .PP
64
+ .B esl\-alimask
65
+ runs in "truncation mode" if the
66
+ .B \-t
67
+ option is used along with two command line arguments
68
+ (\fImsafile\fR and \fIcoords\fR). In this mode,
69
+ the alignment will be truncated by removing a contiguous set of
70
+ columns from the beginning and end of the alignment. The second
71
+ command line argument is the
72
+ .I coords
73
+ string, that specifies what range of columns to keep in the
74
+ alignment, all columns outside of this range will be removed.
75
+ The
76
+ .I coords
77
+ string consists of start and end coordinates separated
78
+ by any nonnumeric, nonwhitespace character or characters you like; for
79
+ example,
80
+ .BR 23..100 ,
81
+ .BR 23/100 ,
82
+ or
83
+ .B 23\-100
84
+ all work. To keep all alignment columns beginning at 23 until the
85
+ end of the alignment, you
86
+ can omit the end; for example,
87
+ .B 23:
88
+ would work.
89
+ If the
90
+ .B \-\-t\-rf
91
+ option is used in combination with
92
+ .B \-t,
93
+ the coordinates in
94
+ .I coords
95
+ are interpreted as non-gap RF column coordinates. For example,
96
+ with
97
+ .B \-\-t\-rf,
98
+ a
99
+ .I coords
100
+ string of
101
+ .B 23\-100
102
+ would remove all columns before the 23rd non-gap residue in
103
+ the "#=GC RF" annotation and after the 100th non-gap RF residue.
104
+
105
+ .PP
106
+ .B esl\-alimask
107
+ runs in "RF mask" mode if the
108
+ .B \-\-rf\-is\-mask
109
+ option is enabled. In this mode, the alignment must be in Stockholm
110
+ format and contain '#=GC RF' annotation.
111
+ .B esl\-alimask
112
+ will simply remove all columns that are gaps in the RF annotation.
113
+
114
+ .PP
115
+ .B esl\-alimask
116
+ runs in "gap frequency mode" if
117
+ .B \-g
118
+ is enabled. In this mode columns for which greater than
119
+ .I <f>
120
+ fraction of the aligned sequences have gap residues will be removed.
121
+ By default,
122
+ .I <f>
123
+ is 0.5, but this value can be changed to
124
+ .I <f>
125
+ with the
126
+ .BI \-\-gapthresh " <f>"
127
+ option. In this mode, if the alignment is in Stockholm format and
128
+ has RF annotation, then all columns that are gaps in the RF annotation
129
+ will automatically be removed, unless
130
+ .B \-\-saveins
131
+ is enabled.
132
+
133
+ .PP
134
+ .B esl\-alimask
135
+ runs in "posterior probability mode" if
136
+ .B \-p
137
+ is enabled. In this mode, masking is based on posterior probability annotation,
138
+ and the input alignment must be in Stockholm format and contain '#=GR
139
+ PP' (posterior probability) annotation for all sequences. As a special
140
+ case, if
141
+ .B \-p
142
+ is used in combination with
143
+ .B \-\-ppcons,
144
+ then the input alignment need not have '#=GR PP' annotation, but must
145
+ contain '#=GC PP_cons' (posterior probability consensus) annotation.
146
+
147
+ .PP
148
+ Characters in Stockholm alignment posterior probability annotation
149
+ (both '#=GR PP' and '#=GC PP_cons') can have 12 possible values: the
150
+ ten digits '0-9', '*', and '.'. If '.', the position must correspond to
151
+ a gap in the sequence (for '#=GR PP') or in the RF annotation (for '#=GC
152
+ PP_cons'). A value of '0' indicates a posterior probability of
153
+ between 0.0 and 0.05, '1' indicates between 0.05 and 0.15, '2'
154
+ indicates between 0.15 and 0.25 and so on up to '9' which indicates
155
+ between 0.85 and 0.95. A value of '*' indicates a posterior
156
+ probability of between 0.95 and 1.0. Higher posterior probabilities
157
+ correspond to greater confidence that the aligned residue belongs
158
+ where it appears in the alignment.
159
+
160
+ .PP
161
+ When
162
+ .B \-p
163
+ is enabled with
164
+ .BI \-\-ppcons " <x>",
165
+ columns which have a consensus posterior probability of less than
166
+ .I <x>
167
+ will be removed during masking, and all other columns will not be removed.
168
+
169
+ .PP
170
+ When
171
+ .B \-p
172
+ is enabled without
173
+ .B \-\-ppcons,
174
+ the number of each possible PP value in each column is counted.
175
+ If
176
+ .I <x>
177
+ fraction of the sequences that contain aligned residues (i.e. do not
178
+ contain gaps) in a column have a posterior probability
179
+ greater than or equal to
180
+ .I <y>,
181
+ then that column will not be removed during masking. All columns that
182
+ do not meet this criterion will be removed. By default, the values of both
183
+ .I <x>
184
+ and
185
+ .I <y>
186
+ are 0.95, but they can be changed with the
187
+ .BI \-\-pfract " <x>"
188
+ and
189
+ .BI \-\-pthresh " <y>"
190
+ options, respectively.
191
+
192
+ .PP
193
+ In posterior probability mode, all columns that have 0 residues
194
+ (i.e. that are 100% gaps) will be automatically removed, unless the
195
+ .B \-\-pallgapok
196
+ option is enabled, in which case such columns will not be removed.
197
+
198
+ .PP
199
+ Importantly, during posterior probability masking, unless
200
+ .B \-\-pavg
201
+ is used, PP annotation
202
+ values are always considered to be the minimum numerical value in
203
+ their corresponding range. For example, a PP '9' character is converted
204
+ to a numerical posterior probability of 0.85. If
205
+ .B \-\-pavg
206
+ is used, PP annotation values are considered to be the average
207
+ numerical value in their range. For example, a PP '9' character is
208
+ converted to a numerical posterior probability of 0.90.
209
+
210
+ .PP
211
+ In posterior probability mode, if the alignment is in Stockholm format and
212
+ has RF annotation, then all columns that are gaps in the RF annotation
213
+ will automatically be removed, unless
214
+ .B \-\-saveins
215
+ is enabled.
216
+
217
+ .PP
218
+ A single run of
219
+ .B esl\-alimask
220
+ can perform both gap frequency-based masking and posterior
221
+ probability-based masking if both the
222
+ .B \-g
223
+ and
224
+ .B \-p
225
+ options are enabled. In this case, a gap frequency-based mask and a
226
+ posterior probability-based mask are independently computed. These
227
+ two masks are combined to create the final mask using a logical 'and'
228
+ operation. Any column that is to be removed by either the gap or PP
229
+ mask will be removed by the final mask.
230
+
231
+ .PP
232
+ With the
233
+ .B \-\-small
234
+ option,
235
+ .B esl\-alimask
236
+ will operate in memory saving mode and the required RAM for the masking
237
+ will be minimal (usually less than a Mb) and independent of the
238
+ alignment size. To use
239
+ .BR \-\-small ,
240
+ the alignment alphabet must be specified with either
241
+ .BR \-\-amino ,
242
+ .BR \-\-dna ,
243
+ or
244
+ .BR \-\-rna ,
245
+ and the alignment must be in Pfam format (non-interleaved, 1
246
+ line/sequence Stockholm format). Pfam format is the default output
247
+ format of INFERNAL's
248
+ .B cmalign
249
+ program. Without
250
+ .B \-\-small
251
+ the required RAM will be equal to roughly the size of the first input
252
+ alignment (the size of the alignment file itself if it only contains
253
+ one alignment).
254
+
255
+
256
+ .SH OUTPUT
257
+
258
+ By default,
259
+ .B esl\-alimask
260
+ will print only the masked alignment to stdout and then exit.
261
+ If the
262
+ .BI \-o " <f>"
263
+ option is used, the alignment will be saved to file
264
+ .I <f>
265
+ , and information on the number of columns kept and removed will be
266
+ printed to stdout. If
267
+ .B \-q
268
+ is used in combination with
269
+ .BR \-o ,
270
+ nothing is printed to stdout.
271
+
272
+ .PP
273
+ The mask(s) computed by
274
+ .B esl\-alimask
275
+ when the
276
+ .BR \-t ,
277
+ .BR \-p ,
278
+ .BR \-g ,
279
+ or
280
+ .B \-\-rf\-is\-mask
281
+ options are used can be saved to output files using the options
282
+ \fB\-\-fmask\-rf\fR\fI <f>\fR,
283
+ \fB\-\-fmask\-all\fR\fI <f>\fR,
284
+ \fB\-\-gmask\-rf\fR\fI <f>\fR,
285
+ \fB\-\-gmask\-all\fR\fI <f>\fR,
286
+ \fB\-\-pmask\-rf\fR\fI <f>\fR, and
287
+ \fB\-\-pmask\-all\fR\fI <f>\fR.
288
+ In all cases,
289
+ .I <f>
290
+ will contain a single line, a bit vector of length
291
+ .I <n>,
292
+ where
293
+ .I <n>
294
+ is the either the total number of columns in the alignment (for the
295
+ options suffixed with 'all') or the number of non-gap columns in the
296
+ RF annotation (for the options suffixed with 'rf'). The mask will be a
297
+ string of '0' and '1' characters: a '0' at position x in the mask
298
+ indicates column x was removed (excluded) by the mask, and a '1' at
299
+ position x indicates column x was kept (included) by the mask. For
300
+ the 'rf' suffixed options, the mask only applies to non-gap RF
301
+ columns. The options beginning with 'f' will save the 'final' mask
302
+ used to keep/remove columns from the alignment. The options beginning
303
+ with 'g' save the masks based on gap frequency and require
304
+ .BR \-g .
305
+ The options beginning with 'p' save the masks based on posterior
306
+ probabilities and require
307
+ .BR \-p .
308
+
309
+
310
+ .SH OPTIONS
311
+
312
+ .TP
313
+ .B \-h
314
+ Print brief help; includes version number and summary of
315
+ all options, including expert options.
316
+
317
+ .TP
318
+ .BI \-o " <f>"
319
+ Output the final, masked alignment to file
320
+ .I <f>
321
+ instead of to stdout.
322
+ When this option is used, information about the number of columns
323
+ kept/removed is printed to stdout.
324
+
325
+ .TP
326
+ .B \-q
327
+ Be quiet; do not print anything to stdout.
328
+ This option can only be used in combination with the
329
+ .B \-o
330
+ option.
331
+
332
+ .TP
333
+ .B \-\-small
334
+ Operate in memory saving mode. Required RAM will be independent of the
335
+ size of the input alignment to mask, instead of roughly the size of the
336
+ input alignment. When enabled, the alignment must be in
337
+ Pfam Stockholm (non-interleaved 1 line/seq) format (see
338
+ .BR esl\-reformat )
339
+ and the output alignment will be in Pfam format.
340
+
341
+ .TP
342
+ .BI \-\-informat " <s>"
343
+ Assert that input
344
+ .I msafile
345
+ is in alignment format
346
+ .IR <s> .
347
+ Common choices for
348
+ .I <s>
349
+ include:
350
+ .BR stockholm ,
351
+ .BR a2m ,
352
+ .BR afa ,
353
+ .BR psiblast ,
354
+ .BR clustal ,
355
+ .BR phylip .
356
+ For more information, and for codes for some less common formats,
357
+ see main documentation.
358
+ The string
359
+ .I <s>
360
+ is case-insensitive (\fBa2m\fR or \fBA2M\fR both work).
361
+ Default is
362
+ .B stockholm
363
+ format, unless
364
+ .B \-\-small
365
+ is used, in which case
366
+ .B pfam
367
+ format (non-interleaved Stockholm) is assumed.
368
+
369
+ .TP
370
+ .BI \-\-outformat " <s>"
371
+ Write the output
372
+ .I msafile
373
+ in alignment format
374
+ .IR <s> .
375
+ Common choices for
376
+ .I <s>
377
+ include:
378
+ .BR stockholm ,
379
+ .BR a2m ,
380
+ .BR afa ,
381
+ .BR psiblast ,
382
+ .BR clustal ,
383
+ .BR phylip .
384
+ The string
385
+ .I <s>
386
+ is case-insensitive (\fBa2m\fR or \fBA2M\fR both work).
387
+ Default is
388
+ .BR stockholm ,
389
+ unless
390
+ .B \-\-small
391
+ is enabled, in which case
392
+ .B pfam
393
+ (noninterleaved Stockholm) is the default output format.
394
+
395
+
396
+ .TP
397
+ .BI \-\-fmask\-rf " <f>"
398
+ Save the non-gap RF-length final mask used to mask the alignment
399
+ to file
400
+ .IR <f> .
401
+ The input alignment must be in Stockholm format and contain '#=GC RF'
402
+ annotation for this option to be valid. See the OUTPUT section above for
403
+ more details on output mask files.
404
+
405
+ .TP
406
+ .BI \-\-fmask\-all " <f>"
407
+ Save the full alignment-length final mask used to mask the alignment
408
+ to file
409
+ .IR <f> .
410
+ See the OUTPUT section above for more details on output mask files.
411
+
412
+ .TP
413
+ .B \-\-amino
414
+ Specify that the input alignment is a protein alignment.
415
+ By default,
416
+ .B esl\-alimask
417
+ will try to autodetect the alphabet, but if the alignment is
418
+ sufficiently small it may be ambiguous. This option defines the
419
+ alphabet as protein. Importantly, if
420
+ .B \-\-small
421
+ is enabled, the alphabet must be specified with either
422
+ .BR \-\-amino ,
423
+ .BR \-\-dna ,
424
+ or
425
+ .BR \-\-rna .
426
+
427
+ .TP
428
+ .B \-\-dna
429
+ Specify that the input alignment is a DNA alignment.
430
+
431
+ .TP
432
+ .B \-\-rna
433
+ Specify that the input alignment is an RNA alignment.
434
+
435
+ .TP
436
+ .B \-\-t\-rf
437
+ With
438
+ .BR \-t ,
439
+ specify that the start and end coordinates defined in
440
+ the second command line argument
441
+ .I coords
442
+ correspond to non-gap RF coordinates. To use this option, the
443
+ alignment must be in Stockholm format and have "#=GC RF"
444
+ annotation. See the DESCRIPTION section for an example of using the
445
+ .B \-\-t\-rf
446
+ option.
447
+
448
+ .TP
449
+ .B \-\-t\-rmins
450
+ With
451
+ .BR \-t ,
452
+ specify that all columns that are gaps in the reference (RF)
453
+ annotation in between the specified start and end coordinates be
454
+ removed. By default, these columns will be kept.
455
+ To use this option, the alignment must be in Stockholm format and
456
+ have "#=GC RF" annotation.
457
+
458
+ .TP
459
+ .BI \-\-gapthresh " <x>"
460
+ With
461
+ .BR \-g ,
462
+ specify that a column is kept (included by mask) if no more
463
+ than
464
+ .I <f>
465
+ fraction of sequences in the alignment have a gap ('.', '\-', or '_')
466
+ at that position. All other columns are removed (excluded by mask).
467
+ By default,
468
+ .I <x>
469
+ is 0.5.
470
+
471
+ .TP
472
+ .BI \-\-gmask\-rf " <f>"
473
+ Save the non-gap RF-length gap frequency-based mask used to mask the alignment
474
+ to file
475
+ .IR <f> .
476
+ The input alignment must be in Stockholm format and contain '#=GC RF'
477
+ annotation for this option to be valid. See the OUTPUT section above for
478
+ more details on output mask files.
479
+
480
+ .TP
481
+ .BI \-\-gmask\-all " <f>"
482
+ Save the full alignment-length gap frequency-based mask used to mask the alignment
483
+ to file
484
+ .IR <f> .
485
+ See the OUTPUT section above for more details on output mask files.
486
+
487
+
488
+ .TP
489
+ .BI \-\-pfract " <x>"
490
+ With
491
+ .BR \-p ,
492
+ specify that a column is kept (included by mask) if the
493
+ fraction of sequences with a non-gap residue in that column with a
494
+ posterior probability of at least
495
+ .I <y>
496
+ (from \fB\-\-pthresh\fR\fI <y>\fR) is
497
+ .I <x>
498
+ or greater. All other columns are removed (excluded by mask).
499
+ By default
500
+ .I <x>
501
+ is 0.95.
502
+
503
+ .TP
504
+ .BI \-\-pthresh " <y>"
505
+ With
506
+ .BR \-p ,
507
+ specify that a column is kept (included by mask) if
508
+ .I <x>
509
+ (from \fB\-\-pfract \fR\fI<x>\fR)
510
+ fraction of sequences with a non-gap residue in that column have a
511
+ posterior probability of at least
512
+ .IR <y> .
513
+ All other columns are removed (excluded by mask).
514
+ By default
515
+ .I <y>
516
+ is 0.95. See the DESCRIPTION section for more on
517
+ posterior probability (PP) masking.
518
+ Due to the granularity of the PP annotation, different
519
+ .I <y>
520
+ values within a range covered by a single PP character will be
521
+ have the same effect on masking. For example, using
522
+ .B \-\-pthresh 0.86
523
+ will have the same effect as using
524
+ \fB\-\-pthresh 0.94\fR.
525
+
526
+ .TP
527
+ .BI \-\-pavg " <x>"
528
+ With
529
+ .BR \-p ,
530
+ specify that a column is kept (included by mask) if
531
+ the average posterior probability of non-gap residues in that column
532
+ is at least
533
+ .IR <x> .
534
+ See the DESCRIPTION section for more on
535
+ posterior probability (PP) masking.
536
+
537
+ .TP
538
+ .BI \-\-ppcons " <x>"
539
+ With
540
+ .BR \-p ,
541
+ use the '#=GC PP_cons' annotation to define which columns to
542
+ keep/remove. A column is kept (included by mask) if the PP_cons value
543
+ for that column is
544
+ .I <x>
545
+ or greater. Otherwise it is removed.
546
+
547
+ .TP
548
+ .B \-\-pallgapok
549
+ With
550
+ .BR \-p ,
551
+ do not automatically remove any columns that are 100% gaps
552
+ (i.e. contain 0 aligned residues). By default, such columns will be
553
+ removed.
554
+
555
+ .TP
556
+ .BI \-\-pmask\-rf " <f>"
557
+ Save the non-gap RF-length posterior probability-based mask used to mask the alignment
558
+ to file
559
+ .IR <f> .
560
+ The input alignment must be in Stockholm format and contain '#=GC RF'
561
+ annotation for this option to be valid. See the OUTPUT section above for
562
+ more details on output mask files.
563
+
564
+ .TP
565
+ .BI \-\-pmask\-all " <f>"
566
+ Save the full alignment-length posterior probability-based mask used to mask the alignment
567
+ to file
568
+ .IR <f> .
569
+ See the OUTPUT section above for more details on output mask files.
570
+
571
+
572
+ .TP
573
+ .B \-\-keepins
574
+ If
575
+ .B \-p
576
+ and/or
577
+ .B \-g
578
+ is enabled and the alignment is in Stockholm or Pfam format and has '#=GC RF'
579
+ annotation, then allow columns that are gaps in the RF annotation to
580
+ possibly be kept. By default, all gap RF columns would be removed
581
+ automatically, but with this option enabled gap and non-gap RF columns
582
+ are treated identically.
583
+ To automatically remove all gap RF columns when using a
584
+ .I maskfile
585
+ , then define the mask in
586
+ .I maskfile
587
+ as having length equal to the non-gap RF length in the alignment.
588
+ To automatically remove all gap RF columns when using
589
+ .B \-t,
590
+ use the
591
+ .B \-\-t\-rmins
592
+ option.
593
+
594
+
595
+
596
+
597
+
598
+
599
+
600
+
601
+ .SH SEE ALSO
602
+
603
+ .nf
604
+ http://bioeasel.org/
605
+ .fi
606
+
607
+ .SH COPYRIGHT
608
+
609
+ .nf
610
+ Copyright (C) 2023 Howard Hughes Medical Institute.
611
+ Freely distributed under the BSD open source license.
612
+ .fi
613
+
614
+ .SH AUTHOR
615
+
616
+ .nf
617
+ http://eddylab.org
618
+ .fi
flax_model/alphafold3/_tools/hmmer/share/man/man1/hmmalign.1 ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .TH "hmmalign" 1 "Aug 2023" "HMMER 3.4" "HMMER Manual"
2
+
3
+ .SH NAME
4
+ hmmalign \- align sequences to a profile
5
+
6
+ .SH SYNOPSIS
7
+ .B hmmalign
8
+ [\fIoptions\fR]
9
+ .I hmmfile
10
+ .I seqfile
11
+
12
+ .SH DESCRIPTION
13
+
14
+ .PP
15
+ Perform a multiple sequence alignment of all the sequences in
16
+ .I seqfile
17
+ by aligning them individually to the profile HMM in
18
+ .I hmmfile.
19
+ The new alignment is output to stdout.
20
+
21
+ .PP
22
+ The
23
+ .I hmmfile
24
+ should contain only a single profile. If it contains more, only the
25
+ first profile in the file will be used.
26
+
27
+ .PP
28
+ Either
29
+ .I hmmfile
30
+ or
31
+ .I seqfile
32
+ (but not both) may be '\-' (dash), which
33
+ means reading this input from stdin rather than a file.
34
+
35
+ .PP
36
+ The sequences in
37
+ .I seqfile
38
+ are aligned in unihit local alignment mode. Therefore they should
39
+ already be known to contain only a single domain (or a fragment of one).
40
+ The optimal alignment
41
+ may assign some residues as nonhomologous (N and C states), in which
42
+ case these residues are still included in the resulting alignment, but
43
+ shoved to the outer edges. To trim these unaligned nonhomologous residues from
44
+ the result, see the
45
+ .B \-\-trim
46
+ option.
47
+
48
+
49
+ .SH OPTIONS
50
+
51
+ .TP
52
+ .B \-h
53
+ Help; print a brief reminder of command line usage and all available
54
+ options.
55
+
56
+ .TP
57
+ .BI \-o " <f>"
58
+ Direct the output alignment to file
59
+ .I <f>,
60
+ rather than to stdout.
61
+
62
+ .TP
63
+ .BI \-\-mapali " <f>"
64
+ Merge the existing alignment in file
65
+ .I <f>
66
+ into the result, where
67
+ .I <f>
68
+ is exactly the same alignment that was used to build the model in
69
+ .I hmmfile.
70
+ This is done using a map of alignment columns to consensus
71
+ profile positions that is stored in the
72
+ .I hmmfile.
73
+ The multiple alignment in
74
+ .I <f>
75
+ will be exactly reproduced in its consensus columns (as defined by the
76
+ profile), but the displayed alignment in insert columns may be
77
+ altered, because insertions relative to a profile are considered by
78
+ convention to be unaligned data.
79
+
80
+
81
+ .TP
82
+ .B \-\-trim
83
+ Trim nonhomologous residues (assigned to N and C states in the optimal
84
+ alignments) from the resulting multiple alignment output.
85
+
86
+ .TP
87
+ .B \-\-amino
88
+ Assert that sequences in
89
+ .I seqfile
90
+ are protein, bypassing alphabet autodetection.
91
+
92
+ .TP
93
+ .B \-\-dna
94
+ Assert that sequences in
95
+ .I seqfile
96
+ are DNA, bypassing alphabet autodetection.
97
+
98
+ .TP
99
+ .B \-\-rna
100
+ Assert that sequences in
101
+ .I seqfile
102
+ are RNA, bypassing alphabet autodetection.
103
+
104
+
105
+ .TP
106
+ .BI \-\-informat " <s>"
107
+ Assert that input
108
+ .I seqfile
109
+ is in format
110
+ .IR <s> ,
111
+ bypassing format autodetection.
112
+ Common choices for
113
+ .I <s>
114
+ include:
115
+ .BR fasta ,
116
+ .BR embl ,
117
+ .BR genbank.
118
+ Alignment formats also work;
119
+ common choices include:
120
+ .BR stockholm ,
121
+ .BR a2m ,
122
+ .BR afa ,
123
+ .BR psiblast ,
124
+ .BR clustal ,
125
+ .BR phylip .
126
+ For more information, and for codes for some less common formats,
127
+ see main documentation.
128
+ The string
129
+ .I <s>
130
+ is case-insensitive (\fBfasta\fR or \fBFASTA\fR both work).
131
+
132
+ .TP
133
+ .BI \-\-outformat " <s>"
134
+ Write the output alignment
135
+ in format
136
+ .IR <s> .
137
+ Common choices for
138
+ .I <s>
139
+ include:
140
+ .BR stockholm ,
141
+ .BR a2m ,
142
+ .BR afa ,
143
+ .BR psiblast ,
144
+ .BR clustal ,
145
+ .BR phylip .
146
+ The string
147
+ .I <s>
148
+ is case-insensitive (\fBa2m\fR or \fBA2M\fR both work).
149
+ Default is
150
+ .BR stockholm .
151
+
152
+
153
+
154
+ .SH SEE ALSO
155
+
156
+ See
157
+ .BR hmmer (1)
158
+ for a master man page with a list of all the individual man pages
159
+ for programs in the HMMER package.
160
+
161
+ .PP
162
+ For complete documentation, see the user guide that came with your
163
+ HMMER distribution (Userguide.pdf); or see the HMMER web page
164
+ (http://hmmer.org/).
165
+
166
+
167
+
168
+ .SH COPYRIGHT
169
+
170
+ .nf
171
+ Copyright (C) 2023 Howard Hughes Medical Institute.
172
+ Freely distributed under the BSD open source license.
173
+ .fi
174
+
175
+ For additional information on copyright and licensing, see the file
176
+ called COPYRIGHT in your HMMER source distribution, or see the HMMER
177
+ web page
178
+ (http://hmmer.org/).
179
+
180
+
181
+ .SH AUTHOR
182
+
183
+ .nf
184
+ http://eddylab.org
185
+ .fi
flax_model/alphafold3/_tools/hmmer/share/man/man1/hmmpgmd_shard.1 ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .TH "hmmpgmd_shard" 1 "Aug 2023" "HMMER 3.4" "HMMER Manual"
2
+
3
+ .SH NAME
4
+ hmmpgmd_shard \- sharded daemon for database search web services
5
+
6
+
7
+ .SH SYNOPSIS
8
+ .B hmmpgmd_shard
9
+ [\fIoptions\fR]
10
+
11
+
12
+ .SH DESCRIPTION
13
+
14
+ .PP
15
+ The
16
+ .B hmmpgmd_shard
17
+ program provides a sharded version of the
18
+ .B hmmpgmd
19
+ program that we use internally to implement high-performance HMMER services that can be accessed via the internet. See the
20
+ .B hmmpgmd
21
+ man page for a discussion of how the base
22
+ .B hmmpgmd
23
+ program is used. This man page discusses differences between
24
+ .B hmmpgmd_shard
25
+ and
26
+ .B hmmpgmd.
27
+ The base
28
+ .B hmmpgmd
29
+ program loads the entirety of its database file into RAM on every worker node, in spite of the fact that each worker node searches a predictable fraction of the database(s) contained in that file when performing searches. This wastes RAM, particularly when many worker nodes are used to accelerate searches of large databases.
30
+
31
+ .PP
32
+ .B Hmmpgmd_shard
33
+ addresses this by dividing protein sequence database files into shards. Each worker node loads only 1/Nth of the database file, where N is the number of worker nodes attached to the master. HMM database files are not sharded, meaning that every worker node will load the entire database file into RAM. Current HMM databases are much smaller than current protein sequence databases, and easily fit into the RAM of modern servers even without sharding.
34
+
35
+ .PP
36
+ .B Hmmpgmd_shard
37
+ is used in the same manner as
38
+ .B hmmpgmd
39
+ , except that it takes one additional argument:
40
+ .BI \-\-num_shards " <n>"
41
+ , which specifies the number of shards that protein databases will be divided into, and defaults to 1 if unspecified. This argument is only valid for the master node of a
42
+ .B hmmpgmd
43
+ system (i.e., when
44
+ .BI \-\-master
45
+ is passed to the
46
+ .B hmmpgmd
47
+ program), and must be equal to the number of worker nodes that will connect to the master node.
48
+ .B Hmmpgmd_shard
49
+ will signal an error if more than
50
+ .BI num_shards
51
+ worker nodes attempt to connect to the master node or if a search is started when fewer than
52
+ .BI num_shards
53
+ workers are connected to the master.
54
+
55
+ .SH OPTIONS
56
+
57
+ .TP
58
+ .B \-h
59
+ Help; print a brief reminder of command line usage and all available
60
+ options.
61
+
62
+ .TP
63
+ .BI \-\-master
64
+ Run as the master server.
65
+
66
+ .TP
67
+ .BI \-\-worker " <s>"
68
+ Run as a worker, connecting to the master server that is running on IP
69
+ address
70
+ .IR <s> .
71
+
72
+ .TP
73
+ .BI \-\-cport " <n>"
74
+ Port to use for communication between clients and the master server.
75
+ The default is 51371.
76
+
77
+ .TP
78
+ .BI \-\-wport " <n>"
79
+ Port to use for communication between workers and the master server.
80
+ The default is 51372.
81
+
82
+ .TP
83
+ .BI \-\-ccncts " <n>"
84
+ Maximum number of client connections to accept. The default is 16.
85
+
86
+ .TP
87
+ .BI \-\-wcncts " <n>"
88
+ Maximum number of worker connections to accept. The default is 32.
89
+
90
+ .TP
91
+ .BI \-\-pid " <f>"
92
+ Name of file into which the process id will be written.
93
+
94
+ .TP
95
+ .BI \-\-seqdb " <f>"
96
+ Name of the file (in
97
+ .B hmmpgmd
98
+ format) containing protein sequences.
99
+ The contents of this file will be cached for searches.
100
+
101
+ .TP
102
+ .BI \-\-hmmdb " <f>"
103
+ Name of the file containing protein HMMs. The contents of this file
104
+ will be cached for searches.
105
+
106
+ .TP
107
+ .BI \-\-cpu " <n>"
108
+ Number of parallel threads to use (for
109
+ .B \-\-worker
110
+ ).
111
+
112
+ .TP
113
+ .BI \-\-num_shards " <n>"
114
+ Number of shards to divide cached sequence database(s) into. HMM databases are not sharded, due to their small size.
115
+ This option is only valid when the
116
+ .B \-\-master
117
+ option is present, and defaults to 1 if not specified.
118
+ .B Hmmpgmd_shard
119
+ requires that the number of shards be equal to the number of worker nodes, and will give errors if more than
120
+ .BI num_shards
121
+ workers attempt to connect to the master node or if a search is started with fewer than
122
+ .BI num_shards
123
+ workers connected to the master.
124
+
125
+ .SH SEE ALSO
126
+
127
+ See
128
+ .BR hmmmpgmd (1)
129
+ for a description of the base hmmpgmd command and how the daemon should be used.
130
+
131
+ .BR hmmer (1)
132
+ for a master man page with a list of all the individual man pages
133
+ for programs in the HMMER package.
134
+
135
+ .PP
136
+ For complete documentation, see the user guide that came with your
137
+ HMMER distribution (Userguide.pdf); or see the HMMER web page
138
+ (http://hmmer.org/).
139
+
140
+
141
+
142
+ .SH COPYRIGHT
143
+
144
+ .nf
145
+ Copyright (C) 2023 Howard Hughes Medical Institute.
146
+ Freely distributed under the BSD open source license.
147
+ .fi
148
+
149
+ For additional information on copyright and licensing, see the file
150
+ called COPYRIGHT in your HMMER source distribution, or see the HMMER
151
+ web page
152
+ (http://hmmer.org/).
153
+
154
+
155
+ .SH AUTHOR
156
+
157
+ .nf
158
+ http://eddylab.org
159
+ .fi
160
+
161
+
162
+
flax_model/alphafold3/_tools/hmmer/share/man/man1/hmmstat.1 ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .TH "hmmstat" 1 "Aug 2023" "HMMER 3.4" "HMMER Manual"
2
+
3
+ .SH NAME
4
+ hmmstat \- summary statistics for a profile file
5
+
6
+
7
+ .SH SYNOPSIS
8
+ .B hmmstat
9
+ [\fIoptions\fR]
10
+ .I hmmfile
11
+
12
+
13
+ .SH DESCRIPTION
14
+
15
+ The
16
+ .B hmmstat
17
+ utility prints out a tabular file of summary statistics for each
18
+ profile in
19
+ .IR hmmfile .
20
+
21
+
22
+ .PP
23
+ .I hmmfile
24
+ may be '\-' (a dash character), in which case profiles
25
+ are read from a
26
+ stdin
27
+ pipe instead of from a file.
28
+
29
+ .PP
30
+ The columns are:
31
+
32
+ .TP
33
+ .B idx
34
+ The index of this profile, numbering each profile in the file starting from 1.
35
+
36
+ .TP
37
+ .B name
38
+ The name of the profile.
39
+
40
+ .TP
41
+ .B accession
42
+ The optional accession of the profile, or "\-" if there is none.
43
+
44
+ .TP
45
+ .B nseq
46
+ The number of sequences that the profile was estimated from.
47
+
48
+ .TP
49
+ .B eff_nseq
50
+ The effective number of sequences that the profile was estimated
51
+ from, after HMMER applied an effective sequence number calculation
52
+ such as the default entropy weighting.
53
+
54
+ .TP
55
+ .B M
56
+ The length of the model in consensus residues (match states).
57
+
58
+ .TP
59
+ .B relent
60
+ Mean relative entropy per match state, in bits. This is the expected
61
+ (mean) score per consensus position. This is what the default
62
+ entropy-weighting method for effective sequence number estimation
63
+ focuses on, so for default HMMER3 models, you expect this value to
64
+ reflect the default target for entropy-weighting.
65
+
66
+ .TP
67
+ .B info
68
+ Mean information content per match state, in bits.
69
+ Probably not useful. Information content is a slightly
70
+ different calculation than relative entropy.
71
+
72
+ .TP
73
+ .B "p relE"
74
+ Mean positional relative entropy, in bits.
75
+ This is a fancier version of the per-match-state relative entropy,
76
+ taking into account the transition (insertion/deletion) probabilities;
77
+ it may be a more accurate estimation of the average score contributed
78
+ per model consensus position.
79
+
80
+ .TP
81
+ .B compKL
82
+ Kullback-Leibler divergence from
83
+ the default background frequency distribution to
84
+ the average composition of the profile's consensus match states, in bits.
85
+ The higher this number, the more biased the residue composition of the
86
+ profile is. Highly biased profiles can slow the HMMER3 acceleration
87
+ pipeline, by causing too many nonhomologous sequences to pass the filters.
88
+
89
+
90
+ .SH OPTIONS
91
+
92
+ .TP
93
+ .B \-h
94
+ Help; print a brief reminder of command line usage and all available
95
+ options.
96
+
97
+
98
+ .SH SEE ALSO
99
+
100
+ See
101
+ .BR hmmer (1)
102
+ for a master man page with a list of all the individual man pages
103
+ for programs in the HMMER package.
104
+
105
+ .PP
106
+ For complete documentation, see the user guide that came with your
107
+ HMMER distribution (Userguide.pdf); or see the HMMER web page
108
+ (http://hmmer.org/).
109
+
110
+
111
+
112
+ .SH COPYRIGHT
113
+
114
+ .nf
115
+ Copyright (C) 2023 Howard Hughes Medical Institute.
116
+ Freely distributed under the BSD open source license.
117
+ .fi
118
+
119
+ For additional information on copyright and licensing, see the file
120
+ called COPYRIGHT in your HMMER source distribution, or see the HMMER
121
+ web page
122
+ (http://hmmer.org/).
123
+
124
+
125
+ .SH AUTHOR
126
+
127
+ .nf
128
+ http://eddylab.org
129
+ .fi
130
+
131
+
132
+
133
+
flax_model/alphafold3/_tools/hmmer/share/man/man1/makehmmerdb.1 ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .TH "makehmmerdb" 1 "Aug 2023" "HMMER 3.4" "HMMER Manual"
2
+
3
+ .SH NAME
4
+ makehmmerdb \- build nhmmer database from a sequence file
5
+
6
+
7
+ .SH SYNOPSIS
8
+ .B makehmmerdb
9
+ [\fIoptions\fR]
10
+ .I seqfile
11
+ .I binaryfile
12
+
13
+
14
+ .SH DESCRIPTION
15
+
16
+ .PP
17
+ .B makehmmerdb
18
+ is used to create a binary file from a DNA sequence file. This
19
+ binary file may be used as a target database for the DNA search tool
20
+ .BR nhmmer .
21
+ Using default settings in
22
+ .BR nhmmer ,
23
+ this yields a roughly 10-fold acceleration with small loss of
24
+ sensitivity on benchmarks.
25
+
26
+
27
+ .SH OPTIONS
28
+
29
+ .TP
30
+ .B \-h
31
+ Help; print a brief reminder of command line usage and all available
32
+ options.
33
+
34
+
35
+
36
+ .SH OTHER OPTIONS
37
+
38
+ .TP
39
+ .BI \-\-informat " <s>"
40
+ Assert that input
41
+ .I seqfile
42
+ is in format
43
+ .IR <s> ,
44
+ bypassing format autodetection.
45
+ Common choices for
46
+ .I <s>
47
+ include:
48
+ .BR fasta ,
49
+ .BR embl ,
50
+ .BR genbank.
51
+ Alignment formats also work;
52
+ common choices include:
53
+ .BR stockholm ,
54
+ .BR a2m ,
55
+ .BR afa ,
56
+ .BR psiblast ,
57
+ .BR clustal ,
58
+ .BR phylip .
59
+ For more information, and for codes for some less common formats,
60
+ see main documentation.
61
+ The string
62
+ .I <s>
63
+ is case-insensitive (\fBfasta\fR or \fBFASTA\fR both work).
64
+
65
+
66
+ .TP
67
+ .BI \-\-bin_length " <n>"
68
+ Bin length. The binary file depends on a data structure called the
69
+ FM index, which organizes a permuted copy of the sequence in bins
70
+ of length
71
+ .IR <n> .
72
+ Longer bin length will lead to smaller files (because data is
73
+ captured about each bin) and possibly slower query time. The
74
+ default is 256. Much more than 512 may lead to notable reduction
75
+ in speed.
76
+
77
+
78
+ .TP
79
+ .BI \-\-sa_freq " <n>"
80
+ Suffix array sample rate. The FM index structure also samples from
81
+ the underlying suffix array for the sequence database. More frequent
82
+ sampling (smaller value for
83
+ .IR <n> )
84
+ will yield larger file size and faster search (until file size becomes
85
+ large enough to cause I/O to be a bottleneck). The default value
86
+ is 8. Must be a power of 2.
87
+
88
+
89
+ .TP
90
+ .BI \-\-block_size " <n>"
91
+ The input sequence is broken into blocks of size
92
+ .I <n>
93
+ million letters. An FM index is built for each block, rather than
94
+ building an FM index for the entire sequence database. Default is
95
+ 50. Larger blocks do not seem to yield substantial speed increase.
96
+
97
+
98
+
99
+ .SH SEE ALSO
100
+
101
+ See
102
+ .BR hmmer (1)
103
+ for a master man page with a list of all the individual man pages
104
+ for programs in the HMMER package.
105
+
106
+ .PP
107
+ For complete documentation, see the user guide that came with your
108
+ HMMER distribution (Userguide.pdf); or see the HMMER web page
109
+ (http://hmmer.org/).
110
+
111
+
112
+
113
+ .SH COPYRIGHT
114
+
115
+ .nf
116
+ Copyright (C) 2023 Howard Hughes Medical Institute.
117
+ Freely distributed under the BSD open source license.
118
+ .fi
119
+
120
+ For additional information on copyright and licensing, see the file
121
+ called COPYRIGHT in your HMMER source distribution, or see the HMMER
122
+ web page
123
+ (http://hmmer.org/).
124
+
125
+
126
+ .SH AUTHOR
127
+
128
+ .nf
129
+ http://eddylab.org
130
+ .fi
131
+
132
+
133
+
flax_model/alphafold3/_tools/hmmer/share/man/man1/nhmmer.1 ADDED
@@ -0,0 +1,687 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .TH "nhmmer" 1 "Aug 2023" "HMMER 3.4" "HMMER Manual"
2
+
3
+ .SH NAME
4
+ nhmmer \- search DNA queries against a DNA sequence database
5
+
6
+
7
+ .SH SYNOPSIS
8
+ .B nhmmer
9
+ [\fIoptions\fR]
10
+ .I queryfile
11
+ .I seqdb
12
+
13
+
14
+ .SH DESCRIPTION
15
+
16
+ .PP
17
+ .B nhmmer
18
+ is used to search one or more nucleotide queries against a
19
+ nucleotide sequence database.
20
+ For each query in
21
+ .IR queryfile ,
22
+ use that query to search the target database of sequences in
23
+ .IR seqdb ,
24
+ and output a ranked list of the hits with the most significant
25
+ matches to the query. A query may be either a profile model
26
+ built using
27
+ .BR hmmbuild ,
28
+ a sequence alignment, or a single sequence. Sequence based
29
+ queries can be in a number of formats (see
30
+ .BR \-\-qformat ),
31
+ and can typically be autodetected. Note that only
32
+ Stockholm
33
+ format supports queries made up of more than one sequence
34
+ alignment.
35
+
36
+
37
+
38
+ .PP
39
+ Either the query
40
+ .I queryfile
41
+ or the target
42
+ .I seqdb
43
+ may be '\-' (a dash character), in which case
44
+ the query file or target database input will be read from a <stdin> pipe instead of from a
45
+ file. Only one input source can come through <stdin>, not both.
46
+ If the
47
+ .I queryfile
48
+ contains more than one query, then
49
+ .I seqdb
50
+ cannot come from stdin, because we can't rewind the
51
+ streaming target database to search it with another profile.
52
+
53
+ .PP
54
+ If the query is sequence-based (unaligned or aligned),
55
+ a new file containing the HMM(s) built from the input(s) in
56
+ .I queryfile
57
+ may optionally be produced, with the filename set using the
58
+ .B \-\-hmmout
59
+ flag.
60
+
61
+
62
+ .PP
63
+ The output format is designed to be human-readable, but is often so
64
+ voluminous that reading it is impractical, and parsing it is a pain. The
65
+ .B \-\-tblout
66
+ option saves output in a simple tabular format that is concise and
67
+ easier to parse.
68
+ The
69
+ .B \-o
70
+ option allows redirecting the main output, including throwing it away
71
+ in /dev/null.
72
+
73
+
74
+
75
+ .SH OPTIONS
76
+
77
+ .TP
78
+ .B \-h
79
+ Help; print a brief reminder of command line usage and all available
80
+ options.
81
+
82
+
83
+
84
+ .SH OPTIONS FOR CONTROLLING OUTPUT
85
+
86
+ .TP
87
+ .BI \-o " <f>"
88
+ Direct the main human-readable output to a file
89
+ .I <f>
90
+ instead of the default stdout.
91
+
92
+ .TP
93
+ .BI \-A " <f>"
94
+ Save a multiple alignment of all significant hits (those satisfying
95
+ "inclusion thresholds") to the file
96
+ .IR <f> .
97
+
98
+ .TP
99
+ .BI \-\-tblout " <f>"
100
+ Save a simple tabular (space-delimited) file summarizing the
101
+ per-target output, with one data line per homologous target sequence
102
+ found.
103
+
104
+ .TP
105
+ .BI \-\-dfamtblout " <f>"
106
+ Save a tabular (space-delimited) file summarizing the
107
+ per-hit output, similar to
108
+ .B \-\-tblout
109
+ but more succinct.
110
+
111
+ .TP
112
+ .BI \-\-aliscoresout " <f>"
113
+ Save to file a list of per-position scores for each hit.
114
+ This is useful, for example, in identifying regions of high
115
+ score density for use in resolving overlapping hits from
116
+ different models.
117
+
118
+ .TP
119
+ .BI \-\-hmmout " <f>"
120
+ If
121
+ .I queryfile
122
+ is sequence-based, write the internally-computed HMM(s) to file
123
+ .IR <f> .
124
+
125
+
126
+ .TP
127
+ .B \-\-acc
128
+ Use accessions instead of names in the main output, where available
129
+ for profiles and/or sequences.
130
+
131
+ .TP
132
+ .B \-\-noali
133
+ Omit the alignment section from the main output. This can greatly
134
+ reduce the output volume.
135
+
136
+ .TP
137
+ .B \-\-notextw
138
+ Unlimit the length of each line in the main output. The default
139
+ is a limit of 120 characters per line, which helps in displaying
140
+ the output cleanly on terminals and in editors, but can truncate
141
+ target profile description lines.
142
+
143
+ .TP
144
+ .BI \-\-textw " <n>"
145
+ Set the main output's line length limit to
146
+ .I <n>
147
+ characters per line. The default is 120.
148
+
149
+
150
+
151
+ .SH OPTIONS CONTROLLING SINGLE SEQUENCE SCORING
152
+
153
+ By default, if a query is a single sequence from a file in
154
+ fasta format,
155
+ .B nhmmer
156
+ uses a search model constructed from that sequence and a standard
157
+ 20x20 substitution matrix for residue probabilities, along with two
158
+ additional parameters for position-independent gap open and gap extend
159
+ probabilities. These options allow the default single-sequence scoring
160
+ parameters to be changed, and for single-sequence scoring options to
161
+ be applied to a single sequence coming from an aligned format.
162
+
163
+ .TP
164
+ .BI \-\-singlemx
165
+ If a single sequence query comes from a multiple sequence alignment file,
166
+ such as in Stockholm format, the search model is by default constructed as is typically done
167
+ for multiple sequence alignments. This option forces
168
+ .B nhmmer
169
+ to use the single-sequence method with substitution score matrix.
170
+
171
+ .TP
172
+ .BI \-\-mxfile "<mxfile"
173
+ Obtain residue alignment probabilities from the substitution matrix
174
+ in file
175
+ .IR mxfile .
176
+ The default score matrix is DNA1 (this matrix is internal to
177
+ HMMER and does not have to be available as a file).
178
+ The format of a substitution matrix
179
+ .I mxfile
180
+ is the standard format accepted by BLAST, FASTA, and other sequence
181
+ analysis software.
182
+ See
183
+ .B ftp.ncbi.nlm.nih.gov/blast/matrices/
184
+ for example files. (The only
185
+ exception: we require matrices to be square, so for DNA, use files
186
+ like NCBI's NUC.4.4, not NUC.4.2.)
187
+
188
+
189
+ .TP
190
+ .BI \-\-popen " <x>"
191
+ Set the gap open probability for a single sequence query model to
192
+ .IR <x> .
193
+ The default is 0.02.
194
+ .I <x>
195
+ must be >= 0 and < 0.5.
196
+
197
+ .TP
198
+ .BI \-\-pextend " <x>"
199
+ Set the gap extend probability for a single sequence query model to
200
+ .IR <x> .
201
+ The default is 0.4.
202
+ .I <x>
203
+ must be >= 0 and < 1.0.
204
+
205
+
206
+
207
+ .SH OPTIONS CONTROLLING REPORTING THRESHOLDS
208
+
209
+ Reporting thresholds control which hits are reported in output files
210
+ (the main output,
211
+ .BR \-\-tblout ,
212
+ and
213
+ .BR \-\-dfamtblout ).
214
+ Hits are ranked by statistical significance (E-value).
215
+
216
+
217
+ .TP
218
+ .BI \-E " <x>"
219
+ Report target sequences with an E-value of <=
220
+ .IR <x> .
221
+ The default is 10.0, meaning that on average, about 10 false positives
222
+ will be reported per query, so you can see the top of the noise
223
+ and decide for yourself if it's really noise.
224
+
225
+ .TP
226
+ .BI \-T " <x>"
227
+ Instead of thresholding output on E-value, instead
228
+ report target sequences with a bit score of >=
229
+ .IR <x> .
230
+
231
+
232
+
233
+
234
+ .SH OPTIONS FOR INCLUSION THRESHOLDS
235
+
236
+ Inclusion thresholds are stricter than reporting thresholds.
237
+ Inclusion thresholds control which hits are considered to be reliable
238
+ enough to be included in an output alignment or a subsequent search
239
+ round, or marked as significant ("!") as opposed to questionable ("?")
240
+ in hit output.
241
+
242
+ .TP
243
+ .BI \-\-incE " <x>"
244
+ Use an E-value of <=
245
+ .I <x>
246
+ as the inclusion threshold.
247
+ The default is 0.01, meaning that on average, about 1 false positive
248
+ would be expected in every 100 searches with different query
249
+ sequences.
250
+
251
+ .TP
252
+ .BI \-\-incT " <x>"
253
+ Instead of using E-values for setting the inclusion threshold,
254
+ use a bit score of >=
255
+ .I <x>
256
+ as the inclusion threshold.
257
+ By default this option is unset.
258
+
259
+
260
+
261
+ .SH OPTIONS FOR MODEL-SPECIFIC SCORE THRESHOLDING
262
+
263
+ Curated profile databases may define specific bit score thresholds for
264
+ each profile, superseding any thresholding based on statistical
265
+ significance alone.
266
+
267
+ To use these options, the profile must contain the appropriate (GA,
268
+ TC, and/or NC) optional score threshold annotation; this is picked up
269
+ by
270
+ .B hmmbuild
271
+ from Stockholm format alignment files. For a nucleotide model, each
272
+ thresholding option has a single per-hit threshold <x>
273
+ This acts as if
274
+ .BI \-T " <x>"
275
+ .BI \-\-incT " <x>"
276
+ has been applied specifically using each model's curated thresholds.
277
+
278
+ .TP
279
+ .B \-\-cut_ga
280
+ Use the GA (gathering) bit score threshold in the model to set
281
+ per-hit reporting and inclusion
282
+ thresholds. GA thresholds are generally considered to be the
283
+ reliable curated thresholds defining family membership; for example,
284
+ in Dfam, these thresholds are applied when annotating a genome
285
+ with a model of a family known to be found in that organism. They
286
+ may allow for minimal expected false discovery rate.
287
+
288
+ .TP
289
+ .B \-\-cut_nc
290
+ Use the NC (noise cutoff) bit score threshold in the model to set
291
+ per-hit reporting and inclusion
292
+ thresholds. NC thresholds are less stringent than GA; in the context
293
+ of Pfam, they are generally used to store the score of the
294
+ highest-scoring known false positive.
295
+
296
+ .TP
297
+ .B \-\-cut_tc
298
+ Use the TC (trusted cutoff) bit score threshold in the model to set
299
+ per-hit reporting and inclusion
300
+ thresholds. TC thresholds are more stringent than GA, and are
301
+ generally considered to be the score of the lowest-scoring known
302
+ true positive that is above all known false positives; for example,
303
+ in Dfam, these thresholds are applied when annotating a genome
304
+ with a model of a family not known to be found in that organism.
305
+
306
+
307
+
308
+
309
+ .SH OPTIONS CONTROLLING THE ACCELERATION PIPELINE
310
+
311
+ HMMER3 searches are accelerated in a three-step filter pipeline: the
312
+ scanning-SSV filter, the Viterbi filter, and the Forward filter. The
313
+ first filter is the fastest and most approximate; the last is the full
314
+ Forward scoring algorithm. There is also a bias filter step between
315
+ SSV and Viterbi. Targets that pass all the steps in the acceleration
316
+ pipeline are then subjected to postprocessing -- domain
317
+ identification and scoring using the Forward/Backward algorithm.
318
+
319
+ Changing filter thresholds only removes or includes targets from
320
+ consideration; changing filter thresholds does not alter bit scores,
321
+ E-values, or alignments, all of which are determined solely in
322
+ postprocessing.
323
+
324
+ .TP
325
+ .B \-\-max
326
+ Turn off (nearly) all filters, including the bias filter, and run full
327
+ Forward/Backward postprocessing on most of the target sequence.
328
+ In contrast to
329
+ .B phmmer
330
+ and
331
+ .BR hmmsearch ,
332
+ where this flag really does turn off the filters entirely, the
333
+ .B \-\-max
334
+ flag in
335
+ .B nhmmer
336
+ sets the scanning-SSV filter threshold to 0.4, not 1.0. Use of this
337
+ flag increases sensitivity somewhat, at a large cost in speed.
338
+
339
+ .TP
340
+ .BI \-\-F1 " <x>"
341
+ Set the P-value threshold for the SSV filter step. The default is
342
+ 0.02, meaning that roughly 2% of the highest scoring nonhomologous
343
+ targets are expected to pass the filter.
344
+
345
+ .TP
346
+ .BI \-\-F2 " <x>"
347
+ Set the P-value threshold for the Viterbi filter step.
348
+ The default is 0.001.
349
+
350
+ .TP
351
+ .BI \-\-F3 " <x>"
352
+ Set the P-value threshold for the Forward filter step.
353
+ The default is 1e-5.
354
+
355
+ .TP
356
+ .B \-\-nobias
357
+ Turn off the bias filter. This increases sensitivity somewhat, but can
358
+ come at a high cost in speed, especially if the query has biased
359
+ residue composition (such as a repetitive sequence region, or if it is
360
+ a membrane protein with large regions of hydrophobicity). Without the
361
+ bias filter, too many sequences may pass the filter with biased
362
+ queries, leading to slower than expected performance as the
363
+ computationally intensive Forward/Backward algorithms shoulder an
364
+ abnormally heavy load.
365
+
366
+
367
+
368
+ .SH OPTIONS FOR SPECIFYING THE ALPHABET
369
+
370
+ .TP
371
+ .B \-\-dna
372
+ Assert that sequences in
373
+ .I msafile
374
+ are DNA, bypassing alphabet autodetection.
375
+
376
+ .TP
377
+ .B \-\-rna
378
+ Assert that sequences in
379
+ .I msafile
380
+ are RNA, bypassing alphabet autodetection.
381
+
382
+
383
+
384
+ .SH OPTIONS CONTROLLING SEED SEARCH HEURISTIC
385
+
386
+ When searching with
387
+ .BR nhmmer ,
388
+ one may optionally precompute a binary version of the target database, using
389
+ .BR makehmmerdb ,
390
+ then search against that database. Using default settings, this yields a
391
+ roughly 10-fold acceleration with small loss of sensitivity on benchmarks.
392
+ This is achieved using a heuristic method that searches for seeds (ungapped
393
+ alignments) around which full processing is done. This is essentially
394
+ a replacement to the SSV stage. (This method has been extensively tested,
395
+ but should still be treated as somewhat experimental.)
396
+ The following options only impact
397
+ .B nhmmer
398
+ if the value of
399
+ .B \-\-tformat
400
+ is
401
+ .BR hmmerdb .
402
+
403
+ Changing parameters for this seed-finding step will impact both speed and
404
+ sensitivity - typically faster search leads to lower sensitivity.
405
+
406
+ .TP
407
+ .BI \-\-seed_max_depth " <n>"
408
+ The seed step requires that a seed reach a specified bit score in length
409
+ no longer than
410
+ .IR <n> .
411
+ By default, this value is 15. Longer seeds allow a greater chance of
412
+ meeting the bit score threshold, leading to diminished filtering
413
+ (greater sensitivity, slower run time).
414
+
415
+ .TP
416
+ .BI \-\-seed_sc_thresh " <x>"
417
+ The seed must reach score
418
+ .I <x>
419
+ (in bits). The default is 15.0 bits. A higher threshold increases
420
+ filtering stringency, leading to faster run times and lower
421
+ sensitivity.
422
+
423
+ .TP
424
+ .BI \-\-seed_sc_density " <x>"
425
+ Either all prefixes or all suffixes of a seed must have
426
+ bit density (bits per aligned position) of at least
427
+ .IR <x> .
428
+ The default is 0.8 bits/position. An increase in the density
429
+ requirement leads to increased filtering stringency, thus faster
430
+ run times and lower sensitivity.
431
+
432
+ .TP
433
+ .BI \-\-seed_drop_max_len " <n>"
434
+ A seed may not have a run of length
435
+ .I <n>
436
+ in which the score drops by
437
+ .B \-\-seed_drop_lim
438
+ or more. Basically, this prunes seeds that go through long
439
+ slightly-negative seed extensions. The default is 4. Increasing
440
+ the limit causes (slightly) diminished filtering efficiency, thus
441
+ slower run times and higher sensitivity. (minor tuning option)
442
+
443
+ .TP
444
+ .BI \-\-seed_drop_lim " <x>"
445
+ In a seed, there may be no run of length
446
+ .B \-\-seed_drop_max_len
447
+ in which the score drops by
448
+ .BR \-\-seed_drop_lim .
449
+ The default is 0.3 bits. Larger numbers mean less filtering.
450
+ (minor tuning option)
451
+
452
+ .TP
453
+ .BI \-\-seed_req_pos " <n>"
454
+ A seed must contain a run of at least
455
+ .I <n>
456
+ positive-scoring matches. The default is 5. Larger values mean
457
+ increased filtering.
458
+ (minor tuning option)
459
+
460
+ .TP
461
+ .BI \-\-seed_ssv_length " <n>"
462
+ After finding a short seed, an ungapped alignment is extended
463
+ in both directions in an attempt to meet the
464
+ .B \-\-F1
465
+ score threshold. The window through which this ungapped alignment
466
+ extends is length
467
+ .IR <n> .
468
+ The default is 70.
469
+ Decreasing this value slightly reduces run time, at a small risk of
470
+ reduced sensitivity. (minor tuning option)
471
+
472
+
473
+ .SH OTHER OPTIONS
474
+
475
+
476
+ .TP
477
+ .BI \-\-qformat " <s>"
478
+ Assert that input
479
+ .I queryfile
480
+ is a sequence file (unaligned or aligned), in format
481
+ .IR <s> ,
482
+ bypassing format autodetection.
483
+ Common choices for
484
+ .I <s>
485
+ include:
486
+ .BR fasta ,
487
+ .BR embl ,
488
+ .BR genbank.
489
+ Alignment formats also work, and will serve as the
490
+ basis for automatic creation of a profile HMM used for
491
+ searching;
492
+ common choices include:
493
+ .BR stockholm ,
494
+ .BR a2m ,
495
+ .BR afa ,
496
+ .BR psiblast ,
497
+ .BR clustal ,
498
+ .BR phylip .
499
+ For more information, and for codes for some less common formats,
500
+ see main documentation.
501
+
502
+
503
+ .TP
504
+ .BI \-\-qsingle_seqs
505
+ Force
506
+ .I queryfile
507
+ to be read as individual sequences, even if it is in
508
+ an msa format. For example, if the input is in aligned
509
+ .BR stockholm
510
+ format, the
511
+ .BR \-\-qsingle_seqs
512
+ flag will cause each sequence in that alignment to be used as a separate query sequence.
513
+
514
+ .TP
515
+ .BI \-\-tformat " <s>"
516
+ Assert that target sequence database
517
+ .I seqdb
518
+ is in format
519
+ .IR <s> ,
520
+ bypassing format autodetection.
521
+ Common choices for
522
+ .I <s>
523
+ include:
524
+ .BR fasta ,
525
+ .BR embl ,
526
+ .BR genbank ,
527
+ .BR fmindex .
528
+ Alignment formats also work;
529
+ common choices include:
530
+ .BR stockholm ,
531
+ .BR a2m ,
532
+ .BR afa ,
533
+ .BR psiblast ,
534
+ .BR clustal ,
535
+ .BR phylip .
536
+ For more information, and for codes for some less common formats,
537
+ see main documentation.
538
+ The string
539
+ .I <s>
540
+ is case-insensitive (\fBfasta\fR or \fBFASTA\fR both work).
541
+ The format
542
+ .B fmindex
543
+ indicates that the database file is a binary file produced using
544
+ .BR makehmmerdb .
545
+
546
+
547
+ .TP
548
+ .B \-\-nonull2
549
+ Turn off the null2 score corrections for biased composition.
550
+
551
+ .TP
552
+ .BI \-Z " <x>"
553
+ For the purposes of per-hit E-value calculations,
554
+ Assert that the total size of the target database is
555
+ .I <x>
556
+ million nucleotides,
557
+ rather than the actual number of targets seen.
558
+
559
+
560
+ .TP
561
+ .BI \-\-seed " <n>"
562
+ Set the random number seed to
563
+ .IR <n> .
564
+ Some steps in postprocessing require Monte Carlo simulation. The
565
+ default is to use a fixed seed (42), so that results are exactly
566
+ reproducible. Any other positive integer will give different (but also
567
+ reproducible) results. A choice of 0 uses a randomly chosen seed.
568
+
569
+
570
+ .TP
571
+ .BI \-\-w_beta " <x>"
572
+ Window length tail mass.
573
+ The upper bound,
574
+ .IR W ,
575
+ on the length at which nhmmer expects to find an instance of the
576
+ model is set such that the fraction of all sequences generated
577
+ by the model with length >= W is less than
578
+ .IR <x> .
579
+ The default is 1e-7.
580
+ This flag may be used to override the value of
581
+ .I W
582
+ established for the model by
583
+ .BR hmmbuild ,
584
+ or when the query is sequence-based.
585
+
586
+
587
+
588
+ .TP
589
+ .BI \-\-w_length " <n>"
590
+ Override the model instance length upper bound, W,
591
+ which is otherwise controlled by
592
+ .BR \-\-w_beta .
593
+ It should be larger than the model length. The value of W
594
+ is used deep in the acceleration pipeline, and modest changes
595
+ are not expected to impact results (though larger values of W
596
+ do lead to longer run time).
597
+ This flag may be used to override the value of
598
+ W established for the model by
599
+ .BR hmmbuild ,
600
+ or when the query is sequence-based.
601
+
602
+
603
+
604
+ .TP
605
+ .B \-\-watson
606
+ Only search the top strand. By default both the query sequence
607
+ and its reverse-complement are searched.
608
+
609
+ .TP
610
+ .B \-\-crick
611
+ Only search the bottom (reverse-complement) strand. By
612
+ default both the query sequence and its reverse-complement are searched.
613
+
614
+
615
+ .TP
616
+ .BI \-\-cpu " <n>"
617
+ Set the number of parallel worker threads to
618
+ .IR <n> .
619
+ On multicore machines, the default is 2.
620
+ You can also control this number by setting an environment variable,
621
+ .IR HMMER_NCPU .
622
+ There is also a master thread, so the actual number of threads that
623
+ HMMER spawns is
624
+ .IR <n> +1.
625
+
626
+ This option is not available if HMMER was compiled with POSIX threads
627
+ support turned off.
628
+
629
+
630
+
631
+
632
+ .TP
633
+ .BI \-\-stall
634
+ For debugging the MPI master/worker version: pause after start, to
635
+ enable the developer to attach debuggers to the running master and
636
+ worker(s) processes. Send SIGCONT signal to release the pause.
637
+ (Under gdb:
638
+ .BR "(gdb) signal SIGCONT" )
639
+ (Only available if optional MPI support was enabled at compile-time.)
640
+
641
+ .TP
642
+ .BI \-\-mpi
643
+ Run under MPI control with master/worker parallelization (using
644
+ .BR mpirun ,
645
+ for example, or equivalent). Only available if optional MPI support
646
+ was enabled at compile-time.
647
+
648
+
649
+
650
+
651
+
652
+
653
+ .SH SEE ALSO
654
+
655
+ See
656
+ .BR hmmer (1)
657
+ for a master man page with a list of all the individual man pages
658
+ for programs in the HMMER package.
659
+
660
+ .PP
661
+ For complete documentation, see the user guide that came with your
662
+ HMMER distribution (Userguide.pdf); or see the HMMER web page
663
+ (http://hmmer.org/).
664
+
665
+
666
+
667
+ .SH COPYRIGHT
668
+
669
+ .nf
670
+ Copyright (C) 2023 Howard Hughes Medical Institute.
671
+ Freely distributed under the BSD open source license.
672
+ .fi
673
+
674
+ For additional information on copyright and licensing, see the file
675
+ called COPYRIGHT in your HMMER source distribution, or see the HMMER
676
+ web page
677
+ (http://hmmer.org/).
678
+
679
+
680
+ .SH AUTHOR
681
+
682
+ .nf
683
+ http://eddylab.org
684
+ .fi
685
+
686
+
687
+
flax_model/alphafold3/_tools/hmmer/share/man/man1/nhmmscan.1 ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .TH "nhmmscan" 1 "Aug 2023" "HMMER 3.4" "HMMER Manual"
2
+
3
+ .SH NAME
4
+ nhmmscan \- search DNA sequence(s) against a DNA profile database
5
+
6
+
7
+ .SH SYNOPSIS
8
+ .B nhmmscan
9
+ [\fIoptions\fR]
10
+ .I hmmdb
11
+ .I seqfile
12
+
13
+
14
+
15
+ .SH DESCRIPTION
16
+
17
+ .PP
18
+ .B nhmmscan
19
+ is used to search nucleotide sequences against collections
20
+ of nucleotide profiles. For each sequence in
21
+ .IR seqfile ,
22
+ use that query sequence to search the target database of
23
+ profiles in
24
+ .IR hmmdb ,
25
+ and output ranked lists of the profiles with the
26
+ most significant matches to the sequence.
27
+
28
+ .PP
29
+ The
30
+ .I seqfile
31
+ may contain more than one query sequence. It can be in FASTA format,
32
+ or several other common sequence file formats (genbank, embl, and
33
+ uniprot, among others), or in alignment file formats (stockholm,
34
+ aligned fasta, and others). See the
35
+ .I \-\-qformat
36
+ option for a complete list.
37
+
38
+ .PP
39
+ The
40
+ .I hmmdb
41
+ needs to be press'ed using
42
+ .B hmmpress
43
+ before it can be searched with
44
+ .BR nhmmscan .
45
+ This creates four binary files,
46
+ suffixed
47
+ .B .h3{fimp}.
48
+
49
+ .PP
50
+ The query
51
+ .I seqfile
52
+ may be '\-' (a dash character), in which case
53
+ the query sequences are read from a stdin pipe instead of from a
54
+ file.
55
+ The
56
+ .I hmmdb
57
+ cannot be read from a stdin stream, because it needs to have
58
+ the four auxiliary binary files generated by
59
+ .BR hmmpress .
60
+
61
+ .PP
62
+ The output format is designed to be human-readable, but is often so
63
+ voluminous that reading it is impractical, and parsing it is a pain. The
64
+ .B \-\-tblout
65
+ option saves output in a simple tabular format that is concise and
66
+ easier to parse.
67
+ The
68
+ .B \-o
69
+ option allows redirecting the main output, including throwing it away
70
+ in /dev/null.
71
+
72
+
73
+
74
+ .SH OPTIONS
75
+
76
+ .TP
77
+ .B \-h
78
+ Help; print a brief reminder of command line usage and all available
79
+ options.
80
+
81
+
82
+
83
+ .SH OPTIONS FOR CONTROLLING OUTPUT
84
+
85
+ .TP
86
+ .BI \-o " <f>"
87
+ Direct the main human-readable output to a file
88
+ .I <f>
89
+ instead of the default stdout.
90
+
91
+ .TP
92
+ .BI \-\-tblout " <f>"
93
+ Save a simple tabular (space-delimited) file summarizing the
94
+ per-hit output, with one data line per homologous target model
95
+ hit found.
96
+
97
+ .TP
98
+ .BI \-\-dfamtblout " <f>"
99
+ Save a tabular (space-delimited) file summarizing the
100
+ per-hit output, similar to
101
+ .B \-\-tblout
102
+ but more succinct.
103
+
104
+ .TP
105
+ .BI \-\-aliscoresout " <f>"
106
+ Save to file a list of per-position scores for each hit.
107
+ This is useful, for example, in identifying regions of high
108
+ score density for use in resolving overlapping hits from
109
+ different models.
110
+
111
+
112
+ .TP
113
+ .B \-\-acc
114
+ Use accessions instead of names in the main output, where available
115
+ for profiles and/or sequences.
116
+
117
+ .TP
118
+ .B \-\-noali
119
+ Omit the alignment section from the main output. This can greatly
120
+ reduce the output volume.
121
+
122
+ .TP
123
+ .B \-\-notextw
124
+ Unlimit the length of each line in the main output. The default
125
+ is a limit of 120 characters per line, which helps in displaying
126
+ the output cleanly on terminals and in editors, but can truncate
127
+ target profile description lines.
128
+
129
+ .TP
130
+ .BI \-\-textw " <n>"
131
+ Set the main output's line length limit to
132
+ .I <n>
133
+ characters per line. The default is 120.
134
+
135
+
136
+
137
+ .SH OPTIONS FOR REPORTING THRESHOLDS
138
+
139
+ Reporting thresholds control which hits are reported in output files
140
+ (the main output,
141
+ .BR \-\-tblout ,
142
+ and
143
+ .BR \-\-dfamtblout ).
144
+ Hits are ranked by statistical significance (E-value).
145
+
146
+ .TP
147
+ .BI \-E " <x>"
148
+ Report target profiles with an E-value of <=
149
+ .IR <x> .
150
+ The default is 10.0, meaning that on average, about 10 false positives
151
+ will be reported per query, so you can see the top of the noise
152
+ and decide for yourself if it's really noise.
153
+
154
+ .TP
155
+ .BI \-T " <x>"
156
+ Instead of thresholding output on E-value, instead
157
+ report target profiles with a bit score of >=
158
+ .IR <x> .
159
+
160
+
161
+
162
+
163
+ .SH OPTIONS FOR INCLUSION THRESHOLDS
164
+
165
+ Inclusion thresholds are stricter than reporting thresholds.
166
+ Inclusion thresholds control which hits are considered to be
167
+ reliable enough
168
+ to be included in an output alignment or a subsequent search round.
169
+ In
170
+ .BR nhmmscan ,
171
+ which does not have any alignment output (like
172
+ .BR nhmmer ),
173
+ inclusion thresholds have little effect. They only affect what hits
174
+ get marked as significant (!) or questionable (?) in hit
175
+ output.
176
+
177
+ .TP
178
+ .BI \-\-incE " <x>"
179
+ Use an E-value of <=
180
+ .I <x>
181
+ as the inclusion threshold.
182
+ The default is 0.01, meaning that on average, about 1 false positive
183
+ would be expected in every 100 searches with different query
184
+ sequences.
185
+
186
+ .TP
187
+ .BI \-\-incT " <x>"
188
+ Instead of using E-values for setting the inclusion threshold,
189
+ use a bit score of >=
190
+ .I <x>
191
+ as the inclusion threshold.
192
+ It would be unusual to use bit score thresholds with
193
+ .BR hmmscan ,
194
+ because you don't expect a single score threshold to work for
195
+ different profiles; different profiles have slightly different
196
+ expected score distributions.
197
+
198
+
199
+
200
+ .SH OPTIONS FOR MODEL-SPECIFIC SCORE THRESHOLDING
201
+
202
+ Curated profile databases may define specific bit score thresholds for
203
+ each profile, superseding any thresholding based on statistical
204
+ significance alone.
205
+
206
+ To use these options, the profile must contain the appropriate (GA,
207
+ TC, and/or NC) optional score threshold annotation; this is picked up
208
+ by
209
+ .B hmmbuild
210
+ from Stockholm format alignment files. For a nucleotide model, each
211
+ thresholding option has a single per-hit threshold <x>
212
+ This acts as if
213
+ .BI \-T " <x>"
214
+ .BI \-\-incT " <x>"
215
+ has been applied specifically using each model's curated thresholds.
216
+
217
+ .TP
218
+ .B \-\-cut_ga
219
+ Use the GA (gathering) bit score threshold in the model to set
220
+ per-hit reporting and inclusion
221
+ thresholds. GA thresholds are generally considered to be the
222
+ reliable curated thresholds defining family membership; for example,
223
+ in Dfam, these thresholds are applied when annotating a genome
224
+ with a model of a family known to be found in that organism. They
225
+ may allow for minimal expected false discovery rate.
226
+
227
+ .TP
228
+ .B \-\-cut_nc
229
+ Use the NC (noise cutoff) bit score threshold in the model to set
230
+ per-hit reporting and inclusion
231
+ thresholds. NC thresholds are less stringent than GA; in the context
232
+ of Pfam, they are generally used to store the score of the
233
+ highest-scoring known false positive.
234
+
235
+ .TP
236
+ .B \-\-cut_tc
237
+ Use the TC (trusted cutoff) bit score threshold in the model to set
238
+ per-hit reporting and inclusion
239
+ thresholds. TC thresholds are more stringent than GA, and are
240
+ generally considered to be the score of the lowest-scoring known
241
+ true positive that is above all known false positives; for example,
242
+ in Dfam, these thresholds are applied when annotating a genome
243
+ with a model of a family not known to be found in that organism.
244
+
245
+
246
+
247
+ .SH CONTROL OF THE ACCELERATION PIPELINE
248
+
249
+ HMMER3 searches are accelerated in a three-step filter pipeline: the
250
+ scanning-SSV filter, the Viterbi filter, and the Forward filter. The
251
+ first filter is the fastest and most approximate; the last is the full
252
+ Forward scoring algorithm. There is also a bias filter step between
253
+ SSV and Viterbi. Targets that pass all the steps in the acceleration
254
+ pipeline are then subjected to postprocessing -- domain
255
+ identification and scoring using the Forward/Backward algorithm.
256
+
257
+ Changing filter thresholds only removes or includes targets from
258
+ consideration; changing filter thresholds does not alter bit scores,
259
+ E-values, or alignments, all of which are determined solely in
260
+ postprocessing.
261
+
262
+ .TP
263
+ .B \-\-max
264
+ Turn off (nearly) all filters, including the bias filter, and run full
265
+ Forward/Backward postprocessing on most of the target sequence.
266
+ In contrast to
267
+ .B hmmscan,
268
+ where this flag really does turn off the filters entirely, the
269
+ .B \-\-max
270
+ flag in
271
+ .B nhmmscan
272
+ sets the scanning-SSV filter threshold to 0.4, not 1.0. Use of this
273
+ flag increases sensitivity somewhat, at a large cost in speed.
274
+
275
+ .TP
276
+ .BI \-\-F1 " <x>"
277
+ Set the P-value threshold for the MSV filter step. The default is
278
+ 0.02, meaning that roughly 2% of the highest scoring nonhomologous
279
+ targets are expected to pass the filter.
280
+
281
+ .TP
282
+ .BI \-\-F2 " <x>"
283
+ Set the P-value threshold for the Viterbi filter step.
284
+ The default is 0.001.
285
+
286
+ .TP
287
+ .BI \-\-F3 " <x>"
288
+ Set the P-value threshold for the Forward filter step.
289
+ The default is 1e-5.
290
+
291
+ .TP
292
+ .B \-\-nobias
293
+ Turn off the bias filter. This increases sensitivity somewhat, but can
294
+ come at a high cost in speed, especially if the query has biased
295
+ residue composition (such as a repetitive sequence region, or if it is
296
+ a membrane protein with large regions of hydrophobicity). Without the
297
+ bias filter, too many sequences may pass the filter with biased
298
+ queries, leading to slower than expected performance as the
299
+ computationally intensive Forward/Backward algorithms shoulder an
300
+ abnormally heavy load.
301
+
302
+
303
+
304
+ .SH OTHER OPTIONS
305
+
306
+ .TP
307
+ .B \-\-nonull2
308
+ Turn off the null2 score corrections for biased composition.
309
+
310
+ .TP
311
+ .BI \-Z " <x>"
312
+ Assert that the total number of targets in your searches is
313
+ .IR <x> ,
314
+ for the purposes of per-sequence E-value calculations,
315
+ rather than the actual number of targets seen.
316
+
317
+ .TP
318
+ .BI \-\-seed " <n>"
319
+ Set the random number seed to
320
+ .IR <n> .
321
+ Some steps in postprocessing require Monte Carlo simulation. The
322
+ default is to use a fixed seed (42), so that results are exactly
323
+ reproducible. Any other positive integer will give different (but also
324
+ reproducible) results. A choice of 0 uses an arbitrarily chosen seed.
325
+
326
+ .TP
327
+ .BI \-\-qformat " <s>"
328
+ Assert that input query
329
+ .I seqfile
330
+ is in format
331
+ .IR <s> ,
332
+ bypassing format autodetection.
333
+ Common choices for
334
+ .I <s>
335
+ include:
336
+ .BR fasta ,
337
+ .BR embl ,
338
+ .BR genbank.
339
+ Alignment formats also work;
340
+ common choices include:
341
+ .BR stockholm ,
342
+ .BR a2m ,
343
+ .BR afa ,
344
+ .BR psiblast ,
345
+ .BR clustal ,
346
+ .BR phylip .
347
+ For more information, and for codes for some less common formats,
348
+ see main documentation.
349
+ The string
350
+ .I <s>
351
+ is case-insensitive (\fBfasta\fR or \fBFASTA\fR both work).
352
+
353
+
354
+ .TP
355
+ .BI \-\-w_beta " <x>"
356
+ Window length tail mass.
357
+ The upper bound, W,
358
+ on the length at which nhmmer expects to find an instance of the
359
+ model is set such that the fraction of all sequences generated
360
+ by the model with length >= W is less than
361
+ .IR <x> .
362
+ The default is 1e-7.
363
+ This flag may be used to override the value of W
364
+ established for the model by
365
+ .BR hmmbuild .
366
+
367
+
368
+
369
+ .TP
370
+ .BI \-\-w_length " <n>"
371
+ Override the model instance length upper bound, W,
372
+ which is otherwise controlled by
373
+ .BR \-\-w_beta .
374
+ It should be larger than the model length. The value of W
375
+ is used deep in the acceleration pipeline, and modest changes
376
+ are not expected to impact results (though larger values of W
377
+ do lead to longer run time).
378
+ This flag may be used to override the value of W
379
+ established for the model by
380
+ .BR hmmbuild .
381
+
382
+
383
+ .TP
384
+ .B \-\-watson
385
+ Only search the top strand. By default both the query sequence
386
+ and its reverse-complement are searched.
387
+
388
+ .TP
389
+ .B \-\-crick
390
+ Only search the bottom (reverse-complement) strand. By
391
+ default both the query sequence and its reverse-complement are searched.
392
+
393
+
394
+ .TP
395
+ .BI \-\-cpu " <n>"
396
+ Set the number of parallel worker threads to
397
+ .IR <n> .
398
+ The default is 0, meaning off (no thread-level parallelization), because
399
+ .B nhmmscan
400
+ is typically i/o bound and the extra overhead of our current
401
+ multithreaded
402
+ implementation isn't worthwhile.
403
+ You can also control this number by setting an environment variable,
404
+ .IR HMMER_NCPU .
405
+ There is also a master thread, so the actual number of threads that
406
+ HMMER spawns is at least
407
+ .IR <n> +1.
408
+
409
+ This option is not available if HMMER was compiled with POSIX threads
410
+ support turned off.
411
+
412
+
413
+
414
+
415
+ .TP
416
+ .BI \-\-stall
417
+ For debugging the MPI master/worker version: pause after start, to
418
+ enable the developer to attach debuggers to the running master and
419
+ worker(s) processes. Send SIGCONT signal to release the pause.
420
+ (Under gdb:
421
+ .BR "(gdb) signal SIGCONT" )
422
+
423
+ (Only available if optional MPI support was enabled at compile-time.)
424
+
425
+ .TP
426
+ .BI \-\-mpi
427
+ Run under MPI control with master/worker parallelization (using
428
+ .BR mpirun ,
429
+ for example, or equivalent). Only available if optional MPI support
430
+ was enabled at compile-time.
431
+
432
+
433
+
434
+
435
+
436
+
437
+
438
+
439
+
440
+ .SH SEE ALSO
441
+
442
+ See
443
+ .BR hmmer (1)
444
+ for a master man page with a list of all the individual man pages
445
+ for programs in the HMMER package.
446
+
447
+ .PP
448
+ For complete documentation, see the user guide that came with your
449
+ HMMER distribution (Userguide.pdf); or see the HMMER web page
450
+ (http://hmmer.org/).
451
+
452
+
453
+
454
+ .SH COPYRIGHT
455
+
456
+ .nf
457
+ Copyright (C) 2023 Howard Hughes Medical Institute.
458
+ Freely distributed under the BSD open source license.
459
+ .fi
460
+
461
+ For additional information on copyright and licensing, see the file
462
+ called COPYRIGHT in your HMMER source distribution, or see the HMMER
463
+ web page
464
+ (http://hmmer.org/).
465
+
466
+
467
+ .SH AUTHOR
468
+
469
+ .nf
470
+ http://eddylab.org
471
+ .fi
472
+
flax_model/alphafold3/data/cpp/__init__.py ADDED
File without changes
flax_model/alphafold3/data/cpp/msa_profile_pybind.cc ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Copyright 2024 DeepMind Technologies Limited
2
+ //
3
+ // AlphaFold 3 source code is licensed under CC BY-NC-SA 4.0. To view a copy of
4
+ // this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/
5
+ //
6
+ // To request access to the AlphaFold 3 model parameters, follow the process set
7
+ // out at https://github.com/google-deepmind/alphafold3. You may only use these
8
+ // if received directly from Google. Use is subject to terms of use available at
9
+ // https://github.com/google-deepmind/alphafold3/blob/main/WEIGHTS_TERMS_OF_USE.md
10
+
11
+ #include <algorithm>
12
+
13
+ #include "absl/strings/str_cat.h"
14
+ #include "pybind11/cast.h"
15
+ #include "pybind11/numpy.h"
16
+ #include "pybind11/pybind11.h"
17
+
18
+ namespace {
19
+
20
+ namespace py = pybind11;
21
+
22
+ py::array_t<double> ComputeMsaProfile(
23
+ const py::array_t<int, py::array::c_style>& msa, int num_residue_types) {
24
+ if (msa.size() == 0) {
25
+ throw py::value_error("The MSA must be non-empty.");
26
+ }
27
+ if (msa.ndim() != 2) {
28
+ throw py::value_error(absl::StrCat("The MSA must be rectangular, got ",
29
+ msa.ndim(), "-dimensional MSA array."));
30
+ }
31
+ const int msa_depth = msa.shape()[0];
32
+ const int sequence_length = msa.shape()[1];
33
+
34
+ py::array_t<double> profile({sequence_length, num_residue_types});
35
+ std::fill(profile.mutable_data(), profile.mutable_data() + profile.size(),
36
+ 0.0f);
37
+ auto profile_unchecked = profile.mutable_unchecked<2>();
38
+
39
+ const double normalized_count = 1.0 / msa_depth;
40
+ const int* msa_it = msa.data();
41
+ for (int row_index = 0; row_index < msa_depth; ++row_index) {
42
+ for (int column_index = 0; column_index < sequence_length; ++column_index) {
43
+ const int residue_code = *(msa_it++);
44
+ if (residue_code < 0 || residue_code >= num_residue_types) {
45
+ throw py::value_error(
46
+ absl::StrCat("All residue codes must be positive and smaller than "
47
+ "num_residue_types ",
48
+ num_residue_types, ", got ", residue_code));
49
+ }
50
+ profile_unchecked(column_index, residue_code) += normalized_count;
51
+ }
52
+ }
53
+ return profile;
54
+ }
55
+
56
+ constexpr char kComputeMsaProfileDoc[] = R"(
57
+ Computes MSA profile for the given encoded MSA.
58
+
59
+ Args:
60
+ msa: A Numpy array of shape (num_msa, num_res) with the integer coded MSA.
61
+ num_residue_types: Integer that determines the number of unique residue types.
62
+ This will determine the shape of the output profile.
63
+
64
+ Returns:
65
+ A float Numpy array of shape (num_res, num_residue_types) with residue
66
+ frequency (residue type count normalized by MSA depth) for every column of the
67
+ MSA.
68
+ )";
69
+
70
+ } // namespace
71
+
72
+ namespace alphafold3 {
73
+
74
+ void RegisterModuleMsaProfile(pybind11::module m) {
75
+ m.def("compute_msa_profile", &ComputeMsaProfile, py::arg("msa"),
76
+ py::arg("num_residue_types"), py::doc(kComputeMsaProfileDoc + 1));
77
+ }
78
+
79
+ } // namespace alphafold3
flax_model/alphafold3/data/msa.py ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Functions for getting MSA and calculating alignment features."""
4
+
5
+ from collections.abc import MutableMapping, Sequence
6
+ import string
7
+ from typing import Self
8
+
9
+ from absl import logging
10
+ from flax_model.alphafold3.constants import mmcif_names
11
+ from flax_model.alphafold3.data import msa_config
12
+ from flax_model.alphafold3.data import msa_features
13
+ from flax_model.alphafold3.data import parsers
14
+ from flax_model.alphafold3.data.tools import jackhmmer
15
+ from flax_model.alphafold3.data.tools import msa_tool
16
+ from flax_model.alphafold3.data.tools import nhmmer
17
+ from flax_model.alphafold3.data.tools import mmseqs
18
+ import numpy as np
19
+
20
+
21
+
22
+ class Error(Exception):
23
+ """Error indicatating a problem with MSA Search."""
24
+
25
+
26
+ def _featurize(seq: str, chain_poly_type: str) -> str | list[int]:
27
+ if mmcif_names.is_standard_polymer_type(chain_poly_type):
28
+ featurized_seqs, _ = msa_features.extract_msa_features(
29
+ msa_sequences=[seq], chain_poly_type=chain_poly_type
30
+ )
31
+ return featurized_seqs[0].tolist()
32
+ # For anything else simply require an identical match.
33
+ return seq
34
+
35
+
36
+ def sequences_are_feature_equivalent(
37
+ sequence1: str,
38
+ sequence2: str,
39
+ chain_poly_type: str,
40
+ ) -> bool:
41
+ feat1 = _featurize(sequence1, chain_poly_type)
42
+ feat2 = _featurize(sequence2, chain_poly_type)
43
+ return feat1 == feat2
44
+
45
+
46
+ class Msa:
47
+ """Multiple Sequence Alignment container with methods for manipulating it."""
48
+
49
+ def __init__(
50
+ self,
51
+ query_sequence: str,
52
+ chain_poly_type: str,
53
+ sequences: Sequence[str],
54
+ descriptions: Sequence[str],
55
+ deduplicate: bool = True,
56
+ ):
57
+ """Raw constructor, prefer using the from_{a3m,multiple_msas} class methods.
58
+
59
+ The first sequence must be equal (in featurised form) to the query sequence.
60
+ If sequences/descriptions are empty, they will be initialised to the query.
61
+
62
+ Args:
63
+ query_sequence: The sequence that was used to search for MSA.
64
+ chain_poly_type: Polymer type of the query sequence, see mmcif_names.
65
+ sequences: The sequences returned by the MSA search tool.
66
+ descriptions: Metadata for the sequences returned by the MSA search tool.
67
+ deduplicate: If True, the MSA sequences will be deduplicated in the input
68
+ order. Lowercase letters (insertions) are ignored when deduplicating.
69
+ """
70
+ if len(sequences) != len(descriptions):
71
+ raise ValueError('The number of sequences and descriptions must match.')
72
+
73
+ self.query_sequence = query_sequence
74
+ self.chain_poly_type = chain_poly_type
75
+
76
+ if not deduplicate:
77
+ self.sequences = sequences
78
+ self.descriptions = descriptions
79
+ else:
80
+ self.sequences = []
81
+ self.descriptions = []
82
+ # A replacement table that removes all lowercase characters.
83
+ deletion_table = str.maketrans('', '', string.ascii_lowercase)
84
+ unique_sequences = set()
85
+ for seq, desc in zip(sequences, descriptions, strict=True):
86
+ # Using string.translate is faster than re.sub('[a-z]+', '').
87
+ sequence_no_deletions = seq.translate(deletion_table)
88
+ if sequence_no_deletions not in unique_sequences:
89
+ unique_sequences.add(sequence_no_deletions)
90
+ self.sequences.append(seq)
91
+ self.descriptions.append(desc)
92
+
93
+ # Make sure the MSA always has at least the query.
94
+ self.sequences = self.sequences or [query_sequence]
95
+ self.descriptions = self.descriptions or ['Original query']
96
+
97
+ # Check if the 1st MSA sequence matches the query sequence. Since it may be
98
+ # mutated by the search tool (jackhmmer) check using the featurized version.
99
+ if not sequences_are_feature_equivalent(
100
+ self.sequences[0], query_sequence, chain_poly_type
101
+ ):
102
+ raise ValueError(
103
+ f'First MSA sequence {self.sequences[0]} is not the {query_sequence=}'
104
+ )
105
+
106
+ @classmethod
107
+ def from_multiple_msas(
108
+ cls, msas: Sequence[Self], deduplicate: bool = True
109
+ ) -> Self:
110
+ """Initializes the MSA from multiple MSAs.
111
+
112
+ Args:
113
+ msas: A sequence of Msa objects representing individual MSAs produced by
114
+ different tools/dbs.
115
+ deduplicate: If True, the MSA sequences will be deduplicated in the input
116
+ order. Lowercase letters (insertions) are ignored when deduplicating.
117
+
118
+ Returns:
119
+ An Msa object created by merging multiple MSAs.
120
+ """
121
+ if not msas:
122
+ raise ValueError('At least one MSA must be provided.')
123
+
124
+ query_sequence = msas[0].query_sequence
125
+ chain_poly_type = msas[0].chain_poly_type
126
+ sequences = []
127
+ descriptions = []
128
+
129
+ for msa in msas:
130
+ if msa.query_sequence != query_sequence:
131
+ raise ValueError(
132
+ f'Query sequences must match: {[m.query_sequence for m in msas]}'
133
+ )
134
+ if msa.chain_poly_type != chain_poly_type:
135
+ raise ValueError(
136
+ f'Chain poly types must match: {[m.chain_poly_type for m in msas]}'
137
+ )
138
+ sequences.extend(msa.sequences)
139
+ descriptions.extend(msa.descriptions)
140
+
141
+ return cls(
142
+ query_sequence=query_sequence,
143
+ chain_poly_type=chain_poly_type,
144
+ sequences=sequences,
145
+ descriptions=descriptions,
146
+ deduplicate=deduplicate,
147
+ )
148
+
149
+ @classmethod
150
+ def from_multiple_a3ms(
151
+ cls, a3ms: Sequence[str], chain_poly_type: str, deduplicate: bool = True
152
+ ) -> Self:
153
+ """Initializes the MSA from multiple A3M strings.
154
+
155
+ Args:
156
+ a3ms: A sequence of A3M strings representing individual MSAs produced by
157
+ different tools/dbs.
158
+ chain_poly_type: Polymer type of the query sequence, see mmcif_names.
159
+ deduplicate: If True, the MSA sequences will be deduplicated in the input
160
+ order. Lowercase letters (insertions) are ignored when deduplicating.
161
+
162
+ Returns:
163
+ An Msa object created by merging multiple A3Ms.
164
+ """
165
+ if not a3ms:
166
+ raise ValueError('At least one A3M must be provided.')
167
+
168
+ query_sequence = None
169
+ all_sequences = []
170
+ all_descriptions = []
171
+
172
+ for a3m in a3ms:
173
+ sequences, descriptions = parsers.parse_fasta(a3m)
174
+ if query_sequence is None:
175
+ query_sequence = sequences[0]
176
+
177
+ if sequences[0] != query_sequence:
178
+ raise ValueError(
179
+ f'Query sequences must match: {sequences[0]=} != {query_sequence=}'
180
+ )
181
+ all_sequences.extend(sequences)
182
+ all_descriptions.extend(descriptions)
183
+
184
+ return cls(
185
+ query_sequence=query_sequence,
186
+ chain_poly_type=chain_poly_type,
187
+ sequences=all_sequences,
188
+ descriptions=all_descriptions,
189
+ deduplicate=deduplicate,
190
+ )
191
+
192
+ @classmethod
193
+ def from_a3m(
194
+ cls,
195
+ query_sequence: str,
196
+ chain_poly_type: str,
197
+ a3m: str,
198
+ max_depth: int | None = None,
199
+ deduplicate: bool = True,
200
+ ) -> Self:
201
+ """Parses the single A3M and builds the Msa object."""
202
+ sequences, descriptions = parsers.parse_fasta(a3m)
203
+
204
+ if max_depth is not None and 0 < max_depth < len(sequences):
205
+ logging.info(
206
+ 'MSA cropped from depth of %d to %d for %s.',
207
+ len(sequences),
208
+ max_depth,
209
+ query_sequence,
210
+ )
211
+ sequences = sequences[:max_depth]
212
+ descriptions = descriptions[:max_depth]
213
+
214
+ return cls(
215
+ query_sequence=query_sequence,
216
+ chain_poly_type=chain_poly_type,
217
+ sequences=sequences,
218
+ descriptions=descriptions,
219
+ deduplicate=deduplicate,
220
+ )
221
+
222
+ @classmethod
223
+ def from_empty(cls, query_sequence: str, chain_poly_type: str) -> Self:
224
+ """Creates an empty Msa containing just the query sequence."""
225
+ return cls(
226
+ query_sequence=query_sequence,
227
+ chain_poly_type=chain_poly_type,
228
+ sequences=[],
229
+ descriptions=[],
230
+ deduplicate=False,
231
+ )
232
+
233
+ @property
234
+ def depth(self) -> int:
235
+ return len(self.sequences)
236
+
237
+ def __repr__(self) -> str:
238
+ return f'Msa({self.depth} sequences, {self.chain_poly_type})'
239
+
240
+ def to_a3m(self) -> str:
241
+ """Returns the MSA in the A3M format."""
242
+ a3m_lines = []
243
+ for desc, seq in zip(self.descriptions, self.sequences, strict=True):
244
+ a3m_lines.append(f'>{desc}')
245
+ a3m_lines.append(seq)
246
+ return '\n'.join(a3m_lines) + '\n'
247
+
248
+ def featurize(self) -> MutableMapping[str, np.ndarray]:
249
+ """Featurises the MSA and returns a map of feature names to features.
250
+
251
+ Returns:
252
+ A dictionary mapping feature names to values.
253
+
254
+ Raises:
255
+ msa.Error:
256
+ * If the sequences in the MSA don't have the same length after deletions
257
+ (lower case letters) are removed.
258
+ * If the MSA contains an unknown amino acid code.
259
+ * If there are no sequences after aligning.
260
+ """
261
+ try:
262
+ msa, deletion_matrix = msa_features.extract_msa_features(
263
+ msa_sequences=self.sequences, chain_poly_type=self.chain_poly_type
264
+ )
265
+ except ValueError as e:
266
+ raise Error(f'Error extracting MSA or deletion features: {e}') from e
267
+
268
+ if msa.shape == (0, 0):
269
+ raise Error(f'Empty MSA feature for {self}')
270
+
271
+ species_ids = msa_features.extract_species_ids(self.descriptions)
272
+
273
+ return {
274
+ 'msa_species_identifiers': np.array(species_ids, dtype=object),
275
+ 'num_alignments': np.array(self.depth, dtype=np.int32),
276
+ 'msa': msa,
277
+ 'deletion_matrix': deletion_matrix,
278
+ }
279
+
280
+
281
+ def get_msa_tool(
282
+ msa_tool_config: msa_config.JackhmmerConfig | msa_config.NhmmerConfig | msa_config.MmseqsConfig,
283
+ ) -> msa_tool.MsaTool:
284
+ """Returns the requested MSA tool."""
285
+
286
+ match msa_tool_config:
287
+ case msa_config.JackhmmerConfig():
288
+ return jackhmmer.Jackhmmer(
289
+ binary_path=msa_tool_config.binary_path,
290
+ database_path=msa_tool_config.database_config.path,
291
+ n_cpu=msa_tool_config.n_cpu,
292
+ n_iter=msa_tool_config.n_iter,
293
+ e_value=msa_tool_config.e_value,
294
+ z_value=msa_tool_config.z_value,
295
+ max_sequences=msa_tool_config.max_sequences,
296
+ max_threads=msa_tool_config.max_threads,
297
+ )
298
+ case msa_config.NhmmerConfig():
299
+ return nhmmer.Nhmmer(
300
+ binary_path=msa_tool_config.binary_path,
301
+ hmmalign_binary_path=msa_tool_config.hmmalign_binary_path,
302
+ hmmbuild_binary_path=msa_tool_config.hmmbuild_binary_path,
303
+ database_path=msa_tool_config.database_config.path,
304
+ n_cpu=msa_tool_config.n_cpu,
305
+ e_value=msa_tool_config.e_value,
306
+ max_sequences=msa_tool_config.max_sequences,
307
+ max_threads=msa_tool_config.max_threads,
308
+ alphabet=msa_tool_config.alphabet,
309
+ )
310
+ case msa_config.MmseqsConfig():
311
+ return mmseqs.Mmseqs(
312
+ binary_path=msa_tool_config.binary_path,
313
+ database_path=msa_tool_config.database_config.path,
314
+ n_cpu=msa_tool_config.n_cpu,
315
+ use_gpu=msa_tool_config.use_gpu,
316
+ mmseqs_options=msa_tool_config.mmseqs_options,
317
+ result2msa_options=msa_tool_config.result2msa_options,
318
+ )
319
+ case _:
320
+ raise ValueError(f'Unknown MSA tool: {msa_tool_config}.')
321
+
322
+
323
+ def get_msa(
324
+ target_sequence: str,
325
+ run_config: msa_config.RunConfig,
326
+ chain_poly_type: str,
327
+ deduplicate: bool = False,
328
+ ) -> Msa:
329
+ """Computes the MSA for a given query sequence.
330
+
331
+ Args:
332
+ target_sequence: The target amino-acid sequence.
333
+ run_config: MSA run configuration.
334
+ chain_poly_type: The type of chain for which to get an MSA.
335
+ deduplicate: If True, the MSA sequences will be deduplicated in the input
336
+ order. Lowercase letters (insertions) are ignored when deduplicating.
337
+
338
+ Returns:
339
+ Aligned MSA sequences.
340
+ """
341
+
342
+ return Msa.from_a3m(
343
+ query_sequence=target_sequence,
344
+ chain_poly_type=chain_poly_type,
345
+ a3m=get_msa_tool(run_config.config).query(target_sequence).a3m,
346
+ max_depth=run_config.crop_size,
347
+ deduplicate=deduplicate,
348
+ )
flax_model/alphafold3/data/msa_store.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Interface and implementations for fetching MSA data."""
4
+
5
+ from collections.abc import Sequence
6
+ from typing import Protocol, TypeAlias
7
+
8
+ from flax_model.alphafold3.data import msa
9
+ from flax_model.alphafold3.data import msa_config
10
+
11
+
12
+ MsaErrors: TypeAlias = Sequence[tuple[msa_config.RunConfig, str]]
13
+
14
+
15
+ class MsaProvider(Protocol):
16
+ """Interface for providing Multiple Sequence Alignments."""
17
+
18
+ def __call__(
19
+ self,
20
+ query_sequence: str,
21
+ chain_polymer_type: str,
22
+ ) -> tuple[msa.Msa, MsaErrors]:
23
+ """Retrieve MSA for the given polymer query_sequence.
24
+
25
+ Args:
26
+ query_sequence: The residue sequence of the polymer to search for.
27
+ chain_polymer_type: The polymer type of the query_sequence. This must
28
+ match the chain_polymer_type of the provider.
29
+
30
+ Returns:
31
+ A tuple containing the MSA and MsaErrors. MsaErrors is a Sequence
32
+ containing a tuple for each msa_query that failed. Each tuple contains
33
+ the failing query and the associated error message.
34
+ """
35
+
36
+
37
+ class EmptyMsaProvider:
38
+ """MSA provider that returns just the query sequence, useful for testing."""
39
+
40
+ def __init__(self, chain_polymer_type: str):
41
+ self._chain_polymer_type = chain_polymer_type
42
+
43
+ def __call__(
44
+ self, query_sequence: str, chain_polymer_type: str
45
+ ) -> tuple[msa.Msa, MsaErrors]:
46
+ """Returns an MSA containing just the query sequence, never errors."""
47
+ if chain_polymer_type != self._chain_polymer_type:
48
+ raise ValueError(
49
+ f'EmptyMsaProvider of type {self._chain_polymer_type} called with '
50
+ f'sequence of {chain_polymer_type=}, {query_sequence=}.'
51
+ )
52
+ return (
53
+ msa.Msa.from_empty(
54
+ query_sequence=query_sequence,
55
+ chain_poly_type=self._chain_polymer_type,
56
+ ),
57
+ (),
58
+ )
flax_model/alphafold3/data/template_realign.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Realign sequences found in PDB seqres to the actual CIF sequences."""
4
+
5
+ from collections.abc import Mapping
6
+
7
+
8
+ class AlignmentError(Exception):
9
+ """Failed alignment between the hit sequence and the actual mmCIF sequence."""
10
+
11
+
12
+ def realign_hit_to_structure(
13
+ *,
14
+ hit_sequence: str,
15
+ hit_start_index: int,
16
+ hit_end_index: int,
17
+ full_length: int,
18
+ structure_sequence: str,
19
+ query_to_hit_mapping: Mapping[int, int],
20
+ ) -> Mapping[int, int]:
21
+ """Realigns the hit sequence to the Structure sequence.
22
+
23
+ For example, for the given input:
24
+ query_sequence : ABCDEFGHIJKL
25
+ hit_sequence : ---DEFGHIJK-
26
+ struc_sequence : XDEFGHKL
27
+ the mapping is {3: 0, 4: 1, 5: 2, 6: 3, 7: 4, 8: 5, 9: 6, 10: 7}. However, the
28
+ actual Structure sequence has an extra X at the start as well as no IJ. So the
29
+ alignment from the query to the Structure sequence will be:
30
+ hit_sequence : ---DEFGHIJK-
31
+ struc_aligned : --XDEFGH--KL
32
+ and the new mapping will therefore be: {3: 1, 4: 2, 5: 3, 6: 4, 7: 5, 10: 6}.
33
+
34
+ Args:
35
+ hit_sequence: The PDB seqres hit sequence obtained from Hmmsearch, but
36
+ without any gaps. This is not the full PDB seqres template sequence but
37
+ rather just its subsequence from hit_start_index to hit_end_index.
38
+ hit_start_index: The start index of the hit sequence in the full PDB seqres
39
+ template sequence (inclusive).
40
+ hit_end_index: The end index of the hit sequence in the full PDB seqres
41
+ template sequence (exclusive).
42
+ full_length: The length of the full PDB seqres template sequence.
43
+ structure_sequence: The actual sequence extracted from the Structure
44
+ corresponding to this template. In vast majority of cases this is the same
45
+ as the PDB seqres sequence, but this function handles the cases when not.
46
+ query_to_hit_mapping: The mapping from the query sequence to the
47
+ hit_sequence.
48
+
49
+ Raises:
50
+ AlignmentError: if the alignment between the sequence returned by Hmmsearch
51
+ differs from the actual sequence found in the mmCIF and can't be aligned
52
+ using the simple alignment algorithm.
53
+
54
+ Returns:
55
+ A mapping from the query sequence to the actual Structure sequence.
56
+ """
57
+ max_num_gaps = full_length - len(structure_sequence)
58
+ if max_num_gaps < 0:
59
+ raise AlignmentError(
60
+ f'The Structure sequence ({len(structure_sequence)}) '
61
+ f'must be shorter than the PDB seqres sequence ({full_length}):\n'
62
+ f'Structure sequence : {structure_sequence}\n'
63
+ f'PDB seqres sequence: {hit_sequence}'
64
+ )
65
+
66
+ if len(hit_sequence) != hit_end_index - hit_start_index:
67
+ raise AlignmentError(
68
+ f'The difference of {hit_end_index=} and {hit_start_index=} does not '
69
+ f'equal to the length of the {hit_sequence}: {len(hit_sequence)}'
70
+ )
71
+
72
+ best_score = -1
73
+ best_start = 0
74
+ best_query_to_hit_mapping = query_to_hit_mapping
75
+ max_num_gaps_before_subseq = min(hit_start_index, max_num_gaps)
76
+ # It is possible the gaps needed to align the PDB seqres subsequence and
77
+ # the Structure subsequence need to be inserted before the match region.
78
+ # Try and pick the alignment with the best number of aligned residues.
79
+ for num_gaps_before_subseq in range(0, max_num_gaps_before_subseq + 1):
80
+ start = hit_start_index - num_gaps_before_subseq
81
+ end = hit_end_index - num_gaps_before_subseq
82
+ structure_subseq = structure_sequence[start:end]
83
+
84
+ new_query_to_hit_mapping, score = _remap_to_struc_seq(
85
+ hit_seq=hit_sequence,
86
+ struc_seq=structure_subseq,
87
+ max_num_gaps=max_num_gaps - num_gaps_before_subseq,
88
+ mapping=query_to_hit_mapping,
89
+ )
90
+ if score >= best_score:
91
+ # Use >= to prefer matches with larger number of gaps before.
92
+ best_score = score
93
+ best_start = start
94
+ best_query_to_hit_mapping = new_query_to_hit_mapping
95
+
96
+ return {q: h + best_start for q, h in best_query_to_hit_mapping.items()}
97
+
98
+
99
+ def _remap_to_struc_seq(
100
+ *,
101
+ hit_seq: str,
102
+ struc_seq: str,
103
+ max_num_gaps: int,
104
+ mapping: Mapping[int, int],
105
+ ) -> tuple[Mapping[int, int], int]:
106
+ """Remaps the query -> hit mapping to match the actual Structure sequence.
107
+
108
+ Args:
109
+ hit_seq: The hit sequence - a subsequence of the PDB seqres sequence without
110
+ any Hmmsearch modifications like inserted gaps or lowercased residues.
111
+ struc_seq: The actual sequence obtained from the corresponding Structure.
112
+ max_num_gaps: The maximum number of gaps that can be inserted in the
113
+ Structure sequence. In practice, this is the length difference between the
114
+ PDB seqres sequence and the actual Structure sequence.
115
+ mapping: The mapping from the query residues to the hit residues. This will
116
+ be remapped to point to the actual Structure sequence using a simple
117
+ realignment algorithm.
118
+
119
+ Returns:
120
+ A tuple of (mapping, score):
121
+ * Mapping from the query to the actual Structure sequence.
122
+ * Score which is the number of matching aligned residues.
123
+
124
+ Raises:
125
+ ValueError if the structure sequence isn't shorter than the seqres sequence.
126
+ ValueError if the alignment fails.
127
+ """
128
+ hit_seq_idx = 0
129
+ struc_seq_idx = 0
130
+ hit_to_struc_seq_mapping = {}
131
+ score = 0
132
+
133
+ # This while loop is guaranteed to terminate since we increase both
134
+ # struc_seq_idx and hit_seq_idx by at least 1 in each iteration.
135
+ remaining_num_gaps = max_num_gaps
136
+ while hit_seq_idx < len(hit_seq) and struc_seq_idx < len(struc_seq):
137
+ if hit_seq[hit_seq_idx] != struc_seq[struc_seq_idx]:
138
+ # Explore which alignment aligns the next residue (if present).
139
+ best_shift = 0
140
+ for shift in range(0, remaining_num_gaps + 1):
141
+ next_hit_res = hit_seq[hit_seq_idx + shift : hit_seq_idx + shift + 1]
142
+ next_struc_res = struc_seq[struc_seq_idx : struc_seq_idx + 1]
143
+ if next_hit_res == next_struc_res:
144
+ best_shift = shift
145
+ break
146
+ hit_seq_idx += best_shift
147
+ remaining_num_gaps -= best_shift
148
+
149
+ hit_to_struc_seq_mapping[hit_seq_idx] = struc_seq_idx
150
+ score += hit_seq[hit_seq_idx] == struc_seq[struc_seq_idx]
151
+ hit_seq_idx += 1
152
+ struc_seq_idx += 1
153
+
154
+ fixed_mapping = {}
155
+ for query_idx, original_hit_idx in mapping.items():
156
+ fixed_hit_idx = hit_to_struc_seq_mapping.get(original_hit_idx)
157
+ if fixed_hit_idx is not None:
158
+ fixed_mapping[query_idx] = fixed_hit_idx
159
+
160
+ return fixed_mapping, score
flax_model/alphafold3/data/templates.py ADDED
@@ -0,0 +1,978 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """API for retrieving and manipulating template search results."""
4
+
5
+ from collections.abc import Iterable, Iterator, Mapping, Sequence
6
+ import dataclasses
7
+ import datetime
8
+ import functools
9
+ import os
10
+ import re
11
+ from typing import Any, Final, Self, TypeAlias
12
+
13
+ from absl import logging
14
+ from flax_model.alphafold3 import structure
15
+ from flax_model.alphafold3.common import resources
16
+ from flax_model.alphafold3.constants import atom_types
17
+ from flax_model.alphafold3.constants import mmcif_names
18
+ from flax_model.alphafold3.constants import residue_names
19
+ from flax_model.alphafold3.data import msa_config
20
+ from flax_model.alphafold3.data import parsers
21
+ from flax_model.alphafold3.data import structure_stores
22
+ from flax_model.alphafold3.data import template_realign
23
+ from flax_model.alphafold3.data.tools import hmmsearch
24
+ from flax_model.alphafold3.structure import mmcif
25
+ import numpy as np
26
+
27
+
28
+ _POLYMER_FEATURES: Final[Mapping[str, np.float64 | np.int32 | object]] = {
29
+ 'template_aatype': np.int32,
30
+ 'template_all_atom_masks': np.float64,
31
+ 'template_all_atom_positions': np.float64,
32
+ 'template_domain_names': object,
33
+ 'template_release_date': object,
34
+ 'template_sequence': object,
35
+ }
36
+
37
+ _LIGAND_FEATURES: Final[Mapping[str, Any]] = {
38
+ 'ligand_features': Mapping[str, Any]
39
+ }
40
+
41
+
42
+ TemplateFeatures: TypeAlias = Mapping[
43
+ str, np.ndarray | bytes | Mapping[str, np.ndarray | bytes]
44
+ ]
45
+ _REQUIRED_METADATA_COLUMNS: Final[Sequence[str]] = (
46
+ 'seq_release_date',
47
+ 'seq_unresolved_res_num',
48
+ 'seq_author_chain_id',
49
+ 'seq_sequence',
50
+ )
51
+
52
+
53
+ @dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
54
+ class _Polymer:
55
+ """Container for alphabet specific (dna, rna, protein) atom information."""
56
+
57
+ min_atoms: int
58
+ num_atom_types: int
59
+ atom_order: Mapping[str, int]
60
+
61
+
62
+ _POLYMERS = {
63
+ mmcif_names.PROTEIN_CHAIN: _Polymer(
64
+ min_atoms=5,
65
+ num_atom_types=atom_types.ATOM37_NUM,
66
+ atom_order=atom_types.ATOM37_ORDER,
67
+ ),
68
+ mmcif_names.DNA_CHAIN: _Polymer(
69
+ min_atoms=21,
70
+ num_atom_types=atom_types.ATOM29_NUM,
71
+ atom_order=atom_types.ATOM29_ORDER,
72
+ ),
73
+ mmcif_names.RNA_CHAIN: _Polymer(
74
+ min_atoms=20,
75
+ num_atom_types=atom_types.ATOM29_NUM,
76
+ atom_order=atom_types.ATOM29_ORDER,
77
+ ),
78
+ }
79
+
80
+
81
+ def _encode_restype(
82
+ chain_poly_type: str,
83
+ sequence: str,
84
+ ) -> Sequence[int]:
85
+ """Encodes a sequence of residue names as a sequence of ints.
86
+
87
+ Args:
88
+ chain_poly_type: Polymer chain type to determine sequence encoding.
89
+ sequence: Polymer residues. Protein encoded by single letters. RNA and DNA
90
+ encoded by multi-letter CCD codes.
91
+
92
+ Returns:
93
+ A sequence of integers encoding amino acid types for the given chain type.
94
+ """
95
+ if chain_poly_type == mmcif_names.PROTEIN_CHAIN:
96
+ return [
97
+ residue_names.PROTEIN_TYPES_ONE_LETTER_WITH_UNKNOWN_AND_GAP_TO_INT[
98
+ _STANDARDIZED_AA.get(res, res)
99
+ ]
100
+ for res in sequence
101
+ ]
102
+
103
+ unk_nucleic = residue_names.UNK_NUCLEIC_ONE_LETTER
104
+ unk_nucleic_idx = residue_names.POLYMER_TYPES_ORDER_WITH_UNKNOWN_AND_GAP[
105
+ unk_nucleic
106
+ ]
107
+ if chain_poly_type == mmcif_names.RNA_CHAIN:
108
+ return [
109
+ residue_names.POLYMER_TYPES_ORDER_WITH_UNKNOWN_AND_GAP.get(
110
+ res, unk_nucleic_idx
111
+ )
112
+ for res in sequence
113
+ ]
114
+ elif chain_poly_type == mmcif_names.DNA_CHAIN:
115
+ # Map UNK DNA to the generic nucleic UNK (N), which happens to also be the
116
+ # same as the RNA UNK.
117
+ return [
118
+ residue_names.POLYMER_TYPES_ORDER_WITH_UNKNOWN_AND_GAP.get(
119
+ residue_names.DNA_COMMON_ONE_TO_TWO.get(res, unk_nucleic),
120
+ unk_nucleic_idx,
121
+ )
122
+ for res in sequence
123
+ ]
124
+
125
+ raise NotImplementedError(f'"{chain_poly_type}" unsupported.')
126
+
127
+
128
+ _DAYS_BEFORE_QUERY_DATE: Final[int] = 60
129
+ _HIT_DESCRIPTION_REGEX = re.compile(
130
+ r'(?P<pdb_id>[a-z0-9]{4,})_(?P<chain_id>\w+)/(?P<start>\d+)-(?P<end>\d+) '
131
+ r'.* length:(?P<length>\d+)\b.*'
132
+ )
133
+
134
+ _STANDARDIZED_AA = {'B': 'D', 'J': 'X', 'O': 'X', 'U': 'C', 'Z': 'E'}
135
+
136
+
137
+ class Error(Exception):
138
+ """Base class for exceptions."""
139
+
140
+
141
+ class HitDateError(Error):
142
+ """An error indicating that invalid release date was detected."""
143
+
144
+
145
+ class InvalidTemplateError(Error):
146
+ """An error indicating that template is invalid."""
147
+
148
+
149
+ @dataclasses.dataclass(frozen=True, kw_only=True)
150
+ class Hit:
151
+ """Template hit metrics derived from the MSA for filtering and featurising.
152
+
153
+ Attributes:
154
+ pdb_id: The PDB ID of the hit.
155
+ auth_chain_id: The author chain ID of the hit.
156
+ hmmsearch_sequence: Hit sequence as given in hmmsearch a3m output.
157
+ structure_sequence: Hit sequence as given in PDB structure.
158
+ unresolved_res_indices: Indices of unresolved residues in the structure
159
+ sequence. 0-based.
160
+ query_sequence: The query nucleotide/amino acid sequence.
161
+ start_index: The start index of the sequence relative to the full PDB seqres
162
+ sequence. Inclusive and uses 0-based indexing.
163
+ end_index: The end index of the sequence relative to the full PDB seqres
164
+ sequence. Exclusive and uses 0-based indexing.
165
+ full_length: Length of the full PDB seqres sequence. This can be different
166
+ from the length from the actual sequence we get from the mmCIF and we use
167
+ this to detect whether we need to realign or not.
168
+ release_date: The release date of the PDB corresponding to this hit.
169
+ chain_poly_type: The polymer type of the selected hit structure.
170
+ """
171
+
172
+ pdb_id: str
173
+ auth_chain_id: str
174
+ hmmsearch_sequence: str
175
+ structure_sequence: str
176
+ unresolved_res_indices: Sequence[int] | None
177
+ query_sequence: str
178
+ start_index: int
179
+ end_index: int
180
+ full_length: int
181
+ release_date: datetime.date
182
+ chain_poly_type: str
183
+
184
+ @functools.cached_property
185
+ def query_to_hit_mapping(self) -> Mapping[int, int]:
186
+ """0-based query index to hit index mapping."""
187
+ query_to_hit_mapping = {}
188
+ hit_index = 0
189
+ query_index = 0
190
+ for residue in self.hmmsearch_sequence:
191
+ # Gap inserted in the template
192
+ if residue == '-':
193
+ query_index += 1
194
+ # Deleted residue in the template (would be a gap in the query).
195
+ elif residue.islower():
196
+ hit_index += 1
197
+ # Normal aligned residue, in both query and template. Add to mapping.
198
+ elif residue.isupper():
199
+ query_to_hit_mapping[query_index] = hit_index
200
+ query_index += 1
201
+ hit_index += 1
202
+
203
+ structure_subseq = self.structure_sequence[
204
+ self.start_index : self.end_index
205
+ ]
206
+ if self.matching_sequence != structure_subseq:
207
+ # The seqres sequence doesn't match the structure sequence. Two cases:
208
+ # 1. The sequences have the same length. The sequences are different
209
+ # because our 3->1 residue code mapping is different from the one PDB
210
+ # uses. We don't do anything in this case as both sequences have the
211
+ # same length, so the original query to hit mapping stays valid.
212
+ # 2. The sequences don't have the same length, the one in structure is
213
+ # shorter. In this case we change the mapping to match the actual
214
+ # structure sequence using a simple realignment algorithm.
215
+ # This procedure was validated on all PDB seqres (2023_01_12) sequences
216
+ # and handles all cases that can happen.
217
+ if self.full_length != len(self.structure_sequence):
218
+ return template_realign.realign_hit_to_structure(
219
+ hit_sequence=self.matching_sequence,
220
+ hit_start_index=self.start_index,
221
+ hit_end_index=self.end_index,
222
+ full_length=self.full_length,
223
+ structure_sequence=self.structure_sequence,
224
+ query_to_hit_mapping=query_to_hit_mapping,
225
+ )
226
+
227
+ # Hmmsearch returns a subsequence and so far indices have been relative to
228
+ # the subsequence. Add an offset to index relative to the full structure
229
+ # sequence.
230
+ return {q: h + self.start_index for q, h in query_to_hit_mapping.items()}
231
+
232
+ @property
233
+ def matching_sequence(self) -> str:
234
+ """Returns the matching hit sequence including insertions.
235
+
236
+ Make deleted residues uppercase and remove gaps ("-").
237
+ """
238
+ return self.hmmsearch_sequence.upper().replace('-', '')
239
+
240
+ @functools.cached_property
241
+ def output_templates_sequence(self) -> str:
242
+ """Returns the final template sequence."""
243
+ result_seq = ['-'] * len(self.query_sequence)
244
+ for query_index, template_index in self.query_to_hit_mapping.items():
245
+ result_seq[query_index] = self.structure_sequence[template_index]
246
+ return ''.join(result_seq)
247
+
248
+ @property
249
+ def length_ratio(self) -> float:
250
+ """Ratio of the length of the hit sequence to the query."""
251
+ return len(self.matching_sequence) / len(self.query_sequence)
252
+
253
+ @property
254
+ def align_ratio(self) -> float:
255
+ """Ratio of the number of aligned residues to the query length."""
256
+ return len(self.query_to_hit_mapping) / len(self.query_sequence)
257
+
258
+ @functools.cached_property
259
+ def is_valid(self) -> bool:
260
+ """Whether hit can be used as a template."""
261
+ if self.unresolved_res_indices is None:
262
+ return False
263
+
264
+ return bool(
265
+ set(self.query_to_hit_mapping.values())
266
+ - set(self.unresolved_res_indices)
267
+ )
268
+
269
+ @property
270
+ def full_name(self) -> str:
271
+ """A full name of the hit."""
272
+ return f'{self.pdb_id}_{self.auth_chain_id}'
273
+
274
+ def __post_init__(self):
275
+ if not self.pdb_id.islower() and not self.pdb_id.isdigit():
276
+ raise ValueError(f'pdb_id must be lowercase {self.pdb_id}')
277
+
278
+ if not (0 <= self.start_index <= self.end_index):
279
+ raise ValueError(
280
+ 'Start must be non-negative and less than or equal to end index. '
281
+ f'Range: {self.start_index}-{self.end_index}'
282
+ )
283
+
284
+ if len(self.matching_sequence) != (self.end_index - self.start_index):
285
+ raise ValueError(
286
+ 'Sequence length must be equal to end_index - start_index. '
287
+ f'{len(self.matching_sequence)} != {self.end_index} - '
288
+ f'{self.start_index}'
289
+ )
290
+
291
+ if self.full_length < 0:
292
+ raise ValueError(f'Full length must be non-negative: {self.full_length}')
293
+
294
+ def keep(
295
+ self,
296
+ *,
297
+ release_date_cutoff: datetime.date | None,
298
+ max_subsequence_ratio: float | None,
299
+ min_hit_length: int | None,
300
+ min_align_ratio: float | None,
301
+ ) -> bool:
302
+ """Returns whether the hit should be kept.
303
+
304
+ In addition to filtering on all of the provided parameters, this method also
305
+ excludes hits with unresolved residues.
306
+
307
+ Args:
308
+ release_date_cutoff: Maximum release date of the template.
309
+ max_subsequence_ratio: If set, excludes hits which are an exact
310
+ subsequence of the query sequence, and longer than this ratio. Useful to
311
+ avoid ground truth leakage.
312
+ min_hit_length: If set, excludes hits which have fewer residues than this.
313
+ min_align_ratio: If set, excludes hits where the number of residues
314
+ aligned to the query is less than this proportion of the template
315
+ length.
316
+ """
317
+ # Exclude hits which are too recent.
318
+ if (
319
+ release_date_cutoff is not None
320
+ and self.release_date > release_date_cutoff
321
+ ):
322
+ return False
323
+
324
+ # Exclude hits which are large duplicates of the query_sequence.
325
+ if (
326
+ max_subsequence_ratio is not None
327
+ and self.length_ratio > max_subsequence_ratio
328
+ ):
329
+ if self.matching_sequence in self.query_sequence:
330
+ return False
331
+
332
+ # Exclude hits which are too short.
333
+ if (
334
+ min_hit_length is not None
335
+ and len(self.matching_sequence) < min_hit_length
336
+ ):
337
+ return False
338
+
339
+ # Exclude hits with unresolved residues.
340
+ if not self.is_valid:
341
+ return False
342
+
343
+ # Exclude hits with too few alignments.
344
+ try:
345
+ if min_align_ratio is not None and self.align_ratio <= min_align_ratio:
346
+ return False
347
+ except template_realign.AlignmentError as e:
348
+ logging.warning('Failed to align %s: %s', self, str(e))
349
+ return False
350
+
351
+ return True
352
+
353
+
354
+ def _filter_hits(
355
+ hits: Iterable[Hit],
356
+ release_date_cutoff: datetime.date,
357
+ max_subsequence_ratio: float | None,
358
+ min_align_ratio: float | None,
359
+ min_hit_length: int | None,
360
+ deduplicate_sequences: bool,
361
+ max_hits: int | None,
362
+ ) -> Sequence[Hit]:
363
+ """Filters hits based on the filter config."""
364
+ filtered_hits = []
365
+ seen_before = set()
366
+ for hit in hits:
367
+ if not hit.keep(
368
+ max_subsequence_ratio=max_subsequence_ratio,
369
+ min_align_ratio=min_align_ratio,
370
+ min_hit_length=min_hit_length,
371
+ release_date_cutoff=release_date_cutoff,
372
+ ):
373
+ continue
374
+
375
+ # Remove duplicate templates, keeping the first.
376
+ if deduplicate_sequences:
377
+ if hit.output_templates_sequence in seen_before:
378
+ continue
379
+ seen_before.add(hit.output_templates_sequence)
380
+
381
+ filtered_hits.append(hit)
382
+ if max_hits and len(filtered_hits) == max_hits:
383
+ break
384
+
385
+ return filtered_hits
386
+
387
+
388
+ @dataclasses.dataclass(init=False)
389
+ class Templates:
390
+ """A container for templates that were found for the given query sequence.
391
+
392
+ The structure_store is constructed from the config by default. Callers can
393
+ optionally supply a structure_store to the constructor to avoid the cost of
394
+ construction and metadata loading.
395
+ """
396
+
397
+ def __init__(
398
+ self,
399
+ *,
400
+ query_sequence: str,
401
+ hits: Sequence[Hit],
402
+ max_template_date: datetime.date,
403
+ structure_store: structure_stores.StructureStore,
404
+ query_release_date: datetime.date | None = None,
405
+ ):
406
+ self._query_sequence = query_sequence
407
+ self._hits = tuple(hits)
408
+ self._max_template_date = max_template_date
409
+ self._query_release_date = query_release_date
410
+ self._hit_structures = {}
411
+ self._structure_store = structure_store
412
+
413
+ if any(h.query_sequence != self._query_sequence for h in self.hits):
414
+ raise ValueError('All hits must match the query sequence.')
415
+
416
+ if self._hits:
417
+ chain_poly_type = self._hits[0].chain_poly_type
418
+ if any(h.chain_poly_type != chain_poly_type for h in self.hits):
419
+ raise ValueError('All hits must have the same chain_poly_type.')
420
+
421
+ @classmethod
422
+ def from_seq_and_a3m(
423
+ cls,
424
+ *,
425
+ query_sequence: str,
426
+ msa_a3m: str,
427
+ max_template_date: datetime.date,
428
+ database_path: os.PathLike[str] | str,
429
+ hmmsearch_config: msa_config.HmmsearchConfig,
430
+ max_a3m_query_sequences: int | None,
431
+ structure_store: structure_stores.StructureStore,
432
+ filter_config: msa_config.TemplateFilterConfig | None = None,
433
+ query_release_date: datetime.date | None = None,
434
+ chain_poly_type: str = mmcif_names.PROTEIN_CHAIN,
435
+ ) -> Self:
436
+ """Creates templates from a run of hmmsearch tool against a custom a3m.
437
+
438
+ Args:
439
+ query_sequence: The polymer sequence of the target query.
440
+ msa_a3m: An a3m of related polymers aligned to the query sequence, this is
441
+ used to create an HMM for the hmmsearch run.
442
+ max_template_date: This is used to filter templates for training, ensuring
443
+ that they do not leak ground truth information used in testing sets.
444
+ database_path: A path to the sequence database to search for templates.
445
+ hmmsearch_config: Config with Hmmsearch settings.
446
+ max_a3m_query_sequences: The maximum number of input MSA sequences to use
447
+ to construct the profile which is then used to search for templates.
448
+ structure_store: Structure store to fetch template structures from.
449
+ filter_config: Optional config that controls which and how many hits to
450
+ keep. More performant than constructing and then filtering. If not
451
+ provided, no filtering is done.
452
+ query_release_date: The release_date of the template query, this is used
453
+ to filter templates for training, ensuring that they do not leak
454
+ structure information from the future.
455
+ chain_poly_type: The polymer type of the templates.
456
+
457
+ Returns:
458
+ Templates object containing a list of Hits initialised from the
459
+ structure_store metadata and a3m alignments.
460
+ """
461
+ hmmsearch_a3m = run_hmmsearch_with_a3m(
462
+ database_path=database_path,
463
+ hmmsearch_config=hmmsearch_config,
464
+ max_a3m_query_sequences=max_a3m_query_sequences,
465
+ a3m=msa_a3m,
466
+ )
467
+ return cls.from_hmmsearch_a3m(
468
+ query_sequence=query_sequence,
469
+ a3m=hmmsearch_a3m,
470
+ max_template_date=max_template_date,
471
+ query_release_date=query_release_date,
472
+ chain_poly_type=chain_poly_type,
473
+ structure_store=structure_store,
474
+ filter_config=filter_config,
475
+ )
476
+
477
+ @classmethod
478
+ def from_hmmsearch_a3m(
479
+ cls,
480
+ *,
481
+ query_sequence: str,
482
+ a3m: str,
483
+ max_template_date: datetime.date,
484
+ structure_store: structure_stores.StructureStore,
485
+ filter_config: msa_config.TemplateFilterConfig | None = None,
486
+ query_release_date: datetime.date | None = None,
487
+ chain_poly_type: str = mmcif_names.PROTEIN_CHAIN,
488
+ ) -> Self:
489
+ """Creates Templates from a Hmmsearch A3M.
490
+
491
+ Args:
492
+ query_sequence: The polymer sequence of the target query.
493
+ a3m: Results of Hmmsearch in A3M format. This provides a list of potential
494
+ template alignments and pdb codes.
495
+ max_template_date: This is used to filter templates for training, ensuring
496
+ that they do not leak ground truth information used in testing sets.
497
+ structure_store: Structure store to fetch template structures from.
498
+ filter_config: Optional config that controls which and how many hits to
499
+ keep. More performant than constructing and then filtering. If not
500
+ provided, no filtering is done.
501
+ query_release_date: The release_date of the template query, this is used
502
+ to filter templates for training, ensuring that they do not leak
503
+ structure information from the future.
504
+ chain_poly_type: The polymer type of the templates.
505
+
506
+ Returns:
507
+ Templates object containing a list of Hits initialised from the
508
+ structure_store metadata and a3m alignments.
509
+ """
510
+
511
+ def hit_generator(a3m: str):
512
+ if not a3m:
513
+ return # Hmmsearch could return an empty string if there are no hits.
514
+
515
+ for hit_seq, hit_desc in parsers.lazy_parse_fasta_string(a3m):
516
+ pdb_id, auth_chain_id, start, end, full_length = _parse_hit_description(
517
+ hit_desc
518
+ )
519
+
520
+ release_date, sequence, unresolved_res_ids = _parse_hit_metadata(
521
+ structure_store, pdb_id, auth_chain_id
522
+ )
523
+ if unresolved_res_ids is None:
524
+ continue
525
+
526
+ # seq_unresolved_res_num are 1-based, setting to 0-based indices.
527
+ unresolved_indices = [i - 1 for i in unresolved_res_ids]
528
+
529
+ yield Hit(
530
+ pdb_id=pdb_id,
531
+ auth_chain_id=auth_chain_id,
532
+ hmmsearch_sequence=hit_seq,
533
+ structure_sequence=sequence,
534
+ query_sequence=query_sequence,
535
+ unresolved_res_indices=unresolved_indices,
536
+ start_index=start - 1, # Raw value is residue number, not index.
537
+ end_index=end,
538
+ full_length=full_length,
539
+ release_date=datetime.date.fromisoformat(release_date),
540
+ chain_poly_type=chain_poly_type,
541
+ )
542
+
543
+ if filter_config is None:
544
+ hits = tuple(hit_generator(a3m))
545
+ else:
546
+ hits = _filter_hits(
547
+ hit_generator(a3m),
548
+ release_date_cutoff=filter_config.max_template_date,
549
+ max_subsequence_ratio=filter_config.max_subsequence_ratio,
550
+ min_align_ratio=filter_config.min_align_ratio,
551
+ min_hit_length=filter_config.min_hit_length,
552
+ deduplicate_sequences=filter_config.deduplicate_sequences,
553
+ max_hits=filter_config.max_hits,
554
+ )
555
+
556
+ return Templates(
557
+ query_sequence=query_sequence,
558
+ query_release_date=query_release_date,
559
+ hits=hits,
560
+ max_template_date=max_template_date,
561
+ structure_store=structure_store,
562
+ )
563
+
564
+ @property
565
+ def query_sequence(self) -> str:
566
+ return self._query_sequence
567
+
568
+ @property
569
+ def hits(self) -> tuple[Hit, ...]:
570
+ return self._hits
571
+
572
+ @property
573
+ def query_release_date(self) -> datetime.date | None:
574
+ return self._query_release_date
575
+
576
+ @property
577
+ def num_hits(self) -> int:
578
+ return len(self._hits)
579
+
580
+ @functools.cached_property
581
+ def release_date_cutoff(self) -> datetime.date:
582
+ if self.query_release_date is None:
583
+ return self._max_template_date
584
+ return min(
585
+ self._max_template_date,
586
+ self.query_release_date
587
+ - datetime.timedelta(days=_DAYS_BEFORE_QUERY_DATE),
588
+ )
589
+
590
+ def __repr__(self) -> str:
591
+ return f'Templates({self.num_hits} hits)'
592
+
593
+ def filter(
594
+ self,
595
+ *,
596
+ max_subsequence_ratio: float | None,
597
+ min_align_ratio: float | None,
598
+ min_hit_length: int | None,
599
+ deduplicate_sequences: bool,
600
+ max_hits: int | None,
601
+ ) -> Self:
602
+ """Returns a new Templates object with only the hits that pass all filters.
603
+
604
+ This also filters on query_release_date and max_template_date.
605
+
606
+ Args:
607
+ max_subsequence_ratio: If set, excludes hits which are an exact
608
+ subsequence of the query sequence, and longer than this ratio. Useful to
609
+ avoid ground truth leakage.
610
+ min_align_ratio: If set, excludes hits where the number of residues
611
+ aligned to the query is less than this proportion of the template
612
+ length.
613
+ min_hit_length: If set, excludes hits which have fewer residues than this.
614
+ deduplicate_sequences: Whether to exclude duplicate template sequences,
615
+ keeping only the first. This can be useful in increasing the diversity
616
+ of hits especially in the case of homomer hits.
617
+ max_hits: If set, excludes any hits which exceed this count.
618
+ """
619
+ filtered_hits = _filter_hits(
620
+ hits=self._hits,
621
+ release_date_cutoff=self.release_date_cutoff,
622
+ max_subsequence_ratio=max_subsequence_ratio,
623
+ min_align_ratio=min_align_ratio,
624
+ min_hit_length=min_hit_length,
625
+ deduplicate_sequences=deduplicate_sequences,
626
+ max_hits=max_hits,
627
+ )
628
+ return Templates(
629
+ query_sequence=self.query_sequence,
630
+ query_release_date=self.query_release_date,
631
+ hits=filtered_hits,
632
+ max_template_date=self._max_template_date,
633
+ structure_store=self._structure_store,
634
+ )
635
+
636
+ def get_hits_with_structures(
637
+ self,
638
+ ) -> Sequence[tuple[Hit, structure.Structure]]:
639
+ """Returns hits + Structures, Structures filtered to the hit's chain."""
640
+ results = []
641
+ structures = {struc.name.lower(): struc for struc in self.structures}
642
+ for hit in self.hits:
643
+ if not hit.is_valid:
644
+ raise InvalidTemplateError(
645
+ 'Hits must be filtered before calling get_hits_with_structures.'
646
+ )
647
+ struc = structures[hit.pdb_id]
648
+ label_chain_id = struc.polymer_auth_asym_id_to_label_asym_id().get(
649
+ hit.auth_chain_id
650
+ )
651
+ results.append((hit, struc.filter(chain_id=label_chain_id)))
652
+ return results
653
+
654
+ def featurize(
655
+ self,
656
+ include_ligand_features: bool = True,
657
+ ) -> TemplateFeatures:
658
+ """Featurises the templates and returns a map of feature names to features.
659
+
660
+ NB: If you don't do any prefiltering, this method might be slow to run
661
+ as it has to fetch many CIFs and featurize them all.
662
+
663
+ Args:
664
+ include_ligand_features: Whether to compute ligand features.
665
+
666
+ Returns:
667
+ Template features: A mapping of template feature labels to features, which
668
+ may be numpy arrays, bytes objects, or for the special case of label
669
+ `ligand_features` (if `include_ligand_features` is True), a nested
670
+ feature map of labels to numpy arrays.
671
+
672
+ Raises:
673
+ InvalidTemplateError: If hits haven't been filtered before featurization.
674
+ """
675
+ hits_by_pdb_id = {}
676
+ for idx, hit in enumerate(self.hits):
677
+ if not hit.is_valid:
678
+ raise InvalidTemplateError(
679
+ f'Hits must be filtered before featurizing, got unprocessed {hit=}'
680
+ )
681
+ hits_by_pdb_id.setdefault(hit.pdb_id, []).append((idx, hit))
682
+
683
+ unsorted_features = []
684
+ for struc in self.structures:
685
+ pdb_id = str(struc.name).lower()
686
+ for idx, hit in hits_by_pdb_id[pdb_id]:
687
+ try:
688
+ label_chain_id = struc.polymer_auth_asym_id_to_label_asym_id()[
689
+ hit.auth_chain_id
690
+ ]
691
+ hit_features = {
692
+ **get_polymer_features(
693
+ chain=struc.filter(chain_id=label_chain_id),
694
+ chain_poly_type=hit.chain_poly_type,
695
+ query_sequence_length=len(hit.query_sequence),
696
+ query_to_hit_mapping=hit.query_to_hit_mapping,
697
+ ),
698
+ }
699
+ if include_ligand_features:
700
+ hit_features['ligand_features'] = _get_ligand_features(struc)
701
+ unsorted_features.append((idx, hit_features))
702
+ except Error as e:
703
+ raise type(e)(f'Failed to featurise {hit=}') from e
704
+
705
+ sorted_features = sorted(unsorted_features, key=lambda x: x[0])
706
+ sorted_features = [feat for _, feat in sorted_features]
707
+ return package_template_features(
708
+ hit_features=sorted_features,
709
+ include_ligand_features=include_ligand_features,
710
+ )
711
+
712
+ @property
713
+ def structures(self) -> Iterator[structure.Structure]:
714
+ """Yields template structures for each unique PDB ID among hits.
715
+
716
+ If there are multiple hits in the same Structure, the Structure will be
717
+ included only once by this method.
718
+
719
+ Yields:
720
+ A Structure object for each unique PDB ID among hits.
721
+
722
+ Raises:
723
+ HitDateError: If template's release date exceeds max cutoff date.
724
+ """
725
+
726
+ for hit in self.hits:
727
+ if hit.release_date > self.release_date_cutoff: # pylint: disable=comparison-with-callable
728
+ raise HitDateError(
729
+ f'Invalid release date for hit {hit.pdb_id=}, when release date '
730
+ f'cutoff is {self.release_date_cutoff}.'
731
+ )
732
+
733
+ # Get the set of pdbs to load. In particular, remove duplicate PDB IDs.
734
+ targets_to_load = tuple({hit.pdb_id for hit in self.hits})
735
+
736
+ for target_name in targets_to_load:
737
+ yield structure.from_mmcif(
738
+ mmcif_string=self._structure_store.get_mmcif_str(target_name),
739
+ fix_mse_residues=True,
740
+ fix_arginines=True,
741
+ include_water=False,
742
+ include_bonds=False,
743
+ include_other=True, # For non-standard polymer chains.
744
+ )
745
+
746
+
747
+ def _parse_hit_description(description: str) -> tuple[str, str, int, int, int]:
748
+ """Parses the hmmsearch A3M sequence description line."""
749
+ # Example lines (protein, nucleic, no description):
750
+ # >4pqx_A/2-217 [subseq from] mol:protein length:217 Free text
751
+ # >4pqx_A/2-217 [subseq from] mol:na length:217 Free text
752
+ # >5g3r_A/1-55 [subseq from] mol:protein length:352
753
+ if match := re.fullmatch(_HIT_DESCRIPTION_REGEX, description):
754
+ return (
755
+ match['pdb_id'],
756
+ match['chain_id'],
757
+ int(match['start']),
758
+ int(match['end']),
759
+ int(match['length']),
760
+ )
761
+ else:
762
+ raise ValueError(f'Could not parse description "{description}"')
763
+
764
+
765
+ def _parse_hit_metadata(
766
+ structure_store: structure_stores.StructureStore,
767
+ pdb_id: str,
768
+ auth_chain_id: str,
769
+ ) -> tuple[Any, str | None, Sequence[int] | None]:
770
+ """Parse hit metadata by parsing mmCIF from structure store."""
771
+ try:
772
+ cif = mmcif.from_string(structure_store.get_mmcif_str(pdb_id))
773
+ except structure_stores.NotFoundError:
774
+ logging.warning(
775
+ 'Failed to get mmCIF for %s (author chain %s).', pdb_id, auth_chain_id
776
+ )
777
+ return None, None, None
778
+ release_date = mmcif.get_release_date(cif)
779
+
780
+ try:
781
+ struc = structure.from_parsed_mmcif(
782
+ cif,
783
+ model_id=structure.ModelID.ALL,
784
+ include_water=True,
785
+ include_other=True,
786
+ include_bonds=False,
787
+ )
788
+ except ValueError:
789
+ struc = structure.from_parsed_mmcif(
790
+ cif,
791
+ model_id=structure.ModelID.FIRST,
792
+ include_water=True,
793
+ include_other=True,
794
+ include_bonds=False,
795
+ )
796
+
797
+ sequence = struc.polymer_author_chain_single_letter_sequence(
798
+ include_missing_residues=True,
799
+ protein=True,
800
+ dna=True,
801
+ rna=True,
802
+ other=True,
803
+ )[auth_chain_id]
804
+
805
+ unresolved_res_ids = struc.filter(
806
+ chain_auth_asym_id=auth_chain_id
807
+ ).unresolved_residues.id
808
+
809
+ return release_date, sequence, unresolved_res_ids
810
+
811
+
812
+ def get_polymer_features(
813
+ *,
814
+ chain: structure.Structure,
815
+ chain_poly_type: str,
816
+ query_sequence_length: int,
817
+ query_to_hit_mapping: Mapping[int, int],
818
+ ) -> Mapping[str, Any]:
819
+ """Returns features for this polymer chain.
820
+
821
+ Args:
822
+ chain: Structure object representing the template. Must be already filtered
823
+ to a single chain.
824
+ chain_poly_type: The chain polymer type (protein, DNA, RNA).
825
+ query_sequence_length: The length of the query sequence.
826
+ query_to_hit_mapping: 0-based query index to hit index mapping.
827
+
828
+ Returns:
829
+ A dictionary with polymer features for template_chain_id in the struc.
830
+
831
+ Raises:
832
+ ValueError: If the input structure contains more than just a single chain.
833
+ """
834
+ # if len(chain.polymer_auth_asym_id_to_label_asym_id()) != 1:
835
+ # raise ValueError('The structure must be filtered to a single chain.')
836
+
837
+ if chain.name is None:
838
+ raise ValueError('Template structure must have a name.')
839
+
840
+ if chain.release_date is None:
841
+ raise ValueError(
842
+ f'Template structure {chain.name} must have a release date. You can do'
843
+ ' this by setting "_pdbx_audit_revision_history.revision_date" in the'
844
+ ' template mmCIF to a date in the ISO-8601 format (e.g. 1989-11-17).'
845
+ )
846
+
847
+ num_polymer_chains = len(chain.polymer_auth_asym_id_to_label_asym_id())
848
+ if num_polymer_chains != 1:
849
+ raise ValueError(
850
+ f'Template structure {chain.name} must be filtered to a single polymer'
851
+ f' chain but got a structure with {num_polymer_chains} polymer chains.'
852
+ )
853
+
854
+ auth_chain_id, label_chain_id = next(
855
+ iter(chain.polymer_auth_asym_id_to_label_asym_id().items())
856
+ )
857
+ chain_sequence = chain.chain_single_letter_sequence()[label_chain_id]
858
+
859
+ polymer = _POLYMERS[chain_poly_type]
860
+ res_arrays = chain.to_res_arrays(
861
+ include_missing_residues=True, atom_order=polymer.atom_order
862
+ )
863
+ positions = res_arrays.atom_positions
864
+ positions_mask = res_arrays.atom_mask
865
+ template_all_atom_positions = np.zeros(
866
+ (query_sequence_length, polymer.num_atom_types, 3), dtype=np.float64
867
+ )
868
+ template_all_atom_masks = np.zeros(
869
+ (query_sequence_length, polymer.num_atom_types), dtype=np.int64
870
+ )
871
+
872
+ template_sequence = ['-'] * query_sequence_length
873
+ for query_index, template_index in query_to_hit_mapping.items():
874
+ template_all_atom_positions[query_index] = positions[template_index]
875
+ template_all_atom_masks[query_index] = positions_mask[template_index]
876
+ template_sequence[query_index] = chain_sequence[template_index]
877
+
878
+ template_sequence = ''.join(template_sequence)
879
+ template_aatype = _encode_restype(chain_poly_type, template_sequence)
880
+ template_name = f'{chain.name.lower()}_{auth_chain_id}'
881
+ release_date = chain.release_date.strftime('%Y-%m-%d')
882
+ return {
883
+ 'template_all_atom_positions': template_all_atom_positions,
884
+ 'template_all_atom_masks': template_all_atom_masks,
885
+ 'template_sequence': template_sequence.encode(),
886
+ 'template_aatype': np.array(template_aatype, dtype=np.int32),
887
+ 'template_domain_names': np.array(template_name.encode(), dtype=object),
888
+ 'template_release_date': np.array(release_date.encode(), dtype=object),
889
+ }
890
+
891
+
892
+ def _get_ligand_features(
893
+ struc: structure.Structure,
894
+ ) -> Mapping[str, Mapping[str, np.ndarray | bytes]]:
895
+ """Returns features for the ligands in this structure."""
896
+ ligand_struc = struc.filter_to_entity_type(ligand=True)
897
+ assert ligand_struc.coords is not None
898
+ assert ligand_struc.atom_name is not None
899
+ assert ligand_struc.atom_occupancy is not None
900
+
901
+ ligand_features = {}
902
+ for ligand_chain_id in ligand_struc.chains:
903
+ idxs = np.where(ligand_struc.chain_id == ligand_chain_id)[0]
904
+ if idxs.shape[0]:
905
+ ligand_features[ligand_chain_id] = {
906
+ 'ligand_atom_positions': (
907
+ ligand_struc.coords[idxs, :].astype(np.float32)
908
+ ),
909
+ 'ligand_atom_names': ligand_struc.atom_name[idxs].astype(object),
910
+ 'ligand_atom_occupancies': (
911
+ ligand_struc.atom_occupancy[idxs].astype(np.float32)
912
+ ),
913
+ 'ccd_id': ligand_struc.res_name[idxs][0].encode(),
914
+ }
915
+ return ligand_features
916
+
917
+
918
+ def package_template_features(
919
+ *,
920
+ hit_features: Sequence[Mapping[str, Any]],
921
+ include_ligand_features: bool,
922
+ ) -> Mapping[str, Any]:
923
+ """Stacks polymer features, adds empty and keeps ligand features unstacked."""
924
+
925
+ features_to_include = set(_POLYMER_FEATURES)
926
+ if include_ligand_features:
927
+ features_to_include.update(_LIGAND_FEATURES)
928
+
929
+ features = {
930
+ feat: [single_hit_features[feat] for single_hit_features in hit_features]
931
+ for feat in features_to_include
932
+ }
933
+
934
+ stacked_features = {}
935
+ for k, v in features.items():
936
+ if k in _POLYMER_FEATURES:
937
+ v = np.stack(v, axis=0) if v else np.array([], dtype=_POLYMER_FEATURES[k])
938
+ stacked_features[k] = v
939
+
940
+ return stacked_features
941
+
942
+
943
+ def _resolve_path(path: os.PathLike[str] | str) -> str:
944
+ """Resolves path for data dep paths, stringifies otherwise."""
945
+ # Data dependency paths: db baked into the binary.
946
+ resolved_path = resources.filename(path)
947
+ if os.path.exists(resolved_path):
948
+ return resolved_path
949
+ else:
950
+ # Other paths, e.g. local.
951
+ return str(path)
952
+
953
+
954
+ def run_hmmsearch_with_a3m(
955
+ *,
956
+ database_path: os.PathLike[str] | str,
957
+ hmmsearch_config: msa_config.HmmsearchConfig,
958
+ max_a3m_query_sequences: int | None,
959
+ a3m: str | None,
960
+ ) -> str:
961
+ """Runs Hmmsearch to get a3m string of hits."""
962
+ searcher = hmmsearch.Hmmsearch(
963
+ binary_path=hmmsearch_config.hmmsearch_binary_path,
964
+ hmmbuild_binary_path=hmmsearch_config.hmmbuild_binary_path,
965
+ database_path=_resolve_path(database_path),
966
+ e_value=hmmsearch_config.e_value,
967
+ inc_e=hmmsearch_config.inc_e,
968
+ dom_e=hmmsearch_config.dom_e,
969
+ incdom_e=hmmsearch_config.incdom_e,
970
+ alphabet=hmmsearch_config.alphabet,
971
+ filter_f1=hmmsearch_config.filter_f1,
972
+ filter_f2=hmmsearch_config.filter_f2,
973
+ filter_f3=hmmsearch_config.filter_f3,
974
+ filter_max=hmmsearch_config.filter_max,
975
+ )
976
+ # STO enables us to annotate query non-gap columns as reference columns.
977
+ sto = parsers.convert_a3m_to_stockholm(a3m, max_a3m_query_sequences)
978
+ return searcher.query_with_sto(sto, model_construction='hand')
flax_model/alphafold3/data/tools/__init__.py ADDED
File without changes
flax_model/alphafold3/data/tools/hmmbuild.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """A Python wrapper for hmmbuild - construct HMM profiles from MSA."""
4
+
5
+ import os
6
+ import re
7
+ import tempfile
8
+ from typing import Literal
9
+
10
+ from flax_model.alphafold3.data import parsers
11
+ from flax_model.alphafold3.data.tools import subprocess_utils
12
+
13
+
14
+ class Hmmbuild(object):
15
+ """Python wrapper of the hmmbuild binary."""
16
+
17
+ def __init__(
18
+ self,
19
+ *,
20
+ binary_path: str,
21
+ singlemx: bool = False,
22
+ alphabet: str | None = None,
23
+ ):
24
+ """Initializes the Python hmmbuild wrapper.
25
+
26
+ Args:
27
+ binary_path: The path to the hmmbuild executable.
28
+ singlemx: Whether to use --singlemx flag. If True, it forces HMMBuild to
29
+ just use a common substitution score matrix.
30
+ alphabet: The alphabet to assert when building a profile. Useful when
31
+ hmmbuild cannot guess the alphabet. If None, no alphabet is asserted.
32
+
33
+ Raises:
34
+ RuntimeError: If hmmbuild binary not found within the path.
35
+ """
36
+ self._binary_path = binary_path
37
+ self._singlemx = singlemx
38
+ self._alphabet = alphabet
39
+
40
+ subprocess_utils.check_binary_exists(path=self._binary_path, name='hmmbuild')
41
+
42
+ def build_profile_from_sto(self, sto: str, model_construction='fast') -> str:
43
+ """Builds a HHM for the aligned sequences given as an A3M string.
44
+
45
+ Args:
46
+ sto: A string with the aligned sequences in the Stockholm format.
47
+ model_construction: Whether to use reference annotation in the msa to
48
+ determine consensus columns ('hand') or default ('fast').
49
+
50
+ Returns:
51
+ A string with the profile in the HMM format.
52
+
53
+ Raises:
54
+ RuntimeError: If hmmbuild fails.
55
+ """
56
+ return self._build_profile(
57
+ sto, informat='stockholm', model_construction=model_construction
58
+ )
59
+
60
+ def build_profile_from_a3m(self, a3m: str) -> str:
61
+ """Builds a HHM for the aligned sequences given as an A3M string.
62
+
63
+ Args:
64
+ a3m: A string with the aligned sequences in the A3M format.
65
+
66
+ Returns:
67
+ A string with the profile in the HMM format.
68
+
69
+ Raises:
70
+ RuntimeError: If hmmbuild fails.
71
+ """
72
+ lines = []
73
+ for sequence, description in parsers.lazy_parse_fasta_string(a3m):
74
+ sequence = re.sub('[a-z]+', '', sequence) # Remove inserted residues.
75
+ lines.append(f'>{description}\n{sequence}\n')
76
+ msa = ''.join(lines)
77
+ return self._build_profile(msa, informat='afa')
78
+
79
+ def _build_profile(
80
+ self,
81
+ msa: str,
82
+ informat: Literal['afa', 'stockholm'],
83
+ model_construction: str = 'fast',
84
+ ) -> str:
85
+ """Builds a HMM for the aligned sequences given as an MSA string.
86
+
87
+ Args:
88
+ msa: A string with the aligned sequences, in A3M or STO format.
89
+ informat: One of 'afa' (aligned FASTA) or 'sto' (Stockholm).
90
+ model_construction: Whether to use reference annotation in the msa to
91
+ determine consensus columns ('hand') or default ('fast').
92
+
93
+ Returns:
94
+ A string with the profile in the HMM format.
95
+
96
+ Raises:
97
+ RuntimeError: If hmmbuild fails.
98
+ ValueError: If unspecified arguments are provided.
99
+ """
100
+ if model_construction not in {'hand', 'fast'}:
101
+ raise ValueError(f'Bad {model_construction=}. Only hand or fast allowed.')
102
+
103
+ with tempfile.TemporaryDirectory() as query_tmp_dir:
104
+ input_msa_path = os.path.join(query_tmp_dir, 'query.msa')
105
+ output_hmm_path = os.path.join(query_tmp_dir, 'output.hmm')
106
+
107
+ with open(input_msa_path, 'w') as f:
108
+ f.write(msa)
109
+
110
+ # Specify the format as we don't specify the input file extension. See
111
+ # https://github.com/EddyRivasLab/hmmer/issues/321 for more details.
112
+ cmd_flags = ['--informat', informat]
113
+ # If adding flags, we have to do so before the output and input:
114
+ if model_construction == 'hand':
115
+ cmd_flags.append(f'--{model_construction}')
116
+ if self._singlemx:
117
+ cmd_flags.append('--singlemx')
118
+ if self._alphabet:
119
+ cmd_flags.append(f'--{self._alphabet}')
120
+
121
+ cmd_flags.extend([output_hmm_path, input_msa_path])
122
+
123
+ cmd = [self._binary_path, *cmd_flags]
124
+
125
+ subprocess_utils.run(
126
+ cmd=cmd,
127
+ cmd_name='Hmmbuild',
128
+ log_stdout=False,
129
+ log_stderr=True,
130
+ log_on_process_error=True,
131
+ )
132
+
133
+ with open(output_hmm_path) as f:
134
+ hmm = f.read()
135
+
136
+ return hmm
flax_model/alphafold3/data/tools/hmmsearch.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """A Python wrapper for hmmsearch - search profile against a sequence db."""
4
+
5
+ import os
6
+ import tempfile
7
+
8
+ from absl import logging
9
+ from flax_model.alphafold3.data import parsers
10
+ from flax_model.alphafold3.data.tools import hmmbuild
11
+ from flax_model.alphafold3.data.tools import subprocess_utils
12
+
13
+
14
+ class Hmmsearch(object):
15
+ """Python wrapper of the hmmsearch binary."""
16
+
17
+ def __init__(
18
+ self,
19
+ *,
20
+ binary_path: str,
21
+ hmmbuild_binary_path: str,
22
+ database_path: str,
23
+ alphabet: str = 'amino',
24
+ filter_f1: float | None = None,
25
+ filter_f2: float | None = None,
26
+ filter_f3: float | None = None,
27
+ e_value: float | None = None,
28
+ inc_e: float | None = None,
29
+ dom_e: float | None = None,
30
+ incdom_e: float | None = None,
31
+ filter_max: bool = False,
32
+ ):
33
+ """Initializes the Python hmmsearch wrapper.
34
+
35
+ Args:
36
+ binary_path: The path to the hmmsearch executable.
37
+ hmmbuild_binary_path: The path to the hmmbuild executable. Used to build
38
+ an hmm from an input a3m.
39
+ database_path: The path to the hmmsearch database (FASTA format).
40
+ alphabet: Chain type e.g. amino, rna, dna.
41
+ filter_f1: MSV and biased composition pre-filter, set to >1.0 to turn off.
42
+ filter_f2: Viterbi pre-filter, set to >1.0 to turn off.
43
+ filter_f3: Forward pre-filter, set to >1.0 to turn off.
44
+ e_value: E-value criteria for inclusion in tblout.
45
+ inc_e: E-value criteria for inclusion in MSA/next round.
46
+ dom_e: Domain e-value criteria for inclusion in tblout.
47
+ incdom_e: Domain e-value criteria for inclusion of domains in MSA/next
48
+ round.
49
+ filter_max: Remove all filters, will ignore all filter_f* settings.
50
+
51
+ Raises:
52
+ RuntimeError: If hmmsearch binary not found within the path.
53
+ """
54
+ self._binary_path = binary_path
55
+ self._hmmbuild_runner = hmmbuild.Hmmbuild(
56
+ alphabet=alphabet, binary_path=hmmbuild_binary_path
57
+ )
58
+ self._database_path = database_path
59
+ flags = []
60
+ if filter_max:
61
+ flags.append('--max')
62
+ else:
63
+ if filter_f1 is not None:
64
+ flags.extend(('--F1', filter_f1))
65
+ if filter_f2 is not None:
66
+ flags.extend(('--F2', filter_f2))
67
+ if filter_f3 is not None:
68
+ flags.extend(('--F3', filter_f3))
69
+
70
+ if e_value is not None:
71
+ flags.extend(('-E', e_value))
72
+ if inc_e is not None:
73
+ flags.extend(('--incE', inc_e))
74
+ if dom_e is not None:
75
+ flags.extend(('--domE', dom_e))
76
+ if incdom_e is not None:
77
+ flags.extend(('--incdomE', incdom_e))
78
+
79
+ self._flags = tuple(map(str, flags))
80
+
81
+ subprocess_utils.check_binary_exists(
82
+ path=self._binary_path, name='hmmsearch'
83
+ )
84
+
85
+ if not os.path.exists(self._database_path):
86
+ logging.error('Could not find hmmsearch database %s', database_path)
87
+ raise ValueError(f'Could not find hmmsearch database {database_path}')
88
+
89
+ def query_with_hmm(self, hmm: str) -> str:
90
+ """Queries the database using hmmsearch using a given hmm."""
91
+ with tempfile.TemporaryDirectory() as query_tmp_dir:
92
+ hmm_input_path = os.path.join(query_tmp_dir, 'query.hmm')
93
+ sto_out_path = os.path.join(query_tmp_dir, 'output.sto')
94
+ with open(hmm_input_path, 'w') as f:
95
+ f.write(hmm)
96
+
97
+ cmd = [
98
+ self._binary_path,
99
+ '--noali', # Don't include the alignment in stdout.
100
+ *('--cpu', '8'),
101
+ ]
102
+ # If adding flags, we have to do so before the output and input:
103
+ if self._flags:
104
+ cmd.extend(self._flags)
105
+ cmd.extend([
106
+ *('-A', sto_out_path),
107
+ hmm_input_path,
108
+ self._database_path,
109
+ ])
110
+
111
+ subprocess_utils.run(
112
+ cmd=cmd,
113
+ cmd_name=f'Hmmsearch ({os.path.basename(self._database_path)})',
114
+ log_stdout=False,
115
+ log_stderr=True,
116
+ log_on_process_error=True,
117
+ )
118
+
119
+ with open(sto_out_path) as f:
120
+ a3m_out = parsers.convert_stockholm_to_a3m(
121
+ f, remove_first_row_gaps=False, linewidth=60
122
+ )
123
+
124
+ return a3m_out
125
+
126
+ def query_with_a3m(self, a3m_in: str) -> str:
127
+ """Query the database using hmmsearch using a given a3m."""
128
+
129
+ # Only the "fast" model construction makes sense with A3M, as it doesn't
130
+ # have any way to annotate reference columns.
131
+ hmm = self._hmmbuild_runner.build_profile_from_a3m(a3m_in)
132
+ return self.query_with_hmm(hmm)
133
+
134
+ def query_with_sto(
135
+ self, msa_sto: str, model_construction: str = 'fast'
136
+ ) -> str:
137
+ """Queries the database using hmmsearch using a given stockholm msa."""
138
+ hmm = self._hmmbuild_runner.build_profile_from_sto(
139
+ msa_sto, model_construction=model_construction
140
+ )
141
+ return self.query_with_hmm(hmm)
flax_model/alphafold3/data/tools/jackhmmer.py ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Library to run Jackhmmer from Python."""
4
+
5
+ from collections.abc import Iterable, Sequence
6
+ from concurrent import futures
7
+ import heapq
8
+ import os
9
+ import pathlib
10
+ import shutil
11
+ import tempfile
12
+ import time
13
+
14
+ from absl import logging
15
+ from flax_model.alphafold3.data import parsers
16
+ from flax_model.alphafold3.data.tools import shards
17
+ from flax_model.alphafold3.data.tools import msa_tool
18
+ from flax_model.alphafold3.data.tools import subprocess_utils
19
+
20
+
21
+ class Jackhmmer(msa_tool.MsaTool):
22
+ """Python wrapper of the Jackhmmer binary."""
23
+
24
+ def __init__(
25
+ self,
26
+ *,
27
+ binary_path: str,
28
+ database_path: str,
29
+ n_cpu: int = 8,
30
+ n_iter: int = 3,
31
+ e_value: float | None = 1e-3,
32
+ z_value: float | int | None = None,
33
+ dom_e: float | None = None,
34
+ dom_z_value: float | int | None = None,
35
+ max_sequences: int = 5000,
36
+ filter_f1: float = 5e-4,
37
+ filter_f2: float = 5e-5,
38
+ filter_f3: float = 5e-7,
39
+ max_threads: int | None = None,
40
+ **unused_kwargs,
41
+ ):
42
+ """Initializes the Python Jackhmmer wrapper.
43
+
44
+ NOTE: The MSA obtained by running against sharded dbs won't be always
45
+ exactly the same as the MSA obtained by running against an unsharded db.
46
+ This is because of Jackhmmer deduplication logic, which won't spot duplicate
47
+ hits across multiple shards. Usually this means that the sharded search
48
+ finds more hits (likely bounded by the number of shards), but this should
49
+ not pose an issue given how the results are used downstream. The problem is
50
+ more pronounced with deep MSAs and lower in the hit list (higher e-values).
51
+
52
+ Make sure to set the Z and domZ values when searching against a sharded
53
+ database, otherwise the results won't match the normal unsharded search.
54
+
55
+ Args:
56
+ binary_path: The path to the jackhmmer executable.
57
+ database_path: The path to the jackhmmer database (FASTA format). Sharded
58
+ file specs, e.g. `<db_path>@<num_shards>`, are supported.
59
+ n_cpu: The number of CPUs to give Jackhmmer.
60
+ n_iter: The number of Jackhmmer iterations.
61
+ e_value: The E-value, see Jackhmmer docs for more details.
62
+ z_value: The Z-value representing the number of comparisons done (i.e
63
+ correct database size) for E-value calculation. Make sure to set this
64
+ when searching against a sharded database, otherwise the e-values will
65
+ be incorrectly scaled.
66
+ dom_e: Domain e-value criteria for inclusion in tblout.
67
+ dom_z_value: Domain z-value representing the number of comparisons done
68
+ (i.e correct database size) for domain E-value calculation. Make sure to
69
+ set this when searching against a sharded database, otherwise the domain
70
+ e-values will be incorrectly scaled.
71
+ max_sequences: Maximum number of sequences to return in the MSA.
72
+ filter_f1: MSV and biased composition pre-filter, set to >1.0 to turn off.
73
+ filter_f2: Viterbi pre-filter, set to >1.0 to turn off.
74
+ filter_f3: Forward pre-filter, set to >1.0 to turn off.
75
+ max_threads: If given, the maximum number of threads used when running
76
+ sharded databases.
77
+
78
+ Raises:
79
+ RuntimeError: If Jackhmmer binary not found within the path.
80
+ ValueError: If an invalid configuration is provided in the args.
81
+ """
82
+ self._database_path = database_path
83
+
84
+ if shard_paths := shards.get_sharded_paths(self._database_path):
85
+ if n_iter != 1:
86
+ raise ValueError('For a sharded db, only n_iter=1 is supported.')
87
+ if z_value is None:
88
+ raise ValueError(
89
+ 'The Z-value must be set when searching against a sharded database '
90
+ 'to correctly scale e-values.'
91
+ )
92
+ if max_sequences <= 1:
93
+ raise ValueError(
94
+ 'max_sequences must be greater than 1 when running in sharded '
95
+ 'mode, because each shard would return only the query sequence.'
96
+ )
97
+
98
+ self._shard_paths = shard_paths
99
+ self._max_threads = len(self._shard_paths)
100
+ if max_threads is not None:
101
+ self._max_threads = min(max_threads, self._max_threads)
102
+ logging.info('Jackhmmer running with max_threads = %d', self._max_threads)
103
+ else:
104
+ self._shard_paths = None
105
+ self._max_threads = None
106
+
107
+ self._binary_path = binary_path
108
+ subprocess_utils.check_binary_exists(
109
+ path=self._binary_path, name='Jackhmmer'
110
+ )
111
+
112
+ self._n_cpu = n_cpu
113
+ self._n_iter = n_iter
114
+ self._e_value = e_value
115
+ self._z_value = z_value
116
+ self._dom_e = dom_e
117
+ self._dom_z_value = dom_z_value
118
+ self._max_sequences = max_sequences
119
+ self._filter_f1 = filter_f1
120
+ self._filter_f2 = filter_f2
121
+ self._filter_f3 = filter_f3
122
+
123
+ # If Jackhmmer supports the --seq_limit flag (via our patch), use it to
124
+ # prevent writing out redundant sequences and increasing peak memory usage.
125
+ # If not, the Jackhmmer will be run without the --seq_limit flag.
126
+ self._supports_seq_limit = subprocess_utils.jackhmmer_seq_limit_supported(
127
+ self._binary_path
128
+ )
129
+
130
+ def query(self, target_sequence: str) -> msa_tool.MsaToolResult:
131
+ """Query the database (sharded or unsharded) using Jackhmmer."""
132
+ if self._shard_paths:
133
+ # Sharded case, run the query against each database shard in parallel.
134
+ logging.info(
135
+ 'Query sequence (sharded db): %s',
136
+ target_sequence
137
+ if len(target_sequence) <= 16
138
+ else f'{target_sequence[:16]}... (len {len(target_sequence)})',
139
+ )
140
+
141
+ global_temp_dir = tempfile.mkdtemp()
142
+
143
+ def _query_shard_fn(
144
+ shard_path: str,
145
+ ) -> tuple[msa_tool.MsaToolResult, float]:
146
+ t_start = time.time()
147
+ result = self._query_db_shard(
148
+ target_sequence=target_sequence,
149
+ db_shard_path=shard_path,
150
+ get_tblout=True, # Tblout contains e-values needed for merging.
151
+ global_temp_dir=global_temp_dir,
152
+ )
153
+ return result, time.time() - t_start
154
+
155
+ with futures.ThreadPoolExecutor(max_workers=self._max_threads) as ex:
156
+ tool_outputs, timings = zip(*ex.map(_query_shard_fn, self._shard_paths))
157
+
158
+ logging.info(
159
+ 'Finished query for %d shards, shard timings (seconds): %s',
160
+ len(tool_outputs),
161
+ ', '.join(f'{t:.1f}' for t in timings),
162
+ )
163
+
164
+ shutil.rmtree(global_temp_dir, ignore_errors=True)
165
+ return _merge_jackhmmer_results(tool_outputs, self._max_sequences)
166
+
167
+ else:
168
+ # Non-sharded case, run the query against the whole database.
169
+ logging.info(
170
+ 'Query sequence (non-sharded db): %s',
171
+ target_sequence
172
+ if len(target_sequence) <= 16
173
+ else f'{target_sequence[:16]}... (len {len(target_sequence)})',
174
+ )
175
+ return self._query_db_shard(
176
+ target_sequence=target_sequence,
177
+ db_shard_path=self._database_path,
178
+ get_tblout=False,
179
+ )
180
+
181
+ def _query_db_shard(
182
+ self,
183
+ *,
184
+ target_sequence: str,
185
+ db_shard_path: str,
186
+ get_tblout: bool,
187
+ global_temp_dir: str | None = None,
188
+ ) -> msa_tool.MsaToolResult:
189
+ """Query the database shard using Jackhmmer."""
190
+
191
+ with tempfile.TemporaryDirectory(dir=global_temp_dir) as query_tmp_dir:
192
+ input_fasta_path = os.path.join(query_tmp_dir, 'query.fasta')
193
+ subprocess_utils.create_query_fasta_file(
194
+ sequence=target_sequence, path=input_fasta_path
195
+ )
196
+ output_sto_path = os.path.join(query_tmp_dir, 'output.sto')
197
+ pathlib.Path(output_sto_path).touch()
198
+
199
+ # The F1/F2/F3 are the expected proportion to pass each of the filtering
200
+ # stages (which get progressively more expensive), reducing these
201
+ # speeds up the pipeline at the expensive of sensitivity. They are
202
+ # currently set very low to make querying Mgnify run in a reasonable
203
+ # amount of time.
204
+ cmd_flags = [
205
+ *('-o', '/dev/null'), # Don't pollute stdout with Jackhmmer output.
206
+ *('-A', output_sto_path),
207
+ '--noali',
208
+ *('--F1', str(self._filter_f1)),
209
+ *('--F2', str(self._filter_f2)),
210
+ *('--F3', str(self._filter_f3)),
211
+ *('--cpu', str(self._n_cpu)),
212
+ *('-N', str(self._n_iter)),
213
+ ]
214
+
215
+ if get_tblout:
216
+ output_tblout_path = pathlib.Path(query_tmp_dir, 'tblout.txt')
217
+ output_tblout_path.touch()
218
+ cmd_flags.extend(['--tblout', str(output_tblout_path)])
219
+ else:
220
+ output_tblout_path = None
221
+
222
+ # Report only sequences with E-values <= x in per-sequence output.
223
+ if self._e_value is not None:
224
+ cmd_flags.extend(['-E', str(self._e_value)])
225
+
226
+ # Use the same value as the reporting e-value (`-E` flag).
227
+ cmd_flags.extend(['--incE', str(self._e_value)])
228
+
229
+ if self._z_value is not None:
230
+ cmd_flags.extend(['-Z', str(self._z_value)])
231
+
232
+ if self._dom_z_value is not None:
233
+ cmd_flags.extend(['--domZ', str(self._dom_z_value)])
234
+
235
+ if self._dom_e is not None:
236
+ cmd_flags.extend(['--domE', str(self._dom_e)])
237
+
238
+ if self._max_sequences is not None and self._supports_seq_limit:
239
+ cmd_flags.extend(['--seq_limit', str(self._max_sequences)])
240
+
241
+ # The input FASTA and the input db are the last two arguments.
242
+ cmd = [self._binary_path] + cmd_flags + [input_fasta_path, db_shard_path]
243
+
244
+ subprocess_utils.run(
245
+ cmd=cmd,
246
+ cmd_name=f'Jackhmmer ({os.path.basename(db_shard_path)})',
247
+ log_stdout=False,
248
+ log_stderr=True,
249
+ log_on_process_error=True,
250
+ )
251
+
252
+ with open(output_sto_path) as f:
253
+ a3m = parsers.convert_stockholm_to_a3m(
254
+ f, max_sequences=self._max_sequences
255
+ )
256
+
257
+ # Get the tabular output which has e.g. e-value for each target.
258
+ tbl = '' if output_tblout_path is None else output_tblout_path.read_text()
259
+
260
+ return msa_tool.MsaToolResult(
261
+ target_sequence=target_sequence,
262
+ a3m=a3m,
263
+ e_value=self._e_value,
264
+ tblout=tbl,
265
+ )
266
+
267
+
268
+ def _merge_jackhmmer_results(
269
+ jh_results: Sequence[msa_tool.MsaToolResult], max_sequences: int
270
+ ) -> msa_tool.MsaToolResult:
271
+ """Merges Jackhmmer result protos into a single one."""
272
+ assert len(set(jh_res.target_sequence for jh_res in jh_results)) == 1
273
+ assert len(set(jh_res.e_value for jh_res in jh_results)) == 1
274
+
275
+ # Parse the TBL output, create a mapping from hit name to TBL line.
276
+ parsed_tbl = {}
277
+ for jh_result in jh_results:
278
+ assert jh_result.tblout is not None
279
+ for line in jh_result.tblout.splitlines():
280
+ if not line.startswith('#'):
281
+ parsed_tbl[line.partition(' ')[0]] = line
282
+
283
+ # Create an iterator and merge a3m info with tbl info.
284
+ def _merged_a3m_tbl_iter(a3m: str) -> Iterable[tuple[str, str, str, str]]:
285
+ # Don't parse the entire a3m, lazily parse only as many sequences as needed.
286
+ iterator = iter(parsers.lazy_parse_fasta_string(a3m))
287
+ next(iterator) # Skip the query which isn't present in tblout.
288
+ for sequence, description in iterator:
289
+ name = description.partition(' ')[0].partition('/')[0]
290
+ if tbl_info := parsed_tbl.get(name):
291
+ # Skip sequences for which we don't have tbl information.
292
+ yield sequence, description, tbl_info, name
293
+
294
+ def sort_key(seq_data: tuple[str, str, str, str]) -> tuple[float, str]:
295
+ unused_seq, unused_description, tbl_info, name = seq_data
296
+ # Tblout lines have 19 whitespace delimited columns. "-" used if no value
297
+ # present. We want e-value in column with index 4, so do only 5 splits.
298
+ # Use the name in case of a e-value tie.
299
+ return float(tbl_info.split(maxsplit=5)[4]), name
300
+
301
+ # A3M/TBL is sorted by e-value and name, hence we can merge them efficiently.
302
+ merged_a3m_and_tblout = heapq.merge(
303
+ *[_merged_a3m_tbl_iter(res.a3m) for res in jh_results],
304
+ key=sort_key,
305
+ )
306
+
307
+ # Truncate the a3m to max_sequences. Do not truncate the tblout.
308
+ merged_tblout = []
309
+ merged_a3m = [f'>query\n{jh_results[0].target_sequence}']
310
+ for seq, description, tbl_info, _ in merged_a3m_and_tblout:
311
+ merged_tblout.append(tbl_info)
312
+ if len(merged_a3m) < max_sequences:
313
+ merged_a3m.append(f'>{description}\n{seq}')
314
+
315
+ logging.info(
316
+ 'Limiting merged MSA depth from %d to %d',
317
+ len(merged_tblout),
318
+ max_sequences,
319
+ )
320
+
321
+ return msa_tool.MsaToolResult(
322
+ target_sequence=jh_results[0].target_sequence,
323
+ a3m='\n'.join(merged_a3m),
324
+ e_value=jh_results[0].e_value,
325
+ tblout=None, # We no longer need the tblout.
326
+ )
flax_model/alphafold3/data/tools/mmseqs.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Library to run Mmseqs from Python."""
4
+
5
+ import os
6
+ import tempfile
7
+ import subprocess
8
+ import time
9
+ from typing import List, Dict, Optional
10
+
11
+
12
+ from absl import logging
13
+ from flax_model.alphafold3.data import parsers
14
+ from flax_model.alphafold3.data.tools import msa_tool
15
+ from flax_model.alphafold3.data.tools import subprocess_utils
16
+ import shlex
17
+
18
+ import shutil
19
+ import re
20
+
21
+
22
+ class Mmseqs(msa_tool.MsaTool):
23
+
24
+ _database_types = ["mgnify", "uniprot_cluster_annot", "uniref90", "small_bfd"]
25
+
26
+ def __init__(
27
+ self,
28
+ *,
29
+ binary_path: str,
30
+ database_path: str,
31
+ n_cpu: int = 8,
32
+ e_value: float = 1e-4,
33
+ max_sequences: int = 5000,
34
+ use_gpu: int = 1,
35
+ msa_format_mode: int = 4,
36
+ mmseqs_options: str = "",
37
+ result2msa_options: str = "",
38
+ ):
39
+ self.binary_path = binary_path
40
+ self.database_path = database_path
41
+
42
+ subprocess_utils.check_binary_exists(path=self.binary_path, name='MMseqs')
43
+
44
+ if not os.path.exists(self.database_path):
45
+ raise ValueError(f'Database not found: {database_path}')
46
+
47
+ self.n_cpu = n_cpu
48
+ self.e_value = e_value
49
+ self.max_sequences = max_sequences
50
+ self.use_gpu = use_gpu
51
+ self.msa_format_mode = msa_format_mode
52
+ self.mmseqs_options = mmseqs_options
53
+ self.result2msa_options = result2msa_options
54
+
55
+ def _get_gpu_memory_gb(self) -> float:
56
+ hip_devices = os.environ.get('HIP_VISIBLE_DEVICES', '')
57
+ cuda_devices = os.environ.get('CUDA_VISIBLE_DEVICES', '')
58
+
59
+ hy_smi = shutil.which('hy-smi')
60
+ nvidia_smi = shutil.which('nvidia-smi')
61
+
62
+ prefer_hip = bool(hip_devices or hy_smi)
63
+
64
+ device_id: Optional[str] = None
65
+ if prefer_hip and hip_devices:
66
+ ids = [d.strip() for d in hip_devices.split(',') if d.strip()]
67
+ device_id = ids[0] if ids else None
68
+ elif cuda_devices:
69
+ ids = [d.strip() for d in cuda_devices.split(',') if d.strip()]
70
+ device_id = ids[0] if ids else None
71
+
72
+ # 1) hy-smi (HIP path)
73
+ if hy_smi:
74
+ try:
75
+ mem = self._get_gpu_memory_gb_via_hy_smi(hy_smi, device_id)
76
+ if mem is not None:
77
+ logging.info(f"GPU memory (hy-smi): {mem:.1f}GB")
78
+ return mem
79
+ except Exception as e: # noqa: BLE001
80
+ logging.warning(f"hy-smi query failed: {e}")
81
+
82
+ # 2) NVIDIA fallback when CUDA devices are provided
83
+ if nvidia_smi and cuda_devices:
84
+ try:
85
+ ids = [d.strip() for d in cuda_devices.split(',') if d.strip()]
86
+ if ids:
87
+ cmd = [
88
+ nvidia_smi,
89
+ '--query-gpu=memory.total',
90
+ '--format=csv,noheader,nounits',
91
+ f'--id={ids[0]}',
92
+ ]
93
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
94
+ memory_mb = float(result.stdout.strip())
95
+ return memory_mb / 1024.0
96
+ except (subprocess.CalledProcessError, ValueError, FileNotFoundError) as e:
97
+ logging.warning(f"nvidia-smi query failed: {e}")
98
+
99
+ logging.info("Falling back to default GPU memory: 40GB")
100
+ return 40.0
101
+
102
+ def _parse_memory_gb_from_text(self, text: str) -> Optional[float]:
103
+ units = {
104
+ 'gib': 1.0,
105
+ 'gb': 1.0,
106
+ 'mib': 1.0 / 1024.0,
107
+ 'mb': 1.0 / 1024.0,
108
+ 'kib': 1.0 / (1024.0 * 1024.0),
109
+ 'kb': 1.0 / (1024.0 * 1024.0),
110
+ 'b': 1.0 / (1024.0 * 1024.0 * 1024.0),
111
+ }
112
+ pattern = re.compile(r"(\d+(?:\.\d+)?)\s*(GiB|GB|MiB|MB|KiB|KB|B)", re.IGNORECASE)
113
+ candidates = []
114
+ for match in pattern.finditer(text):
115
+ value = float(match.group(1))
116
+ unit = match.group(2).lower()
117
+ factor = units.get(unit, None)
118
+ if factor is not None:
119
+ candidates.append(value * factor)
120
+ if candidates:
121
+ return max(candidates)
122
+ # rocm-smi often prints bytes as: Total Memory (B): 17163091968
123
+ bytes_pattern = re.compile(r"Total\s*Memory\s*\(B\)\s*[:=]\s*(\d{6,})", re.IGNORECASE)
124
+ m = bytes_pattern.search(text)
125
+ if m:
126
+ try:
127
+ return float(m.group(1)) / (1024.0 * 1024.0 * 1024.0)
128
+ except ValueError:
129
+ return None
130
+ # generic Total: <bytes>
131
+ generic_bytes = re.compile(r"Total\s*[:=]\s*(\d{6,})", re.IGNORECASE)
132
+ m2 = generic_bytes.search(text)
133
+ if m2:
134
+ try:
135
+ return float(m2.group(1)) / (1024.0 * 1024.0 * 1024.0)
136
+ except ValueError:
137
+ return None
138
+ return None
139
+
140
+ def _get_gpu_memory_gb_via_hy_smi(self, hy_smi_path: str, device_id: Optional[str]) -> Optional[float]:
141
+ candidate_cmds = []
142
+ # 1) Plain text output exactly as user provided; most reliable for MiB lines
143
+ if device_id is not None:
144
+ candidate_cmds.append([hy_smi_path, '--showmeminfo', 'vram', '-d', str(device_id)])
145
+ candidate_cmds.append([hy_smi_path, '--showmeminfo', 'vram'])
146
+ # 2) CSV/JSON (may not include units next to values, kept as fallback)
147
+ if device_id is not None:
148
+ candidate_cmds.append([hy_smi_path, '--showmeminfo', 'vram', '--csv', '-d', str(device_id)])
149
+ candidate_cmds.append([hy_smi_path, '--showmeminfo', 'vram', '--csv'])
150
+ if device_id is not None:
151
+ candidate_cmds.append([hy_smi_path, '--showmeminfo', 'vram', '--json', '-d', str(device_id)])
152
+ candidate_cmds.append([hy_smi_path, '--showmeminfo', 'vram', '--json'])
153
+ # 3) All-in-one info as a last resort
154
+ candidate_cmds.append([hy_smi_path, '-a'])
155
+
156
+ for cmd in candidate_cmds:
157
+ try:
158
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
159
+ # Prefer exact vram parser for hy-smi
160
+ parsed = self._parse_hysmi_vram_total_gb(result.stdout, device_id)
161
+ if parsed is None:
162
+ parsed = self._parse_memory_gb_from_text(result.stdout)
163
+ if parsed is not None and parsed > 0:
164
+ return parsed
165
+ except subprocess.CalledProcessError:
166
+ continue
167
+ return None
168
+
169
+ def _parse_hysmi_vram_total_gb(self, text: str, device_id: Optional[str]) -> Optional[float]:
170
+ # Match lines like: HCU[0] : vram Total Memory (MiB): 65520
171
+ pattern = re.compile(r"(?:DCU|HCU)\[(\d+)\].*?vram\s+Total\s+Memory\s*\(MiB\)\s*:\s*(\d+)", re.IGNORECASE)
172
+ matches = pattern.findall(text)
173
+ if not matches:
174
+ return None
175
+ values_gb = []
176
+ for dev, mib_str in matches:
177
+ try:
178
+ if device_id is not None and str(device_id) != str(dev):
179
+ continue
180
+ mib = float(mib_str)
181
+ values_gb.append(mib / 1024.0)
182
+ except ValueError:
183
+ continue
184
+ if not values_gb:
185
+ return None
186
+ return max(values_gb)
187
+
188
+
189
+
190
+ def _get_database_type(self, database_path: str) -> str:
191
+ path_lower = database_path.lower()
192
+ for db_type in self._database_types:
193
+ if db_type in path_lower:
194
+ return db_type
195
+ return "unknown"
196
+
197
+ def _calculate_memory_allocation(self, database_path: str) -> int:
198
+ db_type = self._get_database_type(database_path)
199
+ if db_type == "unknown":
200
+ return 8
201
+
202
+ if not self.use_gpu:
203
+ return 8
204
+
205
+ gpu_memory_gb = self._get_gpu_memory_gb()
206
+
207
+ available_memory = max(0, gpu_memory_gb)
208
+
209
+ if db_type == "mgnify":
210
+ small_dbs_memory = 3 * 2
211
+ allocated_memory = int(available_memory - small_dbs_memory)
212
+ allocated_memory = max(2, allocated_memory)
213
+ else:
214
+ allocated_memory = 2
215
+
216
+ logging.info(f"Database {db_type}: allocated {allocated_memory}GB GPU memory")
217
+ return allocated_memory
218
+
219
+ def query(self, target_sequence: str) -> msa_tool.MsaToolResult:
220
+ with tempfile.TemporaryDirectory() as tmp_dir:
221
+ input_fasta = os.path.join(tmp_dir, 'query.fasta')
222
+ subprocess_utils.create_query_fasta_file(target_sequence, input_fasta)
223
+
224
+ query_db = os.path.join(tmp_dir, 'queryDB')
225
+ self._run_createdb(input_fasta, query_db)
226
+
227
+ result_db = os.path.join(tmp_dir, 'resultDB')
228
+ self._run_search(query_db, result_db)
229
+
230
+ output_sto = os.path.join(tmp_dir, 'output.sto')
231
+ self._run_result2msa(query_db, result_db, output_sto)
232
+
233
+ with open(output_sto) as f:
234
+ a3m = self._parse_output(f)
235
+
236
+ return msa_tool.MsaToolResult(
237
+ target_sequence=target_sequence,
238
+ a3m=a3m,
239
+ e_value=self.e_value
240
+ )
241
+
242
+ def _run_search(self, input_db: str, output_db: str):
243
+ with tempfile.TemporaryDirectory() as tmp_dir:
244
+ cmd = [
245
+ self.binary_path,
246
+ 'search',
247
+ input_db,
248
+ self.database_path,
249
+ output_db,
250
+ tmp_dir,
251
+ '--threads', str(self.n_cpu),
252
+ '-e', str(self.e_value),
253
+ '--gpu', str(self.use_gpu),
254
+ *shlex.split(self.mmseqs_options)
255
+ ]
256
+
257
+ if self.use_gpu:
258
+ max_gpu_mem = self._calculate_memory_allocation(self.database_path)
259
+ cmd.extend(['--max-gpu-mem', f'{max_gpu_mem}G'])
260
+ logging.info(f"Running MMseqs search with max GPU memory: {max_gpu_mem}GB")
261
+ else:
262
+ logging.info("Running MMseqs search in CPU mode")
263
+
264
+ subprocess_utils.run(
265
+ cmd=cmd,
266
+ cmd_name='MMseqs2 search',
267
+ log_stderr=True
268
+ )
269
+
270
+ def _run_createdb(self, input_fasta: str, output_db: str):
271
+ cmd = [self.binary_path, 'createdb', input_fasta, output_db]
272
+ subprocess_utils.run(cmd, 'MMseqs2 createdb')
273
+
274
+ def _run_result2msa(self, query_db: str, result_db: str, output_sto: str):
275
+ cmd = [
276
+ self.binary_path,
277
+ 'result2msa',
278
+ query_db,
279
+ self.database_path,
280
+ result_db,
281
+ output_sto,
282
+ '--msa-format-mode', str(self.msa_format_mode),
283
+ *shlex.split(self.result2msa_options)
284
+ ]
285
+ subprocess_utils.run(cmd, 'MMseqs2 result2msa')
286
+
287
+ def _parse_output(self, file_handle):
288
+ try:
289
+ return parsers.convert_stockholm_to_a3m(file_handle, self.max_sequences)
290
+ except Exception as e:
291
+ logging.warning(f"Stockholm parse failed: {e}")
292
+
293
+ file_handle.seek(0)
294
+ content = file_handle.read()
295
+ debug_file = f"/tmp/stockholm_debug_{int(time.time())}.sto"
296
+ with open(debug_file, 'w') as f:
297
+ f.write(content)
298
+ logging.info(f"Saved debug file: {debug_file}")
299
+
300
+ file_handle.seek(0)
301
+ return parsers.convert_mmseqs_stockholm_to_a3m(file_handle, self.max_sequences)
302
+
303
+
flax_model/alphafold3/data/tools/rdkit_utils.py ADDED
@@ -0,0 +1,535 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Tools for calculating features for ligands."""
4
+
5
+ import collections
6
+ from collections.abc import Mapping, Sequence
7
+
8
+ from absl import logging
9
+ from flax_model.alphafold3.cpp import cif_dict
10
+ import numpy as np
11
+ import rdkit.Chem as rd_chem
12
+ from rdkit.Chem import AllChem as rd_all_chem
13
+
14
+
15
+ _RDKIT_MMCIF_TO_BOND_TYPE: Mapping[str, rd_chem.BondType] = {
16
+ 'SING': rd_chem.BondType.SINGLE,
17
+ 'DOUB': rd_chem.BondType.DOUBLE,
18
+ 'TRIP': rd_chem.BondType.TRIPLE,
19
+ }
20
+
21
+ _RDKIT_BOND_TYPE_TO_MMCIF: Mapping[rd_chem.BondType, str] = {
22
+ v: k for k, v in _RDKIT_MMCIF_TO_BOND_TYPE.items()
23
+ }
24
+
25
+ _RDKIT_BOND_STEREO_TO_MMCIF: Mapping[rd_chem.BondStereo, str] = {
26
+ rd_chem.BondStereo.STEREONONE: 'N',
27
+ rd_chem.BondStereo.STEREOE: 'E',
28
+ rd_chem.BondStereo.STEREOZ: 'Z',
29
+ rd_chem.BondStereo.STEREOCIS: 'Z',
30
+ rd_chem.BondStereo.STEREOTRANS: 'E',
31
+ }
32
+
33
+
34
+ class MolFromMmcifError(Exception):
35
+ """Raised when conversion from mmCIF to RDKit Mol fails."""
36
+
37
+
38
+ class UnsupportedMolBondError(Exception):
39
+ """Raised when we try to handle unsupported RDKit bonds."""
40
+
41
+
42
+ def _populate_atoms_in_mol(
43
+ mol: rd_chem.Mol,
44
+ atom_names: Sequence[str],
45
+ atom_types: Sequence[str],
46
+ atom_charges: Sequence[int],
47
+ implicit_hydrogens: bool,
48
+ ligand_name: str,
49
+ atom_leaving_flags: Sequence[str],
50
+ ):
51
+ """Populate the atoms of a Mol given atom features.
52
+
53
+ Args:
54
+ mol: Mol object.
55
+ atom_names: Names of the atoms.
56
+ atom_types: Types of the atoms.
57
+ atom_charges: Charges of the atoms.
58
+ implicit_hydrogens: Whether to mark the atoms to allow implicit Hs.
59
+ ligand_name: Name of the ligand which the atoms are in.
60
+ atom_leaving_flags: Whether the atom is possibly a leaving atom. Values from
61
+ the CCD column `_chem_comp_atom.pdbx_leaving_atom_flag`. The expected
62
+ values are 'Y' (yes), 'N' (no), '?' (unknown/unset, interpreted as no).
63
+
64
+ Raises:
65
+ ValueError: If atom type is invalid.
66
+ """
67
+ # Map atom names to the position they will take in the rdkit molecule.
68
+ atom_name_to_idx = {name: i for i, name in enumerate(atom_names)}
69
+
70
+ for atom_name, atom_type, atom_charge, atom_leaving_flag in zip(
71
+ atom_names, atom_types, atom_charges, atom_leaving_flags, strict=True
72
+ ):
73
+ try:
74
+ if atom_type == 'X':
75
+ atom_type = '*'
76
+ atom = rd_chem.Atom(atom_type)
77
+ except RuntimeError as e:
78
+ raise ValueError(f'Failed to use atom type: {str(e)}') from e
79
+
80
+ if not implicit_hydrogens:
81
+ atom.SetNoImplicit(True)
82
+
83
+ atom.SetProp('atom_name', atom_name)
84
+ atom.SetProp('atom_leaving_flag', atom_leaving_flag)
85
+ atom.SetFormalCharge(atom_charge)
86
+ residue_info = rd_chem.AtomPDBResidueInfo()
87
+ residue_info.SetName(_format_atom_name(atom_name, atom_type))
88
+ residue_info.SetIsHeteroAtom(True)
89
+ residue_info.SetResidueName(ligand_name)
90
+ residue_info.SetResidueNumber(1)
91
+ atom.SetPDBResidueInfo(residue_info)
92
+ atom_index = mol.AddAtom(atom)
93
+ assert atom_index == atom_name_to_idx[atom_name]
94
+
95
+
96
+ def _populate_bonds_in_mol(
97
+ mol: rd_chem.Mol,
98
+ atom_names: Sequence[str],
99
+ bond_begins: Sequence[str],
100
+ bond_ends: Sequence[str],
101
+ bond_orders: Sequence[str],
102
+ bond_is_aromatics: Sequence[bool],
103
+ ):
104
+ """Populate the bonds of a Mol given bond features.
105
+
106
+ Args:
107
+ mol: Mol object.
108
+ atom_names: Names of atoms in the molecule.
109
+ bond_begins: Names of atoms at the beginning of the bond.
110
+ bond_ends: Names of atoms at the end of the bond.
111
+ bond_orders: What order the bonds are.
112
+ bond_is_aromatics: Whether the bonds are aromatic.
113
+ """
114
+ atom_name_to_idx = {name: i for i, name in enumerate(atom_names)}
115
+ for begin, end, bond_type, is_aromatic in zip(
116
+ bond_begins, bond_ends, bond_orders, bond_is_aromatics, strict=True
117
+ ):
118
+ begin_name, end_name = atom_name_to_idx[begin], atom_name_to_idx[end]
119
+ bond_idx = mol.AddBond(begin_name, end_name, bond_type)
120
+ mol.GetBondWithIdx(bond_idx - 1).SetIsAromatic(is_aromatic)
121
+
122
+
123
+ def sanitize_mol(mol, sort_alphabetically, remove_hydrogens) -> rd_chem.Mol:
124
+ # https://www.rdkit.org/docs/source/rdkit.Chem.rdmolops.html#rdkit.Chem.rdmolops.SanitizeMol
125
+ # Kekulize, check valencies, set aromaticity, conjugation and hybridization.
126
+ # This can repair e.g. incorrect aromatic flags.
127
+ rd_chem.SanitizeMol(mol)
128
+ if sort_alphabetically:
129
+ mol = sort_atoms_by_name(mol)
130
+ if remove_hydrogens:
131
+ mol = rd_chem.RemoveHs(mol)
132
+ return mol
133
+
134
+
135
+ def _add_conformer_to_mol(mol, conformer, force_parse) -> rd_chem.Mol:
136
+ # Create conformer and use it to assign stereochemistry.
137
+ if conformer is not None:
138
+ try:
139
+ mol.AddConformer(conformer)
140
+ rd_chem.AssignStereochemistryFrom3D(mol)
141
+ except ValueError as e:
142
+ logging.warning('Failed to parse conformer: %s', e)
143
+ if not force_parse:
144
+ raise
145
+
146
+
147
+ def mol_from_ccd_cif(
148
+ mol_cif: cif_dict.CifDict,
149
+ *,
150
+ force_parse: bool = False,
151
+ sort_alphabetically: bool = True,
152
+ remove_hydrogens: bool = True,
153
+ implicit_hydrogens: bool = False,
154
+ ) -> rd_chem.Mol:
155
+ """Creates an rdkit Mol object from a CCD mmcif data block.
156
+
157
+ The atoms are renumbered so that their names are in alphabetical order and
158
+ these names are placed on the atoms under property 'atom_name'.
159
+ Only hydrogens which are not required to define the molecule are removed.
160
+ For example, hydrogens that define stereochemistry around a double bond are
161
+ retained.
162
+ See this link for more details.
163
+ https://www.rdkit.org/docs/source/rdkit.Chem.rdmolops.html#rdkit.Chem.rdmolops.RemoveHs
164
+
165
+ Args:
166
+ mol_cif: An mmcif object representing a molecule.
167
+ force_parse: If True, assumes missing aromatic flags are false, substitutes
168
+ deuterium for hydrogen, assumes missing charges are 0 and ignores missing
169
+ conformer / stereochemistry information.
170
+ sort_alphabetically: True: sort atom alphabetically; False: keep CCD order
171
+ remove_hydrogens: if True, remove non-important hydrogens
172
+ implicit_hydrogens: Sets a marker on the atom that allows implicit Hs.
173
+
174
+ Returns:
175
+ An rdkit molecule, with the atoms sorted by name.
176
+
177
+ Raises:
178
+ MolToMmcifError: If conversion from mmcif to rdkit Mol fails. More detailed
179
+ error is available as this error's cause.
180
+ """
181
+ # Read data fields.
182
+ try:
183
+ atom_names, atom_types, atom_charges, atom_leaving_flags = parse_atom_data(
184
+ mol_cif, force_parse
185
+ )
186
+ bond_begins, bond_ends, bond_orders, bond_is_aromatics = parse_bond_data(
187
+ mol_cif, force_parse
188
+ )
189
+ lig_name = mol_cif['_chem_comp.id'][0].rjust(3)
190
+ except (KeyError, ValueError) as e:
191
+ raise MolFromMmcifError from e
192
+
193
+ # Build Rdkit molecule.
194
+ mol = rd_chem.RWMol()
195
+
196
+ # Per atom features.
197
+ try:
198
+ _populate_atoms_in_mol(
199
+ mol=mol,
200
+ atom_names=atom_names,
201
+ atom_types=atom_types,
202
+ atom_charges=atom_charges,
203
+ implicit_hydrogens=implicit_hydrogens,
204
+ ligand_name=lig_name,
205
+ atom_leaving_flags=atom_leaving_flags,
206
+ )
207
+ except (ValueError, RuntimeError) as e:
208
+ raise MolFromMmcifError from e
209
+
210
+ _populate_bonds_in_mol(
211
+ mol, atom_names, bond_begins, bond_ends, bond_orders, bond_is_aromatics
212
+ )
213
+
214
+ try:
215
+ conformer = _parse_ideal_conformer(mol_cif)
216
+ except (KeyError, ValueError) as e:
217
+ logging.warning('Failed to parse ideal conformer: %s', e)
218
+ if not force_parse:
219
+ raise MolFromMmcifError from e
220
+ conformer = None
221
+
222
+ mol.UpdatePropertyCache(strict=False)
223
+
224
+ try:
225
+ _add_conformer_to_mol(mol, conformer, force_parse)
226
+ mol = sanitize_mol(mol, sort_alphabetically, remove_hydrogens)
227
+ except (
228
+ ValueError,
229
+ rd_chem.KekulizeException,
230
+ rd_chem.AtomValenceException,
231
+ ) as e:
232
+ raise MolFromMmcifError from e
233
+
234
+ return mol
235
+
236
+
237
+ def mol_to_ccd_cif(
238
+ mol: rd_chem.Mol,
239
+ component_id: str,
240
+ pdbx_smiles: str | None = None,
241
+ include_hydrogens: bool = True,
242
+ ) -> cif_dict.CifDict:
243
+ """Creates a CCD-like mmcif data block from an rdkit Mol object.
244
+
245
+ Only a subset of associated mmcif fields is populated, but that is
246
+ sufficient for further usage, e.g. in featurization code.
247
+
248
+ Atom names can be specified via `atom_name` property. For atoms with
249
+ unspecified value of that property, the name is assigned based on element type
250
+ and the order in the Mol object.
251
+
252
+ If the Mol object has associated conformers, atom positions from the first of
253
+ them will be populated in the resulting mmcif file.
254
+
255
+ Args:
256
+ mol: An rdkit molecule.
257
+ component_id: Name of the molecule to use in the resulting mmcif. That is
258
+ equivalent to CCD code.
259
+ pdbx_smiles: If specified, the value will be used to populate
260
+ `_chem_comp.pdbx_smiles`.
261
+ include_hydrogens: Whether to include atom and bond data involving
262
+ hydrogens.
263
+
264
+ Returns:
265
+ An mmcif data block corresponding for the given rdkit molecule.
266
+
267
+ Raises:
268
+ UnsupportedMolBond: When a molecule contains a bond that can't be
269
+ represented with mmcif.
270
+ """
271
+ mol = rd_chem.Mol(mol)
272
+ if include_hydrogens:
273
+ mol = rd_chem.AddHs(mol)
274
+ rd_chem.Kekulize(mol)
275
+
276
+ if mol.GetNumConformers() > 0:
277
+ ideal_conformer = mol.GetConformer(0).GetPositions()
278
+ ideal_conformer = np.vectorize(lambda x: f'{x:.3f}')(ideal_conformer)
279
+ else:
280
+ # No data will be populated in the resulting mmcif if the molecule doesn't
281
+ # have any conformers attached to it.
282
+ ideal_conformer = None
283
+
284
+ mol_cif = collections.defaultdict(list)
285
+ mol_cif['data_'] = [component_id]
286
+ mol_cif['_chem_comp.id'] = [component_id]
287
+ if pdbx_smiles:
288
+ mol_cif['_chem_comp.pdbx_smiles'] = [pdbx_smiles]
289
+
290
+ mol = assign_atom_names_from_graph(mol, keep_existing_names=True)
291
+
292
+ for atom_idx, atom in enumerate(mol.GetAtoms()):
293
+ element = atom.GetSymbol()
294
+ if not include_hydrogens and element in ('H', 'D'):
295
+ continue
296
+
297
+ mol_cif['_chem_comp_atom.comp_id'].append(component_id)
298
+ mol_cif['_chem_comp_atom.atom_id'].append(atom.GetProp('atom_name'))
299
+ mol_cif['_chem_comp_atom.type_symbol'].append(atom.GetSymbol().upper())
300
+ mol_cif['_chem_comp_atom.charge'].append(str(atom.GetFormalCharge()))
301
+ if ideal_conformer is not None:
302
+ coords = ideal_conformer[atom_idx]
303
+ mol_cif['_chem_comp_atom.pdbx_model_Cartn_x_ideal'].append(coords[0])
304
+ mol_cif['_chem_comp_atom.pdbx_model_Cartn_y_ideal'].append(coords[1])
305
+ mol_cif['_chem_comp_atom.pdbx_model_Cartn_z_ideal'].append(coords[2])
306
+
307
+ for bond in mol.GetBonds():
308
+ atom1 = bond.GetBeginAtom()
309
+ atom2 = bond.GetEndAtom()
310
+ if not include_hydrogens and (
311
+ atom1.GetSymbol() in ('H', 'D') or atom2.GetSymbol() in ('H', 'D')
312
+ ):
313
+ continue
314
+ mol_cif['_chem_comp_bond.comp_id'].append(component_id)
315
+ mol_cif['_chem_comp_bond.atom_id_1'].append(
316
+ bond.GetBeginAtom().GetProp('atom_name')
317
+ )
318
+ mol_cif['_chem_comp_bond.atom_id_2'].append(
319
+ bond.GetEndAtom().GetProp('atom_name')
320
+ )
321
+ try:
322
+ bond_type = bond.GetBondType()
323
+ # Older versions of RDKit did not have a DATIVE bond type. Convert it to
324
+ # SINGLE to match the AF3 training setup.
325
+ if bond_type == rd_chem.BondType.DATIVE:
326
+ bond_type = rd_chem.BondType.SINGLE
327
+ mol_cif['_chem_comp_bond.value_order'].append(
328
+ _RDKIT_BOND_TYPE_TO_MMCIF[bond_type]
329
+ )
330
+ mol_cif['_chem_comp_bond.pdbx_stereo_config'].append(
331
+ _RDKIT_BOND_STEREO_TO_MMCIF[bond.GetStereo()]
332
+ )
333
+ except KeyError as e:
334
+ raise UnsupportedMolBondError from e
335
+ mol_cif['_chem_comp_bond.pdbx_aromatic_flag'].append(
336
+ 'Y' if bond.GetIsAromatic() else 'N'
337
+ )
338
+
339
+ return cif_dict.CifDict(mol_cif)
340
+
341
+
342
+ def _format_atom_name(atom_name: str, atom_type: str) -> str:
343
+ """Formats an atom name to fit in the four characters specified in PDB.
344
+
345
+ See for example the following note on atom name formatting in PDB files:
346
+ https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/tutorials/pdbintro.html#note1
347
+
348
+ Args:
349
+ atom_name: The unformatted atom name.
350
+ atom_type: The atom element symbol.
351
+
352
+ Returns:
353
+ formatted_atom_name: The formatted 4-character atom name.
354
+ """
355
+ atom_name = atom_name.strip()
356
+ atom_type = atom_type.strip().upper()
357
+ if len(atom_name) == 1:
358
+ return atom_name.rjust(2).ljust(4)
359
+ elif len(atom_name) == 2:
360
+ if atom_name == atom_type:
361
+ return atom_name.ljust(4)
362
+ return atom_name.center(4)
363
+ elif len(atom_name) == 3:
364
+ if atom_name[:2] == atom_type:
365
+ return atom_name.ljust(4)
366
+ return atom_name.rjust(4)
367
+ elif len(atom_name) == 4:
368
+ return atom_name
369
+ else:
370
+ raise ValueError(
371
+ f'Atom name `{atom_name}` has more than four characters '
372
+ 'or is an empty string.'
373
+ )
374
+
375
+
376
+ def parse_atom_data(
377
+ mol_cif: cif_dict.CifDict | Mapping[str, Sequence[str]], force_parse: bool
378
+ ) -> tuple[Sequence[str], Sequence[str], Sequence[int], Sequence[str]]:
379
+ """Parses atoms. If force_parse is True, fix deuterium and missing charge."""
380
+ atom_types = [t.capitalize() for t in mol_cif['_chem_comp_atom.type_symbol']]
381
+ atom_names = mol_cif['_chem_comp_atom.atom_id']
382
+ atom_charges = mol_cif['_chem_comp_atom.charge']
383
+ atom_leaving_flags = ['?'] * len(atom_names)
384
+ if '_chem_comp_atom.pdbx_leaving_atom_flag' in mol_cif:
385
+ atom_leaving_flags = mol_cif['_chem_comp_atom.pdbx_leaving_atom_flag']
386
+
387
+ if force_parse:
388
+ # Replace missing charges with 0.
389
+ atom_charges = [charge if charge != '?' else '0' for charge in atom_charges]
390
+ # Deuterium for hydrogen.
391
+ atom_types = [type_ if type_ != 'D' else 'H' for type_ in atom_types]
392
+
393
+ atom_charges = [int(atom_charge) for atom_charge in atom_charges]
394
+ return atom_names, atom_types, atom_charges, atom_leaving_flags
395
+
396
+
397
+ def parse_bond_data(
398
+ mol_cif: cif_dict.CifDict | Mapping[str, Sequence[str]], force_parse: bool
399
+ ) -> tuple[
400
+ Sequence[str], Sequence[str], Sequence[rd_chem.BondType], Sequence[bool]
401
+ ]:
402
+ """Parses bond data. If force_parse is True, ignore missing aromatic flags."""
403
+ # The bond table isn't present if there are no bonds. Use [] in that case.
404
+ begin_atoms = mol_cif.get('_chem_comp_bond.atom_id_1', [])
405
+ end_atoms = mol_cif.get('_chem_comp_bond.atom_id_2', [])
406
+ orders = mol_cif.get('_chem_comp_bond.value_order', [])
407
+ bond_types = [_RDKIT_MMCIF_TO_BOND_TYPE[order] for order in orders]
408
+
409
+ try:
410
+ aromatic_flags = mol_cif.get('_chem_comp_bond.pdbx_aromatic_flag', [])
411
+ is_aromatic = [{'Y': True, 'N': False}[flag] for flag in aromatic_flags]
412
+ except KeyError:
413
+ if force_parse:
414
+ # Set them all to not aromatic.
415
+ is_aromatic = [False for _ in begin_atoms]
416
+ else:
417
+ raise
418
+
419
+ return begin_atoms, end_atoms, bond_types, is_aromatic
420
+
421
+
422
+ def _parse_ideal_conformer(mol_cif: cif_dict.CifDict) -> rd_chem.Conformer:
423
+ """Builds a conformer containing the ideal coordinates from the CCD.
424
+
425
+ Args:
426
+ mol_cif: An mmcif object representing a molecule.
427
+
428
+ Returns:
429
+ An rdkit conformer filled with the ideal positions from the mmcif.
430
+
431
+ Raises:
432
+ ValueError: if the positions can't be interpreted.
433
+ """
434
+ atom_x = [
435
+ float(x) for x in mol_cif['_chem_comp_atom.pdbx_model_Cartn_x_ideal']
436
+ ]
437
+ atom_y = [
438
+ float(y) for y in mol_cif['_chem_comp_atom.pdbx_model_Cartn_y_ideal']
439
+ ]
440
+ atom_z = [
441
+ float(z) for z in mol_cif['_chem_comp_atom.pdbx_model_Cartn_z_ideal']
442
+ ]
443
+ atom_positions = zip(atom_x, atom_y, atom_z, strict=True)
444
+
445
+ conformer = rd_chem.Conformer(len(atom_x))
446
+ for atom_index, atom_position in enumerate(atom_positions):
447
+ conformer.SetAtomPosition(atom_index, atom_position)
448
+
449
+ return conformer
450
+
451
+
452
+ def sort_atoms_by_name(mol: rd_chem.Mol) -> rd_chem.Mol:
453
+ """Sorts the atoms in the molecule by their names."""
454
+ atom_names = {
455
+ atom.GetProp('atom_name'): atom.GetIdx() for atom in mol.GetAtoms()
456
+ }
457
+
458
+ # Sort the name, int tuples by the names.
459
+ sorted_atom_names = sorted(atom_names.items())
460
+
461
+ # Zip these tuples back together to the sorted indices.
462
+ _, new_order = zip(*sorted_atom_names, strict=True)
463
+
464
+ # Reorder the molecule.
465
+ # new_order is effectively an argsort of the names.
466
+ return rd_chem.RenumberAtoms(mol, new_order)
467
+
468
+
469
+ def assign_atom_names_from_graph(
470
+ mol: rd_chem.Mol,
471
+ keep_existing_names: bool = False,
472
+ ) -> rd_chem.Mol:
473
+ """Assigns atom names from the molecular graph.
474
+
475
+ The atom name is stored as an atom property 'atom_name', accessible
476
+ with atom.GetProp('atom_name'). If the property is already specified, and
477
+ keep_existing_names is True we keep the original name.
478
+
479
+ We traverse the graph in the order of the rdkit atom index and give each atom
480
+ a name equal to '{ELEMENT_TYPE}{INDEX}'. E.g. C5 is the name for the fifth
481
+ unnamed carbon encountered.
482
+
483
+ NOTE: A new mol is returned, the original is not changed in place.
484
+
485
+ Args:
486
+ mol: Mol object.
487
+ keep_existing_names: If True, atoms that already have the atom_name property
488
+ will keep their assigned names.
489
+
490
+ Returns:
491
+ A new mol, with potentially new 'atom_name' properties.
492
+ """
493
+ mol = rd_chem.Mol(mol)
494
+
495
+ specified_atom_names = {
496
+ atom.GetProp('atom_name')
497
+ for atom in mol.GetAtoms()
498
+ if atom.HasProp('atom_name') and keep_existing_names
499
+ }
500
+
501
+ element_counts = collections.Counter()
502
+ for atom in mol.GetAtoms():
503
+ if not atom.HasProp('atom_name') or not keep_existing_names:
504
+ element = atom.GetSymbol()
505
+ while True:
506
+ element_counts[element] += 1
507
+ # Standardize names by using uppercase element type, as in CCD. Only
508
+ # effects elements with more than one letter, e.g. 'Cl' becomes 'CL'.
509
+ new_name = f'{element.upper()}{element_counts[element]}'
510
+ if new_name not in specified_atom_names:
511
+ break
512
+ atom.SetProp('atom_name', new_name)
513
+
514
+ return mol
515
+
516
+
517
+ def get_random_conformer(
518
+ mol: rd_chem.Mol,
519
+ random_seed: int,
520
+ max_iterations: int | None,
521
+ logging_name: str,
522
+ ) -> rd_chem.Conformer | None:
523
+ """Stochastic conformer search method using V3 ETK."""
524
+ params = rd_all_chem.ETKDGv3()
525
+ params.randomSeed = random_seed
526
+ if max_iterations is not None: # Override default value.
527
+ params.maxIterations = max_iterations
528
+ mol_copy = rd_chem.Mol(mol)
529
+ try:
530
+ conformer_id = rd_all_chem.EmbedMolecule(mol_copy, params)
531
+ conformer = mol_copy.GetConformer(conformer_id)
532
+ except ValueError:
533
+ logging.warning('Failed to generate conformer for: %s', logging_name)
534
+ conformer = None
535
+ return conformer
flax_model/alphafold3/data/tools/shards.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 DeepMind Technologies Limited
2
+ #
3
+ # AlphaFold 3 source code is licensed under CC BY-NC-SA 4.0. To view a copy of
4
+ # this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/
5
+ #
6
+ # To request access to the AlphaFold 3 model parameters, follow the process set
7
+ # out at https://github.com/google-deepmind/alphafold3. You may only use these
8
+ # if received directly from Google. Use is subject to terms of use available at
9
+ # https://github.com/google-deepmind/alphafold3/blob/main/WEIGHTS_TERMS_OF_USE.md
10
+
11
+ """A library to handle shards of the format file_path@NUM_SHARDS.
12
+
13
+ For instance, /path/to/file@20 will generate the following shards:
14
+
15
+ - /path/to/file-00000-of-00020
16
+ - /path/to/file-00001-of-00020
17
+ - ...
18
+ - /path/to/file-00019-of-00020
19
+
20
+ This also supports @* pattern, which will determine the number of shards based
21
+ on the filesystem content.
22
+ """
23
+
24
+ from collections.abc import Sequence
25
+ import dataclasses
26
+ import pathlib
27
+ import re
28
+
29
+
30
+ _MAX_NUM_SHARDS = 99_999
31
+ _SHARD_RE = re.compile(
32
+ r"""
33
+ ^(?P<prefix>[^\?\],\*]+)@
34
+ (?P<shards>(\d{1,5})|\*)
35
+ (?P<suffix>[\._][^\?\]@\*\/]*)?
36
+ $""",
37
+ re.X,
38
+ )
39
+
40
+
41
+ @dataclasses.dataclass(frozen=True)
42
+ class ShardSpec:
43
+ prefix: str
44
+ num_shards: int
45
+ suffix: str
46
+
47
+
48
+ def parse_shard_spec(path: str) -> ShardSpec | None:
49
+ """Returns the shard spec or None if the path is not a shard spec.
50
+
51
+ For instance, if the shard spec is '/path/to/file@20', the output will be
52
+ ('/path/to/file', 20).
53
+
54
+ Args:
55
+ path: the path to parse, e.g. /path/to/file@20 or /path/to/file@*.
56
+ """
57
+ parsed = re.fullmatch(_SHARD_RE, path)
58
+ if not parsed:
59
+ return None
60
+ prefix = parsed.group('prefix')
61
+ shards = parsed.group('shards')
62
+ suffix = parsed.group('suffix') or ''
63
+
64
+ if shards != '*':
65
+ return ShardSpec(prefix=prefix, num_shards=int(shards), suffix=suffix)
66
+ shard_slice = slice(len(prefix) + 10, len(prefix) + 15)
67
+ shard_path = pathlib.Path(f'{prefix}-00000-of-?????{suffix}')
68
+ for shard in sorted(shard_path.parent.glob(shard_path.name), reverse=True):
69
+ try:
70
+ num_shards = int(str(shard)[shard_slice])
71
+ return ShardSpec(prefix=prefix, num_shards=num_shards, suffix=suffix)
72
+ except ValueError:
73
+ continue
74
+ return None
75
+
76
+
77
+ def get_sharded_paths(shard_spec: str) -> Sequence[str] | None:
78
+ """Returns a list of file path or None if the input is not a shard spec.
79
+
80
+ Args:
81
+ shard_spec: the specifications of the shard, e.g. /path/to/file@20.
82
+ """
83
+ parsed_spec = parse_shard_spec(shard_spec)
84
+ if not parsed_spec:
85
+ return None
86
+
87
+ prefix = parsed_spec.prefix
88
+ num_shards = parsed_spec.num_shards
89
+ suffix = parsed_spec.suffix
90
+ if num_shards > _MAX_NUM_SHARDS:
91
+ raise ValueError(f'Shard count for {shard_spec} exceeds {_MAX_NUM_SHARDS}')
92
+ return [
93
+ f'{prefix}-{i:05d}-of-{num_shards:05d}{suffix}' for i in range(num_shards)
94
+ ]
flax_model/alphafold3/data/tools/subprocess_utils.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Helper functions for launching external tools."""
4
+
5
+ from collections.abc import Sequence
6
+ import os
7
+ import subprocess
8
+ import time
9
+ from typing import Any
10
+
11
+ from absl import logging
12
+
13
+
14
+ def create_query_fasta_file(sequence: str, path: str, linewidth: int = 80):
15
+ """Creates a fasta file with the sequence with line width limit."""
16
+ with open(path, 'w') as f:
17
+ f.write('>query\n')
18
+
19
+ i = 0
20
+ while i < len(sequence):
21
+ f.write(f'{sequence[i:(i + linewidth)]}\n')
22
+ i += linewidth
23
+
24
+
25
+ def check_binary_exists(path: str, name: str) -> None:
26
+ """Checks if a binary exists on the given path and raises otherwise."""
27
+ if not os.path.exists(path):
28
+ raise RuntimeError(f'{name} binary not found at {path}')
29
+
30
+
31
+ def jackhmmer_seq_limit_supported(jackhmmer_path: str) -> bool:
32
+ """Checks if Jackhmmer supports the --seq-limit flag."""
33
+ try:
34
+ subprocess.run(
35
+ [jackhmmer_path, '-h', '--seq_limit', '1'],
36
+ stdout=subprocess.DEVNULL,
37
+ stderr=subprocess.DEVNULL,
38
+ check=True,
39
+ )
40
+ except subprocess.CalledProcessError:
41
+ return False
42
+ return True
43
+
44
+
45
+ def run(
46
+ cmd: Sequence[str],
47
+ cmd_name: str,
48
+ log_on_process_error: bool = False,
49
+ log_stderr: bool = False,
50
+ log_stdout: bool = False,
51
+ max_out_streams_len: int | None = 500_000,
52
+ **run_kwargs,
53
+ ) -> subprocess.CompletedProcess[Any]:
54
+ """Launches a subprocess, times it, and checks for errors.
55
+
56
+ Args:
57
+ cmd: Command to launch.
58
+ cmd_name: Human-readable command name to be used in logs.
59
+ log_on_process_error: Whether to use `logging.error` to log the process'
60
+ stderr on failure.
61
+ log_stderr: Whether to log the stderr of the command.
62
+ log_stdout: Whether to log the stdout of the command.
63
+ max_out_streams_len: Max length of prefix of stdout and stderr included in
64
+ the exception message. Set to `None` to disable truncation.
65
+ **run_kwargs: Any other kwargs for `subprocess.run`.
66
+
67
+ Returns:
68
+ The completed process object.
69
+
70
+ Raises:
71
+ RuntimeError: if the process completes with a non-zero return code.
72
+ """
73
+
74
+ logging.info('Launching subprocess "%s"', ' '.join(cmd))
75
+
76
+ start_time = time.time()
77
+ try:
78
+ completed_process = subprocess.run(
79
+ cmd,
80
+ check=True,
81
+ stderr=subprocess.PIPE,
82
+ stdout=subprocess.PIPE,
83
+ text=True,
84
+ **run_kwargs,
85
+ )
86
+ except subprocess.CalledProcessError as e:
87
+ if log_on_process_error:
88
+ # Logs have a 15k character limit, so log the error line by line.
89
+ logging.error('%s failed. %s stderr begin:', cmd_name, cmd_name)
90
+ for error_line in e.stderr.splitlines():
91
+ if stripped_error_line := error_line.strip():
92
+ logging.error(stripped_error_line)
93
+ logging.error('%s stderr end.', cmd_name)
94
+
95
+ error_msg = (
96
+ f'{cmd_name} failed'
97
+ f'\nstdout:\n{e.stdout[:max_out_streams_len]}\n'
98
+ f'\nstderr:\n{e.stderr[:max_out_streams_len]}'
99
+ )
100
+ raise RuntimeError(error_msg) from e
101
+ end_time = time.time()
102
+
103
+ logging.info('Finished %s in %.3f seconds', cmd_name, end_time - start_time)
104
+ stdout, stderr = completed_process.stdout, completed_process.stderr
105
+
106
+ if log_stdout and stdout:
107
+ logging.info('%s stdout:\n%s', cmd_name, stdout)
108
+
109
+ if log_stderr and stderr:
110
+ logging.info('%s stderr:\n%s', cmd_name, stderr)
111
+
112
+ return completed_process
flax_model/alphafold3/jax/__init__.py ADDED
File without changes
flax_model/alphafold3/jax/attention/__init__.py ADDED
File without changes
flax_model/alphafold3/jax/attention/attention_base.py ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Common types and utilities for attention kernels."""
4
+
5
+ import abc
6
+ import dataclasses
7
+ import enum
8
+ import functools
9
+ import math
10
+ from typing import Any, Self
11
+
12
+ from flax_model.alphafold3.jax.common import array_view
13
+ from flax_model.alphafold3.jax.common import precision as precision_lib
14
+ import jax
15
+ import jax.numpy as jnp
16
+ from jax.typing import DTypeLike # pylint: disable=g-importing-member
17
+ import jaxtyping
18
+ from jaxtyping import Array, Bool, Float, Int # pylint: disable=g-multiple-import,g-importing-member
19
+ import typeguard
20
+
21
+
22
+ class AUTO: # Used as a sentinel value.
23
+ pass
24
+
25
+
26
+ DotPrecisionLike = jax.lax.Precision | precision_lib.DotPrecision
27
+
28
+
29
+ @jax.tree_util.register_pytree_node_class
30
+ @dataclasses.dataclass(frozen=True)
31
+ class Mask:
32
+ """An attention mask.
33
+
34
+ `k_start` (inclusive) and `k_end` (exclusive) define range of enabled
35
+ k-sequence values for each row of logits.
36
+
37
+ For example, a local attention mask could be defined as follows:
38
+ ```
39
+ seq_len_q = seq_len_k = 4
40
+ window_size = 2
41
+ k_start = jnp.maximum(0, jnp.arange(seq_len_q) + 1 - window_size)
42
+ mask = Mask(k_start=k_start, is_causal=True)
43
+ assert mask.as_array(seq_len_q, seq_len_k) == jnp.array(
44
+ [[1, 0, 0, 0],
45
+ [1, 1, 0, 0],
46
+ [0, 1, 1, 0],
47
+ [0, 0, 1, 1]], dtype=bool)
48
+ ```
49
+ Or equivalently (but less efficiently):
50
+ ```
51
+ k_end = jnp.arange(seq_len_q) + 1
52
+ k_start = jnp.maximum(0, k_end - window_size)
53
+ mask = Mask(k_start=k_start, k_end=k_end)
54
+ assert mask.as_array(seq_len_q, seq_len_k) == jnp.array(
55
+ [[1, 0, 0, 0],
56
+ [1, 1, 0, 0],
57
+ [0, 1, 1, 0],
58
+ [0, 0, 1, 1]], dtype=bool)
59
+ ```
60
+
61
+ A mask for two independent causal sequences could be defined as follows:
62
+ ```
63
+ k_start = jnp.array([0, 0, 2, 2])
64
+ mask = Mask(k_start=k_start, is_causal=True)
65
+ assert mask.as_array(seq_len_q, seq_len_k) == jnp.array(
66
+ [[1, 0, 0, 0],
67
+ [1, 1, 0, 0],
68
+ [0, 0, 1, 0],
69
+ [0, 0, 1, 1]], dtype=bool)
70
+ ```
71
+ """
72
+
73
+ bool_mask: Bool[Array, "*#B #T #t"] | None = None
74
+ _: dataclasses.KW_ONLY
75
+ q_start: Int[Array, "*#B #t"] | None = None
76
+ q_end: Int[Array, "*#B #t"] | None = None
77
+ k_start: Int[Array, "*#B #T"] | None = None
78
+ k_end: Int[Array, "*#B #T"] | None = None
79
+ is_causal: bool = False
80
+
81
+ def tree_flatten(self):
82
+ return (
83
+ self.bool_mask,
84
+ self.q_start,
85
+ self.q_end,
86
+ self.k_start,
87
+ self.k_end,
88
+ ), (self.is_causal,)
89
+
90
+ @classmethod
91
+ def tree_unflatten(cls, aux, children) -> Self:
92
+ (is_causal,) = aux
93
+ bool_mask, q_start, q_end, k_start, k_end = children
94
+ return cls(
95
+ bool_mask,
96
+ q_start=q_start,
97
+ q_end=q_end,
98
+ k_start=k_start,
99
+ k_end=k_end,
100
+ is_causal=is_causal,
101
+ )
102
+
103
+ def as_array(
104
+ self,
105
+ q_len_or_indices: int | Int[Array, "*#B T"],
106
+ k_len_or_indices: int | Int[Array, "*#B t"],
107
+ ) -> Bool[Array, "*#B #T #t"] | None:
108
+ """Returns the mask as a boolean array."""
109
+ if isinstance(q_len_or_indices, int):
110
+ q_indices = jnp.arange(q_len_or_indices)
111
+ else:
112
+ q_indices = q_len_or_indices
113
+
114
+ if isinstance(k_len_or_indices, int):
115
+ k_indices = jnp.arange(k_len_or_indices)
116
+ else:
117
+ k_indices = k_len_or_indices
118
+
119
+ q_indices = q_indices[..., None]
120
+ k_indices = k_indices[..., None, :]
121
+
122
+ mask = []
123
+ if self.bool_mask is not None:
124
+ mask.append(self.bool_mask)
125
+ # Check `bool_mask` shape is compatible with `{q,kv}_indices`.
126
+ _ = jnp.broadcast_shapes(
127
+ q_indices.shape, k_indices.shape, self.bool_mask.shape
128
+ )
129
+
130
+ if self.q_start is not None:
131
+ mask.append(q_indices >= self.q_start[..., None, :])
132
+
133
+ if self.q_end is not None:
134
+ mask.append(q_indices < self.q_end[..., None, :])
135
+
136
+ if self.k_start is not None:
137
+ mask.append(k_indices >= self.k_start[..., None])
138
+
139
+ if self.k_end is not None:
140
+ mask.append(k_indices < self.k_end[..., None])
141
+
142
+ if self.is_causal:
143
+ mask.append(q_indices >= k_indices)
144
+
145
+ logical_and = functools.partial(functools.reduce, jnp.logical_and)
146
+ return jax.lax.broadcast_to_rank(logical_and(mask), 3) if mask else None
147
+
148
+ def take(self, *attrs: str) -> tuple[Any, ...]:
149
+ """Returns a mask with attrs removed and the removed attrs."""
150
+ default_mask = type(self)()
151
+ replacements = {attr: getattr(default_mask, attr) for attr in attrs}
152
+ values = (getattr(self, attr) for attr in attrs)
153
+ return dataclasses.replace(self, **replacements), *values
154
+
155
+ def __and__(self, other: "Bool[Array, '*#B #T #t'] | Mask") -> "Mask": # pylint: disable=g-inconsistent-quotes
156
+ """Returns the intersection of two masks."""
157
+ if not isinstance(other, Mask):
158
+ other = Mask(other)
159
+
160
+ def combine(op):
161
+ return lambda a, b: b if a is None else a if b is None else op(a, b)
162
+
163
+ return Mask(
164
+ bool_mask=combine(jnp.logical_and)(self.bool_mask, other.bool_mask),
165
+ q_end=combine(jnp.minimum)(self.q_end, other.q_end),
166
+ k_start=combine(jnp.maximum)(self.k_start, other.k_start),
167
+ k_end=combine(jnp.minimum)(self.k_end, other.k_end),
168
+ is_causal=self.is_causal or other.is_causal,
169
+ )
170
+
171
+
172
+ CAUSAL_MASK = Mask(is_causal=True)
173
+
174
+
175
+ SoftmaxResidual = (
176
+ tuple[Float[Array, "*B H T"], Float[Array, "*B H T"]]
177
+ | Float[Array, "*B H T"]
178
+ )
179
+
180
+
181
+ @enum.unique
182
+ class SoftmaxResidualMode(enum.Enum):
183
+ """The mode of storing softmax residuals for the backwards pass.
184
+
185
+ The stable softmax calculation performs two reductions calculating:
186
+ - the maximum input value (`x_max`),
187
+ - the sum of exponentiated values (`denom`).
188
+
189
+ We can store these values as residuals to avoid the need to recompute them
190
+ in the backwards pass.
191
+
192
+ It is also possible to combine the two residuals into a single residual,
193
+ `res = x_max + log(denom)`, as `exp(x - res) === exp(x - x_max - log(denom))
194
+ === exp(x - x_max) / denom`. Combining the residuals reduces the memory usage
195
+ of the residuals, but will reduce the accuracy of the backwards pass if
196
+ `abs(x_max) >> log(denom)`.
197
+ """
198
+
199
+ SEPARATE = "separate"
200
+ COMBINED = "combined"
201
+
202
+ def conform(self, aux: SoftmaxResidual) -> SoftmaxResidual | None:
203
+ match self, aux:
204
+ case None, _:
205
+ return None
206
+ case SoftmaxResidualMode.SEPARATE, (_, _):
207
+ return aux
208
+ case SoftmaxResidualMode.SEPARATE, _: # pytype: disable=redundant-match # b/300135240
209
+ raise ValueError("`aux` has been combined.")
210
+ case SoftmaxResidualMode.COMBINED, (x_max, denom):
211
+ return x_max + jnp.log(denom)
212
+ case SoftmaxResidualMode.COMBINED, _: # pytype: disable=redundant-match # b/300135240
213
+ return aux
214
+
215
+
216
+ class DotProductAttention(abc.ABC):
217
+ """Dot product attention function."""
218
+
219
+ @jaxtyping.jaxtyped(typechecker=typeguard.typechecked)
220
+ def __call__(
221
+ self,
222
+ query: Float[Array | array_view.ArrayView, "*B T H D"],
223
+ key: Float[Array | array_view.ArrayView, "*B t h D"],
224
+ value: Float[Array | array_view.ArrayView, "*B t h D"],
225
+ *,
226
+ precision: (
227
+ DotPrecisionLike | tuple[DotPrecisionLike, DotPrecisionLike]
228
+ ) = jax.lax.Precision.DEFAULT,
229
+ logits_dtype: DTypeLike | type[AUTO] = AUTO,
230
+ bias: Float[Array, "*#B #H #T #t"] | None = None,
231
+ mask: Bool[Array, "*#B #H #T #t"] | Mask | None = None,
232
+ q_indices: Int[Array, "*#B #H T"] | None = None,
233
+ k_indices: Int[Array, "*#B #H t"] | None = None,
234
+ ) -> Float[Array, "*B T H D"]:
235
+ """Performs scaled dot-product attention.
236
+
237
+ Scaled dot-product attention from "Attention is all you need"
238
+ https://arxiv.org/abs/1706.03762.
239
+
240
+ Computes self- or cross-attention. The following is computed:
241
+ softmax(qk_scale * query @ key^T + bias) @ value.
242
+
243
+ Supports both multi-head and multi-query attention
244
+ (https://arxiv.org/abs/1911.02150).
245
+
246
+ Arguments:
247
+ query: Query array of shape `[batch, seq_len_q, num_heads_q, head_dim]`.
248
+ It must be a multiple of num_heads_kv.
249
+ Here's an example of how q/kv heads are interleaved:
250
+ For 8 key/value heads and 4 query heads:
251
+ - key/value heads [0, 1] see query head 0
252
+ - key/value heads [2, 3] see query head 1
253
+ - key/value heads [4, 5] see query head 2
254
+ key: Key array of shape `[batch, seq_len_kv, num_heads_kv, head_dim]`. It
255
+ must be divisible by num_heads_q.
256
+ value: Value array of shape `[batch, seq_len_kv, num_heads_kv, head_dim]`.
257
+ precision: The precision for the dot products. Either a tuple `(
258
+ query_key_dot_precision, weights_value_dot_precision)` or a single
259
+ precision applied to both dot products.
260
+ logits_dtype: Data type for attention logits (`query @ key^T`). If `AUTO`
261
+ is passed (the default), the accumulator type from the `query @ key^T`
262
+ dot product will be used.
263
+ bias: Optional bias array, broadcastable to shape `[batch, num_heads,
264
+ seq_len_q, seq_len_kv]`.
265
+ mask: Optional boolean mask, broadcastable to `[batch, num_heads,
266
+ seq_len_q, seq_len_kv]`. Attention weights are masked out if the
267
+ corresponding mask value is `False`.
268
+ q_indices: Optional indices for each token in query sequence.
269
+ k_indices: Optional indices for each token in key/value sequence.
270
+
271
+ Returns:
272
+ An array with the same shape as `query`.
273
+ """ # fmt: skip
274
+ return self.fwd(
275
+ query,
276
+ key,
277
+ value,
278
+ precision=precision,
279
+ logits_dtype=logits_dtype,
280
+ bias=bias,
281
+ mask=mask,
282
+ q_indices=q_indices,
283
+ k_indices=k_indices,
284
+ )
285
+
286
+ @jaxtyping.jaxtyped(typechecker=typeguard.typechecked)
287
+ def fwd(
288
+ self,
289
+ query: Float[Array | array_view.ArrayView, "*B T H D"],
290
+ key: Float[Array | array_view.ArrayView, "*B t h D"],
291
+ value: Float[Array | array_view.ArrayView, "*B t h D"],
292
+ *,
293
+ precision: (
294
+ DotPrecisionLike | tuple[DotPrecisionLike, DotPrecisionLike]
295
+ ) = jax.lax.Precision.DEFAULT,
296
+ logits_dtype: DTypeLike | type[AUTO] = AUTO,
297
+ bias: Float[Array, "*#B #H #T #t"] | None = None,
298
+ mask: Bool[Array, "*#B #H #T #t"] | Mask | None = None,
299
+ q_indices: Int[Array, "*#B #H T"] | None = None,
300
+ k_indices: Int[Array, "*#B #H t"] | None = None,
301
+ ) -> Float[Array, "*B T H D"]:
302
+ """Performs attention."""
303
+ if not isinstance(precision, tuple):
304
+ precision = (precision, precision)
305
+
306
+ q_k_dot_precision, weights_v_dot_precision = precision
307
+
308
+ if not isinstance(q_k_dot_precision, precision_lib.DotPrecision):
309
+ q_k_dot_precision = precision_lib.get_equivalent_dot_precision(
310
+ query.dtype, key.dtype, q_k_dot_precision
311
+ )
312
+
313
+ if not isinstance(weights_v_dot_precision, precision_lib.DotPrecision):
314
+ weights_v_dot_precision = precision_lib.get_equivalent_dot_precision(
315
+ value.dtype, value.dtype, weights_v_dot_precision
316
+ )
317
+
318
+ if logits_dtype is AUTO:
319
+ logits_dtype = q_k_dot_precision.accumulator_dtype
320
+
321
+ if not isinstance(mask, Mask):
322
+ mask = Mask(mask)
323
+
324
+ return self._fwd(
325
+ array_view.as_array_view(query),
326
+ array_view.as_array_view(key),
327
+ array_view.as_array_view(value),
328
+ q_k_dot_precision=q_k_dot_precision,
329
+ logits_dtype=jnp.dtype(logits_dtype),
330
+ logits_scale=1 / math.sqrt(query.shape[-1]),
331
+ bias=bias,
332
+ mask=mask,
333
+ weights_v_dot_precision=weights_v_dot_precision,
334
+ q_indices=q_indices,
335
+ k_indices=k_indices,
336
+ )
337
+
338
+ @abc.abstractmethod
339
+ def _fwd(
340
+ self,
341
+ q: Float[array_view.ArrayView, "*B T H D"],
342
+ k: Float[array_view.ArrayView, "*B t h D"],
343
+ v: Float[array_view.ArrayView, "*B t h D"],
344
+ *,
345
+ q_k_dot_precision: precision_lib.DotPrecision,
346
+ logits_dtype: jnp.dtype,
347
+ logits_scale: float,
348
+ bias: Float[Array, "*#B #H #T #t"] | None,
349
+ mask: Mask | None,
350
+ weights_v_dot_precision: precision_lib.DotPrecision,
351
+ q_indices: Int[Array, "*#B #H T"] | None = None,
352
+ k_indices: Int[Array, "*#B #H t"] | None = None,
353
+ ) -> Float[Array, "*B T H D"]:
354
+ """Performs attention."""
355
+ ...
flax_model/alphafold3/jax/attention/flash_attention.py ADDED
@@ -0,0 +1,697 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Triton FlashAttention implementation."""
4
+
5
+ import dataclasses
6
+ import functools
7
+
8
+ from flax_model.alphafold3.jax.attention import attention_base as base
9
+ from flax_model.alphafold3.jax.common import array_view
10
+ from flax_model.alphafold3.jax.common import precision as precision_lib
11
+ from flax_model.alphafold3.jax.common import triton_utils
12
+ import jax
13
+ import jax.numpy as jnp
14
+ import jax_triton as jt
15
+ import jaxtyping
16
+ from jaxtyping import Array, Bool, Float, Int # pylint: disable=g-multiple-import,g-importing-member
17
+ import triton
18
+ import triton.language as tl
19
+ import typeguard
20
+
21
+
22
+ @triton.jit
23
+ def _fwd_kernel_inner(
24
+ start_loop,
25
+ end_loop,
26
+ q,
27
+ span_q,
28
+ k_block_ptr,
29
+ v_block_ptr,
30
+ bias_block_ptr,
31
+ mask_block_ptr,
32
+ k_start,
33
+ k_end,
34
+ seq_len_k,
35
+ acc,
36
+ m_i,
37
+ l_i,
38
+ bias_advance: tl.constexpr,
39
+ mask_advance: tl.constexpr,
40
+ is_causal: tl.constexpr,
41
+ use_attention_mask: tl.constexpr,
42
+ use_k_start: tl.constexpr,
43
+ use_k_end: tl.constexpr,
44
+ use_bias: tl.constexpr,
45
+ block_k: tl.constexpr,
46
+ use_mask_k: tl.constexpr,
47
+ k_boundary_check: tl.constexpr,
48
+ v_boundary_check: tl.constexpr,
49
+ dot_fn_qk: tl.constexpr,
50
+ dot_fn_kv: tl.constexpr,
51
+ ):
52
+ """Triton MHA forward kernel's inner loop."""
53
+
54
+ for start_k in range(start_loop, end_loop, block_k):
55
+ start_k = tl.multiple_of(start_k, block_k)
56
+ span_k = start_k + tl.arange(0, block_k)
57
+
58
+ k = tl.load(
59
+ k_block_ptr,
60
+ boundary_check=k_boundary_check,
61
+ padding_option="zero" if len(k_boundary_check.value) else "",
62
+ )
63
+ v = tl.load(
64
+ v_block_ptr,
65
+ boundary_check=v_boundary_check,
66
+ padding_option="zero" if len(v_boundary_check.value) else "",
67
+ )
68
+
69
+ if use_bias:
70
+ bias = tl.load(bias_block_ptr)
71
+
72
+ qk = dot_fn_qk(q.to(k.dtype), k) # [block_q, block_k]
73
+
74
+ if use_bias:
75
+ # Prevent dot accumulating into the bias tensor. It appears that Triton
76
+ # doesn't pipeline the bias load as it does the `k` load, so the bias load
77
+ # blocks the matmul if the add is merged.
78
+ qk = qk.to(tl.uint32, bitcast=True) & 0xFFFFFFFF
79
+ qk = qk.to(tl.float32, bitcast=True)
80
+ qk += bias
81
+
82
+ if use_attention_mask | use_k_start | use_k_end:
83
+ mask_value = float(jnp.finfo(jnp.float32).min)
84
+
85
+ if use_attention_mask:
86
+ mask = tl.load(mask_block_ptr)
87
+ qk = tl.where(mask, qk, mask_value)
88
+
89
+ if use_k_start:
90
+ # This check is there to work around a triton compiler bug, but it
91
+ # shouldn't be strictly needed.
92
+ if tl.sum(k_start) != 0:
93
+ qk = tl.where(k_start[:, None] <= span_k[None, :], qk, mask_value)
94
+ if is_causal:
95
+ qk = tl.where(span_q[:, None] >= span_k[None, :], qk, float("-inf"))
96
+ elif use_k_end:
97
+ # When called with k_end and is_causal=True, the causal mask gets folded
98
+ # into k_end and is_causal is set to False.
99
+ qk = tl.where(k_end[:, None] > span_k[None, :], qk, mask_value)
100
+
101
+ if use_mask_k:
102
+ qk = tl.where((span_k < seq_len_k)[None, :], qk, float("-inf"))
103
+
104
+ m_ij = tl.maximum(m_i, tl.max(qk, axis=1)) # Shape [block_q].
105
+ p = tl.exp(qk - m_ij[:, None]) # Shape [block_q, block_k].
106
+ alpha = tl.exp(m_i - m_ij)
107
+ m_i = m_ij
108
+ acc *= alpha[:, None]
109
+ l_i *= alpha
110
+ l_i += tl.sum(p, axis=1)
111
+
112
+ # Add the new block of attention weights.
113
+ acc += dot_fn_kv(p.to(v.dtype), v)
114
+
115
+ k_block_ptr = tl.advance(k_block_ptr, (0, block_k))
116
+ v_block_ptr = tl.advance(v_block_ptr, (block_k, 0))
117
+ bias_block_ptr = tl.advance(bias_block_ptr, bias_advance.value)
118
+ mask_block_ptr = tl.advance(mask_block_ptr, mask_advance.value)
119
+
120
+ return (
121
+ k_block_ptr,
122
+ v_block_ptr,
123
+ bias_block_ptr,
124
+ mask_block_ptr,
125
+ acc,
126
+ m_i,
127
+ l_i,
128
+ )
129
+
130
+
131
+ # Based on Algorithm 1 of https://arxiv.org/abs/2205.14135.
132
+ # Inspired by the official Triton tutorial implementation
133
+ # https://triton-lang.org/main/getting-started/tutorials/06-fused-attention.html
134
+ @triton.jit
135
+ def _fwd_kernel(
136
+ # Input arrays.
137
+ q_ptr,
138
+ k_ptr,
139
+ v_ptr,
140
+ bias_ptr,
141
+ mask_ptr,
142
+ k_start_ptr,
143
+ k_end_ptr,
144
+ # Scalar inputs.
145
+ q_offset,
146
+ k_offset,
147
+ v_offset,
148
+ q_stride_b,
149
+ q_stride_s,
150
+ q_stride_h,
151
+ q_stride_d,
152
+ k_stride_b,
153
+ k_stride_s,
154
+ k_stride_h,
155
+ k_stride_d,
156
+ v_stride_b,
157
+ v_stride_s,
158
+ v_stride_h,
159
+ v_stride_d,
160
+ bias_stride_b,
161
+ bias_stride_h,
162
+ bias_stride_sq,
163
+ bias_stride_sk,
164
+ mask_stride_b,
165
+ mask_stride_h,
166
+ mask_stride_sq,
167
+ mask_stride_sk,
168
+ k_start_stride_b,
169
+ k_start_stride_h,
170
+ k_start_stride_sq,
171
+ k_end_stride_b,
172
+ k_end_stride_h,
173
+ k_end_stride_sq,
174
+ o_stride_b,
175
+ o_stride_s,
176
+ o_stride_h,
177
+ o_stride_d,
178
+ num_heads_q,
179
+ num_heads_k,
180
+ seq_len_q,
181
+ seq_len_k,
182
+ # Output arrays.
183
+ o_ptr,
184
+ # Compile-time constants.
185
+ is_causal: tl.constexpr,
186
+ use_attention_mask: tl.constexpr,
187
+ use_k_start: tl.constexpr,
188
+ use_k_end: tl.constexpr,
189
+ use_bias: tl.constexpr,
190
+ sm_scale: tl.constexpr,
191
+ block_q: tl.constexpr,
192
+ block_k: tl.constexpr,
193
+ head_dim: tl.constexpr,
194
+ use_mask_q: tl.constexpr,
195
+ use_mask_k: tl.constexpr,
196
+ bias_bcast_sq: tl.constexpr,
197
+ mask_bcast_sq: tl.constexpr,
198
+ dot_fn_qk: tl.constexpr,
199
+ dot_fn_kv: tl.constexpr,
200
+ ):
201
+ """Triton MHA forward kernel."""
202
+ # pytype: disable=annotation-type-mismatch,unsupported-operands
203
+ #block_d: tl.constexpr = jt.utils.next_power_of_2(head_dim.value)
204
+ a: tl.constexpr = jt.utils.next_power_of_2(head_dim.value)
205
+ block_d: tl.constexpr = a if a>16 else 32
206
+
207
+ # Each thread block processes one batch element (b) and one head (h).
208
+ start_q = tl.program_id(1) * block_q
209
+ off_h = tl.program_id(0) # int in [0, num_heads_o).
210
+ off_b = tl.program_id(2) # int in [0, batch_size)
211
+
212
+ off_h_k = off_h // (num_heads_q // num_heads_k)
213
+
214
+ q_ptr += off_h * q_stride_h + off_b * q_stride_b + q_offset
215
+ k_ptr += off_h_k * k_stride_h + off_b * k_stride_b + k_offset
216
+ v_ptr += off_h_k * v_stride_h + off_b * v_stride_b + v_offset
217
+ o_ptr += off_h * o_stride_h + off_b * o_stride_b
218
+
219
+ if use_bias:
220
+ bias_ptr += off_b * bias_stride_b + off_h * bias_stride_h
221
+ if use_attention_mask:
222
+ mask_ptr += off_b * mask_stride_b + off_h * mask_stride_h
223
+ if use_k_start:
224
+ k_start_ptr += off_b * k_start_stride_b + off_h * k_start_stride_h
225
+ if use_k_end:
226
+ k_end_ptr += off_b * k_end_stride_b + off_h * k_end_stride_h
227
+
228
+ q_block_ptr = tl.make_block_ptr(
229
+ q_ptr,
230
+ shape=(seq_len_q, head_dim),
231
+ strides=(q_stride_s, q_stride_d),
232
+ offsets=(start_q, 0),
233
+ block_shape=(block_q, block_d),
234
+ order=(1, 0),
235
+ )
236
+ k_block_ptr = tl.make_block_ptr(
237
+ k_ptr,
238
+ shape=(head_dim, seq_len_k),
239
+ strides=(k_stride_d, k_stride_s),
240
+ offsets=(0, 0),
241
+ block_shape=(block_d, block_k),
242
+ order=(0, 1),
243
+ )
244
+ v_block_ptr = tl.make_block_ptr(
245
+ v_ptr,
246
+ shape=(seq_len_k, head_dim),
247
+ strides=(v_stride_s, v_stride_d),
248
+ offsets=(0, 0),
249
+ block_shape=(block_k, block_d),
250
+ order=(1, 0),
251
+ )
252
+
253
+ q_boundary_check0: tl.constexpr = (0,) if use_mask_q else ()
254
+ q_boundary_check1: tl.constexpr = (1,) if head_dim != block_d else ()
255
+ q_boundary_check: tl.constexpr = q_boundary_check0 + q_boundary_check1
256
+ q_padding_option: tl.constexpr = "zero" if len(q_boundary_check.value) else ""
257
+ k_boundary_check: tl.constexpr = (0,) if head_dim != block_d else ()
258
+ v_boundary_check: tl.constexpr = (0,) if use_mask_k else ()
259
+
260
+ # If broadcasting in a given dim, use a 1D block (observed to be faster).
261
+ bias_start_dim: tl.constexpr = 1 if bias_bcast_sq else 0
262
+ bias_block_ptr = tl.make_block_ptr(
263
+ bias_ptr,
264
+ shape=(seq_len_q, seq_len_k)[bias_start_dim:],
265
+ strides=(bias_stride_sq, bias_stride_sk)[bias_start_dim:],
266
+ offsets=(start_q, 0)[bias_start_dim:],
267
+ block_shape=(block_q, block_k)[bias_start_dim:],
268
+ order=(1, 0)[bias_start_dim:],
269
+ )
270
+ bias_advance: tl.constexpr = (0, block_k)[bias_start_dim:]
271
+
272
+ mask_start_dim: tl.constexpr = 1 if mask_bcast_sq else 0
273
+ mask_block_ptr = tl.make_block_ptr(
274
+ mask_ptr,
275
+ shape=(seq_len_q, seq_len_k)[mask_start_dim:],
276
+ strides=(mask_stride_sq, mask_stride_sk)[mask_start_dim:],
277
+ offsets=(start_q, 0)[mask_start_dim:],
278
+ block_shape=(block_q, block_k)[mask_start_dim:],
279
+ order=(1, 0)[mask_start_dim:],
280
+ )
281
+ mask_advance: tl.constexpr = (0, block_k)[mask_start_dim:]
282
+
283
+ k_start_block_ptr = tl.make_block_ptr(
284
+ k_start_ptr,
285
+ shape=(seq_len_q,),
286
+ strides=(k_start_stride_sq,),
287
+ offsets=(start_q,),
288
+ block_shape=(block_q,),
289
+ order=(0,),
290
+ )
291
+ k_end_block_ptr = tl.make_block_ptr(
292
+ k_end_ptr,
293
+ shape=(seq_len_q,),
294
+ strides=(k_end_stride_sq,),
295
+ offsets=(start_q,),
296
+ block_shape=(block_q,),
297
+ order=(0,),
298
+ )
299
+ # pytype: enable=annotation-type-mismatch,unsupported-operands
300
+
301
+ # Each thread block processes a block of block_q queries.
302
+ span_q = start_q + tl.arange(0, block_q)
303
+
304
+ # m_i and l_i (see FlashAttention paper) are updated during the k,v loop.
305
+ m_i = tl.full([block_q], float("-inf"), dtype=tl.float32)
306
+ l_i = tl.zeros([block_q], dtype=tl.float32)
307
+ # acc is the buffer where we accumulate the output on sram.
308
+ acc = tl.zeros([block_q, block_d], dtype=tl.float32)
309
+
310
+ # Load q: it will stay in smem throughout. Indices form a matrix because we
311
+ # read, compute, and write all in 2d chunks. 1 element ~= 1 CUDA thread index.
312
+ q = tl.load(
313
+ q_block_ptr,
314
+ boundary_check=q_boundary_check,
315
+ padding_option=q_padding_option,
316
+ )
317
+ q *= sm_scale
318
+
319
+ # In FlashAttention algorithm 1 there are 2 loops: slow over tiles of kv (size
320
+ # (Bc == block_k here), and fast over blocks of q (size Br == block_q here).
321
+ # Here we only loop over blocks of kv to process entire seq_len, the loop over
322
+ # blocks of q is carried out by the grid.
323
+ k_start = None
324
+ if use_k_start:
325
+ k_start = tl.load(k_start_block_ptr)
326
+ start_loop = tl.maximum(tl.min(k_start), 0)
327
+ blocks_to_skip = start_loop // block_k
328
+ start_loop = block_k * blocks_to_skip # Floor to multiple of block_k.
329
+ for _ in range(blocks_to_skip):
330
+ # Advance all block pointers to the first valid block.
331
+ k_block_ptr = tl.advance(k_block_ptr, (0, block_k))
332
+ v_block_ptr = tl.advance(v_block_ptr, (block_k, 0))
333
+ bias_block_ptr = tl.advance(bias_block_ptr, bias_advance.value)
334
+ mask_block_ptr = tl.advance(mask_block_ptr, mask_advance.value)
335
+ else:
336
+ start_loop = 0
337
+
338
+ k_end = None
339
+ if is_causal:
340
+ end_loop = tl.minimum((start_q // block_k) * block_k, seq_len_k)
341
+ elif use_k_end:
342
+ k_end = tl.load(k_end_block_ptr)
343
+ end_loop = tl.minimum(tl.max(k_end), seq_len_k)
344
+ else:
345
+ end_loop = seq_len_k
346
+
347
+ (
348
+ k_block_ptr,
349
+ v_block_ptr,
350
+ bias_block_ptr,
351
+ mask_block_ptr,
352
+ acc,
353
+ m_i,
354
+ l_i,
355
+ ) = _fwd_kernel_inner(
356
+ start_loop,
357
+ end_loop,
358
+ q,
359
+ span_q,
360
+ k_block_ptr,
361
+ v_block_ptr,
362
+ bias_block_ptr,
363
+ mask_block_ptr,
364
+ k_start,
365
+ k_end,
366
+ seq_len_k,
367
+ acc,
368
+ m_i,
369
+ l_i,
370
+ bias_advance,
371
+ mask_advance,
372
+ False, # is_causal
373
+ use_attention_mask,
374
+ use_k_start,
375
+ use_k_end,
376
+ use_bias,
377
+ block_k,
378
+ use_mask_k,
379
+ k_boundary_check,
380
+ v_boundary_check,
381
+ dot_fn_qk,
382
+ dot_fn_kv,
383
+ )
384
+
385
+ if is_causal:
386
+ tl.debug_barrier() # Help compiler schedule loops independently.
387
+ start_loop, end_loop = end_loop, tl.minimum(end_loop + block_k, seq_len_k)
388
+
389
+ _, _, _, _, acc, _, l_i = _fwd_kernel_inner(
390
+ start_loop,
391
+ end_loop,
392
+ q,
393
+ span_q,
394
+ k_block_ptr,
395
+ v_block_ptr,
396
+ bias_block_ptr,
397
+ mask_block_ptr,
398
+ k_start,
399
+ k_end,
400
+ seq_len_k,
401
+ acc,
402
+ m_i,
403
+ l_i,
404
+ bias_advance,
405
+ mask_advance,
406
+ True, # is_causal
407
+ use_attention_mask,
408
+ use_k_start,
409
+ use_k_end,
410
+ use_bias,
411
+ block_k,
412
+ use_mask_k,
413
+ k_boundary_check,
414
+ v_boundary_check,
415
+ dot_fn_qk,
416
+ dot_fn_kv,
417
+ )
418
+
419
+ # It is possible that every value in a row was masked to f32 min or that the
420
+ # main loop has been completely optimised out, and that `l_i` is `0` for that
421
+ # row. Add epsilon value to avoid NaNs from `0 / 0`.
422
+ l_i += float(jnp.finfo(jnp.float32).tiny)
423
+
424
+ acc /= l_i[:, None]
425
+
426
+ # Write output to dram.
427
+ o_block_ptr = tl.make_block_ptr(
428
+ o_ptr,
429
+ shape=(seq_len_q, head_dim),
430
+ strides=(o_stride_s, o_stride_d),
431
+ offsets=(start_q, 0),
432
+ block_shape=(block_q, block_d),
433
+ order=(1, 0),
434
+ )
435
+ acc = acc.to(o_ptr.dtype.element_ty)
436
+ tl.store(o_block_ptr, acc, boundary_check=q_boundary_check)
437
+
438
+
439
+ @jaxtyping.jaxtyped(typechecker=typeguard.typechecked)
440
+ def _fwd(
441
+ q: Float[array_view.ArrayView, "*B T H D"],
442
+ k: Float[array_view.ArrayView, "*B t h D"],
443
+ v: Float[array_view.ArrayView, "*B t h D"],
444
+ bias: Float[Array, "*#B #H #T #t"] | None,
445
+ mask: Bool[Array, "*#B #H #T #t"] | None,
446
+ k_start: Int[Array, "*#B #H #T"] | None,
447
+ k_end: Int[Array, "*#B #H #T"] | None,
448
+ *,
449
+ logits_scale: float,
450
+ is_causal: bool,
451
+ q_k_dot_precision: precision_lib.DotPrecision,
452
+ weights_v_dot_precision: precision_lib.DotPrecision,
453
+ ) -> Float[Array, "*B T H D"]:
454
+ """Forward pass of Triton FlashAttention."""
455
+
456
+ orig_q_shape = q.shape
457
+ q = q.collapse(0, -3, allow_copy=True)
458
+ batch_size, seq_len_q, num_heads_q, head_dim = q.shape
459
+ *_, seq_len_k, num_heads_kv, _ = k.shape
460
+ # Maybe broadcast `k`/`v` heads dimension.
461
+ kv_shape = (batch_size, seq_len_k, num_heads_kv, head_dim)
462
+ k = k.collapse(0, -3, allow_copy=True).broadcast_to(kv_shape)
463
+ v = v.collapse(0, -3, allow_copy=True).broadcast_to(kv_shape)
464
+
465
+ def get_bias_mask_view(x, dtype):
466
+ if x is None:
467
+ x = jnp.array([], dtype=dtype)
468
+ return array_view.ArrayView(x, shape=(0, 0, 0, 0), strides=(0, 0, 0, 0))
469
+
470
+ shape = orig_q_shape[:-3] + (num_heads_q, seq_len_q, seq_len_k)
471
+ return (
472
+ array_view.ArrayView(x)
473
+ .broadcast_to(shape)
474
+ .collapse(0, -3, allow_copy=True)
475
+ )
476
+
477
+ bias = get_bias_mask_view(bias, dtype=q.dtype)
478
+ mask = get_bias_mask_view(mask, dtype=jnp.bool_)
479
+
480
+ def get_range_view(x, seq_len):
481
+ if x is None:
482
+ x = jnp.array([], dtype=jnp.int32)
483
+ return array_view.ArrayView(x, shape=(0, 0, 0), strides=(0, 0, 0))
484
+
485
+ shape = orig_q_shape[:-3] + (num_heads_q, seq_len)
486
+ return (
487
+ array_view.ArrayView(x)
488
+ .broadcast_to(shape)
489
+ .collapse(0, -2, allow_copy=True)
490
+ )
491
+
492
+ k_start = get_range_view(k_start, seq_len_q)
493
+ k_end = get_range_view(k_end, seq_len_q)
494
+
495
+ block_q = 64
496
+ block_k = 64
497
+
498
+ return jt.triton_call(
499
+ q.base,
500
+ k.base,
501
+ v.base,
502
+ bias.base,
503
+ mask.base,
504
+ k_start.base,
505
+ k_end.base,
506
+ q.offset,
507
+ k.offset,
508
+ v.offset,
509
+ *q.strides,
510
+ *k.strides,
511
+ *v.strides,
512
+ *bias.strides,
513
+ *mask.strides,
514
+ k_start.strides,
515
+ k_end.strides,
516
+ *jt.utils.strides_from_shape(q.shape), # out strides.
517
+ num_heads_q,
518
+ num_heads_kv,
519
+ seq_len_q,
520
+ seq_len_k,
521
+ kernel=_fwd_kernel,
522
+ name="triton_flash_attention",
523
+ out_shape=jax.ShapeDtypeStruct(q.shape, q.dtype),
524
+ grid=(num_heads_q, triton.cdiv(seq_len_q, block_q), batch_size),
525
+ num_stages=1,
526
+ num_warps=4,
527
+ is_causal=is_causal,
528
+ use_attention_mask=(mask.size != 0),
529
+ use_k_start=(k_start.size != 0),
530
+ use_k_end=(k_end.size != 0),
531
+ use_bias=(bias.size != 0),
532
+ sm_scale=logits_scale,
533
+ block_q=block_q,
534
+ block_k=block_k,
535
+ head_dim=head_dim,
536
+ use_mask_q=(seq_len_q % block_q != 0),
537
+ use_mask_k=(seq_len_k % block_q != 0),
538
+ bias_bcast_sq=(bias.strides[-2] == 0),
539
+ mask_bcast_sq=(mask.strides[-2] == 0),
540
+ dot_fn_qk=triton_utils.get_tl_dot_fn(q_k_dot_precision),
541
+ dot_fn_kv=triton_utils.get_tl_dot_fn(weights_v_dot_precision),
542
+ ).reshape(orig_q_shape)
543
+
544
+
545
+ def _as_batched_array_view(x, axis_size):
546
+ batched_shape = (axis_size,) + x.shape
547
+ batched_strides = (x.base.size // axis_size,) + x.strides
548
+ return dataclasses.replace(x, shape=batched_shape, strides=batched_strides)
549
+
550
+
551
+ def _fwd_vmap_rule(
552
+ axis_size, in_batched, *args, fn: jax.custom_batching.custom_vmap
553
+ ):
554
+ """`vmap` rule for Triton FlashAttention forward op."""
555
+ q, k, v, bias, mask, k_start, k_end = args
556
+ (
557
+ q_batched,
558
+ k_batched,
559
+ v_batched,
560
+ bias_batched,
561
+ mask_batched,
562
+ k_start_batched,
563
+ k_end_batched,
564
+ ) = in_batched
565
+
566
+ if q_batched.base:
567
+ q = _as_batched_array_view(q, axis_size)
568
+ if k_batched.base:
569
+ k = _as_batched_array_view(k, axis_size)
570
+ if v_batched.base:
571
+ v = _as_batched_array_view(v, axis_size)
572
+
573
+ # Triton op requires `q`, `k`, `v` batch dims to be identical.
574
+ if q_batched.base and k_batched.base and v_batched.base:
575
+ if bias is not None and not bias_batched:
576
+ bias = jax.lax.broadcast_to_rank(bias, bias.ndim + 1)
577
+ if mask is not None and not mask_batched:
578
+ mask = jax.lax.broadcast_to_rank(mask, mask.ndim + 1)
579
+ if k_start is not None and not k_start_batched:
580
+ k_start = jax.lax.broadcast_to_rank(k_start, k_start.ndim + 1)
581
+ if k_end is not None and not k_end_batched:
582
+ k_end = jax.lax.broadcast_to_rank(k_end, k_end.ndim + 1)
583
+ out = fn(q, k, v, bias, mask, k_start, k_end)
584
+ out_batched = True
585
+ return out, out_batched
586
+
587
+ # Fallback to sequential loop.
588
+ q, k, v = map(jnp.asarray, (q, k, v))
589
+ in_batched = [
590
+ q_batched.base,
591
+ k_batched.base,
592
+ v_batched.base,
593
+ bias_batched,
594
+ mask_batched,
595
+ k_start_batched,
596
+ k_end_batched,
597
+ ]
598
+
599
+ def f(q, k, v, *args, **kwargs):
600
+ q, k, v = map(array_view.ArrayView, (q, k, v))
601
+ return fn.fun(q, k, v, *args, **kwargs)
602
+
603
+ sequential_vmap = jax.custom_batching.sequential_vmap(f)
604
+ return sequential_vmap.vmap_rule(axis_size, in_batched, q, k, v, *args[3:])
605
+
606
+
607
+ def _decompose_mask(mask, q, k, q_indices, k_indices):
608
+ """Decomposes `mask` into a mask array, `is_causal`, `k_start` and `k_end`."""
609
+ if mask is None:
610
+ return None, False, None, None
611
+
612
+ is_causal = False
613
+ k_start = None
614
+ k_end = None
615
+ if q_indices is None and k_indices is None:
616
+ mask, is_causal, k_start, k_end = mask.take("is_causal", "k_start", "k_end")
617
+ if k_start is not None:
618
+ k_start = jax.lax.broadcast_to_rank(k_start, 2)
619
+ if k_end is not None:
620
+ k_end = jax.lax.broadcast_to_rank(k_end, 2)
621
+ if is_causal: # Fold is_causal into k_end
622
+ k_end = jnp.minimum(k_end, jnp.arange(1, q.shape[-3] + 1))
623
+ is_causal = False
624
+
625
+ q_len_or_indices = q.shape[-3] if q_indices is None else q_indices
626
+ k_len_or_indices = k.shape[-3] if k_indices is None else k_indices
627
+ return (
628
+ mask.as_array(q_len_or_indices, k_len_or_indices),
629
+ is_causal,
630
+ k_start,
631
+ k_end,
632
+ )
633
+
634
+
635
+ @dataclasses.dataclass(frozen=True)
636
+ class TritonFlashAttention(base.DotProductAttention):
637
+ """Triton FlashAttention implementation."""
638
+
639
+ @jaxtyping.jaxtyped(typechecker=typeguard.typechecked)
640
+ def _fwd(
641
+ self,
642
+ q: Float[array_view.ArrayView, "*B T H D"],
643
+ k: Float[array_view.ArrayView, "*B t h D"],
644
+ v: Float[array_view.ArrayView, "*B t h D"],
645
+ bias: Float[Array, "*#B #H #T #t"] | None,
646
+ *,
647
+ q_k_dot_precision: precision_lib.DotPrecision,
648
+ logits_dtype: jnp.dtype,
649
+ logits_scale: float,
650
+ mask: base.Mask | None,
651
+ weights_v_dot_precision: precision_lib.DotPrecision,
652
+ q_indices: Int[Array, "*#B #H T"] | None = None,
653
+ k_indices: Int[Array, "*#B #H t"] | None = None,
654
+ ) -> Float[Array, "*B T H D"]:
655
+ if logits_dtype != jnp.float32:
656
+ raise ValueError("`logits_dtype` must be float32.")
657
+
658
+ kwargs = dict(
659
+ logits_scale=logits_scale,
660
+ q_k_dot_precision=q_k_dot_precision,
661
+ weights_v_dot_precision=weights_v_dot_precision,
662
+ )
663
+
664
+ def attend_fwd(
665
+ q,
666
+ k,
667
+ v,
668
+ bias,
669
+ mask_,
670
+ q_indices,
671
+ k_indices,
672
+ ):
673
+
674
+ mask, is_causal, k_start, k_end = _decompose_mask(
675
+ mask_, q, k, q_indices, k_indices
676
+ )
677
+
678
+ fwd_closed_kwargs = dict(
679
+ is_causal=is_causal,
680
+ **kwargs,
681
+ )
682
+
683
+ fwd_closed = functools.partial(_fwd, **fwd_closed_kwargs)
684
+ fwd_closed = jax.custom_batching.custom_vmap(fwd_closed)
685
+ fwd_closed.def_vmap(functools.partial(_fwd_vmap_rule, fn=fwd_closed))
686
+
687
+ return fwd_closed(q, k, v, bias, mask, k_start, k_end)
688
+
689
+ return attend_fwd(
690
+ q,
691
+ k,
692
+ v,
693
+ bias,
694
+ mask,
695
+ q_indices,
696
+ k_indices,
697
+ )
flax_model/alphafold3/jax/attention/xla_attention.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """XLA implementation of scaled dot-product attention."""
4
+
5
+ import dataclasses
6
+
7
+ from flax_model.alphafold3.jax.attention import attention_base as base
8
+ from flax_model.alphafold3.jax.common import array_view
9
+ from flax_model.alphafold3.jax.common import precision as precision_lib
10
+ import jax
11
+ import jax.numpy as jnp
12
+ import jaxtyping
13
+ from jaxtyping import Array, Float, Int # pylint: disable=g-multiple-import,g-importing-member
14
+ import typeguard
15
+
16
+
17
+ def _get_precision(
18
+ backend: str, precision: precision_lib.DotPrecision
19
+ ) -> jax.lax.Precision:
20
+ if backend == "gpu" and precision == precision_lib.DotPrecision.F32_F32:
21
+ return jax.lax.Precision.HIGHEST
22
+ return jax.lax.Precision.DEFAULT
23
+
24
+
25
+ def einsum_with_dot_precision(
26
+ subscript: str,
27
+ a: jax.Array,
28
+ b: jax.Array,
29
+ *,
30
+ precision: precision_lib.DotPrecision,
31
+ ) -> jax.Array:
32
+ """Evaluate `fn` with the given precision."""
33
+ result = jnp.einsum(
34
+ subscript,
35
+ a.astype(precision.operand_dtype),
36
+ b.astype(precision.operand_dtype),
37
+ precision=_get_precision(jax.default_backend().lower(), precision),
38
+ preferred_element_type=precision.accumulator_dtype,
39
+ )
40
+ assert result.dtype == precision.accumulator_dtype
41
+ return result
42
+
43
+
44
+ def _softmax(x: jax.Array) -> jax.Array:
45
+ """Computes softmax."""
46
+ # Always perform reductions in at least f32 precision.
47
+ dtype = jnp.promote_types(x.dtype, jnp.float32)
48
+ x_max = jnp.max(x.astype(dtype), axis=-1, keepdims=True)
49
+ unnormalized = jnp.exp(x - x_max)
50
+ denom = jnp.sum(unnormalized, axis=-1, keepdims=True)
51
+ return unnormalized / denom
52
+
53
+
54
+ @jaxtyping.jaxtyped(typechecker=typeguard.typechecked)
55
+ def _attend(
56
+ q: Float[array_view.ArrayView, "*B T H D"],
57
+ k: Float[array_view.ArrayView, "*B t #H D"],
58
+ v: Float[array_view.ArrayView, "*B t #H D"],
59
+ *,
60
+ q_k_dot_precision: precision_lib.DotPrecision,
61
+ logits_dtype: jnp.dtype,
62
+ logits_scale: float,
63
+ bias: Float[Array, "*#B #H #T #t"] | None,
64
+ mask: base.Mask | None,
65
+ weights_v_dot_precision: precision_lib.DotPrecision,
66
+ q_indices: Int[Array, "*#B #H T"] | None,
67
+ k_indices: Int[Array, "*#B #H t"] | None,
68
+ ) -> Float[Array, "*B T H D"]:
69
+ """Computes attention."""
70
+ logits = einsum_with_dot_precision(
71
+ "...qhd,...khd->...hqk", q, k, precision=q_k_dot_precision
72
+ ).astype(logits_dtype)
73
+
74
+ logits *= logits_scale
75
+
76
+ if bias is not None:
77
+ logits += bias
78
+
79
+ if mask is not None:
80
+ q_len_or_indices = q.shape[-3] if q_indices is None else q_indices
81
+ k_len_or_indices = k.shape[-3] if k_indices is None else k_indices
82
+ mask = mask.as_array(q_len_or_indices, k_len_or_indices)
83
+
84
+ if mask is not None:
85
+ mask_value = float(jnp.finfo(logits.dtype).min)
86
+
87
+ logits = jnp.where(jnp.asarray(mask), logits, mask_value)
88
+
89
+ weights = _softmax(logits)
90
+
91
+ weights = weights.astype(v.dtype)
92
+ out = einsum_with_dot_precision(
93
+ "...hqk,...khd->...qhd", weights, v, precision=weights_v_dot_precision
94
+ ).astype(q.dtype)
95
+ return out
96
+
97
+
98
+ @dataclasses.dataclass(frozen=True)
99
+ class XlaDotProductAttention(base.DotProductAttention):
100
+ """XLA dot product attention function."""
101
+
102
+ _: dataclasses.KW_ONLY
103
+
104
+ def _fwd(
105
+ self,
106
+ q: Float[array_view.ArrayView, "*B T H D"],
107
+ k: Float[array_view.ArrayView, "*B t #H D"],
108
+ v: Float[array_view.ArrayView, "*B t #H D"],
109
+ *,
110
+ q_k_dot_precision: precision_lib.DotPrecision,
111
+ logits_dtype: jnp.dtype,
112
+ logits_scale: float,
113
+ bias: Float[Array, "*#B #H #T #t"] | None,
114
+ mask: base.Mask | None,
115
+ weights_v_dot_precision: precision_lib.DotPrecision,
116
+ q_indices: Int[Array, "*#B #H T"] | None = None,
117
+ k_indices: Int[Array, "*#B #H t"] | None = None,
118
+ ) -> Float[Array, "*B T H D"]:
119
+
120
+ return _attend(
121
+ q,
122
+ k,
123
+ v,
124
+ bias=bias,
125
+ mask=mask,
126
+ q_indices=q_indices,
127
+ k_indices=k_indices,
128
+ q_k_dot_precision=q_k_dot_precision,
129
+ logits_dtype=logits_dtype,
130
+ logits_scale=logits_scale,
131
+ weights_v_dot_precision=weights_v_dot_precision,
132
+ )
flax_model/alphafold3/jax/common/__init__.py ADDED
File without changes
flax_model/alphafold3/jax/common/triton_utils.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Triton utils."""
4
+
5
+ from collections.abc import Callable, Mapping
6
+
7
+ from flax_model.alphafold3.jax.common import precision as precision_lib
8
+ import jax
9
+ import jax.numpy as jnp
10
+ import triton
11
+ import triton.language as tl
12
+
13
+
14
+ _JNP_TO_TL_DTYPES: Mapping[jnp.dtype, tl.dtype] = {
15
+ jnp.bool_: tl.int1,
16
+ jnp.int8: tl.int8,
17
+ jnp.int16: tl.int16,
18
+ jnp.int32: tl.int32,
19
+ jnp.int64: tl.int64,
20
+ jnp.uint8: tl.uint8,
21
+ jnp.uint16: tl.uint16,
22
+ jnp.uint32: tl.uint32,
23
+ jnp.uint64: tl.uint64,
24
+ jnp.float16: tl.float16,
25
+ jnp.bfloat16: tl.bfloat16,
26
+ jnp.float32: tl.float32,
27
+ jnp.float64: tl.float64,
28
+ }
29
+
30
+
31
+ def jnp_to_tl_dtype(jnp_dtype: jnp.dtype) -> tl.dtype:
32
+ return _JNP_TO_TL_DTYPES[jnp_dtype]
33
+
34
+
35
+ def get_tl_dot_fn(
36
+ precision: precision_lib.DotPrecision,
37
+ ) -> Callable[..., tl.tensor]:
38
+ """Returns a tl `dot` implementation with the specified precision.
39
+
40
+ Args:
41
+ precision: The `dot` precision.
42
+ """
43
+ if not is_precision_supported(precision):
44
+ raise ValueError(f'Unsupported dot precision: {precision}')
45
+
46
+ if precision == precision_lib.DotPrecision.TF32_F32_3X:
47
+ return _dot_tf32_f32_3x
48
+
49
+ in_dtype = jnp_to_tl_dtype(precision.operand_dtype)
50
+ out_dtype = jnp_to_tl_dtype(precision.accumulator_dtype)
51
+ allow_tf32 = precision == precision_lib.DotPrecision.TF32_F32
52
+
53
+ @tl.core.extern
54
+ def _dot_fn(
55
+ a: tl.core.tensor,
56
+ b: tl.core.tensor,
57
+ *,
58
+ trans_a: bool = False,
59
+ trans_b: bool = False,
60
+ _builder,
61
+ ):
62
+ if in_dtype == tl.float32:
63
+ tl.static_assert(a.dtype == tl.float32, _builder=_builder)
64
+ tl.static_assert(b.dtype == tl.float32, _builder=_builder)
65
+ else:
66
+ tl.static_assert(a.dtype.is_standard_floating(), _builder=_builder)
67
+ tl.static_assert(b.dtype.is_standard_floating(), _builder=_builder)
68
+ a = a.to(in_dtype, _builder=_builder)
69
+ b = b.to(in_dtype, _builder=_builder)
70
+ a = tl.trans(a, _builder=_builder) if trans_a else a
71
+ b = tl.trans(b, _builder=_builder) if trans_b else b
72
+ return tl.dot(
73
+ a, b, allow_tf32=allow_tf32, out_dtype=out_dtype, _builder=_builder
74
+ )
75
+
76
+ return _dot_fn
77
+
78
+
79
+ def is_precision_supported(precision: precision_lib.DotPrecision) -> bool:
80
+ return precision in {
81
+ precision_lib.DotPrecision.F32_F32,
82
+ precision_lib.DotPrecision.TF32_F32,
83
+ precision_lib.DotPrecision.F16_F32,
84
+ precision_lib.DotPrecision.BF16_F32,
85
+ precision_lib.DotPrecision.TF32_F32_3X,
86
+ }
87
+
88
+
89
+ @triton.jit
90
+ def _dot_tf32_f32_3x(a, b, trans_a=False, trans_b=False):
91
+ """Perform the 3-pass tf32 dot function."""
92
+ tl.static_assert(a.dtype == tl.float32)
93
+ tl.static_assert(b.dtype == tl.float32)
94
+ a_ = (a.to(tl.uint32, bitcast=True) & 0xFFFFE000).to(tl.float32, bitcast=True)
95
+ b_ = (b.to(tl.uint32, bitcast=True) & 0xFFFFE000).to(tl.float32, bitcast=True)
96
+ a_err = a - a_
97
+ b_err = b - b_
98
+ if trans_a:
99
+ a_ = tl.trans(a_)
100
+ a_err = tl.trans(a_err)
101
+ if trans_b:
102
+ b_ = tl.trans(b_)
103
+ b_err = tl.trans(b_err)
104
+ # Add smallest terms first for better accuracy.
105
+ return tl.dot(a_, b_, out_dtype=tl.float32) + (
106
+ tl.dot(a_, b_err, out_dtype=tl.float32)
107
+ + tl.dot(a_err, b_, out_dtype=tl.float32)
108
+ )
109
+
110
+
111
+ def has_triton_support() -> bool:
112
+ """Returns True if Triton is supported by the default JAX device."""
113
+ if jax.default_backend() != 'gpu':
114
+ return False
115
+
116
+ # Only currently supported for Ampere and above.
117
+ # return float(jax.devices()[0].compute_capability) >= 8.0
118
+ return True
flax_model/alphafold3/jax/gated_linear_unit/block.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Pallas block load / store utilities."""
4
+
5
+ from collections.abc import Sequence
6
+ from typing import Any, TypeAlias
7
+
8
+ from flax_model.alphafold3.jax.common import array_view
9
+ import jax
10
+ import jax.experimental
11
+ from jax.experimental import pallas as pl
12
+ import jax.numpy as jnp
13
+ import jaxtyping
14
+ from jaxtyping import Int # pylint: disable=g-importing-member
15
+ import numpy as np
16
+ import typeguard
17
+
18
+ ArrayT: TypeAlias = Any
19
+ ScalarInt: TypeAlias = (
20
+ Int[ArrayT, ""] | Int[np.generic, ""] | Int[jnp.generic, ""]
21
+ )
22
+
23
+
24
+ @jaxtyping.jaxtyped(typechecker=typeguard.typechecked)
25
+ def load_block(
26
+ ref,
27
+ idx: Sequence[int | ScalarInt],
28
+ *,
29
+ block_shape: Sequence[int | None],
30
+ other=None,
31
+ **kwargs,
32
+ ) -> jax.Array:
33
+ """Loads a block from the given `ref`, masking where necessary."""
34
+ idx, mask = _get_block_indexer_and_mask(ref, idx, block_shape=block_shape)
35
+ if isinstance(ref, array_view.ArrayView):
36
+ idx = ref[idx].offsets
37
+ ref = ref.base
38
+ other = None if mask is None else other
39
+ with jax.experimental.enable_x64():
40
+ return pl.load(ref, idx, mask=mask, other=other, **kwargs)
41
+
42
+
43
+ @jaxtyping.jaxtyped(typechecker=typeguard.typechecked)
44
+ def store_block(
45
+ ref,
46
+ val: jax.Array,
47
+ idx: Sequence[int | ScalarInt],
48
+ *,
49
+ block_shape: Sequence[int | None] | None = None,
50
+ **kwargs,
51
+ ):
52
+ """Stores a block from the given `ref`, masking where necessary."""
53
+ if block_shape is None:
54
+ block_shape = val.shape
55
+ idx, mask = _get_block_indexer_and_mask(ref, idx, block_shape=block_shape)
56
+ if isinstance(ref, array_view.ArrayView):
57
+ idx = ref[idx].offsets
58
+ ref = ref.base
59
+ with jax.experimental.enable_x64():
60
+ pl.store(ref, idx, val.astype(ref.dtype), mask=mask, **kwargs)
61
+
62
+
63
+ def in_bounds_mask(
64
+ idx: Sequence[int | slice | pl.Slice | jax.Array],
65
+ shape: Sequence[int],
66
+ *,
67
+ check: Sequence[bool] | None = None,
68
+ ) -> jax.Array | None:
69
+ """Returns a boolean mask denoting which indices are within bounds.
70
+
71
+ Args:
72
+ idx: Indices for each dimension.
73
+ shape: Shape designating the valid bounds.
74
+ check: Whether or not to check bounds in each dimension. Useful for ignoring
75
+ indices known to be in bounds. Defaults to all True.
76
+ """
77
+ if check is None:
78
+ check = [True] * len(shape)
79
+
80
+ # Remove `int` indexed dims (mask shape must match slice result shape).
81
+ shape = [dim for i, dim in enumerate(shape) if not isinstance(idx[i], int)]
82
+ check = [chk for i, chk in enumerate(check) if not isinstance(idx[i], int)]
83
+ idx = [idx for idx in idx if not isinstance(idx, int)]
84
+
85
+ mask = None
86
+ for i, (dim_idx, dim, chk) in enumerate(zip(idx, shape, check, strict=True)):
87
+ if not chk:
88
+ continue
89
+
90
+ if isinstance(dim_idx, slice):
91
+ dim_idx = pl.Slice.from_slice(dim_idx, dim)
92
+ if isinstance(dim_idx, pl.Slice):
93
+ dim_idx = dim_idx.start + dim_idx.stride * jnp.arange(dim_idx.size)
94
+ if dim_idx.ndim != 1:
95
+ raise NotImplementedError("Only one-dimensional indices are supported.")
96
+
97
+ bcast_axes = [a for a in range(len(shape)) if a != i]
98
+ dim_mask = jnp.expand_dims(dim_idx < dim, bcast_axes)
99
+ mask = dim_mask if mask is None else (mask & dim_mask)
100
+ return mask
101
+
102
+
103
+ def _get_block_indexer_and_mask(
104
+ ref, idx: Sequence[int | ScalarInt], *, block_shape: Sequence[int | None]
105
+ ) -> tuple[tuple[int | slice | pl.Slice, ...], jax.Array | None]:
106
+ """Return indices and mask for loading / storing a block."""
107
+ shape = ref.shape
108
+ idxs = []
109
+ check = []
110
+ for dim, block_idx, block_dim in zip(shape, idx, block_shape, strict=True):
111
+ if block_dim is None:
112
+ idxs.append(block_idx)
113
+ check.append(False)
114
+ else:
115
+ idxs.append(pl.dslice(block_dim * block_idx, block_dim))
116
+ check.append(dim % block_dim != 0)
117
+
118
+ return tuple(idxs), in_bounds_mask(idxs, shape, check=check)
flax_model/alphafold3/jax/gated_linear_unit/gated_linear_unit.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Public API for gated linear unit functions."""
4
+
5
+ from collections.abc import Callable
6
+ import typing
7
+ from typing import Literal, TypeAlias
8
+
9
+ from flax_model.alphafold3.jax.common import array_view
10
+ from flax_model.alphafold3.jax.common import triton_utils
11
+ from flax_model.alphafold3.jax.gated_linear_unit import gated_linear_unit_base
12
+ from flax_model.alphafold3.jax.gated_linear_unit import matmul_ext
13
+ import jax
14
+ import jaxtyping
15
+ from jaxtyping import Array, Float # pylint: disable=g-importing-member,g-multiple-import
16
+ import typeguard
17
+
18
+ Implementation: TypeAlias = Literal['xla', 'triton']
19
+
20
+
21
+ class PallasGatedLinearUnit(gated_linear_unit_base.GatedLinearUnit):
22
+ """Pallas gated linear unit."""
23
+
24
+ def _fwd(self, x, weight, *, activation, precision):
25
+ weight_view = array_view.ArrayView(weight)
26
+ return self.apply_vmap_rule_forward(
27
+ matmul_ext.gated_linear_unit,
28
+ activation=activation,
29
+ precision=precision,
30
+ )(
31
+ x,
32
+ weight_view[:, 1],
33
+ weight_view[:, 0],
34
+ )
35
+
36
+
37
+ @jaxtyping.jaxtyped(typechecker=typeguard.typechecked)
38
+ def gated_linear_unit(
39
+ x: Float[Array, '*B M K'],
40
+ weight: Float[Array, 'K 2 N'],
41
+ *,
42
+ activation: Callable[[jax.Array], jax.Array] | None = None,
43
+ precision: jax.lax.Precision | None = None,
44
+ implementation: Implementation | None = None,
45
+ ) -> Float[Array, '*B M N']:
46
+ """Applies a gated linear unit (https://arxiv.org/abs/1612.08083).
47
+
48
+ Computes `activation(x @ weight[:, 0]) * x @ weight[:, 1]`.
49
+
50
+ This is SwiGLU when `activation=jax.nn.swish`, GEGLU when
51
+ `activation=jax.nn.gelu`, REGLU when `activation=jax.nn.relu`, and GLU when
52
+ `activation=jax.nn.sigmoid` (https://arxiv.org/abs/2002.05202).
53
+
54
+ Args:
55
+ x: the input array.
56
+ weight: the combined weight array.
57
+ activation: optional activation function.
58
+ precision: specifies the matrix multiplication precision. Either `None`
59
+ (default), which means the default precision for the backend, or a
60
+ `jax.lax.Precision` enum.
61
+ implementation: if `None` (default), an implementation is automatically
62
+ chosen. 'xla' will use standard XLA and work on any platform, and 'triton'
63
+ will use a fused Triton GPU kernel. Only a subset of data types, shapes
64
+ and GPUs are supported by 'triton', with an exception thrown in this case.
65
+
66
+ Raises:
67
+ NotImplementedError: if `implementation='triton'` does not support a given
68
+ input or device.
69
+ ValueError: if the arguments are invalid.
70
+
71
+ Returns:
72
+ The output array.
73
+ """
74
+
75
+ match implementation:
76
+ case 'triton':
77
+ if not triton_utils.has_triton_support():
78
+ raise NotImplementedError('Triton not supported on this platform.')
79
+ case _:
80
+ ...
81
+
82
+ if x.dtype.name != weight.dtype.name:
83
+ raise ValueError(
84
+ f'Input and weight must have the same dtype. {x.dtype} !='
85
+ f' {weight.dtype}'
86
+ )
87
+
88
+ if implementation is not None:
89
+ named_args = typing.get_args(Implementation)
90
+ if implementation not in named_args:
91
+ raise ValueError(
92
+ f'Unsupported named implementation. Must be one of {named_args}.'
93
+ )
94
+
95
+ # if implementation is None or implementation == 'triton':
96
+ # try:
97
+ # return PallasGatedLinearUnit()(
98
+ # x=x,
99
+ # weight=weight,
100
+ # activation=activation,
101
+ # precision=precision,
102
+ # )
103
+ # # When `implementation=None`, we must catch any exception, and use XLA
104
+ # # as a fallback. As we rely on a third-party library (Triton), it might
105
+ # # not be possible to enumerate all possible exceptions that could be
106
+ # # thrown, hence catching the broadest possible one.
107
+ # except Exception as e: # pylint: disable=broad-exception-caught
108
+ # if implementation == 'triton':
109
+ # raise e
110
+
111
+ return gated_linear_unit_base.gated_linear_unit_xla(
112
+ x=x,
113
+ weight=weight,
114
+ activation=activation,
115
+ precision=precision,
116
+ )
flax_model/alphafold3/jax/gated_linear_unit/matmul_config.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Auto-tuned configs for matmul."""
4
+
5
+ import dataclasses
6
+ import functools
7
+ import math
8
+
9
+ import jax
10
+ from jax.experimental import pallas as pl
11
+
12
+
13
+ @dataclasses.dataclass(frozen=True, kw_only=True)
14
+ class Config:
15
+ block_m: int
16
+ block_n: int
17
+ block_k: int
18
+ num_warps: int
19
+ num_stages: int
20
+
21
+
22
+ @functools.cache
23
+ def _get_best_block_size(
24
+ m: int, n: int, k: int, core_count: int
25
+ ) -> tuple[int, int, int]:
26
+ """Returns the best block size for the given shape."""
27
+ min_block_dim = 32
28
+ block_m = min(max(min_block_dim, pl.next_power_of_2(m)), 128)
29
+ block_n = min(max(min_block_dim, pl.next_power_of_2(n)), 256)
30
+ block_n = min(block_n, (128 * 128) // block_m)
31
+ block_k = 32
32
+ split_k = 1
33
+ num_blocks = pl.cdiv(m, block_m) * pl.cdiv(n, block_n)
34
+ while num_blocks < core_count:
35
+ if block_m > min_block_dim:
36
+ block_m //= 2
37
+ num_blocks = pl.cdiv(m, block_m) * pl.cdiv(n, block_n)
38
+ elif split_k * block_k < pl.next_power_of_2(k):
39
+ split_k *= 2
40
+ num_blocks *= 2
41
+ else:
42
+ break
43
+ return block_m, block_n, block_k
44
+
45
+
46
+ def _abstractify(x):
47
+ return jax.api_util.shaped_abstractify(x) if isinstance(x, jax.Array) else x
48
+
49
+
50
+ def get_config(
51
+ x: jax.Array, w: jax.Array, core_count: int | None = None
52
+ ) -> Config:
53
+ """Returns a config for the given args."""
54
+ if core_count is None:
55
+ core_count = jax.devices()[0].core_count
56
+ x = _abstractify(x)
57
+ w = _abstractify(w)
58
+ m, k = math.prod(x.shape[:-1]), x.shape[-1]
59
+ n = w.shape[1]
60
+ if n >= m: # Prefer `block_n` > `block_m`.
61
+ block_m, block_n, block_k = _get_best_block_size(m, n, k, core_count)
62
+ else:
63
+ block_n, block_m, block_k = _get_best_block_size(n, m, k, core_count)
64
+ return Config(
65
+ block_m=block_m,
66
+ block_n=block_n // 2, # Halve `block_n` as we read two `w` blocks.
67
+ block_k=block_k,
68
+ num_warps=4,
69
+ num_stages=4,
70
+ )
flax_model/alphafold3/jax/geometry/__init__.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """Geometry Module."""
4
+
5
+ from flax_model.alphafold3.jax.geometry import rigid_matrix_vector
6
+ from flax_model.alphafold3.jax.geometry import rotation_matrix
7
+ from flax_model.alphafold3.jax.geometry import struct_of_array
8
+ from flax_model.alphafold3.jax.geometry import vector
9
+
10
+ Rot3Array = rotation_matrix.Rot3Array
11
+ Rigid3Array = rigid_matrix_vector.Rigid3Array
12
+
13
+ StructOfArray = struct_of_array.StructOfArray
14
+
15
+ Vec3Array = vector.Vec3Array
16
+ square_euclidean_distance = vector.square_euclidean_distance
17
+ euclidean_distance = vector.euclidean_distance
18
+ dihedral_angle = vector.dihedral_angle
19
+ dot = vector.dot
20
+ cross = vector.cross
flax_model/alphafold3/test_data/miniature_databases/nt_rna_2023_02_23_clust_seq_id_90_cov_80_rep_seq__subsampled_1000.fasta ADDED
The diff for this file is too large to render. See raw diff
 
scripts/infer_jackhmmer.sh ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
6
+
7
+
8
+ export PATH="${PROJECT_ROOT}/flax_model/alphafold3/_tools/hmmer/bin:${PATH}"
9
+ export TF_CPP_MIN_LOG_LEVEL="${TF_CPP_MIN_LOG_LEVEL:-0}"
10
+ export JAX_TRACEBACK_FILTERING="${JAX_TRACEBACK_FILTERING:-off}"
11
+ export XLA_CLIENT_MEM_FRACTION="${XLA_CLIENT_MEM_FRACTION:-0.95}"
12
+ export TRITON_ENABLE_GLOBAL_TO_LOCAL="${TRITON_ENABLE_GLOBAL_TO_LOCAL:-1}"
13
+ export TRITON_USE_MAKE_BLOCK_PTR="${TRITON_USE_MAKE_BLOCK_PTR:-1}"
14
+ export TRITON_DEFAULT_ENABLE_NUM_VGPRS512="${TRITON_DEFAULT_ENABLE_NUM_VGPRS512:-1}"
15
+ export HIP_VISIBLE_DEVICES="${HIP_VISIBLE_DEVICES:-0}"
16
+
17
+ MODEL_ROOT="${ONESCIENCE_MODELS_DIR:-${PROJECT_ROOT}/weight}"
18
+ MODEL_DIR="${ALPHAFOLD3_MODEL_DIR:-${MODEL_ROOT}}"
19
+ DATASET_ROOT="${ALPHAFOLD3_DATASET_ROOT:-${ONESCIENCE_DATASETS_DIR:-${PROJECT_ROOT}/data}/alphafold3}"
20
+ DB_DIRS="${ALPHAFOLD3_DB_DIR:-${DATASET_ROOT}/public_databases}"
21
+ MMSEQS_DB_DIRS="${ALPHAFOLD3_MMSEQS_DB_DIR:-${DATASET_ROOT}/mmseqsDB}"
22
+ JSON_PATH="${ALPHAFOLD3_JSON_PATH:-${PROJECT_ROOT}/inputs/t1119_search.json}"
23
+ OUTPUT_DIR="${ALPHAFOLD3_OUTPUT_DIR:-${PROJECT_ROOT}/outputs}"
24
+ RUN_INFERENCE="${ALPHAFOLD3_RUN_INFERENCE:-false}"
25
+ FLASH_ATTENTION="${ALPHAFOLD3_FLASH_ATTENTION:-cutlass}"
26
+
27
+ mkdir -p "${OUTPUT_DIR}"
28
+
29
+ python "${PROJECT_ROOT}/scripts/run_alphafold.py" \
30
+ --json_path="${JSON_PATH}" \
31
+ --model_dir="${MODEL_DIR}" \
32
+ --output_dir="${OUTPUT_DIR}" \
33
+ --run_data_pipeline=true \
34
+ --run_inference="${RUN_INFERENCE}" \
35
+ --flash_attention_implementation="${FLASH_ATTENTION}" \
36
+ --use_mmseqs=false \
37
+ --use_mmseqs_gpu=false \
38
+ --db_dir="${DB_DIRS}" \
39
+ --mmseqs_db_dir="${MMSEQS_DB_DIRS}" \
40
+ --small_bfd_database_path="${DATASET_ROOT}/jackhmmer_split/bfd-first_non_consensus_sequences.fasta@64" \
41
+ --small_bfd_z_value=65928866 \
42
+ --mgnify_database_path="${DATASET_ROOT}/jackhmmer_split/mgy_clusters_2022_05.fa@512" \
43
+ --mgnify_z_value=623796864 \
44
+ --uniprot_cluster_annot_database_path="${DATASET_ROOT}/jackhmmer_split/uniprot_cluster_annot_2021_04.fa@256" \
45
+ --uniprot_cluster_annot_z_value=225619586 \
46
+ --uniref90_database_path="${DATASET_ROOT}/jackhmmer_split/uniref90_2022_05.fa@128" \
47
+ --uniref90_z_value=153742194 \
48
+ --jackhmmer_n_cpu="${ALPHAFOLD3_JACKHMMER_N_CPU:-2}" \
49
+ --jackhmmer_max_parallel_shards="${ALPHAFOLD3_JACKHMMER_MAX_PARALLEL_SHARDS:-1}" \
50
+ --jackhmmer_max_threads="${ALPHAFOLD3_JACKHMMER_MAX_THREADS:-8}" \
51
+ --nhmmer_n_cpu="${ALPHAFOLD3_NHMMER_N_CPU:-2}" \
52
+ --nhmmer_max_parallel_shards="${ALPHAFOLD3_NHMMER_MAX_PARALLEL_SHARDS:-1}" \
53
+ --nhmmer_max_threads="${ALPHAFOLD3_NHMMER_MAX_THREADS:-8}"
scripts/preflight.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import hashlib
6
+ import json
7
+ from pathlib import Path
8
+
9
+ try:
10
+ import yaml
11
+ except ImportError as exc:
12
+ raise SystemExit("PyYAML is required for preflight: pip install pyyaml") from exc
13
+
14
+
15
+ MODEL_FILES = {
16
+ "af3.bin": (1146811260, "a43670ea1fae790cd30ccc8c8bf836c4098014c45250fb310a5338407e321de7"),
17
+ "libflash_atten_c.so": (75882104, "2ea956e8d13317dd795ffce846079f20e502a39df66802f485d5f25900ef493b"),
18
+ "mmseqs/bin/mmseqs": (14417192, "d166c67ca79089ea5f89ec1133f67ab1c216e1734a0408bbc7205e588fe865ed"),
19
+ "mmseqs/lib/libmarv.so": (58277568, "fdc787467b6d05d36db1f034a2cb8944b5b072bbe6d46172013810a066583928"),
20
+ }
21
+
22
+ DATASET_REQUIRED = [
23
+ "infer_input_data/readme.md",
24
+ "infer_input_data/all_data/7r6r_data.json",
25
+ "infer_input_data/all_data/t1119_data.json",
26
+ "public_databases/pdb_seqres_2022_09_28.fasta",
27
+ "mmseqsDB/small_bfd_db",
28
+ "mmseqsDB/small_bfd_db.dbtype",
29
+ "jackhmmer_split/bfd-first_non_consensus_sequences.fasta-00000-of-00064",
30
+ ]
31
+
32
+
33
+ def sha256(path: Path) -> str:
34
+ h = hashlib.sha256()
35
+ with path.open("rb") as f:
36
+ for chunk in iter(lambda: f.read(1024 * 1024), b""):
37
+ h.update(chunk)
38
+ return h.hexdigest()
39
+
40
+
41
+ def check_json(path: Path) -> None:
42
+ data = json.loads(path.read_text(encoding="utf-8"))
43
+ if data.get("dialect") != "alphafold3":
44
+ raise AssertionError(f"{path} dialect is not alphafold3")
45
+ if not data.get("name"):
46
+ raise AssertionError(f"{path} missing name")
47
+ if not isinstance(data.get("sequences"), list) or not data["sequences"]:
48
+ raise AssertionError(f"{path} missing sequences")
49
+
50
+
51
+ def main() -> None:
52
+ parser = argparse.ArgumentParser()
53
+ parser.add_argument("--root", default=".")
54
+ parser.add_argument("--dataset-root", default="data/alphafold3_dataset")
55
+ parser.add_argument("--model-dir", default="checkpoints/AlphaFold3")
56
+ parser.add_argument("--skip-hash", action="store_true")
57
+ args = parser.parse_args()
58
+
59
+ root = Path(args.root).resolve()
60
+ model_dir = (root / args.model_dir).resolve()
61
+ dataset_root = (root / args.dataset_root).resolve()
62
+
63
+ config = yaml.safe_load((root / "conf" / "alphafold3_paths.yaml").read_text(encoding="utf-8"))
64
+ if config["model"]["repo_id"] != "OneScience/AlphaFold3/":
65
+ raise AssertionError("model repo_id mismatch in conf/alphafold3_paths.yaml")
66
+ if config["dataset"]["repo_id"] != "OneScience/AlphaFold3_dataset":
67
+ raise AssertionError("dataset repo_id mismatch in conf/alphafold3_paths.yaml")
68
+
69
+ for rel, (size, digest) in MODEL_FILES.items():
70
+ path = model_dir / rel
71
+ if not path.is_file():
72
+ raise AssertionError(f"missing model file: {path}")
73
+ if path.stat().st_size != size:
74
+ raise AssertionError(f"model size mismatch: {path}")
75
+ if not args.skip_hash and sha256(path) != digest:
76
+ raise AssertionError(f"model sha256 mismatch: {path}")
77
+
78
+ for rel in DATASET_REQUIRED:
79
+ path = dataset_root / rel
80
+ if not path.exists():
81
+ raise AssertionError(f"missing dataset dependency: {path}")
82
+
83
+ check_json(root / "inputs" / "7r6r_data.json")
84
+ check_json(root / "inputs" / "t1119_search.json")
85
+ print("model_preflight_ok: true")
86
+
87
+
88
+ if __name__ == "__main__":
89
+ main()
scripts/run_alphafold.py ADDED
@@ -0,0 +1,1194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ """AlphaFold 3 structure prediction script.
4
+
5
+ AlphaFold 3 source code is licensed under CC BY-NC-SA 4.0. To view a copy of
6
+ this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/
7
+
8
+ To request access to the AlphaFold 3 model parameters, follow the process set
9
+ out at https://github.com/google-deepmind/alphafold3. You may only use these
10
+ if received directly from Google. Use is subject to terms of use available at
11
+ https://github.com/google-deepmind/alphafold3/blob/main/WEIGHTS_TERMS_OF_USE.md
12
+ """
13
+
14
+ from collections.abc import Callable, Sequence
15
+ import csv
16
+ import dataclasses
17
+ import datetime
18
+ import functools
19
+ import multiprocessing
20
+ import os
21
+ import pathlib
22
+ import shutil
23
+ import string
24
+ import sys
25
+ import textwrap
26
+ import time
27
+ import typing
28
+ from typing import overload
29
+
30
+ _PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[1]
31
+ if str(_PROJECT_ROOT) not in sys.path:
32
+ sys.path.insert(0, str(_PROJECT_ROOT))
33
+
34
+ from absl import app
35
+ from absl import flags
36
+ from flax_model.alphafold3.common import folding_input
37
+ from flax_model.alphafold3.common import resources
38
+ from flax_model.alphafold3.constants import chemical_components
39
+ import flax_model.alphafold3.cpp as af3_cpp
40
+ from flax_model.alphafold3.data import featurisation
41
+ from flax_model.alphafold3.data import pipeline
42
+ from flax_model.alphafold3.jax.attention import attention
43
+ from flax_model.alphafold3.model import features
44
+ from flax_model.alphafold3.model import model
45
+ from flax_model.alphafold3.model import params
46
+ from flax_model.alphafold3.model import post_processing
47
+ from flax_model.alphafold3.model.components import utils
48
+ import haiku as hk
49
+ import jax
50
+ from jax import numpy as jnp
51
+ import numpy as np
52
+
53
+
54
+ _HOME_DIR = pathlib.Path(os.environ.get('HOME', _PROJECT_ROOT))
55
+ _MODELS_ROOT = pathlib.Path(
56
+ os.environ.get('ONESCIENCE_MODELS_DIR', _PROJECT_ROOT / 'weight')
57
+ )
58
+ _DATASETS_ROOT = pathlib.Path(
59
+ os.environ.get('ONESCIENCE_DATASETS_DIR', _HOME_DIR)
60
+ ) / 'alphafold3'
61
+ _DEFAULT_MODEL_DIR = pathlib.Path(
62
+ os.environ.get('ALPHAFOLD3_MODEL_DIR', _MODELS_ROOT / 'AlphaFold3')
63
+ )
64
+ _DEFAULT_DB_DIR = pathlib.Path(
65
+ os.environ.get('ALPHAFOLD3_DB_DIR', _DATASETS_ROOT / 'public_databases')
66
+ )
67
+ _DEFAULT_MMSEQS_DB_DIR = pathlib.Path(
68
+ os.environ.get('ALPHAFOLD3_MMSEQS_DB_DIR', _DATASETS_ROOT / 'mmseqsDB')
69
+ )
70
+
71
+ # Input and output paths.
72
+ _JSON_PATH = flags.DEFINE_string(
73
+ 'json_path',
74
+ None,
75
+ 'Path to the input JSON file.',
76
+ )
77
+ _INPUT_DIR = flags.DEFINE_string(
78
+ 'input_dir',
79
+ None,
80
+ 'Path to the directory containing input JSON files.',
81
+ )
82
+ _OUTPUT_DIR = flags.DEFINE_string(
83
+ 'output_dir',
84
+ None,
85
+ 'Path to a directory where the results will be saved.',
86
+ )
87
+ MODEL_DIR = flags.DEFINE_string(
88
+ 'model_dir',
89
+ _DEFAULT_MODEL_DIR.as_posix(),
90
+ 'Path to the model to use for inference.',
91
+ )
92
+
93
+ # Control which stages to run.
94
+ _RUN_DATA_PIPELINE = flags.DEFINE_bool(
95
+ 'run_data_pipeline',
96
+ True,
97
+ 'Whether to run the data pipeline on the fold inputs.',
98
+ )
99
+ _RUN_INFERENCE = flags.DEFINE_bool(
100
+ 'run_inference',
101
+ True,
102
+ 'Whether to run inference on the fold inputs.',
103
+ )
104
+
105
+
106
+ _DEFAULT_MMSEQS_OPTIONS ='--num-iterations 1 --db-load-mode 2 -a --max-seqs 10000 --prefilter-mode 1'
107
+ _DEFAULT_R2MSA_OPTIONS ='--filter-msa 1 --filter-min-enable 1000 --diff 3000 --qid 0.0,0.2,0.4,0.6,0.8,1.0 --qsc 0 --max-seq-id 0.95'
108
+ _AF3_DIR = _PROJECT_ROOT / 'flax_model' / 'alphafold3'
109
+ _HMMER_BIN_DIR = _AF3_DIR / '_tools' / 'hmmer' / 'bin'
110
+
111
+
112
+ def _resolve_af3_tool(binary_name: str) -> str | None:
113
+ local_binary = _HMMER_BIN_DIR / binary_name
114
+ if local_binary.exists():
115
+ return str(local_binary)
116
+ return shutil.which(binary_name)
117
+
118
+
119
+ def _available_cpu_count() -> int:
120
+ if hasattr(os, 'sched_getaffinity'):
121
+ return len(os.sched_getaffinity(0))
122
+ return multiprocessing.cpu_count()
123
+
124
+
125
+ _USE_MMSEQS = flags.DEFINE_bool(
126
+ 'use_mmseqs',
127
+ False,
128
+ 'Whether to use mmseqs for protein MSA search',
129
+ )
130
+ _USE_MMSEQS_GPU = flags.DEFINE_bool(
131
+ 'use_mmseqs_gpu',
132
+ False,
133
+ 'Whether to use mmseqs GPU for protein MSA search',
134
+ )
135
+ _MMSEQS_OPTIONS = flags.DEFINE_string(
136
+ 'mmseqs_options',
137
+ _DEFAULT_MMSEQS_OPTIONS,
138
+ 'mmseqs serach options',
139
+ )
140
+ _R2MSA_OPTIONS = flags.DEFINE_string(
141
+ 'result2msa_options',
142
+ _DEFAULT_R2MSA_OPTIONS,
143
+ 'mmseqs result2msa options',
144
+ )
145
+
146
+
147
+ # Binary paths.
148
+ _JACKHMMER_BINARY_PATH = flags.DEFINE_string(
149
+ 'jackhmmer_binary_path',
150
+ _resolve_af3_tool('jackhmmer'),
151
+ 'Path to the Jackhmmer binary.',
152
+ )
153
+ _NHMMER_BINARY_PATH = flags.DEFINE_string(
154
+ 'nhmmer_binary_path',
155
+ _resolve_af3_tool('nhmmer'),
156
+ 'Path to the Nhmmer binary.',
157
+ )
158
+ _HMMALIGN_BINARY_PATH = flags.DEFINE_string(
159
+ 'hmmalign_binary_path',
160
+ _resolve_af3_tool('hmmalign'),
161
+ 'Path to the Hmmalign binary.',
162
+ )
163
+ _HMMSEARCH_BINARY_PATH = flags.DEFINE_string(
164
+ 'hmmsearch_binary_path',
165
+ _resolve_af3_tool('hmmsearch'),
166
+ 'Path to the Hmmsearch binary.',
167
+ )
168
+ _HMMBUILD_BINARY_PATH = flags.DEFINE_string(
169
+ 'hmmbuild_binary_path',
170
+ _resolve_af3_tool('hmmbuild'),
171
+ 'Path to the Hmmbuild binary.',
172
+ )
173
+ _MMSEQS_BINARY_PATH = flags.DEFINE_string(
174
+ 'mmseqs_binary_path',
175
+ shutil.which('mmseqs'),
176
+ 'Path to the mmseqs binary.',
177
+ )
178
+
179
+ # Database paths.
180
+ DB_DIR = flags.DEFINE_multi_string(
181
+ 'db_dir',
182
+ (_DEFAULT_DB_DIR.as_posix(),),
183
+ 'Path to the directory containing the databases. Can be specified multiple'
184
+ ' times to search multiple directories in order.',
185
+ )
186
+ MMSEQS_DB_DIR = flags.DEFINE_multi_string(
187
+ 'mmseqs_db_dir',
188
+ (_DEFAULT_MMSEQS_DB_DIR.as_posix(),),
189
+ 'Path to the directory containing the mmseqs databases. Can be specified multiple'
190
+ ' times to search multiple directories in order.',
191
+ )
192
+
193
+ _SMALL_BFD_DATABASE_PATH = flags.DEFINE_string(
194
+ 'small_bfd_database_path',
195
+ '${DB_DIR}/bfd-first_non_consensus_sequences.fasta',
196
+ 'Small BFD database path, used for protein MSA search.',
197
+ )
198
+ _SMALL_BFD_Z_VALUE = flags.DEFINE_integer(
199
+ 'small_bfd_z_value',
200
+ None,
201
+ 'The Z-value representing the database size in number of sequences for'
202
+ ' E-value calculation. Must be set for sharded databases.',
203
+ lower_bound=0,
204
+ )
205
+ _MGNIFY_DATABASE_PATH = flags.DEFINE_string(
206
+ 'mgnify_database_path',
207
+ '${DB_DIR}/mgy_clusters_2022_05.fa',
208
+ 'Mgnify database path, used for protein MSA search.',
209
+ )
210
+ _MGNIFY_Z_VALUE = flags.DEFINE_integer(
211
+ 'mgnify_z_value',
212
+ None,
213
+ 'The Z-value representing the database size in number of sequences for'
214
+ ' E-value calculation. Must be set for sharded databases.',
215
+ lower_bound=0,
216
+ )
217
+ _UNIPROT_CLUSTER_ANNOT_DATABASE_PATH = flags.DEFINE_string(
218
+ 'uniprot_cluster_annot_database_path',
219
+ '${DB_DIR}/uniprot_all_2021_04.fa',
220
+ 'UniProt database path, used for protein paired MSA search.',
221
+ )
222
+ _UNIPROT_CLUSTER_ANNOT_Z_VALUE = flags.DEFINE_integer(
223
+ 'uniprot_cluster_annot_z_value',
224
+ None,
225
+ 'The Z-value representing the database size in number of sequences for'
226
+ ' E-value calculation. Must be set for sharded databases.',
227
+ lower_bound=0,
228
+ )
229
+ _UNIREF90_DATABASE_PATH = flags.DEFINE_string(
230
+ 'uniref90_database_path',
231
+ '${DB_DIR}/uniref90_2022_05.fa',
232
+ 'UniRef90 database path, used for MSA search. The MSA obtained by '
233
+ 'searching it is used to construct the profile for template search.',
234
+ )
235
+ _UNIREF90_Z_VALUE = flags.DEFINE_integer(
236
+ 'uniref90_z_value',
237
+ None,
238
+ 'The Z-value representing the database size in number of sequences for'
239
+ ' E-value calculation. Must be set for sharded databases.',
240
+ lower_bound=0,
241
+ )
242
+ _NTRNA_DATABASE_PATH = flags.DEFINE_string(
243
+ 'ntrna_database_path',
244
+ '${DB_DIR}/nt_rna_2023_02_23_clust_seq_id_90_cov_80_rep_seq.fasta',
245
+ 'NT-RNA database path, used for RNA MSA search.',
246
+ )
247
+ _NTRNA_Z_VALUE = flags.DEFINE_float(
248
+ 'ntrna_z_value',
249
+ None,
250
+ 'The Z-value representing the database size in megabases for E-value'
251
+ ' calculation. Must be set for sharded databases.',
252
+ lower_bound=0.0,
253
+ )
254
+ _RFAM_DATABASE_PATH = flags.DEFINE_string(
255
+ 'rfam_database_path',
256
+ '${DB_DIR}/rfam_14_9_clust_seq_id_90_cov_80_rep_seq.fasta',
257
+ 'Rfam database path, used for RNA MSA search.',
258
+ )
259
+ _RFAM_Z_VALUE = flags.DEFINE_float(
260
+ 'rfam_z_value',
261
+ None,
262
+ 'The Z-value representing the database size in megabases for E-value'
263
+ ' calculation. Must be set for sharded databases.',
264
+ lower_bound=0.0,
265
+ )
266
+ _RNA_CENTRAL_DATABASE_PATH = flags.DEFINE_string(
267
+ 'rna_central_database_path',
268
+ '${DB_DIR}/rnacentral_active_seq_id_90_cov_80_linclust.fasta',
269
+ 'RNAcentral database path, used for RNA MSA search.',
270
+ )
271
+ _RNA_CENTRAL_Z_VALUE = flags.DEFINE_float(
272
+ 'rna_central_z_value',
273
+ None,
274
+ 'The Z-value representing the database size in megabases for E-value'
275
+ ' calculation. Must be set for sharded databases.',
276
+ lower_bound=0.0,
277
+ )
278
+ _PDB_DATABASE_PATH = flags.DEFINE_string(
279
+ 'pdb_database_path',
280
+ '${DB_DIR}/mmcif_files',
281
+ 'PDB database directory with mmCIF files path, used for template search.',
282
+ )
283
+ _SEQRES_DATABASE_PATH = flags.DEFINE_string(
284
+ 'seqres_database_path',
285
+ '${DB_DIR}/pdb_seqres_2022_09_28.fasta',
286
+ 'PDB sequence database path, used for template search.',
287
+ )
288
+
289
+ # MMSEQS Database paths.
290
+ _MMSEQS_SMALL_BFD_DATABASE_PATH = flags.DEFINE_string(
291
+ 'mmseqs_small_bfd_database_path',
292
+ '${MMSEQS_DB_DIR}/small_bfd_db',
293
+ 'Small BFD database path, used for protein MSA search.',
294
+ )
295
+ _MMSEQS_MGNIFY_DATABASE_PATH = flags.DEFINE_string(
296
+ 'mmseqs_mgnify_database_path',
297
+ '${MMSEQS_DB_DIR}/mgnify_db',
298
+ 'Mgnify database path, used for protein MSA search.',
299
+ )
300
+ _MMSEQS_UNIPROT_CLUSTER_ANNOT_DATABASE_PATH = flags.DEFINE_string(
301
+ 'mmseqs_uniprot_cluster_annot_database_path',
302
+ '${MMSEQS_DB_DIR}/uniprot_cluster_annot_db',
303
+ 'UniProt database path, used for protein paired MSA search.',
304
+ )
305
+ _MMSEQS_UNIREF90_DATABASE_PATH = flags.DEFINE_string(
306
+ 'mmseqs_uniref90_database_path',
307
+ '${MMSEQS_DB_DIR}/uniref90_db',
308
+ 'UniRef90 database path, used for MSA search. ',
309
+ )
310
+
311
+ _JACKHMMER_MAX_THREADS = flags.DEFINE_integer(
312
+ 'jackhmmer_max_threads',
313
+ None,
314
+ 'Maximum number of threads used when running sharded databases. If unset,'
315
+ ' defaults to None (no limit).',
316
+ lower_bound=1,
317
+ )
318
+ # Number of CPUs to use for MSA tools.
319
+ _JACKHMMER_N_CPU = flags.DEFINE_integer(
320
+ 'jackhmmer_n_cpu',
321
+ # Unfortunately, os.process_cpu_count() is only available in Python 3.13+.
322
+ min(_available_cpu_count(), 8),
323
+ 'Number of CPUs to use for Jackhmmer. Defaults to min(cpu_count, 8). Going'
324
+ ' above 8 CPUs provides very little additional speedup.',
325
+ lower_bound=0,
326
+ )
327
+ _JACKHMMER_MAX_PARALLEL_SHARDS = flags.DEFINE_integer(
328
+ 'jackhmmer_max_parallel_shards',
329
+ None,
330
+ 'Maximum number of shards to search against in parallel. If unset, one'
331
+ ' Jackhmmer instance will be run per shard. Only applicable if the'
332
+ ' database is sharded.',
333
+ lower_bound=1,
334
+ )
335
+ _NHMMER_N_CPU = flags.DEFINE_integer(
336
+ 'nhmmer_n_cpu',
337
+ # Unfortunately, os.process_cpu_count() is only available in Python 3.13+.
338
+ min(_available_cpu_count(), 8),
339
+ 'Number of CPUs to use for Nhmmer. Defaults to min(cpu_count, 8). Going'
340
+ ' above 8 CPUs provides very little additional speedup.',
341
+ lower_bound=0,
342
+ )
343
+ _NHMMER_MAX_PARALLEL_SHARDS = flags.DEFINE_integer(
344
+ 'nhmmer_max_parallel_shards',
345
+ None,
346
+ 'Maximum number of shards to search against in parallel. If unset, one'
347
+ ' Nhmmer instance will be run per shard. Only applicable if the'
348
+ ' database is sharded.',
349
+ lower_bound=1,
350
+ )
351
+ _NHMMER_MAX_THREADS = flags.DEFINE_integer(
352
+ 'nhmmer_max_threads',
353
+ None,
354
+ 'Maximum number of threads used when running sharded databases. If unset,'
355
+ ' defaults to None (no limit).',
356
+ lower_bound=1,
357
+ )
358
+ # Data pipeline configuration.
359
+ _RESOLVE_MSA_OVERLAPS = flags.DEFINE_bool(
360
+ 'resolve_msa_overlaps',
361
+ True,
362
+ 'Whether to deduplicate unpaired MSA against paired MSA. The default'
363
+ ' behaviour matches the method described in the AlphaFold 3 paper. Set this'
364
+ ' to false if providing custom paired MSA using the unpaired MSA field to'
365
+ ' keep it exactly as is as deduplication against the paired MSA could break'
366
+ ' the manually crafted pairing between MSA sequences.',
367
+ )
368
+ _MMSEQS_N_CPU = flags.DEFINE_integer(
369
+ 'mmseqs_n_cpu',
370
+ min(multiprocessing.cpu_count(), 8),
371
+ 'Number of CPUs to use for MMseqs. Default to min(cpu_count, 8). Going'
372
+ ' beyond 8 CPUs provides very little additional speedup.',
373
+ )
374
+
375
+ # Template search configuration.
376
+ _MAX_TEMPLATE_DATE = flags.DEFINE_string(
377
+ 'max_template_date',
378
+ '2021-09-30', # By default, use the date from the AlphaFold 3 paper.
379
+ 'Maximum template release date to consider. Format: YYYY-MM-DD. All'
380
+ ' templates released after this date will be ignored. Controls also whether'
381
+ ' to allow use of model coordinates for a chemical component from the CCD'
382
+ ' if RDKit conformer generation fails and the component does not have ideal'
383
+ ' coordinates set. Only for components that have been released before this'
384
+ ' date the model coordinates can be used as a fallback.',
385
+ )
386
+
387
+ _CONFORMER_MAX_ITERATIONS = flags.DEFINE_integer(
388
+ 'conformer_max_iterations',
389
+ None, # Default to RDKit default parameters value.
390
+ 'Optional override for maximum number of iterations to run for RDKit '
391
+ 'conformer search.',
392
+ lower_bound=0,
393
+ )
394
+
395
+ # JAX inference performance tuning.
396
+ _JAX_COMPILATION_CACHE_DIR = flags.DEFINE_string(
397
+ 'jax_compilation_cache_dir',
398
+ None,
399
+ 'Path to a directory for the JAX compilation cache.',
400
+ )
401
+ _GPU_DEVICE = flags.DEFINE_integer(
402
+ 'gpu_device',
403
+ 0,
404
+ 'Optional override for the GPU device to use for inference, uses zero-based'
405
+ ' indexing. Defaults to the 0th GPU on the system. Useful on multi-GPU'
406
+ ' systems to pin each run to a specific GPU. Note that if GPUs are already'
407
+ ' pre-filtered by the environment (e.g. by using CUDA_VISIBLE_DEVICES),'
408
+ ' this flag refers to the GPU index after the filtering has been done.',
409
+ )
410
+ _BUCKETS = flags.DEFINE_list(
411
+ 'buckets',
412
+ # pyformat: disable
413
+ ['256', '512', '768', '1024', '1280', '1536', '2048', '2560', '3072',
414
+ '3584', '4096', '4608', '5120'],
415
+ # pyformat: enable
416
+ 'Strictly increasing order of token sizes for which to cache compilations.'
417
+ ' For any input with more tokens than the largest bucket size, a new bucket'
418
+ ' is created for exactly that number of tokens.',
419
+ )
420
+ _FLASH_ATTENTION_IMPLEMENTATION = flags.DEFINE_enum(
421
+ 'flash_attention_implementation',
422
+ default='triton',
423
+ enum_values=['triton', 'cudnn', 'xla', 'cutlass',],
424
+ help=(
425
+ "Flash attention implementation to use. 'triton' and 'cudnn' uses a"
426
+ ' Triton and cuDNN flash attention implementation, respectively. The'
427
+ ' Triton kernel is fastest and has been tested more thoroughly. The'
428
+ " Triton and cuDNN kernels require Ampere GPUs or later. 'xla' uses an"
429
+ ' XLA attention implementation (no flash attention) and is portable'
430
+ ' across GPU devices.'
431
+ ),
432
+ )
433
+ _NUM_RECYCLES = flags.DEFINE_integer(
434
+ 'num_recycles',
435
+ 10,
436
+ 'Number of recycles to use during inference.',
437
+ lower_bound=1,
438
+ )
439
+ _NUM_DIFFUSION_SAMPLES = flags.DEFINE_integer(
440
+ 'num_diffusion_samples',
441
+ 5,
442
+ 'Number of diffusion samples to generate.',
443
+ lower_bound=1,
444
+ )
445
+ _NUM_SEEDS = flags.DEFINE_integer(
446
+ 'num_seeds',
447
+ None,
448
+ 'Number of seeds to use for inference. If set, only a single seed must be'
449
+ ' provided in the input JSON. AlphaFold 3 will then generate random seeds'
450
+ ' in sequence, starting from the single seed specified in the input JSON.'
451
+ ' The full input JSON produced by AlphaFold 3 will include the generated'
452
+ ' random seeds. If not set, AlphaFold 3 will use the seeds as provided in'
453
+ ' the input JSON.',
454
+ lower_bound=1,
455
+ )
456
+
457
+ # Output controls.
458
+ _SAVE_EMBEDDINGS = flags.DEFINE_bool(
459
+ 'save_embeddings',
460
+ False,
461
+ 'Whether to save the final trunk single and pair embeddings in the output.'
462
+ ' Note that the embeddings are large float16 arrays: num_tokens * 384'
463
+ ' + num_tokens * num_tokens * 128.',
464
+ )
465
+ _SAVE_DISTOGRAM = flags.DEFINE_bool(
466
+ 'save_distogram',
467
+ False,
468
+ 'Whether to save the final distogram in the output. Note that the distogram'
469
+ ' is a large float16 array: num_tokens * num_tokens * 64.',
470
+ )
471
+ _FORCE_OUTPUT_DIR = flags.DEFINE_bool(
472
+ 'force_output_dir',
473
+ False,
474
+ 'Whether to force the output directory to be used even if it already exists'
475
+ ' and is non-empty. Useful to set this to True to run the data pipeline and'
476
+ ' the inference separately, but use the same output directory.',
477
+ )
478
+
479
+
480
+ def make_model_config(
481
+ *,
482
+ flash_attention_implementation: attention.Implementation = 'triton',
483
+ num_diffusion_samples: int = 5,
484
+ num_recycles: int = 10,
485
+ return_embeddings: bool = False,
486
+ return_distogram: bool = False,
487
+ ) -> model.Model.Config:
488
+ """Returns a model config with some defaults overridden."""
489
+ config = model.Model.Config()
490
+ config.global_config.flash_attention_implementation = (
491
+ flash_attention_implementation
492
+ )
493
+ config.heads.diffusion.eval.num_samples = num_diffusion_samples
494
+ config.num_recycles = num_recycles
495
+ config.return_embeddings = return_embeddings
496
+ config.return_distogram = return_distogram
497
+ return config
498
+
499
+
500
+ class ModelRunner:
501
+ """Helper class to run structure prediction stages."""
502
+
503
+ def __init__(
504
+ self,
505
+ config: model.Model.Config,
506
+ device: jax.Device,
507
+ model_dir: pathlib.Path,
508
+ ):
509
+ self._model_config = config
510
+ self._device = device
511
+ self._model_dir = model_dir
512
+
513
+ @functools.cached_property
514
+ def model_params(self) -> hk.Params:
515
+ """Loads model parameters from the model directory."""
516
+ return params.get_model_haiku_params(model_dir=self._model_dir)
517
+
518
+ @functools.cached_property
519
+ def _model(
520
+ self,
521
+ ) -> Callable[[jnp.ndarray, features.BatchDict], model.ModelResult]:
522
+ """Loads model parameters and returns a jitted model forward pass."""
523
+
524
+ @hk.transform
525
+ def forward_fn(batch):
526
+ return model.Model(self._model_config)(batch)
527
+
528
+ return functools.partial(
529
+ jax.jit(forward_fn.apply, device=self._device), self.model_params
530
+ )
531
+
532
+ def run_inference(
533
+ self, featurised_example: features.BatchDict, rng_key: jnp.ndarray
534
+ ) -> model.ModelResult:
535
+ """Computes a forward pass of the model on a featurised example."""
536
+ featurised_example = jax.device_put(
537
+ jax.tree_util.tree_map(
538
+ jnp.asarray, utils.remove_invalidly_typed_feats(featurised_example)
539
+ ),
540
+ self._device,
541
+ )
542
+
543
+ result = self._model(rng_key, featurised_example)
544
+ result = jax.tree.map(np.asarray, result)
545
+ result = jax.tree.map(
546
+ lambda x: x.astype(jnp.float32) if x.dtype == jnp.bfloat16 else x,
547
+ result,
548
+ )
549
+ result = dict(result)
550
+ identifier = self.model_params['__meta__']['__identifier__'].tobytes()
551
+ result['__identifier__'] = identifier
552
+ return result
553
+
554
+ def extract_inference_results(
555
+ self,
556
+ batch: features.BatchDict,
557
+ result: model.ModelResult,
558
+ target_name: str,
559
+ ) -> list[model.InferenceResult]:
560
+ """Extracts inference results from model outputs."""
561
+ return list(
562
+ model.Model.get_inference_result(
563
+ batch=batch, result=result, target_name=target_name
564
+ )
565
+ )
566
+
567
+ def extract_embeddings(
568
+ self, result: model.ModelResult, num_tokens: int
569
+ ) -> dict[str, np.ndarray] | None:
570
+ """Extracts embeddings from model outputs."""
571
+ embeddings = {}
572
+ if 'single_embeddings' in result:
573
+ embeddings['single_embeddings'] = result['single_embeddings'][
574
+ :num_tokens
575
+ ].astype(np.float16)
576
+ if 'pair_embeddings' in result:
577
+ embeddings['pair_embeddings'] = result['pair_embeddings'][
578
+ :num_tokens, :num_tokens
579
+ ].astype(np.float16)
580
+ return embeddings or None
581
+
582
+ def extract_distogram(
583
+ self, result: model.ModelResult, num_tokens: int
584
+ ) -> np.ndarray | None:
585
+ """Extracts distogram from model outputs."""
586
+ if 'distogram' not in result['distogram']:
587
+ return None
588
+ distogram = result['distogram']['distogram'][:num_tokens, :num_tokens, :]
589
+ return distogram
590
+
591
+
592
+ @dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
593
+ class ResultsForSeed:
594
+ """Stores the inference results (diffusion samples) for a single seed.
595
+
596
+ Attributes:
597
+ seed: The seed used to generate the samples.
598
+ inference_results: The inference results, one per sample.
599
+ full_fold_input: The fold input that must also include the results of
600
+ running the data pipeline - MSA and templates.
601
+ embeddings: The final trunk single and pair embeddings, if requested.
602
+ distogram: The token distance histogram, if requested.
603
+ """
604
+
605
+ seed: int
606
+ inference_results: Sequence[model.InferenceResult]
607
+ full_fold_input: folding_input.Input
608
+ embeddings: dict[str, np.ndarray] | None = None
609
+ distogram: np.ndarray | None = None
610
+
611
+
612
+ def predict_structure(
613
+ fold_input: folding_input.Input,
614
+ model_runner: ModelRunner,
615
+ buckets: Sequence[int] | None = None,
616
+ ref_max_modified_date: datetime.date | None = None,
617
+ conformer_max_iterations: int | None = None,
618
+ resolve_msa_overlaps: bool = True,
619
+ ) -> Sequence[ResultsForSeed]:
620
+ """Runs the full inference pipeline to predict structures for each seed."""
621
+
622
+ print(f'Featurising data with {len(fold_input.rng_seeds)} seed(s)...')
623
+ featurisation_start_time = time.time()
624
+ ccd = chemical_components.Ccd(user_ccd=fold_input.user_ccd)
625
+ featurised_examples = featurisation.featurise_input(
626
+ fold_input=fold_input,
627
+ buckets=buckets,
628
+ ccd=ccd,
629
+ verbose=True,
630
+ ref_max_modified_date=ref_max_modified_date,
631
+ conformer_max_iterations=conformer_max_iterations,
632
+ resolve_msa_overlaps=resolve_msa_overlaps,
633
+ )
634
+ print(
635
+ f'Featurising data with {len(fold_input.rng_seeds)} seed(s) took'
636
+ f' {time.time() - featurisation_start_time:.2f} seconds.'
637
+ )
638
+ print(
639
+ 'Running model inference and extracting output structure samples with'
640
+ f' {len(fold_input.rng_seeds)} seed(s)...'
641
+ )
642
+ all_inference_start_time = time.time()
643
+ all_inference_results = []
644
+ for seed, example in zip(fold_input.rng_seeds, featurised_examples):
645
+ print(f'Running model inference with seed {seed}...')
646
+ inference_start_time = time.time()
647
+ rng_key = jax.random.PRNGKey(seed)
648
+ result = model_runner.run_inference(example, rng_key)
649
+ print(
650
+ f'Running model inference with seed {seed} took'
651
+ f' {time.time() - inference_start_time:.2f} seconds.'
652
+ )
653
+ print(f'Extracting inference results with seed {seed}...')
654
+ extract_structures = time.time()
655
+ inference_results = model_runner.extract_inference_results(
656
+ batch=example, result=result, target_name=fold_input.name
657
+ )
658
+ num_tokens = len(inference_results[0].metadata['token_chain_ids'])
659
+ embeddings = model_runner.extract_embeddings(
660
+ result=result, num_tokens=num_tokens
661
+ )
662
+ distogram = model_runner.extract_distogram(
663
+ result=result, num_tokens=num_tokens
664
+ )
665
+ print(
666
+ f'Extracting {len(inference_results)} inference samples with'
667
+ f' seed {seed} took {time.time() - extract_structures:.2f} seconds.'
668
+ )
669
+
670
+ all_inference_results.append(
671
+ ResultsForSeed(
672
+ seed=seed,
673
+ inference_results=inference_results,
674
+ full_fold_input=fold_input,
675
+ embeddings=embeddings,
676
+ distogram=distogram,
677
+ )
678
+ )
679
+ print(
680
+ 'Running model inference and extracting output structures with'
681
+ f' {len(fold_input.rng_seeds)} seed(s) took'
682
+ f' {time.time() - all_inference_start_time:.2f} seconds.'
683
+ )
684
+ return all_inference_results
685
+
686
+
687
+ def write_fold_input_json(
688
+ fold_input: folding_input.Input,
689
+ output_dir: os.PathLike[str] | str,
690
+ ) -> None:
691
+ """Writes the input JSON to the output directory."""
692
+ os.makedirs(output_dir, exist_ok=True)
693
+ path = os.path.join(output_dir, f'{fold_input.sanitised_name()}_data.json')
694
+ print(f'Writing model input JSON to {path}')
695
+ with open(path, 'wt') as f:
696
+ f.write(fold_input.to_json())
697
+
698
+
699
+ def write_outputs(
700
+ all_inference_results: Sequence[ResultsForSeed],
701
+ output_dir: os.PathLike[str] | str,
702
+ job_name: str,
703
+ ) -> None:
704
+ """Writes outputs to the specified output directory."""
705
+ ranking_scores = []
706
+ max_ranking_score = None
707
+ max_ranking_result = None
708
+ try:
709
+ output_terms = (
710
+ pathlib.Path(af3_cpp.__file__).parent / 'OUTPUT_TERMS_OF_USE.md'
711
+ ).read_text()
712
+ except FileNotFoundError:
713
+ output_terms = None
714
+ os.makedirs(output_dir, exist_ok=True)
715
+ for results_for_seed in all_inference_results:
716
+ seed = results_for_seed.seed
717
+ for sample_idx, result in enumerate(results_for_seed.inference_results):
718
+ sample_dir = os.path.join(output_dir, f'seed-{seed}_sample-{sample_idx}')
719
+ os.makedirs(sample_dir, exist_ok=True)
720
+ post_processing.write_output(
721
+ inference_result=result,
722
+ output_dir=sample_dir,
723
+ name=f'{job_name}_seed-{seed}_sample-{sample_idx}',
724
+ )
725
+ ranking_score = float(result.metadata['ranking_score'])
726
+ ranking_scores.append((seed, sample_idx, ranking_score))
727
+ if max_ranking_score is None or ranking_score > max_ranking_score:
728
+ max_ranking_score = ranking_score
729
+ max_ranking_result = result
730
+
731
+ if embeddings := results_for_seed.embeddings:
732
+ embeddings_dir = os.path.join(output_dir, f'seed-{seed}_embeddings')
733
+ os.makedirs(embeddings_dir, exist_ok=True)
734
+ post_processing.write_embeddings(
735
+ embeddings=embeddings,
736
+ output_dir=embeddings_dir,
737
+ name=f'{job_name}_seed-{seed}',
738
+ )
739
+
740
+ if (distogram := results_for_seed.distogram) is not None:
741
+ distogram_dir = os.path.join(output_dir, f'seed-{seed}_distogram')
742
+ os.makedirs(distogram_dir, exist_ok=True)
743
+ distogram_path = os.path.join(
744
+ distogram_dir, f'{job_name}_seed-{seed}_distogram.npz'
745
+ )
746
+ with open(distogram_path, 'wb') as f:
747
+ np.savez_compressed(f, distogram=distogram.astype(np.float16))
748
+
749
+ if max_ranking_result is not None: # True iff ranking_scores non-empty.
750
+ post_processing.write_output(
751
+ inference_result=max_ranking_result,
752
+ output_dir=output_dir,
753
+ # The output terms of use are the same for all seeds/samples.
754
+ terms_of_use=output_terms,
755
+ name=job_name,
756
+ )
757
+ # Save csv of ranking scores with seeds and sample indices, to allow easier
758
+ # comparison of ranking scores across different runs.
759
+ with open(
760
+ os.path.join(output_dir, f'{job_name}_ranking_scores.csv'), 'wt'
761
+ ) as f:
762
+ writer = csv.writer(f)
763
+ writer.writerow(['seed', 'sample', 'ranking_score'])
764
+ writer.writerows(ranking_scores)
765
+
766
+
767
+ def replace_db_dir(path_with_db_dir: str, db_dirs: Sequence[str]) -> str:
768
+ """Replaces the DB_DIR placeholder in a path with the given DB_DIR."""
769
+ template = string.Template(path_with_db_dir)
770
+ if 'DB_DIR' in template.get_identifiers():
771
+ for db_dir in db_dirs:
772
+ path = template.substitute(DB_DIR=db_dir)
773
+ if os.path.exists(path):
774
+ return path
775
+ raise FileNotFoundError(
776
+ f'{path_with_db_dir} with ${{DB_DIR}} not found in any of {db_dirs}.'
777
+ )
778
+ if not os.path.exists(path_with_db_dir):
779
+ raise FileNotFoundError(f'{path_with_db_dir} does not exist.')
780
+ return path_with_db_dir
781
+
782
+
783
+ def replace_mmseqs_db_dir(
784
+ path_with_db_dir: str,
785
+ db_dirs: Sequence[str],
786
+ mmseqs_db_dirs: Sequence[str]
787
+ ) -> str:
788
+ """Replaces the MMSEQS_DB_DIR placeholder in a path with the given MMSEQS_DB_DIR.
789
+
790
+ Args:
791
+ path_with_db_dir: Path containing MMSEQS_DB_DIR placeholder
792
+ db_dirs: List of database directories
793
+ mmseqs_db_dirs: List of mmseqs database directories
794
+ use_gpu: Whether to use GPU version of mmseqs databases
795
+
796
+ Returns:
797
+ The expanded path if found, otherwise raises FileNotFoundError
798
+ """
799
+ template = string.Template(path_with_db_dir)
800
+
801
+ is_jackhmmer_db = any(
802
+ path_with_db_dir.endswith(db) for db in [
803
+ 'bfd-first_non_consensus_sequences.fasta',
804
+ 'mgy_clusters_2022_05.fa',
805
+ 'uniprot_all_2021_04.fa',
806
+ 'uniref90_2022_05.fa'
807
+ ]
808
+ )
809
+
810
+ if 'MMSEQS_DB_DIR' in template.get_identifiers():
811
+ db_suffixes = [
812
+ 'small_bfd_db',
813
+ 'mgnify_db',
814
+ 'uniprot_cluster_annot_db',
815
+ 'uniref90_db'
816
+ ]
817
+
818
+ is_mmseqs_db = any(
819
+ path_with_db_dir.endswith(suffix) for suffix in db_suffixes
820
+ )
821
+
822
+ for mmseqs_db_dir in mmseqs_db_dirs:
823
+ path = template.substitute(MMSEQS_DB_DIR=mmseqs_db_dir)
824
+ if is_mmseqs_db:
825
+ if os.path.exists(path):
826
+ return path
827
+ else:
828
+ return path
829
+
830
+ if is_mmseqs_db:
831
+ raise FileNotFoundError(
832
+ f'{path_with_db_dir} with ${{MMSEQS_DB_DIR}} not found in any of {mmseqs_db_dirs}.'
833
+ )
834
+ return template.substitute(MMSEQS_DB_DIR=mmseqs_db_dirs[0])
835
+
836
+ if 'DB_DIR' in template.get_identifiers():
837
+ for db_dir in db_dirs:
838
+ path = template.substitute(DB_DIR=db_dir)
839
+ if is_jackhmmer_db:
840
+ return path
841
+ if os.path.exists(path):
842
+ return path
843
+ if is_jackhmmer_db:
844
+ return template.substitute(DB_DIR=db_dirs[0])
845
+ raise FileNotFoundError(
846
+ f'{path_with_db_dir} with ${{DB_DIR}} not found in any of {db_dirs}.'
847
+ )
848
+ if not is_jackhmmer_db and not os.path.exists(path_with_db_dir):
849
+ raise FileNotFoundError(f'{path_with_db_dir} does not exist.')
850
+ return path_with_db_dir
851
+
852
+
853
+ @overload
854
+ def process_fold_input(
855
+ fold_input: folding_input.Input,
856
+ data_pipeline_config: pipeline.DataPipelineConfig | None,
857
+ model_runner: None,
858
+ output_dir: os.PathLike[str] | str,
859
+ buckets: Sequence[int] | None = None,
860
+ ref_max_modified_date: datetime.date | None = None,
861
+ conformer_max_iterations: int | None = None,
862
+ resolve_msa_overlaps: bool = True,
863
+ force_output_dir: bool = False,
864
+ ) -> folding_input.Input:
865
+ ...
866
+
867
+
868
+ @overload
869
+ def process_fold_input(
870
+ fold_input: folding_input.Input,
871
+ data_pipeline_config: pipeline.DataPipelineConfig | None,
872
+ model_runner: ModelRunner,
873
+ output_dir: os.PathLike[str] | str,
874
+ buckets: Sequence[int] | None = None,
875
+ ref_max_modified_date: datetime.date | None = None,
876
+ conformer_max_iterations: int | None = None,
877
+ resolve_msa_overlaps: bool = True,
878
+ force_output_dir: bool = False,
879
+ ) -> Sequence[ResultsForSeed]:
880
+ ...
881
+
882
+
883
+ def process_fold_input(
884
+ fold_input: folding_input.Input,
885
+ data_pipeline_config: pipeline.DataPipelineConfig | None,
886
+ model_runner: ModelRunner | None,
887
+ output_dir: os.PathLike[str] | str,
888
+ buckets: Sequence[int] | None = None,
889
+ ref_max_modified_date: datetime.date | None = None,
890
+ conformer_max_iterations: int | None = None,
891
+ resolve_msa_overlaps: bool = True,
892
+ force_output_dir: bool = False,
893
+ ) -> folding_input.Input | Sequence[ResultsForSeed]:
894
+ """Runs data pipeline and/or inference on a single fold input.
895
+
896
+ Args:
897
+ fold_input: Fold input to process.
898
+ data_pipeline_config: Data pipeline config to use. If None, skip the data
899
+ pipeline.
900
+ model_runner: Model runner to use. If None, skip inference.
901
+ output_dir: Output directory to write to.
902
+ buckets: Bucket sizes to pad the data to, to avoid excessive re-compilation
903
+ of the model. If None, calculate the appropriate bucket size from the
904
+ number of tokens. If not None, must be a sequence of at least one integer,
905
+ in strictly increasing order. Will raise an error if the number of tokens
906
+ is more than the largest bucket size.
907
+ ref_max_modified_date: Optional maximum date that controls whether to allow
908
+ use of model coordinates for a chemical component from the CCD if RDKit
909
+ conformer generation fails and the component does not have ideal
910
+ coordinates set. Only for components that have been released before this
911
+ date the model coordinates can be used as a fallback.
912
+ conformer_max_iterations: Optional override for maximum number of iterations
913
+ to run for RDKit conformer search.
914
+ resolve_msa_overlaps: Whether to deduplicate unpaired MSA against paired
915
+ MSA. The default behaviour matches the method described in the AlphaFold 3
916
+ paper. Set this to false if providing custom paired MSA using the unpaired
917
+ MSA field to keep it exactly as is as deduplication against the paired MSA
918
+ could break the manually crafted pairing between MSA sequences.
919
+ force_output_dir: If True, do not create a new output directory even if the
920
+ existing one is non-empty. Instead use the existing output directory and
921
+ potentially overwrite existing files. If False, create a new timestamped
922
+ output directory instead if the existing one is non-empty.
923
+
924
+ Returns:
925
+ The processed fold input, or the inference results for each seed.
926
+
927
+ Raises:
928
+ ValueError: If the fold input has no chains.
929
+ """
930
+ print(f'\nRunning fold job {fold_input.name}...')
931
+
932
+ if not fold_input.chains:
933
+ raise ValueError('Fold input has no chains.')
934
+
935
+ if (
936
+ not force_output_dir
937
+ and os.path.exists(output_dir)
938
+ and os.listdir(output_dir)
939
+ ):
940
+ new_output_dir = (
941
+ f'{output_dir}_{datetime.datetime.now().strftime("%Y%m%d_%H%M%S")}'
942
+ )
943
+ print(
944
+ f'Output will be written in {new_output_dir} since {output_dir} is'
945
+ ' non-empty.'
946
+ )
947
+ output_dir = new_output_dir
948
+ else:
949
+ print(f'Output will be written in {output_dir}')
950
+
951
+ # Create model loader callback function
952
+ def load_model_callback():
953
+ if model_runner is not None:
954
+ _ = model_runner.model_params
955
+
956
+ if data_pipeline_config is None:
957
+ print('Skipping data pipeline...')
958
+ # Load model immediately when skipping pipeline
959
+ if model_runner is not None:
960
+ print('Loading model parameters (no pipeline to wait for)...')
961
+ load_model_callback()
962
+ else:
963
+ print('Running data pipeline...')
964
+ fold_input = pipeline.DataPipeline(data_pipeline_config, load_model_callback).process(fold_input)
965
+
966
+ write_fold_input_json(fold_input, output_dir)
967
+ if model_runner is None:
968
+ print('Skipping model inference...')
969
+ output = fold_input
970
+ else:
971
+ print(
972
+ f'Predicting 3D structure for {fold_input.name} with'
973
+ f' {len(fold_input.rng_seeds)} seed(s)...'
974
+ )
975
+ all_inference_results = predict_structure(
976
+ fold_input=fold_input,
977
+ model_runner=model_runner,
978
+ buckets=buckets,
979
+ ref_max_modified_date=ref_max_modified_date,
980
+ conformer_max_iterations=conformer_max_iterations,
981
+ resolve_msa_overlaps=resolve_msa_overlaps,
982
+ )
983
+ print(f'Writing outputs with {len(fold_input.rng_seeds)} seed(s)...')
984
+ write_outputs(
985
+ all_inference_results=all_inference_results,
986
+ output_dir=output_dir,
987
+ job_name=fold_input.sanitised_name(),
988
+ )
989
+ output = all_inference_results
990
+
991
+ print(f'Fold job {fold_input.name} done, output written to {output_dir}\n')
992
+ return output
993
+
994
+
995
+ def main(_):
996
+ if _JAX_COMPILATION_CACHE_DIR.value is not None:
997
+ jax.config.update(
998
+ 'jax_compilation_cache_dir', _JAX_COMPILATION_CACHE_DIR.value
999
+ )
1000
+
1001
+ if _JSON_PATH.value is None == _INPUT_DIR.value is None:
1002
+ raise ValueError(
1003
+ 'Exactly one of --json_path or --input_dir must be specified.'
1004
+ )
1005
+
1006
+ if not _RUN_INFERENCE.value and not _RUN_DATA_PIPELINE.value:
1007
+ raise ValueError(
1008
+ 'At least one of --run_inference or --run_data_pipeline must be'
1009
+ ' set to true.'
1010
+ )
1011
+
1012
+ if _INPUT_DIR.value is not None:
1013
+ fold_inputs = folding_input.load_fold_inputs_from_dir(
1014
+ pathlib.Path(_INPUT_DIR.value)
1015
+ )
1016
+ elif _JSON_PATH.value is not None:
1017
+ fold_inputs = folding_input.load_fold_inputs_from_path(
1018
+ pathlib.Path(_JSON_PATH.value)
1019
+ )
1020
+ else:
1021
+ raise AssertionError(
1022
+ 'Exactly one of --json_path or --input_dir must be specified.'
1023
+ )
1024
+
1025
+ # Make sure we can create the output directory before running anything.
1026
+ try:
1027
+ os.makedirs(_OUTPUT_DIR.value, exist_ok=True)
1028
+ except OSError as e:
1029
+ print(f'Failed to create output directory {_OUTPUT_DIR.value}: {e}')
1030
+ raise
1031
+
1032
+ # if _RUN_INFERENCE.value:
1033
+ # # Fail early on incompatible devices, but only if we're running inference.
1034
+ # gpu_devices = jax.local_devices(backend='gpu')
1035
+ # if gpu_devices:
1036
+ # compute_capability = float(
1037
+ # gpu_devices[_GPU_DEVICE.value].compute_capability
1038
+ # )
1039
+ # if compute_capability < 6.0:
1040
+ # raise ValueError(
1041
+ # 'AlphaFold 3 requires at least GPU compute capability 6.0 (see'
1042
+ # ' https://developer.nvidia.com/cuda-gpus).'
1043
+ # )
1044
+ # elif 7.0 <= compute_capability < 8.0:
1045
+ # xla_flags = os.environ.get('XLA_FLAGS')
1046
+ # required_flag = '--xla_disable_hlo_passes=custom-kernel-fusion-rewriter'
1047
+ # if not xla_flags or required_flag not in xla_flags:
1048
+ # raise ValueError(
1049
+ # 'For devices with GPU compute capability 7.x (see'
1050
+ # ' https://developer.nvidia.com/cuda-gpus) the ENV XLA_FLAGS must'
1051
+ # f' include "{required_flag}".'
1052
+ # )
1053
+ # if _FLASH_ATTENTION_IMPLEMENTATION.value != 'xla':
1054
+ # raise ValueError(
1055
+ # 'For devices with GPU compute capability 7.x (see'
1056
+ # ' https://developer.nvidia.com/cuda-gpus) the'
1057
+ # ' --flash_attention_implementation must be set to "xla".'
1058
+ # )
1059
+
1060
+ notice = textwrap.wrap(
1061
+ 'Running AlphaFold 3. Please note that standard AlphaFold 3 model'
1062
+ ' parameters are only available under terms of use provided at'
1063
+ ' https://github.com/google-deepmind/alphafold3/blob/main/WEIGHTS_TERMS_OF_USE.md.'
1064
+ ' If you do not agree to these terms and are using AlphaFold 3 derived'
1065
+ ' model parameters, cancel execution of AlphaFold 3 inference with'
1066
+ ' CTRL-C, and do not use the model parameters.',
1067
+ break_long_words=False,
1068
+ break_on_hyphens=False,
1069
+ width=80,
1070
+ )
1071
+ print('\n' + '\n'.join(notice) + '\n')
1072
+
1073
+ max_template_date = datetime.date.fromisoformat(_MAX_TEMPLATE_DATE.value)
1074
+ if _RUN_DATA_PIPELINE.value:
1075
+ if not _USE_MMSEQS.value:
1076
+ expand_path = lambda x: replace_db_dir(x, DB_DIR.value)
1077
+ data_pipeline_config = pipeline.DataPipelineConfig(
1078
+ jackhmmer_binary_path=_JACKHMMER_BINARY_PATH.value,
1079
+ nhmmer_binary_path=_NHMMER_BINARY_PATH.value,
1080
+ hmmalign_binary_path=_HMMALIGN_BINARY_PATH.value,
1081
+ hmmsearch_binary_path=_HMMSEARCH_BINARY_PATH.value,
1082
+ hmmbuild_binary_path=_HMMBUILD_BINARY_PATH.value,
1083
+ mmseqs_binary_path=_MMSEQS_BINARY_PATH.value,
1084
+ small_bfd_database_path=expand_path(_SMALL_BFD_DATABASE_PATH.value),
1085
+ small_bfd_z_value=_SMALL_BFD_Z_VALUE.value,
1086
+ mgnify_database_path=expand_path(_MGNIFY_DATABASE_PATH.value),
1087
+ mgnify_z_value=_MGNIFY_Z_VALUE.value,
1088
+ uniprot_cluster_annot_database_path=expand_path(
1089
+ _UNIPROT_CLUSTER_ANNOT_DATABASE_PATH.value
1090
+ ),
1091
+ uniprot_cluster_annot_z_value=_UNIPROT_CLUSTER_ANNOT_Z_VALUE.value,
1092
+ uniref90_database_path=expand_path(_UNIREF90_DATABASE_PATH.value),
1093
+ uniref90_z_value=_UNIREF90_Z_VALUE.value,
1094
+ ntrna_database_path=expand_path(_NTRNA_DATABASE_PATH.value),
1095
+ ntrna_z_value=_NTRNA_Z_VALUE.value,
1096
+ rfam_database_path=expand_path(_RFAM_DATABASE_PATH.value),
1097
+ rfam_z_value=_RFAM_Z_VALUE.value,
1098
+ rna_central_database_path=expand_path(_RNA_CENTRAL_DATABASE_PATH.value),
1099
+ rna_central_z_value=_RNA_CENTRAL_Z_VALUE.value,
1100
+ pdb_database_path=expand_path(_PDB_DATABASE_PATH.value),
1101
+ seqres_database_path=expand_path(_SEQRES_DATABASE_PATH.value),
1102
+ jackhmmer_n_cpu=_JACKHMMER_N_CPU.value,
1103
+ jackhmmer_max_parallel_shards=_JACKHMMER_MAX_PARALLEL_SHARDS.value,
1104
+ jackhmmer_max_threads=_JACKHMMER_MAX_THREADS.value,
1105
+ nhmmer_n_cpu=_NHMMER_N_CPU.value,
1106
+ nhmmer_max_parallel_shards=_NHMMER_MAX_PARALLEL_SHARDS.value,
1107
+ nhmmer_max_threads=_NHMMER_MAX_THREADS.value,
1108
+ max_template_date=max_template_date,
1109
+ use_mmseqs=_USE_MMSEQS.value,
1110
+ mmseqs_options=_MMSEQS_OPTIONS.value,
1111
+ result2msa_options=_R2MSA_OPTIONS.value,
1112
+ )
1113
+ else:
1114
+ expand_path = lambda x: replace_mmseqs_db_dir(x, DB_DIR.value, MMSEQS_DB_DIR.value)
1115
+ data_pipeline_config = pipeline.DataPipelineConfig(
1116
+ jackhmmer_binary_path=_JACKHMMER_BINARY_PATH.value,
1117
+ nhmmer_binary_path=_NHMMER_BINARY_PATH.value,
1118
+ hmmalign_binary_path=_HMMALIGN_BINARY_PATH.value,
1119
+ hmmsearch_binary_path=_HMMSEARCH_BINARY_PATH.value,
1120
+ hmmbuild_binary_path=_HMMBUILD_BINARY_PATH.value,
1121
+ mmseqs_binary_path=_MMSEQS_BINARY_PATH.value,
1122
+ small_bfd_database_path=expand_path(_MMSEQS_SMALL_BFD_DATABASE_PATH.value),
1123
+ mgnify_database_path=expand_path(_MMSEQS_MGNIFY_DATABASE_PATH.value),
1124
+ uniprot_cluster_annot_database_path=expand_path(
1125
+ _MMSEQS_UNIPROT_CLUSTER_ANNOT_DATABASE_PATH.value
1126
+ ),
1127
+ uniref90_database_path=expand_path(_MMSEQS_UNIREF90_DATABASE_PATH.value),
1128
+ ntrna_database_path=expand_path(_NTRNA_DATABASE_PATH.value),
1129
+ rfam_database_path=expand_path(_RFAM_DATABASE_PATH.value),
1130
+ rna_central_database_path=expand_path(_RNA_CENTRAL_DATABASE_PATH.value),
1131
+ pdb_database_path=expand_path(_PDB_DATABASE_PATH.value),
1132
+ seqres_database_path=expand_path(_SEQRES_DATABASE_PATH.value),
1133
+ mmseqs_n_cpu=_MMSEQS_N_CPU.value,
1134
+ nhmmer_n_cpu=_NHMMER_N_CPU.value,
1135
+ max_template_date=max_template_date,
1136
+ use_mmseqs=_USE_MMSEQS.value,
1137
+ use_mmseqs_gpu=_USE_MMSEQS_GPU.value,
1138
+ mmseqs_options=_MMSEQS_OPTIONS.value,
1139
+ result2msa_options=_R2MSA_OPTIONS.value,
1140
+ )
1141
+ else:
1142
+ data_pipeline_config = None
1143
+
1144
+ if _RUN_INFERENCE.value:
1145
+ devices = jax.local_devices(backend='gpu')
1146
+ print(
1147
+ f'Found local devices: {devices}, using device {_GPU_DEVICE.value}:'
1148
+ f' {devices[_GPU_DEVICE.value]}'
1149
+ )
1150
+
1151
+ print('Building model from scratch...')
1152
+ model_runner = ModelRunner(
1153
+ config=make_model_config(
1154
+ flash_attention_implementation=typing.cast(
1155
+ attention.Implementation, _FLASH_ATTENTION_IMPLEMENTATION.value
1156
+ ),
1157
+ num_diffusion_samples=_NUM_DIFFUSION_SAMPLES.value,
1158
+ num_recycles=_NUM_RECYCLES.value,
1159
+ return_embeddings=_SAVE_EMBEDDINGS.value,
1160
+ return_distogram=_SAVE_DISTOGRAM.value,
1161
+ ),
1162
+ device=devices[_GPU_DEVICE.value],
1163
+ model_dir=pathlib.Path(MODEL_DIR.value),
1164
+ )
1165
+ # Check we can load the model parameters before launching anything.
1166
+ print('Checking that model parameters can be loaded...')
1167
+ _ = model_runner.model_params
1168
+ else:
1169
+ model_runner = None
1170
+
1171
+ num_fold_inputs = 0
1172
+ for fold_input in fold_inputs:
1173
+ if _NUM_SEEDS.value is not None:
1174
+ print(f'Expanding fold job {fold_input.name} to {_NUM_SEEDS.value} seeds')
1175
+ fold_input = fold_input.with_multiple_seeds(_NUM_SEEDS.value)
1176
+ process_fold_input(
1177
+ fold_input=fold_input,
1178
+ data_pipeline_config=data_pipeline_config,
1179
+ model_runner=model_runner,
1180
+ output_dir=os.path.join(_OUTPUT_DIR.value, fold_input.sanitised_name()),
1181
+ buckets=tuple(int(bucket) for bucket in _BUCKETS.value),
1182
+ ref_max_modified_date=max_template_date,
1183
+ conformer_max_iterations=_CONFORMER_MAX_ITERATIONS.value,
1184
+ resolve_msa_overlaps=_RESOLVE_MSA_OVERLAPS.value,
1185
+ force_output_dir=_FORCE_OUTPUT_DIR.value,
1186
+ )
1187
+ num_fold_inputs += 1
1188
+
1189
+ print(f'Done running {num_fold_inputs} fold jobs.')
1190
+
1191
+
1192
+ if __name__ == '__main__':
1193
+ flags.mark_flags_as_required(['output_dir'])
1194
+ app.run(main)
weight/_dep/abseil-cpp/.github/ISSUE_TEMPLATE/config.yml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Question
4
+ url: https://github.com/abseil/abseil-cpp/discussions
5
+ about: Have a question? Ask us anything! :-)
weight/_dep/abseil-cpp/.github/PULL_REQUEST_TEMPLATE.md ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ Thank you for your contribution to Abseil!
2
+
3
+ Before submitting this PR, please be sure to read our [contributing
4
+ guidelines](https://github.com/abseil/abseil-cpp/blob/master/CONTRIBUTING.md).
5
+
6
+ If you are a Googler, please also note that it is required that you send us a
7
+ Piper CL instead of using the GitHub pull-request process. The code propagation
8
+ process will deliver the change to GitHub.
weight/_dep/abseil-cpp/BUILD.bazel ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # Copyright 2020 The Abseil Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # https://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ package(default_visibility = ["//visibility:public"])
18
+
19
+ licenses(["notice"]) # Apache 2.0
20
+
21
+ # Expose license for external usage through bazel.
22
+ exports_files([
23
+ "AUTHORS",
24
+ "LICENSE",
25
+ ])
weight/_dep/abseil-cpp/CMake/Googletest/CMakeLists.txt.in ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cmake_minimum_required(VERSION 3.10)
2
+
3
+ project(googletest-external NONE)
4
+
5
+ include(ExternalProject)
6
+ ExternalProject_Add(googletest
7
+ URL "${absl_gtest_download_url}" # May be empty
8
+ SOURCE_DIR "${absl_gtest_src_dir}"
9
+ BINARY_DIR "${absl_gtest_build_dir}"
10
+ CONFIGURE_COMMAND ""
11
+ BUILD_COMMAND ""
12
+ INSTALL_COMMAND ""
13
+ TEST_COMMAND ""
14
+ )
weight/_dep/abseil-cpp/CMake/Googletest/DownloadGTest.cmake ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Integrates googletest at configure time. Based on the instructions at
2
+ # https://github.com/google/googletest/tree/master/googletest#incorporating-into-an-existing-cmake-project
3
+
4
+ # Set up the external googletest project, downloading the latest from Github
5
+ # master if requested.
6
+ configure_file(
7
+ ${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt.in
8
+ ${CMAKE_BINARY_DIR}/googletest-external/CMakeLists.txt
9
+ )
10
+
11
+ set(ABSL_SAVE_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
12
+ set(ABSL_SAVE_CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
13
+ if (BUILD_SHARED_LIBS)
14
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
15
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGTEST_CREATE_SHARED_LIBRARY=1")
16
+ endif()
17
+
18
+ # Configure and build the googletest source.
19
+ execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
20
+ RESULT_VARIABLE result
21
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-external )
22
+ if(result)
23
+ message(FATAL_ERROR "CMake step for googletest failed: ${result}")
24
+ endif()
25
+
26
+ execute_process(COMMAND ${CMAKE_COMMAND} --build .
27
+ RESULT_VARIABLE result
28
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-external)
29
+ if(result)
30
+ message(FATAL_ERROR "Build step for googletest failed: ${result}")
31
+ endif()
32
+
33
+ set(CMAKE_CXX_FLAGS ${ABSL_SAVE_CMAKE_CXX_FLAGS})
34
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${ABSL_SAVE_CMAKE_RUNTIME_OUTPUT_DIRECTORY})
35
+
36
+ # Prevent overriding the parent project's compiler/linker settings on Windows
37
+ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
38
+
39
+ # Add googletest directly to our build. This defines the gtest and gtest_main
40
+ # targets.
41
+ add_subdirectory(${absl_gtest_src_dir} ${absl_gtest_build_dir} EXCLUDE_FROM_ALL)
weight/_dep/abseil-cpp/CMake/README.md ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Abseil CMake Build Instructions
2
+
3
+ Abseil comes with a CMake build script ([CMakeLists.txt](../CMakeLists.txt))
4
+ that can be used on a wide range of platforms ("C" stands for cross-platform.).
5
+ If you don't have CMake installed already, you can download it for free from
6
+ <https://www.cmake.org/>.
7
+
8
+ CMake works by generating native makefiles or build projects that can
9
+ be used in the compiler environment of your choice.
10
+
11
+ For API/ABI compatibility reasons, we strongly recommend building Abseil in a
12
+ subdirectory of your project or as an embedded dependency.
13
+
14
+ ## Incorporating Abseil Into a CMake Project
15
+
16
+ The recommendations below are similar to those for using CMake within the
17
+ googletest framework
18
+ (<https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project>)
19
+
20
+ ### Step-by-Step Instructions
21
+
22
+ 1. If you want to build the Abseil tests, integrate the Abseil dependency
23
+ [Google Test](https://github.com/google/googletest) into your CMake
24
+ project. To disable Abseil tests, you have to pass either
25
+ `-DBUILD_TESTING=OFF` or `-DABSL_BUILD_TESTING=OFF` when configuring your
26
+ project with CMake.
27
+
28
+ 2. Download Abseil and copy it into a subdirectory in your CMake project or add
29
+ Abseil as a [git submodule](https://git-scm.com/docs/git-submodule) in your
30
+ CMake project.
31
+
32
+ 3. You can then use the CMake command
33
+ [`add_subdirectory()`](https://cmake.org/cmake/help/latest/command/add_subdirectory.html)
34
+ to include Abseil directly in your CMake project.
35
+
36
+ 4. Add the **absl::** target you wish to use to the
37
+ [`target_link_libraries()`](https://cmake.org/cmake/help/latest/command/target_link_libraries.html)
38
+ section of your executable or of your library.<br>
39
+ Here is a short CMakeLists.txt example of an application project using Abseil.
40
+
41
+ ```cmake
42
+ cmake_minimum_required(VERSION 3.10)
43
+ project(my_app_project)
44
+
45
+ # Pick the C++ standard to compile with.
46
+ # Abseil currently supports C++14, C++17, and C++20.
47
+ set(CMAKE_CXX_STANDARD 14)
48
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
49
+
50
+ add_subdirectory(abseil-cpp)
51
+
52
+ add_executable(my_exe source.cpp)
53
+ target_link_libraries(my_exe absl::base absl::synchronization absl::strings)
54
+ ```
55
+
56
+ Note that if you are developing a library designed for use by other clients, you
57
+ should instead leave `CMAKE_CXX_STANDARD` unset (or only set if being built as
58
+ the current top-level CMake project) and configure the minimum required C++
59
+ standard at the target level. If you require a later minimum C++ standard than
60
+ Abseil does, it's a good idea to also enforce that `CMAKE_CXX_STANDARD` (which
61
+ will control Abseil library targets) is set to at least that minimum. For
62
+ example:
63
+
64
+ ```cmake
65
+ cmake_minimum_required(VERSION 3.10)
66
+ project(my_lib_project)
67
+
68
+ # Leave C++ standard up to the root application, so set it only if this is the
69
+ # current top-level CMake project.
70
+ if(CMAKE_SOURCE_DIR STREQUAL my_lib_project_SOURCE_DIR)
71
+ set(CMAKE_CXX_STANDARD 17)
72
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
73
+ endif()
74
+
75
+ add_subdirectory(abseil-cpp)
76
+
77
+ add_library(my_lib source.cpp)
78
+ target_link_libraries(my_lib absl::base absl::synchronization absl::strings)
79
+
80
+ # Enforce that my_lib requires C++17. Important to document for clients that they
81
+ # must set CMAKE_CXX_STANDARD to 17 or higher for proper Abseil ABI compatibility
82
+ # (since otherwise, Abseil library targets could be compiled with a lower C++
83
+ # standard than my_lib).
84
+ target_compile_features(my_lib PUBLIC cxx_std_17)
85
+ if(CMAKE_CXX_STANDARD LESS 17)
86
+ message(FATAL_ERROR
87
+ "my_lib_project requires CMAKE_CXX_STANDARD >= 17 (got: ${CMAKE_CXX_STANDARD})")
88
+ endif()
89
+ ```
90
+
91
+ Then the top-level application project that uses your library is responsible for
92
+ setting a consistent `CMAKE_CXX_STANDARD` that is sufficiently high.
93
+
94
+ ### Running Abseil Tests with CMake
95
+
96
+ Use the `-DABSL_BUILD_TESTING=ON` flag to run Abseil tests. Note that
97
+ BUILD_TESTING must also be on (the default).
98
+
99
+ You will need to provide Abseil with a Googletest dependency. There are two
100
+ options for how to do this:
101
+
102
+ * Use `-DABSL_USE_GOOGLETEST_HEAD`. This will automatically download the latest
103
+ Googletest source into the build directory at configure time. Googletest will
104
+ then be compiled directly alongside Abseil's tests.
105
+ * Manually integrate Googletest with your build. See
106
+ https://github.com/google/googletest/blob/master/googletest/README.md#using-cmake
107
+ for more information on using Googletest in a CMake project.
108
+
109
+ For example, to run just the Abseil tests, you could use this script:
110
+
111
+ ```
112
+ cd path/to/abseil-cpp
113
+ mkdir build
114
+ cd build
115
+ cmake -DABSL_BUILD_TESTING=ON -DABSL_USE_GOOGLETEST_HEAD=ON ..
116
+ make -j
117
+ ctest
118
+ ```
119
+
120
+ Currently, we only run our tests with CMake in a Linux environment, but we are
121
+ working on the rest of our supported platforms. See
122
+ https://github.com/abseil/abseil-cpp/projects/1 and
123
+ https://github.com/abseil/abseil-cpp/issues/109 for more information.
124
+
125
+ ### Available Abseil CMake Public Targets
126
+
127
+ Here's a non-exhaustive list of Abseil CMake public targets:
128
+
129
+ ```cmake
130
+ absl::algorithm
131
+ absl::base
132
+ absl::debugging
133
+ absl::flat_hash_map
134
+ absl::flags
135
+ absl::memory
136
+ absl::meta
137
+ absl::numeric
138
+ absl::random_random
139
+ absl::strings
140
+ absl::synchronization
141
+ absl::time
142
+ absl::utility
143
+ ```
144
+
145
+ ## Traditional CMake Set-Up
146
+
147
+ For larger projects, it may make sense to use the traditional CMake set-up where you build and install projects separately.
148
+
149
+ First, you'd need to build and install Google Test:
150
+ ```
151
+ cmake -S /source/googletest -B /build/googletest -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/installation/dir -DBUILD_GMOCK=ON
152
+ cmake --build /build/googletest --target install
153
+ ```
154
+
155
+ Then you need to configure and build Abseil. Make sure you enable `ABSL_USE_EXTERNAL_GOOGLETEST` and `ABSL_FIND_GOOGLETEST`. You also need to enable `ABSL_ENABLE_INSTALL` so that you can install Abseil itself.
156
+ ```
157
+ cmake -S /source/abseil-cpp -B /build/abseil-cpp -DCMAKE_PREFIX_PATH=/installation/dir -DCMAKE_INSTALL_PREFIX=/installation/dir -DABSL_ENABLE_INSTALL=ON -DABSL_USE_EXTERNAL_GOOGLETEST=ON -DABSL_FIND_GOOGLETEST=ON
158
+ cmake --build /temporary/build/abseil-cpp
159
+ ```
160
+
161
+ (`CMAKE_PREFIX_PATH` is where you already have Google Test installed; `CMAKE_INSTALL_PREFIX` is where you want to have Abseil installed; they can be different.)
162
+
163
+ Run the tests:
164
+ ```
165
+ ctest --test-dir /temporary/build/abseil-cpp
166
+ ```
167
+
168
+ And finally install:
169
+ ```
170
+ cmake --build /temporary/build/abseil-cpp --target install
171
+ ```
172
+
173
+ # CMake Option Synopsis
174
+
175
+ ## Enable Standard CMake Installation
176
+
177
+ `-DABSL_ENABLE_INSTALL=ON`
178
+
179
+ ## Google Test Options
180
+
181
+ `-DABSL_BUILD_TESTING=ON` must be set to enable testing
182
+
183
+ - Have Abseil download and build Google Test for you: `-DABSL_USE_EXTERNAL_GOOGLETEST=OFF` (default)
184
+ - Download and build latest Google Test: `-DABSL_USE_GOOGLETEST_HEAD=ON`
185
+ - Download specific Google Test version (ZIP archive): `-DABSL_GOOGLETEST_DOWNLOAD_URL=https://.../version.zip`
186
+ - Use Google Test from specific local directory: `-DABSL_LOCAL_GOOGLETEST_DIR=/path/to/googletest`
187
+ - Use Google Test included elsewhere in your project: `-DABSL_USE_EXTERNAL_GOOGLETEST=ON`
188
+ - Use standard CMake `find_package(CTest)` to find installed Google Test: `-DABSL_USE_EXTERNAL_GOOGLETEST=ON -DABSL_FIND_GOOGLETEST=ON`
weight/_dep/abseil-cpp/CMake/install_test_project/CMakeLists.txt ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # Copyright 2019 The Abseil Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # https://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ # A simple CMakeLists.txt for testing cmake installation
17
+
18
+ cmake_minimum_required(VERSION 3.10)
19
+ project(absl_cmake_testing CXX)
20
+
21
+ add_executable(simple simple.cc)
22
+
23
+ find_package(absl REQUIRED)
24
+
25
+ target_link_libraries(simple absl::strings absl::config)
weight/_dep/abseil-cpp/CMake/install_test_project/simple.cc ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //
2
+ // Copyright 2019 The Abseil Authors.
3
+ //
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // you may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ //
8
+ // https://www.apache.org/licenses/LICENSE-2.0
9
+ //
10
+ // Unless required by applicable law or agreed to in writing, software
11
+ // distributed under the License is distributed on an "AS IS" BASIS,
12
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ // See the License for the specific language governing permissions and
14
+ // limitations under the License.
15
+
16
+ #include <iostream>
17
+ #include "absl/base/config.h"
18
+ #include "absl/strings/substitute.h"
19
+
20
+ #if !defined(ABSL_LTS_RELEASE_VERSION) || ABSL_LTS_RELEASE_VERSION != 99998877
21
+ #error ABSL_LTS_RELEASE_VERSION is not set correctly.
22
+ #endif
23
+
24
+ #if !defined(ABSL_LTS_RELEASE_PATCH_LEVEL) || ABSL_LTS_RELEASE_PATCH_LEVEL != 0
25
+ #error ABSL_LTS_RELEASE_PATCH_LEVEL is not set correctly.
26
+ #endif
27
+
28
+ int main(int argc, char** argv) {
29
+ for (int i = 0; i < argc; ++i) {
30
+ std::cout << absl::Substitute("Arg $0: $1\n", i, argv[i]);
31
+ }
32
+ }
weight/_dep/abseil-cpp/CMake/install_test_project/test.sh ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ #
3
+ # Copyright 2019 The Abseil Authors.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ # Unit and integration tests for Abseil LTS CMake installation
18
+
19
+ # Fail on any error. Treat unset variables an error. Print commands as executed.
20
+ set -euox pipefail
21
+
22
+ absl_dir=/abseil-cpp
23
+ absl_build_dir=/buildfs
24
+ googletest_builddir=/googletest_builddir
25
+ project_dir="${absl_dir}"/CMake/install_test_project
26
+ project_build_dir=/buildfs/project-build
27
+
28
+ build_shared_libs="OFF"
29
+ if [ "${LINK_TYPE:-}" = "DYNAMIC" ]; then
30
+ build_shared_libs="ON"
31
+ fi
32
+
33
+ # Build and install GoogleTest
34
+ mkdir "${googletest_builddir}"
35
+ pushd "${googletest_builddir}"
36
+ curl -L "${ABSL_GOOGLETEST_DOWNLOAD_URL}" --output "${ABSL_GOOGLETEST_COMMIT}".zip
37
+ unzip "${ABSL_GOOGLETEST_COMMIT}".zip
38
+ pushd "googletest-${ABSL_GOOGLETEST_COMMIT}"
39
+ mkdir build
40
+ pushd build
41
+ cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS="${build_shared_libs}" ..
42
+ make -j $(nproc)
43
+ make install
44
+ ldconfig
45
+ popd
46
+ popd
47
+ popd
48
+
49
+ # Run the LTS transformations
50
+ ./create_lts.py 99998877
51
+
52
+ # Build and install Abseil
53
+ pushd "${absl_build_dir}"
54
+ cmake "${absl_dir}" \
55
+ -DABSL_USE_EXTERNAL_GOOGLETEST=ON \
56
+ -DABSL_FIND_GOOGLETEST=ON \
57
+ -DCMAKE_BUILD_TYPE=Release \
58
+ -DABSL_BUILD_TESTING=ON \
59
+ -DBUILD_SHARED_LIBS="${build_shared_libs}"
60
+ make -j $(nproc)
61
+ ctest -j $(nproc) --output-on-failure
62
+ make install
63
+ ldconfig
64
+ popd
65
+
66
+ # Test the project against the installed Abseil
67
+ mkdir -p "${project_build_dir}"
68
+ pushd "${project_build_dir}"
69
+ cmake "${project_dir}"
70
+ cmake --build . --target simple
71
+
72
+ output="$(${project_build_dir}/simple "printme" 2>&1)"
73
+ if [[ "${output}" != *"Arg 1: printme"* ]]; then
74
+ echo "Faulty output on simple project:"
75
+ echo "${output}"
76
+ exit 1
77
+ fi
78
+
79
+ popd
80
+
81
+ if ! grep absl::strings "/usr/local/lib/cmake/absl/abslTargets.cmake"; then
82
+ cat "/usr/local/lib/cmake/absl/abslTargets.cmake"
83
+ echo "CMake targets named incorrectly"
84
+ exit 1
85
+ fi
86
+
87
+ pushd "${HOME}"
88
+ cat > hello-abseil.cc << EOF
89
+ #include <cstdlib>
90
+
91
+ #include "absl/strings/str_format.h"
92
+
93
+ int main(int argc, char **argv) {
94
+ absl::PrintF("Hello Abseil!\n");
95
+ return EXIT_SUCCESS;
96
+ }
97
+ EOF
98
+
99
+ if [ "${LINK_TYPE:-}" != "DYNAMIC" ]; then
100
+ pc_args=($(pkg-config --cflags --libs --static absl_str_format))
101
+ g++ -static -o hello-abseil hello-abseil.cc "${pc_args[@]}"
102
+ else
103
+ pc_args=($(pkg-config --cflags --libs absl_str_format))
104
+ g++ -o hello-abseil hello-abseil.cc "${pc_args[@]}"
105
+ fi
106
+ hello="$(./hello-abseil)"
107
+ [[ "${hello}" == "Hello Abseil!" ]]
108
+
109
+ popd
110
+
111
+ echo "Install test complete!"
112
+ exit 0
weight/_dep/abseil-cpp/CMakeLists.txt ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # Copyright 2017 The Abseil Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # https://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ # https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md
18
+ # As of 2022-09-06, CMake 3.10 is the minimum supported version.
19
+ cmake_minimum_required(VERSION 3.10)
20
+
21
+ # Compiler id for Apple Clang is now AppleClang.
22
+ if (POLICY CMP0025)
23
+ cmake_policy(SET CMP0025 NEW)
24
+ endif (POLICY CMP0025)
25
+
26
+ # if command can use IN_LIST
27
+ if (POLICY CMP0057)
28
+ cmake_policy(SET CMP0057 NEW)
29
+ endif (POLICY CMP0057)
30
+
31
+ # Project version variables are the empty string if version is unspecified
32
+ if (POLICY CMP0048)
33
+ cmake_policy(SET CMP0048 NEW)
34
+ endif (POLICY CMP0048)
35
+
36
+ # Honor the GTest_ROOT variable if specified
37
+ if (POLICY CMP0074)
38
+ cmake_policy(SET CMP0074 NEW)
39
+ endif (POLICY CMP0074)
40
+
41
+ # option() honor variables
42
+ if (POLICY CMP0077)
43
+ cmake_policy(SET CMP0077 NEW)
44
+ endif (POLICY CMP0077)
45
+
46
+ # Allow the user to specify the MSVC runtime
47
+ if (POLICY CMP0091)
48
+ cmake_policy(SET CMP0091 NEW)
49
+ endif (POLICY CMP0091)
50
+
51
+ # try_compile() honors the CMAKE_CXX_STANDARD value
52
+ if (POLICY CMP0067)
53
+ cmake_policy(SET CMP0067 NEW)
54
+ endif (POLICY CMP0067)
55
+
56
+ # Allow the user to specify the CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
57
+ if (POLICY CMP0141)
58
+ cmake_policy(SET CMP0141 NEW)
59
+ endif (POLICY CMP0141)
60
+
61
+ project(absl LANGUAGES CXX VERSION 20240116)
62
+ include(CTest)
63
+
64
+ # Output directory is correct by default for most build setups. However, when
65
+ # building Abseil as a DLL, it is important to have the DLL in the same
66
+ # directory as the executable using it. Thus, we put all executables in a single
67
+ # /bin directory.
68
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
69
+
70
+ # when absl is included as subproject (i.e. using add_subdirectory(abseil-cpp))
71
+ # in the source tree of a project that uses it, install rules are disabled.
72
+ if(NOT CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
73
+ option(ABSL_ENABLE_INSTALL "Enable install rule" OFF)
74
+ else()
75
+ option(ABSL_ENABLE_INSTALL "Enable install rule" ON)
76
+ endif()
77
+
78
+ option(ABSL_PROPAGATE_CXX_STD
79
+ "Use CMake C++ standard meta features (e.g. cxx_std_14) that propagate to targets that link to Abseil"
80
+ OFF) # TODO: Default to ON for CMake 3.8 and greater.
81
+ if(NOT ABSL_PROPAGATE_CXX_STD)
82
+ message(WARNING "A future Abseil release will default ABSL_PROPAGATE_CXX_STD to ON for CMake 3.8 and up. We recommend enabling this option to ensure your project still builds correctly.")
83
+ endif()
84
+
85
+ option(ABSL_USE_SYSTEM_INCLUDES
86
+ "Silence warnings in Abseil headers by marking them as SYSTEM includes"
87
+ OFF)
88
+
89
+ list(APPEND CMAKE_MODULE_PATH
90
+ ${CMAKE_CURRENT_LIST_DIR}/CMake
91
+ ${CMAKE_CURRENT_LIST_DIR}/absl/copts
92
+ )
93
+
94
+ include(CMakePackageConfigHelpers)
95
+ include(GNUInstallDirs)
96
+ include(AbseilDll)
97
+ include(AbseilHelpers)
98
+
99
+
100
+ ##
101
+ ## Using absl targets
102
+ ##
103
+ ## all public absl targets are
104
+ ## exported with the absl:: prefix
105
+ ##
106
+ ## e.g absl::base absl::synchronization absl::strings ....
107
+ ##
108
+ ## DO NOT rely on the internal targets outside of the prefix
109
+
110
+
111
+ # include current path
112
+ list(APPEND ABSL_COMMON_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR})
113
+
114
+ if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
115
+ set(ABSL_USING_CLANG ON)
116
+ else()
117
+ set(ABSL_USING_CLANG OFF)
118
+ endif()
119
+
120
+ # find dependencies
121
+ ## pthread
122
+ find_package(Threads REQUIRED)
123
+
124
+ include(CMakeDependentOption)
125
+
126
+ option(ABSL_BUILD_TESTING
127
+ "If ON, Abseil will build all of Abseil's own tests." OFF)
128
+
129
+ option(ABSL_BUILD_TEST_HELPERS
130
+ "If ON, Abseil will build libraries that you can use to write tests against Abseil code. This option requires that Abseil is configured to use GoogleTest."
131
+ OFF)
132
+
133
+ option(ABSL_USE_EXTERNAL_GOOGLETEST
134
+ "If ON, Abseil will assume that the targets for GoogleTest are already provided by the including project. This makes sense when Abseil is used with add_subdirectory." OFF)
135
+
136
+ cmake_dependent_option(ABSL_FIND_GOOGLETEST
137
+ "If ON, Abseil will use find_package(GTest) rather than assuming that GoogleTest is already provided by the including project."
138
+ ON
139
+ "ABSL_USE_EXTERNAL_GOOGLETEST"
140
+ OFF)
141
+
142
+
143
+ option(ABSL_USE_GOOGLETEST_HEAD
144
+ "If ON, abseil will download HEAD from GoogleTest at config time." OFF)
145
+
146
+ set(ABSL_GOOGLETEST_DOWNLOAD_URL "" CACHE STRING "If set, download GoogleTest from this URL")
147
+
148
+ set(ABSL_LOCAL_GOOGLETEST_DIR "/usr/src/googletest" CACHE PATH
149
+ "If ABSL_USE_GOOGLETEST_HEAD is OFF and ABSL_GOOGLETEST_URL is not set, specifies the directory of a local GoogleTest checkout."
150
+ )
151
+
152
+ if((BUILD_TESTING AND ABSL_BUILD_TESTING) OR ABSL_BUILD_TEST_HELPERS)
153
+ if (ABSL_USE_EXTERNAL_GOOGLETEST)
154
+ if (ABSL_FIND_GOOGLETEST)
155
+ find_package(GTest REQUIRED)
156
+ elseif(NOT TARGET GTest::gtest)
157
+ if(TARGET gtest)
158
+ # When Google Test is included directly rather than through find_package, the aliases are missing.
159
+ add_library(GTest::gtest ALIAS gtest)
160
+ add_library(GTest::gtest_main ALIAS gtest_main)
161
+ add_library(GTest::gmock ALIAS gmock)
162
+ add_library(GTest::gmock_main ALIAS gmock_main)
163
+ else()
164
+ message(FATAL_ERROR "ABSL_USE_EXTERNAL_GOOGLETEST is ON and ABSL_FIND_GOOGLETEST is OFF, which means that the top-level project must build the Google Test project. However, the target gtest was not found.")
165
+ endif()
166
+ endif()
167
+ else()
168
+ set(absl_gtest_build_dir ${CMAKE_BINARY_DIR}/googletest-build)
169
+ if(ABSL_USE_GOOGLETEST_HEAD AND ABSL_GOOGLETEST_DOWNLOAD_URL)
170
+ message(FATAL_ERROR "Do not set both ABSL_USE_GOOGLETEST_HEAD and ABSL_GOOGLETEST_DOWNLOAD_URL")
171
+ endif()
172
+ if(ABSL_USE_GOOGLETEST_HEAD)
173
+ set(absl_gtest_download_url "https://github.com/google/googletest/archive/main.zip")
174
+ elseif(ABSL_GOOGLETEST_DOWNLOAD_URL)
175
+ set(absl_gtest_download_url ${ABSL_GOOGLETEST_DOWNLOAD_URL})
176
+ endif()
177
+ if(absl_gtest_download_url)
178
+ set(absl_gtest_src_dir ${CMAKE_BINARY_DIR}/googletest-src)
179
+ else()
180
+ set(absl_gtest_src_dir ${ABSL_LOCAL_GOOGLETEST_DIR})
181
+ endif()
182
+ include(CMake/Googletest/DownloadGTest.cmake)
183
+ endif()
184
+ endif()
185
+
186
+ add_subdirectory(absl)
187
+
188
+ if(ABSL_ENABLE_INSTALL)
189
+
190
+
191
+ # install as a subdirectory only
192
+ install(EXPORT ${PROJECT_NAME}Targets
193
+ NAMESPACE absl::
194
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
195
+ )
196
+
197
+ configure_package_config_file(
198
+ CMake/abslConfig.cmake.in
199
+ "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
200
+ INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
201
+ )
202
+ install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
203
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
204
+ )
205
+
206
+ # Abseil only has a version in LTS releases. This mechanism is accomplished
207
+ # Abseil's internal Copybara (https://github.com/google/copybara) workflows and
208
+ # isn't visible in the CMake buildsystem itself.
209
+ if(absl_VERSION)
210
+ write_basic_package_version_file(
211
+ "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
212
+ COMPATIBILITY ExactVersion
213
+ )
214
+
215
+ install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
216
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
217
+ )
218
+ endif() # absl_VERSION
219
+
220
+ install(DIRECTORY absl
221
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
222
+ FILES_MATCHING
223
+ PATTERN "*.inc"
224
+ PATTERN "*.h"
225
+ PATTERN "copts" EXCLUDE
226
+ PATTERN "testdata" EXCLUDE
227
+ )
228
+
229
+ # Rewrite options.h to use the compiled ABI.
230
+ file(READ "absl/base/options.h" ABSL_INTERNAL_OPTIONS_H_CONTENTS)
231
+
232
+ # Handle features that require at least C++20.
233
+ if (ABSL_INTERNAL_AT_LEAST_CXX20)
234
+ foreach(FEATURE "ORDERING")
235
+ string(REPLACE
236
+ "#define ABSL_OPTION_USE_STD_${FEATURE} 2"
237
+ "#define ABSL_OPTION_USE_STD_${FEATURE} 1"
238
+ ABSL_INTERNAL_OPTIONS_H_PINNED
239
+ "${ABSL_INTERNAL_OPTIONS_H_CONTENTS}")
240
+ set(ABSL_INTERNAL_OPTIONS_H_CONTENTS "${ABSL_INTERNAL_OPTIONS_H_PINNED}")
241
+ endforeach()
242
+ endif()
243
+
244
+ # Handle features that require at least C++17.
245
+ if (ABSL_INTERNAL_AT_LEAST_CXX17)
246
+ foreach(FEATURE "ANY" "OPTIONAL" "STRING_VIEW" "VARIANT")
247
+ string(REPLACE
248
+ "#define ABSL_OPTION_USE_STD_${FEATURE} 2"
249
+ "#define ABSL_OPTION_USE_STD_${FEATURE} 1"
250
+ ABSL_INTERNAL_OPTIONS_H_PINNED
251
+ "${ABSL_INTERNAL_OPTIONS_H_CONTENTS}")
252
+ set(ABSL_INTERNAL_OPTIONS_H_CONTENTS "${ABSL_INTERNAL_OPTIONS_H_PINNED}")
253
+ endforeach()
254
+ endif()
255
+
256
+ # Any feature that still has the value of 2 (because it was not handled above)
257
+ # should be set to 0.
258
+ string(REGEX REPLACE
259
+ "#define ABSL_OPTION_USE_STD_([^ ]*) 2"
260
+ "#define ABSL_OPTION_USE_STD_\\1 0"
261
+ ABSL_INTERNAL_OPTIONS_H_PINNED
262
+ "${ABSL_INTERNAL_OPTIONS_H_CONTENTS}")
263
+
264
+ file(WRITE "${CMAKE_BINARY_DIR}/options-pinned.h" "${ABSL_INTERNAL_OPTIONS_H_PINNED}")
265
+
266
+ install(FILES "${CMAKE_BINARY_DIR}/options-pinned.h"
267
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/absl/base
268
+ RENAME "options.h")
269
+
270
+ endif() # ABSL_ENABLE_INSTALL
weight/_dep/abseil-cpp/CONTRIBUTING.md ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # How to Contribute to Abseil
2
+
3
+ We'd love to accept your patches and contributions to this project. There are
4
+ just a few small guidelines you need to follow.
5
+
6
+ NOTE: If you are new to GitHub, please start by reading [Pull Request
7
+ howto](https://help.github.com/articles/about-pull-requests/)
8
+
9
+ ## Contributor License Agreement
10
+
11
+ Contributions to this project must be accompanied by a Contributor License
12
+ Agreement. You (or your employer) retain the copyright to your contribution,
13
+ this simply gives us permission to use and redistribute your contributions as
14
+ part of the project. Head over to <https://cla.developers.google.com/> to see
15
+ your current agreements on file or to sign a new one.
16
+
17
+ You generally only need to submit a CLA once, so if you've already submitted one
18
+ (even if it was for a different project), you probably don't need to do it
19
+ again.
20
+
21
+ ## Contribution Guidelines
22
+
23
+ Potential contributors sometimes ask us if the Abseil project is the appropriate
24
+ home for their utility library code or for specific functions implementing
25
+ missing portions of the standard. Often, the answer to this question is "no".
26
+ We’d like to articulate our thinking on this issue so that our choices can be
27
+ understood by everyone and so that contributors can have a better intuition
28
+ about whether Abseil might be interested in adopting a new library.
29
+
30
+ ### Priorities
31
+
32
+ Although our mission is to augment the C++ standard library, our goal is not to
33
+ provide a full forward-compatible implementation of the latest standard. For us
34
+ to consider a library for inclusion in Abseil, it is not enough that a library
35
+ is useful. We generally choose to release a library when it meets at least one
36
+ of the following criteria:
37
+
38
+ * **Widespread usage** - Using our internal codebase to help gauge usage, most
39
+ of the libraries we've released have tens of thousands of users.
40
+ * **Anticipated widespread usage** - Pre-adoption of some standard-compliant
41
+ APIs may not have broad adoption initially but can be expected to pick up
42
+ usage when it replaces legacy APIs. `absl::from_chars`, for example,
43
+ replaces existing code that converts strings to numbers and will therefore
44
+ likely see usage growth.
45
+ * **High impact** - APIs that provide a key solution to a specific problem,
46
+ such as `absl::FixedArray`, have higher impact than usage numbers may signal
47
+ and are released because of their importance.
48
+ * **Direct support for a library that falls under one of the above** - When we
49
+ want access to a smaller library as an implementation detail for a
50
+ higher-priority library we plan to release, we may release it, as we did
51
+ with portions of `absl/meta/type_traits.h`. One consequence of this is that
52
+ the presence of a library in Abseil does not necessarily mean that other
53
+ similar libraries would be a high priority.
54
+
55
+ ### API Freeze Consequences
56
+
57
+ Via the
58
+ [Abseil Compatibility Guidelines](https://abseil.io/about/compatibility), we
59
+ have promised a large degree of API stability. In particular, we will not make
60
+ backward-incompatible changes to released APIs without also shipping a tool or
61
+ process that can upgrade our users' code. We are not yet at the point of easily
62
+ releasing such tools. Therefore, at this time, shipping a library establishes an
63
+ API contract which is borderline unchangeable. (We can add new functionality,
64
+ but we cannot easily change existing behavior.) This constraint forces us to
65
+ very carefully review all APIs that we ship.
66
+
67
+
68
+ ## Coding Style
69
+
70
+ To keep the source consistent, readable, diffable and easy to merge, we use a
71
+ fairly rigid coding style, as defined by the
72
+ [google-styleguide](https://github.com/google/styleguide) project. All patches
73
+ will be expected to conform to the style outlined
74
+ [here](https://google.github.io/styleguide/cppguide.html).
75
+
76
+ ## Guidelines for Pull Requests
77
+
78
+ * If you are a Googler, it is required that you send us a Piper CL instead of
79
+ using the GitHub pull-request process. The code propagation process will
80
+ deliver the change to GitHub.
81
+
82
+ * Create **small PRs** that are narrowly focused on **addressing a single
83
+ concern**. We often receive PRs that are trying to fix several things at a
84
+ time, but if only one fix is considered acceptable, nothing gets merged and
85
+ both author's & review's time is wasted. Create more PRs to address
86
+ different concerns and everyone will be happy.
87
+
88
+ * For speculative changes, consider opening an [Abseil
89
+ issue](https://github.com/abseil/abseil-cpp/issues) and discussing it first.
90
+ If you are suggesting a behavioral or API change, consider starting with an
91
+ [Abseil proposal template](ABSEIL_ISSUE_TEMPLATE.md).
92
+
93
+ * Provide a good **PR description** as a record of **what** change is being
94
+ made and **why** it was made. Link to a GitHub issue if it exists.
95
+
96
+ * Don't fix code style and formatting unless you are already changing that
97
+ line to address an issue. Formatting of modified lines may be done using
98
+ `git clang-format`. PRs with irrelevant changes won't be merged. If
99
+ you do want to fix formatting or style, do that in a separate PR.
100
+
101
+ * Unless your PR is trivial, you should expect there will be reviewer comments
102
+ that you'll need to address before merging. We expect you to be reasonably
103
+ responsive to those comments, otherwise the PR will be closed after 2-3
104
+ weeks of inactivity.
105
+
106
+ * Maintain **clean commit history** and use **meaningful commit messages**.
107
+ PRs with messy commit history are difficult to review and won't be merged.
108
+ Use `rebase -i upstream/master` to curate your commit history and/or to
109
+ bring in latest changes from master (but avoid rebasing in the middle of a
110
+ code review).
111
+
112
+ * Keep your PR up to date with upstream/master (if there are merge conflicts,
113
+ we can't really merge your change).
114
+
115
+ * **All tests need to be passing** before your change can be merged. We
116
+ recommend you **run tests locally** (see below)
117
+
118
+ * Exceptions to the rules can be made if there's a compelling reason for doing
119
+ so. That is - the rules are here to serve us, not the other way around, and
120
+ the rules need to be serving their intended purpose to be valuable.
121
+
122
+ * All submissions, including submissions by project members, require review.
123
+
124
+ ## Running Tests
125
+
126
+ If you have [Bazel](https://bazel.build/) installed, use `bazel test
127
+ --test_tag_filters="-benchmark" ...` to run the unit tests.
128
+
129
+ If you are running the Linux operating system and have
130
+ [Docker](https://www.docker.com/) installed, you can also run the `linux_*.sh`
131
+ scripts under the `ci/`(https://github.com/abseil/abseil-cpp/tree/master/ci)
132
+ directory to test Abseil under a variety of conditions.
133
+
134
+ ## Abseil Committers
135
+
136
+ The current members of the Abseil engineering team are the only committers at
137
+ present.
138
+
139
+ ## Release Process
140
+
141
+ Abseil lives at head, where latest-and-greatest code can be found.
weight/_dep/abseil-cpp/FAQ.md ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Abseil FAQ
2
+
3
+ ## Is Abseil the right home for my utility library?
4
+
5
+ Most often the answer to the question is "no." As both the [About
6
+ Abseil](https://abseil.io/about/) page and our [contributing
7
+ guidelines](https://github.com/abseil/abseil-cpp/blob/master/CONTRIBUTING.md#contribution-guidelines)
8
+ explain, Abseil contains a variety of core C++ library code that is widely used
9
+ at [Google](https://www.google.com/). As such, Abseil's primary purpose is to be
10
+ used as a dependency by Google's open source C++ projects. While we do hope that
11
+ Abseil is also useful to the C++ community at large, this added constraint also
12
+ means that we are unlikely to accept a contribution of utility code that isn't
13
+ already widely used by Google.
14
+
15
+ ## How to I set the C++ dialect used to build Abseil?
16
+
17
+ The short answer is that whatever mechanism you choose, you need to make sure
18
+ that you set this option consistently at the global level for your entire
19
+ project. If, for example, you want to set the C++ dialect to C++17, with
20
+ [Bazel](https://bazel/build/) as the build system and `gcc` or `clang` as the
21
+ compiler, there several ways to do this:
22
+ * Pass `--cxxopt=-std=c++17` on the command line (for example, `bazel build
23
+ --cxxopt=-std=c++17 ...`)
24
+ * Set the environment variable `BAZEL_CXXOPTS` (for example,
25
+ `BAZEL_CXXOPTS=-std=c++17`)
26
+ * Add `build --cxxopt=-std=c++17` to your [`.bazelrc`
27
+ file](https://docs.bazel.build/versions/master/guide.html#bazelrc)
28
+
29
+ If you are using CMake as the build system, you'll need to add a line like
30
+ `set(CMAKE_CXX_STANDARD 17)` to your top level `CMakeLists.txt` file. If you
31
+ are developing a library designed to be used by other clients, you should
32
+ instead leave `CMAKE_CXX_STANDARD` unset and configure the minimum C++ standard
33
+ required by each of your library targets via `target_compile_features`. See the
34
+ [CMake build
35
+ instructions](https://github.com/abseil/abseil-cpp/blob/master/CMake/README.md)
36
+ for more information.
37
+
38
+ For a longer answer to this question and to understand why some other approaches
39
+ don't work, see the answer to ["What is ABI and why don't you recommend using a
40
+ pre-compiled version of
41
+ Abseil?"](#what-is-abi-and-why-dont-you-recommend-using-a-pre-compiled-version-of-abseil)
42
+
43
+ ## What is ABI and why don't you recommend using a pre-compiled version of Abseil?
44
+
45
+ For the purposes of this discussion, you can think of
46
+ [ABI](https://en.wikipedia.org/wiki/Application_binary_interface) as the
47
+ compiled representation of the interfaces in code. This is in contrast to
48
+ [API](https://en.wikipedia.org/wiki/Application_programming_interface), which
49
+ you can think of as the interfaces as defined by the code itself. [Abseil has a
50
+ strong promise of API compatibility, but does not make any promise of ABI
51
+ compatibility](https://abseil.io/about/compatibility). Let's take a look at what
52
+ this means in practice.
53
+
54
+ You might be tempted to do something like this in a
55
+ [Bazel](https://bazel.build/) `BUILD` file:
56
+
57
+ ```
58
+ # DON'T DO THIS!!!
59
+ cc_library(
60
+ name = "my_library",
61
+ srcs = ["my_library.cc"],
62
+ copts = ["-std=c++17"], # May create a mixed-mode compile!
63
+ deps = ["@com_google_absl//absl/strings"],
64
+ )
65
+ ```
66
+
67
+ Applying `-std=c++17` to an individual target in your `BUILD` file is going to
68
+ compile that specific target in C++17 mode, but it isn't going to ensure the
69
+ Abseil library is built in C++17 mode, since the Abseil library itself is a
70
+ different build target. If your code includes an Abseil header, then your
71
+ program may contain conflicting definitions of the same
72
+ class/function/variable/enum, etc. As a rule, all compile options that affect
73
+ the ABI of a program need to be applied to the entire build on a global basis.
74
+
75
+ C++ has something called the [One Definition
76
+ Rule](https://en.wikipedia.org/wiki/One_Definition_Rule) (ODR). C++ doesn't
77
+ allow multiple definitions of the same class/function/variable/enum, etc. ODR
78
+ violations sometimes result in linker errors, but linkers do not always catch
79
+ violations. Uncaught ODR violations can result in strange runtime behaviors or
80
+ crashes that can be hard to debug.
81
+
82
+ If you build the Abseil library and your code using different compile options
83
+ that affect ABI, there is a good chance you will run afoul of the One Definition
84
+ Rule. Examples of GCC compile options that affect ABI include (but aren't
85
+ limited to) language dialect (e.g. `-std=`), optimization level (e.g. `-O2`),
86
+ code generation flags (e.g. `-fexceptions`), and preprocessor defines
87
+ (e.g. `-DNDEBUG`).
88
+
89
+ If you use a pre-compiled version of Abseil, (for example, from your Linux
90
+ distribution package manager or from something like
91
+ [vcpkg](https://github.com/microsoft/vcpkg)) you have to be very careful to
92
+ ensure ABI compatibility across the components of your program. The only way you
93
+ can be sure your program is going to be correct regarding ABI is to ensure
94
+ you've used the exact same compile options as were used to build the
95
+ pre-compiled library. This does not mean that Abseil cannot work as part of a
96
+ Linux distribution since a knowledgeable binary packager will have ensured that
97
+ all packages have been built with consistent compile options. This is one of the
98
+ reasons we warn against - though do not outright reject - using Abseil as a
99
+ pre-compiled library.
100
+
101
+ Another possible way that you might afoul of ABI issues is if you accidentally
102
+ include two versions of Abseil in your program. Multiple versions of Abseil can
103
+ end up within the same binary if your program uses the Abseil library and
104
+ another library also transitively depends on Abseil (resulting in what is
105
+ sometimes called the diamond dependency problem). In cases such as this you must
106
+ structure your build so that all libraries use the same version of Abseil.
107
+ [Abseil's strong promise of API compatibility between
108
+ releases](https://abseil.io/about/compatibility) means the latest "HEAD" release
109
+ of Abseil is almost certainly the right choice if you are doing as we recommend
110
+ and building all of your code from source.
111
+
112
+ For these reasons we recommend you avoid pre-compiled code and build the Abseil
113
+ library yourself in a consistent manner with the rest of your code.
114
+
115
+ ## What is "live at head" and how do I do it?
116
+
117
+ From Abseil's point-of-view, "live at head" means that every Abseil source
118
+ release (which happens on an almost daily basis) is either API compatible with
119
+ the previous release, or comes with an automated tool that you can run over code
120
+ to make it compatible. In practice, the need to use an automated tool is
121
+ extremely rare. This means that upgrading from one source release to another
122
+ should be a routine practice that can and should be performed often.
123
+
124
+ We recommend you update to the [latest commit in the `master` branch of
125
+ Abseil](https://github.com/abseil/abseil-cpp/commits/master) as often as
126
+ possible. Not only will you pick up bug fixes more quickly, but if you have good
127
+ automated testing, you will catch and be able to fix any [Hyrum's
128
+ Law](https://www.hyrumslaw.com/) dependency problems on an incremental basis
129
+ instead of being overwhelmed by them and having difficulty isolating them if you
130
+ wait longer between updates.
131
+
132
+ If you are using the [Bazel](https://bazel.build/) build system and its
133
+ [external dependencies](https://docs.bazel.build/versions/master/external.html)
134
+ feature, updating the
135
+ [`http_archive`](https://docs.bazel.build/versions/master/repo/http.html#http_archive)
136
+ rule in your
137
+ [`WORKSPACE`](https://docs.bazel.build/versions/master/be/workspace.html) for
138
+ `com_google_abseil` to point to the [latest commit in the `master` branch of
139
+ Abseil](https://github.com/abseil/abseil-cpp/commits/master) is all you need to
140
+ do. For example, on February 11, 2020, the latest commit to the master branch
141
+ was `98eb410c93ad059f9bba1bf43f5bb916fc92a5ea`. To update to this commit, you
142
+ would add the following snippet to your `WORKSPACE` file:
143
+
144
+ ```
145
+ http_archive(
146
+ name = "com_google_absl",
147
+ urls = ["https://github.com/abseil/abseil-cpp/archive/98eb410c93ad059f9bba1bf43f5bb916fc92a5ea.zip"], # 2020-02-11T18:50:53Z
148
+ strip_prefix = "abseil-cpp-98eb410c93ad059f9bba1bf43f5bb916fc92a5ea",
149
+ sha256 = "aabf6c57e3834f8dc3873a927f37eaf69975d4b28117fc7427dfb1c661542a87",
150
+ )
151
+ ```
152
+
153
+ To get the `sha256` of this URL, run `curl -sL --output -
154
+ https://github.com/abseil/abseil-cpp/archive/98eb410c93ad059f9bba1bf43f5bb916fc92a5ea.zip
155
+ | sha256sum -`.
156
+
157
+ You can commit the updated `WORKSPACE` file to your source control every time
158
+ you update, and if you have good automated testing, you might even consider
159
+ automating this.
160
+
161
+ One thing we don't recommend is using GitHub's `master.zip` files (for example
162
+ [https://github.com/abseil/abseil-cpp/archive/master.zip](https://github.com/abseil/abseil-cpp/archive/master.zip)),
163
+ which are always the latest commit in the `master` branch, to implement live at
164
+ head. Since these `master.zip` URLs are not versioned, you will lose build
165
+ reproducibility. In addition, some build systems, including Bazel, will simply
166
+ cache this file, which means you won't actually be updating to the latest
167
+ release until your cache is cleared or invalidated.
weight/_dep/abseil-cpp/MODULE.bazel ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 The Abseil Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # https://bazel.build/external/overview#bzlmod
16
+
17
+ module(
18
+ name = "abseil-cpp",
19
+ version = "20240116.2",
20
+ compatibility_level = 1,
21
+ )
22
+
23
+ # Only direct dependencies need to be listed below.
24
+ # Please keep the versions in sync with the versions in the WORKSPACE file.
25
+
26
+ bazel_dep(name = "bazel_skylib",
27
+ version = "1.5.0")
28
+
29
+ bazel_dep(name = "google_benchmark",
30
+ version = "1.8.3",
31
+ repo_name = "com_github_google_benchmark",
32
+ dev_dependency = True)
33
+
34
+ bazel_dep(name = "googletest",
35
+ version = "1.14.0.bcr.1",
36
+ repo_name = "com_google_googletest")
37
+
38
+ bazel_dep(name = "platforms",
39
+ version = "0.0.8")
weight/_dep/abseil-cpp/PrivacyInfo.xcprivacy ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>NSPrivacyTracking</key>
6
+ <false/>
7
+ <key>NSPrivacyCollectedDataTypes</key>
8
+ <array/>
9
+ <key>NSPrivacyTrackingDomains</key>
10
+ <array/>
11
+ <key>NSPrivacyAccessedAPITypes</key>
12
+ <array/>
13
+ </dict>
14
+ </plist>