dl_binder_design / model /mpnn_fr /dl_interface_design.py
wuxing0105's picture
Upload folder using huggingface_hub
9ae74ae verified
Raw
History Blame Contribute Delete
17.3 kB
#!/usr/bin/env python
import os, sys
from pyrosetta import *
from pyrosetta.rosetta import *
import numpy as np
from collections import OrderedDict
import time
import argparse
import subprocess
import time
import glob
import torch
import json
import util_protein_mpnn as mpnn_util
parent = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(os.path.join(parent, 'include'))
from silent_tools import silent_tools
init( "-beta_nov16 -in:file:silent_struct_type binary -mute all" +
" -use_terminal_residues true -mute basic.io.database core.scoring" )
def cmd(command, wait=True):
the_command = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
if (not wait):
return
the_stuff = the_command.communicate()
return str( the_stuff[0]) + str(the_stuff[1] )
def range1( iterable ): return range( 1, iterable + 1 )
#################################
# Parse Arguments
#################################
parser = argparse.ArgumentParser()
script_dir = os.path.dirname(os.path.realpath(__file__))
package_root = os.path.dirname(os.path.dirname(script_dir))
default_checkpoint_path = os.path.join(
package_root, 'weight', 'ProteinMPNN', 'vanilla_model_weights', 'v_48_020.pt'
)
# I/O Arguments
parser.add_argument( "-pdbdir", type=str, default="", help='The name of a directory of pdbs to run through the model' )
parser.add_argument( "-silent", type=str, default="", help='The name of a silent file to run through the model' )
parser.add_argument( "-outpdbdir", type=str, default="outputs", help='The directory to which the output PDB files will be written, used if the -pdbdir arg is active' )
parser.add_argument( "-outsilent", type=str, default="out.silent", help='The name of the silent file to which output structs will be written, used if the -silent arg is active' )
parser.add_argument( "-runlist", type=str, default='', help="The path of a list of pdb tags to run, only active when the -pdbdir arg is active (default: ''; Run all PDBs)" )
parser.add_argument( "-checkpoint_name", type=str, default='check.point', help="The name of a file where tags which have finished will be written (default: check.point)" )
parser.add_argument( "-debug", action="store_true", default=False, help='When active, errors will cause the script to crash and the error message to be printed out (default: False)')
# Design Arguments
parser.add_argument( "-relax_cycles", type=int, default=1, help="The number of relax cycles to perform on each structure (default: 1)" )
parser.add_argument( "-output_intermediates", action="store_true", help='Whether to write all intermediate sequences from the relax cycles to disk (default: False)' )
parser.add_argument( "-seqs_per_struct", type=int, default="1", help="The number of sequences to generate for each structure (default: 1)" )
# ProteinMPNN-Specific Arguments
parser.add_argument( "-checkpoint_path", type=str, default=default_checkpoint_path, help=f"The path to the ProteinMPNN weights you wish to use, default {default_checkpoint_path}")
parser.add_argument( "-temperature", type=float, default=0.000001, help='The sampling temperature to use when running ProteinMPNN (default: 0.000001)' )
parser.add_argument( "-augment_eps", type=float, default=0, help='The variance of random noise to add to the atomic coordinates (default 0)' )
parser.add_argument( "-protein_features", type=str, default='full', help='What type of protein features to input to ProteinMPNN (default: full)' )
parser.add_argument( "-omit_AAs", type=str, default='CX', help='A string of all residue types (one letter case-insensitive) that you would not like to use for design. Letters not corresponding to residue types will be ignored (default: CX)' )
parser.add_argument( "-bias_AA_jsonl", type=str, default='', help='The path to a JSON file containing a dictionary mapping residue one-letter names to the bias for that residue eg. {A: -1.1, F: 0.7} (default: ''; no bias)' )
parser.add_argument( "-num_connections", type=int, default=48, help='Number of neighbors each residue is connected to. Do not mess around with this argument unless you have a specific set of ProteinMPNN weights which expects a different number of connections. (default: 48)' )
args = parser.parse_args( sys.argv[1:] )
class sample_features():
'''
This is a struct which keeps all the features related to a single sample together
'''
def __init__(self, pose, tag):
self.pose = pose
self.tag = os.path.basename(tag).split('.')[0]
def parse_fixed_res(self):
'''
Parse which residues are fixed from the residue remarks
'''
# Iterate over the residue positions in this pose and check if they are fixed
fixed_list = []
endA = self.pose.split_by_chain()[1].total_residue()
for resi in range1( endA ):
reslabels = self.pose.pdb_info().get_reslabels( resi )
if len(reslabels) == 0: continue
if str(self.pose.pdb_info().get_reslabels( resi )[1]).strip() == 'FIXED':
fixed_list.append( resi )
# Get ordered unique chainIDs for the pose
# The last chain (B) is the target and will have a fixed sequence
self.chains = list( OrderedDict.fromkeys( [ self.pose.pdb_info().chain(i) for i in range1( self.pose.total_residue() ) ] ) )
# Create the fixed res dict, this will be input to ProteinMPNN
self.fixed_res = {
self.chains[0]: fixed_list,
self.chains[1]: []
}
def thread_mpnn_seq(self, binder_seq):
'''
Thread the binder sequence onto the pose being designed
'''
rsd_set = self.pose.residue_type_set_for_pose( core.chemical.FULL_ATOM_t )
for resi, mut_to in enumerate( binder_seq ):
resi += 1 # 1 indexing
name3 = mpnn_util.aa_1_3[ mut_to ]
new_res = core.conformation.ResidueFactory.create_residue( rsd_set.name_map( name3 ) )
self.pose.replace_residue( resi, new_res, True )
class ProteinMPNN_runner():
'''
This class is designed to run the ProteinMPNN model on a single input. This class handles
the loading of the model, the loading of the input data, the FastRelax cycles, and the processing of the output
'''
def __init__(self, args, struct_manager):
self.struct_manager = struct_manager
if torch.cuda.is_available():
print('Found GPU will run ProteinMPNN on GPU')
self.device = "cuda:0"
else:
print('No GPU found, running ProteinMPNN on CPU')
self.device = "cpu"
self.mpnn_model = mpnn_util.init_seq_optimize_model(
self.device,
hidden_dim=128,
num_layers = 3,
backbone_noise = args.augment_eps,
num_connections = args.num_connections,
checkpoint_path = args.checkpoint_path
)
alphabet = 'ACDEFGHIKLMNPQRSTVWYX'
self.debug = args.debug
self.temperature = args.temperature
self.seqs_per_struct = args.seqs_per_struct
self.omit_AAs = [ letter for letter in args.omit_AAs.upper() if letter in list(alphabet) ]
# Parse AA bias settings from json
if os.path.isfile(args.bias_AA_jsonl):
print(f'Found AA bias json file at {args.bias_AA_jsonl}')
with open(args.bias_AA_jsonl, 'r') as json_file:
json_list = list(json_file)
for json_str in json_list:
bias_AA_dict = json.loads(json_str)
self.bias_AAs_np = np.zeros(len(alphabet))
for n, AA in enumerate(alphabet):
if AA in list(bias_AA_dict.keys()):
self.bias_AAs_np[n] = bias_AA_dict[AA]
else:
# Not using AA bias
self.bias_AAs_np = np.zeros(len(alphabet))
# Configs for the FastRelax cycles
xml = os.path.join(script_dir, 'RosettaFastRelaxUtil.xml')
objs = protocols.rosetta_scripts.XmlObjects.create_from_file(xml)
self.FastRelax = objs.get_mover('FastRelax')
self.relax_cycles = args.relax_cycles
def relax_pose(self, sample_feats):
'''
Run FastRelax on the current pose
'''
relaxT0 = time.time()
print('Running FastRelax')
self.FastRelax.apply(sample_feats.pose)
print(f"Completed one cycle of FastRelax in {int(time.time()) - relaxT0} seconds")
def sequence_optimize(self, sample_feats):
mpnn_t0 = time.time()
# Once we have figured out pose I/O without Rosetta this will be easy to swap in
pdbfile = 'temp.pdb'
sample_feats.pose.dump_pdb(pdbfile)
feature_dict = mpnn_util.generate_seqopt_features(pdbfile, sample_feats.chains)
os.remove(pdbfile)
arg_dict = mpnn_util.set_default_args( self.seqs_per_struct, omit_AAs=self.omit_AAs )
arg_dict['temperature'] = self.temperature
masked_chains = sample_feats.chains[:-1]
visible_chains = [sample_feats.chains[-1]]
fixed_positions_dict = {pdbfile[:-len('.pdb')]: sample_feats.fixed_res}
if self.debug:
print(f'Fixed positions dict: {fixed_positions_dict}')
sequences = mpnn_util.generate_sequences(
self.mpnn_model,
self.device,
feature_dict,
arg_dict,
masked_chains,
visible_chains,
bias_AAs_np=self.bias_AAs_np,
fixed_positions_dict=fixed_positions_dict
)
if self.debug:
print(f'Generated sequence(s): {sequences}')
print( f"ProteinMPNN generated {len(sequences)} sequences in {int( time.time() - mpnn_t0 )} seconds" )
return sequences
def proteinmpnn(self, sample_feats):
'''
Run ProteinMPNN sequence optimization on the pose, this does not use FastRelax
'''
seqs_scores = self.sequence_optimize(sample_feats)
# Iterate though each seq score pair and thread the sequence onto the pose
# Then write each pose to a pdb file
prefix = f"{sample_feats.tag}_dldesign"
for idx, (seq, score) in enumerate(seqs_scores):
sample_feats.thread_mpnn_seq(seq)
outtag = f"{prefix}_{idx}"
self.struct_manager.dump_pose(sample_feats.pose, outtag)
def proteinmpnn_fastrelax(self, sample_feats):
'''
Run ProteinMPNN plus FastRelax on the pose being designed
'''
tot_t0 = time.time()
design_counter = 0
prefix = f"{sample_feats.tag}_dldesign"
for cycle in range(args.relax_cycles):
seqs_scores = self.sequence_optimize(sample_feats)
seq, mpnn_score = seqs_scores[0] # We know there is only one entry
sample_feats.thread_mpnn_seq(seq)
self.relax_pose(sample_feats)
if args.output_intermediates:
tag = f"{prefix}_0_cycle{cycle}"
self.struct_manager.dump_pose(sample_feats.pose, tag)
seqs_scores = self.sequence_optimize(sample_feats)
seq, mpnn_score = seqs_scores[0] # We know there is only one entry
sample_feats.thread_mpnn_seq(seq)
tag = f"{prefix}_0_cycle{args.relax_cycles}"
self.struct_manager.dump_pose(sample_feats.pose, tag)
def run_model(self, tag, args):
t0 = time.time()
print(f"Attempting pose: {tag}")
# Load the pose
pose = self.struct_manager.load_pose(tag)
# Initialize the features
sample_feats = sample_features(pose, tag)
# Parse the fixed residues from the pose remarks
sample_feats.parse_fixed_res()
# Now determine which type of run we are doing and execute it
if args.relax_cycles > 0:
if args.seqs_per_struct > 1:
raise Exception('Cannot use --seqs_per_struct > 1 with --relax_cycles > 0')
self.proteinmpnn_fastrelax(sample_feats)
else:
self.proteinmpnn(sample_feats)
seconds = int(time.time() - t0)
print( f"Struct: {pdb} reported success in {seconds} seconds" )
class StructManager():
'''
This class handles all of the input and output for the ProteinMPNN model. It deals with silent files vs. pdbs,
checkpointing, and writing of outputs
Note: This class could be moved to a separate file
'''
def __init__(self, args):
self.args = args
self.silent = False
if not args.silent == '':
self.silent = True
self.struct_iterator = silent_tools.get_silent_index(args.silent)['tags']
self.sfd_in = rosetta.core.io.silent.SilentFileData(rosetta.core.io.silent.SilentFileOptions())
self.sfd_in.read_file(args.silent)
self.sfd_out = core.io.silent.SilentFileData(args.outsilent, False, False, "binary", core.io.silent.SilentFileOptions())
self.outsilent = args.outsilent
self.pdb = False
if not args.pdbdir == '':
self.pdb = True
self.pdbdir = args.pdbdir
self.outpdbdir = args.outpdbdir
self.struct_iterator = glob.glob(os.path.join(args.pdbdir, '*.pdb'))
# Parse the runlist and determine which structures to process
if args.runlist != '':
with open(args.runlist, 'r') as f:
self.runlist = set([line.strip() for line in f])
# Filter the struct iterator to only include those in the runlist
self.struct_iterator = [struct for struct in self.struct_iterator if os.path.basename(struct).split('.')[0] in self.runlist]
print(f'After filtering by runlist, {len(self.struct_iterator)} structures remain')
# Assert that either silent or pdb is true, but not both
assert(self.silent ^ self.pdb), f'Both silent and pdb are set to {args.silent} and {args.pdb} respectively. Only one of these may be active at a time'
# Setup checkpointing
self.chkfn = args.checkpoint_name
self.finished_structs = set()
if os.path.isfile(self.chkfn):
with open(self.chkfn, 'r') as f:
for line in f:
self.finished_structs.add(line.strip())
def record_checkpoint(self, tag):
'''
Record the fact that this tag has been processed.
Write this tag to the list of finished structs
'''
with open(self.chkfn, 'a') as f:
f.write(f'{tag}\n')
def iterate(self):
'''
Iterate over the silent file or pdb directory and run the model on each structure
'''
# Iterate over the structs and for each, check that the struct has not already been processed
for struct in self.struct_iterator:
tag = os.path.basename(struct).split('.')[0]
if tag in self.finished_structs:
print(f'{tag} has already been processed. Skipping')
continue
yield struct
def dump_pose(self, pose, tag):
'''
Dump this pose to either a silent file or a pdb file depending on the input arguments
'''
if self.pdb:
# If the outpdbdir does not exist, create it
# If there are parents in the path that do not exist, create them as well
if not os.path.exists(self.outpdbdir):
os.makedirs(self.outpdbdir)
pdbfile = os.path.join(self.outpdbdir, tag + '.pdb')
pose.dump_pdb(pdbfile)
if self.silent:
struct = self.sfd_out.create_SilentStructOP()
struct.fill_struct(pose, tag)
self.sfd_out.add_structure(struct)
self.sfd_out.write_silent_struct(struct, self.outsilent)
def load_pose(self, tag):
'''
Load a pose from either a silent file or a pdb file depending on the input arguments
'''
if not self.pdb and not self.silent:
raise Exception('Neither pdb nor silent is set to True. Cannot load pose')
if self.pdb:
pose = pose_from_pdb(tag)
if self.silent:
pose = Pose()
self.sfd_in.get_structure(tag).fill_pose(pose)
return pose
####################
####### Main #######
####################
struct_manager = StructManager(args)
proteinmpnn_runner = ProteinMPNN_runner(args, struct_manager)
for pdb in struct_manager.iterate():
if args.debug: proteinmpnn_runner.run_model(pdb, args)
else: # When not in debug mode the script will continue to run even when some poses fail
t0 = time.time()
try: proteinmpnn_runner.run_model(pdb, args)
except KeyboardInterrupt: sys.exit( "Script killed by Control+C, exiting" )
except:
seconds = int(time.time() - t0)
print( "Struct with tag %s failed in %i seconds with error: %s"%( pdb, seconds, sys.exc_info()[0] ) )
# We are done with one pdb, record that we finished
struct_manager.record_checkpoint(pdb)