lhallee commited on
Commit
bb99156
·
verified ·
1 Parent(s): 5780643

Upload folder using huggingface_hub

Browse files
Files changed (7) hide show
  1. cif_writer.py +114 -0
  2. config.json +138 -0
  3. minimal_featurizer.py +469 -0
  4. minimal_structures.py +20 -0
  5. model.safetensors +3 -0
  6. modeling_boltz2.py +813 -0
  7. runtime.py +32 -0
cif_writer.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = coords[sample_index].numpy()
54
+
55
+ mask = atom_mask.detach().cpu()
56
+ if mask.ndim == 2:
57
+ mask = mask[0]
58
+ assert mask.ndim == 1, "Expected atom mask with shape [atoms]."
59
+ assert mask.shape[0] == selected_coords.shape[0], "Atom mask/coord size mismatch."
60
+
61
+ b_iso = _confidence_per_atom(
62
+ plddt=plddt,
63
+ atom_to_residue=structure_template.atom_residue_index,
64
+ num_atoms=structure_template.num_atoms,
65
+ sample_index=sample_index,
66
+ )
67
+ assert b_iso.shape[0] == structure_template.num_atoms
68
+
69
+ lines = [
70
+ "data_boltz2_prediction",
71
+ "#",
72
+ "loop_",
73
+ "_atom_site.group_PDB",
74
+ "_atom_site.id",
75
+ "_atom_site.type_symbol",
76
+ "_atom_site.label_atom_id",
77
+ "_atom_site.label_comp_id",
78
+ "_atom_site.label_asym_id",
79
+ "_atom_site.label_seq_id",
80
+ "_atom_site.Cartn_x",
81
+ "_atom_site.Cartn_y",
82
+ "_atom_site.Cartn_z",
83
+ "_atom_site.occupancy",
84
+ "_atom_site.B_iso_or_equiv",
85
+ "_atom_site.pdbx_PDB_model_num",
86
+ ]
87
+
88
+ atom_id = 1
89
+ for idx in range(structure_template.num_atoms):
90
+ if mask[idx] <= 0:
91
+ continue
92
+
93
+ residue_idx = structure_template.atom_residue_index[idx]
94
+ residue_name = structure_template.residue_names[residue_idx]
95
+ atom_name = structure_template.atom_names[idx]
96
+ element = structure_template.atom_elements[idx]
97
+ chain_id = structure_template.atom_chain_id[idx]
98
+ x_val, y_val, z_val = selected_coords[idx].tolist()
99
+ b_factor = float(b_iso[idx])
100
+
101
+ line = (
102
+ f"ATOM {atom_id} {element} {atom_name} {residue_name} {chain_id} "
103
+ f"{residue_idx + 1} {x_val:.3f} {y_val:.3f} {z_val:.3f} 1.00 {b_factor:.2f} 1"
104
+ )
105
+ lines.append(line)
106
+ atom_id += 1
107
+
108
+ lines.append("#")
109
+ text = "\n".join(lines) + "\n"
110
+
111
+ out_path = Path(output_path)
112
+ out_path.parent.mkdir(parents=True, exist_ok=True)
113
+ out_path.write_text(text, encoding="utf-8")
114
+ return str(out_path)
config.json ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.0,
54
+ "rho": 7,
55
+ "sigma_data": 16.0,
56
+ "sigma_max": 160.0,
57
+ "sigma_min": 0.0004,
58
+ "step_scale": 1.0,
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": false
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
+ },
99
+ "run_trunk_and_structure": true,
100
+ "score_model_args": {
101
+ "activation_checkpointing": true,
102
+ "atom_decoder_depth": 3,
103
+ "atom_decoder_heads": 4,
104
+ "atom_encoder_depth": 3,
105
+ "atom_encoder_heads": 4,
106
+ "conditioning_transition_layers": 2,
107
+ "dim_fourier": 256,
108
+ "sigma_data": 16,
109
+ "token_transformer_depth": 24,
110
+ "token_transformer_heads": 16,
111
+ "transformer_post_ln": false
112
+ },
113
+ "skip_run_structure": false,
114
+ "steering_args": {
115
+ "contact_guidance_update": false,
116
+ "fk_lambda": 4.0,
117
+ "fk_resampling_interval": 3,
118
+ "fk_steering": false,
119
+ "num_gd_steps": 16,
120
+ "num_particles": 3,
121
+ "physical_guidance_update": false
122
+ },
123
+ "token_level_confidence": true,
124
+ "token_s": 384,
125
+ "token_z": 128,
126
+ "use_atom_backbone_feat": false,
127
+ "use_kernels": false,
128
+ "use_no_atom_char": false,
129
+ "use_residue_feats_atoms": false
130
+ },
131
+ "default_diffusion_samples": 5,
132
+ "default_recycling_steps": 3,
133
+ "default_sampling_steps": 200,
134
+ "dtype": "float32",
135
+ "model_type": "boltz2_automodel",
136
+ "num_bins": 64,
137
+ "transformers_version": "5.1.0"
138
+ }
minimal_featurizer.py ADDED
@@ -0,0 +1,469 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 .runtime import ensure_boltz_importable
10
+
11
+
12
+ ensure_boltz_importable()
13
+ from boltz.data import const # noqa: E402
14
+
15
+
16
+ _ELEMENT_TO_Z = {
17
+ "H": 1,
18
+ "C": 6,
19
+ "N": 7,
20
+ "O": 8,
21
+ "P": 15,
22
+ "S": 16,
23
+ }
24
+
25
+
26
+ def _normalize_sequence(sequence: str) -> str:
27
+ seq = sequence.strip().upper()
28
+ assert len(seq) > 0, "Amino acid sequence must be non-empty."
29
+ for aa in seq:
30
+ assert aa in const.prot_letter_to_token, f"Unsupported residue code '{aa}'."
31
+ return seq
32
+
33
+
34
+ def _atom_name_to_element(atom_name: str) -> str:
35
+ name = atom_name.strip().upper()
36
+ if len(name) == 0:
37
+ return "C"
38
+ if name[0].isdigit():
39
+ name = name[1:]
40
+ if len(name) >= 2 and name[0:2] in ("CL", "BR", "FE", "MG", "ZN", "NA", "CA"):
41
+ return name[0]
42
+ return name[0]
43
+
44
+
45
+ def _atom_name_to_codes(atom_name: str) -> torch.Tensor:
46
+ clipped = atom_name.strip()[:4]
47
+ vals = [ord(ch) - 32 for ch in clipped]
48
+ while len(vals) < 4:
49
+ vals.append(0)
50
+ out = torch.tensor(vals, dtype=torch.long)
51
+ assert torch.all(out >= 0) and torch.all(out < 64), (
52
+ f"Invalid atom-name encoding for '{atom_name}'."
53
+ )
54
+ return out
55
+
56
+
57
+ def _default_atom_offset(atom_name: str, atom_idx: int) -> np.ndarray:
58
+ if atom_name == "N":
59
+ return np.array([-1.20, 0.80, 0.00], dtype=np.float32)
60
+ if atom_name == "CA":
61
+ return np.array([0.00, 0.00, 0.00], dtype=np.float32)
62
+ if atom_name == "C":
63
+ return np.array([1.35, 0.65, 0.00], dtype=np.float32)
64
+ if atom_name == "O":
65
+ return np.array([2.25, 0.10, 0.00], dtype=np.float32)
66
+ if atom_name == "CB":
67
+ return np.array([0.15, -1.20, 1.00], dtype=np.float32)
68
+ angle = (atom_idx + 1) * 0.7
69
+ radius = 1.4 + 0.03 * atom_idx
70
+ return np.array(
71
+ [
72
+ radius * math.cos(angle),
73
+ radius * math.sin(angle),
74
+ 0.1 * ((atom_idx % 5) - 2),
75
+ ],
76
+ dtype=np.float32,
77
+ )
78
+
79
+
80
+ def _build_template(
81
+ sequence: str,
82
+ ) -> Tuple[
83
+ ProteinStructureTemplate,
84
+ List[str],
85
+ List[int],
86
+ List[int],
87
+ List[int],
88
+ List[np.ndarray],
89
+ List[int],
90
+ ]:
91
+ residue_names: List[str] = []
92
+ residue_token_ids: List[int] = []
93
+ atom_names: List[str] = []
94
+ atom_elements: List[str] = []
95
+ atom_residue_index: List[int] = []
96
+ atom_chain_id: List[str] = []
97
+ atom_positions: List[np.ndarray] = []
98
+ residue_center_atom_idx: List[int] = []
99
+ residue_disto_atom_idx: List[int] = []
100
+ residue_frame_atom_idx: List[int] = []
101
+
102
+ global_atom_idx = 0
103
+ for res_idx, aa in enumerate(sequence):
104
+ token_name = const.prot_letter_to_token[aa]
105
+ residue_names.append(token_name)
106
+ residue_token_ids.append(const.token_ids[token_name])
107
+
108
+ residue_atoms = const.ref_atoms[token_name]
109
+ assert len(residue_atoms) > 0, f"No reference atoms for residue {token_name}."
110
+ center_atom_name = const.res_to_center_atom[token_name]
111
+ disto_atom_name = const.res_to_disto_atom[token_name]
112
+
113
+ base = np.array([3.8 * res_idx, 0.0, 0.0], dtype=np.float32)
114
+ center_idx = -1
115
+ disto_idx = -1
116
+ n_idx = -1
117
+ ca_idx = -1
118
+ c_idx = -1
119
+
120
+ for local_idx, atom_name in enumerate(residue_atoms):
121
+ atom_names.append(atom_name)
122
+ element = _atom_name_to_element(atom_name)
123
+ atom_elements.append(element)
124
+ atom_residue_index.append(res_idx)
125
+ atom_chain_id.append("A")
126
+
127
+ atom_pos = base + _default_atom_offset(atom_name, local_idx)
128
+ atom_positions.append(atom_pos)
129
+
130
+ if atom_name == center_atom_name:
131
+ center_idx = global_atom_idx
132
+ if atom_name == disto_atom_name:
133
+ disto_idx = global_atom_idx
134
+ if atom_name == "N":
135
+ n_idx = global_atom_idx
136
+ if atom_name == "CA":
137
+ ca_idx = global_atom_idx
138
+ if atom_name == "C":
139
+ c_idx = global_atom_idx
140
+ global_atom_idx += 1
141
+
142
+ if center_idx == -1:
143
+ center_idx = global_atom_idx - len(residue_atoms)
144
+ if disto_idx == -1:
145
+ disto_idx = center_idx
146
+ if n_idx == -1:
147
+ n_idx = center_idx
148
+ if ca_idx == -1:
149
+ ca_idx = center_idx
150
+ if c_idx == -1:
151
+ c_idx = center_idx
152
+
153
+ residue_center_atom_idx.append(center_idx)
154
+ residue_disto_atom_idx.append(disto_idx)
155
+ residue_frame_atom_idx.extend([n_idx, ca_idx, c_idx])
156
+
157
+ template = ProteinStructureTemplate(
158
+ sequence=sequence,
159
+ residue_names=residue_names,
160
+ atom_names=atom_names,
161
+ atom_elements=atom_elements,
162
+ atom_residue_index=atom_residue_index,
163
+ atom_chain_id=atom_chain_id,
164
+ )
165
+
166
+ return (
167
+ template,
168
+ residue_names,
169
+ residue_token_ids,
170
+ residue_center_atom_idx,
171
+ residue_disto_atom_idx,
172
+ atom_positions,
173
+ residue_frame_atom_idx,
174
+ )
175
+
176
+
177
+ def build_boltz2_features(
178
+ amino_acid_sequence: str,
179
+ num_bins: int = 64,
180
+ atoms_per_window_queries: int = 32,
181
+ ) -> Tuple[Dict[str, torch.Tensor], ProteinStructureTemplate]:
182
+ sequence = _normalize_sequence(amino_acid_sequence)
183
+ (
184
+ template,
185
+ residue_names,
186
+ residue_token_ids,
187
+ residue_center_atom_idx,
188
+ residue_disto_atom_idx,
189
+ atom_positions_np,
190
+ residue_frame_atom_idx_flat,
191
+ ) = _build_template(sequence)
192
+
193
+ num_tokens = len(residue_names)
194
+ num_atoms = len(atom_positions_np)
195
+ assert num_tokens > 0 and num_atoms > 0
196
+
197
+ atom_positions = torch.tensor(np.asarray(atom_positions_np), dtype=torch.float32)
198
+ atom_positions = atom_positions - atom_positions.mean(dim=0, keepdim=True)
199
+
200
+ token_index = torch.arange(num_tokens, dtype=torch.long).unsqueeze(0)
201
+ residue_index = torch.arange(num_tokens, dtype=torch.long).unsqueeze(0)
202
+ asym_id = torch.zeros((1, num_tokens), dtype=torch.long)
203
+ entity_id = torch.zeros((1, num_tokens), dtype=torch.long)
204
+ sym_id = torch.zeros((1, num_tokens), dtype=torch.long)
205
+ mol_type = torch.full(
206
+ (1, num_tokens),
207
+ fill_value=const.chain_type_ids["PROTEIN"],
208
+ dtype=torch.long,
209
+ )
210
+
211
+ res_type_ids = torch.tensor(residue_token_ids, dtype=torch.long)
212
+ res_type = one_hot(res_type_ids, num_classes=const.num_tokens).float().unsqueeze(0)
213
+
214
+ token_bonds = torch.zeros((num_tokens, num_tokens), dtype=torch.float32)
215
+ type_bonds = torch.zeros((num_tokens, num_tokens), dtype=torch.long)
216
+ if "COVALENT" in const.bond_type_ids:
217
+ covalent_bond_id = const.bond_type_ids["COVALENT"]
218
+ else:
219
+ covalent_bond_id = 1
220
+ for idx in range(num_tokens - 1):
221
+ token_bonds[idx, idx + 1] = 1.0
222
+ token_bonds[idx + 1, idx] = 1.0
223
+ type_bonds[idx, idx + 1] = covalent_bond_id
224
+ type_bonds[idx + 1, idx] = covalent_bond_id
225
+ token_bonds = token_bonds.unsqueeze(0).unsqueeze(-1)
226
+ type_bonds = type_bonds.unsqueeze(0)
227
+
228
+ token_pad_mask = torch.ones((1, num_tokens), dtype=torch.float32)
229
+ token_resolved_mask = torch.ones((1, num_tokens), dtype=torch.float32)
230
+ token_disto_mask = torch.ones((1, num_tokens), dtype=torch.float32)
231
+
232
+ num_contact_classes = len(const.contact_conditioning_info)
233
+ unspecified_id = const.contact_conditioning_info["UNSPECIFIED"]
234
+ contact_ids = torch.full(
235
+ (num_tokens, num_tokens),
236
+ fill_value=unspecified_id,
237
+ dtype=torch.long,
238
+ )
239
+ contact_conditioning = one_hot(
240
+ contact_ids,
241
+ num_classes=num_contact_classes,
242
+ ).float().unsqueeze(0)
243
+ contact_threshold = torch.zeros((1, num_tokens, num_tokens), dtype=torch.float32)
244
+
245
+ assert "x-ray diffraction" in const.method_types_ids
246
+ method_feature = torch.full(
247
+ (1, num_tokens),
248
+ fill_value=const.method_types_ids["x-ray diffraction"],
249
+ dtype=torch.long,
250
+ )
251
+ modified = torch.zeros((1, num_tokens), dtype=torch.long)
252
+ cyclic_period = torch.zeros((1, num_tokens), dtype=torch.float32)
253
+ affinity_token_mask = torch.zeros((1, num_tokens), dtype=torch.float32)
254
+
255
+ ref_pos = atom_positions.unsqueeze(0)
256
+ atom_pad_mask = torch.ones((1, num_atoms), dtype=torch.float32)
257
+ atom_resolved_mask = torch.ones((1, num_atoms), dtype=torch.float32)
258
+
259
+ atom_name_codes = torch.stack(
260
+ [_atom_name_to_codes(atom_name) for atom_name in template.atom_names],
261
+ dim=0,
262
+ )
263
+ ref_atom_name_chars = one_hot(atom_name_codes, num_classes=64).float().unsqueeze(0)
264
+
265
+ atomic_numbers = []
266
+ for element in template.atom_elements:
267
+ if element in _ELEMENT_TO_Z:
268
+ z_value = _ELEMENT_TO_Z[element]
269
+ else:
270
+ z_value = _ELEMENT_TO_Z["C"]
271
+ assert z_value < const.num_elements
272
+ atomic_numbers.append(z_value)
273
+ ref_element = one_hot(
274
+ torch.tensor(atomic_numbers, dtype=torch.long),
275
+ num_classes=const.num_elements,
276
+ ).float().unsqueeze(0)
277
+
278
+ ref_charge = torch.zeros((1, num_atoms), dtype=torch.float32)
279
+ ref_chirality = torch.zeros((1, num_atoms), dtype=torch.long)
280
+ ref_space_uid = torch.tensor(template.atom_residue_index, dtype=torch.long).unsqueeze(0)
281
+
282
+ atom_to_token = one_hot(
283
+ torch.tensor(template.atom_residue_index, dtype=torch.long),
284
+ num_classes=num_tokens,
285
+ ).float().unsqueeze(0)
286
+ token_to_rep_atom = one_hot(
287
+ torch.tensor(residue_disto_atom_idx, dtype=torch.long),
288
+ num_classes=num_atoms,
289
+ ).float().unsqueeze(0)
290
+ token_to_center_atom = one_hot(
291
+ torch.tensor(residue_center_atom_idx, dtype=torch.long),
292
+ num_classes=num_atoms,
293
+ ).float().unsqueeze(0)
294
+ r_set_to_rep_atom = token_to_center_atom.clone()
295
+
296
+ num_backbone_classes = (
297
+ 1
298
+ + len(const.protein_backbone_atom_index)
299
+ + len(const.nucleic_backbone_atom_index)
300
+ )
301
+ backbone_ids = []
302
+ for atom_name in template.atom_names:
303
+ if atom_name in const.protein_backbone_atom_index:
304
+ backbone_ids.append(const.protein_backbone_atom_index[atom_name] + 1)
305
+ else:
306
+ backbone_ids.append(0)
307
+ atom_backbone_feat = one_hot(
308
+ torch.tensor(backbone_ids, dtype=torch.long),
309
+ num_classes=num_backbone_classes,
310
+ ).float().unsqueeze(0)
311
+
312
+ coords = ref_pos.unsqueeze(1).contiguous()
313
+ disto_coords = torch.stack(
314
+ [atom_positions[idx] for idx in residue_disto_atom_idx],
315
+ dim=0,
316
+ )
317
+ disto_coords_ensemble = disto_coords.unsqueeze(0).unsqueeze(0).contiguous()
318
+
319
+ bfactor = torch.zeros((1, num_atoms), dtype=torch.float32)
320
+ atom_plddt = torch.ones((1, num_atoms), dtype=torch.float32)
321
+
322
+ assert atoms_per_window_queries > 0
323
+ pad_atoms = (
324
+ ((num_atoms - 1) // atoms_per_window_queries + 1) * atoms_per_window_queries
325
+ - num_atoms
326
+ )
327
+ if pad_atoms > 0:
328
+ ref_pos = torch.nn.functional.pad(ref_pos, (0, 0, 0, pad_atoms), value=0.0)
329
+ atom_pad_mask = torch.nn.functional.pad(atom_pad_mask, (0, pad_atoms), value=0.0)
330
+ atom_resolved_mask = torch.nn.functional.pad(
331
+ atom_resolved_mask,
332
+ (0, pad_atoms),
333
+ value=0.0,
334
+ )
335
+ ref_atom_name_chars = torch.nn.functional.pad(
336
+ ref_atom_name_chars,
337
+ (0, 0, 0, 0, 0, pad_atoms),
338
+ value=0.0,
339
+ )
340
+ ref_element = torch.nn.functional.pad(ref_element, (0, 0, 0, pad_atoms), value=0.0)
341
+ ref_charge = torch.nn.functional.pad(ref_charge, (0, pad_atoms), value=0.0)
342
+ ref_chirality = torch.nn.functional.pad(ref_chirality, (0, pad_atoms), value=0)
343
+ atom_backbone_feat = torch.nn.functional.pad(
344
+ atom_backbone_feat,
345
+ (0, 0, 0, pad_atoms),
346
+ value=0.0,
347
+ )
348
+ ref_space_uid = torch.nn.functional.pad(ref_space_uid, (0, pad_atoms), value=0)
349
+ coords = torch.nn.functional.pad(coords, (0, 0, 0, pad_atoms), value=0.0)
350
+ atom_to_token = torch.nn.functional.pad(atom_to_token, (0, 0, 0, pad_atoms), value=0.0)
351
+ token_to_rep_atom = torch.nn.functional.pad(
352
+ token_to_rep_atom,
353
+ (0, pad_atoms),
354
+ value=0.0,
355
+ )
356
+ token_to_center_atom = torch.nn.functional.pad(
357
+ token_to_center_atom,
358
+ (0, pad_atoms),
359
+ value=0.0,
360
+ )
361
+ r_set_to_rep_atom = torch.nn.functional.pad(
362
+ r_set_to_rep_atom,
363
+ (0, pad_atoms),
364
+ value=0.0,
365
+ )
366
+ bfactor = torch.nn.functional.pad(bfactor, (0, pad_atoms), value=0.0)
367
+ atom_plddt = torch.nn.functional.pad(atom_plddt, (0, pad_atoms), value=0.0)
368
+
369
+ frames_idx = torch.tensor(
370
+ residue_frame_atom_idx_flat,
371
+ dtype=torch.long,
372
+ ).reshape(num_tokens, 3)
373
+ frames_idx = frames_idx.unsqueeze(0).unsqueeze(1)
374
+ frame_resolved_mask = torch.ones((1, 1, num_tokens), dtype=torch.float32)
375
+
376
+ msa = torch.tensor(residue_token_ids, dtype=torch.long).unsqueeze(0).unsqueeze(0)
377
+ msa_paired = torch.ones((1, 1, num_tokens), dtype=torch.float32)
378
+ deletion_value = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
379
+ has_deletion = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
380
+ msa_mask = torch.ones((1, 1, num_tokens), dtype=torch.float32)
381
+ deletion_mean = torch.zeros((1, num_tokens), dtype=torch.float32)
382
+ profile = one_hot(
383
+ torch.tensor(residue_token_ids, dtype=torch.long),
384
+ num_classes=const.num_tokens,
385
+ ).float().unsqueeze(0)
386
+
387
+ template_restype = one_hot(
388
+ torch.zeros((1, 1, num_tokens), dtype=torch.long),
389
+ num_classes=const.num_tokens,
390
+ ).float()
391
+ template_frame_rot = torch.zeros((1, 1, num_tokens, 3, 3), dtype=torch.float32)
392
+ template_frame_t = torch.zeros((1, 1, num_tokens, 3), dtype=torch.float32)
393
+ template_cb = torch.zeros((1, 1, num_tokens, 3), dtype=torch.float32)
394
+ template_ca = torch.zeros((1, 1, num_tokens, 3), dtype=torch.float32)
395
+ template_mask_cb = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
396
+ template_mask_frame = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
397
+ template_mask = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
398
+ query_to_template = torch.zeros((1, 1, num_tokens), dtype=torch.long)
399
+ visibility_ids = torch.zeros((1, 1, num_tokens), dtype=torch.float32)
400
+
401
+ disto_target = torch.zeros(
402
+ (1, num_tokens, num_tokens, 1, num_bins),
403
+ dtype=torch.float32,
404
+ )
405
+ disto_center = torch.stack(
406
+ [atom_positions[idx] for idx in residue_disto_atom_idx],
407
+ dim=0,
408
+ ).unsqueeze(0)
409
+
410
+ features: Dict[str, torch.Tensor] = {
411
+ "token_index": token_index,
412
+ "residue_index": residue_index,
413
+ "asym_id": asym_id,
414
+ "entity_id": entity_id,
415
+ "sym_id": sym_id,
416
+ "mol_type": mol_type,
417
+ "res_type": res_type,
418
+ "disto_center": disto_center,
419
+ "token_bonds": token_bonds,
420
+ "type_bonds": type_bonds,
421
+ "token_pad_mask": token_pad_mask,
422
+ "token_resolved_mask": token_resolved_mask,
423
+ "token_disto_mask": token_disto_mask,
424
+ "contact_conditioning": contact_conditioning,
425
+ "contact_threshold": contact_threshold,
426
+ "method_feature": method_feature,
427
+ "modified": modified,
428
+ "cyclic_period": cyclic_period,
429
+ "affinity_token_mask": affinity_token_mask,
430
+ "ref_pos": ref_pos,
431
+ "atom_resolved_mask": atom_resolved_mask,
432
+ "ref_atom_name_chars": ref_atom_name_chars,
433
+ "ref_element": ref_element,
434
+ "ref_charge": ref_charge,
435
+ "ref_chirality": ref_chirality,
436
+ "atom_backbone_feat": atom_backbone_feat,
437
+ "ref_space_uid": ref_space_uid,
438
+ "coords": coords,
439
+ "atom_pad_mask": atom_pad_mask,
440
+ "atom_to_token": atom_to_token,
441
+ "token_to_rep_atom": token_to_rep_atom,
442
+ "r_set_to_rep_atom": r_set_to_rep_atom,
443
+ "token_to_center_atom": token_to_center_atom,
444
+ "disto_target": disto_target,
445
+ "disto_coords_ensemble": disto_coords_ensemble,
446
+ "bfactor": bfactor,
447
+ "plddt": atom_plddt,
448
+ "frames_idx": frames_idx,
449
+ "frame_resolved_mask": frame_resolved_mask,
450
+ "msa": msa,
451
+ "msa_paired": msa_paired,
452
+ "deletion_value": deletion_value,
453
+ "has_deletion": has_deletion,
454
+ "deletion_mean": deletion_mean,
455
+ "profile": profile,
456
+ "msa_mask": msa_mask,
457
+ "template_restype": template_restype,
458
+ "template_frame_rot": template_frame_rot,
459
+ "template_frame_t": template_frame_t,
460
+ "template_cb": template_cb,
461
+ "template_ca": template_ca,
462
+ "template_mask_cb": template_mask_cb,
463
+ "template_mask_frame": template_mask_frame,
464
+ "template_mask": template_mask,
465
+ "query_to_template": query_to_template,
466
+ "visibility_ids": visibility_ids,
467
+ }
468
+
469
+ return features, template
minimal_structures.py ADDED
@@ -0,0 +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)
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:da4a19b5c0afb0643b6895cd283b9e11e576a41d5605e5207727b564b8d14649
3
+ size 2083451028
modeling_boltz2.py ADDED
@@ -0,0 +1,813 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ import inspect
3
+ from collections.abc import Mapping, Sequence
4
+ from dataclasses import dataclass
5
+ from typing import Any, Dict, Optional
6
+
7
+ import torch
8
+ import torch._dynamo
9
+ import torch.nn as nn
10
+ from torch import Tensor
11
+ from transformers import PreTrainedModel, PretrainedConfig
12
+ from transformers.modeling_outputs import ModelOutput
13
+
14
+ from .cif_writer import write_cif
15
+ from .minimal_featurizer import build_boltz2_features
16
+ from .minimal_structures import ProteinStructureTemplate
17
+ from .runtime import ensure_boltz_importable
18
+
19
+
20
+ ensure_boltz_importable()
21
+ import boltz.model.layers.initialize as init # noqa: E402
22
+ from boltz.data import const # noqa: E402
23
+ from boltz.model.layers.pairformer import PairformerModule # noqa: E402
24
+ from boltz.model.modules.confidencev2 import ConfidenceModule # noqa: E402
25
+ from boltz.model.modules.diffusion_conditioning import DiffusionConditioning # noqa: E402
26
+ from boltz.model.modules.diffusionv2 import AtomDiffusion, DiffusionModule # noqa: E402
27
+ from boltz.model.modules.encodersv2 import RelativePositionEncoder # noqa: E402
28
+ from boltz.model.modules.trunkv2 import ( # noqa: E402
29
+ ContactConditioning,
30
+ DistogramModule,
31
+ InputEmbedder,
32
+ MSAModule,
33
+ )
34
+
35
+
36
+ def _default_steering_args() -> dict[str, Any]:
37
+ return {
38
+ "fk_steering": False,
39
+ "num_particles": 3,
40
+ "fk_lambda": 4.0,
41
+ "fk_resampling_interval": 3,
42
+ "physical_guidance_update": False,
43
+ "contact_guidance_update": False,
44
+ "num_gd_steps": 16,
45
+ }
46
+
47
+
48
+ def _require_key(mapping: dict[str, Any], key: str) -> Any:
49
+ assert key in mapping, f"Missing required key '{key}' in checkpoint hyperparameters."
50
+ return mapping[key]
51
+
52
+
53
+ def _state_dict_without_wrappers(state_dict: dict[str, Tensor]) -> dict[str, Tensor]:
54
+ cleaned: dict[str, Tensor] = {}
55
+ for key, value in state_dict.items():
56
+ if key.startswith("ema."):
57
+ continue
58
+ new_key = key
59
+ if new_key.startswith("model."):
60
+ new_key = new_key[len("model.") :]
61
+ if new_key.startswith("module."):
62
+ new_key = new_key[len("module.") :]
63
+ cleaned[new_key] = value
64
+ return cleaned
65
+
66
+
67
+ def _to_cpu_detached(value: Any) -> Any:
68
+ if torch.is_tensor(value):
69
+ return value.detach().cpu()
70
+ if isinstance(value, dict):
71
+ out: dict[Any, Any] = {}
72
+ for key, nested_value in value.items():
73
+ out[key] = _to_cpu_detached(nested_value)
74
+ return out
75
+ if isinstance(value, list):
76
+ return [_to_cpu_detached(item) for item in value]
77
+ if isinstance(value, tuple):
78
+ return tuple(_to_cpu_detached(item) for item in value)
79
+ return value
80
+
81
+
82
+ def _to_plain_python(value: Any) -> Any:
83
+ if isinstance(value, Mapping):
84
+ out: dict[Any, Any] = {}
85
+ for key, nested_value in value.items():
86
+ out[key] = _to_plain_python(nested_value)
87
+ return out
88
+ if isinstance(value, list):
89
+ return [_to_plain_python(item) for item in value]
90
+ if isinstance(value, tuple):
91
+ return [_to_plain_python(item) for item in value]
92
+ if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
93
+ return [_to_plain_python(item) for item in value]
94
+ return value
95
+
96
+
97
+ def _filtered_kwargs(target: Any, kwargs: dict[str, Any]) -> dict[str, Any]:
98
+ signature = inspect.signature(target.__init__)
99
+ allowed = set(signature.parameters.keys())
100
+ allowed.discard("self")
101
+ filtered: dict[str, Any] = {}
102
+ for key, value in kwargs.items():
103
+ if key in allowed:
104
+ filtered[key] = value
105
+ return filtered
106
+
107
+
108
+ @dataclass
109
+ class Boltz2StructureOutput(ModelOutput):
110
+ sample_atom_coords: Optional[torch.Tensor] = None
111
+ atom_pad_mask: Optional[torch.Tensor] = None
112
+ plddt: Optional[torch.Tensor] = None
113
+ confidence_score: Optional[torch.Tensor] = None
114
+ complex_plddt: Optional[torch.Tensor] = None
115
+ iptm: Optional[torch.Tensor] = None
116
+ ptm: Optional[torch.Tensor] = None
117
+ sequence: Optional[str] = None
118
+ structure_template: Optional[ProteinStructureTemplate] = None
119
+ raw_output: Optional[dict[str, torch.Tensor]] = None
120
+
121
+
122
+ class Boltz2Config(PretrainedConfig):
123
+ model_type = "boltz2_automodel"
124
+
125
+ def __init__(
126
+ self,
127
+ core_kwargs: Optional[dict[str, Any]] = None,
128
+ num_bins: int = 64,
129
+ default_recycling_steps: int = 3,
130
+ default_sampling_steps: int = 200,
131
+ default_diffusion_samples: int = 1,
132
+ **kwargs,
133
+ ) -> None:
134
+ super().__init__(**kwargs)
135
+ if core_kwargs is None:
136
+ core_kwargs = {}
137
+ self.core_kwargs = core_kwargs
138
+ self.num_bins = num_bins
139
+ self.default_recycling_steps = default_recycling_steps
140
+ self.default_sampling_steps = default_sampling_steps
141
+ self.default_diffusion_samples = default_diffusion_samples
142
+
143
+ @classmethod
144
+ def from_hyperparameters(
145
+ cls,
146
+ hparams: dict[str, Any],
147
+ use_kernels: bool = False,
148
+ default_recycling_steps: Optional[int] = None,
149
+ default_sampling_steps: Optional[int] = None,
150
+ default_diffusion_samples: Optional[int] = None,
151
+ ) -> "Boltz2Config":
152
+ assert isinstance(hparams, dict), "Expected checkpoint hyperparameters as a dictionary."
153
+ required = [
154
+ "atom_s",
155
+ "atom_z",
156
+ "token_s",
157
+ "token_z",
158
+ "num_bins",
159
+ "embedder_args",
160
+ "msa_args",
161
+ "pairformer_args",
162
+ "score_model_args",
163
+ "diffusion_process_args",
164
+ ]
165
+ for key in required:
166
+ _require_key(hparams, key)
167
+
168
+ core_kwargs: dict[str, Any] = {
169
+ "atom_s": hparams["atom_s"],
170
+ "atom_z": hparams["atom_z"],
171
+ "token_s": hparams["token_s"],
172
+ "token_z": hparams["token_z"],
173
+ "num_bins": hparams["num_bins"],
174
+ "embedder_args": _to_plain_python(copy.deepcopy(hparams["embedder_args"])),
175
+ "msa_args": _to_plain_python(copy.deepcopy(hparams["msa_args"])),
176
+ "pairformer_args": _to_plain_python(copy.deepcopy(hparams["pairformer_args"])),
177
+ "score_model_args": _to_plain_python(copy.deepcopy(hparams["score_model_args"])),
178
+ "diffusion_process_args": _to_plain_python(
179
+ copy.deepcopy(hparams["diffusion_process_args"])
180
+ ),
181
+ "use_kernels": use_kernels,
182
+ }
183
+
184
+ if "confidence_model_args" in hparams:
185
+ core_kwargs["confidence_model_args"] = _to_plain_python(
186
+ copy.deepcopy(hparams["confidence_model_args"])
187
+ )
188
+ else:
189
+ core_kwargs["confidence_model_args"] = None
190
+
191
+ if "confidence_prediction" in hparams:
192
+ core_kwargs["confidence_prediction"] = hparams["confidence_prediction"]
193
+ else:
194
+ core_kwargs["confidence_prediction"] = True
195
+
196
+ if "token_level_confidence" in hparams:
197
+ core_kwargs["token_level_confidence"] = hparams["token_level_confidence"]
198
+ else:
199
+ core_kwargs["token_level_confidence"] = True
200
+
201
+ if "alpha_pae" in hparams:
202
+ core_kwargs["alpha_pae"] = hparams["alpha_pae"]
203
+ else:
204
+ core_kwargs["alpha_pae"] = 0.0
205
+
206
+ if "atoms_per_window_queries" in hparams:
207
+ core_kwargs["atoms_per_window_queries"] = hparams["atoms_per_window_queries"]
208
+ else:
209
+ core_kwargs["atoms_per_window_queries"] = 32
210
+
211
+ if "atoms_per_window_keys" in hparams:
212
+ core_kwargs["atoms_per_window_keys"] = hparams["atoms_per_window_keys"]
213
+ else:
214
+ core_kwargs["atoms_per_window_keys"] = 128
215
+
216
+ if "atom_feature_dim" in hparams:
217
+ core_kwargs["atom_feature_dim"] = hparams["atom_feature_dim"]
218
+ else:
219
+ core_kwargs["atom_feature_dim"] = 128
220
+
221
+ if "bond_type_feature" in hparams:
222
+ core_kwargs["bond_type_feature"] = hparams["bond_type_feature"]
223
+ else:
224
+ core_kwargs["bond_type_feature"] = False
225
+
226
+ if "run_trunk_and_structure" in hparams:
227
+ core_kwargs["run_trunk_and_structure"] = hparams["run_trunk_and_structure"]
228
+ else:
229
+ core_kwargs["run_trunk_and_structure"] = True
230
+
231
+ if "skip_run_structure" in hparams:
232
+ core_kwargs["skip_run_structure"] = hparams["skip_run_structure"]
233
+ else:
234
+ core_kwargs["skip_run_structure"] = False
235
+
236
+ if "fix_sym_check" in hparams:
237
+ core_kwargs["fix_sym_check"] = hparams["fix_sym_check"]
238
+ else:
239
+ core_kwargs["fix_sym_check"] = False
240
+
241
+ if "cyclic_pos_enc" in hparams:
242
+ core_kwargs["cyclic_pos_enc"] = hparams["cyclic_pos_enc"]
243
+ else:
244
+ core_kwargs["cyclic_pos_enc"] = False
245
+
246
+ if "use_no_atom_char" in hparams:
247
+ core_kwargs["use_no_atom_char"] = hparams["use_no_atom_char"]
248
+ else:
249
+ core_kwargs["use_no_atom_char"] = False
250
+
251
+ if "use_atom_backbone_feat" in hparams:
252
+ core_kwargs["use_atom_backbone_feat"] = hparams["use_atom_backbone_feat"]
253
+ else:
254
+ core_kwargs["use_atom_backbone_feat"] = False
255
+
256
+ if "use_residue_feats_atoms" in hparams:
257
+ core_kwargs["use_residue_feats_atoms"] = hparams["use_residue_feats_atoms"]
258
+ else:
259
+ core_kwargs["use_residue_feats_atoms"] = False
260
+
261
+ if "conditioning_cutoff_min" in hparams:
262
+ core_kwargs["conditioning_cutoff_min"] = hparams["conditioning_cutoff_min"]
263
+ else:
264
+ core_kwargs["conditioning_cutoff_min"] = 4.0
265
+
266
+ if "conditioning_cutoff_max" in hparams:
267
+ core_kwargs["conditioning_cutoff_max"] = hparams["conditioning_cutoff_max"]
268
+ else:
269
+ core_kwargs["conditioning_cutoff_max"] = 20.0
270
+
271
+ if "steering_args" in hparams and hparams["steering_args"] is not None:
272
+ core_kwargs["steering_args"] = _to_plain_python(
273
+ copy.deepcopy(hparams["steering_args"])
274
+ )
275
+ else:
276
+ core_kwargs["steering_args"] = _default_steering_args()
277
+
278
+ if "validation_args" in hparams:
279
+ validation_args = hparams["validation_args"]
280
+ assert isinstance(validation_args, Mapping), (
281
+ "Expected 'validation_args' in checkpoint hyperparameters to be a mapping."
282
+ )
283
+ if default_recycling_steps is None and "recycling_steps" in validation_args:
284
+ default_recycling_steps = validation_args["recycling_steps"]
285
+ if default_sampling_steps is None and "sampling_steps" in validation_args:
286
+ default_sampling_steps = validation_args["sampling_steps"]
287
+ if default_diffusion_samples is None and "diffusion_samples" in validation_args:
288
+ default_diffusion_samples = validation_args["diffusion_samples"]
289
+
290
+ if default_recycling_steps is None:
291
+ default_recycling_steps = 3
292
+ if default_sampling_steps is None:
293
+ default_sampling_steps = 200
294
+ if default_diffusion_samples is None:
295
+ default_diffusion_samples = 1
296
+
297
+ return cls(
298
+ core_kwargs=core_kwargs,
299
+ num_bins=hparams["num_bins"],
300
+ default_recycling_steps=default_recycling_steps,
301
+ default_sampling_steps=default_sampling_steps,
302
+ default_diffusion_samples=default_diffusion_samples,
303
+ )
304
+
305
+
306
+ class Boltz2InferenceCore(nn.Module):
307
+ def __init__(
308
+ self,
309
+ atom_s: int,
310
+ atom_z: int,
311
+ token_s: int,
312
+ token_z: int,
313
+ num_bins: int,
314
+ embedder_args: dict[str, Any],
315
+ msa_args: dict[str, Any],
316
+ pairformer_args: dict[str, Any],
317
+ score_model_args: dict[str, Any],
318
+ diffusion_process_args: dict[str, Any],
319
+ confidence_model_args: Optional[dict[str, Any]] = None,
320
+ atom_feature_dim: int = 128,
321
+ confidence_prediction: bool = True,
322
+ token_level_confidence: bool = True,
323
+ alpha_pae: float = 0.0,
324
+ atoms_per_window_queries: int = 32,
325
+ atoms_per_window_keys: int = 128,
326
+ run_trunk_and_structure: bool = True,
327
+ skip_run_structure: bool = False,
328
+ bond_type_feature: bool = False,
329
+ fix_sym_check: bool = False,
330
+ cyclic_pos_enc: bool = False,
331
+ use_no_atom_char: bool = False,
332
+ use_atom_backbone_feat: bool = False,
333
+ use_residue_feats_atoms: bool = False,
334
+ conditioning_cutoff_min: float = 4.0,
335
+ conditioning_cutoff_max: float = 20.0,
336
+ use_kernels: bool = False,
337
+ steering_args: Optional[dict[str, Any]] = None,
338
+ ) -> None:
339
+ super().__init__()
340
+ self.use_kernels = use_kernels
341
+ self.confidence_prediction = confidence_prediction
342
+ self.token_level_confidence = token_level_confidence
343
+ self.alpha_pae = alpha_pae
344
+ self.run_trunk_and_structure = run_trunk_and_structure
345
+ self.skip_run_structure = skip_run_structure
346
+ self.bond_type_feature = bond_type_feature
347
+ self.steering_args = steering_args if steering_args is not None else _default_steering_args()
348
+
349
+ full_embedder_args = {
350
+ "atom_s": atom_s,
351
+ "atom_z": atom_z,
352
+ "token_s": token_s,
353
+ "token_z": token_z,
354
+ "atoms_per_window_queries": atoms_per_window_queries,
355
+ "atoms_per_window_keys": atoms_per_window_keys,
356
+ "atom_feature_dim": atom_feature_dim,
357
+ "use_no_atom_char": use_no_atom_char,
358
+ "use_atom_backbone_feat": use_atom_backbone_feat,
359
+ "use_residue_feats_atoms": use_residue_feats_atoms,
360
+ **embedder_args,
361
+ }
362
+ full_embedder_args = _filtered_kwargs(InputEmbedder, full_embedder_args)
363
+ self.input_embedder = InputEmbedder(**full_embedder_args)
364
+
365
+ self.s_init = nn.Linear(token_s, token_s, bias=False)
366
+ self.z_init_1 = nn.Linear(token_s, token_z, bias=False)
367
+ self.z_init_2 = nn.Linear(token_s, token_z, bias=False)
368
+ self.rel_pos = RelativePositionEncoder(
369
+ token_z,
370
+ fix_sym_check=fix_sym_check,
371
+ cyclic_pos_enc=cyclic_pos_enc,
372
+ )
373
+ self.token_bonds = nn.Linear(1, token_z, bias=False)
374
+ if self.bond_type_feature:
375
+ self.token_bonds_type = nn.Embedding(len(const.bond_types) + 1, token_z)
376
+
377
+ self.contact_conditioning = ContactConditioning(
378
+ token_z=token_z,
379
+ cutoff_min=conditioning_cutoff_min,
380
+ cutoff_max=conditioning_cutoff_max,
381
+ )
382
+ self.s_norm = nn.LayerNorm(token_s)
383
+ self.z_norm = nn.LayerNorm(token_z)
384
+
385
+ self.s_recycle = nn.Linear(token_s, token_s, bias=False)
386
+ self.z_recycle = nn.Linear(token_z, token_z, bias=False)
387
+ init.gating_init_(self.s_recycle.weight)
388
+ init.gating_init_(self.z_recycle.weight)
389
+
390
+ torch._dynamo.config.cache_size_limit = 512 # noqa: SLF001
391
+ torch._dynamo.config.accumulated_cache_size_limit = 512 # noqa: SLF001
392
+
393
+ msa_kwargs = _filtered_kwargs(MSAModule, {"token_z": token_z, "token_s": token_s, **msa_args})
394
+ self.msa_module = MSAModule(**msa_kwargs)
395
+
396
+ pairformer_kwargs = _filtered_kwargs(
397
+ PairformerModule,
398
+ {"token_s": token_s, "token_z": token_z, **pairformer_args},
399
+ )
400
+ assert "token_s" in pairformer_kwargs and "token_z" in pairformer_kwargs
401
+ pairformer_token_s = pairformer_kwargs.pop("token_s")
402
+ pairformer_token_z = pairformer_kwargs.pop("token_z")
403
+ self.pairformer_module = PairformerModule(
404
+ pairformer_token_s,
405
+ pairformer_token_z,
406
+ **pairformer_kwargs,
407
+ )
408
+
409
+ diffusion_conditioning_kwargs = {
410
+ "token_s": token_s,
411
+ "token_z": token_z,
412
+ "atom_s": atom_s,
413
+ "atom_z": atom_z,
414
+ "atoms_per_window_queries": atoms_per_window_queries,
415
+ "atoms_per_window_keys": atoms_per_window_keys,
416
+ "atom_encoder_depth": score_model_args["atom_encoder_depth"],
417
+ "atom_encoder_heads": score_model_args["atom_encoder_heads"],
418
+ "token_transformer_depth": score_model_args["token_transformer_depth"],
419
+ "token_transformer_heads": score_model_args["token_transformer_heads"],
420
+ "atom_decoder_depth": score_model_args["atom_decoder_depth"],
421
+ "atom_decoder_heads": score_model_args["atom_decoder_heads"],
422
+ "atom_feature_dim": atom_feature_dim,
423
+ "conditioning_transition_layers": score_model_args["conditioning_transition_layers"],
424
+ "use_no_atom_char": use_no_atom_char,
425
+ "use_atom_backbone_feat": use_atom_backbone_feat,
426
+ "use_residue_feats_atoms": use_residue_feats_atoms,
427
+ }
428
+ diffusion_conditioning_kwargs = _filtered_kwargs(
429
+ DiffusionConditioning,
430
+ diffusion_conditioning_kwargs,
431
+ )
432
+ self.diffusion_conditioning = DiffusionConditioning(**diffusion_conditioning_kwargs)
433
+
434
+ structure_score_model_args = {
435
+ "token_s": token_s,
436
+ "atom_s": atom_s,
437
+ "atoms_per_window_queries": atoms_per_window_queries,
438
+ "atoms_per_window_keys": atoms_per_window_keys,
439
+ **score_model_args,
440
+ }
441
+ structure_score_model_args = _filtered_kwargs(
442
+ DiffusionModule,
443
+ structure_score_model_args,
444
+ )
445
+ structure_module_kwargs = {
446
+ "score_model_args": structure_score_model_args,
447
+ "compile_score": False,
448
+ **diffusion_process_args,
449
+ }
450
+ structure_module_kwargs = _filtered_kwargs(AtomDiffusion, structure_module_kwargs)
451
+ self.structure_module = AtomDiffusion(**structure_module_kwargs)
452
+ self.distogram_module = DistogramModule(token_z, num_bins)
453
+
454
+ if self.confidence_prediction:
455
+ assert confidence_model_args is not None, (
456
+ "confidence_prediction=True requires confidence_model_args in config."
457
+ )
458
+ confidence_kwargs = {
459
+ "token_s": token_s,
460
+ "token_z": token_z,
461
+ "token_level_confidence": token_level_confidence,
462
+ "bond_type_feature": bond_type_feature,
463
+ "fix_sym_check": fix_sym_check,
464
+ "cyclic_pos_enc": cyclic_pos_enc,
465
+ "conditioning_cutoff_min": conditioning_cutoff_min,
466
+ "conditioning_cutoff_max": conditioning_cutoff_max,
467
+ **confidence_model_args,
468
+ }
469
+ confidence_kwargs = _filtered_kwargs(ConfidenceModule, confidence_kwargs)
470
+ self.confidence_module = ConfidenceModule(**confidence_kwargs)
471
+
472
+ def forward(
473
+ self,
474
+ feats: dict[str, Tensor],
475
+ recycling_steps: int = 3,
476
+ num_sampling_steps: Optional[int] = None,
477
+ diffusion_samples: int = 1,
478
+ max_parallel_samples: Optional[int] = None,
479
+ run_confidence_sequentially: bool = True,
480
+ ) -> dict[str, Tensor]:
481
+ s_inputs = self.input_embedder(feats)
482
+ s_init = self.s_init(s_inputs)
483
+
484
+ z_init = self.z_init_1(s_inputs)[:, :, None] + self.z_init_2(s_inputs)[:, None, :]
485
+ relative_position_encoding = self.rel_pos(feats)
486
+ z_init = z_init + relative_position_encoding
487
+ z_init = z_init + self.token_bonds(feats["token_bonds"].float())
488
+ if self.bond_type_feature:
489
+ z_init = z_init + self.token_bonds_type(feats["type_bonds"].long())
490
+ z_init = z_init + self.contact_conditioning(feats)
491
+
492
+ s = torch.zeros_like(s_init)
493
+ z = torch.zeros_like(z_init)
494
+ mask = feats["token_pad_mask"].float()
495
+ pair_mask = mask[:, :, None] * mask[:, None, :]
496
+
497
+ if self.run_trunk_and_structure:
498
+ for _ in range(recycling_steps + 1):
499
+ s = s_init + self.s_recycle(self.s_norm(s))
500
+ z = z_init + self.z_recycle(self.z_norm(z))
501
+ z = z + self.msa_module(
502
+ z,
503
+ s_inputs,
504
+ feats,
505
+ use_kernels=self.use_kernels,
506
+ )
507
+ s, z = self.pairformer_module(
508
+ s,
509
+ z,
510
+ mask=mask,
511
+ pair_mask=pair_mask,
512
+ use_kernels=self.use_kernels,
513
+ )
514
+
515
+ pdistogram = self.distogram_module(z)
516
+ output: dict[str, Tensor] = {
517
+ "pdistogram": pdistogram,
518
+ "s": s,
519
+ "z": z,
520
+ }
521
+
522
+ if self.run_trunk_and_structure and (not self.skip_run_structure):
523
+ q, c, to_keys, atom_enc_bias, atom_dec_bias, token_trans_bias = (
524
+ self.diffusion_conditioning(
525
+ s_trunk=s,
526
+ z_trunk=z,
527
+ relative_position_encoding=relative_position_encoding,
528
+ feats=feats,
529
+ )
530
+ )
531
+ diffusion_conditioning = {
532
+ "q": q,
533
+ "c": c,
534
+ "to_keys": to_keys,
535
+ "atom_enc_bias": atom_enc_bias,
536
+ "atom_dec_bias": atom_dec_bias,
537
+ "token_trans_bias": token_trans_bias,
538
+ }
539
+ with torch.autocast("cuda", enabled=False):
540
+ struct_out = self.structure_module.sample(
541
+ s_trunk=s.float(),
542
+ s_inputs=s_inputs.float(),
543
+ feats=feats,
544
+ num_sampling_steps=num_sampling_steps,
545
+ atom_mask=feats["atom_pad_mask"].float(),
546
+ multiplicity=diffusion_samples,
547
+ max_parallel_samples=max_parallel_samples,
548
+ steering_args=self.steering_args,
549
+ diffusion_conditioning=diffusion_conditioning,
550
+ )
551
+ output.update(struct_out)
552
+
553
+ if self.confidence_prediction:
554
+ if self.skip_run_structure:
555
+ x_pred = feats["coords"].repeat_interleave(diffusion_samples, 0)
556
+ else:
557
+ assert "sample_atom_coords" in output, (
558
+ "Structure sampling did not produce sample_atom_coords."
559
+ )
560
+ x_pred = output["sample_atom_coords"]
561
+
562
+ output.update(
563
+ self.confidence_module(
564
+ s_inputs=s_inputs.detach(),
565
+ s=s.detach(),
566
+ z=z.detach(),
567
+ x_pred=x_pred.detach(),
568
+ feats=feats,
569
+ pred_distogram_logits=output["pdistogram"][:, :, :, 0].detach(),
570
+ multiplicity=diffusion_samples,
571
+ run_sequentially=run_confidence_sequentially,
572
+ use_kernels=self.use_kernels,
573
+ )
574
+ )
575
+
576
+ return output
577
+
578
+
579
+ class Boltz2Model(PreTrainedModel):
580
+ config_class = Boltz2Config
581
+ base_model_prefix = "core"
582
+
583
+ def __init__(self, config: Boltz2Config) -> None:
584
+ super().__init__(config)
585
+ assert isinstance(config.core_kwargs, dict), "config.core_kwargs must be a dictionary."
586
+ self.core = Boltz2InferenceCore(**config.core_kwargs)
587
+
588
+ def _init_weights(self, module: nn.Module) -> None: # noqa: ARG002
589
+ return
590
+
591
+ def _detied_state_dict(self) -> dict[str, Tensor]:
592
+ raw_state = self.state_dict()
593
+ seen_ptrs: dict[int, str] = {}
594
+ out: dict[str, Tensor] = {}
595
+ for key, tensor in raw_state.items():
596
+ if torch.is_tensor(tensor):
597
+ ptr = tensor.untyped_storage().data_ptr()
598
+ if ptr in seen_ptrs:
599
+ out[key] = tensor.clone()
600
+ else:
601
+ seen_ptrs[ptr] = key
602
+ out[key] = tensor
603
+ else:
604
+ out[key] = tensor
605
+ return out
606
+
607
+ def save_pretrained(self, save_directory: str, **kwargs: Any) -> None:
608
+ if "safe_serialization" not in kwargs:
609
+ kwargs["safe_serialization"] = False
610
+ if "state_dict" not in kwargs:
611
+ kwargs["state_dict"] = self._detied_state_dict()
612
+ super().save_pretrained(save_directory, **kwargs)
613
+
614
+ @property
615
+ def device(self) -> torch.device:
616
+ return next(self.parameters()).device
617
+
618
+ @classmethod
619
+ def from_boltz_checkpoint(
620
+ cls,
621
+ checkpoint_path: str,
622
+ map_location: str | torch.device = "cpu",
623
+ use_kernels: bool = False,
624
+ default_recycling_steps: Optional[int] = None,
625
+ default_sampling_steps: Optional[int] = None,
626
+ default_diffusion_samples: Optional[int] = None,
627
+ ) -> "Boltz2Model":
628
+ # Boltz Lightning checkpoints include OmegaConf objects and require full unpickling.
629
+ checkpoint = torch.load(
630
+ checkpoint_path,
631
+ map_location=map_location,
632
+ weights_only=False,
633
+ )
634
+ assert isinstance(checkpoint, dict), "Checkpoint must deserialize to a dictionary."
635
+ _require_key(checkpoint, "hyper_parameters")
636
+ _require_key(checkpoint, "state_dict")
637
+
638
+ hparams = checkpoint["hyper_parameters"]
639
+ assert isinstance(hparams, dict), "Checkpoint hyper_parameters must be a dictionary."
640
+ state_dict = checkpoint["state_dict"]
641
+ assert isinstance(state_dict, dict), "Checkpoint state_dict must be a dictionary."
642
+
643
+ config = Boltz2Config.from_hyperparameters(
644
+ hparams,
645
+ use_kernels=use_kernels,
646
+ default_recycling_steps=default_recycling_steps,
647
+ default_sampling_steps=default_sampling_steps,
648
+ default_diffusion_samples=default_diffusion_samples,
649
+ )
650
+ model = cls(config)
651
+ cleaned = _state_dict_without_wrappers(state_dict)
652
+ target_keys = set(model.core.state_dict().keys())
653
+ filtered: dict[str, Tensor] = {}
654
+ for key, value in cleaned.items():
655
+ if key in target_keys:
656
+ filtered[key] = value
657
+
658
+ missing = sorted(target_keys.difference(filtered.keys()))
659
+ allowed_missing_substrings = [
660
+ ".attention.norm_s.weight",
661
+ ".attention.norm_s.bias",
662
+ ]
663
+ disallowed_missing: list[str] = []
664
+ for key in missing:
665
+ is_allowed = False
666
+ for token in allowed_missing_substrings:
667
+ if token in key:
668
+ is_allowed = True
669
+ if not is_allowed:
670
+ disallowed_missing.append(key)
671
+ assert len(disallowed_missing) == 0, (
672
+ "Checkpoint is missing required parameters for Boltz2 inference core. "
673
+ f"Missing keys (first 20): {disallowed_missing[:20]}"
674
+ )
675
+
676
+ load_result = model.core.load_state_dict(filtered, strict=False)
677
+ loaded_missing = sorted(load_result.missing_keys)
678
+ loaded_disallowed_missing: list[str] = []
679
+ for key in loaded_missing:
680
+ is_allowed = False
681
+ for token in allowed_missing_substrings:
682
+ if token in key:
683
+ is_allowed = True
684
+ if not is_allowed:
685
+ loaded_disallowed_missing.append(key)
686
+ assert len(loaded_disallowed_missing) == 0, (
687
+ "Model has unexpected missing keys after load_state_dict. "
688
+ f"Missing keys (first 20): {loaded_disallowed_missing[:20]}"
689
+ )
690
+ assert len(load_result.unexpected_keys) == 0
691
+ model.eval()
692
+ return model
693
+
694
+ def forward(
695
+ self,
696
+ feats: dict[str, Tensor],
697
+ recycling_steps: Optional[int] = None,
698
+ num_sampling_steps: Optional[int] = None,
699
+ diffusion_samples: Optional[int] = None,
700
+ max_parallel_samples: Optional[int] = None,
701
+ run_confidence_sequentially: bool = True,
702
+ ) -> dict[str, Tensor]:
703
+ if recycling_steps is None:
704
+ recycling_steps = self.config.default_recycling_steps
705
+ if num_sampling_steps is None:
706
+ num_sampling_steps = self.config.default_sampling_steps
707
+ if diffusion_samples is None:
708
+ diffusion_samples = self.config.default_diffusion_samples
709
+ return self.core(
710
+ feats=feats,
711
+ recycling_steps=recycling_steps,
712
+ num_sampling_steps=num_sampling_steps,
713
+ diffusion_samples=diffusion_samples,
714
+ max_parallel_samples=max_parallel_samples,
715
+ run_confidence_sequentially=run_confidence_sequentially,
716
+ )
717
+
718
+ def _to_model_device(
719
+ self,
720
+ feats: dict[str, Tensor],
721
+ float_dtype: torch.dtype,
722
+ ) -> dict[str, Tensor]:
723
+ moved: dict[str, Tensor] = {}
724
+ for key, value in feats.items():
725
+ if torch.is_tensor(value):
726
+ if value.is_floating_point():
727
+ moved[key] = value.to(device=self.device, dtype=float_dtype)
728
+ else:
729
+ moved[key] = value.to(device=self.device)
730
+ else:
731
+ moved[key] = value
732
+ return moved
733
+
734
+ def predict_structure(
735
+ self,
736
+ amino_acid_sequence: str,
737
+ recycling_steps: Optional[int] = None,
738
+ num_sampling_steps: Optional[int] = None,
739
+ diffusion_samples: Optional[int] = None,
740
+ max_parallel_samples: Optional[int] = None,
741
+ run_confidence_sequentially: bool = True,
742
+ float_dtype: Optional[torch.dtype] = None,
743
+ ) -> Boltz2StructureOutput:
744
+ if float_dtype is None:
745
+ float_dtype = torch.float32
746
+
747
+ feats, template = build_boltz2_features(
748
+ amino_acid_sequence=amino_acid_sequence,
749
+ num_bins=self.config.num_bins,
750
+ atoms_per_window_queries=self.core.input_embedder.atom_encoder.atoms_per_window_queries,
751
+ )
752
+ feats = self._to_model_device(feats, float_dtype=float_dtype)
753
+
754
+ with torch.no_grad():
755
+ output = self.forward(
756
+ feats=feats,
757
+ recycling_steps=recycling_steps,
758
+ num_sampling_steps=num_sampling_steps,
759
+ diffusion_samples=diffusion_samples,
760
+ max_parallel_samples=max_parallel_samples,
761
+ run_confidence_sequentially=run_confidence_sequentially,
762
+ )
763
+
764
+ sample_atom_coords = output["sample_atom_coords"].detach().cpu()
765
+ atom_pad_mask = feats["atom_pad_mask"][0].detach().cpu()
766
+ plddt = output["plddt"].detach().cpu() if "plddt" in output else None
767
+ complex_plddt = output["complex_plddt"].detach().cpu() if "complex_plddt" in output else None
768
+ iptm = output["iptm"].detach().cpu() if "iptm" in output else None
769
+ ptm = output["ptm"].detach().cpu() if "ptm" in output else None
770
+
771
+ confidence_score = None
772
+ if (complex_plddt is not None) and (iptm is not None) and (ptm is not None):
773
+ if torch.allclose(iptm, torch.zeros_like(iptm)):
774
+ confidence_score = (4 * complex_plddt + ptm) / 5
775
+ else:
776
+ confidence_score = (4 * complex_plddt + iptm) / 5
777
+
778
+ return Boltz2StructureOutput(
779
+ sample_atom_coords=sample_atom_coords,
780
+ atom_pad_mask=atom_pad_mask,
781
+ plddt=plddt,
782
+ confidence_score=confidence_score,
783
+ complex_plddt=complex_plddt,
784
+ iptm=iptm,
785
+ ptm=ptm,
786
+ sequence=template.sequence,
787
+ structure_template=template,
788
+ raw_output={key: _to_cpu_detached(val) for key, val in output.items()},
789
+ )
790
+
791
+ def save_as_cif(
792
+ self,
793
+ structure_output: Boltz2StructureOutput,
794
+ output_path: str,
795
+ sample_index: int = 0,
796
+ ) -> str:
797
+ assert structure_output.structure_template is not None, (
798
+ "structure_output.structure_template is required for CIF export."
799
+ )
800
+ assert structure_output.sample_atom_coords is not None, (
801
+ "structure_output.sample_atom_coords is required for CIF export."
802
+ )
803
+ assert structure_output.atom_pad_mask is not None, (
804
+ "structure_output.atom_pad_mask is required for CIF export."
805
+ )
806
+ return write_cif(
807
+ structure_template=structure_output.structure_template,
808
+ atom_coords=structure_output.sample_atom_coords,
809
+ atom_mask=structure_output.atom_pad_mask,
810
+ output_path=output_path,
811
+ plddt=structure_output.plddt,
812
+ sample_index=sample_index,
813
+ )
runtime.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ from pathlib import Path
4
+
5
+
6
+ def ensure_boltz_importable() -> None:
7
+ candidates: list[Path] = []
8
+
9
+ if "BOLTZ_SRC_DIR" in os.environ:
10
+ env_override = os.environ["BOLTZ_SRC_DIR"]
11
+ if len(env_override.strip()) > 0:
12
+ candidates.append(Path(env_override.strip()))
13
+
14
+ module_dir = Path(__file__).resolve().parent
15
+ for parent in [module_dir, *module_dir.parents]:
16
+ candidates.append(parent / "boltz" / "src")
17
+
18
+ cwd_dir = Path.cwd().resolve()
19
+ for parent in [cwd_dir, *cwd_dir.parents]:
20
+ candidates.append(parent / "boltz" / "src")
21
+
22
+ for candidate in candidates:
23
+ if candidate.exists():
24
+ candidate_str = str(candidate)
25
+ if candidate_str not in sys.path:
26
+ sys.path.insert(0, candidate_str)
27
+ return
28
+
29
+ assert False, (
30
+ "Could not locate cloned Boltz source directory ('boltz/src'). "
31
+ "Set BOLTZ_SRC_DIR explicitly if needed."
32
+ )