Source code for schrodinger.application.vsw.vswinput
"""
Class for writing VSW (not general Pipeline) input files.
Copyright Schrodinger, LLC. All rights reserved.
"""
# Contributors: Matvey Adzhigirey, Jon-Eric Eufemio
import os
import sys
import schrodinger.job.util as jobutil  # To check for Epik installation
import schrodinger.pipeline.input as vswinput
from schrodinger.structutils import structalign
# Check whether SCHRODINGER_PYTHON_DEBUG is set for debugging:
from schrodinger.utils import cmdline
from schrodinger.utils import log
from schrodinger.utils.ligfilter import FILTERFILE_EXT
DEBUG = (log.get_environ_log_level() <= log.DEBUG)
logger = log.get_output_logger("vswinput")
VSW_COMPOUNDFIELD = 's_vsw_compound_code'
GPHDB_COMPOUNDFIELD = 's_m_title'  # Ev:105243
VARIANTFIELD = 's_vsw_variant'
PHASEDB = "PhaseDB"
STRUCTURES = "Structures"
GRID = "Grid"
[docs]def parse_command_line():
    """
    Returns the path to input file, as determined from command line option parser.
    """
    usage = """
    To specify a parameters file that will be read to set the job parameters in the VSW job input file:
    $SCHRODINGER/run vsw_input.py -p <job parameter file>"""
    description = """Description: This script generates a VSW input file.  The parameters used for the VSW input file can be specified in a parameters file."""
    parser = cmdline.SingleDashOptionParser(usage=usage,
                                            description=description)
    parser.add_option("-p",
                      "-parameters",
                      type="string",
                      dest="job_params",
                      default="",
                      metavar="FILE",
                      help="Job parameters file")
    (options, args) = parser.parse_args()
    return options.job_params
[docs]class VSWWriter():
    """
    Use writeInputFile() method
    """
[docs]    def __init__(self, jp=None):
        """
        Initialize from parameters dictionary.
        """
        # Initialize to defaults:
        # minimization method
        self.prepare = False
        # Controls for the MergeDuplicates stage:
        self.merge_duplicates = False
        self.desalt = False
        self.neutralize = False
        self.merge_props = False
        self.dockligs_files = None
        self.dockligs_db = None
        self.dockligs_dbsubset = None
        # Triggers phasedb_manage -unique smiles option.
        self.remove_duplicates = False
        # If True, the <db>_unique file will be deleted before adding ligands.
        self.delete_unique_file = False
        self.regularize = False
        self.ph = 7.0
        self.pht = 2.0
        self.useepik = True
        self.metal_binding = False
        self.mixligs = True
        self.nringconfs = 1
        self.stereo_source = 'geometry'
        self.uniquefield = "NONE"
        # NONE means to retitle by position
        self.recombine = True
        self.hiprot = False
        self.sample_rings = False
        self.maxstereo = 4
        self.lipinski = False
        self.reactive = False
        self.runqikprop = False
        self.precustomfilter = None  # Will only run IF preparing
        self.postcustomfilter = None  # Will run before docking
        self.titlefilterfile = None
        self.receptors = []
        self.align_receptors = False
        self.lvdw = 0.8
        self.lig_ccut = 0.15
        self.maxatoms = 300
        self.maxrotbonds = 50
        self.res_inter_radius = None  # <None> means disable by default
        self.runhtvs = False
        # whether to keep number of structures as opposed to percentage
        self.htvs_donum = False
        self.htvsper = 10.0
        self.htvsnum = 1
        self.htvsposes = 1
        self.htvskeep = 'All states'
        self.htvsconfs = 'confgen'
        self.htvsusecons = False
        self.htvsamide = 'penal'
        self.htvs_postdock = True
        self.htvs_postdockstrain = False
        self.htvs_forcefield = None
        self.htvs_epik_penalties = False
        self.htvs_enhanced_planarity = False  # Ev:120767
        self.runsp = False
        self.sp_donum = False
        self.spper = 10.0
        self.spnum = 1
        self.spposes = 1
        self.spkeep = 'All states'
        self.spconfs = 'confgen'
        self.spusecons = False
        self.spamide = 'penal'
        self.spwritedesc = False
        self.sp_nsampling = 1
        self.sp_postdock = True
        self.sp_postdockstrain = False
        self.sp_forcefield = None
        self.sp_epik_penalties = False
        self.sp_enhanced_planarity = False  # Ev:120767
        self.runxp = False
        self.xpgenconfs = False
        self.xpwritedesc = False
        self.xpcompmax = False
        self.xpcompmax_cons = True
        self.mmgbsa = False
        self.xp_donum = False
        self.xpper = 100.0
        self.xpnum = 1
        self.xpposes = 1
        self.xpkeep = 'All states'
        self.xpconfs = 'confgen'
        self.xpusecons = False
        self.xpamide = 'penal'
        self.xp_postdock = True
        self.xp_postdockstrain = False
        self.xp_forcefield = None
        self.xp_epik_penalties = False
        self.xp_enhanced_planarity = False  # Ev:120767
        self.phasedb_path = None
        self.phasedb_new = True
        self.phasedb_confs_mode = "auto"
        self.phasedb_maxconfs = 100
        self.phasedb_maxperbond = 10
        self.phasedb_amide = "vary"
        self.phasedb_energy_window = 10.0
        self.phasedb_sample_method = "rapid"
        self.jobname = 'vsw'
        # Where to write the input file:
        self.input_file_path = None  # None: <jobname>.inp
        # Adjust to specified parameters dict:
        if jp:
            self.__dict__.update(jp)
[docs]    def writeDockingStage(self,
                          mode,
                          receptor,
                          input,
                          output,
                          outtype,
                          gencodes_out_set=None,
                          frags=False,
                          recombine=True,
                          unique_field=None,
                          multiple_confs=False):
        """
        Writes stage that describes how the docking will be executed.
        """
        if receptor.identifier == '':
            receptor_suffix = ''
        else:
            receptor_suffix = '_' + receptor.identifier
        if frags:
            sname = "DOCK_%s%s_FRAGS" % (mode, receptor_suffix)
        else:
            sname = "DOCK_%s%s" % (mode, receptor_suffix)
        sclass = "glide.DockingStage"
        ingrid = "GRID%s" % receptor_suffix
        #outputs = [output]
        lmode = mode.lower()
        precision = mode.upper()
        docking_uniquefield = None
        if recombine:
            # Determine good min/max num of ligand per subjobs:
            if frags:
                method = "confgen"
            else:
                method = getattr(self, '%sconfs' % lmode)
            # Determine the approximate size of a 1 hour job:
            min_job_size = None
            if precision == "HTVS":
                if method == "inplace" or method == "refineinput":
                    msg = "ERROR: %s unavailable for %s" % (method, precision)
                    raise RuntimeError(msg)
                elif method == "rigid":
                    min_job_size = 8000
                elif method == "confgen":
                    min_job_size = 4000
            elif precision == "SP":
                if method == "inplace":
                    min_job_size = 10000  # 25,000 ~ 1 hour; 10,000 ~ 25min
                elif method == "refineinput":
                    min_job_size = 5000  # 8,000 ~ 1 hour; 5,000 ~ 40min
                elif method == "rigid":
                    min_job_size = 2000
                elif method == "confgen":
                    min_job_size = 300
            elif precision == "XP":
                if method == "inplace":
                    min_job_size = 4000
                elif method == "refineinput":
                    min_job_size = 100
                elif method == "rigid":
                    min_job_size = 200
                elif method == "confgen":
                    min_job_size = 20
            else:
                # Should never happen
                msg = 'ERROR: Invalid precision mode: "%s"' % precision
                raise RuntimeError(msg)
            if min_job_size is None:
                # Should no longer ever happen
                msg = 'ERROR: docking method "%s" is unavailable for precision "%s"' % (
                    method, precision)
                raise RuntimeError(msg)
            max_job_size = min_job_size * 10  # about 10 hours
            kw = {
                # Use -NJOBS to determine number of subjobs
                'NUMOUT': 'njobs',
                'OUTFORMAT': 'maegz',
                'MIN_SUBJOB_STS': min_job_size,
                'MAX_SUBJOB_STS': max_job_size,
            }
            recom_output = sname + '_INPUT'
            if gencodes_out_set:
                # If generating variant codes:
                kw.update({
                    'GENCODES': True,
                    'OUTCOMPOUNDFIELD': VSW_COMPOUNDFIELD,
                    'OUTVARIANTFIELD': VARIANTFIELD,
                    # This is the first PRE_DOCK stage, generate codes from the
                    # user-specified uniquefield:
                    'UNIQUEFIELD': unique_field,
                })
                docking_uniquefield = VSW_COMPOUNDFIELD
                # Only 1 stage should have RECOMBINE_OUT as output
                recom_output = gencodes_out_set
            elif frags:
                # Docking fragments (they don't have variant codes
                kw.update({
                    'GENCODES': True,
                    'OUTCOMPOUNDFIELD': VSW_COMPOUNDFIELD,
                    'OUTVARIANTFIELD': VARIANTFIELD,
                    'UNIQUEFIELD': "s_m_title",
                })
                docking_uniquefield = "s_m_title"
            else:
                # Not generating variant codes:
                kw['GENCODES'] = False  # Ev:124913
                if frags:
                    # Docking fragments, they don't have the user-specified
                    # unique property:
                    kw['UNIQUEFIELD'] = 's_m_title'
                    docking_uniquefield = "s_m_title"
                else:
                    kw['UNIQUEFIELD'] = unique_field
                    docking_uniquefield = unique_field
            recomsname = 'PRE_' + sname
            recomsclass = 'gencodes.RecombineStage'
            self.writer.addStage(recomsname, recomsclass, [input],
                                 [recom_output], kw)
            input = recom_output
        else:
            docking_uniquefield = unique_field
        # Should never happen:
        if docking_uniquefield is None:
            raise RuntimeError("unique-field logic error")
        # Beginning of the docking stage keywords
        kw = {
            # We are now always including RecombineStage if recombining is
            # necessary
            'RECOMBINE': False,
            'PRECISION': mode,
            'UNIQUEFIELD': docking_uniquefield,
        }
        # If docking fragments for XPVis (Ev:63790) keep all of them:
        if frags:
            kw['PERCENT_TO_KEEP'] = 100
            kw['DOCKING_METHOD'] = 'confgen'
        else:
            if getattr(self, '%s_donum' % lmode):
                kw['NUM_TO_KEEP'] = getattr(self, '%snum' % lmode)
            else:
                kw['PERCENT_TO_KEEP'] = getattr(self, '%sper' % lmode)
            kw['DOCKING_METHOD'] = getattr(self, '%sconfs' % lmode)
        if frags:  # For fragments, keep 9 poses per ligand (Ev:68950)
            poses_per_lig = 9
        else:
            poses_per_lig = getattr(self, '%sposes' % lmode)
        kw['POSES_PER_LIG'] = poses_per_lig
        if mode == 'SP':
            kw['WRITE_XP_DESC'] = self.spwritedesc
            kw['NENHANCED_SAMPLING'] = self.sp_nsampling
        elif mode == 'XP':
            kw['WRITE_XP_DESC'] = self.xpwritedesc
        keepmatch = {
            'Only best scoring state': 'YES',
            'Only best scoring pose': 'YES',
            'All good scoring states': 'NO',
            'All good scoring poses': 'NO',
            'All states': 'NO',
            'best': 'YES',  # NEW
            'good': 'NO',  # NEW
            'all': 'NO',  # NEW
        }
        keep_best = keepmatch[getattr(self, '%skeep' % lmode)]
        kw.update({
            'BEST_BY_TITLE': keep_best,
            'LIG_VSCALE': self.lvdw,
            'LIG_CCUT': self.lig_ccut,
            'MAXATOMS': self.maxatoms,
            'MAXROTBONDS': self.maxrotbonds,
        })
        # Whether to report per-residue interactions VSW-824:
        res_inter_radius = self.res_inter_radius
        if res_inter_radius:
            kw['WRITE_RES_INTERACTION'] = True
            kw['RADIUS_RES_INTERACTION'] = res_inter_radius
        # Match pull down output with the short name to pass to MMIM:
        amide_mode = getattr(self, '%samide' % lmode)
        # If any conf other than confgen (flexible) is selected, is it
        # necessary to set the keyword to default value?????
        # NO for SP and YES for XP
        forcefield = getattr(self, '%s_forcefield' % lmode)
        postdock = getattr(self, '%s_postdock' % lmode)
        postdockstrain = getattr(self, '%s_postdockstrain' % lmode)
        kw['AMIDE_MODE'] = amide_mode
        kw['POSE_OUTTYPE'] = outtype
        if forcefield is not None:
            kw['FORCEFIELD'] = forcefield
        kw.update({
            'POSTDOCK': postdock,
            'POSTDOCKSTRAIN': postdockstrain,  # Ev:70957
            'COMPRESS_POSES': True,  # Ev:87161
        })
        # Ev:80478 & Ev:90171
        epik_penalties = getattr(self, '%s_epik_penalties' % lmode)
        kw['EPIK_PENALTIES'] = epik_penalties
        enhanced_planarity = getattr(self, '%s_enhanced_planarity' % lmode)
        kw['FORCEPLANAR'] = enhanced_planarity
        if multiple_confs:
            # If input ligands have multiple conformers, we need to make
            # sure we do not canonicalize them into a single conformer:
            kw['CANONICALIZE'] = False
            self.htvs_enhanced_planarity = False  # Ev:120767
        if not frags or (frags and self.xpcompmax_cons):
            # Ev:72134 Setup constraints for fragments only if requested
            # Setup HBOND and METAL constraints:
            if getattr(self, '%susecons' % lmode):  # Use constraints:
                # At least one specified for this receptor
                if receptor.usecons:
                    if receptor.features:
                        # If at least one positional constraint is present
                        # (requires features):
                        feature_dict = {}
                        for feature_num, feature in receptor.features.items():
                            feature_config = {}
                            for pattern_num, pattern in enumerate(
                                    feature.patterns, start=1):
                                pattern_name = "PATTERN%i" % pattern_num
                                atoms_str = ",".join(map(str, pattern.atoms))
                                if pattern.exclude:
                                    inc_exc_str = "exclude"
                                else:
                                    inc_exc_str = "include"
                                feature_config[pattern_name] = "%s %s %s" % (
                                    pattern.smarts, atoms_str, inc_exc_str)
                            kw["FEATURE:%i" % feature_num] = feature_config
                    # Write a single constraint group to the VSW input file:
                    kw['CONSTRAINT_GROUP:1'] = {
                        "USE_CONS": receptor.usecons,
                        "NREQUIRED_CONS": receptor.nrequired,
                    }
            # Setup CORE constraint:
            if receptor.usecore:  # Core defined for this receptor
                core_file = '%s-receptor%s-core.mae' % \
                    (self.jobname, receptor_suffix)
                receptor.core_structure.write(core_file)
                kw.update({
                    'CORE_RESTRAIN': True,
                    'USE_REF_LIGAND': True,
                    'REF_LIGAND_FILE': core_file,
                    'CORE_POS_MAX_RMSD': receptor.core_tolerance,
                    'CORE_DEFINITION': receptor.core_atoms,
                    'CORE_FILTER': receptor.core_skipnonmatching,
                })
                if receptor.core_atoms == 'smarts':
                    # Which atoms of the ligand define the core:
                    kw['CORE_SMARTS'] = receptor.core_smarts
                    kw['CORE_ATOMS'] = receptor.core_atom_list
        self.writer.addStage(sname, sclass, [input, ingrid], [output], kw)
[docs]    def writeGenConfsStage(self, receptor_suffix, input, output):
        """
        Writes stages that generate MMFFs and OPLS conformers from the input,
        combine them with the input, and put into output.
        """
        sname = "MMFFS" + receptor_suffix
        sclass = "macromodel.ConfSearchStage"
        out1 = "MMFFS_OUT" + receptor_suffix
        kw = {
            'FORCE_FIELD': "MMFFs",
            'MINI_METHOD': "TNCG",
            'SOLVENT': "Water",
            'CUTOFF': "Extended",
        }
        self.writer.addStage(sname, sclass, [input], [out1], kw)
        sname = "OPLS" + receptor_suffix
        sclass = "macromodel.ConfSearchStage"
        out2 = "OPLS_OUT" + receptor_suffix
        kw = {
            'FORCE_FIELD': "OPLS_2005",
            'MINI_METHOD': "TNCG",
            'SOLVENT': "Water",
            'CUTOFF': "Extended",
        }
        self.writer.addStage(sname, sclass, [input], [out2], kw)
        sname = "COMBINE" + receptor_suffix
        sclass = "combine.CombineStage"
        kw = {
            'LABELFIELD': "s_vsw_conformer_field",
            'LABELS': ["Original", "MMFFS", "OPLS"],
        }
        self.writer.addStage(sname, sclass, [input, out1, out2], [output], kw)
[docs]    def writePreFilterStage(self, input, output, custom_filter_text):
        """
        Writes the stage that filters by specified LigFilter criteria
        """
        custom_filter_file = '%s-custom-filter.%s' % \
            (self.jobname, FILTERFILE_EXT)
        fh = open(custom_filter_file, 'w')
        fh.write(custom_filter_text)
        fh.close()
        sname = "CUSTOMFILTER"
        sclass = "filtering.LigFilterStage"
        kw = {'FILTER_FILE': custom_filter_file}
        self.writer.addStage(sname, sclass, [input], [output], kw)
[docs]    def writePrepareStages(self, lastoutput, recombine):
        if self.precustomfilter:
            output = 'CUSTOMFILTER_OUT'
            self.writePreFilterStage(lastoutput, output, self.precustomfilter)
            lastoutput = output
        sname = 'LIGPREP'
        sclass = "ligprep.LigPrepStage"
        input = lastoutput
        lastoutput = 'LIGPREP_OUT'
        # This stage is going to be the first in the workflow, so set the compound code,
        # unless the user chose to not recombine:
        if recombine:
            compoundfield = VSW_COMPOUNDFIELD
            kw = {
                'RECOMBINE': True,
                'RETITLE': True,
                'MIXLIGS': self.mixligs,
                'SKIP_BAD_LIGANDS': True,
                'UNIQUEFIELD': self.uniquefield,
                'OUTCOMPOUNDFIELD': compoundfield,
            }
            # For the docking stages:
            self.uniquefield = VSW_COMPOUNDFIELD
        else:
            kw = {
                'RECOMBINE': False,
                'RETITLE': False,
                'UNIQUEFIELD': self.uniquefield,
            }
        kw.update({
            'USE_EPIK': self.useepik,
            'METAL_BINDING': self.metal_binding,
            'PH': self.ph,
            'PHT': self.pht,
            'NRINGCONFS': self.nringconfs,
            'COMBINEOUTS': False,
            'STEREO_SOURCE': self.stereo_source,
            'NUM_STEREOISOMERS': 32,  # For -s option
            'MAX_STEREOISOMERS': self.maxstereo,  # For -m option
        })
        if self.merge_duplicates:
            # If MergeDuplicatesStage is run, the structures are already
            # regularized:
            self.regularize = False
        kw['REGULARIZE'] = self.regularize
        self.writer.addStage(sname, sclass, [input], [lastoutput], kw)
        if self.sample_rings:
            output = 'SAMPLERINGS_OUT'
            keywords = {'OUTCONFS_PER_SEARCH': 10}  # Ev:91804
            self.writer.addStage('SAMPLERINGS', 'macromodel.SampleRingsStage',
                                 [lastoutput], [output], keywords)
            lastoutput = output
        # PostLigPrep stage:
        if recombine:
            uniquefield = VSW_COMPOUNDFIELD
        else:
            if self.uniquefield == "NONE":
                uniquefield = "s_m_title"  # Generated by LigPrep
            else:
                uniquefield = self.uniquefield
        keywords = {
            'UNIQUEFIELD': uniquefield,
            'OUTVARIANTFIELD': VARIANTFIELD,
            # creates same number of QikProp jobs as LigPrep
            'PRESERVE_NJOBS': True,
        }
        keywords['REMOVE_PENALIZED_STATES'] = 'YES' if self.hiprot else 'NO'
        output = 'POSTLIGPREP_OUT'
        self.writer.addStage('POSTLIGPREP', 'ligprep.PostLigPrepStage',
                             [lastoutput], [output], keywords)
        lastoutput = output
        return lastoutput
[docs]    def writeInputFile(self):
        """
        Writes the VSW input file
        Raises a RuntimeError on error.
        """
        if self.recombine:
            compoundfield = VSW_COMPOUNDFIELD
        else:
            compoundfield = self.uniquefield
        pullmatch = {
            'Only best scoring state': VARIANTFIELD,
            'Only best scoring pose': VARIANTFIELD,
            'All good scoring states': VARIANTFIELD,
            'All good scoring poses': VARIANTFIELD,
            'All states': compoundfield,
            'best': VARIANTFIELD,  # NEW
            'good': VARIANTFIELD,  # NEW
            'all': compoundfield,  # NEW
        }
        # Used by the PULL stage:
        htvspull_prop = pullmatch[self.htvskeep]
        sppull_prop = pullmatch[self.spkeep]
        if self.input_file_path:
            vsw_input_file = self.input_file_path
        else:
            vsw_input_file = self.jobname + '.inp'
        # Ev:125529
        txt = "########## Virtual Screening Workflow Input File ###############\n\n"
        txt += "# Run as: $SCHRODINGER/vsw <inputfile> \n"
        if False:  # JON_TEST
            txt += "\n"
            txt += "\n"
            txt += "# Print sorted self.__dict__ key/value pairs): \n"
            txt += "# \n"
            jp_keys = list(self.__dict__)
            sorted_jp_keys = sorted(jp_keys)
            jp_index = 1
            for key in sorted_jp_keys:
                txt += "# " + str(jp_index).rjust(2) + " " + str(key).rjust(20) + " " + \
                    str(type(self.__dict__[key])).ljust(
                        25) + " " + str(self.__dict__[key]) + "\n"
                jp_index += 1
            txt += "\n"
            txt += "\n"
            txt += "# BEGIN Print jp as assignments: \n"
            jp_index = 1
            for key in sorted_jp_keys:
                jp_keyname = "self." + str(key)
                variable_quoting = ''
                if type(self.__dict__[key]).__name__ == 'str':
                    variable_quoting = '"'
                jp_value = variable_quoting + \
                    str(self.__dict__[key]) + variable_quoting
                txt += "# " + jp_keyname.ljust(20) + " = " + jp_value.ljust(50) + \
                    "    # " + \
                    str(type(self.__dict__[key])) + \
                    "\n"
            txt += "# END Print jp as assignments: \n"
            txt += "\n"
        self.writer = vswinput.Writer(vsw_input_file, comment=txt)
        logger.debug("ADDING VARIABLES...")
        if self.dockligs_files is not None:
            # Set input ligands:
            self.writer.addVar('ORIGINAL_LIGANDS', STRUCTURES,
                               self.dockligs_files)
            lastoutput = "ORIGINAL_LIGANDS"
        elif self.dockligs_db is not None:
            # Set input database:
            dbvar = 'INPUT_DATABASE'
            self.writer.addVar(dbvar, PHASEDB, self.dockligs_db)
            lastoutput = 'EXPORTED_LIGANDS'
            keywords = {'OUTFORMAT': 'maegz', 'MAX_CONFS': 1}
            # Save the full path to the file (Ev:127191)
            subsetfile = self.dockligs_dbsubset
            if subsetfile:
                keywords['SUBSET_FILE'] = subsetfile
            self.writer.addStage('EXPORT', 'phase.DBExportStage', [dbvar],
                                 [lastoutput], keywords)
        else:
            raise ValueError("Input ligands are not specified")
        # Make sure that no docking takes place if unchecked:
        if not len(self.receptors) > 0:
            self.runhtvs = False
            self.runsp = False
            self.runxp = False
        if self.align_receptors:
            # Ev:81292 Align all receptors (except pre-generated grids) to the
            # first receptor:
            reference_st = None
            mobile_sts = []
            for rec in self.receptors:
                if rec.generate_grid:
                    if not reference_st:
                        reference_st = rec.model
                    else:
                        mobile_sts.append(rec.model)
            if not reference_st:
                raise RuntimeError(
                    "Can't align receptors because no receptors are specified that are not pre-generated grids."
                )
            if not mobile_sts:
                raise RuntimeError(
                    "Can't align receptors because only one receptor is specified (excluding pre-generated grids)."
                )
            # Will align the actual CTs stored in the Receptor objects:
            ska = structalign.StructAlign()
            ska.align(reference_st, mobile_sts)
        # Set input grids:
        for rec in self.receptors:
            if rec.identifier == '':
                receptor_suffix = ''
            else:
                receptor_suffix = '_' + rec.identifier
            if not rec.generate_grid:
                gridvarname = 'GRID' + receptor_suffix
                self.writer.addVar(gridvarname, GRID, rec.path)
            else:
                # Write the receptor (used to be in Workspace) to a file:
                model_file = '%s-receptor%s.mae' % \
                    (self.jobname, receptor_suffix)
                rec.model.write(model_file)
                modelvarname = 'MODEL' + receptor_suffix
                self.writer.addVar(modelvarname, STRUCTURES, [model_file])
        # Set fragments library (if calculating maximum values Ev:63790):
        fragsvar = None
        if self.runxp and self.xpcompmax:
            glide_exec = jobutil.hunt('glide')
            if not glide_exec:
                msg = "ERROR: Glide is not installed. You can still submit the job to a remote host that has Glide installed; but only if you disable the option to compute maximum values by docking fragments."
                raise RuntimeError(msg)
            glide_dir = os.path.split(os.path.split(
                jobutil.hunt('glide'))[0])[0]
            fragments_file = os.path.join(glide_dir, 'data', 'glide',
                                          'opt-xp-50frags_epik.mae.gz')
            if not os.path.isfile(fragments_file):
                msg = "Could not locate the fragments file: %s" % fragments_file
                raise RuntimeError(msg)
            fragsvar = 'FRAGMENTS'
            self.writer.addVar(fragsvar, STRUCTURES, [fragments_file])
        # Never convert to SMILES if not preparing ligands:
        if not self.prepare and self.regularize:
            print(
                'WARNING: vswinput: prepare is %s and regularize is %s. Will not regularize'
                % (self.prepare, self.regularize))
            self.regularize = False
        if self.merge_duplicates:
            output = 'NODUPLICATES'
            kw = {
                'NEUTRALIZE': self.neutralize,
                'DESALT': self.desalt,
                'MERGE_PROPS': self.merge_props,
                'OUTFORMAT': 'sdf',
                'SMILES_FIELD': 'VendorSMILES',
                'CODE_FIELD': None,
                'CODE_PREFIX': None,
                'REQUIRE_PROPS': False,
            }
            self.writer.addStage('MERGE_DUPLICATES',
                                 'filtering.MergeDuplicatesStage', [lastoutput],
                                 [output], kw)
            lastoutput = output
        if self.titlefilterfile:
            # Write stage for filtering based on title (or other prop):
            # (Supports SMILES files as well)
            output = 'TITLEFILTER_OUT'
            kw = {
                'PROPERTY': self.uniquefield,
                'VALUE_FILE': self.titlefilterfile,
            }
            self.writer.addStage('TITLEFILTER', 'filtering.SubSetStage',
                                 [lastoutput], [output], kw)
            lastoutput = output
        if self.prepare:
            logger.debug("ADDING PREPARE STAGES")
            lastoutput = self.writePrepareStages(lastoutput, self.recombine)
        else:  # not self.prepare
            pass
        logger.debug("ADDING PRE-DOCKING STAGES")
        # Gets here whether or not Prepare ligands is checked
        if self.runqikprop:
            recombine_ligands = (not self.prepare and self.recombine)
            kw = {'RECOMBINE': recombine_ligands}
            output = "QIKPROP_OUT"
            self.writer.addStage('QIKPROP', 'qikprop.QikPropStage',
                                 [lastoutput], [output], kw)
            lastoutput = output
        if self.lipinski:
            sname = 'LIPINSKI_FILTER'
            sclass = 'filtering.LigFilterStage'
            input = lastoutput
            lastoutput = 'LIPFILTER_OUT'
            conditions = [
                'r_qp_mol_MW <= 500', 'r_qp_QPlogPo/w <= 5',
                'r_qp_donorHB <= 5', 'r_qp_accptHB <= 10'
            ]
            kw = {'CONDITIONS': conditions}
            self.writer.addStage(sname, sclass, [input], [lastoutput], kw)
        if self.reactive:
            sname = 'REACTIVE_FILTER'
            sclass = 'filtering.LigFilterStage'
            input = lastoutput
            lastoutput = 'REACTIVEFILTER_OUT'
            conditions = ['Reactive_groups == 0']
            kw = {'CONDITIONS': conditions}
            self.writer.addStage(sname, sclass, [input], [lastoutput], kw)
        if self.postcustomfilter:
            output = 'CUSTOMFILTER_OUT'
            self.writePreFilterStage(lastoutput, output, self.postcustomfilter)
            lastoutput = output
        if self.xpgenconfs and self.runxp and not self.runhtvs and not self.runsp:
            # Ev:53413 Generate conformers once for all grids if only XP
            # docking is used:
            output = "MIN_COMBINED"
            self.writeGenConfsStage("", lastoutput, output)
            lastoutput = output
        self.prepared_ligand_set = lastoutput
        if self.prepare or not self.receptors:
            # Ev:131515 Return the last ligand set to the user if
            # preparation took place OR no docking took place
            self.writer.userOutput(self.prepared_ligand_set)
            if not self.receptors:
                # We are not performing docking
                self.writer.setStructureOutput(self.prepared_ligand_set)
        # Do not use VARIANTFIELD for pulling if codes were not generated.
        # This would happen if recombine is False and not Preparing.
        if (not self.prepare) and (not self.recombine):
            # FIXME: Would be better to check if the input files have
            # the variant property in them...
            if self.uniquefield == 'NONE':
                htvspull_prop = 's_m_title'
                sppull_prop = 's_m_title'
            else:
                htvspull_prop = self.uniquefield
                sppull_prop = self.uniquefield
        docking_outputs = []
        docking_offsets = []
        docking_identifiers = []
        # Add docking stages for all receptors:
        logger.debug("ADDING DOCKING STAGES")
        for rec in self.receptors:
            if rec.identifier == '':
                receptor_suffix = ''
            else:
                receptor_suffix = '_' + rec.identifier
            # If recombining (and ligands were not prepared), use a special
            # pull set for this receptor:
            if not self.prepare and self.recombine:
                pull_from_set = 'RECOMBINE_OUT' + receptor_suffix
                # First docking stage will produce this:
                gencodes_out_set = pull_from_set
            else:
                # Use prepared ligands (or original ligands):
                # (or not preparing and not recombining)
                pull_from_set = self.prepared_ligand_set
                gencodes_out_set = None
            gridvarname = 'GRID' + receptor_suffix
            if rec.generate_grid:
                # Generate the grids:
                modelvarname = 'MODEL' + receptor_suffix
                sname = "GRIDGEN" + receptor_suffix
                sclass = "glide.GridgenStage"
                inner_box_length = 10  # center of ligand box
                kw = {
                    'GRID_CENTER': rec.box_center,  # Ev:100860
                    # box length (advanced options; int)
                    'INNERBOX': inner_box_length,
                    # box length + ligsize
                    'OUTERBOX': float(inner_box_length) + rec.lig_size,
                    'RECEP_VSCALE': rec.rvdw,
                    'WRITEZIP': True,
                }
                # Add constraints to be generated:
                hc = ['%s' % c for c in rec.hbond_constraints]
                if hc:
                    # hc is a list of "label atomnum" strings
                    kw['HBOND_CONSTRAINTS'] = hc
                mc = ['%s' % c for c in rec.metal_constraints]
                if mc:
                    # mc is a list of "label atomnum" strings
                    kw['METAL_CONSTRAINTS'] = mc
                pc = ['%s' % c for c in rec.posit_constraints]
                if pc:
                    # pc is a list of "label x y z radius" strings
                    kw['POSIT_CONSTRAINTS'] = pc
                nc = ['%s' % c for c in rec.noe_constraints]
                if nc:
                    # pc is a list of "label x y z rmin rmax" strings
                    kw['NOE_CONSTRAINTS'] = nc
                self.writer.addStage(sname, sclass, [modelvarname],
                                     [gridvarname], kw)
            lastoutput = self.prepared_ligand_set
            # HTVS output after being pulled from the input set (optimization):
            htvs_out_orig = None
            if self.runhtvs:
                first_docking = True
                last_docking = (not self.runsp and not self.runxp)
                # Will NOT recombine ONLY when this is the first stage AND
                # self.recombine is False:
                recombine = self.prepare or self.recombine or (
                    not first_docking)
                if not last_docking:
                    outtype = 'LIB'
                else:
                    outtype = 'PV'
                # This is the first docking stage. Honor the users's
                # uniquefield:
                stage_unique_field = self.uniquefield
                output = "HTVS_OUT" + receptor_suffix
                self.writeDockingStage('HTVS',
                                       rec,
                                       lastoutput,
                                       output,
                                       outtype,
                                       gencodes_out_set,
                                       recombine=recombine,
                                       unique_field=stage_unique_field)
                gencodes_out_set = None
                lastoutput = output
                pull = False
                if self.runsp:  # Next stage is SP
                    if self.spconfs not in ['inplace', 'refineinput']:
                        pull = True
                elif self.runxp:  # Next stage is XP
                    if self.xpconfs not in ['inplace', 'refineinput']:
                        pull = True
                if pull:
                    sname = "PULL_HTVS" + receptor_suffix
                    sclass = "pull.PullStage"
                    in1 = lastoutput
                    in2 = pull_from_set
                    lastoutput = "HTVS_OUT_ORIG" + receptor_suffix
                    kw = {'UNIQUEFIELD': htvspull_prop}
                    self.writer.addStage(sname, sclass, [in1, in2],
                                         [lastoutput], kw)
                    htvs_out_orig = lastoutput
            if self.runsp:
                first_docking = (not self.runhtvs)
                last_docking = (not self.runxp)
                # Will NOT recombine ONLY when this is the first stage AND
                # self.recombine is False:
                recombine = self.prepare or self.recombine or (
                    not first_docking)
                if not last_docking:
                    outtype = 'LIB'
                else:
                    outtype = 'PV'
                # If this is the first docking stage, honor the user's uniquefield.
                # If the user chose to not recombine, also honor the user's
                # uniquefield.
                if first_docking or not self.recombine:
                    stage_unique_field = self.uniquefield
                else:
                    stage_unique_field = VSW_COMPOUNDFIELD
                output = "SP_OUT" + receptor_suffix
                self.writeDockingStage('SP',
                                       rec,
                                       lastoutput,
                                       output,
                                       outtype,
                                       gencodes_out_set,
                                       recombine=recombine,
                                       unique_field=stage_unique_field)
                gencodes_out_set = None
                lastoutput = output
                if self.runxp and self.xpconfs not in [
                        'inplace', 'refineinput'
                ]:
                    sname = "PULL_SP" + receptor_suffix
                    sclass = "pull.PullStage"
                    in1 = "SP_OUT" + receptor_suffix
                    if htvs_out_orig is not None:
                        # HTVS output pulled from the original set
                        # (optimization):
                        in2 = htvs_out_orig
                    else:
                        in2 = pull_from_set
                    lastoutput = "SP_OUT_ORIG" + receptor_suffix
                    kw = {'UNIQUEFIELD': sppull_prop}
                    self.writer.addStage(sname, sclass, [in1, in2],
                                         [lastoutput], kw)
            if self.runxp:
                first_docking = (not self.runhtvs and not self.runsp)
                last_docking = True
                # Will NOT recombine ONLY when this is the first stage AND
                # self.recombine is False:
                recombine = self.prepare or self.recombine or (
                    not first_docking)
                if self.xpgenconfs and (self.runhtvs or self.runsp):
                    # Ev:53413 Generate conformers before the XP stage if there
                    # are docking stages before this one:
                    output = "MIN_COMBINED" + receptor_suffix
                    self.writeGenConfsStage(receptor_suffix, lastoutput, output)
                    lastoutput = output
                # If this is the first docking stage, honor the user's uniquefield.
                # If the user chose to not recombine, also honor the user's
                # uniquefield.
                if first_docking or not self.recombine:
                    stage_unique_field = self.uniquefield
                else:
                    stage_unique_field = VSW_COMPOUNDFIELD
                output = "XP_OUT%s" % receptor_suffix
                self.writeDockingStage('XP',
                                       rec,
                                       lastoutput,
                                       output,
                                       'PV',
                                       gencodes_out_set,
                                       recombine=recombine,
                                       unique_field=stage_unique_field,
                                       multiple_confs=self.xpgenconfs)
                gencodes_out_set = None
                self.writer.userOutput(output)  # Ev:72832
                lastoutput = output
                if self.xpcompmax:
                    output = 'XP_OUT%s_xpfrags' % receptor_suffix
                    # fragsvar was set earlier
                    stage_unique_field = "s_m_title"
                    self.writeDockingStage('XP',
                                           rec,
                                           fragsvar,
                                           output,
                                           'PV',
                                           frags=True,
                                           recombine=recombine,
                                           unique_field=stage_unique_field)
                    self.writer.userOutput(output)
            if self.mmgbsa:
                output = 'MMGBSA' + receptor_suffix
                self.writer.addStage('MMGBSA' + receptor_suffix,
                                     'prime.MMGBSAStage', [lastoutput],
                                     [output])
                lastoutput = output
            # Output from LAST docking stage
            docking_outputs.append(lastoutput)
            self.writer.userOutput(lastoutput)
            docking_offsets.append(str(rec.offset))  # Offset for this receptor
            # Marker for this receptor
            docking_identifiers.append(rec.identifier)
        logger.debug("ADDING POST-DOCKING STAGES")
        if len(docking_outputs) > 1:  # Ensemble docking was done
            # Merge the docking results using the Merge stage:
            # Also keeps 1 (or max_per_lig) conformers per compound
            # so needs to run if XP has multiple input structures per root.
            final_dock_out = "FINAL_DOCK_OUT"
            # If not recombining, the output of the first docking stage won't
            # have the VSW_COMPOUNDFIELD property:
            if not self.recombine:
                # "Use input files directly" radio selected
                stage_unique_field = self.uniquefield
            else:
                stage_unique_field = VSW_COMPOUNDFIELD
            kw = {
                'MAXPERLIG': 1,
                'NREPORT': 10000,
                'UNIQUEFIELD': stage_unique_field,
                'OFFSETS': docking_offsets,
                'MARKERS': docking_identifiers,
            }
            self.writer.addStage("DOCKMERGE", "glide.MergeStage",
                                 docking_outputs, [final_dock_out], kw)
            # Add merged output to useroutput:
            self.writer.userOutput(final_dock_out)
            self.writer.setStructureOutput(final_dock_out)
        elif len(docking_outputs) == 1:
            self.writer.setStructureOutput(docking_outputs[0])
        logger.debug('WRITING THE INPUT FILE')
        self.writer.write()
        logger.debug('DONE')
        return vsw_input_file
if __name__ == '__main__':
    params_file = parse_command_line()
    if not params_file:
        sys.exit(1)
    # check that job parameters file was specified; if it was
    # check that it exists before trying to use it.
    if not os.path.isfile(params_file):
        print('\n Error: Job parameters file "%s" does not exist.' %
              params_file)
        sys.exit(1)
    class vsw_receptor:
        pass
    receptor = vsw_receptor()
    receptor.generate_grid = False
    jp = {'receptors': [receptor]}
    for param_file_line in open(params_file):
        exec(param_file_line)
    inputfile = VSWWriter(jp).writeInputFile()
    print('Written input file:', inputfile)
# EOF