Download model/mpnn_fr/dl_interface_design.py from OneScience-Group/dl_binder_design: direct link, hf CLI and curl.
- Browser
- Download file 17.3 kB
-
https://huggingface.co/OneScience-Group/dl_binder_design/resolve/main/model/mpnn_fr/dl_interface_design.py
- Command line
-
hf download hf://OneScience-Group/dl_binder_design/model/mpnn_fr/dl_interface_design.py
-
curl -L -o dl_interface_design.py https://huggingface.co/OneScience-Group/dl_binder_design/resolve/main/model/mpnn_fr/dl_interface_design.py
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) | |