lhallee commited on
Commit
961083d
·
verified ·
1 Parent(s): 827d9ec

Upload folder using huggingface_hub

Browse files
__init__.py CHANGED
@@ -1,11 +1,11 @@
1
- from boltz_automodel.modeling_boltz2 import (
2
- Boltz2Config,
3
- Boltz2Model,
4
- Boltz2StructureOutput,
5
- )
6
-
7
- __all__ = [
8
- "Boltz2Config",
9
- "Boltz2Model",
10
- "Boltz2StructureOutput",
11
- ]
 
1
+ from boltz_fastplms.modeling_boltz2 import (
2
+ Boltz2Config,
3
+ Boltz2Model,
4
+ Boltz2StructureOutput,
5
+ )
6
+
7
+ __all__ = [
8
+ "Boltz2Config",
9
+ "Boltz2Model",
10
+ "Boltz2StructureOutput",
11
+ ]
cif_writer.py CHANGED
@@ -1,126 +1,126 @@
1
- from pathlib import Path
2
- from typing import Optional
3
-
4
- import numpy as np
5
- import torch
6
-
7
- from .minimal_structures import ProteinStructureTemplate
8
-
9
-
10
- def _confidence_per_atom(
11
- plddt: Optional[torch.Tensor],
12
- atom_to_residue: list[int],
13
- num_atoms: int,
14
- sample_index: int,
15
- ) -> np.ndarray:
16
- if plddt is None:
17
- return np.ones((num_atoms,), dtype=np.float32) * 100.0
18
-
19
- values = plddt.detach().cpu()
20
- if values.ndim == 1:
21
- values = values.unsqueeze(0)
22
- assert values.ndim == 2, "Expected pLDDT with shape [samples, tokens/atoms]."
23
- assert sample_index < values.shape[0], "sample_index out of range for pLDDT."
24
-
25
- selected = values[sample_index]
26
- if selected.shape[0] == num_atoms:
27
- return (selected.numpy() * 100.0).astype(np.float32)
28
-
29
- num_residues = max(atom_to_residue) + 1
30
- if selected.shape[0] == num_residues:
31
- expanded = np.zeros((num_atoms,), dtype=np.float32)
32
- selected_np = selected.numpy()
33
- for atom_idx, residue_idx in enumerate(atom_to_residue):
34
- expanded[atom_idx] = selected_np[residue_idx] * 100.0
35
- return expanded
36
-
37
- return np.ones((num_atoms,), dtype=np.float32) * 100.0
38
-
39
-
40
- def write_cif(
41
- structure_template: ProteinStructureTemplate,
42
- atom_coords: torch.Tensor,
43
- atom_mask: torch.Tensor,
44
- output_path: str,
45
- plddt: Optional[torch.Tensor] = None,
46
- sample_index: int = 0,
47
- ) -> str:
48
- coords = atom_coords.detach().cpu()
49
- if coords.ndim == 2:
50
- coords = coords.unsqueeze(0)
51
- assert coords.ndim == 3, "Expected coordinates with shape [samples, atoms, 3]."
52
- assert sample_index < coords.shape[0], "sample_index out of range."
53
- selected_coords_tensor = coords[sample_index]
54
- all_non_finite = torch.logical_not(torch.isfinite(selected_coords_tensor))
55
- assert not torch.any(all_non_finite), (
56
- "CIF export received non-finite coordinates. "
57
- f"Non-finite count: {int(all_non_finite.sum().item())}"
58
- )
59
- selected_coords = selected_coords_tensor.numpy()
60
-
61
- mask = atom_mask.detach().cpu()
62
- if mask.ndim == 2:
63
- mask = mask[0]
64
- assert mask.ndim == 1, "Expected atom mask with shape [atoms]."
65
- assert mask.shape[0] == selected_coords.shape[0], "Atom mask/coord size mismatch."
66
- assert torch.any(mask > 0), "Atom mask has no valid atoms for CIF export."
67
- valid_non_finite = torch.logical_not(torch.isfinite(selected_coords_tensor[mask > 0]))
68
- assert not torch.any(valid_non_finite), (
69
- "CIF export has non-finite coordinates in unmasked atoms. "
70
- f"Non-finite count: {int(valid_non_finite.sum().item())}"
71
- )
72
-
73
- b_iso = _confidence_per_atom(
74
- plddt=plddt,
75
- atom_to_residue=structure_template.atom_residue_index,
76
- num_atoms=structure_template.num_atoms,
77
- sample_index=sample_index,
78
- )
79
- assert b_iso.shape[0] == structure_template.num_atoms
80
-
81
- lines = [
82
- "data_boltz2_prediction",
83
- "#",
84
- "loop_",
85
- "_atom_site.group_PDB",
86
- "_atom_site.id",
87
- "_atom_site.type_symbol",
88
- "_atom_site.label_atom_id",
89
- "_atom_site.label_comp_id",
90
- "_atom_site.label_asym_id",
91
- "_atom_site.label_seq_id",
92
- "_atom_site.Cartn_x",
93
- "_atom_site.Cartn_y",
94
- "_atom_site.Cartn_z",
95
- "_atom_site.occupancy",
96
- "_atom_site.B_iso_or_equiv",
97
- "_atom_site.pdbx_PDB_model_num",
98
- ]
99
-
100
- atom_id = 1
101
- for idx in range(structure_template.num_atoms):
102
- if mask[idx] <= 0:
103
- continue
104
-
105
- residue_idx = structure_template.atom_residue_index[idx]
106
- residue_name = structure_template.residue_names[residue_idx]
107
- atom_name = structure_template.atom_names[idx]
108
- element = structure_template.atom_elements[idx]
109
- chain_id = structure_template.atom_chain_id[idx]
110
- x_val, y_val, z_val = selected_coords[idx].tolist()
111
- b_factor = float(b_iso[idx])
112
-
113
- line = (
114
- f"ATOM {atom_id} {element} {atom_name} {residue_name} {chain_id} "
115
- f"{residue_idx + 1} {x_val:.3f} {y_val:.3f} {z_val:.3f} 1.00 {b_factor:.2f} 1"
116
- )
117
- lines.append(line)
118
- atom_id += 1
119
-
120
- lines.append("#")
121
- text = "\n".join(lines) + "\n"
122
-
123
- out_path = Path(output_path)
124
- out_path.parent.mkdir(parents=True, exist_ok=True)
125
- out_path.write_text(text, encoding="utf-8")
126
- return str(out_path)
 
1
+ from pathlib import Path
2
+ from typing import Optional
3
+
4
+ import numpy as np
5
+ import torch
6
+
7
+ from .minimal_structures import ProteinStructureTemplate
8
+
9
+
10
+ def _confidence_per_atom(
11
+ plddt: Optional[torch.Tensor],
12
+ atom_to_residue: list[int],
13
+ num_atoms: int,
14
+ sample_index: int,
15
+ ) -> np.ndarray:
16
+ if plddt is None:
17
+ return np.ones((num_atoms,), dtype=np.float32) * 100.0
18
+
19
+ values = plddt.detach().cpu()
20
+ if values.ndim == 1:
21
+ values = values.unsqueeze(0)
22
+ assert values.ndim == 2, "Expected pLDDT with shape [samples, tokens/atoms]."
23
+ assert sample_index < values.shape[0], "sample_index out of range for pLDDT."
24
+
25
+ selected = values[sample_index]
26
+ if selected.shape[0] == num_atoms:
27
+ return (selected.numpy() * 100.0).astype(np.float32)
28
+
29
+ num_residues = max(atom_to_residue) + 1
30
+ if selected.shape[0] == num_residues:
31
+ expanded = np.zeros((num_atoms,), dtype=np.float32)
32
+ selected_np = selected.numpy()
33
+ for atom_idx, residue_idx in enumerate(atom_to_residue):
34
+ expanded[atom_idx] = selected_np[residue_idx] * 100.0
35
+ return expanded
36
+
37
+ return np.ones((num_atoms,), dtype=np.float32) * 100.0
38
+
39
+
40
+ def write_cif(
41
+ structure_template: ProteinStructureTemplate,
42
+ atom_coords: torch.Tensor,
43
+ atom_mask: torch.Tensor,
44
+ output_path: str,
45
+ plddt: Optional[torch.Tensor] = None,
46
+ sample_index: int = 0,
47
+ ) -> str:
48
+ coords = atom_coords.detach().cpu()
49
+ if coords.ndim == 2:
50
+ coords = coords.unsqueeze(0)
51
+ assert coords.ndim == 3, "Expected coordinates with shape [samples, atoms, 3]."
52
+ assert sample_index < coords.shape[0], "sample_index out of range."
53
+ selected_coords_tensor = coords[sample_index]
54
+ all_non_finite = torch.logical_not(torch.isfinite(selected_coords_tensor))
55
+ assert not torch.any(all_non_finite), (
56
+ "CIF export received non-finite coordinates. "
57
+ f"Non-finite count: {int(all_non_finite.sum().item())}"
58
+ )
59
+ selected_coords = selected_coords_tensor.numpy()
60
+
61
+ mask = atom_mask.detach().cpu()
62
+ if mask.ndim == 2:
63
+ mask = mask[0]
64
+ assert mask.ndim == 1, "Expected atom mask with shape [atoms]."
65
+ assert mask.shape[0] == selected_coords.shape[0], "Atom mask/coord size mismatch."
66
+ assert torch.any(mask > 0), "Atom mask has no valid atoms for CIF export."
67
+ valid_non_finite = torch.logical_not(torch.isfinite(selected_coords_tensor[mask > 0]))
68
+ assert not torch.any(valid_non_finite), (
69
+ "CIF export has non-finite coordinates in unmasked atoms. "
70
+ f"Non-finite count: {int(valid_non_finite.sum().item())}"
71
+ )
72
+
73
+ b_iso = _confidence_per_atom(
74
+ plddt=plddt,
75
+ atom_to_residue=structure_template.atom_residue_index,
76
+ num_atoms=structure_template.num_atoms,
77
+ sample_index=sample_index,
78
+ )
79
+ assert b_iso.shape[0] == structure_template.num_atoms
80
+
81
+ lines = [
82
+ "data_boltz2_prediction",
83
+ "#",
84
+ "loop_",
85
+ "_atom_site.group_PDB",
86
+ "_atom_site.id",
87
+ "_atom_site.type_symbol",
88
+ "_atom_site.label_atom_id",
89
+ "_atom_site.label_comp_id",
90
+ "_atom_site.label_asym_id",
91
+ "_atom_site.label_seq_id",
92
+ "_atom_site.Cartn_x",
93
+ "_atom_site.Cartn_y",
94
+ "_atom_site.Cartn_z",
95
+ "_atom_site.occupancy",
96
+ "_atom_site.B_iso_or_equiv",
97
+ "_atom_site.pdbx_PDB_model_num",
98
+ ]
99
+
100
+ atom_id = 1
101
+ for idx in range(structure_template.num_atoms):
102
+ if mask[idx] <= 0:
103
+ continue
104
+
105
+ residue_idx = structure_template.atom_residue_index[idx]
106
+ residue_name = structure_template.residue_names[residue_idx]
107
+ atom_name = structure_template.atom_names[idx]
108
+ element = structure_template.atom_elements[idx]
109
+ chain_id = structure_template.atom_chain_id[idx]
110
+ x_val, y_val, z_val = selected_coords[idx].tolist()
111
+ b_factor = float(b_iso[idx])
112
+
113
+ line = (
114
+ f"ATOM {atom_id} {element} {atom_name} {residue_name} {chain_id} "
115
+ f"{residue_idx + 1} {x_val:.3f} {y_val:.3f} {z_val:.3f} 1.00 {b_factor:.2f} 1"
116
+ )
117
+ lines.append(line)
118
+ atom_id += 1
119
+
120
+ lines.append("#")
121
+ text = "\n".join(lines) + "\n"
122
+
123
+ out_path = Path(output_path)
124
+ out_path.parent.mkdir(parents=True, exist_ok=True)
125
+ out_path.write_text(text, encoding="utf-8")
126
+ return str(out_path)
config.json CHANGED
@@ -1,139 +1,139 @@
1
- {
2
- "architectures": [
3
- "Boltz2Model"
4
- ],
5
- "auto_map": {
6
- "AutoConfig": "modeling_boltz2.Boltz2Config",
7
- "AutoModel": "modeling_boltz2.Boltz2Model"
8
- },
9
- "core_kwargs": {
10
- "alpha_pae": 1,
11
- "atom_feature_dim": 388,
12
- "atom_s": 128,
13
- "atom_z": 16,
14
- "atoms_per_window_keys": 128,
15
- "atoms_per_window_queries": 32,
16
- "bond_type_feature": true,
17
- "conditioning_cutoff_max": 20.0,
18
- "conditioning_cutoff_min": 4.0,
19
- "confidence_model_args": {
20
- "add_s_input_to_s": true,
21
- "add_s_to_z_prod": true,
22
- "add_z_input_to_z": true,
23
- "confidence_args": {
24
- "num_pae_bins": 64,
25
- "num_pde_bins": 64,
26
- "num_plddt_bins": 50,
27
- "relative_confidence": "none",
28
- "use_separate_heads": true
29
- },
30
- "max_dist": 22,
31
- "no_trunk_feats": false,
32
- "num_dist_bins": 64,
33
- "pairformer_args": {
34
- "dropout": 0.25,
35
- "num_blocks": 8,
36
- "num_heads": 16,
37
- "v2": true
38
- },
39
- "use_gaussian": false,
40
- "use_miniformer": false,
41
- "use_s_diffusion": false
42
- },
43
- "confidence_prediction": true,
44
- "cyclic_pos_enc": true,
45
- "diffusion_process_args": {
46
- "P_mean": -1.2,
47
- "P_std": 1.5,
48
- "alignment_reverse_diff": true,
49
- "coordinate_augmentation": true,
50
- "gamma_0": 0.8,
51
- "gamma_min": 1.0,
52
- "mse_rotational_alignment": true,
53
- "noise_scale": 1.003,
54
- "rho": 7,
55
- "sigma_data": 16.0,
56
- "sigma_max": 160.0,
57
- "sigma_min": 0.0001,
58
- "step_scale": 1.5,
59
- "step_scale_random": [
60
- 1.0,
61
- 1.1,
62
- 1.2,
63
- 1.3,
64
- 1.4,
65
- 1.5
66
- ],
67
- "synchronize_sigmas": true
68
- },
69
- "embedder_args": {
70
- "add_cyclic_flag": true,
71
- "add_method_conditioning": true,
72
- "add_modified_flag": true,
73
- "add_mol_type_feat": true,
74
- "atom_encoder_depth": 3,
75
- "atom_encoder_heads": 4
76
- },
77
- "fix_sym_check": true,
78
- "msa_args": {
79
- "activation_checkpointing": true,
80
- "miniformer_blocks": false,
81
- "msa_blocks": 4,
82
- "msa_dropout": 0.15,
83
- "msa_s": 64,
84
- "pairwise_head_width": 32,
85
- "pairwise_num_heads": 4,
86
- "use_paired_feature": true,
87
- "use_trifast": true,
88
- "z_dropout": 0.25
89
- },
90
- "num_bins": 64,
91
- "pairformer_args": {
92
- "activation_checkpointing": true,
93
- "dropout": 0.25,
94
- "num_blocks": 64,
95
- "num_heads": 16,
96
- "post_layer_norm": false,
97
- "use_trifast": true,
98
- "v2": true
99
- },
100
- "run_trunk_and_structure": true,
101
- "score_model_args": {
102
- "activation_checkpointing": true,
103
- "atom_decoder_depth": 3,
104
- "atom_decoder_heads": 4,
105
- "atom_encoder_depth": 3,
106
- "atom_encoder_heads": 4,
107
- "conditioning_transition_layers": 2,
108
- "dim_fourier": 256,
109
- "sigma_data": 16,
110
- "token_transformer_depth": 24,
111
- "token_transformer_heads": 16,
112
- "transformer_post_ln": false
113
- },
114
- "skip_run_structure": false,
115
- "steering_args": {
116
- "contact_guidance_update": false,
117
- "fk_lambda": 4.0,
118
- "fk_resampling_interval": 3,
119
- "fk_steering": false,
120
- "num_gd_steps": 16,
121
- "num_particles": 3,
122
- "physical_guidance_update": false
123
- },
124
- "token_level_confidence": true,
125
- "token_s": 384,
126
- "token_z": 128,
127
- "use_atom_backbone_feat": false,
128
- "use_kernels": false,
129
- "use_no_atom_char": false,
130
- "use_residue_feats_atoms": false
131
- },
132
- "default_diffusion_samples": 5,
133
- "default_recycling_steps": 3,
134
- "default_sampling_steps": 200,
135
- "dtype": "float32",
136
- "model_type": "boltz2_automodel",
137
- "num_bins": 64,
138
- "transformers_version": "5.1.0"
139
- }
 
1
+ {
2
+ "architectures": [
3
+ "Boltz2Model"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "modeling_boltz2.Boltz2Config",
7
+ "AutoModel": "modeling_boltz2.Boltz2Model"
8
+ },
9
+ "core_kwargs": {
10
+ "alpha_pae": 1,
11
+ "atom_feature_dim": 388,
12
+ "atom_s": 128,
13
+ "atom_z": 16,
14
+ "atoms_per_window_keys": 128,
15
+ "atoms_per_window_queries": 32,
16
+ "bond_type_feature": true,
17
+ "conditioning_cutoff_max": 20.0,
18
+ "conditioning_cutoff_min": 4.0,
19
+ "confidence_model_args": {
20
+ "add_s_input_to_s": true,
21
+ "add_s_to_z_prod": true,
22
+ "add_z_input_to_z": true,
23
+ "confidence_args": {
24
+ "num_pae_bins": 64,
25
+ "num_pde_bins": 64,
26
+ "num_plddt_bins": 50,
27
+ "relative_confidence": "none",
28
+ "use_separate_heads": true
29
+ },
30
+ "max_dist": 22,
31
+ "no_trunk_feats": false,
32
+ "num_dist_bins": 64,
33
+ "pairformer_args": {
34
+ "dropout": 0.25,
35
+ "num_blocks": 8,
36
+ "num_heads": 16,
37
+ "v2": true
38
+ },
39
+ "use_gaussian": false,
40
+ "use_miniformer": false,
41
+ "use_s_diffusion": false
42
+ },
43
+ "confidence_prediction": true,
44
+ "cyclic_pos_enc": true,
45
+ "diffusion_process_args": {
46
+ "P_mean": -1.2,
47
+ "P_std": 1.5,
48
+ "alignment_reverse_diff": true,
49
+ "coordinate_augmentation": true,
50
+ "gamma_0": 0.8,
51
+ "gamma_min": 1.0,
52
+ "mse_rotational_alignment": true,
53
+ "noise_scale": 1.003,
54
+ "rho": 7,
55
+ "sigma_data": 16.0,
56
+ "sigma_max": 160.0,
57
+ "sigma_min": 0.0001,
58
+ "step_scale": 1.5,
59
+ "step_scale_random": [
60
+ 1.0,
61
+ 1.1,
62
+ 1.2,
63
+ 1.3,
64
+ 1.4,
65
+ 1.5
66
+ ],
67
+ "synchronize_sigmas": true
68
+ },
69
+ "embedder_args": {
70
+ "add_cyclic_flag": true,
71
+ "add_method_conditioning": true,
72
+ "add_modified_flag": true,
73
+ "add_mol_type_feat": true,
74
+ "atom_encoder_depth": 3,
75
+ "atom_encoder_heads": 4
76
+ },
77
+ "fix_sym_check": true,
78
+ "msa_args": {
79
+ "activation_checkpointing": true,
80
+ "miniformer_blocks": false,
81
+ "msa_blocks": 4,
82
+ "msa_dropout": 0.15,
83
+ "msa_s": 64,
84
+ "pairwise_head_width": 32,
85
+ "pairwise_num_heads": 4,
86
+ "use_paired_feature": true,
87
+ "use_trifast": true,
88
+ "z_dropout": 0.25
89
+ },
90
+ "num_bins": 64,
91
+ "pairformer_args": {
92
+ "activation_checkpointing": true,
93
+ "dropout": 0.25,
94
+ "num_blocks": 64,
95
+ "num_heads": 16,
96
+ "post_layer_norm": false,
97
+ "use_trifast": true,
98
+ "v2": true
99
+ },
100
+ "run_trunk_and_structure": true,
101
+ "score_model_args": {
102
+ "activation_checkpointing": true,
103
+ "atom_decoder_depth": 3,
104
+ "atom_decoder_heads": 4,
105
+ "atom_encoder_depth": 3,
106
+ "atom_encoder_heads": 4,
107
+ "conditioning_transition_layers": 2,
108
+ "dim_fourier": 256,
109
+ "sigma_data": 16,
110
+ "token_transformer_depth": 24,
111
+ "token_transformer_heads": 16,
112
+ "transformer_post_ln": false
113
+ },
114
+ "skip_run_structure": false,
115
+ "steering_args": {
116
+ "contact_guidance_update": false,
117
+ "fk_lambda": 4.0,
118
+ "fk_resampling_interval": 3,
119
+ "fk_steering": false,
120
+ "num_gd_steps": 16,
121
+ "num_particles": 3,
122
+ "physical_guidance_update": false
123
+ },
124
+ "token_level_confidence": true,
125
+ "token_s": 384,
126
+ "token_z": 128,
127
+ "use_atom_backbone_feat": false,
128
+ "use_kernels": false,
129
+ "use_no_atom_char": false,
130
+ "use_residue_feats_atoms": false
131
+ },
132
+ "default_diffusion_samples": 5,
133
+ "default_recycling_steps": 3,
134
+ "default_sampling_steps": 200,
135
+ "model_type": "boltz2_automodel",
136
+ "num_bins": 64,
137
+ "torch_dtype": "float32",
138
+ "transformers_version": "4.48.1"
139
+ }
entrypoint_setup.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch._inductor.config as inductor_config
3
+ import torch._dynamo as dynamo
4
+
5
+ # Enable TensorFloat32 tensor cores for float32 matmul (Ampere+ GPUs)
6
+ # Provides significant speedup with minimal precision loss
7
+ torch.set_float32_matmul_precision('high')
8
+
9
+ # Enable TF32 for matrix multiplications and cuDNN operations
10
+ torch.backends.cuda.matmul.allow_tf32 = True
11
+ torch.backends.cudnn.allow_tf32 = True
12
+
13
+ # Enable cuDNN autotuner - finds fastest algorithms for your hardware
14
+ # Best when input sizes are consistent; may slow down first iterations
15
+ torch.backends.cudnn.benchmark = True
16
+
17
+ # Deterministic operations off for speed (set True if reproducibility needed)
18
+ torch.backends.cudnn.deterministic = False
19
+ inductor_config.max_autotune_gemm_backends = "ATEN,CUTLASS,FBGEMM"
20
+
21
+ dynamo.config.capture_scalar_outputs = True
22
+ torch._dynamo.config.recompile_limit = 64
minimal_featurizer.py CHANGED
@@ -1,487 +1,487 @@
1
- import math
2
- from typing import Dict, List, Tuple
3
-
4
- import numpy as np
5
- import torch
6
- from torch.nn.functional import one_hot
7
-
8
- from .minimal_structures import ProteinStructureTemplate
9
- from . import vb_const as const
10
-
11
-
12
- _ELEMENT_TO_Z = {
13
- "H": 1,
14
- "C": 6,
15
- "N": 7,
16
- "O": 8,
17
- "P": 15,
18
- "S": 16,
19
- }
20
-
21
-
22
- def _normalize_sequence(sequence: str) -> str:
23
- seq = sequence.strip().upper()
24
- assert len(seq) > 0, "Amino acid sequence must be non-empty."
25
- for aa in seq:
26
- assert aa in const.prot_letter_to_token, f"Unsupported residue code '{aa}'."
27
- return seq
28
-
29
-
30
- def _atom_name_to_element(atom_name: str) -> str:
31
- name = atom_name.strip().upper()
32
- if len(name) == 0:
33
- return "C"
34
- if name[0].isdigit():
35
- name = name[1:]
36
- if len(name) >= 2 and name[0:2] in ("CL", "BR", "FE", "MG", "ZN", "NA", "CA"):
37
- return name[0]
38
- return name[0]
39
-
40
-
41
- def _atom_name_to_codes(atom_name: str) -> torch.Tensor:
42
- clipped = atom_name.strip()[:4]
43
- vals = [ord(ch) - 32 for ch in clipped]
44
- while len(vals) < 4:
45
- vals.append(0)
46
- out = torch.tensor(vals, dtype=torch.long)
47
- assert torch.all(out >= 0) and torch.all(out < 64), (
48
- f"Invalid atom-name encoding for '{atom_name}'."
49
- )
50
- return out
51
-
52
-
53
- def _default_atom_offset(atom_name: str, atom_idx: int) -> np.ndarray:
54
- if atom_name == "N":
55
- return np.array([-1.20, 0.80, 0.00], dtype=np.float32)
56
- if atom_name == "CA":
57
- return np.array([0.00, 0.00, 0.00], dtype=np.float32)
58
- if atom_name == "C":
59
- return np.array([1.35, 0.65, 0.00], dtype=np.float32)
60
- if atom_name == "O":
61
- return np.array([2.25, 0.10, 0.00], dtype=np.float32)
62
- if atom_name == "CB":
63
- return np.array([0.15, -1.20, 1.00], dtype=np.float32)
64
- angle = (atom_idx + 1) * 0.7
65
- radius = 1.4 + 0.03 * atom_idx
66
- return np.array(
67
- [
68
- radius * math.cos(angle),
69
- radius * math.sin(angle),
70
- 0.1 * ((atom_idx % 5) - 2),
71
- ],
72
- dtype=np.float32,
73
- )
74
-
75
-
76
- def _build_template(
77
- sequence: str,
78
- ) -> Tuple[
79
- ProteinStructureTemplate,
80
- List[str],
81
- List[int],
82
- List[int],
83
- List[int],
84
- List[np.ndarray],
85
- List[int],
86
- ]:
87
- residue_names: List[str] = []
88
- residue_token_ids: List[int] = []
89
- atom_names: List[str] = []
90
- atom_elements: List[str] = []
91
- atom_residue_index: List[int] = []
92
- atom_chain_id: List[str] = []
93
- atom_positions: List[np.ndarray] = []
94
- residue_center_atom_idx: List[int] = []
95
- residue_disto_atom_idx: List[int] = []
96
- residue_frame_atom_idx: List[int] = []
97
-
98
- global_atom_idx = 0
99
- for res_idx, aa in enumerate(sequence):
100
- token_name = const.prot_letter_to_token[aa]
101
- residue_names.append(token_name)
102
- residue_token_ids.append(const.token_ids[token_name])
103
-
104
- residue_atoms = const.ref_atoms[token_name]
105
- assert len(residue_atoms) > 0, f"No reference atoms for residue {token_name}."
106
- center_atom_name = const.res_to_center_atom[token_name]
107
- disto_atom_name = const.res_to_disto_atom[token_name]
108
-
109
- center_idx = -1
110
- disto_idx = -1
111
- n_idx = -1
112
- ca_idx = -1
113
- c_idx = -1
114
-
115
- for local_idx, atom_name in enumerate(residue_atoms):
116
- atom_names.append(atom_name)
117
- element = _atom_name_to_element(atom_name)
118
- atom_elements.append(element)
119
- atom_residue_index.append(res_idx)
120
- atom_chain_id.append("A")
121
-
122
- atom_pos = _default_atom_offset(atom_name, local_idx)
123
- atom_positions.append(atom_pos)
124
-
125
- if atom_name == center_atom_name:
126
- center_idx = global_atom_idx
127
- if atom_name == disto_atom_name:
128
- disto_idx = global_atom_idx
129
- if atom_name == "N":
130
- n_idx = global_atom_idx
131
- if atom_name == "CA":
132
- ca_idx = global_atom_idx
133
- if atom_name == "C":
134
- c_idx = global_atom_idx
135
- global_atom_idx += 1
136
-
137
- if center_idx == -1:
138
- center_idx = global_atom_idx - len(residue_atoms)
139
- if disto_idx == -1:
140
- disto_idx = center_idx
141
- if n_idx == -1:
142
- n_idx = center_idx
143
- if ca_idx == -1:
144
- ca_idx = center_idx
145
- if c_idx == -1:
146
- c_idx = center_idx
147
-
148
- residue_center_atom_idx.append(center_idx)
149
- residue_disto_atom_idx.append(disto_idx)
150
- residue_frame_atom_idx.extend([n_idx, ca_idx, c_idx])
151
-
152
- template = ProteinStructureTemplate(
153
- sequence=sequence,
154
- residue_names=residue_names,
155
- atom_names=atom_names,
156
- atom_elements=atom_elements,
157
- atom_residue_index=atom_residue_index,
158
- atom_chain_id=atom_chain_id,
159
- )
160
-
161
- return (
162
- template,
163
- residue_names,
164
- residue_token_ids,
165
- residue_center_atom_idx,
166
- residue_disto_atom_idx,
167
- atom_positions,
168
- residue_frame_atom_idx,
169
- )
170
-
171
-
172
- def _center_atoms_per_residue(
173
- atom_positions: torch.Tensor,
174
- atom_residue_index: List[int],
175
- num_residues: int,
176
- ) -> torch.Tensor:
177
- centered = atom_positions.clone()
178
- residue_index_tensor = torch.tensor(atom_residue_index, dtype=torch.long)
179
- for residue_idx in range(num_residues):
180
- residue_mask = residue_index_tensor == residue_idx
181
- assert torch.any(residue_mask), f"Residue index {residue_idx} has no atoms."
182
- residue_coords = centered[residue_mask]
183
- residue_center = residue_coords.mean(dim=0, keepdim=True)
184
- centered[residue_mask] = residue_coords - residue_center
185
- return centered
186
-
187
-
188
- def build_boltz2_features(
189
- amino_acid_sequence: str,
190
- num_bins: int = 64,
191
- atoms_per_window_queries: int = 32,
192
- ) -> Tuple[Dict[str, torch.Tensor], ProteinStructureTemplate]:
193
- sequence = _normalize_sequence(amino_acid_sequence)
194
- (
195
- template,
196
- residue_names,
197
- residue_token_ids,
198
- residue_center_atom_idx,
199
- residue_disto_atom_idx,
200
- atom_positions_np,
201
- residue_frame_atom_idx_flat,
202
- ) = _build_template(sequence)
203
-
204
- num_tokens = len(residue_names)
205
- num_atoms = len(atom_positions_np)
206
- assert num_tokens > 0 and num_atoms > 0
207
-
208
- atom_positions = torch.tensor(np.asarray(atom_positions_np), dtype=torch.float32)
209
- atom_positions = _center_atoms_per_residue(
210
- atom_positions=atom_positions,
211
- atom_residue_index=template.atom_residue_index,
212
- num_residues=num_tokens,
213
- )
214
-
215
- token_index = torch.arange(num_tokens, dtype=torch.long).unsqueeze(0)
216
- residue_index = torch.arange(num_tokens, dtype=torch.long).unsqueeze(0)
217
- asym_id = torch.zeros((1, num_tokens), dtype=torch.long)
218
- entity_id = torch.zeros((1, num_tokens), dtype=torch.long)
219
- sym_id = torch.zeros((1, num_tokens), dtype=torch.long)
220
- mol_type = torch.full(
221
- (1, num_tokens),
222
- fill_value=const.chain_type_ids["PROTEIN"],
223
- dtype=torch.long,
224
- )
225
-
226
- res_type_ids = torch.tensor(residue_token_ids, dtype=torch.long)
227
- res_type = one_hot(res_type_ids, num_classes=const.num_tokens).float().unsqueeze(0)
228
-
229
- token_bonds = torch.zeros((num_tokens, num_tokens), dtype=torch.float32)
230
- type_bonds = torch.zeros((num_tokens, num_tokens), dtype=torch.long)
231
- assert "SINGLE" in const.bond_type_ids
232
- assert "COVALENT" in const.bond_type_ids
233
- single_bond_id = const.bond_type_ids["SINGLE"] + 1
234
- covalent_bond_id = const.bond_type_ids["COVALENT"] + 1
235
- for idx in range(num_tokens):
236
- token_bonds[idx, idx] = 1.0
237
- type_bonds[idx, idx] = single_bond_id
238
- for idx in range(num_tokens - 1):
239
- token_bonds[idx, idx + 1] = 1.0
240
- token_bonds[idx + 1, idx] = 1.0
241
- type_bonds[idx, idx + 1] = covalent_bond_id
242
- type_bonds[idx + 1, idx] = covalent_bond_id
243
- token_bonds = token_bonds.unsqueeze(0).unsqueeze(-1)
244
- type_bonds = type_bonds.unsqueeze(0)
245
-
246
- token_pad_mask = torch.ones((1, num_tokens), dtype=torch.float32)
247
- token_resolved_mask = torch.ones((1, num_tokens), dtype=torch.float32)
248
- token_disto_mask = torch.ones((1, num_tokens), dtype=torch.float32)
249
-
250
- num_contact_classes = len(const.contact_conditioning_info)
251
- unspecified_id = const.contact_conditioning_info["UNSPECIFIED"]
252
- contact_ids = torch.full(
253
- (num_tokens, num_tokens),
254
- fill_value=unspecified_id,
255
- dtype=torch.long,
256
- )
257
- contact_conditioning = one_hot(
258
- contact_ids,
259
- num_classes=num_contact_classes,
260
- ).float().unsqueeze(0)
261
- contact_threshold = torch.zeros((1, num_tokens, num_tokens), dtype=torch.float32)
262
-
263
- assert "x-ray diffraction" in const.method_types_ids
264
- method_feature = torch.full(
265
- (1, num_tokens),
266
- fill_value=const.method_types_ids["x-ray diffraction"],
267
- dtype=torch.long,
268
- )
269
- modified = torch.zeros((1, num_tokens), dtype=torch.long)
270
- cyclic_period = torch.zeros((1, num_tokens), dtype=torch.float32)
271
- affinity_token_mask = torch.zeros((1, num_tokens), dtype=torch.float32)
272
-
273
- ref_pos = atom_positions.unsqueeze(0)
274
- atom_pad_mask = torch.ones((1, num_atoms), dtype=torch.float32)
275
- atom_resolved_mask = torch.ones((1, num_atoms), dtype=torch.float32)
276
-
277
- atom_name_codes = torch.stack(
278
- [_atom_name_to_codes(atom_name) for atom_name in template.atom_names],
279
- dim=0,
280
- )
281
- ref_atom_name_chars = one_hot(atom_name_codes, num_classes=64).float().unsqueeze(0)
282
-
283
- atomic_numbers = []
284
- for element in template.atom_elements:
285
- if element in _ELEMENT_TO_Z:
286
- z_value = _ELEMENT_TO_Z[element]
287
- else:
288
- z_value = _ELEMENT_TO_Z["C"]
289
- assert z_value < const.num_elements
290
- atomic_numbers.append(z_value)
291
- ref_element = one_hot(
292
- torch.tensor(atomic_numbers, dtype=torch.long),
293
- num_classes=const.num_elements,
294
- ).float().unsqueeze(0)
295
-
296
- ref_charge = torch.zeros((1, num_atoms), dtype=torch.float32)
297
- ref_chirality = torch.zeros((1, num_atoms), dtype=torch.long)
298
- ref_space_uid = torch.tensor(template.atom_residue_index, dtype=torch.long).unsqueeze(0)
299
-
300
- atom_to_token = one_hot(
301
- torch.tensor(template.atom_residue_index, dtype=torch.long),
302
- num_classes=num_tokens,
303
- ).float().unsqueeze(0)
304
- token_to_rep_atom = one_hot(
305
- torch.tensor(residue_disto_atom_idx, dtype=torch.long),
306
- num_classes=num_atoms,
307
- ).float().unsqueeze(0)
308
- token_to_center_atom = one_hot(
309
- torch.tensor(residue_center_atom_idx, dtype=torch.long),
310
- num_classes=num_atoms,
311
- ).float().unsqueeze(0)
312
- r_set_to_rep_atom = token_to_center_atom.clone()
313
-
314
- num_backbone_classes = (
315
- 1
316
- + len(const.protein_backbone_atom_index)
317
- + len(const.nucleic_backbone_atom_index)
318
- )
319
- backbone_ids = []
320
- for atom_name in template.atom_names:
321
- if atom_name in const.protein_backbone_atom_index:
322
- backbone_ids.append(const.protein_backbone_atom_index[atom_name] + 1)
323
- else:
324
- backbone_ids.append(0)
325
- atom_backbone_feat = one_hot(
326
- torch.tensor(backbone_ids, dtype=torch.long),
327
- num_classes=num_backbone_classes,
328
- ).float().unsqueeze(0)
329
-
330
- coords = ref_pos.unsqueeze(1).contiguous()
331
- disto_coords = torch.stack(
332
- [atom_positions[idx] for idx in residue_disto_atom_idx],
333
- dim=0,
334
- )
335
- disto_coords_ensemble = disto_coords.unsqueeze(0).unsqueeze(0).contiguous()
336
-
337
- bfactor = torch.zeros((1, num_atoms), dtype=torch.float32)
338
- atom_plddt = torch.ones((1, num_atoms), dtype=torch.float32)
339
-
340
- assert atoms_per_window_queries > 0
341
- pad_atoms = (
342
- ((num_atoms - 1) // atoms_per_window_queries + 1) * atoms_per_window_queries
343
- - num_atoms
344
- )
345
- if pad_atoms > 0:
346
- ref_pos = torch.nn.functional.pad(ref_pos, (0, 0, 0, pad_atoms), value=0.0)
347
- atom_pad_mask = torch.nn.functional.pad(atom_pad_mask, (0, pad_atoms), value=0.0)
348
- atom_resolved_mask = torch.nn.functional.pad(
349
- atom_resolved_mask,
350
- (0, pad_atoms),
351
- value=0.0,
352
- )
353
- ref_atom_name_chars = torch.nn.functional.pad(
354
- ref_atom_name_chars,
355
- (0, 0, 0, 0, 0, pad_atoms),
356
- value=0.0,
357
- )
358
- ref_element = torch.nn.functional.pad(ref_element, (0, 0, 0, pad_atoms), value=0.0)
359
- ref_charge = torch.nn.functional.pad(ref_charge, (0, pad_atoms), value=0.0)
360
- ref_chirality = torch.nn.functional.pad(ref_chirality, (0, pad_atoms), value=0)
361
- atom_backbone_feat = torch.nn.functional.pad(
362
- atom_backbone_feat,
363
- (0, 0, 0, pad_atoms),
364
- value=0.0,
365
- )
366
- ref_space_uid = torch.nn.functional.pad(ref_space_uid, (0, pad_atoms), value=0)
367
- coords = torch.nn.functional.pad(coords, (0, 0, 0, pad_atoms), value=0.0)
368
- atom_to_token = torch.nn.functional.pad(atom_to_token, (0, 0, 0, pad_atoms), value=0.0)
369
- token_to_rep_atom = torch.nn.functional.pad(
370
- token_to_rep_atom,
371
- (0, pad_atoms),
372
- value=0.0,
373
- )
374
- token_to_center_atom = torch.nn.functional.pad(
375
- token_to_center_atom,
376
- (0, pad_atoms),
377
- value=0.0,
378
- )
379
- r_set_to_rep_atom = torch.nn.functional.pad(
380
- r_set_to_rep_atom,
381
- (0, pad_atoms),
382
- value=0.0,
383
- )
384
- bfactor = torch.nn.functional.pad(bfactor, (0, pad_atoms), value=0.0)
385
- atom_plddt = torch.nn.functional.pad(atom_plddt, (0, pad_atoms), value=0.0)
386
-
387
- frames_idx = torch.tensor(
388
- residue_frame_atom_idx_flat,
389
- dtype=torch.long,
390
- ).reshape(num_tokens, 3)
391
- frames_idx = frames_idx.unsqueeze(0).unsqueeze(1)
392
- frame_resolved_mask = torch.ones((1, 1, num_tokens), dtype=torch.float32)
393
-
394
- msa = torch.tensor(residue_token_ids, dtype=torch.long).unsqueeze(0).unsqueeze(0)
395
- msa_paired = torch.ones((1, 1, num_tokens), dtype=torch.float32)
396
- deletion_value = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
397
- has_deletion = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
398
- msa_mask = torch.ones((1, 1, num_tokens), dtype=torch.float32)
399
- deletion_mean = torch.zeros((1, num_tokens), dtype=torch.float32)
400
- profile = one_hot(
401
- torch.tensor(residue_token_ids, dtype=torch.long),
402
- num_classes=const.num_tokens,
403
- ).float().unsqueeze(0)
404
-
405
- template_restype = one_hot(
406
- torch.zeros((1, 1, num_tokens), dtype=torch.long),
407
- num_classes=const.num_tokens,
408
- ).float()
409
- template_frame_rot = torch.zeros((1, 1, num_tokens, 3, 3), dtype=torch.float32)
410
- template_frame_t = torch.zeros((1, 1, num_tokens, 3), dtype=torch.float32)
411
- template_cb = torch.zeros((1, 1, num_tokens, 3), dtype=torch.float32)
412
- template_ca = torch.zeros((1, 1, num_tokens, 3), dtype=torch.float32)
413
- template_mask_cb = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
414
- template_mask_frame = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
415
- template_mask = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
416
- query_to_template = torch.zeros((1, 1, num_tokens), dtype=torch.long)
417
- visibility_ids = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
418
-
419
- disto_target = torch.zeros(
420
- (1, num_tokens, num_tokens, 1, num_bins),
421
- dtype=torch.float32,
422
- )
423
- disto_center = torch.stack(
424
- [atom_positions[idx] for idx in residue_disto_atom_idx],
425
- dim=0,
426
- ).unsqueeze(0)
427
-
428
- features: Dict[str, torch.Tensor] = {
429
- "token_index": token_index,
430
- "residue_index": residue_index,
431
- "asym_id": asym_id,
432
- "entity_id": entity_id,
433
- "sym_id": sym_id,
434
- "mol_type": mol_type,
435
- "res_type": res_type,
436
- "disto_center": disto_center,
437
- "token_bonds": token_bonds,
438
- "type_bonds": type_bonds,
439
- "token_pad_mask": token_pad_mask,
440
- "token_resolved_mask": token_resolved_mask,
441
- "token_disto_mask": token_disto_mask,
442
- "contact_conditioning": contact_conditioning,
443
- "contact_threshold": contact_threshold,
444
- "method_feature": method_feature,
445
- "modified": modified,
446
- "cyclic_period": cyclic_period,
447
- "affinity_token_mask": affinity_token_mask,
448
- "ref_pos": ref_pos,
449
- "atom_resolved_mask": atom_resolved_mask,
450
- "ref_atom_name_chars": ref_atom_name_chars,
451
- "ref_element": ref_element,
452
- "ref_charge": ref_charge,
453
- "ref_chirality": ref_chirality,
454
- "atom_backbone_feat": atom_backbone_feat,
455
- "ref_space_uid": ref_space_uid,
456
- "coords": coords,
457
- "atom_pad_mask": atom_pad_mask,
458
- "atom_to_token": atom_to_token,
459
- "token_to_rep_atom": token_to_rep_atom,
460
- "r_set_to_rep_atom": r_set_to_rep_atom,
461
- "token_to_center_atom": token_to_center_atom,
462
- "disto_target": disto_target,
463
- "disto_coords_ensemble": disto_coords_ensemble,
464
- "bfactor": bfactor,
465
- "plddt": atom_plddt,
466
- "frames_idx": frames_idx,
467
- "frame_resolved_mask": frame_resolved_mask,
468
- "msa": msa,
469
- "msa_paired": msa_paired,
470
- "deletion_value": deletion_value,
471
- "has_deletion": has_deletion,
472
- "deletion_mean": deletion_mean,
473
- "profile": profile,
474
- "msa_mask": msa_mask,
475
- "template_restype": template_restype,
476
- "template_frame_rot": template_frame_rot,
477
- "template_frame_t": template_frame_t,
478
- "template_cb": template_cb,
479
- "template_ca": template_ca,
480
- "template_mask_cb": template_mask_cb,
481
- "template_mask_frame": template_mask_frame,
482
- "template_mask": template_mask,
483
- "query_to_template": query_to_template,
484
- "visibility_ids": visibility_ids,
485
- }
486
-
487
- return features, template
 
1
+ import math
2
+ from typing import Dict, List, Tuple
3
+
4
+ import numpy as np
5
+ import torch
6
+ from torch.nn.functional import one_hot
7
+
8
+ from .minimal_structures import ProteinStructureTemplate
9
+ from . import vb_const as const
10
+
11
+
12
+ _ELEMENT_TO_Z = {
13
+ "H": 1,
14
+ "C": 6,
15
+ "N": 7,
16
+ "O": 8,
17
+ "P": 15,
18
+ "S": 16,
19
+ }
20
+
21
+
22
+ def _normalize_sequence(sequence: str) -> str:
23
+ seq = sequence.strip().upper()
24
+ assert len(seq) > 0, "Amino acid sequence must be non-empty."
25
+ for aa in seq:
26
+ assert aa in const.prot_letter_to_token, f"Unsupported residue code '{aa}'."
27
+ return seq
28
+
29
+
30
+ def _atom_name_to_element(atom_name: str) -> str:
31
+ name = atom_name.strip().upper()
32
+ if len(name) == 0:
33
+ return "C"
34
+ if name[0].isdigit():
35
+ name = name[1:]
36
+ if len(name) >= 2 and name[0:2] in ("CL", "BR", "FE", "MG", "ZN", "NA", "CA"):
37
+ return name[0]
38
+ return name[0]
39
+
40
+
41
+ def _atom_name_to_codes(atom_name: str) -> torch.Tensor:
42
+ clipped = atom_name.strip()[:4]
43
+ vals = [ord(ch) - 32 for ch in clipped]
44
+ while len(vals) < 4:
45
+ vals.append(0)
46
+ out = torch.tensor(vals, dtype=torch.long)
47
+ assert torch.all(out >= 0) and torch.all(out < 64), (
48
+ f"Invalid atom-name encoding for '{atom_name}'."
49
+ )
50
+ return out
51
+
52
+
53
+ def _default_atom_offset(atom_name: str, atom_idx: int) -> np.ndarray:
54
+ if atom_name == "N":
55
+ return np.array([-1.20, 0.80, 0.00], dtype=np.float32)
56
+ if atom_name == "CA":
57
+ return np.array([0.00, 0.00, 0.00], dtype=np.float32)
58
+ if atom_name == "C":
59
+ return np.array([1.35, 0.65, 0.00], dtype=np.float32)
60
+ if atom_name == "O":
61
+ return np.array([2.25, 0.10, 0.00], dtype=np.float32)
62
+ if atom_name == "CB":
63
+ return np.array([0.15, -1.20, 1.00], dtype=np.float32)
64
+ angle = (atom_idx + 1) * 0.7
65
+ radius = 1.4 + 0.03 * atom_idx
66
+ return np.array(
67
+ [
68
+ radius * math.cos(angle),
69
+ radius * math.sin(angle),
70
+ 0.1 * ((atom_idx % 5) - 2),
71
+ ],
72
+ dtype=np.float32,
73
+ )
74
+
75
+
76
+ def _build_template(
77
+ sequence: str,
78
+ ) -> Tuple[
79
+ ProteinStructureTemplate,
80
+ List[str],
81
+ List[int],
82
+ List[int],
83
+ List[int],
84
+ List[np.ndarray],
85
+ List[int],
86
+ ]:
87
+ residue_names: List[str] = []
88
+ residue_token_ids: List[int] = []
89
+ atom_names: List[str] = []
90
+ atom_elements: List[str] = []
91
+ atom_residue_index: List[int] = []
92
+ atom_chain_id: List[str] = []
93
+ atom_positions: List[np.ndarray] = []
94
+ residue_center_atom_idx: List[int] = []
95
+ residue_disto_atom_idx: List[int] = []
96
+ residue_frame_atom_idx: List[int] = []
97
+
98
+ global_atom_idx = 0
99
+ for res_idx, aa in enumerate(sequence):
100
+ token_name = const.prot_letter_to_token[aa]
101
+ residue_names.append(token_name)
102
+ residue_token_ids.append(const.token_ids[token_name])
103
+
104
+ residue_atoms = const.ref_atoms[token_name]
105
+ assert len(residue_atoms) > 0, f"No reference atoms for residue {token_name}."
106
+ center_atom_name = const.res_to_center_atom[token_name]
107
+ disto_atom_name = const.res_to_disto_atom[token_name]
108
+
109
+ center_idx = -1
110
+ disto_idx = -1
111
+ n_idx = -1
112
+ ca_idx = -1
113
+ c_idx = -1
114
+
115
+ for local_idx, atom_name in enumerate(residue_atoms):
116
+ atom_names.append(atom_name)
117
+ element = _atom_name_to_element(atom_name)
118
+ atom_elements.append(element)
119
+ atom_residue_index.append(res_idx)
120
+ atom_chain_id.append("A")
121
+
122
+ atom_pos = _default_atom_offset(atom_name, local_idx)
123
+ atom_positions.append(atom_pos)
124
+
125
+ if atom_name == center_atom_name:
126
+ center_idx = global_atom_idx
127
+ if atom_name == disto_atom_name:
128
+ disto_idx = global_atom_idx
129
+ if atom_name == "N":
130
+ n_idx = global_atom_idx
131
+ if atom_name == "CA":
132
+ ca_idx = global_atom_idx
133
+ if atom_name == "C":
134
+ c_idx = global_atom_idx
135
+ global_atom_idx += 1
136
+
137
+ if center_idx == -1:
138
+ center_idx = global_atom_idx - len(residue_atoms)
139
+ if disto_idx == -1:
140
+ disto_idx = center_idx
141
+ if n_idx == -1:
142
+ n_idx = center_idx
143
+ if ca_idx == -1:
144
+ ca_idx = center_idx
145
+ if c_idx == -1:
146
+ c_idx = center_idx
147
+
148
+ residue_center_atom_idx.append(center_idx)
149
+ residue_disto_atom_idx.append(disto_idx)
150
+ residue_frame_atom_idx.extend([n_idx, ca_idx, c_idx])
151
+
152
+ template = ProteinStructureTemplate(
153
+ sequence=sequence,
154
+ residue_names=residue_names,
155
+ atom_names=atom_names,
156
+ atom_elements=atom_elements,
157
+ atom_residue_index=atom_residue_index,
158
+ atom_chain_id=atom_chain_id,
159
+ )
160
+
161
+ return (
162
+ template,
163
+ residue_names,
164
+ residue_token_ids,
165
+ residue_center_atom_idx,
166
+ residue_disto_atom_idx,
167
+ atom_positions,
168
+ residue_frame_atom_idx,
169
+ )
170
+
171
+
172
+ def _center_atoms_per_residue(
173
+ atom_positions: torch.Tensor,
174
+ atom_residue_index: List[int],
175
+ num_residues: int,
176
+ ) -> torch.Tensor:
177
+ centered = atom_positions.clone()
178
+ residue_index_tensor = torch.tensor(atom_residue_index, dtype=torch.long)
179
+ for residue_idx in range(num_residues):
180
+ residue_mask = residue_index_tensor == residue_idx
181
+ assert torch.any(residue_mask), f"Residue index {residue_idx} has no atoms."
182
+ residue_coords = centered[residue_mask]
183
+ residue_center = residue_coords.mean(dim=0, keepdim=True)
184
+ centered[residue_mask] = residue_coords - residue_center
185
+ return centered
186
+
187
+
188
+ def build_boltz2_features(
189
+ amino_acid_sequence: str,
190
+ num_bins: int = 64,
191
+ atoms_per_window_queries: int = 32,
192
+ ) -> Tuple[Dict[str, torch.Tensor], ProteinStructureTemplate]:
193
+ sequence = _normalize_sequence(amino_acid_sequence)
194
+ (
195
+ template,
196
+ residue_names,
197
+ residue_token_ids,
198
+ residue_center_atom_idx,
199
+ residue_disto_atom_idx,
200
+ atom_positions_np,
201
+ residue_frame_atom_idx_flat,
202
+ ) = _build_template(sequence)
203
+
204
+ num_tokens = len(residue_names)
205
+ num_atoms = len(atom_positions_np)
206
+ assert num_tokens > 0 and num_atoms > 0
207
+
208
+ atom_positions = torch.tensor(np.asarray(atom_positions_np), dtype=torch.float32)
209
+ atom_positions = _center_atoms_per_residue(
210
+ atom_positions=atom_positions,
211
+ atom_residue_index=template.atom_residue_index,
212
+ num_residues=num_tokens,
213
+ )
214
+
215
+ token_index = torch.arange(num_tokens, dtype=torch.long).unsqueeze(0)
216
+ residue_index = torch.arange(num_tokens, dtype=torch.long).unsqueeze(0)
217
+ asym_id = torch.zeros((1, num_tokens), dtype=torch.long)
218
+ entity_id = torch.zeros((1, num_tokens), dtype=torch.long)
219
+ sym_id = torch.zeros((1, num_tokens), dtype=torch.long)
220
+ mol_type = torch.full(
221
+ (1, num_tokens),
222
+ fill_value=const.chain_type_ids["PROTEIN"],
223
+ dtype=torch.long,
224
+ )
225
+
226
+ res_type_ids = torch.tensor(residue_token_ids, dtype=torch.long)
227
+ res_type = one_hot(res_type_ids, num_classes=const.num_tokens).float().unsqueeze(0)
228
+
229
+ token_bonds = torch.zeros((num_tokens, num_tokens), dtype=torch.float32)
230
+ type_bonds = torch.zeros((num_tokens, num_tokens), dtype=torch.long)
231
+ assert "SINGLE" in const.bond_type_ids
232
+ assert "COVALENT" in const.bond_type_ids
233
+ single_bond_id = const.bond_type_ids["SINGLE"] + 1
234
+ covalent_bond_id = const.bond_type_ids["COVALENT"] + 1
235
+ for idx in range(num_tokens):
236
+ token_bonds[idx, idx] = 1.0
237
+ type_bonds[idx, idx] = single_bond_id
238
+ for idx in range(num_tokens - 1):
239
+ token_bonds[idx, idx + 1] = 1.0
240
+ token_bonds[idx + 1, idx] = 1.0
241
+ type_bonds[idx, idx + 1] = covalent_bond_id
242
+ type_bonds[idx + 1, idx] = covalent_bond_id
243
+ token_bonds = token_bonds.unsqueeze(0).unsqueeze(-1)
244
+ type_bonds = type_bonds.unsqueeze(0)
245
+
246
+ token_pad_mask = torch.ones((1, num_tokens), dtype=torch.float32)
247
+ token_resolved_mask = torch.ones((1, num_tokens), dtype=torch.float32)
248
+ token_disto_mask = torch.ones((1, num_tokens), dtype=torch.float32)
249
+
250
+ num_contact_classes = len(const.contact_conditioning_info)
251
+ unspecified_id = const.contact_conditioning_info["UNSPECIFIED"]
252
+ contact_ids = torch.full(
253
+ (num_tokens, num_tokens),
254
+ fill_value=unspecified_id,
255
+ dtype=torch.long,
256
+ )
257
+ contact_conditioning = one_hot(
258
+ contact_ids,
259
+ num_classes=num_contact_classes,
260
+ ).float().unsqueeze(0)
261
+ contact_threshold = torch.zeros((1, num_tokens, num_tokens), dtype=torch.float32)
262
+
263
+ assert "x-ray diffraction" in const.method_types_ids
264
+ method_feature = torch.full(
265
+ (1, num_tokens),
266
+ fill_value=const.method_types_ids["x-ray diffraction"],
267
+ dtype=torch.long,
268
+ )
269
+ modified = torch.zeros((1, num_tokens), dtype=torch.long)
270
+ cyclic_period = torch.zeros((1, num_tokens), dtype=torch.float32)
271
+ affinity_token_mask = torch.zeros((1, num_tokens), dtype=torch.float32)
272
+
273
+ ref_pos = atom_positions.unsqueeze(0)
274
+ atom_pad_mask = torch.ones((1, num_atoms), dtype=torch.float32)
275
+ atom_resolved_mask = torch.ones((1, num_atoms), dtype=torch.float32)
276
+
277
+ atom_name_codes = torch.stack(
278
+ [_atom_name_to_codes(atom_name) for atom_name in template.atom_names],
279
+ dim=0,
280
+ )
281
+ ref_atom_name_chars = one_hot(atom_name_codes, num_classes=64).float().unsqueeze(0)
282
+
283
+ atomic_numbers = []
284
+ for element in template.atom_elements:
285
+ if element in _ELEMENT_TO_Z:
286
+ z_value = _ELEMENT_TO_Z[element]
287
+ else:
288
+ z_value = _ELEMENT_TO_Z["C"]
289
+ assert z_value < const.num_elements
290
+ atomic_numbers.append(z_value)
291
+ ref_element = one_hot(
292
+ torch.tensor(atomic_numbers, dtype=torch.long),
293
+ num_classes=const.num_elements,
294
+ ).float().unsqueeze(0)
295
+
296
+ ref_charge = torch.zeros((1, num_atoms), dtype=torch.float32)
297
+ ref_chirality = torch.zeros((1, num_atoms), dtype=torch.long)
298
+ ref_space_uid = torch.tensor(template.atom_residue_index, dtype=torch.long).unsqueeze(0)
299
+
300
+ atom_to_token = one_hot(
301
+ torch.tensor(template.atom_residue_index, dtype=torch.long),
302
+ num_classes=num_tokens,
303
+ ).float().unsqueeze(0)
304
+ token_to_rep_atom = one_hot(
305
+ torch.tensor(residue_disto_atom_idx, dtype=torch.long),
306
+ num_classes=num_atoms,
307
+ ).float().unsqueeze(0)
308
+ token_to_center_atom = one_hot(
309
+ torch.tensor(residue_center_atom_idx, dtype=torch.long),
310
+ num_classes=num_atoms,
311
+ ).float().unsqueeze(0)
312
+ r_set_to_rep_atom = token_to_center_atom.clone()
313
+
314
+ num_backbone_classes = (
315
+ 1
316
+ + len(const.protein_backbone_atom_index)
317
+ + len(const.nucleic_backbone_atom_index)
318
+ )
319
+ backbone_ids = []
320
+ for atom_name in template.atom_names:
321
+ if atom_name in const.protein_backbone_atom_index:
322
+ backbone_ids.append(const.protein_backbone_atom_index[atom_name] + 1)
323
+ else:
324
+ backbone_ids.append(0)
325
+ atom_backbone_feat = one_hot(
326
+ torch.tensor(backbone_ids, dtype=torch.long),
327
+ num_classes=num_backbone_classes,
328
+ ).float().unsqueeze(0)
329
+
330
+ coords = ref_pos.unsqueeze(1).contiguous()
331
+ disto_coords = torch.stack(
332
+ [atom_positions[idx] for idx in residue_disto_atom_idx],
333
+ dim=0,
334
+ )
335
+ disto_coords_ensemble = disto_coords.unsqueeze(0).unsqueeze(0).contiguous()
336
+
337
+ bfactor = torch.zeros((1, num_atoms), dtype=torch.float32)
338
+ atom_plddt = torch.ones((1, num_atoms), dtype=torch.float32)
339
+
340
+ assert atoms_per_window_queries > 0
341
+ pad_atoms = (
342
+ ((num_atoms - 1) // atoms_per_window_queries + 1) * atoms_per_window_queries
343
+ - num_atoms
344
+ )
345
+ if pad_atoms > 0:
346
+ ref_pos = torch.nn.functional.pad(ref_pos, (0, 0, 0, pad_atoms), value=0.0)
347
+ atom_pad_mask = torch.nn.functional.pad(atom_pad_mask, (0, pad_atoms), value=0.0)
348
+ atom_resolved_mask = torch.nn.functional.pad(
349
+ atom_resolved_mask,
350
+ (0, pad_atoms),
351
+ value=0.0,
352
+ )
353
+ ref_atom_name_chars = torch.nn.functional.pad(
354
+ ref_atom_name_chars,
355
+ (0, 0, 0, 0, 0, pad_atoms),
356
+ value=0.0,
357
+ )
358
+ ref_element = torch.nn.functional.pad(ref_element, (0, 0, 0, pad_atoms), value=0.0)
359
+ ref_charge = torch.nn.functional.pad(ref_charge, (0, pad_atoms), value=0.0)
360
+ ref_chirality = torch.nn.functional.pad(ref_chirality, (0, pad_atoms), value=0)
361
+ atom_backbone_feat = torch.nn.functional.pad(
362
+ atom_backbone_feat,
363
+ (0, 0, 0, pad_atoms),
364
+ value=0.0,
365
+ )
366
+ ref_space_uid = torch.nn.functional.pad(ref_space_uid, (0, pad_atoms), value=0)
367
+ coords = torch.nn.functional.pad(coords, (0, 0, 0, pad_atoms), value=0.0)
368
+ atom_to_token = torch.nn.functional.pad(atom_to_token, (0, 0, 0, pad_atoms), value=0.0)
369
+ token_to_rep_atom = torch.nn.functional.pad(
370
+ token_to_rep_atom,
371
+ (0, pad_atoms),
372
+ value=0.0,
373
+ )
374
+ token_to_center_atom = torch.nn.functional.pad(
375
+ token_to_center_atom,
376
+ (0, pad_atoms),
377
+ value=0.0,
378
+ )
379
+ r_set_to_rep_atom = torch.nn.functional.pad(
380
+ r_set_to_rep_atom,
381
+ (0, pad_atoms),
382
+ value=0.0,
383
+ )
384
+ bfactor = torch.nn.functional.pad(bfactor, (0, pad_atoms), value=0.0)
385
+ atom_plddt = torch.nn.functional.pad(atom_plddt, (0, pad_atoms), value=0.0)
386
+
387
+ frames_idx = torch.tensor(
388
+ residue_frame_atom_idx_flat,
389
+ dtype=torch.long,
390
+ ).reshape(num_tokens, 3)
391
+ frames_idx = frames_idx.unsqueeze(0).unsqueeze(1)
392
+ frame_resolved_mask = torch.ones((1, 1, num_tokens), dtype=torch.float32)
393
+
394
+ msa = torch.tensor(residue_token_ids, dtype=torch.long).unsqueeze(0).unsqueeze(0)
395
+ msa_paired = torch.ones((1, 1, num_tokens), dtype=torch.float32)
396
+ deletion_value = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
397
+ has_deletion = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
398
+ msa_mask = torch.ones((1, 1, num_tokens), dtype=torch.float32)
399
+ deletion_mean = torch.zeros((1, num_tokens), dtype=torch.float32)
400
+ profile = one_hot(
401
+ torch.tensor(residue_token_ids, dtype=torch.long),
402
+ num_classes=const.num_tokens,
403
+ ).float().unsqueeze(0)
404
+
405
+ template_restype = one_hot(
406
+ torch.zeros((1, 1, num_tokens), dtype=torch.long),
407
+ num_classes=const.num_tokens,
408
+ ).float()
409
+ template_frame_rot = torch.zeros((1, 1, num_tokens, 3, 3), dtype=torch.float32)
410
+ template_frame_t = torch.zeros((1, 1, num_tokens, 3), dtype=torch.float32)
411
+ template_cb = torch.zeros((1, 1, num_tokens, 3), dtype=torch.float32)
412
+ template_ca = torch.zeros((1, 1, num_tokens, 3), dtype=torch.float32)
413
+ template_mask_cb = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
414
+ template_mask_frame = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
415
+ template_mask = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
416
+ query_to_template = torch.zeros((1, 1, num_tokens), dtype=torch.long)
417
+ visibility_ids = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
418
+
419
+ disto_target = torch.zeros(
420
+ (1, num_tokens, num_tokens, 1, num_bins),
421
+ dtype=torch.float32,
422
+ )
423
+ disto_center = torch.stack(
424
+ [atom_positions[idx] for idx in residue_disto_atom_idx],
425
+ dim=0,
426
+ ).unsqueeze(0)
427
+
428
+ features: Dict[str, torch.Tensor] = {
429
+ "token_index": token_index,
430
+ "residue_index": residue_index,
431
+ "asym_id": asym_id,
432
+ "entity_id": entity_id,
433
+ "sym_id": sym_id,
434
+ "mol_type": mol_type,
435
+ "res_type": res_type,
436
+ "disto_center": disto_center,
437
+ "token_bonds": token_bonds,
438
+ "type_bonds": type_bonds,
439
+ "token_pad_mask": token_pad_mask,
440
+ "token_resolved_mask": token_resolved_mask,
441
+ "token_disto_mask": token_disto_mask,
442
+ "contact_conditioning": contact_conditioning,
443
+ "contact_threshold": contact_threshold,
444
+ "method_feature": method_feature,
445
+ "modified": modified,
446
+ "cyclic_period": cyclic_period,
447
+ "affinity_token_mask": affinity_token_mask,
448
+ "ref_pos": ref_pos,
449
+ "atom_resolved_mask": atom_resolved_mask,
450
+ "ref_atom_name_chars": ref_atom_name_chars,
451
+ "ref_element": ref_element,
452
+ "ref_charge": ref_charge,
453
+ "ref_chirality": ref_chirality,
454
+ "atom_backbone_feat": atom_backbone_feat,
455
+ "ref_space_uid": ref_space_uid,
456
+ "coords": coords,
457
+ "atom_pad_mask": atom_pad_mask,
458
+ "atom_to_token": atom_to_token,
459
+ "token_to_rep_atom": token_to_rep_atom,
460
+ "r_set_to_rep_atom": r_set_to_rep_atom,
461
+ "token_to_center_atom": token_to_center_atom,
462
+ "disto_target": disto_target,
463
+ "disto_coords_ensemble": disto_coords_ensemble,
464
+ "bfactor": bfactor,
465
+ "plddt": atom_plddt,
466
+ "frames_idx": frames_idx,
467
+ "frame_resolved_mask": frame_resolved_mask,
468
+ "msa": msa,
469
+ "msa_paired": msa_paired,
470
+ "deletion_value": deletion_value,
471
+ "has_deletion": has_deletion,
472
+ "deletion_mean": deletion_mean,
473
+ "profile": profile,
474
+ "msa_mask": msa_mask,
475
+ "template_restype": template_restype,
476
+ "template_frame_rot": template_frame_rot,
477
+ "template_frame_t": template_frame_t,
478
+ "template_cb": template_cb,
479
+ "template_ca": template_ca,
480
+ "template_mask_cb": template_mask_cb,
481
+ "template_mask_frame": template_mask_frame,
482
+ "template_mask": template_mask,
483
+ "query_to_template": query_to_template,
484
+ "visibility_ids": visibility_ids,
485
+ }
486
+
487
+ return features, template
minimal_structures.py CHANGED
@@ -1,20 +1,20 @@
1
- from dataclasses import dataclass
2
- from typing import List
3
-
4
-
5
- @dataclass
6
- class ProteinStructureTemplate:
7
- sequence: str
8
- residue_names: List[str]
9
- atom_names: List[str]
10
- atom_elements: List[str]
11
- atom_residue_index: List[int]
12
- atom_chain_id: List[str]
13
-
14
- @property
15
- def num_atoms(self) -> int:
16
- return len(self.atom_names)
17
-
18
- @property
19
- def num_residues(self) -> int:
20
- return len(self.residue_names)
 
1
+ from dataclasses import dataclass
2
+ from typing import List
3
+
4
+
5
+ @dataclass
6
+ class ProteinStructureTemplate:
7
+ sequence: str
8
+ residue_names: List[str]
9
+ atom_names: List[str]
10
+ atom_elements: List[str]
11
+ atom_residue_index: List[int]
12
+ atom_chain_id: List[str]
13
+
14
+ @property
15
+ def num_atoms(self) -> int:
16
+ return len(self.atom_names)
17
+
18
+ @property
19
+ def num_residues(self) -> int:
20
+ return len(self.residue_names)
modeling_boltz2.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import copy
2
  import inspect
3
  from collections.abc import Mapping, Sequence
 
1
+ import entrypoint_setup
2
  import copy
3
  import inspect
4
  from collections.abc import Mapping, Sequence
pytorch_model.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5f090917cc00280b57246910eae790940cdeea01f68b0f2b793d81a844021505
3
+ size 2084301186
vb_const.py CHANGED
@@ -1,1184 +1,1184 @@
1
- ####################################################################################################
2
- # CHAINS
3
- ####################################################################################################
4
-
5
- chain_types = [
6
- "PROTEIN",
7
- "DNA",
8
- "RNA",
9
- "NONPOLYMER",
10
- ]
11
- chain_type_ids = {chain: i for i, chain in enumerate(chain_types)}
12
-
13
- out_types = [
14
- "dna_protein",
15
- "rna_protein",
16
- "ligand_protein",
17
- "dna_ligand",
18
- "rna_ligand",
19
- "intra_ligand",
20
- "intra_dna",
21
- "intra_rna",
22
- "intra_protein",
23
- "protein_protein",
24
- "modified",
25
- ]
26
-
27
- out_types_weights_af3 = {
28
- "dna_protein": 10.0,
29
- "rna_protein": 10.0,
30
- "ligand_protein": 10.0,
31
- "dna_ligand": 5.0,
32
- "rna_ligand": 5.0,
33
- "intra_ligand": 20.0,
34
- "intra_dna": 4.0,
35
- "intra_rna": 16.0,
36
- "intra_protein": 20.0,
37
- "protein_protein": 20.0,
38
- "modified": 0.0,
39
- }
40
-
41
- out_types_weights = {
42
- "dna_protein": 5.0,
43
- "rna_protein": 5.0,
44
- "ligand_protein": 20.0,
45
- "dna_ligand": 2.0,
46
- "rna_ligand": 2.0,
47
- "intra_ligand": 20.0,
48
- "intra_dna": 2.0,
49
- "intra_rna": 8.0,
50
- "intra_protein": 20.0,
51
- "protein_protein": 20.0,
52
- "modified": 0.0,
53
- }
54
-
55
-
56
- out_single_types = ["protein", "ligand", "dna", "rna"]
57
-
58
- clash_types = [
59
- "dna_protein",
60
- "rna_protein",
61
- "ligand_protein",
62
- "protein_protein",
63
- "dna_ligand",
64
- "rna_ligand",
65
- "ligand_ligand",
66
- "rna_dna",
67
- "dna_dna",
68
- "rna_rna",
69
- ]
70
-
71
- chain_types_to_clash_type = {
72
- frozenset(("PROTEIN", "DNA")): "dna_protein",
73
- frozenset(("PROTEIN", "RNA")): "rna_protein",
74
- frozenset(("PROTEIN", "NONPOLYMER")): "ligand_protein",
75
- frozenset(("PROTEIN",)): "protein_protein",
76
- frozenset(("NONPOLYMER", "DNA")): "dna_ligand",
77
- frozenset(("NONPOLYMER", "RNA")): "rna_ligand",
78
- frozenset(("NONPOLYMER",)): "ligand_ligand",
79
- frozenset(("DNA", "RNA")): "rna_dna",
80
- frozenset(("DNA",)): "dna_dna",
81
- frozenset(("RNA",)): "rna_rna",
82
- }
83
-
84
- chain_type_to_out_single_type = {
85
- "PROTEIN": "protein",
86
- "DNA": "dna",
87
- "RNA": "rna",
88
- "NONPOLYMER": "ligand",
89
- }
90
- ####################################################################################################
91
- # RESIDUES & TOKENS
92
- ####################################################################################################
93
-
94
-
95
- canonical_tokens = [
96
- "ALA",
97
- "ARG",
98
- "ASN",
99
- "ASP",
100
- "CYS",
101
- "GLN",
102
- "GLU",
103
- "GLY",
104
- "HIS",
105
- "ILE",
106
- "LEU",
107
- "LYS",
108
- "MET",
109
- "PHE",
110
- "PRO",
111
- "SER",
112
- "THR",
113
- "TRP",
114
- "TYR",
115
- "VAL",
116
- "UNK", # unknown protein token
117
- ]
118
-
119
- tokens = [
120
- "<pad>",
121
- "-",
122
- *canonical_tokens,
123
- "A",
124
- "G",
125
- "C",
126
- "U",
127
- "N", # unknown rna token
128
- "DA",
129
- "DG",
130
- "DC",
131
- "DT",
132
- "DN", # unknown dna token
133
- ]
134
-
135
- token_ids = {token: i for i, token in enumerate(tokens)}
136
- num_tokens = len(tokens)
137
- unk_token = {"PROTEIN": "UNK", "DNA": "DN", "RNA": "N"}
138
- unk_token_ids = {m: token_ids[t] for m, t in unk_token.items()}
139
-
140
- prot_letter_to_token = {
141
- "A": "ALA",
142
- "R": "ARG",
143
- "N": "ASN",
144
- "D": "ASP",
145
- "C": "CYS",
146
- "E": "GLU",
147
- "Q": "GLN",
148
- "G": "GLY",
149
- "H": "HIS",
150
- "I": "ILE",
151
- "L": "LEU",
152
- "K": "LYS",
153
- "M": "MET",
154
- "F": "PHE",
155
- "P": "PRO",
156
- "S": "SER",
157
- "T": "THR",
158
- "W": "TRP",
159
- "Y": "TYR",
160
- "V": "VAL",
161
- "X": "UNK",
162
- "J": "UNK",
163
- "B": "UNK",
164
- "Z": "UNK",
165
- "O": "UNK",
166
- "U": "UNK",
167
- "-": "-",
168
- }
169
-
170
- prot_token_to_letter = {v: k for k, v in prot_letter_to_token.items()}
171
- prot_token_to_letter["UNK"] = "X"
172
-
173
- rna_letter_to_token = {
174
- "A": "A",
175
- "G": "G",
176
- "C": "C",
177
- "U": "U",
178
- "N": "N",
179
- }
180
- rna_token_to_letter = {v: k for k, v in rna_letter_to_token.items()}
181
-
182
- dna_letter_to_token = {
183
- "A": "DA",
184
- "G": "DG",
185
- "C": "DC",
186
- "T": "DT",
187
- "N": "DN",
188
- }
189
- dna_token_to_letter = {v: k for k, v in dna_letter_to_token.items()}
190
-
191
- ####################################################################################################
192
- # ATOMS
193
- ####################################################################################################
194
-
195
- num_elements = 128
196
-
197
- chirality_types = [
198
- "CHI_UNSPECIFIED",
199
- "CHI_TETRAHEDRAL_CW",
200
- "CHI_TETRAHEDRAL_CCW",
201
- "CHI_SQUAREPLANAR",
202
- "CHI_OCTAHEDRAL",
203
- "CHI_TRIGONALBIPYRAMIDAL",
204
- "CHI_OTHER",
205
- ]
206
- chirality_type_ids = {chirality: i for i, chirality in enumerate(chirality_types)}
207
- unk_chirality_type = "CHI_OTHER"
208
-
209
- hybridization_map = [
210
- "S",
211
- "SP",
212
- "SP2",
213
- "SP2D",
214
- "SP3",
215
- "SP3D",
216
- "SP3D2",
217
- "OTHER",
218
- "UNSPECIFIED",
219
- ]
220
- hybridization_type_ids = {hybrid: i for i, hybrid in enumerate(hybridization_map)}
221
- unk_hybridization_type = "UNSPECIFIED"
222
-
223
- # fmt: off
224
- ref_atoms = {
225
- "PAD": [],
226
- "UNK": ["N", "CA", "C", "O", "CB"],
227
- "-": [],
228
- "ALA": ["N", "CA", "C", "O", "CB"],
229
- "ARG": ["N", "CA", "C", "O", "CB", "CG", "CD", "NE", "CZ", "NH1", "NH2"],
230
- "ASN": ["N", "CA", "C", "O", "CB", "CG", "OD1", "ND2"],
231
- "ASP": ["N", "CA", "C", "O", "CB", "CG", "OD1", "OD2"],
232
- "CYS": ["N", "CA", "C", "O", "CB", "SG"],
233
- "GLN": ["N", "CA", "C", "O", "CB", "CG", "CD", "OE1", "NE2"],
234
- "GLU": ["N", "CA", "C", "O", "CB", "CG", "CD", "OE1", "OE2"],
235
- "GLY": ["N", "CA", "C", "O"],
236
- "HIS": ["N", "CA", "C", "O", "CB", "CG", "ND1", "CD2", "CE1", "NE2"],
237
- "ILE": ["N", "CA", "C", "O", "CB", "CG1", "CG2", "CD1"],
238
- "LEU": ["N", "CA", "C", "O", "CB", "CG", "CD1", "CD2"],
239
- "LYS": ["N", "CA", "C", "O", "CB", "CG", "CD", "CE", "NZ"],
240
- "MET": ["N", "CA", "C", "O", "CB", "CG", "SD", "CE"],
241
- "PHE": ["N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "CE1", "CE2", "CZ"],
242
- "PRO": ["N", "CA", "C", "O", "CB", "CG", "CD"],
243
- "SER": ["N", "CA", "C", "O", "CB", "OG"],
244
- "THR": ["N", "CA", "C", "O", "CB", "OG1", "CG2"],
245
- "TRP": ["N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "NE1", "CE2", "CE3", "CZ2", "CZ3", "CH2"], # noqa: E501
246
- "TYR": ["N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "CE1", "CE2", "CZ", "OH"],
247
- "VAL": ["N", "CA", "C", "O", "CB", "CG1", "CG2"],
248
- "A": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "O2'", "C1'", "N9", "C8", "N7", "C5", "C6", "N6", "N1", "C2", "N3", "C4"], # noqa: E501
249
- "G": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "O2'", "C1'", "N9", "C8", "N7", "C5", "C6", "O6", "N1", "C2", "N2", "N3", "C4"], # noqa: E501
250
- "C": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "O2'", "C1'", "N1", "C2", "O2", "N3", "C4", "N4", "C5", "C6"], # noqa: E501
251
- "U": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "O2'", "C1'", "N1", "C2", "O2", "N3", "C4", "O4", "C5", "C6"], # noqa: E501
252
- "N": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "O2'", "C1'"], # noqa: E501
253
- "DA": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "C1'", "N9", "C8", "N7", "C5", "C6", "N6", "N1", "C2", "N3", "C4"], # noqa: E501
254
- "DG": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "C1'", "N9", "C8", "N7", "C5", "C6", "O6", "N1", "C2", "N2", "N3", "C4"], # noqa: E501
255
- "DC": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "C1'", "N1", "C2", "O2", "N3", "C4", "N4", "C5", "C6"], # noqa: E501
256
- "DT": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "C1'", "N1", "C2", "O2", "N3", "C4", "O4", "C5", "C7", "C6"], # noqa: E501
257
- "DN": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "C1'"]
258
- }
259
-
260
- protein_backbone_atom_names = ["N", "CA", "C", "O"]
261
- nucleic_backbone_atom_names = ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "O2'", "C1'"]
262
-
263
- protein_backbone_atom_index = {name: i for i, name in enumerate(protein_backbone_atom_names)}
264
- nucleic_backbone_atom_index = {name: i for i, name in enumerate(nucleic_backbone_atom_names)}
265
-
266
- ref_symmetries = {
267
- "PAD": [],
268
- "ALA": [],
269
- "ARG": [],
270
- "ASN": [],
271
- "ASP": [[(6, 7), (7, 6)]],
272
- "CYS": [],
273
- "GLN": [],
274
- "GLU": [[(7, 8), (8, 7)]],
275
- "GLY": [],
276
- "HIS": [],
277
- "ILE": [],
278
- "LEU": [],
279
- "LYS": [],
280
- "MET": [],
281
- "PHE": [[(6, 7), (7, 6), (8, 9), (9, 8)]],
282
- "PRO": [],
283
- "SER": [],
284
- "THR": [],
285
- "TRP": [],
286
- "TYR": [[(6, 7), (7, 6), (8, 9), (9, 8)]],
287
- "VAL": [],
288
- "A": [[(1, 2), (2, 1)]],
289
- "G": [[(1, 2), (2, 1)]],
290
- "C": [[(1, 2), (2, 1)]],
291
- "U": [[(1, 2), (2, 1)]],
292
- #"N": [[(1, 2), (2, 1)]],
293
- "DA": [[(1, 2), (2, 1)]],
294
- "DG": [[(1, 2), (2, 1)]],
295
- "DC": [[(1, 2), (2, 1)]],
296
- "DT": [[(1, 2), (2, 1)]],
297
- #"DN": [[(1, 2), (2, 1)]]
298
- }
299
-
300
-
301
- res_to_center_atom = {
302
- "UNK": "CA",
303
- "ALA": "CA",
304
- "ARG": "CA",
305
- "ASN": "CA",
306
- "ASP": "CA",
307
- "CYS": "CA",
308
- "GLN": "CA",
309
- "GLU": "CA",
310
- "GLY": "CA",
311
- "HIS": "CA",
312
- "ILE": "CA",
313
- "LEU": "CA",
314
- "LYS": "CA",
315
- "MET": "CA",
316
- "PHE": "CA",
317
- "PRO": "CA",
318
- "SER": "CA",
319
- "THR": "CA",
320
- "TRP": "CA",
321
- "TYR": "CA",
322
- "VAL": "CA",
323
- "A": "C1'",
324
- "G": "C1'",
325
- "C": "C1'",
326
- "U": "C1'",
327
- "N": "C1'",
328
- "DA": "C1'",
329
- "DG": "C1'",
330
- "DC": "C1'",
331
- "DT": "C1'",
332
- "DN": "C1'"
333
- }
334
-
335
- res_to_disto_atom = {
336
- "UNK": "CB",
337
- "ALA": "CB",
338
- "ARG": "CB",
339
- "ASN": "CB",
340
- "ASP": "CB",
341
- "CYS": "CB",
342
- "GLN": "CB",
343
- "GLU": "CB",
344
- "GLY": "CA",
345
- "HIS": "CB",
346
- "ILE": "CB",
347
- "LEU": "CB",
348
- "LYS": "CB",
349
- "MET": "CB",
350
- "PHE": "CB",
351
- "PRO": "CB",
352
- "SER": "CB",
353
- "THR": "CB",
354
- "TRP": "CB",
355
- "TYR": "CB",
356
- "VAL": "CB",
357
- "A": "C4",
358
- "G": "C4",
359
- "C": "C2",
360
- "U": "C2",
361
- "N": "C1'",
362
- "DA": "C4",
363
- "DG": "C4",
364
- "DC": "C2",
365
- "DT": "C2",
366
- "DN": "C1'"
367
- }
368
-
369
- res_to_center_atom_id = {
370
- res: ref_atoms[res].index(atom)
371
- for res, atom in res_to_center_atom.items()
372
- }
373
-
374
- res_to_disto_atom_id = {
375
- res: ref_atoms[res].index(atom)
376
- for res, atom in res_to_disto_atom.items()
377
- }
378
-
379
- # fmt: on
380
-
381
- ####################################################################################################
382
- # BONDS
383
- ####################################################################################################
384
-
385
- atom_interface_cutoff = 5.0
386
- interface_cutoff = 15.0
387
-
388
- bond_types = [
389
- "OTHER",
390
- "SINGLE",
391
- "DOUBLE",
392
- "TRIPLE",
393
- "AROMATIC",
394
- "COVALENT",
395
- ]
396
- bond_type_ids = {bond: i for i, bond in enumerate(bond_types)}
397
- unk_bond_type = "OTHER"
398
-
399
-
400
- ####################################################################################################
401
- # Contacts
402
- ####################################################################################################
403
-
404
-
405
- pocket_contact_info = {
406
- "UNSPECIFIED": 0,
407
- "UNSELECTED": 1,
408
- "POCKET": 2,
409
- "BINDER": 3,
410
- }
411
-
412
- contact_conditioning_info = {
413
- "UNSPECIFIED": 0,
414
- "UNSELECTED": 1,
415
- "POCKET>BINDER": 2,
416
- "BINDER>POCKET": 3,
417
- "CONTACT": 4,
418
- }
419
-
420
-
421
- ####################################################################################################
422
- # MSA
423
- ####################################################################################################
424
-
425
- max_msa_seqs = 16384
426
- max_paired_seqs = 8192
427
-
428
-
429
- ####################################################################################################
430
- # CHUNKING
431
- ####################################################################################################
432
-
433
- chunk_size_threshold = 384
434
-
435
- ####################################################################################################
436
- # Method conditioning
437
- ####################################################################################################
438
-
439
- # Methods
440
- method_types_ids = {
441
- "MD": 0,
442
- "X-RAY DIFFRACTION": 1,
443
- "ELECTRON MICROSCOPY": 2,
444
- "SOLUTION NMR": 3,
445
- "SOLID-STATE NMR": 4,
446
- "NEUTRON DIFFRACTION": 4,
447
- "ELECTRON CRYSTALLOGRAPHY": 4,
448
- "FIBER DIFFRACTION": 4,
449
- "POWDER DIFFRACTION": 4,
450
- "INFRARED SPECTROSCOPY": 4,
451
- "FLUORESCENCE TRANSFER": 4,
452
- "EPR": 4,
453
- "THEORETICAL MODEL": 4,
454
- "SOLUTION SCATTERING": 4,
455
- "OTHER": 4,
456
- "AFDB": 5,
457
- "BOLTZ-1": 6,
458
- "FUTURE1": 7, # Placeholder for future supervision sources
459
- "FUTURE2": 8,
460
- "FUTURE3": 9,
461
- "FUTURE4": 10,
462
- "FUTURE5": 11,
463
- }
464
- method_types_ids = {k.lower(): v for k, v in method_types_ids.items()}
465
- num_method_types = len(set(method_types_ids.values()))
466
-
467
- # Temperature
468
- temperature_bins = [(265, 280), (280, 295), (295, 310)]
469
- temperature_bins_ids = {temp: i for i, temp in enumerate(temperature_bins)}
470
- temperature_bins_ids["other"] = len(temperature_bins)
471
- num_temp_bins = len(temperature_bins_ids)
472
-
473
-
474
- # pH
475
- ph_bins = [(0, 6), (6, 8), (8, 14)]
476
- ph_bins_ids = {ph: i for i, ph in enumerate(ph_bins)}
477
- ph_bins_ids["other"] = len(ph_bins)
478
- num_ph_bins = len(ph_bins_ids)
479
-
480
- ####################################################################################################
481
- # VDW_RADII
482
- ####################################################################################################
483
-
484
- # fmt: off
485
- vdw_radii = [
486
- 1.2, 1.4, 2.2, 1.9, 1.8, 1.7, 1.6, 1.55, 1.5, 1.54,
487
- 2.4, 2.2, 2.1, 2.1, 1.95, 1.8, 1.8, 1.88, 2.8, 2.4,
488
- 2.3, 2.15, 2.05, 2.05, 2.05, 2.05, 2.0, 2.0, 2.0, 2.1,
489
- 2.1, 2.1, 2.05, 1.9, 1.9, 2.02, 2.9, 2.55, 2.4, 2.3,
490
- 2.15, 2.1, 2.05, 2.05, 2.0, 2.05, 2.1, 2.2, 2.2, 2.25,
491
- 2.2, 2.1, 2.1, 2.16, 3.0, 2.7, 2.5, 2.48, 2.47, 2.45,
492
- 2.43, 2.42, 2.4, 2.38, 2.37, 2.35, 2.33, 2.32, 2.3, 2.28,
493
- 2.27, 2.25, 2.2, 2.1, 2.05, 2.0, 2.0, 2.05, 2.1, 2.05,
494
- 2.2, 2.3, 2.3, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.4,
495
- 2.0, 2.3, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0,
496
- 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0,
497
- 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0
498
- ]
499
- # fmt: on
500
-
501
- ####################################################################################################
502
- # Excluded ligands
503
- ####################################################################################################
504
-
505
- ligand_exclusion = {
506
- "144",
507
- "15P",
508
- "1PE",
509
- "2F2",
510
- "2JC",
511
- "3HR",
512
- "3SY",
513
- "7N5",
514
- "7PE",
515
- "9JE",
516
- "AAE",
517
- "ABA",
518
- "ACE",
519
- "ACN",
520
- "ACT",
521
- "ACY",
522
- "AZI",
523
- "BAM",
524
- "BCN",
525
- "BCT",
526
- "BDN",
527
- "BEN",
528
- "BME",
529
- "BO3",
530
- "BTB",
531
- "BTC",
532
- "BU1",
533
- "C8E",
534
- "CAD",
535
- "CAQ",
536
- "CBM",
537
- "CCN",
538
- "CIT",
539
- "CL",
540
- "CLR",
541
- "CM",
542
- "CMO",
543
- "CO3",
544
- "CPT",
545
- "CXS",
546
- "D10",
547
- "DEP",
548
- "DIO",
549
- "DMS",
550
- "DN",
551
- "DOD",
552
- "DOX",
553
- "EDO",
554
- "EEE",
555
- "EGL",
556
- "EOH",
557
- "EOX",
558
- "EPE",
559
- "ETF",
560
- "FCY",
561
- "FJO",
562
- "FLC",
563
- "FMT",
564
- "FW5",
565
- "GOL",
566
- "GSH",
567
- "GTT",
568
- "GYF",
569
- "HED",
570
- "IHP",
571
- "IHS",
572
- "IMD",
573
- "IOD",
574
- "IPA",
575
- "IPH",
576
- "LDA",
577
- "MB3",
578
- "MEG",
579
- "MES",
580
- "MLA",
581
- "MLI",
582
- "MOH",
583
- "MPD",
584
- "MRD",
585
- "MSE",
586
- "MYR",
587
- "N",
588
- "NA",
589
- "NH2",
590
- "NH4",
591
- "NHE",
592
- "NO3",
593
- "O4B",
594
- "OHE",
595
- "OLA",
596
- "OLC",
597
- "OMB",
598
- "OME",
599
- "OXA",
600
- "P6G",
601
- "PE3",
602
- "PE4",
603
- "PEG",
604
- "PEO",
605
- "PEP",
606
- "PG0",
607
- "PG4",
608
- "PGE",
609
- "PGR",
610
- "PLM",
611
- "PO4",
612
- "POL",
613
- "POP",
614
- "PVO",
615
- "SAR",
616
- "SCN",
617
- "SEO",
618
- "SEP",
619
- "SIN",
620
- "SO4",
621
- "SPD",
622
- "SPM",
623
- "SR",
624
- "STE",
625
- "STO",
626
- "STU",
627
- "TAR",
628
- "TBU",
629
- "TME",
630
- "TPO",
631
- "TRS",
632
- "UNK",
633
- "UNL",
634
- "UNX",
635
- "UPL",
636
- "URE",
637
- }
638
-
639
-
640
- ####################################################################################################
641
- # TEMPLATES
642
- ####################################################################################################
643
-
644
- min_coverage_residues = 10
645
- min_coverage_fraction = 0.1
646
-
647
-
648
- ####################################################################################################
649
- # Ambiguous atoms
650
- ####################################################################################################
651
-
652
- ambiguous_atoms = {
653
- "CA": {
654
- "*": "C",
655
- "OEX": "CA",
656
- "OEC": "CA",
657
- "543": "CA",
658
- "OC6": "CA",
659
- "OC1": "CA",
660
- "OC7": "CA",
661
- "OEY": "CA",
662
- "OC4": "CA",
663
- "OC3": "CA",
664
- "ICA": "CA",
665
- "CA": "CA",
666
- "OC2": "CA",
667
- "OC5": "CA",
668
- },
669
- "CD": {"*": "C", "CD": "CD", "CD3": "CD", "CD5": "CD", "CD1": "CD"},
670
- "BR": "BR",
671
- "CL": {
672
- "*": "CL",
673
- "C8P": "C",
674
- "L3T": "C",
675
- "TLC": "C",
676
- "TZ0": "C",
677
- "471": "C",
678
- "NLK": "C",
679
- "PGM": "C",
680
- "PNE": "C",
681
- "RCY": "C",
682
- "11F": "C",
683
- "PII": "C",
684
- "C1Q": "C",
685
- "4MD": "C",
686
- "R5A": "C",
687
- "KW2": "C",
688
- "I7M": "C",
689
- "R48": "C",
690
- "FC3": "C",
691
- "55V": "C",
692
- "KPF": "C",
693
- "SPZ": "C",
694
- "0TT": "C",
695
- "R9A": "C",
696
- "5NA": "C",
697
- "C55": "C",
698
- "NIX": "C",
699
- "5PM": "C",
700
- "PP8": "C",
701
- "544": "C",
702
- "812": "C",
703
- "NPM": "C",
704
- "KU8": "C",
705
- "A1AMM": "C",
706
- "4S0": "C",
707
- "AQC": "C",
708
- "2JK": "C",
709
- "WJR": "C",
710
- "A1AAW": "C",
711
- "85E": "C",
712
- "MB0": "C",
713
- "ZAB": "C",
714
- "85K": "C",
715
- "GBP": "C",
716
- "A1H80": "C",
717
- "A1AFR": "C",
718
- "L9M": "C",
719
- "MYK": "C",
720
- "MB9": "C",
721
- "38R": "C",
722
- "EKB": "C",
723
- "NKF": "C",
724
- "UMQ": "C",
725
- "T4K": "C",
726
- "3PT": "C",
727
- "A1A7S": "C",
728
- "1Q9": "C",
729
- "11R": "C",
730
- "D2V": "C",
731
- "SM8": "C",
732
- "IFC": "C",
733
- "DB5": "C",
734
- "L2T": "C",
735
- "GNB": "C",
736
- "PP7": "C",
737
- "072": "C",
738
- "P88": "C",
739
- "DRL": "C",
740
- "C9W": "C",
741
- "NTP": "C",
742
- "4HJ": "C",
743
- "7NA": "C",
744
- "LPC": "C",
745
- "T8W": "C",
746
- "63R": "C",
747
- "570": "C",
748
- "R4A": "C",
749
- "3BG": "C",
750
- "4RB": "C",
751
- "GSO": "C",
752
- "BQ6": "C",
753
- "R4P": "C",
754
- "5CP": "C",
755
- "TTR": "C",
756
- "6UZ": "C",
757
- "SPJ": "C",
758
- "0SA": "C",
759
- "ZL1": "C",
760
- "BYG": "C",
761
- "F0E": "C",
762
- "PC0": "C",
763
- "B2Q": "C",
764
- "KV6": "C",
765
- "NTO": "C",
766
- "CLG": "C",
767
- "R7U": "C",
768
- "SMQ": "C",
769
- "GM2": "C",
770
- "Z7P": "C",
771
- "NXF": "C",
772
- "C6Q": "C",
773
- "A1G": "C",
774
- "433": "C",
775
- "L9N": "C",
776
- "7OX": "C",
777
- "A1H84": "C",
778
- "97L": "C",
779
- "HDV": "C",
780
- "LUO": "C",
781
- "R6A": "C",
782
- "1PC": "C",
783
- "4PT": "C",
784
- "SBZ": "C",
785
- "EAB": "C",
786
- "FL4": "C",
787
- "OPS": "C",
788
- "C2X": "C",
789
- "SLL": "C",
790
- "BFC": "C",
791
- "GIP": "C",
792
- "7CP": "C",
793
- "CLH": "C",
794
- "34E": "C",
795
- "5NE": "C",
796
- "PBF": "C",
797
- "ABD": "C",
798
- "ABC": "C",
799
- "LPF": "C",
800
- "TIZ": "C",
801
- "4HH": "C",
802
- "AFC": "C",
803
- "WQH": "C",
804
- "9JL": "C",
805
- "CS3": "C",
806
- "NL0": "C",
807
- "KPY": "C",
808
- "DNA": "C",
809
- "B3C": "C",
810
- "TKL": "C",
811
- "KVS": "C",
812
- "HO6": "C",
813
- "NLH": "C",
814
- "1PB": "C",
815
- "CYF": "C",
816
- "G4M": "C",
817
- "R5B": "C",
818
- "N4S": "C",
819
- "N11": "C",
820
- "C8F": "C",
821
- "PIJ": "C",
822
- "WIN": "C",
823
- "NT1": "C",
824
- "WJW": "C",
825
- "HF7": "C",
826
- "TY1": "C",
827
- "VM1": "C",
828
- },
829
- "OS": {"*": "O", "DWC": "OS", "OHX": "OS", "OS": "OS", "8WV": "OS", "OS4": "OS"},
830
- "PB": {"*": "P", "ZN9": "PB", "ZN7": "PB", "PBM": "PB", "PB": "PB", "CSB": "PB"},
831
- "CE": {"*": "C", "CE": "CE"},
832
- "FE": {"*": "FE", "TFR": "F", "PF5": "F", "IFC": "F", "F5C": "F"},
833
- "NA": {"*": "N", "CGO": "NA", "R2K": "NA", "LVQ": "NA", "NA": "NA"},
834
- "ND": {"*": "N", "ND": "ND"},
835
- "CF": {"*": "C", "CF": "CF"},
836
- "RU": "RU",
837
- "BRAF": "BR",
838
- "EU": "EU",
839
- "CLAA": "CL",
840
- "CLBQ": "CL",
841
- "CM": {"*": "C", "ZCM": "CM"},
842
- "SN": {"*": "SN", "TAP": "S", "SND": "S", "TAD": "S", "XPT": "S"},
843
- "AG": "AG",
844
- "CLN": "CL",
845
- "CLM": "CL",
846
- "CLA": {"*": "CL", "PII": "C", "TDL": "C", "D0J": "C", "GM2": "C", "PIJ": "C"},
847
- "CLB": {
848
- "*": "CL",
849
- "TD5": "C",
850
- "PII": "C",
851
- "TDL": "C",
852
- "GM2": "C",
853
- "TD7": "C",
854
- "TD6": "C",
855
- "PIJ": "C",
856
- },
857
- "CR": {
858
- "*": "C",
859
- "BW9": "CR",
860
- "CQ4": "CR",
861
- "AC9": "CR",
862
- "TIL": "CR",
863
- "J7U": "CR",
864
- "CR": "CR",
865
- },
866
- "CLAY": "CL",
867
- "CLBC": "CL",
868
- "PD": {
869
- "*": "P",
870
- "F6Q": "PD",
871
- "SVP": "PD",
872
- "SXC": "PD",
873
- "U5U": "PD",
874
- "PD": "PD",
875
- "PLL": "PD",
876
- },
877
- "CO": {
878
- "*": "C",
879
- "J1S": "CO",
880
- "OCN": "CO",
881
- "OL3": "CO",
882
- "OL4": "CO",
883
- "B12": "CO",
884
- "XCO": "CO",
885
- "UFU": "CO",
886
- "CON": "CO",
887
- "OL5": "CO",
888
- "B13": "CO",
889
- "7KI": "CO",
890
- "PL1": "CO",
891
- "OCO": "CO",
892
- "J1R": "CO",
893
- "COH": "CO",
894
- "SIR": "CO",
895
- "6KI": "CO",
896
- "NCO": "CO",
897
- "9CO": "CO",
898
- "PC3": "CO",
899
- "BWU": "CO",
900
- "B1Z": "CO",
901
- "J83": "CO",
902
- "CO": "CO",
903
- "COY": "CO",
904
- "CNC": "CO",
905
- "3CO": "CO",
906
- "OCL": "CO",
907
- "R5Q": "CO",
908
- "X5Z": "CO",
909
- "CBY": "CO",
910
- "OLS": "CO",
911
- "F0X": "CO",
912
- "I2A": "CO",
913
- "OCM": "CO",
914
- },
915
- "CU": {
916
- "*": "C",
917
- "8ZR": "CU",
918
- "K7E": "CU",
919
- "CU3": "CU",
920
- "SI9": "CU",
921
- "35N": "CU",
922
- "C2O": "CU",
923
- "SI7": "CU",
924
- "B15": "CU",
925
- "SI0": "CU",
926
- "CUP": "CU",
927
- "SQ1": "CU",
928
- "CUK": "CU",
929
- "CUL": "CU",
930
- "SI8": "CU",
931
- "IC4": "CU",
932
- "CUM": "CU",
933
- "MM2": "CU",
934
- "B30": "CU",
935
- "S32": "CU",
936
- "V79": "CU",
937
- "IMF": "CU",
938
- "CUN": "CU",
939
- "MM1": "CU",
940
- "MP1": "CU",
941
- "IME": "CU",
942
- "B17": "CU",
943
- "C2C": "CU",
944
- "1CU": "CU",
945
- "CU6": "CU",
946
- "C1O": "CU",
947
- "CU1": "CU",
948
- "B22": "CU",
949
- "CUS": "CU",
950
- "RUQ": "CU",
951
- "CUF": "CU",
952
- "CUA": "CU",
953
- "CU": "CU",
954
- "CUO": "CU",
955
- "0TE": "CU",
956
- "SI4": "CU",
957
- },
958
- "CS": {"*": "C", "CS": "CS"},
959
- "CLQ": "CL",
960
- "CLR": "CL",
961
- "CLU": "CL",
962
- "TE": "TE",
963
- "NI": {
964
- "*": "N",
965
- "USN": "NI",
966
- "NFO": "NI",
967
- "NI2": "NI",
968
- "NFS": "NI",
969
- "NFR": "NI",
970
- "82N": "NI",
971
- "R5N": "NI",
972
- "NFU": "NI",
973
- "A1ICD": "NI",
974
- "NI3": "NI",
975
- "M43": "NI",
976
- "MM5": "NI",
977
- "BF8": "NI",
978
- "TCN": "NI",
979
- "NIK": "NI",
980
- "CUV": "NI",
981
- "MM6": "NI",
982
- "J52": "NI",
983
- "NI": "NI",
984
- "SNF": "NI",
985
- "XCC": "NI",
986
- "F0L": "NI",
987
- "UWE": "NI",
988
- "NFC": "NI",
989
- "3NI": "NI",
990
- "HNI": "NI",
991
- "F43": "NI",
992
- "RQM": "NI",
993
- "NFE": "NI",
994
- "NFB": "NI",
995
- "B51": "NI",
996
- "NI1": "NI",
997
- "WCC": "NI",
998
- "NUF": "NI",
999
- },
1000
- "SB": {"*": "S", "UJI": "SB", "SB": "SB", "118": "SB", "SBO": "SB", "3CG": "SB"},
1001
- "MO": "MO",
1002
- "SEG": "SE",
1003
- "CLL": "CL",
1004
- "CLAH": "CL",
1005
- "CLC": {
1006
- "*": "CL",
1007
- "TD5": "C",
1008
- "PII": "C",
1009
- "TDL": "C",
1010
- "GM2": "C",
1011
- "TD7": "C",
1012
- "TD6": "C",
1013
- "PIJ": "C",
1014
- },
1015
- "CLD": {"*": "CL", "PII": "C", "GM2": "C", "PIJ": "C"},
1016
- "CLAD": "CL",
1017
- "CLAE": "CL",
1018
- "LA": "LA",
1019
- "RH": "RH",
1020
- "BRAC": "BR",
1021
- "BRAD": "BR",
1022
- "CLBN": "CL",
1023
- "CLAC": "CL",
1024
- "BRAB": "BR",
1025
- "BRAE": "BR",
1026
- "MG": "MG",
1027
- "IR": "IR",
1028
- "SE": {
1029
- "*": "SE",
1030
- "HII": "S",
1031
- "NT2": "S",
1032
- "R2P": "S",
1033
- "S2P": "S",
1034
- "0IU": "S",
1035
- "QMB": "S",
1036
- "81S": "S",
1037
- "0QB": "S",
1038
- "UB4": "S",
1039
- "OHS": "S",
1040
- "Q78": "S",
1041
- "0Y2": "S",
1042
- "B3M": "S",
1043
- "NT1": "S",
1044
- "81R": "S",
1045
- },
1046
- "BRAG": "BR",
1047
- "CLF": {"*": "CL", "PII": "C", "GM2": "C", "PIJ": "C"},
1048
- "CLE": {"*": "CL", "PII": "C", "GM2": "C", "PIJ": "C"},
1049
- "BRAX": "BR",
1050
- "CLK": "CL",
1051
- "ZN": "ZN",
1052
- "AS": "AS",
1053
- "AU": "AU",
1054
- "PT": "PT",
1055
- "CLAS": "CL",
1056
- "MN": "MN",
1057
- "CLBE": "CL",
1058
- "CLBF": "CL",
1059
- "CLAF": "CL",
1060
- "NA'": {"*": "N", "CGO": "NA"},
1061
- "BRAH": "BR",
1062
- "BRAI": "BR",
1063
- "BRA": "BR",
1064
- "BRB": "BR",
1065
- "BRAV": "BR",
1066
- "HG": {
1067
- "*": "HG",
1068
- "BBA": "H",
1069
- "MID": "H",
1070
- "APM": "H",
1071
- "4QQ": "H",
1072
- "0ZG": "H",
1073
- "APH": "H",
1074
- },
1075
- "AR": "AR",
1076
- "D": "H",
1077
- "CLAN": "CL",
1078
- "SI": "SI",
1079
- "CLS": "CL",
1080
- "ZR": "ZR",
1081
- "CLAR": {"*": "CL", "ZM4": "C"},
1082
- "HO": "HO",
1083
- "CLI": {"*": "CL", "GM2": "C"},
1084
- "CLH": {"*": "CL", "GM2": "C"},
1085
- "CLAP": "CL",
1086
- "CLBL": "CL",
1087
- "CLBM": "CL",
1088
- "PR": {"*": "PR", "UF0": "P", "252": "P"},
1089
- "IN": "IN",
1090
- "CLJ": "CL",
1091
- "BRU": "BR",
1092
- "SC": {"*": "S", "SFL": "SC"},
1093
- "CLG": {"*": "CL", "GM2": "C"},
1094
- "BRAT": "BR",
1095
- "BRAR": "BR",
1096
- "CLAG": "CL",
1097
- "CLAB": "CL",
1098
- "CLV": "CL",
1099
- "TI": "TI",
1100
- "CLAX": "CL",
1101
- "CLAJ": "CL",
1102
- "CL'": {"*": "CL", "BNR": "C", "25A": "C", "BDA": "C"},
1103
- "CLAW": "CL",
1104
- "BRF": "BR",
1105
- "BRE": "BR",
1106
- "RE": "RE",
1107
- "GD": "GD",
1108
- "SM": {"*": "S", "SM": "SM"},
1109
- "CLBH": "CL",
1110
- "CLBI": "CL",
1111
- "CLAI": "CL",
1112
- "CLY": "CL",
1113
- "CLZ": "CL",
1114
- "AC": "AC",
1115
- "BR'": "BR",
1116
- "CLT": "CL",
1117
- "CLO": "CL",
1118
- "CLP": "CL",
1119
- "LU": "LU",
1120
- "BA": {"*": "B", "BA": "BA"},
1121
- "CLAU": "CL",
1122
- "RB": "RB",
1123
- "LI": "LI",
1124
- "MOM": "MO",
1125
- "BRAQ": "BR",
1126
- "SR": {"*": "S", "SR": "SR", "OER": "SR"},
1127
- "CLAT": "CL",
1128
- "BRAL": "BR",
1129
- "SEB": "SE",
1130
- "CLW": "CL",
1131
- "CLX": "CL",
1132
- "BE": "BE",
1133
- "BRG": "BR",
1134
- "SEA": "SE",
1135
- "BRAW": "BR",
1136
- "BRBB": "BR",
1137
- "ER": "ER",
1138
- "TH": "TH",
1139
- "BRR": "BR",
1140
- "CLBV": "CL",
1141
- "AL": "AL",
1142
- "CLAV": "CL",
1143
- "BRH": "BR",
1144
- "CLAQ": "CL",
1145
- "GA": "GA",
1146
- "X": "*",
1147
- "TL": "TL",
1148
- "CLBB": "CL",
1149
- "TB": "TB",
1150
- "CLAK": "CL",
1151
- "XE": {"*": "*", "XE": "XE"},
1152
- "SEL": "SE",
1153
- "PU": {"*": "P", "4PU": "PU"},
1154
- "CLAZ": "CL",
1155
- "SE'": "SE",
1156
- "CLBA": "CL",
1157
- "SEN": "SE",
1158
- "SNN": "SN",
1159
- "MOB": "MO",
1160
- "YB": "YB",
1161
- "BRC": "BR",
1162
- "BRD": "BR",
1163
- "CLAM": "CL",
1164
- "DA": "H",
1165
- "DB": "H",
1166
- "DC": "H",
1167
- "DXT": "H",
1168
- "DXU": "H",
1169
- "DXX": "H",
1170
- "DXY": "H",
1171
- "DXZ": "H",
1172
- "DY": "DY",
1173
- "TA": "TA",
1174
- "XD": "*",
1175
- "SED": "SE",
1176
- "CLAL": "CL",
1177
- "BRAJ": "BR",
1178
- "AM": "AM",
1179
- "CLAO": "CL",
1180
- "BI": "BI",
1181
- "KR": "KR",
1182
- "BRBJ": "BR",
1183
- "UNK": "*",
1184
- }
 
1
+ ####################################################################################################
2
+ # CHAINS
3
+ ####################################################################################################
4
+
5
+ chain_types = [
6
+ "PROTEIN",
7
+ "DNA",
8
+ "RNA",
9
+ "NONPOLYMER",
10
+ ]
11
+ chain_type_ids = {chain: i for i, chain in enumerate(chain_types)}
12
+
13
+ out_types = [
14
+ "dna_protein",
15
+ "rna_protein",
16
+ "ligand_protein",
17
+ "dna_ligand",
18
+ "rna_ligand",
19
+ "intra_ligand",
20
+ "intra_dna",
21
+ "intra_rna",
22
+ "intra_protein",
23
+ "protein_protein",
24
+ "modified",
25
+ ]
26
+
27
+ out_types_weights_af3 = {
28
+ "dna_protein": 10.0,
29
+ "rna_protein": 10.0,
30
+ "ligand_protein": 10.0,
31
+ "dna_ligand": 5.0,
32
+ "rna_ligand": 5.0,
33
+ "intra_ligand": 20.0,
34
+ "intra_dna": 4.0,
35
+ "intra_rna": 16.0,
36
+ "intra_protein": 20.0,
37
+ "protein_protein": 20.0,
38
+ "modified": 0.0,
39
+ }
40
+
41
+ out_types_weights = {
42
+ "dna_protein": 5.0,
43
+ "rna_protein": 5.0,
44
+ "ligand_protein": 20.0,
45
+ "dna_ligand": 2.0,
46
+ "rna_ligand": 2.0,
47
+ "intra_ligand": 20.0,
48
+ "intra_dna": 2.0,
49
+ "intra_rna": 8.0,
50
+ "intra_protein": 20.0,
51
+ "protein_protein": 20.0,
52
+ "modified": 0.0,
53
+ }
54
+
55
+
56
+ out_single_types = ["protein", "ligand", "dna", "rna"]
57
+
58
+ clash_types = [
59
+ "dna_protein",
60
+ "rna_protein",
61
+ "ligand_protein",
62
+ "protein_protein",
63
+ "dna_ligand",
64
+ "rna_ligand",
65
+ "ligand_ligand",
66
+ "rna_dna",
67
+ "dna_dna",
68
+ "rna_rna",
69
+ ]
70
+
71
+ chain_types_to_clash_type = {
72
+ frozenset(("PROTEIN", "DNA")): "dna_protein",
73
+ frozenset(("PROTEIN", "RNA")): "rna_protein",
74
+ frozenset(("PROTEIN", "NONPOLYMER")): "ligand_protein",
75
+ frozenset(("PROTEIN",)): "protein_protein",
76
+ frozenset(("NONPOLYMER", "DNA")): "dna_ligand",
77
+ frozenset(("NONPOLYMER", "RNA")): "rna_ligand",
78
+ frozenset(("NONPOLYMER",)): "ligand_ligand",
79
+ frozenset(("DNA", "RNA")): "rna_dna",
80
+ frozenset(("DNA",)): "dna_dna",
81
+ frozenset(("RNA",)): "rna_rna",
82
+ }
83
+
84
+ chain_type_to_out_single_type = {
85
+ "PROTEIN": "protein",
86
+ "DNA": "dna",
87
+ "RNA": "rna",
88
+ "NONPOLYMER": "ligand",
89
+ }
90
+ ####################################################################################################
91
+ # RESIDUES & TOKENS
92
+ ####################################################################################################
93
+
94
+
95
+ canonical_tokens = [
96
+ "ALA",
97
+ "ARG",
98
+ "ASN",
99
+ "ASP",
100
+ "CYS",
101
+ "GLN",
102
+ "GLU",
103
+ "GLY",
104
+ "HIS",
105
+ "ILE",
106
+ "LEU",
107
+ "LYS",
108
+ "MET",
109
+ "PHE",
110
+ "PRO",
111
+ "SER",
112
+ "THR",
113
+ "TRP",
114
+ "TYR",
115
+ "VAL",
116
+ "UNK", # unknown protein token
117
+ ]
118
+
119
+ tokens = [
120
+ "<pad>",
121
+ "-",
122
+ *canonical_tokens,
123
+ "A",
124
+ "G",
125
+ "C",
126
+ "U",
127
+ "N", # unknown rna token
128
+ "DA",
129
+ "DG",
130
+ "DC",
131
+ "DT",
132
+ "DN", # unknown dna token
133
+ ]
134
+
135
+ token_ids = {token: i for i, token in enumerate(tokens)}
136
+ num_tokens = len(tokens)
137
+ unk_token = {"PROTEIN": "UNK", "DNA": "DN", "RNA": "N"}
138
+ unk_token_ids = {m: token_ids[t] for m, t in unk_token.items()}
139
+
140
+ prot_letter_to_token = {
141
+ "A": "ALA",
142
+ "R": "ARG",
143
+ "N": "ASN",
144
+ "D": "ASP",
145
+ "C": "CYS",
146
+ "E": "GLU",
147
+ "Q": "GLN",
148
+ "G": "GLY",
149
+ "H": "HIS",
150
+ "I": "ILE",
151
+ "L": "LEU",
152
+ "K": "LYS",
153
+ "M": "MET",
154
+ "F": "PHE",
155
+ "P": "PRO",
156
+ "S": "SER",
157
+ "T": "THR",
158
+ "W": "TRP",
159
+ "Y": "TYR",
160
+ "V": "VAL",
161
+ "X": "UNK",
162
+ "J": "UNK",
163
+ "B": "UNK",
164
+ "Z": "UNK",
165
+ "O": "UNK",
166
+ "U": "UNK",
167
+ "-": "-",
168
+ }
169
+
170
+ prot_token_to_letter = {v: k for k, v in prot_letter_to_token.items()}
171
+ prot_token_to_letter["UNK"] = "X"
172
+
173
+ rna_letter_to_token = {
174
+ "A": "A",
175
+ "G": "G",
176
+ "C": "C",
177
+ "U": "U",
178
+ "N": "N",
179
+ }
180
+ rna_token_to_letter = {v: k for k, v in rna_letter_to_token.items()}
181
+
182
+ dna_letter_to_token = {
183
+ "A": "DA",
184
+ "G": "DG",
185
+ "C": "DC",
186
+ "T": "DT",
187
+ "N": "DN",
188
+ }
189
+ dna_token_to_letter = {v: k for k, v in dna_letter_to_token.items()}
190
+
191
+ ####################################################################################################
192
+ # ATOMS
193
+ ####################################################################################################
194
+
195
+ num_elements = 128
196
+
197
+ chirality_types = [
198
+ "CHI_UNSPECIFIED",
199
+ "CHI_TETRAHEDRAL_CW",
200
+ "CHI_TETRAHEDRAL_CCW",
201
+ "CHI_SQUAREPLANAR",
202
+ "CHI_OCTAHEDRAL",
203
+ "CHI_TRIGONALBIPYRAMIDAL",
204
+ "CHI_OTHER",
205
+ ]
206
+ chirality_type_ids = {chirality: i for i, chirality in enumerate(chirality_types)}
207
+ unk_chirality_type = "CHI_OTHER"
208
+
209
+ hybridization_map = [
210
+ "S",
211
+ "SP",
212
+ "SP2",
213
+ "SP2D",
214
+ "SP3",
215
+ "SP3D",
216
+ "SP3D2",
217
+ "OTHER",
218
+ "UNSPECIFIED",
219
+ ]
220
+ hybridization_type_ids = {hybrid: i for i, hybrid in enumerate(hybridization_map)}
221
+ unk_hybridization_type = "UNSPECIFIED"
222
+
223
+ # fmt: off
224
+ ref_atoms = {
225
+ "PAD": [],
226
+ "UNK": ["N", "CA", "C", "O", "CB"],
227
+ "-": [],
228
+ "ALA": ["N", "CA", "C", "O", "CB"],
229
+ "ARG": ["N", "CA", "C", "O", "CB", "CG", "CD", "NE", "CZ", "NH1", "NH2"],
230
+ "ASN": ["N", "CA", "C", "O", "CB", "CG", "OD1", "ND2"],
231
+ "ASP": ["N", "CA", "C", "O", "CB", "CG", "OD1", "OD2"],
232
+ "CYS": ["N", "CA", "C", "O", "CB", "SG"],
233
+ "GLN": ["N", "CA", "C", "O", "CB", "CG", "CD", "OE1", "NE2"],
234
+ "GLU": ["N", "CA", "C", "O", "CB", "CG", "CD", "OE1", "OE2"],
235
+ "GLY": ["N", "CA", "C", "O"],
236
+ "HIS": ["N", "CA", "C", "O", "CB", "CG", "ND1", "CD2", "CE1", "NE2"],
237
+ "ILE": ["N", "CA", "C", "O", "CB", "CG1", "CG2", "CD1"],
238
+ "LEU": ["N", "CA", "C", "O", "CB", "CG", "CD1", "CD2"],
239
+ "LYS": ["N", "CA", "C", "O", "CB", "CG", "CD", "CE", "NZ"],
240
+ "MET": ["N", "CA", "C", "O", "CB", "CG", "SD", "CE"],
241
+ "PHE": ["N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "CE1", "CE2", "CZ"],
242
+ "PRO": ["N", "CA", "C", "O", "CB", "CG", "CD"],
243
+ "SER": ["N", "CA", "C", "O", "CB", "OG"],
244
+ "THR": ["N", "CA", "C", "O", "CB", "OG1", "CG2"],
245
+ "TRP": ["N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "NE1", "CE2", "CE3", "CZ2", "CZ3", "CH2"], # noqa: E501
246
+ "TYR": ["N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "CE1", "CE2", "CZ", "OH"],
247
+ "VAL": ["N", "CA", "C", "O", "CB", "CG1", "CG2"],
248
+ "A": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "O2'", "C1'", "N9", "C8", "N7", "C5", "C6", "N6", "N1", "C2", "N3", "C4"], # noqa: E501
249
+ "G": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "O2'", "C1'", "N9", "C8", "N7", "C5", "C6", "O6", "N1", "C2", "N2", "N3", "C4"], # noqa: E501
250
+ "C": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "O2'", "C1'", "N1", "C2", "O2", "N3", "C4", "N4", "C5", "C6"], # noqa: E501
251
+ "U": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "O2'", "C1'", "N1", "C2", "O2", "N3", "C4", "O4", "C5", "C6"], # noqa: E501
252
+ "N": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "O2'", "C1'"], # noqa: E501
253
+ "DA": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "C1'", "N9", "C8", "N7", "C5", "C6", "N6", "N1", "C2", "N3", "C4"], # noqa: E501
254
+ "DG": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "C1'", "N9", "C8", "N7", "C5", "C6", "O6", "N1", "C2", "N2", "N3", "C4"], # noqa: E501
255
+ "DC": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "C1'", "N1", "C2", "O2", "N3", "C4", "N4", "C5", "C6"], # noqa: E501
256
+ "DT": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "C1'", "N1", "C2", "O2", "N3", "C4", "O4", "C5", "C7", "C6"], # noqa: E501
257
+ "DN": ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "C1'"]
258
+ }
259
+
260
+ protein_backbone_atom_names = ["N", "CA", "C", "O"]
261
+ nucleic_backbone_atom_names = ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "O2'", "C1'"]
262
+
263
+ protein_backbone_atom_index = {name: i for i, name in enumerate(protein_backbone_atom_names)}
264
+ nucleic_backbone_atom_index = {name: i for i, name in enumerate(nucleic_backbone_atom_names)}
265
+
266
+ ref_symmetries = {
267
+ "PAD": [],
268
+ "ALA": [],
269
+ "ARG": [],
270
+ "ASN": [],
271
+ "ASP": [[(6, 7), (7, 6)]],
272
+ "CYS": [],
273
+ "GLN": [],
274
+ "GLU": [[(7, 8), (8, 7)]],
275
+ "GLY": [],
276
+ "HIS": [],
277
+ "ILE": [],
278
+ "LEU": [],
279
+ "LYS": [],
280
+ "MET": [],
281
+ "PHE": [[(6, 7), (7, 6), (8, 9), (9, 8)]],
282
+ "PRO": [],
283
+ "SER": [],
284
+ "THR": [],
285
+ "TRP": [],
286
+ "TYR": [[(6, 7), (7, 6), (8, 9), (9, 8)]],
287
+ "VAL": [],
288
+ "A": [[(1, 2), (2, 1)]],
289
+ "G": [[(1, 2), (2, 1)]],
290
+ "C": [[(1, 2), (2, 1)]],
291
+ "U": [[(1, 2), (2, 1)]],
292
+ #"N": [[(1, 2), (2, 1)]],
293
+ "DA": [[(1, 2), (2, 1)]],
294
+ "DG": [[(1, 2), (2, 1)]],
295
+ "DC": [[(1, 2), (2, 1)]],
296
+ "DT": [[(1, 2), (2, 1)]],
297
+ #"DN": [[(1, 2), (2, 1)]]
298
+ }
299
+
300
+
301
+ res_to_center_atom = {
302
+ "UNK": "CA",
303
+ "ALA": "CA",
304
+ "ARG": "CA",
305
+ "ASN": "CA",
306
+ "ASP": "CA",
307
+ "CYS": "CA",
308
+ "GLN": "CA",
309
+ "GLU": "CA",
310
+ "GLY": "CA",
311
+ "HIS": "CA",
312
+ "ILE": "CA",
313
+ "LEU": "CA",
314
+ "LYS": "CA",
315
+ "MET": "CA",
316
+ "PHE": "CA",
317
+ "PRO": "CA",
318
+ "SER": "CA",
319
+ "THR": "CA",
320
+ "TRP": "CA",
321
+ "TYR": "CA",
322
+ "VAL": "CA",
323
+ "A": "C1'",
324
+ "G": "C1'",
325
+ "C": "C1'",
326
+ "U": "C1'",
327
+ "N": "C1'",
328
+ "DA": "C1'",
329
+ "DG": "C1'",
330
+ "DC": "C1'",
331
+ "DT": "C1'",
332
+ "DN": "C1'"
333
+ }
334
+
335
+ res_to_disto_atom = {
336
+ "UNK": "CB",
337
+ "ALA": "CB",
338
+ "ARG": "CB",
339
+ "ASN": "CB",
340
+ "ASP": "CB",
341
+ "CYS": "CB",
342
+ "GLN": "CB",
343
+ "GLU": "CB",
344
+ "GLY": "CA",
345
+ "HIS": "CB",
346
+ "ILE": "CB",
347
+ "LEU": "CB",
348
+ "LYS": "CB",
349
+ "MET": "CB",
350
+ "PHE": "CB",
351
+ "PRO": "CB",
352
+ "SER": "CB",
353
+ "THR": "CB",
354
+ "TRP": "CB",
355
+ "TYR": "CB",
356
+ "VAL": "CB",
357
+ "A": "C4",
358
+ "G": "C4",
359
+ "C": "C2",
360
+ "U": "C2",
361
+ "N": "C1'",
362
+ "DA": "C4",
363
+ "DG": "C4",
364
+ "DC": "C2",
365
+ "DT": "C2",
366
+ "DN": "C1'"
367
+ }
368
+
369
+ res_to_center_atom_id = {
370
+ res: ref_atoms[res].index(atom)
371
+ for res, atom in res_to_center_atom.items()
372
+ }
373
+
374
+ res_to_disto_atom_id = {
375
+ res: ref_atoms[res].index(atom)
376
+ for res, atom in res_to_disto_atom.items()
377
+ }
378
+
379
+ # fmt: on
380
+
381
+ ####################################################################################################
382
+ # BONDS
383
+ ####################################################################################################
384
+
385
+ atom_interface_cutoff = 5.0
386
+ interface_cutoff = 15.0
387
+
388
+ bond_types = [
389
+ "OTHER",
390
+ "SINGLE",
391
+ "DOUBLE",
392
+ "TRIPLE",
393
+ "AROMATIC",
394
+ "COVALENT",
395
+ ]
396
+ bond_type_ids = {bond: i for i, bond in enumerate(bond_types)}
397
+ unk_bond_type = "OTHER"
398
+
399
+
400
+ ####################################################################################################
401
+ # Contacts
402
+ ####################################################################################################
403
+
404
+
405
+ pocket_contact_info = {
406
+ "UNSPECIFIED": 0,
407
+ "UNSELECTED": 1,
408
+ "POCKET": 2,
409
+ "BINDER": 3,
410
+ }
411
+
412
+ contact_conditioning_info = {
413
+ "UNSPECIFIED": 0,
414
+ "UNSELECTED": 1,
415
+ "POCKET>BINDER": 2,
416
+ "BINDER>POCKET": 3,
417
+ "CONTACT": 4,
418
+ }
419
+
420
+
421
+ ####################################################################################################
422
+ # MSA
423
+ ####################################################################################################
424
+
425
+ max_msa_seqs = 16384
426
+ max_paired_seqs = 8192
427
+
428
+
429
+ ####################################################################################################
430
+ # CHUNKING
431
+ ####################################################################################################
432
+
433
+ chunk_size_threshold = 384
434
+
435
+ ####################################################################################################
436
+ # Method conditioning
437
+ ####################################################################################################
438
+
439
+ # Methods
440
+ method_types_ids = {
441
+ "MD": 0,
442
+ "X-RAY DIFFRACTION": 1,
443
+ "ELECTRON MICROSCOPY": 2,
444
+ "SOLUTION NMR": 3,
445
+ "SOLID-STATE NMR": 4,
446
+ "NEUTRON DIFFRACTION": 4,
447
+ "ELECTRON CRYSTALLOGRAPHY": 4,
448
+ "FIBER DIFFRACTION": 4,
449
+ "POWDER DIFFRACTION": 4,
450
+ "INFRARED SPECTROSCOPY": 4,
451
+ "FLUORESCENCE TRANSFER": 4,
452
+ "EPR": 4,
453
+ "THEORETICAL MODEL": 4,
454
+ "SOLUTION SCATTERING": 4,
455
+ "OTHER": 4,
456
+ "AFDB": 5,
457
+ "BOLTZ-1": 6,
458
+ "FUTURE1": 7, # Placeholder for future supervision sources
459
+ "FUTURE2": 8,
460
+ "FUTURE3": 9,
461
+ "FUTURE4": 10,
462
+ "FUTURE5": 11,
463
+ }
464
+ method_types_ids = {k.lower(): v for k, v in method_types_ids.items()}
465
+ num_method_types = len(set(method_types_ids.values()))
466
+
467
+ # Temperature
468
+ temperature_bins = [(265, 280), (280, 295), (295, 310)]
469
+ temperature_bins_ids = {temp: i for i, temp in enumerate(temperature_bins)}
470
+ temperature_bins_ids["other"] = len(temperature_bins)
471
+ num_temp_bins = len(temperature_bins_ids)
472
+
473
+
474
+ # pH
475
+ ph_bins = [(0, 6), (6, 8), (8, 14)]
476
+ ph_bins_ids = {ph: i for i, ph in enumerate(ph_bins)}
477
+ ph_bins_ids["other"] = len(ph_bins)
478
+ num_ph_bins = len(ph_bins_ids)
479
+
480
+ ####################################################################################################
481
+ # VDW_RADII
482
+ ####################################################################################################
483
+
484
+ # fmt: off
485
+ vdw_radii = [
486
+ 1.2, 1.4, 2.2, 1.9, 1.8, 1.7, 1.6, 1.55, 1.5, 1.54,
487
+ 2.4, 2.2, 2.1, 2.1, 1.95, 1.8, 1.8, 1.88, 2.8, 2.4,
488
+ 2.3, 2.15, 2.05, 2.05, 2.05, 2.05, 2.0, 2.0, 2.0, 2.1,
489
+ 2.1, 2.1, 2.05, 1.9, 1.9, 2.02, 2.9, 2.55, 2.4, 2.3,
490
+ 2.15, 2.1, 2.05, 2.05, 2.0, 2.05, 2.1, 2.2, 2.2, 2.25,
491
+ 2.2, 2.1, 2.1, 2.16, 3.0, 2.7, 2.5, 2.48, 2.47, 2.45,
492
+ 2.43, 2.42, 2.4, 2.38, 2.37, 2.35, 2.33, 2.32, 2.3, 2.28,
493
+ 2.27, 2.25, 2.2, 2.1, 2.05, 2.0, 2.0, 2.05, 2.1, 2.05,
494
+ 2.2, 2.3, 2.3, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.4,
495
+ 2.0, 2.3, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0,
496
+ 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0,
497
+ 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0
498
+ ]
499
+ # fmt: on
500
+
501
+ ####################################################################################################
502
+ # Excluded ligands
503
+ ####################################################################################################
504
+
505
+ ligand_exclusion = {
506
+ "144",
507
+ "15P",
508
+ "1PE",
509
+ "2F2",
510
+ "2JC",
511
+ "3HR",
512
+ "3SY",
513
+ "7N5",
514
+ "7PE",
515
+ "9JE",
516
+ "AAE",
517
+ "ABA",
518
+ "ACE",
519
+ "ACN",
520
+ "ACT",
521
+ "ACY",
522
+ "AZI",
523
+ "BAM",
524
+ "BCN",
525
+ "BCT",
526
+ "BDN",
527
+ "BEN",
528
+ "BME",
529
+ "BO3",
530
+ "BTB",
531
+ "BTC",
532
+ "BU1",
533
+ "C8E",
534
+ "CAD",
535
+ "CAQ",
536
+ "CBM",
537
+ "CCN",
538
+ "CIT",
539
+ "CL",
540
+ "CLR",
541
+ "CM",
542
+ "CMO",
543
+ "CO3",
544
+ "CPT",
545
+ "CXS",
546
+ "D10",
547
+ "DEP",
548
+ "DIO",
549
+ "DMS",
550
+ "DN",
551
+ "DOD",
552
+ "DOX",
553
+ "EDO",
554
+ "EEE",
555
+ "EGL",
556
+ "EOH",
557
+ "EOX",
558
+ "EPE",
559
+ "ETF",
560
+ "FCY",
561
+ "FJO",
562
+ "FLC",
563
+ "FMT",
564
+ "FW5",
565
+ "GOL",
566
+ "GSH",
567
+ "GTT",
568
+ "GYF",
569
+ "HED",
570
+ "IHP",
571
+ "IHS",
572
+ "IMD",
573
+ "IOD",
574
+ "IPA",
575
+ "IPH",
576
+ "LDA",
577
+ "MB3",
578
+ "MEG",
579
+ "MES",
580
+ "MLA",
581
+ "MLI",
582
+ "MOH",
583
+ "MPD",
584
+ "MRD",
585
+ "MSE",
586
+ "MYR",
587
+ "N",
588
+ "NA",
589
+ "NH2",
590
+ "NH4",
591
+ "NHE",
592
+ "NO3",
593
+ "O4B",
594
+ "OHE",
595
+ "OLA",
596
+ "OLC",
597
+ "OMB",
598
+ "OME",
599
+ "OXA",
600
+ "P6G",
601
+ "PE3",
602
+ "PE4",
603
+ "PEG",
604
+ "PEO",
605
+ "PEP",
606
+ "PG0",
607
+ "PG4",
608
+ "PGE",
609
+ "PGR",
610
+ "PLM",
611
+ "PO4",
612
+ "POL",
613
+ "POP",
614
+ "PVO",
615
+ "SAR",
616
+ "SCN",
617
+ "SEO",
618
+ "SEP",
619
+ "SIN",
620
+ "SO4",
621
+ "SPD",
622
+ "SPM",
623
+ "SR",
624
+ "STE",
625
+ "STO",
626
+ "STU",
627
+ "TAR",
628
+ "TBU",
629
+ "TME",
630
+ "TPO",
631
+ "TRS",
632
+ "UNK",
633
+ "UNL",
634
+ "UNX",
635
+ "UPL",
636
+ "URE",
637
+ }
638
+
639
+
640
+ ####################################################################################################
641
+ # TEMPLATES
642
+ ####################################################################################################
643
+
644
+ min_coverage_residues = 10
645
+ min_coverage_fraction = 0.1
646
+
647
+
648
+ ####################################################################################################
649
+ # Ambiguous atoms
650
+ ####################################################################################################
651
+
652
+ ambiguous_atoms = {
653
+ "CA": {
654
+ "*": "C",
655
+ "OEX": "CA",
656
+ "OEC": "CA",
657
+ "543": "CA",
658
+ "OC6": "CA",
659
+ "OC1": "CA",
660
+ "OC7": "CA",
661
+ "OEY": "CA",
662
+ "OC4": "CA",
663
+ "OC3": "CA",
664
+ "ICA": "CA",
665
+ "CA": "CA",
666
+ "OC2": "CA",
667
+ "OC5": "CA",
668
+ },
669
+ "CD": {"*": "C", "CD": "CD", "CD3": "CD", "CD5": "CD", "CD1": "CD"},
670
+ "BR": "BR",
671
+ "CL": {
672
+ "*": "CL",
673
+ "C8P": "C",
674
+ "L3T": "C",
675
+ "TLC": "C",
676
+ "TZ0": "C",
677
+ "471": "C",
678
+ "NLK": "C",
679
+ "PGM": "C",
680
+ "PNE": "C",
681
+ "RCY": "C",
682
+ "11F": "C",
683
+ "PII": "C",
684
+ "C1Q": "C",
685
+ "4MD": "C",
686
+ "R5A": "C",
687
+ "KW2": "C",
688
+ "I7M": "C",
689
+ "R48": "C",
690
+ "FC3": "C",
691
+ "55V": "C",
692
+ "KPF": "C",
693
+ "SPZ": "C",
694
+ "0TT": "C",
695
+ "R9A": "C",
696
+ "5NA": "C",
697
+ "C55": "C",
698
+ "NIX": "C",
699
+ "5PM": "C",
700
+ "PP8": "C",
701
+ "544": "C",
702
+ "812": "C",
703
+ "NPM": "C",
704
+ "KU8": "C",
705
+ "A1AMM": "C",
706
+ "4S0": "C",
707
+ "AQC": "C",
708
+ "2JK": "C",
709
+ "WJR": "C",
710
+ "A1AAW": "C",
711
+ "85E": "C",
712
+ "MB0": "C",
713
+ "ZAB": "C",
714
+ "85K": "C",
715
+ "GBP": "C",
716
+ "A1H80": "C",
717
+ "A1AFR": "C",
718
+ "L9M": "C",
719
+ "MYK": "C",
720
+ "MB9": "C",
721
+ "38R": "C",
722
+ "EKB": "C",
723
+ "NKF": "C",
724
+ "UMQ": "C",
725
+ "T4K": "C",
726
+ "3PT": "C",
727
+ "A1A7S": "C",
728
+ "1Q9": "C",
729
+ "11R": "C",
730
+ "D2V": "C",
731
+ "SM8": "C",
732
+ "IFC": "C",
733
+ "DB5": "C",
734
+ "L2T": "C",
735
+ "GNB": "C",
736
+ "PP7": "C",
737
+ "072": "C",
738
+ "P88": "C",
739
+ "DRL": "C",
740
+ "C9W": "C",
741
+ "NTP": "C",
742
+ "4HJ": "C",
743
+ "7NA": "C",
744
+ "LPC": "C",
745
+ "T8W": "C",
746
+ "63R": "C",
747
+ "570": "C",
748
+ "R4A": "C",
749
+ "3BG": "C",
750
+ "4RB": "C",
751
+ "GSO": "C",
752
+ "BQ6": "C",
753
+ "R4P": "C",
754
+ "5CP": "C",
755
+ "TTR": "C",
756
+ "6UZ": "C",
757
+ "SPJ": "C",
758
+ "0SA": "C",
759
+ "ZL1": "C",
760
+ "BYG": "C",
761
+ "F0E": "C",
762
+ "PC0": "C",
763
+ "B2Q": "C",
764
+ "KV6": "C",
765
+ "NTO": "C",
766
+ "CLG": "C",
767
+ "R7U": "C",
768
+ "SMQ": "C",
769
+ "GM2": "C",
770
+ "Z7P": "C",
771
+ "NXF": "C",
772
+ "C6Q": "C",
773
+ "A1G": "C",
774
+ "433": "C",
775
+ "L9N": "C",
776
+ "7OX": "C",
777
+ "A1H84": "C",
778
+ "97L": "C",
779
+ "HDV": "C",
780
+ "LUO": "C",
781
+ "R6A": "C",
782
+ "1PC": "C",
783
+ "4PT": "C",
784
+ "SBZ": "C",
785
+ "EAB": "C",
786
+ "FL4": "C",
787
+ "OPS": "C",
788
+ "C2X": "C",
789
+ "SLL": "C",
790
+ "BFC": "C",
791
+ "GIP": "C",
792
+ "7CP": "C",
793
+ "CLH": "C",
794
+ "34E": "C",
795
+ "5NE": "C",
796
+ "PBF": "C",
797
+ "ABD": "C",
798
+ "ABC": "C",
799
+ "LPF": "C",
800
+ "TIZ": "C",
801
+ "4HH": "C",
802
+ "AFC": "C",
803
+ "WQH": "C",
804
+ "9JL": "C",
805
+ "CS3": "C",
806
+ "NL0": "C",
807
+ "KPY": "C",
808
+ "DNA": "C",
809
+ "B3C": "C",
810
+ "TKL": "C",
811
+ "KVS": "C",
812
+ "HO6": "C",
813
+ "NLH": "C",
814
+ "1PB": "C",
815
+ "CYF": "C",
816
+ "G4M": "C",
817
+ "R5B": "C",
818
+ "N4S": "C",
819
+ "N11": "C",
820
+ "C8F": "C",
821
+ "PIJ": "C",
822
+ "WIN": "C",
823
+ "NT1": "C",
824
+ "WJW": "C",
825
+ "HF7": "C",
826
+ "TY1": "C",
827
+ "VM1": "C",
828
+ },
829
+ "OS": {"*": "O", "DWC": "OS", "OHX": "OS", "OS": "OS", "8WV": "OS", "OS4": "OS"},
830
+ "PB": {"*": "P", "ZN9": "PB", "ZN7": "PB", "PBM": "PB", "PB": "PB", "CSB": "PB"},
831
+ "CE": {"*": "C", "CE": "CE"},
832
+ "FE": {"*": "FE", "TFR": "F", "PF5": "F", "IFC": "F", "F5C": "F"},
833
+ "NA": {"*": "N", "CGO": "NA", "R2K": "NA", "LVQ": "NA", "NA": "NA"},
834
+ "ND": {"*": "N", "ND": "ND"},
835
+ "CF": {"*": "C", "CF": "CF"},
836
+ "RU": "RU",
837
+ "BRAF": "BR",
838
+ "EU": "EU",
839
+ "CLAA": "CL",
840
+ "CLBQ": "CL",
841
+ "CM": {"*": "C", "ZCM": "CM"},
842
+ "SN": {"*": "SN", "TAP": "S", "SND": "S", "TAD": "S", "XPT": "S"},
843
+ "AG": "AG",
844
+ "CLN": "CL",
845
+ "CLM": "CL",
846
+ "CLA": {"*": "CL", "PII": "C", "TDL": "C", "D0J": "C", "GM2": "C", "PIJ": "C"},
847
+ "CLB": {
848
+ "*": "CL",
849
+ "TD5": "C",
850
+ "PII": "C",
851
+ "TDL": "C",
852
+ "GM2": "C",
853
+ "TD7": "C",
854
+ "TD6": "C",
855
+ "PIJ": "C",
856
+ },
857
+ "CR": {
858
+ "*": "C",
859
+ "BW9": "CR",
860
+ "CQ4": "CR",
861
+ "AC9": "CR",
862
+ "TIL": "CR",
863
+ "J7U": "CR",
864
+ "CR": "CR",
865
+ },
866
+ "CLAY": "CL",
867
+ "CLBC": "CL",
868
+ "PD": {
869
+ "*": "P",
870
+ "F6Q": "PD",
871
+ "SVP": "PD",
872
+ "SXC": "PD",
873
+ "U5U": "PD",
874
+ "PD": "PD",
875
+ "PLL": "PD",
876
+ },
877
+ "CO": {
878
+ "*": "C",
879
+ "J1S": "CO",
880
+ "OCN": "CO",
881
+ "OL3": "CO",
882
+ "OL4": "CO",
883
+ "B12": "CO",
884
+ "XCO": "CO",
885
+ "UFU": "CO",
886
+ "CON": "CO",
887
+ "OL5": "CO",
888
+ "B13": "CO",
889
+ "7KI": "CO",
890
+ "PL1": "CO",
891
+ "OCO": "CO",
892
+ "J1R": "CO",
893
+ "COH": "CO",
894
+ "SIR": "CO",
895
+ "6KI": "CO",
896
+ "NCO": "CO",
897
+ "9CO": "CO",
898
+ "PC3": "CO",
899
+ "BWU": "CO",
900
+ "B1Z": "CO",
901
+ "J83": "CO",
902
+ "CO": "CO",
903
+ "COY": "CO",
904
+ "CNC": "CO",
905
+ "3CO": "CO",
906
+ "OCL": "CO",
907
+ "R5Q": "CO",
908
+ "X5Z": "CO",
909
+ "CBY": "CO",
910
+ "OLS": "CO",
911
+ "F0X": "CO",
912
+ "I2A": "CO",
913
+ "OCM": "CO",
914
+ },
915
+ "CU": {
916
+ "*": "C",
917
+ "8ZR": "CU",
918
+ "K7E": "CU",
919
+ "CU3": "CU",
920
+ "SI9": "CU",
921
+ "35N": "CU",
922
+ "C2O": "CU",
923
+ "SI7": "CU",
924
+ "B15": "CU",
925
+ "SI0": "CU",
926
+ "CUP": "CU",
927
+ "SQ1": "CU",
928
+ "CUK": "CU",
929
+ "CUL": "CU",
930
+ "SI8": "CU",
931
+ "IC4": "CU",
932
+ "CUM": "CU",
933
+ "MM2": "CU",
934
+ "B30": "CU",
935
+ "S32": "CU",
936
+ "V79": "CU",
937
+ "IMF": "CU",
938
+ "CUN": "CU",
939
+ "MM1": "CU",
940
+ "MP1": "CU",
941
+ "IME": "CU",
942
+ "B17": "CU",
943
+ "C2C": "CU",
944
+ "1CU": "CU",
945
+ "CU6": "CU",
946
+ "C1O": "CU",
947
+ "CU1": "CU",
948
+ "B22": "CU",
949
+ "CUS": "CU",
950
+ "RUQ": "CU",
951
+ "CUF": "CU",
952
+ "CUA": "CU",
953
+ "CU": "CU",
954
+ "CUO": "CU",
955
+ "0TE": "CU",
956
+ "SI4": "CU",
957
+ },
958
+ "CS": {"*": "C", "CS": "CS"},
959
+ "CLQ": "CL",
960
+ "CLR": "CL",
961
+ "CLU": "CL",
962
+ "TE": "TE",
963
+ "NI": {
964
+ "*": "N",
965
+ "USN": "NI",
966
+ "NFO": "NI",
967
+ "NI2": "NI",
968
+ "NFS": "NI",
969
+ "NFR": "NI",
970
+ "82N": "NI",
971
+ "R5N": "NI",
972
+ "NFU": "NI",
973
+ "A1ICD": "NI",
974
+ "NI3": "NI",
975
+ "M43": "NI",
976
+ "MM5": "NI",
977
+ "BF8": "NI",
978
+ "TCN": "NI",
979
+ "NIK": "NI",
980
+ "CUV": "NI",
981
+ "MM6": "NI",
982
+ "J52": "NI",
983
+ "NI": "NI",
984
+ "SNF": "NI",
985
+ "XCC": "NI",
986
+ "F0L": "NI",
987
+ "UWE": "NI",
988
+ "NFC": "NI",
989
+ "3NI": "NI",
990
+ "HNI": "NI",
991
+ "F43": "NI",
992
+ "RQM": "NI",
993
+ "NFE": "NI",
994
+ "NFB": "NI",
995
+ "B51": "NI",
996
+ "NI1": "NI",
997
+ "WCC": "NI",
998
+ "NUF": "NI",
999
+ },
1000
+ "SB": {"*": "S", "UJI": "SB", "SB": "SB", "118": "SB", "SBO": "SB", "3CG": "SB"},
1001
+ "MO": "MO",
1002
+ "SEG": "SE",
1003
+ "CLL": "CL",
1004
+ "CLAH": "CL",
1005
+ "CLC": {
1006
+ "*": "CL",
1007
+ "TD5": "C",
1008
+ "PII": "C",
1009
+ "TDL": "C",
1010
+ "GM2": "C",
1011
+ "TD7": "C",
1012
+ "TD6": "C",
1013
+ "PIJ": "C",
1014
+ },
1015
+ "CLD": {"*": "CL", "PII": "C", "GM2": "C", "PIJ": "C"},
1016
+ "CLAD": "CL",
1017
+ "CLAE": "CL",
1018
+ "LA": "LA",
1019
+ "RH": "RH",
1020
+ "BRAC": "BR",
1021
+ "BRAD": "BR",
1022
+ "CLBN": "CL",
1023
+ "CLAC": "CL",
1024
+ "BRAB": "BR",
1025
+ "BRAE": "BR",
1026
+ "MG": "MG",
1027
+ "IR": "IR",
1028
+ "SE": {
1029
+ "*": "SE",
1030
+ "HII": "S",
1031
+ "NT2": "S",
1032
+ "R2P": "S",
1033
+ "S2P": "S",
1034
+ "0IU": "S",
1035
+ "QMB": "S",
1036
+ "81S": "S",
1037
+ "0QB": "S",
1038
+ "UB4": "S",
1039
+ "OHS": "S",
1040
+ "Q78": "S",
1041
+ "0Y2": "S",
1042
+ "B3M": "S",
1043
+ "NT1": "S",
1044
+ "81R": "S",
1045
+ },
1046
+ "BRAG": "BR",
1047
+ "CLF": {"*": "CL", "PII": "C", "GM2": "C", "PIJ": "C"},
1048
+ "CLE": {"*": "CL", "PII": "C", "GM2": "C", "PIJ": "C"},
1049
+ "BRAX": "BR",
1050
+ "CLK": "CL",
1051
+ "ZN": "ZN",
1052
+ "AS": "AS",
1053
+ "AU": "AU",
1054
+ "PT": "PT",
1055
+ "CLAS": "CL",
1056
+ "MN": "MN",
1057
+ "CLBE": "CL",
1058
+ "CLBF": "CL",
1059
+ "CLAF": "CL",
1060
+ "NA'": {"*": "N", "CGO": "NA"},
1061
+ "BRAH": "BR",
1062
+ "BRAI": "BR",
1063
+ "BRA": "BR",
1064
+ "BRB": "BR",
1065
+ "BRAV": "BR",
1066
+ "HG": {
1067
+ "*": "HG",
1068
+ "BBA": "H",
1069
+ "MID": "H",
1070
+ "APM": "H",
1071
+ "4QQ": "H",
1072
+ "0ZG": "H",
1073
+ "APH": "H",
1074
+ },
1075
+ "AR": "AR",
1076
+ "D": "H",
1077
+ "CLAN": "CL",
1078
+ "SI": "SI",
1079
+ "CLS": "CL",
1080
+ "ZR": "ZR",
1081
+ "CLAR": {"*": "CL", "ZM4": "C"},
1082
+ "HO": "HO",
1083
+ "CLI": {"*": "CL", "GM2": "C"},
1084
+ "CLH": {"*": "CL", "GM2": "C"},
1085
+ "CLAP": "CL",
1086
+ "CLBL": "CL",
1087
+ "CLBM": "CL",
1088
+ "PR": {"*": "PR", "UF0": "P", "252": "P"},
1089
+ "IN": "IN",
1090
+ "CLJ": "CL",
1091
+ "BRU": "BR",
1092
+ "SC": {"*": "S", "SFL": "SC"},
1093
+ "CLG": {"*": "CL", "GM2": "C"},
1094
+ "BRAT": "BR",
1095
+ "BRAR": "BR",
1096
+ "CLAG": "CL",
1097
+ "CLAB": "CL",
1098
+ "CLV": "CL",
1099
+ "TI": "TI",
1100
+ "CLAX": "CL",
1101
+ "CLAJ": "CL",
1102
+ "CL'": {"*": "CL", "BNR": "C", "25A": "C", "BDA": "C"},
1103
+ "CLAW": "CL",
1104
+ "BRF": "BR",
1105
+ "BRE": "BR",
1106
+ "RE": "RE",
1107
+ "GD": "GD",
1108
+ "SM": {"*": "S", "SM": "SM"},
1109
+ "CLBH": "CL",
1110
+ "CLBI": "CL",
1111
+ "CLAI": "CL",
1112
+ "CLY": "CL",
1113
+ "CLZ": "CL",
1114
+ "AC": "AC",
1115
+ "BR'": "BR",
1116
+ "CLT": "CL",
1117
+ "CLO": "CL",
1118
+ "CLP": "CL",
1119
+ "LU": "LU",
1120
+ "BA": {"*": "B", "BA": "BA"},
1121
+ "CLAU": "CL",
1122
+ "RB": "RB",
1123
+ "LI": "LI",
1124
+ "MOM": "MO",
1125
+ "BRAQ": "BR",
1126
+ "SR": {"*": "S", "SR": "SR", "OER": "SR"},
1127
+ "CLAT": "CL",
1128
+ "BRAL": "BR",
1129
+ "SEB": "SE",
1130
+ "CLW": "CL",
1131
+ "CLX": "CL",
1132
+ "BE": "BE",
1133
+ "BRG": "BR",
1134
+ "SEA": "SE",
1135
+ "BRAW": "BR",
1136
+ "BRBB": "BR",
1137
+ "ER": "ER",
1138
+ "TH": "TH",
1139
+ "BRR": "BR",
1140
+ "CLBV": "CL",
1141
+ "AL": "AL",
1142
+ "CLAV": "CL",
1143
+ "BRH": "BR",
1144
+ "CLAQ": "CL",
1145
+ "GA": "GA",
1146
+ "X": "*",
1147
+ "TL": "TL",
1148
+ "CLBB": "CL",
1149
+ "TB": "TB",
1150
+ "CLAK": "CL",
1151
+ "XE": {"*": "*", "XE": "XE"},
1152
+ "SEL": "SE",
1153
+ "PU": {"*": "P", "4PU": "PU"},
1154
+ "CLAZ": "CL",
1155
+ "SE'": "SE",
1156
+ "CLBA": "CL",
1157
+ "SEN": "SE",
1158
+ "SNN": "SN",
1159
+ "MOB": "MO",
1160
+ "YB": "YB",
1161
+ "BRC": "BR",
1162
+ "BRD": "BR",
1163
+ "CLAM": "CL",
1164
+ "DA": "H",
1165
+ "DB": "H",
1166
+ "DC": "H",
1167
+ "DXT": "H",
1168
+ "DXU": "H",
1169
+ "DXX": "H",
1170
+ "DXY": "H",
1171
+ "DXZ": "H",
1172
+ "DY": "DY",
1173
+ "TA": "TA",
1174
+ "XD": "*",
1175
+ "SED": "SE",
1176
+ "CLAL": "CL",
1177
+ "BRAJ": "BR",
1178
+ "AM": "AM",
1179
+ "CLAO": "CL",
1180
+ "BI": "BI",
1181
+ "KR": "KR",
1182
+ "BRBJ": "BR",
1183
+ "UNK": "*",
1184
+ }
vb_layers_attention.py CHANGED
@@ -1,136 +1,136 @@
1
- from typing import Optional
2
-
3
- import torch
4
- from einops.layers.torch import Rearrange
5
- from torch import Tensor, nn
6
-
7
- from . import vb_layers_initialize as init
8
-
9
-
10
- class AttentionPairBias(nn.Module):
11
- """Attention pair bias layer."""
12
-
13
- def __init__(
14
- self,
15
- c_s: int,
16
- c_z: int,
17
- num_heads: int,
18
- inf: float = 1e6,
19
- initial_norm: bool = True,
20
- ) -> None:
21
- """Initialize the attention pair bias layer.
22
-
23
- Parameters
24
- ----------
25
- c_s : int
26
- The input sequence dimension.
27
- c_z : int
28
- The input pairwise dimension.
29
- num_heads : int
30
- The number of heads.
31
- inf : float, optional
32
- The inf value, by default 1e6
33
- initial_norm: bool, optional
34
- Whether to apply layer norm to the input, by default True
35
-
36
- """
37
- super().__init__()
38
-
39
- assert c_s % num_heads == 0
40
-
41
- self.c_s = c_s
42
- self.num_heads = num_heads
43
- self.head_dim = c_s // num_heads
44
- self.inf = inf
45
-
46
- self.initial_norm = initial_norm
47
- if self.initial_norm:
48
- self.norm_s = nn.LayerNorm(c_s)
49
-
50
- self.proj_q = nn.Linear(c_s, c_s)
51
- self.proj_k = nn.Linear(c_s, c_s, bias=False)
52
- self.proj_v = nn.Linear(c_s, c_s, bias=False)
53
- self.proj_g = nn.Linear(c_s, c_s, bias=False)
54
-
55
- self.proj_z = nn.Sequential(
56
- nn.LayerNorm(c_z),
57
- nn.Linear(c_z, num_heads, bias=False),
58
- Rearrange("b ... h -> b h ..."),
59
- )
60
-
61
- self.proj_o = nn.Linear(c_s, c_s, bias=False)
62
- init.final_init_(self.proj_o.weight)
63
-
64
- def forward(
65
- self,
66
- s: Tensor,
67
- z: Tensor,
68
- mask: Tensor,
69
- k_in: Optional[Tensor] = None,
70
- multiplicity: int = 1,
71
- to_keys=None,
72
- model_cache=None,
73
- ) -> Tensor:
74
- """Forward pass.
75
-
76
- Parameters
77
- ----------
78
- s : torch.Tensor
79
- The input sequence tensor (B, S, D)
80
- z : torch.Tensor
81
- The input pairwise tensor (B, N, N, D)
82
- mask : torch.Tensor
83
- The pairwise mask tensor (B, N)
84
- multiplicity : int, optional
85
- The diffusion batch size, by default 1
86
-
87
- Returns
88
- -------
89
- torch.Tensor
90
- The output sequence tensor.
91
-
92
- """
93
- B = s.shape[0]
94
-
95
- # Layer norms
96
- if self.initial_norm:
97
- s = self.norm_s(s)
98
-
99
- if to_keys is not None:
100
- k_in = to_keys(s)
101
- mask = to_keys(mask.unsqueeze(-1)).squeeze(-1)
102
- else:
103
- if k_in is None:
104
- k_in = s
105
-
106
- # Compute projections
107
- q = self.proj_q(s).view(B, -1, self.num_heads, self.head_dim)
108
- k = self.proj_k(k_in).view(B, -1, self.num_heads, self.head_dim)
109
- v = self.proj_v(k_in).view(B, -1, self.num_heads, self.head_dim)
110
-
111
- # Caching z projection during diffusion roll-out
112
- if model_cache is None or "z" not in model_cache:
113
- z = self.proj_z(z)
114
-
115
- if model_cache is not None:
116
- model_cache["z"] = z
117
- else:
118
- z = model_cache["z"]
119
- z = z.repeat_interleave(multiplicity, 0)
120
-
121
- g = self.proj_g(s).sigmoid()
122
-
123
- with torch.autocast("cuda", enabled=False):
124
- # Compute attention weights
125
- attn = torch.einsum("bihd,bjhd->bhij", q.float(), k.float())
126
- attn = attn / (self.head_dim**0.5) + z.float()
127
- # The pairwise mask tensor (B, N) is broadcasted to (B, 1, 1, N) and (B, H, N, N)
128
- attn = attn + (1 - mask[:, None, None].float()) * -self.inf
129
- attn = attn.softmax(dim=-1)
130
-
131
- # Compute output
132
- o = torch.einsum("bhij,bjhd->bihd", attn, v.float()).to(v.dtype)
133
- o = o.reshape(B, -1, self.c_s)
134
- o = self.proj_o(g * o)
135
-
136
- return o
 
1
+ from typing import Optional
2
+
3
+ import torch
4
+ from einops.layers.torch import Rearrange
5
+ from torch import Tensor, nn
6
+
7
+ from . import vb_layers_initialize as init
8
+
9
+
10
+ class AttentionPairBias(nn.Module):
11
+ """Attention pair bias layer."""
12
+
13
+ def __init__(
14
+ self,
15
+ c_s: int,
16
+ c_z: int,
17
+ num_heads: int,
18
+ inf: float = 1e6,
19
+ initial_norm: bool = True,
20
+ ) -> None:
21
+ """Initialize the attention pair bias layer.
22
+
23
+ Parameters
24
+ ----------
25
+ c_s : int
26
+ The input sequence dimension.
27
+ c_z : int
28
+ The input pairwise dimension.
29
+ num_heads : int
30
+ The number of heads.
31
+ inf : float, optional
32
+ The inf value, by default 1e6
33
+ initial_norm: bool, optional
34
+ Whether to apply layer norm to the input, by default True
35
+
36
+ """
37
+ super().__init__()
38
+
39
+ assert c_s % num_heads == 0
40
+
41
+ self.c_s = c_s
42
+ self.num_heads = num_heads
43
+ self.head_dim = c_s // num_heads
44
+ self.inf = inf
45
+
46
+ self.initial_norm = initial_norm
47
+ if self.initial_norm:
48
+ self.norm_s = nn.LayerNorm(c_s)
49
+
50
+ self.proj_q = nn.Linear(c_s, c_s)
51
+ self.proj_k = nn.Linear(c_s, c_s, bias=False)
52
+ self.proj_v = nn.Linear(c_s, c_s, bias=False)
53
+ self.proj_g = nn.Linear(c_s, c_s, bias=False)
54
+
55
+ self.proj_z = nn.Sequential(
56
+ nn.LayerNorm(c_z),
57
+ nn.Linear(c_z, num_heads, bias=False),
58
+ Rearrange("b ... h -> b h ..."),
59
+ )
60
+
61
+ self.proj_o = nn.Linear(c_s, c_s, bias=False)
62
+ init.final_init_(self.proj_o.weight)
63
+
64
+ def forward(
65
+ self,
66
+ s: Tensor,
67
+ z: Tensor,
68
+ mask: Tensor,
69
+ k_in: Optional[Tensor] = None,
70
+ multiplicity: int = 1,
71
+ to_keys=None,
72
+ model_cache=None,
73
+ ) -> Tensor:
74
+ """Forward pass.
75
+
76
+ Parameters
77
+ ----------
78
+ s : torch.Tensor
79
+ The input sequence tensor (B, S, D)
80
+ z : torch.Tensor
81
+ The input pairwise tensor (B, N, N, D)
82
+ mask : torch.Tensor
83
+ The pairwise mask tensor (B, N)
84
+ multiplicity : int, optional
85
+ The diffusion batch size, by default 1
86
+
87
+ Returns
88
+ -------
89
+ torch.Tensor
90
+ The output sequence tensor.
91
+
92
+ """
93
+ B = s.shape[0]
94
+
95
+ # Layer norms
96
+ if self.initial_norm:
97
+ s = self.norm_s(s)
98
+
99
+ if to_keys is not None:
100
+ k_in = to_keys(s)
101
+ mask = to_keys(mask.unsqueeze(-1)).squeeze(-1)
102
+ else:
103
+ if k_in is None:
104
+ k_in = s
105
+
106
+ # Compute projections
107
+ q = self.proj_q(s).view(B, -1, self.num_heads, self.head_dim)
108
+ k = self.proj_k(k_in).view(B, -1, self.num_heads, self.head_dim)
109
+ v = self.proj_v(k_in).view(B, -1, self.num_heads, self.head_dim)
110
+
111
+ # Caching z projection during diffusion roll-out
112
+ if model_cache is None or "z" not in model_cache:
113
+ z = self.proj_z(z)
114
+
115
+ if model_cache is not None:
116
+ model_cache["z"] = z
117
+ else:
118
+ z = model_cache["z"]
119
+ z = z.repeat_interleave(multiplicity, 0)
120
+
121
+ g = self.proj_g(s).sigmoid()
122
+
123
+ with torch.autocast("cuda", enabled=False):
124
+ # Compute attention weights
125
+ attn = torch.einsum("bihd,bjhd->bhij", q.float(), k.float())
126
+ attn = attn / (self.head_dim**0.5) + z.float()
127
+ # The pairwise mask tensor (B, N) is broadcasted to (B, 1, 1, N) and (B, H, N, N)
128
+ attn = attn + (1 - mask[:, None, None].float()) * -self.inf
129
+ attn = attn.softmax(dim=-1)
130
+
131
+ # Compute output
132
+ o = torch.einsum("bhij,bjhd->bihd", attn, v.float()).to(v.dtype)
133
+ o = o.reshape(B, -1, self.c_s)
134
+ o = self.proj_o(g * o)
135
+
136
+ return o
vb_layers_attentionv2.py CHANGED
@@ -1,111 +1,111 @@
1
- from typing import Optional
2
-
3
- import torch
4
- from einops.layers.torch import Rearrange
5
- from torch import Tensor, nn
6
-
7
- from . import vb_layers_initialize as init
8
-
9
-
10
- class AttentionPairBias(nn.Module):
11
- """Attention pair bias layer."""
12
-
13
- def __init__(
14
- self,
15
- c_s: int,
16
- c_z: Optional[int] = None,
17
- num_heads: Optional[int] = None,
18
- inf: float = 1e6,
19
- compute_pair_bias: bool = True,
20
- ) -> None:
21
- """Initialize the attention pair bias layer.
22
-
23
- Parameters
24
- ----------
25
- c_s : int
26
- The input sequence dimension.
27
- c_z : int
28
- The input pairwise dimension.
29
- num_heads : int
30
- The number of heads.
31
- inf : float, optional
32
- The inf value, by default 1e6
33
-
34
- """
35
- super().__init__()
36
-
37
- assert c_s % num_heads == 0
38
-
39
- self.c_s = c_s
40
- self.num_heads = num_heads
41
- self.head_dim = c_s // num_heads
42
- self.inf = inf
43
-
44
- self.proj_q = nn.Linear(c_s, c_s)
45
- self.proj_k = nn.Linear(c_s, c_s, bias=False)
46
- self.proj_v = nn.Linear(c_s, c_s, bias=False)
47
- self.proj_g = nn.Linear(c_s, c_s, bias=False)
48
-
49
- self.compute_pair_bias = compute_pair_bias
50
- if compute_pair_bias:
51
- self.proj_z = nn.Sequential(
52
- nn.LayerNorm(c_z),
53
- nn.Linear(c_z, num_heads, bias=False),
54
- Rearrange("b ... h -> b h ..."),
55
- )
56
- else:
57
- self.proj_z = Rearrange("b ... h -> b h ...")
58
-
59
- self.proj_o = nn.Linear(c_s, c_s, bias=False)
60
- init.final_init_(self.proj_o.weight)
61
-
62
- def forward(
63
- self,
64
- s: Tensor,
65
- z: Tensor,
66
- mask: Tensor,
67
- k_in: Tensor,
68
- multiplicity: int = 1,
69
- ) -> Tensor:
70
- """Forward pass.
71
-
72
- Parameters
73
- ----------
74
- s : torch.Tensor
75
- The input sequence tensor (B, S, D)
76
- z : torch.Tensor
77
- The input pairwise tensor or bias (B, N, N, D)
78
- mask : torch.Tensor
79
- The pairwise mask tensor (B, N, N)
80
-
81
- Returns
82
- -------
83
- torch.Tensor
84
- The output sequence tensor.
85
-
86
- """
87
- B = s.shape[0]
88
-
89
- # Compute projections
90
- q = self.proj_q(s).view(B, -1, self.num_heads, self.head_dim)
91
- k = self.proj_k(k_in).view(B, -1, self.num_heads, self.head_dim)
92
- v = self.proj_v(k_in).view(B, -1, self.num_heads, self.head_dim)
93
-
94
- bias = self.proj_z(z)
95
- bias = bias.repeat_interleave(multiplicity, 0)
96
-
97
- g = self.proj_g(s).sigmoid()
98
-
99
- with torch.autocast("cuda", enabled=False):
100
- # Compute attention weights
101
- attn = torch.einsum("bihd,bjhd->bhij", q.float(), k.float())
102
- attn = attn / (self.head_dim**0.5) + bias.float()
103
- attn = attn + (1 - mask[:, None, None].float()) * -self.inf
104
- attn = attn.softmax(dim=-1)
105
-
106
- # Compute output
107
- o = torch.einsum("bhij,bjhd->bihd", attn, v.float()).to(v.dtype)
108
- o = o.reshape(B, -1, self.c_s)
109
- o = self.proj_o(g * o)
110
-
111
- return o
 
1
+ from typing import Optional
2
+
3
+ import torch
4
+ from einops.layers.torch import Rearrange
5
+ from torch import Tensor, nn
6
+
7
+ from . import vb_layers_initialize as init
8
+
9
+
10
+ class AttentionPairBias(nn.Module):
11
+ """Attention pair bias layer."""
12
+
13
+ def __init__(
14
+ self,
15
+ c_s: int,
16
+ c_z: Optional[int] = None,
17
+ num_heads: Optional[int] = None,
18
+ inf: float = 1e6,
19
+ compute_pair_bias: bool = True,
20
+ ) -> None:
21
+ """Initialize the attention pair bias layer.
22
+
23
+ Parameters
24
+ ----------
25
+ c_s : int
26
+ The input sequence dimension.
27
+ c_z : int
28
+ The input pairwise dimension.
29
+ num_heads : int
30
+ The number of heads.
31
+ inf : float, optional
32
+ The inf value, by default 1e6
33
+
34
+ """
35
+ super().__init__()
36
+
37
+ assert c_s % num_heads == 0
38
+
39
+ self.c_s = c_s
40
+ self.num_heads = num_heads
41
+ self.head_dim = c_s // num_heads
42
+ self.inf = inf
43
+
44
+ self.proj_q = nn.Linear(c_s, c_s)
45
+ self.proj_k = nn.Linear(c_s, c_s, bias=False)
46
+ self.proj_v = nn.Linear(c_s, c_s, bias=False)
47
+ self.proj_g = nn.Linear(c_s, c_s, bias=False)
48
+
49
+ self.compute_pair_bias = compute_pair_bias
50
+ if compute_pair_bias:
51
+ self.proj_z = nn.Sequential(
52
+ nn.LayerNorm(c_z),
53
+ nn.Linear(c_z, num_heads, bias=False),
54
+ Rearrange("b ... h -> b h ..."),
55
+ )
56
+ else:
57
+ self.proj_z = Rearrange("b ... h -> b h ...")
58
+
59
+ self.proj_o = nn.Linear(c_s, c_s, bias=False)
60
+ init.final_init_(self.proj_o.weight)
61
+
62
+ def forward(
63
+ self,
64
+ s: Tensor,
65
+ z: Tensor,
66
+ mask: Tensor,
67
+ k_in: Tensor,
68
+ multiplicity: int = 1,
69
+ ) -> Tensor:
70
+ """Forward pass.
71
+
72
+ Parameters
73
+ ----------
74
+ s : torch.Tensor
75
+ The input sequence tensor (B, S, D)
76
+ z : torch.Tensor
77
+ The input pairwise tensor or bias (B, N, N, D)
78
+ mask : torch.Tensor
79
+ The pairwise mask tensor (B, N, N)
80
+
81
+ Returns
82
+ -------
83
+ torch.Tensor
84
+ The output sequence tensor.
85
+
86
+ """
87
+ B = s.shape[0]
88
+
89
+ # Compute projections
90
+ q = self.proj_q(s).view(B, -1, self.num_heads, self.head_dim)
91
+ k = self.proj_k(k_in).view(B, -1, self.num_heads, self.head_dim)
92
+ v = self.proj_v(k_in).view(B, -1, self.num_heads, self.head_dim)
93
+
94
+ bias = self.proj_z(z)
95
+ bias = bias.repeat_interleave(multiplicity, 0)
96
+
97
+ g = self.proj_g(s).sigmoid()
98
+
99
+ with torch.autocast("cuda", enabled=False):
100
+ # Compute attention weights
101
+ attn = torch.einsum("bihd,bjhd->bhij", q.float(), k.float())
102
+ attn = attn / (self.head_dim**0.5) + bias.float()
103
+ attn = attn + (1 - mask[:, None, None].float()) * -self.inf
104
+ attn = attn.softmax(dim=-1)
105
+
106
+ # Compute output
107
+ o = torch.einsum("bhij,bjhd->bihd", attn, v.float()).to(v.dtype)
108
+ o = o.reshape(B, -1, self.c_s)
109
+ o = self.proj_o(g * o)
110
+
111
+ return o
vb_layers_confidence_utils.py CHANGED
@@ -1,231 +1,231 @@
1
- import torch
2
- from torch import nn
3
-
4
- from . import vb_const as const
5
-
6
-
7
- def compute_collinear_mask(v1, v2):
8
- norm1 = torch.norm(v1, dim=1, keepdim=True)
9
- norm2 = torch.norm(v2, dim=1, keepdim=True)
10
- v1 = v1 / (norm1 + 1e-6)
11
- v2 = v2 / (norm2 + 1e-6)
12
- mask_angle = torch.abs(torch.sum(v1 * v2, dim=1)) < 0.9063
13
- mask_overlap1 = norm1.reshape(-1) > 1e-2
14
- mask_overlap2 = norm2.reshape(-1) > 1e-2
15
- return mask_angle & mask_overlap1 & mask_overlap2
16
-
17
-
18
- def compute_frame_pred(
19
- pred_atom_coords,
20
- frames_idx_true,
21
- feats,
22
- multiplicity,
23
- resolved_mask=None,
24
- inference=False,
25
- ):
26
- with torch.amp.autocast("cuda", enabled=False):
27
- asym_id_token = feats["asym_id"]
28
- asym_id_atom = torch.bmm(
29
- feats["atom_to_token"].float(), asym_id_token.unsqueeze(-1).float()
30
- ).squeeze(-1)
31
-
32
- B, N, _ = pred_atom_coords.shape
33
- pred_atom_coords = pred_atom_coords.reshape(B // multiplicity, multiplicity, -1, 3)
34
- frames_idx_pred = (
35
- frames_idx_true.clone()
36
- .repeat_interleave(multiplicity, 0)
37
- .reshape(B // multiplicity, multiplicity, -1, 3)
38
- )
39
-
40
- # Iterate through the batch and modify the frames for nonpolymers
41
- for i, pred_atom_coord in enumerate(pred_atom_coords):
42
- token_idx = 0
43
- atom_idx = 0
44
- for id in torch.unique(asym_id_token[i]):
45
- mask_chain_token = (asym_id_token[i] == id) * feats["token_pad_mask"][i]
46
- mask_chain_atom = (asym_id_atom[i] == id) * feats["atom_pad_mask"][i]
47
- num_tokens = int(mask_chain_token.sum().item())
48
- num_atoms = int(mask_chain_atom.sum().item())
49
- if (
50
- feats["mol_type"][i, token_idx] != const.chain_type_ids["NONPOLYMER"]
51
- or num_atoms < 3
52
- ):
53
- token_idx += num_tokens
54
- atom_idx += num_atoms
55
- continue
56
- dist_mat = (
57
- (
58
- pred_atom_coord[:, mask_chain_atom.bool()][:, None, :, :]
59
- - pred_atom_coord[:, mask_chain_atom.bool()][:, :, None, :]
60
- )
61
- ** 2
62
- ).sum(-1) ** 0.5
63
- if inference:
64
- resolved_pair = 1 - (
65
- feats["atom_pad_mask"][i][mask_chain_atom.bool()][None, :]
66
- * feats["atom_pad_mask"][i][mask_chain_atom.bool()][:, None]
67
- ).to(torch.float32)
68
- resolved_pair[resolved_pair == 1] = torch.inf
69
- indices = torch.sort(dist_mat + resolved_pair, axis=2).indices
70
- else:
71
- if resolved_mask is None:
72
- resolved_mask = feats["atom_resolved_mask"]
73
- resolved_pair = 1 - (
74
- resolved_mask[i][mask_chain_atom.bool()][None, :]
75
- * resolved_mask[i][mask_chain_atom.bool()][:, None]
76
- ).to(torch.float32)
77
- resolved_pair[resolved_pair == 1] = torch.inf
78
- indices = torch.sort(dist_mat + resolved_pair, axis=2).indices
79
- frames = (
80
- torch.cat(
81
- [
82
- indices[:, :, 1:2],
83
- indices[:, :, 0:1],
84
- indices[:, :, 2:3],
85
- ],
86
- dim=2,
87
- )
88
- + atom_idx
89
- )
90
- try:
91
- frames_idx_pred[i, :, token_idx : token_idx + num_atoms, :] = frames
92
- except Exception as e:
93
- print(f"Failed to process {feats['pdb_id']} due to {e}")
94
- token_idx += num_tokens
95
- atom_idx += num_atoms
96
-
97
- frames_expanded = pred_atom_coords[
98
- torch.arange(0, B // multiplicity, 1)[:, None, None, None].to(
99
- frames_idx_pred.device
100
- ),
101
- torch.arange(0, multiplicity, 1)[None, :, None, None].to(
102
- frames_idx_pred.device
103
- ),
104
- frames_idx_pred,
105
- ].reshape(-1, 3, 3)
106
-
107
- # Compute masks for collinearity / overlap
108
- mask_collinear_pred = compute_collinear_mask(
109
- frames_expanded[:, 1] - frames_expanded[:, 0],
110
- frames_expanded[:, 1] - frames_expanded[:, 2],
111
- ).reshape(B // multiplicity, multiplicity, -1)
112
- return frames_idx_pred, mask_collinear_pred * feats["token_pad_mask"][:, None, :]
113
-
114
-
115
- def compute_aggregated_metric(logits, end=1.0):
116
- # Compute aggregated metric from logits
117
- num_bins = logits.shape[-1]
118
- bin_width = end / num_bins
119
- bounds = torch.arange(
120
- start=0.5 * bin_width, end=end, step=bin_width, device=logits.device
121
- )
122
- probs = nn.functional.softmax(logits, dim=-1)
123
- plddt = torch.sum(
124
- probs * bounds.view(*((1,) * len(probs.shape[:-1])), *bounds.shape),
125
- dim=-1,
126
- )
127
- return plddt
128
-
129
-
130
- def tm_function(d, Nres):
131
- d0 = 1.24 * (torch.clip(Nres, min=19) - 15) ** (1 / 3) - 1.8
132
- return 1 / (1 + (d / d0) ** 2)
133
-
134
-
135
- def compute_ptms(logits, x_preds, feats, multiplicity):
136
- # It needs to take as input the mask of the frames as they are not used to compute the PTM
137
- _, mask_collinear_pred = compute_frame_pred(
138
- x_preds, feats["frames_idx"], feats, multiplicity, inference=True
139
- )
140
- # mask overlapping, collinear tokens and ions (invalid frames)
141
- mask_pad = feats["token_pad_mask"].repeat_interleave(multiplicity, 0)
142
- maski = mask_collinear_pred.reshape(-1, mask_collinear_pred.shape[-1])
143
- pair_mask_ptm = maski[:, :, None] * mask_pad[:, None, :] * mask_pad[:, :, None]
144
- asym_id = feats["asym_id"].repeat_interleave(multiplicity, 0)
145
- pair_mask_iptm = (
146
- maski[:, :, None]
147
- * (asym_id[:, None, :] != asym_id[:, :, None])
148
- * mask_pad[:, None, :]
149
- * mask_pad[:, :, None]
150
- )
151
- num_bins = logits.shape[-1]
152
- bin_width = 32.0 / num_bins
153
- end = 32.0
154
- pae_value = torch.arange(
155
- start=0.5 * bin_width, end=end, step=bin_width, device=logits.device
156
- ).unsqueeze(0)
157
- N_res = mask_pad.sum(dim=-1, keepdim=True)
158
- tm_value = tm_function(pae_value, N_res).unsqueeze(1).unsqueeze(2)
159
- probs = nn.functional.softmax(logits, dim=-1)
160
- tm_expected_value = torch.sum(
161
- probs * tm_value,
162
- dim=-1,
163
- ) # shape (B, N, N)
164
- ptm = torch.max(
165
- torch.sum(tm_expected_value * pair_mask_ptm, dim=-1)
166
- / (torch.sum(pair_mask_ptm, dim=-1) + 1e-5),
167
- dim=1,
168
- ).values
169
- iptm = torch.max(
170
- torch.sum(tm_expected_value * pair_mask_iptm, dim=-1)
171
- / (torch.sum(pair_mask_iptm, dim=-1) + 1e-5),
172
- dim=1,
173
- ).values
174
-
175
- # compute ligand and protein iPTM
176
- token_type = feats["mol_type"]
177
- token_type = token_type.repeat_interleave(multiplicity, 0)
178
- is_ligand_token = (token_type == const.chain_type_ids["NONPOLYMER"]).float()
179
- is_protein_token = (token_type == const.chain_type_ids["PROTEIN"]).float()
180
-
181
- ligand_iptm_mask = (
182
- maski[:, :, None]
183
- * (asym_id[:, None, :] != asym_id[:, :, None])
184
- * mask_pad[:, None, :]
185
- * mask_pad[:, :, None]
186
- * (
187
- (is_ligand_token[:, :, None] * is_protein_token[:, None, :])
188
- + (is_protein_token[:, :, None] * is_ligand_token[:, None, :])
189
- )
190
- )
191
- protein_ipmt_mask = (
192
- maski[:, :, None]
193
- * (asym_id[:, None, :] != asym_id[:, :, None])
194
- * mask_pad[:, None, :]
195
- * mask_pad[:, :, None]
196
- * (is_protein_token[:, :, None] * is_protein_token[:, None, :])
197
- )
198
-
199
- ligand_iptm = torch.max(
200
- torch.sum(tm_expected_value * ligand_iptm_mask, dim=-1)
201
- / (torch.sum(ligand_iptm_mask, dim=-1) + 1e-5),
202
- dim=1,
203
- ).values
204
- protein_iptm = torch.max(
205
- torch.sum(tm_expected_value * protein_ipmt_mask, dim=-1)
206
- / (torch.sum(protein_ipmt_mask, dim=-1) + 1e-5),
207
- dim=1,
208
- ).values
209
-
210
- # Compute pair chain ipTM
211
- chain_pair_iptm = {}
212
- asym_ids_list = torch.unique(asym_id).tolist()
213
- for idx1 in asym_ids_list:
214
- chain_iptm = {}
215
- for idx2 in asym_ids_list:
216
- mask_pair_chain = (
217
- maski[:, :, None]
218
- * (asym_id[:, None, :] == idx1)
219
- * (asym_id[:, :, None] == idx2)
220
- * mask_pad[:, None, :]
221
- * mask_pad[:, :, None]
222
- )
223
-
224
- chain_iptm[idx2] = torch.max(
225
- torch.sum(tm_expected_value * mask_pair_chain, dim=-1)
226
- / (torch.sum(mask_pair_chain, dim=-1) + 1e-5),
227
- dim=1,
228
- ).values
229
- chain_pair_iptm[idx1] = chain_iptm
230
-
231
- return ptm, iptm, ligand_iptm, protein_iptm, chain_pair_iptm
 
1
+ import torch
2
+ from torch import nn
3
+
4
+ from . import vb_const as const
5
+
6
+
7
+ def compute_collinear_mask(v1, v2):
8
+ norm1 = torch.norm(v1, dim=1, keepdim=True)
9
+ norm2 = torch.norm(v2, dim=1, keepdim=True)
10
+ v1 = v1 / (norm1 + 1e-6)
11
+ v2 = v2 / (norm2 + 1e-6)
12
+ mask_angle = torch.abs(torch.sum(v1 * v2, dim=1)) < 0.9063
13
+ mask_overlap1 = norm1.reshape(-1) > 1e-2
14
+ mask_overlap2 = norm2.reshape(-1) > 1e-2
15
+ return mask_angle & mask_overlap1 & mask_overlap2
16
+
17
+
18
+ def compute_frame_pred(
19
+ pred_atom_coords,
20
+ frames_idx_true,
21
+ feats,
22
+ multiplicity,
23
+ resolved_mask=None,
24
+ inference=False,
25
+ ):
26
+ with torch.amp.autocast("cuda", enabled=False):
27
+ asym_id_token = feats["asym_id"]
28
+ asym_id_atom = torch.bmm(
29
+ feats["atom_to_token"].float(), asym_id_token.unsqueeze(-1).float()
30
+ ).squeeze(-1)
31
+
32
+ B, N, _ = pred_atom_coords.shape
33
+ pred_atom_coords = pred_atom_coords.reshape(B // multiplicity, multiplicity, -1, 3)
34
+ frames_idx_pred = (
35
+ frames_idx_true.clone()
36
+ .repeat_interleave(multiplicity, 0)
37
+ .reshape(B // multiplicity, multiplicity, -1, 3)
38
+ )
39
+
40
+ # Iterate through the batch and modify the frames for nonpolymers
41
+ for i, pred_atom_coord in enumerate(pred_atom_coords):
42
+ token_idx = 0
43
+ atom_idx = 0
44
+ for id in torch.unique(asym_id_token[i]):
45
+ mask_chain_token = (asym_id_token[i] == id) * feats["token_pad_mask"][i]
46
+ mask_chain_atom = (asym_id_atom[i] == id) * feats["atom_pad_mask"][i]
47
+ num_tokens = int(mask_chain_token.sum().item())
48
+ num_atoms = int(mask_chain_atom.sum().item())
49
+ if (
50
+ feats["mol_type"][i, token_idx] != const.chain_type_ids["NONPOLYMER"]
51
+ or num_atoms < 3
52
+ ):
53
+ token_idx += num_tokens
54
+ atom_idx += num_atoms
55
+ continue
56
+ dist_mat = (
57
+ (
58
+ pred_atom_coord[:, mask_chain_atom.bool()][:, None, :, :]
59
+ - pred_atom_coord[:, mask_chain_atom.bool()][:, :, None, :]
60
+ )
61
+ ** 2
62
+ ).sum(-1) ** 0.5
63
+ if inference:
64
+ resolved_pair = 1 - (
65
+ feats["atom_pad_mask"][i][mask_chain_atom.bool()][None, :]
66
+ * feats["atom_pad_mask"][i][mask_chain_atom.bool()][:, None]
67
+ ).to(torch.float32)
68
+ resolved_pair[resolved_pair == 1] = torch.inf
69
+ indices = torch.sort(dist_mat + resolved_pair, axis=2).indices
70
+ else:
71
+ if resolved_mask is None:
72
+ resolved_mask = feats["atom_resolved_mask"]
73
+ resolved_pair = 1 - (
74
+ resolved_mask[i][mask_chain_atom.bool()][None, :]
75
+ * resolved_mask[i][mask_chain_atom.bool()][:, None]
76
+ ).to(torch.float32)
77
+ resolved_pair[resolved_pair == 1] = torch.inf
78
+ indices = torch.sort(dist_mat + resolved_pair, axis=2).indices
79
+ frames = (
80
+ torch.cat(
81
+ [
82
+ indices[:, :, 1:2],
83
+ indices[:, :, 0:1],
84
+ indices[:, :, 2:3],
85
+ ],
86
+ dim=2,
87
+ )
88
+ + atom_idx
89
+ )
90
+ try:
91
+ frames_idx_pred[i, :, token_idx : token_idx + num_atoms, :] = frames
92
+ except Exception as e:
93
+ print(f"Failed to process {feats['pdb_id']} due to {e}")
94
+ token_idx += num_tokens
95
+ atom_idx += num_atoms
96
+
97
+ frames_expanded = pred_atom_coords[
98
+ torch.arange(0, B // multiplicity, 1)[:, None, None, None].to(
99
+ frames_idx_pred.device
100
+ ),
101
+ torch.arange(0, multiplicity, 1)[None, :, None, None].to(
102
+ frames_idx_pred.device
103
+ ),
104
+ frames_idx_pred,
105
+ ].reshape(-1, 3, 3)
106
+
107
+ # Compute masks for collinearity / overlap
108
+ mask_collinear_pred = compute_collinear_mask(
109
+ frames_expanded[:, 1] - frames_expanded[:, 0],
110
+ frames_expanded[:, 1] - frames_expanded[:, 2],
111
+ ).reshape(B // multiplicity, multiplicity, -1)
112
+ return frames_idx_pred, mask_collinear_pred * feats["token_pad_mask"][:, None, :]
113
+
114
+
115
+ def compute_aggregated_metric(logits, end=1.0):
116
+ # Compute aggregated metric from logits
117
+ num_bins = logits.shape[-1]
118
+ bin_width = end / num_bins
119
+ bounds = torch.arange(
120
+ start=0.5 * bin_width, end=end, step=bin_width, device=logits.device
121
+ )
122
+ probs = nn.functional.softmax(logits, dim=-1)
123
+ plddt = torch.sum(
124
+ probs * bounds.view(*((1,) * len(probs.shape[:-1])), *bounds.shape),
125
+ dim=-1,
126
+ )
127
+ return plddt
128
+
129
+
130
+ def tm_function(d, Nres):
131
+ d0 = 1.24 * (torch.clip(Nres, min=19) - 15) ** (1 / 3) - 1.8
132
+ return 1 / (1 + (d / d0) ** 2)
133
+
134
+
135
+ def compute_ptms(logits, x_preds, feats, multiplicity):
136
+ # It needs to take as input the mask of the frames as they are not used to compute the PTM
137
+ _, mask_collinear_pred = compute_frame_pred(
138
+ x_preds, feats["frames_idx"], feats, multiplicity, inference=True
139
+ )
140
+ # mask overlapping, collinear tokens and ions (invalid frames)
141
+ mask_pad = feats["token_pad_mask"].repeat_interleave(multiplicity, 0)
142
+ maski = mask_collinear_pred.reshape(-1, mask_collinear_pred.shape[-1])
143
+ pair_mask_ptm = maski[:, :, None] * mask_pad[:, None, :] * mask_pad[:, :, None]
144
+ asym_id = feats["asym_id"].repeat_interleave(multiplicity, 0)
145
+ pair_mask_iptm = (
146
+ maski[:, :, None]
147
+ * (asym_id[:, None, :] != asym_id[:, :, None])
148
+ * mask_pad[:, None, :]
149
+ * mask_pad[:, :, None]
150
+ )
151
+ num_bins = logits.shape[-1]
152
+ bin_width = 32.0 / num_bins
153
+ end = 32.0
154
+ pae_value = torch.arange(
155
+ start=0.5 * bin_width, end=end, step=bin_width, device=logits.device
156
+ ).unsqueeze(0)
157
+ N_res = mask_pad.sum(dim=-1, keepdim=True)
158
+ tm_value = tm_function(pae_value, N_res).unsqueeze(1).unsqueeze(2)
159
+ probs = nn.functional.softmax(logits, dim=-1)
160
+ tm_expected_value = torch.sum(
161
+ probs * tm_value,
162
+ dim=-1,
163
+ ) # shape (B, N, N)
164
+ ptm = torch.max(
165
+ torch.sum(tm_expected_value * pair_mask_ptm, dim=-1)
166
+ / (torch.sum(pair_mask_ptm, dim=-1) + 1e-5),
167
+ dim=1,
168
+ ).values
169
+ iptm = torch.max(
170
+ torch.sum(tm_expected_value * pair_mask_iptm, dim=-1)
171
+ / (torch.sum(pair_mask_iptm, dim=-1) + 1e-5),
172
+ dim=1,
173
+ ).values
174
+
175
+ # compute ligand and protein iPTM
176
+ token_type = feats["mol_type"]
177
+ token_type = token_type.repeat_interleave(multiplicity, 0)
178
+ is_ligand_token = (token_type == const.chain_type_ids["NONPOLYMER"]).float()
179
+ is_protein_token = (token_type == const.chain_type_ids["PROTEIN"]).float()
180
+
181
+ ligand_iptm_mask = (
182
+ maski[:, :, None]
183
+ * (asym_id[:, None, :] != asym_id[:, :, None])
184
+ * mask_pad[:, None, :]
185
+ * mask_pad[:, :, None]
186
+ * (
187
+ (is_ligand_token[:, :, None] * is_protein_token[:, None, :])
188
+ + (is_protein_token[:, :, None] * is_ligand_token[:, None, :])
189
+ )
190
+ )
191
+ protein_ipmt_mask = (
192
+ maski[:, :, None]
193
+ * (asym_id[:, None, :] != asym_id[:, :, None])
194
+ * mask_pad[:, None, :]
195
+ * mask_pad[:, :, None]
196
+ * (is_protein_token[:, :, None] * is_protein_token[:, None, :])
197
+ )
198
+
199
+ ligand_iptm = torch.max(
200
+ torch.sum(tm_expected_value * ligand_iptm_mask, dim=-1)
201
+ / (torch.sum(ligand_iptm_mask, dim=-1) + 1e-5),
202
+ dim=1,
203
+ ).values
204
+ protein_iptm = torch.max(
205
+ torch.sum(tm_expected_value * protein_ipmt_mask, dim=-1)
206
+ / (torch.sum(protein_ipmt_mask, dim=-1) + 1e-5),
207
+ dim=1,
208
+ ).values
209
+
210
+ # Compute pair chain ipTM
211
+ chain_pair_iptm = {}
212
+ asym_ids_list = torch.unique(asym_id).tolist()
213
+ for idx1 in asym_ids_list:
214
+ chain_iptm = {}
215
+ for idx2 in asym_ids_list:
216
+ mask_pair_chain = (
217
+ maski[:, :, None]
218
+ * (asym_id[:, None, :] == idx1)
219
+ * (asym_id[:, :, None] == idx2)
220
+ * mask_pad[:, None, :]
221
+ * mask_pad[:, :, None]
222
+ )
223
+
224
+ chain_iptm[idx2] = torch.max(
225
+ torch.sum(tm_expected_value * mask_pair_chain, dim=-1)
226
+ / (torch.sum(mask_pair_chain, dim=-1) + 1e-5),
227
+ dim=1,
228
+ ).values
229
+ chain_pair_iptm[idx1] = chain_iptm
230
+
231
+ return ptm, iptm, ligand_iptm, protein_iptm, chain_pair_iptm
vb_layers_dropout.py CHANGED
@@ -1,34 +1,34 @@
1
- import torch
2
- from torch import Tensor
3
-
4
-
5
- def get_dropout_mask(
6
- dropout: float,
7
- z: Tensor,
8
- training: bool,
9
- columnwise: bool = False,
10
- ) -> Tensor:
11
- """Get the dropout mask.
12
-
13
- Parameters
14
- ----------
15
- dropout : float
16
- The dropout rate
17
- z : torch.Tensor
18
- The tensor to apply dropout to
19
- training : bool
20
- Whether the model is in training mode
21
- columnwise : bool, optional
22
- Whether to apply dropout columnwise
23
-
24
- Returns
25
- -------
26
- torch.Tensor
27
- The dropout mask
28
-
29
- """
30
- dropout = dropout * training
31
- v = z[:, 0:1, :, 0:1] if columnwise else z[:, :, 0:1, 0:1]
32
- d = torch.rand(v.shape, dtype=torch.float32, device=v.device) >= dropout
33
- d = d * 1.0 / (1.0 - dropout)
34
- return d
 
1
+ import torch
2
+ from torch import Tensor
3
+
4
+
5
+ def get_dropout_mask(
6
+ dropout: float,
7
+ z: Tensor,
8
+ training: bool,
9
+ columnwise: bool = False,
10
+ ) -> Tensor:
11
+ """Get the dropout mask.
12
+
13
+ Parameters
14
+ ----------
15
+ dropout : float
16
+ The dropout rate
17
+ z : torch.Tensor
18
+ The tensor to apply dropout to
19
+ training : bool
20
+ Whether the model is in training mode
21
+ columnwise : bool, optional
22
+ Whether to apply dropout columnwise
23
+
24
+ Returns
25
+ -------
26
+ torch.Tensor
27
+ The dropout mask
28
+
29
+ """
30
+ dropout = dropout * training
31
+ v = z[:, 0:1, :, 0:1] if columnwise else z[:, :, 0:1, 0:1]
32
+ d = torch.rand(v.shape, dtype=torch.float32, device=v.device) >= dropout
33
+ d = d * 1.0 / (1.0 - dropout)
34
+ return d
vb_layers_initialize.py CHANGED
@@ -1,86 +1,86 @@
1
- """Utility functions for initializing weights and biases."""
2
-
3
- # Copyright 2021 AlQuraishi Laboratory
4
- # Copyright 2021 DeepMind Technologies Limited
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
-
18
- import math
19
-
20
- import torch
21
-
22
-
23
- def _calculate_fan(linear_weight_shape, fan="fan_in"):
24
- fan_out, fan_in = linear_weight_shape
25
-
26
- if fan == "fan_in":
27
- f = fan_in
28
- elif fan == "fan_out":
29
- f = fan_out
30
- elif fan == "fan_avg":
31
- f = (fan_in + fan_out) / 2
32
- else:
33
- raise ValueError("Invalid fan option")
34
-
35
- return f
36
-
37
-
38
- def trunc_normal_init_(weights, scale=1.0, fan="fan_in"):
39
- shape = weights.shape
40
- f = _calculate_fan(shape, fan)
41
- scale = scale / max(1, f)
42
- std = math.sqrt(scale)
43
- with torch.no_grad():
44
- torch.nn.init.trunc_normal_(weights, mean=0.0, std=std, a=-2 * std, b=2 * std)
45
-
46
-
47
- def lecun_normal_init_(weights):
48
- trunc_normal_init_(weights, scale=1.0)
49
-
50
-
51
- def he_normal_init_(weights):
52
- trunc_normal_init_(weights, scale=2.0)
53
-
54
-
55
- def glorot_uniform_init_(weights):
56
- torch.nn.init.xavier_uniform_(weights, gain=1)
57
-
58
-
59
- def final_init_(weights):
60
- with torch.no_grad():
61
- weights.fill_(0.0)
62
-
63
-
64
- def gating_init_(weights):
65
- with torch.no_grad():
66
- weights.fill_(0.0)
67
-
68
-
69
- def bias_init_zero_(bias):
70
- with torch.no_grad():
71
- bias.fill_(0.0)
72
-
73
-
74
- def bias_init_one_(bias):
75
- with torch.no_grad():
76
- bias.fill_(1.0)
77
-
78
-
79
- def normal_init_(weights):
80
- torch.nn.init.kaiming_normal_(weights, nonlinearity="linear")
81
-
82
-
83
- def ipa_point_weights_init_(weights):
84
- with torch.no_grad():
85
- softplus_inverse_1 = 0.541324854612918
86
- weights.fill_(softplus_inverse_1)
 
1
+ """Utility functions for initializing weights and biases."""
2
+
3
+ # Copyright 2021 AlQuraishi Laboratory
4
+ # Copyright 2021 DeepMind Technologies Limited
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ import math
19
+
20
+ import torch
21
+
22
+
23
+ def _calculate_fan(linear_weight_shape, fan="fan_in"):
24
+ fan_out, fan_in = linear_weight_shape
25
+
26
+ if fan == "fan_in":
27
+ f = fan_in
28
+ elif fan == "fan_out":
29
+ f = fan_out
30
+ elif fan == "fan_avg":
31
+ f = (fan_in + fan_out) / 2
32
+ else:
33
+ raise ValueError("Invalid fan option")
34
+
35
+ return f
36
+
37
+
38
+ def trunc_normal_init_(weights, scale=1.0, fan="fan_in"):
39
+ shape = weights.shape
40
+ f = _calculate_fan(shape, fan)
41
+ scale = scale / max(1, f)
42
+ std = math.sqrt(scale)
43
+ with torch.no_grad():
44
+ torch.nn.init.trunc_normal_(weights, mean=0.0, std=std, a=-2 * std, b=2 * std)
45
+
46
+
47
+ def lecun_normal_init_(weights):
48
+ trunc_normal_init_(weights, scale=1.0)
49
+
50
+
51
+ def he_normal_init_(weights):
52
+ trunc_normal_init_(weights, scale=2.0)
53
+
54
+
55
+ def glorot_uniform_init_(weights):
56
+ torch.nn.init.xavier_uniform_(weights, gain=1)
57
+
58
+
59
+ def final_init_(weights):
60
+ with torch.no_grad():
61
+ weights.fill_(0.0)
62
+
63
+
64
+ def gating_init_(weights):
65
+ with torch.no_grad():
66
+ weights.fill_(0.0)
67
+
68
+
69
+ def bias_init_zero_(bias):
70
+ with torch.no_grad():
71
+ bias.fill_(0.0)
72
+
73
+
74
+ def bias_init_one_(bias):
75
+ with torch.no_grad():
76
+ bias.fill_(1.0)
77
+
78
+
79
+ def normal_init_(weights):
80
+ torch.nn.init.kaiming_normal_(weights, nonlinearity="linear")
81
+
82
+
83
+ def ipa_point_weights_init_(weights):
84
+ with torch.no_grad():
85
+ softplus_inverse_1 = 0.541324854612918
86
+ weights.fill_(softplus_inverse_1)
vb_layers_outer_product_mean.py CHANGED
@@ -1,98 +1,98 @@
1
- import torch
2
- from torch import Tensor, nn
3
-
4
- from . import vb_layers_initialize as init
5
-
6
-
7
- class OuterProductMean(nn.Module):
8
- """Outer product mean layer."""
9
-
10
- def __init__(self, c_in: int, c_hidden: int, c_out: int) -> None:
11
- """Initialize the outer product mean layer.
12
-
13
- Parameters
14
- ----------
15
- c_in : int
16
- The input dimension.
17
- c_hidden : int
18
- The hidden dimension.
19
- c_out : int
20
- The output dimension.
21
-
22
- """
23
- super().__init__()
24
- self.c_hidden = c_hidden
25
- self.norm = nn.LayerNorm(c_in)
26
- self.proj_a = nn.Linear(c_in, c_hidden, bias=False)
27
- self.proj_b = nn.Linear(c_in, c_hidden, bias=False)
28
- self.proj_o = nn.Linear(c_hidden * c_hidden, c_out)
29
- init.final_init_(self.proj_o.weight)
30
- init.final_init_(self.proj_o.bias)
31
-
32
- def forward(self, m: Tensor, mask: Tensor, chunk_size: int = None) -> Tensor:
33
- """Forward pass.
34
-
35
- Parameters
36
- ----------
37
- m : torch.Tensor
38
- The sequence tensor (B, S, N, c_in).
39
- mask : torch.Tensor
40
- The mask tensor (B, S, N).
41
-
42
- Returns
43
- -------
44
- torch.Tensor
45
- The output tensor (B, N, N, c_out).
46
-
47
- """
48
- # Expand mask
49
- mask = mask.unsqueeze(-1).to(m)
50
-
51
- # Compute projections
52
- m = self.norm(m)
53
- a = self.proj_a(m) * mask
54
- b = self.proj_b(m) * mask
55
-
56
- # Compute outer product mean
57
- if chunk_size is not None and not self.training:
58
- # Compute pairwise mask
59
- for i in range(0, mask.shape[1], 64):
60
- if i == 0:
61
- num_mask = (
62
- mask[:, i : i + 64, None, :] * mask[:, i : i + 64, :, None]
63
- ).sum(1)
64
- else:
65
- num_mask += (
66
- mask[:, i : i + 64, None, :] * mask[:, i : i + 64, :, None]
67
- ).sum(1)
68
- num_mask = num_mask.clamp(min=1)
69
-
70
- # Compute squentially in chunks
71
- for i in range(0, self.c_hidden, chunk_size):
72
- a_chunk = a[:, :, :, i : i + chunk_size]
73
- sliced_weight_proj_o = self.proj_o.weight[
74
- :, i * self.c_hidden : (i + chunk_size) * self.c_hidden
75
- ]
76
-
77
- z = torch.einsum("bsic,bsjd->bijcd", a_chunk, b)
78
- z = z.reshape(*z.shape[:3], -1)
79
- z = z / num_mask
80
-
81
- # Project to output
82
- if i == 0:
83
- z_out = z.to(m) @ sliced_weight_proj_o.T
84
- else:
85
- z_out = z_out + z.to(m) @ sliced_weight_proj_o.T
86
-
87
- z_out = z_out + self.proj_o.bias # add bias
88
- return z_out
89
- else:
90
- mask = mask[:, :, None, :] * mask[:, :, :, None]
91
- num_mask = mask.sum(1).clamp(min=1)
92
- z = torch.einsum("bsic,bsjd->bijcd", a.float(), b.float())
93
- z = z.reshape(*z.shape[:3], -1)
94
- z = z / num_mask
95
-
96
- # Project to output
97
- z = self.proj_o(z.to(m))
98
- return z
 
1
+ import torch
2
+ from torch import Tensor, nn
3
+
4
+ from . import vb_layers_initialize as init
5
+
6
+
7
+ class OuterProductMean(nn.Module):
8
+ """Outer product mean layer."""
9
+
10
+ def __init__(self, c_in: int, c_hidden: int, c_out: int) -> None:
11
+ """Initialize the outer product mean layer.
12
+
13
+ Parameters
14
+ ----------
15
+ c_in : int
16
+ The input dimension.
17
+ c_hidden : int
18
+ The hidden dimension.
19
+ c_out : int
20
+ The output dimension.
21
+
22
+ """
23
+ super().__init__()
24
+ self.c_hidden = c_hidden
25
+ self.norm = nn.LayerNorm(c_in)
26
+ self.proj_a = nn.Linear(c_in, c_hidden, bias=False)
27
+ self.proj_b = nn.Linear(c_in, c_hidden, bias=False)
28
+ self.proj_o = nn.Linear(c_hidden * c_hidden, c_out)
29
+ init.final_init_(self.proj_o.weight)
30
+ init.final_init_(self.proj_o.bias)
31
+
32
+ def forward(self, m: Tensor, mask: Tensor, chunk_size: int = None) -> Tensor:
33
+ """Forward pass.
34
+
35
+ Parameters
36
+ ----------
37
+ m : torch.Tensor
38
+ The sequence tensor (B, S, N, c_in).
39
+ mask : torch.Tensor
40
+ The mask tensor (B, S, N).
41
+
42
+ Returns
43
+ -------
44
+ torch.Tensor
45
+ The output tensor (B, N, N, c_out).
46
+
47
+ """
48
+ # Expand mask
49
+ mask = mask.unsqueeze(-1).to(m)
50
+
51
+ # Compute projections
52
+ m = self.norm(m)
53
+ a = self.proj_a(m) * mask
54
+ b = self.proj_b(m) * mask
55
+
56
+ # Compute outer product mean
57
+ if chunk_size is not None and not self.training:
58
+ # Compute pairwise mask
59
+ for i in range(0, mask.shape[1], 64):
60
+ if i == 0:
61
+ num_mask = (
62
+ mask[:, i : i + 64, None, :] * mask[:, i : i + 64, :, None]
63
+ ).sum(1)
64
+ else:
65
+ num_mask += (
66
+ mask[:, i : i + 64, None, :] * mask[:, i : i + 64, :, None]
67
+ ).sum(1)
68
+ num_mask = num_mask.clamp(min=1)
69
+
70
+ # Compute squentially in chunks
71
+ for i in range(0, self.c_hidden, chunk_size):
72
+ a_chunk = a[:, :, :, i : i + chunk_size]
73
+ sliced_weight_proj_o = self.proj_o.weight[
74
+ :, i * self.c_hidden : (i + chunk_size) * self.c_hidden
75
+ ]
76
+
77
+ z = torch.einsum("bsic,bsjd->bijcd", a_chunk, b)
78
+ z = z.reshape(*z.shape[:3], -1)
79
+ z = z / num_mask
80
+
81
+ # Project to output
82
+ if i == 0:
83
+ z_out = z.to(m) @ sliced_weight_proj_o.T
84
+ else:
85
+ z_out = z_out + z.to(m) @ sliced_weight_proj_o.T
86
+
87
+ z_out = z_out + self.proj_o.bias # add bias
88
+ return z_out
89
+ else:
90
+ mask = mask[:, :, None, :] * mask[:, :, :, None]
91
+ num_mask = mask.sum(1).clamp(min=1)
92
+ z = torch.einsum("bsic,bsjd->bijcd", a.float(), b.float())
93
+ z = z.reshape(*z.shape[:3], -1)
94
+ z = z / num_mask
95
+
96
+ # Project to output
97
+ z = self.proj_o(z.to(m))
98
+ return z
vb_layers_pair_averaging.py CHANGED
@@ -1,135 +1,135 @@
1
- import torch
2
- from torch import Tensor, nn
3
-
4
- from . import vb_layers_initialize as init
5
-
6
-
7
- class PairWeightedAveraging(nn.Module):
8
- """Pair weighted averaging layer."""
9
-
10
- def __init__(
11
- self,
12
- c_m: int,
13
- c_z: int,
14
- c_h: int,
15
- num_heads: int,
16
- inf: float = 1e6,
17
- ) -> None:
18
- """Initialize the pair weighted averaging layer.
19
-
20
- Parameters
21
- ----------
22
- c_m: int
23
- The dimension of the input sequence.
24
- c_z: int
25
- The dimension of the input pairwise tensor.
26
- c_h: int
27
- The dimension of the hidden.
28
- num_heads: int
29
- The number of heads.
30
- inf: float
31
- The value to use for masking, default 1e6.
32
-
33
- """
34
- super().__init__()
35
- self.c_m = c_m
36
- self.c_z = c_z
37
- self.c_h = c_h
38
- self.num_heads = num_heads
39
- self.inf = inf
40
-
41
- self.norm_m = nn.LayerNorm(c_m)
42
- self.norm_z = nn.LayerNorm(c_z)
43
-
44
- self.proj_m = nn.Linear(c_m, c_h * num_heads, bias=False)
45
- self.proj_g = nn.Linear(c_m, c_h * num_heads, bias=False)
46
- self.proj_z = nn.Linear(c_z, num_heads, bias=False)
47
- self.proj_o = nn.Linear(c_h * num_heads, c_m, bias=False)
48
- init.final_init_(self.proj_o.weight)
49
-
50
- def forward(
51
- self, m: Tensor, z: Tensor, mask: Tensor, chunk_heads: False = bool
52
- ) -> Tensor:
53
- """Forward pass.
54
-
55
- Parameters
56
- ----------
57
- m : torch.Tensor
58
- The input sequence tensor (B, S, N, D)
59
- z : torch.Tensor
60
- The input pairwise tensor (B, N, N, D)
61
- mask : torch.Tensor
62
- The pairwise mask tensor (B, N, N)
63
-
64
- Returns
65
- -------
66
- torch.Tensor
67
- The output sequence tensor (B, S, N, D)
68
-
69
- """
70
- # Compute layer norms
71
- m = self.norm_m(m)
72
- z = self.norm_z(z)
73
-
74
- if chunk_heads and not self.training:
75
- # Compute heads sequentially
76
- o_chunks = []
77
- for head_idx in range(self.num_heads):
78
- sliced_weight_proj_m = self.proj_m.weight[
79
- head_idx * self.c_h : (head_idx + 1) * self.c_h, :
80
- ]
81
- sliced_weight_proj_g = self.proj_g.weight[
82
- head_idx * self.c_h : (head_idx + 1) * self.c_h, :
83
- ]
84
- sliced_weight_proj_z = self.proj_z.weight[head_idx : (head_idx + 1), :]
85
- sliced_weight_proj_o = self.proj_o.weight[
86
- :, head_idx * self.c_h : (head_idx + 1) * self.c_h
87
- ]
88
-
89
- # Project input tensors
90
- v: Tensor = m @ sliced_weight_proj_m.T
91
- v = v.reshape(*v.shape[:3], 1, self.c_h)
92
- v = v.permute(0, 3, 1, 2, 4)
93
-
94
- # Compute weights
95
- b: Tensor = z @ sliced_weight_proj_z.T
96
- b = b.permute(0, 3, 1, 2)
97
- b = b + (1 - mask[:, None]) * -self.inf
98
- w = torch.softmax(b, dim=-1)
99
-
100
- # Compute gating
101
- g: Tensor = m @ sliced_weight_proj_g.T
102
- g = g.sigmoid()
103
-
104
- # Compute output
105
- o = torch.einsum("bhij,bhsjd->bhsid", w, v)
106
- o = o.permute(0, 2, 3, 1, 4)
107
- o = o.reshape(*o.shape[:3], 1 * self.c_h)
108
- o_chunks = g * o
109
- if head_idx == 0:
110
- o_out = o_chunks @ sliced_weight_proj_o.T
111
- else:
112
- o_out += o_chunks @ sliced_weight_proj_o.T
113
- return o_out
114
- else:
115
- # Project input tensors
116
- v: Tensor = self.proj_m(m)
117
- v = v.reshape(*v.shape[:3], self.num_heads, self.c_h)
118
- v = v.permute(0, 3, 1, 2, 4)
119
-
120
- # Compute weights
121
- b: Tensor = self.proj_z(z)
122
- b = b.permute(0, 3, 1, 2)
123
- b = b + (1 - mask[:, None]) * -self.inf
124
- w = torch.softmax(b, dim=-1)
125
-
126
- # Compute gating
127
- g: Tensor = self.proj_g(m)
128
- g = g.sigmoid()
129
-
130
- # Compute output
131
- o = torch.einsum("bhij,bhsjd->bhsid", w, v)
132
- o = o.permute(0, 2, 3, 1, 4)
133
- o = o.reshape(*o.shape[:3], self.num_heads * self.c_h)
134
- o = self.proj_o(g * o)
135
- return o
 
1
+ import torch
2
+ from torch import Tensor, nn
3
+
4
+ from . import vb_layers_initialize as init
5
+
6
+
7
+ class PairWeightedAveraging(nn.Module):
8
+ """Pair weighted averaging layer."""
9
+
10
+ def __init__(
11
+ self,
12
+ c_m: int,
13
+ c_z: int,
14
+ c_h: int,
15
+ num_heads: int,
16
+ inf: float = 1e6,
17
+ ) -> None:
18
+ """Initialize the pair weighted averaging layer.
19
+
20
+ Parameters
21
+ ----------
22
+ c_m: int
23
+ The dimension of the input sequence.
24
+ c_z: int
25
+ The dimension of the input pairwise tensor.
26
+ c_h: int
27
+ The dimension of the hidden.
28
+ num_heads: int
29
+ The number of heads.
30
+ inf: float
31
+ The value to use for masking, default 1e6.
32
+
33
+ """
34
+ super().__init__()
35
+ self.c_m = c_m
36
+ self.c_z = c_z
37
+ self.c_h = c_h
38
+ self.num_heads = num_heads
39
+ self.inf = inf
40
+
41
+ self.norm_m = nn.LayerNorm(c_m)
42
+ self.norm_z = nn.LayerNorm(c_z)
43
+
44
+ self.proj_m = nn.Linear(c_m, c_h * num_heads, bias=False)
45
+ self.proj_g = nn.Linear(c_m, c_h * num_heads, bias=False)
46
+ self.proj_z = nn.Linear(c_z, num_heads, bias=False)
47
+ self.proj_o = nn.Linear(c_h * num_heads, c_m, bias=False)
48
+ init.final_init_(self.proj_o.weight)
49
+
50
+ def forward(
51
+ self, m: Tensor, z: Tensor, mask: Tensor, chunk_heads: False = bool
52
+ ) -> Tensor:
53
+ """Forward pass.
54
+
55
+ Parameters
56
+ ----------
57
+ m : torch.Tensor
58
+ The input sequence tensor (B, S, N, D)
59
+ z : torch.Tensor
60
+ The input pairwise tensor (B, N, N, D)
61
+ mask : torch.Tensor
62
+ The pairwise mask tensor (B, N, N)
63
+
64
+ Returns
65
+ -------
66
+ torch.Tensor
67
+ The output sequence tensor (B, S, N, D)
68
+
69
+ """
70
+ # Compute layer norms
71
+ m = self.norm_m(m)
72
+ z = self.norm_z(z)
73
+
74
+ if chunk_heads and not self.training:
75
+ # Compute heads sequentially
76
+ o_chunks = []
77
+ for head_idx in range(self.num_heads):
78
+ sliced_weight_proj_m = self.proj_m.weight[
79
+ head_idx * self.c_h : (head_idx + 1) * self.c_h, :
80
+ ]
81
+ sliced_weight_proj_g = self.proj_g.weight[
82
+ head_idx * self.c_h : (head_idx + 1) * self.c_h, :
83
+ ]
84
+ sliced_weight_proj_z = self.proj_z.weight[head_idx : (head_idx + 1), :]
85
+ sliced_weight_proj_o = self.proj_o.weight[
86
+ :, head_idx * self.c_h : (head_idx + 1) * self.c_h
87
+ ]
88
+
89
+ # Project input tensors
90
+ v: Tensor = m @ sliced_weight_proj_m.T
91
+ v = v.reshape(*v.shape[:3], 1, self.c_h)
92
+ v = v.permute(0, 3, 1, 2, 4)
93
+
94
+ # Compute weights
95
+ b: Tensor = z @ sliced_weight_proj_z.T
96
+ b = b.permute(0, 3, 1, 2)
97
+ b = b + (1 - mask[:, None]) * -self.inf
98
+ w = torch.softmax(b, dim=-1)
99
+
100
+ # Compute gating
101
+ g: Tensor = m @ sliced_weight_proj_g.T
102
+ g = g.sigmoid()
103
+
104
+ # Compute output
105
+ o = torch.einsum("bhij,bhsjd->bhsid", w, v)
106
+ o = o.permute(0, 2, 3, 1, 4)
107
+ o = o.reshape(*o.shape[:3], 1 * self.c_h)
108
+ o_chunks = g * o
109
+ if head_idx == 0:
110
+ o_out = o_chunks @ sliced_weight_proj_o.T
111
+ else:
112
+ o_out += o_chunks @ sliced_weight_proj_o.T
113
+ return o_out
114
+ else:
115
+ # Project input tensors
116
+ v: Tensor = self.proj_m(m)
117
+ v = v.reshape(*v.shape[:3], self.num_heads, self.c_h)
118
+ v = v.permute(0, 3, 1, 2, 4)
119
+
120
+ # Compute weights
121
+ b: Tensor = self.proj_z(z)
122
+ b = b.permute(0, 3, 1, 2)
123
+ b = b + (1 - mask[:, None]) * -self.inf
124
+ w = torch.softmax(b, dim=-1)
125
+
126
+ # Compute gating
127
+ g: Tensor = self.proj_g(m)
128
+ g = g.sigmoid()
129
+
130
+ # Compute output
131
+ o = torch.einsum("bhij,bhsjd->bhsid", w, v)
132
+ o = o.permute(0, 2, 3, 1, 4)
133
+ o = o.reshape(*o.shape[:3], self.num_heads * self.c_h)
134
+ o = self.proj_o(g * o)
135
+ return o
vb_layers_pairformer.py CHANGED
@@ -1,335 +1,335 @@
1
- from typing import Optional
2
-
3
- import torch
4
- from torch import Tensor, nn
5
-
6
- from . import vb_const as const
7
- from .vb_layers_attention import AttentionPairBias
8
- from .vb_layers_attentionv2 import AttentionPairBias as AttentionPairBiasV2
9
- from .vb_layers_dropout import get_dropout_mask
10
- from .vb_layers_transition import Transition
11
- from .vb_tri_attn_attention import (
12
- TriangleAttentionEndingNode,
13
- TriangleAttentionStartingNode,
14
- )
15
- from .vb_layers_triangular_mult import (
16
- TriangleMultiplicationIncoming,
17
- TriangleMultiplicationOutgoing,
18
- )
19
-
20
-
21
- class PairformerLayer(nn.Module):
22
- """Pairformer module."""
23
-
24
- def __init__(
25
- self,
26
- token_s: int,
27
- token_z: int,
28
- num_heads: int = 16,
29
- dropout: float = 0.25,
30
- pairwise_head_width: int = 32,
31
- pairwise_num_heads: int = 4,
32
- post_layer_norm: bool = False,
33
- v2: bool = False,
34
- ) -> None:
35
- super().__init__()
36
- self.token_z = token_z
37
- self.dropout = dropout
38
- self.num_heads = num_heads
39
- self.post_layer_norm = post_layer_norm
40
-
41
- self.pre_norm_s = nn.LayerNorm(token_s)
42
- if v2:
43
- self.attention = AttentionPairBiasV2(token_s, token_z, num_heads)
44
- else:
45
- self.attention = AttentionPairBias(token_s, token_z, num_heads)
46
-
47
- self.tri_mul_out = TriangleMultiplicationOutgoing(token_z)
48
- self.tri_mul_in = TriangleMultiplicationIncoming(token_z)
49
-
50
- self.tri_att_start = TriangleAttentionStartingNode(
51
- token_z, pairwise_head_width, pairwise_num_heads, inf=1e9
52
- )
53
- self.tri_att_end = TriangleAttentionEndingNode(
54
- token_z, pairwise_head_width, pairwise_num_heads, inf=1e9
55
- )
56
-
57
- self.transition_s = Transition(token_s, token_s * 4)
58
- self.transition_z = Transition(token_z, token_z * 4)
59
-
60
- self.s_post_norm = (
61
- nn.LayerNorm(token_s) if self.post_layer_norm else nn.Identity()
62
- )
63
-
64
- def forward(
65
- self,
66
- s: Tensor,
67
- z: Tensor,
68
- mask: Tensor,
69
- pair_mask: Tensor,
70
- chunk_size_tri_attn: Optional[int] = None,
71
- use_kernels: bool = False,
72
- use_cuequiv_mul: bool = False,
73
- use_cuequiv_attn: bool = False,
74
- ) -> tuple[Tensor, Tensor]:
75
- # Compute pairwise stack
76
- dropout = get_dropout_mask(self.dropout, z, self.training)
77
- z = z + dropout * self.tri_mul_out(
78
- z, mask=pair_mask, use_kernels=use_cuequiv_mul or use_kernels
79
- )
80
-
81
- dropout = get_dropout_mask(self.dropout, z, self.training)
82
- z = z + dropout * self.tri_mul_in(
83
- z, mask=pair_mask, use_kernels=use_cuequiv_mul or use_kernels
84
- )
85
-
86
- dropout = get_dropout_mask(self.dropout, z, self.training)
87
- z = z + dropout * self.tri_att_start(
88
- z,
89
- mask=pair_mask,
90
- chunk_size=chunk_size_tri_attn,
91
- use_kernels=use_cuequiv_attn or use_kernels,
92
- )
93
-
94
- dropout = get_dropout_mask(self.dropout, z, self.training, columnwise=True)
95
- z = z + dropout * self.tri_att_end(
96
- z,
97
- mask=pair_mask,
98
- chunk_size=chunk_size_tri_attn,
99
- use_kernels=use_cuequiv_attn or use_kernels,
100
- )
101
-
102
- z = z + self.transition_z(z)
103
-
104
- # Compute sequence stack
105
- with torch.autocast("cuda", enabled=False):
106
- s_normed = self.pre_norm_s(s.float())
107
- s = s.float() + self.attention(
108
- s=s_normed, z=z.float(), mask=mask.float(), k_in=s_normed
109
- )
110
- s = s + self.transition_s(s)
111
- s = self.s_post_norm(s)
112
-
113
- return s, z
114
-
115
-
116
- class PairformerModule(nn.Module):
117
- """Pairformer module."""
118
-
119
- def __init__(
120
- self,
121
- token_s: int,
122
- token_z: int,
123
- num_blocks: int,
124
- num_heads: int = 16,
125
- dropout: float = 0.25,
126
- pairwise_head_width: int = 32,
127
- pairwise_num_heads: int = 4,
128
- post_layer_norm: bool = False,
129
- activation_checkpointing: bool = False,
130
- v2: bool = False,
131
- **kwargs,
132
- ) -> None:
133
- super().__init__()
134
- self.token_z = token_z
135
- self.num_blocks = num_blocks
136
- self.dropout = dropout
137
- self.num_heads = num_heads
138
- self.post_layer_norm = post_layer_norm
139
- self.activation_checkpointing = activation_checkpointing
140
-
141
- self.layers = nn.ModuleList()
142
- for _ in range(num_blocks):
143
- self.layers.append(
144
- PairformerLayer(
145
- token_s,
146
- token_z,
147
- num_heads,
148
- dropout,
149
- pairwise_head_width,
150
- pairwise_num_heads,
151
- post_layer_norm,
152
- v2,
153
- ),
154
- )
155
-
156
- def forward(
157
- self,
158
- s: Tensor,
159
- z: Tensor,
160
- mask: Tensor,
161
- pair_mask: Tensor,
162
- use_kernels: bool = False,
163
- ) -> tuple[Tensor, Tensor]:
164
- """Perform the forward pass.
165
-
166
- Parameters
167
- ----------
168
- s : Tensor
169
- The sequence stack.
170
- z : Tensor
171
- The pairwise stack.
172
- mask : Tensor
173
- The mask.
174
- pair_mask : Tensor
175
- The pairwise mask.
176
- use_kernels : bool
177
- Whether to use kernels.
178
-
179
- """
180
- if not self.training:
181
- if z.shape[1] > const.chunk_size_threshold:
182
- chunk_size_tri_attn = 128
183
- else:
184
- chunk_size_tri_attn = 512
185
- else:
186
- chunk_size_tri_attn = None
187
-
188
- for layer in self.layers:
189
- if self.activation_checkpointing and self.training:
190
- s, z = torch.utils.checkpoint.checkpoint(
191
- layer,
192
- s,
193
- z,
194
- mask,
195
- pair_mask,
196
- chunk_size_tri_attn,
197
- use_kernels,
198
- )
199
- else:
200
- s, z = layer(s, z, mask, pair_mask, chunk_size_tri_attn, use_kernels)
201
- return s, z
202
-
203
-
204
- class PairformerNoSeqLayer(nn.Module):
205
- """Pairformer module without sequence track."""
206
-
207
- def __init__(
208
- self,
209
- token_z: int,
210
- dropout: float = 0.25,
211
- pairwise_head_width: int = 32,
212
- pairwise_num_heads: int = 4,
213
- post_layer_norm: bool = False,
214
- ) -> None:
215
- super().__init__()
216
- self.token_z = token_z
217
- self.dropout = dropout
218
- self.post_layer_norm = post_layer_norm
219
-
220
- self.tri_mul_out = TriangleMultiplicationOutgoing(token_z)
221
- self.tri_mul_in = TriangleMultiplicationIncoming(token_z)
222
-
223
- self.tri_att_start = TriangleAttentionStartingNode(
224
- token_z, pairwise_head_width, pairwise_num_heads, inf=1e9
225
- )
226
- self.tri_att_end = TriangleAttentionEndingNode(
227
- token_z, pairwise_head_width, pairwise_num_heads, inf=1e9
228
- )
229
-
230
- self.transition_z = Transition(token_z, token_z * 4)
231
-
232
- def forward(
233
- self,
234
- z: Tensor,
235
- pair_mask: Tensor,
236
- chunk_size_tri_attn: Optional[int] = None,
237
- use_kernels: bool = False,
238
- use_cuequiv_mul: bool = False,
239
- use_cuequiv_attn: bool = False,
240
- ) -> Tensor:
241
- # Compute pairwise stack
242
- dropout = get_dropout_mask(self.dropout, z, self.training)
243
- z = z + dropout * self.tri_mul_out(
244
- z, mask=pair_mask, use_kernels=use_cuequiv_mul or use_kernels
245
- )
246
-
247
- dropout = get_dropout_mask(self.dropout, z, self.training)
248
- z = z + dropout * self.tri_mul_in(
249
- z, mask=pair_mask, use_kernels=use_cuequiv_mul or use_kernels
250
- )
251
-
252
- dropout = get_dropout_mask(self.dropout, z, self.training)
253
- z = z + dropout * self.tri_att_start(
254
- z,
255
- mask=pair_mask,
256
- chunk_size=chunk_size_tri_attn,
257
- use_kernels=use_cuequiv_attn or use_kernels,
258
- )
259
-
260
- dropout = get_dropout_mask(self.dropout, z, self.training, columnwise=True)
261
- z = z + dropout * self.tri_att_end(
262
- z,
263
- mask=pair_mask,
264
- chunk_size=chunk_size_tri_attn,
265
- use_kernels=use_cuequiv_attn or use_kernels,
266
- )
267
-
268
- z = z + self.transition_z(z)
269
- return z
270
-
271
-
272
- class PairformerNoSeqModule(nn.Module):
273
- """Pairformer module without sequence track."""
274
-
275
- def __init__(
276
- self,
277
- token_z: int,
278
- num_blocks: int,
279
- dropout: float = 0.25,
280
- pairwise_head_width: int = 32,
281
- pairwise_num_heads: int = 4,
282
- post_layer_norm: bool = False,
283
- activation_checkpointing: bool = False,
284
- **kwargs,
285
- ) -> None:
286
- super().__init__()
287
- self.token_z = token_z
288
- self.num_blocks = num_blocks
289
- self.dropout = dropout
290
- self.post_layer_norm = post_layer_norm
291
- self.activation_checkpointing = activation_checkpointing
292
-
293
- self.layers = nn.ModuleList()
294
- for i in range(num_blocks):
295
- self.layers.append(
296
- PairformerNoSeqLayer(
297
- token_z,
298
- dropout,
299
- pairwise_head_width,
300
- pairwise_num_heads,
301
- post_layer_norm,
302
- ),
303
- )
304
-
305
- def forward(
306
- self,
307
- z: Tensor,
308
- pair_mask: Tensor,
309
- use_kernels: bool = False,
310
- ) -> Tensor:
311
- if not self.training:
312
- if z.shape[1] > const.chunk_size_threshold:
313
- chunk_size_tri_attn = 128
314
- else:
315
- chunk_size_tri_attn = 512
316
- else:
317
- chunk_size_tri_attn = None
318
-
319
- for layer in self.layers:
320
- if self.activation_checkpointing and self.training:
321
- z = torch.utils.checkpoint.checkpoint(
322
- layer,
323
- z,
324
- pair_mask,
325
- chunk_size_tri_attn,
326
- use_kernels,
327
- )
328
- else:
329
- z = layer(
330
- z,
331
- pair_mask,
332
- chunk_size_tri_attn,
333
- use_kernels,
334
- )
335
- return z
 
1
+ from typing import Optional
2
+
3
+ import torch
4
+ from torch import Tensor, nn
5
+
6
+ from . import vb_const as const
7
+ from .vb_layers_attention import AttentionPairBias
8
+ from .vb_layers_attentionv2 import AttentionPairBias as AttentionPairBiasV2
9
+ from .vb_layers_dropout import get_dropout_mask
10
+ from .vb_layers_transition import Transition
11
+ from .vb_tri_attn_attention import (
12
+ TriangleAttentionEndingNode,
13
+ TriangleAttentionStartingNode,
14
+ )
15
+ from .vb_layers_triangular_mult import (
16
+ TriangleMultiplicationIncoming,
17
+ TriangleMultiplicationOutgoing,
18
+ )
19
+
20
+
21
+ class PairformerLayer(nn.Module):
22
+ """Pairformer module."""
23
+
24
+ def __init__(
25
+ self,
26
+ token_s: int,
27
+ token_z: int,
28
+ num_heads: int = 16,
29
+ dropout: float = 0.25,
30
+ pairwise_head_width: int = 32,
31
+ pairwise_num_heads: int = 4,
32
+ post_layer_norm: bool = False,
33
+ v2: bool = False,
34
+ ) -> None:
35
+ super().__init__()
36
+ self.token_z = token_z
37
+ self.dropout = dropout
38
+ self.num_heads = num_heads
39
+ self.post_layer_norm = post_layer_norm
40
+
41
+ self.pre_norm_s = nn.LayerNorm(token_s)
42
+ if v2:
43
+ self.attention = AttentionPairBiasV2(token_s, token_z, num_heads)
44
+ else:
45
+ self.attention = AttentionPairBias(token_s, token_z, num_heads)
46
+
47
+ self.tri_mul_out = TriangleMultiplicationOutgoing(token_z)
48
+ self.tri_mul_in = TriangleMultiplicationIncoming(token_z)
49
+
50
+ self.tri_att_start = TriangleAttentionStartingNode(
51
+ token_z, pairwise_head_width, pairwise_num_heads, inf=1e9
52
+ )
53
+ self.tri_att_end = TriangleAttentionEndingNode(
54
+ token_z, pairwise_head_width, pairwise_num_heads, inf=1e9
55
+ )
56
+
57
+ self.transition_s = Transition(token_s, token_s * 4)
58
+ self.transition_z = Transition(token_z, token_z * 4)
59
+
60
+ self.s_post_norm = (
61
+ nn.LayerNorm(token_s) if self.post_layer_norm else nn.Identity()
62
+ )
63
+
64
+ def forward(
65
+ self,
66
+ s: Tensor,
67
+ z: Tensor,
68
+ mask: Tensor,
69
+ pair_mask: Tensor,
70
+ chunk_size_tri_attn: Optional[int] = None,
71
+ use_kernels: bool = False,
72
+ use_cuequiv_mul: bool = False,
73
+ use_cuequiv_attn: bool = False,
74
+ ) -> tuple[Tensor, Tensor]:
75
+ # Compute pairwise stack
76
+ dropout = get_dropout_mask(self.dropout, z, self.training)
77
+ z = z + dropout * self.tri_mul_out(
78
+ z, mask=pair_mask, use_kernels=use_cuequiv_mul or use_kernels
79
+ )
80
+
81
+ dropout = get_dropout_mask(self.dropout, z, self.training)
82
+ z = z + dropout * self.tri_mul_in(
83
+ z, mask=pair_mask, use_kernels=use_cuequiv_mul or use_kernels
84
+ )
85
+
86
+ dropout = get_dropout_mask(self.dropout, z, self.training)
87
+ z = z + dropout * self.tri_att_start(
88
+ z,
89
+ mask=pair_mask,
90
+ chunk_size=chunk_size_tri_attn,
91
+ use_kernels=use_cuequiv_attn or use_kernels,
92
+ )
93
+
94
+ dropout = get_dropout_mask(self.dropout, z, self.training, columnwise=True)
95
+ z = z + dropout * self.tri_att_end(
96
+ z,
97
+ mask=pair_mask,
98
+ chunk_size=chunk_size_tri_attn,
99
+ use_kernels=use_cuequiv_attn or use_kernels,
100
+ )
101
+
102
+ z = z + self.transition_z(z)
103
+
104
+ # Compute sequence stack
105
+ with torch.autocast("cuda", enabled=False):
106
+ s_normed = self.pre_norm_s(s.float())
107
+ s = s.float() + self.attention(
108
+ s=s_normed, z=z.float(), mask=mask.float(), k_in=s_normed
109
+ )
110
+ s = s + self.transition_s(s)
111
+ s = self.s_post_norm(s)
112
+
113
+ return s, z
114
+
115
+
116
+ class PairformerModule(nn.Module):
117
+ """Pairformer module."""
118
+
119
+ def __init__(
120
+ self,
121
+ token_s: int,
122
+ token_z: int,
123
+ num_blocks: int,
124
+ num_heads: int = 16,
125
+ dropout: float = 0.25,
126
+ pairwise_head_width: int = 32,
127
+ pairwise_num_heads: int = 4,
128
+ post_layer_norm: bool = False,
129
+ activation_checkpointing: bool = False,
130
+ v2: bool = False,
131
+ **kwargs,
132
+ ) -> None:
133
+ super().__init__()
134
+ self.token_z = token_z
135
+ self.num_blocks = num_blocks
136
+ self.dropout = dropout
137
+ self.num_heads = num_heads
138
+ self.post_layer_norm = post_layer_norm
139
+ self.activation_checkpointing = activation_checkpointing
140
+
141
+ self.layers = nn.ModuleList()
142
+ for _ in range(num_blocks):
143
+ self.layers.append(
144
+ PairformerLayer(
145
+ token_s,
146
+ token_z,
147
+ num_heads,
148
+ dropout,
149
+ pairwise_head_width,
150
+ pairwise_num_heads,
151
+ post_layer_norm,
152
+ v2,
153
+ ),
154
+ )
155
+
156
+ def forward(
157
+ self,
158
+ s: Tensor,
159
+ z: Tensor,
160
+ mask: Tensor,
161
+ pair_mask: Tensor,
162
+ use_kernels: bool = False,
163
+ ) -> tuple[Tensor, Tensor]:
164
+ """Perform the forward pass.
165
+
166
+ Parameters
167
+ ----------
168
+ s : Tensor
169
+ The sequence stack.
170
+ z : Tensor
171
+ The pairwise stack.
172
+ mask : Tensor
173
+ The mask.
174
+ pair_mask : Tensor
175
+ The pairwise mask.
176
+ use_kernels : bool
177
+ Whether to use kernels.
178
+
179
+ """
180
+ if not self.training:
181
+ if z.shape[1] > const.chunk_size_threshold:
182
+ chunk_size_tri_attn = 128
183
+ else:
184
+ chunk_size_tri_attn = 512
185
+ else:
186
+ chunk_size_tri_attn = None
187
+
188
+ for layer in self.layers:
189
+ if self.activation_checkpointing and self.training:
190
+ s, z = torch.utils.checkpoint.checkpoint(
191
+ layer,
192
+ s,
193
+ z,
194
+ mask,
195
+ pair_mask,
196
+ chunk_size_tri_attn,
197
+ use_kernels,
198
+ )
199
+ else:
200
+ s, z = layer(s, z, mask, pair_mask, chunk_size_tri_attn, use_kernels)
201
+ return s, z
202
+
203
+
204
+ class PairformerNoSeqLayer(nn.Module):
205
+ """Pairformer module without sequence track."""
206
+
207
+ def __init__(
208
+ self,
209
+ token_z: int,
210
+ dropout: float = 0.25,
211
+ pairwise_head_width: int = 32,
212
+ pairwise_num_heads: int = 4,
213
+ post_layer_norm: bool = False,
214
+ ) -> None:
215
+ super().__init__()
216
+ self.token_z = token_z
217
+ self.dropout = dropout
218
+ self.post_layer_norm = post_layer_norm
219
+
220
+ self.tri_mul_out = TriangleMultiplicationOutgoing(token_z)
221
+ self.tri_mul_in = TriangleMultiplicationIncoming(token_z)
222
+
223
+ self.tri_att_start = TriangleAttentionStartingNode(
224
+ token_z, pairwise_head_width, pairwise_num_heads, inf=1e9
225
+ )
226
+ self.tri_att_end = TriangleAttentionEndingNode(
227
+ token_z, pairwise_head_width, pairwise_num_heads, inf=1e9
228
+ )
229
+
230
+ self.transition_z = Transition(token_z, token_z * 4)
231
+
232
+ def forward(
233
+ self,
234
+ z: Tensor,
235
+ pair_mask: Tensor,
236
+ chunk_size_tri_attn: Optional[int] = None,
237
+ use_kernels: bool = False,
238
+ use_cuequiv_mul: bool = False,
239
+ use_cuequiv_attn: bool = False,
240
+ ) -> Tensor:
241
+ # Compute pairwise stack
242
+ dropout = get_dropout_mask(self.dropout, z, self.training)
243
+ z = z + dropout * self.tri_mul_out(
244
+ z, mask=pair_mask, use_kernels=use_cuequiv_mul or use_kernels
245
+ )
246
+
247
+ dropout = get_dropout_mask(self.dropout, z, self.training)
248
+ z = z + dropout * self.tri_mul_in(
249
+ z, mask=pair_mask, use_kernels=use_cuequiv_mul or use_kernels
250
+ )
251
+
252
+ dropout = get_dropout_mask(self.dropout, z, self.training)
253
+ z = z + dropout * self.tri_att_start(
254
+ z,
255
+ mask=pair_mask,
256
+ chunk_size=chunk_size_tri_attn,
257
+ use_kernels=use_cuequiv_attn or use_kernels,
258
+ )
259
+
260
+ dropout = get_dropout_mask(self.dropout, z, self.training, columnwise=True)
261
+ z = z + dropout * self.tri_att_end(
262
+ z,
263
+ mask=pair_mask,
264
+ chunk_size=chunk_size_tri_attn,
265
+ use_kernels=use_cuequiv_attn or use_kernels,
266
+ )
267
+
268
+ z = z + self.transition_z(z)
269
+ return z
270
+
271
+
272
+ class PairformerNoSeqModule(nn.Module):
273
+ """Pairformer module without sequence track."""
274
+
275
+ def __init__(
276
+ self,
277
+ token_z: int,
278
+ num_blocks: int,
279
+ dropout: float = 0.25,
280
+ pairwise_head_width: int = 32,
281
+ pairwise_num_heads: int = 4,
282
+ post_layer_norm: bool = False,
283
+ activation_checkpointing: bool = False,
284
+ **kwargs,
285
+ ) -> None:
286
+ super().__init__()
287
+ self.token_z = token_z
288
+ self.num_blocks = num_blocks
289
+ self.dropout = dropout
290
+ self.post_layer_norm = post_layer_norm
291
+ self.activation_checkpointing = activation_checkpointing
292
+
293
+ self.layers = nn.ModuleList()
294
+ for i in range(num_blocks):
295
+ self.layers.append(
296
+ PairformerNoSeqLayer(
297
+ token_z,
298
+ dropout,
299
+ pairwise_head_width,
300
+ pairwise_num_heads,
301
+ post_layer_norm,
302
+ ),
303
+ )
304
+
305
+ def forward(
306
+ self,
307
+ z: Tensor,
308
+ pair_mask: Tensor,
309
+ use_kernels: bool = False,
310
+ ) -> Tensor:
311
+ if not self.training:
312
+ if z.shape[1] > const.chunk_size_threshold:
313
+ chunk_size_tri_attn = 128
314
+ else:
315
+ chunk_size_tri_attn = 512
316
+ else:
317
+ chunk_size_tri_attn = None
318
+
319
+ for layer in self.layers:
320
+ if self.activation_checkpointing and self.training:
321
+ z = torch.utils.checkpoint.checkpoint(
322
+ layer,
323
+ z,
324
+ pair_mask,
325
+ chunk_size_tri_attn,
326
+ use_kernels,
327
+ )
328
+ else:
329
+ z = layer(
330
+ z,
331
+ pair_mask,
332
+ chunk_size_tri_attn,
333
+ use_kernels,
334
+ )
335
+ return z
vb_layers_transition.py CHANGED
@@ -1,78 +1,78 @@
1
- from typing import Optional
2
-
3
- from torch import Tensor, nn
4
-
5
- from . import vb_layers_initialize as init
6
-
7
-
8
- class Transition(nn.Module):
9
- """Perform a two-layer MLP."""
10
-
11
- def __init__(
12
- self,
13
- dim: int = 128,
14
- hidden: int = 512,
15
- out_dim: Optional[int] = None,
16
- ) -> None:
17
- """Initialize the TransitionUpdate module.
18
-
19
- Parameters
20
- ----------
21
- dim: int
22
- The dimension of the input, default 128
23
- hidden: int
24
- The dimension of the hidden, default 512
25
- out_dim: Optional[int]
26
- The dimension of the output, default None
27
-
28
- """
29
- super().__init__()
30
- if out_dim is None:
31
- out_dim = dim
32
-
33
- self.norm = nn.LayerNorm(dim, eps=1e-5)
34
- self.fc1 = nn.Linear(dim, hidden, bias=False)
35
- self.fc2 = nn.Linear(dim, hidden, bias=False)
36
- self.fc3 = nn.Linear(hidden, out_dim, bias=False)
37
- self.silu = nn.SiLU()
38
- self.hidden = hidden
39
-
40
- init.bias_init_one_(self.norm.weight)
41
- init.bias_init_zero_(self.norm.bias)
42
-
43
- init.lecun_normal_init_(self.fc1.weight)
44
- init.lecun_normal_init_(self.fc2.weight)
45
- init.final_init_(self.fc3.weight)
46
-
47
- def forward(self, x: Tensor, chunk_size: int = None) -> Tensor:
48
- """Perform a forward pass.
49
-
50
- Parameters
51
- ----------
52
- x: torch.Tensor
53
- The input data of shape (..., D)
54
-
55
- Returns
56
- -------
57
- x: torch.Tensor
58
- The output data of shape (..., D)
59
-
60
- """
61
- x = self.norm(x)
62
-
63
- if chunk_size is None or self.training:
64
- x = self.silu(self.fc1(x)) * self.fc2(x)
65
- x = self.fc3(x)
66
- return x
67
- else:
68
- # Compute in chunks
69
- for i in range(0, self.hidden, chunk_size):
70
- fc1_slice = self.fc1.weight[i : i + chunk_size, :]
71
- fc2_slice = self.fc2.weight[i : i + chunk_size, :]
72
- fc3_slice = self.fc3.weight[:, i : i + chunk_size]
73
- x_chunk = self.silu((x @ fc1_slice.T)) * (x @ fc2_slice.T)
74
- if i == 0:
75
- x_out = x_chunk @ fc3_slice.T
76
- else:
77
- x_out = x_out + x_chunk @ fc3_slice.T
78
- return x_out
 
1
+ from typing import Optional
2
+
3
+ from torch import Tensor, nn
4
+
5
+ from . import vb_layers_initialize as init
6
+
7
+
8
+ class Transition(nn.Module):
9
+ """Perform a two-layer MLP."""
10
+
11
+ def __init__(
12
+ self,
13
+ dim: int = 128,
14
+ hidden: int = 512,
15
+ out_dim: Optional[int] = None,
16
+ ) -> None:
17
+ """Initialize the TransitionUpdate module.
18
+
19
+ Parameters
20
+ ----------
21
+ dim: int
22
+ The dimension of the input, default 128
23
+ hidden: int
24
+ The dimension of the hidden, default 512
25
+ out_dim: Optional[int]
26
+ The dimension of the output, default None
27
+
28
+ """
29
+ super().__init__()
30
+ if out_dim is None:
31
+ out_dim = dim
32
+
33
+ self.norm = nn.LayerNorm(dim, eps=1e-5)
34
+ self.fc1 = nn.Linear(dim, hidden, bias=False)
35
+ self.fc2 = nn.Linear(dim, hidden, bias=False)
36
+ self.fc3 = nn.Linear(hidden, out_dim, bias=False)
37
+ self.silu = nn.SiLU()
38
+ self.hidden = hidden
39
+
40
+ init.bias_init_one_(self.norm.weight)
41
+ init.bias_init_zero_(self.norm.bias)
42
+
43
+ init.lecun_normal_init_(self.fc1.weight)
44
+ init.lecun_normal_init_(self.fc2.weight)
45
+ init.final_init_(self.fc3.weight)
46
+
47
+ def forward(self, x: Tensor, chunk_size: int = None) -> Tensor:
48
+ """Perform a forward pass.
49
+
50
+ Parameters
51
+ ----------
52
+ x: torch.Tensor
53
+ The input data of shape (..., D)
54
+
55
+ Returns
56
+ -------
57
+ x: torch.Tensor
58
+ The output data of shape (..., D)
59
+
60
+ """
61
+ x = self.norm(x)
62
+
63
+ if chunk_size is None or self.training:
64
+ x = self.silu(self.fc1(x)) * self.fc2(x)
65
+ x = self.fc3(x)
66
+ return x
67
+ else:
68
+ # Compute in chunks
69
+ for i in range(0, self.hidden, chunk_size):
70
+ fc1_slice = self.fc1.weight[i : i + chunk_size, :]
71
+ fc2_slice = self.fc2.weight[i : i + chunk_size, :]
72
+ fc3_slice = self.fc3.weight[:, i : i + chunk_size]
73
+ x_chunk = self.silu((x @ fc1_slice.T)) * (x @ fc2_slice.T)
74
+ if i == 0:
75
+ x_out = x_chunk @ fc3_slice.T
76
+ else:
77
+ x_out = x_out + x_chunk @ fc3_slice.T
78
+ return x_out
vb_layers_triangular_mult.py CHANGED
@@ -1,215 +1,215 @@
1
- import importlib
2
-
3
- import torch
4
- from torch import Tensor, nn
5
-
6
- from . import vb_layers_initialize as init
7
-
8
-
9
- @torch.compiler.disable
10
- def kernel_triangular_mult(
11
- x,
12
- direction,
13
- mask,
14
- norm_in_weight,
15
- norm_in_bias,
16
- p_in_weight,
17
- g_in_weight,
18
- norm_out_weight,
19
- norm_out_bias,
20
- p_out_weight,
21
- g_out_weight,
22
- eps,
23
- ):
24
- triangle_module = importlib.import_module("cuequivariance_torch.primitives.triangle")
25
- triangle_multiplicative_update = triangle_module.triangle_multiplicative_update
26
- return triangle_multiplicative_update(
27
- x,
28
- direction=direction,
29
- mask=mask,
30
- norm_in_weight=norm_in_weight,
31
- norm_in_bias=norm_in_bias,
32
- p_in_weight=p_in_weight,
33
- g_in_weight=g_in_weight,
34
- norm_out_weight=norm_out_weight,
35
- norm_out_bias=norm_out_bias,
36
- p_out_weight=p_out_weight,
37
- g_out_weight=g_out_weight,
38
- eps=eps,
39
- )
40
-
41
-
42
- class TriangleMultiplicationOutgoing(nn.Module):
43
- """TriangleMultiplicationOutgoing."""
44
-
45
- def __init__(self, dim: int = 128) -> None:
46
- """Initialize the TriangularUpdate module.
47
-
48
- Parameters
49
- ----------
50
- dim: int
51
- The dimension of the input, default 128
52
-
53
- """
54
- super().__init__()
55
-
56
- self.norm_in = nn.LayerNorm(dim, eps=1e-5)
57
- self.p_in = nn.Linear(dim, 2 * dim, bias=False)
58
- self.g_in = nn.Linear(dim, 2 * dim, bias=False)
59
-
60
- self.norm_out = nn.LayerNorm(dim)
61
- self.p_out = nn.Linear(dim, dim, bias=False)
62
- self.g_out = nn.Linear(dim, dim, bias=False)
63
-
64
- init.bias_init_one_(self.norm_in.weight)
65
- init.bias_init_zero_(self.norm_in.bias)
66
-
67
- init.lecun_normal_init_(self.p_in.weight)
68
- init.gating_init_(self.g_in.weight)
69
-
70
- init.bias_init_one_(self.norm_out.weight)
71
- init.bias_init_zero_(self.norm_out.bias)
72
-
73
- init.final_init_(self.p_out.weight)
74
- init.gating_init_(self.g_out.weight)
75
-
76
- def forward(self, x: Tensor, mask: Tensor, use_kernels: bool = False) -> Tensor:
77
- """Perform a forward pass.
78
-
79
- Parameters
80
- ----------
81
- x: torch.Tensor
82
- The input data of shape (B, N, N, D)
83
- mask: torch.Tensor
84
- The input mask of shape (B, N, N)
85
- use_kernels: bool
86
- Whether to use the kernel
87
-
88
- Returns
89
- -------
90
- x: torch.Tensor
91
- The output data of shape (B, N, N, D)
92
-
93
- """
94
- if use_kernels:
95
- return kernel_triangular_mult(
96
- x,
97
- direction="outgoing",
98
- mask=mask,
99
- norm_in_weight=self.norm_in.weight,
100
- norm_in_bias=self.norm_in.bias,
101
- p_in_weight=self.p_in.weight,
102
- g_in_weight=self.g_in.weight,
103
- norm_out_weight=self.norm_out.weight,
104
- norm_out_bias=self.norm_out.bias,
105
- p_out_weight=self.p_out.weight,
106
- g_out_weight=self.g_out.weight,
107
- eps=1e-5,
108
- )
109
-
110
- # Input gating: D -> D
111
- x = self.norm_in(x)
112
- x_in = x
113
- x = self.p_in(x) * self.g_in(x).sigmoid()
114
-
115
- # Apply mask
116
- x = x * mask.unsqueeze(-1)
117
-
118
- # Split input and cast to float
119
- a, b = torch.chunk(x.float(), 2, dim=-1)
120
-
121
- # Triangular projection
122
- x = torch.einsum("bikd,bjkd->bijd", a, b)
123
-
124
- # Output gating
125
- x = self.p_out(self.norm_out(x)) * self.g_out(x_in).sigmoid()
126
-
127
- return x
128
-
129
-
130
- class TriangleMultiplicationIncoming(nn.Module):
131
- """TriangleMultiplicationIncoming."""
132
-
133
- def __init__(self, dim: int = 128) -> None:
134
- """Initialize the TriangularUpdate module.
135
-
136
- Parameters
137
- ----------
138
- dim: int
139
- The dimension of the input, default 128
140
-
141
- """
142
- super().__init__()
143
-
144
- self.norm_in = nn.LayerNorm(dim, eps=1e-5)
145
- self.p_in = nn.Linear(dim, 2 * dim, bias=False)
146
- self.g_in = nn.Linear(dim, 2 * dim, bias=False)
147
-
148
- self.norm_out = nn.LayerNorm(dim)
149
- self.p_out = nn.Linear(dim, dim, bias=False)
150
- self.g_out = nn.Linear(dim, dim, bias=False)
151
-
152
- init.bias_init_one_(self.norm_in.weight)
153
- init.bias_init_zero_(self.norm_in.bias)
154
-
155
- init.lecun_normal_init_(self.p_in.weight)
156
- init.gating_init_(self.g_in.weight)
157
-
158
- init.bias_init_one_(self.norm_out.weight)
159
- init.bias_init_zero_(self.norm_out.bias)
160
-
161
- init.final_init_(self.p_out.weight)
162
- init.gating_init_(self.g_out.weight)
163
-
164
- def forward(self, x: Tensor, mask: Tensor, use_kernels: bool = False) -> Tensor:
165
- """Perform a forward pass.
166
-
167
- Parameters
168
- ----------
169
- x: torch.Tensor
170
- The input data of shape (B, N, N, D)
171
- mask: torch.Tensor
172
- The input mask of shape (B, N, N)
173
- use_kernels: bool
174
- Whether to use the kernel
175
-
176
- Returns
177
- -------
178
- x: torch.Tensor
179
- The output data of shape (B, N, N, D)
180
-
181
- """
182
- if use_kernels:
183
- return kernel_triangular_mult(
184
- x,
185
- direction="incoming",
186
- mask=mask,
187
- norm_in_weight=self.norm_in.weight,
188
- norm_in_bias=self.norm_in.bias,
189
- p_in_weight=self.p_in.weight,
190
- g_in_weight=self.g_in.weight,
191
- norm_out_weight=self.norm_out.weight,
192
- norm_out_bias=self.norm_out.bias,
193
- p_out_weight=self.p_out.weight,
194
- g_out_weight=self.g_out.weight,
195
- eps=1e-5,
196
- )
197
-
198
- # Input gating: D -> D
199
- x = self.norm_in(x)
200
- x_in = x
201
- x = self.p_in(x) * self.g_in(x).sigmoid()
202
-
203
- # Apply mask
204
- x = x * mask.unsqueeze(-1)
205
-
206
- # Split input and cast to float
207
- a, b = torch.chunk(x.float(), 2, dim=-1)
208
-
209
- # Triangular projection
210
- x = torch.einsum("bkid,bkjd->bijd", a, b)
211
-
212
- # Output gating
213
- x = self.p_out(self.norm_out(x)) * self.g_out(x_in).sigmoid()
214
-
215
- return x
 
1
+ import importlib
2
+
3
+ import torch
4
+ from torch import Tensor, nn
5
+
6
+ from . import vb_layers_initialize as init
7
+
8
+
9
+ @torch.compiler.disable
10
+ def kernel_triangular_mult(
11
+ x,
12
+ direction,
13
+ mask,
14
+ norm_in_weight,
15
+ norm_in_bias,
16
+ p_in_weight,
17
+ g_in_weight,
18
+ norm_out_weight,
19
+ norm_out_bias,
20
+ p_out_weight,
21
+ g_out_weight,
22
+ eps,
23
+ ):
24
+ triangle_module = importlib.import_module("cuequivariance_torch.primitives.triangle")
25
+ triangle_multiplicative_update = triangle_module.triangle_multiplicative_update
26
+ return triangle_multiplicative_update(
27
+ x,
28
+ direction=direction,
29
+ mask=mask,
30
+ norm_in_weight=norm_in_weight,
31
+ norm_in_bias=norm_in_bias,
32
+ p_in_weight=p_in_weight,
33
+ g_in_weight=g_in_weight,
34
+ norm_out_weight=norm_out_weight,
35
+ norm_out_bias=norm_out_bias,
36
+ p_out_weight=p_out_weight,
37
+ g_out_weight=g_out_weight,
38
+ eps=eps,
39
+ )
40
+
41
+
42
+ class TriangleMultiplicationOutgoing(nn.Module):
43
+ """TriangleMultiplicationOutgoing."""
44
+
45
+ def __init__(self, dim: int = 128) -> None:
46
+ """Initialize the TriangularUpdate module.
47
+
48
+ Parameters
49
+ ----------
50
+ dim: int
51
+ The dimension of the input, default 128
52
+
53
+ """
54
+ super().__init__()
55
+
56
+ self.norm_in = nn.LayerNorm(dim, eps=1e-5)
57
+ self.p_in = nn.Linear(dim, 2 * dim, bias=False)
58
+ self.g_in = nn.Linear(dim, 2 * dim, bias=False)
59
+
60
+ self.norm_out = nn.LayerNorm(dim)
61
+ self.p_out = nn.Linear(dim, dim, bias=False)
62
+ self.g_out = nn.Linear(dim, dim, bias=False)
63
+
64
+ init.bias_init_one_(self.norm_in.weight)
65
+ init.bias_init_zero_(self.norm_in.bias)
66
+
67
+ init.lecun_normal_init_(self.p_in.weight)
68
+ init.gating_init_(self.g_in.weight)
69
+
70
+ init.bias_init_one_(self.norm_out.weight)
71
+ init.bias_init_zero_(self.norm_out.bias)
72
+
73
+ init.final_init_(self.p_out.weight)
74
+ init.gating_init_(self.g_out.weight)
75
+
76
+ def forward(self, x: Tensor, mask: Tensor, use_kernels: bool = False) -> Tensor:
77
+ """Perform a forward pass.
78
+
79
+ Parameters
80
+ ----------
81
+ x: torch.Tensor
82
+ The input data of shape (B, N, N, D)
83
+ mask: torch.Tensor
84
+ The input mask of shape (B, N, N)
85
+ use_kernels: bool
86
+ Whether to use the kernel
87
+
88
+ Returns
89
+ -------
90
+ x: torch.Tensor
91
+ The output data of shape (B, N, N, D)
92
+
93
+ """
94
+ if use_kernels:
95
+ return kernel_triangular_mult(
96
+ x,
97
+ direction="outgoing",
98
+ mask=mask,
99
+ norm_in_weight=self.norm_in.weight,
100
+ norm_in_bias=self.norm_in.bias,
101
+ p_in_weight=self.p_in.weight,
102
+ g_in_weight=self.g_in.weight,
103
+ norm_out_weight=self.norm_out.weight,
104
+ norm_out_bias=self.norm_out.bias,
105
+ p_out_weight=self.p_out.weight,
106
+ g_out_weight=self.g_out.weight,
107
+ eps=1e-5,
108
+ )
109
+
110
+ # Input gating: D -> D
111
+ x = self.norm_in(x)
112
+ x_in = x
113
+ x = self.p_in(x) * self.g_in(x).sigmoid()
114
+
115
+ # Apply mask
116
+ x = x * mask.unsqueeze(-1)
117
+
118
+ # Split input and cast to float
119
+ a, b = torch.chunk(x.float(), 2, dim=-1)
120
+
121
+ # Triangular projection
122
+ x = torch.einsum("bikd,bjkd->bijd", a, b)
123
+
124
+ # Output gating
125
+ x = self.p_out(self.norm_out(x)) * self.g_out(x_in).sigmoid()
126
+
127
+ return x
128
+
129
+
130
+ class TriangleMultiplicationIncoming(nn.Module):
131
+ """TriangleMultiplicationIncoming."""
132
+
133
+ def __init__(self, dim: int = 128) -> None:
134
+ """Initialize the TriangularUpdate module.
135
+
136
+ Parameters
137
+ ----------
138
+ dim: int
139
+ The dimension of the input, default 128
140
+
141
+ """
142
+ super().__init__()
143
+
144
+ self.norm_in = nn.LayerNorm(dim, eps=1e-5)
145
+ self.p_in = nn.Linear(dim, 2 * dim, bias=False)
146
+ self.g_in = nn.Linear(dim, 2 * dim, bias=False)
147
+
148
+ self.norm_out = nn.LayerNorm(dim)
149
+ self.p_out = nn.Linear(dim, dim, bias=False)
150
+ self.g_out = nn.Linear(dim, dim, bias=False)
151
+
152
+ init.bias_init_one_(self.norm_in.weight)
153
+ init.bias_init_zero_(self.norm_in.bias)
154
+
155
+ init.lecun_normal_init_(self.p_in.weight)
156
+ init.gating_init_(self.g_in.weight)
157
+
158
+ init.bias_init_one_(self.norm_out.weight)
159
+ init.bias_init_zero_(self.norm_out.bias)
160
+
161
+ init.final_init_(self.p_out.weight)
162
+ init.gating_init_(self.g_out.weight)
163
+
164
+ def forward(self, x: Tensor, mask: Tensor, use_kernels: bool = False) -> Tensor:
165
+ """Perform a forward pass.
166
+
167
+ Parameters
168
+ ----------
169
+ x: torch.Tensor
170
+ The input data of shape (B, N, N, D)
171
+ mask: torch.Tensor
172
+ The input mask of shape (B, N, N)
173
+ use_kernels: bool
174
+ Whether to use the kernel
175
+
176
+ Returns
177
+ -------
178
+ x: torch.Tensor
179
+ The output data of shape (B, N, N, D)
180
+
181
+ """
182
+ if use_kernels:
183
+ return kernel_triangular_mult(
184
+ x,
185
+ direction="incoming",
186
+ mask=mask,
187
+ norm_in_weight=self.norm_in.weight,
188
+ norm_in_bias=self.norm_in.bias,
189
+ p_in_weight=self.p_in.weight,
190
+ g_in_weight=self.g_in.weight,
191
+ norm_out_weight=self.norm_out.weight,
192
+ norm_out_bias=self.norm_out.bias,
193
+ p_out_weight=self.p_out.weight,
194
+ g_out_weight=self.g_out.weight,
195
+ eps=1e-5,
196
+ )
197
+
198
+ # Input gating: D -> D
199
+ x = self.norm_in(x)
200
+ x_in = x
201
+ x = self.p_in(x) * self.g_in(x).sigmoid()
202
+
203
+ # Apply mask
204
+ x = x * mask.unsqueeze(-1)
205
+
206
+ # Split input and cast to float
207
+ a, b = torch.chunk(x.float(), 2, dim=-1)
208
+
209
+ # Triangular projection
210
+ x = torch.einsum("bkid,bkjd->bijd", a, b)
211
+
212
+ # Output gating
213
+ x = self.p_out(self.norm_out(x)) * self.g_out(x_in).sigmoid()
214
+
215
+ return x
vb_loss_diffusionv2.py CHANGED
@@ -1,138 +1,138 @@
1
- # started from code from https://github.com/lucidrains/alphafold3-pytorch, MIT License, Copyright (c) 2024 Phil Wang
2
-
3
- import torch
4
- import torch.nn.functional as F
5
- from einops import einsum, rearrange
6
-
7
-
8
- def weighted_rigid_align(
9
- true_coords, # Float['b n 3'], # true coordinates
10
- pred_coords, # Float['b n 3'], # predicted coordinates
11
- weights, # Float['b n'], # weights for each atom
12
- mask, # Bool['b n'] | None = None # mask for variable lengths
13
- ): # -> Float['b n 3']:
14
- """Algorithm 28 : note there is a problem with the pseudocode in the paper where predicted and
15
- GT are swapped in algorithm 28, but correct in equation (2)."""
16
-
17
- out_shape = torch.broadcast_shapes(true_coords.shape, pred_coords.shape)
18
- *batch_size, num_points, dim = out_shape
19
- weights = (mask * weights).unsqueeze(-1)
20
-
21
- # Compute weighted centroids
22
- true_centroid = (true_coords * weights).sum(dim=-2, keepdim=True) / weights.sum(
23
- dim=-2, keepdim=True
24
- )
25
- pred_centroid = (pred_coords * weights).sum(dim=-2, keepdim=True) / weights.sum(
26
- dim=-2, keepdim=True
27
- )
28
-
29
- # Center the coordinates
30
- true_coords_centered = true_coords - true_centroid
31
- pred_coords_centered = pred_coords - pred_centroid
32
-
33
- if torch.any(mask.sum(dim=-1) < (dim + 1)):
34
- print(
35
- "Warning: The size of one of the point clouds is <= dim+1. "
36
- + "`WeightedRigidAlign` cannot return a unique rotation."
37
- )
38
-
39
- # Compute the weighted covariance matrix
40
- cov_matrix = einsum(
41
- weights * pred_coords_centered,
42
- true_coords_centered,
43
- "... n i, ... n j -> ... i j",
44
- )
45
-
46
- # Compute the SVD of the covariance matrix, required float32 for svd and determinant
47
- original_dtype = cov_matrix.dtype
48
- cov_matrix_32 = cov_matrix.to(dtype=torch.float32)
49
-
50
- U, S, V = torch.linalg.svd(
51
- cov_matrix_32, driver="gesvd" if cov_matrix_32.is_cuda else None
52
- )
53
- V = V.mH
54
-
55
- # Catch ambiguous rotation by checking the magnitude of singular values
56
- if (S.abs() <= 1e-15).any() and not (num_points < (dim + 1)):
57
- print(
58
- "Warning: Excessively low rank of "
59
- + "cross-correlation between aligned point clouds. "
60
- + "`WeightedRigidAlign` cannot return a unique rotation."
61
- )
62
-
63
- # Compute the rotation matrix
64
- rot_matrix = torch.einsum("... i j, ... k j -> ... i k", U, V).to(
65
- dtype=torch.float32
66
- )
67
-
68
- # Ensure proper rotation matrix with determinant 1
69
- F = torch.eye(dim, dtype=cov_matrix_32.dtype, device=cov_matrix.device)[
70
- None
71
- ].repeat(*batch_size, 1, 1)
72
- F[..., -1, -1] = torch.det(rot_matrix)
73
- rot_matrix = einsum(U, F, V, "... i j, ... j k, ... l k -> ... i l")
74
- rot_matrix = rot_matrix.to(dtype=original_dtype)
75
-
76
- # Apply the rotation and translation
77
- aligned_coords = (
78
- einsum(true_coords_centered, rot_matrix, "... n i, ... j i -> ... n j")
79
- + pred_centroid
80
- )
81
- aligned_coords.detach_()
82
-
83
- return aligned_coords
84
-
85
-
86
- def smooth_lddt_loss(
87
- pred_coords, # Float['b n 3'],
88
- true_coords, # Float['b n 3'],
89
- is_nucleotide, # Bool['b n'],
90
- coords_mask, # Bool['b n'] | None = None,
91
- nucleic_acid_cutoff: float = 30.0,
92
- other_cutoff: float = 15.0,
93
- multiplicity: int = 1,
94
- ): # -> Float['']:
95
- """Algorithm 27
96
- pred_coords: predicted coordinates
97
- true_coords: true coordinates
98
- Note: for efficiency pred_coords is the only one with the multiplicity expanded
99
- TODO: add weighing which overweight the smooth lddt contribution close to t=0 (not present in the paper)
100
- """
101
- lddt = []
102
- for i in range(true_coords.shape[0]):
103
- true_dists = torch.cdist(true_coords[i], true_coords[i])
104
-
105
- is_nucleotide_i = is_nucleotide[i // multiplicity]
106
- coords_mask_i = coords_mask[i // multiplicity]
107
-
108
- is_nucleotide_pair = is_nucleotide_i.unsqueeze(-1).expand(
109
- -1, is_nucleotide_i.shape[-1]
110
- )
111
-
112
- mask = is_nucleotide_pair * (true_dists < nucleic_acid_cutoff).float()
113
- mask += (1 - is_nucleotide_pair) * (true_dists < other_cutoff).float()
114
- mask *= 1 - torch.eye(pred_coords.shape[1], device=pred_coords.device)
115
- mask *= coords_mask_i.unsqueeze(-1)
116
- mask *= coords_mask_i.unsqueeze(-2)
117
-
118
- valid_pairs = mask.nonzero()
119
- true_dists_i = true_dists[valid_pairs[:, 0], valid_pairs[:, 1]]
120
-
121
- pred_coords_i1 = pred_coords[i, valid_pairs[:, 0]]
122
- pred_coords_i2 = pred_coords[i, valid_pairs[:, 1]]
123
- pred_dists_i = F.pairwise_distance(pred_coords_i1, pred_coords_i2)
124
-
125
- dist_diff_i = torch.abs(true_dists_i - pred_dists_i)
126
-
127
- eps_i = (
128
- F.sigmoid(0.5 - dist_diff_i)
129
- + F.sigmoid(1.0 - dist_diff_i)
130
- + F.sigmoid(2.0 - dist_diff_i)
131
- + F.sigmoid(4.0 - dist_diff_i)
132
- ) / 4.0
133
-
134
- lddt_i = eps_i.sum() / (valid_pairs.shape[0] + 1e-5)
135
- lddt.append(lddt_i)
136
-
137
- # average over batch & multiplicity
138
- return 1.0 - torch.stack(lddt, dim=0).mean(dim=0)
 
1
+ # started from code from https://github.com/lucidrains/alphafold3-pytorch, MIT License, Copyright (c) 2024 Phil Wang
2
+
3
+ import torch
4
+ import torch.nn.functional as F
5
+ from einops import einsum, rearrange
6
+
7
+
8
+ def weighted_rigid_align(
9
+ true_coords, # Float['b n 3'], # true coordinates
10
+ pred_coords, # Float['b n 3'], # predicted coordinates
11
+ weights, # Float['b n'], # weights for each atom
12
+ mask, # Bool['b n'] | None = None # mask for variable lengths
13
+ ): # -> Float['b n 3']:
14
+ """Algorithm 28 : note there is a problem with the pseudocode in the paper where predicted and
15
+ GT are swapped in algorithm 28, but correct in equation (2)."""
16
+
17
+ out_shape = torch.broadcast_shapes(true_coords.shape, pred_coords.shape)
18
+ *batch_size, num_points, dim = out_shape
19
+ weights = (mask * weights).unsqueeze(-1)
20
+
21
+ # Compute weighted centroids
22
+ true_centroid = (true_coords * weights).sum(dim=-2, keepdim=True) / weights.sum(
23
+ dim=-2, keepdim=True
24
+ )
25
+ pred_centroid = (pred_coords * weights).sum(dim=-2, keepdim=True) / weights.sum(
26
+ dim=-2, keepdim=True
27
+ )
28
+
29
+ # Center the coordinates
30
+ true_coords_centered = true_coords - true_centroid
31
+ pred_coords_centered = pred_coords - pred_centroid
32
+
33
+ if torch.any(mask.sum(dim=-1) < (dim + 1)):
34
+ print(
35
+ "Warning: The size of one of the point clouds is <= dim+1. "
36
+ + "`WeightedRigidAlign` cannot return a unique rotation."
37
+ )
38
+
39
+ # Compute the weighted covariance matrix
40
+ cov_matrix = einsum(
41
+ weights * pred_coords_centered,
42
+ true_coords_centered,
43
+ "... n i, ... n j -> ... i j",
44
+ )
45
+
46
+ # Compute the SVD of the covariance matrix, required float32 for svd and determinant
47
+ original_dtype = cov_matrix.dtype
48
+ cov_matrix_32 = cov_matrix.to(dtype=torch.float32)
49
+
50
+ U, S, V = torch.linalg.svd(
51
+ cov_matrix_32, driver="gesvd" if cov_matrix_32.is_cuda else None
52
+ )
53
+ V = V.mH
54
+
55
+ # Catch ambiguous rotation by checking the magnitude of singular values
56
+ if (S.abs() <= 1e-15).any() and not (num_points < (dim + 1)):
57
+ print(
58
+ "Warning: Excessively low rank of "
59
+ + "cross-correlation between aligned point clouds. "
60
+ + "`WeightedRigidAlign` cannot return a unique rotation."
61
+ )
62
+
63
+ # Compute the rotation matrix
64
+ rot_matrix = torch.einsum("... i j, ... k j -> ... i k", U, V).to(
65
+ dtype=torch.float32
66
+ )
67
+
68
+ # Ensure proper rotation matrix with determinant 1
69
+ F = torch.eye(dim, dtype=cov_matrix_32.dtype, device=cov_matrix.device)[
70
+ None
71
+ ].repeat(*batch_size, 1, 1)
72
+ F[..., -1, -1] = torch.det(rot_matrix)
73
+ rot_matrix = einsum(U, F, V, "... i j, ... j k, ... l k -> ... i l")
74
+ rot_matrix = rot_matrix.to(dtype=original_dtype)
75
+
76
+ # Apply the rotation and translation
77
+ aligned_coords = (
78
+ einsum(true_coords_centered, rot_matrix, "... n i, ... j i -> ... n j")
79
+ + pred_centroid
80
+ )
81
+ aligned_coords.detach_()
82
+
83
+ return aligned_coords
84
+
85
+
86
+ def smooth_lddt_loss(
87
+ pred_coords, # Float['b n 3'],
88
+ true_coords, # Float['b n 3'],
89
+ is_nucleotide, # Bool['b n'],
90
+ coords_mask, # Bool['b n'] | None = None,
91
+ nucleic_acid_cutoff: float = 30.0,
92
+ other_cutoff: float = 15.0,
93
+ multiplicity: int = 1,
94
+ ): # -> Float['']:
95
+ """Algorithm 27
96
+ pred_coords: predicted coordinates
97
+ true_coords: true coordinates
98
+ Note: for efficiency pred_coords is the only one with the multiplicity expanded
99
+ TODO: add weighing which overweight the smooth lddt contribution close to t=0 (not present in the paper)
100
+ """
101
+ lddt = []
102
+ for i in range(true_coords.shape[0]):
103
+ true_dists = torch.cdist(true_coords[i], true_coords[i])
104
+
105
+ is_nucleotide_i = is_nucleotide[i // multiplicity]
106
+ coords_mask_i = coords_mask[i // multiplicity]
107
+
108
+ is_nucleotide_pair = is_nucleotide_i.unsqueeze(-1).expand(
109
+ -1, is_nucleotide_i.shape[-1]
110
+ )
111
+
112
+ mask = is_nucleotide_pair * (true_dists < nucleic_acid_cutoff).float()
113
+ mask += (1 - is_nucleotide_pair) * (true_dists < other_cutoff).float()
114
+ mask *= 1 - torch.eye(pred_coords.shape[1], device=pred_coords.device)
115
+ mask *= coords_mask_i.unsqueeze(-1)
116
+ mask *= coords_mask_i.unsqueeze(-2)
117
+
118
+ valid_pairs = mask.nonzero()
119
+ true_dists_i = true_dists[valid_pairs[:, 0], valid_pairs[:, 1]]
120
+
121
+ pred_coords_i1 = pred_coords[i, valid_pairs[:, 0]]
122
+ pred_coords_i2 = pred_coords[i, valid_pairs[:, 1]]
123
+ pred_dists_i = F.pairwise_distance(pred_coords_i1, pred_coords_i2)
124
+
125
+ dist_diff_i = torch.abs(true_dists_i - pred_dists_i)
126
+
127
+ eps_i = (
128
+ F.sigmoid(0.5 - dist_diff_i)
129
+ + F.sigmoid(1.0 - dist_diff_i)
130
+ + F.sigmoid(2.0 - dist_diff_i)
131
+ + F.sigmoid(4.0 - dist_diff_i)
132
+ ) / 4.0
133
+
134
+ lddt_i = eps_i.sum() / (valid_pairs.shape[0] + 1e-5)
135
+ lddt.append(lddt_i)
136
+
137
+ # average over batch & multiplicity
138
+ return 1.0 - torch.stack(lddt, dim=0).mean(dim=0)
vb_modules_confidencev2.py CHANGED
@@ -1,498 +1,498 @@
1
- import torch
2
- from torch import nn
3
- from torch.nn.functional import pad
4
-
5
- from . import vb_const as const
6
- from . import vb_layers_initialize as init
7
- from .vb_layers_confidence_utils import (
8
- compute_aggregated_metric,
9
- compute_ptms,
10
- )
11
- from .vb_layers_pairformer import PairformerModule
12
- from .vb_modules_encodersv2 import RelativePositionEncoder
13
- from .vb_modules_trunkv2 import (
14
- ContactConditioning,
15
- )
16
- from .vb_modules_utils import LinearNoBias
17
-
18
-
19
- class ConfidenceModule(nn.Module):
20
- """Algorithm 31"""
21
-
22
- def __init__(
23
- self,
24
- token_s,
25
- token_z,
26
- pairformer_args: dict,
27
- num_dist_bins=64,
28
- token_level_confidence=True,
29
- max_dist=22,
30
- add_s_to_z_prod=False,
31
- add_s_input_to_s=False,
32
- add_z_input_to_z=False,
33
- maximum_bond_distance=0,
34
- bond_type_feature=False,
35
- confidence_args: dict = None,
36
- compile_pairformer=False,
37
- fix_sym_check=False,
38
- cyclic_pos_enc=False,
39
- return_latent_feats=False,
40
- conditioning_cutoff_min=None,
41
- conditioning_cutoff_max=None,
42
- **kwargs,
43
- ):
44
- super().__init__()
45
- self.max_num_atoms_per_token = 23
46
- if "no_update_s" in pairformer_args:
47
- self.no_update_s = pairformer_args["no_update_s"]
48
- else:
49
- self.no_update_s = False
50
- boundaries = torch.linspace(2, max_dist, num_dist_bins - 1)
51
- self.register_buffer("boundaries", boundaries)
52
- self.dist_bin_pairwise_embed = nn.Embedding(num_dist_bins, token_z)
53
- init.gating_init_(self.dist_bin_pairwise_embed.weight)
54
- self.token_level_confidence = token_level_confidence
55
-
56
- self.s_to_z = LinearNoBias(token_s, token_z)
57
- self.s_to_z_transpose = LinearNoBias(token_s, token_z)
58
- init.gating_init_(self.s_to_z.weight)
59
- init.gating_init_(self.s_to_z_transpose.weight)
60
-
61
- self.add_s_to_z_prod = add_s_to_z_prod
62
- if add_s_to_z_prod:
63
- self.s_to_z_prod_in1 = LinearNoBias(token_s, token_z)
64
- self.s_to_z_prod_in2 = LinearNoBias(token_s, token_z)
65
- self.s_to_z_prod_out = LinearNoBias(token_z, token_z)
66
- init.gating_init_(self.s_to_z_prod_out.weight)
67
-
68
- self.s_inputs_norm = nn.LayerNorm(token_s)
69
- if not self.no_update_s:
70
- self.s_norm = nn.LayerNorm(token_s)
71
- self.z_norm = nn.LayerNorm(token_z)
72
-
73
- self.add_s_input_to_s = add_s_input_to_s
74
- if add_s_input_to_s:
75
- self.s_input_to_s = LinearNoBias(token_s, token_s)
76
- init.gating_init_(self.s_input_to_s.weight)
77
-
78
- self.add_z_input_to_z = add_z_input_to_z
79
- if add_z_input_to_z:
80
- self.rel_pos = RelativePositionEncoder(
81
- token_z, fix_sym_check=fix_sym_check, cyclic_pos_enc=cyclic_pos_enc
82
- )
83
- self.token_bonds = nn.Linear(
84
- 1 if maximum_bond_distance == 0 else maximum_bond_distance + 2,
85
- token_z,
86
- bias=False,
87
- )
88
- self.bond_type_feature = bond_type_feature
89
- if bond_type_feature:
90
- self.token_bonds_type = nn.Embedding(len(const.bond_types) + 1, token_z)
91
-
92
- self.contact_conditioning = ContactConditioning(
93
- token_z=token_z,
94
- cutoff_min=conditioning_cutoff_min,
95
- cutoff_max=conditioning_cutoff_max,
96
- )
97
- pairformer_args["v2"] = True
98
- self.pairformer_stack = PairformerModule(
99
- token_s,
100
- token_z,
101
- **pairformer_args,
102
- )
103
- self.return_latent_feats = return_latent_feats
104
-
105
- self.confidence_heads = ConfidenceHeads(
106
- token_s,
107
- token_z,
108
- token_level_confidence=token_level_confidence,
109
- **confidence_args,
110
- )
111
-
112
- def forward(
113
- self,
114
- s_inputs, # Float['b n ts']
115
- s, # Float['b n ts']
116
- z, # Float['b n n tz']
117
- x_pred, # Float['bm m 3']
118
- feats,
119
- pred_distogram_logits,
120
- multiplicity=1,
121
- run_sequentially=False,
122
- use_kernels: bool = False,
123
- ):
124
- if run_sequentially and multiplicity > 1:
125
- assert z.shape[0] == 1, "Not supported with batch size > 1"
126
- out_dicts = []
127
- for sample_idx in range(multiplicity):
128
- out_dicts.append( # noqa: PERF401
129
- self.forward(
130
- s_inputs,
131
- s,
132
- z,
133
- x_pred[sample_idx : sample_idx + 1],
134
- feats,
135
- pred_distogram_logits,
136
- multiplicity=1,
137
- run_sequentially=False,
138
- use_kernels=use_kernels,
139
- )
140
- )
141
-
142
- out_dict = {}
143
- for key in out_dicts[0]:
144
- if key != "pair_chains_iptm":
145
- out_dict[key] = torch.cat([out[key] for out in out_dicts], dim=0)
146
- else:
147
- pair_chains_iptm = {}
148
- for chain_idx1 in out_dicts[0][key]:
149
- chains_iptm = {}
150
- for chain_idx2 in out_dicts[0][key][chain_idx1]:
151
- chains_iptm[chain_idx2] = torch.cat(
152
- [out[key][chain_idx1][chain_idx2] for out in out_dicts],
153
- dim=0,
154
- )
155
- pair_chains_iptm[chain_idx1] = chains_iptm
156
- out_dict[key] = pair_chains_iptm
157
- return out_dict
158
-
159
- s_inputs = self.s_inputs_norm(s_inputs)
160
- if not self.no_update_s:
161
- s = self.s_norm(s)
162
-
163
- if self.add_s_input_to_s:
164
- s = s + self.s_input_to_s(s_inputs)
165
-
166
- z = self.z_norm(z)
167
-
168
- if self.add_z_input_to_z:
169
- relative_position_encoding = self.rel_pos(feats)
170
- z = z + relative_position_encoding
171
- z = z + self.token_bonds(feats["token_bonds"].float())
172
- if self.bond_type_feature:
173
- z = z + self.token_bonds_type(feats["type_bonds"].long())
174
- z = z + self.contact_conditioning(feats)
175
-
176
- s = s.repeat_interleave(multiplicity, 0)
177
-
178
- z = (
179
- z
180
- + self.s_to_z(s_inputs)[:, :, None, :]
181
- + self.s_to_z_transpose(s_inputs)[:, None, :, :]
182
- )
183
- if self.add_s_to_z_prod:
184
- z = z + self.s_to_z_prod_out(
185
- self.s_to_z_prod_in1(s_inputs)[:, :, None, :]
186
- * self.s_to_z_prod_in2(s_inputs)[:, None, :, :]
187
- )
188
-
189
- z = z.repeat_interleave(multiplicity, 0)
190
- s_inputs = s_inputs.repeat_interleave(multiplicity, 0)
191
-
192
- token_to_rep_atom = feats["token_to_rep_atom"]
193
- token_to_rep_atom = token_to_rep_atom.repeat_interleave(multiplicity, 0)
194
- if len(x_pred.shape) == 4:
195
- B, mult, N, _ = x_pred.shape
196
- x_pred = x_pred.reshape(B * mult, N, -1)
197
- else:
198
- BM, N, _ = x_pred.shape
199
- x_pred_repr = torch.bmm(token_to_rep_atom.float(), x_pred)
200
- d = torch.cdist(x_pred_repr, x_pred_repr)
201
- distogram = (d.unsqueeze(-1) > self.boundaries).sum(dim=-1).long()
202
- distogram = self.dist_bin_pairwise_embed(distogram)
203
- z = z + distogram
204
-
205
- mask = feats["token_pad_mask"].repeat_interleave(multiplicity, 0)
206
- pair_mask = mask[:, :, None] * mask[:, None, :]
207
-
208
- s_t, z_t = self.pairformer_stack(
209
- s, z, mask=mask, pair_mask=pair_mask, use_kernels=use_kernels
210
- )
211
-
212
- # AF3 has residual connections, we remove them
213
- s = s_t
214
- z = z_t
215
-
216
- out_dict = {}
217
-
218
- if self.return_latent_feats:
219
- out_dict["s_conf"] = s
220
- out_dict["z_conf"] = z
221
-
222
- # confidence heads
223
- out_dict.update(
224
- self.confidence_heads(
225
- s=s,
226
- z=z,
227
- x_pred=x_pred,
228
- d=d,
229
- feats=feats,
230
- multiplicity=multiplicity,
231
- pred_distogram_logits=pred_distogram_logits,
232
- )
233
- )
234
- return out_dict
235
-
236
-
237
- class ConfidenceHeads(nn.Module):
238
- def __init__(
239
- self,
240
- token_s,
241
- token_z,
242
- num_plddt_bins=50,
243
- num_pde_bins=64,
244
- num_pae_bins=64,
245
- token_level_confidence=True,
246
- use_separate_heads: bool = False,
247
- **kwargs,
248
- ):
249
- super().__init__()
250
- self.max_num_atoms_per_token = 23
251
- self.token_level_confidence = token_level_confidence
252
- self.use_separate_heads = use_separate_heads
253
-
254
- if self.use_separate_heads:
255
- self.to_pae_intra_logits = LinearNoBias(token_z, num_pae_bins)
256
- self.to_pae_inter_logits = LinearNoBias(token_z, num_pae_bins)
257
- else:
258
- self.to_pae_logits = LinearNoBias(token_z, num_pae_bins)
259
-
260
- if self.use_separate_heads:
261
- self.to_pde_intra_logits = LinearNoBias(token_z, num_pde_bins)
262
- self.to_pde_inter_logits = LinearNoBias(token_z, num_pde_bins)
263
- else:
264
- self.to_pde_logits = LinearNoBias(token_z, num_pde_bins)
265
-
266
- if self.token_level_confidence:
267
- self.to_plddt_logits = LinearNoBias(token_s, num_plddt_bins)
268
- self.to_resolved_logits = LinearNoBias(token_s, 2)
269
- else:
270
- self.to_plddt_logits = LinearNoBias(
271
- token_s, num_plddt_bins * self.max_num_atoms_per_token
272
- )
273
- self.to_resolved_logits = LinearNoBias(
274
- token_s, 2 * self.max_num_atoms_per_token
275
- )
276
-
277
- def forward(
278
- self,
279
- s, # Float['b n ts']
280
- z, # Float['b n n tz']
281
- x_pred, # Float['bm m 3']
282
- d,
283
- feats,
284
- pred_distogram_logits,
285
- multiplicity=1,
286
- ):
287
- if self.use_separate_heads:
288
- asym_id_token = feats["asym_id"]
289
- is_same_chain = asym_id_token.unsqueeze(-1) == asym_id_token.unsqueeze(-2)
290
- is_different_chain = ~is_same_chain
291
-
292
- if self.use_separate_heads:
293
- pae_intra_logits = self.to_pae_intra_logits(z)
294
- pae_intra_logits = pae_intra_logits * is_same_chain.float().unsqueeze(-1)
295
-
296
- pae_inter_logits = self.to_pae_inter_logits(z)
297
- pae_inter_logits = pae_inter_logits * is_different_chain.float().unsqueeze(
298
- -1
299
- )
300
-
301
- pae_logits = pae_inter_logits + pae_intra_logits
302
- else:
303
- pae_logits = self.to_pae_logits(z)
304
-
305
- if self.use_separate_heads:
306
- pde_intra_logits = self.to_pde_intra_logits(z + z.transpose(1, 2))
307
- pde_intra_logits = pde_intra_logits * is_same_chain.float().unsqueeze(-1)
308
-
309
- pde_inter_logits = self.to_pde_inter_logits(z + z.transpose(1, 2))
310
- pde_inter_logits = pde_inter_logits * is_different_chain.float().unsqueeze(
311
- -1
312
- )
313
-
314
- pde_logits = pde_inter_logits + pde_intra_logits
315
- else:
316
- pde_logits = self.to_pde_logits(z + z.transpose(1, 2))
317
- resolved_logits = self.to_resolved_logits(s)
318
- plddt_logits = self.to_plddt_logits(s)
319
-
320
- ligand_weight = 20
321
- non_interface_weight = 1
322
- interface_weight = 10
323
-
324
- token_type = feats["mol_type"]
325
- token_type = token_type.repeat_interleave(multiplicity, 0)
326
- is_ligand_token = (token_type == const.chain_type_ids["NONPOLYMER"]).float()
327
-
328
- if self.token_level_confidence:
329
- plddt = compute_aggregated_metric(plddt_logits)
330
- token_pad_mask = feats["token_pad_mask"].repeat_interleave(multiplicity, 0)
331
- complex_plddt = (plddt * token_pad_mask).sum(dim=-1) / token_pad_mask.sum(
332
- dim=-1
333
- )
334
-
335
- is_contact = (d < 8).float()
336
- is_different_chain = (
337
- feats["asym_id"].unsqueeze(-1) != feats["asym_id"].unsqueeze(-2)
338
- ).float()
339
- is_different_chain = is_different_chain.repeat_interleave(multiplicity, 0)
340
- token_interface_mask = torch.max(
341
- is_contact * is_different_chain * (1 - is_ligand_token).unsqueeze(-1),
342
- dim=-1,
343
- ).values
344
- token_non_interface_mask = (1 - token_interface_mask) * (
345
- 1 - is_ligand_token
346
- )
347
- iplddt_weight = (
348
- is_ligand_token * ligand_weight
349
- + token_interface_mask * interface_weight
350
- + token_non_interface_mask * non_interface_weight
351
- )
352
- complex_iplddt = (plddt * token_pad_mask * iplddt_weight).sum(
353
- dim=-1
354
- ) / torch.sum(token_pad_mask * iplddt_weight, dim=-1)
355
-
356
- else:
357
- # token to atom conversion for resolved logits
358
- B, N, _ = resolved_logits.shape
359
- resolved_logits = resolved_logits.reshape(
360
- B, N, self.max_num_atoms_per_token, 2
361
- )
362
-
363
- arange_max_num_atoms = (
364
- torch.arange(self.max_num_atoms_per_token)
365
- .reshape(1, 1, -1)
366
- .to(resolved_logits.device)
367
- )
368
- max_num_atoms_mask = (
369
- feats["atom_to_token"].sum(1).unsqueeze(-1) > arange_max_num_atoms
370
- )
371
- resolved_logits = resolved_logits[:, max_num_atoms_mask.squeeze(0)]
372
- resolved_logits = pad(
373
- resolved_logits,
374
- (
375
- 0,
376
- 0,
377
- 0,
378
- int(
379
- feats["atom_pad_mask"].shape[1]
380
- - feats["atom_pad_mask"].sum().item()
381
- ),
382
- ),
383
- value=0,
384
- )
385
- plddt_logits = plddt_logits.reshape(B, N, self.max_num_atoms_per_token, -1)
386
- plddt_logits = plddt_logits[:, max_num_atoms_mask.squeeze(0)]
387
- plddt_logits = pad(
388
- plddt_logits,
389
- (
390
- 0,
391
- 0,
392
- 0,
393
- int(
394
- feats["atom_pad_mask"].shape[1]
395
- - feats["atom_pad_mask"].sum().item()
396
- ),
397
- ),
398
- value=0,
399
- )
400
- atom_pad_mask = feats["atom_pad_mask"].repeat_interleave(multiplicity, 0)
401
- plddt = compute_aggregated_metric(plddt_logits)
402
-
403
- complex_plddt = (plddt * atom_pad_mask).sum(dim=-1) / atom_pad_mask.sum(
404
- dim=-1
405
- )
406
- token_type = feats["mol_type"].float()
407
- atom_to_token = feats["atom_to_token"].float()
408
- chain_id_token = feats["asym_id"].float()
409
- atom_type = torch.bmm(atom_to_token, token_type.unsqueeze(-1)).squeeze(-1)
410
- is_ligand_atom = (atom_type == const.chain_type_ids["NONPOLYMER"]).float()
411
- d_atom = torch.cdist(x_pred, x_pred)
412
- is_contact = (d_atom < 8).float()
413
- chain_id_atom = torch.bmm(
414
- atom_to_token, chain_id_token.unsqueeze(-1)
415
- ).squeeze(-1)
416
- is_different_chain = (
417
- chain_id_atom.unsqueeze(-1) != chain_id_atom.unsqueeze(-2)
418
- ).float()
419
-
420
- atom_interface_mask = torch.max(
421
- is_contact * is_different_chain * (1 - is_ligand_atom).unsqueeze(-1),
422
- dim=-1,
423
- ).values
424
- atom_non_interface_mask = (1 - atom_interface_mask) * (1 - is_ligand_atom)
425
- iplddt_weight = (
426
- is_ligand_atom * ligand_weight
427
- + atom_interface_mask * interface_weight
428
- + atom_non_interface_mask * non_interface_weight
429
- )
430
-
431
- complex_iplddt = (plddt * feats["atom_pad_mask"] * iplddt_weight).sum(
432
- dim=-1
433
- ) / torch.sum(feats["atom_pad_mask"] * iplddt_weight, dim=-1)
434
-
435
- # Compute the gPDE and giPDE
436
- pde = compute_aggregated_metric(pde_logits, end=32)
437
- pred_distogram_prob = nn.functional.softmax(
438
- pred_distogram_logits, dim=-1
439
- ).repeat_interleave(multiplicity, 0)
440
- contacts = torch.zeros((1, 1, 1, 64), dtype=pred_distogram_prob.dtype).to(
441
- pred_distogram_prob.device
442
- )
443
- contacts[:, :, :, :20] = 1.0
444
- prob_contact = (pred_distogram_prob * contacts).sum(-1)
445
- token_pad_mask = feats["token_pad_mask"].repeat_interleave(multiplicity, 0)
446
- token_pad_pair_mask = (
447
- token_pad_mask.unsqueeze(-1)
448
- * token_pad_mask.unsqueeze(-2)
449
- * (
450
- 1
451
- - torch.eye(
452
- token_pad_mask.shape[1], device=token_pad_mask.device
453
- ).unsqueeze(0)
454
- )
455
- )
456
- token_pair_mask = token_pad_pair_mask * prob_contact
457
- complex_pde = (pde * token_pair_mask).sum(dim=(1, 2)) / token_pair_mask.sum(
458
- dim=(1, 2)
459
- )
460
- asym_id = feats["asym_id"].repeat_interleave(multiplicity, 0)
461
- token_interface_pair_mask = token_pair_mask * (
462
- asym_id.unsqueeze(-1) != asym_id.unsqueeze(-2)
463
- )
464
- complex_ipde = (pde * token_interface_pair_mask).sum(dim=(1, 2)) / (
465
- token_interface_pair_mask.sum(dim=(1, 2)) + 1e-5
466
- )
467
- out_dict = dict(
468
- pde_logits=pde_logits,
469
- plddt_logits=plddt_logits,
470
- resolved_logits=resolved_logits,
471
- pde=pde,
472
- plddt=plddt,
473
- complex_plddt=complex_plddt,
474
- complex_iplddt=complex_iplddt,
475
- complex_pde=complex_pde,
476
- complex_ipde=complex_ipde,
477
- )
478
- out_dict["pae_logits"] = pae_logits
479
- out_dict["pae"] = compute_aggregated_metric(pae_logits, end=32)
480
-
481
- try:
482
- ptm, iptm, ligand_iptm, protein_iptm, pair_chains_iptm = compute_ptms(
483
- pae_logits, x_pred, feats, multiplicity
484
- )
485
- out_dict["ptm"] = ptm
486
- out_dict["iptm"] = iptm
487
- out_dict["ligand_iptm"] = ligand_iptm
488
- out_dict["protein_iptm"] = protein_iptm
489
- out_dict["pair_chains_iptm"] = pair_chains_iptm
490
- except Exception as e:
491
- print(f"Error in compute_ptms: {e}")
492
- out_dict["ptm"] = torch.zeros_like(complex_plddt)
493
- out_dict["iptm"] = torch.zeros_like(complex_plddt)
494
- out_dict["ligand_iptm"] = torch.zeros_like(complex_plddt)
495
- out_dict["protein_iptm"] = torch.zeros_like(complex_plddt)
496
- out_dict["pair_chains_iptm"] = torch.zeros_like(complex_plddt)
497
-
498
- return out_dict
 
1
+ import torch
2
+ from torch import nn
3
+ from torch.nn.functional import pad
4
+
5
+ from . import vb_const as const
6
+ from . import vb_layers_initialize as init
7
+ from .vb_layers_confidence_utils import (
8
+ compute_aggregated_metric,
9
+ compute_ptms,
10
+ )
11
+ from .vb_layers_pairformer import PairformerModule
12
+ from .vb_modules_encodersv2 import RelativePositionEncoder
13
+ from .vb_modules_trunkv2 import (
14
+ ContactConditioning,
15
+ )
16
+ from .vb_modules_utils import LinearNoBias
17
+
18
+
19
+ class ConfidenceModule(nn.Module):
20
+ """Algorithm 31"""
21
+
22
+ def __init__(
23
+ self,
24
+ token_s,
25
+ token_z,
26
+ pairformer_args: dict,
27
+ num_dist_bins=64,
28
+ token_level_confidence=True,
29
+ max_dist=22,
30
+ add_s_to_z_prod=False,
31
+ add_s_input_to_s=False,
32
+ add_z_input_to_z=False,
33
+ maximum_bond_distance=0,
34
+ bond_type_feature=False,
35
+ confidence_args: dict = None,
36
+ compile_pairformer=False,
37
+ fix_sym_check=False,
38
+ cyclic_pos_enc=False,
39
+ return_latent_feats=False,
40
+ conditioning_cutoff_min=None,
41
+ conditioning_cutoff_max=None,
42
+ **kwargs,
43
+ ):
44
+ super().__init__()
45
+ self.max_num_atoms_per_token = 23
46
+ if "no_update_s" in pairformer_args:
47
+ self.no_update_s = pairformer_args["no_update_s"]
48
+ else:
49
+ self.no_update_s = False
50
+ boundaries = torch.linspace(2, max_dist, num_dist_bins - 1)
51
+ self.register_buffer("boundaries", boundaries)
52
+ self.dist_bin_pairwise_embed = nn.Embedding(num_dist_bins, token_z)
53
+ init.gating_init_(self.dist_bin_pairwise_embed.weight)
54
+ self.token_level_confidence = token_level_confidence
55
+
56
+ self.s_to_z = LinearNoBias(token_s, token_z)
57
+ self.s_to_z_transpose = LinearNoBias(token_s, token_z)
58
+ init.gating_init_(self.s_to_z.weight)
59
+ init.gating_init_(self.s_to_z_transpose.weight)
60
+
61
+ self.add_s_to_z_prod = add_s_to_z_prod
62
+ if add_s_to_z_prod:
63
+ self.s_to_z_prod_in1 = LinearNoBias(token_s, token_z)
64
+ self.s_to_z_prod_in2 = LinearNoBias(token_s, token_z)
65
+ self.s_to_z_prod_out = LinearNoBias(token_z, token_z)
66
+ init.gating_init_(self.s_to_z_prod_out.weight)
67
+
68
+ self.s_inputs_norm = nn.LayerNorm(token_s)
69
+ if not self.no_update_s:
70
+ self.s_norm = nn.LayerNorm(token_s)
71
+ self.z_norm = nn.LayerNorm(token_z)
72
+
73
+ self.add_s_input_to_s = add_s_input_to_s
74
+ if add_s_input_to_s:
75
+ self.s_input_to_s = LinearNoBias(token_s, token_s)
76
+ init.gating_init_(self.s_input_to_s.weight)
77
+
78
+ self.add_z_input_to_z = add_z_input_to_z
79
+ if add_z_input_to_z:
80
+ self.rel_pos = RelativePositionEncoder(
81
+ token_z, fix_sym_check=fix_sym_check, cyclic_pos_enc=cyclic_pos_enc
82
+ )
83
+ self.token_bonds = nn.Linear(
84
+ 1 if maximum_bond_distance == 0 else maximum_bond_distance + 2,
85
+ token_z,
86
+ bias=False,
87
+ )
88
+ self.bond_type_feature = bond_type_feature
89
+ if bond_type_feature:
90
+ self.token_bonds_type = nn.Embedding(len(const.bond_types) + 1, token_z)
91
+
92
+ self.contact_conditioning = ContactConditioning(
93
+ token_z=token_z,
94
+ cutoff_min=conditioning_cutoff_min,
95
+ cutoff_max=conditioning_cutoff_max,
96
+ )
97
+ pairformer_args["v2"] = True
98
+ self.pairformer_stack = PairformerModule(
99
+ token_s,
100
+ token_z,
101
+ **pairformer_args,
102
+ )
103
+ self.return_latent_feats = return_latent_feats
104
+
105
+ self.confidence_heads = ConfidenceHeads(
106
+ token_s,
107
+ token_z,
108
+ token_level_confidence=token_level_confidence,
109
+ **confidence_args,
110
+ )
111
+
112
+ def forward(
113
+ self,
114
+ s_inputs, # Float['b n ts']
115
+ s, # Float['b n ts']
116
+ z, # Float['b n n tz']
117
+ x_pred, # Float['bm m 3']
118
+ feats,
119
+ pred_distogram_logits,
120
+ multiplicity=1,
121
+ run_sequentially=False,
122
+ use_kernels: bool = False,
123
+ ):
124
+ if run_sequentially and multiplicity > 1:
125
+ assert z.shape[0] == 1, "Not supported with batch size > 1"
126
+ out_dicts = []
127
+ for sample_idx in range(multiplicity):
128
+ out_dicts.append( # noqa: PERF401
129
+ self.forward(
130
+ s_inputs,
131
+ s,
132
+ z,
133
+ x_pred[sample_idx : sample_idx + 1],
134
+ feats,
135
+ pred_distogram_logits,
136
+ multiplicity=1,
137
+ run_sequentially=False,
138
+ use_kernels=use_kernels,
139
+ )
140
+ )
141
+
142
+ out_dict = {}
143
+ for key in out_dicts[0]:
144
+ if key != "pair_chains_iptm":
145
+ out_dict[key] = torch.cat([out[key] for out in out_dicts], dim=0)
146
+ else:
147
+ pair_chains_iptm = {}
148
+ for chain_idx1 in out_dicts[0][key]:
149
+ chains_iptm = {}
150
+ for chain_idx2 in out_dicts[0][key][chain_idx1]:
151
+ chains_iptm[chain_idx2] = torch.cat(
152
+ [out[key][chain_idx1][chain_idx2] for out in out_dicts],
153
+ dim=0,
154
+ )
155
+ pair_chains_iptm[chain_idx1] = chains_iptm
156
+ out_dict[key] = pair_chains_iptm
157
+ return out_dict
158
+
159
+ s_inputs = self.s_inputs_norm(s_inputs)
160
+ if not self.no_update_s:
161
+ s = self.s_norm(s)
162
+
163
+ if self.add_s_input_to_s:
164
+ s = s + self.s_input_to_s(s_inputs)
165
+
166
+ z = self.z_norm(z)
167
+
168
+ if self.add_z_input_to_z:
169
+ relative_position_encoding = self.rel_pos(feats)
170
+ z = z + relative_position_encoding
171
+ z = z + self.token_bonds(feats["token_bonds"].float())
172
+ if self.bond_type_feature:
173
+ z = z + self.token_bonds_type(feats["type_bonds"].long())
174
+ z = z + self.contact_conditioning(feats)
175
+
176
+ s = s.repeat_interleave(multiplicity, 0)
177
+
178
+ z = (
179
+ z
180
+ + self.s_to_z(s_inputs)[:, :, None, :]
181
+ + self.s_to_z_transpose(s_inputs)[:, None, :, :]
182
+ )
183
+ if self.add_s_to_z_prod:
184
+ z = z + self.s_to_z_prod_out(
185
+ self.s_to_z_prod_in1(s_inputs)[:, :, None, :]
186
+ * self.s_to_z_prod_in2(s_inputs)[:, None, :, :]
187
+ )
188
+
189
+ z = z.repeat_interleave(multiplicity, 0)
190
+ s_inputs = s_inputs.repeat_interleave(multiplicity, 0)
191
+
192
+ token_to_rep_atom = feats["token_to_rep_atom"]
193
+ token_to_rep_atom = token_to_rep_atom.repeat_interleave(multiplicity, 0)
194
+ if len(x_pred.shape) == 4:
195
+ B, mult, N, _ = x_pred.shape
196
+ x_pred = x_pred.reshape(B * mult, N, -1)
197
+ else:
198
+ BM, N, _ = x_pred.shape
199
+ x_pred_repr = torch.bmm(token_to_rep_atom.float(), x_pred)
200
+ d = torch.cdist(x_pred_repr, x_pred_repr)
201
+ distogram = (d.unsqueeze(-1) > self.boundaries).sum(dim=-1).long()
202
+ distogram = self.dist_bin_pairwise_embed(distogram)
203
+ z = z + distogram
204
+
205
+ mask = feats["token_pad_mask"].repeat_interleave(multiplicity, 0)
206
+ pair_mask = mask[:, :, None] * mask[:, None, :]
207
+
208
+ s_t, z_t = self.pairformer_stack(
209
+ s, z, mask=mask, pair_mask=pair_mask, use_kernels=use_kernels
210
+ )
211
+
212
+ # AF3 has residual connections, we remove them
213
+ s = s_t
214
+ z = z_t
215
+
216
+ out_dict = {}
217
+
218
+ if self.return_latent_feats:
219
+ out_dict["s_conf"] = s
220
+ out_dict["z_conf"] = z
221
+
222
+ # confidence heads
223
+ out_dict.update(
224
+ self.confidence_heads(
225
+ s=s,
226
+ z=z,
227
+ x_pred=x_pred,
228
+ d=d,
229
+ feats=feats,
230
+ multiplicity=multiplicity,
231
+ pred_distogram_logits=pred_distogram_logits,
232
+ )
233
+ )
234
+ return out_dict
235
+
236
+
237
+ class ConfidenceHeads(nn.Module):
238
+ def __init__(
239
+ self,
240
+ token_s,
241
+ token_z,
242
+ num_plddt_bins=50,
243
+ num_pde_bins=64,
244
+ num_pae_bins=64,
245
+ token_level_confidence=True,
246
+ use_separate_heads: bool = False,
247
+ **kwargs,
248
+ ):
249
+ super().__init__()
250
+ self.max_num_atoms_per_token = 23
251
+ self.token_level_confidence = token_level_confidence
252
+ self.use_separate_heads = use_separate_heads
253
+
254
+ if self.use_separate_heads:
255
+ self.to_pae_intra_logits = LinearNoBias(token_z, num_pae_bins)
256
+ self.to_pae_inter_logits = LinearNoBias(token_z, num_pae_bins)
257
+ else:
258
+ self.to_pae_logits = LinearNoBias(token_z, num_pae_bins)
259
+
260
+ if self.use_separate_heads:
261
+ self.to_pde_intra_logits = LinearNoBias(token_z, num_pde_bins)
262
+ self.to_pde_inter_logits = LinearNoBias(token_z, num_pde_bins)
263
+ else:
264
+ self.to_pde_logits = LinearNoBias(token_z, num_pde_bins)
265
+
266
+ if self.token_level_confidence:
267
+ self.to_plddt_logits = LinearNoBias(token_s, num_plddt_bins)
268
+ self.to_resolved_logits = LinearNoBias(token_s, 2)
269
+ else:
270
+ self.to_plddt_logits = LinearNoBias(
271
+ token_s, num_plddt_bins * self.max_num_atoms_per_token
272
+ )
273
+ self.to_resolved_logits = LinearNoBias(
274
+ token_s, 2 * self.max_num_atoms_per_token
275
+ )
276
+
277
+ def forward(
278
+ self,
279
+ s, # Float['b n ts']
280
+ z, # Float['b n n tz']
281
+ x_pred, # Float['bm m 3']
282
+ d,
283
+ feats,
284
+ pred_distogram_logits,
285
+ multiplicity=1,
286
+ ):
287
+ if self.use_separate_heads:
288
+ asym_id_token = feats["asym_id"]
289
+ is_same_chain = asym_id_token.unsqueeze(-1) == asym_id_token.unsqueeze(-2)
290
+ is_different_chain = ~is_same_chain
291
+
292
+ if self.use_separate_heads:
293
+ pae_intra_logits = self.to_pae_intra_logits(z)
294
+ pae_intra_logits = pae_intra_logits * is_same_chain.float().unsqueeze(-1)
295
+
296
+ pae_inter_logits = self.to_pae_inter_logits(z)
297
+ pae_inter_logits = pae_inter_logits * is_different_chain.float().unsqueeze(
298
+ -1
299
+ )
300
+
301
+ pae_logits = pae_inter_logits + pae_intra_logits
302
+ else:
303
+ pae_logits = self.to_pae_logits(z)
304
+
305
+ if self.use_separate_heads:
306
+ pde_intra_logits = self.to_pde_intra_logits(z + z.transpose(1, 2))
307
+ pde_intra_logits = pde_intra_logits * is_same_chain.float().unsqueeze(-1)
308
+
309
+ pde_inter_logits = self.to_pde_inter_logits(z + z.transpose(1, 2))
310
+ pde_inter_logits = pde_inter_logits * is_different_chain.float().unsqueeze(
311
+ -1
312
+ )
313
+
314
+ pde_logits = pde_inter_logits + pde_intra_logits
315
+ else:
316
+ pde_logits = self.to_pde_logits(z + z.transpose(1, 2))
317
+ resolved_logits = self.to_resolved_logits(s)
318
+ plddt_logits = self.to_plddt_logits(s)
319
+
320
+ ligand_weight = 20
321
+ non_interface_weight = 1
322
+ interface_weight = 10
323
+
324
+ token_type = feats["mol_type"]
325
+ token_type = token_type.repeat_interleave(multiplicity, 0)
326
+ is_ligand_token = (token_type == const.chain_type_ids["NONPOLYMER"]).float()
327
+
328
+ if self.token_level_confidence:
329
+ plddt = compute_aggregated_metric(plddt_logits)
330
+ token_pad_mask = feats["token_pad_mask"].repeat_interleave(multiplicity, 0)
331
+ complex_plddt = (plddt * token_pad_mask).sum(dim=-1) / token_pad_mask.sum(
332
+ dim=-1
333
+ )
334
+
335
+ is_contact = (d < 8).float()
336
+ is_different_chain = (
337
+ feats["asym_id"].unsqueeze(-1) != feats["asym_id"].unsqueeze(-2)
338
+ ).float()
339
+ is_different_chain = is_different_chain.repeat_interleave(multiplicity, 0)
340
+ token_interface_mask = torch.max(
341
+ is_contact * is_different_chain * (1 - is_ligand_token).unsqueeze(-1),
342
+ dim=-1,
343
+ ).values
344
+ token_non_interface_mask = (1 - token_interface_mask) * (
345
+ 1 - is_ligand_token
346
+ )
347
+ iplddt_weight = (
348
+ is_ligand_token * ligand_weight
349
+ + token_interface_mask * interface_weight
350
+ + token_non_interface_mask * non_interface_weight
351
+ )
352
+ complex_iplddt = (plddt * token_pad_mask * iplddt_weight).sum(
353
+ dim=-1
354
+ ) / torch.sum(token_pad_mask * iplddt_weight, dim=-1)
355
+
356
+ else:
357
+ # token to atom conversion for resolved logits
358
+ B, N, _ = resolved_logits.shape
359
+ resolved_logits = resolved_logits.reshape(
360
+ B, N, self.max_num_atoms_per_token, 2
361
+ )
362
+
363
+ arange_max_num_atoms = (
364
+ torch.arange(self.max_num_atoms_per_token)
365
+ .reshape(1, 1, -1)
366
+ .to(resolved_logits.device)
367
+ )
368
+ max_num_atoms_mask = (
369
+ feats["atom_to_token"].sum(1).unsqueeze(-1) > arange_max_num_atoms
370
+ )
371
+ resolved_logits = resolved_logits[:, max_num_atoms_mask.squeeze(0)]
372
+ resolved_logits = pad(
373
+ resolved_logits,
374
+ (
375
+ 0,
376
+ 0,
377
+ 0,
378
+ int(
379
+ feats["atom_pad_mask"].shape[1]
380
+ - feats["atom_pad_mask"].sum().item()
381
+ ),
382
+ ),
383
+ value=0,
384
+ )
385
+ plddt_logits = plddt_logits.reshape(B, N, self.max_num_atoms_per_token, -1)
386
+ plddt_logits = plddt_logits[:, max_num_atoms_mask.squeeze(0)]
387
+ plddt_logits = pad(
388
+ plddt_logits,
389
+ (
390
+ 0,
391
+ 0,
392
+ 0,
393
+ int(
394
+ feats["atom_pad_mask"].shape[1]
395
+ - feats["atom_pad_mask"].sum().item()
396
+ ),
397
+ ),
398
+ value=0,
399
+ )
400
+ atom_pad_mask = feats["atom_pad_mask"].repeat_interleave(multiplicity, 0)
401
+ plddt = compute_aggregated_metric(plddt_logits)
402
+
403
+ complex_plddt = (plddt * atom_pad_mask).sum(dim=-1) / atom_pad_mask.sum(
404
+ dim=-1
405
+ )
406
+ token_type = feats["mol_type"].float()
407
+ atom_to_token = feats["atom_to_token"].float()
408
+ chain_id_token = feats["asym_id"].float()
409
+ atom_type = torch.bmm(atom_to_token, token_type.unsqueeze(-1)).squeeze(-1)
410
+ is_ligand_atom = (atom_type == const.chain_type_ids["NONPOLYMER"]).float()
411
+ d_atom = torch.cdist(x_pred, x_pred)
412
+ is_contact = (d_atom < 8).float()
413
+ chain_id_atom = torch.bmm(
414
+ atom_to_token, chain_id_token.unsqueeze(-1)
415
+ ).squeeze(-1)
416
+ is_different_chain = (
417
+ chain_id_atom.unsqueeze(-1) != chain_id_atom.unsqueeze(-2)
418
+ ).float()
419
+
420
+ atom_interface_mask = torch.max(
421
+ is_contact * is_different_chain * (1 - is_ligand_atom).unsqueeze(-1),
422
+ dim=-1,
423
+ ).values
424
+ atom_non_interface_mask = (1 - atom_interface_mask) * (1 - is_ligand_atom)
425
+ iplddt_weight = (
426
+ is_ligand_atom * ligand_weight
427
+ + atom_interface_mask * interface_weight
428
+ + atom_non_interface_mask * non_interface_weight
429
+ )
430
+
431
+ complex_iplddt = (plddt * feats["atom_pad_mask"] * iplddt_weight).sum(
432
+ dim=-1
433
+ ) / torch.sum(feats["atom_pad_mask"] * iplddt_weight, dim=-1)
434
+
435
+ # Compute the gPDE and giPDE
436
+ pde = compute_aggregated_metric(pde_logits, end=32)
437
+ pred_distogram_prob = nn.functional.softmax(
438
+ pred_distogram_logits, dim=-1
439
+ ).repeat_interleave(multiplicity, 0)
440
+ contacts = torch.zeros((1, 1, 1, 64), dtype=pred_distogram_prob.dtype).to(
441
+ pred_distogram_prob.device
442
+ )
443
+ contacts[:, :, :, :20] = 1.0
444
+ prob_contact = (pred_distogram_prob * contacts).sum(-1)
445
+ token_pad_mask = feats["token_pad_mask"].repeat_interleave(multiplicity, 0)
446
+ token_pad_pair_mask = (
447
+ token_pad_mask.unsqueeze(-1)
448
+ * token_pad_mask.unsqueeze(-2)
449
+ * (
450
+ 1
451
+ - torch.eye(
452
+ token_pad_mask.shape[1], device=token_pad_mask.device
453
+ ).unsqueeze(0)
454
+ )
455
+ )
456
+ token_pair_mask = token_pad_pair_mask * prob_contact
457
+ complex_pde = (pde * token_pair_mask).sum(dim=(1, 2)) / token_pair_mask.sum(
458
+ dim=(1, 2)
459
+ )
460
+ asym_id = feats["asym_id"].repeat_interleave(multiplicity, 0)
461
+ token_interface_pair_mask = token_pair_mask * (
462
+ asym_id.unsqueeze(-1) != asym_id.unsqueeze(-2)
463
+ )
464
+ complex_ipde = (pde * token_interface_pair_mask).sum(dim=(1, 2)) / (
465
+ token_interface_pair_mask.sum(dim=(1, 2)) + 1e-5
466
+ )
467
+ out_dict = dict(
468
+ pde_logits=pde_logits,
469
+ plddt_logits=plddt_logits,
470
+ resolved_logits=resolved_logits,
471
+ pde=pde,
472
+ plddt=plddt,
473
+ complex_plddt=complex_plddt,
474
+ complex_iplddt=complex_iplddt,
475
+ complex_pde=complex_pde,
476
+ complex_ipde=complex_ipde,
477
+ )
478
+ out_dict["pae_logits"] = pae_logits
479
+ out_dict["pae"] = compute_aggregated_metric(pae_logits, end=32)
480
+
481
+ try:
482
+ ptm, iptm, ligand_iptm, protein_iptm, pair_chains_iptm = compute_ptms(
483
+ pae_logits, x_pred, feats, multiplicity
484
+ )
485
+ out_dict["ptm"] = ptm
486
+ out_dict["iptm"] = iptm
487
+ out_dict["ligand_iptm"] = ligand_iptm
488
+ out_dict["protein_iptm"] = protein_iptm
489
+ out_dict["pair_chains_iptm"] = pair_chains_iptm
490
+ except Exception as e:
491
+ print(f"Error in compute_ptms: {e}")
492
+ out_dict["ptm"] = torch.zeros_like(complex_plddt)
493
+ out_dict["iptm"] = torch.zeros_like(complex_plddt)
494
+ out_dict["ligand_iptm"] = torch.zeros_like(complex_plddt)
495
+ out_dict["protein_iptm"] = torch.zeros_like(complex_plddt)
496
+ out_dict["pair_chains_iptm"] = torch.zeros_like(complex_plddt)
497
+
498
+ return out_dict
vb_modules_diffusion_conditioning.py CHANGED
@@ -1,116 +1,116 @@
1
- from __future__ import annotations
2
-
3
- import torch
4
- from torch import nn
5
- from torch.nn import Module
6
-
7
- from .vb_modules_encodersv2 import (
8
- AtomEncoder,
9
- PairwiseConditioning,
10
- )
11
-
12
-
13
- class DiffusionConditioning(Module):
14
- def __init__(
15
- self,
16
- token_s: int,
17
- token_z: int,
18
- atom_s: int,
19
- atom_z: int,
20
- atoms_per_window_queries: int = 32,
21
- atoms_per_window_keys: int = 128,
22
- atom_encoder_depth: int = 3,
23
- atom_encoder_heads: int = 4,
24
- token_transformer_depth: int = 24,
25
- token_transformer_heads: int = 8,
26
- atom_decoder_depth: int = 3,
27
- atom_decoder_heads: int = 4,
28
- atom_feature_dim: int = 128,
29
- conditioning_transition_layers: int = 2,
30
- use_no_atom_char: bool = False,
31
- use_atom_backbone_feat: bool = False,
32
- use_residue_feats_atoms: bool = False,
33
- ) -> None:
34
- super().__init__()
35
-
36
- self.pairwise_conditioner = PairwiseConditioning(
37
- token_z=token_z,
38
- dim_token_rel_pos_feats=token_z,
39
- num_transitions=conditioning_transition_layers,
40
- )
41
-
42
- self.atom_encoder = AtomEncoder(
43
- atom_s=atom_s,
44
- atom_z=atom_z,
45
- token_s=token_s,
46
- token_z=token_z,
47
- atoms_per_window_queries=atoms_per_window_queries,
48
- atoms_per_window_keys=atoms_per_window_keys,
49
- atom_feature_dim=atom_feature_dim,
50
- structure_prediction=True,
51
- use_no_atom_char=use_no_atom_char,
52
- use_atom_backbone_feat=use_atom_backbone_feat,
53
- use_residue_feats_atoms=use_residue_feats_atoms,
54
- )
55
-
56
- self.atom_enc_proj_z = nn.ModuleList()
57
- for _ in range(atom_encoder_depth):
58
- self.atom_enc_proj_z.append(
59
- nn.Sequential(
60
- nn.LayerNorm(atom_z),
61
- nn.Linear(atom_z, atom_encoder_heads, bias=False),
62
- )
63
- )
64
-
65
- self.atom_dec_proj_z = nn.ModuleList()
66
- for _ in range(atom_decoder_depth):
67
- self.atom_dec_proj_z.append(
68
- nn.Sequential(
69
- nn.LayerNorm(atom_z),
70
- nn.Linear(atom_z, atom_decoder_heads, bias=False),
71
- )
72
- )
73
-
74
- self.token_trans_proj_z = nn.ModuleList()
75
- for _ in range(token_transformer_depth):
76
- self.token_trans_proj_z.append(
77
- nn.Sequential(
78
- nn.LayerNorm(token_z),
79
- nn.Linear(token_z, token_transformer_heads, bias=False),
80
- )
81
- )
82
-
83
- def forward(
84
- self,
85
- s_trunk, # Float['b n ts']
86
- z_trunk, # Float['b n n tz']
87
- relative_position_encoding, # Float['b n n tz']
88
- feats,
89
- ):
90
- z = self.pairwise_conditioner(
91
- z_trunk,
92
- relative_position_encoding,
93
- )
94
-
95
- q, c, p, to_keys = self.atom_encoder(
96
- feats=feats,
97
- s_trunk=s_trunk, # Float['b n ts'],
98
- z=z, # Float['b n n tz'],
99
- )
100
-
101
- atom_enc_bias = []
102
- for layer in self.atom_enc_proj_z:
103
- atom_enc_bias.append(layer(p))
104
- atom_enc_bias = torch.cat(atom_enc_bias, dim=-1)
105
-
106
- atom_dec_bias = []
107
- for layer in self.atom_dec_proj_z:
108
- atom_dec_bias.append(layer(p))
109
- atom_dec_bias = torch.cat(atom_dec_bias, dim=-1)
110
-
111
- token_trans_bias = []
112
- for layer in self.token_trans_proj_z:
113
- token_trans_bias.append(layer(z))
114
- token_trans_bias = torch.cat(token_trans_bias, dim=-1)
115
-
116
- return q, c, to_keys, atom_enc_bias, atom_dec_bias, token_trans_bias
 
1
+ from __future__ import annotations
2
+
3
+ import torch
4
+ from torch import nn
5
+ from torch.nn import Module
6
+
7
+ from .vb_modules_encodersv2 import (
8
+ AtomEncoder,
9
+ PairwiseConditioning,
10
+ )
11
+
12
+
13
+ class DiffusionConditioning(Module):
14
+ def __init__(
15
+ self,
16
+ token_s: int,
17
+ token_z: int,
18
+ atom_s: int,
19
+ atom_z: int,
20
+ atoms_per_window_queries: int = 32,
21
+ atoms_per_window_keys: int = 128,
22
+ atom_encoder_depth: int = 3,
23
+ atom_encoder_heads: int = 4,
24
+ token_transformer_depth: int = 24,
25
+ token_transformer_heads: int = 8,
26
+ atom_decoder_depth: int = 3,
27
+ atom_decoder_heads: int = 4,
28
+ atom_feature_dim: int = 128,
29
+ conditioning_transition_layers: int = 2,
30
+ use_no_atom_char: bool = False,
31
+ use_atom_backbone_feat: bool = False,
32
+ use_residue_feats_atoms: bool = False,
33
+ ) -> None:
34
+ super().__init__()
35
+
36
+ self.pairwise_conditioner = PairwiseConditioning(
37
+ token_z=token_z,
38
+ dim_token_rel_pos_feats=token_z,
39
+ num_transitions=conditioning_transition_layers,
40
+ )
41
+
42
+ self.atom_encoder = AtomEncoder(
43
+ atom_s=atom_s,
44
+ atom_z=atom_z,
45
+ token_s=token_s,
46
+ token_z=token_z,
47
+ atoms_per_window_queries=atoms_per_window_queries,
48
+ atoms_per_window_keys=atoms_per_window_keys,
49
+ atom_feature_dim=atom_feature_dim,
50
+ structure_prediction=True,
51
+ use_no_atom_char=use_no_atom_char,
52
+ use_atom_backbone_feat=use_atom_backbone_feat,
53
+ use_residue_feats_atoms=use_residue_feats_atoms,
54
+ )
55
+
56
+ self.atom_enc_proj_z = nn.ModuleList()
57
+ for _ in range(atom_encoder_depth):
58
+ self.atom_enc_proj_z.append(
59
+ nn.Sequential(
60
+ nn.LayerNorm(atom_z),
61
+ nn.Linear(atom_z, atom_encoder_heads, bias=False),
62
+ )
63
+ )
64
+
65
+ self.atom_dec_proj_z = nn.ModuleList()
66
+ for _ in range(atom_decoder_depth):
67
+ self.atom_dec_proj_z.append(
68
+ nn.Sequential(
69
+ nn.LayerNorm(atom_z),
70
+ nn.Linear(atom_z, atom_decoder_heads, bias=False),
71
+ )
72
+ )
73
+
74
+ self.token_trans_proj_z = nn.ModuleList()
75
+ for _ in range(token_transformer_depth):
76
+ self.token_trans_proj_z.append(
77
+ nn.Sequential(
78
+ nn.LayerNorm(token_z),
79
+ nn.Linear(token_z, token_transformer_heads, bias=False),
80
+ )
81
+ )
82
+
83
+ def forward(
84
+ self,
85
+ s_trunk, # Float['b n ts']
86
+ z_trunk, # Float['b n n tz']
87
+ relative_position_encoding, # Float['b n n tz']
88
+ feats,
89
+ ):
90
+ z = self.pairwise_conditioner(
91
+ z_trunk,
92
+ relative_position_encoding,
93
+ )
94
+
95
+ q, c, p, to_keys = self.atom_encoder(
96
+ feats=feats,
97
+ s_trunk=s_trunk, # Float['b n ts'],
98
+ z=z, # Float['b n n tz'],
99
+ )
100
+
101
+ atom_enc_bias = []
102
+ for layer in self.atom_enc_proj_z:
103
+ atom_enc_bias.append(layer(p))
104
+ atom_enc_bias = torch.cat(atom_enc_bias, dim=-1)
105
+
106
+ atom_dec_bias = []
107
+ for layer in self.atom_dec_proj_z:
108
+ atom_dec_bias.append(layer(p))
109
+ atom_dec_bias = torch.cat(atom_dec_bias, dim=-1)
110
+
111
+ token_trans_bias = []
112
+ for layer in self.token_trans_proj_z:
113
+ token_trans_bias.append(layer(z))
114
+ token_trans_bias = torch.cat(token_trans_bias, dim=-1)
115
+
116
+ return q, c, to_keys, atom_enc_bias, atom_dec_bias, token_trans_bias
vb_modules_diffusionv2.py CHANGED
@@ -1,693 +1,693 @@
1
- # started from code from https://github.com/lucidrains/alphafold3-pytorch, MIT License, Copyright (c) 2024 Phil Wang
2
-
3
- from __future__ import annotations
4
-
5
- from math import sqrt
6
-
7
- import numpy as np
8
- import torch
9
- import torch.nn.functional as F # noqa: N812
10
- from einops import rearrange
11
- from torch import nn
12
- from torch.nn import Module
13
-
14
- from . import vb_const as const
15
- from . import vb_layers_initialize as init
16
- from .vb_loss_diffusionv2 import (
17
- smooth_lddt_loss,
18
- weighted_rigid_align,
19
- )
20
- from .vb_modules_encodersv2 import (
21
- AtomAttentionDecoder,
22
- AtomAttentionEncoder,
23
- SingleConditioning,
24
- )
25
- from .vb_modules_transformersv2 import (
26
- DiffusionTransformer,
27
- )
28
- from .vb_modules_utils import (
29
- LinearNoBias,
30
- center_random_augmentation,
31
- compute_random_augmentation,
32
- default,
33
- log,
34
- )
35
- from .vb_potentials_potentials import get_potentials
36
-
37
-
38
- class DiffusionModule(Module):
39
- """Diffusion module"""
40
-
41
- def __init__(
42
- self,
43
- token_s: int,
44
- atom_s: int,
45
- atoms_per_window_queries: int = 32,
46
- atoms_per_window_keys: int = 128,
47
- sigma_data: int = 16,
48
- dim_fourier: int = 256,
49
- atom_encoder_depth: int = 3,
50
- atom_encoder_heads: int = 4,
51
- token_transformer_depth: int = 24,
52
- token_transformer_heads: int = 8,
53
- atom_decoder_depth: int = 3,
54
- atom_decoder_heads: int = 4,
55
- conditioning_transition_layers: int = 2,
56
- activation_checkpointing: bool = False,
57
- transformer_post_ln: bool = False,
58
- ) -> None:
59
- super().__init__()
60
-
61
- self.atoms_per_window_queries = atoms_per_window_queries
62
- self.atoms_per_window_keys = atoms_per_window_keys
63
- self.sigma_data = sigma_data
64
- self.activation_checkpointing = activation_checkpointing
65
-
66
- # conditioning
67
- self.single_conditioner = SingleConditioning(
68
- sigma_data=sigma_data,
69
- token_s=token_s,
70
- dim_fourier=dim_fourier,
71
- num_transitions=conditioning_transition_layers,
72
- )
73
-
74
- self.atom_attention_encoder = AtomAttentionEncoder(
75
- atom_s=atom_s,
76
- token_s=token_s,
77
- atoms_per_window_queries=atoms_per_window_queries,
78
- atoms_per_window_keys=atoms_per_window_keys,
79
- atom_encoder_depth=atom_encoder_depth,
80
- atom_encoder_heads=atom_encoder_heads,
81
- structure_prediction=True,
82
- activation_checkpointing=activation_checkpointing,
83
- transformer_post_layer_norm=transformer_post_ln,
84
- )
85
-
86
- self.s_to_a_linear = nn.Sequential(
87
- nn.LayerNorm(2 * token_s), LinearNoBias(2 * token_s, 2 * token_s)
88
- )
89
- init.final_init_(self.s_to_a_linear[1].weight)
90
-
91
- self.token_transformer = DiffusionTransformer(
92
- dim=2 * token_s,
93
- dim_single_cond=2 * token_s,
94
- depth=token_transformer_depth,
95
- heads=token_transformer_heads,
96
- activation_checkpointing=activation_checkpointing,
97
- # post_layer_norm=transformer_post_ln,
98
- )
99
-
100
- self.a_norm = nn.LayerNorm(
101
- 2 * token_s
102
- ) # if not transformer_post_ln else nn.Identity()
103
-
104
- self.atom_attention_decoder = AtomAttentionDecoder(
105
- atom_s=atom_s,
106
- token_s=token_s,
107
- attn_window_queries=atoms_per_window_queries,
108
- attn_window_keys=atoms_per_window_keys,
109
- atom_decoder_depth=atom_decoder_depth,
110
- atom_decoder_heads=atom_decoder_heads,
111
- activation_checkpointing=activation_checkpointing,
112
- # transformer_post_layer_norm=transformer_post_ln,
113
- )
114
-
115
- def forward(
116
- self,
117
- s_inputs, # Float['b n ts']
118
- s_trunk, # Float['b n ts']
119
- r_noisy, # Float['bm m 3']
120
- times, # Float['bm 1 1']
121
- feats,
122
- diffusion_conditioning,
123
- multiplicity=1,
124
- ):
125
- if self.activation_checkpointing and self.training:
126
- s, normed_fourier = torch.utils.checkpoint.checkpoint(
127
- self.single_conditioner,
128
- times,
129
- s_trunk.repeat_interleave(multiplicity, 0),
130
- s_inputs.repeat_interleave(multiplicity, 0),
131
- )
132
- else:
133
- s, normed_fourier = self.single_conditioner(
134
- times,
135
- s_trunk.repeat_interleave(multiplicity, 0),
136
- s_inputs.repeat_interleave(multiplicity, 0),
137
- )
138
-
139
- # Sequence-local Atom Attention and aggregation to coarse-grained tokens
140
- a, q_skip, c_skip, to_keys = self.atom_attention_encoder(
141
- feats=feats,
142
- q=diffusion_conditioning["q"].float(),
143
- c=diffusion_conditioning["c"].float(),
144
- atom_enc_bias=diffusion_conditioning["atom_enc_bias"].float(),
145
- to_keys=diffusion_conditioning["to_keys"],
146
- r=r_noisy, # Float['b m 3'],
147
- multiplicity=multiplicity,
148
- )
149
-
150
- # Full self-attention on token level
151
- a = a + self.s_to_a_linear(s)
152
-
153
- mask = feats["token_pad_mask"].repeat_interleave(multiplicity, 0)
154
- a = self.token_transformer(
155
- a,
156
- mask=mask.float(),
157
- s=s,
158
- bias=diffusion_conditioning[
159
- "token_trans_bias"
160
- ].float(), # note z is not expanded with multiplicity until after bias is computed
161
- multiplicity=multiplicity,
162
- )
163
- a = self.a_norm(a)
164
-
165
- # Broadcast token activations to atoms and run Sequence-local Atom Attention
166
- r_update = self.atom_attention_decoder(
167
- a=a,
168
- q=q_skip,
169
- c=c_skip,
170
- atom_dec_bias=diffusion_conditioning["atom_dec_bias"].float(),
171
- feats=feats,
172
- multiplicity=multiplicity,
173
- to_keys=to_keys,
174
- )
175
-
176
- return r_update
177
-
178
-
179
- class AtomDiffusion(Module):
180
- def __init__(
181
- self,
182
- score_model_args,
183
- num_sampling_steps: int = 5, # number of sampling steps
184
- sigma_min: float = 0.0004, # min noise level
185
- sigma_max: float = 160.0, # max noise level
186
- sigma_data: float = 16.0, # standard deviation of data distribution
187
- rho: float = 7, # controls the sampling schedule
188
- P_mean: float = -1.2, # mean of log-normal distribution from which noise is drawn for training
189
- P_std: float = 1.5, # standard deviation of log-normal distribution from which noise is drawn for training
190
- gamma_0: float = 0.8,
191
- gamma_min: float = 1.0,
192
- noise_scale: float = 1.003,
193
- step_scale: float = 1.5,
194
- step_scale_random: list = None,
195
- coordinate_augmentation: bool = True,
196
- coordinate_augmentation_inference=None,
197
- compile_score: bool = False,
198
- alignment_reverse_diff: bool = False,
199
- synchronize_sigmas: bool = False,
200
- ):
201
- super().__init__()
202
- self.score_model = DiffusionModule(
203
- **score_model_args,
204
- )
205
- if compile_score:
206
- self.score_model = torch.compile(
207
- self.score_model, dynamic=False, fullgraph=False
208
- )
209
-
210
- # parameters
211
- self.sigma_min = sigma_min
212
- self.sigma_max = sigma_max
213
- self.sigma_data = sigma_data
214
- self.rho = rho
215
- self.P_mean = P_mean
216
- self.P_std = P_std
217
- self.num_sampling_steps = num_sampling_steps
218
- self.gamma_0 = gamma_0
219
- self.gamma_min = gamma_min
220
- self.noise_scale = noise_scale
221
- self.step_scale = step_scale
222
- self.step_scale_random = step_scale_random
223
- self.coordinate_augmentation = coordinate_augmentation
224
- self.coordinate_augmentation_inference = (
225
- coordinate_augmentation_inference
226
- if coordinate_augmentation_inference is not None
227
- else coordinate_augmentation
228
- )
229
- self.alignment_reverse_diff = alignment_reverse_diff
230
- self.synchronize_sigmas = synchronize_sigmas
231
-
232
- self.token_s = score_model_args["token_s"]
233
- self.register_buffer("zero", torch.tensor(0.0), persistent=False)
234
-
235
- @property
236
- def device(self):
237
- return next(self.score_model.parameters()).device
238
-
239
- def c_skip(self, sigma):
240
- return (self.sigma_data**2) / (sigma**2 + self.sigma_data**2)
241
-
242
- def c_out(self, sigma):
243
- return sigma * self.sigma_data / torch.sqrt(self.sigma_data**2 + sigma**2)
244
-
245
- def c_in(self, sigma):
246
- return 1 / torch.sqrt(sigma**2 + self.sigma_data**2)
247
-
248
- def c_noise(self, sigma):
249
- return log(sigma / self.sigma_data) * 0.25
250
-
251
- def preconditioned_network_forward(
252
- self,
253
- noised_atom_coords, #: Float['b m 3'],
254
- sigma, #: Float['b'] | Float[' '] | float,
255
- network_condition_kwargs: dict,
256
- ):
257
- batch, device = noised_atom_coords.shape[0], noised_atom_coords.device
258
-
259
- if isinstance(sigma, float):
260
- sigma = torch.full((batch,), sigma, device=device)
261
-
262
- padded_sigma = rearrange(sigma, "b -> b 1 1")
263
-
264
- r_update = self.score_model(
265
- r_noisy=self.c_in(padded_sigma) * noised_atom_coords,
266
- times=self.c_noise(sigma),
267
- **network_condition_kwargs,
268
- )
269
-
270
- denoised_coords = (
271
- self.c_skip(padded_sigma) * noised_atom_coords
272
- + self.c_out(padded_sigma) * r_update
273
- )
274
- return denoised_coords
275
-
276
- def sample_schedule(self, num_sampling_steps=None):
277
- num_sampling_steps = default(num_sampling_steps, self.num_sampling_steps)
278
- inv_rho = 1 / self.rho
279
-
280
- steps = torch.arange(
281
- num_sampling_steps, device=self.device, dtype=torch.float32
282
- )
283
- sigmas = (
284
- self.sigma_max**inv_rho
285
- + steps
286
- / (num_sampling_steps - 1)
287
- * (self.sigma_min**inv_rho - self.sigma_max**inv_rho)
288
- ) ** self.rho
289
-
290
- sigmas = sigmas * self.sigma_data
291
-
292
- sigmas = F.pad(sigmas, (0, 1), value=0.0) # last step is sigma value of 0.
293
- return sigmas
294
-
295
- def sample(
296
- self,
297
- atom_mask,
298
- num_sampling_steps=None,
299
- multiplicity=1,
300
- max_parallel_samples=None,
301
- steering_args=None,
302
- **network_condition_kwargs,
303
- ):
304
- if steering_args is not None and (
305
- steering_args["fk_steering"]
306
- or steering_args["physical_guidance_update"]
307
- or steering_args["contact_guidance_update"]
308
- ):
309
- potentials = get_potentials(steering_args, boltz2=True)
310
-
311
- if steering_args["fk_steering"]:
312
- multiplicity = multiplicity * steering_args["num_particles"]
313
- energy_traj = torch.empty((multiplicity, 0), device=self.device)
314
- resample_weights = torch.ones(multiplicity, device=self.device).reshape(
315
- -1, steering_args["num_particles"]
316
- )
317
- if (
318
- steering_args["physical_guidance_update"]
319
- or steering_args["contact_guidance_update"]
320
- ):
321
- scaled_guidance_update = torch.zeros(
322
- (multiplicity, *atom_mask.shape[1:], 3),
323
- dtype=torch.float32,
324
- device=self.device,
325
- )
326
- if max_parallel_samples is None:
327
- max_parallel_samples = multiplicity
328
-
329
- num_sampling_steps = default(num_sampling_steps, self.num_sampling_steps)
330
- atom_mask = atom_mask.repeat_interleave(multiplicity, 0)
331
-
332
- shape = (*atom_mask.shape, 3)
333
-
334
- # get the schedule, which is returned as (sigma, gamma) tuple, and pair up with the next sigma and gamma
335
- sigmas = self.sample_schedule(num_sampling_steps)
336
- gammas = torch.where(sigmas > self.gamma_min, self.gamma_0, 0.0)
337
- sigmas_and_gammas = list(zip(sigmas[:-1], sigmas[1:], gammas[1:]))
338
- if self.training and self.step_scale_random is not None:
339
- step_scale = np.random.choice(self.step_scale_random)
340
- else:
341
- step_scale = self.step_scale
342
-
343
- # atom position is noise at the beginning
344
- init_sigma = sigmas[0]
345
- atom_coords = init_sigma * torch.randn(shape, device=self.device)
346
- token_repr = None
347
- atom_coords_denoised = None
348
-
349
- # gradually denoise
350
- for step_idx, (sigma_tm, sigma_t, gamma) in enumerate(sigmas_and_gammas):
351
- random_R, random_tr = compute_random_augmentation(
352
- multiplicity, device=atom_coords.device, dtype=atom_coords.dtype
353
- )
354
- atom_coords = atom_coords - atom_coords.mean(dim=-2, keepdims=True)
355
- atom_coords = (
356
- torch.einsum("bmd,bds->bms", atom_coords, random_R) + random_tr
357
- )
358
- if atom_coords_denoised is not None:
359
- atom_coords_denoised -= atom_coords_denoised.mean(dim=-2, keepdims=True)
360
- atom_coords_denoised = (
361
- torch.einsum("bmd,bds->bms", atom_coords_denoised, random_R)
362
- + random_tr
363
- )
364
- if (
365
- steering_args["physical_guidance_update"]
366
- or steering_args["contact_guidance_update"]
367
- ) and scaled_guidance_update is not None:
368
- scaled_guidance_update = torch.einsum(
369
- "bmd,bds->bms", scaled_guidance_update, random_R
370
- )
371
-
372
- sigma_tm, sigma_t, gamma = sigma_tm.item(), sigma_t.item(), gamma.item()
373
-
374
- t_hat = sigma_tm * (1 + gamma)
375
- steering_t = 1.0 - (step_idx / num_sampling_steps)
376
- noise_var = self.noise_scale**2 * (t_hat**2 - sigma_tm**2)
377
- eps = sqrt(noise_var) * torch.randn(shape, device=self.device)
378
- atom_coords_noisy = atom_coords + eps
379
-
380
- with torch.no_grad():
381
- atom_coords_denoised = torch.zeros_like(atom_coords_noisy)
382
- sample_ids = torch.arange(multiplicity).to(atom_coords_noisy.device)
383
- sample_ids_chunks = sample_ids.chunk(
384
- multiplicity % max_parallel_samples + 1
385
- )
386
-
387
- for sample_ids_chunk in sample_ids_chunks:
388
- atom_coords_denoised_chunk = self.preconditioned_network_forward(
389
- atom_coords_noisy[sample_ids_chunk],
390
- t_hat,
391
- network_condition_kwargs=dict(
392
- multiplicity=sample_ids_chunk.numel(),
393
- **network_condition_kwargs,
394
- ),
395
- )
396
- atom_coords_denoised[sample_ids_chunk] = atom_coords_denoised_chunk
397
-
398
- if steering_args["fk_steering"] and (
399
- (
400
- step_idx % steering_args["fk_resampling_interval"] == 0
401
- and noise_var > 0
402
- )
403
- or step_idx == num_sampling_steps - 1
404
- ):
405
- # Compute energy of x_0 prediction
406
- energy = torch.zeros(multiplicity, device=self.device)
407
- for potential in potentials:
408
- parameters = potential.compute_parameters(steering_t)
409
- if parameters["resampling_weight"] > 0:
410
- component_energy = potential.compute(
411
- atom_coords_denoised,
412
- network_condition_kwargs["feats"],
413
- parameters,
414
- )
415
- energy += parameters["resampling_weight"] * component_energy
416
- energy_traj = torch.cat((energy_traj, energy.unsqueeze(1)), dim=1)
417
-
418
- # Compute log G values
419
- if step_idx == 0:
420
- log_G = -1 * energy
421
- else:
422
- log_G = energy_traj[:, -2] - energy_traj[:, -1]
423
-
424
- # Compute ll difference between guided and unguided transition distribution
425
- if (
426
- steering_args["physical_guidance_update"]
427
- or steering_args["contact_guidance_update"]
428
- ) and noise_var > 0:
429
- ll_difference = (
430
- eps**2 - (eps + scaled_guidance_update) ** 2
431
- ).sum(dim=(-1, -2)) / (2 * noise_var)
432
- else:
433
- ll_difference = torch.zeros_like(energy)
434
-
435
- # Compute resampling weights
436
- resample_weights = F.softmax(
437
- (ll_difference + steering_args["fk_lambda"] * log_G).reshape(
438
- -1, steering_args["num_particles"]
439
- ),
440
- dim=1,
441
- )
442
-
443
- # Compute guidance update to x_0 prediction
444
- if (
445
- steering_args["physical_guidance_update"]
446
- or steering_args["contact_guidance_update"]
447
- ) and step_idx < num_sampling_steps - 1:
448
- guidance_update = torch.zeros_like(atom_coords_denoised)
449
- for guidance_step in range(steering_args["num_gd_steps"]):
450
- energy_gradient = torch.zeros_like(atom_coords_denoised)
451
- for potential in potentials:
452
- parameters = potential.compute_parameters(steering_t)
453
- if (
454
- parameters["guidance_weight"] > 0
455
- and (guidance_step) % parameters["guidance_interval"]
456
- == 0
457
- ):
458
- energy_gradient += parameters[
459
- "guidance_weight"
460
- ] * potential.compute_gradient(
461
- atom_coords_denoised + guidance_update,
462
- network_condition_kwargs["feats"],
463
- parameters,
464
- )
465
- guidance_update -= energy_gradient
466
- atom_coords_denoised += guidance_update
467
- scaled_guidance_update = (
468
- guidance_update
469
- * -1
470
- * self.step_scale
471
- * (sigma_t - t_hat)
472
- / t_hat
473
- )
474
-
475
- if steering_args["fk_steering"] and (
476
- (
477
- step_idx % steering_args["fk_resampling_interval"] == 0
478
- and noise_var > 0
479
- )
480
- or step_idx == num_sampling_steps - 1
481
- ):
482
- resample_indices = (
483
- torch.multinomial(
484
- resample_weights,
485
- resample_weights.shape[1]
486
- if step_idx < num_sampling_steps - 1
487
- else 1,
488
- replacement=True,
489
- )
490
- + resample_weights.shape[1]
491
- * torch.arange(
492
- resample_weights.shape[0], device=resample_weights.device
493
- ).unsqueeze(-1)
494
- ).flatten()
495
-
496
- atom_coords = atom_coords[resample_indices]
497
- atom_coords_noisy = atom_coords_noisy[resample_indices]
498
- atom_mask = atom_mask[resample_indices]
499
- if atom_coords_denoised is not None:
500
- atom_coords_denoised = atom_coords_denoised[resample_indices]
501
- energy_traj = energy_traj[resample_indices]
502
- if (
503
- steering_args["physical_guidance_update"]
504
- or steering_args["contact_guidance_update"]
505
- ):
506
- scaled_guidance_update = scaled_guidance_update[
507
- resample_indices
508
- ]
509
- if token_repr is not None:
510
- token_repr = token_repr[resample_indices]
511
-
512
- if self.alignment_reverse_diff:
513
- with torch.autocast("cuda", enabled=False):
514
- atom_coords_noisy = weighted_rigid_align(
515
- atom_coords_noisy.float(),
516
- atom_coords_denoised.float(),
517
- atom_mask.float(),
518
- atom_mask.float(),
519
- )
520
-
521
- atom_coords_noisy = atom_coords_noisy.to(atom_coords_denoised)
522
-
523
- denoised_over_sigma = (atom_coords_noisy - atom_coords_denoised) / t_hat
524
- atom_coords_next = (
525
- atom_coords_noisy + step_scale * (sigma_t - t_hat) * denoised_over_sigma
526
- )
527
-
528
- atom_coords = atom_coords_next
529
-
530
- return dict(sample_atom_coords=atom_coords, diff_token_repr=token_repr)
531
-
532
- def loss_weight(self, sigma):
533
- return (sigma**2 + self.sigma_data**2) / ((sigma * self.sigma_data) ** 2)
534
-
535
- def noise_distribution(self, batch_size):
536
- return (
537
- self.sigma_data
538
- * (
539
- self.P_mean
540
- + self.P_std * torch.randn((batch_size,), device=self.device)
541
- ).exp()
542
- )
543
-
544
- def forward(
545
- self,
546
- s_inputs,
547
- s_trunk,
548
- feats,
549
- diffusion_conditioning,
550
- multiplicity=1,
551
- ):
552
- # training diffusion step
553
- batch_size = feats["coords"].shape[0] // multiplicity
554
-
555
- if self.synchronize_sigmas:
556
- sigmas = self.noise_distribution(batch_size).repeat_interleave(
557
- multiplicity, 0
558
- )
559
- else:
560
- sigmas = self.noise_distribution(batch_size * multiplicity)
561
- padded_sigmas = rearrange(sigmas, "b -> b 1 1")
562
-
563
- atom_coords = feats["coords"]
564
-
565
- atom_mask = feats["atom_pad_mask"]
566
- atom_mask = atom_mask.repeat_interleave(multiplicity, 0)
567
-
568
- atom_coords = center_random_augmentation(
569
- atom_coords, atom_mask, augmentation=self.coordinate_augmentation
570
- )
571
-
572
- noise = torch.randn_like(atom_coords)
573
- noised_atom_coords = atom_coords + padded_sigmas * noise
574
-
575
- denoised_atom_coords = self.preconditioned_network_forward(
576
- noised_atom_coords,
577
- sigmas,
578
- network_condition_kwargs={
579
- "s_inputs": s_inputs,
580
- "s_trunk": s_trunk,
581
- "feats": feats,
582
- "multiplicity": multiplicity,
583
- "diffusion_conditioning": diffusion_conditioning,
584
- },
585
- )
586
-
587
- return {
588
- "denoised_atom_coords": denoised_atom_coords,
589
- "sigmas": sigmas,
590
- "aligned_true_atom_coords": atom_coords,
591
- }
592
-
593
- def compute_loss(
594
- self,
595
- feats,
596
- out_dict,
597
- add_smooth_lddt_loss=True,
598
- nucleotide_loss_weight=5.0,
599
- ligand_loss_weight=10.0,
600
- multiplicity=1,
601
- filter_by_plddt=0.0,
602
- ):
603
- with torch.autocast("cuda", enabled=False):
604
- denoised_atom_coords = out_dict["denoised_atom_coords"].float()
605
- sigmas = out_dict["sigmas"].float()
606
-
607
- resolved_atom_mask_uni = feats["atom_resolved_mask"].float()
608
-
609
- if filter_by_plddt > 0:
610
- plddt_mask = feats["plddt"] > filter_by_plddt
611
- resolved_atom_mask_uni = resolved_atom_mask_uni * plddt_mask.float()
612
-
613
- resolved_atom_mask = resolved_atom_mask_uni.repeat_interleave(
614
- multiplicity, 0
615
- )
616
-
617
- align_weights = denoised_atom_coords.new_ones(denoised_atom_coords.shape[:2])
618
- atom_type = (
619
- torch.bmm(
620
- feats["atom_to_token"].float(),
621
- feats["mol_type"].unsqueeze(-1).float(),
622
- )
623
- .squeeze(-1)
624
- .long()
625
- )
626
- atom_type_mult = atom_type.repeat_interleave(multiplicity, 0)
627
-
628
- align_weights = (
629
- align_weights
630
- * (
631
- 1
632
- + nucleotide_loss_weight
633
- * (
634
- torch.eq(atom_type_mult, const.chain_type_ids["DNA"]).float()
635
- + torch.eq(atom_type_mult, const.chain_type_ids["RNA"]).float()
636
- )
637
- + ligand_loss_weight
638
- * torch.eq(
639
- atom_type_mult, const.chain_type_ids["NONPOLYMER"]
640
- ).float()
641
- ).float()
642
- )
643
-
644
- atom_coords = out_dict["aligned_true_atom_coords"].float()
645
- atom_coords_aligned_ground_truth = weighted_rigid_align(
646
- atom_coords.detach(),
647
- denoised_atom_coords.detach(),
648
- align_weights.detach(),
649
- mask=feats["atom_resolved_mask"]
650
- .float()
651
- .repeat_interleave(multiplicity, 0)
652
- .detach(),
653
- )
654
-
655
- # Cast back
656
- atom_coords_aligned_ground_truth = atom_coords_aligned_ground_truth.to(
657
- denoised_atom_coords
658
- )
659
-
660
- # weighted MSE loss of denoised atom positions
661
- mse_loss = (
662
- (denoised_atom_coords - atom_coords_aligned_ground_truth) ** 2
663
- ).sum(dim=-1)
664
- mse_loss = torch.sum(
665
- mse_loss * align_weights * resolved_atom_mask, dim=-1
666
- ) / (torch.sum(3 * align_weights * resolved_atom_mask, dim=-1) + 1e-5)
667
-
668
- # weight by sigma factor
669
- loss_weights = self.loss_weight(sigmas)
670
- mse_loss = (mse_loss * loss_weights).mean()
671
-
672
- total_loss = mse_loss
673
-
674
- # proposed auxiliary smooth lddt loss
675
- lddt_loss = self.zero
676
- if add_smooth_lddt_loss:
677
- lddt_loss = smooth_lddt_loss(
678
- denoised_atom_coords,
679
- feats["coords"],
680
- torch.eq(atom_type, const.chain_type_ids["DNA"]).float()
681
- + torch.eq(atom_type, const.chain_type_ids["RNA"]).float(),
682
- coords_mask=resolved_atom_mask_uni,
683
- multiplicity=multiplicity,
684
- )
685
-
686
- total_loss = total_loss + lddt_loss
687
-
688
- loss_breakdown = {
689
- "mse_loss": mse_loss,
690
- "smooth_lddt_loss": lddt_loss,
691
- }
692
-
693
- return {"loss": total_loss, "loss_breakdown": loss_breakdown}
 
1
+ # started from code from https://github.com/lucidrains/alphafold3-pytorch, MIT License, Copyright (c) 2024 Phil Wang
2
+
3
+ from __future__ import annotations
4
+
5
+ from math import sqrt
6
+
7
+ import numpy as np
8
+ import torch
9
+ import torch.nn.functional as F # noqa: N812
10
+ from einops import rearrange
11
+ from torch import nn
12
+ from torch.nn import Module
13
+
14
+ from . import vb_const as const
15
+ from . import vb_layers_initialize as init
16
+ from .vb_loss_diffusionv2 import (
17
+ smooth_lddt_loss,
18
+ weighted_rigid_align,
19
+ )
20
+ from .vb_modules_encodersv2 import (
21
+ AtomAttentionDecoder,
22
+ AtomAttentionEncoder,
23
+ SingleConditioning,
24
+ )
25
+ from .vb_modules_transformersv2 import (
26
+ DiffusionTransformer,
27
+ )
28
+ from .vb_modules_utils import (
29
+ LinearNoBias,
30
+ center_random_augmentation,
31
+ compute_random_augmentation,
32
+ default,
33
+ log,
34
+ )
35
+ from .vb_potentials_potentials import get_potentials
36
+
37
+
38
+ class DiffusionModule(Module):
39
+ """Diffusion module"""
40
+
41
+ def __init__(
42
+ self,
43
+ token_s: int,
44
+ atom_s: int,
45
+ atoms_per_window_queries: int = 32,
46
+ atoms_per_window_keys: int = 128,
47
+ sigma_data: int = 16,
48
+ dim_fourier: int = 256,
49
+ atom_encoder_depth: int = 3,
50
+ atom_encoder_heads: int = 4,
51
+ token_transformer_depth: int = 24,
52
+ token_transformer_heads: int = 8,
53
+ atom_decoder_depth: int = 3,
54
+ atom_decoder_heads: int = 4,
55
+ conditioning_transition_layers: int = 2,
56
+ activation_checkpointing: bool = False,
57
+ transformer_post_ln: bool = False,
58
+ ) -> None:
59
+ super().__init__()
60
+
61
+ self.atoms_per_window_queries = atoms_per_window_queries
62
+ self.atoms_per_window_keys = atoms_per_window_keys
63
+ self.sigma_data = sigma_data
64
+ self.activation_checkpointing = activation_checkpointing
65
+
66
+ # conditioning
67
+ self.single_conditioner = SingleConditioning(
68
+ sigma_data=sigma_data,
69
+ token_s=token_s,
70
+ dim_fourier=dim_fourier,
71
+ num_transitions=conditioning_transition_layers,
72
+ )
73
+
74
+ self.atom_attention_encoder = AtomAttentionEncoder(
75
+ atom_s=atom_s,
76
+ token_s=token_s,
77
+ atoms_per_window_queries=atoms_per_window_queries,
78
+ atoms_per_window_keys=atoms_per_window_keys,
79
+ atom_encoder_depth=atom_encoder_depth,
80
+ atom_encoder_heads=atom_encoder_heads,
81
+ structure_prediction=True,
82
+ activation_checkpointing=activation_checkpointing,
83
+ transformer_post_layer_norm=transformer_post_ln,
84
+ )
85
+
86
+ self.s_to_a_linear = nn.Sequential(
87
+ nn.LayerNorm(2 * token_s), LinearNoBias(2 * token_s, 2 * token_s)
88
+ )
89
+ init.final_init_(self.s_to_a_linear[1].weight)
90
+
91
+ self.token_transformer = DiffusionTransformer(
92
+ dim=2 * token_s,
93
+ dim_single_cond=2 * token_s,
94
+ depth=token_transformer_depth,
95
+ heads=token_transformer_heads,
96
+ activation_checkpointing=activation_checkpointing,
97
+ # post_layer_norm=transformer_post_ln,
98
+ )
99
+
100
+ self.a_norm = nn.LayerNorm(
101
+ 2 * token_s
102
+ ) # if not transformer_post_ln else nn.Identity()
103
+
104
+ self.atom_attention_decoder = AtomAttentionDecoder(
105
+ atom_s=atom_s,
106
+ token_s=token_s,
107
+ attn_window_queries=atoms_per_window_queries,
108
+ attn_window_keys=atoms_per_window_keys,
109
+ atom_decoder_depth=atom_decoder_depth,
110
+ atom_decoder_heads=atom_decoder_heads,
111
+ activation_checkpointing=activation_checkpointing,
112
+ # transformer_post_layer_norm=transformer_post_ln,
113
+ )
114
+
115
+ def forward(
116
+ self,
117
+ s_inputs, # Float['b n ts']
118
+ s_trunk, # Float['b n ts']
119
+ r_noisy, # Float['bm m 3']
120
+ times, # Float['bm 1 1']
121
+ feats,
122
+ diffusion_conditioning,
123
+ multiplicity=1,
124
+ ):
125
+ if self.activation_checkpointing and self.training:
126
+ s, normed_fourier = torch.utils.checkpoint.checkpoint(
127
+ self.single_conditioner,
128
+ times,
129
+ s_trunk.repeat_interleave(multiplicity, 0),
130
+ s_inputs.repeat_interleave(multiplicity, 0),
131
+ )
132
+ else:
133
+ s, normed_fourier = self.single_conditioner(
134
+ times,
135
+ s_trunk.repeat_interleave(multiplicity, 0),
136
+ s_inputs.repeat_interleave(multiplicity, 0),
137
+ )
138
+
139
+ # Sequence-local Atom Attention and aggregation to coarse-grained tokens
140
+ a, q_skip, c_skip, to_keys = self.atom_attention_encoder(
141
+ feats=feats,
142
+ q=diffusion_conditioning["q"].float(),
143
+ c=diffusion_conditioning["c"].float(),
144
+ atom_enc_bias=diffusion_conditioning["atom_enc_bias"].float(),
145
+ to_keys=diffusion_conditioning["to_keys"],
146
+ r=r_noisy, # Float['b m 3'],
147
+ multiplicity=multiplicity,
148
+ )
149
+
150
+ # Full self-attention on token level
151
+ a = a + self.s_to_a_linear(s)
152
+
153
+ mask = feats["token_pad_mask"].repeat_interleave(multiplicity, 0)
154
+ a = self.token_transformer(
155
+ a,
156
+ mask=mask.float(),
157
+ s=s,
158
+ bias=diffusion_conditioning[
159
+ "token_trans_bias"
160
+ ].float(), # note z is not expanded with multiplicity until after bias is computed
161
+ multiplicity=multiplicity,
162
+ )
163
+ a = self.a_norm(a)
164
+
165
+ # Broadcast token activations to atoms and run Sequence-local Atom Attention
166
+ r_update = self.atom_attention_decoder(
167
+ a=a,
168
+ q=q_skip,
169
+ c=c_skip,
170
+ atom_dec_bias=diffusion_conditioning["atom_dec_bias"].float(),
171
+ feats=feats,
172
+ multiplicity=multiplicity,
173
+ to_keys=to_keys,
174
+ )
175
+
176
+ return r_update
177
+
178
+
179
+ class AtomDiffusion(Module):
180
+ def __init__(
181
+ self,
182
+ score_model_args,
183
+ num_sampling_steps: int = 5, # number of sampling steps
184
+ sigma_min: float = 0.0004, # min noise level
185
+ sigma_max: float = 160.0, # max noise level
186
+ sigma_data: float = 16.0, # standard deviation of data distribution
187
+ rho: float = 7, # controls the sampling schedule
188
+ P_mean: float = -1.2, # mean of log-normal distribution from which noise is drawn for training
189
+ P_std: float = 1.5, # standard deviation of log-normal distribution from which noise is drawn for training
190
+ gamma_0: float = 0.8,
191
+ gamma_min: float = 1.0,
192
+ noise_scale: float = 1.003,
193
+ step_scale: float = 1.5,
194
+ step_scale_random: list = None,
195
+ coordinate_augmentation: bool = True,
196
+ coordinate_augmentation_inference=None,
197
+ compile_score: bool = False,
198
+ alignment_reverse_diff: bool = False,
199
+ synchronize_sigmas: bool = False,
200
+ ):
201
+ super().__init__()
202
+ self.score_model = DiffusionModule(
203
+ **score_model_args,
204
+ )
205
+ if compile_score:
206
+ self.score_model = torch.compile(
207
+ self.score_model, dynamic=False, fullgraph=False
208
+ )
209
+
210
+ # parameters
211
+ self.sigma_min = sigma_min
212
+ self.sigma_max = sigma_max
213
+ self.sigma_data = sigma_data
214
+ self.rho = rho
215
+ self.P_mean = P_mean
216
+ self.P_std = P_std
217
+ self.num_sampling_steps = num_sampling_steps
218
+ self.gamma_0 = gamma_0
219
+ self.gamma_min = gamma_min
220
+ self.noise_scale = noise_scale
221
+ self.step_scale = step_scale
222
+ self.step_scale_random = step_scale_random
223
+ self.coordinate_augmentation = coordinate_augmentation
224
+ self.coordinate_augmentation_inference = (
225
+ coordinate_augmentation_inference
226
+ if coordinate_augmentation_inference is not None
227
+ else coordinate_augmentation
228
+ )
229
+ self.alignment_reverse_diff = alignment_reverse_diff
230
+ self.synchronize_sigmas = synchronize_sigmas
231
+
232
+ self.token_s = score_model_args["token_s"]
233
+ self.register_buffer("zero", torch.tensor(0.0), persistent=False)
234
+
235
+ @property
236
+ def device(self):
237
+ return next(self.score_model.parameters()).device
238
+
239
+ def c_skip(self, sigma):
240
+ return (self.sigma_data**2) / (sigma**2 + self.sigma_data**2)
241
+
242
+ def c_out(self, sigma):
243
+ return sigma * self.sigma_data / torch.sqrt(self.sigma_data**2 + sigma**2)
244
+
245
+ def c_in(self, sigma):
246
+ return 1 / torch.sqrt(sigma**2 + self.sigma_data**2)
247
+
248
+ def c_noise(self, sigma):
249
+ return log(sigma / self.sigma_data) * 0.25
250
+
251
+ def preconditioned_network_forward(
252
+ self,
253
+ noised_atom_coords, #: Float['b m 3'],
254
+ sigma, #: Float['b'] | Float[' '] | float,
255
+ network_condition_kwargs: dict,
256
+ ):
257
+ batch, device = noised_atom_coords.shape[0], noised_atom_coords.device
258
+
259
+ if isinstance(sigma, float):
260
+ sigma = torch.full((batch,), sigma, device=device)
261
+
262
+ padded_sigma = rearrange(sigma, "b -> b 1 1")
263
+
264
+ r_update = self.score_model(
265
+ r_noisy=self.c_in(padded_sigma) * noised_atom_coords,
266
+ times=self.c_noise(sigma),
267
+ **network_condition_kwargs,
268
+ )
269
+
270
+ denoised_coords = (
271
+ self.c_skip(padded_sigma) * noised_atom_coords
272
+ + self.c_out(padded_sigma) * r_update
273
+ )
274
+ return denoised_coords
275
+
276
+ def sample_schedule(self, num_sampling_steps=None):
277
+ num_sampling_steps = default(num_sampling_steps, self.num_sampling_steps)
278
+ inv_rho = 1 / self.rho
279
+
280
+ steps = torch.arange(
281
+ num_sampling_steps, device=self.device, dtype=torch.float32
282
+ )
283
+ sigmas = (
284
+ self.sigma_max**inv_rho
285
+ + steps
286
+ / (num_sampling_steps - 1)
287
+ * (self.sigma_min**inv_rho - self.sigma_max**inv_rho)
288
+ ) ** self.rho
289
+
290
+ sigmas = sigmas * self.sigma_data
291
+
292
+ sigmas = F.pad(sigmas, (0, 1), value=0.0) # last step is sigma value of 0.
293
+ return sigmas
294
+
295
+ def sample(
296
+ self,
297
+ atom_mask,
298
+ num_sampling_steps=None,
299
+ multiplicity=1,
300
+ max_parallel_samples=None,
301
+ steering_args=None,
302
+ **network_condition_kwargs,
303
+ ):
304
+ if steering_args is not None and (
305
+ steering_args["fk_steering"]
306
+ or steering_args["physical_guidance_update"]
307
+ or steering_args["contact_guidance_update"]
308
+ ):
309
+ potentials = get_potentials(steering_args, boltz2=True)
310
+
311
+ if steering_args["fk_steering"]:
312
+ multiplicity = multiplicity * steering_args["num_particles"]
313
+ energy_traj = torch.empty((multiplicity, 0), device=self.device)
314
+ resample_weights = torch.ones(multiplicity, device=self.device).reshape(
315
+ -1, steering_args["num_particles"]
316
+ )
317
+ if (
318
+ steering_args["physical_guidance_update"]
319
+ or steering_args["contact_guidance_update"]
320
+ ):
321
+ scaled_guidance_update = torch.zeros(
322
+ (multiplicity, *atom_mask.shape[1:], 3),
323
+ dtype=torch.float32,
324
+ device=self.device,
325
+ )
326
+ if max_parallel_samples is None:
327
+ max_parallel_samples = multiplicity
328
+
329
+ num_sampling_steps = default(num_sampling_steps, self.num_sampling_steps)
330
+ atom_mask = atom_mask.repeat_interleave(multiplicity, 0)
331
+
332
+ shape = (*atom_mask.shape, 3)
333
+
334
+ # get the schedule, which is returned as (sigma, gamma) tuple, and pair up with the next sigma and gamma
335
+ sigmas = self.sample_schedule(num_sampling_steps)
336
+ gammas = torch.where(sigmas > self.gamma_min, self.gamma_0, 0.0)
337
+ sigmas_and_gammas = list(zip(sigmas[:-1], sigmas[1:], gammas[1:]))
338
+ if self.training and self.step_scale_random is not None:
339
+ step_scale = np.random.choice(self.step_scale_random)
340
+ else:
341
+ step_scale = self.step_scale
342
+
343
+ # atom position is noise at the beginning
344
+ init_sigma = sigmas[0]
345
+ atom_coords = init_sigma * torch.randn(shape, device=self.device)
346
+ token_repr = None
347
+ atom_coords_denoised = None
348
+
349
+ # gradually denoise
350
+ for step_idx, (sigma_tm, sigma_t, gamma) in enumerate(sigmas_and_gammas):
351
+ random_R, random_tr = compute_random_augmentation(
352
+ multiplicity, device=atom_coords.device, dtype=atom_coords.dtype
353
+ )
354
+ atom_coords = atom_coords - atom_coords.mean(dim=-2, keepdims=True)
355
+ atom_coords = (
356
+ torch.einsum("bmd,bds->bms", atom_coords, random_R) + random_tr
357
+ )
358
+ if atom_coords_denoised is not None:
359
+ atom_coords_denoised -= atom_coords_denoised.mean(dim=-2, keepdims=True)
360
+ atom_coords_denoised = (
361
+ torch.einsum("bmd,bds->bms", atom_coords_denoised, random_R)
362
+ + random_tr
363
+ )
364
+ if (
365
+ steering_args["physical_guidance_update"]
366
+ or steering_args["contact_guidance_update"]
367
+ ) and scaled_guidance_update is not None:
368
+ scaled_guidance_update = torch.einsum(
369
+ "bmd,bds->bms", scaled_guidance_update, random_R
370
+ )
371
+
372
+ sigma_tm, sigma_t, gamma = sigma_tm.item(), sigma_t.item(), gamma.item()
373
+
374
+ t_hat = sigma_tm * (1 + gamma)
375
+ steering_t = 1.0 - (step_idx / num_sampling_steps)
376
+ noise_var = self.noise_scale**2 * (t_hat**2 - sigma_tm**2)
377
+ eps = sqrt(noise_var) * torch.randn(shape, device=self.device)
378
+ atom_coords_noisy = atom_coords + eps
379
+
380
+ with torch.no_grad():
381
+ atom_coords_denoised = torch.zeros_like(atom_coords_noisy)
382
+ sample_ids = torch.arange(multiplicity).to(atom_coords_noisy.device)
383
+ sample_ids_chunks = sample_ids.chunk(
384
+ multiplicity % max_parallel_samples + 1
385
+ )
386
+
387
+ for sample_ids_chunk in sample_ids_chunks:
388
+ atom_coords_denoised_chunk = self.preconditioned_network_forward(
389
+ atom_coords_noisy[sample_ids_chunk],
390
+ t_hat,
391
+ network_condition_kwargs=dict(
392
+ multiplicity=sample_ids_chunk.numel(),
393
+ **network_condition_kwargs,
394
+ ),
395
+ )
396
+ atom_coords_denoised[sample_ids_chunk] = atom_coords_denoised_chunk
397
+
398
+ if steering_args["fk_steering"] and (
399
+ (
400
+ step_idx % steering_args["fk_resampling_interval"] == 0
401
+ and noise_var > 0
402
+ )
403
+ or step_idx == num_sampling_steps - 1
404
+ ):
405
+ # Compute energy of x_0 prediction
406
+ energy = torch.zeros(multiplicity, device=self.device)
407
+ for potential in potentials:
408
+ parameters = potential.compute_parameters(steering_t)
409
+ if parameters["resampling_weight"] > 0:
410
+ component_energy = potential.compute(
411
+ atom_coords_denoised,
412
+ network_condition_kwargs["feats"],
413
+ parameters,
414
+ )
415
+ energy += parameters["resampling_weight"] * component_energy
416
+ energy_traj = torch.cat((energy_traj, energy.unsqueeze(1)), dim=1)
417
+
418
+ # Compute log G values
419
+ if step_idx == 0:
420
+ log_G = -1 * energy
421
+ else:
422
+ log_G = energy_traj[:, -2] - energy_traj[:, -1]
423
+
424
+ # Compute ll difference between guided and unguided transition distribution
425
+ if (
426
+ steering_args["physical_guidance_update"]
427
+ or steering_args["contact_guidance_update"]
428
+ ) and noise_var > 0:
429
+ ll_difference = (
430
+ eps**2 - (eps + scaled_guidance_update) ** 2
431
+ ).sum(dim=(-1, -2)) / (2 * noise_var)
432
+ else:
433
+ ll_difference = torch.zeros_like(energy)
434
+
435
+ # Compute resampling weights
436
+ resample_weights = F.softmax(
437
+ (ll_difference + steering_args["fk_lambda"] * log_G).reshape(
438
+ -1, steering_args["num_particles"]
439
+ ),
440
+ dim=1,
441
+ )
442
+
443
+ # Compute guidance update to x_0 prediction
444
+ if (
445
+ steering_args["physical_guidance_update"]
446
+ or steering_args["contact_guidance_update"]
447
+ ) and step_idx < num_sampling_steps - 1:
448
+ guidance_update = torch.zeros_like(atom_coords_denoised)
449
+ for guidance_step in range(steering_args["num_gd_steps"]):
450
+ energy_gradient = torch.zeros_like(atom_coords_denoised)
451
+ for potential in potentials:
452
+ parameters = potential.compute_parameters(steering_t)
453
+ if (
454
+ parameters["guidance_weight"] > 0
455
+ and (guidance_step) % parameters["guidance_interval"]
456
+ == 0
457
+ ):
458
+ energy_gradient += parameters[
459
+ "guidance_weight"
460
+ ] * potential.compute_gradient(
461
+ atom_coords_denoised + guidance_update,
462
+ network_condition_kwargs["feats"],
463
+ parameters,
464
+ )
465
+ guidance_update -= energy_gradient
466
+ atom_coords_denoised += guidance_update
467
+ scaled_guidance_update = (
468
+ guidance_update
469
+ * -1
470
+ * self.step_scale
471
+ * (sigma_t - t_hat)
472
+ / t_hat
473
+ )
474
+
475
+ if steering_args["fk_steering"] and (
476
+ (
477
+ step_idx % steering_args["fk_resampling_interval"] == 0
478
+ and noise_var > 0
479
+ )
480
+ or step_idx == num_sampling_steps - 1
481
+ ):
482
+ resample_indices = (
483
+ torch.multinomial(
484
+ resample_weights,
485
+ resample_weights.shape[1]
486
+ if step_idx < num_sampling_steps - 1
487
+ else 1,
488
+ replacement=True,
489
+ )
490
+ + resample_weights.shape[1]
491
+ * torch.arange(
492
+ resample_weights.shape[0], device=resample_weights.device
493
+ ).unsqueeze(-1)
494
+ ).flatten()
495
+
496
+ atom_coords = atom_coords[resample_indices]
497
+ atom_coords_noisy = atom_coords_noisy[resample_indices]
498
+ atom_mask = atom_mask[resample_indices]
499
+ if atom_coords_denoised is not None:
500
+ atom_coords_denoised = atom_coords_denoised[resample_indices]
501
+ energy_traj = energy_traj[resample_indices]
502
+ if (
503
+ steering_args["physical_guidance_update"]
504
+ or steering_args["contact_guidance_update"]
505
+ ):
506
+ scaled_guidance_update = scaled_guidance_update[
507
+ resample_indices
508
+ ]
509
+ if token_repr is not None:
510
+ token_repr = token_repr[resample_indices]
511
+
512
+ if self.alignment_reverse_diff:
513
+ with torch.autocast("cuda", enabled=False):
514
+ atom_coords_noisy = weighted_rigid_align(
515
+ atom_coords_noisy.float(),
516
+ atom_coords_denoised.float(),
517
+ atom_mask.float(),
518
+ atom_mask.float(),
519
+ )
520
+
521
+ atom_coords_noisy = atom_coords_noisy.to(atom_coords_denoised)
522
+
523
+ denoised_over_sigma = (atom_coords_noisy - atom_coords_denoised) / t_hat
524
+ atom_coords_next = (
525
+ atom_coords_noisy + step_scale * (sigma_t - t_hat) * denoised_over_sigma
526
+ )
527
+
528
+ atom_coords = atom_coords_next
529
+
530
+ return dict(sample_atom_coords=atom_coords, diff_token_repr=token_repr)
531
+
532
+ def loss_weight(self, sigma):
533
+ return (sigma**2 + self.sigma_data**2) / ((sigma * self.sigma_data) ** 2)
534
+
535
+ def noise_distribution(self, batch_size):
536
+ return (
537
+ self.sigma_data
538
+ * (
539
+ self.P_mean
540
+ + self.P_std * torch.randn((batch_size,), device=self.device)
541
+ ).exp()
542
+ )
543
+
544
+ def forward(
545
+ self,
546
+ s_inputs,
547
+ s_trunk,
548
+ feats,
549
+ diffusion_conditioning,
550
+ multiplicity=1,
551
+ ):
552
+ # training diffusion step
553
+ batch_size = feats["coords"].shape[0] // multiplicity
554
+
555
+ if self.synchronize_sigmas:
556
+ sigmas = self.noise_distribution(batch_size).repeat_interleave(
557
+ multiplicity, 0
558
+ )
559
+ else:
560
+ sigmas = self.noise_distribution(batch_size * multiplicity)
561
+ padded_sigmas = rearrange(sigmas, "b -> b 1 1")
562
+
563
+ atom_coords = feats["coords"]
564
+
565
+ atom_mask = feats["atom_pad_mask"]
566
+ atom_mask = atom_mask.repeat_interleave(multiplicity, 0)
567
+
568
+ atom_coords = center_random_augmentation(
569
+ atom_coords, atom_mask, augmentation=self.coordinate_augmentation
570
+ )
571
+
572
+ noise = torch.randn_like(atom_coords)
573
+ noised_atom_coords = atom_coords + padded_sigmas * noise
574
+
575
+ denoised_atom_coords = self.preconditioned_network_forward(
576
+ noised_atom_coords,
577
+ sigmas,
578
+ network_condition_kwargs={
579
+ "s_inputs": s_inputs,
580
+ "s_trunk": s_trunk,
581
+ "feats": feats,
582
+ "multiplicity": multiplicity,
583
+ "diffusion_conditioning": diffusion_conditioning,
584
+ },
585
+ )
586
+
587
+ return {
588
+ "denoised_atom_coords": denoised_atom_coords,
589
+ "sigmas": sigmas,
590
+ "aligned_true_atom_coords": atom_coords,
591
+ }
592
+
593
+ def compute_loss(
594
+ self,
595
+ feats,
596
+ out_dict,
597
+ add_smooth_lddt_loss=True,
598
+ nucleotide_loss_weight=5.0,
599
+ ligand_loss_weight=10.0,
600
+ multiplicity=1,
601
+ filter_by_plddt=0.0,
602
+ ):
603
+ with torch.autocast("cuda", enabled=False):
604
+ denoised_atom_coords = out_dict["denoised_atom_coords"].float()
605
+ sigmas = out_dict["sigmas"].float()
606
+
607
+ resolved_atom_mask_uni = feats["atom_resolved_mask"].float()
608
+
609
+ if filter_by_plddt > 0:
610
+ plddt_mask = feats["plddt"] > filter_by_plddt
611
+ resolved_atom_mask_uni = resolved_atom_mask_uni * plddt_mask.float()
612
+
613
+ resolved_atom_mask = resolved_atom_mask_uni.repeat_interleave(
614
+ multiplicity, 0
615
+ )
616
+
617
+ align_weights = denoised_atom_coords.new_ones(denoised_atom_coords.shape[:2])
618
+ atom_type = (
619
+ torch.bmm(
620
+ feats["atom_to_token"].float(),
621
+ feats["mol_type"].unsqueeze(-1).float(),
622
+ )
623
+ .squeeze(-1)
624
+ .long()
625
+ )
626
+ atom_type_mult = atom_type.repeat_interleave(multiplicity, 0)
627
+
628
+ align_weights = (
629
+ align_weights
630
+ * (
631
+ 1
632
+ + nucleotide_loss_weight
633
+ * (
634
+ torch.eq(atom_type_mult, const.chain_type_ids["DNA"]).float()
635
+ + torch.eq(atom_type_mult, const.chain_type_ids["RNA"]).float()
636
+ )
637
+ + ligand_loss_weight
638
+ * torch.eq(
639
+ atom_type_mult, const.chain_type_ids["NONPOLYMER"]
640
+ ).float()
641
+ ).float()
642
+ )
643
+
644
+ atom_coords = out_dict["aligned_true_atom_coords"].float()
645
+ atom_coords_aligned_ground_truth = weighted_rigid_align(
646
+ atom_coords.detach(),
647
+ denoised_atom_coords.detach(),
648
+ align_weights.detach(),
649
+ mask=feats["atom_resolved_mask"]
650
+ .float()
651
+ .repeat_interleave(multiplicity, 0)
652
+ .detach(),
653
+ )
654
+
655
+ # Cast back
656
+ atom_coords_aligned_ground_truth = atom_coords_aligned_ground_truth.to(
657
+ denoised_atom_coords
658
+ )
659
+
660
+ # weighted MSE loss of denoised atom positions
661
+ mse_loss = (
662
+ (denoised_atom_coords - atom_coords_aligned_ground_truth) ** 2
663
+ ).sum(dim=-1)
664
+ mse_loss = torch.sum(
665
+ mse_loss * align_weights * resolved_atom_mask, dim=-1
666
+ ) / (torch.sum(3 * align_weights * resolved_atom_mask, dim=-1) + 1e-5)
667
+
668
+ # weight by sigma factor
669
+ loss_weights = self.loss_weight(sigmas)
670
+ mse_loss = (mse_loss * loss_weights).mean()
671
+
672
+ total_loss = mse_loss
673
+
674
+ # proposed auxiliary smooth lddt loss
675
+ lddt_loss = self.zero
676
+ if add_smooth_lddt_loss:
677
+ lddt_loss = smooth_lddt_loss(
678
+ denoised_atom_coords,
679
+ feats["coords"],
680
+ torch.eq(atom_type, const.chain_type_ids["DNA"]).float()
681
+ + torch.eq(atom_type, const.chain_type_ids["RNA"]).float(),
682
+ coords_mask=resolved_atom_mask_uni,
683
+ multiplicity=multiplicity,
684
+ )
685
+
686
+ total_loss = total_loss + lddt_loss
687
+
688
+ loss_breakdown = {
689
+ "mse_loss": mse_loss,
690
+ "smooth_lddt_loss": lddt_loss,
691
+ }
692
+
693
+ return {"loss": total_loss, "loss_breakdown": loss_breakdown}
vb_modules_encodersv2.py CHANGED
@@ -1,565 +1,565 @@
1
- # started from code from https://github.com/lucidrains/alphafold3-pytorch, MIT License, Copyright (c) 2024 Phil Wang
2
- from functools import partial
3
- from math import pi
4
-
5
- import torch
6
- from einops import rearrange
7
- from torch import nn
8
- from torch.nn import Linear, Module, ModuleList
9
- from torch.nn.functional import one_hot
10
-
11
- from . import vb_layers_initialize as init
12
- from .vb_layers_transition import Transition
13
- from .vb_modules_transformersv2 import AtomTransformer
14
- from .vb_modules_utils import LinearNoBias
15
-
16
-
17
- class FourierEmbedding(Module):
18
- """Algorithm 22."""
19
-
20
- def __init__(self, dim):
21
- super().__init__()
22
- self.proj = nn.Linear(1, dim)
23
- torch.nn.init.normal_(self.proj.weight, mean=0, std=1)
24
- torch.nn.init.normal_(self.proj.bias, mean=0, std=1)
25
- self.proj.requires_grad_(False)
26
-
27
- def forward(
28
- self,
29
- times, # Float[' b'],
30
- ): # -> Float['b d']:
31
- times = rearrange(times, "b -> b 1")
32
- rand_proj = self.proj(times)
33
- return torch.cos(2 * pi * rand_proj)
34
-
35
-
36
- class RelativePositionEncoder(Module):
37
- """Algorithm 3."""
38
-
39
- def __init__(
40
- self, token_z, r_max=32, s_max=2, fix_sym_check=False, cyclic_pos_enc=False
41
- ):
42
- super().__init__()
43
- self.r_max = r_max
44
- self.s_max = s_max
45
- self.linear_layer = LinearNoBias(4 * (r_max + 1) + 2 * (s_max + 1) + 1, token_z)
46
- self.fix_sym_check = fix_sym_check
47
- self.cyclic_pos_enc = cyclic_pos_enc
48
-
49
- def forward(self, feats):
50
- b_same_chain = torch.eq(
51
- feats["asym_id"][:, :, None], feats["asym_id"][:, None, :]
52
- )
53
- b_same_residue = torch.eq(
54
- feats["residue_index"][:, :, None], feats["residue_index"][:, None, :]
55
- )
56
- b_same_entity = torch.eq(
57
- feats["entity_id"][:, :, None], feats["entity_id"][:, None, :]
58
- )
59
-
60
- d_residue = (
61
- feats["residue_index"][:, :, None] - feats["residue_index"][:, None, :]
62
- )
63
-
64
- if self.cyclic_pos_enc and torch.any(feats["cyclic_period"] > 0):
65
- period = torch.where(
66
- feats["cyclic_period"] > 0,
67
- feats["cyclic_period"],
68
- torch.zeros_like(feats["cyclic_period"]) + 10000,
69
- )
70
- d_residue = (d_residue - period * torch.round(d_residue / period)).long()
71
-
72
- d_residue = torch.clip(
73
- d_residue + self.r_max,
74
- 0,
75
- 2 * self.r_max,
76
- )
77
- d_residue = torch.where(
78
- b_same_chain, d_residue, torch.zeros_like(d_residue) + 2 * self.r_max + 1
79
- )
80
- a_rel_pos = one_hot(d_residue, 2 * self.r_max + 2)
81
-
82
- d_token = torch.clip(
83
- feats["token_index"][:, :, None]
84
- - feats["token_index"][:, None, :]
85
- + self.r_max,
86
- 0,
87
- 2 * self.r_max,
88
- )
89
- d_token = torch.where(
90
- b_same_chain & b_same_residue,
91
- d_token,
92
- torch.zeros_like(d_token) + 2 * self.r_max + 1,
93
- )
94
- a_rel_token = one_hot(d_token, 2 * self.r_max + 2)
95
-
96
- d_chain = torch.clip(
97
- feats["sym_id"][:, :, None] - feats["sym_id"][:, None, :] + self.s_max,
98
- 0,
99
- 2 * self.s_max,
100
- )
101
- d_chain = torch.where(
102
- (~b_same_entity) if self.fix_sym_check else b_same_chain,
103
- torch.zeros_like(d_chain) + 2 * self.s_max + 1,
104
- d_chain,
105
- )
106
- # Note: added | (~b_same_entity) based on observation of ProteinX manuscript
107
- a_rel_chain = one_hot(d_chain, 2 * self.s_max + 2)
108
-
109
- p = self.linear_layer(
110
- torch.cat(
111
- [
112
- a_rel_pos.float(),
113
- a_rel_token.float(),
114
- b_same_entity.unsqueeze(-1).float(),
115
- a_rel_chain.float(),
116
- ],
117
- dim=-1,
118
- )
119
- )
120
- return p
121
-
122
-
123
- class SingleConditioning(Module):
124
- """Algorithm 21."""
125
-
126
- def __init__(
127
- self,
128
- sigma_data: float,
129
- token_s: int = 384,
130
- dim_fourier: int = 256,
131
- num_transitions: int = 2,
132
- transition_expansion_factor: int = 2,
133
- eps: float = 1e-20,
134
- disable_times: bool = False,
135
- ) -> None:
136
- super().__init__()
137
- self.eps = eps
138
- self.sigma_data = sigma_data
139
- self.disable_times = disable_times
140
-
141
- self.norm_single = nn.LayerNorm(2 * token_s)
142
- self.single_embed = nn.Linear(2 * token_s, 2 * token_s)
143
- if not self.disable_times:
144
- self.fourier_embed = FourierEmbedding(dim_fourier)
145
- self.norm_fourier = nn.LayerNorm(dim_fourier)
146
- self.fourier_to_single = LinearNoBias(dim_fourier, 2 * token_s)
147
-
148
- transitions = ModuleList([])
149
- for _ in range(num_transitions):
150
- transition = Transition(
151
- dim=2 * token_s, hidden=transition_expansion_factor * 2 * token_s
152
- )
153
- transitions.append(transition)
154
-
155
- self.transitions = transitions
156
-
157
- def forward(
158
- self,
159
- times, # Float[' b'],
160
- s_trunk, # Float['b n ts'],
161
- s_inputs, # Float['b n ts'],
162
- ): # -> Float['b n 2ts']:
163
- s = torch.cat((s_trunk, s_inputs), dim=-1)
164
- s = self.single_embed(self.norm_single(s))
165
- if not self.disable_times:
166
- fourier_embed = self.fourier_embed(
167
- times
168
- ) # note: sigma rescaling done in diffusion module
169
- normed_fourier = self.norm_fourier(fourier_embed)
170
- fourier_to_single = self.fourier_to_single(normed_fourier)
171
-
172
- s = rearrange(fourier_to_single, "b d -> b 1 d") + s
173
-
174
- for transition in self.transitions:
175
- s = transition(s) + s
176
-
177
- return s, normed_fourier if not self.disable_times else None
178
-
179
-
180
- class PairwiseConditioning(Module):
181
- """Algorithm 21."""
182
-
183
- def __init__(
184
- self,
185
- token_z,
186
- dim_token_rel_pos_feats,
187
- num_transitions=2,
188
- transition_expansion_factor=2,
189
- ):
190
- super().__init__()
191
-
192
- self.dim_pairwise_init_proj = nn.Sequential(
193
- nn.LayerNorm(token_z + dim_token_rel_pos_feats),
194
- LinearNoBias(token_z + dim_token_rel_pos_feats, token_z),
195
- )
196
-
197
- transitions = ModuleList([])
198
- for _ in range(num_transitions):
199
- transition = Transition(
200
- dim=token_z, hidden=transition_expansion_factor * token_z
201
- )
202
- transitions.append(transition)
203
-
204
- self.transitions = transitions
205
-
206
- def forward(
207
- self,
208
- z_trunk, # Float['b n n tz'],
209
- token_rel_pos_feats, # Float['b n n 3'],
210
- ): # -> Float['b n n tz']:
211
- z = torch.cat((z_trunk, token_rel_pos_feats), dim=-1)
212
- z = self.dim_pairwise_init_proj(z)
213
-
214
- for transition in self.transitions:
215
- z = transition(z) + z
216
-
217
- return z
218
-
219
-
220
- def get_indexing_matrix(K, W, H, device):
221
- assert W % 2 == 0
222
- assert H % (W // 2) == 0
223
-
224
- h = H // (W // 2)
225
- assert h % 2 == 0
226
-
227
- arange = torch.arange(2 * K, device=device)
228
- index = ((arange.unsqueeze(0) - arange.unsqueeze(1)) + h // 2).clamp(
229
- min=0, max=h + 1
230
- )
231
- index = index.view(K, 2, 2 * K)[:, 0, :]
232
- onehot = one_hot(index, num_classes=h + 2)[..., 1:-1].transpose(1, 0)
233
- return onehot.reshape(2 * K, h * K).float()
234
-
235
-
236
- def single_to_keys(single, indexing_matrix, W, H):
237
- B, N, D = single.shape
238
- K = N // W
239
- single = single.view(B, 2 * K, W // 2, D)
240
- return torch.einsum("b j i d, j k -> b k i d", single, indexing_matrix).reshape(
241
- B, K, H, D
242
- ) # j = 2K, i = W//2, k = h * K
243
-
244
-
245
- class AtomEncoder(Module):
246
- def __init__(
247
- self,
248
- atom_s,
249
- atom_z,
250
- token_s,
251
- token_z,
252
- atoms_per_window_queries,
253
- atoms_per_window_keys,
254
- atom_feature_dim,
255
- structure_prediction=True,
256
- use_no_atom_char=False,
257
- use_atom_backbone_feat=False,
258
- use_residue_feats_atoms=False,
259
- ):
260
- super().__init__()
261
-
262
- self.embed_atom_features = Linear(atom_feature_dim, atom_s)
263
- self.embed_atompair_ref_pos = LinearNoBias(3, atom_z)
264
- self.embed_atompair_ref_dist = LinearNoBias(1, atom_z)
265
- self.embed_atompair_mask = LinearNoBias(1, atom_z)
266
- self.atoms_per_window_queries = atoms_per_window_queries
267
- self.atoms_per_window_keys = atoms_per_window_keys
268
- self.use_no_atom_char = use_no_atom_char
269
- self.use_atom_backbone_feat = use_atom_backbone_feat
270
- self.use_residue_feats_atoms = use_residue_feats_atoms
271
-
272
- self.structure_prediction = structure_prediction
273
- if structure_prediction:
274
- self.s_to_c_trans = nn.Sequential(
275
- nn.LayerNorm(token_s), LinearNoBias(token_s, atom_s)
276
- )
277
- init.final_init_(self.s_to_c_trans[1].weight)
278
-
279
- self.z_to_p_trans = nn.Sequential(
280
- nn.LayerNorm(token_z), LinearNoBias(token_z, atom_z)
281
- )
282
- init.final_init_(self.z_to_p_trans[1].weight)
283
-
284
- self.c_to_p_trans_k = nn.Sequential(
285
- nn.ReLU(),
286
- LinearNoBias(atom_s, atom_z),
287
- )
288
- init.final_init_(self.c_to_p_trans_k[1].weight)
289
-
290
- self.c_to_p_trans_q = nn.Sequential(
291
- nn.ReLU(),
292
- LinearNoBias(atom_s, atom_z),
293
- )
294
- init.final_init_(self.c_to_p_trans_q[1].weight)
295
-
296
- self.p_mlp = nn.Sequential(
297
- nn.ReLU(),
298
- LinearNoBias(atom_z, atom_z),
299
- nn.ReLU(),
300
- LinearNoBias(atom_z, atom_z),
301
- nn.ReLU(),
302
- LinearNoBias(atom_z, atom_z),
303
- )
304
- init.final_init_(self.p_mlp[5].weight)
305
-
306
- def forward(
307
- self,
308
- feats,
309
- s_trunk=None, # Float['bm n ts'],
310
- z=None, # Float['bm n n tz'],
311
- ):
312
- with torch.autocast("cuda", enabled=False):
313
- B, N, _ = feats["ref_pos"].shape
314
- atom_mask = feats["atom_pad_mask"].bool() # Bool['b m'],
315
-
316
- atom_ref_pos = feats["ref_pos"] # Float['b m 3'],
317
- atom_uid = feats["ref_space_uid"] # Long['b m'],
318
-
319
- atom_feats = [
320
- atom_ref_pos,
321
- feats["ref_charge"].unsqueeze(-1),
322
- feats["ref_element"],
323
- ]
324
- if not self.use_no_atom_char:
325
- atom_feats.append(feats["ref_atom_name_chars"].reshape(B, N, 4 * 64))
326
- if self.use_atom_backbone_feat:
327
- atom_feats.append(feats["atom_backbone_feat"])
328
- if self.use_residue_feats_atoms:
329
- res_feats = torch.cat(
330
- [
331
- feats["res_type"],
332
- feats["modified"].unsqueeze(-1),
333
- one_hot(feats["mol_type"], num_classes=4).float(),
334
- ],
335
- dim=-1,
336
- )
337
- atom_to_token = feats["atom_to_token"].float()
338
- atom_res_feats = torch.bmm(atom_to_token, res_feats)
339
- atom_feats.append(atom_res_feats)
340
-
341
- atom_feats = torch.cat(atom_feats, dim=-1)
342
-
343
- c = self.embed_atom_features(atom_feats)
344
-
345
- # note we are already creating the windows to make it more efficient
346
- W, H = self.atoms_per_window_queries, self.atoms_per_window_keys
347
- B, N = c.shape[:2]
348
- K = N // W
349
- keys_indexing_matrix = get_indexing_matrix(K, W, H, c.device)
350
- to_keys = partial(
351
- single_to_keys, indexing_matrix=keys_indexing_matrix, W=W, H=H
352
- )
353
-
354
- atom_ref_pos_queries = atom_ref_pos.view(B, K, W, 1, 3)
355
- atom_ref_pos_keys = to_keys(atom_ref_pos).view(B, K, 1, H, 3)
356
-
357
- d = atom_ref_pos_keys - atom_ref_pos_queries # Float['b k w h 3']
358
- d_norm = torch.sum(d * d, dim=-1, keepdim=True) # Float['b k w h 1']
359
- d_norm = 1 / (
360
- 1 + d_norm
361
- ) # AF3 feeds in the reciprocal of the distance norm
362
-
363
- atom_mask_queries = atom_mask.view(B, K, W, 1)
364
- atom_mask_keys = (
365
- to_keys(atom_mask.unsqueeze(-1).float()).view(B, K, 1, H).bool()
366
- )
367
- atom_uid_queries = atom_uid.view(B, K, W, 1)
368
- atom_uid_keys = (
369
- to_keys(atom_uid.unsqueeze(-1).float()).view(B, K, 1, H).long()
370
- )
371
- v = (
372
- (
373
- atom_mask_queries
374
- & atom_mask_keys
375
- & (atom_uid_queries == atom_uid_keys)
376
- )
377
- .float()
378
- .unsqueeze(-1)
379
- ) # Bool['b k w h 1']
380
-
381
- p = self.embed_atompair_ref_pos(d) * v
382
- p = p + self.embed_atompair_ref_dist(d_norm) * v
383
- p = p + self.embed_atompair_mask(v) * v
384
-
385
- q = c
386
-
387
- if self.structure_prediction:
388
- # run only in structure model not in initial encoding
389
- atom_to_token = feats["atom_to_token"].float() # Long['b m n'],
390
-
391
- s_to_c = self.s_to_c_trans(s_trunk.float())
392
- s_to_c = torch.bmm(atom_to_token, s_to_c)
393
- c = c + s_to_c.to(c)
394
-
395
- atom_to_token_queries = atom_to_token.view(
396
- B, K, W, atom_to_token.shape[-1]
397
- )
398
- atom_to_token_keys = to_keys(atom_to_token)
399
- z_to_p = self.z_to_p_trans(z.float())
400
- z_to_p = torch.einsum(
401
- "bijd,bwki,bwlj->bwkld",
402
- z_to_p,
403
- atom_to_token_queries,
404
- atom_to_token_keys,
405
- )
406
- p = p + z_to_p.to(p)
407
-
408
- p = p + self.c_to_p_trans_q(c.view(B, K, W, 1, c.shape[-1]))
409
- p = p + self.c_to_p_trans_k(to_keys(c).view(B, K, 1, H, c.shape[-1]))
410
- p = p + self.p_mlp(p)
411
- return q, c, p, to_keys
412
-
413
-
414
- class AtomAttentionEncoder(Module):
415
- def __init__(
416
- self,
417
- atom_s,
418
- token_s,
419
- atoms_per_window_queries,
420
- atoms_per_window_keys,
421
- atom_encoder_depth=3,
422
- atom_encoder_heads=4,
423
- structure_prediction=True,
424
- activation_checkpointing=False,
425
- transformer_post_layer_norm=False,
426
- ):
427
- super().__init__()
428
-
429
- self.structure_prediction = structure_prediction
430
- if structure_prediction:
431
- self.r_to_q_trans = LinearNoBias(3, atom_s)
432
- init.final_init_(self.r_to_q_trans.weight)
433
-
434
- self.atom_encoder = AtomTransformer(
435
- dim=atom_s,
436
- dim_single_cond=atom_s,
437
- attn_window_queries=atoms_per_window_queries,
438
- attn_window_keys=atoms_per_window_keys,
439
- depth=atom_encoder_depth,
440
- heads=atom_encoder_heads,
441
- activation_checkpointing=activation_checkpointing,
442
- post_layer_norm=transformer_post_layer_norm,
443
- )
444
-
445
- self.atom_to_token_trans = nn.Sequential(
446
- LinearNoBias(atom_s, 2 * token_s if structure_prediction else token_s),
447
- nn.ReLU(),
448
- )
449
-
450
- def forward(
451
- self,
452
- feats,
453
- q,
454
- c,
455
- atom_enc_bias,
456
- to_keys,
457
- r=None, # Float['bm m 3'],
458
- multiplicity=1,
459
- ):
460
- B, N, _ = feats["ref_pos"].shape
461
- atom_mask = feats["atom_pad_mask"].bool() # Bool['b m'],
462
-
463
- if self.structure_prediction:
464
- # only here the multiplicity kicks in because we use the different positions r
465
- q = q.repeat_interleave(multiplicity, 0)
466
- r_to_q = self.r_to_q_trans(r)
467
- q = q + r_to_q
468
-
469
- c = c.repeat_interleave(multiplicity, 0)
470
- atom_mask = atom_mask.repeat_interleave(multiplicity, 0)
471
-
472
- q = self.atom_encoder(
473
- q=q,
474
- mask=atom_mask,
475
- c=c,
476
- bias=atom_enc_bias,
477
- multiplicity=multiplicity,
478
- to_keys=to_keys,
479
- )
480
-
481
- with torch.autocast("cuda", enabled=False):
482
- q_to_a = self.atom_to_token_trans(q).float()
483
- atom_to_token = feats["atom_to_token"].float()
484
- atom_to_token = atom_to_token.repeat_interleave(multiplicity, 0)
485
- atom_to_token_mean = atom_to_token / (
486
- atom_to_token.sum(dim=1, keepdim=True) + 1e-6
487
- )
488
- a = torch.bmm(atom_to_token_mean.transpose(1, 2), q_to_a)
489
-
490
- a = a.to(q)
491
-
492
- return a, q, c, to_keys
493
-
494
-
495
- class AtomAttentionDecoder(Module):
496
- """Algorithm 6."""
497
-
498
- def __init__(
499
- self,
500
- atom_s,
501
- token_s,
502
- attn_window_queries,
503
- attn_window_keys,
504
- atom_decoder_depth=3,
505
- atom_decoder_heads=4,
506
- activation_checkpointing=False,
507
- transformer_post_layer_norm=False,
508
- ):
509
- super().__init__()
510
-
511
- self.a_to_q_trans = LinearNoBias(2 * token_s, atom_s)
512
- init.final_init_(self.a_to_q_trans.weight)
513
-
514
- self.atom_decoder = AtomTransformer(
515
- dim=atom_s,
516
- dim_single_cond=atom_s,
517
- attn_window_queries=attn_window_queries,
518
- attn_window_keys=attn_window_keys,
519
- depth=atom_decoder_depth,
520
- heads=atom_decoder_heads,
521
- activation_checkpointing=activation_checkpointing,
522
- post_layer_norm=transformer_post_layer_norm,
523
- )
524
-
525
- if transformer_post_layer_norm:
526
- self.atom_feat_to_atom_pos_update = LinearNoBias(atom_s, 3)
527
- init.final_init_(self.atom_feat_to_atom_pos_update.weight)
528
- else:
529
- self.atom_feat_to_atom_pos_update = nn.Sequential(
530
- nn.LayerNorm(atom_s), LinearNoBias(atom_s, 3)
531
- )
532
- init.final_init_(self.atom_feat_to_atom_pos_update[1].weight)
533
-
534
- def forward(
535
- self,
536
- a, # Float['bm n 2ts'],
537
- q, # Float['bm m as'],
538
- c, # Float['bm m as'],
539
- atom_dec_bias, # Float['bm m m az'],
540
- feats,
541
- to_keys,
542
- multiplicity=1,
543
- ):
544
- with torch.autocast("cuda", enabled=False):
545
- atom_to_token = feats["atom_to_token"].float()
546
- atom_to_token = atom_to_token.repeat_interleave(multiplicity, 0)
547
-
548
- a_to_q = self.a_to_q_trans(a.float())
549
- a_to_q = torch.bmm(atom_to_token, a_to_q)
550
-
551
- q = q + a_to_q.to(q)
552
- atom_mask = feats["atom_pad_mask"] # Bool['b m'],
553
- atom_mask = atom_mask.repeat_interleave(multiplicity, 0)
554
-
555
- q = self.atom_decoder(
556
- q=q,
557
- mask=atom_mask,
558
- c=c,
559
- bias=atom_dec_bias,
560
- multiplicity=multiplicity,
561
- to_keys=to_keys,
562
- )
563
-
564
- r_update = self.atom_feat_to_atom_pos_update(q)
565
- return r_update
 
1
+ # started from code from https://github.com/lucidrains/alphafold3-pytorch, MIT License, Copyright (c) 2024 Phil Wang
2
+ from functools import partial
3
+ from math import pi
4
+
5
+ import torch
6
+ from einops import rearrange
7
+ from torch import nn
8
+ from torch.nn import Linear, Module, ModuleList
9
+ from torch.nn.functional import one_hot
10
+
11
+ from . import vb_layers_initialize as init
12
+ from .vb_layers_transition import Transition
13
+ from .vb_modules_transformersv2 import AtomTransformer
14
+ from .vb_modules_utils import LinearNoBias
15
+
16
+
17
+ class FourierEmbedding(Module):
18
+ """Algorithm 22."""
19
+
20
+ def __init__(self, dim):
21
+ super().__init__()
22
+ self.proj = nn.Linear(1, dim)
23
+ torch.nn.init.normal_(self.proj.weight, mean=0, std=1)
24
+ torch.nn.init.normal_(self.proj.bias, mean=0, std=1)
25
+ self.proj.requires_grad_(False)
26
+
27
+ def forward(
28
+ self,
29
+ times, # Float[' b'],
30
+ ): # -> Float['b d']:
31
+ times = rearrange(times, "b -> b 1")
32
+ rand_proj = self.proj(times)
33
+ return torch.cos(2 * pi * rand_proj)
34
+
35
+
36
+ class RelativePositionEncoder(Module):
37
+ """Algorithm 3."""
38
+
39
+ def __init__(
40
+ self, token_z, r_max=32, s_max=2, fix_sym_check=False, cyclic_pos_enc=False
41
+ ):
42
+ super().__init__()
43
+ self.r_max = r_max
44
+ self.s_max = s_max
45
+ self.linear_layer = LinearNoBias(4 * (r_max + 1) + 2 * (s_max + 1) + 1, token_z)
46
+ self.fix_sym_check = fix_sym_check
47
+ self.cyclic_pos_enc = cyclic_pos_enc
48
+
49
+ def forward(self, feats):
50
+ b_same_chain = torch.eq(
51
+ feats["asym_id"][:, :, None], feats["asym_id"][:, None, :]
52
+ )
53
+ b_same_residue = torch.eq(
54
+ feats["residue_index"][:, :, None], feats["residue_index"][:, None, :]
55
+ )
56
+ b_same_entity = torch.eq(
57
+ feats["entity_id"][:, :, None], feats["entity_id"][:, None, :]
58
+ )
59
+
60
+ d_residue = (
61
+ feats["residue_index"][:, :, None] - feats["residue_index"][:, None, :]
62
+ )
63
+
64
+ if self.cyclic_pos_enc and torch.any(feats["cyclic_period"] > 0):
65
+ period = torch.where(
66
+ feats["cyclic_period"] > 0,
67
+ feats["cyclic_period"],
68
+ torch.zeros_like(feats["cyclic_period"]) + 10000,
69
+ )
70
+ d_residue = (d_residue - period * torch.round(d_residue / period)).long()
71
+
72
+ d_residue = torch.clip(
73
+ d_residue + self.r_max,
74
+ 0,
75
+ 2 * self.r_max,
76
+ )
77
+ d_residue = torch.where(
78
+ b_same_chain, d_residue, torch.zeros_like(d_residue) + 2 * self.r_max + 1
79
+ )
80
+ a_rel_pos = one_hot(d_residue, 2 * self.r_max + 2)
81
+
82
+ d_token = torch.clip(
83
+ feats["token_index"][:, :, None]
84
+ - feats["token_index"][:, None, :]
85
+ + self.r_max,
86
+ 0,
87
+ 2 * self.r_max,
88
+ )
89
+ d_token = torch.where(
90
+ b_same_chain & b_same_residue,
91
+ d_token,
92
+ torch.zeros_like(d_token) + 2 * self.r_max + 1,
93
+ )
94
+ a_rel_token = one_hot(d_token, 2 * self.r_max + 2)
95
+
96
+ d_chain = torch.clip(
97
+ feats["sym_id"][:, :, None] - feats["sym_id"][:, None, :] + self.s_max,
98
+ 0,
99
+ 2 * self.s_max,
100
+ )
101
+ d_chain = torch.where(
102
+ (~b_same_entity) if self.fix_sym_check else b_same_chain,
103
+ torch.zeros_like(d_chain) + 2 * self.s_max + 1,
104
+ d_chain,
105
+ )
106
+ # Note: added | (~b_same_entity) based on observation of ProteinX manuscript
107
+ a_rel_chain = one_hot(d_chain, 2 * self.s_max + 2)
108
+
109
+ p = self.linear_layer(
110
+ torch.cat(
111
+ [
112
+ a_rel_pos.float(),
113
+ a_rel_token.float(),
114
+ b_same_entity.unsqueeze(-1).float(),
115
+ a_rel_chain.float(),
116
+ ],
117
+ dim=-1,
118
+ )
119
+ )
120
+ return p
121
+
122
+
123
+ class SingleConditioning(Module):
124
+ """Algorithm 21."""
125
+
126
+ def __init__(
127
+ self,
128
+ sigma_data: float,
129
+ token_s: int = 384,
130
+ dim_fourier: int = 256,
131
+ num_transitions: int = 2,
132
+ transition_expansion_factor: int = 2,
133
+ eps: float = 1e-20,
134
+ disable_times: bool = False,
135
+ ) -> None:
136
+ super().__init__()
137
+ self.eps = eps
138
+ self.sigma_data = sigma_data
139
+ self.disable_times = disable_times
140
+
141
+ self.norm_single = nn.LayerNorm(2 * token_s)
142
+ self.single_embed = nn.Linear(2 * token_s, 2 * token_s)
143
+ if not self.disable_times:
144
+ self.fourier_embed = FourierEmbedding(dim_fourier)
145
+ self.norm_fourier = nn.LayerNorm(dim_fourier)
146
+ self.fourier_to_single = LinearNoBias(dim_fourier, 2 * token_s)
147
+
148
+ transitions = ModuleList([])
149
+ for _ in range(num_transitions):
150
+ transition = Transition(
151
+ dim=2 * token_s, hidden=transition_expansion_factor * 2 * token_s
152
+ )
153
+ transitions.append(transition)
154
+
155
+ self.transitions = transitions
156
+
157
+ def forward(
158
+ self,
159
+ times, # Float[' b'],
160
+ s_trunk, # Float['b n ts'],
161
+ s_inputs, # Float['b n ts'],
162
+ ): # -> Float['b n 2ts']:
163
+ s = torch.cat((s_trunk, s_inputs), dim=-1)
164
+ s = self.single_embed(self.norm_single(s))
165
+ if not self.disable_times:
166
+ fourier_embed = self.fourier_embed(
167
+ times
168
+ ) # note: sigma rescaling done in diffusion module
169
+ normed_fourier = self.norm_fourier(fourier_embed)
170
+ fourier_to_single = self.fourier_to_single(normed_fourier)
171
+
172
+ s = rearrange(fourier_to_single, "b d -> b 1 d") + s
173
+
174
+ for transition in self.transitions:
175
+ s = transition(s) + s
176
+
177
+ return s, normed_fourier if not self.disable_times else None
178
+
179
+
180
+ class PairwiseConditioning(Module):
181
+ """Algorithm 21."""
182
+
183
+ def __init__(
184
+ self,
185
+ token_z,
186
+ dim_token_rel_pos_feats,
187
+ num_transitions=2,
188
+ transition_expansion_factor=2,
189
+ ):
190
+ super().__init__()
191
+
192
+ self.dim_pairwise_init_proj = nn.Sequential(
193
+ nn.LayerNorm(token_z + dim_token_rel_pos_feats),
194
+ LinearNoBias(token_z + dim_token_rel_pos_feats, token_z),
195
+ )
196
+
197
+ transitions = ModuleList([])
198
+ for _ in range(num_transitions):
199
+ transition = Transition(
200
+ dim=token_z, hidden=transition_expansion_factor * token_z
201
+ )
202
+ transitions.append(transition)
203
+
204
+ self.transitions = transitions
205
+
206
+ def forward(
207
+ self,
208
+ z_trunk, # Float['b n n tz'],
209
+ token_rel_pos_feats, # Float['b n n 3'],
210
+ ): # -> Float['b n n tz']:
211
+ z = torch.cat((z_trunk, token_rel_pos_feats), dim=-1)
212
+ z = self.dim_pairwise_init_proj(z)
213
+
214
+ for transition in self.transitions:
215
+ z = transition(z) + z
216
+
217
+ return z
218
+
219
+
220
+ def get_indexing_matrix(K, W, H, device):
221
+ assert W % 2 == 0
222
+ assert H % (W // 2) == 0
223
+
224
+ h = H // (W // 2)
225
+ assert h % 2 == 0
226
+
227
+ arange = torch.arange(2 * K, device=device)
228
+ index = ((arange.unsqueeze(0) - arange.unsqueeze(1)) + h // 2).clamp(
229
+ min=0, max=h + 1
230
+ )
231
+ index = index.view(K, 2, 2 * K)[:, 0, :]
232
+ onehot = one_hot(index, num_classes=h + 2)[..., 1:-1].transpose(1, 0)
233
+ return onehot.reshape(2 * K, h * K).float()
234
+
235
+
236
+ def single_to_keys(single, indexing_matrix, W, H):
237
+ B, N, D = single.shape
238
+ K = N // W
239
+ single = single.view(B, 2 * K, W // 2, D)
240
+ return torch.einsum("b j i d, j k -> b k i d", single, indexing_matrix).reshape(
241
+ B, K, H, D
242
+ ) # j = 2K, i = W//2, k = h * K
243
+
244
+
245
+ class AtomEncoder(Module):
246
+ def __init__(
247
+ self,
248
+ atom_s,
249
+ atom_z,
250
+ token_s,
251
+ token_z,
252
+ atoms_per_window_queries,
253
+ atoms_per_window_keys,
254
+ atom_feature_dim,
255
+ structure_prediction=True,
256
+ use_no_atom_char=False,
257
+ use_atom_backbone_feat=False,
258
+ use_residue_feats_atoms=False,
259
+ ):
260
+ super().__init__()
261
+
262
+ self.embed_atom_features = Linear(atom_feature_dim, atom_s)
263
+ self.embed_atompair_ref_pos = LinearNoBias(3, atom_z)
264
+ self.embed_atompair_ref_dist = LinearNoBias(1, atom_z)
265
+ self.embed_atompair_mask = LinearNoBias(1, atom_z)
266
+ self.atoms_per_window_queries = atoms_per_window_queries
267
+ self.atoms_per_window_keys = atoms_per_window_keys
268
+ self.use_no_atom_char = use_no_atom_char
269
+ self.use_atom_backbone_feat = use_atom_backbone_feat
270
+ self.use_residue_feats_atoms = use_residue_feats_atoms
271
+
272
+ self.structure_prediction = structure_prediction
273
+ if structure_prediction:
274
+ self.s_to_c_trans = nn.Sequential(
275
+ nn.LayerNorm(token_s), LinearNoBias(token_s, atom_s)
276
+ )
277
+ init.final_init_(self.s_to_c_trans[1].weight)
278
+
279
+ self.z_to_p_trans = nn.Sequential(
280
+ nn.LayerNorm(token_z), LinearNoBias(token_z, atom_z)
281
+ )
282
+ init.final_init_(self.z_to_p_trans[1].weight)
283
+
284
+ self.c_to_p_trans_k = nn.Sequential(
285
+ nn.ReLU(),
286
+ LinearNoBias(atom_s, atom_z),
287
+ )
288
+ init.final_init_(self.c_to_p_trans_k[1].weight)
289
+
290
+ self.c_to_p_trans_q = nn.Sequential(
291
+ nn.ReLU(),
292
+ LinearNoBias(atom_s, atom_z),
293
+ )
294
+ init.final_init_(self.c_to_p_trans_q[1].weight)
295
+
296
+ self.p_mlp = nn.Sequential(
297
+ nn.ReLU(),
298
+ LinearNoBias(atom_z, atom_z),
299
+ nn.ReLU(),
300
+ LinearNoBias(atom_z, atom_z),
301
+ nn.ReLU(),
302
+ LinearNoBias(atom_z, atom_z),
303
+ )
304
+ init.final_init_(self.p_mlp[5].weight)
305
+
306
+ def forward(
307
+ self,
308
+ feats,
309
+ s_trunk=None, # Float['bm n ts'],
310
+ z=None, # Float['bm n n tz'],
311
+ ):
312
+ with torch.autocast("cuda", enabled=False):
313
+ B, N, _ = feats["ref_pos"].shape
314
+ atom_mask = feats["atom_pad_mask"].bool() # Bool['b m'],
315
+
316
+ atom_ref_pos = feats["ref_pos"] # Float['b m 3'],
317
+ atom_uid = feats["ref_space_uid"] # Long['b m'],
318
+
319
+ atom_feats = [
320
+ atom_ref_pos,
321
+ feats["ref_charge"].unsqueeze(-1),
322
+ feats["ref_element"],
323
+ ]
324
+ if not self.use_no_atom_char:
325
+ atom_feats.append(feats["ref_atom_name_chars"].reshape(B, N, 4 * 64))
326
+ if self.use_atom_backbone_feat:
327
+ atom_feats.append(feats["atom_backbone_feat"])
328
+ if self.use_residue_feats_atoms:
329
+ res_feats = torch.cat(
330
+ [
331
+ feats["res_type"],
332
+ feats["modified"].unsqueeze(-1),
333
+ one_hot(feats["mol_type"], num_classes=4).float(),
334
+ ],
335
+ dim=-1,
336
+ )
337
+ atom_to_token = feats["atom_to_token"].float()
338
+ atom_res_feats = torch.bmm(atom_to_token, res_feats)
339
+ atom_feats.append(atom_res_feats)
340
+
341
+ atom_feats = torch.cat(atom_feats, dim=-1)
342
+
343
+ c = self.embed_atom_features(atom_feats)
344
+
345
+ # note we are already creating the windows to make it more efficient
346
+ W, H = self.atoms_per_window_queries, self.atoms_per_window_keys
347
+ B, N = c.shape[:2]
348
+ K = N // W
349
+ keys_indexing_matrix = get_indexing_matrix(K, W, H, c.device)
350
+ to_keys = partial(
351
+ single_to_keys, indexing_matrix=keys_indexing_matrix, W=W, H=H
352
+ )
353
+
354
+ atom_ref_pos_queries = atom_ref_pos.view(B, K, W, 1, 3)
355
+ atom_ref_pos_keys = to_keys(atom_ref_pos).view(B, K, 1, H, 3)
356
+
357
+ d = atom_ref_pos_keys - atom_ref_pos_queries # Float['b k w h 3']
358
+ d_norm = torch.sum(d * d, dim=-1, keepdim=True) # Float['b k w h 1']
359
+ d_norm = 1 / (
360
+ 1 + d_norm
361
+ ) # AF3 feeds in the reciprocal of the distance norm
362
+
363
+ atom_mask_queries = atom_mask.view(B, K, W, 1)
364
+ atom_mask_keys = (
365
+ to_keys(atom_mask.unsqueeze(-1).float()).view(B, K, 1, H).bool()
366
+ )
367
+ atom_uid_queries = atom_uid.view(B, K, W, 1)
368
+ atom_uid_keys = (
369
+ to_keys(atom_uid.unsqueeze(-1).float()).view(B, K, 1, H).long()
370
+ )
371
+ v = (
372
+ (
373
+ atom_mask_queries
374
+ & atom_mask_keys
375
+ & (atom_uid_queries == atom_uid_keys)
376
+ )
377
+ .float()
378
+ .unsqueeze(-1)
379
+ ) # Bool['b k w h 1']
380
+
381
+ p = self.embed_atompair_ref_pos(d) * v
382
+ p = p + self.embed_atompair_ref_dist(d_norm) * v
383
+ p = p + self.embed_atompair_mask(v) * v
384
+
385
+ q = c
386
+
387
+ if self.structure_prediction:
388
+ # run only in structure model not in initial encoding
389
+ atom_to_token = feats["atom_to_token"].float() # Long['b m n'],
390
+
391
+ s_to_c = self.s_to_c_trans(s_trunk.float())
392
+ s_to_c = torch.bmm(atom_to_token, s_to_c)
393
+ c = c + s_to_c.to(c)
394
+
395
+ atom_to_token_queries = atom_to_token.view(
396
+ B, K, W, atom_to_token.shape[-1]
397
+ )
398
+ atom_to_token_keys = to_keys(atom_to_token)
399
+ z_to_p = self.z_to_p_trans(z.float())
400
+ z_to_p = torch.einsum(
401
+ "bijd,bwki,bwlj->bwkld",
402
+ z_to_p,
403
+ atom_to_token_queries,
404
+ atom_to_token_keys,
405
+ )
406
+ p = p + z_to_p.to(p)
407
+
408
+ p = p + self.c_to_p_trans_q(c.view(B, K, W, 1, c.shape[-1]))
409
+ p = p + self.c_to_p_trans_k(to_keys(c).view(B, K, 1, H, c.shape[-1]))
410
+ p = p + self.p_mlp(p)
411
+ return q, c, p, to_keys
412
+
413
+
414
+ class AtomAttentionEncoder(Module):
415
+ def __init__(
416
+ self,
417
+ atom_s,
418
+ token_s,
419
+ atoms_per_window_queries,
420
+ atoms_per_window_keys,
421
+ atom_encoder_depth=3,
422
+ atom_encoder_heads=4,
423
+ structure_prediction=True,
424
+ activation_checkpointing=False,
425
+ transformer_post_layer_norm=False,
426
+ ):
427
+ super().__init__()
428
+
429
+ self.structure_prediction = structure_prediction
430
+ if structure_prediction:
431
+ self.r_to_q_trans = LinearNoBias(3, atom_s)
432
+ init.final_init_(self.r_to_q_trans.weight)
433
+
434
+ self.atom_encoder = AtomTransformer(
435
+ dim=atom_s,
436
+ dim_single_cond=atom_s,
437
+ attn_window_queries=atoms_per_window_queries,
438
+ attn_window_keys=atoms_per_window_keys,
439
+ depth=atom_encoder_depth,
440
+ heads=atom_encoder_heads,
441
+ activation_checkpointing=activation_checkpointing,
442
+ post_layer_norm=transformer_post_layer_norm,
443
+ )
444
+
445
+ self.atom_to_token_trans = nn.Sequential(
446
+ LinearNoBias(atom_s, 2 * token_s if structure_prediction else token_s),
447
+ nn.ReLU(),
448
+ )
449
+
450
+ def forward(
451
+ self,
452
+ feats,
453
+ q,
454
+ c,
455
+ atom_enc_bias,
456
+ to_keys,
457
+ r=None, # Float['bm m 3'],
458
+ multiplicity=1,
459
+ ):
460
+ B, N, _ = feats["ref_pos"].shape
461
+ atom_mask = feats["atom_pad_mask"].bool() # Bool['b m'],
462
+
463
+ if self.structure_prediction:
464
+ # only here the multiplicity kicks in because we use the different positions r
465
+ q = q.repeat_interleave(multiplicity, 0)
466
+ r_to_q = self.r_to_q_trans(r)
467
+ q = q + r_to_q
468
+
469
+ c = c.repeat_interleave(multiplicity, 0)
470
+ atom_mask = atom_mask.repeat_interleave(multiplicity, 0)
471
+
472
+ q = self.atom_encoder(
473
+ q=q,
474
+ mask=atom_mask,
475
+ c=c,
476
+ bias=atom_enc_bias,
477
+ multiplicity=multiplicity,
478
+ to_keys=to_keys,
479
+ )
480
+
481
+ with torch.autocast("cuda", enabled=False):
482
+ q_to_a = self.atom_to_token_trans(q).float()
483
+ atom_to_token = feats["atom_to_token"].float()
484
+ atom_to_token = atom_to_token.repeat_interleave(multiplicity, 0)
485
+ atom_to_token_mean = atom_to_token / (
486
+ atom_to_token.sum(dim=1, keepdim=True) + 1e-6
487
+ )
488
+ a = torch.bmm(atom_to_token_mean.transpose(1, 2), q_to_a)
489
+
490
+ a = a.to(q)
491
+
492
+ return a, q, c, to_keys
493
+
494
+
495
+ class AtomAttentionDecoder(Module):
496
+ """Algorithm 6."""
497
+
498
+ def __init__(
499
+ self,
500
+ atom_s,
501
+ token_s,
502
+ attn_window_queries,
503
+ attn_window_keys,
504
+ atom_decoder_depth=3,
505
+ atom_decoder_heads=4,
506
+ activation_checkpointing=False,
507
+ transformer_post_layer_norm=False,
508
+ ):
509
+ super().__init__()
510
+
511
+ self.a_to_q_trans = LinearNoBias(2 * token_s, atom_s)
512
+ init.final_init_(self.a_to_q_trans.weight)
513
+
514
+ self.atom_decoder = AtomTransformer(
515
+ dim=atom_s,
516
+ dim_single_cond=atom_s,
517
+ attn_window_queries=attn_window_queries,
518
+ attn_window_keys=attn_window_keys,
519
+ depth=atom_decoder_depth,
520
+ heads=atom_decoder_heads,
521
+ activation_checkpointing=activation_checkpointing,
522
+ post_layer_norm=transformer_post_layer_norm,
523
+ )
524
+
525
+ if transformer_post_layer_norm:
526
+ self.atom_feat_to_atom_pos_update = LinearNoBias(atom_s, 3)
527
+ init.final_init_(self.atom_feat_to_atom_pos_update.weight)
528
+ else:
529
+ self.atom_feat_to_atom_pos_update = nn.Sequential(
530
+ nn.LayerNorm(atom_s), LinearNoBias(atom_s, 3)
531
+ )
532
+ init.final_init_(self.atom_feat_to_atom_pos_update[1].weight)
533
+
534
+ def forward(
535
+ self,
536
+ a, # Float['bm n 2ts'],
537
+ q, # Float['bm m as'],
538
+ c, # Float['bm m as'],
539
+ atom_dec_bias, # Float['bm m m az'],
540
+ feats,
541
+ to_keys,
542
+ multiplicity=1,
543
+ ):
544
+ with torch.autocast("cuda", enabled=False):
545
+ atom_to_token = feats["atom_to_token"].float()
546
+ atom_to_token = atom_to_token.repeat_interleave(multiplicity, 0)
547
+
548
+ a_to_q = self.a_to_q_trans(a.float())
549
+ a_to_q = torch.bmm(atom_to_token, a_to_q)
550
+
551
+ q = q + a_to_q.to(q)
552
+ atom_mask = feats["atom_pad_mask"] # Bool['b m'],
553
+ atom_mask = atom_mask.repeat_interleave(multiplicity, 0)
554
+
555
+ q = self.atom_decoder(
556
+ q=q,
557
+ mask=atom_mask,
558
+ c=c,
559
+ bias=atom_dec_bias,
560
+ multiplicity=multiplicity,
561
+ to_keys=to_keys,
562
+ )
563
+
564
+ r_update = self.atom_feat_to_atom_pos_update(q)
565
+ return r_update
vb_modules_transformersv2.py CHANGED
@@ -1,262 +1,262 @@
1
- # started from code from https://github.com/lucidrains/alphafold3-pytorch, MIT License, Copyright (c) 2024 Phil Wang
2
-
3
- import torch
4
- from torch import nn, sigmoid
5
- from torch.nn import (
6
- LayerNorm,
7
- Linear,
8
- Module,
9
- ModuleList,
10
- Sequential,
11
- )
12
-
13
- from .vb_layers_attentionv2 import AttentionPairBias
14
- from .vb_modules_utils import LinearNoBias, SwiGLU, default
15
-
16
-
17
- class AdaLN(Module):
18
- """Algorithm 26"""
19
-
20
- def __init__(self, dim, dim_single_cond):
21
- super().__init__()
22
- self.a_norm = LayerNorm(dim, elementwise_affine=False, bias=False)
23
- self.s_norm = LayerNorm(dim_single_cond, bias=False)
24
- self.s_scale = Linear(dim_single_cond, dim)
25
- self.s_bias = LinearNoBias(dim_single_cond, dim)
26
-
27
- def forward(self, a, s):
28
- a = self.a_norm(a)
29
- s = self.s_norm(s)
30
- a = sigmoid(self.s_scale(s)) * a + self.s_bias(s)
31
- return a
32
-
33
-
34
- class ConditionedTransitionBlock(Module):
35
- """Algorithm 25"""
36
-
37
- def __init__(self, dim_single, dim_single_cond, expansion_factor=2):
38
- super().__init__()
39
-
40
- self.adaln = AdaLN(dim_single, dim_single_cond)
41
-
42
- dim_inner = int(dim_single * expansion_factor)
43
- self.swish_gate = Sequential(
44
- LinearNoBias(dim_single, dim_inner * 2),
45
- SwiGLU(),
46
- )
47
- self.a_to_b = LinearNoBias(dim_single, dim_inner)
48
- self.b_to_a = LinearNoBias(dim_inner, dim_single)
49
-
50
- output_projection_linear = Linear(dim_single_cond, dim_single)
51
- nn.init.zeros_(output_projection_linear.weight)
52
- nn.init.constant_(output_projection_linear.bias, -2.0)
53
-
54
- self.output_projection = nn.Sequential(output_projection_linear, nn.Sigmoid())
55
-
56
- def forward(
57
- self,
58
- a, # Float['... d']
59
- s,
60
- ): # -> Float['... d']:
61
- a = self.adaln(a, s)
62
- b = self.swish_gate(a) * self.a_to_b(a)
63
- a = self.output_projection(s) * self.b_to_a(b)
64
-
65
- return a
66
-
67
-
68
- class DiffusionTransformer(Module):
69
- """Algorithm 23"""
70
-
71
- def __init__(
72
- self,
73
- depth,
74
- heads,
75
- dim=384,
76
- dim_single_cond=None,
77
- pair_bias_attn=True,
78
- activation_checkpointing=False,
79
- post_layer_norm=False,
80
- ):
81
- super().__init__()
82
- self.activation_checkpointing = activation_checkpointing
83
- dim_single_cond = default(dim_single_cond, dim)
84
- self.pair_bias_attn = pair_bias_attn
85
-
86
- self.layers = ModuleList()
87
- for _ in range(depth):
88
- self.layers.append(
89
- DiffusionTransformerLayer(
90
- heads,
91
- dim,
92
- dim_single_cond,
93
- post_layer_norm,
94
- )
95
- )
96
-
97
- def forward(
98
- self,
99
- a, # Float['bm n d'],
100
- s, # Float['bm n ds'],
101
- bias=None, # Float['b n n dp']
102
- mask=None, # Bool['b n'] | None = None
103
- to_keys=None,
104
- multiplicity=1,
105
- ):
106
- if self.pair_bias_attn:
107
- B, N, M, D = bias.shape
108
- L = len(self.layers)
109
- bias = bias.view(B, N, M, L, D // L)
110
-
111
- for i, layer in enumerate(self.layers):
112
- if self.pair_bias_attn:
113
- bias_l = bias[:, :, :, i]
114
- else:
115
- bias_l = None
116
-
117
- if self.activation_checkpointing and self.training:
118
- a = torch.utils.checkpoint.checkpoint(
119
- layer,
120
- a,
121
- s,
122
- bias_l,
123
- mask,
124
- to_keys,
125
- multiplicity,
126
- )
127
-
128
- else:
129
- a = layer(
130
- a, # Float['bm n d'],
131
- s, # Float['bm n ds'],
132
- bias_l, # Float['b n n dp']
133
- mask, # Bool['b n'] | None = None
134
- to_keys,
135
- multiplicity,
136
- )
137
- return a
138
-
139
-
140
- class DiffusionTransformerLayer(Module):
141
- """Algorithm 23"""
142
-
143
- def __init__(
144
- self,
145
- heads,
146
- dim=384,
147
- dim_single_cond=None,
148
- post_layer_norm=False,
149
- ):
150
- super().__init__()
151
-
152
- dim_single_cond = default(dim_single_cond, dim)
153
-
154
- self.adaln = AdaLN(dim, dim_single_cond)
155
- self.pair_bias_attn = AttentionPairBias(
156
- c_s=dim, num_heads=heads, compute_pair_bias=False
157
- )
158
-
159
- self.output_projection_linear = Linear(dim_single_cond, dim)
160
- nn.init.zeros_(self.output_projection_linear.weight)
161
- nn.init.constant_(self.output_projection_linear.bias, -2.0)
162
-
163
- self.output_projection = nn.Sequential(
164
- self.output_projection_linear, nn.Sigmoid()
165
- )
166
- self.transition = ConditionedTransitionBlock(
167
- dim_single=dim, dim_single_cond=dim_single_cond
168
- )
169
-
170
- if post_layer_norm:
171
- self.post_lnorm = nn.LayerNorm(dim)
172
- else:
173
- self.post_lnorm = nn.Identity()
174
-
175
- def forward(
176
- self,
177
- a, # Float['bm n d'],
178
- s, # Float['bm n ds'],
179
- bias=None, # Float['b n n dp']
180
- mask=None, # Bool['b n'] | None = None
181
- to_keys=None,
182
- multiplicity=1,
183
- ):
184
- b = self.adaln(a, s)
185
-
186
- k_in = b
187
- if to_keys is not None:
188
- k_in = to_keys(b)
189
- mask = to_keys(mask.unsqueeze(-1)).squeeze(-1)
190
-
191
- if self.pair_bias_attn:
192
- b = self.pair_bias_attn(
193
- s=b,
194
- z=bias,
195
- mask=mask,
196
- multiplicity=multiplicity,
197
- k_in=k_in,
198
- )
199
- else:
200
- b = self.no_pair_bias_attn(s=b, mask=mask, k_in=k_in)
201
-
202
- b = self.output_projection(s) * b
203
-
204
- a = a + b
205
- a = a + self.transition(a, s)
206
-
207
- a = self.post_lnorm(a)
208
- return a
209
-
210
-
211
- class AtomTransformer(Module):
212
- """Algorithm 7"""
213
-
214
- def __init__(
215
- self,
216
- attn_window_queries,
217
- attn_window_keys,
218
- **diffusion_transformer_kwargs,
219
- ):
220
- super().__init__()
221
- self.attn_window_queries = attn_window_queries
222
- self.attn_window_keys = attn_window_keys
223
- self.diffusion_transformer = DiffusionTransformer(
224
- **diffusion_transformer_kwargs
225
- )
226
-
227
- def forward(
228
- self,
229
- q, # Float['b m d'],
230
- c, # Float['b m ds'],
231
- bias, # Float['b m m dp']
232
- to_keys,
233
- mask, # Bool['b m'] | None = None
234
- multiplicity=1,
235
- ):
236
- W = self.attn_window_queries
237
- H = self.attn_window_keys
238
-
239
- B, N, D = q.shape
240
- NW = N // W
241
-
242
- # reshape tokens
243
- q = q.view((B * NW, W, -1))
244
- c = c.view((B * NW, W, -1))
245
- mask = mask.view(B * NW, W)
246
- bias = bias.repeat_interleave(multiplicity, 0)
247
- bias = bias.view((bias.shape[0] * NW, W, H, -1))
248
-
249
- to_keys_new = lambda x: to_keys(x.view(B, NW * W, -1)).view(B * NW, H, -1)
250
-
251
- # main transformer
252
- q = self.diffusion_transformer(
253
- a=q,
254
- s=c,
255
- bias=bias,
256
- mask=mask.float(),
257
- multiplicity=1, # bias term already expanded with multiplicity
258
- to_keys=to_keys_new,
259
- )
260
-
261
- q = q.view((B, NW * W, D))
262
- return q
 
1
+ # started from code from https://github.com/lucidrains/alphafold3-pytorch, MIT License, Copyright (c) 2024 Phil Wang
2
+
3
+ import torch
4
+ from torch import nn, sigmoid
5
+ from torch.nn import (
6
+ LayerNorm,
7
+ Linear,
8
+ Module,
9
+ ModuleList,
10
+ Sequential,
11
+ )
12
+
13
+ from .vb_layers_attentionv2 import AttentionPairBias
14
+ from .vb_modules_utils import LinearNoBias, SwiGLU, default
15
+
16
+
17
+ class AdaLN(Module):
18
+ """Algorithm 26"""
19
+
20
+ def __init__(self, dim, dim_single_cond):
21
+ super().__init__()
22
+ self.a_norm = LayerNorm(dim, elementwise_affine=False, bias=False)
23
+ self.s_norm = LayerNorm(dim_single_cond, bias=False)
24
+ self.s_scale = Linear(dim_single_cond, dim)
25
+ self.s_bias = LinearNoBias(dim_single_cond, dim)
26
+
27
+ def forward(self, a, s):
28
+ a = self.a_norm(a)
29
+ s = self.s_norm(s)
30
+ a = sigmoid(self.s_scale(s)) * a + self.s_bias(s)
31
+ return a
32
+
33
+
34
+ class ConditionedTransitionBlock(Module):
35
+ """Algorithm 25"""
36
+
37
+ def __init__(self, dim_single, dim_single_cond, expansion_factor=2):
38
+ super().__init__()
39
+
40
+ self.adaln = AdaLN(dim_single, dim_single_cond)
41
+
42
+ dim_inner = int(dim_single * expansion_factor)
43
+ self.swish_gate = Sequential(
44
+ LinearNoBias(dim_single, dim_inner * 2),
45
+ SwiGLU(),
46
+ )
47
+ self.a_to_b = LinearNoBias(dim_single, dim_inner)
48
+ self.b_to_a = LinearNoBias(dim_inner, dim_single)
49
+
50
+ output_projection_linear = Linear(dim_single_cond, dim_single)
51
+ nn.init.zeros_(output_projection_linear.weight)
52
+ nn.init.constant_(output_projection_linear.bias, -2.0)
53
+
54
+ self.output_projection = nn.Sequential(output_projection_linear, nn.Sigmoid())
55
+
56
+ def forward(
57
+ self,
58
+ a, # Float['... d']
59
+ s,
60
+ ): # -> Float['... d']:
61
+ a = self.adaln(a, s)
62
+ b = self.swish_gate(a) * self.a_to_b(a)
63
+ a = self.output_projection(s) * self.b_to_a(b)
64
+
65
+ return a
66
+
67
+
68
+ class DiffusionTransformer(Module):
69
+ """Algorithm 23"""
70
+
71
+ def __init__(
72
+ self,
73
+ depth,
74
+ heads,
75
+ dim=384,
76
+ dim_single_cond=None,
77
+ pair_bias_attn=True,
78
+ activation_checkpointing=False,
79
+ post_layer_norm=False,
80
+ ):
81
+ super().__init__()
82
+ self.activation_checkpointing = activation_checkpointing
83
+ dim_single_cond = default(dim_single_cond, dim)
84
+ self.pair_bias_attn = pair_bias_attn
85
+
86
+ self.layers = ModuleList()
87
+ for _ in range(depth):
88
+ self.layers.append(
89
+ DiffusionTransformerLayer(
90
+ heads,
91
+ dim,
92
+ dim_single_cond,
93
+ post_layer_norm,
94
+ )
95
+ )
96
+
97
+ def forward(
98
+ self,
99
+ a, # Float['bm n d'],
100
+ s, # Float['bm n ds'],
101
+ bias=None, # Float['b n n dp']
102
+ mask=None, # Bool['b n'] | None = None
103
+ to_keys=None,
104
+ multiplicity=1,
105
+ ):
106
+ if self.pair_bias_attn:
107
+ B, N, M, D = bias.shape
108
+ L = len(self.layers)
109
+ bias = bias.view(B, N, M, L, D // L)
110
+
111
+ for i, layer in enumerate(self.layers):
112
+ if self.pair_bias_attn:
113
+ bias_l = bias[:, :, :, i]
114
+ else:
115
+ bias_l = None
116
+
117
+ if self.activation_checkpointing and self.training:
118
+ a = torch.utils.checkpoint.checkpoint(
119
+ layer,
120
+ a,
121
+ s,
122
+ bias_l,
123
+ mask,
124
+ to_keys,
125
+ multiplicity,
126
+ )
127
+
128
+ else:
129
+ a = layer(
130
+ a, # Float['bm n d'],
131
+ s, # Float['bm n ds'],
132
+ bias_l, # Float['b n n dp']
133
+ mask, # Bool['b n'] | None = None
134
+ to_keys,
135
+ multiplicity,
136
+ )
137
+ return a
138
+
139
+
140
+ class DiffusionTransformerLayer(Module):
141
+ """Algorithm 23"""
142
+
143
+ def __init__(
144
+ self,
145
+ heads,
146
+ dim=384,
147
+ dim_single_cond=None,
148
+ post_layer_norm=False,
149
+ ):
150
+ super().__init__()
151
+
152
+ dim_single_cond = default(dim_single_cond, dim)
153
+
154
+ self.adaln = AdaLN(dim, dim_single_cond)
155
+ self.pair_bias_attn = AttentionPairBias(
156
+ c_s=dim, num_heads=heads, compute_pair_bias=False
157
+ )
158
+
159
+ self.output_projection_linear = Linear(dim_single_cond, dim)
160
+ nn.init.zeros_(self.output_projection_linear.weight)
161
+ nn.init.constant_(self.output_projection_linear.bias, -2.0)
162
+
163
+ self.output_projection = nn.Sequential(
164
+ self.output_projection_linear, nn.Sigmoid()
165
+ )
166
+ self.transition = ConditionedTransitionBlock(
167
+ dim_single=dim, dim_single_cond=dim_single_cond
168
+ )
169
+
170
+ if post_layer_norm:
171
+ self.post_lnorm = nn.LayerNorm(dim)
172
+ else:
173
+ self.post_lnorm = nn.Identity()
174
+
175
+ def forward(
176
+ self,
177
+ a, # Float['bm n d'],
178
+ s, # Float['bm n ds'],
179
+ bias=None, # Float['b n n dp']
180
+ mask=None, # Bool['b n'] | None = None
181
+ to_keys=None,
182
+ multiplicity=1,
183
+ ):
184
+ b = self.adaln(a, s)
185
+
186
+ k_in = b
187
+ if to_keys is not None:
188
+ k_in = to_keys(b)
189
+ mask = to_keys(mask.unsqueeze(-1)).squeeze(-1)
190
+
191
+ if self.pair_bias_attn:
192
+ b = self.pair_bias_attn(
193
+ s=b,
194
+ z=bias,
195
+ mask=mask,
196
+ multiplicity=multiplicity,
197
+ k_in=k_in,
198
+ )
199
+ else:
200
+ b = self.no_pair_bias_attn(s=b, mask=mask, k_in=k_in)
201
+
202
+ b = self.output_projection(s) * b
203
+
204
+ a = a + b
205
+ a = a + self.transition(a, s)
206
+
207
+ a = self.post_lnorm(a)
208
+ return a
209
+
210
+
211
+ class AtomTransformer(Module):
212
+ """Algorithm 7"""
213
+
214
+ def __init__(
215
+ self,
216
+ attn_window_queries,
217
+ attn_window_keys,
218
+ **diffusion_transformer_kwargs,
219
+ ):
220
+ super().__init__()
221
+ self.attn_window_queries = attn_window_queries
222
+ self.attn_window_keys = attn_window_keys
223
+ self.diffusion_transformer = DiffusionTransformer(
224
+ **diffusion_transformer_kwargs
225
+ )
226
+
227
+ def forward(
228
+ self,
229
+ q, # Float['b m d'],
230
+ c, # Float['b m ds'],
231
+ bias, # Float['b m m dp']
232
+ to_keys,
233
+ mask, # Bool['b m'] | None = None
234
+ multiplicity=1,
235
+ ):
236
+ W = self.attn_window_queries
237
+ H = self.attn_window_keys
238
+
239
+ B, N, D = q.shape
240
+ NW = N // W
241
+
242
+ # reshape tokens
243
+ q = q.view((B * NW, W, -1))
244
+ c = c.view((B * NW, W, -1))
245
+ mask = mask.view(B * NW, W)
246
+ bias = bias.repeat_interleave(multiplicity, 0)
247
+ bias = bias.view((bias.shape[0] * NW, W, H, -1))
248
+
249
+ to_keys_new = lambda x: to_keys(x.view(B, NW * W, -1)).view(B * NW, H, -1)
250
+
251
+ # main transformer
252
+ q = self.diffusion_transformer(
253
+ a=q,
254
+ s=c,
255
+ bias=bias,
256
+ mask=mask.float(),
257
+ multiplicity=1, # bias term already expanded with multiplicity
258
+ to_keys=to_keys_new,
259
+ )
260
+
261
+ q = q.view((B, NW * W, D))
262
+ return q
vb_modules_trunkv2.py CHANGED
@@ -1,828 +1,828 @@
1
- import torch
2
- from torch import Tensor, nn
3
- from torch.nn.functional import one_hot
4
-
5
- from . import vb_const as const
6
- from .vb_layers_outer_product_mean import OuterProductMean
7
- from .vb_layers_pair_averaging import PairWeightedAveraging
8
- from .vb_layers_pairformer import (
9
- PairformerNoSeqLayer,
10
- PairformerNoSeqModule,
11
- get_dropout_mask,
12
- )
13
- from .vb_layers_transition import Transition
14
- from .vb_modules_encodersv2 import (
15
- AtomAttentionEncoder,
16
- AtomEncoder,
17
- FourierEmbedding,
18
- )
19
-
20
-
21
- class ContactConditioning(nn.Module):
22
- def __init__(self, token_z: int, cutoff_min: float, cutoff_max: float):
23
- super().__init__()
24
-
25
- self.fourier_embedding = FourierEmbedding(token_z)
26
- self.encoder = nn.Linear(
27
- token_z + len(const.contact_conditioning_info) - 1, token_z
28
- )
29
- self.encoding_unspecified = nn.Parameter(torch.zeros(token_z))
30
- self.encoding_unselected = nn.Parameter(torch.zeros(token_z))
31
- self.cutoff_min = cutoff_min
32
- self.cutoff_max = cutoff_max
33
-
34
- def forward(self, feats):
35
- assert const.contact_conditioning_info["UNSPECIFIED"] == 0
36
- assert const.contact_conditioning_info["UNSELECTED"] == 1
37
- contact_conditioning = feats["contact_conditioning"][:, :, :, 2:]
38
- contact_threshold = feats["contact_threshold"]
39
- contact_threshold_normalized = (contact_threshold - self.cutoff_min) / (
40
- self.cutoff_max - self.cutoff_min
41
- )
42
- contact_threshold_fourier = self.fourier_embedding(
43
- contact_threshold_normalized.flatten()
44
- ).reshape(contact_threshold_normalized.shape + (-1,))
45
-
46
- contact_conditioning = torch.cat(
47
- [
48
- contact_conditioning,
49
- contact_threshold_normalized.unsqueeze(-1),
50
- contact_threshold_fourier,
51
- ],
52
- dim=-1,
53
- )
54
- contact_conditioning = self.encoder(contact_conditioning)
55
-
56
- contact_conditioning = (
57
- contact_conditioning
58
- * (
59
- 1
60
- - feats["contact_conditioning"][:, :, :, 0:2].sum(dim=-1, keepdim=True)
61
- )
62
- + self.encoding_unspecified * feats["contact_conditioning"][:, :, :, 0:1]
63
- + self.encoding_unselected * feats["contact_conditioning"][:, :, :, 1:2]
64
- )
65
- return contact_conditioning
66
-
67
-
68
- class InputEmbedder(nn.Module):
69
- def __init__(
70
- self,
71
- atom_s: int,
72
- atom_z: int,
73
- token_s: int,
74
- token_z: int,
75
- atoms_per_window_queries: int,
76
- atoms_per_window_keys: int,
77
- atom_feature_dim: int,
78
- atom_encoder_depth: int,
79
- atom_encoder_heads: int,
80
- activation_checkpointing: bool = False,
81
- add_method_conditioning: bool = False,
82
- add_modified_flag: bool = False,
83
- add_cyclic_flag: bool = False,
84
- add_mol_type_feat: bool = False,
85
- use_no_atom_char: bool = False,
86
- use_atom_backbone_feat: bool = False,
87
- use_residue_feats_atoms: bool = False,
88
- ) -> None:
89
- """Initialize the input embedder.
90
-
91
- Parameters
92
- ----------
93
- atom_s : int
94
- The atom embedding size.
95
- atom_z : int
96
- The atom pairwise embedding size.
97
- token_s : int
98
- The token embedding size.
99
-
100
- """
101
- super().__init__()
102
- self.token_s = token_s
103
- self.add_method_conditioning = add_method_conditioning
104
- self.add_modified_flag = add_modified_flag
105
- self.add_cyclic_flag = add_cyclic_flag
106
- self.add_mol_type_feat = add_mol_type_feat
107
-
108
- self.atom_encoder = AtomEncoder(
109
- atom_s=atom_s,
110
- atom_z=atom_z,
111
- token_s=token_s,
112
- token_z=token_z,
113
- atoms_per_window_queries=atoms_per_window_queries,
114
- atoms_per_window_keys=atoms_per_window_keys,
115
- atom_feature_dim=atom_feature_dim,
116
- structure_prediction=False,
117
- use_no_atom_char=use_no_atom_char,
118
- use_atom_backbone_feat=use_atom_backbone_feat,
119
- use_residue_feats_atoms=use_residue_feats_atoms,
120
- )
121
-
122
- self.atom_enc_proj_z = nn.Sequential(
123
- nn.LayerNorm(atom_z),
124
- nn.Linear(atom_z, atom_encoder_depth * atom_encoder_heads, bias=False),
125
- )
126
-
127
- self.atom_attention_encoder = AtomAttentionEncoder(
128
- atom_s=atom_s,
129
- token_s=token_s,
130
- atoms_per_window_queries=atoms_per_window_queries,
131
- atoms_per_window_keys=atoms_per_window_keys,
132
- atom_encoder_depth=atom_encoder_depth,
133
- atom_encoder_heads=atom_encoder_heads,
134
- structure_prediction=False,
135
- activation_checkpointing=activation_checkpointing,
136
- )
137
-
138
- self.res_type_encoding = nn.Linear(const.num_tokens, token_s, bias=False)
139
- self.msa_profile_encoding = nn.Linear(const.num_tokens + 1, token_s, bias=False)
140
-
141
- if add_method_conditioning:
142
- self.method_conditioning_init = nn.Embedding(
143
- const.num_method_types, token_s
144
- )
145
- self.method_conditioning_init.weight.data.fill_(0)
146
- if add_modified_flag:
147
- self.modified_conditioning_init = nn.Embedding(2, token_s)
148
- self.modified_conditioning_init.weight.data.fill_(0)
149
- if add_cyclic_flag:
150
- self.cyclic_conditioning_init = nn.Linear(1, token_s, bias=False)
151
- self.cyclic_conditioning_init.weight.data.fill_(0)
152
- if add_mol_type_feat:
153
- self.mol_type_conditioning_init = nn.Embedding(
154
- len(const.chain_type_ids), token_s
155
- )
156
- self.mol_type_conditioning_init.weight.data.fill_(0)
157
-
158
- def forward(self, feats: dict[str, Tensor], affinity: bool = False) -> Tensor:
159
- """Perform the forward pass.
160
-
161
- Parameters
162
- ----------
163
- feats : dict[str, Tensor]
164
- Input features
165
-
166
- Returns
167
- -------
168
- Tensor
169
- The embedded tokens.
170
-
171
- """
172
- # Load relevant features
173
- res_type = feats["res_type"].float()
174
- if affinity:
175
- profile = feats["profile_affinity"]
176
- deletion_mean = feats["deletion_mean_affinity"].unsqueeze(-1)
177
- else:
178
- profile = feats["profile"]
179
- deletion_mean = feats["deletion_mean"].unsqueeze(-1)
180
-
181
- # Compute input embedding
182
- q, c, p, to_keys = self.atom_encoder(feats)
183
- atom_enc_bias = self.atom_enc_proj_z(p)
184
- a, _, _, _ = self.atom_attention_encoder(
185
- feats=feats,
186
- q=q,
187
- c=c,
188
- atom_enc_bias=atom_enc_bias,
189
- to_keys=to_keys,
190
- )
191
-
192
- s = (
193
- a
194
- + self.res_type_encoding(res_type)
195
- + self.msa_profile_encoding(torch.cat([profile, deletion_mean], dim=-1))
196
- )
197
-
198
- if self.add_method_conditioning:
199
- s = s + self.method_conditioning_init(feats["method_feature"])
200
- if self.add_modified_flag:
201
- s = s + self.modified_conditioning_init(feats["modified"])
202
- if self.add_cyclic_flag:
203
- cyclic = feats["cyclic_period"].clamp(max=1.0).unsqueeze(-1)
204
- s = s + self.cyclic_conditioning_init(cyclic)
205
- if self.add_mol_type_feat:
206
- s = s + self.mol_type_conditioning_init(feats["mol_type"])
207
-
208
- return s
209
-
210
-
211
- class TemplateModule(nn.Module):
212
- """Template module."""
213
-
214
- def __init__(
215
- self,
216
- token_z: int,
217
- template_dim: int,
218
- template_blocks: int,
219
- dropout: float = 0.25,
220
- pairwise_head_width: int = 32,
221
- pairwise_num_heads: int = 4,
222
- post_layer_norm: bool = False,
223
- activation_checkpointing: bool = False,
224
- min_dist: float = 3.25,
225
- max_dist: float = 50.75,
226
- num_bins: int = 38,
227
- **kwargs,
228
- ) -> None:
229
- """Initialize the template module.
230
-
231
- Parameters
232
- ----------
233
- token_z : int
234
- The token pairwise embedding size.
235
-
236
- """
237
- super().__init__()
238
- self.min_dist = min_dist
239
- self.max_dist = max_dist
240
- self.num_bins = num_bins
241
- self.relu = nn.ReLU()
242
- self.z_norm = nn.LayerNorm(token_z)
243
- self.v_norm = nn.LayerNorm(template_dim)
244
- self.z_proj = nn.Linear(token_z, template_dim, bias=False)
245
- self.a_proj = nn.Linear(
246
- const.num_tokens * 2 + num_bins + 5,
247
- template_dim,
248
- bias=False,
249
- )
250
- self.u_proj = nn.Linear(template_dim, token_z, bias=False)
251
- self.pairformer = PairformerNoSeqModule(
252
- template_dim,
253
- num_blocks=template_blocks,
254
- dropout=dropout,
255
- pairwise_head_width=pairwise_head_width,
256
- pairwise_num_heads=pairwise_num_heads,
257
- post_layer_norm=post_layer_norm,
258
- activation_checkpointing=activation_checkpointing,
259
- )
260
-
261
- def forward(
262
- self,
263
- z: Tensor,
264
- feats: dict[str, Tensor],
265
- pair_mask: Tensor,
266
- use_kernels: bool = False,
267
- ) -> Tensor:
268
- """Perform the forward pass.
269
-
270
- Parameters
271
- ----------
272
- z : Tensor
273
- The pairwise embeddings
274
- feats : dict[str, Tensor]
275
- Input features
276
- pair_mask : Tensor
277
- The pair mask
278
-
279
- Returns
280
- -------
281
- Tensor
282
- The updated pairwise embeddings.
283
-
284
- """
285
- # Load relevant features
286
- asym_id = feats["asym_id"]
287
- res_type = feats["template_restype"]
288
- frame_rot = feats["template_frame_rot"]
289
- frame_t = feats["template_frame_t"]
290
- frame_mask = feats["template_mask_frame"]
291
- cb_coords = feats["template_cb"]
292
- ca_coords = feats["template_ca"]
293
- cb_mask = feats["template_mask_cb"]
294
- template_mask = feats["template_mask"].any(dim=2).float()
295
- num_templates = template_mask.sum(dim=1)
296
- num_templates = num_templates.clamp(min=1)
297
-
298
- # Compute pairwise masks
299
- b_cb_mask = cb_mask[:, :, :, None] * cb_mask[:, :, None, :]
300
- b_frame_mask = frame_mask[:, :, :, None] * frame_mask[:, :, None, :]
301
-
302
- b_cb_mask = b_cb_mask[..., None]
303
- b_frame_mask = b_frame_mask[..., None]
304
-
305
- # Compute asym mask, template features only attend within the same chain
306
- B, T = res_type.shape[:2] # noqa: N806
307
- asym_mask = (asym_id[:, :, None] == asym_id[:, None, :]).float()
308
- asym_mask = asym_mask[:, None].expand(-1, T, -1, -1)
309
-
310
- # Compute template features
311
- with torch.autocast(device_type="cuda", enabled=False):
312
- # Compute distogram
313
- cb_dists = torch.cdist(cb_coords, cb_coords)
314
- boundaries = torch.linspace(self.min_dist, self.max_dist, self.num_bins - 1)
315
- boundaries = boundaries.to(cb_dists.device)
316
- distogram = (cb_dists[..., None] > boundaries).sum(dim=-1).long()
317
- distogram = one_hot(distogram, num_classes=self.num_bins)
318
-
319
- # Compute unit vector in each frame
320
- frame_rot = frame_rot.unsqueeze(2).transpose(-1, -2)
321
- frame_t = frame_t.unsqueeze(2).unsqueeze(-1)
322
- ca_coords = ca_coords.unsqueeze(3).unsqueeze(-1)
323
- vector = torch.matmul(frame_rot, (ca_coords - frame_t))
324
- norm = torch.norm(vector, dim=-1, keepdim=True)
325
- unit_vector = torch.where(norm > 0, vector / norm, torch.zeros_like(vector))
326
- unit_vector = unit_vector.squeeze(-1)
327
-
328
- # Concatenate input features
329
- a_tij = [distogram, b_cb_mask, unit_vector, b_frame_mask]
330
- a_tij = torch.cat(a_tij, dim=-1)
331
- a_tij = a_tij * asym_mask.unsqueeze(-1)
332
-
333
- res_type_i = res_type[:, :, :, None]
334
- res_type_j = res_type[:, :, None, :]
335
- res_type_i = res_type_i.expand(-1, -1, -1, res_type.size(2), -1)
336
- res_type_j = res_type_j.expand(-1, -1, res_type.size(2), -1, -1)
337
- a_tij = torch.cat([a_tij, res_type_i, res_type_j], dim=-1)
338
- a_tij = self.a_proj(a_tij)
339
-
340
- # Expand mask
341
- pair_mask = pair_mask[:, None].expand(-1, T, -1, -1)
342
- pair_mask = pair_mask.reshape(B * T, *pair_mask.shape[2:])
343
-
344
- # Compute input projections
345
- v = self.z_proj(self.z_norm(z[:, None])) + a_tij
346
- v = v.view(B * T, *v.shape[2:])
347
- v = v + self.pairformer(v, pair_mask, use_kernels=use_kernels)
348
- v = self.v_norm(v)
349
- v = v.view(B, T, *v.shape[1:])
350
-
351
- # Aggregate templates
352
- template_mask = template_mask[:, :, None, None, None]
353
- num_templates = num_templates[:, None, None, None]
354
- u = (v * template_mask).sum(dim=1) / num_templates.to(v)
355
-
356
- # Compute output projection
357
- u = self.u_proj(self.relu(u))
358
- return u
359
-
360
-
361
- class TemplateV2Module(nn.Module):
362
- """Template module."""
363
-
364
- def __init__(
365
- self,
366
- token_z: int,
367
- template_dim: int,
368
- template_blocks: int,
369
- dropout: float = 0.25,
370
- pairwise_head_width: int = 32,
371
- pairwise_num_heads: int = 4,
372
- post_layer_norm: bool = False,
373
- activation_checkpointing: bool = False,
374
- min_dist: float = 3.25,
375
- max_dist: float = 50.75,
376
- num_bins: int = 38,
377
- **kwargs,
378
- ) -> None:
379
- """Initialize the template module.
380
-
381
- Parameters
382
- ----------
383
- token_z : int
384
- The token pairwise embedding size.
385
-
386
- """
387
- super().__init__()
388
- self.min_dist = min_dist
389
- self.max_dist = max_dist
390
- self.num_bins = num_bins
391
- self.relu = nn.ReLU()
392
- self.z_norm = nn.LayerNorm(token_z)
393
- self.v_norm = nn.LayerNorm(template_dim)
394
- self.z_proj = nn.Linear(token_z, template_dim, bias=False)
395
- self.a_proj = nn.Linear(
396
- const.num_tokens * 2 + num_bins + 5,
397
- template_dim,
398
- bias=False,
399
- )
400
- self.u_proj = nn.Linear(template_dim, token_z, bias=False)
401
- self.pairformer = PairformerNoSeqModule(
402
- template_dim,
403
- num_blocks=template_blocks,
404
- dropout=dropout,
405
- pairwise_head_width=pairwise_head_width,
406
- pairwise_num_heads=pairwise_num_heads,
407
- post_layer_norm=post_layer_norm,
408
- activation_checkpointing=activation_checkpointing,
409
- )
410
-
411
- def forward(
412
- self,
413
- z: Tensor,
414
- feats: dict[str, Tensor],
415
- pair_mask: Tensor,
416
- use_kernels: bool = False,
417
- ) -> Tensor:
418
- """Perform the forward pass.
419
-
420
- Parameters
421
- ----------
422
- z : Tensor
423
- The pairwise embeddings
424
- feats : dict[str, Tensor]
425
- Input features
426
- pair_mask : Tensor
427
- The pair mask
428
-
429
- Returns
430
- -------
431
- Tensor
432
- The updated pairwise embeddings.
433
-
434
- """
435
- # Load relevant features
436
- res_type = feats["template_restype"]
437
- frame_rot = feats["template_frame_rot"]
438
- frame_t = feats["template_frame_t"]
439
- frame_mask = feats["template_mask_frame"]
440
- cb_coords = feats["template_cb"]
441
- ca_coords = feats["template_ca"]
442
- cb_mask = feats["template_mask_cb"]
443
- visibility_ids = feats["visibility_ids"]
444
- template_mask = feats["template_mask"].any(dim=2).float()
445
- num_templates = template_mask.sum(dim=1)
446
- num_templates = num_templates.clamp(min=1)
447
-
448
- # Compute pairwise masks
449
- b_cb_mask = cb_mask[:, :, :, None] * cb_mask[:, :, None, :]
450
- b_frame_mask = frame_mask[:, :, :, None] * frame_mask[:, :, None, :]
451
-
452
- b_cb_mask = b_cb_mask[..., None]
453
- b_frame_mask = b_frame_mask[..., None]
454
-
455
- # Compute asym mask, template features only attend within the same chain
456
- B, T = res_type.shape[:2] # noqa: N806
457
- tmlp_pair_mask = (
458
- visibility_ids[:, :, :, None] == visibility_ids[:, :, None, :]
459
- ).float()
460
-
461
- # Compute template features
462
- with torch.autocast(device_type="cuda", enabled=False):
463
- # Compute distogram
464
- cb_dists = torch.cdist(cb_coords, cb_coords)
465
- boundaries = torch.linspace(self.min_dist, self.max_dist, self.num_bins - 1)
466
- boundaries = boundaries.to(cb_dists.device)
467
- distogram = (cb_dists[..., None] > boundaries).sum(dim=-1).long()
468
- distogram = one_hot(distogram, num_classes=self.num_bins)
469
-
470
- # Compute unit vector in each frame
471
- frame_rot = frame_rot.unsqueeze(2).transpose(-1, -2)
472
- frame_t = frame_t.unsqueeze(2).unsqueeze(-1)
473
- ca_coords = ca_coords.unsqueeze(3).unsqueeze(-1)
474
- vector = torch.matmul(frame_rot, (ca_coords - frame_t))
475
- norm = torch.norm(vector, dim=-1, keepdim=True)
476
- unit_vector = torch.where(norm > 0, vector / norm, torch.zeros_like(vector))
477
- unit_vector = unit_vector.squeeze(-1)
478
-
479
- # Concatenate input features
480
- a_tij = [distogram, b_cb_mask, unit_vector, b_frame_mask]
481
- a_tij = torch.cat(a_tij, dim=-1)
482
- a_tij = a_tij * tmlp_pair_mask.unsqueeze(-1)
483
-
484
- res_type_i = res_type[:, :, :, None]
485
- res_type_j = res_type[:, :, None, :]
486
- res_type_i = res_type_i.expand(-1, -1, -1, res_type.size(2), -1)
487
- res_type_j = res_type_j.expand(-1, -1, res_type.size(2), -1, -1)
488
- a_tij = torch.cat([a_tij, res_type_i, res_type_j], dim=-1)
489
- a_tij = self.a_proj(a_tij)
490
-
491
- # Expand mask
492
- pair_mask = pair_mask[:, None].expand(-1, T, -1, -1)
493
- pair_mask = pair_mask.reshape(B * T, *pair_mask.shape[2:])
494
-
495
- # Compute input projections
496
- v = self.z_proj(self.z_norm(z[:, None])) + a_tij
497
- v = v.view(B * T, *v.shape[2:])
498
- v = v + self.pairformer(v, pair_mask, use_kernels=use_kernels)
499
- v = self.v_norm(v)
500
- v = v.view(B, T, *v.shape[1:])
501
-
502
- # Aggregate templates
503
- template_mask = template_mask[:, :, None, None, None]
504
- num_templates = num_templates[:, None, None, None]
505
- u = (v * template_mask).sum(dim=1) / num_templates.to(v)
506
-
507
- # Compute output projection
508
- u = self.u_proj(self.relu(u))
509
- return u
510
-
511
-
512
- class MSAModule(nn.Module):
513
- """MSA module."""
514
-
515
- def __init__(
516
- self,
517
- msa_s: int,
518
- token_z: int,
519
- token_s: int,
520
- msa_blocks: int,
521
- msa_dropout: float,
522
- z_dropout: float,
523
- pairwise_head_width: int = 32,
524
- pairwise_num_heads: int = 4,
525
- activation_checkpointing: bool = False,
526
- use_paired_feature: bool = True,
527
- subsample_msa: bool = False,
528
- num_subsampled_msa: int = 1024,
529
- **kwargs,
530
- ) -> None:
531
- """Initialize the MSA module.
532
-
533
- Parameters
534
- ----------
535
- token_z : int
536
- The token pairwise embedding size.
537
-
538
- """
539
- super().__init__()
540
- self.msa_blocks = msa_blocks
541
- self.msa_dropout = msa_dropout
542
- self.z_dropout = z_dropout
543
- self.use_paired_feature = use_paired_feature
544
- self.activation_checkpointing = activation_checkpointing
545
- self.subsample_msa = subsample_msa
546
- self.num_subsampled_msa = num_subsampled_msa
547
-
548
- self.s_proj = nn.Linear(token_s, msa_s, bias=False)
549
- self.msa_proj = nn.Linear(
550
- const.num_tokens + 2 + int(use_paired_feature),
551
- msa_s,
552
- bias=False,
553
- )
554
- self.layers = nn.ModuleList()
555
- for i in range(msa_blocks):
556
- self.layers.append(
557
- MSALayer(
558
- msa_s,
559
- token_z,
560
- msa_dropout,
561
- z_dropout,
562
- pairwise_head_width,
563
- pairwise_num_heads,
564
- )
565
- )
566
-
567
- def forward(
568
- self,
569
- z: Tensor,
570
- emb: Tensor,
571
- feats: dict[str, Tensor],
572
- use_kernels: bool = False,
573
- ) -> Tensor:
574
- """Perform the forward pass.
575
-
576
- Parameters
577
- ----------
578
- z : Tensor
579
- The pairwise embeddings
580
- emb : Tensor
581
- The input embeddings
582
- feats : dict[str, Tensor]
583
- Input features
584
- use_kernels: bool
585
- Whether to use kernels for triangular updates
586
-
587
- Returns
588
- -------
589
- Tensor
590
- The output pairwise embeddings.
591
-
592
- """
593
- # Set chunk sizes
594
- if not self.training:
595
- if z.shape[1] > const.chunk_size_threshold:
596
- chunk_heads_pwa = True
597
- chunk_size_transition_z = 64
598
- chunk_size_transition_msa = 32
599
- chunk_size_outer_product = 4
600
- chunk_size_tri_attn = 128
601
- else:
602
- chunk_heads_pwa = False
603
- chunk_size_transition_z = None
604
- chunk_size_transition_msa = None
605
- chunk_size_outer_product = None
606
- chunk_size_tri_attn = 512
607
- else:
608
- chunk_heads_pwa = False
609
- chunk_size_transition_z = None
610
- chunk_size_transition_msa = None
611
- chunk_size_outer_product = None
612
- chunk_size_tri_attn = None
613
-
614
- # Load relevant features
615
- msa = feats["msa"]
616
- msa = torch.nn.functional.one_hot(msa, num_classes=const.num_tokens)
617
- has_deletion = feats["has_deletion"].unsqueeze(-1)
618
- deletion_value = feats["deletion_value"].unsqueeze(-1)
619
- is_paired = feats["msa_paired"].unsqueeze(-1)
620
- msa_mask = feats["msa_mask"]
621
- token_mask = feats["token_pad_mask"].float()
622
- token_mask = token_mask[:, :, None] * token_mask[:, None, :]
623
-
624
- # Compute MSA embeddings
625
- if self.use_paired_feature:
626
- m = torch.cat([msa, has_deletion, deletion_value, is_paired], dim=-1)
627
- else:
628
- m = torch.cat([msa, has_deletion, deletion_value], dim=-1)
629
-
630
- # Subsample the MSA
631
- if self.subsample_msa:
632
- msa_indices = torch.randperm(msa.shape[1])[: self.num_subsampled_msa]
633
- m = m[:, msa_indices]
634
- msa_mask = msa_mask[:, msa_indices]
635
-
636
- # Compute input projections
637
- m = self.msa_proj(m)
638
- m = m + self.s_proj(emb).unsqueeze(1)
639
-
640
- # Perform MSA blocks
641
- for i in range(self.msa_blocks):
642
- if self.activation_checkpointing and self.training:
643
- z, m = torch.utils.checkpoint.checkpoint(
644
- self.layers[i],
645
- z,
646
- m,
647
- token_mask,
648
- msa_mask,
649
- chunk_heads_pwa,
650
- chunk_size_transition_z,
651
- chunk_size_transition_msa,
652
- chunk_size_outer_product,
653
- chunk_size_tri_attn,
654
- use_kernels,
655
- )
656
- else:
657
- z, m = self.layers[i](
658
- z,
659
- m,
660
- token_mask,
661
- msa_mask,
662
- chunk_heads_pwa,
663
- chunk_size_transition_z,
664
- chunk_size_transition_msa,
665
- chunk_size_outer_product,
666
- chunk_size_tri_attn,
667
- use_kernels,
668
- )
669
- return z
670
-
671
-
672
- class MSALayer(nn.Module):
673
- """MSA module."""
674
-
675
- def __init__(
676
- self,
677
- msa_s: int,
678
- token_z: int,
679
- msa_dropout: float,
680
- z_dropout: float,
681
- pairwise_head_width: int = 32,
682
- pairwise_num_heads: int = 4,
683
- ) -> None:
684
- """Initialize the MSA module.
685
-
686
- Parameters
687
- ----------
688
- token_z : int
689
- The token pairwise embedding size.
690
-
691
- """
692
- super().__init__()
693
- self.msa_dropout = msa_dropout
694
- self.msa_transition = Transition(dim=msa_s, hidden=msa_s * 4)
695
- self.pair_weighted_averaging = PairWeightedAveraging(
696
- c_m=msa_s,
697
- c_z=token_z,
698
- c_h=32,
699
- num_heads=8,
700
- )
701
-
702
- self.pairformer_layer = PairformerNoSeqLayer(
703
- token_z=token_z,
704
- dropout=z_dropout,
705
- pairwise_head_width=pairwise_head_width,
706
- pairwise_num_heads=pairwise_num_heads,
707
- )
708
- self.outer_product_mean = OuterProductMean(
709
- c_in=msa_s,
710
- c_hidden=32,
711
- c_out=token_z,
712
- )
713
-
714
- def forward(
715
- self,
716
- z: Tensor,
717
- m: Tensor,
718
- token_mask: Tensor,
719
- msa_mask: Tensor,
720
- chunk_heads_pwa: bool = False,
721
- chunk_size_transition_z: int = None,
722
- chunk_size_transition_msa: int = None,
723
- chunk_size_outer_product: int = None,
724
- chunk_size_tri_attn: int = None,
725
- use_kernels: bool = False,
726
- ) -> tuple[Tensor, Tensor]:
727
- """Perform the forward pass.
728
-
729
- Parameters
730
- ----------
731
- z : Tensor
732
- The pairwise embeddings
733
- emb : Tensor
734
- The input embeddings
735
- feats : dict[str, Tensor]
736
- Input features
737
-
738
- Returns
739
- -------
740
- Tensor
741
- The output pairwise embeddings.
742
-
743
- """
744
- # Communication to MSA stack
745
- msa_dropout = get_dropout_mask(self.msa_dropout, m, self.training)
746
- m = m + msa_dropout * self.pair_weighted_averaging(
747
- m, z, token_mask, chunk_heads_pwa
748
- )
749
- m = m + self.msa_transition(m, chunk_size_transition_msa)
750
-
751
- z = z + self.outer_product_mean(m, msa_mask, chunk_size_outer_product)
752
-
753
- # Compute pairwise stack
754
- z = self.pairformer_layer(
755
- z, token_mask, chunk_size_tri_attn, use_kernels=use_kernels
756
- )
757
-
758
- return z, m
759
-
760
-
761
- class BFactorModule(nn.Module):
762
- """BFactor Module."""
763
-
764
- def __init__(self, token_s: int, num_bins: int) -> None:
765
- """Initialize the bfactor module.
766
-
767
- Parameters
768
- ----------
769
- token_s : int
770
- The token embedding size.
771
-
772
- """
773
- super().__init__()
774
- self.bfactor = nn.Linear(token_s, num_bins)
775
- self.num_bins = num_bins
776
-
777
- def forward(self, s: Tensor) -> Tensor:
778
- """Perform the forward pass.
779
-
780
- Parameters
781
- ----------
782
- s : Tensor
783
- The sequence embeddings
784
-
785
- Returns
786
- -------
787
- Tensor
788
- The predicted bfactor histogram.
789
-
790
- """
791
- return self.bfactor(s)
792
-
793
-
794
- class DistogramModule(nn.Module):
795
- """Distogram Module."""
796
-
797
- def __init__(self, token_z: int, num_bins: int, num_distograms: int = 1) -> None:
798
- """Initialize the distogram module.
799
-
800
- Parameters
801
- ----------
802
- token_z : int
803
- The token pairwise embedding size.
804
-
805
- """
806
- super().__init__()
807
- self.distogram = nn.Linear(token_z, num_distograms * num_bins)
808
- self.num_distograms = num_distograms
809
- self.num_bins = num_bins
810
-
811
- def forward(self, z: Tensor) -> Tensor:
812
- """Perform the forward pass.
813
-
814
- Parameters
815
- ----------
816
- z : Tensor
817
- The pairwise embeddings
818
-
819
- Returns
820
- -------
821
- Tensor
822
- The predicted distogram.
823
-
824
- """
825
- z = z + z.transpose(1, 2)
826
- return self.distogram(z).reshape(
827
- z.shape[0], z.shape[1], z.shape[2], self.num_distograms, self.num_bins
828
- )
 
1
+ import torch
2
+ from torch import Tensor, nn
3
+ from torch.nn.functional import one_hot
4
+
5
+ from . import vb_const as const
6
+ from .vb_layers_outer_product_mean import OuterProductMean
7
+ from .vb_layers_pair_averaging import PairWeightedAveraging
8
+ from .vb_layers_pairformer import (
9
+ PairformerNoSeqLayer,
10
+ PairformerNoSeqModule,
11
+ get_dropout_mask,
12
+ )
13
+ from .vb_layers_transition import Transition
14
+ from .vb_modules_encodersv2 import (
15
+ AtomAttentionEncoder,
16
+ AtomEncoder,
17
+ FourierEmbedding,
18
+ )
19
+
20
+
21
+ class ContactConditioning(nn.Module):
22
+ def __init__(self, token_z: int, cutoff_min: float, cutoff_max: float):
23
+ super().__init__()
24
+
25
+ self.fourier_embedding = FourierEmbedding(token_z)
26
+ self.encoder = nn.Linear(
27
+ token_z + len(const.contact_conditioning_info) - 1, token_z
28
+ )
29
+ self.encoding_unspecified = nn.Parameter(torch.zeros(token_z))
30
+ self.encoding_unselected = nn.Parameter(torch.zeros(token_z))
31
+ self.cutoff_min = cutoff_min
32
+ self.cutoff_max = cutoff_max
33
+
34
+ def forward(self, feats):
35
+ assert const.contact_conditioning_info["UNSPECIFIED"] == 0
36
+ assert const.contact_conditioning_info["UNSELECTED"] == 1
37
+ contact_conditioning = feats["contact_conditioning"][:, :, :, 2:]
38
+ contact_threshold = feats["contact_threshold"]
39
+ contact_threshold_normalized = (contact_threshold - self.cutoff_min) / (
40
+ self.cutoff_max - self.cutoff_min
41
+ )
42
+ contact_threshold_fourier = self.fourier_embedding(
43
+ contact_threshold_normalized.flatten()
44
+ ).reshape(contact_threshold_normalized.shape + (-1,))
45
+
46
+ contact_conditioning = torch.cat(
47
+ [
48
+ contact_conditioning,
49
+ contact_threshold_normalized.unsqueeze(-1),
50
+ contact_threshold_fourier,
51
+ ],
52
+ dim=-1,
53
+ )
54
+ contact_conditioning = self.encoder(contact_conditioning)
55
+
56
+ contact_conditioning = (
57
+ contact_conditioning
58
+ * (
59
+ 1
60
+ - feats["contact_conditioning"][:, :, :, 0:2].sum(dim=-1, keepdim=True)
61
+ )
62
+ + self.encoding_unspecified * feats["contact_conditioning"][:, :, :, 0:1]
63
+ + self.encoding_unselected * feats["contact_conditioning"][:, :, :, 1:2]
64
+ )
65
+ return contact_conditioning
66
+
67
+
68
+ class InputEmbedder(nn.Module):
69
+ def __init__(
70
+ self,
71
+ atom_s: int,
72
+ atom_z: int,
73
+ token_s: int,
74
+ token_z: int,
75
+ atoms_per_window_queries: int,
76
+ atoms_per_window_keys: int,
77
+ atom_feature_dim: int,
78
+ atom_encoder_depth: int,
79
+ atom_encoder_heads: int,
80
+ activation_checkpointing: bool = False,
81
+ add_method_conditioning: bool = False,
82
+ add_modified_flag: bool = False,
83
+ add_cyclic_flag: bool = False,
84
+ add_mol_type_feat: bool = False,
85
+ use_no_atom_char: bool = False,
86
+ use_atom_backbone_feat: bool = False,
87
+ use_residue_feats_atoms: bool = False,
88
+ ) -> None:
89
+ """Initialize the input embedder.
90
+
91
+ Parameters
92
+ ----------
93
+ atom_s : int
94
+ The atom embedding size.
95
+ atom_z : int
96
+ The atom pairwise embedding size.
97
+ token_s : int
98
+ The token embedding size.
99
+
100
+ """
101
+ super().__init__()
102
+ self.token_s = token_s
103
+ self.add_method_conditioning = add_method_conditioning
104
+ self.add_modified_flag = add_modified_flag
105
+ self.add_cyclic_flag = add_cyclic_flag
106
+ self.add_mol_type_feat = add_mol_type_feat
107
+
108
+ self.atom_encoder = AtomEncoder(
109
+ atom_s=atom_s,
110
+ atom_z=atom_z,
111
+ token_s=token_s,
112
+ token_z=token_z,
113
+ atoms_per_window_queries=atoms_per_window_queries,
114
+ atoms_per_window_keys=atoms_per_window_keys,
115
+ atom_feature_dim=atom_feature_dim,
116
+ structure_prediction=False,
117
+ use_no_atom_char=use_no_atom_char,
118
+ use_atom_backbone_feat=use_atom_backbone_feat,
119
+ use_residue_feats_atoms=use_residue_feats_atoms,
120
+ )
121
+
122
+ self.atom_enc_proj_z = nn.Sequential(
123
+ nn.LayerNorm(atom_z),
124
+ nn.Linear(atom_z, atom_encoder_depth * atom_encoder_heads, bias=False),
125
+ )
126
+
127
+ self.atom_attention_encoder = AtomAttentionEncoder(
128
+ atom_s=atom_s,
129
+ token_s=token_s,
130
+ atoms_per_window_queries=atoms_per_window_queries,
131
+ atoms_per_window_keys=atoms_per_window_keys,
132
+ atom_encoder_depth=atom_encoder_depth,
133
+ atom_encoder_heads=atom_encoder_heads,
134
+ structure_prediction=False,
135
+ activation_checkpointing=activation_checkpointing,
136
+ )
137
+
138
+ self.res_type_encoding = nn.Linear(const.num_tokens, token_s, bias=False)
139
+ self.msa_profile_encoding = nn.Linear(const.num_tokens + 1, token_s, bias=False)
140
+
141
+ if add_method_conditioning:
142
+ self.method_conditioning_init = nn.Embedding(
143
+ const.num_method_types, token_s
144
+ )
145
+ self.method_conditioning_init.weight.data.fill_(0)
146
+ if add_modified_flag:
147
+ self.modified_conditioning_init = nn.Embedding(2, token_s)
148
+ self.modified_conditioning_init.weight.data.fill_(0)
149
+ if add_cyclic_flag:
150
+ self.cyclic_conditioning_init = nn.Linear(1, token_s, bias=False)
151
+ self.cyclic_conditioning_init.weight.data.fill_(0)
152
+ if add_mol_type_feat:
153
+ self.mol_type_conditioning_init = nn.Embedding(
154
+ len(const.chain_type_ids), token_s
155
+ )
156
+ self.mol_type_conditioning_init.weight.data.fill_(0)
157
+
158
+ def forward(self, feats: dict[str, Tensor], affinity: bool = False) -> Tensor:
159
+ """Perform the forward pass.
160
+
161
+ Parameters
162
+ ----------
163
+ feats : dict[str, Tensor]
164
+ Input features
165
+
166
+ Returns
167
+ -------
168
+ Tensor
169
+ The embedded tokens.
170
+
171
+ """
172
+ # Load relevant features
173
+ res_type = feats["res_type"].float()
174
+ if affinity:
175
+ profile = feats["profile_affinity"]
176
+ deletion_mean = feats["deletion_mean_affinity"].unsqueeze(-1)
177
+ else:
178
+ profile = feats["profile"]
179
+ deletion_mean = feats["deletion_mean"].unsqueeze(-1)
180
+
181
+ # Compute input embedding
182
+ q, c, p, to_keys = self.atom_encoder(feats)
183
+ atom_enc_bias = self.atom_enc_proj_z(p)
184
+ a, _, _, _ = self.atom_attention_encoder(
185
+ feats=feats,
186
+ q=q,
187
+ c=c,
188
+ atom_enc_bias=atom_enc_bias,
189
+ to_keys=to_keys,
190
+ )
191
+
192
+ s = (
193
+ a
194
+ + self.res_type_encoding(res_type)
195
+ + self.msa_profile_encoding(torch.cat([profile, deletion_mean], dim=-1))
196
+ )
197
+
198
+ if self.add_method_conditioning:
199
+ s = s + self.method_conditioning_init(feats["method_feature"])
200
+ if self.add_modified_flag:
201
+ s = s + self.modified_conditioning_init(feats["modified"])
202
+ if self.add_cyclic_flag:
203
+ cyclic = feats["cyclic_period"].clamp(max=1.0).unsqueeze(-1)
204
+ s = s + self.cyclic_conditioning_init(cyclic)
205
+ if self.add_mol_type_feat:
206
+ s = s + self.mol_type_conditioning_init(feats["mol_type"])
207
+
208
+ return s
209
+
210
+
211
+ class TemplateModule(nn.Module):
212
+ """Template module."""
213
+
214
+ def __init__(
215
+ self,
216
+ token_z: int,
217
+ template_dim: int,
218
+ template_blocks: int,
219
+ dropout: float = 0.25,
220
+ pairwise_head_width: int = 32,
221
+ pairwise_num_heads: int = 4,
222
+ post_layer_norm: bool = False,
223
+ activation_checkpointing: bool = False,
224
+ min_dist: float = 3.25,
225
+ max_dist: float = 50.75,
226
+ num_bins: int = 38,
227
+ **kwargs,
228
+ ) -> None:
229
+ """Initialize the template module.
230
+
231
+ Parameters
232
+ ----------
233
+ token_z : int
234
+ The token pairwise embedding size.
235
+
236
+ """
237
+ super().__init__()
238
+ self.min_dist = min_dist
239
+ self.max_dist = max_dist
240
+ self.num_bins = num_bins
241
+ self.relu = nn.ReLU()
242
+ self.z_norm = nn.LayerNorm(token_z)
243
+ self.v_norm = nn.LayerNorm(template_dim)
244
+ self.z_proj = nn.Linear(token_z, template_dim, bias=False)
245
+ self.a_proj = nn.Linear(
246
+ const.num_tokens * 2 + num_bins + 5,
247
+ template_dim,
248
+ bias=False,
249
+ )
250
+ self.u_proj = nn.Linear(template_dim, token_z, bias=False)
251
+ self.pairformer = PairformerNoSeqModule(
252
+ template_dim,
253
+ num_blocks=template_blocks,
254
+ dropout=dropout,
255
+ pairwise_head_width=pairwise_head_width,
256
+ pairwise_num_heads=pairwise_num_heads,
257
+ post_layer_norm=post_layer_norm,
258
+ activation_checkpointing=activation_checkpointing,
259
+ )
260
+
261
+ def forward(
262
+ self,
263
+ z: Tensor,
264
+ feats: dict[str, Tensor],
265
+ pair_mask: Tensor,
266
+ use_kernels: bool = False,
267
+ ) -> Tensor:
268
+ """Perform the forward pass.
269
+
270
+ Parameters
271
+ ----------
272
+ z : Tensor
273
+ The pairwise embeddings
274
+ feats : dict[str, Tensor]
275
+ Input features
276
+ pair_mask : Tensor
277
+ The pair mask
278
+
279
+ Returns
280
+ -------
281
+ Tensor
282
+ The updated pairwise embeddings.
283
+
284
+ """
285
+ # Load relevant features
286
+ asym_id = feats["asym_id"]
287
+ res_type = feats["template_restype"]
288
+ frame_rot = feats["template_frame_rot"]
289
+ frame_t = feats["template_frame_t"]
290
+ frame_mask = feats["template_mask_frame"]
291
+ cb_coords = feats["template_cb"]
292
+ ca_coords = feats["template_ca"]
293
+ cb_mask = feats["template_mask_cb"]
294
+ template_mask = feats["template_mask"].any(dim=2).float()
295
+ num_templates = template_mask.sum(dim=1)
296
+ num_templates = num_templates.clamp(min=1)
297
+
298
+ # Compute pairwise masks
299
+ b_cb_mask = cb_mask[:, :, :, None] * cb_mask[:, :, None, :]
300
+ b_frame_mask = frame_mask[:, :, :, None] * frame_mask[:, :, None, :]
301
+
302
+ b_cb_mask = b_cb_mask[..., None]
303
+ b_frame_mask = b_frame_mask[..., None]
304
+
305
+ # Compute asym mask, template features only attend within the same chain
306
+ B, T = res_type.shape[:2] # noqa: N806
307
+ asym_mask = (asym_id[:, :, None] == asym_id[:, None, :]).float()
308
+ asym_mask = asym_mask[:, None].expand(-1, T, -1, -1)
309
+
310
+ # Compute template features
311
+ with torch.autocast(device_type="cuda", enabled=False):
312
+ # Compute distogram
313
+ cb_dists = torch.cdist(cb_coords, cb_coords)
314
+ boundaries = torch.linspace(self.min_dist, self.max_dist, self.num_bins - 1)
315
+ boundaries = boundaries.to(cb_dists.device)
316
+ distogram = (cb_dists[..., None] > boundaries).sum(dim=-1).long()
317
+ distogram = one_hot(distogram, num_classes=self.num_bins)
318
+
319
+ # Compute unit vector in each frame
320
+ frame_rot = frame_rot.unsqueeze(2).transpose(-1, -2)
321
+ frame_t = frame_t.unsqueeze(2).unsqueeze(-1)
322
+ ca_coords = ca_coords.unsqueeze(3).unsqueeze(-1)
323
+ vector = torch.matmul(frame_rot, (ca_coords - frame_t))
324
+ norm = torch.norm(vector, dim=-1, keepdim=True)
325
+ unit_vector = torch.where(norm > 0, vector / norm, torch.zeros_like(vector))
326
+ unit_vector = unit_vector.squeeze(-1)
327
+
328
+ # Concatenate input features
329
+ a_tij = [distogram, b_cb_mask, unit_vector, b_frame_mask]
330
+ a_tij = torch.cat(a_tij, dim=-1)
331
+ a_tij = a_tij * asym_mask.unsqueeze(-1)
332
+
333
+ res_type_i = res_type[:, :, :, None]
334
+ res_type_j = res_type[:, :, None, :]
335
+ res_type_i = res_type_i.expand(-1, -1, -1, res_type.size(2), -1)
336
+ res_type_j = res_type_j.expand(-1, -1, res_type.size(2), -1, -1)
337
+ a_tij = torch.cat([a_tij, res_type_i, res_type_j], dim=-1)
338
+ a_tij = self.a_proj(a_tij)
339
+
340
+ # Expand mask
341
+ pair_mask = pair_mask[:, None].expand(-1, T, -1, -1)
342
+ pair_mask = pair_mask.reshape(B * T, *pair_mask.shape[2:])
343
+
344
+ # Compute input projections
345
+ v = self.z_proj(self.z_norm(z[:, None])) + a_tij
346
+ v = v.view(B * T, *v.shape[2:])
347
+ v = v + self.pairformer(v, pair_mask, use_kernels=use_kernels)
348
+ v = self.v_norm(v)
349
+ v = v.view(B, T, *v.shape[1:])
350
+
351
+ # Aggregate templates
352
+ template_mask = template_mask[:, :, None, None, None]
353
+ num_templates = num_templates[:, None, None, None]
354
+ u = (v * template_mask).sum(dim=1) / num_templates.to(v)
355
+
356
+ # Compute output projection
357
+ u = self.u_proj(self.relu(u))
358
+ return u
359
+
360
+
361
+ class TemplateV2Module(nn.Module):
362
+ """Template module."""
363
+
364
+ def __init__(
365
+ self,
366
+ token_z: int,
367
+ template_dim: int,
368
+ template_blocks: int,
369
+ dropout: float = 0.25,
370
+ pairwise_head_width: int = 32,
371
+ pairwise_num_heads: int = 4,
372
+ post_layer_norm: bool = False,
373
+ activation_checkpointing: bool = False,
374
+ min_dist: float = 3.25,
375
+ max_dist: float = 50.75,
376
+ num_bins: int = 38,
377
+ **kwargs,
378
+ ) -> None:
379
+ """Initialize the template module.
380
+
381
+ Parameters
382
+ ----------
383
+ token_z : int
384
+ The token pairwise embedding size.
385
+
386
+ """
387
+ super().__init__()
388
+ self.min_dist = min_dist
389
+ self.max_dist = max_dist
390
+ self.num_bins = num_bins
391
+ self.relu = nn.ReLU()
392
+ self.z_norm = nn.LayerNorm(token_z)
393
+ self.v_norm = nn.LayerNorm(template_dim)
394
+ self.z_proj = nn.Linear(token_z, template_dim, bias=False)
395
+ self.a_proj = nn.Linear(
396
+ const.num_tokens * 2 + num_bins + 5,
397
+ template_dim,
398
+ bias=False,
399
+ )
400
+ self.u_proj = nn.Linear(template_dim, token_z, bias=False)
401
+ self.pairformer = PairformerNoSeqModule(
402
+ template_dim,
403
+ num_blocks=template_blocks,
404
+ dropout=dropout,
405
+ pairwise_head_width=pairwise_head_width,
406
+ pairwise_num_heads=pairwise_num_heads,
407
+ post_layer_norm=post_layer_norm,
408
+ activation_checkpointing=activation_checkpointing,
409
+ )
410
+
411
+ def forward(
412
+ self,
413
+ z: Tensor,
414
+ feats: dict[str, Tensor],
415
+ pair_mask: Tensor,
416
+ use_kernels: bool = False,
417
+ ) -> Tensor:
418
+ """Perform the forward pass.
419
+
420
+ Parameters
421
+ ----------
422
+ z : Tensor
423
+ The pairwise embeddings
424
+ feats : dict[str, Tensor]
425
+ Input features
426
+ pair_mask : Tensor
427
+ The pair mask
428
+
429
+ Returns
430
+ -------
431
+ Tensor
432
+ The updated pairwise embeddings.
433
+
434
+ """
435
+ # Load relevant features
436
+ res_type = feats["template_restype"]
437
+ frame_rot = feats["template_frame_rot"]
438
+ frame_t = feats["template_frame_t"]
439
+ frame_mask = feats["template_mask_frame"]
440
+ cb_coords = feats["template_cb"]
441
+ ca_coords = feats["template_ca"]
442
+ cb_mask = feats["template_mask_cb"]
443
+ visibility_ids = feats["visibility_ids"]
444
+ template_mask = feats["template_mask"].any(dim=2).float()
445
+ num_templates = template_mask.sum(dim=1)
446
+ num_templates = num_templates.clamp(min=1)
447
+
448
+ # Compute pairwise masks
449
+ b_cb_mask = cb_mask[:, :, :, None] * cb_mask[:, :, None, :]
450
+ b_frame_mask = frame_mask[:, :, :, None] * frame_mask[:, :, None, :]
451
+
452
+ b_cb_mask = b_cb_mask[..., None]
453
+ b_frame_mask = b_frame_mask[..., None]
454
+
455
+ # Compute asym mask, template features only attend within the same chain
456
+ B, T = res_type.shape[:2] # noqa: N806
457
+ tmlp_pair_mask = (
458
+ visibility_ids[:, :, :, None] == visibility_ids[:, :, None, :]
459
+ ).float()
460
+
461
+ # Compute template features
462
+ with torch.autocast(device_type="cuda", enabled=False):
463
+ # Compute distogram
464
+ cb_dists = torch.cdist(cb_coords, cb_coords)
465
+ boundaries = torch.linspace(self.min_dist, self.max_dist, self.num_bins - 1)
466
+ boundaries = boundaries.to(cb_dists.device)
467
+ distogram = (cb_dists[..., None] > boundaries).sum(dim=-1).long()
468
+ distogram = one_hot(distogram, num_classes=self.num_bins)
469
+
470
+ # Compute unit vector in each frame
471
+ frame_rot = frame_rot.unsqueeze(2).transpose(-1, -2)
472
+ frame_t = frame_t.unsqueeze(2).unsqueeze(-1)
473
+ ca_coords = ca_coords.unsqueeze(3).unsqueeze(-1)
474
+ vector = torch.matmul(frame_rot, (ca_coords - frame_t))
475
+ norm = torch.norm(vector, dim=-1, keepdim=True)
476
+ unit_vector = torch.where(norm > 0, vector / norm, torch.zeros_like(vector))
477
+ unit_vector = unit_vector.squeeze(-1)
478
+
479
+ # Concatenate input features
480
+ a_tij = [distogram, b_cb_mask, unit_vector, b_frame_mask]
481
+ a_tij = torch.cat(a_tij, dim=-1)
482
+ a_tij = a_tij * tmlp_pair_mask.unsqueeze(-1)
483
+
484
+ res_type_i = res_type[:, :, :, None]
485
+ res_type_j = res_type[:, :, None, :]
486
+ res_type_i = res_type_i.expand(-1, -1, -1, res_type.size(2), -1)
487
+ res_type_j = res_type_j.expand(-1, -1, res_type.size(2), -1, -1)
488
+ a_tij = torch.cat([a_tij, res_type_i, res_type_j], dim=-1)
489
+ a_tij = self.a_proj(a_tij)
490
+
491
+ # Expand mask
492
+ pair_mask = pair_mask[:, None].expand(-1, T, -1, -1)
493
+ pair_mask = pair_mask.reshape(B * T, *pair_mask.shape[2:])
494
+
495
+ # Compute input projections
496
+ v = self.z_proj(self.z_norm(z[:, None])) + a_tij
497
+ v = v.view(B * T, *v.shape[2:])
498
+ v = v + self.pairformer(v, pair_mask, use_kernels=use_kernels)
499
+ v = self.v_norm(v)
500
+ v = v.view(B, T, *v.shape[1:])
501
+
502
+ # Aggregate templates
503
+ template_mask = template_mask[:, :, None, None, None]
504
+ num_templates = num_templates[:, None, None, None]
505
+ u = (v * template_mask).sum(dim=1) / num_templates.to(v)
506
+
507
+ # Compute output projection
508
+ u = self.u_proj(self.relu(u))
509
+ return u
510
+
511
+
512
+ class MSAModule(nn.Module):
513
+ """MSA module."""
514
+
515
+ def __init__(
516
+ self,
517
+ msa_s: int,
518
+ token_z: int,
519
+ token_s: int,
520
+ msa_blocks: int,
521
+ msa_dropout: float,
522
+ z_dropout: float,
523
+ pairwise_head_width: int = 32,
524
+ pairwise_num_heads: int = 4,
525
+ activation_checkpointing: bool = False,
526
+ use_paired_feature: bool = True,
527
+ subsample_msa: bool = False,
528
+ num_subsampled_msa: int = 1024,
529
+ **kwargs,
530
+ ) -> None:
531
+ """Initialize the MSA module.
532
+
533
+ Parameters
534
+ ----------
535
+ token_z : int
536
+ The token pairwise embedding size.
537
+
538
+ """
539
+ super().__init__()
540
+ self.msa_blocks = msa_blocks
541
+ self.msa_dropout = msa_dropout
542
+ self.z_dropout = z_dropout
543
+ self.use_paired_feature = use_paired_feature
544
+ self.activation_checkpointing = activation_checkpointing
545
+ self.subsample_msa = subsample_msa
546
+ self.num_subsampled_msa = num_subsampled_msa
547
+
548
+ self.s_proj = nn.Linear(token_s, msa_s, bias=False)
549
+ self.msa_proj = nn.Linear(
550
+ const.num_tokens + 2 + int(use_paired_feature),
551
+ msa_s,
552
+ bias=False,
553
+ )
554
+ self.layers = nn.ModuleList()
555
+ for i in range(msa_blocks):
556
+ self.layers.append(
557
+ MSALayer(
558
+ msa_s,
559
+ token_z,
560
+ msa_dropout,
561
+ z_dropout,
562
+ pairwise_head_width,
563
+ pairwise_num_heads,
564
+ )
565
+ )
566
+
567
+ def forward(
568
+ self,
569
+ z: Tensor,
570
+ emb: Tensor,
571
+ feats: dict[str, Tensor],
572
+ use_kernels: bool = False,
573
+ ) -> Tensor:
574
+ """Perform the forward pass.
575
+
576
+ Parameters
577
+ ----------
578
+ z : Tensor
579
+ The pairwise embeddings
580
+ emb : Tensor
581
+ The input embeddings
582
+ feats : dict[str, Tensor]
583
+ Input features
584
+ use_kernels: bool
585
+ Whether to use kernels for triangular updates
586
+
587
+ Returns
588
+ -------
589
+ Tensor
590
+ The output pairwise embeddings.
591
+
592
+ """
593
+ # Set chunk sizes
594
+ if not self.training:
595
+ if z.shape[1] > const.chunk_size_threshold:
596
+ chunk_heads_pwa = True
597
+ chunk_size_transition_z = 64
598
+ chunk_size_transition_msa = 32
599
+ chunk_size_outer_product = 4
600
+ chunk_size_tri_attn = 128
601
+ else:
602
+ chunk_heads_pwa = False
603
+ chunk_size_transition_z = None
604
+ chunk_size_transition_msa = None
605
+ chunk_size_outer_product = None
606
+ chunk_size_tri_attn = 512
607
+ else:
608
+ chunk_heads_pwa = False
609
+ chunk_size_transition_z = None
610
+ chunk_size_transition_msa = None
611
+ chunk_size_outer_product = None
612
+ chunk_size_tri_attn = None
613
+
614
+ # Load relevant features
615
+ msa = feats["msa"]
616
+ msa = torch.nn.functional.one_hot(msa, num_classes=const.num_tokens)
617
+ has_deletion = feats["has_deletion"].unsqueeze(-1)
618
+ deletion_value = feats["deletion_value"].unsqueeze(-1)
619
+ is_paired = feats["msa_paired"].unsqueeze(-1)
620
+ msa_mask = feats["msa_mask"]
621
+ token_mask = feats["token_pad_mask"].float()
622
+ token_mask = token_mask[:, :, None] * token_mask[:, None, :]
623
+
624
+ # Compute MSA embeddings
625
+ if self.use_paired_feature:
626
+ m = torch.cat([msa, has_deletion, deletion_value, is_paired], dim=-1)
627
+ else:
628
+ m = torch.cat([msa, has_deletion, deletion_value], dim=-1)
629
+
630
+ # Subsample the MSA
631
+ if self.subsample_msa:
632
+ msa_indices = torch.randperm(msa.shape[1])[: self.num_subsampled_msa]
633
+ m = m[:, msa_indices]
634
+ msa_mask = msa_mask[:, msa_indices]
635
+
636
+ # Compute input projections
637
+ m = self.msa_proj(m)
638
+ m = m + self.s_proj(emb).unsqueeze(1)
639
+
640
+ # Perform MSA blocks
641
+ for i in range(self.msa_blocks):
642
+ if self.activation_checkpointing and self.training:
643
+ z, m = torch.utils.checkpoint.checkpoint(
644
+ self.layers[i],
645
+ z,
646
+ m,
647
+ token_mask,
648
+ msa_mask,
649
+ chunk_heads_pwa,
650
+ chunk_size_transition_z,
651
+ chunk_size_transition_msa,
652
+ chunk_size_outer_product,
653
+ chunk_size_tri_attn,
654
+ use_kernels,
655
+ )
656
+ else:
657
+ z, m = self.layers[i](
658
+ z,
659
+ m,
660
+ token_mask,
661
+ msa_mask,
662
+ chunk_heads_pwa,
663
+ chunk_size_transition_z,
664
+ chunk_size_transition_msa,
665
+ chunk_size_outer_product,
666
+ chunk_size_tri_attn,
667
+ use_kernels,
668
+ )
669
+ return z
670
+
671
+
672
+ class MSALayer(nn.Module):
673
+ """MSA module."""
674
+
675
+ def __init__(
676
+ self,
677
+ msa_s: int,
678
+ token_z: int,
679
+ msa_dropout: float,
680
+ z_dropout: float,
681
+ pairwise_head_width: int = 32,
682
+ pairwise_num_heads: int = 4,
683
+ ) -> None:
684
+ """Initialize the MSA module.
685
+
686
+ Parameters
687
+ ----------
688
+ token_z : int
689
+ The token pairwise embedding size.
690
+
691
+ """
692
+ super().__init__()
693
+ self.msa_dropout = msa_dropout
694
+ self.msa_transition = Transition(dim=msa_s, hidden=msa_s * 4)
695
+ self.pair_weighted_averaging = PairWeightedAveraging(
696
+ c_m=msa_s,
697
+ c_z=token_z,
698
+ c_h=32,
699
+ num_heads=8,
700
+ )
701
+
702
+ self.pairformer_layer = PairformerNoSeqLayer(
703
+ token_z=token_z,
704
+ dropout=z_dropout,
705
+ pairwise_head_width=pairwise_head_width,
706
+ pairwise_num_heads=pairwise_num_heads,
707
+ )
708
+ self.outer_product_mean = OuterProductMean(
709
+ c_in=msa_s,
710
+ c_hidden=32,
711
+ c_out=token_z,
712
+ )
713
+
714
+ def forward(
715
+ self,
716
+ z: Tensor,
717
+ m: Tensor,
718
+ token_mask: Tensor,
719
+ msa_mask: Tensor,
720
+ chunk_heads_pwa: bool = False,
721
+ chunk_size_transition_z: int = None,
722
+ chunk_size_transition_msa: int = None,
723
+ chunk_size_outer_product: int = None,
724
+ chunk_size_tri_attn: int = None,
725
+ use_kernels: bool = False,
726
+ ) -> tuple[Tensor, Tensor]:
727
+ """Perform the forward pass.
728
+
729
+ Parameters
730
+ ----------
731
+ z : Tensor
732
+ The pairwise embeddings
733
+ emb : Tensor
734
+ The input embeddings
735
+ feats : dict[str, Tensor]
736
+ Input features
737
+
738
+ Returns
739
+ -------
740
+ Tensor
741
+ The output pairwise embeddings.
742
+
743
+ """
744
+ # Communication to MSA stack
745
+ msa_dropout = get_dropout_mask(self.msa_dropout, m, self.training)
746
+ m = m + msa_dropout * self.pair_weighted_averaging(
747
+ m, z, token_mask, chunk_heads_pwa
748
+ )
749
+ m = m + self.msa_transition(m, chunk_size_transition_msa)
750
+
751
+ z = z + self.outer_product_mean(m, msa_mask, chunk_size_outer_product)
752
+
753
+ # Compute pairwise stack
754
+ z = self.pairformer_layer(
755
+ z, token_mask, chunk_size_tri_attn, use_kernels=use_kernels
756
+ )
757
+
758
+ return z, m
759
+
760
+
761
+ class BFactorModule(nn.Module):
762
+ """BFactor Module."""
763
+
764
+ def __init__(self, token_s: int, num_bins: int) -> None:
765
+ """Initialize the bfactor module.
766
+
767
+ Parameters
768
+ ----------
769
+ token_s : int
770
+ The token embedding size.
771
+
772
+ """
773
+ super().__init__()
774
+ self.bfactor = nn.Linear(token_s, num_bins)
775
+ self.num_bins = num_bins
776
+
777
+ def forward(self, s: Tensor) -> Tensor:
778
+ """Perform the forward pass.
779
+
780
+ Parameters
781
+ ----------
782
+ s : Tensor
783
+ The sequence embeddings
784
+
785
+ Returns
786
+ -------
787
+ Tensor
788
+ The predicted bfactor histogram.
789
+
790
+ """
791
+ return self.bfactor(s)
792
+
793
+
794
+ class DistogramModule(nn.Module):
795
+ """Distogram Module."""
796
+
797
+ def __init__(self, token_z: int, num_bins: int, num_distograms: int = 1) -> None:
798
+ """Initialize the distogram module.
799
+
800
+ Parameters
801
+ ----------
802
+ token_z : int
803
+ The token pairwise embedding size.
804
+
805
+ """
806
+ super().__init__()
807
+ self.distogram = nn.Linear(token_z, num_distograms * num_bins)
808
+ self.num_distograms = num_distograms
809
+ self.num_bins = num_bins
810
+
811
+ def forward(self, z: Tensor) -> Tensor:
812
+ """Perform the forward pass.
813
+
814
+ Parameters
815
+ ----------
816
+ z : Tensor
817
+ The pairwise embeddings
818
+
819
+ Returns
820
+ -------
821
+ Tensor
822
+ The predicted distogram.
823
+
824
+ """
825
+ z = z + z.transpose(1, 2)
826
+ return self.distogram(z).reshape(
827
+ z.shape[0], z.shape[1], z.shape[2], self.num_distograms, self.num_bins
828
+ )
vb_modules_utils.py CHANGED
@@ -1,303 +1,303 @@
1
- # started from code from https://github.com/lucidrains/alphafold3-pytorch, MIT License, Copyright (c) 2024 Phil Wang
2
-
3
- from functools import partial
4
- from typing import Optional
5
-
6
- import torch
7
- import torch.nn.functional as F
8
- from torch.nn import (
9
- Linear,
10
- Module,
11
- )
12
- from torch.types import Device
13
-
14
- LinearNoBias = partial(Linear, bias=False)
15
-
16
-
17
- def exists(v):
18
- return v is not None
19
-
20
-
21
- def default(v, d):
22
- return v if exists(v) else d
23
-
24
-
25
- def log(t, eps=1e-20):
26
- return torch.log(t.clamp(min=eps))
27
-
28
-
29
- class SwiGLU(Module):
30
- def forward(
31
- self,
32
- x, #: Float['... d']
33
- ): # -> Float[' ... (d//2)']:
34
- x, gates = x.chunk(2, dim=-1)
35
- return F.silu(gates) * x
36
-
37
-
38
- def center(atom_coords, atom_mask):
39
- atom_mean = torch.sum(
40
- atom_coords * atom_mask[:, :, None], dim=1, keepdim=True
41
- ) / torch.sum(atom_mask[:, :, None], dim=1, keepdim=True)
42
- atom_coords = atom_coords - atom_mean
43
- return atom_coords
44
-
45
-
46
- def compute_random_augmentation(
47
- multiplicity, s_trans=1.0, device=None, dtype=torch.float32
48
- ):
49
- R = random_rotations(multiplicity, dtype=dtype, device=device)
50
- random_trans = (
51
- torch.randn((multiplicity, 1, 3), dtype=dtype, device=device) * s_trans
52
- )
53
- return R, random_trans
54
-
55
-
56
- def randomly_rotate(coords, return_second_coords=False, second_coords=None):
57
- R = random_rotations(len(coords), coords.dtype, coords.device)
58
-
59
- if return_second_coords:
60
- return torch.einsum("bmd,bds->bms", coords, R), torch.einsum(
61
- "bmd,bds->bms", second_coords, R
62
- ) if second_coords is not None else None
63
-
64
- return torch.einsum("bmd,bds->bms", coords, R)
65
-
66
-
67
- def center_random_augmentation(
68
- atom_coords,
69
- atom_mask,
70
- s_trans=1.0,
71
- augmentation=True,
72
- centering=True,
73
- return_second_coords=False,
74
- second_coords=None,
75
- ):
76
- """Algorithm 19"""
77
- if centering:
78
- atom_mean = torch.sum(
79
- atom_coords * atom_mask[:, :, None], dim=1, keepdim=True
80
- ) / torch.sum(atom_mask[:, :, None], dim=1, keepdim=True)
81
- atom_coords = atom_coords - atom_mean
82
-
83
- if second_coords is not None:
84
- # apply same transformation also to this input
85
- second_coords = second_coords - atom_mean
86
-
87
- if augmentation:
88
- atom_coords, second_coords = randomly_rotate(
89
- atom_coords, return_second_coords=True, second_coords=second_coords
90
- )
91
- random_trans = torch.randn_like(atom_coords[:, 0:1, :]) * s_trans
92
- atom_coords = atom_coords + random_trans
93
-
94
- if second_coords is not None:
95
- second_coords = second_coords + random_trans
96
-
97
- if return_second_coords:
98
- return atom_coords, second_coords
99
-
100
- return atom_coords
101
-
102
-
103
- class ExponentialMovingAverage:
104
- """from https://github.com/yang-song/score_sde_pytorch/blob/main/models/ema.py, Apache-2.0 license
105
- Maintains (exponential) moving average of a set of parameters."""
106
-
107
- def __init__(self, parameters, decay, use_num_updates=True):
108
- """
109
- Args:
110
- parameters: Iterable of `torch.nn.Parameter`; usually the result of
111
- `model.parameters()`.
112
- decay: The exponential decay.
113
- use_num_updates: Whether to use number of updates when computing
114
- averages.
115
- """
116
- if decay < 0.0 or decay > 1.0:
117
- raise ValueError("Decay must be between 0 and 1")
118
- self.decay = decay
119
- self.num_updates = 0 if use_num_updates else None
120
- self.shadow_params = [p.clone().detach() for p in parameters if p.requires_grad]
121
- self.collected_params = []
122
-
123
- def update(self, parameters):
124
- """
125
- Update currently maintained parameters.
126
- Call this every time the parameters are updated, such as the result of
127
- the `optimizer.step()` call.
128
- Args:
129
- parameters: Iterable of `torch.nn.Parameter`; usually the same set of
130
- parameters used to initialize this object.
131
- """
132
- decay = self.decay
133
- if self.num_updates is not None:
134
- self.num_updates += 1
135
- decay = min(decay, (1 + self.num_updates) / (10 + self.num_updates))
136
- one_minus_decay = 1.0 - decay
137
- with torch.no_grad():
138
- parameters = [p for p in parameters if p.requires_grad]
139
- for s_param, param in zip(self.shadow_params, parameters):
140
- s_param.sub_(one_minus_decay * (s_param - param))
141
-
142
- def compatible(self, parameters):
143
- if len(self.shadow_params) != len(parameters):
144
- print(
145
- f"Model has {len(self.shadow_params)} parameter tensors, the incoming ema {len(parameters)}"
146
- )
147
- return False
148
-
149
- for s_param, param in zip(self.shadow_params, parameters):
150
- if param.data.shape != s_param.data.shape:
151
- print(
152
- f"Model has parameter tensor of shape {s_param.data.shape} , the incoming ema {param.data.shape}"
153
- )
154
- return False
155
- return True
156
-
157
- def copy_to(self, parameters):
158
- """
159
- Copy current parameters into given collection of parameters.
160
- Args:
161
- parameters: Iterable of `torch.nn.Parameter`; the parameters to be
162
- updated with the stored moving averages.
163
- """
164
- parameters = [p for p in parameters if p.requires_grad]
165
- for s_param, param in zip(self.shadow_params, parameters):
166
- if param.requires_grad:
167
- param.data.copy_(s_param.data)
168
-
169
- def store(self, parameters):
170
- """
171
- Save the current parameters for restoring later.
172
- Args:
173
- parameters: Iterable of `torch.nn.Parameter`; the parameters to be
174
- temporarily stored.
175
- """
176
- self.collected_params = [param.clone() for param in parameters]
177
-
178
- def restore(self, parameters):
179
- """
180
- Restore the parameters stored with the `store` method.
181
- Useful to validate the model with EMA parameters without affecting the
182
- original optimization process. Store the parameters before the
183
- `copy_to` method. After validation (or model saving), use this to
184
- restore the former parameters.
185
- Args:
186
- parameters: Iterable of `torch.nn.Parameter`; the parameters to be
187
- updated with the stored parameters.
188
- """
189
- for c_param, param in zip(self.collected_params, parameters):
190
- param.data.copy_(c_param.data)
191
-
192
- def state_dict(self):
193
- return dict(
194
- decay=self.decay,
195
- num_updates=self.num_updates,
196
- shadow_params=self.shadow_params,
197
- )
198
-
199
- def load_state_dict(self, state_dict, device):
200
- self.decay = state_dict["decay"]
201
- self.num_updates = state_dict["num_updates"]
202
- self.shadow_params = [
203
- tensor.to(device) for tensor in state_dict["shadow_params"]
204
- ]
205
-
206
- def to(self, device):
207
- self.shadow_params = [tensor.to(device) for tensor in self.shadow_params]
208
-
209
-
210
- # the following is copied from Torch3D, BSD License, Copyright (c) Meta Platforms, Inc. and affiliates.
211
-
212
-
213
- def _copysign(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
214
- """
215
- Return a tensor where each element has the absolute value taken from the,
216
- corresponding element of a, with sign taken from the corresponding
217
- element of b. This is like the standard copysign floating-point operation,
218
- but is not careful about negative 0 and NaN.
219
-
220
- Args:
221
- a: source tensor.
222
- b: tensor whose signs will be used, of the same shape as a.
223
-
224
- Returns:
225
- Tensor of the same shape as a with the signs of b.
226
- """
227
- signs_differ = (a < 0) != (b < 0)
228
- return torch.where(signs_differ, -a, a)
229
-
230
-
231
- def quaternion_to_matrix(quaternions: torch.Tensor) -> torch.Tensor:
232
- """
233
- Convert rotations given as quaternions to rotation matrices.
234
-
235
- Args:
236
- quaternions: quaternions with real part first,
237
- as tensor of shape (..., 4).
238
-
239
- Returns:
240
- Rotation matrices as tensor of shape (..., 3, 3).
241
- """
242
- r, i, j, k = torch.unbind(quaternions, -1)
243
- # pyre-fixme[58]: `/` is not supported for operand types `float` and `Tensor`.
244
- two_s = 2.0 / (quaternions * quaternions).sum(-1)
245
-
246
- o = torch.stack(
247
- (
248
- 1 - two_s * (j * j + k * k),
249
- two_s * (i * j - k * r),
250
- two_s * (i * k + j * r),
251
- two_s * (i * j + k * r),
252
- 1 - two_s * (i * i + k * k),
253
- two_s * (j * k - i * r),
254
- two_s * (i * k - j * r),
255
- two_s * (j * k + i * r),
256
- 1 - two_s * (i * i + j * j),
257
- ),
258
- -1,
259
- )
260
- return o.reshape(quaternions.shape[:-1] + (3, 3))
261
-
262
-
263
- def random_quaternions(
264
- n: int, dtype: Optional[torch.dtype] = None, device: Optional[Device] = None
265
- ) -> torch.Tensor:
266
- """
267
- Generate random quaternions representing rotations,
268
- i.e. versors with nonnegative real part.
269
-
270
- Args:
271
- n: Number of quaternions in a batch to return.
272
- dtype: Type to return.
273
- device: Desired device of returned tensor. Default:
274
- uses the current device for the default tensor type.
275
-
276
- Returns:
277
- Quaternions as tensor of shape (N, 4).
278
- """
279
- if isinstance(device, str):
280
- device = torch.device(device)
281
- o = torch.randn((n, 4), dtype=dtype, device=device)
282
- s = (o * o).sum(1)
283
- o = o / _copysign(torch.sqrt(s), o[:, 0])[:, None]
284
- return o
285
-
286
-
287
- def random_rotations(
288
- n: int, dtype: Optional[torch.dtype] = None, device: Optional[Device] = None
289
- ) -> torch.Tensor:
290
- """
291
- Generate random rotations as 3x3 rotation matrices.
292
-
293
- Args:
294
- n: Number of rotation matrices in a batch to return.
295
- dtype: Type to return.
296
- device: Device of returned tensor. Default: if None,
297
- uses the current device for the default tensor type.
298
-
299
- Returns:
300
- Rotation matrices as tensor of shape (n, 3, 3).
301
- """
302
- quaternions = random_quaternions(n, dtype=dtype, device=device)
303
- return quaternion_to_matrix(quaternions)
 
1
+ # started from code from https://github.com/lucidrains/alphafold3-pytorch, MIT License, Copyright (c) 2024 Phil Wang
2
+
3
+ from functools import partial
4
+ from typing import Optional
5
+
6
+ import torch
7
+ import torch.nn.functional as F
8
+ from torch.nn import (
9
+ Linear,
10
+ Module,
11
+ )
12
+ from torch.types import Device
13
+
14
+ LinearNoBias = partial(Linear, bias=False)
15
+
16
+
17
+ def exists(v):
18
+ return v is not None
19
+
20
+
21
+ def default(v, d):
22
+ return v if exists(v) else d
23
+
24
+
25
+ def log(t, eps=1e-20):
26
+ return torch.log(t.clamp(min=eps))
27
+
28
+
29
+ class SwiGLU(Module):
30
+ def forward(
31
+ self,
32
+ x, #: Float['... d']
33
+ ): # -> Float[' ... (d//2)']:
34
+ x, gates = x.chunk(2, dim=-1)
35
+ return F.silu(gates) * x
36
+
37
+
38
+ def center(atom_coords, atom_mask):
39
+ atom_mean = torch.sum(
40
+ atom_coords * atom_mask[:, :, None], dim=1, keepdim=True
41
+ ) / torch.sum(atom_mask[:, :, None], dim=1, keepdim=True)
42
+ atom_coords = atom_coords - atom_mean
43
+ return atom_coords
44
+
45
+
46
+ def compute_random_augmentation(
47
+ multiplicity, s_trans=1.0, device=None, dtype=torch.float32
48
+ ):
49
+ R = random_rotations(multiplicity, dtype=dtype, device=device)
50
+ random_trans = (
51
+ torch.randn((multiplicity, 1, 3), dtype=dtype, device=device) * s_trans
52
+ )
53
+ return R, random_trans
54
+
55
+
56
+ def randomly_rotate(coords, return_second_coords=False, second_coords=None):
57
+ R = random_rotations(len(coords), coords.dtype, coords.device)
58
+
59
+ if return_second_coords:
60
+ return torch.einsum("bmd,bds->bms", coords, R), torch.einsum(
61
+ "bmd,bds->bms", second_coords, R
62
+ ) if second_coords is not None else None
63
+
64
+ return torch.einsum("bmd,bds->bms", coords, R)
65
+
66
+
67
+ def center_random_augmentation(
68
+ atom_coords,
69
+ atom_mask,
70
+ s_trans=1.0,
71
+ augmentation=True,
72
+ centering=True,
73
+ return_second_coords=False,
74
+ second_coords=None,
75
+ ):
76
+ """Algorithm 19"""
77
+ if centering:
78
+ atom_mean = torch.sum(
79
+ atom_coords * atom_mask[:, :, None], dim=1, keepdim=True
80
+ ) / torch.sum(atom_mask[:, :, None], dim=1, keepdim=True)
81
+ atom_coords = atom_coords - atom_mean
82
+
83
+ if second_coords is not None:
84
+ # apply same transformation also to this input
85
+ second_coords = second_coords - atom_mean
86
+
87
+ if augmentation:
88
+ atom_coords, second_coords = randomly_rotate(
89
+ atom_coords, return_second_coords=True, second_coords=second_coords
90
+ )
91
+ random_trans = torch.randn_like(atom_coords[:, 0:1, :]) * s_trans
92
+ atom_coords = atom_coords + random_trans
93
+
94
+ if second_coords is not None:
95
+ second_coords = second_coords + random_trans
96
+
97
+ if return_second_coords:
98
+ return atom_coords, second_coords
99
+
100
+ return atom_coords
101
+
102
+
103
+ class ExponentialMovingAverage:
104
+ """from https://github.com/yang-song/score_sde_pytorch/blob/main/models/ema.py, Apache-2.0 license
105
+ Maintains (exponential) moving average of a set of parameters."""
106
+
107
+ def __init__(self, parameters, decay, use_num_updates=True):
108
+ """
109
+ Args:
110
+ parameters: Iterable of `torch.nn.Parameter`; usually the result of
111
+ `model.parameters()`.
112
+ decay: The exponential decay.
113
+ use_num_updates: Whether to use number of updates when computing
114
+ averages.
115
+ """
116
+ if decay < 0.0 or decay > 1.0:
117
+ raise ValueError("Decay must be between 0 and 1")
118
+ self.decay = decay
119
+ self.num_updates = 0 if use_num_updates else None
120
+ self.shadow_params = [p.clone().detach() for p in parameters if p.requires_grad]
121
+ self.collected_params = []
122
+
123
+ def update(self, parameters):
124
+ """
125
+ Update currently maintained parameters.
126
+ Call this every time the parameters are updated, such as the result of
127
+ the `optimizer.step()` call.
128
+ Args:
129
+ parameters: Iterable of `torch.nn.Parameter`; usually the same set of
130
+ parameters used to initialize this object.
131
+ """
132
+ decay = self.decay
133
+ if self.num_updates is not None:
134
+ self.num_updates += 1
135
+ decay = min(decay, (1 + self.num_updates) / (10 + self.num_updates))
136
+ one_minus_decay = 1.0 - decay
137
+ with torch.no_grad():
138
+ parameters = [p for p in parameters if p.requires_grad]
139
+ for s_param, param in zip(self.shadow_params, parameters):
140
+ s_param.sub_(one_minus_decay * (s_param - param))
141
+
142
+ def compatible(self, parameters):
143
+ if len(self.shadow_params) != len(parameters):
144
+ print(
145
+ f"Model has {len(self.shadow_params)} parameter tensors, the incoming ema {len(parameters)}"
146
+ )
147
+ return False
148
+
149
+ for s_param, param in zip(self.shadow_params, parameters):
150
+ if param.data.shape != s_param.data.shape:
151
+ print(
152
+ f"Model has parameter tensor of shape {s_param.data.shape} , the incoming ema {param.data.shape}"
153
+ )
154
+ return False
155
+ return True
156
+
157
+ def copy_to(self, parameters):
158
+ """
159
+ Copy current parameters into given collection of parameters.
160
+ Args:
161
+ parameters: Iterable of `torch.nn.Parameter`; the parameters to be
162
+ updated with the stored moving averages.
163
+ """
164
+ parameters = [p for p in parameters if p.requires_grad]
165
+ for s_param, param in zip(self.shadow_params, parameters):
166
+ if param.requires_grad:
167
+ param.data.copy_(s_param.data)
168
+
169
+ def store(self, parameters):
170
+ """
171
+ Save the current parameters for restoring later.
172
+ Args:
173
+ parameters: Iterable of `torch.nn.Parameter`; the parameters to be
174
+ temporarily stored.
175
+ """
176
+ self.collected_params = [param.clone() for param in parameters]
177
+
178
+ def restore(self, parameters):
179
+ """
180
+ Restore the parameters stored with the `store` method.
181
+ Useful to validate the model with EMA parameters without affecting the
182
+ original optimization process. Store the parameters before the
183
+ `copy_to` method. After validation (or model saving), use this to
184
+ restore the former parameters.
185
+ Args:
186
+ parameters: Iterable of `torch.nn.Parameter`; the parameters to be
187
+ updated with the stored parameters.
188
+ """
189
+ for c_param, param in zip(self.collected_params, parameters):
190
+ param.data.copy_(c_param.data)
191
+
192
+ def state_dict(self):
193
+ return dict(
194
+ decay=self.decay,
195
+ num_updates=self.num_updates,
196
+ shadow_params=self.shadow_params,
197
+ )
198
+
199
+ def load_state_dict(self, state_dict, device):
200
+ self.decay = state_dict["decay"]
201
+ self.num_updates = state_dict["num_updates"]
202
+ self.shadow_params = [
203
+ tensor.to(device) for tensor in state_dict["shadow_params"]
204
+ ]
205
+
206
+ def to(self, device):
207
+ self.shadow_params = [tensor.to(device) for tensor in self.shadow_params]
208
+
209
+
210
+ # the following is copied from Torch3D, BSD License, Copyright (c) Meta Platforms, Inc. and affiliates.
211
+
212
+
213
+ def _copysign(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
214
+ """
215
+ Return a tensor where each element has the absolute value taken from the,
216
+ corresponding element of a, with sign taken from the corresponding
217
+ element of b. This is like the standard copysign floating-point operation,
218
+ but is not careful about negative 0 and NaN.
219
+
220
+ Args:
221
+ a: source tensor.
222
+ b: tensor whose signs will be used, of the same shape as a.
223
+
224
+ Returns:
225
+ Tensor of the same shape as a with the signs of b.
226
+ """
227
+ signs_differ = (a < 0) != (b < 0)
228
+ return torch.where(signs_differ, -a, a)
229
+
230
+
231
+ def quaternion_to_matrix(quaternions: torch.Tensor) -> torch.Tensor:
232
+ """
233
+ Convert rotations given as quaternions to rotation matrices.
234
+
235
+ Args:
236
+ quaternions: quaternions with real part first,
237
+ as tensor of shape (..., 4).
238
+
239
+ Returns:
240
+ Rotation matrices as tensor of shape (..., 3, 3).
241
+ """
242
+ r, i, j, k = torch.unbind(quaternions, -1)
243
+ # pyre-fixme[58]: `/` is not supported for operand types `float` and `Tensor`.
244
+ two_s = 2.0 / (quaternions * quaternions).sum(-1)
245
+
246
+ o = torch.stack(
247
+ (
248
+ 1 - two_s * (j * j + k * k),
249
+ two_s * (i * j - k * r),
250
+ two_s * (i * k + j * r),
251
+ two_s * (i * j + k * r),
252
+ 1 - two_s * (i * i + k * k),
253
+ two_s * (j * k - i * r),
254
+ two_s * (i * k - j * r),
255
+ two_s * (j * k + i * r),
256
+ 1 - two_s * (i * i + j * j),
257
+ ),
258
+ -1,
259
+ )
260
+ return o.reshape(quaternions.shape[:-1] + (3, 3))
261
+
262
+
263
+ def random_quaternions(
264
+ n: int, dtype: Optional[torch.dtype] = None, device: Optional[Device] = None
265
+ ) -> torch.Tensor:
266
+ """
267
+ Generate random quaternions representing rotations,
268
+ i.e. versors with nonnegative real part.
269
+
270
+ Args:
271
+ n: Number of quaternions in a batch to return.
272
+ dtype: Type to return.
273
+ device: Desired device of returned tensor. Default:
274
+ uses the current device for the default tensor type.
275
+
276
+ Returns:
277
+ Quaternions as tensor of shape (N, 4).
278
+ """
279
+ if isinstance(device, str):
280
+ device = torch.device(device)
281
+ o = torch.randn((n, 4), dtype=dtype, device=device)
282
+ s = (o * o).sum(1)
283
+ o = o / _copysign(torch.sqrt(s), o[:, 0])[:, None]
284
+ return o
285
+
286
+
287
+ def random_rotations(
288
+ n: int, dtype: Optional[torch.dtype] = None, device: Optional[Device] = None
289
+ ) -> torch.Tensor:
290
+ """
291
+ Generate random rotations as 3x3 rotation matrices.
292
+
293
+ Args:
294
+ n: Number of rotation matrices in a batch to return.
295
+ dtype: Type to return.
296
+ device: Device of returned tensor. Default: if None,
297
+ uses the current device for the default tensor type.
298
+
299
+ Returns:
300
+ Rotation matrices as tensor of shape (n, 3, 3).
301
+ """
302
+ quaternions = random_quaternions(n, dtype=dtype, device=device)
303
+ return quaternion_to_matrix(quaternions)
vb_potentials_potentials.py CHANGED
@@ -1,787 +1,787 @@
1
- from abc import ABC, abstractmethod
2
- from typing import Optional, Dict, Any, Set, List, Union
3
-
4
- import torch
5
- import numpy as np
6
- from . import vb_const as const
7
- from .vb_potentials_schedules import (
8
- ParameterSchedule,
9
- ExponentialInterpolation,
10
- PiecewiseStepFunction,
11
- )
12
- from .vb_loss_diffusionv2 import weighted_rigid_align
13
-
14
-
15
- class Potential(ABC):
16
- def __init__(
17
- self,
18
- parameters: Optional[
19
- Dict[str, Union[ParameterSchedule, float, int, bool]]
20
- ] = None,
21
- ):
22
- self.parameters = parameters
23
-
24
- def compute(self, coords, feats, parameters):
25
- index, args, com_args, ref_args, operator_args = self.compute_args(
26
- feats, parameters
27
- )
28
-
29
- if index.shape[1] == 0:
30
- return torch.zeros(coords.shape[:-2], device=coords.device)
31
-
32
- if com_args is not None:
33
- com_index, atom_pad_mask = com_args
34
- unpad_com_index = com_index[atom_pad_mask]
35
- unpad_coords = coords[..., atom_pad_mask, :]
36
- coords = torch.zeros(
37
- (*unpad_coords.shape[:-2], unpad_com_index.max() + 1, 3),
38
- device=coords.device,
39
- ).scatter_reduce(
40
- -2,
41
- unpad_com_index.unsqueeze(-1).expand_as(unpad_coords),
42
- unpad_coords,
43
- "mean",
44
- )
45
- else:
46
- com_index, atom_pad_mask = None, None
47
-
48
- if ref_args is not None:
49
- ref_coords, ref_mask, ref_atom_index, ref_token_index = ref_args
50
- coords = coords[..., ref_atom_index, :]
51
- else:
52
- ref_coords, ref_mask, ref_atom_index, ref_token_index = (
53
- None,
54
- None,
55
- None,
56
- None,
57
- )
58
-
59
- if operator_args is not None:
60
- negation_mask, union_index = operator_args
61
- else:
62
- negation_mask, union_index = None, None
63
-
64
- value = self.compute_variable(
65
- coords,
66
- index,
67
- ref_coords=ref_coords,
68
- ref_mask=ref_mask,
69
- compute_gradient=False,
70
- )
71
- energy = self.compute_function(
72
- value, *args, negation_mask=negation_mask, compute_derivative=False
73
- )
74
-
75
- if union_index is not None:
76
- neg_exp_energy = torch.exp(-1 * parameters["union_lambda"] * energy)
77
- Z = torch.zeros(
78
- (*energy.shape[:-1], union_index.max() + 1), device=union_index.device
79
- ).scatter_reduce(
80
- -1,
81
- union_index.expand_as(neg_exp_energy),
82
- neg_exp_energy,
83
- "sum",
84
- )
85
- softmax_energy = neg_exp_energy / Z[..., union_index]
86
- softmax_energy[Z[..., union_index] == 0] = 0
87
- return (energy * softmax_energy).sum(dim=-1)
88
-
89
- return energy.sum(dim=tuple(range(1, energy.dim())))
90
-
91
- def compute_gradient(self, coords, feats, parameters):
92
- index, args, com_args, ref_args, operator_args = self.compute_args(
93
- feats, parameters
94
- )
95
- if index.shape[1] == 0:
96
- return torch.zeros_like(coords)
97
-
98
- if com_args is not None:
99
- com_index, atom_pad_mask = com_args
100
- unpad_coords = coords[..., atom_pad_mask, :]
101
- unpad_com_index = com_index[atom_pad_mask]
102
- coords = torch.zeros(
103
- (*unpad_coords.shape[:-2], unpad_com_index.max() + 1, 3),
104
- device=coords.device,
105
- ).scatter_reduce(
106
- -2,
107
- unpad_com_index.unsqueeze(-1).expand_as(unpad_coords),
108
- unpad_coords,
109
- "mean",
110
- )
111
- com_counts = torch.bincount(com_index[atom_pad_mask])
112
- else:
113
- com_index, atom_pad_mask = None, None
114
-
115
- if ref_args is not None:
116
- ref_coords, ref_mask, ref_atom_index, ref_token_index = ref_args
117
- coords = coords[..., ref_atom_index, :]
118
- else:
119
- ref_coords, ref_mask, ref_atom_index, ref_token_index = (
120
- None,
121
- None,
122
- None,
123
- None,
124
- )
125
-
126
- if operator_args is not None:
127
- negation_mask, union_index = operator_args
128
- else:
129
- negation_mask, union_index = None, None
130
-
131
- value, grad_value = self.compute_variable(
132
- coords,
133
- index,
134
- ref_coords=ref_coords,
135
- ref_mask=ref_mask,
136
- compute_gradient=True,
137
- )
138
- energy, dEnergy = self.compute_function(
139
- value,
140
- *args, negation_mask=negation_mask, compute_derivative=True
141
- )
142
- if union_index is not None:
143
- neg_exp_energy = torch.exp(-1 * parameters["union_lambda"] * energy)
144
- Z = torch.zeros(
145
- (*energy.shape[:-1], union_index.max() + 1), device=union_index.device
146
- ).scatter_reduce(
147
- -1,
148
- union_index.expand_as(energy),
149
- neg_exp_energy,
150
- "sum",
151
- )
152
- softmax_energy = neg_exp_energy / Z[..., union_index]
153
- softmax_energy[Z[..., union_index] == 0] = 0
154
- f = torch.zeros(
155
- (*energy.shape[:-1], union_index.max() + 1), device=union_index.device
156
- ).scatter_reduce(
157
- -1,
158
- union_index.expand_as(energy),
159
- energy * softmax_energy,
160
- "sum",
161
- )
162
- dSoftmax = (
163
- dEnergy
164
- * softmax_energy
165
- * (1 + parameters["union_lambda"] * (energy - f[..., union_index]))
166
- )
167
- prod = dSoftmax.tile(grad_value.shape[-3]).unsqueeze(
168
- -1
169
- ) * grad_value.flatten(start_dim=-3, end_dim=-2)
170
- if prod.dim() > 3:
171
- prod = prod.sum(dim=list(range(1, prod.dim() - 2)))
172
- grad_atom = torch.zeros_like(coords).scatter_reduce(
173
- -2,
174
- index.flatten(start_dim=0, end_dim=1)
175
- .unsqueeze(-1)
176
- .expand((*coords.shape[:-2], -1, 3)),
177
- prod,
178
- "sum",
179
- )
180
- else:
181
- prod = dEnergy.tile(grad_value.shape[-3]).unsqueeze(
182
- -1
183
- ) * grad_value.flatten(start_dim=-3, end_dim=-2)
184
- if prod.dim() > 3:
185
- prod = prod.sum(dim=list(range(1, prod.dim() - 2)))
186
- grad_atom = torch.zeros_like(coords).scatter_reduce(
187
- -2,
188
- index.flatten(start_dim=0, end_dim=1)
189
- .unsqueeze(-1)
190
- .expand((*coords.shape[:-2], -1, 3)), # 9 x 516 x 3
191
- prod,
192
- "sum",
193
- )
194
-
195
- if com_index is not None:
196
- grad_atom = grad_atom[..., com_index, :]
197
- elif ref_token_index is not None:
198
- grad_atom = grad_atom[..., ref_token_index, :]
199
-
200
- return grad_atom
201
-
202
- def compute_parameters(self, t):
203
- if self.parameters is None:
204
- return None
205
- parameters = {
206
- name: parameter
207
- if not isinstance(parameter, ParameterSchedule)
208
- else parameter.compute(t)
209
- for name, parameter in self.parameters.items()
210
- }
211
- return parameters
212
-
213
- @abstractmethod
214
- def compute_function(
215
- self, value, *args, negation_mask=None, compute_derivative=False
216
- ):
217
- raise NotImplementedError
218
-
219
- @abstractmethod
220
- def compute_variable(self, coords, index, compute_gradient=False):
221
- raise NotImplementedError
222
-
223
- @abstractmethod
224
- def compute_args(self, t, feats, **parameters):
225
- raise NotImplementedError
226
-
227
- def get_reference_coords(self, feats, parameters):
228
- return None, None
229
-
230
-
231
- class FlatBottomPotential(Potential):
232
- def compute_function(
233
- self,
234
- value,
235
- k,
236
- lower_bounds,
237
- upper_bounds,
238
- negation_mask=None,
239
- compute_derivative=False,
240
- ):
241
- if lower_bounds is None:
242
- lower_bounds = torch.full_like(value, float("-inf"))
243
- if upper_bounds is None:
244
- upper_bounds = torch.full_like(value, float("inf"))
245
- lower_bounds = lower_bounds.expand_as(value).clone()
246
- upper_bounds = upper_bounds.expand_as(value).clone()
247
-
248
- if negation_mask is not None:
249
- unbounded_below_mask = torch.isneginf(lower_bounds)
250
- unbounded_above_mask = torch.isposinf(upper_bounds)
251
- unbounded_mask = unbounded_below_mask + unbounded_above_mask
252
- assert torch.all(unbounded_mask + negation_mask)
253
- lower_bounds[~unbounded_above_mask * ~negation_mask] = upper_bounds[
254
- ~unbounded_above_mask * ~negation_mask
255
- ]
256
- upper_bounds[~unbounded_above_mask * ~negation_mask] = float("inf")
257
- upper_bounds[~unbounded_below_mask * ~negation_mask] = lower_bounds[
258
- ~unbounded_below_mask * ~negation_mask
259
- ]
260
- lower_bounds[~unbounded_below_mask * ~negation_mask] = float("-inf")
261
-
262
- neg_overflow_mask = value < lower_bounds
263
- pos_overflow_mask = value > upper_bounds
264
-
265
- energy = torch.zeros_like(value)
266
- energy[neg_overflow_mask] = (k * (lower_bounds - value))[neg_overflow_mask]
267
- energy[pos_overflow_mask] = (k * (value - upper_bounds))[pos_overflow_mask]
268
- if not compute_derivative:
269
- return energy
270
-
271
- dEnergy = torch.zeros_like(value)
272
- dEnergy[neg_overflow_mask] = (
273
- -1 * k.expand_as(neg_overflow_mask)[neg_overflow_mask]
274
- )
275
- dEnergy[pos_overflow_mask] = (
276
- 1 * k.expand_as(pos_overflow_mask)[pos_overflow_mask]
277
- )
278
-
279
- return energy, dEnergy
280
-
281
-
282
- class ReferencePotential(Potential):
283
- def compute_variable(
284
- self, coords, index, ref_coords, ref_mask, compute_gradient=False
285
- ):
286
- aligned_ref_coords = weighted_rigid_align(
287
- ref_coords.float(),
288
- coords[:, index].float(),
289
- ref_mask,
290
- ref_mask,
291
- )
292
-
293
- r = coords[:, index] - aligned_ref_coords
294
- r_norm = torch.linalg.norm(r, dim=-1)
295
-
296
- if not compute_gradient:
297
- return r_norm
298
-
299
- r_hat = r / r_norm.unsqueeze(-1)
300
- grad = (r_hat * ref_mask.unsqueeze(-1)).unsqueeze(1)
301
- return r_norm, grad
302
-
303
-
304
- class DistancePotential(Potential):
305
- def compute_variable(
306
- self, coords, index, ref_coords=None, ref_mask=None, compute_gradient=False
307
- ):
308
- r_ij = coords.index_select(-2, index[0]) - coords.index_select(-2, index[1])
309
- r_ij_norm = torch.linalg.norm(r_ij, dim=-1)
310
- r_hat_ij = r_ij / r_ij_norm.unsqueeze(-1)
311
-
312
- if not compute_gradient:
313
- return r_ij_norm
314
-
315
- grad_i = r_hat_ij
316
- grad_j = -1 * r_hat_ij
317
- grad = torch.stack((grad_i, grad_j), dim=1)
318
- return r_ij_norm, grad
319
-
320
-
321
- class DihedralPotential(Potential):
322
- def compute_variable(
323
- self, coords, index, ref_coords=None, ref_mask=None, compute_gradient=False
324
- ):
325
- r_ij = coords.index_select(-2, index[0]) - coords.index_select(-2, index[1])
326
- r_kj = coords.index_select(-2, index[2]) - coords.index_select(-2, index[1])
327
- r_kl = coords.index_select(-2, index[2]) - coords.index_select(-2, index[3])
328
-
329
- n_ijk = torch.cross(r_ij, r_kj, dim=-1)
330
- n_jkl = torch.cross(r_kj, r_kl, dim=-1)
331
-
332
- r_kj_norm = torch.linalg.norm(r_kj, dim=-1)
333
- n_ijk_norm = torch.linalg.norm(n_ijk, dim=-1)
334
- n_jkl_norm = torch.linalg.norm(n_jkl, dim=-1)
335
-
336
- sign_phi = torch.sign(
337
- r_kj.unsqueeze(-2) @ torch.cross(n_ijk, n_jkl, dim=-1).unsqueeze(-1)
338
- ).squeeze(-1, -2)
339
- phi = sign_phi * torch.arccos(
340
- torch.clamp(
341
- (n_ijk.unsqueeze(-2) @ n_jkl.unsqueeze(-1)).squeeze(-1, -2)
342
- / (n_ijk_norm * n_jkl_norm),
343
- -1 + 1e-8,
344
- 1 - 1e-8,
345
- )
346
- )
347
-
348
- if not compute_gradient:
349
- return phi
350
-
351
- a = (
352
- (r_ij.unsqueeze(-2) @ r_kj.unsqueeze(-1)).squeeze(-1, -2) / (r_kj_norm**2)
353
- ).unsqueeze(-1)
354
- b = (
355
- (r_kl.unsqueeze(-2) @ r_kj.unsqueeze(-1)).squeeze(-1, -2) / (r_kj_norm**2)
356
- ).unsqueeze(-1)
357
-
358
- grad_i = n_ijk * (r_kj_norm / n_ijk_norm**2).unsqueeze(-1)
359
- grad_l = -1 * n_jkl * (r_kj_norm / n_jkl_norm**2).unsqueeze(-1)
360
- grad_j = (a - 1) * grad_i - b * grad_l
361
- grad_k = (b - 1) * grad_l - a * grad_i
362
- grad = torch.stack((grad_i, grad_j, grad_k, grad_l), dim=1)
363
- return phi, grad
364
-
365
-
366
- class AbsDihedralPotential(DihedralPotential):
367
- def compute_variable(
368
- self, coords, index, ref_coords=None, ref_mask=None, compute_gradient=False
369
- ):
370
- if not compute_gradient:
371
- phi = super().compute_variable(
372
- coords, index, compute_gradient=compute_gradient
373
- )
374
- phi = torch.abs(phi)
375
- return phi
376
-
377
- phi, grad = super().compute_variable(
378
- coords, index, compute_gradient=compute_gradient
379
- )
380
- grad[(phi < 0)[..., None, :, None].expand_as(grad)] *= -1
381
- phi = torch.abs(phi)
382
-
383
- return phi, grad
384
-
385
-
386
- class PoseBustersPotential(FlatBottomPotential, DistancePotential):
387
- def compute_args(self, feats, parameters):
388
- pair_index = feats["rdkit_bounds_index"][0]
389
- lower_bounds = feats["rdkit_lower_bounds"][0].clone()
390
- upper_bounds = feats["rdkit_upper_bounds"][0].clone()
391
- bond_mask = feats["rdkit_bounds_bond_mask"][0]
392
- angle_mask = feats["rdkit_bounds_angle_mask"][0]
393
-
394
- lower_bounds[bond_mask * ~angle_mask] *= 1.0 - parameters["bond_buffer"]
395
- upper_bounds[bond_mask * ~angle_mask] *= 1.0 + parameters["bond_buffer"]
396
- lower_bounds[~bond_mask * angle_mask] *= 1.0 - parameters["angle_buffer"]
397
- upper_bounds[~bond_mask * angle_mask] *= 1.0 + parameters["angle_buffer"]
398
- lower_bounds[bond_mask * angle_mask] *= 1.0 - min(
399
- parameters["bond_buffer"], parameters["angle_buffer"]
400
- )
401
- upper_bounds[bond_mask * angle_mask] *= 1.0 + min(
402
- parameters["bond_buffer"], parameters["angle_buffer"]
403
- )
404
- lower_bounds[~bond_mask * ~angle_mask] *= 1.0 - parameters["clash_buffer"]
405
- upper_bounds[~bond_mask * ~angle_mask] = float("inf")
406
-
407
- vdw_radii = torch.zeros(
408
- const.num_elements, dtype=torch.float32, device=pair_index.device
409
- )
410
- vdw_radii[1:119] = torch.tensor(
411
- const.vdw_radii, dtype=torch.float32, device=pair_index.device
412
- )
413
- atom_vdw_radii = (
414
- feats["ref_element"].float() @ vdw_radii.unsqueeze(-1)
415
- ).squeeze(-1)[0]
416
- bond_cutoffs = 0.35 + atom_vdw_radii[pair_index].mean(dim=0)
417
- lower_bounds[~bond_mask] = torch.max(lower_bounds[~bond_mask], bond_cutoffs[~bond_mask])
418
- upper_bounds[bond_mask] = torch.min(upper_bounds[bond_mask], bond_cutoffs[bond_mask])
419
-
420
- k = torch.ones_like(lower_bounds)
421
-
422
- return pair_index, (k, lower_bounds, upper_bounds), None, None, None
423
-
424
-
425
- class ConnectionsPotential(FlatBottomPotential, DistancePotential):
426
- def compute_args(self, feats, parameters):
427
- pair_index = feats["connected_atom_index"][0]
428
- lower_bounds = None
429
- upper_bounds = torch.full(
430
- (pair_index.shape[1],), parameters["buffer"], device=pair_index.device
431
- )
432
- k = torch.ones_like(upper_bounds)
433
-
434
- return pair_index, (k, lower_bounds, upper_bounds), None, None, None
435
-
436
-
437
- class VDWOverlapPotential(FlatBottomPotential, DistancePotential):
438
- def compute_args(self, feats, parameters):
439
- atom_chain_id = (
440
- torch.bmm(
441
- feats["atom_to_token"].float(), feats["asym_id"].unsqueeze(-1).float()
442
- )
443
- .squeeze(-1)
444
- .long()
445
- )[0]
446
- atom_pad_mask = feats["atom_pad_mask"][0].bool()
447
- chain_sizes = torch.bincount(atom_chain_id[atom_pad_mask])
448
- single_ion_mask = (chain_sizes > 1)[atom_chain_id]
449
-
450
- vdw_radii = torch.zeros(
451
- const.num_elements, dtype=torch.float32, device=atom_chain_id.device
452
- )
453
- vdw_radii[1:119] = torch.tensor(
454
- const.vdw_radii, dtype=torch.float32, device=atom_chain_id.device
455
- )
456
- atom_vdw_radii = (
457
- feats["ref_element"].float() @ vdw_radii.unsqueeze(-1)
458
- ).squeeze(-1)[0]
459
-
460
- pair_index = torch.triu_indices(
461
- atom_chain_id.shape[0],
462
- atom_chain_id.shape[0],
463
- 1,
464
- device=atom_chain_id.device,
465
- )
466
-
467
- pair_pad_mask = atom_pad_mask[pair_index].all(dim=0)
468
- pair_ion_mask = single_ion_mask[pair_index[0]] * single_ion_mask[pair_index[1]]
469
-
470
- num_chains = atom_chain_id.max() + 1
471
- connected_chain_index = feats["connected_chain_index"][0]
472
- connected_chain_matrix = torch.eye(
473
- num_chains, device=atom_chain_id.device, dtype=torch.bool
474
- )
475
- connected_chain_matrix[connected_chain_index[0], connected_chain_index[1]] = (
476
- True
477
- )
478
- connected_chain_matrix[connected_chain_index[1], connected_chain_index[0]] = (
479
- True
480
- )
481
- connected_chain_mask = connected_chain_matrix[
482
- atom_chain_id[pair_index[0]], atom_chain_id[pair_index[1]]
483
- ]
484
-
485
- pair_index = pair_index[
486
- :, pair_pad_mask * pair_ion_mask * ~connected_chain_mask
487
- ]
488
-
489
- lower_bounds = atom_vdw_radii[pair_index].sum(dim=0) * (
490
- 1.0 - parameters["buffer"]
491
- )
492
- upper_bounds = None
493
- k = torch.ones_like(lower_bounds)
494
-
495
- return pair_index, (k, lower_bounds, upper_bounds), None, None, None
496
-
497
-
498
- class SymmetricChainCOMPotential(FlatBottomPotential, DistancePotential):
499
- def compute_args(self, feats, parameters):
500
- atom_chain_id = (
501
- torch.bmm(
502
- feats["atom_to_token"].float(), feats["asym_id"].unsqueeze(-1).float()
503
- )
504
- .squeeze(-1)
505
- .long()
506
- )[0]
507
- atom_pad_mask = feats["atom_pad_mask"][0].bool()
508
- chain_sizes = torch.bincount(atom_chain_id[atom_pad_mask])
509
- single_ion_mask = chain_sizes > 1
510
-
511
- pair_index = feats["symmetric_chain_index"][0]
512
- pair_ion_mask = single_ion_mask[pair_index[0]] * single_ion_mask[pair_index[1]]
513
- pair_index = pair_index[:, pair_ion_mask]
514
- lower_bounds = torch.full(
515
- (pair_index.shape[1],),
516
- parameters["buffer"],
517
- dtype=torch.float32,
518
- device=pair_index.device,
519
- )
520
- upper_bounds = None
521
- k = torch.ones_like(lower_bounds)
522
-
523
- return (
524
- pair_index,
525
- (k, lower_bounds, upper_bounds),
526
- (atom_chain_id, atom_pad_mask),
527
- None,
528
- None,
529
- )
530
-
531
-
532
- class StereoBondPotential(FlatBottomPotential, AbsDihedralPotential):
533
- def compute_args(self, feats, parameters):
534
- stereo_bond_index = feats["stereo_bond_index"][0]
535
- stereo_bond_orientations = feats["stereo_bond_orientations"][0].bool()
536
-
537
- lower_bounds = torch.zeros(
538
- stereo_bond_orientations.shape, device=stereo_bond_orientations.device
539
- )
540
- upper_bounds = torch.zeros(
541
- stereo_bond_orientations.shape, device=stereo_bond_orientations.device
542
- )
543
- lower_bounds[stereo_bond_orientations] = torch.pi - parameters["buffer"]
544
- upper_bounds[stereo_bond_orientations] = float("inf")
545
- lower_bounds[~stereo_bond_orientations] = float("-inf")
546
- upper_bounds[~stereo_bond_orientations] = parameters["buffer"]
547
-
548
- k = torch.ones_like(lower_bounds)
549
-
550
- return stereo_bond_index, (k, lower_bounds, upper_bounds), None, None, None
551
-
552
-
553
- class ChiralAtomPotential(FlatBottomPotential, DihedralPotential):
554
- def compute_args(self, feats, parameters):
555
- chiral_atom_index = feats["chiral_atom_index"][0]
556
- chiral_atom_orientations = feats["chiral_atom_orientations"][0].bool()
557
-
558
- lower_bounds = torch.zeros(
559
- chiral_atom_orientations.shape, device=chiral_atom_orientations.device
560
- )
561
- upper_bounds = torch.zeros(
562
- chiral_atom_orientations.shape, device=chiral_atom_orientations.device
563
- )
564
- lower_bounds[chiral_atom_orientations] = parameters["buffer"]
565
- upper_bounds[chiral_atom_orientations] = float("inf")
566
- upper_bounds[~chiral_atom_orientations] = -1 * parameters["buffer"]
567
- lower_bounds[~chiral_atom_orientations] = float("-inf")
568
-
569
- k = torch.ones_like(lower_bounds)
570
- return chiral_atom_index, (k, lower_bounds, upper_bounds), None, None, None
571
-
572
-
573
- class PlanarBondPotential(FlatBottomPotential, AbsDihedralPotential):
574
- def compute_args(self, feats, parameters):
575
- double_bond_index = feats["planar_bond_index"][0].T
576
- double_bond_improper_index = torch.tensor(
577
- [
578
- [1, 2, 3, 0],
579
- [4, 5, 0, 3],
580
- ],
581
- device=double_bond_index.device,
582
- ).T
583
- improper_index = (
584
- double_bond_index[:, double_bond_improper_index]
585
- .swapaxes(0, 1)
586
- .flatten(start_dim=1)
587
- )
588
- lower_bounds = None
589
- upper_bounds = torch.full(
590
- (improper_index.shape[1],),
591
- parameters["buffer"],
592
- device=improper_index.device,
593
- )
594
- k = torch.ones_like(upper_bounds)
595
-
596
- return improper_index, (k, lower_bounds, upper_bounds), None, None, None
597
-
598
-
599
- class TemplateReferencePotential(FlatBottomPotential, ReferencePotential):
600
- def compute_args(self, feats, parameters):
601
- if "template_mask_cb" not in feats or "template_force" not in feats:
602
- return torch.empty([1, 0]), None, None, None, None
603
-
604
- template_mask = feats["template_mask_cb"][feats["template_force"]]
605
- if template_mask.shape[0] == 0:
606
- return torch.empty([1, 0]), None, None, None, None
607
-
608
- ref_coords = feats["template_cb"][feats["template_force"]].clone()
609
- ref_mask = feats["template_mask_cb"][feats["template_force"]].clone()
610
- ref_atom_index = (
611
- torch.bmm(
612
- feats["token_to_rep_atom"].float(),
613
- torch.arange(
614
- feats["atom_pad_mask"].shape[1],
615
- device=feats["atom_pad_mask"].device,
616
- dtype=torch.float32,
617
- )[None, :, None],
618
- )
619
- .squeeze(-1)
620
- .long()
621
- )[0]
622
- ref_token_index = (
623
- torch.bmm(
624
- feats["atom_to_token"].float(),
625
- feats["token_index"].unsqueeze(-1).float(),
626
- )
627
- .squeeze(-1)
628
- .long()
629
- )[0]
630
-
631
- index = torch.arange(
632
- template_mask.shape[-1], dtype=torch.long, device=template_mask.device
633
- )[None]
634
- upper_bounds = torch.full(
635
- template_mask.shape, float("inf"), device=index.device, dtype=torch.float32
636
- )
637
- ref_idxs = torch.argwhere(template_mask).T
638
- upper_bounds[ref_idxs.unbind()] = feats["template_force_threshold"][
639
- feats["template_force"]
640
- ][ref_idxs[0]]
641
-
642
- lower_bounds = None
643
- k = torch.ones_like(upper_bounds)
644
- return (
645
- index,
646
- (k, lower_bounds, upper_bounds),
647
- None,
648
- (ref_coords, ref_mask, ref_atom_index, ref_token_index),
649
- None,
650
- )
651
-
652
-
653
- class ContactPotentital(FlatBottomPotential, DistancePotential):
654
- def compute_args(self, feats, parameters):
655
- index = feats["contact_pair_index"][0]
656
- union_index = feats["contact_union_index"][0]
657
- negation_mask = feats["contact_negation_mask"][0]
658
- lower_bounds = None
659
- upper_bounds = feats["contact_thresholds"][0].clone()
660
- k = torch.ones_like(upper_bounds)
661
- return (
662
- index,
663
- (k, lower_bounds, upper_bounds),
664
- None,
665
- None,
666
- (negation_mask, union_index),
667
- )
668
-
669
-
670
- def get_potentials(steering_args, boltz2=False):
671
- potentials = []
672
- if steering_args["fk_steering"] or steering_args["physical_guidance_update"]:
673
- potentials.extend(
674
- [
675
- SymmetricChainCOMPotential(
676
- parameters={
677
- "guidance_interval": 4,
678
- "guidance_weight": 0.5
679
- if steering_args["physical_guidance_update"]
680
- else 0.0,
681
- "resampling_weight": 0.5,
682
- "buffer": ExponentialInterpolation(
683
- start=1.0, end=5.0, alpha=-2.0
684
- ),
685
- }
686
- ),
687
- VDWOverlapPotential(
688
- parameters={
689
- "guidance_interval": 5,
690
- "guidance_weight": (
691
- PiecewiseStepFunction(thresholds=[0.4], values=[0.125, 0.0])
692
- if steering_args["physical_guidance_update"]
693
- else 0.0
694
- ),
695
- "resampling_weight": PiecewiseStepFunction(
696
- thresholds=[0.6], values=[0.01, 0.0]
697
- ),
698
- "buffer": 0.225,
699
- }
700
- ),
701
- ConnectionsPotential(
702
- parameters={
703
- "guidance_interval": 1,
704
- "guidance_weight": 0.15
705
- if steering_args["physical_guidance_update"]
706
- else 0.0,
707
- "resampling_weight": 1.0,
708
- "buffer": 2.0,
709
- }
710
- ),
711
- PoseBustersPotential(
712
- parameters={
713
- "guidance_interval": 1,
714
- "guidance_weight": 0.01
715
- if steering_args["physical_guidance_update"]
716
- else 0.0,
717
- "resampling_weight": 0.1,
718
- "bond_buffer": 0.125,
719
- "angle_buffer": 0.125,
720
- "clash_buffer": 0.10,
721
- }
722
- ),
723
- ChiralAtomPotential(
724
- parameters={
725
- "guidance_interval": 1,
726
- "guidance_weight": 0.1
727
- if steering_args["physical_guidance_update"]
728
- else 0.0,
729
- "resampling_weight": 1.0,
730
- "buffer": 0.52360,
731
- }
732
- ),
733
- StereoBondPotential(
734
- parameters={
735
- "guidance_interval": 1,
736
- "guidance_weight": 0.05
737
- if steering_args["physical_guidance_update"]
738
- else 0.0,
739
- "resampling_weight": 1.0,
740
- "buffer": 0.52360,
741
- }
742
- ),
743
- PlanarBondPotential(
744
- parameters={
745
- "guidance_interval": 1,
746
- "guidance_weight": 0.05
747
- if steering_args["physical_guidance_update"]
748
- else 0.0,
749
- "resampling_weight": 1.0,
750
- "buffer": 0.26180,
751
- }
752
- ),
753
- ]
754
- )
755
- if boltz2 and (
756
- steering_args["fk_steering"] or steering_args["contact_guidance_update"]
757
- ):
758
- potentials.extend(
759
- [
760
- ContactPotentital(
761
- parameters={
762
- "guidance_interval": 4,
763
- "guidance_weight": (
764
- PiecewiseStepFunction(
765
- thresholds=[0.25, 0.75], values=[0.0, 0.5, 1.0]
766
- )
767
- if steering_args["contact_guidance_update"]
768
- else 0.0
769
- ),
770
- "resampling_weight": 1.0,
771
- "union_lambda": ExponentialInterpolation(
772
- start=8.0, end=0.0, alpha=-2.0
773
- ),
774
- }
775
- ),
776
- TemplateReferencePotential(
777
- parameters={
778
- "guidance_interval": 2,
779
- "guidance_weight": 0.1
780
- if steering_args["contact_guidance_update"]
781
- else 0.0,
782
- "resampling_weight": 1.0,
783
- }
784
- ),
785
- ]
786
- )
787
- return potentials
 
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional, Dict, Any, Set, List, Union
3
+
4
+ import torch
5
+ import numpy as np
6
+ from . import vb_const as const
7
+ from .vb_potentials_schedules import (
8
+ ParameterSchedule,
9
+ ExponentialInterpolation,
10
+ PiecewiseStepFunction,
11
+ )
12
+ from .vb_loss_diffusionv2 import weighted_rigid_align
13
+
14
+
15
+ class Potential(ABC):
16
+ def __init__(
17
+ self,
18
+ parameters: Optional[
19
+ Dict[str, Union[ParameterSchedule, float, int, bool]]
20
+ ] = None,
21
+ ):
22
+ self.parameters = parameters
23
+
24
+ def compute(self, coords, feats, parameters):
25
+ index, args, com_args, ref_args, operator_args = self.compute_args(
26
+ feats, parameters
27
+ )
28
+
29
+ if index.shape[1] == 0:
30
+ return torch.zeros(coords.shape[:-2], device=coords.device)
31
+
32
+ if com_args is not None:
33
+ com_index, atom_pad_mask = com_args
34
+ unpad_com_index = com_index[atom_pad_mask]
35
+ unpad_coords = coords[..., atom_pad_mask, :]
36
+ coords = torch.zeros(
37
+ (*unpad_coords.shape[:-2], unpad_com_index.max() + 1, 3),
38
+ device=coords.device,
39
+ ).scatter_reduce(
40
+ -2,
41
+ unpad_com_index.unsqueeze(-1).expand_as(unpad_coords),
42
+ unpad_coords,
43
+ "mean",
44
+ )
45
+ else:
46
+ com_index, atom_pad_mask = None, None
47
+
48
+ if ref_args is not None:
49
+ ref_coords, ref_mask, ref_atom_index, ref_token_index = ref_args
50
+ coords = coords[..., ref_atom_index, :]
51
+ else:
52
+ ref_coords, ref_mask, ref_atom_index, ref_token_index = (
53
+ None,
54
+ None,
55
+ None,
56
+ None,
57
+ )
58
+
59
+ if operator_args is not None:
60
+ negation_mask, union_index = operator_args
61
+ else:
62
+ negation_mask, union_index = None, None
63
+
64
+ value = self.compute_variable(
65
+ coords,
66
+ index,
67
+ ref_coords=ref_coords,
68
+ ref_mask=ref_mask,
69
+ compute_gradient=False,
70
+ )
71
+ energy = self.compute_function(
72
+ value, *args, negation_mask=negation_mask, compute_derivative=False
73
+ )
74
+
75
+ if union_index is not None:
76
+ neg_exp_energy = torch.exp(-1 * parameters["union_lambda"] * energy)
77
+ Z = torch.zeros(
78
+ (*energy.shape[:-1], union_index.max() + 1), device=union_index.device
79
+ ).scatter_reduce(
80
+ -1,
81
+ union_index.expand_as(neg_exp_energy),
82
+ neg_exp_energy,
83
+ "sum",
84
+ )
85
+ softmax_energy = neg_exp_energy / Z[..., union_index]
86
+ softmax_energy[Z[..., union_index] == 0] = 0
87
+ return (energy * softmax_energy).sum(dim=-1)
88
+
89
+ return energy.sum(dim=tuple(range(1, energy.dim())))
90
+
91
+ def compute_gradient(self, coords, feats, parameters):
92
+ index, args, com_args, ref_args, operator_args = self.compute_args(
93
+ feats, parameters
94
+ )
95
+ if index.shape[1] == 0:
96
+ return torch.zeros_like(coords)
97
+
98
+ if com_args is not None:
99
+ com_index, atom_pad_mask = com_args
100
+ unpad_coords = coords[..., atom_pad_mask, :]
101
+ unpad_com_index = com_index[atom_pad_mask]
102
+ coords = torch.zeros(
103
+ (*unpad_coords.shape[:-2], unpad_com_index.max() + 1, 3),
104
+ device=coords.device,
105
+ ).scatter_reduce(
106
+ -2,
107
+ unpad_com_index.unsqueeze(-1).expand_as(unpad_coords),
108
+ unpad_coords,
109
+ "mean",
110
+ )
111
+ com_counts = torch.bincount(com_index[atom_pad_mask])
112
+ else:
113
+ com_index, atom_pad_mask = None, None
114
+
115
+ if ref_args is not None:
116
+ ref_coords, ref_mask, ref_atom_index, ref_token_index = ref_args
117
+ coords = coords[..., ref_atom_index, :]
118
+ else:
119
+ ref_coords, ref_mask, ref_atom_index, ref_token_index = (
120
+ None,
121
+ None,
122
+ None,
123
+ None,
124
+ )
125
+
126
+ if operator_args is not None:
127
+ negation_mask, union_index = operator_args
128
+ else:
129
+ negation_mask, union_index = None, None
130
+
131
+ value, grad_value = self.compute_variable(
132
+ coords,
133
+ index,
134
+ ref_coords=ref_coords,
135
+ ref_mask=ref_mask,
136
+ compute_gradient=True,
137
+ )
138
+ energy, dEnergy = self.compute_function(
139
+ value,
140
+ *args, negation_mask=negation_mask, compute_derivative=True
141
+ )
142
+ if union_index is not None:
143
+ neg_exp_energy = torch.exp(-1 * parameters["union_lambda"] * energy)
144
+ Z = torch.zeros(
145
+ (*energy.shape[:-1], union_index.max() + 1), device=union_index.device
146
+ ).scatter_reduce(
147
+ -1,
148
+ union_index.expand_as(energy),
149
+ neg_exp_energy,
150
+ "sum",
151
+ )
152
+ softmax_energy = neg_exp_energy / Z[..., union_index]
153
+ softmax_energy[Z[..., union_index] == 0] = 0
154
+ f = torch.zeros(
155
+ (*energy.shape[:-1], union_index.max() + 1), device=union_index.device
156
+ ).scatter_reduce(
157
+ -1,
158
+ union_index.expand_as(energy),
159
+ energy * softmax_energy,
160
+ "sum",
161
+ )
162
+ dSoftmax = (
163
+ dEnergy
164
+ * softmax_energy
165
+ * (1 + parameters["union_lambda"] * (energy - f[..., union_index]))
166
+ )
167
+ prod = dSoftmax.tile(grad_value.shape[-3]).unsqueeze(
168
+ -1
169
+ ) * grad_value.flatten(start_dim=-3, end_dim=-2)
170
+ if prod.dim() > 3:
171
+ prod = prod.sum(dim=list(range(1, prod.dim() - 2)))
172
+ grad_atom = torch.zeros_like(coords).scatter_reduce(
173
+ -2,
174
+ index.flatten(start_dim=0, end_dim=1)
175
+ .unsqueeze(-1)
176
+ .expand((*coords.shape[:-2], -1, 3)),
177
+ prod,
178
+ "sum",
179
+ )
180
+ else:
181
+ prod = dEnergy.tile(grad_value.shape[-3]).unsqueeze(
182
+ -1
183
+ ) * grad_value.flatten(start_dim=-3, end_dim=-2)
184
+ if prod.dim() > 3:
185
+ prod = prod.sum(dim=list(range(1, prod.dim() - 2)))
186
+ grad_atom = torch.zeros_like(coords).scatter_reduce(
187
+ -2,
188
+ index.flatten(start_dim=0, end_dim=1)
189
+ .unsqueeze(-1)
190
+ .expand((*coords.shape[:-2], -1, 3)), # 9 x 516 x 3
191
+ prod,
192
+ "sum",
193
+ )
194
+
195
+ if com_index is not None:
196
+ grad_atom = grad_atom[..., com_index, :]
197
+ elif ref_token_index is not None:
198
+ grad_atom = grad_atom[..., ref_token_index, :]
199
+
200
+ return grad_atom
201
+
202
+ def compute_parameters(self, t):
203
+ if self.parameters is None:
204
+ return None
205
+ parameters = {
206
+ name: parameter
207
+ if not isinstance(parameter, ParameterSchedule)
208
+ else parameter.compute(t)
209
+ for name, parameter in self.parameters.items()
210
+ }
211
+ return parameters
212
+
213
+ @abstractmethod
214
+ def compute_function(
215
+ self, value, *args, negation_mask=None, compute_derivative=False
216
+ ):
217
+ raise NotImplementedError
218
+
219
+ @abstractmethod
220
+ def compute_variable(self, coords, index, compute_gradient=False):
221
+ raise NotImplementedError
222
+
223
+ @abstractmethod
224
+ def compute_args(self, t, feats, **parameters):
225
+ raise NotImplementedError
226
+
227
+ def get_reference_coords(self, feats, parameters):
228
+ return None, None
229
+
230
+
231
+ class FlatBottomPotential(Potential):
232
+ def compute_function(
233
+ self,
234
+ value,
235
+ k,
236
+ lower_bounds,
237
+ upper_bounds,
238
+ negation_mask=None,
239
+ compute_derivative=False,
240
+ ):
241
+ if lower_bounds is None:
242
+ lower_bounds = torch.full_like(value, float("-inf"))
243
+ if upper_bounds is None:
244
+ upper_bounds = torch.full_like(value, float("inf"))
245
+ lower_bounds = lower_bounds.expand_as(value).clone()
246
+ upper_bounds = upper_bounds.expand_as(value).clone()
247
+
248
+ if negation_mask is not None:
249
+ unbounded_below_mask = torch.isneginf(lower_bounds)
250
+ unbounded_above_mask = torch.isposinf(upper_bounds)
251
+ unbounded_mask = unbounded_below_mask + unbounded_above_mask
252
+ assert torch.all(unbounded_mask + negation_mask)
253
+ lower_bounds[~unbounded_above_mask * ~negation_mask] = upper_bounds[
254
+ ~unbounded_above_mask * ~negation_mask
255
+ ]
256
+ upper_bounds[~unbounded_above_mask * ~negation_mask] = float("inf")
257
+ upper_bounds[~unbounded_below_mask * ~negation_mask] = lower_bounds[
258
+ ~unbounded_below_mask * ~negation_mask
259
+ ]
260
+ lower_bounds[~unbounded_below_mask * ~negation_mask] = float("-inf")
261
+
262
+ neg_overflow_mask = value < lower_bounds
263
+ pos_overflow_mask = value > upper_bounds
264
+
265
+ energy = torch.zeros_like(value)
266
+ energy[neg_overflow_mask] = (k * (lower_bounds - value))[neg_overflow_mask]
267
+ energy[pos_overflow_mask] = (k * (value - upper_bounds))[pos_overflow_mask]
268
+ if not compute_derivative:
269
+ return energy
270
+
271
+ dEnergy = torch.zeros_like(value)
272
+ dEnergy[neg_overflow_mask] = (
273
+ -1 * k.expand_as(neg_overflow_mask)[neg_overflow_mask]
274
+ )
275
+ dEnergy[pos_overflow_mask] = (
276
+ 1 * k.expand_as(pos_overflow_mask)[pos_overflow_mask]
277
+ )
278
+
279
+ return energy, dEnergy
280
+
281
+
282
+ class ReferencePotential(Potential):
283
+ def compute_variable(
284
+ self, coords, index, ref_coords, ref_mask, compute_gradient=False
285
+ ):
286
+ aligned_ref_coords = weighted_rigid_align(
287
+ ref_coords.float(),
288
+ coords[:, index].float(),
289
+ ref_mask,
290
+ ref_mask,
291
+ )
292
+
293
+ r = coords[:, index] - aligned_ref_coords
294
+ r_norm = torch.linalg.norm(r, dim=-1)
295
+
296
+ if not compute_gradient:
297
+ return r_norm
298
+
299
+ r_hat = r / r_norm.unsqueeze(-1)
300
+ grad = (r_hat * ref_mask.unsqueeze(-1)).unsqueeze(1)
301
+ return r_norm, grad
302
+
303
+
304
+ class DistancePotential(Potential):
305
+ def compute_variable(
306
+ self, coords, index, ref_coords=None, ref_mask=None, compute_gradient=False
307
+ ):
308
+ r_ij = coords.index_select(-2, index[0]) - coords.index_select(-2, index[1])
309
+ r_ij_norm = torch.linalg.norm(r_ij, dim=-1)
310
+ r_hat_ij = r_ij / r_ij_norm.unsqueeze(-1)
311
+
312
+ if not compute_gradient:
313
+ return r_ij_norm
314
+
315
+ grad_i = r_hat_ij
316
+ grad_j = -1 * r_hat_ij
317
+ grad = torch.stack((grad_i, grad_j), dim=1)
318
+ return r_ij_norm, grad
319
+
320
+
321
+ class DihedralPotential(Potential):
322
+ def compute_variable(
323
+ self, coords, index, ref_coords=None, ref_mask=None, compute_gradient=False
324
+ ):
325
+ r_ij = coords.index_select(-2, index[0]) - coords.index_select(-2, index[1])
326
+ r_kj = coords.index_select(-2, index[2]) - coords.index_select(-2, index[1])
327
+ r_kl = coords.index_select(-2, index[2]) - coords.index_select(-2, index[3])
328
+
329
+ n_ijk = torch.cross(r_ij, r_kj, dim=-1)
330
+ n_jkl = torch.cross(r_kj, r_kl, dim=-1)
331
+
332
+ r_kj_norm = torch.linalg.norm(r_kj, dim=-1)
333
+ n_ijk_norm = torch.linalg.norm(n_ijk, dim=-1)
334
+ n_jkl_norm = torch.linalg.norm(n_jkl, dim=-1)
335
+
336
+ sign_phi = torch.sign(
337
+ r_kj.unsqueeze(-2) @ torch.cross(n_ijk, n_jkl, dim=-1).unsqueeze(-1)
338
+ ).squeeze(-1, -2)
339
+ phi = sign_phi * torch.arccos(
340
+ torch.clamp(
341
+ (n_ijk.unsqueeze(-2) @ n_jkl.unsqueeze(-1)).squeeze(-1, -2)
342
+ / (n_ijk_norm * n_jkl_norm),
343
+ -1 + 1e-8,
344
+ 1 - 1e-8,
345
+ )
346
+ )
347
+
348
+ if not compute_gradient:
349
+ return phi
350
+
351
+ a = (
352
+ (r_ij.unsqueeze(-2) @ r_kj.unsqueeze(-1)).squeeze(-1, -2) / (r_kj_norm**2)
353
+ ).unsqueeze(-1)
354
+ b = (
355
+ (r_kl.unsqueeze(-2) @ r_kj.unsqueeze(-1)).squeeze(-1, -2) / (r_kj_norm**2)
356
+ ).unsqueeze(-1)
357
+
358
+ grad_i = n_ijk * (r_kj_norm / n_ijk_norm**2).unsqueeze(-1)
359
+ grad_l = -1 * n_jkl * (r_kj_norm / n_jkl_norm**2).unsqueeze(-1)
360
+ grad_j = (a - 1) * grad_i - b * grad_l
361
+ grad_k = (b - 1) * grad_l - a * grad_i
362
+ grad = torch.stack((grad_i, grad_j, grad_k, grad_l), dim=1)
363
+ return phi, grad
364
+
365
+
366
+ class AbsDihedralPotential(DihedralPotential):
367
+ def compute_variable(
368
+ self, coords, index, ref_coords=None, ref_mask=None, compute_gradient=False
369
+ ):
370
+ if not compute_gradient:
371
+ phi = super().compute_variable(
372
+ coords, index, compute_gradient=compute_gradient
373
+ )
374
+ phi = torch.abs(phi)
375
+ return phi
376
+
377
+ phi, grad = super().compute_variable(
378
+ coords, index, compute_gradient=compute_gradient
379
+ )
380
+ grad[(phi < 0)[..., None, :, None].expand_as(grad)] *= -1
381
+ phi = torch.abs(phi)
382
+
383
+ return phi, grad
384
+
385
+
386
+ class PoseBustersPotential(FlatBottomPotential, DistancePotential):
387
+ def compute_args(self, feats, parameters):
388
+ pair_index = feats["rdkit_bounds_index"][0]
389
+ lower_bounds = feats["rdkit_lower_bounds"][0].clone()
390
+ upper_bounds = feats["rdkit_upper_bounds"][0].clone()
391
+ bond_mask = feats["rdkit_bounds_bond_mask"][0]
392
+ angle_mask = feats["rdkit_bounds_angle_mask"][0]
393
+
394
+ lower_bounds[bond_mask * ~angle_mask] *= 1.0 - parameters["bond_buffer"]
395
+ upper_bounds[bond_mask * ~angle_mask] *= 1.0 + parameters["bond_buffer"]
396
+ lower_bounds[~bond_mask * angle_mask] *= 1.0 - parameters["angle_buffer"]
397
+ upper_bounds[~bond_mask * angle_mask] *= 1.0 + parameters["angle_buffer"]
398
+ lower_bounds[bond_mask * angle_mask] *= 1.0 - min(
399
+ parameters["bond_buffer"], parameters["angle_buffer"]
400
+ )
401
+ upper_bounds[bond_mask * angle_mask] *= 1.0 + min(
402
+ parameters["bond_buffer"], parameters["angle_buffer"]
403
+ )
404
+ lower_bounds[~bond_mask * ~angle_mask] *= 1.0 - parameters["clash_buffer"]
405
+ upper_bounds[~bond_mask * ~angle_mask] = float("inf")
406
+
407
+ vdw_radii = torch.zeros(
408
+ const.num_elements, dtype=torch.float32, device=pair_index.device
409
+ )
410
+ vdw_radii[1:119] = torch.tensor(
411
+ const.vdw_radii, dtype=torch.float32, device=pair_index.device
412
+ )
413
+ atom_vdw_radii = (
414
+ feats["ref_element"].float() @ vdw_radii.unsqueeze(-1)
415
+ ).squeeze(-1)[0]
416
+ bond_cutoffs = 0.35 + atom_vdw_radii[pair_index].mean(dim=0)
417
+ lower_bounds[~bond_mask] = torch.max(lower_bounds[~bond_mask], bond_cutoffs[~bond_mask])
418
+ upper_bounds[bond_mask] = torch.min(upper_bounds[bond_mask], bond_cutoffs[bond_mask])
419
+
420
+ k = torch.ones_like(lower_bounds)
421
+
422
+ return pair_index, (k, lower_bounds, upper_bounds), None, None, None
423
+
424
+
425
+ class ConnectionsPotential(FlatBottomPotential, DistancePotential):
426
+ def compute_args(self, feats, parameters):
427
+ pair_index = feats["connected_atom_index"][0]
428
+ lower_bounds = None
429
+ upper_bounds = torch.full(
430
+ (pair_index.shape[1],), parameters["buffer"], device=pair_index.device
431
+ )
432
+ k = torch.ones_like(upper_bounds)
433
+
434
+ return pair_index, (k, lower_bounds, upper_bounds), None, None, None
435
+
436
+
437
+ class VDWOverlapPotential(FlatBottomPotential, DistancePotential):
438
+ def compute_args(self, feats, parameters):
439
+ atom_chain_id = (
440
+ torch.bmm(
441
+ feats["atom_to_token"].float(), feats["asym_id"].unsqueeze(-1).float()
442
+ )
443
+ .squeeze(-1)
444
+ .long()
445
+ )[0]
446
+ atom_pad_mask = feats["atom_pad_mask"][0].bool()
447
+ chain_sizes = torch.bincount(atom_chain_id[atom_pad_mask])
448
+ single_ion_mask = (chain_sizes > 1)[atom_chain_id]
449
+
450
+ vdw_radii = torch.zeros(
451
+ const.num_elements, dtype=torch.float32, device=atom_chain_id.device
452
+ )
453
+ vdw_radii[1:119] = torch.tensor(
454
+ const.vdw_radii, dtype=torch.float32, device=atom_chain_id.device
455
+ )
456
+ atom_vdw_radii = (
457
+ feats["ref_element"].float() @ vdw_radii.unsqueeze(-1)
458
+ ).squeeze(-1)[0]
459
+
460
+ pair_index = torch.triu_indices(
461
+ atom_chain_id.shape[0],
462
+ atom_chain_id.shape[0],
463
+ 1,
464
+ device=atom_chain_id.device,
465
+ )
466
+
467
+ pair_pad_mask = atom_pad_mask[pair_index].all(dim=0)
468
+ pair_ion_mask = single_ion_mask[pair_index[0]] * single_ion_mask[pair_index[1]]
469
+
470
+ num_chains = atom_chain_id.max() + 1
471
+ connected_chain_index = feats["connected_chain_index"][0]
472
+ connected_chain_matrix = torch.eye(
473
+ num_chains, device=atom_chain_id.device, dtype=torch.bool
474
+ )
475
+ connected_chain_matrix[connected_chain_index[0], connected_chain_index[1]] = (
476
+ True
477
+ )
478
+ connected_chain_matrix[connected_chain_index[1], connected_chain_index[0]] = (
479
+ True
480
+ )
481
+ connected_chain_mask = connected_chain_matrix[
482
+ atom_chain_id[pair_index[0]], atom_chain_id[pair_index[1]]
483
+ ]
484
+
485
+ pair_index = pair_index[
486
+ :, pair_pad_mask * pair_ion_mask * ~connected_chain_mask
487
+ ]
488
+
489
+ lower_bounds = atom_vdw_radii[pair_index].sum(dim=0) * (
490
+ 1.0 - parameters["buffer"]
491
+ )
492
+ upper_bounds = None
493
+ k = torch.ones_like(lower_bounds)
494
+
495
+ return pair_index, (k, lower_bounds, upper_bounds), None, None, None
496
+
497
+
498
+ class SymmetricChainCOMPotential(FlatBottomPotential, DistancePotential):
499
+ def compute_args(self, feats, parameters):
500
+ atom_chain_id = (
501
+ torch.bmm(
502
+ feats["atom_to_token"].float(), feats["asym_id"].unsqueeze(-1).float()
503
+ )
504
+ .squeeze(-1)
505
+ .long()
506
+ )[0]
507
+ atom_pad_mask = feats["atom_pad_mask"][0].bool()
508
+ chain_sizes = torch.bincount(atom_chain_id[atom_pad_mask])
509
+ single_ion_mask = chain_sizes > 1
510
+
511
+ pair_index = feats["symmetric_chain_index"][0]
512
+ pair_ion_mask = single_ion_mask[pair_index[0]] * single_ion_mask[pair_index[1]]
513
+ pair_index = pair_index[:, pair_ion_mask]
514
+ lower_bounds = torch.full(
515
+ (pair_index.shape[1],),
516
+ parameters["buffer"],
517
+ dtype=torch.float32,
518
+ device=pair_index.device,
519
+ )
520
+ upper_bounds = None
521
+ k = torch.ones_like(lower_bounds)
522
+
523
+ return (
524
+ pair_index,
525
+ (k, lower_bounds, upper_bounds),
526
+ (atom_chain_id, atom_pad_mask),
527
+ None,
528
+ None,
529
+ )
530
+
531
+
532
+ class StereoBondPotential(FlatBottomPotential, AbsDihedralPotential):
533
+ def compute_args(self, feats, parameters):
534
+ stereo_bond_index = feats["stereo_bond_index"][0]
535
+ stereo_bond_orientations = feats["stereo_bond_orientations"][0].bool()
536
+
537
+ lower_bounds = torch.zeros(
538
+ stereo_bond_orientations.shape, device=stereo_bond_orientations.device
539
+ )
540
+ upper_bounds = torch.zeros(
541
+ stereo_bond_orientations.shape, device=stereo_bond_orientations.device
542
+ )
543
+ lower_bounds[stereo_bond_orientations] = torch.pi - parameters["buffer"]
544
+ upper_bounds[stereo_bond_orientations] = float("inf")
545
+ lower_bounds[~stereo_bond_orientations] = float("-inf")
546
+ upper_bounds[~stereo_bond_orientations] = parameters["buffer"]
547
+
548
+ k = torch.ones_like(lower_bounds)
549
+
550
+ return stereo_bond_index, (k, lower_bounds, upper_bounds), None, None, None
551
+
552
+
553
+ class ChiralAtomPotential(FlatBottomPotential, DihedralPotential):
554
+ def compute_args(self, feats, parameters):
555
+ chiral_atom_index = feats["chiral_atom_index"][0]
556
+ chiral_atom_orientations = feats["chiral_atom_orientations"][0].bool()
557
+
558
+ lower_bounds = torch.zeros(
559
+ chiral_atom_orientations.shape, device=chiral_atom_orientations.device
560
+ )
561
+ upper_bounds = torch.zeros(
562
+ chiral_atom_orientations.shape, device=chiral_atom_orientations.device
563
+ )
564
+ lower_bounds[chiral_atom_orientations] = parameters["buffer"]
565
+ upper_bounds[chiral_atom_orientations] = float("inf")
566
+ upper_bounds[~chiral_atom_orientations] = -1 * parameters["buffer"]
567
+ lower_bounds[~chiral_atom_orientations] = float("-inf")
568
+
569
+ k = torch.ones_like(lower_bounds)
570
+ return chiral_atom_index, (k, lower_bounds, upper_bounds), None, None, None
571
+
572
+
573
+ class PlanarBondPotential(FlatBottomPotential, AbsDihedralPotential):
574
+ def compute_args(self, feats, parameters):
575
+ double_bond_index = feats["planar_bond_index"][0].T
576
+ double_bond_improper_index = torch.tensor(
577
+ [
578
+ [1, 2, 3, 0],
579
+ [4, 5, 0, 3],
580
+ ],
581
+ device=double_bond_index.device,
582
+ ).T
583
+ improper_index = (
584
+ double_bond_index[:, double_bond_improper_index]
585
+ .swapaxes(0, 1)
586
+ .flatten(start_dim=1)
587
+ )
588
+ lower_bounds = None
589
+ upper_bounds = torch.full(
590
+ (improper_index.shape[1],),
591
+ parameters["buffer"],
592
+ device=improper_index.device,
593
+ )
594
+ k = torch.ones_like(upper_bounds)
595
+
596
+ return improper_index, (k, lower_bounds, upper_bounds), None, None, None
597
+
598
+
599
+ class TemplateReferencePotential(FlatBottomPotential, ReferencePotential):
600
+ def compute_args(self, feats, parameters):
601
+ if "template_mask_cb" not in feats or "template_force" not in feats:
602
+ return torch.empty([1, 0]), None, None, None, None
603
+
604
+ template_mask = feats["template_mask_cb"][feats["template_force"]]
605
+ if template_mask.shape[0] == 0:
606
+ return torch.empty([1, 0]), None, None, None, None
607
+
608
+ ref_coords = feats["template_cb"][feats["template_force"]].clone()
609
+ ref_mask = feats["template_mask_cb"][feats["template_force"]].clone()
610
+ ref_atom_index = (
611
+ torch.bmm(
612
+ feats["token_to_rep_atom"].float(),
613
+ torch.arange(
614
+ feats["atom_pad_mask"].shape[1],
615
+ device=feats["atom_pad_mask"].device,
616
+ dtype=torch.float32,
617
+ )[None, :, None],
618
+ )
619
+ .squeeze(-1)
620
+ .long()
621
+ )[0]
622
+ ref_token_index = (
623
+ torch.bmm(
624
+ feats["atom_to_token"].float(),
625
+ feats["token_index"].unsqueeze(-1).float(),
626
+ )
627
+ .squeeze(-1)
628
+ .long()
629
+ )[0]
630
+
631
+ index = torch.arange(
632
+ template_mask.shape[-1], dtype=torch.long, device=template_mask.device
633
+ )[None]
634
+ upper_bounds = torch.full(
635
+ template_mask.shape, float("inf"), device=index.device, dtype=torch.float32
636
+ )
637
+ ref_idxs = torch.argwhere(template_mask).T
638
+ upper_bounds[ref_idxs.unbind()] = feats["template_force_threshold"][
639
+ feats["template_force"]
640
+ ][ref_idxs[0]]
641
+
642
+ lower_bounds = None
643
+ k = torch.ones_like(upper_bounds)
644
+ return (
645
+ index,
646
+ (k, lower_bounds, upper_bounds),
647
+ None,
648
+ (ref_coords, ref_mask, ref_atom_index, ref_token_index),
649
+ None,
650
+ )
651
+
652
+
653
+ class ContactPotentital(FlatBottomPotential, DistancePotential):
654
+ def compute_args(self, feats, parameters):
655
+ index = feats["contact_pair_index"][0]
656
+ union_index = feats["contact_union_index"][0]
657
+ negation_mask = feats["contact_negation_mask"][0]
658
+ lower_bounds = None
659
+ upper_bounds = feats["contact_thresholds"][0].clone()
660
+ k = torch.ones_like(upper_bounds)
661
+ return (
662
+ index,
663
+ (k, lower_bounds, upper_bounds),
664
+ None,
665
+ None,
666
+ (negation_mask, union_index),
667
+ )
668
+
669
+
670
+ def get_potentials(steering_args, boltz2=False):
671
+ potentials = []
672
+ if steering_args["fk_steering"] or steering_args["physical_guidance_update"]:
673
+ potentials.extend(
674
+ [
675
+ SymmetricChainCOMPotential(
676
+ parameters={
677
+ "guidance_interval": 4,
678
+ "guidance_weight": 0.5
679
+ if steering_args["physical_guidance_update"]
680
+ else 0.0,
681
+ "resampling_weight": 0.5,
682
+ "buffer": ExponentialInterpolation(
683
+ start=1.0, end=5.0, alpha=-2.0
684
+ ),
685
+ }
686
+ ),
687
+ VDWOverlapPotential(
688
+ parameters={
689
+ "guidance_interval": 5,
690
+ "guidance_weight": (
691
+ PiecewiseStepFunction(thresholds=[0.4], values=[0.125, 0.0])
692
+ if steering_args["physical_guidance_update"]
693
+ else 0.0
694
+ ),
695
+ "resampling_weight": PiecewiseStepFunction(
696
+ thresholds=[0.6], values=[0.01, 0.0]
697
+ ),
698
+ "buffer": 0.225,
699
+ }
700
+ ),
701
+ ConnectionsPotential(
702
+ parameters={
703
+ "guidance_interval": 1,
704
+ "guidance_weight": 0.15
705
+ if steering_args["physical_guidance_update"]
706
+ else 0.0,
707
+ "resampling_weight": 1.0,
708
+ "buffer": 2.0,
709
+ }
710
+ ),
711
+ PoseBustersPotential(
712
+ parameters={
713
+ "guidance_interval": 1,
714
+ "guidance_weight": 0.01
715
+ if steering_args["physical_guidance_update"]
716
+ else 0.0,
717
+ "resampling_weight": 0.1,
718
+ "bond_buffer": 0.125,
719
+ "angle_buffer": 0.125,
720
+ "clash_buffer": 0.10,
721
+ }
722
+ ),
723
+ ChiralAtomPotential(
724
+ parameters={
725
+ "guidance_interval": 1,
726
+ "guidance_weight": 0.1
727
+ if steering_args["physical_guidance_update"]
728
+ else 0.0,
729
+ "resampling_weight": 1.0,
730
+ "buffer": 0.52360,
731
+ }
732
+ ),
733
+ StereoBondPotential(
734
+ parameters={
735
+ "guidance_interval": 1,
736
+ "guidance_weight": 0.05
737
+ if steering_args["physical_guidance_update"]
738
+ else 0.0,
739
+ "resampling_weight": 1.0,
740
+ "buffer": 0.52360,
741
+ }
742
+ ),
743
+ PlanarBondPotential(
744
+ parameters={
745
+ "guidance_interval": 1,
746
+ "guidance_weight": 0.05
747
+ if steering_args["physical_guidance_update"]
748
+ else 0.0,
749
+ "resampling_weight": 1.0,
750
+ "buffer": 0.26180,
751
+ }
752
+ ),
753
+ ]
754
+ )
755
+ if boltz2 and (
756
+ steering_args["fk_steering"] or steering_args["contact_guidance_update"]
757
+ ):
758
+ potentials.extend(
759
+ [
760
+ ContactPotentital(
761
+ parameters={
762
+ "guidance_interval": 4,
763
+ "guidance_weight": (
764
+ PiecewiseStepFunction(
765
+ thresholds=[0.25, 0.75], values=[0.0, 0.5, 1.0]
766
+ )
767
+ if steering_args["contact_guidance_update"]
768
+ else 0.0
769
+ ),
770
+ "resampling_weight": 1.0,
771
+ "union_lambda": ExponentialInterpolation(
772
+ start=8.0, end=0.0, alpha=-2.0
773
+ ),
774
+ }
775
+ ),
776
+ TemplateReferencePotential(
777
+ parameters={
778
+ "guidance_interval": 2,
779
+ "guidance_weight": 0.1
780
+ if steering_args["contact_guidance_update"]
781
+ else 0.0,
782
+ "resampling_weight": 1.0,
783
+ }
784
+ ),
785
+ ]
786
+ )
787
+ return potentials
vb_potentials_schedules.py CHANGED
@@ -1,37 +1,37 @@
1
- import math
2
- from abc import ABC
3
-
4
-
5
- class ParameterSchedule(ABC):
6
- def compute(self, t):
7
- raise NotImplementedError
8
-
9
-
10
- class ExponentialInterpolation(ParameterSchedule):
11
- def __init__(self, start, end, alpha):
12
- self.start = start
13
- self.end = end
14
- self.alpha = alpha
15
-
16
- def compute(self, t):
17
- if self.alpha != 0:
18
- return self.start + (self.end - self.start) * (
19
- math.exp(self.alpha * t) - 1
20
- ) / (math.exp(self.alpha) - 1)
21
- else:
22
- return self.start + (self.end - self.start) * t
23
-
24
-
25
- class PiecewiseStepFunction(ParameterSchedule):
26
- def __init__(self, thresholds, values):
27
- self.thresholds = thresholds
28
- self.values = values
29
-
30
- def compute(self, t):
31
- assert len(self.thresholds) > 0
32
- assert len(self.values) == len(self.thresholds) + 1
33
-
34
- idx = 0
35
- while idx < len(self.thresholds) and t > self.thresholds[idx]:
36
- idx += 1
37
- return self.values[idx]
 
1
+ import math
2
+ from abc import ABC
3
+
4
+
5
+ class ParameterSchedule(ABC):
6
+ def compute(self, t):
7
+ raise NotImplementedError
8
+
9
+
10
+ class ExponentialInterpolation(ParameterSchedule):
11
+ def __init__(self, start, end, alpha):
12
+ self.start = start
13
+ self.end = end
14
+ self.alpha = alpha
15
+
16
+ def compute(self, t):
17
+ if self.alpha != 0:
18
+ return self.start + (self.end - self.start) * (
19
+ math.exp(self.alpha * t) - 1
20
+ ) / (math.exp(self.alpha) - 1)
21
+ else:
22
+ return self.start + (self.end - self.start) * t
23
+
24
+
25
+ class PiecewiseStepFunction(ParameterSchedule):
26
+ def __init__(self, thresholds, values):
27
+ self.thresholds = thresholds
28
+ self.values = values
29
+
30
+ def compute(self, t):
31
+ assert len(self.thresholds) > 0
32
+ assert len(self.values) == len(self.thresholds) + 1
33
+
34
+ idx = 0
35
+ while idx < len(self.thresholds) and t > self.thresholds[idx]:
36
+ idx += 1
37
+ return self.values[idx]
vb_tri_attn_attention.py CHANGED
@@ -1,189 +1,189 @@
1
- # Copyright 2021 AlQuraishi Laboratory
2
- # Copyright 2021 DeepMind Technologies Limited
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
- # http://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
- from functools import partial, partialmethod
17
- from typing import Optional
18
-
19
- import torch
20
- import torch.nn as nn
21
-
22
- from .vb_tri_attn_primitives import (
23
- Attention,
24
- LayerNorm,
25
- Linear,
26
- )
27
- from .vb_tri_attn_utils import (
28
- chunk_layer,
29
- permute_final_dims,
30
- )
31
-
32
-
33
- class TriangleAttention(nn.Module):
34
- """Implement Algorithm 12."""
35
-
36
- def __init__(
37
- self,
38
- c_in: int,
39
- c_hidden: int,
40
- no_heads: int,
41
- starting: bool = True,
42
- inf: float = 1e9,
43
- ) -> None:
44
- super().__init__()
45
-
46
- self.c_in = c_in
47
- self.c_hidden = c_hidden
48
- self.no_heads = no_heads
49
- self.starting = starting
50
- self.inf = inf
51
-
52
- self.layer_norm = LayerNorm(self.c_in)
53
-
54
- self.linear = Linear(c_in, self.no_heads, bias=False, init="normal")
55
-
56
- self.mha = Attention(
57
- self.c_in, self.c_in, self.c_in, self.c_hidden, self.no_heads
58
- )
59
-
60
- @torch.jit.ignore
61
- def _chunk(
62
- self,
63
- x: torch.Tensor,
64
- tri_bias: torch.Tensor,
65
- mask_bias: torch.Tensor,
66
- mask: torch.Tensor,
67
- chunk_size: int,
68
- use_kernels: bool = False,
69
- ) -> torch.Tensor:
70
- """Compute triangle attention.
71
-
72
- Parameters
73
- ----------
74
- x : torch.Tensor
75
- Input tensor of shape [*, I, J, C_in]
76
- biases : list[torch.Tensor]
77
- List of bias tensors of shape [*, H, I, J]
78
- chunk_size : int
79
- Size of chunks for memory efficient computation
80
- use_kernels : bool, default=False
81
- Whether to use optimized CUDA kernels
82
-
83
- Returns
84
- -------
85
- torch.Tensor
86
- Output tensor of shape [*, I, J, C_in]
87
-
88
- """
89
- mha_inputs = {
90
- "q_x": x,
91
- "kv_x": x,
92
- "tri_bias": tri_bias,
93
- "mask_bias": mask_bias,
94
- "mask": mask,
95
- }
96
-
97
- return chunk_layer(
98
- partial(
99
- self.mha,
100
- use_kernels=use_kernels,
101
- ),
102
- mha_inputs,
103
- chunk_size=chunk_size,
104
- no_batch_dims=len(x.shape[:-2]),
105
- _out=None,
106
- )
107
-
108
- def forward(
109
- self,
110
- x: torch.Tensor,
111
- mask: Optional[torch.Tensor] = None,
112
- chunk_size: Optional[int] = None,
113
- use_kernels: bool = False,
114
- ) -> torch.Tensor:
115
- """Compute triangle attention.
116
-
117
- Parameters
118
- ----------
119
- x : torch.Tensor
120
- Input tensor of shape [*, I, J, C_in]
121
- mask : torch.Tensor, optional
122
- Attention mask of shape [*, I, J]
123
- chunk_size : int, optional
124
- Size of chunks for memory efficient computation
125
- use_kernels : bool, default=False
126
- Whether to use optimized CUDA kernels
127
-
128
- Returns
129
- -------
130
- torch.Tensor
131
- Output tensor of shape [*, I, J, C_in]
132
-
133
- """
134
- if mask is None:
135
- # [*, I, J]
136
- mask = x.new_ones(
137
- x.shape[:-1],
138
- )
139
-
140
- if not self.starting:
141
- x = x.transpose(-2, -3)
142
- mask = mask.transpose(-1, -2)
143
-
144
- # [*, I, J, C_in]
145
- x = self.layer_norm(x)
146
-
147
- # [*, I, 1, 1, J]
148
- mask = mask[..., :, None, None, :]
149
- mask_bias = self.inf * (mask - 1)
150
-
151
- # [*, H, I, J]
152
- triangle_bias = permute_final_dims(self.linear(x), (2, 0, 1))
153
-
154
- # [*, 1, H, I, J]
155
- triangle_bias = triangle_bias.unsqueeze(-4)
156
-
157
- if chunk_size is not None and not use_kernels:
158
- x = self._chunk(
159
- x,
160
- triangle_bias,
161
- mask_bias,
162
- mask,
163
- chunk_size,
164
- use_kernels=use_kernels,
165
- )
166
- else:
167
- x = self.mha(
168
- x,
169
- x,
170
- triangle_bias,
171
- mask_bias,
172
- mask,
173
- use_kernels=use_kernels,
174
- )
175
-
176
- if not self.starting:
177
- x = x.transpose(-2, -3)
178
-
179
- return x
180
-
181
-
182
- # Implements Algorithm 13
183
- TriangleAttentionStartingNode = TriangleAttention
184
-
185
-
186
- class TriangleAttentionEndingNode(TriangleAttention):
187
- """Implement Algorithm 14."""
188
-
189
- __init__ = partialmethod(TriangleAttention.__init__, starting=False)
 
1
+ # Copyright 2021 AlQuraishi Laboratory
2
+ # Copyright 2021 DeepMind Technologies Limited
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
+ # http://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
+ from functools import partial, partialmethod
17
+ from typing import Optional
18
+
19
+ import torch
20
+ import torch.nn as nn
21
+
22
+ from .vb_tri_attn_primitives import (
23
+ Attention,
24
+ LayerNorm,
25
+ Linear,
26
+ )
27
+ from .vb_tri_attn_utils import (
28
+ chunk_layer,
29
+ permute_final_dims,
30
+ )
31
+
32
+
33
+ class TriangleAttention(nn.Module):
34
+ """Implement Algorithm 12."""
35
+
36
+ def __init__(
37
+ self,
38
+ c_in: int,
39
+ c_hidden: int,
40
+ no_heads: int,
41
+ starting: bool = True,
42
+ inf: float = 1e9,
43
+ ) -> None:
44
+ super().__init__()
45
+
46
+ self.c_in = c_in
47
+ self.c_hidden = c_hidden
48
+ self.no_heads = no_heads
49
+ self.starting = starting
50
+ self.inf = inf
51
+
52
+ self.layer_norm = LayerNorm(self.c_in)
53
+
54
+ self.linear = Linear(c_in, self.no_heads, bias=False, init="normal")
55
+
56
+ self.mha = Attention(
57
+ self.c_in, self.c_in, self.c_in, self.c_hidden, self.no_heads
58
+ )
59
+
60
+ @torch.jit.ignore
61
+ def _chunk(
62
+ self,
63
+ x: torch.Tensor,
64
+ tri_bias: torch.Tensor,
65
+ mask_bias: torch.Tensor,
66
+ mask: torch.Tensor,
67
+ chunk_size: int,
68
+ use_kernels: bool = False,
69
+ ) -> torch.Tensor:
70
+ """Compute triangle attention.
71
+
72
+ Parameters
73
+ ----------
74
+ x : torch.Tensor
75
+ Input tensor of shape [*, I, J, C_in]
76
+ biases : list[torch.Tensor]
77
+ List of bias tensors of shape [*, H, I, J]
78
+ chunk_size : int
79
+ Size of chunks for memory efficient computation
80
+ use_kernels : bool, default=False
81
+ Whether to use optimized CUDA kernels
82
+
83
+ Returns
84
+ -------
85
+ torch.Tensor
86
+ Output tensor of shape [*, I, J, C_in]
87
+
88
+ """
89
+ mha_inputs = {
90
+ "q_x": x,
91
+ "kv_x": x,
92
+ "tri_bias": tri_bias,
93
+ "mask_bias": mask_bias,
94
+ "mask": mask,
95
+ }
96
+
97
+ return chunk_layer(
98
+ partial(
99
+ self.mha,
100
+ use_kernels=use_kernels,
101
+ ),
102
+ mha_inputs,
103
+ chunk_size=chunk_size,
104
+ no_batch_dims=len(x.shape[:-2]),
105
+ _out=None,
106
+ )
107
+
108
+ def forward(
109
+ self,
110
+ x: torch.Tensor,
111
+ mask: Optional[torch.Tensor] = None,
112
+ chunk_size: Optional[int] = None,
113
+ use_kernels: bool = False,
114
+ ) -> torch.Tensor:
115
+ """Compute triangle attention.
116
+
117
+ Parameters
118
+ ----------
119
+ x : torch.Tensor
120
+ Input tensor of shape [*, I, J, C_in]
121
+ mask : torch.Tensor, optional
122
+ Attention mask of shape [*, I, J]
123
+ chunk_size : int, optional
124
+ Size of chunks for memory efficient computation
125
+ use_kernels : bool, default=False
126
+ Whether to use optimized CUDA kernels
127
+
128
+ Returns
129
+ -------
130
+ torch.Tensor
131
+ Output tensor of shape [*, I, J, C_in]
132
+
133
+ """
134
+ if mask is None:
135
+ # [*, I, J]
136
+ mask = x.new_ones(
137
+ x.shape[:-1],
138
+ )
139
+
140
+ if not self.starting:
141
+ x = x.transpose(-2, -3)
142
+ mask = mask.transpose(-1, -2)
143
+
144
+ # [*, I, J, C_in]
145
+ x = self.layer_norm(x)
146
+
147
+ # [*, I, 1, 1, J]
148
+ mask = mask[..., :, None, None, :]
149
+ mask_bias = self.inf * (mask - 1)
150
+
151
+ # [*, H, I, J]
152
+ triangle_bias = permute_final_dims(self.linear(x), (2, 0, 1))
153
+
154
+ # [*, 1, H, I, J]
155
+ triangle_bias = triangle_bias.unsqueeze(-4)
156
+
157
+ if chunk_size is not None and not use_kernels:
158
+ x = self._chunk(
159
+ x,
160
+ triangle_bias,
161
+ mask_bias,
162
+ mask,
163
+ chunk_size,
164
+ use_kernels=use_kernels,
165
+ )
166
+ else:
167
+ x = self.mha(
168
+ x,
169
+ x,
170
+ triangle_bias,
171
+ mask_bias,
172
+ mask,
173
+ use_kernels=use_kernels,
174
+ )
175
+
176
+ if not self.starting:
177
+ x = x.transpose(-2, -3)
178
+
179
+ return x
180
+
181
+
182
+ # Implements Algorithm 13
183
+ TriangleAttentionStartingNode = TriangleAttention
184
+
185
+
186
+ class TriangleAttentionEndingNode(TriangleAttention):
187
+ """Implement Algorithm 14."""
188
+
189
+ __init__ = partialmethod(TriangleAttention.__init__, starting=False)
vb_tri_attn_primitives.py CHANGED
@@ -1,411 +1,411 @@
1
- # Copyright 2021 AlQuraishi Laboratory
2
- # Copyright 2021 DeepMind Technologies Limited
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
- # http://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
- import math
17
- import importlib
18
- from typing import Callable, List, Optional, Tuple
19
-
20
- import torch
21
- from einops import rearrange
22
- from torch import nn
23
-
24
- from . import vb_layers_initialize as initialize
25
- from .vb_tri_attn_utils import (
26
- flatten_final_dims,
27
- permute_final_dims,
28
- )
29
-
30
-
31
- class Linear(nn.Linear):
32
- """
33
- A Linear layer with built-in nonstandard initializations. Called just
34
- like torch.nn.Linear.
35
-
36
- Implements the initializers in 1.11.4, plus some additional ones found
37
- in the code.
38
- """
39
-
40
- def __init__(
41
- self,
42
- in_dim: int,
43
- out_dim: int,
44
- bias: bool = True,
45
- init: str = "default",
46
- init_fn: Optional[Callable[[torch.Tensor, torch.Tensor], None]] = None,
47
- precision=None,
48
- ):
49
- """Initialize the linear layer.
50
-
51
- Parameters
52
- ----------
53
- in_dim : int
54
- The final dimension of inputs to the layer
55
- out_dim : int
56
- The final dimension of layer outputs
57
- bias : bool, default=True
58
- Whether to learn an additive bias
59
- init : str, default='default'
60
- The initializer to use. Choose from:
61
-
62
- - "default": LeCun fan-in truncated normal initialization
63
- - "relu": He initialization w/ truncated normal distribution
64
- - "glorot": Fan-average Glorot uniform initialization
65
- - "gating": Weights=0, Bias=1
66
- - "normal": Normal initialization with std=1/sqrt(fan_in)
67
- - "final": Weights=0, Bias=0
68
-
69
- Overridden by init_fn if the latter is not None.
70
- init_fn : callable, optional
71
- A custom initializer taking weight and bias as inputs.
72
- Overrides init if not None.
73
-
74
- """
75
- super().__init__(in_dim, out_dim, bias=bias)
76
-
77
- if bias:
78
- with torch.no_grad():
79
- self.bias.fill_(0)
80
-
81
- with torch.no_grad():
82
- if init_fn is not None:
83
- init_fn(self.weight, self.bias)
84
- else:
85
- if init == "default":
86
- initialize.lecun_normal_init_(self.weight)
87
- elif init == "relu":
88
- initialize.he_normal_init_(self.weight)
89
- elif init == "glorot":
90
- initialize.glorot_uniform_init_(self.weight)
91
- elif init == "gating":
92
- initialize.gating_init_(self.weight)
93
- if bias:
94
- self.bias.fill_(1.0)
95
- elif init == "normal":
96
- initialize.normal_init_(self.weight)
97
- elif init == "final":
98
- initialize.final_init_(self.weight)
99
- else:
100
- raise ValueError("Invalid init string.")
101
-
102
- self.precision = precision
103
-
104
- def forward(self, input: torch.Tensor) -> torch.Tensor:
105
- d = input.dtype
106
- if self.precision is not None:
107
- with torch.autocast("cuda", enabled=False):
108
- bias = (
109
- self.bias.to(dtype=self.precision)
110
- if self.bias is not None
111
- else None
112
- )
113
- return nn.functional.linear(
114
- input.to(dtype=self.precision),
115
- self.weight.to(dtype=self.precision),
116
- bias,
117
- ).to(dtype=d)
118
-
119
- if d is torch.bfloat16:
120
- with torch.autocast("cuda", enabled=False):
121
- bias = self.bias.to(dtype=d) if self.bias is not None else None
122
- return nn.functional.linear(input, self.weight.to(dtype=d), bias)
123
-
124
- return nn.functional.linear(input, self.weight, self.bias)
125
-
126
-
127
- class LayerNorm(nn.Module):
128
- def __init__(self, c_in, eps=1e-5):
129
- super(LayerNorm, self).__init__()
130
-
131
- self.c_in = (c_in,)
132
- self.eps = eps
133
-
134
- self.weight = nn.Parameter(torch.ones(c_in))
135
- self.bias = nn.Parameter(torch.zeros(c_in))
136
-
137
- def forward(self, x):
138
- d = x.dtype
139
- if d is torch.bfloat16:
140
- with torch.autocast("cuda", enabled=False):
141
- out = nn.functional.layer_norm(
142
- x,
143
- self.c_in,
144
- self.weight.to(dtype=d),
145
- self.bias.to(dtype=d),
146
- self.eps,
147
- )
148
- else:
149
- out = nn.functional.layer_norm(
150
- x,
151
- self.c_in,
152
- self.weight,
153
- self.bias,
154
- self.eps,
155
- )
156
-
157
- return out
158
-
159
-
160
- @torch.jit.ignore
161
- def softmax_no_cast(t: torch.Tensor, dim: int = -1) -> torch.Tensor:
162
- """
163
- Softmax, but without automatic casting to fp32 when the input is of
164
- type bfloat16
165
- """
166
- d = t.dtype
167
- if d is torch.bfloat16:
168
- with torch.autocast("cuda", enabled=False):
169
- s = torch.nn.functional.softmax(t, dim=dim)
170
- else:
171
- s = torch.nn.functional.softmax(t, dim=dim)
172
-
173
- return s
174
-
175
-
176
- # @torch.jit.script
177
- def _attention(
178
- query: torch.Tensor,
179
- key: torch.Tensor,
180
- value: torch.Tensor,
181
- biases: List[torch.Tensor],
182
- ) -> torch.Tensor:
183
- # [*, H, C_hidden, K]
184
- key = permute_final_dims(key, (1, 0))
185
-
186
- # [*, H, Q, K]
187
- a = torch.matmul(query, key)
188
-
189
- for b in biases:
190
- a += b
191
-
192
- a = softmax_no_cast(a, -1)
193
-
194
- # [*, H, Q, C_hidden]
195
- a = torch.matmul(a, value)
196
-
197
- return a
198
-
199
-
200
- @torch.compiler.disable
201
- def kernel_triangular_attn(q, k, v, tri_bias, mask, scale):
202
- triangle_module = importlib.import_module("cuequivariance_torch.primitives.triangle")
203
- triangle_attention = triangle_module.triangle_attention
204
- return triangle_attention(q, k, v, tri_bias, mask=mask, scale=scale)
205
-
206
-
207
- class Attention(nn.Module):
208
- """
209
- Standard multi-head attention using AlphaFold's default layer
210
- initialization. Allows multiple bias vectors.
211
- """
212
-
213
- def __init__(
214
- self,
215
- c_q: int,
216
- c_k: int,
217
- c_v: int,
218
- c_hidden: int,
219
- no_heads: int,
220
- gating: bool = True,
221
- ):
222
- """Initialize the attention layer.
223
-
224
- Parameters
225
- ----------
226
- c_q : int
227
- Input dimension of query data
228
- c_k : int
229
- Input dimension of key data
230
- c_v : int
231
- Input dimension of value data
232
- c_hidden : int
233
- Per-head hidden dimension
234
- no_heads : int
235
- Number of attention heads
236
- gating : bool, default=True
237
- Whether the output should be gated using query data
238
-
239
- """
240
- super().__init__()
241
-
242
- self.c_q = c_q
243
- self.c_k = c_k
244
- self.c_v = c_v
245
- self.c_hidden = c_hidden
246
- self.no_heads = no_heads
247
- self.gating = gating
248
-
249
- # DISCREPANCY: c_hidden is not the per-head channel dimension, as
250
- # stated in the supplement, but the overall channel dimension.
251
-
252
- self.linear_q = Linear(
253
- self.c_q, self.c_hidden * self.no_heads, bias=False, init="glorot"
254
- )
255
- self.linear_k = Linear(
256
- self.c_k, self.c_hidden * self.no_heads, bias=False, init="glorot"
257
- )
258
- self.linear_v = Linear(
259
- self.c_v, self.c_hidden * self.no_heads, bias=False, init="glorot"
260
- )
261
- self.linear_o = Linear(
262
- self.c_hidden * self.no_heads, self.c_q, bias=False, init="final"
263
- )
264
-
265
- self.linear_g = None
266
- if self.gating:
267
- self.linear_g = Linear(
268
- self.c_q, self.c_hidden * self.no_heads, bias=False, init="gating"
269
- )
270
-
271
- self.sigmoid = nn.Sigmoid()
272
-
273
- def _prep_qkv(
274
- self, q_x: torch.Tensor, kv_x: torch.Tensor, apply_scale: bool = True
275
- ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
276
- # [*, Q/K/V, H * C_hidden]
277
- q = self.linear_q(q_x)
278
- k = self.linear_k(kv_x)
279
- v = self.linear_v(kv_x)
280
-
281
- # [*, Q/K, H, C_hidden]
282
- q = q.view(q.shape[:-1] + (self.no_heads, -1))
283
- k = k.view(k.shape[:-1] + (self.no_heads, -1))
284
- v = v.view(v.shape[:-1] + (self.no_heads, -1))
285
-
286
- # [*, H, Q/K, C_hidden]
287
- q = q.transpose(-2, -3)
288
- k = k.transpose(-2, -3)
289
- v = v.transpose(-2, -3)
290
-
291
- if apply_scale:
292
- q /= math.sqrt(self.c_hidden)
293
-
294
- return q, k, v
295
-
296
- def _wrap_up(self, o: torch.Tensor, q_x: torch.Tensor) -> torch.Tensor:
297
- if self.linear_g is not None:
298
- g = self.sigmoid(self.linear_g(q_x))
299
-
300
- # [*, Q, H, C_hidden]
301
- g = g.view(g.shape[:-1] + (self.no_heads, -1))
302
- o = o * g
303
-
304
- # [*, Q, H * C_hidden]
305
- o = flatten_final_dims(o, 2)
306
-
307
- # [*, Q, C_q]
308
- o = self.linear_o(o)
309
-
310
- return o
311
-
312
- def forward(
313
- self,
314
- q_x: torch.Tensor,
315
- kv_x: torch.Tensor,
316
- tri_bias: torch.Tensor,
317
- mask_bias: torch.Tensor,
318
- mask: torch.Tensor,
319
- use_kernels: bool = False,
320
- ) -> torch.Tensor:
321
- """Compute attention.
322
-
323
- Parameters
324
- ----------
325
- q_x : torch.Tensor
326
- [*, Q, C_q] query data
327
- kv_x : torch.Tensor
328
- [*, K, C_k] key data
329
- tri_bias : torch.Tensor
330
- [*, H, Q, K] triangular bias
331
- mask_bias : torch.Tensor
332
- [*, H, Q, K] mask bias
333
- mask : torch.Tensor
334
- [*, Q, K] mask
335
- use_kernels : bool, default=False
336
- Whether to use optimized CUDA kernels
337
-
338
- Returns
339
- -------
340
- [*, Q, C_q] attention update
341
-
342
- """
343
- # Attention kernel applies scaling internally
344
- q, k, v = self._prep_qkv(
345
- q_x,
346
- kv_x,
347
- apply_scale=not use_kernels,
348
- )
349
-
350
- if use_kernels:
351
- scale = 1.0 / math.sqrt(self.c_hidden)
352
- o = kernel_triangular_attn(
353
- q,
354
- k,
355
- v,
356
- tri_bias=tri_bias,
357
- mask=mask.bool(),
358
- scale=scale,
359
- )
360
- o = o.transpose(-2, -3)
361
- else:
362
- biases = [mask_bias, tri_bias]
363
- o = _attention(q, k, v, biases)
364
- o = o.transpose(-2, -3)
365
-
366
- o = self._wrap_up(o, q_x)
367
-
368
- return o
369
-
370
-
371
- def _trifast_attn(q, k, v, biases):
372
- orig_n_dims = len(q.shape)
373
-
374
- if len(biases) != 2:
375
- raise ValueError(f"Trifast expects two bias terms, found {len(biases)}")
376
-
377
- mask, b = biases
378
-
379
- if len(b.shape) == 5:
380
- # Sometimes there is an extra batch dim -- why?
381
- b = b.squeeze(1)
382
-
383
- if orig_n_dims == 4:
384
- # add fake batch dim
385
- q = q.unsqueeze(0)
386
- k = k.unsqueeze(0)
387
- v = v.unsqueeze(0)
388
- # b = b.unsqueeze(0) not sure why this and only this has a batch dim?
389
- mask = mask.unsqueeze(0)
390
-
391
- if len(q.shape) != 5:
392
- raise ValueError(f"Trifast expects q/k/v to be 5D, found {len(q.shape)}")
393
-
394
- # Reorder q/k/v
395
- q = rearrange(q, "b i h j d -> b h i j d")
396
- k = rearrange(k, "b i h j d -> b h i j d")
397
- v = rearrange(v, "b i h j d -> b h i j d")
398
-
399
- # Make mask the right shape.
400
- mask = rearrange(mask, "b i () () j -> b i j").bool()
401
-
402
- # Delay import to here to avoid initializing cuda too early
403
- from trifast import triangle_attention
404
-
405
- o = triangle_attention(q, k, v, b, mask)
406
- o = rearrange(o, "b h i j d -> b i j h d")
407
-
408
- # Remove the batch dim if we added it.
409
- if orig_n_dims == 4:
410
- o = o.squeeze(0)
411
- return o
 
1
+ # Copyright 2021 AlQuraishi Laboratory
2
+ # Copyright 2021 DeepMind Technologies Limited
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
+ # http://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
+ import math
17
+ import importlib
18
+ from typing import Callable, List, Optional, Tuple
19
+
20
+ import torch
21
+ from einops import rearrange
22
+ from torch import nn
23
+
24
+ from . import vb_layers_initialize as initialize
25
+ from .vb_tri_attn_utils import (
26
+ flatten_final_dims,
27
+ permute_final_dims,
28
+ )
29
+
30
+
31
+ class Linear(nn.Linear):
32
+ """
33
+ A Linear layer with built-in nonstandard initializations. Called just
34
+ like torch.nn.Linear.
35
+
36
+ Implements the initializers in 1.11.4, plus some additional ones found
37
+ in the code.
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ in_dim: int,
43
+ out_dim: int,
44
+ bias: bool = True,
45
+ init: str = "default",
46
+ init_fn: Optional[Callable[[torch.Tensor, torch.Tensor], None]] = None,
47
+ precision=None,
48
+ ):
49
+ """Initialize the linear layer.
50
+
51
+ Parameters
52
+ ----------
53
+ in_dim : int
54
+ The final dimension of inputs to the layer
55
+ out_dim : int
56
+ The final dimension of layer outputs
57
+ bias : bool, default=True
58
+ Whether to learn an additive bias
59
+ init : str, default='default'
60
+ The initializer to use. Choose from:
61
+
62
+ - "default": LeCun fan-in truncated normal initialization
63
+ - "relu": He initialization w/ truncated normal distribution
64
+ - "glorot": Fan-average Glorot uniform initialization
65
+ - "gating": Weights=0, Bias=1
66
+ - "normal": Normal initialization with std=1/sqrt(fan_in)
67
+ - "final": Weights=0, Bias=0
68
+
69
+ Overridden by init_fn if the latter is not None.
70
+ init_fn : callable, optional
71
+ A custom initializer taking weight and bias as inputs.
72
+ Overrides init if not None.
73
+
74
+ """
75
+ super().__init__(in_dim, out_dim, bias=bias)
76
+
77
+ if bias:
78
+ with torch.no_grad():
79
+ self.bias.fill_(0)
80
+
81
+ with torch.no_grad():
82
+ if init_fn is not None:
83
+ init_fn(self.weight, self.bias)
84
+ else:
85
+ if init == "default":
86
+ initialize.lecun_normal_init_(self.weight)
87
+ elif init == "relu":
88
+ initialize.he_normal_init_(self.weight)
89
+ elif init == "glorot":
90
+ initialize.glorot_uniform_init_(self.weight)
91
+ elif init == "gating":
92
+ initialize.gating_init_(self.weight)
93
+ if bias:
94
+ self.bias.fill_(1.0)
95
+ elif init == "normal":
96
+ initialize.normal_init_(self.weight)
97
+ elif init == "final":
98
+ initialize.final_init_(self.weight)
99
+ else:
100
+ raise ValueError("Invalid init string.")
101
+
102
+ self.precision = precision
103
+
104
+ def forward(self, input: torch.Tensor) -> torch.Tensor:
105
+ d = input.dtype
106
+ if self.precision is not None:
107
+ with torch.autocast("cuda", enabled=False):
108
+ bias = (
109
+ self.bias.to(dtype=self.precision)
110
+ if self.bias is not None
111
+ else None
112
+ )
113
+ return nn.functional.linear(
114
+ input.to(dtype=self.precision),
115
+ self.weight.to(dtype=self.precision),
116
+ bias,
117
+ ).to(dtype=d)
118
+
119
+ if d is torch.bfloat16:
120
+ with torch.autocast("cuda", enabled=False):
121
+ bias = self.bias.to(dtype=d) if self.bias is not None else None
122
+ return nn.functional.linear(input, self.weight.to(dtype=d), bias)
123
+
124
+ return nn.functional.linear(input, self.weight, self.bias)
125
+
126
+
127
+ class LayerNorm(nn.Module):
128
+ def __init__(self, c_in, eps=1e-5):
129
+ super(LayerNorm, self).__init__()
130
+
131
+ self.c_in = (c_in,)
132
+ self.eps = eps
133
+
134
+ self.weight = nn.Parameter(torch.ones(c_in))
135
+ self.bias = nn.Parameter(torch.zeros(c_in))
136
+
137
+ def forward(self, x):
138
+ d = x.dtype
139
+ if d is torch.bfloat16:
140
+ with torch.autocast("cuda", enabled=False):
141
+ out = nn.functional.layer_norm(
142
+ x,
143
+ self.c_in,
144
+ self.weight.to(dtype=d),
145
+ self.bias.to(dtype=d),
146
+ self.eps,
147
+ )
148
+ else:
149
+ out = nn.functional.layer_norm(
150
+ x,
151
+ self.c_in,
152
+ self.weight,
153
+ self.bias,
154
+ self.eps,
155
+ )
156
+
157
+ return out
158
+
159
+
160
+ @torch.jit.ignore
161
+ def softmax_no_cast(t: torch.Tensor, dim: int = -1) -> torch.Tensor:
162
+ """
163
+ Softmax, but without automatic casting to fp32 when the input is of
164
+ type bfloat16
165
+ """
166
+ d = t.dtype
167
+ if d is torch.bfloat16:
168
+ with torch.autocast("cuda", enabled=False):
169
+ s = torch.nn.functional.softmax(t, dim=dim)
170
+ else:
171
+ s = torch.nn.functional.softmax(t, dim=dim)
172
+
173
+ return s
174
+
175
+
176
+ # @torch.jit.script
177
+ def _attention(
178
+ query: torch.Tensor,
179
+ key: torch.Tensor,
180
+ value: torch.Tensor,
181
+ biases: List[torch.Tensor],
182
+ ) -> torch.Tensor:
183
+ # [*, H, C_hidden, K]
184
+ key = permute_final_dims(key, (1, 0))
185
+
186
+ # [*, H, Q, K]
187
+ a = torch.matmul(query, key)
188
+
189
+ for b in biases:
190
+ a += b
191
+
192
+ a = softmax_no_cast(a, -1)
193
+
194
+ # [*, H, Q, C_hidden]
195
+ a = torch.matmul(a, value)
196
+
197
+ return a
198
+
199
+
200
+ @torch.compiler.disable
201
+ def kernel_triangular_attn(q, k, v, tri_bias, mask, scale):
202
+ triangle_module = importlib.import_module("cuequivariance_torch.primitives.triangle")
203
+ triangle_attention = triangle_module.triangle_attention
204
+ return triangle_attention(q, k, v, tri_bias, mask=mask, scale=scale)
205
+
206
+
207
+ class Attention(nn.Module):
208
+ """
209
+ Standard multi-head attention using AlphaFold's default layer
210
+ initialization. Allows multiple bias vectors.
211
+ """
212
+
213
+ def __init__(
214
+ self,
215
+ c_q: int,
216
+ c_k: int,
217
+ c_v: int,
218
+ c_hidden: int,
219
+ no_heads: int,
220
+ gating: bool = True,
221
+ ):
222
+ """Initialize the attention layer.
223
+
224
+ Parameters
225
+ ----------
226
+ c_q : int
227
+ Input dimension of query data
228
+ c_k : int
229
+ Input dimension of key data
230
+ c_v : int
231
+ Input dimension of value data
232
+ c_hidden : int
233
+ Per-head hidden dimension
234
+ no_heads : int
235
+ Number of attention heads
236
+ gating : bool, default=True
237
+ Whether the output should be gated using query data
238
+
239
+ """
240
+ super().__init__()
241
+
242
+ self.c_q = c_q
243
+ self.c_k = c_k
244
+ self.c_v = c_v
245
+ self.c_hidden = c_hidden
246
+ self.no_heads = no_heads
247
+ self.gating = gating
248
+
249
+ # DISCREPANCY: c_hidden is not the per-head channel dimension, as
250
+ # stated in the supplement, but the overall channel dimension.
251
+
252
+ self.linear_q = Linear(
253
+ self.c_q, self.c_hidden * self.no_heads, bias=False, init="glorot"
254
+ )
255
+ self.linear_k = Linear(
256
+ self.c_k, self.c_hidden * self.no_heads, bias=False, init="glorot"
257
+ )
258
+ self.linear_v = Linear(
259
+ self.c_v, self.c_hidden * self.no_heads, bias=False, init="glorot"
260
+ )
261
+ self.linear_o = Linear(
262
+ self.c_hidden * self.no_heads, self.c_q, bias=False, init="final"
263
+ )
264
+
265
+ self.linear_g = None
266
+ if self.gating:
267
+ self.linear_g = Linear(
268
+ self.c_q, self.c_hidden * self.no_heads, bias=False, init="gating"
269
+ )
270
+
271
+ self.sigmoid = nn.Sigmoid()
272
+
273
+ def _prep_qkv(
274
+ self, q_x: torch.Tensor, kv_x: torch.Tensor, apply_scale: bool = True
275
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
276
+ # [*, Q/K/V, H * C_hidden]
277
+ q = self.linear_q(q_x)
278
+ k = self.linear_k(kv_x)
279
+ v = self.linear_v(kv_x)
280
+
281
+ # [*, Q/K, H, C_hidden]
282
+ q = q.view(q.shape[:-1] + (self.no_heads, -1))
283
+ k = k.view(k.shape[:-1] + (self.no_heads, -1))
284
+ v = v.view(v.shape[:-1] + (self.no_heads, -1))
285
+
286
+ # [*, H, Q/K, C_hidden]
287
+ q = q.transpose(-2, -3)
288
+ k = k.transpose(-2, -3)
289
+ v = v.transpose(-2, -3)
290
+
291
+ if apply_scale:
292
+ q /= math.sqrt(self.c_hidden)
293
+
294
+ return q, k, v
295
+
296
+ def _wrap_up(self, o: torch.Tensor, q_x: torch.Tensor) -> torch.Tensor:
297
+ if self.linear_g is not None:
298
+ g = self.sigmoid(self.linear_g(q_x))
299
+
300
+ # [*, Q, H, C_hidden]
301
+ g = g.view(g.shape[:-1] + (self.no_heads, -1))
302
+ o = o * g
303
+
304
+ # [*, Q, H * C_hidden]
305
+ o = flatten_final_dims(o, 2)
306
+
307
+ # [*, Q, C_q]
308
+ o = self.linear_o(o)
309
+
310
+ return o
311
+
312
+ def forward(
313
+ self,
314
+ q_x: torch.Tensor,
315
+ kv_x: torch.Tensor,
316
+ tri_bias: torch.Tensor,
317
+ mask_bias: torch.Tensor,
318
+ mask: torch.Tensor,
319
+ use_kernels: bool = False,
320
+ ) -> torch.Tensor:
321
+ """Compute attention.
322
+
323
+ Parameters
324
+ ----------
325
+ q_x : torch.Tensor
326
+ [*, Q, C_q] query data
327
+ kv_x : torch.Tensor
328
+ [*, K, C_k] key data
329
+ tri_bias : torch.Tensor
330
+ [*, H, Q, K] triangular bias
331
+ mask_bias : torch.Tensor
332
+ [*, H, Q, K] mask bias
333
+ mask : torch.Tensor
334
+ [*, Q, K] mask
335
+ use_kernels : bool, default=False
336
+ Whether to use optimized CUDA kernels
337
+
338
+ Returns
339
+ -------
340
+ [*, Q, C_q] attention update
341
+
342
+ """
343
+ # Attention kernel applies scaling internally
344
+ q, k, v = self._prep_qkv(
345
+ q_x,
346
+ kv_x,
347
+ apply_scale=not use_kernels,
348
+ )
349
+
350
+ if use_kernels:
351
+ scale = 1.0 / math.sqrt(self.c_hidden)
352
+ o = kernel_triangular_attn(
353
+ q,
354
+ k,
355
+ v,
356
+ tri_bias=tri_bias,
357
+ mask=mask.bool(),
358
+ scale=scale,
359
+ )
360
+ o = o.transpose(-2, -3)
361
+ else:
362
+ biases = [mask_bias, tri_bias]
363
+ o = _attention(q, k, v, biases)
364
+ o = o.transpose(-2, -3)
365
+
366
+ o = self._wrap_up(o, q_x)
367
+
368
+ return o
369
+
370
+
371
+ def _trifast_attn(q, k, v, biases):
372
+ orig_n_dims = len(q.shape)
373
+
374
+ if len(biases) != 2:
375
+ raise ValueError(f"Trifast expects two bias terms, found {len(biases)}")
376
+
377
+ mask, b = biases
378
+
379
+ if len(b.shape) == 5:
380
+ # Sometimes there is an extra batch dim -- why?
381
+ b = b.squeeze(1)
382
+
383
+ if orig_n_dims == 4:
384
+ # add fake batch dim
385
+ q = q.unsqueeze(0)
386
+ k = k.unsqueeze(0)
387
+ v = v.unsqueeze(0)
388
+ # b = b.unsqueeze(0) not sure why this and only this has a batch dim?
389
+ mask = mask.unsqueeze(0)
390
+
391
+ if len(q.shape) != 5:
392
+ raise ValueError(f"Trifast expects q/k/v to be 5D, found {len(q.shape)}")
393
+
394
+ # Reorder q/k/v
395
+ q = rearrange(q, "b i h j d -> b h i j d")
396
+ k = rearrange(k, "b i h j d -> b h i j d")
397
+ v = rearrange(v, "b i h j d -> b h i j d")
398
+
399
+ # Make mask the right shape.
400
+ mask = rearrange(mask, "b i () () j -> b i j").bool()
401
+
402
+ # Delay import to here to avoid initializing cuda too early
403
+ from trifast import triangle_attention
404
+
405
+ o = triangle_attention(q, k, v, b, mask)
406
+ o = rearrange(o, "b h i j d -> b i j h d")
407
+
408
+ # Remove the batch dim if we added it.
409
+ if orig_n_dims == 4:
410
+ o = o.squeeze(0)
411
+ return o
vb_tri_attn_utils.py CHANGED
@@ -1,380 +1,380 @@
1
- # Copyright 2021 AlQuraishi Laboratory
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
- # http://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
- from functools import partial
16
- from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
17
-
18
- import torch
19
-
20
-
21
- def add(m1, m2, inplace):
22
- # The first operation in a checkpoint can't be in-place, but it's
23
- # nice to have in-place addition during inference. Thus...
24
- if not inplace:
25
- m1 = m1 + m2
26
- else:
27
- m1 += m2
28
-
29
- return m1
30
-
31
-
32
- def permute_final_dims(tensor: torch.Tensor, inds: List[int]):
33
- zero_index = -1 * len(inds)
34
- first_inds = list(range(len(tensor.shape[:zero_index])))
35
- return tensor.permute(first_inds + [zero_index + i for i in inds])
36
-
37
-
38
- def is_fp16_enabled():
39
- # Autocast world
40
- fp16_enabled = torch.get_autocast_gpu_dtype() == torch.float16
41
- fp16_enabled = fp16_enabled and torch.is_autocast_enabled()
42
-
43
- return fp16_enabled
44
-
45
-
46
- # With tree_map, a poor man's JAX tree_map
47
- def dict_map(fn, dic, leaf_type):
48
- new_dict = {}
49
- for k, v in dic.items():
50
- if type(v) is dict:
51
- new_dict[k] = dict_map(fn, v, leaf_type)
52
- else:
53
- new_dict[k] = tree_map(fn, v, leaf_type)
54
-
55
- return new_dict
56
-
57
-
58
- def tree_map(fn, tree, leaf_type):
59
- if isinstance(tree, dict):
60
- return dict_map(fn, tree, leaf_type)
61
- elif isinstance(tree, list):
62
- return [tree_map(fn, x, leaf_type) for x in tree]
63
- elif isinstance(tree, tuple):
64
- return tuple([tree_map(fn, x, leaf_type) for x in tree])
65
- elif isinstance(tree, leaf_type):
66
- return fn(tree)
67
- else:
68
- raise ValueError(f"Tree of type {type(tree)} not supported")
69
-
70
-
71
- tensor_tree_map = partial(tree_map, leaf_type=torch.Tensor)
72
-
73
-
74
- def flatten_final_dims(t: torch.Tensor, no_dims: int):
75
- return t.reshape(t.shape[:-no_dims] + (-1,))
76
-
77
-
78
- def _fetch_dims(tree):
79
- shapes = []
80
- tree_type = type(tree)
81
- if tree_type is dict:
82
- for v in tree.values():
83
- shapes.extend(_fetch_dims(v))
84
- elif tree_type is list or tree_type is tuple:
85
- for t in tree:
86
- shapes.extend(_fetch_dims(t))
87
- elif tree_type is torch.Tensor:
88
- shapes.append(tree.shape)
89
- else:
90
- raise ValueError("Not supported")
91
-
92
- return shapes
93
-
94
-
95
- @torch.jit.ignore
96
- def _flat_idx_to_idx(
97
- flat_idx: int,
98
- dims: Tuple[int],
99
- ) -> Tuple[int]:
100
- idx = []
101
- for d in reversed(dims):
102
- idx.append(flat_idx % d)
103
- flat_idx = flat_idx // d
104
-
105
- return tuple(reversed(idx))
106
-
107
-
108
- @torch.jit.ignore
109
- def _get_minimal_slice_set(
110
- start: Sequence[int],
111
- end: Sequence[int],
112
- dims: int,
113
- start_edges: Optional[Sequence[bool]] = None,
114
- end_edges: Optional[Sequence[bool]] = None,
115
- ) -> Sequence[Tuple[int]]:
116
- """
117
- Produces an ordered sequence of tensor slices that, when used in
118
- sequence on a tensor with shape dims, yields tensors that contain every
119
- leaf in the contiguous range [start, end]. Care is taken to yield a
120
- short sequence of slices, and perhaps even the shortest possible (I'm
121
- pretty sure it's the latter).
122
-
123
- end is INCLUSIVE.
124
- """
125
-
126
- # start_edges and end_edges both indicate whether, starting from any given
127
- # dimension, the start/end index is at the top/bottom edge of the
128
- # corresponding tensor, modeled as a tree
129
- def reduce_edge_list(l):
130
- tally = 1
131
- for i in range(len(l)):
132
- reversed_idx = -1 * (i + 1)
133
- l[reversed_idx] *= tally
134
- tally = l[reversed_idx]
135
-
136
- if start_edges is None:
137
- start_edges = [s == 0 for s in start]
138
- reduce_edge_list(start_edges)
139
- if end_edges is None:
140
- end_edges = [e == (d - 1) for e, d in zip(end, dims)]
141
- reduce_edge_list(end_edges)
142
-
143
- # Base cases. Either start/end are empty and we're done, or the final,
144
- # one-dimensional tensor can be simply sliced
145
- if len(start) == 0:
146
- return [tuple()]
147
- elif len(start) == 1:
148
- return [(slice(start[0], end[0] + 1),)]
149
-
150
- slices = []
151
- path = []
152
-
153
- # Dimensions common to start and end can be selected directly
154
- for s, e in zip(start, end):
155
- if s == e:
156
- path.append(slice(s, s + 1))
157
- else:
158
- break
159
-
160
- path = tuple(path)
161
- divergence_idx = len(path)
162
-
163
- # start == end, and we're done
164
- if divergence_idx == len(dims):
165
- return [tuple(path)]
166
-
167
- def upper():
168
- sdi = start[divergence_idx]
169
- return [
170
- path + (slice(sdi, sdi + 1),) + s
171
- for s in _get_minimal_slice_set(
172
- start[divergence_idx + 1 :],
173
- [d - 1 for d in dims[divergence_idx + 1 :]],
174
- dims[divergence_idx + 1 :],
175
- start_edges=start_edges[divergence_idx + 1 :],
176
- end_edges=[1 for _ in end_edges[divergence_idx + 1 :]],
177
- )
178
- ]
179
-
180
- def lower():
181
- edi = end[divergence_idx]
182
- return [
183
- path + (slice(edi, edi + 1),) + s
184
- for s in _get_minimal_slice_set(
185
- [0 for _ in start[divergence_idx + 1 :]],
186
- end[divergence_idx + 1 :],
187
- dims[divergence_idx + 1 :],
188
- start_edges=[1 for _ in start_edges[divergence_idx + 1 :]],
189
- end_edges=end_edges[divergence_idx + 1 :],
190
- )
191
- ]
192
-
193
- # If both start and end are at the edges of the subtree rooted at
194
- # divergence_idx, we can just select the whole subtree at once
195
- if start_edges[divergence_idx] and end_edges[divergence_idx]:
196
- slices.append(path + (slice(start[divergence_idx], end[divergence_idx] + 1),))
197
- # If just start is at the edge, we can grab almost all of the subtree,
198
- # treating only the ragged bottom edge as an edge case
199
- elif start_edges[divergence_idx]:
200
- slices.append(path + (slice(start[divergence_idx], end[divergence_idx]),))
201
- slices.extend(lower())
202
- # Analogous to the previous case, but the top is ragged this time
203
- elif end_edges[divergence_idx]:
204
- slices.extend(upper())
205
- slices.append(
206
- path + (slice(start[divergence_idx] + 1, end[divergence_idx] + 1),)
207
- )
208
- # If both sides of the range are ragged, we need to handle both sides
209
- # separately. If there's contiguous meat in between them, we can index it
210
- # in one big chunk
211
- else:
212
- slices.extend(upper())
213
- middle_ground = end[divergence_idx] - start[divergence_idx]
214
- if middle_ground > 1:
215
- slices.append(
216
- path + (slice(start[divergence_idx] + 1, end[divergence_idx]),)
217
- )
218
- slices.extend(lower())
219
-
220
- return [tuple(s) for s in slices]
221
-
222
-
223
- @torch.jit.ignore
224
- def _chunk_slice(
225
- t: torch.Tensor,
226
- flat_start: int,
227
- flat_end: int,
228
- no_batch_dims: int,
229
- ) -> torch.Tensor:
230
- """
231
- Equivalent to
232
-
233
- t.reshape((-1,) + t.shape[no_batch_dims:])[flat_start:flat_end]
234
-
235
- but without the need for the initial reshape call, which can be
236
- memory-intensive in certain situations. The only reshape operations
237
- in this function are performed on sub-tensors that scale with
238
- (flat_end - flat_start), the chunk size.
239
- """
240
-
241
- batch_dims = t.shape[:no_batch_dims]
242
- start_idx = list(_flat_idx_to_idx(flat_start, batch_dims))
243
- # _get_minimal_slice_set is inclusive
244
- end_idx = list(_flat_idx_to_idx(flat_end - 1, batch_dims))
245
-
246
- # Get an ordered list of slices to perform
247
- slices = _get_minimal_slice_set(
248
- start_idx,
249
- end_idx,
250
- batch_dims,
251
- )
252
-
253
- sliced_tensors = [t[s] for s in slices]
254
-
255
- return torch.cat([s.view((-1,) + t.shape[no_batch_dims:]) for s in sliced_tensors])
256
-
257
-
258
- def chunk_layer(
259
- layer: Callable,
260
- inputs: Dict[str, Any],
261
- chunk_size: int,
262
- no_batch_dims: int,
263
- low_mem: bool = False,
264
- _out: Any = None,
265
- _add_into_out: bool = False,
266
- ) -> Any:
267
- """
268
- Implements the "chunking" procedure described in section 1.11.8.
269
-
270
- Layer outputs and inputs are assumed to be simple "pytrees,"
271
- consisting only of (arbitrarily nested) lists, tuples, and dicts with
272
- torch.Tensor leaves.
273
-
274
- Args:
275
- layer:
276
- The layer to be applied chunk-wise
277
- inputs:
278
- A (non-nested) dictionary of keyworded inputs. All leaves must
279
- be tensors and must share the same batch dimensions.
280
- chunk_size:
281
- The number of sub-batches per chunk. If multiple batch
282
- dimensions are specified, a "sub-batch" is defined as a single
283
- indexing of all batch dimensions simultaneously (s.t. the
284
- number of sub-batches is the product of the batch dimensions).
285
- no_batch_dims:
286
- How many of the initial dimensions of each input tensor can
287
- be considered batch dimensions.
288
- low_mem:
289
- Avoids flattening potentially large input tensors. Unnecessary
290
- in most cases, and is ever so slightly slower than the default
291
- setting.
292
- Returns:
293
- The reassembled output of the layer on the inputs.
294
- """
295
- if not (len(inputs) > 0):
296
- raise ValueError("Must provide at least one input")
297
-
298
- initial_dims = [shape[:no_batch_dims] for shape in _fetch_dims(inputs)]
299
- orig_batch_dims = tuple([max(s) for s in zip(*initial_dims)])
300
-
301
- def _prep_inputs(t):
302
- if not low_mem:
303
- if not sum(t.shape[:no_batch_dims]) == no_batch_dims:
304
- t = t.expand(orig_batch_dims + t.shape[no_batch_dims:])
305
- t = t.reshape(-1, *t.shape[no_batch_dims:])
306
- else:
307
- t = t.expand(orig_batch_dims + t.shape[no_batch_dims:])
308
- return t
309
-
310
- prepped_inputs = tensor_tree_map(_prep_inputs, inputs)
311
- prepped_outputs = None
312
- if _out is not None:
313
- reshape_fn = lambda t: t.view([-1] + list(t.shape[no_batch_dims:]))
314
- prepped_outputs = tensor_tree_map(reshape_fn, _out)
315
-
316
- flat_batch_dim = 1
317
- for d in orig_batch_dims:
318
- flat_batch_dim *= d
319
-
320
- no_chunks = flat_batch_dim // chunk_size + (flat_batch_dim % chunk_size != 0)
321
-
322
- i = 0
323
- out = prepped_outputs
324
- for _ in range(no_chunks):
325
- # Chunk the input
326
- if not low_mem:
327
- select_chunk = lambda t: t[i : i + chunk_size] if t.shape[0] != 1 else t
328
- else:
329
- select_chunk = partial(
330
- _chunk_slice,
331
- flat_start=i,
332
- flat_end=min(flat_batch_dim, i + chunk_size),
333
- no_batch_dims=len(orig_batch_dims),
334
- )
335
-
336
- chunks = tensor_tree_map(select_chunk, prepped_inputs)
337
-
338
- # Run the layer on the chunk
339
- output_chunk = layer(**chunks)
340
-
341
- # Allocate space for the output
342
- if out is None:
343
- allocate = lambda t: t.new_zeros((flat_batch_dim,) + t.shape[1:])
344
- out = tensor_tree_map(allocate, output_chunk)
345
-
346
- # Put the chunk in its pre-allocated space
347
- out_type = type(output_chunk)
348
- if out_type is dict:
349
-
350
- def assign(d1, d2):
351
- for k, v in d1.items():
352
- if type(v) is dict:
353
- assign(v, d2[k])
354
- else:
355
- if _add_into_out:
356
- v[i : i + chunk_size] += d2[k]
357
- else:
358
- v[i : i + chunk_size] = d2[k]
359
-
360
- assign(out, output_chunk)
361
- elif out_type is tuple:
362
- for x1, x2 in zip(out, output_chunk):
363
- if _add_into_out:
364
- x1[i : i + chunk_size] += x2
365
- else:
366
- x1[i : i + chunk_size] = x2
367
- elif out_type is torch.Tensor:
368
- if _add_into_out:
369
- out[i : i + chunk_size] += output_chunk
370
- else:
371
- out[i : i + chunk_size] = output_chunk
372
- else:
373
- raise ValueError("Not supported")
374
-
375
- i += chunk_size
376
-
377
- reshape = lambda t: t.view(orig_batch_dims + t.shape[1:])
378
- out = tensor_tree_map(reshape, out)
379
-
380
- return out
 
1
+ # Copyright 2021 AlQuraishi Laboratory
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
+ # http://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
+ from functools import partial
16
+ from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
17
+
18
+ import torch
19
+
20
+
21
+ def add(m1, m2, inplace):
22
+ # The first operation in a checkpoint can't be in-place, but it's
23
+ # nice to have in-place addition during inference. Thus...
24
+ if not inplace:
25
+ m1 = m1 + m2
26
+ else:
27
+ m1 += m2
28
+
29
+ return m1
30
+
31
+
32
+ def permute_final_dims(tensor: torch.Tensor, inds: List[int]):
33
+ zero_index = -1 * len(inds)
34
+ first_inds = list(range(len(tensor.shape[:zero_index])))
35
+ return tensor.permute(first_inds + [zero_index + i for i in inds])
36
+
37
+
38
+ def is_fp16_enabled():
39
+ # Autocast world
40
+ fp16_enabled = torch.get_autocast_gpu_dtype() == torch.float16
41
+ fp16_enabled = fp16_enabled and torch.is_autocast_enabled()
42
+
43
+ return fp16_enabled
44
+
45
+
46
+ # With tree_map, a poor man's JAX tree_map
47
+ def dict_map(fn, dic, leaf_type):
48
+ new_dict = {}
49
+ for k, v in dic.items():
50
+ if type(v) is dict:
51
+ new_dict[k] = dict_map(fn, v, leaf_type)
52
+ else:
53
+ new_dict[k] = tree_map(fn, v, leaf_type)
54
+
55
+ return new_dict
56
+
57
+
58
+ def tree_map(fn, tree, leaf_type):
59
+ if isinstance(tree, dict):
60
+ return dict_map(fn, tree, leaf_type)
61
+ elif isinstance(tree, list):
62
+ return [tree_map(fn, x, leaf_type) for x in tree]
63
+ elif isinstance(tree, tuple):
64
+ return tuple([tree_map(fn, x, leaf_type) for x in tree])
65
+ elif isinstance(tree, leaf_type):
66
+ return fn(tree)
67
+ else:
68
+ raise ValueError(f"Tree of type {type(tree)} not supported")
69
+
70
+
71
+ tensor_tree_map = partial(tree_map, leaf_type=torch.Tensor)
72
+
73
+
74
+ def flatten_final_dims(t: torch.Tensor, no_dims: int):
75
+ return t.reshape(t.shape[:-no_dims] + (-1,))
76
+
77
+
78
+ def _fetch_dims(tree):
79
+ shapes = []
80
+ tree_type = type(tree)
81
+ if tree_type is dict:
82
+ for v in tree.values():
83
+ shapes.extend(_fetch_dims(v))
84
+ elif tree_type is list or tree_type is tuple:
85
+ for t in tree:
86
+ shapes.extend(_fetch_dims(t))
87
+ elif tree_type is torch.Tensor:
88
+ shapes.append(tree.shape)
89
+ else:
90
+ raise ValueError("Not supported")
91
+
92
+ return shapes
93
+
94
+
95
+ @torch.jit.ignore
96
+ def _flat_idx_to_idx(
97
+ flat_idx: int,
98
+ dims: Tuple[int],
99
+ ) -> Tuple[int]:
100
+ idx = []
101
+ for d in reversed(dims):
102
+ idx.append(flat_idx % d)
103
+ flat_idx = flat_idx // d
104
+
105
+ return tuple(reversed(idx))
106
+
107
+
108
+ @torch.jit.ignore
109
+ def _get_minimal_slice_set(
110
+ start: Sequence[int],
111
+ end: Sequence[int],
112
+ dims: int,
113
+ start_edges: Optional[Sequence[bool]] = None,
114
+ end_edges: Optional[Sequence[bool]] = None,
115
+ ) -> Sequence[Tuple[int]]:
116
+ """
117
+ Produces an ordered sequence of tensor slices that, when used in
118
+ sequence on a tensor with shape dims, yields tensors that contain every
119
+ leaf in the contiguous range [start, end]. Care is taken to yield a
120
+ short sequence of slices, and perhaps even the shortest possible (I'm
121
+ pretty sure it's the latter).
122
+
123
+ end is INCLUSIVE.
124
+ """
125
+
126
+ # start_edges and end_edges both indicate whether, starting from any given
127
+ # dimension, the start/end index is at the top/bottom edge of the
128
+ # corresponding tensor, modeled as a tree
129
+ def reduce_edge_list(l):
130
+ tally = 1
131
+ for i in range(len(l)):
132
+ reversed_idx = -1 * (i + 1)
133
+ l[reversed_idx] *= tally
134
+ tally = l[reversed_idx]
135
+
136
+ if start_edges is None:
137
+ start_edges = [s == 0 for s in start]
138
+ reduce_edge_list(start_edges)
139
+ if end_edges is None:
140
+ end_edges = [e == (d - 1) for e, d in zip(end, dims)]
141
+ reduce_edge_list(end_edges)
142
+
143
+ # Base cases. Either start/end are empty and we're done, or the final,
144
+ # one-dimensional tensor can be simply sliced
145
+ if len(start) == 0:
146
+ return [tuple()]
147
+ elif len(start) == 1:
148
+ return [(slice(start[0], end[0] + 1),)]
149
+
150
+ slices = []
151
+ path = []
152
+
153
+ # Dimensions common to start and end can be selected directly
154
+ for s, e in zip(start, end):
155
+ if s == e:
156
+ path.append(slice(s, s + 1))
157
+ else:
158
+ break
159
+
160
+ path = tuple(path)
161
+ divergence_idx = len(path)
162
+
163
+ # start == end, and we're done
164
+ if divergence_idx == len(dims):
165
+ return [tuple(path)]
166
+
167
+ def upper():
168
+ sdi = start[divergence_idx]
169
+ return [
170
+ path + (slice(sdi, sdi + 1),) + s
171
+ for s in _get_minimal_slice_set(
172
+ start[divergence_idx + 1 :],
173
+ [d - 1 for d in dims[divergence_idx + 1 :]],
174
+ dims[divergence_idx + 1 :],
175
+ start_edges=start_edges[divergence_idx + 1 :],
176
+ end_edges=[1 for _ in end_edges[divergence_idx + 1 :]],
177
+ )
178
+ ]
179
+
180
+ def lower():
181
+ edi = end[divergence_idx]
182
+ return [
183
+ path + (slice(edi, edi + 1),) + s
184
+ for s in _get_minimal_slice_set(
185
+ [0 for _ in start[divergence_idx + 1 :]],
186
+ end[divergence_idx + 1 :],
187
+ dims[divergence_idx + 1 :],
188
+ start_edges=[1 for _ in start_edges[divergence_idx + 1 :]],
189
+ end_edges=end_edges[divergence_idx + 1 :],
190
+ )
191
+ ]
192
+
193
+ # If both start and end are at the edges of the subtree rooted at
194
+ # divergence_idx, we can just select the whole subtree at once
195
+ if start_edges[divergence_idx] and end_edges[divergence_idx]:
196
+ slices.append(path + (slice(start[divergence_idx], end[divergence_idx] + 1),))
197
+ # If just start is at the edge, we can grab almost all of the subtree,
198
+ # treating only the ragged bottom edge as an edge case
199
+ elif start_edges[divergence_idx]:
200
+ slices.append(path + (slice(start[divergence_idx], end[divergence_idx]),))
201
+ slices.extend(lower())
202
+ # Analogous to the previous case, but the top is ragged this time
203
+ elif end_edges[divergence_idx]:
204
+ slices.extend(upper())
205
+ slices.append(
206
+ path + (slice(start[divergence_idx] + 1, end[divergence_idx] + 1),)
207
+ )
208
+ # If both sides of the range are ragged, we need to handle both sides
209
+ # separately. If there's contiguous meat in between them, we can index it
210
+ # in one big chunk
211
+ else:
212
+ slices.extend(upper())
213
+ middle_ground = end[divergence_idx] - start[divergence_idx]
214
+ if middle_ground > 1:
215
+ slices.append(
216
+ path + (slice(start[divergence_idx] + 1, end[divergence_idx]),)
217
+ )
218
+ slices.extend(lower())
219
+
220
+ return [tuple(s) for s in slices]
221
+
222
+
223
+ @torch.jit.ignore
224
+ def _chunk_slice(
225
+ t: torch.Tensor,
226
+ flat_start: int,
227
+ flat_end: int,
228
+ no_batch_dims: int,
229
+ ) -> torch.Tensor:
230
+ """
231
+ Equivalent to
232
+
233
+ t.reshape((-1,) + t.shape[no_batch_dims:])[flat_start:flat_end]
234
+
235
+ but without the need for the initial reshape call, which can be
236
+ memory-intensive in certain situations. The only reshape operations
237
+ in this function are performed on sub-tensors that scale with
238
+ (flat_end - flat_start), the chunk size.
239
+ """
240
+
241
+ batch_dims = t.shape[:no_batch_dims]
242
+ start_idx = list(_flat_idx_to_idx(flat_start, batch_dims))
243
+ # _get_minimal_slice_set is inclusive
244
+ end_idx = list(_flat_idx_to_idx(flat_end - 1, batch_dims))
245
+
246
+ # Get an ordered list of slices to perform
247
+ slices = _get_minimal_slice_set(
248
+ start_idx,
249
+ end_idx,
250
+ batch_dims,
251
+ )
252
+
253
+ sliced_tensors = [t[s] for s in slices]
254
+
255
+ return torch.cat([s.view((-1,) + t.shape[no_batch_dims:]) for s in sliced_tensors])
256
+
257
+
258
+ def chunk_layer(
259
+ layer: Callable,
260
+ inputs: Dict[str, Any],
261
+ chunk_size: int,
262
+ no_batch_dims: int,
263
+ low_mem: bool = False,
264
+ _out: Any = None,
265
+ _add_into_out: bool = False,
266
+ ) -> Any:
267
+ """
268
+ Implements the "chunking" procedure described in section 1.11.8.
269
+
270
+ Layer outputs and inputs are assumed to be simple "pytrees,"
271
+ consisting only of (arbitrarily nested) lists, tuples, and dicts with
272
+ torch.Tensor leaves.
273
+
274
+ Args:
275
+ layer:
276
+ The layer to be applied chunk-wise
277
+ inputs:
278
+ A (non-nested) dictionary of keyworded inputs. All leaves must
279
+ be tensors and must share the same batch dimensions.
280
+ chunk_size:
281
+ The number of sub-batches per chunk. If multiple batch
282
+ dimensions are specified, a "sub-batch" is defined as a single
283
+ indexing of all batch dimensions simultaneously (s.t. the
284
+ number of sub-batches is the product of the batch dimensions).
285
+ no_batch_dims:
286
+ How many of the initial dimensions of each input tensor can
287
+ be considered batch dimensions.
288
+ low_mem:
289
+ Avoids flattening potentially large input tensors. Unnecessary
290
+ in most cases, and is ever so slightly slower than the default
291
+ setting.
292
+ Returns:
293
+ The reassembled output of the layer on the inputs.
294
+ """
295
+ if not (len(inputs) > 0):
296
+ raise ValueError("Must provide at least one input")
297
+
298
+ initial_dims = [shape[:no_batch_dims] for shape in _fetch_dims(inputs)]
299
+ orig_batch_dims = tuple([max(s) for s in zip(*initial_dims)])
300
+
301
+ def _prep_inputs(t):
302
+ if not low_mem:
303
+ if not sum(t.shape[:no_batch_dims]) == no_batch_dims:
304
+ t = t.expand(orig_batch_dims + t.shape[no_batch_dims:])
305
+ t = t.reshape(-1, *t.shape[no_batch_dims:])
306
+ else:
307
+ t = t.expand(orig_batch_dims + t.shape[no_batch_dims:])
308
+ return t
309
+
310
+ prepped_inputs = tensor_tree_map(_prep_inputs, inputs)
311
+ prepped_outputs = None
312
+ if _out is not None:
313
+ reshape_fn = lambda t: t.view([-1] + list(t.shape[no_batch_dims:]))
314
+ prepped_outputs = tensor_tree_map(reshape_fn, _out)
315
+
316
+ flat_batch_dim = 1
317
+ for d in orig_batch_dims:
318
+ flat_batch_dim *= d
319
+
320
+ no_chunks = flat_batch_dim // chunk_size + (flat_batch_dim % chunk_size != 0)
321
+
322
+ i = 0
323
+ out = prepped_outputs
324
+ for _ in range(no_chunks):
325
+ # Chunk the input
326
+ if not low_mem:
327
+ select_chunk = lambda t: t[i : i + chunk_size] if t.shape[0] != 1 else t
328
+ else:
329
+ select_chunk = partial(
330
+ _chunk_slice,
331
+ flat_start=i,
332
+ flat_end=min(flat_batch_dim, i + chunk_size),
333
+ no_batch_dims=len(orig_batch_dims),
334
+ )
335
+
336
+ chunks = tensor_tree_map(select_chunk, prepped_inputs)
337
+
338
+ # Run the layer on the chunk
339
+ output_chunk = layer(**chunks)
340
+
341
+ # Allocate space for the output
342
+ if out is None:
343
+ allocate = lambda t: t.new_zeros((flat_batch_dim,) + t.shape[1:])
344
+ out = tensor_tree_map(allocate, output_chunk)
345
+
346
+ # Put the chunk in its pre-allocated space
347
+ out_type = type(output_chunk)
348
+ if out_type is dict:
349
+
350
+ def assign(d1, d2):
351
+ for k, v in d1.items():
352
+ if type(v) is dict:
353
+ assign(v, d2[k])
354
+ else:
355
+ if _add_into_out:
356
+ v[i : i + chunk_size] += d2[k]
357
+ else:
358
+ v[i : i + chunk_size] = d2[k]
359
+
360
+ assign(out, output_chunk)
361
+ elif out_type is tuple:
362
+ for x1, x2 in zip(out, output_chunk):
363
+ if _add_into_out:
364
+ x1[i : i + chunk_size] += x2
365
+ else:
366
+ x1[i : i + chunk_size] = x2
367
+ elif out_type is torch.Tensor:
368
+ if _add_into_out:
369
+ out[i : i + chunk_size] += output_chunk
370
+ else:
371
+ out[i : i + chunk_size] = output_chunk
372
+ else:
373
+ raise ValueError("Not supported")
374
+
375
+ i += chunk_size
376
+
377
+ reshape = lambda t: t.view(orig_batch_dims + t.shape[1:])
378
+ out = tensor_tree_map(reshape, out)
379
+
380
+ return out