Download CupOFMoCA-PDC/smiles/generation/guided.py from ChatterjeeLab/CupOFMoCA: direct link, hf CLI and curl.
- Browser
- Download file 29.5 kB
-
https://huggingface.co/ChatterjeeLab/CupOFMoCA/resolve/main/CupOFMoCA-PDC/smiles/generation/guided.py
- Command line
-
hf download hf://ChatterjeeLab/CupOFMoCA/CupOFMoCA-PDC/smiles/generation/guided.py
-
curl -L -o guided.py https://huggingface.co/ChatterjeeLab/CupOFMoCA/resolve/main/CupOFMoCA-PDC/smiles/generation/guided.py
29.5 kB
| import argparse | |
| import re | |
| import random | |
| from collections import Counter | |
| import csv | |
| import json | |
| import os | |
| from datetime import datetime | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from tqdm import tqdm | |
| from smiles.models.train import MDLMLightningModule | |
| from smiles.smiles_tokenizer.my_tokenizers import SMILES_SPE_Tokenizer | |
| import numpy as np | |
| from smiles.evaluation.peptide_analyzer import PeptideAnalyzer | |
| from smiles.evaluation.pdc_analyzer import PDCAnalyzer | |
| from smiles.scoring.classifiers import Conjugation, BindingAffinity | |
| class GenerationTracker: | |
| """ | |
| Tracks detailed validity information during generation | |
| """ | |
| def __init__(self, output_dir='./generation_logs'): | |
| self.output_dir = output_dir | |
| os.makedirs(output_dir, exist_ok=True) | |
| self.run_id = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| self.step_logs = [] | |
| self.final_results = [] | |
| def log_step(self, step, scores, detailed_results=None, improved_indices=None): | |
| """Log information for a generation step""" | |
| step_info = { | |
| 'step': step, | |
| 'scores': scores.cpu().numpy().tolist() if isinstance(scores, torch.Tensor) else scores, | |
| 'num_valid': int(sum(1 for s in scores if s > 0)) if hasattr(scores, '__iter__') else 0, | |
| 'improved_indices': improved_indices or [] | |
| } | |
| if detailed_results: | |
| step_info['detailed'] = [] | |
| for i, result in enumerate(detailed_results): | |
| step_info['detailed'].append({ | |
| 'sample_idx': i, | |
| 'is_valid_peptide': result.get('is_valid_peptide', False), | |
| 'has_conjugation_site': result.get('has_conjugation_site', False), | |
| 'conjugation_success': result.get('conjugation_success', False), | |
| 'pdc_valid': result.get('pdc_valid', False), | |
| 'num_conjugates': len(result.get('conjugates', [])), | |
| 'error': result.get('error') | |
| }) | |
| self.step_logs.append(step_info) | |
| def log_final(self, sequences, detailed_results_with_props): | |
| """Log final results with properties""" | |
| for i, (seq, result) in enumerate(zip(sequences, detailed_results_with_props)): | |
| final_info = { | |
| 'sample_idx': i, | |
| 'peptide_smiles': seq, | |
| 'is_valid_peptide': result.get('is_valid_peptide', False), | |
| 'has_conjugation_site': result.get('has_conjugation_site', False), | |
| 'num_conjugates': len(result.get('conjugates', [])), | |
| 'num_valid_pdcs': len(result.get('valid_pdcs', [])), | |
| 'best_pdc': None, | |
| 'properties': result.get('properties', {}) | |
| } | |
| if result.get('best_pdc'): | |
| final_info['best_pdc'] = { | |
| 'smiles': result['best_pdc'].get('smiles', ''), | |
| 'checks': result['best_pdc'].get('checks', {}) | |
| } | |
| self.final_results.append(final_info) | |
| def save(self, filename_prefix='generation'): | |
| """Save all logs to files""" | |
| # Save step logs | |
| step_log_path = os.path.join(self.output_dir, f"{filename_prefix}_{self.run_id}_steps.json") | |
| with open(step_log_path, 'w') as f: | |
| json.dump(self.step_logs, f, indent=2, default=str) | |
| # Save final results | |
| final_path = os.path.join(self.output_dir, f"{filename_prefix}_{self.run_id}_final.json") | |
| with open(final_path, 'w') as f: | |
| json.dump(self.final_results, f, indent=2, default=str) | |
| # Save summary CSV | |
| csv_path = os.path.join(self.output_dir, f"{filename_prefix}_{self.run_id}_summary.csv") | |
| with open(csv_path, 'w', newline='') as f: | |
| writer = csv.writer(f) | |
| writer.writerow([ | |
| 'sample_idx', 'peptide_smiles', 'is_valid_peptide', 'has_conjugation_site', | |
| 'num_conjugates', 'num_valid_pdcs', 'best_pdc_smiles', | |
| 'mf', 'polararea', 'xlogp', 'heavycnt', 'rotbonds' | |
| ]) | |
| for result in self.final_results: | |
| props = result.get('properties', {}) | |
| writer.writerow([ | |
| result['sample_idx'], | |
| result['peptide_smiles'], | |
| result['is_valid_peptide'], | |
| result['has_conjugation_site'], | |
| result['num_conjugates'], | |
| result['num_valid_pdcs'], | |
| result['best_pdc']['smiles'] if result.get('best_pdc') else '', | |
| props.get('mf', ''), | |
| props.get('polararea', ''), | |
| props.get('xlogp', ''), | |
| props.get('heavycnt', ''), | |
| props.get('rotbonds', '') | |
| ]) | |
| print(f"\n=== Generation logs saved ===") | |
| print(f" Step logs: {step_log_path}") | |
| print(f" Final results: {final_path}") | |
| print(f" Summary CSV: {csv_path}") | |
| return step_log_path, final_path, csv_path | |
| def print_step_summary(self, step, scores, detailed_results=None): | |
| """Print a summary for the current step""" | |
| if isinstance(scores, torch.Tensor): | |
| scores_list = scores.cpu().numpy().tolist() | |
| else: | |
| scores_list = list(scores) | |
| num_valid = sum(1 for s in scores_list if s > 0) | |
| print(f"\n--- Step {step} Summary ---") | |
| print(f" Valid: {num_valid}/{len(scores_list)}") | |
| if detailed_results: | |
| valid_peptide = sum(1 for r in detailed_results if r.get('is_valid_peptide')) | |
| has_site = sum(1 for r in detailed_results if r.get('has_conjugation_site')) | |
| conj_success = sum(1 for r in detailed_results if r.get('conjugation_success')) | |
| pdc_valid = sum(1 for r in detailed_results if r.get('pdc_valid')) | |
| print(f" Breakdown:") | |
| print(f" Valid peptides: {valid_peptide}") | |
| print(f" Has conjugation site: {has_site}") | |
| print(f" Conjugation success: {conj_success}") | |
| print(f" PDC valid: {pdc_valid}") | |
| def print_final_summary(self): | |
| """Print final summary""" | |
| print("\n" + "=" * 60) | |
| print("FINAL GENERATION SUMMARY") | |
| print("=" * 60) | |
| total = len(self.final_results) | |
| valid_pdcs = sum(1 for r in self.final_results if r.get('num_valid_pdcs', 0) > 0) | |
| print(f"\nTotal sequences: {total}") | |
| print(f"With valid PDCs: {valid_pdcs}") | |
| print("\nDetailed results:") | |
| for result in self.final_results: | |
| status = "" if result.get('num_valid_pdcs', 0) > 0 else "" | |
| print(f"\n [{status}] Sample {result['sample_idx']}:") | |
| print(f" Peptide: {result['peptide_smiles'][:60]}...") | |
| print(f" Valid peptide: {result['is_valid_peptide']}") | |
| print(f" Has conjugation site: {result['has_conjugation_site']}") | |
| print(f" Conjugates: {result['num_conjugates']}") | |
| print(f" Valid PDCs: {result['num_valid_pdcs']}") | |
| if result.get('properties'): | |
| props = result['properties'] | |
| print(f" Properties:") | |
| for k, v in props.items(): | |
| print(f" {k}: {v}") | |
| def peptide_bond_mask(smiles_list): | |
| # Initialize the batch mask | |
| batch_size = len(smiles_list) | |
| max_seq_length = 1035 #max(len(smiles) for smiles in smiles_list) # Find the longest SMILES | |
| mask = torch.zeros((batch_size, max_seq_length), dtype=torch.int) # Mask filled with zeros | |
| bond_patterns = [ | |
| (r'OC\(=O\)', 'ester'), | |
| (r'N\(C\)C\(=O\)', 'n_methyl'), | |
| (r'N[12]C\(=O\)', 'peptide'), # Pro peptide bonds | |
| (r'NC\(=O\)', 'peptide'), # Regular peptide bonds | |
| (r'C\(=O\)N\(C\)', 'n_methyl'), | |
| (r'C\(=O\)N[12]?', 'peptide') | |
| ] | |
| for batch_idx, smiles in enumerate(smiles_list): | |
| positions = [] | |
| used = set() | |
| # Identify bonds | |
| for pattern, bond_type in bond_patterns: | |
| for match in re.finditer(pattern, smiles): | |
| if not any(p in range(match.start(), match.end()) for p in used): | |
| positions.append({ | |
| 'start': match.start(), | |
| 'end': match.end(), | |
| 'type': bond_type, | |
| 'pattern': match.group() | |
| }) | |
| used.update(range(match.start(), match.end())) | |
| # Update the mask for the current SMILES | |
| for pos in positions: | |
| mask[batch_idx, pos['start']:pos['end']] = 1 | |
| return mask | |
| def peptide_token_mask(smiles_list, token_lists): | |
| # Initialize the batch mask | |
| batch_size = len(smiles_list) | |
| token_seq_length = max(len(tokens) for tokens in token_lists) # Find the longest tokenized sequence | |
| tokenized_masks = torch.zeros((batch_size, token_seq_length), dtype=torch.int) # Mask filled with zeros | |
| atomwise_masks = peptide_bond_mask(smiles_list) | |
| for batch_idx, atomwise_mask in enumerate(atomwise_masks): | |
| token_seq = token_lists[batch_idx] | |
| atom_idx = 0 | |
| for token_idx, token in enumerate(token_seq): | |
| if token_idx != 0 and token_idx != len(token_seq) - 1: | |
| if torch.sum(atomwise_mask[atom_idx:atom_idx+len(token)]) >= 1: | |
| tokenized_masks[batch_idx][token_idx] = 1 | |
| atom_idx += len(token) | |
| return tokenized_masks | |
| class MOGGenerator: | |
| def __init__(self, model, device, objectives, args, tracker=None): | |
| self.model = model | |
| self.device = device | |
| self.objectives = objectives | |
| self.args = args | |
| self.num_objectives = len(objectives) | |
| self.peptide_analyzer = PeptideAnalyzer() | |
| self.pdc_analyzer = PDCAnalyzer(device) | |
| self.invalid = [0, 1, 2, 3, 4, 585, 586] | |
| self.tracker = tracker | |
| # Find the Conjugation objective for detailed tracking | |
| self.conjugation_obj = None | |
| for obj in objectives: | |
| if isinstance(obj, Conjugation): | |
| self.conjugation_obj = obj | |
| break | |
| def _generate_single_x0(self, n_steps=16, temperature=1.0): | |
| """Generate a single batch of x0 sequences.""" | |
| self.model.eval() | |
| # 1. Start with a tensor of random tokens (pure noise at t=0) | |
| x = torch.randint( | |
| 0, | |
| self.model.tokenizer.vocab_size, | |
| (self.args.num_samples, self.args.gen_len), | |
| device=self.device | |
| ) | |
| # 2. Define the time schedule for the forward process (0.0 to 1.0) | |
| time_steps = torch.linspace(0.0, 1.0, n_steps + 1, device=self.device) | |
| # 3. Iteratively follow the flow from noise to data | |
| with torch.no_grad(): | |
| for i in tqdm(range(n_steps), desc="Flow Matching Steps", leave=False): | |
| t_curr = time_steps[i] | |
| t_next = time_steps[i+1] | |
| # Prepare the current timestep tensor for the model | |
| t_tensor = torch.full((self.args.num_samples,), t_curr, device=self.device) | |
| # Get the model's prediction for the final clean sequence (at t=1) | |
| logits = self.model(x, t_tensor) | |
| logits = logits / temperature | |
| logits[:, :, 586] = -1000 | |
| pred_x1 = torch.argmax(logits, dim=-1) | |
| # On the last step, the result is the final prediction | |
| if i == n_steps - 1: | |
| x = pred_x1 | |
| break | |
| # --- Construct the next state x_{t_next} --- | |
| # The probability of a token being noise at time t_next is (1 - t_next). | |
| noise_prob = 1.0 - t_next | |
| mask = torch.rand(x.shape, device=self.device) < noise_prob | |
| # Generate new random tokens for the noise positions | |
| noise = torch.randint( | |
| 0, | |
| self.model.tokenizer.vocab_size, | |
| x.shape, | |
| device=self.device | |
| ) | |
| # Combine the final prediction with noise to form the next intermediate state | |
| x = torch.where(mask, noise, pred_x1) | |
| return x | |
| def _check_conjugation_validity(self, x): | |
| """ | |
| Check if sequences can be conjugated. | |
| Returns a boolean tensor indicating validity for each sample. | |
| """ | |
| if self.conjugation_obj is None: | |
| # Fall back to peptide validity check | |
| sequences = self.model.tokenizer.batch_decode(x) | |
| return torch.tensor([self.peptide_analyzer.is_peptide(seq) for seq in sequences], device=self.device) | |
| # Use conjugation objective - score > 0 means valid | |
| scores = self.conjugation_obj(x) | |
| return scores > 0 | |
| def generate_x0(self, n_steps=16, temperature=1.0, require_valid=False, max_retries=10): | |
| """ | |
| Generate initial x0 sequences. | |
| Args: | |
| n_steps: Number of flow matching steps | |
| temperature: Sampling temperature | |
| require_valid: If True, regenerate invalid samples until all are valid conjugatable peptides | |
| max_retries: Maximum number of regeneration attempts per sample | |
| """ | |
| print("Starting initial SMILES generation...") | |
| x = self._generate_single_x0(n_steps, temperature) | |
| if require_valid: | |
| print("Checking conjugation validity and regenerating invalid samples...") | |
| for retry in range(max_retries): | |
| validity = self._check_conjugation_validity(x) | |
| num_valid = validity.sum().item() | |
| num_invalid = self.args.num_samples - num_valid | |
| if num_invalid == 0: | |
| print(f"All {self.args.num_samples} samples are valid conjugatable peptides!") | |
| break | |
| print(f"Retry {retry + 1}/{max_retries}: {num_valid}/{self.args.num_samples} valid, regenerating {num_invalid} invalid samples...") | |
| # Get indices of invalid samples | |
| invalid_indices = torch.where(~validity)[0] | |
| # Generate new candidates for invalid samples | |
| # Temporarily adjust num_samples to generate replacements | |
| original_num_samples = self.args.num_samples | |
| self.args.num_samples = num_invalid | |
| new_x = self._generate_single_x0(n_steps, temperature) | |
| self.args.num_samples = original_num_samples | |
| # Replace invalid samples with new ones | |
| x[invalid_indices] = new_x | |
| # Final validity check | |
| final_validity = self._check_conjugation_validity(x) | |
| num_final_valid = final_validity.sum().item() | |
| if num_final_valid < self.args.num_samples: | |
| print(f"Warning: After {max_retries} retries, only {num_final_valid}/{self.args.num_samples} samples are valid.") | |
| else: | |
| print(f"Success: All {self.args.num_samples} samples are valid conjugatable peptides.") | |
| # 4. Decode the final token IDs into SMILES strings | |
| generated_sequences = self.model.tokenizer.batch_decode(x) | |
| # 5. Analyze the validity of the generated sequences | |
| validities = [] | |
| for seq in generated_sequences: | |
| validities.append(self.peptide_analyzer.is_peptide(seq)) | |
| print(seq) | |
| print(f"Initial Sequence Validity: {validities}") | |
| return x | |
| def _get_scores(self, x_batch): | |
| """Calculates the normalized scores for a batch of sequences.""" | |
| scores = [] | |
| for obj_func in self.objectives: | |
| scores.append(obj_func(x_batch.to(self.device))) | |
| return torch.stack(scores, dim=0).to(self.device) | |
| def _barker_g(self, u): | |
| """Barker balancing function.""" | |
| return u / (1 + u) | |
| def validity(self, x): | |
| sampled_sequences = self.model.tokenizer.batch_decode(x) | |
| return [1.0 if self.peptide_analyzer.is_peptide(seq) else 0.0 for seq in sampled_sequences] | |
| def generate(self): | |
| """Main generation loop with tracking.""" | |
| shape = (self.args.num_samples, self.args.gen_len) | |
| x = self.generate_x0( | |
| require_valid=self.args.require_valid_x0, | |
| max_retries=self.args.max_x0_retries | |
| ) | |
| print(x) | |
| if args.weights is None: | |
| # The first weight is for conjugation (peptide + PDC validity) | |
| # We need to ensure the SMILES sequences are valid peptides with valid PDCs | |
| weights = torch.tensor([1] + [1/(self.num_objectives-1)] * (self.num_objectives-1), device=self.device).view(-1,1) | |
| else: | |
| weights = torch.tensor(self.args.weights, device=self.device).view(-1, 1) | |
| if len(weights) != self.num_objectives: | |
| raise ValueError("Number of weights must match number of objectives.") | |
| print(f"Weights: {weights}") | |
| with torch.no_grad(): | |
| for t in tqdm(range(self.args.optimization_steps), desc="MOG Generation"): | |
| improved = False | |
| improved_indices = [] | |
| # Anneal guidance strength | |
| eta_t = self.args.eta_min + (self.args.eta_max - self.args.eta_min) * (t / (self.args.optimization_steps - 1)) | |
| # Choose a random position to mutate | |
| mut_idx = random.randint(1, self.args.gen_len-2) | |
| # Determine the generation timestep | |
| # We cycle through the timesteps to ensure all are visited | |
| generation_step = t % self.args.optimization_steps | |
| time_t = torch.full((self.args.num_samples,), (generation_step / self.args.optimization_steps), device=self.device) | |
| # Get proposal distribution from ReDi model for the chosen position | |
| logits = self.model(x, time_t) | |
| probs = F.softmax(logits, dim=-1) | |
| pos_probs = probs[:, mut_idx, :] | |
| pos_probs[:, x[:, mut_idx]] = 0 # We don't evalute the same token | |
| pos_probs[:, self.invalid] = 0 | |
| # Prune candidate vocabulary using top-p sampling | |
| sorted_probs, sorted_indices = torch.sort(pos_probs, descending=True) | |
| cumulative_probs = torch.cumsum(sorted_probs, dim=-1) | |
| remove_mask = cumulative_probs > self.args.top_p | |
| remove_mask[..., 1:] = remove_mask[..., :-1].clone() | |
| remove_mask[..., 0] = 0 | |
| # Get the set of candidate tokens for each sample in the batch | |
| candidate_tokens_list = [] | |
| for i in range(self.args.num_samples): | |
| sample_mask = remove_mask[i] | |
| candidates = sorted_indices[i, ~sample_mask] | |
| candidate_tokens_list.append(candidates) | |
| # Get current scores | |
| current_scores = self._get_scores(x) | |
| w_current = torch.exp(eta_t * torch.min(weights * current_scores, dim=0).values) | |
| if t == 0: | |
| print(f"Initial Scores: {current_scores}") | |
| # Log initial step with detailed results | |
| if self.tracker and self.conjugation_obj: | |
| detailed = self.conjugation_obj.get_last_detailed_results() | |
| self.tracker.log_step(t, current_scores[0], detailed) | |
| self.tracker.print_step_summary(t, current_scores[0], detailed) | |
| # Evaluate all candidate tokens for each sample | |
| final_proposal_tokens = [] | |
| for i in range(self.args.num_samples): | |
| candidates = candidate_tokens_list[i] | |
| candidates = torch.tensor([token for token in candidates if token not in self.invalid], device=candidates.device) | |
| if len(candidates) >= 200: | |
| candidates = candidates[:200] | |
| num_candidates = len(candidates) | |
| # Create a batch of proposed sequences for the current sample | |
| x_prop_batch = x[i].repeat(num_candidates, 1) | |
| x_prop_batch[:, mut_idx] = candidates | |
| # Evaluate all proposals | |
| proposal_scores = self._get_scores(x_prop_batch) | |
| proposal_s_omega = torch.min(weights * proposal_scores, dim=0).values | |
| w_proposal = torch.exp(eta_t * proposal_s_omega) | |
| # Get ReDi probabilities for the candidates | |
| redi_probs = pos_probs[i, candidates] | |
| # Calculate unnormalized guided probabilities | |
| tilde_q = redi_probs * self._barker_g(w_proposal / w_current[i]) | |
| # Normalize and sample the final token | |
| final_probs = tilde_q / (torch.sum(tilde_q) + 1e-9) | |
| index = torch.multinomial(final_probs, 1).item() | |
| if (random.uniform(0,1) > 1 and proposal_scores[:,index][0] == 1) or torch.sum(weights.squeeze(1) * proposal_scores[:, index]) >= torch.sum(weights.squeeze(1) * current_scores[:,i]): | |
| final_token = candidates[index] | |
| print(f"Previous Weighted Sum: {torch.sum(weights.squeeze(1) * current_scores[:,i])}") | |
| print(f"Previous Scores: {current_scores[:,i]}") | |
| print(f"New Weighted Sum: {torch.sum(weights.squeeze(1) * proposal_scores[:, index])}") | |
| print(f"New Scores: {proposal_scores[:,index]}") | |
| improved = True | |
| improved_indices.append(i) | |
| else: | |
| final_token = x[i][mut_idx] | |
| final_proposal_tokens.append(final_token) | |
| # Update the sequences with the chosen tokens | |
| x[torch.arange(self.args.num_samples), mut_idx] = torch.stack(final_proposal_tokens) | |
| if improved: | |
| print(self.model.tokenizer.batch_decode(x)) | |
| # Log step with tracking (every 4 steps to reduce overhead) | |
| if self.tracker and self.conjugation_obj and (t % 4 == 0 or t == self.args.optimization_steps - 1): | |
| # Re-evaluate for detailed logging | |
| step_scores = self._get_scores(x) | |
| detailed = self.conjugation_obj.get_last_detailed_results() | |
| self.tracker.log_step(t, step_scores[0], detailed, improved_indices) | |
| # Print summary for final step | |
| if t == self.args.optimization_steps - 1: | |
| self.tracker.print_step_summary(t, step_scores[0], detailed) | |
| return x | |
| # --- Main Execution --- | |
| def main(args): | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| print(f"Using device: {device}") | |
| target = args.target | |
| tokenizer = SMILES_SPE_Tokenizer('./smiles/smiles_tokenizer/new_vocab.txt', './smiles/smiles_tokenizer/new_splits.txt') | |
| # Initialize Conjugation model with specified builder type | |
| conjugation_model = Conjugation(tokenizer, device=device, builder_type=args.builder_type) | |
| # hemolysis_model = Hemolysis(device) | |
| # nonfouling_model = Nonfouling(device) | |
| # solubility_model = Solubility(device) | |
| # permeability_model = Permeability(device) | |
| affinity_model = BindingAffinity(target, device) | |
| # List of all objective functions | |
| OBJECTIVE_FUNCTIONS = [conjugation_model, affinity_model] | |
| # Initialize tracker for logging | |
| tracker = GenerationTracker(output_dir=args.output_dir) | |
| # --- Set random seeds for reproducibility --- | |
| if args.seed is not None: | |
| import random | |
| import numpy as np | |
| random.seed(args.seed) | |
| np.random.seed(args.seed) | |
| torch.manual_seed(args.seed) | |
| if torch.cuda.is_available(): | |
| torch.cuda.manual_seed(args.seed) | |
| torch.cuda.manual_seed_all(args.seed) | |
| print(f"Random seed set to: {args.seed}") | |
| # --- Load Model --- | |
| print(f"Loading model from checkpoint: {args.checkpoint}") | |
| checkpoint = torch.load(args.checkpoint, map_location=device, weights_only=False) | |
| model_hparams = checkpoint["hyper_parameters"]["args"] | |
| model = MDLMLightningModule.load_from_checkpoint( | |
| args.checkpoint, | |
| args=model_hparams, | |
| tokenizer=tokenizer, | |
| map_location=device, | |
| strict=False | |
| ) | |
| model.to(device) | |
| print("Model loaded successfully.") | |
| mog_generator = MOGGenerator(model, device, OBJECTIVE_FUNCTIONS, args, tracker=tracker) | |
| all_sequences = [] | |
| all_final_results = [] | |
| for batch_idx in range(args.num_batches): | |
| print(f"\n{'='*60}") | |
| print(f"BATCH {batch_idx + 1}/{args.num_batches}") | |
| print(f"{'='*60}") | |
| generated_tokens = mog_generator.generate() | |
| final_scores = mog_generator._get_scores(generated_tokens.detach()).detach().cpu().numpy() | |
| sequence_str = tokenizer.batch_decode(generated_tokens) | |
| print("\n--- Batch Results ---") | |
| print(f"Sequences: {sequence_str}") | |
| print(f"Scores: {final_scores}") | |
| all_sequences.extend(sequence_str) | |
| # Get detailed results with properties for final sequences | |
| detailed_with_props = conjugation_model.get_detailed_scores_with_properties(generated_tokens) | |
| tracker.log_final(sequence_str, detailed_with_props) | |
| all_final_results.extend(detailed_with_props) | |
| # Print and save final summary | |
| tracker.print_final_summary() | |
| tracker.save(filename_prefix='pdc_generation') | |
| # Also save sequences to simple text file | |
| with open(args.output_file, 'w') as f: | |
| for seq in all_sequences: | |
| f.write(seq + '\n') | |
| print(f"\nSequences saved to: {args.output_file}") | |
| print("\nGeneration complete.") | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="Multi-Objective Generation with LBP-MOG-ReDi (Single Mutation).") | |
| parser.add_argument("--checkpoint", type=str, required=True, help="Path to the trained ReDi model checkpoint.") | |
| parser.add_argument("--num_samples", type=int, default=10, help="Number of samples to generate.") | |
| parser.add_argument("--num_batches", type=int, default=10, help="Number of batches to generate.") | |
| parser.add_argument("--output_file", type=str, default="./smiles.txt", help="File to save the generated sequences.") | |
| parser.add_argument("--output_dir", type=str, default="./generation_logs", help="Directory to save detailed generation logs.") | |
| parser.add_argument("--gen_len", type=int, default=50, help="Length of the sequences to generate.") | |
| parser.add_argument("--optimization_steps", type=int, default=16, help="Number of optimization steps per generation.") | |
| parser.add_argument("--weights", type=float, nargs='+', required=False, help="Weights for the objectives (e.g., 0.5 0.5).") | |
| parser.add_argument("--eta_min", type=float, default=1.0, help="Minimum guidance strength for annealing.") | |
| parser.add_argument("--eta_max", type=float, default=20.0, help="Maximum guidance strength for annealing.") | |
| parser.add_argument("--top_p", type=float, default=0.9, help="Top-p for pruning candidate tokens.") | |
| # PDC builder | |
| parser.add_argument("--builder_type", type=str, default="AoaDau", | |
| choices=["AoaDau", "ValAlaDau", "ValCitDau", "GlutarylDau", "Dau", | |
| "SSCpt", "VcMMAE", "DSPACpt", "MalHexCpt", "DSECCpt", "Cpt", | |
| "DSPASN38", "DSEBSN38", "DSEPSN38", "SN38"], | |
| help="PDC builder type for conjugation. " | |
| "'Dau' samples linker by GNRHR+Dau frequency. " | |
| "'Cpt' samples linker by ERBB2+Cpt frequency. " | |
| "'SN38' samples linker by NRP1+SN38 frequency.") | |
| parser.add_argument("--seed", type=int, default=None, help="Random seed for reproducibility.") | |
| parser.add_argument("--require_valid_x0", action="store_true", | |
| help="If set, regenerate x0 samples until they are valid conjugatable peptides.") | |
| parser.add_argument("--max_x0_retries", type=int, default=10, | |
| help="Maximum retries when require_valid_x0 is enabled.") | |
| parser.add_argument("--target", type=str, required=True, help="Target protein sequence for binding affinity.") | |
| args = parser.parse_args() | |
| main(args) | |