"""
Prime homology modeling functions.
Copyright Schrodinger, LLC. All rights reserved.
"""
# Contributors: Piotr Rotkiewicz
import os
import tempfile
from . import constants
from . import dialogs
from . import fileio
from . import jobs
from . import maestro as maestro_helpers
from .align import align
from .prime_gui import getPrimeLigandsForEntryID
try:
    from schrodinger.maestro import maestro
except:
    maestro = None
try:
    from schrodinger.job import jobcontrol
except ImportError:
    jobcontrol = None
try:
    from schrodinger.infra import mm
except ImportError:
    mm = None
try:
    import _pymmlibs
except ImportError:
    _pymmlibs = None
[docs]def prime_get_default_settings():
    """
    Returns a dictionary with default Prime settings.
    """
    parameters = {}
    parameters["preview_model"] = False
    parameters["keep_rotamers"] = True
    parameters["optimize_side_chains"] = True
    parameters["minimize_residues"] = True
    parameters["build_insertions"] = True
    parameters["max_insertion_length"] = 20
    parameters["build_transitions"] = True
    parameters["build_deletions"] = True
    parameters["knowledge_based"] = False
    parameters["num_output_struct"] = 10
    parameters["template_residue_numbers"] = False
    return parameters 
[docs]def prime_build_consensus_model(sequence_group,
                                progress_dialog=None,
                                job_settings="",
                                viewer=None):
    """
    Builds a Prime consensus homology model.
    """
    if not sequence_group or not sequence_group.reference:
        return False
    # Get Schrodinger path.
    schrodinger_path = jobs.getSchrodingerPath()
    if schrodinger_path is None:
        return False
    query = sequence_group.reference
    template_list = []
    for seq in sequence_group.sequences:
        if seq.type == constants.SEQ_AMINO_ACIDS:
            ssa = None
            for child in seq.children:
                if child.type == constants.SEQ_SECONDARY:
                    ssa = child
                    break
            if seq != query and seq.has_structure and seq.selected:
                if seq.from_maestro:
                    # Attempt to extract structural data from a Maestro
                    # structure.
                    if not maestro_helpers.maestroExtractStructureData(seq):
                        continue
                template_list.append((seq, ssa))
    if len(template_list) < 2:
        return False
    # Store current directory.
    cwd = os.getcwd()
    tmpdir = None
    if job_settings and job_settings["temporary_dir"]:
        # Create a temporary directory where the job will run and change
        # current directory to temporary.
        try:
            tmpdir = tempfile.mkdtemp()
            os.chdir(tmpdir)
        except:
            dialogs.error_dialog(
                "Cannot Run Job",
                "Cannot create a temporary directory for Prime job.")
            return False
    # This is a return status.
    status = True
    input_file_name = "consensus_model.inp"
    alignment_file_name = "consensus_model.align"
    try:
        alignment_file = open(alignment_file_name, "w")
    except:
        status = False
    if status:
        try:
            name = ">" + \
                
query.short_name[:4].upper() + "_" + query.chain_id.upper()
            alignment_file.write(">" + query.short_name + "\n")
            alignment_file.write(
                query.text().replace('-', '~').replace('X', '~') + "\n")
            for seq, ssa in template_list:
                chain_id = seq.chain_id.upper()
                if chain_id <= ' ':
                    chain_id = 'A'
                tmpl_name = seq.short_name[:4].upper() + "_" + chain_id
                name = ">" + tmpl_name + " "
                alignment_file.write(name + "Chain,\n")
                alignment_file.write(
                    seq.text().replace('-', '~').replace('X', '~') + "\n")
                alignment_file.write(name + "ssa,\n")
                alignment_file.write(ssa.text().replace(' ', '~') + "\n")
                structure_file = open(tmpl_name + ".pdb", "w")
                for res in seq.residues:
                    if not res.is_gap:
                        res_lines = []
                        # In case chain_id = ' ' replace it with 'A'
                        for line in res.structure:
                            if len(line) > 21 and line[21] != chain_id[0]:
                                new_line = list(line)
                                new_line[21] = chain_id[0]
                                line = "".join(new_line)
                            res_lines.append(line)
                        structure_file.writelines(res_lines)
                structure_file.write("TER\nEND\n")
                structure_file.close()
            alignment_file.close()
        except:
            status = False
    if status:
        try:
            input_file = open(input_file_name, "w")
        except:
            status = False
    if status:
        try:
            input_file.write("ALIGN_FILE consensus_model.align\n")
            input_file.close()
        except:
            status = False
    if status:
        # Run Prime consensus_homology program.
        job_command = schrodinger_path + "consensus_homology"
        current_entries = maestro_helpers.maestroGetListOfEntryIDs()
        # Launch the job and wait until it incorporates. Pass all input
        # file names to Prime as command line parameters.
        job_result = jobs.run_job_via_jobcontrol(
            [job_command] + ["consensus_model"],
            "consensus_model.log",
            dialog_title="Building Consensus Homology Model",
            progress_dialog=progress_dialog,
            settings=job_settings)
        # Check the job return status.
        if job_result == jobs.JOB_STATUS_MISSING:
            # Missing or invalid job control module.
            dialogs.error_dialog("Job Failed",
                                 "Could not run homology modeling job.")
            status = False
        elif job_result == jobs.JOB_STATUS_FAILED:
            # Job could not be launched (probably missing backend).
            dialogs.error_dialog(
                "Job Failed", "Could not build homology model.\n"
                "Do you have Prime installed?")
            status = False
        elif job_result == jobs.JOB_STATUS_KILLED:
            # Job was interrupted.
            dialogs.error_dialog("Job Interrupted",
                                 "Homology modeling job was interrupted.")
            status = False
    if status:
        if maestro:
            # Incorporate the model into Maestro.
            model_file_name = "consensus_model-out.maegz"
            if os.path.isfile(model_file_name):
                try:
                    # Get Maestro entry of the built model.
                    entry_id = maestro_helpers.getEntryByJobName(
                        "consensus_model")
                    if not entry_id:
                        # Preserve wsreplace setting.
                        wsreplace = maestro.get_command_option(
                            "entryimport", "wsreplace")
                        # Incorporate model, but leave other structures
                        # included in workspace.
                        maestro.command(
                            "entryimport wsreplace=true format=maestro \"" +
                            model_file_name + "\"")
                        # Restore original value for wsreplace.
                        maestro.command("entryimport" + " wsreplace=" +
                                        wsreplace)
                    if entry_id:
                        # Create a model name.
                        model_name = "Model of " + query.short_name
                        model_name = model_name.rstrip()
                        # Rename the entry and set a new title.
                        maestro.command("entrysetprop "
                                        "property=s_m_title value=\"" +
                                        model_name + "\" entry \"" +
                                        str(entry_id) + "\"")
                        maestro.command("entrysetprop "
                                        "property=s_m_entry_name value=\"" +
                                        model_name + "\" entry \"" +
                                        str(entry_id) + "\"")
                        # Propagate current MSV colors to the structure.
                        for group in [sequence_group]:
                            group.unmarkTemplateRegions()
                            reference = group.reference
                            if not reference:
                                continue
                            model_name = "Model of " + reference.short_name
                            model_name = model_name.rstrip()
                            # Incorporate the model entry into a current group.
                            maestro_helpers.maestroIncorporateEntries(
                                group, what="all", ignore=current_entries)
                            # Get all sequences associated with this entry.
                            sequences = group.getMaestroSequencesForEntryId(
                                entry_id)
                            for seq in sequences:
                                seq.name = model_name
                                seq.short_name = model_name
                                # Propagate gaps from query sequence to the model
                                # sequence.
                                new_residues = reference.propagateGaps(
                                    seq, parent_sequence=seq)
                                if not new_residues:
                                    seq.visible = False
                                    continue
                                seq.residues = new_residues
                                seq.propagateGapsToChildren()
                                # Align the model sequence with the reference
                                align(reference, seq)
                        # Fit to workspace.
                        maestro.command("fit")
                        if viewer:
                            maestro_helpers.synchronizePropertiesWithMaestro(
                                viewer, colors=True)
                            viewer.setColorMode(constants.COLOR_MAESTRO)
                            viewer.contents_changed = True
                            viewer.updateView()
                except:
                    pass
    # Change current directory back to the original directory and remove
    # temporary files and directory.
    try:
        os.chdir(cwd)
        if job_settings:
            keep_files = job_settings["keep_files"]
        else:
            keep_files = False
        if tmpdir and not keep_files:
            jobs.removeTmpJobDir(tmpdir, keep=[model_file_name])
    except:
        dialogs.error_dialog("Cannot Remove Temporary Files",
                             "Cannot remove temporary Prime job files."),
        status = False
    return status 
[docs]def prime_run(
        settings=None,
        mode=constants.PRIME_MODE_SINGLE,
        group_list=[],  # noqa: M511
        valid_template_list=[],  # noqa: M511
        progress_dialog=None,
        viewer=None,
        job_settings=""):
    """
    This function is used to create homology models. It generates input files
    for Prime, runs the Prime job, and incorporates built models into Maestro.
    This function is called when the user clicks on "Build Model" button
    in Build Homology Model panel.
    There are several possible homology modeling scenarios that need to be
    handled by this function:
    1) modeling using a single query and a single template,
    2) modeling using a single query and multiple templates with selected
    template regions: all templates are used to build a single composite
    model,
    3) modeling using a single query and multiple templates in order to build
    a homo-multimer: each template is used to create an individual model,
    but query sequence is identical for each of the models,
    4) modeling using multiple queries and multiple templates: each query /
    template pair is treated independently in order to build a hetero-multimer.
    In addition, there are possible mixed scenarios, e.g. building
    a hetero-multimer consisting of homo-multimers.
    :note: We are using "query" and "sequence group" as synonyms. A sequence
        group can include a single query sequence and several template sequences.
        The "reference" and "query sequence" are also used as synonyms.
    :type sequence_group: `SequenceGroup`
    :param sequence_group: A default (current) sequence group.
    :rtype: boolean
    :return: True on successful job completion, False if the job could not
        be completed.
    """
    # MSV doesn't allow to run two jobs simultaneously. Quit if another
    # job is already running.
    if jobs.checkJobIsRunning():
        return False
    # If there are no queries selected in Prime settings panel
    # use current sequence group for building the model.
    if not group_list:
        return False
    sequence_group = group_list[0]
    # Get Schrodinger path.
    schrodinger_path = jobs.getSchrodingerPath()
    if schrodinger_path is None:
        return False
    if mode == constants.PRIME_MODE_CONSENSUS:
        return prime_build_consensus_model(group_list[0],
                                           progress_dialog=progress_dialog,
                                           job_settings=job_settings)
    # Note: in current implementation a job is run in a temporary directory
    # that is removed after the job is completed. This behavior may be changed
    # in future versions.
    # Store current directory.
    cwd = os.getcwd()
    tmpdir = None
    if job_settings and job_settings["temporary_dir"]:
        # Create a temporary directory where the job will run and change
        # current directory to temporary.
        try:
            tmpdir = tempfile.mkdtemp()
            os.chdir(tmpdir)
        except:
            dialogs.error_dialog(
                "Cannot Run Job",
                "Cannot create a temporary directory for Prime job.")
            return False
    # This is a return status.
    status = True
    # Extract Prime parameters from a parameter list (the parameters are
    # stored in Prime settings panel).
    quick_build_mode = settings["preview_model"]
    keep_rotamers = settings["keep_rotamers"]
    side_opt = settings["optimize_side_chains"]
    minimize = settings["minimize_residues"]
    build_insertions = settings["build_insertions"]
    max_insertion_size = settings["max_insertion_length"]
    build_transitions = settings["build_transitions"]
    build_deletions = settings["build_deletions"]
    knowledge_based = settings["knowledge_based"]
    num_output_struct = settings["num_output_struct"]
    template_residue_numbers = settings["template_residue_numbers"]
    if quick_build_mode:
        side_opt = False
        minimize = False
        build_insertions = False
        build_transitions = False
        build_deletions = False
        max_insertion_size = 0
    if not build_insertions:
        max_insertion_size = 0
    # List of input file name prefixes.
    input_file_list = []
    n_templates = len(valid_template_list)
    if mode == constants.PRIME_MODE_COMPOSITE and n_templates > 1:
        answer = dialogs.question_dialog(
            "Structure Alignment",
            "Composite/chimera models require that the templates "
            "be structurally aligned prior to Model Building.\n\n"
            "Do you want to structurally align the templates?")
        if answer == "yes":
            maestro.command("entrywsexclude all")
            maestro.command("showpanel structalign")
            for template in valid_template_list:
                maestro.command("entrywsinclude entry " +
                                str(template.maestro_entry_id))
            maestro.command("structalignstart all")
            maestro.command("hidepanel structalign")
        elif answer == "cancel":
            return False
    all_template_names = ""
    global_template_index = 0
    # Write input files for each query sequence.
    # Iterate over all sequence groups.
    for query_index, group in enumerate(group_list):
        if not status:
            # Break if something went wrong.
            break
        # Check if the group includes a valid reference (query) sequence.
        reference = group.reference
        if reference is None:
            # No reference, so ignore this group.
            continue
        # print "DEBUG query", query_index, reference.name
        # Make sure all sequences have identical lengths by adding gaps
        # to the end of the sequence.
        group.removeTerminalGaps()
        group.padAlignment()
        # Build a list of templates. Valid template sequences need to have
        # structural information associated with them, and cannot be
        # a query (reference) sequence.
        template_list = []
        for seq in group.sequences:
            if seq.type == constants.SEQ_AMINO_ACIDS:
                if seq.isValidTemplate(reference=reference):
                    if seq.from_maestro:
                        # Attempt to extract structural data from a Maestro
                        # structure.
                        if not maestro_helpers.maestroExtractStructureData(seq):
                            continue
                    template_list.append(seq)
                    # print "DEBUG template", seq.name
        if len(template_list) == 0:
            # No valid templates, so just ignore this query.
            continue
        # Make a list of selected sequences.
        selected_template_list = [seq for seq in template_list if seq.selected]
        # Make a list of templates with marked regions.
        marked_template_list = []
        if len(selected_template_list) == 0:
            # If no sequences are selected we are either building
            # a composite model using marked template regions, or just
            # a single-template model.
            # If residues are marked in a template sequence, append this
            # sequence to marked templates list.
            for template in template_list:
                for res in template.residues:
                    # Is the residue marked?
                    if res.model:
                        marked_template_list.append(template)
                        break
            if len(marked_template_list) > 0:
                # We have at least single template with marked regions.
                if len(marked_template_list) > 9:
                    # Prime input format can only support 9 or less templates
                    # for building the composite model, so we need to trim
                    # the list of templates if there are too many. This is
                    # very unlikely to happen.
                    dialogs.warning_dialog(
                        "Building Homology Model"
                        "Only 9 first templates will be used "
                        "for building the composite model.")
                    # Trim the marked template list.
                    marked_template_list = marked_template_list[:9]
                # Use the marked template list for modeling.
                template_list = marked_template_list
            else:
                # We don't have any selected sequences nor marked residues.
                # Use a first available template for modeling.
                template_list = template_list[:1]
        else:
            # We have selected templates - assume we are going to build
            # a homo-multimer (or just a single chain model based on a
            # single selected template).
            template_list = selected_template_list
        # Iterate over the templates.
        for template_index, template in enumerate(template_list):
            # print "DEBUG using template", template_index, template.name
            if template_index == 0 or len(selected_template_list) > 1:
                # Initialize Prime input file only if this is first template
                # or if we are building a homo-multimer (number of selected
                # template sequences > 1).
                # Make a list of lines to be written to Prime input file.
                input_file_lines = [
                    "JOB_TYPE        MODEL\n", "QUERY_OFFSET    0\n"
                ]
            chain_id = template.chain_id
            global_template_index += 1
            template_name = "template%d" % (global_template_index)
            if chain_id != ' ':
                template_name += '_' + chain_id
            # Write template info.
            input_file_lines.append("TEMPLATE_NAME   %s\n" % template_name)
            input_file_lines.append("%s_NUMBER %d\n" %
                                    (template_name, template_index + 1))
            input_file_lines.append("%s_STRUCT_FILE %s.ent\n" %
                                    (template_name, template_name))
            input_file_lines.append("%s_ALIGN_FILE %s.aln\n" %
                                    (template_name, template_name))
            # Get list of all ligands associated with the template entry.
            # all_ligand_names include the entry ligand names in a
            # form of entry_name + ' ' + residue_name + ' '  +
            # chain_name + ':' + residue_number
            all_ligand_names, all_ligand_entries = maestro_helpers.\
                
getMaestroLigandList(template_list=[template])
            # Write a template PDB file.
            structure_file_name = template_name + ".ent"
            structure_file = open(structure_file_name, "w")
            # The Residue.structure list keeps structure information
            # in PDB format, so we just need to write out these lines.
            # Also, create template sequence and make sure query
            # sequence does match it.
            query_seq_txt = ''
            original_query_seq_txt = group.reference.text()
            template_seq_txt = ''
            for index, res in enumerate(template.residues):
                test_resname = res.getSignature()
                if any(test_resname in name for name in all_ligand_names):
                    # Skip this alignment column because the template
                    # residue is a ligand, i.e. test_resname partially
                    # matches one of items in all_ligand_names.
                    continue
                if res.structure:
                    structure_file.writelines(res.structure)
                    template_seq_txt += res.code
                else:
                    # For structureless residues write gap instead
                    template_seq_txt += '.'
                query_seq_txt += original_query_seq_txt[index]
            # Create query sequence line. Replace gaps with dots.
            query_seq_txt = 'ProbeAA: ' + \
                
query_seq_txt.replace('~', '.').replace('-', '.') + '\n'
            # Create template sequence line. Replace gaps with dots.
            template_seq_txt = 'Fold AA: ' + \
                 
template_seq_txt.replace('~', '.').replace('-', '.') + '\n'
            structure_file.writelines(["TER\n"])
            ligand_crosslinks = ""
            lig_index = 0
            if hasattr(group, 'ligand_list') and group.ligand_list:
                ligands = getPrimeLigandsForEntryID(template.maestro_entry_id)
                # group.ligand_list stores the ligands selected in the GUI
                # 'ligands' is the full list of ligand ct that belong to
                # the current entry
                ligand_names, ligand_constraints = list(zip(*group.ligand_list))
                for ligand in ligands:
                    if len(ligand.atom) == 0:
                        continue
                    ligname = ligand.atom[1].pdbres[:3] + ' ' + \
                        
ligand.atom[1].chain + ':' + \
                        
str(ligand.atom[1].resnum)
                    if ligname not in list(ligand_names):
                        continue
                    input_file_lines.append(
                        "%s_HETERO_%d %s %c:%d\n" %
                        (template_name, lig_index, ligand.atom[1].pdbres[:3],
                         ligand.atom[1].chain, ligand.atom[1].resnum))
                    ligand_lines = []
                    for atom in ligand.atom:
                        pdbline = "HETATM%5d %-4s %4s%c%4d%c   %8.3f%8.3f%8.3f\n" % \
                            
(atom.index, atom.pdbname, atom.pdbres, atom.chain,
                             atom.resnum, atom.inscode, atom.x, atom.y, atom.z)
                        ligand_lines.append(pdbline)
                    ligand_lines.append("TER\n")
                    structure_file.writelines(ligand_lines)
                    # Extract the ligand constraints if they are defined
                    constraint_seq = ligand_constraints[ligand_names.index(
                        ligname)]
                    for index, res in enumerate(
                            constraint_seq.gaplessResidues()):
                        if hasattr(res, 'query_constraint'):
                            ligand_crosslinks += " A:" + str(index + 1) + \
                                                 
" Z:" + str(500 + lig_index)
                    lig_index += 1
            structure_file.writelines(["END\n"])
            structure_file.close()
            # Write aligment file for this template.
            alignment_file_name = template_name + ".aln"
            try:
                alignment_file = open(alignment_file_name, "w")
                alignment_file.writelines(query_seq_txt)
                alignment_file.writelines(template_seq_txt)
                alignment_file.close()
            except IOError:
                dialogs.error_dialog("Cannot Build Homology Model",
                                     "Error writing alignment file.")
                status = False
                break
            if template_index == len(template_list) - 1 or \
               
len(selected_template_list) > 1:
                # Write Prime input file only if we iterated over all templates
                # or if we are building a homo-multimer (number of selected
                # template sequences > 1).
                # Write out Prime settings.
                if tmpdir:
                    input_file_lines.append("DIR_NAME        %s\n" % tmpdir)
                else:
                    input_file_lines.append("DIR_NAME        %s\n" % cwd)
                input_file_lines.append("TAILS   0\n")
                input_file_lines.append("SIDE_OPT\t" + str(side_opt).lower() +
                                        "\n")
                input_file_lines.append("MINIMIZE\t" + str(minimize).lower() +
                                        "\n")
                input_file_lines.append("TEMPLATE_RESIDUE_NUMBERS\t" +
                                        str(template_residue_numbers).lower() +
                                        "\n")
                input_file_lines.append("BUILD_INSERTIONS\t" +
                                        str(build_insertions).lower() + "\n")
                input_file_lines.append("MAX_INSERTION_SIZE\t" +
                                        str(max_insertion_size).lower() + "\n")
                input_file_lines.append("BUILD_TRANSITIONS\t" +
                                        str(build_transitions).lower() + "\n")
                input_file_lines.append("BUILD_DELETIONS\t" +
                                        str(build_deletions).lower() + "\n")
                input_file_lines.append("KEEP_ROTAMERS\t" +
                                        str(keep_rotamers).lower() + "\n")
                if knowledge_based:
                    input_file_lines.append("KNOWLEDGE_BASED\ttrue\n")
                    input_file_lines.append("NUM_OUTPUT_STRUCT\t" +
                                            str(num_output_struct) + "\n")
                # Retrieve crosslink information from the sequence group.
                crosslinks = []
                for seq in group.sequences:
                    if seq.type == constants.SEQ_CONSTRAINTS:
                        for constraint in seq.constraint_list:
                            start, end, seq, prime = constraint
                            # Use this constraint only if is defined for Prime
                            # model building.
                            if prime and seq == reference:
                                # Get actual residues.
                                cstart_res = reference.getResidue(start,
                                                                  ungapped=True)
                                cend_res = reference.getResidue(end,
                                                                ungapped=True)
                                # Write residue numbers.
                                crosslinks.append(
                                    (cstart_res.num, cend_res.num))
                # Write a CROSSLINK line if the crosslinks are present
                # and the modeling method is Energy-based
                if (len(crosslinks) > 0 or ligand_crosslinks) \
                        
and not knowledge_based:
                    line = "CROSSLINK   "
                    for start, end in crosslinks:
                        line += " A:" + str(start) + " A:" + str(end)
                    line += ligand_crosslinks
                    line += "\n"
                    input_file_lines.append(line)
                # Build a composite string only if there are several templates
                # with marked regions.
                if len(marked_template_list) > 1:
                    composite = ""
                    for index, res in enumerate(group.reference.residues):
                        if res.is_gap:
                            # The composite string covers only template
                            # regions, not gaps.
                            continue
                        # We initially put "1" at this position - it is going
                        # to be ignored if there is no template coverage here.
                        pos = "1"
                        for tmp_index, template in enumerate(
                                marked_template_list):
                            if index < len(template.residues):
                                if template.residues[index].model:
                                    pos = str(tmp_index + 1)
                                    break
                        composite += pos
                    # Write composite array string.
                    input_file_lines.append("COMPOSITE_ARRAY " + composite)
                # Create input file prefix.
                inp_file_prefix = "prime_bldstruct_%d_%d" % (query_index,
                                                             template_index)
                # Create a unique file based on the input file prefix
                try:
                    with tempfile.NamedTemporaryFile(prefix=inp_file_prefix,
                                                     mode="w",
                                                     delete=False) as inp_file:
                        inp_file_name = inp_file.name
                except IOError:
                    dialogs.error_dialog("Cannot Build Homology Model",
                                         "Error creating an input file.")
                    status = False
                    break
                # Use new file name as a prefix and job name
                inp_file_prefix = os.path.basename(inp_file_name)
                # Create input file name.
                inp_file_name = inp_file_prefix + ".inp"
                # Write input file.
                try:
                    inp_file = open(inp_file_name, 'w')
                    inp_file.writelines(input_file_lines)
                    inp_file.close()
                except Exception:
                    dialogs.error_dialog("Cannot Build Homology Model",
                                         "Error writing input file.")
                    status = False
                    break
                # Append this file name prefix to the list.
                input_file_list.append(inp_file_prefix)
                for template in template_list:
                    all_template_names += " " + \
                        
template.short_name + "_" + template.chain_id
    if status:
        # We cannot continue if there were not input files written.
        if len(input_file_list) == 0:
            dialogs.error_dialog("Cannot Build Homology Model",
                                 "No valid template structures were found.")
            status = False
        if status:
            if maestro:
                current_entries = maestro_helpers.maestroGetListOfEntryIDs()
            all_references = ""
            # Generate a description string.
            if len(group_list) > 1:
                description = "Building hetero-multimer model of "
                for group in group_list:
                    all_references += group.reference.short_name + "_" + \
                        
group.reference.chain_id + " "
            else:
                if len(selected_template_list) > 1:
                    description = "Building homo-multimer model of "
                    all_references = sequence_group.reference.short_name + \
                    
"_" + sequence_group.reference.chain_id
                elif len(marked_template_list) > 1:
                    description = "Building composite model of "
                    all_references = sequence_group.reference.short_name + \
                    
"_" + sequence_group.reference.chain_id
                else:
                    description = "Building single chain model of "
                    all_references = sequence_group.reference.short_name + \
                    
"_" + sequence_group.reference.chain_id
            description += all_references + "\nbased on " + \
                            
all_template_names + "\n\n"
            # Run Prime bldstruct program.
            job_command = schrodinger_path + "prime"
            # First input file prefix will be used for input, log
            # and model file names.
            inp_file_prefix = input_file_list[0]
            # Launch the job and wait until it incorporates. Pass all input
            # file names to Prime as command line parameters.
            job_result = jobs.run_job_via_jobcontrol(
                [job_command] + input_file_list,
                inp_file_prefix + ".log",
                dialog_title="Building Homology Model",
                initial_text=description,
                progress_dialog=progress_dialog,
                no_jobid=quick_build_mode,
                settings=job_settings)
            # print "DEBUG PSP job result = ", job_result
            # Check the job return status.
            if job_result == jobs.JOB_STATUS_MISSING:
                # Missing or invalid job control module.
                dialogs.error_dialog("Job Failed",
                                     "Could not run homology modeling job.")
                status = False
            elif job_result == jobs.JOB_STATUS_FAILED:
                # Job could not be launched (probably missing backend)
                dialogs.error_dialog(
                    "Job Failed", "Could not build homology model.\n"
                    "Do you have Prime installed?")
                status = False
            elif job_result == jobs.JOB_STATUS_KILLED:
                # Job was interrupted.
                dialogs.error_dialog("Job Interrupted",
                                     "Homology modeling job was interrupted.")
                status = False
    if status:
        # Incorporate built model.
        model_incorporated = False
        if maestro:
            # Incorporate the model into Maestro.
            model_file_name = inp_file_prefix + "-out.mae"
            if os.path.isfile(model_file_name):
                try:
                    # Get Maestro entry of the built model.
                    entry_id = maestro_helpers.getEntryByJobName(
                        inp_file_prefix)
                    if not entry_id:
                        # Preserve wsreplace setting.
                        wsreplace = maestro.get_command_option(
                            "entryimport", "wsreplace")
                        # Incorporate model, but leave other structures included
                        # in workspace.
                        maestro.command(
                            "entryimport wsreplace=true format=maestro \"" +
                            model_file_name + "\"")
                        # Restore original value for wsreplace.
                        maestro.command("entryimport wsreplace=" + wsreplace)
                        # Get entry ID of the incorporated model
                        entry_id = maestro_helpers.getEntryByName(
                            inp_file_prefix + "-out.1")
                    if entry_id:
                        model_incorporated = True
                        # Create a model name.
                        model_name = "Model of " + all_references
                        if all_template_names:
                            model_name += " based on" + all_template_names
                        model_name = model_name.rstrip()
                        # Rename the entry and set a new title.
                        maestro.command("entrysetprop "
                                        "property=s_m_title value=\"" + \
                                        
model_name + "\" entry \"" + \
                                        
str(entry_id) + "\"")
                        maestro.command("entrysetprop "
                                        "property=s_m_entry_name value=\"" + \
                                        
model_name + "\" entry \"" + \
                                        
str(entry_id) + "\"")
                        # Propagate current MSV colors to the structure.
                        for group in group_list:
                            group.unmarkTemplateRegions()
                            reference = group.reference
                            if not reference:
                                continue
                            model_name = "Model of " + reference.short_name
                            if all_template_names:
                                model_name += " based on" + all_template_names
                            model_name = model_name.rstrip()
                            # Incorporate the model entry into a current group.
                            maestro_helpers.maestroIncorporateEntries(
                                group, what="all", ignore=current_entries)
                            # Get all sequences associated with this entry.
                            sequences = group.getMaestroSequencesForEntryId(
                                entry_id)
                            for seq in sequences:
                                if not seq.residues:
                                    continue
                                seq.name = model_name
                                seq.short_name = model_name
                                # Propagate gaps from query sequence to the model
                                # sequence.
                                new_residues = reference.propagateGaps(
                                    seq, parent_sequence=seq)
                                if new_residues:
                                    seq.residues = new_residues
                                seq.propagateGapsToChildren()
                        # Fit to workspace.
                        maestro.command("fit")
                        if viewer:
                            maestro_helpers.synchronizePropertiesWithMaestro(
                                viewer, colors=True)
                            viewer.setColorMode(constants.COLOR_MAESTRO)
                            viewer.contents_changed = True
                            viewer.updateView()
                except:
                    pass
        else:
            # MSV is running outside of Maestro.
            # Incorporate the final model PDB file into MSV.
            model_file_name = inp_file_prefix + "-out.pdb"
            if os.path.isfile(model_file_name):
                # Import the PDB file into a temporary group.
                tmp_group = sequence_group.SequenceGroup()
                if fileio.load_PDB_file(tmp_group, model_file_name):
                    reference = sequence_group.reference
                    if len(tmp_group.sequences) > 0:
                        # Align all model sequences with the reference
                        # sequence.
                        for seq in tmp_group.sequences:
                            # Rename the sequence.
                            seq.short_name = "Model_of_" + reference.short_name
                            seq.name = seq.short_name
                            # Propagate gaps from query sequence to the model
                            # sequence.
                            new_residues = reference.propagateGaps(
                                seq, parent_sequence=seq)
                            if new_residues:
                                seq.residues = new_residues
                            seq.propagateGapsToChildren()
                            # Color sequences using a current color mode.
                            if viewer:
                                sequence_group.colorSequences(
                                    sequence_group.color_mode)
                        # Append temporary group to the current sequence group.
                        sequence_group.sequences += tmp_group.sequences
                    model_incorporated = True
                    if viewer:
                        viewer.contents_changed = True
                        viewer.updateView()
        if not model_incorporated:
            dialogs.error_dialog("Job Failed",
                                 "Could not incorporate the homology model.")
    # Change current directory back to the original directory and remove
    # temporary files and directory.
    try:
        os.chdir(cwd)
        if job_settings:
            keep_files = job_settings["keep_files"]
        else:
            keep_files = False
        if tmpdir and not keep_files:
            jobs.removeTmpJobDir(tmpdir, keep=[model_file_name])
    except:
        dialogs.error_dialog("Cannot Remove Temporary Files",
                             "Cannot remove temporary Prime job files."),
        status = False
    return status