"""
Utilities for writing MacroModel com, sbc, and hst (native input) files.
Copyright Schrodinger, LLC. All rights reserved.
"""
# Contributors:  K Shawn Watts
################################################################################
# Globals/Constants
################################################################################
import os
import re
import warnings
from past.utils import old_div
import schrodinger.infra.mm as mm
import schrodinger.structure as structure
import schrodinger.utils.fileutils as fileutils
_version = '$Revision: 1.32.2.1 $'
################################################################################
# Packages
################################################################################
try:
    import schrodinger.job.jobcontrol as jobcontrol
except ImportError:
    jobcontrol = None
################################################################################
# Functions
################################################################################
[docs]def write_hst_file(com_file, host_list):
    """
    Returns the name of the jobname.hst file written specifically for
    the passed com file.  jobname.com -> jobname.hst.  host_list is
    a list of hostentry name strings, the attributes are looked up
    by jobcontrol.get_host().  If schrodinger.job.jobcontrol can't be
    imported it returns an empty string.  These file are typically only
    used for NPRC distributed bmin jobs.
    """
    if not jobcontrol:
        return ''
    hst_file = '%s.hst' % com_file[:-4]
    hst_fh = open(hst_file, 'w')
    hst_fh.write("# host file for %s\n" % com_file)
    for hostentry in host_list:
        host = jobcontrol.get_host(hostentry)
        for host_attribute in str(host).split(','):
            hst_fh.write(host_attribute.strip())
            hst_fh.write("\n")
        hst_fh.write("\n")
    hst_fh.flush()
    hst_fh.close()
    return hst_file 
################################################################################
# Classes
################################################################################
[docs]class ComUtil:
    """
    A class for writing MacroModel com files.
    The class has methods to write jobname.com files for a broad range
    of calculation types.  The class is initallized with reasonable OPCD
    argument default values, so the methods like mini() are usable 'out
    of the box'.  The OPCD arguments are easily modified by accessing the
    dict values.  There is a generic writeComFile() method that allows
    you write any com file by passing a list of arguments, IO and OPCDs.
    If no arguments are provide to the constructor the defaults are:
        - Potential energy surface is OPLS_2005, gas phase, with extended
          non-bonded cutoffs.
        - Minimization run up to 50000 iterations of PRCG.  Convergence is
          set to a gradient of 0.01.
        - Energy window is 50 kJ/mol and is only checked at the end of the
          minimization.
        - MacroModel/Maestro interaction files are not generated.  Enable by
          setting self.mmod = True.
        - Atom distance for conformer equivalence is 0.25 angstroms.
    API examples::
        # Example 1 ############################################################
        # Writing an energy listing com file via writeComFile.
        import schrodinger.application.macromodel.utils as mmodutils
        mcu = mmodutils.ComUtil() # create a default instance of the class
        com_file_args = [
            'example1.com', # The name of the com file to write.
            'input.mae', # The name of the input structure file.
            'output.mae', # The name of the output structure file.
            'EXNB',
            'BDCO',
            'FFLD',
            'READ',
            'ELST'
        ]
        mcu.writeComFile(com_file_args)
        # Example 2 ############################################################
        # Writing an energy listing com file via a utility methods.
        import schrodinger.application.macromodel.utils as mmodutils
        mcu = mmodutils.ComUtil()
        com_file_name = mcu.elst('example2.mae')
        # Example 3 ############################################################
        # Writing an minimization com file with customized parameters.
        import schrodinger.application.macromodel.utils as mmodutils
        mcu = mmodutils.ComUtil()
        mcu.MINI[1] = 9 # Use Truncated Newton Conjugate Gradient minimizer.
        mcu.CONV[5] = 0.0001 # Halt when gradient reaches this stringent value.
        mcu.mini('example3.mae')
        # Example 4 ############################################################
        # Writing an minimization com file that includes water GB/SA solvation.
        import schrodinger.application.macromodel.utils as mmodutils
        mcu = mmodutils.ComUtil(solv=True)
        mcu.mini('example4.mae')
        # Example 5 ############################################################
        # Writing an minimization com file that requests a substructure.
        # See the SbcUtil class for how to construct a .sbc file, this
        # example assumes an appropriate .sbc file already exists.
        import schrodinger.application.macromodel.utils as mmodutils
        mcu = mmodutils.ComUtil(subs=True)
        mcu.mini('example5.mae')
    :ivar serial_nonconf_input:
         If True enable DEBG 55 property management when self.serial is also
         True.
    :vartype serial_nonconf_input:
            boolean
    :ivar cgo3:
            If True enable the writing of CGO3 to com files.  By default
            bdco=True and the opcode is written if it appears in the list
            of opcodes.
    :vartype cgo3:
            boolean
    :ivar cgo4:
            If True enable the writing of CGO3 to com files.  By default
            bdco=True and the opcode is written if it appears in the list
            of opcodes.
    :vartype cgo4:
            boolean
    :ivar cgo5:
            If True enable the writing of CGO5 to com files.  By default
            bdco=True and the opcode is written if it appears in the list
            of opcodes.
    :vartype cgo5:
            boolean
    :ivar cgo6:
            If True enable the writing of CGO6 to com files.  By default
            bdco=True and the opcode is written if it appears in the list
            of opcodes.
    :vartype cgo6:
            boolean
    :ivar beautify:
            If True don't write disabled opcds.  If False, disabled opcds
            are printed as commented-out lines.  Default is True
    :vartype beautify:
            boolean
    :ivar refit_args:
            If True reformat large float value to fit in the alternate field
            format (%6.3f - %9.0f).  If False, values are printed as %5.4f
            Default is True.
    :vartype refit_args:
            boolean
    :ivar mmod:
            If True enable MMOD and write MacroModel/Maestro
            interaction-monitor files.  This is seldom useful in a script
            context, but is provided to as an option to make com files look
            more like Maestro generated files.  Default is False.
    :vartype mmod:
            boolean
    :ivar structure_file_ext:
            Default structure file extention for file names automatically
            determined by outComFile().  Default is '.mae'.
    :vartype structure_file_ext:
            string
    """
[docs]    def __init__(self,
                 ffld="oplsaa2005",
                 solv=False,
                 ddde=False,
                 bdco=True,
                 chgf=False,
                 exnb=True,
                 auto=True,
                 auop=True,
                 subs=False,
                 serial=False,
                 demx=True,
                 demx_final=50,
                 demx_prelim=100,
                 algn=False,
                 nant=False,
                 chyd=True,
                 nprc=False,
                 flap=False,
                 mcrc=False,
                 ddebug=False,
                 nice=False,
                 wait=False,
                 host="",
                 hostfile="",
                 arch="",
                 local=False,
                 interval=5,
                 no_redirect=False,
                 tmpdir="",
                 ver="",
                 rel="",
                 proj="",
                 disp="",
                 user="",
                 para_bmin=False,
                 para_bmin_jobcts=100,
                 para_bmin_njobs=10,
                 para_bmin_output_org="",
                 debug=False,
                 beautify=True,
                 refit_args=True):
        """
        This method loads the default data members and methods.
        It sets default OPCD arguments, and com file preferences
        (e.g. no solvent model, OPLSAA_2005, yes to BDCO).
        :param ffld:
                String identifier for the force field.  The FFLD value is
                assigned by regular expression evaluation of the string.
        :type ffld:
                string
        :param solv:
                If True enable the writing of SOLV to com files.  By default
                solv=False and the opcode is not written, even if it appears
                in the list of opcodes.
        :type solv:
                boolean
        :param ddde:
                If True enable distance-dependent dielectric electrostatic
                treatment, with a dielectric constant of 4.0.
        :type ddde:
                boolean
        :param serial:
                If True enable serial processing mode.  This sets AUTO arg6=1,
                and MCOP arg4=1 where appropriate.
        :type serial:
                boolean
        :param bdco:
                If True enable the writing of BDCO to com files.  By default
                bdco=True and the opcode is written if it appears in the
                list of opcodes.
        :type bdco:
                boolean
        :param subs:
                If True enable the writing of SUBS to com files.  By default
                subs=False and the opcode is not written, even if it appears
                in the list of opcodes.
        :type subs:
                boolean
        :param chgf:
                If True enable the writing of CHGF to com files.  By default
                chgf=False and the opcode is not written, even if it appears
                in the list of opcodes.
        :type chgf:
                boolean
        :param exnb:
                If True enable the writing of EXNB to com files.  By default
                exnb=True.  If False it is not written, even if it appears
                in the list of opcodes.
        :type exnb:
                boolean
        :param auto:
                If True enable the writing of AUTO to com files.  By default
                auto=True.  If False it is not written, even if it appears
                in the list of opcodes.
        :type auto:
                boolean
        :param auop:
                If True enable the writing of AUOP to com files.  By default
                auop=True.  If False it is not written, even if it appears
                in the list of opcodes.
        :type auop:
                boolean
        :param nant:
                If True enable the writing of NANT to com files.  By default
                nant=False.  If False it is not written, even if it appears
                in the list of opcodes.
        :type nant:
                boolean
        :param algn:
                If True enable the writing of COPY/ALGN to MBAE com files
                generated by the pre-defined methods, e.g. mbaeCsLmod()
        :type algn:
                boolean
        :param demx:
                If True enable the writing of DEMX to com files.  By default
                demx=True.  If False it is not written, even if it appears
                in the list of opcodes.
        :type demx:
                boolean
        :param demx_final:
                Energy window for the final DEMX test.  Default is 50 kJ/mol
        :type demx_final:
                float
        :param demx_prelim:
                Energy window for the preliminary DEMX test.  Default is
                100 kJ/mol.   By default DEMX arg2 is set to 10000, so the
                prelimnary test is seldom performed.
        :type demx_prelim:
                float
        """
        ##
        # Data members
        ##
        # version
        self.version = '$Revision: 1.32.2.1 $'
        self._version = self.version  # Adopt current _version convention
        self._debug = debug  # this class only, not MacroModel
        # These modify the command expression returned by getLaunchCommand()
        self.ddebug = ddebug  # verbose jobcontrol debugging
        self.nice = nice  # niced jobs finish last
        self.wait = wait  # return prompt option
        self.host = host  # run jobs locally
        self.hostfile = hostfile  # use selected hostfile
        self.proj = proj  # maestro project path
        self.disp = disp  # maestro project disposition
        self.user = user  # user id for job invocation
        self.arch = arch  # platform architechure
        self.local = local  # no file copies
        self.interval = interval  # jobcontrol monitoring interval (5 is typical)
        self.no_redirect = no_redirect  # output redirection
        self.tmpdir = tmpdir  # temp file directory
        self.ver = ver  # schrodinger build path
        self.rel = rel  # schrodinger build release
        self.para_bmin = para_bmin  # use utilites/para_bmin, not bmin
        self.para_bmin_jobcts = para_bmin_jobcts  # pbmin max structs/subjob
        self.para_bmin_njobs = para_bmin_njobs  # pbmin num of subtasks
        self.para_bmin_output_org = para_bmin_output_org  # pbmin output
        # These modify the OPCD in getOpcdArgs(), a '!' comment turns off OPCD
        self.solv = solv  # GB/SA with water if true
        self.exnb = exnb  # extended nonbonded distances (8 vdw, 20 q-q) if true
        self.bdco = bdco  # cutoffs based on interaction type, smart mu cutoffs
        self.chgf = chgf  # partial atomic charges from structure file, not ffld
        self.ddde = ddde  # dist dependent electrostat., dielectric epsilon=4
        self.auto = auto  # auto setup for mult-MINI, MCMM, mixed MCMM low-mode
        self.auop = auop  # auop modifiers
        self.subs = subs  # use sbc file if true
        self.serial = serial  # AUTO/MCOP set for serial if true
        self.demx = demx  # use delta energy window if true
        self.algn = algn  # use ALGN/COPY to position ligands in MBAE if true
        self.chyd = chyd  # disable hybond dipoles if true
        self.nant = nant  # don't consider enantiomers to be duplicates if true
        self.nprc = nprc  # enable dbmin
        self.flap = flap  # enable FLAP ring vertex sampling.
        self.mcrc = mcrc  # enable MCRC ring template.
        self.mmod = False  # enable MacroModel/Maestro interaction files.
        self.serial_nonconf_input = False  # Set DEBG 55 property management if self.serial is also True.
        # ffld, demx_final and demx_prelim bound below
        self.cgo3 = True
        self.cgo4 = True
        self.cgo5 = True
        self.cgo6 = True
        # These track and control how OPCD are writen in writeComFile()
        self.beautify = beautify  # if true, don't print commented-out opcds
        self.refit_args = refit_args  # if true, quietly refit to max value
        self._written = []  # array of opcd written, used to test multiplicity
        self.structure_file_ext = '.mae'  # File extension in outComFile()
        # MacroModel debugging-switches
        self.DEBG = {}  # assoc. array of debug switches
        # Input structure reading commands.
        self.READ = {}
        self.READ[1] = 0
        self.BGIN = {}
        self.BGIN[1] = 0
        self.BGIN[2] = 0
        # MacroModel force field args
        self.FFLD = {}
        self.FFLD[1] = 14  # OPLS_2005 - unmatched ffld string assignment.
        self.FFLD[2] = 1  # constant dielectric treatment
        self.FFLD[3] = 0  # ffld h-bond
        self.FFLD[4] = 0  # BMFF option
        self.FFLD[5] = 1.0  # dielectric constant
        # force field selection requires some regex
        mm2 = re.compile(r'^MM2$', re.IGNORECASE)
        mm3 = re.compile(r'^MM3$', re.IGNORECASE)
        amber = re.compile(r'^AMBER\*?$', re.IGNORECASE)
        amber94 = re.compile(r'^AMBER94$', re.IGNORECASE)
        opls = re.compile(r'^OPLS$', re.IGNORECASE)
        mmff = re.compile(r'^MMFF(-|_)?(94)?$', re.IGNORECASE)
        mmffs = re.compile(r'^MMFF(-|_)?(94)?s(94)?$', re.IGNORECASE)
        opls_2003 = re.compile(r'^OPLS-?(AA)?(-|_)?(2003)$', re.IGNORECASE)
        opls_2005 = re.compile(r'^OPLS-?(AA)?(-|_)?(2005)$', re.IGNORECASE)
        opls_2_x = re.compile(r'^OPLS2\.(0|1)$', re.IGNORECASE)
        if mm2.match(ffld):
            self.FFLD[1] = 1
        elif mm3.match(ffld):
            self.FFLD[1] = 2
        elif amber.match(ffld):
            self.FFLD[1] = 3
        elif amber94.match(ffld):
            self.FFLD[1] = 4
        elif opls.match(ffld):
            self.FFLD[1] = 5
        elif mmff.match(ffld):
            self.FFLD[1] = 10
            self.FFLD[4] = 0  # turn off BMFF
        elif mmffs.match(ffld):
            self.FFLD[1] = 10
            self.FFLD[4] = 1  # turn on BMFF
        elif opls_2003.match(ffld):
            self.FFLD[1] = 14
        elif opls_2005.match(ffld):  # keep for back compatibility
            self.FFLD[1] = 14
        elif mm.opls_name_to_version(ffld) == 14:
            self.FFLD[1] = 14
        elif opls_2_x.match(ffld):  # keep for back compatibility
            self.FFLD[1] = 16
        elif mm.opls_name_to_version(ffld) == 16:
            self.FFLD[1] = 16
        # MacroModel solvent args
        self.SOLV = {}
        self.SOLV[1] = 3  # GB/SA
        self.SOLV[2] = 1  # water
        # MacroModel extended nonbonded args
        self.EXNB = {}
        self.EXNB[1] = 0  # just leverage BDCO
        self.EXNB[2] = 5  # close/long range cutoff
        self.EXNB[5] = 8.0  # vdW cutoff, A
        self.EXNB[6] = 20.0  # q-q cutoff, A
        self.EXNB[7] = 4.0  # H-bond cutoff, A
        self.EXNB[8] = 8.0  # Fmm cutoff for fixed/frozen atoms
        # MacroModel bond dipole cutoff args
        self.BDCO = {}
        self.BDCO[5] = 0.0  # q-mu cutoff = sqrt((cutoff_q-q^3))
        self.BDCO[6] = 99999.0  # q-q cutoff, ~none
        # MacroModel charge file args
        self.CHGF = {}
        self.CHGF[1] = 0  # charges from input structure file
        # MacroModel energy list args
        self.ELST = {}
        self.ELST[1] = -1  # just energy to log file
        self.ELST[2] = 0  # kJ/mol
        self.DLST = {}
        self.DLST[3] = 3  # really, really complete
        # MacroModel geometry optimization
        self.CONV = {}
        self.CONV[1] = 2  # converge on derivative gradient
        self.CONV[2] = 0  # extent modifier
        self.CONV[5] = 0.01  # extent converge on gradient
        self.MINI = {}
        self.MINI[1] = 1  # PRCG, ok general and best for large structures
        self.MINI[2] = 0  # default line searching
        self.MINI[3] = 50000  # iterations
        self.MINI[4] = 0  # DEMX window id
        self.MINI[5] = 0  # default step-size buffer
        self.MINI[6] = 0.001  # hessian cutoff, where appropriate
        self.MINI[7] = 0.0  # update nonbond tied to vdw
        self.MINI[8] = 0  # report interval energy/rms grad/ rms move
        self.MINI[9] = 1  # PRCG, best general and large structures
        self.MINI[10] = 0  # default line searching
        self.MINI[11] = 50000  # iterations
        self.MINI[12] = 0  # DEMX window id
        self.MINI[13] = 0  # default step-size buffer
        self.MINI[14] = 0.001  # hessian cutoff, where appropriate
        self.MINI[15] = 0.0  # update nonbond tied to vdw
        self.MINI[16] = 0  # report energy/rms grad/ rms move
        # MacroModel MBAE (ediff defaults)
        self.MBAE = {}
        self.MBAE[1] = 1  # ediff
        self.MBAE[2] = 0  # mini calc
        self.MBAE[3] = 0  # save complex
        self.MBAE[9] = -1  # turn off mbae
        # MacroModel ASET args for MBAE
        self.ASET = {}
        # MacroModel geometry drives
        # distance scan A-B, E-F
        # angle scan A-B-C, E-F-G
        # torsion scan A-B-C-D, E-F-G-H
        # Nsteps = |arg5-arg6|/arg7 + 1, with a max of 100
        self.DRIV = {}
        # MacroModel mult minimization filter args
        self.DEMX = {}
        self.DEMX[1] = 0  #  MINI window id
        self.DEMX[
            2] = 10000  # pretest E after arg2 iterations=>pretest unlikely
        self.DEMX[5] = demx_final  # window for final test, kJ/mol [50 kJ/mol]
        self.DEMX[6] = demx_prelim  # window for pre test, kJ/mol [100 kJ/mol]
        # MacroModel logP for mini
        # DEBG with 530, 531
        self.LOGP = {}
        self.LOGP[2] = 9  # water->octanol
        # MacroModel Molecular Symmetry Library
        self.MSYM = {}
        self.MSYM[1] = 1  #  use it
        # MacroModel low mode csearch args
        self.LMCS = {}
        self.LMCS[1] = 1000  # step search
        self.LMCS[2] = 0  # max stored structs = arg1 or 10,000
        self.LMCS[3] = 10  # 10 pure low modes explored
        self.LMCS[4] = 0  # global search
        self.LMCS[5] = 0  # single LMCS steps
        self.LMCS[6] = 0.25  # allowed approach distance in A
        self.LMCS[7] = 3.0  # minimum distance, A
        self.LMCS[8] = 6.0  # maximum distance, A
        self.LMCS[9] = 1000  # step search
        self.LMCS[10] = 0  # max stored structs = arg1 or 10,000
        self.LMCS[11] = 10  # 10 pure low modes explored
        self.LMCS[12] = 0  # global search
        self.LMCS[13] = 0  # single LMCS steps
        self.LMCS[14] = 0.25  # allowed approach distance in A
        self.LMCS[15] = 3.0  # minimum distance, A
        self.LMCS[16] = 6.0  # maximum distance, A
        # MacroModel large scale low mode csearch args
        self.LMC2 = {}
        self.LMC2[1] = 1000  # search steps
        self.LMC2[2] = 0  # keep upto arg1 structs
        self.LMC2[3] = 30  # number of lowmodes
        self.LMC2[4] = 0  # global search mode
        self.LMC2[5] = 0  # calc only the first time
        self.LMC2[6] = 0.25  # allowed approach fraction of vdw
        self.LMC2[7] = 3.0  # min  dist
        self.LMC2[8] = 6.0  # max dist
        self.ARPK = {}
        self.ARPK[1] = 0  # analytical hessian treatment
        self.ARPK[2] = 300  # dimension of small prob
        self.ARPK[3] = 450  # number of small probs
        self.ARPK[4] = 200  # MB of memory (arg1=0)
        self.ARPK[5] = 0  # default accuracy 0.0001
        self.ARPK[6] = 0  # not used with arg1=0
        self.ARPK[7] = 0.01  # not used with arg1=0
        self.ARPK[8] = 0.001  # sparse hessian cutoff
        # MacroModel confsearch general
        self.MCSS = {}
        self.MCSS[1] = 2  # use directed search
        self.MCSS[2] = 0  # don't modify arg5 energy window
        self.MCSS[3] = 0  # don't use torsional memory
        self.MCSS[5] = self.DEMX[5]  # e window for start point acceptance
        self.MCOP = {}
        self.MCOP[1] = 1  # report every step
        self.MCOP[4] = 0  # lcms serial search if > 0
        self.MCOP[5] = 0  # fraction of mols/tors moves
        self.MCOP[6] = 0  # maximum number of confs kept
        self.MCOP[7] = 0  # number of confs read for csrch seed
        self.MCOP[9] = 1  # report every step
        self.MCOP[12] = 1  # lcms serial search if > 0
        self.MCOP[14] = 0  # maximum number of confs kept
        self.MCOP[15] = 0  # number of confs read for csrch seed
        self.NANT = {}  # takes no args
        self.CRMS = {}
        self.CRMS[5] = 4.184  # max energy diff required to skip geometric comp
        self.CRMS[6] = 0.25  # max dist between 'equivalent' corresponding atoms
        self.CRMS[7] = 60.0  # maximum dihedral angle change for polar hydrogens
        self.SEED = {}
        self.SEED[1] = 0  # random number generator seed
        self.COMP = {}  # this is more for addc, util.py methods use AUTO
        self.ADDC = {}
        self.ADDC[1] = 0  # Do not use Jaguar energies
        self.ADDC[2] = 0  # Store at max. 10,000 structures
        # MacroModel ConfGen torsion searches
        self.CGEN = {}
        self.CGEN[1] = 1000  # number of CGEN steps
        self.CGEN[2] = 0  # save up to arg1 results
        self.CGEN[4] = 2  # sample all combinations of peripheral groups
        self.CGEN[6] = 0.25  # closest vdw approach
        self.CGOP = {}
        self.CGOP[1] = 0  # look for symmetry in terminal groups
        self.CGOP[2] = 2  # use ring_conf library for sampling rings
        self.CGOP[3] = 500  # minimization iterations
        self.CGOP[4] = 1  # sample amide geometry
        self.CGOP[5] = 20  # maximum relative ring conformation energy in kJ/mol
        self.CGOP[6] = 8.0  # max num of total ring confs
        self.CGOP[7] = 2.0  # max num of confs per ring system
        self.CGOP[8] = 50.0  # maximum relative internal (confgen) energy
        self.CGO2 = {}
        self.CGO2[5] = 0.6  # vdw radii scale factor for close approach
        self.CGO2[6] = 1.0  # scale factor for close atom gradients/penalty
        self.CGO2[7] = 0.0  # min vdw atom radius
        self.CGO3 = {}  # sp2-sp3 small barrier sampling
        self.CGO3[1] = 6  # Cosine function frequency, number of minima
        self.CGO3[5] = 12.0  # Energy range between min and max in cosine pot'l
        self.CGO3[6] = 0.0  # Don't eliminate high energy minima
        self.CGO3[7] = 1000.0  # Restraint prefactor (v1?)
        self.CGO3[8] = 10.0  # in degrees, half-width of flat bottom well.
        self.CGO4 = {}  # q-q contact Guassian penalty
        self.CGO4[5] = 5.0  # in Ang, standard deviaton of Gaussian penalty, s
        self.CGO4[6] = 2.5  # maximum value for Gaussian penalty, A
        self.CGO4[7] = 0.75  # Penalty cutoff
        self.CGO5 = {}  # q-h_bond contact Guassian penalty
        self.CGO5[5] = 5.0  # in Ang, standard deviaton of Gaussian penalty, s
        self.CGO5[6] = 1.0  # maximum value for Gaussian penalty, A
        self.CGO5[7] = 0.25  # Penalty cutoff
        self.CGO6 = {}  # close contact Guassian penalty
        self.CGO6[5] = 2.5  # in Ang, standard deviaton of Gaussian penalty, s
        self.CGO6[6] = 0.1  # maximum value for Gaussian penalty, A
        self.CGO6[7] = 1.00  # Penalty cutoff
        self.CHYD = {}
        self.CHYD[1] = 1  # turn off hydrogen bond electrostatics
        self.CHYD[2] = 0  #
        self.CHYD[3] = 0  #
        self.CHYD[4] = 0  #
        self.CHYD[5] = 0.0  #
        self.CHYD[6] = 0.0  #
        self.CHYD[7] = 0.0  #
        self.CHYD[9] = 0.0  #
        self.FLAP = {}
        self.FLAP[5] = 0.5  # probability of using a FLAP step.
        self.MCRC = {}
        self.MCRC[1] = 0  # use ring templates in conformational search.
        self.MCRC[5] = 0.5  # probability of using a template.
        # MacroModel monte carlo confsearches
        self.MCNV = {}
        self.MCNV[1] = 1  # min number DOF altered
        self.MCNV[2] = 0  # max number DOF altered => entire system
        self.MCNV[3] = 0  # don't use torsional clusters
        self.MCMM = {}
        self.MCMM[1] = 1000  # iterations search
        self.MCMM[2] = 0  # save up to arg1 results
        self.MCMM[4] = 0  # global seach mode
        self.MCMM[6] = 0.25  # closest vdW approach
        self.MCMM[9] = 1000  # iterations search
        self.MCMM[10] = 0  # save up to arg1 results
        self.MCMM[12] = 0  # global seach mode
        self.MCMM[14] = 0.25  # closest vdW approach
        self.SPMC = {}
        self.SPMC[1] = 1000  # iterations search
        self.SPMC[2] = 0  # save up to arg1 results
        self.SPMC[3] = 24  # 360/arg3 degs max res of tors mods 15 degs
        self.SPMC[4] = 0  # global seach mode
        self.SPMC[6] = 0.25  # closest vdW approach
        # DEBG with 520, 521
        self.AUTO = {}
        self.AUTO[1] = 0  # use it, new setup
        self.AUTO[2] = 2  # comp new setup with heavy + OH
        self.AUTO[3] = 1  # chig atoms, new setup
        self.AUTO[4] = 1  # torc, new setup
        self.AUTO[5] = 1  # tors, new setup
        self.AUTO[6] = -1.0  # non-serial
        self.AUTO[7] = 5.0  # minimum ring size to setup
        self.AUTO[8] = 0.0  # 'restrictive' sampling (MacroModel 9.0 deflault)
        self.AUOP = {}
        self.AUOP[5] = -1  # N search steps = (arg5)*(number of torsions+mols)
        # -1 so we get the same ComUtil behavior as always
        # MacroModel Molecular Dynamic args
        self.MDYN = {}
        self.MDYN[1] = 0  # no .mmo listing
        self.MDYN[2] = 1  # SHAKE bonds to H.  Don't SHAKE MCSD jobs
        self.MDYN[3] = 1  # Stochastic
        self.MDYN[4] = 0  # 25 fs enthalpy data collation interval*
        self.MDYN[5] = 1.5  # time step (fs)
        self.MDYN[6] = 1  # simulation times, ps
        self.MDYN[7] = 300.0  # bath temp, K
        self.MDYN[8] = 0.2  # bath time constant
        self.MDYN[9] = 0  # no .mmo listing
        self.MDYN[10] = 1  # SHAKE bonds to H.  Don't SHAKE MCSD jobs
        self.MDYN[11] = 1  # Stochastic
        self.MDYN[12] = 0  # 25 fs enthalpy data collation interval*
        self.MDYN[13] = 1.5  # time step (fs)
        self.MDYN[14] = 1  # simulation times, ps
        self.MDYN[15] = 300.0  # bath temp, K
        self.MDYN[16] = 0.2  # bath time constant
        # * this dynamically assigned based on run length:
        # 5 fs for runs < 10 ps long
        # >25 fs for runs > 1000 ps long
        self.MDIT = {}
        self.MDIT[5] = 300.0  # initial temp, K (~27 deg C)
        self.MDFT = {}
        self.MDFT[5] = 300.0  # final temp, K (~27 deg C)
        self.MDVE = {}  # arg1=0(write) arg1=1(read)
        # MacroModel Monte Carlo/Stochastic Dynamic mixed mode
        self.MCSD = {}
        self.MCSD[1] = 1  # 1:1 mc/sd
        self.MCSD[5] = 0  # acceptance ratio
        # MacroModel structure selection
        self.MDSA = {}
        self.MDSA[1] = 10  # write 10 structures
        # MacroModel Metropolis Monte Carlo
        self.MCLO = {}
        self.MCLO[1] = 1  # list to mmo file
        self.MCLO[3] = -1  # perform one million steps
        self.MCLO[7] = 300  # temp, kelvin
        # MacroModel IMPS/JBW/ADF
        self.IMPS = {}
        self.IMPS[1] = 1  # perform IMPS transforms
        self.IMPS[2] = 0  # all trials equally probable
        self.IMPS[7] = -10.0  # transform if stuck after one million ps
        self.IMPO = {}
        self.IMPO[1] = 1  # print E matix to log file
        self.IMPO[2] = 0  # abort if ediff exceeds threshold
        self.IMPO[3] = 0  # write only high energy structures
        self.IMPO[4] = 0  # no .ino output
        self.IMPO[5] = 20  # conversion threshold, kJ
        self.IMPO[6] = -1.0  # compare with COMP
        # MacroModel ZMAT args
        self.ZMAT = {}  # all numerical args, groups of eight
        # MacroModel Free energy perturbation args
        self.FESA = {}
        self.FESA[5] = 0.0  # start of left side window
        self.FESA[6] = 0.05  # start of right side window
        # this default calls for 20 lambda windows
        # MacroModel loop modeling/rebuilding
        self.LOOP = {}
        self.LOOP[1] = 0  # N-terminus:  Nitrogen's atom number
        self.LOOP[2] = 0  # C-terminus:  Carbonyl Carbon's atom number
        self.LOOP[3] = 0  # generate alt confs of input loop  (1=new seq)
        self.LOOP[4] = 100  # number of confs to generate
        self.LOOP[5] = 10000  # max number of confs to keep
        self.LOOP[6] = 0.25  # allowed approach distance: arg6*(sum(vdW-radii))
        self.LOOP[7] = 0  # generate COMP and CHIG for heavy atoms in loop
        self.LPOP = {}  #
        # MacroModel GEOM
        self.GEOM = {}
        # MacroModel Align/copy
        self.ALGN = {}
        self.ALGN[1] = 3  # center and princ axes align with ref
        self.ALGN[2] = 1  # weight atoms by atomic mass
        self.ALGN[3] = 5  # generate all four and write
        # primary, primary*C2, secondary, secondary*C2
        # MacroModel MINTA args
        self.MNTA = {}
        self.MNTA[1] = 5  # MINTA interations: number of blocks
        self.MNTA[2] = 2000  # evaluations per block
        self.MNTA[3] = 0  # non-adaptive mode (default)
        self.MNTA[4] = 0  # numerical integration all DOFs (default)
        self.MNTA[5] = 300.0  # temp, Kelvin
        self.MNTA[6] = 1.0  # Ang dist from ideal geom, hard limit
        self.MNTA[7] = 3.0  # Stdev from ideal geom, soft limit
        # MacroModel SUBS, FXAT, FXCO, FXDI, FXTA, FXBA,
        # Note: ASL1, ASL2 don't belong in .com files, use SbcUtil instead
        self.SUBS = {}  # native potential (may all have FXAT constraint)
        self.FXAT = {}  # constrained/frozen atoms
        self.FXDI = {}  # constrain distance
        self.FXTA = {}  # constrain torsion angle
        self.FXBA = {}  # constrain bond angle
        self.FXCO = {}  # constrain all torsions
        # MacroModel distributed bmin
        self.NPRC = {}
        self.NPRC[1] = 4  # four hosts
        self.NPRC[2] = 100  # steps or molecules per block
        self.NPRC[3] = 60  # 60 sec interval for internal master checkups
        self.NPRC[4] = 0  # no energy pre-test
        # MacroModel oscillator calcs
        self.MTST = {}
        self.MTST[1] = 0  # no listing of freqs
        self.MTST[5] = 2.0  # lower freq limit in wavenumbers (nu-bar)
        self.RRHO = {}
        self.RRHO[1] = 0  # summary in log file
        self.RRHO[2] = 0  # calc: trans, rot and vib
        self.RRHO[3] = 1  # symmetry
        self.RRHO[5] = 300  # temp in K
        self.RRHO[6] = 1.0  # volume in L
        self.RRHO[7] = 2.0  # lower freq limit in wavenumbers (nu-bar)
        self.VIBR = {}
        self.VIBR[1] = 1  # first mode to animate
        self.VIBR[2] = 0  # last mode (0 => only mode arg1 will be animated)
        self.VIBR[3] = 10  # number of frames per 1/4 period
        self.VIBR[4] = 0  # print just eigenvalues to log
        self.VIBR[5] = 1.0  # amplitude
        self.VBR2 = {}
        self.VBR2[1] = 1  # first mode to animate
        self.VBR2[2] = 0  # last mode (0 => only mode arg1 will be animated)
        self.VBR2[3] = 10  # number of frames per 1/4 period
        self.VBR2[4] = 0  # print just eigenvalues to log
        self.VBR2[5] = 5.0  # amplitude
        self.VBR2[6] = 0.0001  # min. magnitude of eigenvalue for 'real' modes
        #For ligprep information
        self.MXAT = {}
        self.MXAT[1] = 0
        self.MXAT[2] = 0
        self.MXAT[3] = 0
        self.MXAT[4] = 0
        self.MXAT[5] = 0.000
        self.MXAT[6] = 0.000
        self.MXAT[7] = 0.000
        self.MXAT[8] = 0.000
        self.CHIP = {}
        self.CHIP[1] = 0
        self.CHIP[2] = 0
        self.CHIP[3] = 0
        self.CHIP[4] = 0
        self.CHIP[5] = 0.000
        self.CHIP[6] = 0.000
        self.CHIP[7] = 0.000
        self.CHIP[8] = 0.000
        self.CHOP = {}
        self.CHOP[1] = 0
        self.CHOP[2] = 0
        self.CHOP[3] = 0
        self.CHOP[4] = 0
        self.CHOP[5] = 0.000
        self.CHOP[6] = 0.000
        self.CHOP[7] = 0.000
        self.CHOP[8] = 0.000
        self.SPAT = {}
        # MacroModel/Maestro interaction.
        self.MMOD = {}
        self.MMOD[1] = 1  # Default.  write mon.mae as frequently as possible.
        self.MMOD[2] = 1  # Recolor atoms by energy gradient.
        # Make collective settings (i.e. ddde, serial) so the
        # instance looks self-consistent.  These setting are enforced
        # again in getOpcdArgs()
        if self.ddde:
            self.solv = False  # mutually exclusive because epsilon != 1
            self.FFLD[2] = 2  # distance dependent
            self.FFLD[5] = 4.0  # epsilon
        if self.serial:
            self.AUTO[6] = 1
        # done with init
        return None 
[docs]    def writeComFile(self, com_args=[]):  # noqa: M511
        """
        This method writes a com file, and returns the name of the
        written com file.
        It requires a list containing the name of the com file to be
        written, the name of the input file, the name of the output file,
        and a series of OPCDs.  The arguments for the OPCDs are looked
        up in turn, evaluating the next eight elements for the specific
        OPCD argument dictionary, each time the OPCD is encountered.
        See setOpcdArgs(), getOpcdArgs(), and __init__() for more
        information about default arguments.
        """
        com_file = com_args[0]  # name of com file and return value
        inp_file = com_args[1]  # name of calculation input mae file
        out_file = com_args[2]  # name of calculation output mae file
        # Get our file handl ready to write, clobber existing file
        com_fh = open(com_file, mode='w')
        # Write the job IO
        com_fh.write(inp_file)
        com_fh.write('\n')
        com_fh.write(out_file)
        com_fh.write('\n')
        # Write the OPCDs with their args
        opcd_data = ''
        for opcd in com_args[3:]:
            # Marshall the opcd arg values
            opcd_data = self.getOpcdArgs(opcd)
            # Skip commented out opcds if beautify is true
            if self.beautify and opcd_data.startswith('!'):
                continue
            # Write the opcd with args
            com_fh.write(opcd_data)
        # Flush, close
        com_fh.flush()
        com_fh.close()
        # Clean up for next write
        self._written = []
        # Return the name of the com file written
        return com_file 
[docs]    def writeSbcFile(self, sbc_args=[]):  # noqa: M511
        """
        Deprecated wrapper for `SbcUtil.writeSbcFile`.
        """
        warnings.warn(
            'This method is deprecated; please use '
            'schrodinger.application.macromodel.utils.SbcUtil.writeSbcFile '
            'as a replacement.',
            DeprecationWarning,
            stacklevel=2)
        msb = SbcUtil()
        msb.SUBS.update(self.SUBS)  # native (may have FXAT constraint also)
        msb.FXAT.update(self.FXAT)  # constrained/frozen atoms
        msb.FXDI.update(self.FXDI)  # constrain distance
        msb.FXTA.update(self.FXTA)  # constrain torsion angle
        msb.FXBA.update(self.FXBA)  # constrain bond angle
        msb.FXCO.update(self.FXCO)  # constrain all torsions
        # return the name of the sbc file written
        return msb.writeSbcFile(sbc_args) 
[docs]    def getOpcdArgs(self, opcd=""):
        """
        This method returns a formatted string of OPCD and arguments
        for the passed OPCD string.
        The arguments come from the hash data members, any unknown or
        unassigned values default to 0 or 0.0.  You may customize the
        self hash arguments to your heart's content prior to invoking
        the method to get the exact behavior you want.  The 'OPCD'
        lookup requires the passed argument to be a key for the hash,
        i.e. uppercase with no whitespace.  The array of arg values are
        numbers, not strings.
        This method keeps count of the number of times an OPCD is
        called and automatically increments the array of args for the
        passed OPCD.  The first call for a given OPCD uses self['OPCD']
        arg slices 1-8, the next call for that OPCD uses slices 9-16,
        the next 17-24, and so on.  writeComFile() zeros the count after
        the com file is serialized.
        """
        # Default opcd arguments. bmin usually interprets 0 as some
        # other default value
        args = [opcd, 0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0]
        # Check multiplicity of this opcode call, i is the index into
        # the opcd array of args.  This is only needed for complex
        # com files, like when multiple calls to MCOP or MBAE are made
        i = 8 * self._written.count(opcd)
        # Apply collective values again to make sure settings are consistent
        if self.ddde:
            self.solv = False  # mutually exclusive because epsilon != 1
            self.FFLD[2] = 2  # distance dependent
            self.FFLD[5] = 4.0  # epsilon
        if self.serial:
            self.AUTO[6] = 1
        if self.serial and self.serial_nonconf_input:
            self.DEBG[1] = 55
        # Gather assigned args for this call
        if opcd in self.__dict__:
            args[0] = opcd
            args[1] = self.__dict__[opcd].get(i + 1, 0)
            args[2] = self.__dict__[opcd].get(i + 2, 0)
            args[3] = self.__dict__[opcd].get(i + 3, 0)
            args[4] = self.__dict__[opcd].get(i + 4, 0)
            args[5] = self.__dict__[opcd].get(i + 5, 0)
            args[6] = self.__dict__[opcd].get(i + 6, 0)
            args[7] = self.__dict__[opcd].get(i + 7, 0)
            args[8] = self.__dict__[opcd].get(i + 8, 0)
        # Record opcd used to later multiplicity checks.  This
        # semi-private array gets emptied after writeComFile()
        # serializes the com file
        self._written.append(opcd)
        # Toggle unrequested options off
        # NPRC on/off
        if (opcd == 'NPRC' and not self.nprc):
            args[0] = '!NPRC'
        # GB/SA on/off
        elif (opcd == 'SOLV' and not self.solv):
            args[0] = '!SOLV'
        # Extended nonbonded on/off
        elif (opcd == 'EXNB' and not self.exnb):
            args[0] = '!EXNB'
        # Charges from structure input file on/off
        elif (opcd == 'CHGF' and not self.chgf):
            args[0] = '!CHGF'
        # Bond dipole cutoff on/off
        elif (opcd == 'BDCO' and not self.bdco):
            args[0] = '!BDCO'
        # Automatic variable setup/serial setup on/off
        elif (opcd == 'AUTO'):
            if not self.auto:
                args[0] = '!AUTO'
        # Automatic variable setup/serial setup on/off
        elif (opcd == 'AUOP'):
            if not self.auop:
                args[0] = '!AUOP'
        # .sbc file in use
        elif (opcd == 'SUBS' and not self.subs):
            args[0] = '!SUBS'
        # Allow hydrogen bond electrostatics
        elif (opcd == 'CHYD' and not self.chyd):
            args[0] = '!CHYD'
        # Don't consider enantiomers as duplicates
        elif (opcd == 'NANT' and not self.nant):
            args[0] = '!NANT'
        # Enable 9.6 Confgen options: CGO3,CGO4, CGO5, CGO6
        elif (opcd == 'CGO3' and not self.cgo3):
            args[0] = '!CGO3'
        elif (opcd == 'CGO4' and not self.cgo4):
            args[0] = '!CGO4'
        elif (opcd == 'CGO5' and not self.cgo5):
            args[0] = '!CGO5'
        elif (opcd == 'CGO6' and not self.cgo6):
            args[0] = '!CGO6'
        elif (opcd == 'FLAP' and not self.flap):
            args[0] = '!FLAP'
        elif (opcd == 'MCRC' and not self.mcrc):
            args[0] = '!MCRC'
        elif (opcd == 'MMOD' and not self.mmod):
            args[0] = '!MMOD'
        # Energy window assignments on/off
        elif (opcd == 'DEMX'):
            if not self.demx:
                args[0] = '!DEMX'
# No real reason to redefine arg2, maybe the user knows best?
# now arg2 is set to a large value by default: 10000 iterations
# which means the pretest is unlikely to be triggered
#           args[2] = int(self.MINI[3]/2) # pre test
# END formating workaround to get a four char string
        elif (opcd == 'END'):
            args[0] = ' END '
        # arg1-4 value size check
        for arg_pos in [1, 2, 3, 4]:
            if len(str(args[arg_pos])) > 6:
                print("%s arg %d value is too large for com file format." %
                      (opcd, args[arg_pos]))
                if self.refit_args and args[arg_pos] > 0:
                    args[arg_pos] = 999999
                elif self.refit_args and args[arg_pos] < 0:
                    args[arg_pos] = -99999
        # arg5-8 value size check, reformatting if needed.
        opcd_format = {}
        for arg_pos in [5, 6, 7, 8]:
            opcd_format[arg_pos] = '%5.4f'
            value = str(args[arg_pos])
            if len(value.split('.')[0]) == 6:
                opcd_format[arg_pos] = '%6.3f'
            elif len(value.split('.')[0]) == 7:
                opcd_format[arg_pos] = '%7.2f'
            elif len(value.split('.')[0]) == 8:
                opcd_format[arg_pos] = '%8.1f'
            elif len(value.split('.')[0]) == 9:
                opcd_format[arg_pos] = '%9.0f.'  # note the trailing '.'
            elif len(value.split('.')[0]) >= 10:
                print("%s arg %f value is too large for com file format." %
                      (opcd, args[arg_pos]))
                if self.refit_args and args[arg_pos] > 0:
                    args[arg_pos] = 999999999
                elif self.refit_args and args[arg_pos] < 0:
                    args[arg_pos] = -99999999
                opcd_format[arg_pos] = '%9.0f.'
        # Cast arg value number as a string.
        args[1] = '%d' % args[1]
        args[2] = '%d' % args[2]
        args[3] = '%d' % args[3]
        args[4] = '%d' % args[4]
        args[5] = opcd_format[5] % args[5]
        args[6] = opcd_format[6] % args[6]
        args[7] = opcd_format[7] % args[7]
        args[8] = opcd_format[8] % args[8]
        # Justify the string so we can join the list elements contigiously.
        args[0] = args[0].rjust(5)
        args[1] = args[1].rjust(8)  # eight takes up the OPCD trailing space
        args[2] = args[2].rjust(7)
        args[3] = args[3].rjust(7)
        args[4] = args[4].rjust(7)
        args[5] = args[5].rjust(11)  # the decimal takes a char
        args[6] = args[6].rjust(11)
        args[7] = args[7].rjust(11)
        args[8] = args[8].rjust(11)
        args.append('\n')
        args_string = "".join(args)
        return args_string 
[docs]    def setOpcdArgs(self,
                    opcd="",
                    arg1=0,
                    arg2=0,
                    arg3=0,
                    arg4=0,
                    arg5=0.0,
                    arg6=0.0,
                    arg7=0.0,
                    arg8=0.0):
        """
        This method returns True after adding the passed values to the
        desired opcd dictionary.
        The dictionary is selected by the first parameter, which is
        required.  The rest of the parameters are mapped to the next
        eight argument indices in the dictionary.  Unspecified values
        default to 0 or 0.0.
        This method determines the highest existing index (numeric hash
        key), and then assigns the passed args to the next available
        slots.  You may want to first blank the dictionary with the
        appropriate call of self['OPCD'].clear().
        Assumming the dictionary has has been cleared, the first 'set' of
        a given OPCD assigns self['OPCD'] arg indices 1-8, the next call
        for that OPCD defines indices 9-16, the next 17-24, and so on.
        """
        # Try counting the highest indexed key.
        i = 0
        try:
            keys = list(self.__dict__[opcd])
            if len(keys) > 0:
                i = keys[-1]
        except KeyError:
            self.__dict__[opcd] = {}
        # Determine the proper index
        y = divmod(i,
                   8)  # tuple[0] = num of whole sets, tuple[1] = num partials
        i = y[0]
        if y[1] > 0:
            i += 1
        # Advance the index by eight, or one 'row', or one call...
        i = 8 * i
        # Assign
        self.__dict__[opcd][i + 1] = arg1
        self.__dict__[opcd][i + 2] = arg2
        self.__dict__[opcd][i + 3] = arg3
        self.__dict__[opcd][i + 4] = arg4
        self.__dict__[opcd][i + 5] = arg5
        self.__dict__[opcd][i + 6] = arg6
        self.__dict__[opcd][i + 7] = arg7
        self.__dict__[opcd][i + 8] = arg8
        # Return True if we made it this far
        return True 
[docs]    def outComFile(file_name="", structure_file_ext='.mae'):
        """
        This is a static helper method for the jobs that write com files.
        Returns an 'out' string for the passed 'in' string.
        Raises an Exception if the file name can't be determined.
        :param file_name:
                If passed 'jobname.com' string argument, it returns the
                appropriate 'jobname-out.mae' string.  If passed a 'foo.mae'
                file, it returns an appropriate 'foo.com' file name.
        :type file_name:
                string
        :param structure_file_ext:
                Output structure file extension.  Default is '.mae'.
        :type structure_file_ext:
                string
        """
        # Initialize false return value, split the file_name.
        out_com_name = ""
        (root, ext) = fileutils.splitext(file_name)
        # Return com file if we received mae file.
        if ext in ['.mae', '.maegz', '.mae.gz']:
            out_com_name = "%s.com" % root
        # Return out.mae|mae.gz|maegz file if we received a com file.
        elif ext in ['.com']:
            out_com_name = "%s-out%s" % (root, structure_file_ext)
        # Panic
        else:
            msg = \
            
"ComUtil.outComFile() can't determine file name pattern: %s." % (
                file_name
            )
            raise Exception(msg)
        return out_com_name 
    # Announce as static method
    outComFile = staticmethod(outComFile)
[docs]    def setupAset(self, set_dict, arg7=0, arg8=0):
        """
        Define ASET arguments from a set dictionary.
        Numeric hash keys index the lists of atom numbers.
        The numerical key is taken as the set number identifier.
        """
        # FIXME: determine if we have mutually exclusive atom sets in set_dict
        # ev35981
        # ASET -
        # arg5 Set number identifier
        # arg6 Command mode
        # 1, 0 add
        # -1 delete
        # 2 add range arg1-arg2
        # -2 delete range arg1-arg2
        # 3 add molecules containing atoms arg1-arg4
        # -3 delete molecules containing atoms arg1-arg4
        #
        # arg7 Recording Inter/Intra Set Energies
        # 0 Inter-Set Energies between Set1 and other Sets
        # 2 Inter-Set Energies between all sets
        # 4 Intra-Set Energies for all sets
        #
        # arg8 Recording Energetic Properties
        # 0 Total Energy
        # 2 Total Energy and Non-bond Energy
        # 4 All Energetic Components
        # Wipe the dictionary
        self.ASET.clear()
        # Clear any previous sets, defines property output
        self.setOpcdArgs('ASET', arg7=arg7, arg8=arg8)
        # Break up set into contiguous atom number blocks
        # add atoms arg1-4:  arg6==1,
        # add atom range arg1, arg4:  arg6==2
        # add atom's molecule:  arg6==3
        # atoms for molecule numbers are identified by a
        # negative value
        for key in list(set_dict):
            i_atom = 0  # range start
            j_atom = 0  # range end
            atoms = set_dict[key]
            atoms.sort()
            for atom in atoms:
                # Make any 'molecule number' assignments
                if atom < 0:
                    self.setOpcdArgs('ASET',
                                     -1 * atom,
                                     arg5=float(key),
                                     arg6=3,
                                     arg7=arg7,
                                     arg8=arg8)
                # Make 'atom number' assignments, atom numbers must be > 0
                if atom > 0 and i_atom == 0:
                    # Seed range
                    i_atom = atom
                    j_atom = atom
                if atom > 0 and i_atom > 0 and atom == j_atom + 1:
                    # Expand range
                    j_atom = atom
                if atom > 0 and i_atom > 0 and atom > j_atom + 1:
                    # Commit singleton atom arg6==1, range arg6==2
                    if i_atom == j_atom or atom == atoms[-1]:
                        self.setOpcdArgs('ASET',
                                         i_atom,
                                         arg5=float(key),
                                         arg6=1.0,
                                         arg7=arg7,
                                         arg8=arg8)
                    else:
                        self.setOpcdArgs('ASET',
                                         i_atom,
                                         j_atom,
                                         arg5=float(key),
                                         arg6=2.0,
                                         arg7=arg7,
                                         arg8=arg8)
                    # Reseed
                    if atom > 0:
                        j_atom = atom
                        i_atom = atom
            # Commit range
            if atom > 0:
                self.setOpcdArgs('ASET',
                                 i_atom,
                                 atom,
                                 arg5=float(key),
                                 arg6=2,
                                 arg7=arg7,
                                 arg8=arg8)
        return 
[docs]    def getLaunchCommand(self, com_file=""):
        """
        This method returns a list of arguments that form the toplevel
        command invocation with all the flags set in the instance.
        It takes a string argument that specifies the name of the com
        file to run, and returns the fully qualified executable and all
        commandline arguments.
        """
        # Initialize values
        flags = []  # list of option and arg strings
        cmd_args = []  # component strings
        # Set MacroModel command line flags/args
        if self.proj:
            flags.extend(["-PROJ", self.proj])
        if self.disp:
            flags.extend(["-DISP", self.disp])
        if self.user:
            flags.extend(["-USER", self.user])
        if self.arch:
            flags.extend(["-ARCH", self.arch])
        if self.host:
            flags.extend(["-HOST", self.host])
        if self.hostfile:
            flags.extend(["-HOSTFILE", self.hostfile])
        if self.tmpdir:
            flags.extend(["-TMPDIR", self.tmpdir])
        if self.nice:
            flags.extend(["-NICE"])
        if self.wait:
            flags.extend(["-WAIT"])
        if self.local:
            flags.extend(["-LOCAL"])
        if self.interval:
            flags.extend(["-INTERVAL", str(self.interval)])
        if self.ddebug:
            flags.extend(["-DDEBUG"])
        if self.no_redirect:
            flags.extend(["-NO_REDIRECT"])
        if self.ver:
            flags.extend(["-VER", self.ver])
        if self.rel:
            flags.extend(["-REL", self.rel])
        if self.para_bmin and self.para_bmin_jobcts:
            flags.extend(["-JOBCTS", str(self.para_bmin_jobcts)])
        if self.para_bmin and self.para_bmin_njobs:
            flags.extend(["-NJOBS", str(self.para_bmin_njobs)])
        if self.para_bmin and self.para_bmin_output_org:
            flags.extend(["-OUTPUT_ORG", self.para_bmin_output_org])
        # Truncate 'jobname.com' to 'jobname'
        jobname_re = re.compile(r'.com$')
        com_file = jobname_re.sub('', com_file)
        # Fashion command expression
        if self.para_bmin:
            cmd_args.append('para_bmin')
        else:
            cmd_args.append('bmin')
        cmd_args.extend(flags)
        cmd_args.append(com_file)
        if self._debug:
            print(cmd_args)
            print(" ".join(cmd_args))
        return cmd_args 
[docs]    def elst(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a single point energy listing,
        returns the name of the com file written.
        It requires a string containing the the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        if self.serial:
            self.DEBG[1] = 601  # write and output file with WRIT only
            com_args = [
                com_file, mae_file, out_file, 'DEBG', 'SOLV', 'EXNB', 'BDCO',
                'CHGF', 'FFLD', 'BGIN', 'READ', 'ELST', 'WRIT', 'END'
            ]
        else:
            com_args = [
                com_file, mae_file, out_file, 'DEBG', 'SOLV', 'EXNB', 'BDCO',
                'CHGF', 'FFLD', 'READ', 'ELST'
            ]
        return self.writeComFile(com_args) 
[docs]    def dlst(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a single point energy listing of
        1st and 2nd derivatives, returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'DEBG', 'SOLV', 'EXNB', 'BDCO',
            'CHGF', 'FFLD', 'READ', 'DLST'
        ]
        return self.writeComFile(com_args) 
[docs]    def mini(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a geometry optimization,
        returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SOLV',
            'EXNB', 'BDCO', 'CHGF', 'FFLD', 'BGIN', 'SUBS', 'READ', 'CONV',
            'MINI', 'END'
        ]
        return self.writeComFile(com_args) 
[docs]    def addc(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for redundant conformer elimination,
        returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        COMP atoms must be set outside of this method.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'FFLD', 'DEMX',
            'CRMS', 'NANT'
        ]
        # add the correct number of args
        i = 0
        while (i < old_div(len(self.COMP), 8)):
            com_args.append('COMP')
            i += 1
        # big finish
        com_args.append('MSYM')
        com_args.append('BGIN')
        com_args.append('READ')
        if (len(self.COMP) == 0):
            com_args.append('AUTO')
        com_args.append('ADDC')
        com_args.append('END')
        return self.writeComFile(com_args) 
[docs]    def filter(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a special atom filtering,
        similar to premin's, but with 0 minimization iterations; returns
        the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        self.DEBG[1] = 3
        self.DEBG[2] = 57
        self.DEBG[3] = 49
        self.MINI[3] = 0
        self.setOpcdArgs('SPAT', 151, 152, 153, 154, 1.0)
        self.setOpcdArgs('SPAT', 155, 156, 157, 158, 1.0)
        self.setOpcdArgs('SPAT', 159, 160, 161, 162, 1.0)
        self.setOpcdArgs('SPAT', 163, 164, 165, 166, 1.0)
        self.setOpcdArgs('SPAT', 167, 168, 169, 170, 1.0)
        self.setOpcdArgs('SPAT', 171, 172, 173, 65, 1.0)
        self.setOpcdArgs('SPAT', 66, 67, 68, 69, 1.0)
        self.setOpcdArgs('SPAT', 70, 71, 72, 73, 1.0)
        self.setOpcdArgs('SPAT', 74, 75, 76, 77, 1.0)
        self.setOpcdArgs('SPAT', 78, 79, 80, 81, 1.0)
        self.setOpcdArgs('SPAT', 82, 83, 84, 85, 1.0)
        self.setOpcdArgs('SPAT', 86, 87, 88, 89, 1.0)
        self.setOpcdArgs('SPAT', 90, 91, 102, arg5=1.0)
        com_args = []  # passed to writeComFile()
        com_args = [com_file, mae_file, out_file, 'DEBG', 'EXNB', 'FFLD']
        # add the correct number of args
        i = 0
        while (i < old_div(len(self.SPAT), 8)):
            com_args.append('SPAT')
            i += 1
        # big finish
        com_args.append('BGIN')
        com_args.append('READ')
        com_args.append('CONV')
        com_args.append('MINI')
        com_args.append('END')
        return self.writeComFile(com_args) 
[docs]    def mult(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a geometry optimization and
        filtering with MULT and COMP;  returns the name of the com
        file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.  It is intended for filtering
        conformation search output.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SOLV',
            'EXNB', 'BDCO', 'CHGF', 'FFLD', 'DEMX', 'BGIN', 'READ', 'MULT',
            'COMP', 'CRMS', 'NANT', 'MSYM', 'CONV', 'MINI', 'END'
        ]
        return self.writeComFile(com_args) 
[docs]    def driv(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a geometry drive;  returns
        the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.  It is intended for filtering
        conformation search output.
        DEGB 150 starts the drive from the input geometry, not the
        endpoint of the previous iteration.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file,
            mae_file,
            out_file,
            'MMOD',
            'DEBG',
            'SOLV',
            'EXNB',
            'BDCO',
            'CHGF',
            'FFLD',
            'READ',
            'BGIN',
        ]
        # add the correct number of args
        i = 0
        while (i < old_div(len(self.DRIV), 8)):
            com_args.append('DRIV')
            i += 1
        # add the big finish...
        com_args.append('CONV')
        com_args.append('MINI')
        com_args.append('END')
        return self.writeComFile(com_args) 
[docs]    def lmod(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a lowmode conformation search;
        returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        if self.serial:
            self.MCOP[4] = 1
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file,
            mae_file,
            out_file,
            'MMOD',
            'DEBG',
            'NPRC',
            'SEED',
            'SOLV',
            'EXNB',
            'BDCO',
            'CHGF',
            'FFLD',
            'SUBS',
            'READ',
            'FLAP',
            'MCRC',
            'LMCS',
            'MCSS',
            'MCOP',
            'DEMX',
            'MSYM',
            'AUTO',  # chig and comp setup, tors/mols if mixed
            'CRMS',
            'NANT',
            'CONV',
            'MINI'
        ]
        return self.writeComFile(com_args) 
[docs]    def llmd(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a large scale lowmode
        conformation search;  returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        if self.serial:
            self.MCOP[4] = 1
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file,
            mae_file,
            out_file,
            'MMOD',
            'DEBG',
            'NPRC',
            'SEED',
            'SOLV',
            'EXNB',
            'BDCO',
            'CHGF',
            'FFLD',
            'SUBS',
            'READ',
            'ARPK',
            'LMC2',
            'MCSS',
            'MCOP',
            'DEMX',
            'MSYM',
            'AUTO',  # chig and comp setup, tors/mols if mixed
            'CRMS',
            'NANT',
            'CONV',
            'MINI'
        ]
        return self.writeComFile(com_args) 
[docs]    def mcmm(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a monte carlo multiple minimum
        conformation search;  returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SEED',
            'SOLV', 'EXNB', 'BDCO', 'CHGF', 'FFLD', 'SUBS', 'READ', 'FLAP',
            'MCRC', 'MCMM', 'MCSS', 'MCNV', 'MCOP', 'DEMX', 'MSYM', 'AUOP',
            'AUTO', 'CRMS', 'NANT', 'CONV', 'MINI'
        ]
        return self.writeComFile(com_args) 
[docs]    def spmc(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a systematic pseudo monte
        carlo multiple minimum conformation search; returns the name of
        the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root of
        the generated -out.mae file.  This method always uses torsional
        memory and will preoptimize ring closure distances.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        self.MCOP[2] = 1  # preoptimize ring closure dists
        self.MCSS[3] = 1  # SUMM => use torsional memory
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SEED',
            'SOLV', 'EXNB', 'BDCO', 'CHGF', 'FFLD', 'SUBS', 'READ', 'SPMC',
            'MCSS', 'MCNV', 'MCOP', 'DEMX', 'MSYM', 'AUOP', 'AUTO', 'CRMS',
            'NANT', 'CONV', 'MINI'
        ]
        return self.writeComFile(com_args) 
[docs]    def mcmmlmod(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a mixed monte carlo multiple
        minimum/lowmode conformation search;  returns the name of the
        com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        # 1:1 mix if nothing else defined
        if not self.MCOP[5]:
            self.MCOP[5] = 0.5
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SEED',
            'SOLV', 'EXNB', 'BDCO', 'CHGF', 'FFLD', 'SUBS', 'READ', 'FLAP',
            'MCRC', 'LMCS', 'MCSS', 'MCNV', 'MCOP', 'DEMX', 'MSYM', 'AUOP',
            'AUTO', 'CRMS', 'NANT', 'CONV', 'MINI'
        ]
        return self.writeComFile(com_args) 
[docs]    def mcmmllmd(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a mixed monte carlo multiple
        minimum/large scale lowmode conformation search;  returns the
        name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        # 1:1 mix if nothing else defined
        if not self.MCOP[5]:
            self.MCOP[5] = 0.5
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SEED',
            'SOLV', 'EXNB', 'BDCO', 'CHGF', 'FFLD', 'SUBS', 'READ', 'ARPK',
            'LMC2', 'MCNV', 'MCSS', 'MCOP', 'DEMX', 'MSYM', 'AUOP', 'AUTO',
            'CRMS', 'NANT', 'CONV', 'MINI'
        ]
        return self.writeComFile(com_args) 
[docs]    def cgen(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a ligand torsion search with
        ConfGen;  returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'DEBG', 'SOLV', 'EXNB', 'BDCO',
            'CHGF', 'FFLD', 'READ', 'CRMS', 'NANT', 'CGEN', 'CGOP', 'CGO2',
            'CGO3', 'CGO4', 'CGO5', 'CGO6', 'CHYD', 'MCOP', 'DEMX', 'MSYM',
            'AUOP', 'AUTO', 'CONV', 'MINI'
        ]
        return self.writeComFile(com_args) 
[docs]    def mdyn(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a molecular dynamics simulation;
        returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'DEBG', 'MMOD', 'SOLV', 'EXNB',
            'BDCO', 'CHGF', 'FFLD', 'READ', 'CONV', 'MINI', 'MDIT', 'MDYN',
            'MDSA', 'MDFT', 'MDYN', 'WRIT'
        ]
        return self.writeComFile(com_args) 
[docs]    def mcsd(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a mixed Monte Carlo/Stochastic
        Dynamics simulation; returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        self.AUTO[2] = -1  # no comps needed
        self.AUTO[3] = -1  # no chig needed
        self.MDYN[2] = 0  # no shake for MCSD, we have MC criteria
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file,
            mae_file,
            out_file,
            'DEBG',
            'MMOD',
            'SOLV',
            'EXNB',
            'BDCO',
            'CHGF',
            'FFLD',
            'READ',
            'CONV',
            'MINI',
            'MCNV',
            'AUTO',
            'MCSD',
            'MDIT',
            'MDYN',
            'MDSA',
            'MDYN',
            'MDFT',
        ]
        return self.writeComFile(com_args) 
[docs]    def mmc(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a Metropolis Monte Carlo
        ensemble generation; returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        The arguments for ZMAT must be set outside this method.  ZMAT
        makes all the structural changes so the min-max range should
        allow diverse sampling (5 deg bends, 180 tors).
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'DEBG', 'SEED', 'SOLV', 'EXNB',
            'BDCO', 'CHGF', 'FFLD', 'READ', 'MCNV'
        ]
        # add the correct number of args
        i = 0
        while (i < old_div(len(self.ZMAT), 8)):
            com_args.append('ZMAT')
            i += 1
        # add the big finish...
        com_args.append('MINI')
        com_args.append('MDSA')
        com_args.append('MCLO')
        return self.writeComFile(com_args) 
[docs]    def adf(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for an All Degrees of Freedom
        simulation (a Metropolis Monte Carlo with passive Importance
        sampling) calculation; returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        The arguments for ZMAT must be set outside this method.  ZMAT
        makes all the structural changes so the min-max range should
        allow diverse sampling (5 deg bends, 180 tors).
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file,
            mae_file,
            out_file,
            'DEBG',
            'SOLV',
            'EXNB',
            'BDCO',
            'CHGF',
            'FFLD',
            'READ',
            'COMP',  # all heavy atoms for IMPS similarity assignment
        ]
        # add the correct number of args
        i = 0
        while (i < old_div(len(self.ZMAT), 8)):
            com_args.append('ZMAT')
            i += 1
        # add the big finish...
        com_args.append('MCNV')
        com_args.append('IMPS')
        com_args.append('IMPO')
        com_args.append('MINI')
        com_args.append('MDSA')
        com_args.append('MCLO')
        return self.writeComFile(com_args) 
[docs]    def jbw(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a Jump Between Wells simulation
        (a Metropolis Monte carlo simulation with active Importance
        sampling); returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        The arguments for ZMAT must be set outside this method.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        self.IMPS[1] = 1  # all steps IMPS steps
        self.MDSA[2] = 1  # report IMPS population and statistics
        self.MDSA[3] = 1  # report IMPS transition statistics
        self.MDSA[4] = 1  # report IMPS transition statistics
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file,
            mae_file,
            out_file,
            'DEBG',
            'SOLV',
            'EXNB',
            'BDCO',
            'CHGF',
            'FFLD',
            'COMP',  # all heavy atoms for IMPS similarity assignment
            'READ',
        ]
        # add the correct number of args
        i = 0
        while (i < old_div(len(self.ZMAT), 8)):
            com_args.append('ZMAT')
            i += 1
        # add the big finish...
        com_args.append('IMPS')
        com_args.append('IMPO')
        com_args.append('MDSA')
        com_args.append('MCLO')
        return self.writeComFile(com_args) 
[docs]    def jbw_sd(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a Jump Between Wells/Stochastic
        Dynamics simulation (a MCSD simulation with active Importance
        sampling);  returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        The arguments for ZMAT must be set outside this method.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        self.IMPS[1] = 1  # all steps IMPS steps
        self.MDSA[2] = 1  # report IMPS population and statistics
        self.MDSA[3] = 1  # report IMPS transition statistics
        self.MDSA[4] = 1  # report IMPS transition statistics
        self.MCSD[1] = 1  # no MC, we have JBW
        self.MCSD[2] = -1  # don't randomize JBW step
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file,
            mae_file,
            out_file,
            'DEBG',
            'SOLV',
            'EXNB',
            'BDCO',
            'CHGF',
            'FFLD',
            'MCSD',
            'COMP',  # all heavy atoms for IMPS similarity assignment
            'READ',
        ]
        # add the correct number of args
        i = 0
        while (i < old_div(len(self.ZMAT), 8)):
            com_args.append('ZMAT')
            i += 1
        # add the big finish...
        com_args.append('IMPS')
        com_args.append('IMPO')
        com_args.append('MDIT')
        com_args.append('MDSA')
        com_args.append('MDYN')
        return self.writeComFile(com_args) 
[docs]    def geom(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a geometry analysis;  returns
        the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        The arguments for GEOM must be set outside this method.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file,
            mae_file,
            out_file,
            'DEBG',
            'FFLD',
            'BGIN',
            'READ',
        ]
        # add the correct number of args
        i = 0
        while (i < old_div(len(self.GEOM), 8)):
            com_args.append('GEOM')
            i += 1
        # add the big finish...
        com_args.append('WRIT')
        com_args.append('END')
        return self.writeComFile(com_args) 
[docs]    def copyalgn(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for an ALGN/COPY positioning of
        ligands on a reference ligand;  returns the name of the com
        file written.
        The ligands are positioned on the first ligand (all four ways)
        with ALGN/COPY.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        self.ALGN[1] = 3
        self.ALGN[2] = 1
        self.ALGN[3] = 5
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'FFLD', 'READ', 'COPY', 'BGIN',
            'READ', 'ALGN', 'END'
        ]
        return self.writeComFile(com_args) 
[docs]    def mbaeMiniEdiff(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for an MBAE multiple minimization
        run, in energy difference mode;  returns the name of the com
        file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        self.MBAE[1] = 1  # ediff
        self.MBAE[2] = 0  # mini calc
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SOLV',
            'EXNB', 'BDCO', 'CHGF', 'FFLD', 'MBAE', 'SUBS', 'READ', 'CONV',
            'MINI', 'DEMX', 'BGIN', 'READ', 'CONV', 'MINI', 'END', 'MBAE'
        ]
        return self.writeComFile(com_args) 
[docs]    def mbaeMiniInter(self,
                      mae_file=None,
                      com_file=None,
                      out_file=None,
                      recep_atom=None):
        """
        This method writes a com file for an MBAE multiple minimization
        run, in set interaction mode;  returns the name of the com file
        written.  recep_atom is the last atomic index of the receptor
        structure.  If recep_atom is None then the first  structure in
        the mae_file is evaluated.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        The ASET arguments are set by snooping the input file for the
        number of atoms in the receptor.
        raises IOError if the receptor atom total can't be determined
        from the input structure file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        self.ELST[1] = -1  # don't fill the disk with complete energy listings
        self.MBAE[1] = 0  # atom set interaction mode
        self.MBAE[2] = 0  # mini calc
        # Get the size of the receptor if needed.
        if recep_atom is None:
            try:
                recep_st = structure.Structure.read(mae_file)
                recep_atom = recep_st.atom_total
            except:
                msg = 'Unable to determine the receptor size in file %s' % (
                    mae_file)
                raise IOError(msg)
            # blank any previous sets
        self.ASET[1] = 0
        self.ASET[2] = 0
        self.ASET[3] = 0
        self.ASET[4] = 0
        self.ASET[5] = 0.0
        self.ASET[6] = 0.0
        self.ASET[7] = 0.0
        self.ASET[8] = 0.0
        # add all atoms to set 2
        self.ASET[13] = 2.0
        # cut recptor atoms from set 2
        self.ASET[17] = 1
        self.ASET[18] = recep_atom
        self.ASET[21] = 2.0
        self.ASET[22] = -2.0
        # paste recptor atoms into set 1
        self.ASET[25] = 1
        self.ASET[26] = recep_atom
        self.ASET[29] = 1.0
        self.ASET[30] = 2.0
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SOLV',
            'EXNB', 'BDCO', 'CHGF', 'FFLD', 'MBAE', 'SUBS', 'READ', 'DEMX',
            'BGIN', 'READ', 'ASET', 'ASET', 'ASET', 'ASET', 'CONV', 'MINI',
            'ELST', 'END', 'MBAE'
        ]
        return self.writeComFile(com_args) 
[docs]    def mbaeCsLmod(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for an MBAE low mode conformation
        search run, in energy difference mode;  returns the name of the
        com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        If ComUtil.algn is true, then a the job will positions the
        ligands on the reference ligand, with all four alignments.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        self.MBAE[1] = 1  # ediff
        self.MBAE[2] = 1  # confs calc
        self.MCOP[6] = 1  # only one receptor conf
        self.MCOP[12] = 1  # lcms serial search if > 0
        com_args = []  # passed to writeComFile()
        # compound job with algn/copy as the first step
        if self.algn:
            self.SUBS[2] = 1  # specify correct sbc file
            self.ALGN[1] = 3  # center and princ axes align with ref
            self.ALGN[2] = 1  # weight atoms by atomic mass
            self.ALGN[3] = 5  # generate all four and write
            com_args = [
                com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SOLV',
                'EXNB', 'BDCO', 'CHGF', 'FFLD', 'MSYM', 'READ', 'WRIT', 'READ',
                'COPY', 'BGIN', 'READ', 'ALGN', 'END', 'RWND', 'MBAE', 'LMCS',
                'MCSS', 'MCOP', 'SUBS', 'AUTO', 'CONV', 'MINI', 'DEMX', 'LMCS',
                'BGIN', 'MCOP', 'READ', 'MINI', 'END', 'MBAE'
            ]
        else:
            com_args = [
                com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SOLV',
                'EXNB', 'BDCO', 'CHGF', 'FFLD', 'MSYM', 'MBAE', 'LMCS', 'MCSS',
                'MCOP', 'DEMX', 'SUBS', 'READ', 'AUTO', 'CONV', 'MINI', 'LMCS',
                'BGIN', 'MCOP', 'READ', 'MINI', 'END', 'MBAE'
            ]
        return self.writeComFile(com_args) 
[docs]    def mbaeCsMcmm(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for an MBAE monte carlo conformation
        search run, in energy difference mode;  returns the name of the
        com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        If ComUtil.algn is true, then a the job will positions the
        ligands on the reference ligand, with all four alignments.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        self.AUTO[6] = 1  # serial setup
        self.MBAE[1] = 1  # ediff
        self.MBAE[2] = 1  # confs calc
        self.MCOP[6] = 1  # one receptor conf
        com_args = []  # passed to writeComFile()
        # compound job with algn/copy as the first step
        if self.algn:
            self.SUBS[2] = 1  # specify correct sbc file
            self.ALGN[1] = 3  # center and princ axes align with ref
            self.ALGN[2] = 1  # weight atoms by atomic mass
            self.ALGN[3] = 5  # generate all four and write
            com_args = [
                com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SOLV',
                'EXNB', 'BDCO', 'CHGF', 'FFLD', 'MSYM', 'READ', 'WRIT', 'READ',
                'COPY', 'BGIN', 'READ', 'ALGN', 'END', 'RWND', 'MBAE', 'MCMM',
                'MCSS', 'MCOP', 'DEMX', 'SUBS', 'AUTO', 'CONV', 'MINI', 'LMCS',
                'BGIN', 'MCOP', 'READ', 'MINI', 'END', 'MBAE'
            ]
        else:
            com_args = [
                com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SOLV',
                'EXNB', 'BDCO', 'CHGF', 'FFLD', 'MSYM', 'MBAE', 'MCMM', 'MCSS',
                'MCOP', 'DEMX', 'SUBS', 'READ', 'AUTO', 'CONV', 'MINI', 'MCMM',
                'BGIN', 'MCOP', 'READ', 'MINI', 'END', 'MBAE'
            ]
        return self.writeComFile(com_args) 
[docs]    def mbaeCsMcmmLmod(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for an MBAE mixed lowmode monte
        carlo conformation search run, in energy difference mode;
        returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        If ComUtil.algn is true, then a the job will positions the
        ligands on the reference ligand, with all four alignments.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        self.MBAE[1] = 1  # ediff
        self.MBAE[2] = 1  # confs calc
        if not self.MCOP[5]:
            self.MCOP[5] = 0.5  # half mcmm / half lowmode
        self.MCOP[6] = 1  # one receptor conf
        return self.mbaeCsLmod(mae_file, com_file) 
[docs]    def logP(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a multiple minimization with
        logP calculation;  returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        self.serial = True  # serial auto
        self.solv = True  # must have solvent
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SOLV',
            'EXNB', 'BDCO', 'CHGF', 'FFLD', 'LOGP', 'BGIN', 'READ', 'AUTO',
            'CONV', 'MINI', 'END'
        ]
        return self.writeComFile(com_args) 
[docs]    def fep(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a Free Energy Perturbation
        with stochastic dynamics sampling;  returns the name of the com
        file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'NPRC', 'SOLV',
            'EXNB', 'BDCO', 'CHGF', 'FFLD', 'READ', 'FEIA', 'BGIN', 'FEAV',
            'CONV', 'MINI', 'MDIT', 'MDYN', 'FESA', 'MDYN', 'END', 'FESU'
        ]
        return self.writeComFile(com_args) 
[docs]    def loop(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a protein LOOP conformation
        search;  returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'SOLV', 'EXNB',
            'BDCO', 'CHGF', 'FFLD', 'SUBS', 'READ', 'LOOP', 'LPOP', 'DEMX',
            'COMP', 'MSYM', 'MCOP', 'CONV', 'MINI'
        ]
        return self.writeComFile(com_args) 
[docs]    def aset(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a atom set interaction (ASET)
        analysis;  returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        The arguments for ASET must be set outside this method.
        Uses a BGIN/END loop to process each input file member, but
        single structure input files are allowed.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file,
            mae_file,
            out_file,
            'MMOD',
            'DEBG',
            'SOLV',
            'EXNB',
            'BDCO',
            'CHGF',
            'FFLD',
            'SUBS',
            'BGIN',
            'READ',
        ]
        # add the correct number of args
        i = 0
        while (i < old_div(len(self.ASET), 8)):
            com_args.append('ASET')
            i += 1
        # add the big finish...
        com_args.append('ELST')
        com_args.append('END')
        return self.writeComFile(com_args) 
[docs]    def minta(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a Mode integration MINTA
        calculation (conformational free energy);  returns the name of
        the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = [
            com_file, mae_file, out_file, 'MMOD', 'DEBG', 'SOLV', 'EXNB',
            'BDCO', 'CHGF', 'FFLD', 'BGIN', 'READ', 'CONV', 'MINI', 'MNTA',
            'END'
        ]
        return self.writeComFile(com_args) 
[docs]    def rrho(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file for a Rigid Rotor Harmonic
        Oscillator calculation (normal mode analysis of thermodyanic
        quantities);  returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        The structure is minimized prior to RRHO calculation.
        The optimization can be omitted by setting MINI[3] = 0
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = [
            com_file,
            mae_file,
            out_file,
            'DEBG',
            'SOLV',
            'EXNB',
            'BDCO',
            'CHGF',
            'FFLD',
            'SUBS',
            'READ',
            'CONV',
            'MINI',
            'MTST',
            'RRHO',
        ]
        return self.writeComFile(com_args) 
[docs]    def vibr(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file to visualize vibrational modes
        with VIBR; returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        The structure is minimized prior to VIBR calculation.
        The optimization can be omitted by setting MINI[3] = 0
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = [
            com_file,
            mae_file,
            out_file,
            'MMOD',
            'DEBG',
            'SOLV',
            'EXNB',
            'BDCO',
            'CHGF',
            'FFLD',
            'SUBS',
            'READ',
            'CONV',
            'MINI',
            'VIBR',
        ]
        return self.writeComFile(com_args) 
[docs]    def vbr2(self, mae_file=None, com_file=None, out_file=None):
        """
        This method writes a com file to visualize vibrational modes
        with VBR2; returns the name of the com file written.
        It requires a string containing the name of a maestro format
        file that will be used as the job input, and returns the string
        name of the com file.  It may also accept an optional string
        file name that becomes the name of the com file plus the root
        of the generated -out.mae file.
        The structure is minimized prior to VBR2 calculation.
        The optimization can be omitted by setting MINI[3] = 0
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        com_args = [
            com_file,
            mae_file,
            out_file,
            'MMOD',
            'DEBG',
            'SOLV',
            'EXNB',
            'BDCO',
            'CHGF',
            'FFLD',
            'SUBS',
            'READ',
            'CONV',
            'MINI',
            'ARPK',
            'VBR2',
        ]
        return self.writeComFile(com_args)  
[docs]class ComUtilAppSci(ComUtil):
    """
    Subclass of ComUtil with defaults that are closer to the current
    Maestro version.
    Defaults are:
        - Potential energy surface is OPLS_2005, GB/SA water solvent, with
          extended non-bonded cutoff.
        - Minimization run up to 500 iterations of PRCG.  Convergence is set
          to a gradient of 0.05.
        - Energy window is 21 kJ/mol and an initial energy window of 42
          kJ/mol is applied after 166 iteration.
        - MacroModel/Maestro interaction files are generated.  Disable by
          setting self.mmod = False.
        - Atom distance for conformer equivalence is 0.50 angstroms.
        - The maximum number of degrees of freedom sampled per MC step is 5.
        - Automatic conformation search parameters scale the number of steps
          to the number of torsions.
        - Single point energy listing com file follows the Maestro form.
        - Input structure file is assumed to contain multiple non-conformer
          ligands.  i.e. self.serial = True such that automatic variables
          are generated in serial mode by AUTO, and
          self.serial_nonconf_input = True such that DEBG 55 is set for
          improved property management of non conformer input structures.
        - The constructor takes no kwargs, but the instance data members can
          be altered.
    """
[docs]    def __init__(self):
        ComUtil.__init__(self)
        self.nant = True
        self.exnb = True
        self.solv = True
        self.mmod = True
        self.serial = True
        self.serial_nonconf_input = True
        self.BDCO[5] = 89.4427  # sqrt(cutoff_electrostatics^3)
        self.MINI[3] = 500  # Not enough iters, but satisfies ev93833.
        self.MINI[11] = 500
        self.CONV[5] = 0.05
        self.MCSS[5] = 21
        self.DEMX[2] = 166
        self.DEMX[5] = 21.0
        self.DEMX[6] = 42.0
        self.MDSA[1] = 0  # No structures, but satisfies ev93833.
        self.MDSA[7] = 1.0  # No structures, but satisfies ev93833.
        self.MCNV[2] = 5  # max number DOF altered.
        self.CRMS[6] = 0.50  # max dist between 'equivalent' corresponding atoms
        self.AUTO[8] = 1.0  # 'intermediate' sampling (MacroModel 9.0 deflault)
        self.AUOP[5] = 100  # N search steps = (arg5)*(number of torsions+mols) 
[docs]    def elst(self, mae_file=None, com_file=None, out_file=None):
        """
        :return:
                The name of the single point energy listing com file written.
        :rtype:
                string
        :param mae_file:
                Name of the structure file input.
        :type mae_file:
                string
        :param com_file:
                Optional com_file name.  By default it is determined from
                the basename of the mae_file.
        :type com_file:
                string
        :param out_file:
                Optional out_file name.  By default it determined from the
                basename of the com_file.
        :type out_file:
                string
        """
        # Fashion com file name, if needed
        if not com_file:
            com_file = self.outComFile(mae_file)
        # Fashion output file name
        if not out_file:
            out_file = self.outComFile(com_file, self.structure_file_ext)
        # Prep com file arguments
        orig_read = {}
        orig_read.update(self.READ)
        self.READ[1] = -1
        com_args = []  # passed to writeComFile()
        com_args = [
            com_file, mae_file, out_file, 'DEBG', 'MMOD', 'FFLD', 'SOLV',
            'EXNB', 'BDCO', 'CHGF', 'BGIN', 'READ', 'ELST', 'WRIT', 'END'
        ]
        com_file = self.writeComFile(com_args)
        self.READ.update(orig_read)
        return com_file  
[docs]class ComUtil_2010(ComUtilAppSci):
    """
    This class will be deprecated in the next release.  Consider
    ComUtilAppSci instead.
    """
[docs]    def __init__(self):
        warnings.warn(
            'This class will be deprecated in the next release.  Consider ComUtilAppSci instead.',
            DeprecationWarning,
            stacklevel=2)
        ComUtilAppSci.__init__(self)
        return  
[docs]class SbcUtil:
    """
    A class for writing substructure/constraint files to accompany
    jobname.com.
    MacroModel has different ways to define freely moving, restrained and
    constrained atoms. A good understanding of the SUBS/FXAT behavior is
    often essential; see the MacroModel Reference Manual for more details.
    Below are a few examples of some common idioms.
    This class now supports ASL2 with half-width flat-bottomed restraint
    potential. If self.use_flat_bottom_asl2 is True, then the
    ASL2 arguments are formatted thus::
        ASL2 arg1 = Flat-bottom width, in units of tenths of an angstrom.
        ASL2 arg2 = Force constant.
        ASL2 arg3 = Atom selection language string
    otherwise, ASL2 argument are formated as::
        ASL2 arg1 = Force constant.
        ASL2 arg2 = Atom selection language string
    API examples::
        # Example 1 ############################################################
        # Writing a sbc file to restrain atoms during a minimization with ASL2.
        # Write com file instructions for a simple geometry optimization.
        # The ComUtil instance is configured such that it will write the
        # SUBS opcode in the com file.
        import schrodinger.application.macromodel.utils as mmodutils
        mcu = mmodutils.ComUtil(subs=True)
        com_file = mcu.mini("example_1.mae")
        # MacroModel requires that the sbc file name match the input
        # structure file, not the com file.
        sbc_file = 'example_1.sbc'
        # Write sbc file that will modify the geometry optimization such that
        # all atoms are harmonically restrained about their starting coordinates.
        sbu = mmodutils.SbcUtil()
        sbu.ASL2[1] = 200 # force constant for Cartesian restraints.
        sbu.ASL2[2] = 'mol.n 1' # ASL identifying the restrained atoms.
        sbu_args = [sbc_file, 'ASL2']
        sbu.writeSbcFile(sbu_args)
        # Example 2 ############################################################
        # Writing a sbc file to perform a conformation search on part of a
        # structure with ASL1/ASL2.
        # Write com file instructions for a simple conformation search.
        # The ComUtil instance is configured such that it will write the
        # SUBS opcode in the com file.
        import schrodinger.application.macromodel.utils as mmodutils
        mcu = mmodutils.ComUtil(subs=True)
        mcu.MCMM[1] = 100 # Short test search.
        com_file = mcu.mcmm("example_2.mae")
        # MacroModel requires that the sbc file name match the input
        # structure file, not the com file.
        sbc_file = "example_2.sbc"
        # Write an sbc file that will modify the search such that
        # only sidechain atoms are assigned search parameters and sampled.
        sbu = mmodutils.SbcUtil()
        sbu.ASL1[1] = 0 # Add the substructure atoms (set up for search).
        sbu.ASL1[2] = 'sidechain' # Substructure atoms (set up for search).
        sbu.ASL2[1] = 250 # Restraint's harmonic force constant.
        sbu.ASL2[2] = 'not sidechain' # The restrained atoms.
        sbu_args = [sbc_file, 'ASL1', 'ASL2']
        sbu.writeSbcFile(sbu_args)
        # Example 3 ############################################################
        # Writing a sbc file to restrain/constrain part of the structure
        # with SUBS/FXAT during a simple minimization. Here, the input
        # structure is a ligand-receptor complex where the ligand has
        # residue name MTW. The ligand will be freely optimized within the
        # context of the receptor. The side-chain atoms will be fixed
        # (restrained with a flat-bottom potential, 0.1 angstrom half-width)
        # and a ~3 angstrom shell of nearby atoms will be frozen
        # (constrained). All other atoms are ignored.
        # Write com file instructions for a simple geometry optimization.
        # The ComUtil instance is configured such that it will write the
        # SUBS opcode in the com file.
        import schrodinger.application.macromodel.utils as mmodutils
        import schrodinger.structure as structure
        import schrodinger.structutils.analyze as analyze
        mcu = mmodutils.ComUtil(subs=True)
        com_file = mcu.mini("example_3.mae")
        sbc_file = "example_3.sbc"
        # Identify the atoms to optimize, restrain and constrain. All other
        # atoms are ignored.
        st = structure.Structure.read("example_3.mae")
        ligand_atoms = analyze.evaluate_asl(st, 'res.pt MTW')
        binding_site_atoms = analyze.evaluate_asl(
            st,
            'fillres (sidechain and within 5 res.pt MTW)'
        )
        nearby_atoms = analyze.evaluate_asl(
            st,
            'not (res.pt MTW or fillres (sidechain and within 5 res.pt MTW)) and within 10 res.pt MTW'
        )
        # Assign the atoms to SUBS and FXAT commands.
        sbu = mmodutils.SbcUtil()
        sbu_args = [sbc_file]
        sbu_args.extend(sbu.setSubs(ligand_atoms))
        sbu_args.extend(
            sbu.setFixed(
                binding_site_atoms,
                force_constant=500,
                half_width=1
            )
        )
        sbu_args.extend(sbu.setFrozen(nearby_atoms))
        sbu.writeSbcFile(sbu_args)
        # Example 4 ############################################################
        # Writing a sbc file to restrain/constrain part of the structure
        # with ASL1/ASL2, where the input is a series of non-conformers.
        # Here, the structure file contains a series of non-conformer
        # polypeptides. The side-chains will be freely optimized and the
        # backbone will be restrained. Note well, READ arg1=-1 tells
        # MacroModel to evaluate the sbc for each input structure; a
        # requirement since the input are non-conformers.
        # Write com file instructions for a geometry optimization on a
        # series of structures. The ComUtil instance is configured such
        # that it will write the SUBS opcode in the com file.
        import schrodinger.application.macromodel.utils as mmodutils
        mcu = mmodutils.ComUtil(subs=True)
        mcu.READ[1] = -1 # re-evaluate substructure information.
        com_file = mcu.mini("example_4.mae")
        sbc_file = "example_4.sbc"
        # Assign the ASL1/2 commands and write the file.
        sbu = mmodutils.SbcUtil()
        sbu.ASL1[1] = 0 # Add the substructure atoms (set up for search).
        sbu.ASL1[2] = 'sidechain' # Substructure atoms (set up for search).
        sbu.ASL2[1] = 200 # Restraint's harmonic force constant.
        sbu.ASL2[2] = 'not sidechain' # The restrained atoms.
        sbu_args = [sbc_file, 'ASL1', 'ASL2']
        sbu.writeSbcFile(sbu_args)
        # Example 5 ############################################################
        # Writing a sbc file to restrain part of the structure with
        # ASL1/ASL2 and flat-bottom restraints. Here, the structure file
        # contains a series of non-conformer polypeptides. The side-chains
        # will be freely optimized and the backbone will be restrained.
        # Note well, READ arg1=-1 which tells MacroModel to evaluate the sbc
        # for each input structure; a requirement since the input are
        # non-conformers.
        # Write com file instructions for a geometry optimization on a
        # series of structures. The ComUtil instance is configured such
        # that it will write the SUBS opcode in the com file.
        import schrodinger.application.macromodel.utils as mmodutils
        mcu = mmodutils.ComUtil(subs=True)
        mcu.READ[1] = -1 # re-evaluate substructure information.
        com_file = mcu.mini("example_5.mae")
        sbc_file = "example_5.sbc"
        # Assign the ASL1/2 commands and write the file.
        sbu = mmodutils.SbcUtil(use_flat_bottom_asl2=True)
        sbu.ASL1[1] = 0 # Add the substructure atoms (set up for search).
        sbu.ASL1[2] = 'sidechain' # Substructure atoms (set up for search).
        sbu.ASL2[1] = 5 # half-width, 0.5 angstroms.
        sbu.ASL2[2] = 200.0 # restrain 200 kJ/mol*A^2.
        sbu.ASL2[3] = 'not sidechain' # The restrained atoms.
        sbu_args = [sbc_file, 'ASL1', 'ASL2']
        sbu.writeSbcFile(sbu_args)
        # Example 6 ############################################################
        # Writing a sbc file to restrain atoms during a minimization
        # with ASL2, confirming backwards compatible support for
        # determining a suitable sbc file name.
        # Write com file instructions for a simple geometry optimization.
        # The ComUtil instance is configured such that it will write the
        # SUBS opcode in the com file.
        import schrodinger.application.macromodel.utils as mmodutils
        mcu = mmodutils.ComUtil(subs=True)
        com_file = mcu.mini("example_6.mae", "example_6_test.com")
        # Write sbc file that will modify the geometry optimization such that
        # all atoms are harmonically restrained about their starting coordinates.
        sbu = mmodutils.SbcUtil()
        sbu.ASL2[1] = 200 # force constant for Cartesian restraints.
        sbu.ASL2[2] = 'mol.n 1' # ASL identifying the restrained atoms.
        sbu_args = [com_file, 'ASL2']
        # MacroModel requires that the sbc file name match the input
        # structure file, not the com file.  The method expects a
        # sbc file name as the first argument, but for backwards
        # compatibility, supports .com extensions.
        sbc_file = sbu.writeSbcFile(sbu_args)
    """
[docs]    def __init__(self, use_flat_bottom_asl2=False):
        self.use_flat_bottom_asl2 = use_flat_bottom_asl2
        self.SUBS = {}
        self.FXAT = {}
        self.FXDI = {}  # restrain distance
        self.FXTA = {}  # restrain torsion angle
        self.FXBA = {}  # restrain bond angle
        self.FXCO = {}  # restrain all torsions
        self.ASL1 = {}  # ASL string for SUBS
        self.ASL1[1] = 0  # Add the subs asl
        self.ASL2 = {}  # ASL string for FXAT
        self.ASL2[1] = 200.0  # restraint force constant
        self.ASL2[2] = 'all'  # ASL for the fixed/frozen atoms.
        if self.use_flat_bottom_asl2:
            self.ASL2[
                1] = 0  # Flat bottom well width, in tenths of an angstrom.
            self.ASL2[2] = 200.0  # restraint force constant
            self.ASL2[3] = 'all'  # ASL for the fixed/frozen atoms.
        self._written = []
        self.refit_args = True
        return 
[docs]    def writeSbcFile(self, sbc_args=[]):  # noqa: M511
        """
        Writes a sbc file for the list of opcodes, and returns the name
        of the written sbc file.
        :param sbc_args:
                List of arguments to serialize.  The first element should be
                the file name to write, e.g. 'jobname.sbc', where the sbc
                file name corresponds to the input structure file name not
                the input com file name.  The second and subsequent elements
                should be OPCD strings.
        :type sbc_args:
                list
        :raises:
                ValueError if the first element doesn't end with .sbc and
                an appropriate sbc file name can't be determined.
        The arguments for the OPCDs are looked up in turn, evaluating
        the next eight list elements for the specific OPCD argument
        list, each time the OPCD is encountered.  See getOpcdArgs()
        and __init__() for more information about default arguments.
        For backwards compatibility, the first argument may also be a
        '.com', which will write basename.sbc if the com file can't
        be read, but will otherwise inspect the com file for the input
        structure file name and construct a suitable sbc file name.
        """
        sbc_file = ""
        if sbc_args[0].endswith('.sbc'):
            sbc_file = sbc_args[0]
        elif sbc_args[0].endswith('.com'):
            com_file = sbc_args[0]
            mae_file = ''
            # If the file exists try to get the name of the input
            # structure file from the com file contents.  Otherwise base
            # it on the com file name which may or may not be valid.
            try:
                with open(com_file) as com_fh:
                    mae_file = com_fh.readline().strip()
                (root, ext) = fileutils.splitext(mae_file)
                sbc_file = "%s.sbc" % root
            except IOError:
                com_re = re.compile(r'\.com$')
                sbc_file = com_re.sub('.sbc', com_file, 1)
        # TODO:  Determine if this method should also accept .mae(\.?gz)
        # and fashion a sbc file name.
        else:
            msg = "SbcUtil.writeSbcFile() can't determine sbc file name."
            raise ValueError(msg)
        sbc_fh = open(sbc_file, mode='w')
        for opcd in sbc_args[1:]:
            sbc_fh.write(self.getOpcdArgs(opcd))
        sbc_fh.flush()
        sbc_fh.close()
        # Clean up for next write.
        self._written = []
        return sbc_file 
[docs]    def setSubs(self, subs_list):
        """
        :return:
                List of 'SUBS' strings after assiging all atom indexed in
                subs_list to the self.SUBS dict, .  This is a simple wrapper
                for setOpcdArgs() to conveniently set a large number of atoms
                at once.  Assigned SUBS have args5-8 set to 0.0 (the default).
        :rtype:
                list
        :param subs_list:
                List of atom indexes
        :type subs_list:
                list
        """
        opcd_list = []
        # Do slices of 4,
        while len(subs_list) >= 4:
            self.setOpcdArgs('SUBS', subs_list.pop(0), subs_list.pop(0),
                             subs_list.pop(0), subs_list.pop(0))
            opcd_list.append('SUBS')
        # Assign the dregs
        if subs_list:
            self.setOpcdArgs('SUBS', *subs_list)
            opcd_list.append('SUBS')
        return opcd_list 
[docs]    def setFixed(
            self,
            fixed_list=[],  # noqa: M511
            force_constant=200,
            half_width=0):
        """
        :return:
                List of 'FXAT' strings after assiging all atom indexed in
                fixed_list to the self.FXAT dict.  This is a simple wrapper
                for setOpcdArgs() to conveniently set a large number of
                atoms at once.  Assigned FXATs have arg5 = force_constant,
                and arg6 = half_width
        :rtype:
                list
        :param fixed_list:
                List of atom indexes
        :type fixed_list:
                list
        :param force_constant:
                Restraining force constant, typically in units of kJ/mol*A^2.
                Default is 200.
        :type force_constant:
                float
        :param half_width:
                Half-width of the flat-bottom restraint potential, in tenths
                of an angstrom units.  Default is 0.
        :type half_width:
                integer
        """
        opcd_list = []
        # Each atom as one line
        for iat in fixed_list:
            self.setOpcdArgs('FXAT',
                             arg1=iat,
                             arg4=half_width,
                             arg5=force_constant)
            opcd_list.append('FXAT')
        return opcd_list 
[docs]    def setFrozen(self, frozen_list=[]):  # noqa: M511
        """
        :return:
                List of 'FXAT' strings after assiging all atom indexed
                in frozen_list to the self.FXAT dict, .  This is a simple
                wrapper for setOpcdArgs() to conveniently set a large number
                of atoms at once.  Assigned FXATs have arg5 = -1, to freeze
                (constrain) the atoms in place.
        :rtype:
                list
        :param frozen_list:
                List of atom indexes
        :type frozen_list:
                list
        """
        opcd_list = []
        # Each atom as one line
        for iat in frozen_list:
            self.setOpcdArgs('FXAT', arg1=iat, arg5=-1)
            opcd_list.append('FXAT')
        return opcd_list 
[docs]    def getOpcdArgs(self, opcd=""):
        """
        The arguments come from the hash data members, any unknown or
        unassigned values default to 0 or 0.0.  You may customize the
        self hash arguments to your heart's content prior to invoking
        the method to get the exact behavior you want.  The 'OPCD'
        lookup requires the passed argument to be a key for the hash,
        i.e. uppercase with no whitespace.  The array of arg values are
        numbers, not strings.
        This method keeps count of the number of times an OPCD is
        called and automatically increments the array of args for the
        passed OPCD.  The first call for a given OPCD uses self['OPCD']
        arg slices 1-8, the next call for that OPCD uses slices 9-16,
        the next 17-24, and so on.  writeSbcFile() zeros the count after
        the com or sbc file is serialized.
        Returns a formatted string of OPCD and arguments for the passed OPCD
        string.
                :param opcd:
                        MacroModel operation code.
                :type opcd:
                        string
        """
        # Default opcd arguments. bmin usually interprets 0 as some
        # other default value.
        args = [opcd, 0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0]
        args_str = ""
        # Check multiplicity of this opcode call, i is the index into
        # the opcd array of args.
        i = 8 * self._written.count(opcd)
        if opcd in self.__dict__:
            args[0] = opcd
            args[1] = self.__dict__[opcd].get(i + 1, 0)
            args[2] = self.__dict__[opcd].get(i + 2, 0)
            args[3] = self.__dict__[opcd].get(i + 3, 0)
            args[4] = self.__dict__[opcd].get(i + 4, 0)
            args[5] = self.__dict__[opcd].get(i + 5, 0)
            args[6] = self.__dict__[opcd].get(i + 6, 0)
            args[7] = self.__dict__[opcd].get(i + 7, 0)
            args[8] = self.__dict__[opcd].get(i + 8, 0)
        if opcd in ['ASL1']:
            args_str = self._formatSbcAsl1Str(args)
        elif opcd in ['ASL2']:
            args_str = self._getSbcAsl2Str(args)
        else:
            args_str = self._formatOpcdStr(args)
        # Record opcd used to later multiplicity checks.  This
        # semi-private array gets emptied after writeSbcFile() serializes
        # the sbc file.
        self._written.append(opcd)
        return args_str 
    def _formatOpcdStr(self, args):
        # arg1-4 value size check.
        for arg_pos in [1, 2, 3, 4]:
            if len(str(args[arg_pos])) > 6:
                if self.refit_args and args[arg_pos] > 0:
                    args[arg_pos] = 999999
                elif self.refit_args and args[arg_pos] < 0:
                    args[arg_pos] = -99999
        # arg5-8 value size check, reformatted if needed.
        opcd_format = {}
        for arg_pos in [5, 6, 7, 8]:
            opcd_format[arg_pos] = '%5.4f'
            value = str("%f" % args[arg_pos])
            if len(value.split('.')[0]) == 6:
                opcd_format[arg_pos] = '%6.3f'
            elif len(value.split('.')[0]) == 7:
                opcd_format[arg_pos] = '%7.2f'
            elif len(value.split('.')[0]) == 8:
                opcd_format[arg_pos] = '%8.1f'
            elif len(value.split('.')[0]) == 9:
                opcd_format[arg_pos] = '%9.0f.'  # note the trailing '.'
            elif len(value.split('.')[0]) >= 10:
                if self.refit_args and args[arg_pos] > 0:
                    args[arg_pos] = 999999999
                elif self.refit_args and args[arg_pos] < 0:
                    args[arg_pos] = -99999999
                opcd_format[arg_pos] = '%9.0f.'
        args[1] = '%d' % args[1]
        args[2] = '%d' % args[2]
        args[3] = '%d' % args[3]
        args[4] = '%d' % args[4]
        args[5] = opcd_format[5] % args[5]
        args[6] = opcd_format[6] % args[6]
        args[7] = opcd_format[7] % args[7]
        args[8] = opcd_format[8] % args[8]
        # Justify the string so we can join the list elements contigiously
        args[0] = args[0].rjust(5)
        args[1] = args[1].rjust(8)  # eight takes up the OPCD trailing space
        args[2] = args[2].rjust(7)
        args[3] = args[3].rjust(7)
        args[4] = args[4].rjust(7)
        args[5] = args[5].rjust(11)  # the decimal takes a char.
        args[6] = args[6].rjust(11)
        args[7] = args[7].rjust(11)
        args[8] = args[8].rjust(11)
        args.append('\n')
        args_string = "".join(args)
        return args_string
    def _formatSbcAsl1Str(self, args):
        # ASL1 takes a special string-aware format: 1X,A4,1X,I7,X,A
        args[0] = args[0].rjust(5)
        args[1] = '%d'.rjust(8) % args[1]
        args[2] = "  %s\n" % args[2]
        args_string = ''.join(args[0:3])
        return args_string
    def _getSbcAsl2Str(self, args):
        # As of suite2010 ASL2 takes two different formats:
        # format: 1X,A4,1X,I4,X,F7.3,X,A  : suite2010 flat-bottom half-width
        # format: 1X,A4,1X,F7.3,X,A       : pre-suite2009
        if self.use_flat_bottom_asl2:
            #OPCD IIII FFFF.FFF SSSSSSSSSSSSSSSSSSSSSSSSSS...many chars
            args[0] = args[0].rjust(5)
            args[1] = '%4d'.rjust(4) % args[1]  # four char plus a pad.
            args[3] = ' %s\n' % args[3]
            value = str("%f" % args[2])
            if len(value.split('.')[0]) >= 7:
                if self.refit_args and args[2] > 0:
                    args[2] = '%7.0f.'.rjust(7) % 9999999
                elif self.refit_args and args[2] < 0:
                    args[2] = '%7.0f.'.rjust(7) % -999999
            elif len(value.split('.')[0]) == 6:
                args[2] = '%6.1f'.rjust(6) % args[2]
            elif len(value.split('.')[0]) == 5:
                args[2] = '%5.2f'.rjust(6) % args[2]
            elif len(value.split('.')[0]) == 4:
                args[2] = '%4.3f'.rjust(6) % args[2]
            elif len(value.split('.')[0]) == 3:
                args[2] = '%3.4f'.rjust(6) % args[2]
            elif len(value.split('.')[0]) == 2:
                args[2] = '%2.5f'.rjust(6) % args[2]
            elif len(value.split('.')[0]) == 1:
                args[2] = '%1.6f'.rjust(6) % args[2]
            elif len(value.split('.')[0]) == 0:
                args[2] = '%0.7f'.rjust(6) % args[2]
            else:
                args[2] = '%7.1f'.rjust(7) % args[2]
            args_string = ''.join(args[0:4])
        else:
            #OPCD FFFF.FFF SSSSSSSSSSSSSSSSSSSSSSSSSS...many chars
            args[0] = args[0].rjust(5)
            value = str("%f" % args[1])
            if len(value.split('.')[0]) >= 7:
                if self.refit_args and args[1] > 0:
                    args[1] = '%7.0f.'.rjust(7) % 9999999
                elif self.refit_args and args[1] < 0:
                    args[1] = '%7.0f.'.rjust(7) % -999999
            elif len(value.split('.')[0]) == 6:
                args[1] = '%6.1f'.rjust(1) % args[1]
            elif len(value.split('.')[0]) == 5:
                args[1] = '%5.2f'.rjust(6) % args[1]
            elif len(value.split('.')[0]) == 4:
                args[1] = '%4.3f'.rjust(6) % args[1]
            elif len(value.split('.')[0]) == 3:
                args[1] = '%3.4f'.rjust(6) % args[1]
            elif len(value.split('.')[0]) == 2:
                args[1] = '%2.5f'.rjust(6) % args[1]
            elif len(value.split('.')[0]) == 1:
                args[1] = '%1.6f'.rjust(6) % args[1]
            elif len(value.split('.')[0]) == 0:
                args[1] = '%0.7f'.rjust(6) % args[1]
            else:
                args[1] = '%7.1f'.rjust(7) % args[1]
            args[2] = ' %s\n' % args[2]
            args_string = ''.join(args[0:3])
        return args_string
[docs]    def setOpcdArgs(self,
                    opcd="",
                    arg1=0,
                    arg2=0,
                    arg3=0,
                    arg4=0,
                    arg5=0.0,
                    arg6=0.0,
                    arg7=0.0,
                    arg8=0.0):
        """
        This method returns True after adding the passed values to the
        desired opcd dictionary.
        The dictionary is selected by the first parameter, which is
        required.  The rest of the parameters are mapped to the next
        eight argument indices in the dictionary.  Unspecified values
        default to 0 or 0.0.
        This method determines the highest existing index (numeric hash
        key), and then assigns the passed args to the next available
        slots.  You may want to first blank the dictionary with the
        appropriate call of self['OPCD'].clear().
        Assumming the dictionary has has been cleared, the first 'set' of
        a given OPCD assigns self['OPCD'] arg indices 1-8, the next call
        for that OPCD defines indices 9-16, the next 17-24, and so on.
        """
        # Try counting the highest indexed key.
        i = 0
        try:
            keys = list(self.__dict__[opcd])
            if len(keys) > 0:
                i = keys[-1]
        except KeyError:
            self.__dict__[opcd] = {}
        # Determine the proper index
        y = divmod(i,
                   8)  # tuple[0] = num of whole sets, tuple[1] = num partials
        i = y[0]
        if y[1] > 0:
            i += 1
        # Advance the index by eight, or one 'row', or one call...
        i = 8 * i
        # Assign
        self.__dict__[opcd][i + 1] = arg1
        self.__dict__[opcd][i + 2] = arg2
        self.__dict__[opcd][i + 3] = arg3
        self.__dict__[opcd][i + 4] = arg4
        self.__dict__[opcd][i + 5] = arg5
        self.__dict__[opcd][i + 6] = arg6
        self.__dict__[opcd][i + 7] = arg7
        self.__dict__[opcd][i + 8] = arg8
        # Return True if we made it this far
        return True  
[docs]class CluUtil:
    """
    A class for writing and running Cluster clu files.
    The class can fabricate jobname.clu files, and optionally run them.
    The class allows running Cluster or Xcluster jobs.
    """
[docs]    def __init__(
            self,
            arms=['heavy'],  # noqa: M511
            nrms=[],  # noqa: M511
            trms=[],  # noqa: M511
            thresh=0,
            mmsym=True,
            nant=False):
        """
        This method loads the default data members and methods.
        """
        ##
        # Data members
        ##
        # version
        self.version = '$Revision: 1.32.2.1 $'
        self.arms = arms  # array of atom numbers, printed 1 per line
        self.nrms = nrms  # array of atom numbers, printed 1 per line
        self.trms = trms  # array of atom numbers, printed 4 per line
        self.thresh = thresh  # integer describing the critical distance
        self.writecls = ""  # string defining file name and cluster level
        self.writerep = ""  # string defining file name and cluster level
        self.writeavg = ""  # string defining file name and cluster level
        self.writelead = ""  # string defining file name and cluster level
        self.writemap = ""  # string defining file name
        self.writedst = ""  # string defining file name
        self.mmsym = mmsym
        self.nant = nant
        # done with init
        return None 
[docs]    def writeCluFile(self, in_file=""):
        """
        This method writes a jobname.clu command file for the passed
        'jobname.mae', or 'jobname.dst' file;  returns the name fo the
        clu file written.
        The keyword values are taken from the self object, and need to be
        defined prior to calling this method.  Returns a string for the
        'jobname.clu' file written.
        The first argument for either Arms: or Nrms: is checked for the
        value 'heavy' or 'all'.  If the first argument in these arrays
        does not match those values then the array is assumed to contain
        numerical values and is formatted.
        If the provided input is a distance file, recognized by a .dst
        extention, then the job will be run in dfile mode.  No additional
        options are used in dfile mode, and exiting self values for mmsym,
        nant, arms, and trms are set to false/empty values.
        """
        # Prep regular expressions
        mae_file_re = re.compile(r'.mae$')
        dst_file_re = re.compile(r'.dst$')
        # Change file names, and object defaults
        if dst_file_re.search(in_file):
            clu_file = dst_file_re.sub('.clu', in_file)
            self.mmsym = False
            self.nant = False
            self.arms = []
            self.trms = []
        else:
            clu_file = mae_file_re.sub('.clu', in_file)
        # Get our file handle ready to write, clobber existing file
        clu_fh = open(clu_file, mode='w')
        # Apply keyword arg regex
        kw_re = re.compile(r'^(heavy|all)$')
        # Write keywords and args
        if dst_file_re.search(in_file):
            clu_fh.write("".join(["Dfile: ", in_file, '\n']))
        else:
            clu_fh.write("".join(["Sfile: ", in_file, '\n']))
        if self.mmsym:
            clu_fh.write("Mmsym: \n")
        if self.nant:
            clu_fh.write("Nant: \n")
        if self.arms:
            clu_fh.write("Arms: ")
            if isinstance(self.arms[0], str) and kw_re.match(self.arms[0]):
                clu_fh.write("".join([self.arms[0], '\n']))
            else:
                clu_fh.write('\n')
                for atomnum in self.arms:
                    atomline = '%d' % atomnum
                    atomline.rjust(5)
                    clu_fh.write("".join([atomline, '\n']))
                clu_fh.write("\n")
        if self.nrms:
            clu_fh.write("Nrms: ")
            if isinstance(self.nrms[0], str) and kw_re.match(self.nrms[0]):
                clu_fh.write("".join([self.nrms[0], '\n']))
            else:
                clu_fh.write('\n')
                for atomnum in self.nrms:
                    atomline = '%d' % atomnum
                    atomline.rjust(5)
                    clu_fh.write("".join([atomline, '\n']))
                clu_fh.write('\n')
        if self.trms:
            clu_fh.write("Trms:\n")
            while self.trms:
                atomline = '     %d     %d     %d     %d' % (
                    self.trms.pop(), self.trms.pop(), self.trms.pop(),
                    self.trms.pop())
                clu_fh.write("".join([atomline, '\n']))
            clu_fh.write('\n')
        clu_fh.write("Cluster: \n")  # required command, no args
        if self.thresh:
            clu_fh.write("".join(["Thresh: ", repr(self.thresh), "\n"]))
        if self.writecls:
            clu_fh.write("".join(["Writecls: ", self.writecls, "\n"]))
        if self.writerep:
            clu_fh.write("".join(["Writerep: ", self.writerep, "\n"]))
        if self.writelead:
            clu_fh.write("".join(["Writelead: ", self.writelead, "\n"]))
        if self.writeavg:
            clu_fh.write("".join(["Writeavg: ", self.writeavg, "\n"]))
        if self.writemap:
            clu_fh.write("".join(["Writemap: ", self.writemap, "\n"]))
        if self.writedst:
            clu_fh.write("".join(["Writedst: ", self.writedst, "\n"]))
        # Flush, close
        clu_fh.flush()
        clu_fh.close()
        # Return file name for written clu file
        return clu_file 
[docs]    def runCluFile(self, clu_file=""):
        """
        This method uses jobcontrol.launch_job (or system call if import
        failed) to invoke cluster for the passed jobname.clu file.
        Returns True.
        It requires a string containing the 'jobname.clu' file name.
        """
        clu_file_re = re.compile(r'.clu$')  # truncate file name
        clu_file = clu_file_re.sub('', clu_file)
        cmd = " ".join([
            os.path.join(os.environ.get('SCHRODINGER'), 'cluster'),
            clu_file,
        ])
        # ev45987 fixes for windows
        # If no jobcontrol, do clean up ourselves as described by Aditya
        cmd = cmd.replace("\\", "\\\\")
        cmd = "%s" % cmd
        os.system(cmd)
        return True 
[docs]    def runXCluFile(self, clu_file=""):
        """
        This method uses jobcontrol.launch_job (or a system call if
        import failed) to invoke Xcluster for the passed jobname.clu file.
        Returns True.
        It requires a string containing the 'jobname.clu' file name.
        """
        clu_file_re = re.compile(r'.clu$')  # truncate file name
        clu_file = clu_file_re.sub('', clu_file)
        cmd = " ".join([
            os.path.join(os.environ.get('SCHRODINGER'), 'xcluster -r'),
            clu_file,
        ])
        # ev45987 fixes for windows
        # Do command line clean up ourselves as described by Aditya
        cmd = cmd.replace("\\", "\\\\")
        cmd = "%s" % cmd
        os.system(cmd)
        return True 
[docs]    def doCluster(self, in_file=""):
        """
        This method writes and runs a cluster job for the passed
        mae or dst file name. returns True
        A combination of writeCluFile and runCluFile.
        """
        # Do it
        return self.runCluFile(self.writeCluFile(in_file)) 
[docs]    def doXCluster(self, in_file=""):
        """
        This method writes and runs a Xcluster job for the passed mae
        or dst file name, returns True
        A combination of writeCluFile and runXCluFile.
        """
        # Do it
        return self.runXCluFile(self.writeCluFile(in_file)) 
[docs]    def writeDstFile(self, dst_file="cluster.dst", values=[]):  # noqa: M511
        """
        This method writes a distance file with the provided name,
        for the passed values.  Returns the name of the distance file
        written, which is cluster.dst by default.
        This method calculates the 'distances' between the passed values.
        The distances are calculated as the absolute value of i - j,
        truncated as 5.5f, right justified as 16 characters, and printed
        five per line.
        For example, if passed a list with nine values, the distance
        file would look like:
        9
        d12 d13 d14 d15 d16
        d17 d18 d19 d23 d24
        d25 d26 d27 d28 d29
        d34 d35 d36 d37 d38
        d39 d45 d46 d47 d48
        d49 d56 d57 d58 d59
        d67 d68 d69 d78 d79
        d89
        where d12 is the formatted result of abs(values[0] - values[1])
        """
        # Count the total number of N values
        total_num_points = len(values)
        # List for calculated distances
        distances = []
        # calculate unique i != j distances
        for i in range(0, total_num_points):
            for j in range(0, total_num_points):
                if i == j:
                    pass  # skip on-diagonal values
                elif i != 0 and j < i:
                    pass  # skip symmetric values
                else:
                    distances.append(abs(values[i] - values[j]))
        # Get our file handle ready to write, clobber existing file
        dst_fh = open(dst_file, mode='w')
        # Thankfully, cluster is forgiving with its dfile format
        # because this is sketchy
        dst_fh.write("".join([repr(total_num_points), '\n']))
        count = 0
        for dist in distances:
            count = count + 1
            dist = '%5.5f' % dist
            dist = dist.rjust(16)  # the decimal takes a char
            dst_fh.write(dist)
            if count % 5 == 0:
                dst_fh.write('\n')
            # Flush, close
        dst_fh.flush()
        dst_fh.close()
        return dst_file  
# EOF