Source code for ample.modelling.rosetta_model

"""
Created on 21 Feb 2013

@author: jmht
"""

# Python modules
import copy
import glob
import logging
import os
import random
import shutil

# Our modules
from ample.modelling import energy_functions, octopus_predict, multimer_definitions
from ample.parsers import psipred_parser
from ample.util import ample_util, pdb_edit, sequence_util
from pyjob.factory import TaskFactory
from pyjob.script import ScriptCollector, Script

logger = logging.getLogger(__name__)


[docs]def align_mafft(query_seq, template_seq, logger, mafft_exe=None): if not mafft_exe: mafft_exe = os.path.join(os.environ['CCP4'], 'libexec', 'mafft') if not ample_util.is_exe(mafft_exe): raise RuntimeError("Cannot find CCP4 mafft binary: {0}".format(mafft_exe)) logger.info( "Running mafft binary {0} to generate alignment between target fasta and the NMR model sequence.".format( mafft_exe ) ) name = "{0}__{1}".format(query_seq.name, template_seq.name) mafft_input = "{0}_concat.fasta".format(name) # Join two sequences mafft_seq = copy.copy(query_seq) mafft_seq += template_seq mafft_seq.write_fasta(mafft_input) cmd = [mafft_exe, '--maxiterate', '1000', '--localpair', '--quiet', mafft_input] logfile = os.path.abspath('mafft.out') # Due to a bug in the CCP4 mafft installation, we need to set MAFFT_BINARIES env = copy.copy(os.environ) env['MAFFT_BINARIES'] = os.path.join(env['CCP4'], 'libexec') ret = ample_util.run_command(cmd, logfile=logfile, env=env) if ret != 0: raise RuntimeError("Error running mafft for alignnment - check logfile: {0}".format(logfile)) seq_align = sequence_util.Sequence() seq_align.from_fasta(logfile, canonicalise=False) logger.info("Got Alignment:\n{0}\n{1}".format(seq_align.sequences[0], seq_align.sequences[1])) logger.info("If you want to use a different alignment, import using -alignment_file") alignment_file = "{0}_align.grishin".format(name) with open(alignment_file, 'w') as w: # First line is query and template name - must match the name of the template pdb w.write( """## {0} {1} # hhsearch\n scores_from_program: 0 1.00\n'+ 0 {2} 0 {3} """.format( query_seq.name, template_seq.name, seq_align.sequences[0], seq_align.sequences[1] ) ) return os.path.abspath(alignment_file)
[docs]def align_clustalw(query_seq, template_seq, logger, clustalw_exe=None): if not clustalw_exe: mafft_exe = os.path.join(os.environ['CCP4'], 'libexec', 'clustalw2') if not ample_util.is_exe(mafft_exe): raise RuntimeError("Cannot find CCP4 clustalw2 binary: {}".format(mafft_exe)) name = "{0}__{1}".format(query_seq.name, template_seq.name) clustalw_input = "{0}_concat.fasta".format(name) query_seq.concat(template_seq, clustalw_input) align_out = "{0}_align.fasta".format(name) logfile = os.path.abspath("clustalw2.log") cmd = [ clustalw_exe, '-align', '-outorder=input', '-output=fasta', '-infile={0}'.format(clustalw_input), '-outfile={0}'.format(align_out), ] ret = ample_util.run_command(cmd, logfile=logfile) if ret != 0: raise RuntimeError("Error running clustalw2 for alignnment - check logfile: {0}".format(logfile)) seq_align = sequence_util.Sequence() seq_align.from_fasta(align_out, canonicalise=False) logger.info("Got Alignment:\n{0}\n{1}".format(seq_align.sequences[0], seq_align.sequences[1])) logger.info("If you want to use a different alignment, import using -alignment_file") alignment_file = "{0}_align.grishin".format(name) with open(alignment_file, 'w') as w: # First line is query and template name - must match the name of the template pdb w.write( """## {0} {1} # hhsearch\n scores_from_program: 0 1.00\n'+ 0 {2} 0 {3} """.format( query_seq.name, template_seq.name, seq_align.sequences[0], seq_align.sequences[1] ) ) return os.path.abspath(alignment_file)
[docs]class RosettaModel(object): """ Class to run Rosetta modelling """ def __init__(self, optd=None, rosetta_dir=None): self.debug = None self.nproc = None self.nmodels = None self.work_dir = None # Where the modelling happens - can be deleted on exit self.ample_dir = None self.models_dir = None self.rosetta_dir = None self.rosetta_bin = None self.rosetta_relax_exe = None self.rosetta_minirosetta_exe = None self.rosetta_mr_protocols = None self.rosetta_idealize_jd2 = None self.rosetta_db = None self.rosetta_version = None self.fasta = None self.sequence_length = None self.all_atom = None # Fragment variables self.name = None self.frags_3mers = None self.frags_9mers = None self.use_homs = None self.fragments_directory = None self.fragments_exe = None # Transmembrane variables self.transmembrane = None self.transmembrane_old = None self.tm_patch_file = None self.octopus2span = None self.run_lips = None self.align_blast = None self.nr = None self.blastpgp = None self.octopusTopology = None self.spanfile = None self.lipofile = None # Extra options self.psipred_ss2 = None self.domain_termini_distance = None self.rad_gyr_reweight = None self.improve_template = None self.multimer_modelling = None self.num_chains = None self.nativePdbStd = None self.rosetta_flagsfile = None self.restraints_file = None self.restraints_weight = None self.disulfide_constraints_file = None self.nmr_remodel = None self.nmr_process_ntimes = None self.nmr_alignment_file = None self.mnr_remodel_fasta = None if optd: self.set_paths(optd=optd, rosetta_dir=rosetta_dir) self.set_from_dict(optd) if self.work_dir and not os.path.isdir(self.work_dir): os.mkdir(self.work_dir) return
[docs] def ab_initio_cmd(self): """Return the command to run rosetta as a list suitable for subprocess""" cmd = [ '-database', self.rosetta_db, '-in::file::fasta', self.fasta, '-in:file:frag3', self.frags_3mers, '-in:file:frag9', self.frags_9mers, '-out:pdb', '-out:file:silent', 'silent.out', '-run:constant_seed', '-abinitio:relax', '-relax::fast', ] if self.rosetta_version >= 3.4: # Recommended default paramenters - see also Radius of gyration reweight # we omit the -use_filters true option as we want to continue with refinement # even for 'failed' models as the failed models will be used anyway cmd += [ "-abinitio::increase_cycles", "10", "-abinitio::rsd_wt_helix", "0.5", "-abinitio::rsd_wt_loop", "0.5", ] if self.psipred_ss2: # not sure if this works < 3.4 cmd += ["-psipred_ss2", self.psipred_ss2] if self.all_atom: cmd += ['-return_full_atom', 'true'] else: cmd += ['-return_full_atom', 'false'] if self.transmembrane_old: cmd += [ '-in:file:spanfile', self.spanfile, '-in:file:lipofile', self.lipofile, '-abinitio:membrane', '-membrane:no_interpolate_Mpair', '-membrane:Menv_penalties', '-score:find_neighbors_3dgrid', '-membrane:normal_cycles', '40', '-membrane:normal_mag', '15', '-membrane:center_mag', '2', '-mute core.io.database', '-mute core.scoring.MembranePotential', ] elif self.transmembrane: cmd += ['-score:patch', self.tm_patch_file] if self.restraints_file and os.path.isfile(self.restraints_file): self.restraints_weight = 3 # Radius of gyration reweight if self.rad_gyr_reweight is not None: cmd += ['-rg_reweight', str(self.rad_gyr_reweight)] else: cmd += ['-rg_reweight', "0.5"] # Add any restraints cmd = self.cmd_add_restraints(cmd) # Improve Template if self.improve_template: cmd += [ '-in:file:native', self.improve_template, '-abinitio:steal_3mers', 'true', '-abinitio:steal9mers', 'true', '-abinitio:start_native', 'true', '-templates:force_native_topology', 'true', ] return cmd
[docs] def ab_initio_model(self, processed_models=None): """Run the ab initio modelling and return a list of models. Parameters ---------- processed_models : list, optional A list of pdb models that should have been checked for suitability. NB - currently only required by NMR remodelling Returns ------- list The list of created ab initio pdb models """ if self.nmr_remodel: return self.do_nmr_remodel(processed_models) elif self.multimer_modelling: return self.do_multimer_modelling() rosetta_executable = self.rosetta_relax_exe flagsfile = self.rosetta_flagsfile if not flagsfile: # Run any setup requried if self.transmembrane_old: rosetta_executable = self.transmembrane_exe self.tm_make_files() elif self.transmembrane: self.tm2_make_patch(self.work_dir) if self.domain_termini_distance > 0 and not self.multimer_modelling: if self.restraints_file: raise RuntimeError( "Cannot set up domain restraints with existing restraints file: {}!".format( self.restraints_file ) ) self.restraints_file = self.setup_domain_restraints() # create the flags file with the rosetta directives flagsfile = os.path.join(self.work_dir, 'rosetta.flags') flags = self.process_cmd_list(self.ab_initio_cmd()) with open(flagsfile, 'w') as w: w.write(flags) return self.model_from_flagsfile(flagsfile, rosetta_executable=rosetta_executable)
[docs] def cmd_add_restraints(self, cmd): """Add any restraints and files to the ROSETTA command-line options""" if self.restraints_file: cmd += ['-constraints:cst_file', self.restraints_file, '-constraints:cst_fa_file', self.restraints_file] if self.restraints_weight is not None: cmd += [ '-constraints:cst_weight', str(self.restraints_weight), '-constraints:cst_fa_weight', str(self.restraints_weight), ] # Add compatibility for extra disulfide restraints if self.disulfide_constraints_file and os.path.isfile(self.disulfide_constraints_file): cmd += ['-in::fix_disulf', str(self.disulfide_constraints_file)] return cmd
[docs] def do_multimer_modelling(self): symmetry_file = self.create_multimer_symmetry_file() constraints_file = self.create_multimer_constraints_file() broker_file = self.create_broker_definition_file() flagsfile = self.create_multimer_flagsfile( broker_file=broker_file, symmetry_file=symmetry_file, constraints_file=constraints_file ) models = self.model_from_flagsfile( flagsfile=flagsfile, rosetta_executable=self.rosetta_minirosetta_exe, consolidate_pdbs=False ) return self.process_multimer_models(models)
[docs] def process_multimer_models(self, modelsin): """Merge the multi-chain pdbs into a single chain""" # Work out how many chains we're using chaind = sequence_util.sequence_data(modelsin[0]) chains = None if self.num_chains and self.num_chains < len(chaind): # for now we just assume we want continguous ones chains = list(chaind.keys())[: self.num_chains] logger.info("Selecting chains %s from multimer models", chains) models = [] for i, pdbin in enumerate(modelsin): pdbout = os.path.join(self.models_dir, "multimermodel_{}.pdb".format(i)) if chains and len(chains) == 1: pdb_edit.extract_chain(pdbin, pdbout, chainID=chains[0]) else: pdb_edit.merge_chains(pdbin, pdbout, chains=chains) models.append(pdbout) return models
[docs] def create_broker_definition_file(self): broker_file = os.path.join(self.work_dir, 'setup_init.tpb') with open(broker_file, 'w') as w: w.write(multimer_definitions.BROKER_SETUP_STR) return broker_file
[docs] def create_multimer_symmetry_file(self): if self.multimer_modelling == multimer_definitions.DIMER: symfile_name = 'symdef_dimer.dat' symdef = multimer_definitions.SYMFILE_DIMER elif self.multimer_modelling == multimer_definitions.TRIMER: symfile_name = 'symdef_trimer.dat' symdef = multimer_definitions.SYMFILE_TRIMER elif self.multimer_modelling == multimer_definitions.TETRAMER: symfile_name = 'symdef_tetramer.dat' symdef = multimer_definitions.SYMFILE_TETRAMER else: raise RuntimeError("Unrecognised multimer_modelling mode: {}".format(self.multimer_modelling)) symfile_path = os.path.join(self.work_dir, symfile_name) with open(symfile_path, 'w') as w: w.write(symdef) return symfile_path
[docs] def create_multimer_constraints_file(self): restraints_file = os.path.join(self.work_dir, 'multimer_constraints.txt') if self.multimer_modelling == multimer_definitions.DIMER: nchains = 2 elif self.multimer_modelling == multimer_definitions.TRIMER: nchains = 3 elif self.multimer_modelling == multimer_definitions.TETRAMER: nchains = 4 restraint_str = energy_functions.RosettaFunctionConstructs().FLAT_HARMONIC domain_termini_distance = self.sequence_length * 1.5 import string import itertools chains = list(string.ascii_uppercase)[:nchains] fstr = "" for chain1, chain2 in itertools.combinations(chains, 2): # First add the terminal restraints optd = { 'atom1': 'CA', 'res1_seq': 1, 'atom2': 'CA', 'res2_seq': self.sequence_length, 'x0': domain_termini_distance, 'stddev': 3.0, 'tol': 5.0, } fstr += restraint_str.format(**optd) + os.linesep # Now add the restraints between chains for resseq in range(1, self.sequence_length + 1): resseq = str(resseq) optd = { 'atom1': 'CA', 'res1_seq': resseq + chain1, 'atom2': 'CA', 'res2_seq': resseq + chain2, 'x0': 10, 'stddev': 3.0, 'tol': 5.0, } fstr += restraint_str.format(**optd) + os.linesep with open(restraints_file, 'w') as w: w.write(fstr) return restraints_file
[docs] def create_multimer_flagsfile(self, broker_file=None, symmetry_file=None, constraints_file=None): flagsfile = os.path.join(self.work_dir, 'multimer_flagsfile.txt') optd = { 'broker_file': broker_file, 'symdef_file': symmetry_file, 'constraint_file': constraints_file, 'fasta_file': self.fasta, 'frags3': self.frags_3mers, 'frags9': self.frags_9mers, } with open(flagsfile, 'w') as w: w.write(multimer_definitions.FLAGSFILE_STR.format(**optd)) return flagsfile
[docs] def find_binary(self, name): """ Find a rosetta binary on different platforms separate from object as it's currently used by the NMR stuff - which is in dire need of refactoring. """ if not self.rosetta_bin and os.path.isdir(self.rosetta_bin): raise RuntimeError("Cannot find rosetta_bin directory: {}".format(self.rosetta_bin)) binaries = glob.glob(os.path.join(self.rosetta_bin, "{0}.*".format(name))) if not len(binaries): return False # Could check for shortest - for now just return the first binary = os.path.abspath(binaries[0]) if os.path.isfile(binary): return binary return False
[docs] def generate_seeds(self, nseeds, start=1000000, end=4000000): """Generate a list of nseed seeds Parameters ---------- nseeds : int The number of seeds required start : int Beginning of random range nseeds : int End of random range """ assert 0 < nseeds < end - start, "Invalid seed count: {0}".format(nseeds) seeds = set() while len(seeds) < nseeds: seeds.add(random.randint(start, end)) return list(seeds)
[docs] def fragment_cmd(self): """ Return the command to make the fragments as a list """ # It seems that the script can't tolerate "-" in the directory name leading to the fasta file, # so we need to copy the fasta file into the fragments directory and just use the name here fasta = os.path.split(self.fasta)[1] cmd = [self.fragments_exe, '-rundir', self.fragments_directory, '-id', self.name, fasta] if self.transmembrane_old: # cmd += [ '-noporter', '-nopsipred','-sam'] pass else: # version dependent flags if self.rosetta_version == 3.3: # jmht the last 3 don't seem to work with 3.4 cmd += ['-noporter', '-nojufo', '-nosam', '-noprof'] elif self.rosetta_version >= 3.4: cmd += ['-noporter'] # Whether to exclude homologs if not self.use_homs: cmd.append('-nohoms') # Use user-supplied psipred_ss2 file (thanks for Felix for spotting this omission) if self.psipred_ss2: cmd += ['-nopsipred', '-psipredfile', self.psipred_ss2] # Be 'chatty' cmd.append('-verbose') return cmd
[docs] def generate_fragments(self, amoptd): """ Run the script to generate the fragments """ logger.info('----- making fragments--------') if not os.path.exists(self.fragments_directory): os.mkdir(self.fragments_directory) # It seems that the script can't tolerate "-" in the directory name leading to the fasta file, # so we need to copy the fasta file into the fragments directory fasta = os.path.split(self.fasta)[1] shutil.copy2(self.fasta, os.path.join(self.fragments_directory, fasta)) collector = ScriptCollector(None) script = Script(directory=self.fragments_directory, stem="mkfrags") script.append(" ".join(self.fragment_cmd())) collector.add(script) success = self.run_scripts(collector, run_dir=self.fragments_directory, job_time=21600, monitor=None) logfile = "{0}.log".format(os.path.splitext(script)[0]) if not success: logfile = "{0}.log".format(os.path.abspath(os.path.splitext(os.path.basename(script))[0])) msg = "Error generating fragments!\nPlease check the logfile {0}".format(logfile) raise RuntimeError(msg) if self.rosetta_version >= 3.4: # new name format: $options{runid}.$options{n_frags}.$size" . "mers self.frags_3mers = os.path.join(self.fragments_directory, self.name + '.200.3mers') self.frags_9mers = os.path.join(self.fragments_directory, self.name + '.200.9mers') else: # old_name_format: aa$options{runid}$fragsize\_05.$options{n_frags}\_v1_3" self.frags_3mers = os.path.join(self.fragments_directory, 'aa' + self.name + '03_05.200_v1_3') self.frags_9mers = os.path.join(self.fragments_directory, 'aa' + self.name + '09_05.200_v1_3') if not os.path.exists(self.frags_3mers) or not os.path.exists(self.frags_9mers): raise RuntimeError( "Error making fragments - could not find fragment files:\n{}\n{}\nPlease check logfile {} for details.".format( self.frags_3mers, self.frags_9mers, logfile ) ) logger.info('Fragments Done\n3mers at: ' + self.frags_3mers + '\n9mers at: ' + self.frags_9mers + '\n\n') psipred_ss2 = os.path.join(self.fragments_directory, self.name + '.psipred_ss2') if os.path.exists(psipred_ss2): psipred_parser.PsipredSs2Parser(psipred_ss2).check_content() self.psipred_ss2 = psipred_ss2 return
[docs] def get_version(self): """ Return the Rosetta version as a string""" # Get version version = None version_file = os.path.join(self.rosetta_dir, 'README.version') if os.path.exists(version_file): try: for line in open(version_file, 'r'): line.strip() if line.startswith('Rosetta'): tversion = line.split()[1].strip() # version can be 3 digits - e.g. 3.2.4 - we only care about 2 version = float(".".join(tversion.split(".")[0:2])) # logger.info( 'Your Rosetta version is: {0}'.format( version ) ) except Exception as e: logger.critical("Error determining rosetta version from file: {0}\n{1}".format(version_file, e)) return False else: # Version file is absent in 3.5, so we need to use the directory name logger.debug('Version file for Rosetta not found - checking to see if its 3.5 or 3.6') self.rosetta_dir = self.rosetta_dir.rstrip(os.sep) if self.rosetta_dir.endswith("3.5"): version = 3.5 # 3.6 bundles seem to look like: rosetta_2014.30.57114_bundle # elif re.search("rosetta_\d{4}\.\d{2}\.\d{5}_bundle",dirname): # Ignore as people change the directory names - just check for the presence of the folders: elif self._chk36(self.rosetta_dir): version = 3.6 else: logger.debug("Cannot determine rosetta version in directory: %s", self.rosetta_dir) return False logger.info('Rosetta version is: {0}'.format(version)) return version
def _chk36(self, rosetta_dir): # Make sure all the expected directories are found # expected=frozenset(['demos','documentation','main','tools']) expected = frozenset(['demos', 'main', 'tools']) found = frozenset( [os.path.basename(d) for d in os.listdir(rosetta_dir) if os.path.isdir(os.path.join(rosetta_dir, d))] ) if len(expected.intersection(found)) == len(expected): return True else: return False
[docs] def get_bin_dir(self): """Determine the binary directory for the version""" assert ( self.rosetta_version and type(self.rosetta_version) is float ), "self.rosetta_version needs to be set before calling get_bin_dir" assert os.path.isdir(self.rosetta_dir), "self.rosetta_dir needs to have been set before calling get_bin_dir" bin_dir = os.path.join(self.rosetta_dir, 'rosetta_source', 'bin') if self.rosetta_version >= 3.6: bin_dir = os.path.join(self.rosetta_dir, 'main', 'source', 'bin') return bin_dir
[docs] def idealize_cmd(self, pdbin): """Return command to idealize pdbin""" return [self.rosetta_idealize_jd2, "-database", self.rosetta_db, "-s", pdbin]
[docs] def idealize_models(self, models): # Loop through each model, idealise them and get an alignment owd = os.getcwd() idealise_dir = os.path.join(self.work_dir, 'idealised_models') os.mkdir(idealise_dir) os.chdir(idealise_dir) logger.info("Idealising {0} models in directory: {1}".format(len(models), idealise_dir)) collector = ScriptCollector(None) id_pdbs = [] for model in models: name = os.path.splitext(os.path.basename(model))[0] script = Script(directory=idealise_dir, prefix=name, stem="_idealize") script.append(" ".join(self.idealize_cmd(pdbin=model))) # Get the name of the pdb that will be output id_pdbs.append(self.idealize_pdbout(pdbin=model, directory=idealise_dir)) collector.add(script) # Run the jobs success = self.run_scripts(collector, run_dir=idealise_dir, job_time=7200, job_name='idealize', monitor=None) if not success: raise RuntimeError( "Error running ROSETTA in directory: {0}\nPlease check the log files for more information.".format( idealise_dir ) ) os.chdir(owd) return id_pdbs
[docs] def idealize_pdbout(self, pdbin, directory=None): """Return the path to the pdb generated by idealize for pdbin""" pdir, fname = os.path.split(pdbin) if not directory: directory = pdir name, ext = os.path.splitext(fname) return os.path.join(directory, "{0}_0001{1}".format(name, ext))
[docs] def model_from_flagsfile(self, flagsfile, rosetta_executable=None, job_time=43200, consolidate_pdbs=True): """Run ROSETTA modelling from a flagsfile""" assert os.path.isfile(flagsfile), "Cannot find ROSETTA flagsfile: {}".format(flagsfile) # Remember starting directory owd = os.getcwd() os.chdir(self.work_dir) if not os.path.isdir(self.models_dir): os.mkdir(self.models_dir) # Split jobs onto separate processors - 1 for cluster, as many as will fit for desktop if self.submit_qtype != 'local': jobs_per_proc = [1] * self.nmodels else: jobs_per_proc = self.split_jobs(self.nmodels, self.nproc) if rosetta_executable is None: rosetta_executable = self.rosetta_relax_exe seeds = self.generate_seeds(len(jobs_per_proc)) collector = ScriptCollector(None) dir_list = [] for i, njobs in enumerate(jobs_per_proc): if njobs < 1: continue d = os.path.join(self.work_dir, "job_{0}".format(i)) os.mkdir(d) dir_list.append(d) script = Script(directory=d, prefix="job_", stem=str(i)) script.append("cd {}".format(d)) script.append("{} \\".format(rosetta_executable)) script.append("-database {} \\".format(self.rosetta_db)) script.append("@{} \\".format(flagsfile)) script.append("-out:nstruct {} \\".format(njobs)) script.append("-run:constant_seed \\") script.append("-run:jran {}".format(seeds[i])) script.append("cd {}".format(self.work_dir)) collector.add(script) success = self.run_scripts(collector, run_dir=self.work_dir, job_time=job_time, job_name='abinitio') if not success: raise RuntimeError( "Error running ROSETTA in directory: {0}\nPlease check the log files for more information.".format( self.work_dir ) ) # Copy the models into the models directory - need to rename them accordingly _pdbs = [] for d in dir_list: ps = glob.glob(os.path.join(d, "*.pdb")) _pdbs += ps if consolidate_pdbs: # Copy files into the models directory pdbs = [] for i, pdbin in enumerate(_pdbs): pdbout = os.path.join(self.models_dir, "model_{0}.pdb".format(i)) shutil.copyfile(pdbin, pdbout) pdbs.append(pdbout) else: pdbs = _pdbs if not pdbs: msg = ( "No models created after modelling!" + os.linesep + "Please check the log files in the directory {0} " + "for more information." ) raise RuntimeError(msg.format(self.work_dir)) if len(pdbs) != self.nmodels: msg = ( "Expected to create {0} models but found {1}." + os.linesep + "Please check the log files in the directory {2} " "for more information." ) raise RuntimeError(msg.format(self.nmodels, len(pdbs), self.work_dir)) os.chdir(owd) # Go back to where we came from return pdbs
[docs] def mr_cmd(self, template, alignment, nstruct, seed): cmd = [ self.rosetta_mr_protocols, '-database ', self.rosetta_db, '-MR:mode', 'cm', '-in:file:extended_pose', '1', '-in:file:fasta', self.fasta, '-in:file:alignment', alignment, '-in:file:template_pdb', template, '-loops:frag_sizes', '9 3 1', '-loops:frag_files', self.frags_9mers, self.frags_3mers, 'none', '-loops:random_order', '-loops:random_grow_loops_by', '5', '-loops:extended', '-loops:remodel', 'quick_ccd', '-loops:relax', 'relax', '-relax:default_repeats', '4', '-relax:jump_move', 'true', '-cm:aln_format', 'grishin', '-MR:max_gaplength_to_model', '8', '-nstruct', str(nstruct), '-ignore_unrecognized_res', '-overwrite', '-run:constant_seed', '-run:jran', str(seed), ] cmd = self.cmd_add_restraints(cmd) return cmd
[docs] def do_nmr_remodel(self, models): if self.mnr_remodel_fasta and not os.path.isfile(self.mnr_remodel_fasta): raise RuntimeError("Cannot find remodel_fasta: {0}".format(self.mnr_remodel_fasta)) if self.nmr_process_ntimes and not isinstance(self.nmr_process_ntimes, int): raise RuntimeError("ntimes is not an int: {0}".format(self.nmr_process_ntimes)) num_nmr_models = len(models) if not self.nmr_process_ntimes: self.nmr_process_ntimes = 1000 / num_nmr_models num_models = self.nmr_process_ntimes * num_nmr_models logger.info('Processing each model %d times. %d models will be made', self.nmr_process_ntimes, num_models) # Idealize all the nmr models to have standard bond lengths, angles etc id_pdbs = self.idealize_models(models) logger.info('{0} models successfully idealized'.format(len(id_pdbs))) owd = os.getcwd() remodel_dir = os.path.join(self.work_dir, 'remodelling') os.mkdir(remodel_dir) os.chdir(remodel_dir) # Sequence object for idealized models id_seq = sequence_util.Sequence(pdb=id_pdbs[0]) # Get the alignment for the structure - assumes all models have the same sequence if not self.nmr_alignment_file: # fasta sequence of first model remodel_seq = sequence_util.Sequence(fasta=self.mnr_remodel_fasta) alignment_file = align_mafft(remodel_seq, id_seq, logger) else: alignment_file = self.nmr_alignment_file # Remodel each idealized model nmr_process times pdbs_to_return = self.remodel(id_pdbs, self.nmr_process_ntimes, alignment_file) os.chdir(owd) return pdbs_to_return
[docs] @staticmethod def process_cmd_list(cmds): """Create a string from a list of commands""" cmd_str = "" for i, c in enumerate(cmds): if c.startswith('-') and i > 0: cmd_str += os.linesep cmd_str += c + " " return cmd_str
[docs] def remodel(self, id_pdbs, ntimes, alignment_file): remodel_dir = os.getcwd() proc_map = self.remodel_proc_map(id_pdbs, ntimes) seeds = self.generate_seeds(len(proc_map)) dir_list = [] job_time = 7200 collector = ScriptCollector(None) for i, (id_model, nstruct) in enumerate(proc_map): name = "job_{0}".format(i) d = os.path.join(remodel_dir, name) os.mkdir(d) dir_list.append(d) # job script script = Script(directory=d, stem=name) cmd = self.mr_cmd(template=id_model, alignment=alignment_file, nstruct=nstruct, seed=seeds[i]) script.append("cd {}".format(d)) script.append(" \\\n".join(cmd)) script.append("cd {}".format(remodel_dir)) collector.add(script) success = self.run_scripts(collector, run_dir=remodel_dir, job_time=job_time, job_name='remodel', monitor=None) if not success: msg = ( "Error running ROSETTA in directory: {0}." + os.linesep + "Please check the log files for more information." ) raise RuntimeError(msg.format(remodel_dir)) # Copy the models into the models directory - need to rename them accordingly pdbs = [] for d in dir_list: ps = glob.glob(os.path.join(d, "*.pdb")) pdbs += ps if not len(pdbs): msg = ( "No pdbs after remodelling in directory: {0}." + os.linesep + "Please check the log files for more information." ) raise RuntimeError(msg.format(remodel_dir)) # We also need to strip the H atoms as the remodelling occasionally fails to model an H atom, # which leads to pdbs with differing numbers of atoms, which causes theseus to fail. However # H-atoms are unlikely to be useful for our purposes. pdbs_moved = [] for i, remodelled_pdb in enumerate(pdbs): final_pdb = os.path.join(self.models_dir, "model_{0}.pdb".format(i)) logger.debug( "Stripping H atoms from remodelled pdb {0} to create final model: {1}".format(remodelled_pdb, final_pdb) ) pdb_edit.strip(remodelled_pdb, final_pdb, hydrogen=True) pdbs_moved.append(final_pdb) return pdbs_moved
[docs] def remodel_proc_map(self, id_pdbs, ntimes): if self.submit_qtype != 'local': # For clusters we saturate the queue with single model jobs (ideally in batch mode) so that cluster # can manage the usage for us proc_map = [(pdb, 1) for pdb in id_pdbs for _ in range(ntimes)] else: len_id_pdbs = len(id_pdbs) if self.nproc < len_id_pdbs: # if we have fewer processors then pdbs to remodel, each job is an idealised pdb that will be processed # on a processor nmr_process times proc_map = [(pdb, ntimes) for pdb in id_pdbs] else: proc_per_pdb = self.nproc / len( id_pdbs ) # ignore remainder - we're dealing with a shed-load of cpus here if proc_per_pdb == 0: # More cpus then jobs, so we just assign one to each as in parallel case proc_map = [(pdb, 1) for pdb in id_pdbs for _ in range(ntimes)] else: jobs_per_proc = self.split_jobs(ntimes, proc_per_pdb) proc_map = [] for pdb in id_pdbs: for count in jobs_per_proc: proc_map.append( (pdb, count) ) # We need to split things so that each processor does a chunk of the work return proc_map
[docs] def run_scripts(self, collector, run_dir=None, job_time=None, job_name=None, monitor=None): with TaskFactory( self.submit_qtype, collector, directory=run_dir, environment=self.submit_pe, run_time=job_time, name=job_name, processes=self.nprocesses, max_array_size=self.submit_max_array, queue=self.submit_queue, shell="/bin/bash", ) as task: task.run() task.wait(interval=5, monitor_f=monitor) return task.completed
[docs] def setup_domain_restraints(self): """Create the file for restricting the domain termini and return the path to the file.""" logger.info('restricting termini distance: {0}'.format(self.domain_termini_distance)) restraints_file = os.path.join(self.work_dir, 'domain_constraints.txt') optd = { 'atom1': 'CA', 'res1_seq': 1, 'atom2': 'CA', 'res2_seq': self.sequence_length, 'mean': self.domain_termini_distance, 'stddev': 5.0, } restraint = energy_functions.RosettaFunctionConstructs().GAUSSIAN.format(**optd) with open(restraints_file, "w") as w: w.write(restraint + os.linesep) return restraints_file
[docs] def set_from_dict(self, optd): """Set the values from a dictionary""" # Common variables self.fasta = optd['fasta'] self.sequence_length = optd['fasta_length'] self.name = optd['name'] self.nmodels = optd['nmodels'] self.nproc = optd['nproc'] # Directories self.ample_dir = optd['work_dir'] self.work_dir = os.path.join(self.ample_dir, 'modelling') if not optd['models_dir']: self.models_dir = os.path.join(self.ample_dir, "models") else: self.models_dir = optd['models_dir'] # psipred secondary structure prediction if optd['psipred_ss2']: if not os.path.isfile(optd['psipred_ss2']): raise RuntimeError("Cannot find psipred_ss2 file: {}".format(optd['psipred_ss2'])) self.psipred_ss2 = optd['psipred_ss2'] if not optd['make_frags']: self.frags_3mers = optd['frags_3mers'] self.frags_9mers = optd['frags_9mers'] if not (os.path.exists(self.frags_3mers) and os.path.exists(self.frags_9mers)): raise RuntimeError( "Cannot find both fragment files:\n{0}\n{1}\n".format(self.frags_3mers, self.frags_9mers) ) else: # Fragment variables self.use_homs = optd['use_homs'] self.fragments_directory = os.path.join(self.work_dir, 'rosetta_fragments') # Extra modelling options self.all_atom = optd['all_atom'] self.domain_termini_distance = optd['domain_termini_distance'] self.rad_gyr_reweight = optd['rg_reweight'] if optd['improve_template']: if not os.path.exists(optd['improve_template']): raise RuntimeError('cant find template to improve') self.improve_template = optd['improve_template'] if optd['restraints_file']: if not os.path.exists(optd['restraints_file']): raise RuntimeError("Cannot find restraints file: {0}".format(optd['restraints_file'])) self.restraints_file = optd['restraints_file'] self.restraints_weight = optd['restraints_weight'] if optd['disulfide_constraints_file']: if not os.path.exists(optd['disulfide_constraints_file']): raise RuntimeError( "Cannot find disulfide constraints file: {0}".format(optd['disulfide_constraints_file']) ) self.disulfide_constraints_file = optd['disulfide_constraints_file'] if optd['rosetta_flagsfile']: self.rosetta_flagsfile = optd['rosetta_flagsfile'] # NMR options self.nmr_remodel = optd['nmr_remodel'] self.nmr_process_ntimes = optd['nmr_process'] self.nmr_alignment_file = optd['alignment_file'] self.mnr_remodel_fasta = optd['nmr_remodel_fasta'] # Multimer modelling self.multimer_modelling = optd['multimer_modelling'] self.num_chains = optd['nmasu'] # Runtime options self.submit_qtype = optd['submit_qtype'] self.nprocesses = optd['nproc'] self.submit_max_array = optd['submit_max_array'] self.submit_queue = optd['submit_queue'] self.submit_array = optd['submit_array'] self.submit_pe = optd['submit_pe'] if optd['transmembrane_old']: self.transmembrane_old = True if optd['blast_dir']: blastpgp = os.path.join(optd['blast_dir'], "bin/blastpgp") self.blastpgp = ample_util.find_exe(blastpgp) if self.blastpgp: logger.debug("Using user-supplied blast_dir for blastpgp executable: {0}".format(self.blastpgp)) # nr database if optd['nr']: if not os.path.exists(optd['nr'] + ".pal"): raise RuntimeError( "Cannot find the nr database: {0}\n" "Please give the location with the nr argument to the script.".format(optd['nr']) ) else: self.nr = optd['nr'] if self.nr: logger.debug("Using user-supplied nr database: {0}".format(self.nr)) self.spanfile = optd['transmembrane_spanfile'] self.lipofile = optd['transmembrane_lipofile'] self.octopusTopology = optd['transmembrane_octopusfile'] # Check if we've been given files if self.octopusTopology and not (os.path.isfile(self.octopusTopology)): msg = "Cannot find provided transmembrane octopus topology prediction: {0}".format(self.octopusTopology) raise RuntimeError(msg) if self.spanfile and not os.path.isfile(self.spanfile): msg = "Cannot find provided transmembrane spanfile: {0}".format(self.spanfile) raise RuntimeError(msg) if self.lipofile and not (os.path.isfile(self.lipofile)): msg = "Cannot find provided transmembrane lipofile: {0}".format(self.lipofile) raise RuntimeError(msg) if (self.spanfile and not self.lipofile) or (self.lipofile and not self.spanfile): msg = "You need to provide both a spanfile and a lipofile" raise RuntimeError(msg) elif optd['transmembrane']: self.transmembrane = True # End transmembrane checks return
[docs] def set_paths(self, optd=None, rosetta_dir=None): if rosetta_dir and os.path.isdir(rosetta_dir): self.rosetta_dir = rosetta_dir elif 'rosetta_dir' not in optd or not optd['rosetta_dir']: raise RuntimeError( "rosetta_dir not set - please use the -rosetta_dir flag to point at the directory where ROSETTA is installed" ) elif not os.path.isdir(optd['rosetta_dir']): raise RuntimeError( "Cannot find rosetta_dir directory: {0}\nPlease set the correct rosetta_dir variable to point at the top Rosetta directory.".format( optd['rosetta_dir'] ) ) else: self.rosetta_dir = optd['rosetta_dir'] # Determine version if optd and 'rosetta_version' in optd and optd['rosetta_version'] is not None: logger.debug('Using user-supplied Rosetta version: {0}'.format(optd['rosetta_version'])) version = optd['rosetta_version'] else: version = self.get_version() if not version: raise RuntimeError('Cannot determine Rosetta version in directory: {0}'.format(self.rosetta_dir)) self.rosetta_version = version # Find the path to the binary directory self.rosetta_bin = self.get_bin_dir() # Now set all relevant paths # Rosetta db if optd and optd['rosetta_db'] and os.path.isfile(optd['rosetta_db']): self.rosetta_db = optd['rosetta_db'] else: if self.rosetta_version < 3.6: self.rosetta_db = os.path.join(self.rosetta_dir, 'rosetta_database') else: self.rosetta_db = os.path.join(self.rosetta_dir, 'main', 'database') if not os.path.exists(self.rosetta_db): raise RuntimeError('cannot find Rosetta DB: {0}'.format(self.rosetta_db)) self.rosetta_relax_exe = self.find_binary('AbinitioRelax') if not self.rosetta_relax_exe: raise RuntimeError("Cannot find ROSETTA AbinitioRelax binary in: {0}".format(self.rosetta_bin)) # Set path to script if optd and optd['rosetta_fragments_exe'] and os.path.isfile(optd['rosetta_fragments_exe']): self.fragments_exe = optd['rosetta_fragments_exe'] else: if self.rosetta_version == 3.3: self.fragments_exe = os.path.join(self.rosetta_dir, 'rosetta_fragments', 'make_fragments.pl') elif self.rosetta_version == 3.4 or self.rosetta_version == 3.5: self.fragments_exe = os.path.join( self.rosetta_dir, 'rosetta_tools', 'fragment_tools', 'make_fragments.pl' ) elif self.rosetta_version == 3.6: self.fragments_exe = os.path.join(self.rosetta_dir, 'tools', 'fragment_tools', 'make_fragments.pl') # for nmr self.rosetta_mr_protocols = self.find_binary('mr_protocols') self.rosetta_idealize_jd2 = self.find_binary('idealize_jd2') self.rosetta_minirosetta_exe = self.find_binary('minirosetta') if optd and optd['transmembrane_old']: self.tm_set_paths(optd)
[docs] def split_jobs(self, njobs, nproc): """ Return a list of number of jobs to run on each processor """ if njobs < nproc: return [1] * njobs split_jobs = njobs / nproc # split jobs between processors remainder = njobs % nproc jobs = [] for _ in range(nproc): njobs = split_jobs # Separate out remainder over jobs if remainder > 0: njobs += 1 remainder -= 1 jobs.append(njobs) return jobs
[docs] def tm_make_files(self): """ Generate the various files needed for modelling transmembrane proteins REM the fasta as it needs to reside in this directory or the script may fail due to problems with parsing directory names with 'funny' characters """ # Files have already been created if os.path.isfile(str(self.spanfile)) and os.path.isfile(str(self.lipofile)): logger.debug("Using given span file: {0}\n and given lipo file: {1}".format(self.spanfile, self.lipofile)) return owd = os.getcwd() # Remember where we started tm_dir = os.path.join(self.work_dir, 'tm_predict') os.mkdir(tm_dir) os.chdir(tm_dir) # It seems that the script can't tolerate "-" in the directory name leading to the fasta file, # so we need to copy the fasta file into the fragments directory fasta = os.path.split(self.fasta)[1] shutil.copy2(self.fasta, os.path.join(tm_dir, fasta)) # See if we need to query the octopus server if os.path.isfile(str(self.octopusTopology)): logger.info("Using user-supplied topology prediction file: {0}".format(self.octopusTopology)) else: # Query octopus server for prediction octo = octopus_predict.OctopusPredict() logger.info( "Generating predictions for transmembrane regions using octopus server: {0}".format(octo.octopus_url) ) # fastaseq = octo.getFasta(self.fasta) # Problem with 3LBW prediction when remove X fastaseq = octo.getFasta(self.fasta) octo.getPredict(self.name, fastaseq, directory=tm_dir) self.octopusTopology = octo.topo logger.debug("Got topology prediction file: {0}".format(self.octopusTopology)) # Generate span file from predict self.spanfile = os.path.join(tm_dir, self.name + ".span") logger.debug('Generating span file {0}'.format(self.spanfile)) cmd = [self.octopus2span, self.octopusTopology] retcode = ample_util.run_command(cmd, logfile=self.spanfile, directory=tm_dir) if retcode != 0: raise RuntimeError("Error generating span file. Please check the log in {0}".format(self.spanfile)) # Now generate lips file logger.debug('Generating lips file from span') collector = ScriptCollector(None) lips_script = Script(directory=tm_dir, stem="run_lips") cmd = [self.run_lips, fasta, self.spanfile, self.blastpgp, self.nr, self.align_blast] lips_script.append(" ".join(cmd)) collector.add(lips_script) success = self.run_scripts(collector, run_dir=tm_dir, job_time=21600, monitor=None) # Script only uses first 4 chars to name files lipofile = os.path.join(tm_dir, self.name[0:4] + ".lips4") if not success or not os.path.exists(lipofile): logfile = "run_lips.log" raise RuntimeError("Error generating lips file {0}. Please check the logfile {1}".format(lipofile, logfile)) self.lipofile = lipofile os.chdir(owd)
[docs] def tm2_make_patch(self, work_dir): wts_str = """fa_atr = 0.8 fa_rep = 0.8 fa_sol = 0.0 """ self.tm_patch_file = os.path.abspath(os.path.join(work_dir, 'membrane.wts')) with open(self.tm_patch_file, 'w') as w: w.write(wts_str) return
[docs] def tm_set_paths(self, optd): # Transmembrane stuff mem_bin = 'membrane_abinitio2' self.transmembrane_exe = self.find_binary(mem_bin) if not self.transmembrane_exe: raise RuntimeError("Cannot find ROSETTA {0} binary in: {1}".format(mem_bin, self.rosetta_bin)) if self.rosetta_version < 3.6: tm_script_dir = os.path.join( self.rosetta_dir, 'rosetta_source', 'src', 'apps', 'public', 'membrane_abinitio' ) else: tm_script_dir = os.path.join(self.rosetta_dir, 'tools', 'membrane_tools') self.octopus2span = os.path.join(tm_script_dir, "octopus2span.pl") self.run_lips = os.path.join(tm_script_dir, "run_lips.pl") self.align_blast = os.path.join(tm_script_dir, "alignblast.pl") self.octopusTopology = optd['transmembrane_octopusfile'] self.lipofile = optd['transmembrane_lipofile'] self.spanfile = optd['transmembrane_spanfile'] # Check if we've been given files if self.octopusTopology and not (os.path.isfile(self.octopusTopology)): raise RuntimeError( "Cannot find provided transmembrane octopus topology prediction: {0}".format(self.octopusTopology) ) if self.spanfile and not (os.path.isfile(self.spanfile)): raise RuntimeError("Cannot find provided transmembrane spanfile: {0}".format(self.spanfile)) if self.lipofile and not (os.path.isfile(self.lipofile)): raise RuntimeError("Cannot find provided transmembrane lipofile: {0}".format(self.lipofile)) if (self.spanfile and not self.lipofile) or (self.lipofile and not self.spanfile): raise RuntimeError("You need to provide both a spanfile and a lipofile") if not (self.spanfile and self.lipofile): # We only need to check these if we haven't been given a spanfile and lipofile if ( not os.path.exists(self.octopus2span) or not os.path.exists(self.run_lips) or not os.path.exists(self.align_blast) ): raise RuntimeError( "Cannot find the required executables: octopus2span.pl ,run_lips.pl and align_blast.pl in the directory\n" + "{0}\nPlease check these files are in place".format(tm_script_dir) ) if optd['blast_dir']: blastpgp = os.path.join(optd['blast_dir'], "bin/blastpgp") self.blastpgp = ample_util.find_exe(blastpgp) if self.blastpgp: logger.debug("Using user-supplied blast_dir for blastpgp executable: {0}".format(self.blastpgp)) # nr database if optd['nr']: if not os.path.exists(optd['nr'] + ".pal"): raise RuntimeError( "Cannot find the nr database: {0}\nPlease give the location with the nr argument to the script.".format( optd['nr'] ) ) else: self.nr = optd['nr'] if self.nr: logger.debug("Using user-supplied nr database: {0}".format(self.nr)) if not (self.nr and self.blastpgp): if self.rosetta_version > 3.5: blastpgp = os.path.join(self.rosetta_dir, 'tools', 'fragment_tools', 'blast', 'bin', 'blastpgp') nr = os.path.join(self.rosetta_dir, 'tools', 'fragment_tools', 'databases', 'nr') if not (os.path.exists(blastpgp) and os.path.exists(nr + '.pal')): msg = ( "Cannot find blastpgp executable and nr database requried for transmembrane modelling\n" + "Please try running the {0} script to download these for rosetta.".format( os.path.join(self.rosetta_dir, 'tools', 'fragment_tools', 'install_dependencies.pl') ) ) raise RuntimeError(msg) else: self.blastpgp = blastpgp self.nr = nr if self.blastpgp: logger.debug("Using blastpgp executable: {0}".format(self.blastpgp)) if self.nr: logger.debug("Using nr database: {0}".format(self.nr)) else: msg = ( "Cannot find blastpgp executable and nr database requried for transmembrane modelling\n" + "Please install blast and the nr database and use the -blast_dir and -nr_dir options to ample." ) raise RuntimeError(msg) return