import torch import torch.nn as nn import torch.nn.functional as F import pickle import numpy as np import re from transformers import T5Tokenizer, T5EncoderModel class BioCLIP(nn.Module): def __init__(self, seq_dim=1024, text_dim=768, shared_dim=512): super().__init__() self.seq_projector = nn.Sequential( nn.Linear(seq_dim, shared_dim), nn.GELU(), nn.Dropout(0.1), nn.Linear(shared_dim, shared_dim) ) self.text_projector = nn.Sequential( nn.Linear(text_dim, shared_dim), nn.GELU(), nn.Dropout(0.1), nn.Linear(shared_dim, shared_dim) ) self.temperature = nn.Parameter(torch.ones([]) * np.log(1 / 0.07)) def forward(self, seq_emb, text_emb): z_seq = F.normalize(self.seq_projector(seq_emb), p=2, dim=-1) z_text = F.normalize(self.text_projector(text_emb), p=2, dim=-1) return z_seq, z_text class ProteinEmbedder: def __init__(self, device="cpu"): print("Loading ProtT5 Language Model (this takes a moment)...") self.device = device self.tokenizer = T5Tokenizer.from_pretrained("Rostlab/prot_t5_xl_uniref50", do_lower_case=False) self.model = T5EncoderModel.from_pretrained("Rostlab/prot_t5_xl_uniref50").to(self.device) self.model.eval() print("ProtT5 Online!") def embed_raw_sequence(self, sequence: str): seq = re.sub(r"[UZOB]", "X", sequence.upper()) seq_spaced = " ".join(list(seq)) with torch.no_grad(): ids = self.tokenizer([seq_spaced], add_special_tokens=True, padding=True, return_tensors="pt") input_ids = ids['input_ids'].to(self.device) attention_mask = ids['attention_mask'].to(self.device) embedding = self.model(input_ids=input_ids, attention_mask=attention_mask) embedding = embedding.last_hidden_state seq_len = (attention_mask[0] == 1).sum() protein_emb = embedding[0, :seq_len-1].mean(dim=0) return protein_emb.cpu().numpy() class ProtocolRecommender: def __init__(self, device="cpu"): self.device = device self.steps = ['lysis', 'elution', 'desalting'] self.models = {} self.databases = {} print("Loading Bio-CLIP Expert Models...") for step in self.steps: model = BioCLIP().to(self.device) weights_path = f"bioclip_weights_{step}.pth" model.load_state_dict(torch.load(weights_path, map_location=self.device, weights_only=True)) model.eval() self.models[step] = model db_path = f"aligned_spaces_{step}.pkl" with open(db_path, "rb") as f: db = pickle.load(f) self.databases[step] = { "text_vectors": torch.tensor(db["aligned_text"]).float().to(self.device), "raw_texts": db["raw_texts"] } print("Ready! All systems online.\n") def search(self, protein_sequence_1024d, top_k=3): results = {} if not isinstance(protein_sequence_1024d, torch.Tensor): seq_tensor = torch.tensor(protein_sequence_1024d).float().unsqueeze(0).to(self.device) else: seq_tensor = protein_sequence_1024d.to(self.device) if seq_tensor.dim() == 1: seq_tensor = seq_tensor.unsqueeze(0) with torch.no_grad(): for step in self.steps: model = self.models[step] db = self.databases[step] z_query = model.seq_projector(seq_tensor) z_query = F.normalize(z_query, p=2, dim=-1) similarities = (z_query @ db["text_vectors"].T).squeeze() top_scores, top_indices = torch.topk(similarities, k=top_k) step_results = [] for score, idx in zip(top_scores.cpu().numpy(), top_indices.cpu().numpy()): step_results.append({ "confidence": score, "text": db["raw_texts"][idx] }) results[step] = step_results return results if __name__ == "__main__": device = torch.device("cuda" if torch.cuda.is_available() else "cpu") try: embedder = ProteinEmbedder(device=device) engine = ProtocolRecommender(device=device) print("\n" + "="*60) raw_amino_acids = "MSKGEELFTGVVPILVELDGDVNGHKFSVSGEGEGDATYGKLTLKFICTTGKLPVPWPTLVTTFSYGVQCFSRYPDHMKQHDFFKSAMPEGYVQERTIFFKDDGNYKTRAEVKFEGDTLVNRIELKGIDFKEDGNILGHKLEYNYNSHNVYIMADKQKNGIKVNFKIRHNIEDGSVQLADHYQQNTPIGDGPVLLPDNHYLSTQSALSKDPNEKRDHMVLLEFVTAAGITHGMDELYK" print(f"User inputted sequence: {raw_amino_acids[:30]}... (Length: {len(raw_amino_acids)})") print("="*60) print("1. Passing sequence through ProtT5...") new_protein_vector = embedder.embed_raw_sequence(raw_amino_acids) print("2. Querying Bio-CLIP databases...\n") recommendations = engine.search(new_protein_vector, top_k=3) print("="*60) print("BIO-CLIP RECOMMENDED PURIFICATION PIPELINE") print("="*60) for step in ['lysis', 'elution', 'desalting']: print(f"\n--- {step.upper()} EXPERT ---") for i, rec in enumerate(recommendations[step]): confidence_pct = max(0, min(100, (rec['confidence'] + 0.1) * 100)) print(f"Option {i+1} [Confidence: {confidence_pct:.1f}%]") print(f"Protocol: {rec['text']}\n") except FileNotFoundError as e: print(f"\nERROR: Could not find model files. Make sure you run this in the same folder as your .pth and .pkl files!") print(f"Details: {e}")