PyTorch
protein
stability
mutation-effects
StabilityArc / stabilityarc /inference.py
aaronfeller's picture
Publish minimal StabilityArc inference model
4691fb1 verified
Raw History Blame Contribute Delete
2.18 kB
"""Sequence-only single-substitution inference."""
from __future__ import annotations
from pathlib import Path
import torch
from stabilityarc.esmc import FrozenESMC
from stabilityarc.model import AMINO_ACIDS, load_stabilityarc
class StabilityArcPredictor:
def __init__(self, checkpoint: Path, device: str = "cpu", encoder: FrozenESMC | None = None) -> None:
self.device = device
self.model, _ = load_stabilityarc(checkpoint, device)
self.encoder = encoder
def predict(self, sequence: str) -> list[dict[str, str | int | float]]:
"""Return all non-synonymous single substitutions at 1-based positions."""
sequence = sequence.strip().upper()
if not sequence or any(residue not in AMINO_ACIDS for residue in sequence):
raise ValueError("Sequence must contain only the 20 standard amino-acid letters")
if self.encoder is None:
self.encoder = FrozenESMC(device=self.device)
embedding = self.encoder.embed_sequence(sequence)
if embedding.shape != (len(sequence), self.model.backbone.input_proj.in_features):
raise ValueError("ESMC embedding shape does not match the input sequence and decoder")
with torch.inference_mode():
_, scores = self.model.backbone(embedding.to(self.device).unsqueeze(0))
scores = scores.squeeze(0).cpu().tolist()
return [
{
"mutation": f"{wildtype}{position}{mutant}",
"position": position,
"wildtype": wildtype,
"mutant": mutant,
"stabilityarc_stability_score": float(scores[position - 1][index]),
"stabilityarc_predicted_ddG": -float(scores[position - 1][index]),
}
for position, wildtype in enumerate(sequence, 1)
for index, mutant in enumerate(AMINO_ACIDS)
if mutant != wildtype
]
def predict_sequence(
sequence: str,
checkpoint: Path,
device: str = "cpu",
encoder: FrozenESMC | None = None,
) -> list[dict[str, str | int | float]]:
return StabilityArcPredictor(checkpoint, device, encoder).predict(sequence)