English
OneScience
protein structure generation
anzhi2710gmailcom's picture
Upload folder using huggingface_hub
8e04e6f verified
Raw
History Blame Contribute Delete
9.31 kB
import argparse
import os
import sys
from pathlib import Path
from typing import Dict, Tuple
import hydra
import lightning as L
# import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
from dotenv import load_dotenv
from loguru import logger
from sklearn.decomposition import PCA
from .autoencoder import AutoEncoder
COLORS_RT = [
"#FF0000", # Red
"#008000", # Green
"#0000FF", # Blue
"#FFFF00", # Yellow
"#FFA500", # Orange
"#800080", # Purple
"#00FFFF", # Cyan
"#FF00FF", # Magenta
"#00FF00", # Lime
"#FFC0CB", # Pink
"#008080", # Teal
"#E6E6FA", # Lavender
"#A52A2A", # Brown
"#F5F5DC", # Beige
"#800000", # Maroon
"#808000", # Olive
"#FF7F50", # Coral
"#000080", # Navy
"#AAF0D1", # Mint
"#FFDB58", # Mustard
]
CONFIG_ROOT = Path(__file__).resolve().parents[2] / "configs"
def parse_args_and_cfg() -> Tuple[Dict, Dict, str]:
"""
Parses command line arguments and loads the corresponding config file.
Returns:
Command line arguments (dict)
Config file (dict)
config_name (string)
"""
parser = argparse.ArgumentParser(description="Job info")
parser.add_argument(
"--config_name",
type=str,
default="inference_ae",
help="Name of the config yaml file.",
)
parser.add_argument(
"--config_number", type=int, default=-1, help="Number of the config yaml file."
)
parser.add_argument(
"--config_subdir",
type=str,
help="(Optional) Name of directory with config files, if not included uses base inference config.\
Likely only used when submitting to the cluster with script.",
)
args = parser.parse_args()
# Inference config
# If config_subdir is None then use base inference config
# Otherwise use config_subdir/some_config
if args.config_subdir is None:
config_path = str(CONFIG_ROOT)
else:
config_path = str(CONFIG_ROOT / args.config_subdir)
with hydra.initialize_config_dir(config_dir=config_path, version_base=hydra.__version__):
# If number provided use it, otherwise name
if args.config_number != -1:
config_name = f"inf_{args.config_number}"
else:
config_name = args.config_name
cfg = hydra.compose(config_name=config_name)
logger.info(f"Inference config {cfg}")
return args, cfg, config_name
def extract_ckpt_info(ckpt_file_path):
ae_name = ckpt_file_path.split("/")[-3]
ckpt_name = ckpt_file_path.split("/")[-1]
return ae_name, ckpt_name
def setup(
cfg: Dict,
config_name: str,
create_root: bool = True,
) -> str:
"""
Checks if metrics being computed are compatible, sets the right seed, and creates the root directory
where the run will store things.
Returns:
Path of the root directory (string)
"""
logger.info(" ".join(sys.argv))
assert (
torch.cuda.is_available()
), "CUDA not available" # Needed for ESMfold and designability
logger.add(
sys.stdout,
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {file}:{line} | {message}",
) # Send to stdout
# Set root path for this inference run
root_path = f"./inference/{config_name}"
if create_root:
os.makedirs(root_path, exist_ok=True)
else:
if not os.path.exists(root_path):
raise ValueError("Results path %s does not exist" % root_path)
# Set seed
logger.info(f"Seeding everything to seed {cfg.seed}")
L.seed_everything(cfg.seed)
return root_path
def load_dataloader(cfg):
"""
Loads data config file and returns dataloader.
"""
if cfg.dataset == "genie2":
raise ValueError(
"dataset=genie2 is not packaged in this OneScience integration. "
"Use dataset=pdb or add the missing afdb_fromraw configs."
)
elif cfg.dataset == "pdb":
config_path = str(CONFIG_ROOT / "dataset" / "pdb")
config_name = "pdb_train_ucond"
elif cfg.dataset == "pdb_multimer":
raise ValueError(
"dataset=pdb_multimer is not packaged in this OneScience integration. "
"Use dataset=pdb or add the missing pdb_multimer configs."
)
else:
raise ValueError(f"Dataset {cfg.dataset} not implemented")
with hydra.initialize_config_dir(config_dir=config_path, version_base=hydra.__version__):
cfg_data = hydra.compose(config_name=config_name)
cfg_data["datamodule"]["batch_size"] = cfg.bs
datamodule = hydra.utils.instantiate(cfg_data.datamodule)
datamodule.prepare_data()
datamodule.setup("fit")
dataloader = datamodule.val_dataloader()
print(
f"Number of batches in dataloader: {len(dataloader)}, batch size: {cfg.bs}, total number of structures: {len(dataloader) * cfg.bs}"
)
return dataloader
def extract_pdb_ids(predictions):
logger.info(f"Extracting PDBs we test on")
vals = []
for x_in, _ in predictions:
v = x_in["id"]
vals += v
return vals
def compute_all_atom_rmsd(predictions, model):
logger.info(f"Computing all-atom RMSD")
vals = []
for x_in, x_out in predictions:
v = model.compute_struct_rec_loss(
output_dec=x_out,
batch=x_in,
)["rmsd_no_align_a37_ang_justlog"]
vals += v.tolist()
return vals
def compute_sec_rec_rate(predictions, model):
logger.info(f"Computing sequence recovery rate")
vals = []
for x_in, x_out in predictions:
v = model.compute_seq_rec_loss(
output_dec=x_out,
batch=x_in,
)["seq_rec_rate_justlog"]
vals += v.tolist()
return vals
def compute_kl_latent(predictions, model):
logger.info(f"Computing sequence recovery rate")
vals = []
for _, x_out in predictions:
v = model.compute_kl_penalty(
mean=x_out["mean"],
log_scale=x_out["log_scale"],
mask=x_out["residue_mask"],
w=1.0,
)["kl_now_justlog"]
vals += v.tolist()
return vals
def compute_metric(metric, predictions, model):
if metric == "all_atom_rmsd":
return compute_all_atom_rmsd(predictions, model) # List of floats
elif metric == "seq_rec_rate":
return compute_sec_rec_rate(predictions, model) # List of floats
elif metric == "kl_latent_dist":
return compute_kl_latent(predictions, model) # List of floats
else:
raise IOError(f"Metric {metric} not implemented")
def get_df_stats(df):
numeric_cols = [col for col in df.columns if pd.api.types.is_numeric_dtype(df[col])]
means = df[numeric_cols].mean()
stds = df[numeric_cols].std()
stats_data = {"stat_type": ["mean", "std"]}
for col in numeric_cols:
stats_data[col] = [means[col], stds[col]]
return pd.DataFrame(stats_data)
def main() -> None:
load_dotenv()
# Load config
args, cfg, config_name = parse_args_and_cfg()
ae_name, ckpt_name = extract_ckpt_info(cfg.ckpt_file)
# Some setup
root_path = setup(cfg, create_root=True, config_name=config_name)
df_file_store = os.path.join(root_path, f"../results_{config_name}.csv")
df_file_store_summary = os.path.join(
root_path, f"../results_{config_name}_summary.csv"
)
# Get dataloader
dataloader = load_dataloader(cfg)
# Model
model = AutoEncoder.load_from_checkpoint(cfg.ckpt_file)
# Make predictions, store them together with inputs
trainer = L.Trainer(
accelerator="gpu", devices=1, limit_predict_batches=int(cfg.n_structs / cfg.bs)
)
predictions = trainer.predict(model, dataloader)
# List of tuples, each tuple is (data_batch, predicted_batch)
# and the predicted batch has all outputs from the endocer and decoder
# Compute requested metrics
metrics = {}
metrics_to_compute = [k for k in cfg.metrics if cfg.metrics[k]]
for metric in metrics_to_compute:
metrics[metric] = compute_metric(
metric=metric, predictions=predictions, model=model
)
# Extract PDB ids
pdb_id = extract_pdb_ids(predictions) # List of strs
# Plot requested stuff
dir_storages = {}
# Create dataframe with results
info_df = {"pdb_id": pdb_id}
info_df.update(metrics)
df = pd.DataFrame(info_df)
# Save summary results
col_names = ["ae_name", "ckpt_name", "dataset"]
values = [ae_name, ckpt_name, cfg.dataset]
for m in metrics:
col_names += [f"{m}_mean", f"{m}_std", f"{m}_max", f"{m}_min"]
vals_aux = np.array(metrics[m])
values += [vals_aux.mean(), vals_aux.std(), vals_aux.max(), vals_aux.min()]
col_names += [k for k in dir_storages]
values += [dir_storages[k] for k in dir_storages]
df_summary = pd.DataFrame(
{col_names[i]: [values[i]] for i in range(len(col_names))}
)
# Save dataframes
df.to_csv(df_file_store, index=False)
df_summary.to_csv(df_file_store_summary, index=False)
# Save df
df.to_csv(df_file_store, index=False)
df_summary.to_csv(df_file_store_summary, index=False)
print("Done saving dataframes")
if __name__ == "__main__":
main()