|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| import os
|
|
|
| import numpy as np
|
| import pandas as pd
|
| from tqdm.auto import tqdm
|
|
|
| from pxdbench.metrics import consistency
|
| from pxdbench.tasks.base import BaseTask
|
| from pxdbench.tools import esmfold
|
| from pxdbench.tools.protmpnn.vanilla_mpnn_predictor import VanillaMPNNPredictor
|
| from pxdbench.utils import save_eval_results
|
|
|
| from .registry import register_task
|
|
|
|
|
| @register_task("monomer")
|
| class MonomerTask(BaseTask):
|
| def __init__(self, input_data, cfg, device_id: int, seed: int):
|
| """
|
| Initialize a MonomerTask instance.
|
|
|
| Args:
|
| input_data (dict): Task input parameters with PDB directory and names.
|
| cfg (dict): Configuration dictionary with task settings.
|
| device_id (int): GPU device ID (-1 for CPU).
|
| seed (int): Random seed for reproducibility.
|
| """
|
| self.task_type = "monomer"
|
| self.task_name = input_data.get("name", "monomer")
|
| self.eval_diversity = cfg.get("eval_diversity", False)
|
| super().__init__(input_data, cfg, device_id, seed)
|
|
|
| def get_target_fn(self, item):
|
| return item["name"] + f"_seq{item['seq_idx']}.pdb"
|
|
|
| def prepare_consistency_inputs(self, results, folding_dir):
|
| inputs = {}
|
| for item in results:
|
| name = f"{item['name']}_seq{item['seq_idx']}"
|
| inputs[name] = {
|
| "source_file": os.path.join(self.pdb_dir, item["name"] + ".pdb"),
|
| "target_file": os.path.join(folding_dir, self.get_target_fn(item)),
|
| }
|
| return inputs
|
|
|
| def design_sequence(self, verbose=True):
|
| """
|
| Design monomer sequences using Vanilla MPNN.
|
|
|
| Initializes a VanillaMPNNPredictor and uses it to generate sequences for monomer proteins.
|
|
|
| Args:
|
| verbose (bool, optional): Whether to print detailed progress. Defaults to True.
|
|
|
| Returns:
|
| list[dict]: List of design results with "name", "seq_idx", and "sequence" keys.
|
| """
|
| mpnn_predictor = VanillaMPNNPredictor(
|
| self.cfg.tools.mpnn,
|
| device_id=self.device_id,
|
| verbose=verbose,
|
| seed=self.seed,
|
| )
|
| results = mpnn_predictor.design_monomer(
|
| self.pdb_dir, self.pdb_names, self.num_seqs
|
| )
|
| return results
|
|
|
| def run(self):
|
| """
|
| Execute the complete monomer design evaluation workflow.
|
|
|
| Workflow steps:
|
| 1. Design sequences via design_sequence()
|
| 2. Predict structures using ESMFold and evaluate self consistency
|
| 3. Calculate secondary structure metrics
|
| 4. Compute diversity and success rates based on scRMSD thresholds
|
| 5. Save sample-level results to CSV and summary metrics to JSON
|
|
|
| Returns:
|
| dict: Task metadata and output file paths.
|
| """
|
| results = self.design_sequence()
|
| esmfold_model = esmfold.ESMFold(self.get_device())
|
| print("Load esmfold done!")
|
| folding_dir = os.path.join(self.out_dir, "esmfold")
|
| os.makedirs(folding_dir, exist_ok=True)
|
|
|
| for item in tqdm(results, desc="ESMFold eval"):
|
| pdb_str, plddt = esmfold_model.predict([item["sequence"]])
|
| assert len(pdb_str) == 1 and len(plddt) == 1
|
| with open(os.path.join(folding_dir, self.get_target_fn(item)), "w") as f:
|
| f.write(pdb_str[0])
|
| item["plddt"] = plddt[0]
|
|
|
| inputs = self.prepare_consistency_inputs(results, folding_dir)
|
| outputs = consistency.self_consistency(inputs)
|
| for item in results:
|
| consistency_key = f"{item['name']}_seq{item['seq_idx']}"
|
| item.update(outputs[consistency_key])
|
|
|
| self.cal_secondary(results, chain_id="A")
|
|
|
| overall = {}
|
| for threshold in [2, 5]:
|
| success_names = []
|
| for item in results:
|
| if item["scRMSD"] < threshold:
|
| success_names.append(item["name"])
|
| div = self.cal_diversity(set(success_names))
|
| overall[f"scRMSD_lt{threshold}"] = len(success_names) / len(results)
|
| overall[f"scRMSD_lt{threshold}_str"] = len(set(success_names)) / len(
|
| self.pdb_names
|
| )
|
| overall[f"div_scRMSD_lt{threshold}"] = div
|
|
|
|
|
| overall_consistency = {}
|
| for item in results:
|
| key = item["name"]
|
| if key not in overall_consistency:
|
| overall_consistency[key] = {"scTM": 0.00001, "scRMSD": 10000.0}
|
| cur = overall_consistency[key]
|
| overall_consistency[key]["scTM"] = max(cur["scTM"], item["scTM"])
|
| overall_consistency[key]["scRMSD"] = min(cur["scRMSD"], item["scRMSD"])
|
| avg_tm = np.mean([v["scTM"] for v in overall_consistency.values()])
|
| avg_rmsd = np.mean([v["scRMSD"] for v in overall_consistency.values()])
|
| overall.update({"scTM": avg_tm, "scRMSD": avg_rmsd})
|
| sample_df = pd.DataFrame(results)
|
| sample_df = sample_df.sort_values(by=["name", "seq_idx"])
|
| summary_dict = {"task": self.task_type, "name": self.task_name}
|
| summary_dict.update(
|
| self.summary_from_df(
|
| sample_df,
|
| other_metrics=overall,
|
| )
|
| )
|
| sample_save_path, summary_save_path = save_eval_results(
|
| sample_df, summary_dict, self.out_dir, self.sample_fn, self.summary_fn
|
| )
|
| print(
|
| f"Eval done! Results are saved in {sample_save_path} and {summary_save_path}"
|
| )
|
| return {
|
| "task": self.task_type,
|
| "name": self.task_name,
|
| "sample_save_path": sample_save_path,
|
| "summary_save_path": summary_save_path,
|
| }
|
|
|