diff --git a/README.md b/README.md index 37c612af..b0e4f4f5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # A world model of protein biology: ESMC, ESMFold2, & ESM Atlas -[ESMC & ESMFold2 Preprint](https://www.biorxiv.org/content/10.64898/2026.06.03.729735) ⋅ [Atlas](https://biohub.ai/esm/protein/atlas) ⋅ [Tutorials](https://github.com/Biohub/esm/tree/main/cookbook/tutorials) ⋅ [Slack](https://join.slack.com/t/biohub-community/shared_invite/zt-3yxdy77dv-rJHI97SprbDXwEYP3Y1tqQ)
+[ESMC & ESMFold2 Preprint](https://www.biorxiv.org/content/10.64898/2026.06.03.729735) ⋅ [Atlas](https://biohub.ai/esm/protein/atlas) ⋅ [Tutorials](https://github.com/Biohub/esm/tree/main/cookbook/tutorials) ⋅ [Slack](https://bit.ly/esm-slack)
We are releasing a world model for protein biology: a scientific engine for prediction, design, and discovery. Built on the latest generation of Evolutionary Scale Modeling (ESM), this system learns from the protein sequences produced by evolution and uses that knowledge to represent, map, predict, and design proteins across scales — from atomic interactions to evolutionary relationships spanning billions of years. The system includes three artifacts: ESMC, ESMFold2, and ESM Atlas. diff --git a/cookbook/tutorials/binder_design.ipynb b/cookbook/tutorials/binder_design.ipynb index 8985cd5d..9e9ba699 100644 --- a/cookbook/tutorials/binder_design.ipynb +++ b/cookbook/tutorials/binder_design.ipynb @@ -222,7 +222,7 @@ "outputs": [], "source": [ "# ---- Monitor ----\n", - "# Tail a function's output here in jupyter\n", + "# Tail a function's output here in jupyter. Interrupt the kernel to stop the tail.\n", "! modal app logs esmfold2-design -f --function-call {future.object_id}" ] }, @@ -313,8 +313,9 @@ " target=targets,\n", " binder=binders,\n", " use_scaling_critics=[True],\n", - " seed=list(range(128)),\n", - " batch_size=[1],\n", + " seed=list(range(16)),\n", + " # NOTE - reduce if you want lower latency to get results.\n", + " batch_size=[6],\n", ")\n", "df = pd.DataFrame(product(*line_sweeps.values()), columns=line_sweeps.keys())\n", "df[\"target_name\"], df[\"target_sequence\"] = zip(*df[\"target\"], strict=True)\n", diff --git a/cookbook/tutorials/binder_design.py b/cookbook/tutorials/binder_design.py index b6e424f2..c6a540e9 100644 --- a/cookbook/tutorials/binder_design.py +++ b/cookbook/tutorials/binder_design.py @@ -22,6 +22,8 @@ from functools import cache from typing import Any +os.environ.setdefault("CUBLAS_WORKSPACE_CONFIG", ":4096:8") + import biotite.structure import modal import numpy as np @@ -31,7 +33,10 @@ from transformers.models.esmc.modeling_esmc import ESMCForMaskedLM from transformers.models.esmc.tokenization_esmc import ESMCTokenizer from transformers.models.esmfold2.modeling_esmfold2_common import ( + BACKEND_CUEQ, + BACKEND_FUSED, CUE_AVAILABLE, + TRITON_KERNELS_AVAILABLE, PairUpdateBlock, ) from transformers.models.esmfold2.modeling_esmfold2_common import ( @@ -88,7 +93,7 @@ BinderPromptStr = str # Design -LOSS_WEIGHTS = {"intra_contact": 0.5, "inter_contact": 0.5, "glob": 0.2} +LOSS_WEIGHTS = {"intra_contact": 0.5, "inter_contact": 0.5, "glob": 0.2, "epitope": 0.5} STEPS = 150 LOG_INTERVAL = 5 LEARNING_RATE = 0.1 @@ -338,8 +343,113 @@ def compute_globularity_loss( return F.elu(rg_term - rg_th) +def get_epitope_mask( + target_sequence: str, + binder_length: int, + target_hotspot_ids: list[str], + batch_size: int, + device: torch.device, +) -> torch.Tensor: + """Full-complex mask for sequence-local target hotspot residue IDs.""" + target_length = len(target_sequence) + epitope_mask = torch.zeros( + target_length + binder_length, dtype=torch.bool, device=device + ) + for hotspot_id in target_hotspot_ids: + assert len(hotspot_id) >= 2, ( + f"Hotspot residue ID {hotspot_id!r} must have form , " + "for example L150." + ) + residue = hotspot_id[0] + hotspot_idx_text = hotspot_id[1:] + assert residue in PROTEIN_1TO3, ( + f"Hotspot residue ID {hotspot_id!r} starts with unknown residue " + f"{residue!r}." + ) + assert hotspot_idx_text.isdecimal(), ( + f"Hotspot residue ID {hotspot_id!r} must have form , " + "for example L150." + ) + hotspot_idx = int(hotspot_idx_text) + assert 1 <= hotspot_idx <= target_length, ( + f"Hotspot residue {hotspot_id!r} is out of range for target sequence " + f"length {target_length}. Hotspots are 1-indexed within the provided " + "target sequence." + ) + target_residue = target_sequence[hotspot_idx - 1] + assert target_residue == residue, ( + f"Hotspot residue ID {hotspot_id!r} does not match target sequence: " + f"target residue at 1-indexed position {hotspot_idx} is " + f"{target_residue!r}." + ) + epitope_mask[hotspot_idx - 1] = True + return epitope_mask[None].repeat(batch_size, 1) + + +def compute_epitope_loss( + distogram_logits: torch.Tensor, + epitope_mask: torch.Tensor, + target_length: int, + binder_length: int, + bin_distance: torch.Tensor, + epitope_contact_distance: float = 12.0, +) -> torch.Tensor: + """Encourage binder contacts to the requested target hotspot residues. + + Converts full-complex distogram logits into contact probabilities below + ``epitope_contact_distance``, then penalizes each hotspot by the best few + binder contacts. The target is assumed to occupy the first ``target_length`` + positions of the complex and the binder the final ``binder_length`` positions. + + Parameters + ---------- + distogram_logits + Full-complex distogram logits with shape ``(B, L, L, num_bins)``. + epitope_mask + Boolean full-complex mask with shape ``(B, L)`` and true values at + target hotspot positions. + target_length + Number of target residues at the start of the complex sequence. + binder_length + Number of binder residues at the end of the complex sequence. + bin_distance + Distance represented by each distogram bin. + epitope_contact_distance + Distance threshold used to define a hotspot-binder contact. + + Returns + ------- + torch.Tensor + Per-example epitope losses with shape ``(B,)``. + """ + + B, L, L2, _ = distogram_logits.shape + assert L == L2, (L, L2) + assert L == target_length + binder_length, (L, target_length, binder_length) + + binder_mask = torch.zeros((B, L), dtype=torch.bool, device=distogram_logits.device) + binder_mask[:, target_length:] = True + + positive_mask = bin_distance < epitope_contact_distance + assert binder_mask.shape == (B, L), f"Binder mask: {binder_mask.shape} != {B, L}" + assert epitope_mask.shape == (B, L), f"Epitope mask: {epitope_mask.shape} != {B, L}" + # NOTE - this could be made bidirectional. + epitope_binder_mask = epitope_mask[:, :, None] & binder_mask[:, None, :] + + probabilities = torch.softmax(distogram_logits, dim=-1) + positive_probs = torch.sum(probabilities * positive_mask, dim=-1) + contact_measure = -torch.log(positive_probs + 1e-8) + + epitope_loss = masked_min_k(contact_measure, mask=epitope_binder_mask, k=5) + return masked_average(epitope_loss, mask=epitope_mask) + + def compute_structure_losses( - distogram_logits: torch.Tensor, binder_length: int + distogram_logits: torch.Tensor, + binder_length: int, + target_sequence: str | None = None, + target_hotspot_ids: list[str] | None = None, + epitope_contact_distance: float = 12.0, ) -> dict[str, torch.Tensor]: """Compute structural losses and a weighted total.""" @@ -359,6 +469,34 @@ def compute_structure_losses( total = total + LOSS_WEIGHTS["intra_contact"] * losses["intra_contact_loss"] total = total + LOSS_WEIGHTS["inter_contact"] * losses["inter_contact_loss"] total = total + LOSS_WEIGHTS["glob"] * losses["glob_loss"] + if target_hotspot_ids is not None: + target_length = distogram_logits.shape[1] - binder_length + assert ( + target_sequence is not None + ), "target_sequence is required when target_hotspot_ids is provided." + assert ( + "|" not in target_sequence + ), "Epitope loss only supports one target chain." + assert len(target_sequence) == target_length, ( + f"Target sequence length {len(target_sequence)} does not match distogram " + f"target length {target_length}." + ) + epitope_mask = get_epitope_mask( + target_sequence=target_sequence, + binder_length=binder_length, + target_hotspot_ids=target_hotspot_ids, + batch_size=B, + device=distogram_logits.device, + ) + losses["epitope_loss"] = compute_epitope_loss( + distogram_logits=distogram_logits, + epitope_mask=epitope_mask, + target_length=target_length, + binder_length=binder_length, + bin_distance=bin_distance, + epitope_contact_distance=epitope_contact_distance, + ) + total = total + LOSS_WEIGHTS["epitope"] * losses["epitope_loss"] losses["total_loss"] = total return losses @@ -829,6 +967,7 @@ def design_binder( inversion_models: dict[str, ESMFold2ExperimentalModel], hf_critic_models: dict[str, ESMFold2ExperimentalModel], esmc_model: ESMCForMaskedLM, + scaling_critic_names: list[str] | None, target_name: str, target_sequence: str | None, binder_name: str, @@ -836,6 +975,8 @@ def design_binder( is_antibody: bool | None, seed: int, batch_size: int = 1, + target_hotspot_ids: list[str] | None = None, + epitope_contact_distance: float = 12.0, ) -> tuple[list[str], Trajectory, list[dict]]: """ Algorithm 11 Gradient-Guided Binder Sequence Optimization. @@ -847,6 +988,9 @@ def design_binder( Hero critics expose iPTM; scaling critics contribute distogram scores only. ``distogram_binding_confidence`` / ``cdr_distogram_binding_confidence`` come from the distogram in all cases. + + ``target_hotspot_ids`` enables epitope loss and is interpreted as 1-indexed + residue IDs within the provided target sequence, for example ``["L150"]``. """ # Setup device = "cuda" @@ -927,7 +1071,11 @@ def run_step( ) sequences: list[str] = fold_result["seq_list"] losses = compute_structure_losses( - fold_result["distogram_logits"], binder_length + fold_result["distogram_logits"], + binder_length, + target_sequence=target_sequence, + target_hotspot_ids=target_hotspot_ids, + epitope_contact_distance=epitope_contact_distance, ) structure_loss = losses["total_loss"] structure_grad = torch.autograd.grad(structure_loss.mean(), logits)[0] @@ -1002,44 +1150,64 @@ def run_step( target_length = len(target_sequence.replace("|", "")) final_total_loss = trajectory[global_step - 1]["total_loss"] assert isinstance(final_total_loss, torch.Tensor) - for batch_idx in range(batch_size): + + def score_critic( + critic_name: str, + critic_model: ESMFold2ExperimentalModel, + batch_idx: int, + is_scaling_critic: bool, + ) -> None: best_seq = best_sequences[batch_idx] binder_seq = best_seq.split("|")[-1] binder_design = sequence_to_one_hot(binder_seq)[..., 2:22] - for critic_name, critic_model in hf_critic_models.items(): - is_scaling_critic = "ESMFold2-Experimental-Fast-base" in critic_name - if is_scaling_critic: - critic_model.cuda() - final_fold = fold_and_get_distogram( - critic_model, - target_sequence, - target_one_hot, - binder_design, - num_loops=3, - num_sampling_steps=200, - calculate_confidence=True, - seed=seed, - ) - if is_scaling_critic: - critic_model.cpu() - pred_complex = build_complex(final_fold["inputs"], final_fold["output"]) - iptm_proxy_scores = compute_distogram_iptm_proxy( - final_fold["distogram_logits"], target_length, binder_seq, is_antibody - ) - iptm = final_fold["iptm"].item() if final_fold["iptm"] is not None else None - critic_results.append( - { - "is_antibody": is_antibody, - "critic_name": critic_name, - "batch_idx": batch_idx, - "designed_sequence": best_seq, - "complex": pred_complex, - "final_loss": final_total_loss[batch_idx].item(), - "iptm": iptm, - "logits": logits[batch_idx].detach().cpu(), - **iptm_proxy_scores, - } - ) + final_fold = fold_and_get_distogram( + critic_model, + target_sequence, + target_one_hot, + binder_design, + num_loops=3, + num_sampling_steps=1 if is_scaling_critic else 200, + calculate_confidence=not is_scaling_critic, + seed=seed, + ) + pred_complex = ( + None + if is_scaling_critic + else build_complex(final_fold["inputs"], final_fold["output"]) + ) + iptm_proxy_scores = compute_distogram_iptm_proxy( + final_fold["distogram_logits"], target_length, binder_seq, is_antibody + ) + iptm_tensor = final_fold.get("iptm") + iptm = iptm_tensor.item() if iptm_tensor is not None else None + critic_results.append( + { + "is_antibody": is_antibody, + "critic_name": critic_name, + "is_scaling_critic": is_scaling_critic, + "batch_idx": batch_idx, + "designed_sequence": best_seq, + "complex": pred_complex, + "final_loss": final_total_loss[batch_idx].item(), + "iptm": iptm, + "logits": logits[batch_idx].detach().cpu(), + **iptm_proxy_scores, + } + ) + del final_fold + + for critic_name, critic_model in hf_critic_models.items(): + for batch_idx in range(batch_size): + score_critic(critic_name, critic_model, batch_idx, is_scaling_critic=False) + + for critic_name in scaling_critic_names or []: + critic_model = _load_hf_model( + critic_name, lm_dropout=0.25, cache_esmc=False, device="cuda" + ) + for batch_idx in range(batch_size): + score_critic(critic_name, critic_model, batch_idx, is_scaling_critic=True) + del critic_model + torch.cuda.empty_cache() if not critic_results: for batch_idx in range(batch_size): @@ -1076,7 +1244,12 @@ def _load_hf_model( _ESMC_CACHE[esmc_id] = model._esmc model._esmc = _ESMC_CACHE[esmc_id] model.configure_lm_dropout(lm_dropout, force_lm_dropout_during_inference=True) - model.set_kernel_backend("cuequivariance" if CUE_AVAILABLE else None) + kernel_backend = None + if TRITON_KERNELS_AVAILABLE: + kernel_backend = BACKEND_FUSED + elif CUE_AVAILABLE: + kernel_backend = BACKEND_CUEQ + model.set_kernel_backend(kernel_backend) return model.to(device=device).eval().requires_grad_(False) @@ -1110,6 +1283,7 @@ class ESMFold2Design: scaling_critic_hf_paths: list[str] = [] def load(self, use_scaling_critics: bool): + self.scaling_critic_hf_paths = [] if use_scaling_critics: self.scaling_critic_hf_paths = [ f"ESMFold2-Experimental-Fast-base{size}-step{step}k" @@ -1132,10 +1306,6 @@ def load(self, use_scaling_critics: bool): self.hf_critic_models[name] = _load_hf_model( name, lm_dropout=0.25, cache_esmc=True, device="cuda" ) - for name in self.scaling_critic_hf_paths: - self.hf_critic_models[name] = _load_hf_model( - name, lm_dropout=0.25, cache_esmc=False, device="cpu" - ) self.esmc_model = ESMCForMaskedLM.from_pretrained( self.lm_name, torch_dtype=torch.float32 @@ -1161,11 +1331,14 @@ def design( is_antibody: bool | None = None, seed: int = 0, batch_size: int = 1, + target_hotspot_ids: list[str] | None = None, + epitope_contact_distance: float = 12.0, ) -> tuple[list[str], Trajectory, list[dict]]: return design_binder( self.inversion_models, self.hf_critic_models, self.esmc_model, + self.scaling_critic_hf_paths, target_name=target_name, target_sequence=target_sequence, binder_name=binder_name, @@ -1173,6 +1346,8 @@ def design( is_antibody=is_antibody, seed=seed, batch_size=batch_size, + target_hotspot_ids=target_hotspot_ids, + epitope_contact_distance=epitope_contact_distance, ) @@ -1251,9 +1426,12 @@ class ESMFold2DesignModal(ESMFold2Design): """ use_scaling_critics: bool = modal.parameter(default=True) + deterministic: bool = modal.parameter(default=False) @modal.enter() def load(self): + if self.deterministic: + torch.use_deterministic_algorithms(True, warn_only=True) return super().load(self.use_scaling_critics) @modal.method() @@ -1272,6 +1450,8 @@ def main( local: bool = False, seed: int = 0, batch_size: int = 1, + target_hotspot_ids: list[str] | None = None, + epitope_contact_distance: float = 12.0, ): if local: assert not use_scaling_critics, ( @@ -1295,6 +1475,8 @@ def main( is_antibody=is_antibody, seed=seed, batch_size=batch_size, + target_hotspot_ids=target_hotspot_ids, + epitope_contact_distance=epitope_contact_distance, ) avg_final_loss = sum(r["final_loss"] for r in results) / len(results) diff --git a/cookbook/tutorials/embed.ipynb b/cookbook/tutorials/embed.ipynb index 6f0693e7..940fee8d 100644 --- a/cookbook/tutorials/embed.ipynb +++ b/cookbook/tutorials/embed.ipynb @@ -308,9 +308,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Pixi (ESM)", + "display_name": "Python 3", "language": "python", - "name": "pixi-esm" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -322,7 +322,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.12" + "version": "3.12.13" } }, "nbformat": 4, diff --git a/cookbook/tutorials/esmc_sae_feature_interpretation.ipynb b/cookbook/tutorials/esmc_sae_feature_interpretation.ipynb index 5791f683..17f09a21 100644 --- a/cookbook/tutorials/esmc_sae_feature_interpretation.ipynb +++ b/cookbook/tutorials/esmc_sae_feature_interpretation.ipynb @@ -45,7 +45,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "cell-3", "metadata": { "id": "cell-3" @@ -60,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "4eb60dfb", "metadata": {}, "outputs": [], @@ -877,6 +877,388 @@ " Feature annotations are automatically generated from large-scale activation patterns and may be incomplete or incorrect. Use them as starting points for investigation, not definitive labels." ] }, + { + "cell_type": "markdown", + "id": "b6fe605e", + "metadata": {}, + "source": [ + "# Case Study: Protein Characterization with SAE Features, a Retrospective Analysis\n", + "\n", + "As an example, we're going to do a retrospective SAE analysis to try to predict the function of a protein characterized in 2026. We will investigate SSB-M5, a small bacterial protein identified from a hot spring metagenome in Vatnajokull National Park, Iceland. Structural and functional analysis confirmed that SSB-M5 is a single-stranded DNA binding protein *(Werbowy et al., Protein Science, 2026;35(4):e70538)*. For the sake of this example, we'll use ESMC's SAE features to explore SSB-M5 as if it were still uncharacterized, and see whether we can land on the relevant motifs described by the paper. \n", + "\n", + "Our approach can be summarized in two general steps:\n", + "\n", + "**Step 1: identify the top features.** We'll run the SSB-M5 sequence through ESMC's SAE model and pull out its top five features, ranked by normalized activation. Every protein activates dozens or hundreds of SAE features to varying degrees. Ranking by normalized activation surfaces the ones that are both strongly and *selectively* active for this specific sequence, rather than features that just light up on nearly everything. Each feature comes with an LLM-generated feature description.\n", + "\n", + "**Step 2: compare against the ESM Atlas.** The Atlas is a map of 6.8 billion sequences, pulled from public sequence databases and large-scale metagenomic surveys, that have been run through ESMC and had SAE features extracted. Each feature has been mapped to a biological concept by examining which proteins activate it and what those proteins have in common. SAE features capture a wide range of biological concepts, from broad patterns like aromatic residues to specific functional motifs like folate cofactor-binding pockets. Proteins in the Atlas are organized into clusters based on how similar their SAE feature vectors are to each other, not on sequence identity. Therefore, two proteins that fold and function similarly can end up in the same cluster even if their actual amino acid sequences look quite different. \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ded80df", + "metadata": {}, + "outputs": [], + "source": [ + "# Fetch the real structure and sequence (PDB 7R4W, chain A)\n", + "ssb_m5_sequence = fetch_pdb_sequence(\"7r4w\", \"A\")\n", + "\n", + "print(ssb_m5_sequence)" + ] + }, + { + "cell_type": "markdown", + "id": "e457d9bf", + "metadata": {}, + "source": [ + "## Let's use ESMC's SAE model to identify features\n", + "\n", + "First we identify the top-K features from SSB-M5 and rank them by normalized activation using the same principle the ESM Atlas uses to rank a protein's most diagnostic features. Each feature's activation is scaled by its own typical range across a large reference protein set, so features that fire strongly *and selectively* rank above features that just fire on nearly everything." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8f994bf0", + "metadata": {}, + "outputs": [], + "source": [ + "TOP_K_FEATURES = 10\n", + "\n", + "ssb_m5_sequence, ssb_m5_features = extract_sae_features(\n", + " ssb_m5_sequence, model, sae_model_name\n", + ")\n", + "\n", + "max_vals = ssb_m5_features.max(axis=0)\n", + "top_k_indices = np.argsort(-max_vals)[:TOP_K_FEATURES]\n", + "\n", + "ssb_m5_descriptions = {}\n", + "ssb_m5_labels = {}\n", + "for feature_id in top_k_indices:\n", + " info = get_feature_info(int(feature_id))\n", + " ssb_m5_descriptions[int(feature_id)] = info.get(\n", + " \"description\", f\"Unknown feature {feature_id}\"\n", + " )\n", + " ssb_m5_labels[int(feature_id)] = info.get(\"label\", \"\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a39d94d9", + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Markdown, display\n", + "\n", + "\n", + "def summarize_description(description: str, max_chars: int = 300) -> str:\n", + " \"\"\"Pull just the leading summary sentence out of a feature description\n", + " and shorten it for display in a table.\"\"\"\n", + " summary = description.split(\"Activation pattern:\")[0]\n", + " summary = summary.replace(\"Summary:\", \"\").strip()\n", + " if len(summary) > max_chars:\n", + " summary = summary[:max_chars].rsplit(\" \", 1)[0] + \"...\"\n", + " return summary\n", + "\n", + "\n", + "rows = [\"| Feature ID | Label | Summary |\", \"|---|---|---|\"]\n", + "for feature_id in top_k_indices:\n", + " label = ssb_m5_labels[int(feature_id)]\n", + " short_summary = summarize_description(ssb_m5_descriptions[int(feature_id)])\n", + " rows.append(f\"| {feature_id} | {label} | {short_summary} |\")\n", + "\n", + "display(Markdown(\"\\n\".join(rows)))" + ] + }, + { + "cell_type": "markdown", + "id": "e23dbf84", + "metadata": {}, + "source": [ + "**Note** Descriptions are shortened here for readability. If you're passing these descriptions to an agent, use the full, untruncated description (`ssb_m5_descriptions[feature_id]`) rather than this summary." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6460e798", + "metadata": {}, + "outputs": [], + "source": [ + "top_3_features = [9680, 3306, 5277]\n", + "\n", + "for feature_id in top_3_features:\n", + " label = ssb_m5_labels[int(feature_id)]\n", + " print(f\"Feature {feature_id}: {label}\")\n", + " view = visualize_feature_on_structure(\"7r4w\", \"A\", ssb_m5_features, int(feature_id))\n", + " view.show()" + ] + }, + { + "cell_type": "markdown", + "id": "3ebadc60", + "metadata": {}, + "source": [ + "## Next, we will do a feature similarity search against the ESM Atlas\n", + "\n", + "We will search the ESM Atlas for other proteins with similar SAE feature vectors to SSB-M5, then look at what's known about them. The query sequence doesn't need to already be indexed in the Atlas for this to work." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f520fc0d", + "metadata": {}, + "outputs": [], + "source": [ + "def get_atlas_similar_proteins(\n", + " sequence: str,\n", + " topk_results: int = 10,\n", + " topk_features: int = 5,\n", + " min_similarity: float | None = None,\n", + " cluster_pct_characterized_max: int | None = None,\n", + " include_cluster_info: bool = True,\n", + ") -> dict[str, Any]:\n", + " \"\"\"Search the ESM Atlas for proteins with similar SAE feature vectors.\n", + "\n", + " See: GET /esm/protein/api/v1alpha1/similarity-search\n", + " \"\"\"\n", + " url = \"https://biohub.ai/esm/protein/api/v1alpha1/similarity-search\"\n", + "\n", + " params: dict[str, Any] = {\n", + " \"sequence\": sequence,\n", + " \"topk_results\": topk_results,\n", + " \"topk_features\": topk_features,\n", + " \"include_cluster_info\": include_cluster_info,\n", + " }\n", + " if min_similarity is not None:\n", + " params[\"min_similarity\"] = min_similarity\n", + " if cluster_pct_characterized_max is not None:\n", + " params[\"cluster_pct_characterized_max\"] = cluster_pct_characterized_max\n", + "\n", + " response = requests.get(url, params=params)\n", + " response.raise_for_status()\n", + " return response.json()\n", + "\n", + "\n", + "atlas_hits = get_atlas_similar_proteins(ssb_m5_sequence, topk_results=10)\n", + "similar = atlas_hits[\"similar_proteins\"]\n", + "\n", + "top_hit = similar[0]\n", + "\n", + "rows = [\n", + " \"| Rank | Protein | Similarity | Cluster Size | Mean pLDDT |\",\n", + " \"|---|---|---|---|---|\",\n", + "]\n", + "for idx, hit in enumerate(similar, 1):\n", + " label = (\n", + " hit.get(\"protein_name\") or hit.get(\"protein_accession\") or hit[\"protein_hash\"]\n", + " )\n", + " plddt = hit.get(\"mean_plddt\")\n", + " plddt_str = f\"{plddt:.2f}\" if plddt is not None else \"n/a\"\n", + " cluster_size = hit.get(\"cluster_size\", \"n/a\")\n", + " rows.append(\n", + " f\"| {idx} | {label} | {hit['similarity_score']:.3f} | {cluster_size} | {plddt_str} |\"\n", + " )\n", + "\n", + "display(Markdown(\"\\n\".join(rows)))" + ] + }, + { + "cell_type": "markdown", + "id": "2ec2f2f8", + "metadata": {}, + "source": [ + "## Let's look at the metadata associated with similar clusters\n", + "\n", + "`cluster_size` tells us how many proteins share a cluster, but not what they actually are. The `/clusters/{protein_hash}` endpoint gives cluster-level metadata for a representative protein, including its top Pfam domains and its own defining SAE features." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52f5a538", + "metadata": {}, + "outputs": [], + "source": [ + "def get_cluster_members(protein_hash: str, topk_features: int = 5) -> dict[str, Any]:\n", + " \"\"\"Look up cluster metadata for a cluster representative protein.\n", + "\n", + " See: GET /esm/protein/api/v1alpha1/clusters/{protein_hash}\n", + " \"\"\"\n", + " url = f\"https://biohub.ai/esm/protein/api/v1alpha1/clusters/{protein_hash}\"\n", + " response = requests.get(url, params={\"topk_features\": topk_features})\n", + " response.raise_for_status()\n", + " return response.json()\n", + "\n", + "\n", + "top_hit_hash = similar[0][\"protein_hash\"]\n", + "cluster_info = get_cluster_members(top_hit_hash)\n", + "\n", + "print(f\"Cluster representative: {cluster_info['protein_name']}\")\n", + "print(\n", + " f\"Cluster size: {cluster_info['cluster_size']} members, \"\n", + " f\"{cluster_info['cluster_pct_characterized']}% characterized\\n\"\n", + ")\n", + "\n", + "print(\"Top Pfam domains in this cluster:\")\n", + "for pfam_id, info in cluster_info[\"cluster_top_pfam_domains\"].items():\n", + " print(\n", + " f\" {pfam_id}: {info['name']} ({info['count']}/{cluster_info['cluster_size']} members)\"\n", + " )\n", + "\n", + "print(f\"\\nTop phyla represented: {cluster_info['top_phyla']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e537edfb", + "metadata": {}, + "outputs": [], + "source": [ + "your_top_feature_ids = set(int(f) for f in top_k_indices)\n", + "\n", + "rows = [\"| Feature ID | Label | Also in SSB-M5's top 10? |\", \"|---|---|---|\"]\n", + "for feat in cluster_info[\"cluster_representative_features\"]:\n", + " in_common = \"yes\" if feat[\"feature_index\"] in your_top_feature_ids else \"no\"\n", + " rows.append(f\"| {feat['feature_index']} | {feat['label']} | {in_common} |\")\n", + "\n", + "display(Markdown(\"\\n\".join(rows)))" + ] + }, + { + "cell_type": "markdown", + "id": "3a262bb1", + "metadata": {}, + "source": [ + "So far we've shown that this cluster's defining features overlap with SSB-M5's own top features. Let's map those same shared features onto this protein's own predicted structure, the same way we mapped SSB-M5's features onto its structure earlier. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dffd27e2", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Top hit: {cluster_info['protein_name']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c1f25d5", + "metadata": {}, + "outputs": [], + "source": [ + "def get_protein_sequence(protein_hash: str) -> str:\n", + " url = f\"https://biohub.ai/esm/protein/api/v1alpha1/proteins/{protein_hash}\"\n", + " response = requests.get(url)\n", + " response.raise_for_status()\n", + " return response.json()[\"sequence\"]\n", + "\n", + "\n", + "rep_sequence = get_protein_sequence(top_hit_hash)\n", + "print(\"Top hit sequence:\")\n", + "print(rep_sequence)\n", + "\n", + "rep_sequence, rep_features = extract_sae_features(rep_sequence, model, sae_model_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ac4a2236", + "metadata": {}, + "outputs": [], + "source": [ + "def visualize_feature_side_by_side(\n", + " feature_id,\n", + " ssb_m5_pdb_id=\"7r4w\",\n", + " ssb_m5_chain=\"A\",\n", + " ssb_m5_features=ssb_m5_features,\n", + " rep_pdb_text=None,\n", + " rep_features=None,\n", + " width=900,\n", + " height=450,\n", + "):\n", + " # Fetch SSB-M5's real structure (same source used earlier)\n", + " pdb_url = f\"https://files.rcsb.org/download/{ssb_m5_pdb_id}.pdb\"\n", + " response = requests.get(pdb_url)\n", + " if response.status_code != 200:\n", + " raise ValueError(f\"Failed to fetch PDB {ssb_m5_pdb_id}\")\n", + " ssb_m5_pdb_text = response.text\n", + "\n", + " view = py3Dmol.view(width=width, height=height, viewergrid=(1, 2), linked=False)\n", + "\n", + " # --- Left panel: SSB-M5's own structure ---\n", + " view.addModel(ssb_m5_pdb_text, \"pdb\", viewer=(0, 0))\n", + " view.setStyle({}, {\"cartoon\": {\"hidden\": True}}, viewer=(0, 0))\n", + " view.setStyle({\"hetflag\": True}, {\"stick\": {\"hidden\": True}}, viewer=(0, 0))\n", + "\n", + " left_activations = ssb_m5_features[:, feature_id]\n", + " left_max = left_activations.max()\n", + " for i, act in enumerate(left_activations):\n", + " color = interpolate_color(act, 0, left_max)\n", + " view.setStyle(\n", + " {\"chain\": ssb_m5_chain, \"resi\": i + 1},\n", + " {\"cartoon\": {\"color\": color}},\n", + " viewer=(0, 0),\n", + " )\n", + " view.zoomTo({\"chain\": ssb_m5_chain}, viewer=(0, 0))\n", + "\n", + " # --- Right panel: atlas exemplar's predicted structure ---\n", + " view.addModel(rep_pdb_text, \"pdb\", viewer=(0, 1))\n", + " view.setStyle({\"cartoon\": {\"hidden\": True}}, viewer=(0, 1))\n", + "\n", + " right_activations = rep_features[:, feature_id]\n", + " right_max = right_activations.max()\n", + " for i, act in enumerate(right_activations):\n", + " color = interpolate_color(act, 0, right_max)\n", + " view.setStyle({\"resi\": i + 1}, {\"cartoon\": {\"color\": color}}, viewer=(0, 1))\n", + " view.zoomTo(viewer=(0, 1))\n", + "\n", + " return view\n", + "\n", + "\n", + "# Use the same 3 features shown on SSB-M5's own structure earlier\n", + "matching_feature_ids = [9680, 3306, 5277]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "722a47a9", + "metadata": {}, + "outputs": [], + "source": [ + "rep_protein_name = top_hit.get(\"protein_name\") or \"Atlas hit\"\n", + "\n", + "for feature_id in matching_feature_ids:\n", + " label = ssb_m5_labels[feature_id]\n", + " print(\n", + " f\"Feature {feature_id}: {label} (SSB-M5, left, vs. {rep_protein_name}, right)\"\n", + " )\n", + " view = visualize_feature_side_by_side(\n", + " feature_id=feature_id, rep_pdb_text=top_hit[\"pdb\"], rep_features=rep_features\n", + " )\n", + " view.show()" + ] + }, + { + "cell_type": "markdown", + "id": "15b07fb8", + "metadata": {}, + "source": [ + "## Takeaway\n", + "The SAE features of the SSB-M5 sequence point to a single-stranded DNA-binding protein. Searching the Atlas for proteins with similar features turns up other bacterial single-stranded DNA-binding proteins.\n", + "\n", + "SSB-M5 was characterized and published in 2026 *(Werbowy et al., [Protein Science, 2026;35(4):e70538](https://onlinelibrary.wiley.com/doi/abs/10.1002/pro.70538))*, confirming it as a bacterial single-stranded DNA-binding protein (SSB)." + ] + }, { "cell_type": "markdown", "id": "imDmtK39rQys", @@ -906,11 +1288,9 @@ "\n", "**Search for similar proteins**: Use feature vectors to find proteins with similar properties in large databases. Proteins with similar feature profiles often have related functions.\n", "\n", - "**Engineer new proteins**: Design sequences with specific feature combinations. For example, you could engineer a protein to have features associated with both thermostability and catalytic activity.\n", - "\n", "**Discover novel biology**: Find features that activate together in unexpected ways. Co-occurring features might reveal functional relationships that aren't obvious from sequence or structure alone.\n", "\n", - "**Understand model behavior**: SAE features help explain what ESM has learned about proteins. This can guide better prompting strategies for protein generation with ESM3.\n", + "**Deep dive into a particular protein**: Get mechanistic insight into a particular protein of interest. \n", "\n", "Check out the other tutorials [here](https://github.com/Biohub/esm/tree/main/cookbook/tutorials).\n" ] @@ -921,9 +1301,9 @@ "provenance": [] }, "kernelspec": { - "display_name": "Pixi (ESM)", + "display_name": "Python 3", "language": "python", - "name": "pixi-esm" + "name": "python3" }, "language_info": { "codemirror_mode": { diff --git a/cookbook/tutorials/esmfold2.ipynb b/cookbook/tutorials/esmfold2.ipynb index c1a06144..56f27f31 100644 --- a/cookbook/tutorials/esmfold2.ipynb +++ b/cookbook/tutorials/esmfold2.ipynb @@ -250,9 +250,7 @@ "source": [ "token = getpass(\"Biohub token: \")\n", "\n", - "client = esmfold2_client(\n", - " model=\"esmfold2-fast-2026-05\", url=\"https://biohub.ai\", token=token\n", - ")" + "client = esmfold2_client(model=\"esmfold2-2026-05\", url=\"https://biohub.ai\", token=token)" ] }, { @@ -2033,31 +2031,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "# **Example 4: Folding Complexes with Paired MSAs**\n", - "\n", - "For multi-chain complexes — antibody–antigen being the canonical case — ESMFold2 does more than concatenate per-chain MSAs: it **pairs MSA rows across chains by taxonomy**, so the model can read co-evolutionary signal *between* chains. This inter-chain pairing is a large part of what drives ESMFold2's antibody–antigen accuracy.\n", - "\n", - "You supply **one MSA per chain** (each its own a3m) and pass each chain as its own `ProteinInput`. Pairing is automatic:\n", - "\n", - "- Rows whose FASTA header carries a `key=` token are **paired** across chains that share the same id (one paired row per shared organism), placed at the top of the stacked MSA.\n", - "- Every remaining hit is kept as an **unpaired** row (stacked densely, gap-filled in the other chains' columns), so single-chain evolutionary signal is preserved too.\n", - "\n", - "So you get *both* paired and unpaired MSA from the same per-chain inputs — you don't build the block layout yourself, you just annotate taxonomy in the headers.\n", - "\n", - "> **Header requirement:** pairing keys off `key=` in each sequence's header (the query/first row needs no key). Bring per-chain a3m files annotated with `key=` — e.g. from a UniProt/ColabFold search, rewriting each header's `OX=` to `key=`. A chain whose hits have no `key=` still folds; its rows are simply treated as unpaired.\n", - "\n", - "An a3m header for a chain then looks like:\n", - "\n", - "```\n", - ">query\n", - "EVQLVESGGGLVQPGGSLRLSCAAS...\n", - ">UniRef100_A0A... key=9606 # human homolog → pairs with key=9606 rows in other chains\n", - "QVQLVQSGAEVKKPGAS...\n", - ">UniRef100_B1B... key=10090 # mouse homolog\n", - "...\n", - "```\n" - ] + "source": "# **Example 4: Folding a Protein–Protein Complex with MSAs**\n\nFor multi-chain complexes, an MSA can be the difference between a wrong and a correct interface. You supply **one MSA per chain** and pass each chain as its own `ProteinInput`, reusing the same `msa=` argument as Example 3.\n\nHere we fold the **vaccinia virus G3/L5 entry sub-complex** (PDB [7YTU](https://www.rcsb.org/structure/7YTU)), a two-chain viral complex. From single sequence the model misplaces the interface (ipTM ≈ 0.19, interface DockQ ≈ 0.11 vs the crystal structure); **with per-chain MSAs it recovers the assembly** (ipTM ≈ 0.85, interface DockQ ≈ 0.73). Example a3m files for both chains ship alongside this notebook (`g3l5_chainA.a3m`, `g3l5_chainB.a3m`).\n\n**Cross-chain pairing (optional).** A header may carry a `key=` token; rows that share the same `key` across chains are placed in the *same* MSA row, letting the model read co-evolution *between* chains (the `key` usually encodes the source organism). Rows with no `key` are used as ordinary per-chain rows — most of the gain here comes from per-chain depth, with pairing as an added signal. You annotate the headers rather than building the layout yourself:\n\n```\n>query\nGPYYPTNKLQAAVM...\n>UniRef100_A0A... key=10245 # same organism → paired with the key=10245 row in the other chain\n...\n```\n\nTo make your own MSAs: run a paired search (e.g. ColabFold/MMseqs2), then rewrite each hit's `OX=` tag to `key=`. A chain whose hits carry no `key` still folds — its rows are simply unpaired." }, { "cell_type": "code", @@ -2068,31 +2042,30 @@ "from esm.utils.msa import MSA\n", "from esm.utils.structure.input_builder import ProteinInput, StructurePredictionInput\n", "\n", - "# Example antibody–antigen complex: antibody heavy + light chains and the antigen.\n", - "heavy_seq = \"EVQLVESGGGLVQPGGSLRLSCAASGFNIKDTYIHWVRQAPGKGLEWVARIYPTNGYTRYADSVKGRFTISADTSKNTAYLQMNSLRAEDTAVYYCSRWGGDGFYAMDYWGQGTLVTVSS\"\n", - "light_seq = \"DIQMTQSPSSLSASVGDRVTITCRASQDVNTAVAWYQQKPGKAPKLLIYSASFLYSGVPSRFSGSRSGTDFTLTISSLQPEDFATYYCQQHYTTPPTFGQGTKVEIK\"\n", - "antigen_seq = \"MKQLEDKVEELLSKNYHLENEVARLKKLVGER\"\n", + "# Vaccinia virus G3/L5 entry sub-complex (PDB 7YTU), two chains.\n", + "chainA_seq = \"GPYYPTNKLQAAVMETDRENAIIRQRNDEIPTRTLDTAIFTDASTVASAQIHLYYNSNIGKIIMSLNGKKHTFNLYDDNDIRTLLPILLLSK\"\n", + "chainB_seq = (\n", + " \"GPNMFFMPKRKIPDPIDRLRRANLACEDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR\"\n", + ")\n", "\n", - "# One MSA per chain. Headers must carry `key=` for cross-chain pairing.\n", - "heavy_msa = MSA.from_a3m(path=\"antibody_heavy.a3m\", remove_insertions=True)\n", - "light_msa = MSA.from_a3m(path=\"antibody_light.a3m\", remove_insertions=True)\n", - "antigen_msa = MSA.from_a3m(path=\"antigen.a3m\", remove_insertions=True)\n", + "# One MSA per chain (headers carry key= for optional cross-chain pairing).\n", + "chainA_msa = MSA.from_a3m(path=\"g3l5_chainA.a3m\")\n", + "chainB_msa = MSA.from_a3m(path=\"g3l5_chainB.a3m\")\n", "\n", - "# Pass each chain as its own ProteinInput; pairing happens automatically.\n", "fold_result = client.fold_all_atom(\n", " StructurePredictionInput(\n", " sequences=[\n", - " ProteinInput(id=\"H\", sequence=heavy_seq, msa=heavy_msa),\n", - " ProteinInput(id=\"L\", sequence=light_seq, msa=light_msa),\n", - " ProteinInput(id=\"A\", sequence=antigen_seq, msa=antigen_msa),\n", + " ProteinInput(id=\"A\", sequence=chainA_seq, msa=chainA_msa),\n", + " ProteinInput(id=\"B\", sequence=chainB_seq, msa=chainB_msa),\n", " ]\n", - " )\n", + " ),\n", + " config=config,\n", ")\n", "\n", "print(f\"pTM: {fold_result.ptm:.3f}, ipTM: {fold_result.iptm:.3f}\")\n", "print(f\"Average pLDDT: {fold_result.plddt.mean().item():.1f}\")\n", "\n", - "with open(\"antibody_antigen_paired_msa.cif\", \"w\") as f:\n", + "with open(\"g3l5_complex.cif\", \"w\") as f:\n", " f.write(fold_result.complex.to_mmcif())" ] } diff --git a/cookbook/tutorials/g3l5_chainA.a3m b/cookbook/tutorials/g3l5_chainA.a3m new file mode 100644 index 00000000..6f312fa9 --- /dev/null +++ b/cookbook/tutorials/g3l5_chainA.a3m @@ -0,0 +1,200 @@ +>key=0 +GPYYPTNKLQAAVMETDRENAIIRQRNDEIPTRTLDTAIFTDASTVASAQIHLYYNSNIGKIIMSLNGKKHTFNLYDDNDIRTLLPILLLSK +>key=1 +--YYPTNKLQAAVMETDRENSIIRQRNDEIPTRTLDTAIFTDASTVASAQIHLYYNSNIGKIIMSLNGKKHTFNLYDDNDIRTLLPILLLSK +>key=2 +--YYPTNKLQAAVMETDRENAIIRQRNEEIPTRTLDTAIFTDASTVSSAQIHLYYNSNIGKIIMSLNGKKHTFNLYDDNDIRTLLPILLLSK +>key=3 +--YYPTNKLQAAVMETDRENAIIRQRNDEIPIRTLDTAIFTYASTVASAQIHLYYNSNIGKNIMSLNGKKHTFDLYDDNDIRTLLRILLLSK +>key=4 +--YYPTNKLQAAVMETDRENAIIRQRNDEIPTRTLDTAIFTDASTVASAQIHLYYNSNIGKIIMSLNGKKHTFNLYDDNDIRTLLPILLLSK +>key=5 +GPYYPTNKLQAAVMETDRENAIIRQRNDEIPTRTLDTAIFTDASTVASAQIHLYYNSNIGKIIMSLNGKKHTFNLYDDNDIRTLLPILLLSK +>key=6 +--YYPTNKLQAAVMETDRENDIFRKRNEEIPTRSLDAKIFTDASTVVSAQIHLYYNSNIGKIIMSYNGKKYTFNLYDDNDIRTLLPILLLSK +>key=7 +--FLPTNKMQLAVHEL-ADARAWRQRTDAQLDGISETVLFPRPDAPQSSAVIAGMDSRRARVTVAHGRKKSTFDLKRRGDVQTLLPILLLGK +>key=8 +--YYPTNKLLMSVKQKNEENNVIKNINNTFPT-SLNSIIFSKPESLSLTKIDTYYDSLSGTVTAISNNKKYVFNLYFDEDIRTLLPILLLSK +>key=9 +--YYfnkhPTNKLELSVDKLNRENKIIKQRDDAFP-AVLNTIVFTRPGAPVPTKVNTYYDSVTGVVTVVSNNKKRIFRLDFDEDVRTLLPILLLSK +>key=10 +---YPTNKLELSVDKLNRENKIIKQRDDAFPV-VLNTTVFTRPETPVPTKVHTYYDSATGVVTMLSNNKKRIFRLDFDDDVRTLLPILLLSK +>key=11 +--YYPTNKLQMSLKNLNTENIIIKQQNNNIPT-FLDTIIFTKPESAIPTRVNMYYDSALGIVTILENNKKRIFRLDFEEDVRTLLPILLLSK +>key=12 +--YYPTNKLQAAVMETDRENDIFRKRNEEIPTRSLDAAIFTDASTVVNAQIHLYYNSNIGKIIMSYNGRKYTFNLYDDNDIRTLLPILLLSK +>key=13 +--YYPTNKLLMSVNQKNEENNVIKNINNTFPT-SLNSMIFSKPESVALTKIDTYYDSLSGTVTVISNNKKYVFNLYFDEDIRTLLPILLLSK +>key=14 +--YKPTNKLHILIDKINLYNEKIKKQNDILP-KTMNSIIFSN-GNPVNENINLYYDSLLETVTLLYgNNKKYTFNLRHDEDIATLLPILLLSK +>key=15 +--YYPTNKLQIAVKNLNYEYQIYKQQDNDFPQK-LNTLFFLDKQKFVSEEVNAYYNSSIGIVTLLTKSKKLLFNINFSDDVSALLPILLLSK +>key=16 +----PTNKMQLAVREL-ADDRLYRQRLDAQLDAVSESVLFQTPDAPRASAVLVSMDSRNERVTVAHGRDKSVFDLKRRGDVQTLIPILLLSK +>key=17 +--YYPTNKLQIAVKNLNYEYQIYKQQDNEFPQK-LSTLFFLDKQKFVGEEVNAYYNSSIGIVTLLTKSKKILFNINFSDDVSALLPILLLSK +>key=18 +--YYPTNKLQAAVMETDRENDIFRKRNEEIPTRSLDAAIFTDASTVVSAQIHLYYNSNIGKIIMSYNGRKYTFNLYDDNDIRTLLPILLLSK +>key=19 +--YYPTNKLLMSVKQKNEENNVIKNINNAFPT-SLNSIIFSKPESLSLTKIDTYYDSLSGTVTAISNNKKYVFNLYFDEDIRTLLPILLLSK +>key=20 +--YFPTTKMQFSVRELTDEKLRRQAVDEQL-DGISESVIFAESDRPQSSAVLVTMDSRNSIVTVAYGREKALFNLKRRRDVQTLLPILLLSK +>key=21 +--YYPMNKMHMSLDRLRRATEARRAAAETVPERPRHAVVFPDPATPVPTTVRARLDPYSGDVTVSWPKNRRQFKLGEDEDVRTLLPILLLSK +>key=22 +--YYPTNKLQMSLKNLNTENIIIKQQNNNIPT-FLDTIIFTKPESAIPTRVNVYYDSALGIVTILENNKKRIFRLDFEEDVRTLLPILLLSK +>key=23 +--YYPTNKLQLSVTRLNLENKLLKKRDDVFP-ASLNTLIFLNNEKFIPSTVNTYYNSSSGTVTVVSDNKKRVFRLDFDEDVRTLLPILLLSK +>key=24 +--YYPTNKLQLSVTRLNLENQLLKKRDDVFPS-SLNTLIFLNNEKFIPSTVNTYYNSSSGTVTVVSDNKKRVFRLDFDEDVRTLLPILLLSK +>key=25 +----PMNKLQSSLKELNKEDILFKQFDNEIVIGN-TTTLFTDKDTITSSNLKAIFDSRNNKVTIITKLGKQIFNFNSSIDVRTLLPILLLSK +>key=26 +--YYPTNKLQAAVMETDRENAIIIQRNDEIPTRTLDTAIFTDASTVASAQIYLYYNSNIGKIIMSLNGKKHTFNLYDDNDIRTLLPILLLSK +>key=27 +--YYPTNKLQMAVKNLNYEYEVRKQQDTNFPQK-LNTIIFSDKEEFISDNVRAYYNASTGTVAVLTSSKKLLFNVNFDDDVSALLPILLLSK +>key=28 +--YKPTNKLQIITDKINSCNKKIKKQNDILP-KTINSVIFSN-GNPVNENINLYYDSLSENVTILYGiNKKYTFNLRHDEDIATLLPILLLSK +>key=29 +--YYPTNKLESSVREIDIENEIIKERNEEIPSRLLDAVIFTDSSTIDKSLIHLYYNSNMGNVIMSYKGSKYTFNLYDNNDIRTLLPILLLSK +>key=30 +--YYPTNKLQLSVVELEKENKRFKEIDNTFP-ASFNTILFSKGNTYTKSNVNTYYNSQLKTITVNVNSKKQIFHLDNEDDVYTLLPILLLSK +>key=31 +--YYPTNKLLMSVKQKNEENNVIKNINNTFPT-SLNSIIFSKPESLSLTKIDTYYDSLSGTVTAISNNKKYVFNLYFDEDIRTLLPILLLSK +>key=32 +----PTNKMQLAVREL-TDARLHRQRVDSQLDAVSEAVLFVRPDAPQASAVIASLDSRRSLVTVAHGRDKSVFDLKRRGDVQTLLPILLLSK +>key=33 +--FGSSNKMQLTIKNMNYWQNASQKIDNSLPS-ILDTVIFQDYNNPIYAKVNATYDSQNKKITIKFGKDTKTFNLDSSSDRQTVFPILLLSK +>key=34 +--YYPTNKLQAAVRETDRENDIIKQRNDEIPSRLLNATIFTDESTVDKVQIHLYYNSNIGKVIMSYNGSKYTFNLYDNNDIRTLLPILLLSK +>key=35 +--YYPTNKLQAAVMETDRENAIIRQRNEEIPTRTLDTAIFTDASTVSSAKIHLYYNSNIGKIMMSLNGKKYTFNLYNDNDIRTLLPILLLSK +>key=36 +--YYPTNKLQLSIVELERENKQFKEIDNTFP-ASFNTILFSKGNEYTNSNINTYYNSQLKTITVNVNSKKQIFYLDNEDDVHTLLPILLLSK +>key=37 +--YYPTNKLQAAVMETDRENDIIRQRNEEIPTRTLDAAIFTDASTVSSAKIHLYYNSNIGKIMMSLNGKKYTFNLYNDNDIRTLLPILLLSK +>key=38 +----PTNKRALVLHEHQAQLArelAARERERQRP---LLTTLFLAQDDYVTDTVFVTASADARTVTVERRGQRYVYHTDQERERRMLLPLLLLSK +>key=39 +--YKPTNKLQLLVNKINNENNIIKERNEKVP-KFIDTSIFLSSDKPTETKANLYYDSASGIFTLLSDSKKYIFRLDTDEDVRSLLPILLLSK +>key=-1 +--YYPTNKLQAAVMETDRENAIIRQRNDEIPTRTLDTAIFTDASTVASAQIHLYYNSNIGKIIMSLNGKKHTFNLYDDNDIRTLLPILLLSK +>key=-1 +--YYPTNKLQAAVMETDRENAIIRQRNEEIPTRTLDTAIFTDSSTVSSAKIHLYYNSNIGKIMMSLNGKKYTFNLYNDNDIRTLLPILLLSK +>key=-1 +--YYPTNKLQAAVMETDRENDIFRKRNEEIPTRSLDAKIFTDASTVVSAQIHLYYNSNIGKIIMSYNGKKYTFNLYDDNDIRTLLPILLLSK +>key=-1 +--YYPTNKLQAAVRETDRENDIIKQRNDEIPSRLLNATIFTDESTVDKAQIHLYYNSNIGKVIMSYNGSKYTFNLYDNNDIRTLLPILLLSK +>key=-1 +--YYPTNKLQAAVMETDRENAIIRQRNDEIPIRTLDTAIFTYASTVASAQIHLYYNSNIGKNIMSLNGKKHTFDLYDDNDIRTLLRILLLSK +>key=-1 +----PMNKLQSSLKELNKEDILFKQFDNEIVIGN-TTTLFTDKDTITSSNLKAIFDSRNNKVTIITKLGKQIFNFNSSIDVRTLLPILLLSK +>key=-1 +----PVNKLDLALAAARARHSASAASESD---RYLSTTLFVAPGMVSRGTMRGRYSEEKRTFTVRYRGRDYVYHTDRESDMQKLLPLLLLSK +>key=-1 +--YYPTNKLESSVREIDIENEIIKERNEEIPSRLLDAVIFTDSSTIDKSLIHLYYNSNMGNVIMSYKGSKYTFNLYDNNDIRTLLPILLLSK +>key=-1 +---YPTNKLQLAINEINKIHSDQNNIDKSLP-NTLNTVIFQDFDKALYTIANVYVDSKKNQVTIKYSKTKKIFNIDSYIDMQTLLPILLLSK +>key=-1 +--FGSSNKMQLTIKNMNYWQNASQKIDNSLPS-ILDTVIFQDYNNPIYAKVNATYDSQNKKITIKFGKDTKTFNLDSSSDRQTVFPILLLSK +>key=-1 +---YPTNKLELSVDKLNRENKIIKQRDDAFPV-VLNTTVFTRPETPVPTKVHTYYDSATGVVTMLSNNKKRIFRLDFDDDVRTLLPILLLSK +>key=-1 +--YYPTNKLQMSLKNLNTENIIIKQQNNNIPT-FLDTIIFTKPESAIPTRVNMYYDSALGIVTILENNKKRIFRLDFEEDVRTLLPILLLSK +>key=-1 +--YYPTNKLLMSVKQKNEENNVIKNINNTFPT-SLNSMIFSKPESVALTKIDTYYDSLSGTVTVISNNKKYVFNLYFDEDIRTLLPILLLSK +>key=-1 +--YYPTNKLQLSVVELEKENKRFKEIDNTFP-ASFNTILFSKGNTYTKSNVNTYYNSQLKTITVNVNSKKQIFHLDNEDDVYTLLPILLLSK +>key=-1 +--YYfnkhPTNKLELSVDKLNRENKIIKQRDDAFP-AVLNTIVFTRPGAPVPTKVNTYYDSVTGVVTVVSNNKKRIFRLDFDEDVRTLLPILLLSK +>key=-1 +--YYPTNKLQLSVTRLNLENQLLKKRDDVFPT-SLNTLIFLNNEKFIPSTVNTYYNSSSGTVTVVSDNKKRVFRLDFDEDVRTLLPILLLSK +>key=-1 +--YYPTNKLQMAVKNLNYEYEVRKQQDTNFPQK-LNTIIFSDKEEFISDNVRAYYNASTGTVAVLTSSKKLLFNVNFDDDVSALLPILLLSK +>key=-1 +--YYPTNKLQLSIVELERENKQFKEIDNTFP-ASFNTILFSKGNEYTNSNINTYYNSQLKTITVNVNSKKQIFYLDNEDDVHTLLPILLLSK +>key=-1 +--YYPTNKLQIAVKNLNYEYQIYKQQDNDFPQK-LNTLFFLDKQKFVSEEVNAYYNSSIGIVTLLTKSKKLLFNINFSDDVSALLPILLLSK +>key=-1 +----PTNKRALVLHEHQAQLArelAARERERQRP---LLTTLFLAQDDYVTDTVFVTASADARTVTVERRGQRYVYHTDQERERRMLLPLLLLSK +>key=-1 +--YKPTNKLQLLVNKINNENNIIKERNEKVP-KFIDTSIFLSSDKPTETKANLYYDSASGIFTLLSDSKKYIFRLDTDEDVRSLLPILLLSK +>key=-1 +----PTNKHALVLRDAQARAAVQ-ARADEAP-RARRGTLLLARGDYVSDTLWLRYDARARAVRVARRESRWTLRADDERERRMLLPILLLSK +>key=-1 +--YKPTNKLHILIDKINLYNEKIKKQNDILP-KTMNSIIFSN-GNPVNENINLYYDSLLETVTLLYgNNKKYTFNLRHDEDIATLLPILLLSK +>key=-1 +--YYPMNKMHMSLDRLRRATEARRAAAETVPERPRHAVVFPDPATPVPTTVRARLDPYSGDVTVSWPKNRRQFKLGEDEDVRTLLPILLLSK +>key=-1 +--YKPTNKLQIITDKINSCNKKIKKQNDILP-KTINSVIFSN-GNPVNENINLYYDSLSENVTILYGiNKKYTFNLRHDEDIATLLPILLLSK +>key=-1 +--YFPTTKMQFSVRELTDEKLRRQAVDEQL-DGISESVIFAESDRPQSSAVLVTMDSRNSIVTVAYGREKALFNLKRRRDVQTLLPILLLSK +>key=-1 +--FLPTNKMQLAVHEL-ADARAWRQRTDAQLDGISETVLFPRPDAPQSSAVIAGMDSRRARVTVAHGRKKSTFDLKRRGDVQTLLPILLLGK +>key=-1 +--FLPTNKMQLAVREL-ADARAWRQRTDAQLDGISETVLFPRPDAPQGSAVIASIDARRARVTVAHGREKSTFDLKRRGDVQTLLPILLLSK +>key=-1 +----PTNKMQLAVREL-ADDRLYRQRLDAQLDAVSESVLFQTPDAPRASAVLVSMDSRNERVTVAHGRDKSVFDLKRRGDVQTLIPILLLSK +>key=-1 +----PTNKMQLAVREL-TDARLHRQRVDSQLDAVSEAVLFVRPDAPQASAVIASLDSRRSLVTVAHGRDKSVFDLKRRGDVQTLLPILLLSK +>key=-1 +-----TNKMEIGINPIKK----IPWSDNEHV---FVSSLFTNKDKYLTGPMRLTYKPDSKTAVLNFKGTNYTYHLDNFDDVRKLLPTLLLSK +>key=-1 +--------------------------------------LFTNKDKYLTGPMRLTYKPDSKTTVLNFKGTNYTYHLDNFDDVRKLLPTLLLSK +>key=-1 +--------------------------------------IFQSRNNYTKVPMKLNYYPDKTTITLEYNNNKHSYNISSFTDIRKLIPILLLSK +>key=-1 +-------------------------------------TLFHNNNKYVTGPMKLNYDPEKKGVVIEFKNTKYTYDLTDFSDARKLIPTLLLSK +>key=-1 +-------------------------------------TLFQDKNTYVTGPMKLKYDPEKKSAVIDFKNTTYNYDINNFKDVRKLIPTLLLSK +>key=-1 +-----TNKMEIGINPIKK----IPWSDNE---HIFVSSLFTNKDKYLTGPMRLTYRPDSKTAVLDFKGTNYTYYLDNFDDVRKLVPTLLLSK +>key=-1 +-----TNKMDIGINPIKK----IPWSDKD---HIFVSTLFPKNNQYVTGPMRLTYDPVKKSVVIDFKNNKYVYDISNFTDARKLIPTLLLSK +>key=-1 +--------------------------------------LFQADNKYVTGPMRLKYDPIDKKAVITFRGTNYSYRLEELDDTRKLIPILLLAK +>key=-1 +GPYYPTNKLQAAVMETDRENAIIRQRNDEIPTRTLDTAIFTDASTVASAQIHLYYNSNIGKIIMSLNGKKHTFNLYDDNDIRTLLPILLLSK +>key=-1 +--YYPTNKLQAAVMETDRENDIFRKRNEEIPTRSLDAAIFTDASTVVSAQIHLYYNSNIGKIIMSYNGRKYTFNLYDDNDIRTLLPILLLSK +>key=-1 +----PTNKRALVLHEHQAQLARelaARERERQRP---LLTTLFLAQDDYVTDTVFVTASADARTVTVERRGQRYVYHTDQERERRMLLPLLLLSK +>key=-1 +--YYPTNKLQLSVVELEKENKRFKEIDNTFPA-SFNTILFSKGNTYTKSNVNTYYNSQLKTITVNVNSKKQIFHLDNEDDVYTLLPILLLSK +>key=-1 +-----TNKMEMGISPIHK----VPWDDNE---HVFVSYIFQSRNNYTKVPMKLNYYPDKTTITLEYNNNKHSYNISSFTDIRKLIPILLLSK +>key=-1 +-----TNKMEMGISPIHK----VPWDDNE---HVFVSYIFQSRNNYTKVPMKLNYYPDKTTITLEYNNNKHSYNISSFTDIRKLIPILLLSK +>key=-1 +--YYfnkhPTNKLELSVDKLNRENKIIKQRDDAFPS-VLNTTVFTRPATPVPTKVNTYYDSATGVVTVVSNNKKRIFRLDFDEDVRTLLPILLLSK +>key=-1 +--FLPTNKMQLAVRELADAHAWRQRTDAQLDGIS-ESVLFPRPDAPQSSAVIAGMDSRRARVTVAHGREKSTFDLKRRGDVQTLLPILLLGK +>key=-1 +--YYPTNKLLMSVKQKNEENNVIKNINNTFPT-SLNSIIFSKPESLSLTKIDTYYDSLSGTVTAISNNKKYVFNLYFDEDIRTLLPILLLSK +>key=-1 +--FLPTNKMQLAVRELTDARAWRQRTDAQLDGLS-ESVLFPRPDAPQGSAVIAELDSRRARVTVAHGREKSTFDLKRRGDVQTLLPILLLGK +>key=-1 +--YFPTTKMQFSVRELTDEKLRRQAVDEQLDGIS-ESVIFAESDRPQSSAVLVTMDSRNSIVTVAYGREKALFNLKRRRDVQTLLPILLLSK +>key=-1 +---LPTNKMQLAVRELTDARLHRQRVDSQLDAVS-EAVLFVRPDAPQASAVIASLDSRRSLVTVAHGRDKSVFDLKRRGDVQTLLPILLLSK +>key=-1 +---LPTNKMQLAVRELADDRLYRQRLDAQLDAVS-ESVLFQTPDAPRASAVLVSMDSRNERVTVAHGRDKSVFDLKRRGDVQTLIPILLLSK +>key=-1 +-----TNKMEIGINPIKK----IPWSDNE---HIFVSSLFTNKDKYLTGPMRLTYRPDSKTAVLDFKGTNYTYYLDNFDDVRKLVPTLLLSK +>key=-1 +-----TNKMDIGINPIKK----TPWSGND---HLFVSTLFHNNNKYVTGPMKLNYDPEKKGVVIEFKNTKYTYDLTDFADARKLIPTLLLSK +>key=-1 +--YYPTNKLQMAVKNLNYEYEVRKQQDTNFPQK-LNTIIFSDKEEFISDNVRAYYNASTGTVAVLTSSKKLLFNVNFDDDVSALLPILLLSK +>key=-1 +-----TNKMDIGINPITK----IPWTGND---HIFVSTLFQDKNTYVTGPMKLKYDPEKKSAVIDFKNTTYNYDINNFKDVRKLIPTLLLSK +>key=-1 +--YYPTNKLQIAVKNLNYEYQIYKQQDNDFPQK-LNTLFFLDKQKFVSEEVNAYYNSSIGIVTLLTKSKKLLFNINFSDDVSALLPILLLSK +>key=-1 +--YKPTNKLHILIDKINLYNEKIKKQNDILP-KTMNSIIFSN-GNPVNENINLYYDSLLETVTLLYgNNKKYTFNLRHDEDIATLLPILLLSK +>key=-1 +----PTNKRALVLRDAQARAAVQARADEAPRARR--GTLLLARGDYVSDTLWLRYDARARAVRVARRERRWTLRADDERERRMLLPILLLSK +>key=-1 +--YYPMNKMHMSLDRLRRATEARRAAAETVPERPRHAVVFPDPATPVPTTVRARLDPYSGDVTVSWPKNRRQFKLGEDEDVRTLLPILLLSK +>key=-1 +------NKMQLTIKNMNYWQNASQKIDNSLPS-ILDTVIFQDYNNPIYAKVNATYDSQNKKITIKFGKDTKTFNLDSSSDRQTVFPILLLSK diff --git a/cookbook/tutorials/g3l5_chainB.a3m b/cookbook/tutorials/g3l5_chainB.a3m new file mode 100644 index 00000000..9e971b2f --- /dev/null +++ b/cookbook/tutorials/g3l5_chainB.a3m @@ -0,0 +1,194 @@ +>key=0 +GPNMFFMPKRKIPDPIDRLRRANLACEDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR +>key=1 +--NMFFMPKRKIPDPIDRLRRANLACEDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR +>key=2 +--NMFFMPKRKIPDPIDRLRRANLACEDDKLMIYGLPWITTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR +>key=3 +--NMFFMPKRKIPDPIDRLRRANLSCEDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR +>key=4 +--NMFFMPKRKIPDPIDRLRRANLACEDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR +>key=5 +GPNMFFMPKRKIPDPIDRLRRANLACEDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR +>key=6 +--NMFFMPKRKIPNPIDRLRRATLSCEDNKLMIYGLPWITTQSSALSINSKPIVYKDCAKLLRSINGSQPVSVDDVLRR +>key=7 +-----FRRAPRAPSPLDAYLQASLVCEGDALLI-ELP--EGRVPALALDGRPVAFPGCESLLYRINGPRKVRLVDVL-- +>key=8 +---LFNFKQQKVISPIDKFSKTTLYCKENKLFISGLPNTMYSKEALSLNRQPITYKYCNDLLQSINGSQQVSINDILRK +>key=9 +-------------DPIKQFTSTSLVCKGNALFIKHMPNLTELKTALAINRKQIVHENCEALLQSINGSRKVSLNDILKR +>key=10 +-------------DPIKQFTSTSLTCKGNALFIKHMPNSTELKPALAINRKQIVHENCVALLQSINGSRKVSLNDILER +>key=11 +---MLISYKDDIPiNPIDNFSKSWLSCDGNKLVINNLPNTNGTKTALAINRQDITYKDCNFLLQSINGSSKISLNDVLRR +>key=12 +---MFFMPKREIPNPMDRLRRATLACEDNKLMIYGLPWMTTQSSALSINSKPIVYKDCTKLLRSINGSQPVSIDDVLSR +>key=13 +--NMLFnFKQRKVISPIDKFSKTTLYCKENKLFISGLPNTMYSKEALSLNRQPITYKYCNDLLQSINGSQQVSINDILRK +>key=14 +---MLFLKDDKNDTIFNTLMKTSLSCNGNNLVISGLPNKS-NVNALYIdNKKPIIYDKCNEVLMSINGSHKIHLNDILRK +>key=15 +-------------NPIDKFKDTFLFCKENKMMIGGLPN-NNVTEALTTDKIPIVVDNCEDILKSINGSHKVSLNDILRR +>key=16 +------RGRQRPPSPLDAYGEASLACDGDALMI-ELP--GGRLPALALDGRPVAFPGCDGVLRRINGTRKVRLVDVLGR +>key=17 +-------------NPIDKFKDTFLFCKENKMMIGGLPN-NNVTEALTTDKIPIVVDNCEDILKSINGSHKVSLNDILRR +>key=18 +--NMFFMPKRKIPNPMDRLRRATLACEDNKLMIYGLPWITTQSSALSINSKPIVYKDCAKLLRSINGSQPVSVNDVLSR +>key=19 +--NMLFnFKQRKVISPIDKFSKTTLYCKENKLFISGLPNTMYSKEALSLNRQPITYKYCNDLLQSINGSQQVSINDILRK +>key=20 +-----FARARKTPSPLEPYAQAALACTGNAL-VLELP--EGRVPALALNGQPVVFAGCDSVLRRINGPRKVHLVDVLGR +>key=21 +------------PDPLERFRKARLECaADGGLMIAGL--GARPRPALAGDGRPIRPADCAAVLRSVnNGPLAVSLDDVLR- +>key=22 +-------------NPIDNFSKSWLSCDGNKLVINNLPNTNGTKTALAINRQDITYKDCNFLLQSINGSSKISLNDVLRR +>key=23 +-----------VKDPLEKFLKTSLSCNGDKLIINKLPNSSSNVNALAINRTPITNKNCKALLQSINGSQQVSLNDILRR +>key=24 +-----------VKDPLEKFLKTSLSCNGDKLIINKLPNSSSNVNALAINRTPITNKNCKDLLQSINGSQQVSLNDILRR +>key=25 +----FFTPGMTVSNPLNVVKQSKLLCKNNDLYIK-LPN-YKIVSAISLNGQPISYNKCEELISSLnNGSQ---------- +>key=26 +--NMFFMHKRKIPDPIDRLRRANLACEDDKLMIYGLPWITTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR +>key=27 +---MSFDKVQSINNPIDKLKNTFLFCKENKMMIGGLPN-NITTEALSVNKLPIVIDNCEDILMSINGSHKVSLDDIFRR +>key=28 +---MLFFNDYINDSMFKTLMKTSLSCNGNNLVISGLPNKN-NVNALYIdNKKPIIYDKCNEVLMSINGSHKIYLNDILRK +>key=29 +----FLKYREFLPNPLDRLRAATLSCEGNNLNIHGVPILGTEKSAISLNSKPIVYKDCLKLLQTINGSRTISFNDVLRR +>key=30 +---MLLRPKKyKSENYINKLKSATLICDGNVLKITGLANAKIL-PALALNKEPISINECKELLYSINGSRQVSLNDVLRK +>key=31 +--NMLFnFKQRKVISPIDKFSKTTLYCKENKLFISGLPNTMYSKEALSLNRQPITYKYCDDLLQSINGSQQVSINDILRK +>key=32 +---------RRDPSPLDAYANAELGCDGDRLML-ALP--EGRVPALALDGRPVALADCAGLLRRINGPRAVRLVDVLGR +>key=33 +-----------PINELDKYKDTYLTCYGNKLYII---SDNKQLPALSLNFKQIEFEGCSTILRNINGSRTVRISDVLNR +>key=34 +----FFKSRDDIPNPVDKLRTATLVCEGNKLMINGLTRLGTERSALSLNSKPIVYKDCLKLLQTINGSRSISFNDVLRR +>key=35 +--NMFFMPKRKIPDPIDRLRRANLACEDDKLMIYGLPWMTTQTSALSINSKPIVYKDCVKLLRSINGSHPVSLDDVLR- +>key=36 +---MIFRlKKHETENYLNKVKSASLICDGKTLKITGLANAKIL-PALALNKEPIIINECKELLHSINGSRQVSLNDVLYR +>key=37 +---MFFMTKQKIPDPIDRLRRANLACNDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKLLRSINGSRSVSLDDVLRR +>key=38 +----FARPPPPPPSPLHALTAGlRLACRGDELLIHGLP--GGTAPALNRDGSPVRLRGCEALLRSVNG------------ +>key=39 +-------------DKLDKYKNAKLLCNGKNLYITGI--GKESVSALDINKNPIIYDDCLNLLKSINGSRKVSLDNILRK +>key=-1 +GPNMFFMPKRKIPDPIDRLRRANLACEDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR +>key=-1 +--NMFFMPKRKIPDPIDRLRRTNLACEDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR +>key=-1 +--NMFFMPKRKIPDPIDRLRCANLACEDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR +>key=-1 +--NMFFMHKRKIPDPIDRLRRANLACEDDKLMIYGLPWITTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR +>key=-1 +--NMFFMPKRKIPNPIDRLRRATLSCEDNKLMIYGLPWITTQSSALSINSKPIVYKDCAKLLRSINGSQPVSVDDVLRR +>key=-1 +---MFFMPKREIPNPMDRLRRATLACEDNKLMIYGLPWMTTQSSALSINSKPIVYKDCTKLLRSINGSQPVSIDDVLSR +>key=-1 +---MFFMTKQKIPDPIDRLRRANLACNDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKLLRSINGSRSVSLDDVLRR +>key=-1 +-------------DPIKQFTSTSLTCKGNALFIKHMPNSTELKPALAINRKQIVHENCAALLQSINGSRKVSLNDILER +>key=-1 +-------------DPIKQFTSTSLTCKGNALFIKHMPNSTELKPALAINRKQIVHENCVALLQSINGSRKVSLNDILER +>key=-1 +---LFNFKQQKVISPIDKFSKTTLYCKENKLFISGLPNTMYSKEALSLNRQPITYKYCNDLLQSINGSQQVSINDILRK +>key=-1 +-------------DPIKQFTSTSLVCKGNALFIKHMPNLTELKTALAINRKQIVHENCEALLQSINGSRKVSLNDILKR +>key=-1 +--NMLFnFKQRKVISLIDKFSKTTLYCKENKLFISGLPNTMYSKEALSLNRQPITYKYCDDLLQSINGSQQVSINDILRK +>key=-1 +---MFFMPKRKIPDPMDRLLRANPACEDDKFMFYELPWMTTQTSALSINSKPIVY------------------------ +>key=-1 +----FFKSRDDIPNPVDKLRTATLVCEGNKLMINGLTRLGTERSALSLNSKPIVYKDCLKLLQTINGSRSISFNDVLRR +>key=-1 +----FLKYREFLPNPLDRLRAATLSCEGNNLNIHGVPILGTEKSAISLNSKPIVYKDCLKLLQTINGSRTISFNDVLRR +>key=-1 +---MLISYKDDIPiNPIDNFSKSWLSCDGNKLVINNLPNTNGTKTALAINRQDITYKDCNFLLQSINGSSKISLNDVLRR +>key=-1 +-----------VKDPLEKFLKTSLSCNGDKLIINKLPNSSSNVNALAINRTPITNKNCKALLQSINGSQQVSLNDILRR +>key=-1 +-------------------------CKENKLFISGLPNTMYSKEALSLNRQPITYKYCNDLLQSINGSQQLSIN----- +>key=-1 +---MSFDKVQSINNPIDKLKNTFLFCKENKMMIGGLPN-NITTEALSVNKLPIVIDNCEDILMSINGSHKVSLDDIFRR +>key=-1 +-------------NPIDKFKDTFLFCKENKMMIGGLPN-NNVTEALTTDKIPIVVDNCEDILKSINGSHKVSLNDILRR +>key=-1 +-------------NPIDKFKDTFLFCKENKMMIGGLHN-NNVTEALTTDKIPIVVDNCEDILKSINGSHKVSLNDILRR +>key=-1 +---MLLRPKKyKSENYINKLKSATLICDGNVLKITGLANAKIL-PALALNKEPISINECKELLYSINGSRQVSLNDVLRK +>key=-1 +---MLFLKDDKNDTIFNTLMKTSLSCNGNNLVISGLPNKS-NVNALYIdNKKPIIYDKCNEVLMSINGSHKIHLNDILRK +>key=-1 +---MLFFNDYINDSMFKTLMKTSLSCNGNNLVISGLPNKN-NVNALYIdNKKPIIYDKCNEVLMSINGSHKIYLNDILRK +>key=-1 +---MIFRlKKHETENYLNKVKSASLICDGKTLKITGLANAKIL-PALALNKEPIIINECKELLHSINGSRQVSLNDVLYR +>key=-1 +-----FRRAPRAPSPLDAYLQASLVCEGDALML-ELP--EGRLPALALDGRPIAFPGCAGLLHRINGPRKVRLVDVL-- +>key=-1 +-----FRRAPRAPSPLDAYLQASLVCEGDALLI-ELP--EGRVPALALDGRPVAFPGCESLLYRINGPRKVRLVDVL-- +>key=-1 +------RGRQRPPSPLDAYGEASLACDGDALMI-ELP--GGRLPALALDGRPVAFPGCDGVLRRINGTRKVRLVDVLGR +>key=-1 +------RGRRRVPAPLDAYGEASLACDGDALMI-ELP--GGRLPALALDGRPVAFPGCDGVLRRINGTRKVRLVDVLGR +>key=-1 +-----FRRAPRAPSPLDAYLQASLVCDGDALLI-ELP--EGRVPALALDGRPVAFPGCESLLYRINGPRKVRLVDVL-- +>key=-1 +-----FARARKTPSPLEPYAQAALACTGNAL-VLELP--EGRVPALALNGQPVVFAGCDSVLRRINGPRKVHLVDVLGR +>key=-1 +-------------DKLDKYKNAKLLCNGKNLYITGI--GKESVSALDINKNPIIYDDCLNLLKSINGSRKVSLDNILRK +>key=-1 +-----------PINELDKYKDTYLTCYGNKLYII---SDNKQLPALSLNFKQIEFEGCSTILRNINGSRTVRISDVLNR +>key=-1 +---------RRDPSPLDAYANAELGCDGDRLML-ALP--EGRVPALALDGRPVALADCAGLLRRINGPRAVRLVDVLGR +>key=-1 +------------PDPLERFRKARLECaADGGLMIAGL--GARPRPALAGDGRPIRPADCAAVLRSVnNGPLAVSLDDVLR- +>key=-1 +----FARPPPPPPSPLHALTAGlRLACRGDELLIHGLP--GGTAPALNRDGSPVRLRGCEALLRSVNG------------ +>key=-1 +----FFTPGMTVSNPLNVVKQSKLLCKNNDLYIK-LPN-YKIVSAISLNGQPISYNKCEELISSLnNGSQ---------- +>key=-1 +-------------NPLIKYKDNYLTCFKNKLYVVD--NGAY-VPALSLNGKHIVTNNCENILHNLNESQPVRVFDVFSR +>key=-1 +-----FIPVPVPKNAItNLLNKTTLSCEENGTMMIH--RESGKYPALNLDGSPIIIDDCSLVLSSLNG------------ +>key=-1 +-----FFPLAKPPkNSINgLLDRTMLKCEEDGSLMISRPSGIY--SALSLDGSPIRIPDCGLLLSSING------------ +>key=-1 +-----FLPLQKPPkNSINSLLdKTFLKCEENGSLMIVRPSGTY--PALNLEGNPIQISDCSLLLSSLNG------------ +>key=-1 +-----FFPLAKPsKNSINSLLdRTMLKCEEDGSLMISRPSGIY--SALSLDGSPVRISDCSLLLSSING------------ +>key=-1 +-----FLPLQKtPKNSINSLLdKTFLKCEDDGSLMIVRPSGTY--PALNLDDTPVQFSDCSLLLSSLNG------------ +>key=-1 +-----FLPLRKPPkNSINSLLdKTFLKCEDNGTLMIVRPSGTFT--ALNLDGSPVQIPECSLLLSSLNG------------ +>key=-1 +GPNMFFMPKRKIPDPIDRLRRANLACEDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKLLRSINGSQPVSLNDVLRR +>key=-1 +---MLLRPKKyKSENYINKLKSATLICDGNVLKITGLANAKIL-PALALNKEPISINECKELLYSINGSRQVSLNDVLRK +>key=-1 +---MLLRPKKyKSENYINKLKSATLICDGNVLKITGLANAKIL-PALALNKEPISINECKELLYSINGSRQVSLNDVLRK +>key=-1 +----FFKSRDDIPNPVDKLRTATLVCEGNKLMINGLTRLGTERSALSLNSKPIVYKDCLKLLQTINGSRSISFNDVLRR +>key=-1 +--NMFFMHKRKIPDPIDRLRRANLACEDDKLMIYGLPWMTTQTSALSINSKPIVYKDCAKL------------------ +>key=-1 +---MLLNYKNDmVKDPLEKFLKTSLSCNGDKLIINKLPNSSSNVNALAINRTPITNKNCKDLLQSINGSQQVSLNDILRR +>key=-1 +-----FNKTKVVNNPIDKFKDTFLFCKENKMMIGGLPN-NNVTEALTTDKIPIVVDNCEDILKSINGSHKVSLNDILRR +>key=-1 +----LFRRAPRAPSPLDAYLQASLVCDGDALLIE-LPEGRV--PALALDGRPVAFPGCESLLYRINGPRKVRLVDVL-- +>key=-1 +----LFARARKTPSPLEPYAQAALACTGNALVLE-LPEGRV--PALALNGQPVVFAGCDSVLRRINGPRKVHLVDVLGR +>key=-1 +-----------PINELDKYKDTYLTCYGNKLYI---ISDNKQLPALSLNFKQIEFEGCSTILRNINGSRTVRISDVLNR +>key=-1 +------------PDPLERFRKARLECaADGGLMIAGL--GARPRPALAGDGRPIRPADCAAVLRSVnNGPLAVSLDDVLR- +>key=-1 +-----FFPLAKPPkNSINGLLDRTmLKCEEDgSLMIS---RPSGIYSALSLDGSPIRIPDCGLLLSSINGaSSSTSPYSVFNR +>key=-1 +-----FLPLQKTPkNSINSLLDKTfLKCEDDgSLMIV---RPSGTYPALNLDDTPVQFSDCSLLLSSLNG------------ diff --git a/esm/sdk/base_forge_client.py b/esm/sdk/base_forge_client.py index 94500e63..c7b3f4da 100644 --- a/esm/sdk/base_forge_client.py +++ b/esm/sdk/base_forge_client.py @@ -109,6 +109,7 @@ async def _async_post( params: dict[str, Any] = {}, headers: dict[str, str] = {}, return_bytes: bool = False, + timeout: int | None = None, ): try: request, headers = self.prepare_request( @@ -119,7 +120,7 @@ async def _async_post( json=request, params=params, headers=headers, - timeout=self.request_timeout, + timeout=timeout if timeout is not None else self.request_timeout, ) data = self.prepare_data(response, endpoint) return data @@ -139,6 +140,7 @@ def _post( params: dict[str, Any] = {}, headers: dict[str, str] = {}, return_bytes: bool = False, + timeout: int | None = None, ): try: request, headers = self.prepare_request( @@ -149,7 +151,7 @@ def _post( json=request, params=params, headers=headers, - timeout=self.request_timeout, + timeout=timeout if timeout is not None else self.request_timeout, ) data = self.prepare_data(response, endpoint) return data diff --git a/esm/sdk/forge.py b/esm/sdk/forge.py index dda3f10e..11c4739c 100644 --- a/esm/sdk/forge.py +++ b/esm/sdk/forge.py @@ -36,7 +36,7 @@ ESMFOLD2_FAST, ESMFOLD2_MAX_MSA_SEQS, ) -from esm.utils.misc import deserialize_tensors, maybe_list, maybe_tensor +from esm.utils.misc import deserialize_tensors, maybe_list, maybe_tensor, to_float32 from esm.utils.msa import MSA from esm.utils.structure.input_builder import ( ProteinInput, @@ -105,7 +105,7 @@ def __init__( url: str = "https://biohub.ai", model: str | None = None, token: str = "", - request_timeout: int | None = None, + request_timeout: int | None = 120, min_retry_wait: int = 1, max_retry_wait: int = 10, max_retry_attempts: int = 5, @@ -117,7 +117,7 @@ def __init__( url: URL of the Forge/Biohub Platform server. model: Name of the model to be used for folding / inv folding. token: API token. - request_timeout: Override the system default request timeout, in seconds. + request_timeout: Override the system default request timeout (120s), in seconds. """ super().__init__( model=model or "", @@ -500,7 +500,7 @@ def __init__( model: str, url: str = "https://biohub.ai", token: str = "", - request_timeout: int | None = None, + request_timeout: int | None = 120, min_retry_wait: int = 1, max_retry_wait: int = 10, max_retry_attempts: int = 5, @@ -1151,7 +1151,7 @@ def __init__( model: str, url: str = "https://biohub.ai", token: str = "", - request_timeout: int | None = None, + request_timeout: int | None = 60, min_retry_wait: int = 1, max_retry_wait: int = 10, max_retry_attempts: int = 5, @@ -1222,11 +1222,13 @@ def _b64_decode(obj): ) sae_outputs = cast(dict[str, torch.Tensor] | None, sae_outputs) output = LogitsOutput( - logits=ForwardTrackData(sequence=_maybe_logits(data, "sequence")), - embeddings=maybe_tensor(data["embeddings"]), - mean_embedding=data["mean_embedding"], - hidden_states=maybe_tensor(data["hidden_states"]), - mean_hidden_state=maybe_tensor(data["mean_hidden_state"]), + logits=ForwardTrackData( + sequence=to_float32(_maybe_logits(data, "sequence")) + ), + embeddings=to_float32(maybe_tensor(data["embeddings"])), + mean_embedding=to_float32(data["mean_embedding"]), + hidden_states=to_float32(maybe_tensor(data["hidden_states"])), + mean_hidden_state=to_float32(maybe_tensor(data["mean_hidden_state"])), sae_outputs=sae_outputs, ) return output diff --git a/esm/sdk/retry.py b/esm/sdk/retry.py index 302d6cf0..aa2fe287 100644 --- a/esm/sdk/retry.py +++ b/esm/sdk/retry.py @@ -7,7 +7,7 @@ retry_if_exception, retry_if_result, stop_after_attempt, - wait_incrementing, + wait_random_exponential, ) from esm.sdk.api import ESMProteinError @@ -20,11 +20,11 @@ def retry_if_specific_error(exception): We only retry on specific errors. """ return isinstance(exception, ESMProteinError) and exception.error_code in { - 429, - 500, - 502, - 504, - 500, + 429, # Too Many Requests + 500, # Internal Server Error + 502, # Bad Gateway + 503, # Service Unavailable + 504, # Gateway Timeout } @@ -56,8 +56,9 @@ async def async_wrapper(instance, *args, **kwargs): retry_error_callback=return_last_value, retry=retry_if_result(retry_if_specific_error) | retry_if_exception(retry_if_specific_error), - wait=wait_incrementing( - increment=1, start=instance.min_retry_wait, max=instance.max_retry_wait + # Full-jitter exponential backoff + wait=wait_random_exponential( + multiplier=instance.min_retry_wait, max=instance.max_retry_wait ), stop=stop_after_attempt(instance.max_retry_attempts), before_sleep=log_retry_attempt, @@ -73,8 +74,9 @@ def wrapper(instance, *args, **kwargs): retry_error_callback=return_last_value, retry=retry_if_result(retry_if_specific_error) | retry_if_exception(retry_if_specific_error), - wait=wait_incrementing( - increment=1, start=instance.min_retry_wait, max=instance.max_retry_wait + # Full-jitter exponential backoff + wait=wait_random_exponential( + multiplier=instance.min_retry_wait, max=instance.max_retry_wait ), stop=stop_after_attempt(instance.max_retry_attempts), before_sleep=log_retry_attempt, diff --git a/esm/utils/function/lsh.py b/esm/utils/function/lsh.py index fd2c7e9b..0c082e7f 100644 --- a/esm/utils/function/lsh.py +++ b/esm/utils/function/lsh.py @@ -65,7 +65,7 @@ def write_hyperplanes(self, filepath: PathLike): hyperplanes: dict[str, np.ndarray] = { str(i): table.hyperplanes for i, table in enumerate(self.tables) } - np.savez(filepath, **hyperplanes) + np.savez(filepath, **hyperplanes) # ty: ignore[invalid-argument-type] def __call__(self, array): tokens = np.stack([table(array) for table in self.tables], 1) diff --git a/esm/utils/misc.py b/esm/utils/misc.py index cc874ff5..24705ce0 100644 --- a/esm/utils/misc.py +++ b/esm/utils/misc.py @@ -364,6 +364,13 @@ def maybe_tensor(x, convert_none_to_nan: bool = False) -> torch.Tensor | None: return torch.tensor(x) +def to_float32(x): + """Upcast reduced-precision activations to float32""" + if isinstance(x, torch.Tensor) and x.is_floating_point(): + return x.to(torch.float32) + return x + + def maybe_list(x, convert_nan_to_none: bool = False) -> list | None: if x is None: return None diff --git a/esm/utils/structure/molecular_complex.py b/esm/utils/structure/molecular_complex.py index 2e856b33..6b3a0b25 100644 --- a/esm/utils/structure/molecular_complex.py +++ b/esm/utils/structure/molecular_complex.py @@ -697,6 +697,96 @@ def from_mmcif(cls, inp: str, id: str | None = None) -> "MolecularComplex": atom_hetero=atom_hetero, ) + @classmethod + def from_atomarray( + cls, atom_arr: bs.AtomArray, id: str = "complex" + ) -> "MolecularComplex": + """Build a MolecularComplex from a biotite AtomArray, keeping ALL atoms. + + This is the inverse of :meth:`to_mmcif` (which expands the flat + token/atom arrays into an ``AtomArray``): it groups the AtomArray's atoms + into tokens by ``(chain_id, res_id)`` in array order and rebuilds the flat + ``atom_positions`` / ``atom_elements`` / ``atom_names`` / ``atom_hetero`` + arrays plus the token-level ``token_to_atoms`` / ``chain_id`` / + ``sequence`` / ``plddt`` arrays. Unlike ``ProteinChain.from_atomarray`` + it retains every atom (e.g. the phosphate atoms of a phosphorylated + residue), not just the atom37 protein set. + + Args: + atom_arr: biotite AtomArray. Atoms of a residue are assumed contiguous + (as produced by ``evolutionaryscale.structure.utils.to_atom_array``). + id: identifier to assign to the complex. + + Returns: + MolecularComplex with flat atom arrays and token-based indexing. + """ + n_atoms = len(atom_arr) + coords = np.asarray(atom_arr.coord, dtype=np.float32) + chain_labels = np.asarray(atom_arr.chain_id, dtype=object) + res_ids = np.asarray(atom_arr.res_id) + res_names = np.asarray(atom_arr.res_name, dtype=object) + atom_names = np.asarray(atom_arr.atom_name, dtype=object) + elements = np.asarray(atom_arr.element, dtype=object) + + annotations = atom_arr.get_annotation_categories() + hetero = ( + np.asarray(atom_arr.hetero, dtype=bool) + if "hetero" in annotations + else np.zeros(n_atoms, dtype=bool) + ) + b_factor = ( + np.asarray(atom_arr.b_factor, dtype=np.float32) + if "b_factor" in annotations + else np.zeros(n_atoms, dtype=np.float32) + ) + + # Numeric chain ids assigned in order of first appearance so that + # metadata.chain_lookup round-trips through to_mmcif. + chain_label_to_numeric: dict[str, int] = {} + for lbl in chain_labels: + if lbl not in chain_label_to_numeric: + chain_label_to_numeric[lbl] = len(chain_label_to_numeric) + + sequence: list[str] = [] + token_to_atoms: list[list[int]] = [] + token_chain_ids: list[int] = [] + token_plddt: list[float] = [] + + def _close_token(start: int, end: int) -> None: + token_to_atoms.append([start, end]) + sequence.append(str(res_names[start])) + token_chain_ids.append(chain_label_to_numeric[chain_labels[start]]) + token_plddt.append(float(b_factor[start:end].mean()) / PLDDT_B_FACTOR_SCALE) + + token_start = 0 + prev_key: tuple[Any, int] | None = None + for i in range(n_atoms): + key = (chain_labels[i], int(res_ids[i])) + if i > 0 and key != prev_key: + _close_token(token_start, i) + token_start = i + prev_key = key + if n_atoms > 0: + _close_token(token_start, n_atoms) + + chain_lookup = {num: lbl for lbl, num in chain_label_to_numeric.items()} + metadata = MolecularComplexMetadata( + entity_lookup={}, chain_lookup=chain_lookup, assembly_composition=None + ) + + return cls( + id=id, + sequence=sequence, + atom_positions=coords, + atom_elements=elements, + token_to_atoms=np.array(token_to_atoms, dtype=np.int32).reshape(-1, 2), + chain_id=np.array(token_chain_ids, dtype=np.int64), + plddt=np.array(token_plddt, dtype=np.float32), + metadata=metadata, + atom_names=atom_names, + atom_hetero=hetero, + ) + def _get_entity_mapping( self, ) -> tuple[dict[str, list[str]], dict[str, int], dict[int, tuple[str, ...]]]: diff --git a/esm/utils/structure/protein_chain.py b/esm/utils/structure/protein_chain.py index 4e433bd4..a5023a01 100644 --- a/esm/utils/structure/protein_chain.py +++ b/esm/utils/structure/protein_chain.py @@ -574,6 +574,7 @@ def globularity(self) -> float: # https://www.mdpi.com/2073-4352/11/12/1539 # NOTE(@zeming): due to the approximation we make here, that atoms never overlap, you might get >1 globularity mask = self.atom37_mask.any(-1) + assert isinstance(mask, np.ndarray) points = self.atom37_positions[self.atom37_mask] sequence = [aa for aa, m in zip(self.sequence, mask) if m] A, _ = self._mvee(points, tol=1e-3) diff --git a/esm/utils/structure/protein_complex.py b/esm/utils/structure/protein_complex.py index eaea2911..78a42789 100644 --- a/esm/utils/structure/protein_complex.py +++ b/esm/utils/structure/protein_complex.py @@ -122,6 +122,18 @@ class ProteinComplexMetadata: assembly_composition: dict[str, list[str]] | None = None +def _dockq_f1(f1: float, interface_rms: float, ligand_rms: float) -> float: + """DockQ's DockQ_F1: the DockQ formula with F1 substituted for fnat. + + DockQ v2.1.2 dropped the DockQ_F1 output, so recompute it with the upstream + ``dockq_formula``. Imported lazily because DockQ is an optional dependency, + only needed when scoring. + """ + from DockQ.DockQ import dockq_formula + + return dockq_formula(f1, interface_rms, ligand_rms) + + @dataclass class DockQSingleScore: native_chains: tuple[str, str] @@ -893,16 +905,19 @@ def sanity_check_chain_ids(pc: ProteinComplex): interfaces.append(current_interface) def parse_dict(d: dict[str, Any]) -> DockQSingleScore: + interface_rms = float(d["iRMSD"]) + ligand_rms = float(d["LRMSD"]) + f1 = float(d["F1"]) return DockQSingleScore( native_chains=tuple(d["Native chains"]), DockQ=float(d["DockQ"]), - interface_rms=float(d["irms"]), - ligand_rms=float(d["Lrms"]), # Note the capitalization difference + interface_rms=interface_rms, + ligand_rms=ligand_rms, fnat=float(d["fnat"]), fnonnat=float(d["fnonnat"]), clashes=float(d["clashes"]), - F1=float(d["F1"]), - DockQ_F1=float(d["DockQ_F1"]), + F1=f1, + DockQ_F1=_dockq_f1(f1, interface_rms, ligand_rms), ) inv_mapping = { diff --git a/esm/widgets/views/login.py b/esm/widgets/views/login.py index acdc91e0..aa3f3456 100644 --- a/esm/widgets/views/login.py +++ b/esm/widgets/views/login.py @@ -52,7 +52,7 @@ def create_login_ui(client_container: ClientInitContainer): # If not logged in, show login form forge_info = widgets.HTML( - value="

Copy a token from your Forge console page and paste it below:

" + value="

Copy a token from your Biohub developer console and paste it below:

" ) forge_token_input = widgets.Text( description="Token:", @@ -96,7 +96,7 @@ def create_login_ui(client_container: ClientInitContainer): ) forge_model_selection_info = widgets.HTML( - value="

Enter the model name from the Forge console page that you would like to use:

" + value="

Enter the model name from the Biohub developer console that you would like to use:

" ) model_selection_header = widgets.HTML(value="

Select a Model

") diff --git a/pixi.lock b/pixi.lock index baab98d9..36399393 100644 --- a/pixi.lock +++ b/pixi.lock @@ -74,7 +74,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.5-py312he3d6523_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nh3-0.2.21-py39h77e2912_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py312heda63a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.5-py312h33ff503_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.3-h55fea9a_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.1-py312hf79963d_0.conda @@ -102,6 +102,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/backports.tarfile-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.1.31-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.9-py312hd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.21.2-pyhd8ed1ab_1.conda @@ -147,6 +148,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - pypi: . - pypi: git+https://github.com/Biohub/transformers.git?rev=main#3a8956fb4d4ea16b0ec8e71deef2c2909b6a5cbf + - pypi: git+https://github.com/nrontsis/DockQ.git?rev=ba4df5adaad7c77fd60851d0b7b05f2b77061ba2#ba4df5adaad7c77fd60851d0b7b05f2b77061ba2 - pypi: https://files.pythonhosted.org/packages/00/78/9cbcc1c073b9d4918e925af1a059762265dc65004e020511b2a06fbfd020/pydssp-0.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/e9/367e81e114deb92a6e0d5740f0bff4548af710be318af65265b9aad72237/botocore-1.40.9-py3-none-any.whl @@ -183,6 +185,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/cd/c607b6337ddcf55cc0249527819b727ef28481d7c56ace54cca69400c2b9/biotraj-1.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/81/b6/100d5811cb60bfc13626910cb48872b8ca53f1dcaf2127aac71185575a7f/parallelbar-2.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/87/62/9773de14fe6c45c23649e98b83231fffd7b9892b6cf863251dc2afa73643/einops-0.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl @@ -229,6 +232,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.11-py312hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.22-pyhd8ed1ab_0.conda @@ -324,7 +328,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.5-py312h05635fa_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nh3-0.3.0-py39h24c5d98_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-1.26.4-py312h8442bc7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.5-py312he281c53_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.3-h889cd5d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.1-py312h98f7732_0.conda @@ -347,6 +351,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - pypi: . - pypi: git+https://github.com/Biohub/transformers.git?rev=main#3a8956fb4d4ea16b0ec8e71deef2c2909b6a5cbf + - pypi: git+https://github.com/nrontsis/DockQ.git?rev=ba4df5adaad7c77fd60851d0b7b05f2b77061ba2#ba4df5adaad7c77fd60851d0b7b05f2b77061ba2 - pypi: https://files.pythonhosted.org/packages/00/78/9cbcc1c073b9d4918e925af1a059762265dc65004e020511b2a06fbfd020/pydssp-0.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/e9/367e81e114deb92a6e0d5740f0bff4548af710be318af65265b9aad72237/botocore-1.40.9-py3-none-any.whl @@ -383,6 +388,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/81/b6/100d5811cb60bfc13626910cb48872b8ca53f1dcaf2127aac71185575a7f/parallelbar-2.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/62/9773de14fe6c45c23649e98b83231fffd7b9892b6cf863251dc2afa73643/einops-0.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8c/c9/bb114c158540ee17907ec470d01980957fdaf87b4aa07914c24eba87b9c6/safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl @@ -507,7 +513,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-9.0.1-he0572af_6.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nh3-0.2.21-py39h77e2912_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py312heda63a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.5-py312h33ff503_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.3-h55fea9a_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.9-he970967_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda @@ -635,6 +641,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - pypi: . - pypi: git+https://github.com/Biohub/transformers.git?rev=main#3a8956fb4d4ea16b0ec8e71deef2c2909b6a5cbf + - pypi: git+https://github.com/nrontsis/DockQ.git?rev=ba4df5adaad7c77fd60851d0b7b05f2b77061ba2#ba4df5adaad7c77fd60851d0b7b05f2b77061ba2 - pypi: https://files.pythonhosted.org/packages/00/78/9cbcc1c073b9d4918e925af1a059762265dc65004e020511b2a06fbfd020/pydssp-0.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/e9/367e81e114deb92a6e0d5740f0bff4548af710be318af65265b9aad72237/botocore-1.40.9-py3-none-any.whl @@ -671,6 +678,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/cd/c607b6337ddcf55cc0249527819b727ef28481d7c56ace54cca69400c2b9/biotraj-1.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/81/b6/100d5811cb60bfc13626910cb48872b8ca53f1dcaf2127aac71185575a7f/parallelbar-2.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/87/62/9773de14fe6c45c23649e98b83231fffd7b9892b6cf863251dc2afa73643/einops-0.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl @@ -834,7 +842,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.5-py312h05635fa_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nh3-0.3.0-py39h24c5d98_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-1.26.4-py312h8442bc7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.5-py312he281c53_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.3-h889cd5d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.1-py312h98f7732_0.conda @@ -861,6 +869,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - pypi: . - pypi: git+https://github.com/Biohub/transformers.git?rev=main#3a8956fb4d4ea16b0ec8e71deef2c2909b6a5cbf + - pypi: git+https://github.com/nrontsis/DockQ.git?rev=ba4df5adaad7c77fd60851d0b7b05f2b77061ba2#ba4df5adaad7c77fd60851d0b7b05f2b77061ba2 - pypi: https://files.pythonhosted.org/packages/00/78/9cbcc1c073b9d4918e925af1a059762265dc65004e020511b2a06fbfd020/pydssp-0.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/e9/367e81e114deb92a6e0d5740f0bff4548af710be318af65265b9aad72237/botocore-1.40.9-py3-none-any.whl @@ -897,6 +906,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/81/b6/100d5811cb60bfc13626910cb48872b8ca53f1dcaf2127aac71185575a7f/parallelbar-2.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/62/9773de14fe6c45c23649e98b83231fffd7b9892b6cf863251dc2afa73643/einops-0.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8c/c9/bb114c158540ee17907ec470d01980957fdaf87b4aa07914c24eba87b9c6/safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl @@ -2139,17 +2149,18 @@ packages: - pkg:pypi/nh3?source=hash-mapping size: 621078 timestamp: 1741652643562 -- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py312heda63a1_0.conda - sha256: fe3459c75cf84dcef6ef14efcc4adb0ade66038ddd27cadb894f34f4797687d8 - md5: d8285bea2a350f63fab23bf460221f3f +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.5-py312h33ff503_1.conda + sha256: 76ad6a6f4761084b074a587fe1512956891f04b5250cec0fd39aca0f39ad122b + md5: 03baecffb72fa96fe234fd505908065f depends: + - python + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 - libblas >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 - - libgcc-ng >=12 - liblapack >=3.9.0,<4.0a0 - - libstdcxx-ng >=12 - - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 + - libcblas >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause @@ -2158,9 +2169,9 @@ packages: - pkg:pypi/numpy?source=hash-mapping run_exports: weak: - - numpy >=1.26.4,<2.0a0 - size: 7484186 - timestamp: 1707225809722 + - numpy >=1.23,<3 + size: 8820597 + timestamp: 1766383409220 - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.3-h55fea9a_1.conda sha256: 0b7396dacf988f0b859798711b26b6bc9c6161dca21bacfd778473da58730afa md5: 01243c4aaf71bde0297966125aea4706 @@ -4856,16 +4867,17 @@ packages: - pkg:pypi/nh3?source=hash-mapping size: 624089 timestamp: 1752853325963 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-1.26.4-py312h8442bc7_0.conda - sha256: c8841d6d6f61fd70ca80682efbab6bdb8606dc77c68d8acabfbd7c222054f518 - md5: d83fc83d589e2625a3451c9a7e21047c +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.5-py312he281c53_1.conda + sha256: 0377c031951fc7ac3023f4b832c4a075e0e562015060e6f87bd751b45a1ef5ab + md5: 5a064b1a93c26d2960bbc49fa1de524b depends: + - python + - libcxx >=19 + - __osx >=11.0 + - python 3.12.* *_cpython + - liblapack >=3.9.0,<4.0a0 - libblas >=3.9.0,<4.0a0 - libcblas >=3.9.0,<4.0a0 - - libcxx >=16 - - liblapack >=3.9.0,<4.0a0 - - python >=3.12,<3.13.0a0 - - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 constrains: - numpy-base <0a0 @@ -4875,9 +4887,9 @@ packages: - pkg:pypi/numpy?source=hash-mapping run_exports: weak: - - numpy >=1.26.4,<2.0a0 - size: 6073136 - timestamp: 1707226249608 + - numpy >=1.23,<3 + size: 6706018 + timestamp: 1766383302517 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.3-h889cd5d_1.conda sha256: 6013916893fcd9bc97c479279cfe4616de7735ec566bad0ee41bc729e14d31b2 md5: ab581998c77c512d455a13befcddaac3 @@ -5278,6 +5290,7 @@ packages: - pygtrie - dna-features-viewer - accelerate + - dockq @ git+https://github.com/nrontsis/DockQ.git@ba4df5adaad7c77fd60851d0b7b05f2b77061ba2 requires_python: '>=3.12,<3.13' - pypi: git+https://github.com/Biohub/transformers.git?rev=main#3a8956fb4d4ea16b0ec8e71deef2c2909b6a5cbf name: transformers @@ -5741,6 +5754,15 @@ packages: - opentelemetry-exporter-otlp ; extra == 'open-telemetry' - opentelemetry-sdk ; extra == 'open-telemetry' requires_python: '>=3.9.0' +- pypi: git+https://github.com/nrontsis/DockQ.git?rev=ba4df5adaad7c77fd60851d0b7b05f2b77061ba2#ba4df5adaad7c77fd60851d0b7b05f2b77061ba2 + name: dockq + version: 2.1.3 + requires_dist: + - numpy>=2.0 + - biopython>=1.79 + - networkx + - parallelbar + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/00/78/9cbcc1c073b9d4918e925af1a059762265dc65004e020511b2a06fbfd020/pydssp-0.9.1-py3-none-any.whl name: pydssp version: 0.9.1 @@ -6838,6 +6860,14 @@ packages: - psutil ; extra == 'test' - netcdf4>=1.7.1 ; extra == 'test' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/81/b6/100d5811cb60bfc13626910cb48872b8ca53f1dcaf2127aac71185575a7f/parallelbar-2.5-py3-none-any.whl + name: parallelbar + version: '2.5' + sha256: 2aba5064e313cdd2fe49350db512ed0d4bc779f43e370e51c44d2bac347af43e + requires_dist: + - tqdm + - colorama + - dill ; extra == 'dill' - pypi: https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl name: nvidia-nvtx-cu12 version: 12.4.127 diff --git a/pyproject.toml b/pyproject.toml index e2c64e1c..c4d32c19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,10 @@ dependencies = [ "pygtrie", "dna_features_viewer", "accelerate", + # Fork adding numpy-2 support on top of upstream v2.1.2. That upstream release + # renamed the RMSD keys (iRMSD/LRMSD) and dropped the DockQ_F1 output, which + # protein_complex.py recomputes from F1. + "dockq @ git+https://github.com/nrontsis/DockQ.git@ba4df5adaad7c77fd60851d0b7b05f2b77061ba2", ] # Pytest [tool.pytest.ini_options] @@ -86,9 +90,7 @@ upload-wheel = "python -m twine upload --repository pypi" [tool.pixi.feature.dev.dependencies] matplotlib = "*" -# Pin the lint/test env to numpy<2: the numpy 2.x type stubs trip ty false positives -# (savez/zip) that don't reflect runtime behavior. Runtime deps stay numpy-unconstrained. -numpy = ">=1.26.0,<2.0.0" +numpy = ">=2.0.0,<2.4" pre-commit = "*" pytest = "*" pytest-cov = "*"