joduor's picture
Deploy AlphaFold Adaptive API with morphic simulations and ESM-2
17b2088 verified
Raw
History Blame Contribute Delete
3.41 kB
"""
HuggingFace model integration.
Uses ESM-2 (facebook/esm2_t6_8M_UR50D) for protein sequence embeddings
and structural property prediction. Lazy-loaded on first use.
"""
from __future__ import annotations
import os, time
import numpy as np
_esm_model = None
_esm_tokenizer = None
MODEL_ID = os.getenv("ESM_MODEL", "facebook/esm2_t6_8M_UR50D")
def _load_esm():
global _esm_model, _esm_tokenizer
if _esm_model is not None:
return
try:
from transformers import AutoTokenizer, AutoModel
import torch
_esm_tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
_esm_model = AutoModel.from_pretrained(MODEL_ID)
_esm_model.eval()
except Exception as e:
print(f"[models] ESM-2 load failed: {e}. Falling back to mock embeddings.")
def embed_sequence(sequence: str) -> list[float]:
"""
Return a 320-dim embedding for the protein sequence.
Uses ESM-2 if available, otherwise a deterministic hash-based mock.
"""
if not sequence:
return [0.0] * 320
_load_esm()
if _esm_model is not None:
try:
import torch
seq = sequence[:1022] # ESM-2 max length
inputs = _esm_tokenizer(seq, return_tensors="pt", truncation=True)
with torch.no_grad():
out = _esm_model(**inputs)
# Mean pool over residue dimension
emb = out.last_hidden_state[0, 1:-1].mean(dim=0).numpy().tolist()
return emb[:320] if len(emb) >= 320 else emb + [0.0] * (320 - len(emb))
except Exception as e:
print(f"[models] ESM-2 inference error: {e}")
# Deterministic fallback: hash-based mock embedding
import hashlib
h = hashlib.sha256(sequence.encode()).digest()
rng = np.random.default_rng(int.from_bytes(h[:8], "big"))
return rng.uniform(-1, 1, 320).tolist()
def predict_disorder(sequence: str) -> list[float]:
"""
Predict per-residue disorder probability (0=ordered, 1=disordered).
Simple heuristic using amino acid physicochemical properties.
"""
DISORDER_PRONE = set("GSEQKAPRT")
ORDER_PRONE = set("CFYWILVMH")
scores = []
for aa in sequence.upper():
if aa in DISORDER_PRONE:
scores.append(0.7)
elif aa in ORDER_PRONE:
scores.append(0.2)
else:
scores.append(0.45)
# Smooth with a sliding window
w = 9
padded = [scores[0]] * (w // 2) + scores + [scores[-1]] * (w // 2)
smoothed = [
sum(padded[i:i + w]) / w for i in range(len(scores))
]
return smoothed
def classify_secondary_structure_heuristic(sequence: str) -> dict:
"""
Fast heuristic secondary structure estimate.
Returns fraction of helix, sheet, coil residues.
"""
HELIX = set("AELMQKR")
SHEET = set("VIFYW")
total = len(sequence) or 1
h = sum(1 for aa in sequence.upper() if aa in HELIX) / total
s = sum(1 for aa in sequence.upper() if aa in SHEET) / total
return {
"helix_fraction": round(h, 3),
"sheet_fraction": round(s, 3),
"coil_fraction": round(max(0, 1 - h - s), 3),
}
def cosine_similarity(a: list[float], b: list[float]) -> float:
"""Cosine similarity between two embedding vectors."""
a, b = np.array(a), np.array(b)
norm = np.linalg.norm(a) * np.linalg.norm(b)
return float(np.dot(a, b) / norm) if norm > 0 else 0.0