| |
|
|
| """ |
| Script to propose mutations using trained multievolve models. |
| Modified to load local models instead of using wandb. |
| """ |
|
|
| import os |
| os.environ["WANDB_MODE"] = "disabled" |
| os.environ["WANDB_SILENT"] = "true" |
|
|
| import wandb |
| import argparse |
| import pandas as pd |
| import numpy as np |
| from Bio import SeqIO |
| import matplotlib |
| matplotlib.use('Agg') |
| import glob |
| import re |
| import torch |
|
|
| from model.splitters import * |
| from model.featurizers import * |
| from model.predictors import * |
| from model.proposers import * |
|
|
|
|
| def parse_args(): |
| """Parse command line arguments.""" |
| parser = argparse.ArgumentParser(description='Propose mutations using trained models') |
| parser.add_argument( |
| '--experiment-name', |
| required=True, |
| help='Name of experiment' |
| ) |
| parser.add_argument( |
| '--protein-name', |
| required=True, |
| help='Name of protein' |
| ) |
| parser.add_argument( |
| '--wt-files', |
| required=True, |
| help='Comma separated list of paths to the wildtype FASTA files' |
| ) |
| parser.add_argument( |
| '--training-dataset', |
| required=True, |
| help='Path to training dataset CSV' |
| ) |
| parser.add_argument( |
| '--mutation-pool', |
| required=True, |
| help='Path to mutation pool CSV' |
| ) |
| parser.add_argument( |
| '--top-muts-per-load', |
| type=int, |
| default=3, |
| help='Number of top mutations to select per load (default: 3)' |
| ) |
| parser.add_argument( |
| '--export-name', |
| required=True, |
| help='Name for export files' |
| ) |
|
|
| args = parser.parse_args() |
| args.wt_files = [f.strip() for f in args.wt_files.split(',')] |
| return args |
|
|
|
|
| def main(): |
| """Main function.""" |
|
|
| |
| args = parse_args() |
|
|
| |
| experiment_name = args.experiment_name |
| protein_name = args.protein_name |
| wt_files = args.wt_files |
| training_dataset_fname = args.training_dataset |
| mutation_pool_fname = args.mutation_pool |
| top_muts_per_load = args.top_muts_per_load |
| export_name = args.export_name |
|
|
| |
| mutation_pool = pd.read_csv(mutation_pool_fname, header=None).values.flatten().tolist() |
| wt_seq = "".join([str(SeqIO.read(wt_file, "fasta").seq.upper()) for wt_file in wt_files]) |
|
|
| |
| bs = 32 |
| lr = 0.0001 |
| hidden = 100 |
| layers = 1 |
| print(bs, lr, hidden, layers) |
|
|
| |
| config = { |
| 'layer_size': hidden, |
| 'num_layers' : layers, |
| 'learning_rate': lr, |
| 'batch_size': bs, |
| 'optimizer': 'adam', |
| 'epochs': 300 |
| } |
|
|
| |
| split = KFoldProteinSplitter(protein_name, training_dataset_fname, wt_files, |
| csv_has_header=True, use_cache=True, y_scaling=True, val_split=0.15) |
| splits = split.generate_splits(n_splits=5) |
|
|
| |
| feature = OneHotFeaturizer(protein=protein_name, use_cache=True) |
|
|
| |
| |
| dataset_dir = splits[0].file_attrs['dataset_dir'] |
| dataset_name = splits[0].file_attrs['dataset_name'] |
| model_dir = os.path.join(dataset_dir, 'model_cache', dataset_name, 'objects') |
| |
| model_files = glob.glob(os.path.join(model_dir, 'split_by_kfold-*.pth')) |
| |
| model_files.sort(key=lambda x: int(re.search(r'split_by_kfold-(\d+)_', os.path.basename(x)).group(1))) |
| print(f"Found {len(model_files)} model files in {model_dir}") |
|
|
| models = [] |
| device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') |
| for i, split in enumerate(splits): |
| model = Fcn(split, feature, config=config, use_cache=True) |
| model.load_state_dict(torch.load(model_files[i], map_location=device, weights_only=True)) |
| model.to(device) |
| model.eval() |
| models.append(model) |
| print(f"Loaded model from {model_files[i]}") |
|
|
| print("Proposing mutations...") |
|
|
| |
| proposer = CombinatorialProposer( |
| start_seq=wt_seq, |
| models=models, |
| trust_radius=11, |
| num_seeds=-1, |
| mutation_pool=mutation_pool) |
| proposer.propose(output_df=False) |
| proposer.evaluate_proposals() |
| proposer.save_proposals(f'{experiment_name}_proposals_all') |
|
|
| |
| df = proposer.proposals |
| df_ls = [] |
| for num_mut in range(3, 11, 1): |
| subset = df[df['num_muts'] == num_mut].copy() |
| subset.sort_values(by='average', ascending=False, inplace=True) |
| top_subset = subset.head(top_muts_per_load).copy() |
| df_ls.append(top_subset) |
| top_df = pd.concat(df_ls, ignore_index=True) |
|
|
| |
| print('Saving all proposals...') |
| top_df.to_csv(os.path.join(splits[0].file_attrs['dataset_dir'], 'proposers/results', |
| f'{experiment_name}_proposals_top_{top_muts_per_load}.csv'), index=False) |
| top_df[['Mut_string']].to_csv(os.path.join(splits[0].file_attrs['dataset_dir'], f'{export_name}.csv'), |
| index=False, header=None) |
|
|
| |
| def reverse_multichain_mutations(mut_strings, chain_lengths): |
| cumulative_lengths = [sum(chain_lengths[:i]) for i in range(len(chain_lengths))] |
| mutation_map = {} |
| for mut_string in mut_strings: |
| mutations = mut_string.split('/') |
| chain_mutations = {i: [] for i in range(len(chain_lengths))} |
| for mut in mutations: |
| position = int(mut[1:-1]) |
| wt_aa = mut[0] |
| mut_aa = mut[-1] |
| for chain_idx, start_pos in enumerate(cumulative_lengths): |
| if position <= cumulative_lengths[chain_idx + 1] if chain_idx + 1 < len(cumulative_lengths) else float('inf'): |
| chain_pos = position - start_pos |
| chain_mutations[chain_idx].append(f"{wt_aa}{chain_pos}{mut_aa}") |
| break |
| mutation_map[mut_string] = chain_mutations |
| return mutation_map |
|
|
| def mutation_map_to_df(mutation_map): |
| rows = [] |
| for mut_string, chain_muts in mutation_map.items(): |
| row = {'Mut_string': mut_string} |
| for chain_idx, mutations in chain_muts.items(): |
| row[f'chain_{chain_idx + 1}'] = '/'.join(mutations) if mutations else '' |
| rows.append(row) |
| df = pd.DataFrame(rows) |
| chain_cols = [col for col in df.columns if col.startswith('chain_')] |
| df = df[['Mut_string'] + sorted(chain_cols)] |
| return df |
|
|
| if len(wt_files) > 1: |
| mutations = top_df['Mut_string'].values.tolist() |
| chain_lens = splits[0].wt_seq_lens |
| dict_mutations = reverse_multichain_mutations(mutations, chain_lens) |
| df_mutations = mutation_map_to_df(dict_mutations) |
|
|
| top_df = pd.merge(top_df, df_mutations, on='Mut_string', how='left') |
| top_df.to_csv(os.path.join(splits[0].file_attrs['dataset_dir'], 'proposers/results', |
| f'{experiment_name}_proposals_top_{top_muts_per_load}.csv'), index=False) |
|
|
| for col in df_mutations.columns[1:]: |
| mutations = set(df_mutations[col].tolist()) |
| if '' in mutations: |
| mutations.remove('') |
| df_mutations_col = pd.DataFrame(mutations, columns=[col]) |
| df_mutations_col.to_csv(os.path.join(splits[0].file_attrs['dataset_dir'], |
| f'{export_name}_{col}_mutants.csv'), index=False, header=None) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|