def setupHydrationCalculation(molecule):
    """
    """

    # get current directory
    current_path = os.getcwd()

    # Center the molecule.
    OECenter(molecule)

    # get molecule name
    molecule_name = molecule.GetTitle()
    print molecule_name

    # create molecule path/directory
    molecule_path = os.path.abspath(os.path.join('molecules', molecule_name))
    os.makedirs(molecule_path)

    # Write mol2 file for the molecule.
    writeMolecule(molecule, os.path.join(molecule_path, 'solute.mol2'))

    # Write GAFF parameters for gromacs, using antechamber to generate AM1-BCC charges.
    parameterizeForGromacs(molecule, topology_filename = os.path.join(molecule_path,'solute.top'), coordinate_filename = os.path.join(molecule_path,'solute.gro'), charge_model = 'bcc', resname = 'MOL')

    # Modify gromacs topology file for alchemical free energy calculation.
    perturbGromacsTopology(os.path.join(molecule_path,'solute.top'), molecule)

    # Convert gromacs topology to an .itp file suitable for inclusion.
    top_to_itp(os.path.join(molecule_path,'solute.top'), os.path.join(molecule_path,'solute.itp'), moleculetype = molecule_name)

    # SET UP VACUUM SIMULATION

    # construct pathname for vacuum simulations
    vacuum_path = os.path.join(molecule_path, 'vacuum')
    os.mkdir(vacuum_path)
    os.chdir(vacuum_path)
    
    # create molecule in enlarged box
    command = 'editconf_d -f ../solute.gro -o system.gro -bt cubic -d 50.0 -center 0.0 0.0 0.0'
    print command
    output = commands.getoutput(command)
    print output

    # create topology file
    topology_contents = """
; amber forcefield
#include "ffamber03.itp"

; my molecule
#include "../solute.itp"

[ system ]
%(molecule_name)s in vacuum

[ molecules ]
; Compound        nmols
%(molecule_name)s            1
""" % vars()
    writeFile('system.top', topology_contents)

    # construct .mdp files
    mdpfile = MdpFile(compose_blocks(['header', 'minimization', 'nonbonded-vacuum', 'output']))
    mdpfile.write(os.path.join(vacuum_path, 'minimization-unconstrained.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'minimization', 'nonbonded-vacuum', 'constraints', 'output']))
    mdpfile.write(os.path.join(vacuum_path, 'minimization-constrained.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-vacuum', 'constraints', 'thermostat', 'free-energy', 'output']))
    mdpfile.randomizeSeed() # randomize velocities
    mdpfile.write(os.path.join(vacuum_path, 'production.mdp'))

    # write shell script for minimization and production
    contents = """\
#/bin/tcsh

source /Users/jchodera/local/gromacs-3.1.4-fe/i386-apple-darwin8.11.1/bin/GMXRC.csh
setenv GROMPP grompp_d
setenv MDRUN mdrun_d
    
# unconstrained minimization
$GROMPP -f minimization-unconstrained.mdp -c system.gro -p system.top -o minimization-unconstrained.tpr -maxwarn 10000
$MDRUN -s minimization-unconstrained.tpr -x minimization-unconstrained.xtc -c minimized-unconstrained.gro -e minimization-unconstrained.edr -g minimization-unconstrained.log
    
# constrained minimization
$GROMPP -f minimization-constrained.mdp -c minimized-unconstrained.gro -p system.top -o minimization-constrained.tpr -maxwarn 10000
$MDRUN -s minimization-constrained.tpr -x minimization-constrained.xtc -c minimized-constrained.gro -e minimization-constrained.edr -g minimization-constrained.log

# production
$GROMPP -f production.mdp -c minimized-constrained.gro -p system.top -o production.tpr -maxwarn 10000
$MDRUN -v -s production.tpr -o production.trr -x production.xtc -c production.gro -e production.edr -g production.log
echo 0 | trjconv_d -f production.xtc -o production.pdb -s production.tpr
""" % vars()    
    writeFile(os.path.join(vacuum_path, 'run.sh'), contents)

    # SET UP SOLVATED CALCULATION

    # construct pathname for solvent simulations
    solvated_path = os.path.join(molecule_path, 'solvent')
    os.mkdir(solvated_path)
    os.chdir(solvated_path)
    
    # write enlarged box for solvent
    command = 'editconf_d -f ../solute.gro -o solute.gro -bt octahedron -d 1.0 -center 0.0 0.0 0.0'
    print command
    output = commands.getoutput(command)
    print output

    # write topology
    topology_contents = """
; amber forcefield
#include "ffamber03.itp"

; my molecule
#include "../solute.itp"

; TIP3P water
#include "ffamber_tip3p.itp"

[ system ]
%(molecule_name)s in solvent

[ molecules ]
; Compound        nmols
%(molecule_name)s            1
""" % vars()
    writeFile('system.top', topology_contents)

    # solvate
    command = 'genbox_d -cp solute.gro -cs ffamber_tip3p.gro -o system.gro -p system.top'
    # command = 'genbox_d -cp solute.gro -cs tip3p-pme-waterbox.gro -o system.gro -p system.top' # use our special tip3p box
    # command = 'genbox_d -cp solute.gro -cs tip3p-pme-waterbox.gro -o system.gro -p system.top -vdwd 0.0' # insert waters but don't cull overlap
    print command
    output = commands.getoutput(command)
    print output
    
    # construct .mdp files
    mdpfile = MdpFile(compose_blocks(['header', 'minimization', 'nonbonded-solvent', 'output']))
    mdpfile.write(os.path.join(solvated_path, 'minimization-unconstrained.mdp'))
    
    mdpfile = MdpFile(compose_blocks(['header', 'minimization', 'nonbonded-solvent', 'constraints', 'output']))
    mdpfile.write(os.path.join(solvated_path, 'minimization-constrained.mdp'))
    
#    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-solvent', 'constraints', 'thermostat-equilibration', 'barostat', 'output']))
#    mdpfile.randomizeSeed() # randomize velocities
#    mdpfile.setParameter('nsteps', '25000') # 10 ps equilibration
#    mdpfile.write(os.path.join(solvated_path, 'equilibration.mdp'))
    
    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-solvent', 'constraints', 'thermostat', 'barostat', 'free-energy', 'output']))
    mdpfile.randomizeSeed() # randomize velocities
    mdpfile.write(os.path.join(solvated_path, 'production.mdp'))

    # write run script
    contents = """\
#/bin/tcsh

source /Users/jchodera/local/gromacs-3.1.4-fe/i386-apple-darwin8.11.1/bin/GMXRC.csh
setenv GROMPP grompp_d
setenv MDRUN mdrun_d
    
# unconstrained minimization
$GROMPP -f minimization-unconstrained.mdp -c system.gro -p system.top -o minimization-unconstrained.tpr -maxwarn 10000
$MDRUN -s minimization-unconstrained.tpr -x minimization-unconstrained.xtc -c minimized-unconstrained.gro -e minimization-unconstrained.edr -g minimization-unconstrained.log

# constrained minimization
$GROMPP -f minimization-constrained.mdp -c minimized-unconstrained.gro -p system.top -o minimization-constrained.tpr -maxwarn 10000
$MDRUN -s minimization-constrained.tpr -x minimization-constrained.xtc -c minimized-constrained.gro -e minimization-constrained.edr -g minimization-constrained.log

# equilibration
#$GROMPP -f equilibration.mdp -c minimized-constrained.gro -p system.top -o equilibration.tpr -maxwarn 10000
#$MDRUN -v -s equilibration.tpr -o equilibration.trr -x equilibration.xtc -c equilibration.gro -e equilibration.edr -g equilibration.log
#echo 0 | trjconv_d -f equilibration.xtc -o equilibration.pdb -s equilibration.tpr

# production
$GROMPP -f production.mdp -c minimized-constrained.gro -p system.top -o production.tpr -maxwarn 10000
#$GROMPP -f production.mdp -c equilibration.gro -p system.top -o production.tpr -maxwarn 10000
#$GROMPP -f production.mdp -c system.gro -p system.top -o production.tpr -maxwarn 10000
$MDRUN -v -s production.tpr -o production.trr -x production.xtc -c production.gro -e production.edr -g production.log
echo 0 | trjconv_d -f production.xtc -o production.pdb -s production.tpr
""" % vars()
    writeFile(os.path.join(solvated_path, 'run.sh'), contents)

    # restore working directory
    os.chdir(current_path)

    return
def setupHydrationCalculation(solute,
                              nreplicates=1,
                              verbose=True,
                              jobname="hydration"):
    """Set up an absolute alchemical hydration free energy calculation for the given molecule.

    ARGUMENTS
      solute (OEMol) - the molecule for which hydration free energy is to be computed (with fully explicit hydrogens) in the desired protonation state.

    OPTIONAL ARGUMENTS
      nreplicates (integer) - the number of replicates to set up (default: 1)
      verbose (boolean) - if True, extra debug information will be printed (default: True)
      jobname (string) - string to use for job name (default: "hydration")

    NOTES
      A directory will be created 'molecules/[molecule name]' as obtained from molecule.GetTitle().
    
    """

    # get current directory
    current_path = os.getcwd()

    # Center the solute molecule.
    OECenter(solute)

    # get molecule name
    solute_name = molecule.GetTitle()
    if verbose: print solute_name

    # create molecule path/directory
    work_path = os.path.abspath(os.path.join('molecules', solute_name))
    os.makedirs(work_path)

    #    # Write mol2 file for the molecule.
    #    writeMolecule(solute, os.path.join(solute_path, 'solute.mol2'))

    #    # Write GAFF parameters for gromacs, using antechamber to generate AM1-BCC charges.
    #    parameterizeForGromacs(molecule, topology_filename = os.path.join(molecule_path,'solute.top'), coordinate_filename = os.path.join(molecule_path,'solute.gro'), charge_model = 'bcc', resname = 'MOL')

    #    # Modify gromacs topology file for alchemical free energy calculation.
    #    perturbGromacsTopology(os.path.join(molecule_path,'solute.top'), molecule)

    #    # Convert gromacs topology to an .itp file suitable for inclusion.
    #    top_to_itp(os.path.join(molecule_path,'solute.top'), os.path.join(molecule_path,'solute.itp'), moleculetype = molecule_name)

    # get pathnames
    mdrun = globals()['mdrun']
    grompp = globals()['grompp']
    editconf = globals()['editconf']

    # SET UP SOLUTE TOPOLOGY

    if verbose: print "\nCONSTRUCTING SOLUTE TOPOLOGY"

    # Get formal charge of ligand.
    solute_charge = formalCharge(solute)
    if verbose: print "solute formal charge is %d" % solute_charge

    # Write molecule with explicit hydrogens to mol2 file.
    print "Writing solute mol2 file..."
    solute_mol2_filename = os.path.abspath(
        os.path.join(work_path, 'solute.mol2'))
    writeMolecule(solute, solute_mol2_filename)

    # Set substructure name (which will become residue name).
    print "Modifying molecule name..."
    modifySubstructureName(solute_mol2_filename, 'MOL')

    # Run antechamber to assign GAFF atom types.
    print "Running antechamber..."
    os.chdir(work_path)
    gaff_mol2_filename = os.path.join(work_path, 'solute.gaff.mol2')
    charge_model = 'bcc'
    command = 'antechamber -i %(solute_mol2_filename)s -fi mol2 -o solute.gaff.mol2 -fo mol2 -c %(charge_model)s -nc %(solute_charge)d > antechamber.out' % vars(
    )
    if verbose: print command
    output = commands.getoutput(command)
    if verbose: print output
    os.chdir(current_path)

    # Generate frcmod file for additional GAFF parameters.
    solute_frcmod_filename = os.path.join(work_path, 'frcmod.solute')
    command = 'parmchk -i %(gaff_mol2_filename)s -f mol2 -o %(solute_frcmod_filename)s' % vars(
    )
    if verbose: print command
    output = commands.getoutput(command)
    if verbose: print output

    # Run LEaP to generate topology / coordinates.
    solute_prmtop_filename = os.path.join(work_path, 'solute.prmtop')
    solute_crd_filename = os.path.join(work_path, 'solute.crd')
    solute_off_filename = os.path.join(work_path, 'solute.off')

    tleap_input_filename = os.path.join(work_path, 'setup-solute.leap.in')
    tleap_output_filename = os.path.join(work_path, 'setup-solute.leap.out')
    contents = """
# Load GAFF parameters.
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(solute_frcmod_filename)s

# load solute
solute = loadMol2 %(gaff_mol2_filename)s

# check the solute
check solute

# report net charge
charge solute

# save AMBER parameters
saveAmberParm solute %(solute_prmtop_filename)s %(solute_crd_filename)s

# write .off file
saveOff solute %(solute_off_filename)s

# exit
quit
""" % vars()
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars(
    )
    output = commands.getoutput(command)

    # extract total charge
    solute_charge = commands.getoutput(
        'grep "Total unperturbed charge" %(tleap_output_filename)s | cut -c 27-'
        % vars())
    solute_charge = int(round(
        float(solute_charge)))  # round to nearest whole charge
    if verbose: print "solute charge is %d" % solute_charge

    # PREPARE SOLVATED SOLUTE

    print "\nPREPARING SOLVATED SOLUTE"

    # create the directory if it doesn't exist
    solvent_path = os.path.join(work_path, 'solvent')
    if not os.path.exists(solvent_path):
        os.makedirs(solvent_path)

    # solvate the solute
    print "Solvating the solute with tleap..."
    system_prmtop_filename = os.path.join(solvent_path, 'system.prmtop')
    system_crd_filename = os.path.join(solvent_path, 'system.crd')
    tleap_input_filename = os.path.join(solvent_path, 'setup-system.leap.in')
    tleap_output_filename = os.path.join(solvent_path, 'setup-system.leap.out')
    clearance = globals()['clearance']  # clearance around solute (in A)
    contents = """
source leaprc.ff99
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(solute_frcmod_filename)s

# Load solute.
loadOff %(solute_off_filename)s

# Create system.
system = combine { solute }
""" % vars()
    # add counterions
    if (solute_charge != 0):
        nions = abs(solute_charge)
        if solute_charge < 0: iontype = 'Na+'
        if solute_charge > 0: iontype = 'Cl-'
        contents += """
# Add counterions.
addions system %(iontype)s %(nions)d
""" % vars()
    #
    contents += """
# Solvate in truncated octahedral box.
solvateBox system TIP3PBOX %(clearance)f iso

# Check the system
check system

# Write the system
saveamberparm system %(system_prmtop_filename)s %(system_crd_filename)s
""" % vars()
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars(
    )
    output = commands.getoutput(command)

    # convert to gromacs
    print "Converting to gromacs..."
    amb2gmx = os.path.join(os.getenv('MMTOOLSPATH'), 'converters',
                           'amb2gmx.pl')
    system_prefix = os.path.join(solvent_path, 'system')
    os.chdir(solvent_path)
    command = '%(amb2gmx)s --prmtop system.prmtop --crd system.crd --outname system' % vars(
    )
    print command
    output = commands.getoutput(command)
    print output
    os.chdir(current_path)

    # Extract box size.
    g96_filename = os.path.join(solvent_path, 'system.g96')
    g96_file = open(g96_filename, 'r')
    g96_lines = g96_file.readlines()
    g96_file.close()
    box_size = zeros([3], float32)
    for line_number in range(len(g96_lines)):
        if g96_lines[line_number][0:3] == 'BOX':
            # parse line with box size in nm
            line = g96_lines[line_number + 1]
            elements = line.split()
            box_size[0] = float(elements[0]) * 10.0
            box_size[1] = float(elements[1]) * 10.0
            box_size[2] = float(elements[2]) * 10.0
    print "box_size = "
    print box_size

    # make a PDB file for checking
    print "Converting system to PDB..."
    os.chdir(solvent_path)
    command = 'cat system.crd | ambpdb -p system.prmtop > system.pdb' % vars()
    output = commands.getoutput(command)
    print output
    os.chdir(current_path)

    # set up perturbation
    print "Modifying topology file for perturbation..."
    system_top_filename = os.path.join(solvent_path, 'system.top')
    lines = read_file(system_top_filename)
    perturb_atom_indices = list()
    indices = extract_section(lines, 'atoms')
    for index in indices:
        # extract the line
        line = stripcomments(lines[index])
        # parse the line
        elements = line.split()
        nelements = len(elements)
        # skip if not all elements found
        if (nelements < 8): continue
        # parse line
        atom = dict()
        atom['nr'] = int(elements[0])
        atom['type'] = elements[1]
        atom['resnr'] = int(elements[2])
        atom['residue'] = elements[3]
        atom['atom'] = elements[4]
        atom['cgnr'] = int(elements[5])
        atom['charge'] = float(elements[6])
        atom['mass'] = float(elements[7])
        # add those atoms in the solute to our list
        if atom['residue'] == 'MOL':
            perturb_atom_indices.append(atom['nr'])
    perturbGromacsTopology(system_top_filename,
                           solute,
                           perturb_torsions=True,
                           perturb_vdw=True,
                           perturb_charges=True,
                           perturb_atom_indices=perturb_atom_indices)

    # set up replicates
    for replicate in range(nreplicates):
        # Create replicate directory.
        working_path = os.path.join(solvent_path, '%d' % replicate)
        os.makedirs(working_path)

        # TODO: Modify solute coordinates in system.g96 to cycle through available conformations.

        # set up mdp files
        print "Writing mdp files for replicate %d..." % replicate

        mdpfile = MdpFile(
            compose_blocks([
                'header', 'minimization', 'nonbonded-solvent', 'constraints',
                'output'
            ]))
        mdpfile.write(os.path.join(working_path, 'minimize.mdp'))

        mdpfile = MdpFile(
            compose_blocks([
                'header', 'dynamics', 'nonbonded-solvent', 'constraints',
                'thermostat', 'barostat', 'output'
            ]))
        mdpfile.setParameter('nsteps', '10000')  # 20 ps equilibration
        mdpfile.randomizeSeed()  # randomize velocities
        mdpfile.write(os.path.join(working_path, 'equilibration.mdp'))

        mdpfile = MdpFile(
            compose_blocks([
                'header', 'dynamics', 'nonbonded-solvent', 'constraints',
                'thermostat', 'barostat', 'free-energy', 'output'
            ]))
        mdpfile.randomizeSeed()  # randomize velocities
        mdpfile.write(os.path.join(working_path, 'production.mdp'))

        # write run script
        print "Writing run script..."
        solvent_path = os.path.abspath(solvent_path)
        contents = """\
set#!/bin/tcsh
#BSUB -J %(jobname)s_solvent
#BSUB -o %(working_path)s/outfile.%%J
#BSUB -e %(working_path)s/errfile.%%J
#BSUB -n 1
#BSUB -M 2000000
#BSUB -W 168:0

source $GMXRC
cd %(working_path)s

# constrained minimize
%(grompp)s -f minimize.mdp -c ../system.g96 -p ../system.top -o minimize.tpr -maxwarn 10000 -n ../system.ndx
%(mdrun)s -s minimize.tpr -x minimize.xtc -c minimize.g96 -e minimize.edr -g minimize.log

# equilibration
%(grompp)s -f equilibration.mdp -c minimize.g96 -p ../system.top -o equilibration.tpr -maxwarn 10000 -n ../system.ndx
%(mdrun)s -s equilibration.tpr -o equilibration.trr -x equilibration.xtc -c equilibration.g96 -e equilibration.edr -g equilibration.log

# production
%(grompp)s -f production.mdp -c equilibration.g96 -p ../system.top -o production.tpr -maxwarn 10000 -n ../system.ndx
%(mdrun)s -s production.tpr -o production.trr -x production.xtc -c production.g96 -e production.edr -g production.log

# signal completion
mv run.sh run.sh.done
""" % vars()
        write_file(os.path.join(working_path, 'run.sh'), contents)

    # SET UP VACUUM SIMULATION

    # construct pathname for vacuum simulations
    vacuum_path = os.path.join(work_path, 'vacuum')
    if not os.path.exists(vacuum_path):
        os.makedirs(vacuum_path)

    # solvate the solute
    print "Preparing vacuum solute with tleap..."
    system_prmtop_filename = os.path.join(vacuum_path, 'system.prmtop')
    system_crd_filename = os.path.join(vacuum_path, 'system.crd')
    tleap_input_filename = os.path.join(vacuum_path, 'setup-system.leap.in')
    tleap_output_filename = os.path.join(vacuum_path, 'setup-system.leap.out')
    clearance = 50.0  # clearance in A
    contents = """
source leaprc.ff99
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(solute_frcmod_filename)s

# Load solute.
loadOff %(solute_off_filename)s

# Create system.
system = combine { solute }
""" % vars()
    # add counterions
    if (solute_charge != 0):
        nions = abs(solute_charge)
        if solute_charge < 0: iontype = 'Na+'
        if solute_charge > 0: iontype = 'Cl-'
        contents += """
# Add counterions.
addions system %(iontype)s %(nions)d
""" % vars()
    #
    contents += """

# Create big box.
setBox system centers %(clearance)f iso

# Check the system
check system

# Write the system
saveamberparm system %(system_prmtop_filename)s %(system_crd_filename)s
""" % vars()
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars(
    )
    output = commands.getoutput(command)

    # convert to gromacs
    print "Converting to gromacs..."
    amb2gmx = os.path.join(os.getenv('MMTOOLSPATH'), 'converters',
                           'amb2gmx.pl')
    os.chdir(vacuum_path)
    command = '%(amb2gmx)s --prmtop system.prmtop --crd system.crd --outname system' % vars(
    )
    print command
    output = commands.getoutput(command)
    print output
    os.chdir(current_path)

    # make a PDB file for checking
    print "Converting system to PDB..."
    os.chdir(vacuum_path)
    command = 'cat system.crd | ambpdb -p system.prmtop > system.pdb' % vars()
    output = commands.getoutput(command)
    print output
    os.chdir(current_path)

    # write enlarged box for solvent because LEaP doesn't do it right.
    system_g96_filename = os.path.join(vacuum_path, 'system.g96')
    command = '%s -f %s -o %s -bt cubic -box %f %f %f -center 0.0 0.0 0.0' % (
        editconf, system_g96_filename, system_g96_filename, box_size[0] / 10.0,
        box_size[1] / 10.0, box_size[2] / 10.0)
    print command
    output = commands.getoutput(command)
    print output

    # set up perturbation
    print "Modifying topology file for perturbation..."
    system_top_filename = os.path.join(vacuum_path, 'system.top')
    lines = read_file(system_top_filename)
    perturb_atom_indices = list()
    indices = extract_section(lines, 'atoms')
    nions = 0
    for index in indices:
        # extract the line
        line = stripcomments(lines[index])
        # parse the line
        elements = line.split()
        nelements = len(elements)
        # skip if not all elements found
        if (nelements < 8): continue
        # parse line
        atom = dict()
        atom['nr'] = int(elements[0])
        atom['type'] = elements[1]
        atom['resnr'] = int(elements[2])
        atom['residue'] = elements[3]
        atom['atom'] = elements[4]
        atom['cgnr'] = int(elements[5])
        atom['charge'] = float(elements[6])
        atom['mass'] = float(elements[7])
        # DEBUG
        # add those atoms in the solute to our list
        if atom['residue'] == 'MOL':
            perturb_atom_indices.append(atom['nr'])
        # add counterions to balance solute
        if ((atom['residue'] == 'Na+') or
            (atom['residue'] == 'Cl-')) and (nions < abs(solute_charge)):
            print "WARNING: Solute has net charge of %(solute_charge)d -- will perturb counterions too." % vars(
            )
            perturb_atom_indices.append(atom['nr'])
            nions += 1
    perturbGromacsTopology(system_top_filename,
                           solute,
                           perturb_torsions=True,
                           perturb_vdw=True,
                           perturb_charges=True,
                           perturb_atom_indices=perturb_atom_indices)

    # construct replicates
    for replicate in range(nreplicates):
        # Create replicate path.
        working_path = os.path.join(vacuum_path, '%d' % replicate)
        os.makedirs(working_path)

        # TODO: Modify solute coordinates in system.g96 to cycle through available conformations.

        # construct .mdp files
        print "Writing mdp files for replicate %d..." % replicate

        mdpfile = MdpFile(
            compose_blocks([
                'header', 'minimization', 'nonbonded-vacuum', 'constraints',
                'output'
            ]))
        cutoff = box_size.min() / 10.0 / 2.0 - 0.0001  # compute cutoff
        mdpfile.setParameter('rlist', '%f' % cutoff)
        mdpfile.setParameter('rvdw', '%f' % cutoff)
        mdpfile.setParameter('rcoulomb', '%f' % cutoff)
        mdpfile.write(os.path.join(working_path, 'minimize.mdp'))

        mdpfile = MdpFile(
            compose_blocks([
                'header', 'dynamics', 'nonbonded-vacuum', 'constraints',
                'thermostat', 'free-energy', 'output'
            ]))
        mdpfile.randomizeSeed()  # randomize velocities
        mdpfile.setParameter('rlist', '%f' % cutoff)
        mdpfile.setParameter('rvdw', '%f' % cutoff)
        mdpfile.setParameter('rcoulomb', '%f' % cutoff)
        mdpfile.write(os.path.join(working_path, 'production.mdp'))

        # write shell script for minimization and production
        contents = """\
#!/bin/tcsh
#BSUB -J %(jobname)s_vacuum
#BSUB -o %(working_path)s/outfile.%%J
#BSUB -e %(working_path)s/errfile.%%J
#BSUB -n 1
#BSUB -M 2000000
#BSUB -W 168:0

source $GMXRC
cd %(working_path)s

# constrained minimize
%(grompp)s -f minimize.mdp -c ../system.g96 -p ../system.top -o minimize.tpr -maxwarn 10000 -n ../system.ndx
%(mdrun)s -s minimize.tpr -x minimize.xtc -c minimize.g96 -e minimize.edr -g minimize.log

# production
%(grompp)s -f production.mdp -c minimize.g96 -p ../system.top -o production.tpr -maxwarn 10000 -n ../system.ndx
%(mdrun)s -s production.tpr -o production.trr -x production.xtc -c production.g96 -e production.edr -g production.log

# signal completion
mv run.sh run.sh.done
""" % vars()
        write_file(os.path.join(working_path, 'run.sh'), contents)

    return
Exemple #3
0
def setup_system(protein_pdb_filename, ligand_filename, work_path, parameter_path, GMXRC, jobname):
    """Set up a system for alchemical free energy calculation in gromacs.

    ARGUMENTS
      protein_pdb_filename (string) - name of ffamber-named protein PDB file
      ligand_filename (string) - name of mol2 file describing ligand
      work_path (string) - name of directory to place files in

    """

    # get current directory
    current_path = os.getcwd()

    # create the work directory if it doesn't exist
    if not os.path.exists(work_path):
        os.makedirs(work_path)

    mdrun = globals()['mdrun']
    grompp = globals()['grompp']

    # SET UP PROTEIN TOPOLOGY

    print "\nCONSTRUCTING PROTEIN TOPOLOGY"

    # convert protein PDB file to AMBER naming conventions, dropping atoms that AMBER won't recognize (like protons)
    print "Converting PDB naming to AMBER..."
    protein_amberpdb_filename = os.path.join(work_path, 'protein-ambernames.pdb')
    rename_pdb_for_amber(protein_pdb_filename, protein_amberpdb_filename)

    # run leap to set up protein and report on net charge
    print "Running LEaP to set up protein..."

    protein_prmtop_filename = os.path.join(work_path,'protein.prmtop')
    protein_crd_filename = os.path.join(work_path,'protein.crd')
    protein_off_filename = os.path.join(work_path, 'protein.off')

    tleap_input_filename = os.path.join(work_path, 'setup-protein.leap.in')
    tleap_output_filename = os.path.join(work_path, 'setup-protein.leap.out')

    contents = """
# Load AMBER ff99 parameters.
source leaprc.ff99

# Load phosphoresidue parameters.
loadoff %(parameter_path)s/T1P.off
loadoff %(parameter_path)s/T2P.off
loadoff %(parameter_path)s/Y1P.off
loadoff %(parameter_path)s/Y2P.off
loadamberparams %(parameter_path)s/frcmod_t1p
loadamberparams %(parameter_path)s/frcmod_t2p
loadamberparams %(parameter_path)s/frcmod_y1p
loadamberparams %(parameter_path)s/frcmod_y2p

# Load PDB file with AMBER naming conventions, stripped of hydrogens.
protein = loadpdb %(protein_amberpdb_filename)s

# check the system
check protein

# report net charge
charge protein

# write out parameters
saveAmberParm protein %(protein_prmtop_filename)s %(protein_crd_filename)s

# write as LEaP object
saveOff protein %(protein_off_filename)s

# exit
quit
""" % vars()
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars()
    output = commands.getoutput(command)

    # extract total charge
    protein_charge = commands.getoutput('grep "Total unperturbed charge" %(tleap_output_filename)s | cut -c 27-' % vars())
    protein_charge = int(round(float(protein_charge))) # round to nearest whole charge
    print protein_charge

    # SET UP LIGAND TOPOLOGY

    print "\nCONSTRUCTING LIGAND TOPOLOGY"

    # Read the ligand into OEMol.
    print "Reading ligand..."
    ligand = readMolecule(ligand_filename, normalize = True)

    # Get formal charge of ligand.
    ligand_charge = formalCharge(ligand)
    print "formal charge is %d" % ligand_charge
    
    # TODO: Choose most appropriate protonation and tautomeric state.
    
    # Write molecule with explicit hydrogens to mol2 file.
    print "Writing ligand mol2 file..."
    ligand_mol2_filename = os.path.abspath(os.path.join(work_path, 'ligand.mol2'))
    writeMolecule(ligand, ligand_mol2_filename)

    # Set substructure name (which will become residue name).
    print "Modifying molecule name..."
    modifySubstructureName(ligand_mol2_filename, 'MOL')

    # Run antechamber to assign GAFF atom types.
    print "Running antechamber..."
    os.chdir(work_path)
    gaff_mol2_filename = os.path.join(work_path, 'ligand.gaff.mol2')
    charge_model = 'bcc'
    command = 'antechamber -i %(ligand_mol2_filename)s -fi mol2 -o ligand.gaff.mol2 -fo mol2 -c %(charge_model)s -nc %(ligand_charge)d > antechamber.out' % vars()
    print command
    output = commands.getoutput(command)    
    os.chdir(current_path)

    # Generate frcmod file for additional GAFF parameters.
    ligand_frcmod_filename = os.path.join(work_path, 'frcmod.ligand')
    output = commands.getoutput('parmchk -i %(gaff_mol2_filename)s -f mol2 -o %(ligand_frcmod_filename)s' % vars())
    print output

    # Run LEaP to generate topology / coordinates.
    ligand_prmtop_filename = os.path.join(work_path,'ligand.prmtop')
    ligand_crd_filename = os.path.join(work_path,'ligand.crd')
    ligand_off_filename = os.path.join(work_path, 'ligand.off')
    
    tleap_input_filename = os.path.join(work_path, 'setup-ligand.leap.in')
    tleap_output_filename = os.path.join(work_path, 'setup-ligand.leap.out')
    contents = """
# Load GAFF parameters.
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(ligand_frcmod_filename)s

# load ligand
ligand = loadMol2 %(gaff_mol2_filename)s

# check the ligand
check ligand

# report net charge
charge ligand

# save AMBER parameters
saveAmberParm ligand %(ligand_prmtop_filename)s %(ligand_crd_filename)s

# write .off file
saveOff ligand %(ligand_off_filename)s

# exit
quit
""" % vars()
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars()
    output = commands.getoutput(command)

    # extract total charge
    ligand_charge = commands.getoutput('grep "Total unperturbed charge" %(tleap_output_filename)s | cut -c 27-' % vars())
    ligand_charge = int(round(float(ligand_charge))) # round to nearest whole charge
    print "ligand charge is %d" % ligand_charge    

    # SET UP VACUUM SIMULATION

    # construct pathname for vacuum simulations
    vacuum_path = os.path.join(work_path, 'vacuum')
    if not os.path.exists(vacuum_path):
        os.makedirs(vacuum_path)

    # solvate the ligand
    print "Preparing vacuum ligand with tleap..."
    system_prmtop_filename = os.path.join(vacuum_path,'system.prmtop')
    system_crd_filename = os.path.join(vacuum_path,'system.crd')
    tleap_input_filename = os.path.join(vacuum_path, 'setup-system.leap.in')
    tleap_output_filename = os.path.join(vacuum_path, 'setup-system.leap.out')
    clearance = 50.0 # clearance in A
    contents = """
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(ligand_frcmod_filename)s

# Load ligand.
loadOff %(ligand_off_filename)s

# Create system.
system = combine { ligand }
""" % vars()
    # add counterions
    if (ligand_charge != 0):
        nions = abs(ligand_charge)
        if ligand_charge < 0: iontype = 'Na+'
        if ligand_charge > 0: iontype = 'Cl-'
        contents += """
# Add counterions.
addions system %(iontype)s %(nions)d
""" % vars()
    #
    contents += """

# Create big box.
setBox system centers %(clearance)f

# Check the system
check system

# Write the system
saveamberparm system %(system_prmtop_filename)s %(system_crd_filename)s
""" % vars()    
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars()
    output = commands.getoutput(command)    

    # convert to gromacs
    print "Converting to gromacs..."
    amb2gmx = os.path.join(os.getenv('MMTOOLSPATH'), 'converters', 'amb2gmx.pl')
    system_prefix = os.path.join(vacuum_path, 'system')
    command = '%(amb2gmx)s --prmtop %(system_prmtop_filename)s --crd %(system_crd_filename)s --outname %(system_prefix)s' % vars()
    print command
    output = commands.getoutput(command)
    print output

    # write enlarged box for solvent because LEaP doesn't do it right.
    system_g96_filename = os.path.join(vacuum_path, 'system.g96')
    command = '%s -f %s -o %s -bt octahedron -d %f -center 0.0 0.0 0.0' % (editconf, system_g96_filename, system_g96_filename, clearance)
    print command
    output = commands.getoutput(command)
    print output

    # set up perturbation
    print "Modifying topology file for perturbation..."
    system_top_filename = os.path.join(vacuum_path, 'system.top')
    lines = read_file(system_top_filename)
    perturb_atom_indices = list()
    indices = extract_section(lines, 'atoms')
    nions = 0
    for index in indices:
        # extract the line
        line = stripcomments(lines[index])
        # parse the line
        elements = line.split()
        nelements = len(elements)
        # skip if not all elements found
        if (nelements < 8): continue
        # parse line
        atom = dict()
        atom['nr'] = int(elements[0])
        atom['type'] = elements[1]
        atom['resnr'] = int(elements[2])
        atom['residue'] = elements[3]
        atom['atom'] = elements[4]
        atom['cgnr'] = int(elements[5])
        atom['charge'] = float(elements[6])
        atom['mass'] = float(elements[7])
        # add those atoms in the ligand to our list
        if atom['residue'] == 'MOL':
            perturb_atom_indices.append(atom['nr'])
        # add counterions to balance ligand
        if ((atom['residue'] == 'Na+') or (atom['residue'] == 'Cl-')) and (nions < abs(ligand_charge)):
            perturb_atom_indices.append(atom['nr'])
            nions += 1            
    perturbGromacsTopology(system_top_filename, ligand, perturb_torsions = True, perturb_vdw = True, perturb_charges = True, perturb_atom_indices = perturb_atom_indices)

    # construct .mdp files
    mdpfile = MdpFile(compose_blocks(['header', 'minimization', 'nonbonded-vacuum', 'constraints', 'output']))
    mdpfile.write(os.path.join(vacuum_path, 'minimize.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-vacuum', 'constraints', 'thermostat', 'free-energy', 'output']))
    mdpfile.randomizeSeed() # randomize velocities
    mdpfile.write(os.path.join(vacuum_path, 'production.mdp'))
    vacuum_path = os.path.abspath(vacuum_path)

    # write shell script for minimization and production
    contents = """\
#!/bin/tcsh
#BSUB -J %(jobname)s_vacuum
#BSUB -o %(vacuum_path)s/outfile.%%J
#BSUB -e %(vacuum_path)s/errfile.%%J
#BSUB -n 1
#BSUB -M 2000000
#BSUB -W 168:0

source %(GMXRC)s
cd %(vacuum_path)s
setenv RANSEED 1234

# constrained minimize
%(grompp)s -f minimize.mdp -c system.g96 -p system.top -o minimize.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s minimize.tpr -x minimize.xtc -c minimize.g96 -e minimize.edr -g minimize.log

# production
%(grompp)s -f production.mdp -c minimize.g96 -p system.top -o production.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s production.tpr -o production.trr -x production.xtc -c production.g96 -e production.edr -g production.log

# signal completion
mv run.sh run.sh.done
""" % vars()    
    write_file(os.path.join(vacuum_path, 'run.sh'), contents)

    # PREPARE SOLVATED LIGAND

    print "\nPREPARING SOLVATED LIGAND"

    # create the directory if it doesn't exist
    solvent_path = os.path.join(work_path, 'solvent')
    if not os.path.exists(solvent_path):
        os.makedirs(solvent_path)

    # solvate the ligand
    print "Solvating the ligand with tleap..."
    system_prmtop_filename = os.path.join(solvent_path,'system.prmtop')
    system_crd_filename = os.path.join(solvent_path,'system.crd')
    tleap_input_filename = os.path.join(solvent_path, 'setup-system.leap.in')
    tleap_output_filename = os.path.join(solvent_path, 'setup-system.leap.out')
    clearance = 10.0 # clearance in A
    contents = """
source leaprc.ff99
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(ligand_frcmod_filename)s

# Load ligand.
loadOff %(ligand_off_filename)s

# Create system.
system = combine { ligand }
""" % vars()
    # add counterions
    if (ligand_charge != 0):
        nions = abs(ligand_charge)
        if ligand_charge < 0: iontype = 'Na+'
        if ligand_charge > 0: iontype = 'Cl-'
        contents += """
# Add counterions.
addions system %(iontype)s %(nions)d
""" % vars()
    #
    contents += """
# Solvate in truncated octahedral box.
solvateOct system TIP3PBOX %(clearance)f

# Check the system
check system

# Write the system
saveamberparm system %(system_prmtop_filename)s %(system_crd_filename)s
""" % vars()    
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars()
    output = commands.getoutput(command)    

    # convert to gromacs
    print "Converting to gromacs..."
    amb2gmx = os.path.join(os.getenv('MMTOOLSPATH'), 'converters', 'amb2gmx.pl')
    system_prefix = os.path.join(solvent_path, 'system')
    command = '%(amb2gmx)s --prmtop %(system_prmtop_filename)s --crd %(system_crd_filename)s --outname %(system_prefix)s' % vars()
    print command
    output = commands.getoutput(command)
    print output

    # set up perturbation
    print "Modifying topology file for perturbation..."
    system_top_filename = os.path.join(solvent_path, 'system.top')
    lines = read_file(system_top_filename)
    perturb_atom_indices = list()
    indices = extract_section(lines, 'atoms')
    for index in indices:
        # extract the line
        line = stripcomments(lines[index])
        # parse the line
        elements = line.split()
        nelements = len(elements)
        # skip if not all elements found
        if (nelements < 8): continue
        # parse line
        atom = dict()
        atom['nr'] = int(elements[0])
        atom['type'] = elements[1]
        atom['resnr'] = int(elements[2])
        atom['residue'] = elements[3]
        atom['atom'] = elements[4]
        atom['cgnr'] = int(elements[5])
        atom['charge'] = float(elements[6])
        atom['mass'] = float(elements[7])
        # add those atoms in the ligand to our list
        if atom['residue'] == 'MOL':
            perturb_atom_indices.append(atom['nr'])    
    perturbGromacsTopology(system_top_filename, ligand, perturb_torsions = True, perturb_vdw = True, perturb_charges = True, perturb_atom_indices = perturb_atom_indices)

    # set up mdp files
    print "Writing mdp files..."
    mdpfile = MdpFile(compose_blocks(['header', 'minimization', 'nonbonded-solvent', 'constraints', 'output']))
    mdpfile.write(os.path.join(solvent_path, 'minimize.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-solvent', 'constraints', 'thermostat', 'barostat', 'output']))
    mdpfile.setParameter('nsteps', '10000') # 20 ps equilibration
    mdpfile.randomizeSeed() # randomize velocities
    mdpfile.write(os.path.join(solvent_path, 'equilibration.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-solvent', 'constraints', 'thermostat', 'barostat', 'free-energy', 'output']))
    mdpfile.randomizeSeed() # randomize velocities
    mdpfile.write(os.path.join(solvent_path, 'production.mdp'))

    # write run script
    print "Writing run script..."
    solvent_path = os.path.abspath(solvent_path)
    contents = """\
#!/bin/tcsh
#BSUB -J %(jobname)s_solvent
#BSUB -o %(solvent_path)s/outfile.%%J
#BSUB -e %(solvent_path)s/errfile.%%J
#BSUB -n 1
#BSUB -M 2000000
#BSUB -W 168:0

source %(GMXRC)s
cd %(solvent_path)s
setenv RANSEED 1234

# constrained minimize
%(grompp)s -f minimize.mdp -c system.g96 -p system.top -o minimize.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s minimize.tpr -x minimize.xtc -c minimize.g96 -e minimize.edr -g minimize.log

# equilibration
%(grompp)s -f equilibration.mdp -c minimize.g96 -p system.top -o equilibration.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s equilibration.tpr -o equilibration.trr -x equilibration.xtc -c equilibration.g96 -e equilibration.edr -g equilibration.log

# production
%(grompp)s -f production.mdp -c equilibration.g96 -p system.top -o production.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s production.tpr -o production.trr -x production.xtc -c production.g96 -e production.edr -g production.log

# signal completion
mv run.sh run.sh.done
""" % vars()
    write_file(os.path.join(solvent_path, 'run.sh'), contents)

    # PREPARE SOLVATED COMPLEX

    print "\nPREPARING SOLVATED COMPLEX"

    # create the directory if it doesn't exist
    complex_path = os.path.join(work_path, 'complex')
    if not os.path.exists(complex_path):
        os.makedirs(complex_path)

    # solvate the ligand
    print "Solvating the complex with tleap..."
    system_prmtop_filename = os.path.join(complex_path,'system.prmtop')
    system_crd_filename = os.path.join(complex_path,'system.crd')
    tleap_input_filename = os.path.join(complex_path, 'setup-system.leap.in')
    tleap_output_filename = os.path.join(complex_path, 'setup-system.leap.out')
    clearance = 10.0 # clearance in A
    contents = """
source leaprc.ff99
source leaprc.gaff
loadamberparams %(parameter_path)s/frcmod_t1p
loadamberparams %(parameter_path)s/frcmod_t2p
loadamberparams %(parameter_path)s/frcmod_y1p
loadamberparams %(parameter_path)s/frcmod_y2p

# load antechamber-generated additional parameters
mods = loadAmberParams %(ligand_frcmod_filename)s

# Load protein.
loadOff %(protein_off_filename)s

# Load ligand.
loadOff %(ligand_off_filename)s

# Create system.
system = combine { protein ligand }
""" % vars()
    # add counterions for ligand
    if (ligand_charge != 0):
        nions = abs(ligand_charge)
        if ligand_charge < 0: iontype = 'Na+'
        if ligand_charge > 0: iontype = 'Cl-'
        contents += """
# Add counterions for ligand (will be annihilated with ligand).
addions system %(iontype)s %(nions)d
""" % vars()
    #
    # add counterions for protein
    if (protein_charge != 0):
        nions = abs(protein_charge)
        if protein_charge < 0: iontype = 'Na+'
        if protein_charge > 0: iontype = 'Cl-'
        contents += """
# Add counterions for protein.
addions system %(iontype)s %(nions)d
""" % vars()
    #
    contents += """
# Solvate in truncated octahedral box.
solvateOct system TIP3PBOX %(clearance)f

# Check the system
check system

# Write the system
saveamberparm system %(system_prmtop_filename)s %(system_crd_filename)s
""" % vars()    
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars()
    output = commands.getoutput(command)    

    # convert to gromacs
    print "Converting to gromacs..."
    amb2gmx = os.path.join(os.getenv('MMTOOLSPATH'), 'converters', 'amb2gmx.pl')
    system_prefix = os.path.join(complex_path, 'system')
    command = '%(amb2gmx)s --prmtop %(system_prmtop_filename)s --crd %(system_crd_filename)s --outname %(system_prefix)s' % vars()
    print command
    output = commands.getoutput(command)
    print output

    # set up perturbation
    print "Modifying topology file for perturbation..."
    system_top_filename = os.path.join(complex_path, 'system.top')
    lines = read_file(system_top_filename)
    perturb_atom_indices = list()
    indices = extract_section(lines, 'atoms')
    nions = 0
    for index in indices:
        # extract the line
        line = stripcomments(lines[index])
        # parse the line
        elements = line.split()
        nelements = len(elements)
        # skip if not all elements found
        if (nelements < 8): continue
        # parse line
        atom = dict()
        atom['nr'] = int(elements[0])
        atom['type'] = elements[1]
        atom['resnr'] = int(elements[2])
        atom['residue'] = elements[3]
        atom['atom'] = elements[4]
        atom['cgnr'] = int(elements[5])
        atom['charge'] = float(elements[6])
        atom['mass'] = float(elements[7])
        # add those atoms in the ligand to our list
        if atom['residue'] == 'MOL':
            perturb_atom_indices.append(atom['nr'])
        # add counterions to balance ligand
        if ((atom['residue'] == 'Na+') or (atom['residue'] == 'Cl-')) and (nions < abs(ligand_charge)):
            perturb_atom_indices.append(atom['nr'])
            nions += 1
            
    perturbGromacsTopology(system_top_filename, ligand, perturb_torsions = True, perturb_vdw = True, perturb_charges = True, perturb_atom_indices = perturb_atom_indices)

    # set up mdp files
    print "Writing mdp files..."
    mdpfile = MdpFile(compose_blocks(['header', 'minimization', 'nonbonded-solvent', 'constraints', 'restraints', 'output']))
    mdpfile.write(os.path.join(complex_path, 'minimize.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-solvent', 'constraints', 'thermostat', 'barostat', 'output']))
    mdpfile.setParameter('nsteps', '10000') # 20 ps equilibration
    mdpfile.randomizeSeed() # randomize velocities
    mdpfile.write(os.path.join(complex_path, 'equilibration.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-solvent', 'constraints', 'restraints', 'thermostat', 'barostat', 'free-energy', 'output']))
    mdpfile.randomizeSeed() # randomize velocities
    mdpfile.write(os.path.join(complex_path, 'production.mdp'))    

    # Copy restraint scripts to run directory
    shutil.copy(compute_angles,complex_path)
    shutil.copy(restraint_topology,complex_path)
    # write run script
    print "Writing run script..."
    complex_path = os.path.abspath(complex_path)
    contents = """\
#!/bin/tcsh
#BSUB -J %(jobname)s_complex
#BSUB -o %(complex_path)s/outfile.%%J
#BSUB -e %(complex_path)s/errfile.%%J
#BSUB -n 1
#BSUB -M 2000000
#BSUB -W 168:0

source %(GMXRC)s
cd %(complex_path)s
setenv RANSEED 1234

# create a tpr file from which to create restraints    
%(grompp)s -f minimize.mdp -c system.g96 -p system.top -o minimize.tpr -maxwarn 10000 -n system.ndx
# Integrate restraints into pre-minimization topfile
python computeangles.py -f system.g96 -s minimize.tpr -d $RANSEED
python restraint_topology.py -n system -p .

# constrained minimize with restraints
%(grompp)s -f minimize.mdp -c system_restr.g96 -p system_restr.top -o minimize.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s minimize.tpr -x minimize.xtc -c minimize.g96 -e minimize.edr -g minimize.log

# equilibration with restraints
%(grompp)s -f equilibration.mdp -c minimize.g96 -p system.top -o equilibration.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s equilibration.tpr -o equilibration.trr -x equilibration.xtc -c equilibration.g96 -e equilibration.edr -g equilibration.log

# create a TPR file, integrate restraints
%(grompp)s -f production.mdp -c equilibration.g96 -p system_restr.top -o production.tpr -maxwarn 10000 -n system.ndx
cp system_restr.top minimize.top
python computeangles.py -f equilibration.g96 -s production.tpr -d $RANSEED
python restraint_topology.py -n equilibration -p .

# production with restraints
%(grompp)s -f production.mdp -c equilibration_restr.g96 -p minimize_restr.top -o production.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s production.tpr -o production.trr -x production.xtc -c production.g96 -e production.edr -g production.log

# signal completion
mv run.sh run.sh.done
""" % vars()
    write_file(os.path.join(complex_path, 'run.sh'), contents)

    return
def setup_complex_simulation(complex_path, jobname, ligand, ligand_off_filename, ligand_frcmod_filename, protein_off_filename, protein_charge, common_substructure):
    """Set up free energy calculation for ligand in complex.

    ARGUMENTS
      complex_path (string) - the pathname where the simulation is to be set up (created if doesn't exist).
      jobname (string) - job name to be used to form batch queue job name
      ligand (OEMol) - the ligand
      ligand_off_filename (string) - leap library for ligand
      ligand_frcmod_filename (string) - additional ligand parameters
      common_substructure (OEMol) - 
    """

    print "\nPREPARING SOLVATED COMPLEX"

    # create path if it doesn't exist
    if not os.path.exists(complex_path):
        os.makedirs(complex_path)

    # get ligand formal charge
    ligand_charge = formalCharge(ligand)

    # set up the system in tLEaP
    print "Solvating the complex with tleap..."
    system_prmtop_filename = os.path.join(complex_path,'system.prmtop')
    system_crd_filename = os.path.join(complex_path,'system.crd')
    tleap_input_filename = os.path.join(complex_path, 'setup-system.leap.in')
    tleap_output_filename = os.path.join(complex_path, 'setup-system.leap.out')
    clearance = 10.0 # clearance in A to add around protein
    contents = """
source leaprc.ff03
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(ligand_frcmod_filename)s

# Load protein.
loadOff %(protein_off_filename)s

# Load ligand.
loadOff %(ligand_off_filename)s

# Create system.
system = combine { protein ligand }
""" % vars()
    # add counterions for ligand
    if (ligand_charge != 0):
        nions = abs(ligand_charge)
        if ligand_charge < 0: iontype = 'Na+'
        if ligand_charge > 0: iontype = 'Cl-'
        contents += """
# Add counterions for ligand (will be annihilated with ligand).
addions system %(iontype)s %(nions)d
""" % vars()
    #
    # add counterions for protein
    if (protein_charge != 0):
        nions = abs(protein_charge)
        if protein_charge < 0: iontype = 'Na+'
        if protein_charge > 0: iontype = 'Cl-'
        contents += """
# Add counterions for protein.
addions system %(iontype)s %(nions)d
""" % vars()
    #
    contents += """
# Solvate in truncated octahedral box.
# solvateOct system TIP3PBOX %(clearance)f
# solvate in rectilinear box
solvateBox system TIP3PBOX %(clearance)f

# Check the system
check system

# Write the system
saveamberparm system %(system_prmtop_filename)s %(system_crd_filename)s
""" % vars()
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars()
    output = commands.getoutput(command)

    # convert to gromacs
    print "Converting to gromacs..."
    amb2gmx = os.path.join(os.getenv('MMTOOLSPATH'), 'converters', 'amb2gmx.pl')
    system_prefix = os.path.join(complex_path, 'system')
    current_path = os.getcwd()
    os.chdir(complex_path)
    #command = '%(amb2gmx)s --prmtop %(system_prmtop_filename)s --crd %(system_crd_filename)s --outname %(system_prefix)s' % vars()
    command = '%(amb2gmx)s --prmtop system.prmtop --crd system.crd --outname system' % vars()
    print command
    output = commands.getoutput(command)
    print output
    os.chdir(current_path)

    # set up perturbation
    print "Modifying topology file for perturbation..."
    system_top_filename = os.path.join(complex_path, 'system.top')
    perturbGromacsTopologyToIntermediate(system_top_filename, 'MOL', ligand, common_substructure, perturb_torsions = True)

    # set up mdp files
    print "Writing mdp files..."
    mdpfile = MdpFile(compose_blocks(['header', 'minimizationMZ', 'nonbonded-solvent', 'constraints', 'restraints', 'output']))
    mdpfile.write(os.path.join(complex_path, 'minimize.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-solvent', 'constraints', 'thermostat', 'barostat', 'output']))
    mdpfile.setParameter('nsteps', '10000') # 20 ps equilibration
    mdpfile.randomizeSeed() # randomize velocities
    mdpfile.write(os.path.join(complex_path, 'equilibration.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-solvent', 'constraints', 'restraints', 'thermostat', 'barostat', 'free-energy', 'output']))
    mdpfile.randomizeSeed() # randomize velocities
    mdpfile.write(os.path.join(complex_path, 'production.mdp'))    

    # Copy restraint scripts to run directory
    shutil.copy(os.path.join(script_basepath,'compute_angles.py'),complex_path)
    shutil.copy(os.path.join(script_basepath,'restraint_topology.py'),complex_path)
    # write batch queue script
    print "Writing batch queue script..."
    complex_path = os.path.abspath(complex_path)
    contents = """\
#!/bin/tcsh
#BSUB -J %(jobname)s_complex
#BSUB -n 1
#BSUB -M 2000000

source $GMXRC

# create a tpr file from which to create restraints    
grompp -f minimize.mdp -c system.g96 -p system.top -o minimize.tpr -maxwarn 10000 -n system.ndx
# Integrate restraints into pre-minimization topfile
python compute_angles.py -f system.g96 -s minimize.tpr -d $RANSEED
python restraint_topology.py -n system -p .

# constrained minimize with restraints
grompp -f minimize.mdp -c system_restr.g96 -p system_restr.top -o minimize.tpr -maxwarn 10000 -n system.ndx
mdrun -s minimize.tpr -x minimize.xtc -c minimize.g96 -e minimize.edr -g minimize.log

# create a tpr file from which to create restraints    
grompp -f equilibration.mdp -c minimize.g96 -p system_restr.top -o equilibration.tpr -maxwarn 10000 -n system.ndx
# Integrate restraints into pre-equilibration topfile
cp system_restr.top minimize.top
#python compute_angles.py -f minimize.g96 -s equilibration.tpr -d $RANSEED
python restraint_topology.py -n minimize -p .

# equilibration with restraints
grompp -f equilibration.mdp -c minimize_restr.g96 -p minimize_restr.top -o equilibration.tpr -maxwarn 10000 -n system.ndx
mdrun -s equilibration.tpr -o equilibration.trr -x equilibration.xtc -c equilibration.g96 -e equilibration.edr -g equilibration.log

# create a TPR file, integrate restraints
grompp -f production.mdp -c equilibration.g96 -p minimize_restr.top -o production.tpr -maxwarn 10000 -n system.ndx
cp minimize_restr.top equilibration.top
#python compute_angles.py -f equilibration.g96 -s production.tpr -d $RANSEED
python restraint_topology.py -n equilibration -p .

# production with restraints
grompp -f production.mdp -c equilibration_restr.g96 -p equilibration_restr.top -o production.tpr -maxwarn 10000 -n system.ndx
mdrun -s production.tpr -o production.trr -x production.xtc -c production.g96 -e production.edr -g production.log

# signal completion
mv run.sh run.sh.done
""" % vars()
    write_file(os.path.join(complex_path, 'run.sh'), contents)

    return
def setup_system(protein_pdb_filename, ligand_filename, work_path,
                 parameter_path, GMXRC, jobname):
    """Set up a system for alchemical free energy calculation in gromacs.

    ARGUMENTS
      protein_pdb_filename (string) - name of ffamber-named protein PDB file
      ligand_filename (string) - name of mol2 file describing ligand
      work_path (string) - name of directory to place files in

    """

    # get current directory
    current_path = os.getcwd()

    # create the work directory if it doesn't exist
    if not os.path.exists(work_path):
        os.makedirs(work_path)

    mdrun = globals()['mdrun']
    grompp = globals()['grompp']

    # SET UP PROTEIN TOPOLOGY

    print "\nCONSTRUCTING PROTEIN TOPOLOGY"

    # convert protein PDB file to AMBER naming conventions, dropping atoms that AMBER won't recognize (like protons)
    print "Converting PDB naming to AMBER..."
    protein_amberpdb_filename = os.path.join(work_path,
                                             'protein-ambernames.pdb')
    rename_pdb_for_amber(protein_pdb_filename, protein_amberpdb_filename)

    # run leap to set up protein and report on net charge
    print "Running LEaP to set up protein..."

    protein_prmtop_filename = os.path.join(work_path, 'protein.prmtop')
    protein_crd_filename = os.path.join(work_path, 'protein.crd')
    protein_off_filename = os.path.join(work_path, 'protein.off')

    tleap_input_filename = os.path.join(work_path, 'setup-protein.leap.in')
    tleap_output_filename = os.path.join(work_path, 'setup-protein.leap.out')

    contents = """
# Load AMBER ff99 parameters.
source leaprc.ff99

# Load phosphoresidue parameters.
loadoff %(parameter_path)s/T1P.off
loadoff %(parameter_path)s/T2P.off
loadoff %(parameter_path)s/Y1P.off
loadoff %(parameter_path)s/Y2P.off
loadamberparams %(parameter_path)s/frcmod_t1p
loadamberparams %(parameter_path)s/frcmod_t2p
loadamberparams %(parameter_path)s/frcmod_y1p
loadamberparams %(parameter_path)s/frcmod_y2p

# Load PDB file with AMBER naming conventions, stripped of hydrogens.
protein = loadpdb %(protein_amberpdb_filename)s

# check the system
check protein

# report net charge
charge protein

# write out parameters
saveAmberParm protein %(protein_prmtop_filename)s %(protein_crd_filename)s

# write as LEaP object
saveOff protein %(protein_off_filename)s

# exit
quit
""" % vars()
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars(
    )
    output = commands.getoutput(command)

    # extract total charge
    protein_charge = commands.getoutput(
        'grep "Total unperturbed charge" %(tleap_output_filename)s | cut -c 27-'
        % vars())
    protein_charge = int(round(
        float(protein_charge)))  # round to nearest whole charge
    print protein_charge

    # SET UP LIGAND TOPOLOGY

    print "\nCONSTRUCTING LIGAND TOPOLOGY"

    # Read the ligand into OEMol.
    print "Reading ligand..."
    ligand = readMolecule(ligand_filename, normalize=True)

    # Get formal charge of ligand.
    ligand_charge = formalCharge(ligand)
    print "formal charge is %d" % ligand_charge

    # TODO: Choose most appropriate protonation and tautomeric state.

    # Write molecule with explicit hydrogens to mol2 file.
    print "Writing ligand mol2 file..."
    ligand_mol2_filename = os.path.abspath(
        os.path.join(work_path, 'ligand.mol2'))
    writeMolecule(ligand, ligand_mol2_filename)

    # Set substructure name (which will become residue name).
    print "Modifying molecule name..."
    modifySubstructureName(ligand_mol2_filename, 'MOL')

    # Run antechamber to assign GAFF atom types.
    print "Running antechamber..."
    os.chdir(work_path)
    gaff_mol2_filename = os.path.join(work_path, 'ligand.gaff.mol2')
    charge_model = 'bcc'
    command = 'antechamber -i %(ligand_mol2_filename)s -fi mol2 -o ligand.gaff.mol2 -fo mol2 -c %(charge_model)s -nc %(ligand_charge)d > antechamber.out' % vars(
    )
    print command
    output = commands.getoutput(command)
    os.chdir(current_path)

    # Generate frcmod file for additional GAFF parameters.
    ligand_frcmod_filename = os.path.join(work_path, 'frcmod.ligand')
    output = commands.getoutput(
        'parmchk -i %(gaff_mol2_filename)s -f mol2 -o %(ligand_frcmod_filename)s'
        % vars())
    print output

    # Run LEaP to generate topology / coordinates.
    ligand_prmtop_filename = os.path.join(work_path, 'ligand.prmtop')
    ligand_crd_filename = os.path.join(work_path, 'ligand.crd')
    ligand_off_filename = os.path.join(work_path, 'ligand.off')

    tleap_input_filename = os.path.join(work_path, 'setup-ligand.leap.in')
    tleap_output_filename = os.path.join(work_path, 'setup-ligand.leap.out')
    contents = """
# Load GAFF parameters.
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(ligand_frcmod_filename)s

# load ligand
ligand = loadMol2 %(gaff_mol2_filename)s

# check the ligand
check ligand

# report net charge
charge ligand

# save AMBER parameters
saveAmberParm ligand %(ligand_prmtop_filename)s %(ligand_crd_filename)s

# write .off file
saveOff ligand %(ligand_off_filename)s

# exit
quit
""" % vars()
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars(
    )
    output = commands.getoutput(command)

    # extract total charge
    ligand_charge = commands.getoutput(
        'grep "Total unperturbed charge" %(tleap_output_filename)s | cut -c 27-'
        % vars())
    ligand_charge = int(round(
        float(ligand_charge)))  # round to nearest whole charge
    print "ligand charge is %d" % ligand_charge

    # SET UP VACUUM SIMULATION

    # construct pathname for vacuum simulations
    vacuum_path = os.path.join(work_path, 'vacuum')
    if not os.path.exists(vacuum_path):
        os.makedirs(vacuum_path)

    # solvate the ligand
    print "Preparing vacuum ligand with tleap..."
    system_prmtop_filename = os.path.join(vacuum_path, 'system.prmtop')
    system_crd_filename = os.path.join(vacuum_path, 'system.crd')
    tleap_input_filename = os.path.join(vacuum_path, 'setup-system.leap.in')
    tleap_output_filename = os.path.join(vacuum_path, 'setup-system.leap.out')
    clearance = 50.0  # clearance in A
    contents = """
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(ligand_frcmod_filename)s

# Load ligand.
loadOff %(ligand_off_filename)s

# Create system.
system = combine { ligand }
""" % vars()
    # add counterions
    if (ligand_charge != 0):
        nions = abs(ligand_charge)
        if ligand_charge < 0: iontype = 'Na+'
        if ligand_charge > 0: iontype = 'Cl-'
        contents += """
# Add counterions.
addions system %(iontype)s %(nions)d
""" % vars()
    #
    contents += """

# Create big box.
setBox system centers %(clearance)f

# Check the system
check system

# Write the system
saveamberparm system %(system_prmtop_filename)s %(system_crd_filename)s
""" % vars()
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars(
    )
    output = commands.getoutput(command)

    # convert to gromacs
    print "Converting to gromacs..."
    amb2gmx = os.path.join(os.getenv('MMTOOLSPATH'), 'converters',
                           'amb2gmx.pl')
    system_prefix = os.path.join(vacuum_path, 'system')
    command = '%(amb2gmx)s --prmtop %(system_prmtop_filename)s --crd %(system_crd_filename)s --outname %(system_prefix)s' % vars(
    )
    print command
    output = commands.getoutput(command)
    print output

    # write enlarged box for solvent because LEaP doesn't do it right.
    system_g96_filename = os.path.join(vacuum_path, 'system.g96')
    command = '%s -f %s -o %s -bt octahedron -d %f -center 0.0 0.0 0.0' % (
        editconf, system_g96_filename, system_g96_filename, clearance)
    print command
    output = commands.getoutput(command)
    print output

    # set up perturbation
    print "Modifying topology file for perturbation..."
    system_top_filename = os.path.join(vacuum_path, 'system.top')
    lines = read_file(system_top_filename)
    perturb_atom_indices = list()
    indices = extract_section(lines, 'atoms')
    nions = 0
    for index in indices:
        # extract the line
        line = stripcomments(lines[index])
        # parse the line
        elements = line.split()
        nelements = len(elements)
        # skip if not all elements found
        if (nelements < 8): continue
        # parse line
        atom = dict()
        atom['nr'] = int(elements[0])
        atom['type'] = elements[1]
        atom['resnr'] = int(elements[2])
        atom['residue'] = elements[3]
        atom['atom'] = elements[4]
        atom['cgnr'] = int(elements[5])
        atom['charge'] = float(elements[6])
        atom['mass'] = float(elements[7])
        # add those atoms in the ligand to our list
        if atom['residue'] == 'MOL':
            perturb_atom_indices.append(atom['nr'])
        # add counterions to balance ligand
        if ((atom['residue'] == 'Na+') or
            (atom['residue'] == 'Cl-')) and (nions < abs(ligand_charge)):
            perturb_atom_indices.append(atom['nr'])
            nions += 1
    perturbGromacsTopology(system_top_filename,
                           ligand,
                           perturb_torsions=True,
                           perturb_vdw=True,
                           perturb_charges=True,
                           perturb_atom_indices=perturb_atom_indices)

    # construct .mdp files
    mdpfile = MdpFile(
        compose_blocks([
            'header', 'minimization', 'nonbonded-vacuum', 'constraints',
            'output'
        ]))
    mdpfile.write(os.path.join(vacuum_path, 'minimize.mdp'))

    mdpfile = MdpFile(
        compose_blocks([
            'header', 'dynamics', 'nonbonded-vacuum', 'constraints',
            'thermostat', 'free-energy', 'output'
        ]))
    mdpfile.randomizeSeed()  # randomize velocities
    mdpfile.write(os.path.join(vacuum_path, 'production.mdp'))
    vacuum_path = os.path.abspath(vacuum_path)

    # write shell script for minimization and production
    contents = """\
#!/bin/tcsh
#BSUB -J %(jobname)s_vacuum
#BSUB -o %(vacuum_path)s/outfile.%%J
#BSUB -e %(vacuum_path)s/errfile.%%J
#BSUB -n 1
#BSUB -M 2000000
#BSUB -W 168:0

source %(GMXRC)s
cd %(vacuum_path)s
setenv RANSEED 1234

# constrained minimize
%(grompp)s -f minimize.mdp -c system.g96 -p system.top -o minimize.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s minimize.tpr -x minimize.xtc -c minimize.g96 -e minimize.edr -g minimize.log

# production
%(grompp)s -f production.mdp -c minimize.g96 -p system.top -o production.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s production.tpr -o production.trr -x production.xtc -c production.g96 -e production.edr -g production.log

# signal completion
mv run.sh run.sh.done
""" % vars()
    write_file(os.path.join(vacuum_path, 'run.sh'), contents)

    # PREPARE SOLVATED LIGAND

    print "\nPREPARING SOLVATED LIGAND"

    # create the directory if it doesn't exist
    solvent_path = os.path.join(work_path, 'solvent')
    if not os.path.exists(solvent_path):
        os.makedirs(solvent_path)

    # solvate the ligand
    print "Solvating the ligand with tleap..."
    system_prmtop_filename = os.path.join(solvent_path, 'system.prmtop')
    system_crd_filename = os.path.join(solvent_path, 'system.crd')
    tleap_input_filename = os.path.join(solvent_path, 'setup-system.leap.in')
    tleap_output_filename = os.path.join(solvent_path, 'setup-system.leap.out')
    clearance = 10.0  # clearance in A
    contents = """
source leaprc.ff99
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(ligand_frcmod_filename)s

# Load ligand.
loadOff %(ligand_off_filename)s

# Create system.
system = combine { ligand }
""" % vars()
    # add counterions
    if (ligand_charge != 0):
        nions = abs(ligand_charge)
        if ligand_charge < 0: iontype = 'Na+'
        if ligand_charge > 0: iontype = 'Cl-'
        contents += """
# Add counterions.
addions system %(iontype)s %(nions)d
""" % vars()
    #
    contents += """
# Solvate in truncated octahedral box.
solvateOct system TIP3PBOX %(clearance)f

# Check the system
check system

# Write the system
saveamberparm system %(system_prmtop_filename)s %(system_crd_filename)s
""" % vars()
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars(
    )
    output = commands.getoutput(command)

    # convert to gromacs
    print "Converting to gromacs..."
    amb2gmx = os.path.join(os.getenv('MMTOOLSPATH'), 'converters',
                           'amb2gmx.pl')
    system_prefix = os.path.join(solvent_path, 'system')
    command = '%(amb2gmx)s --prmtop %(system_prmtop_filename)s --crd %(system_crd_filename)s --outname %(system_prefix)s' % vars(
    )
    print command
    output = commands.getoutput(command)
    print output

    # set up perturbation
    print "Modifying topology file for perturbation..."
    system_top_filename = os.path.join(solvent_path, 'system.top')
    lines = read_file(system_top_filename)
    perturb_atom_indices = list()
    indices = extract_section(lines, 'atoms')
    for index in indices:
        # extract the line
        line = stripcomments(lines[index])
        # parse the line
        elements = line.split()
        nelements = len(elements)
        # skip if not all elements found
        if (nelements < 8): continue
        # parse line
        atom = dict()
        atom['nr'] = int(elements[0])
        atom['type'] = elements[1]
        atom['resnr'] = int(elements[2])
        atom['residue'] = elements[3]
        atom['atom'] = elements[4]
        atom['cgnr'] = int(elements[5])
        atom['charge'] = float(elements[6])
        atom['mass'] = float(elements[7])
        # add those atoms in the ligand to our list
        if atom['residue'] == 'MOL':
            perturb_atom_indices.append(atom['nr'])
    perturbGromacsTopology(system_top_filename,
                           ligand,
                           perturb_torsions=True,
                           perturb_vdw=True,
                           perturb_charges=True,
                           perturb_atom_indices=perturb_atom_indices)

    # set up mdp files
    print "Writing mdp files..."
    mdpfile = MdpFile(
        compose_blocks([
            'header', 'minimization', 'nonbonded-solvent', 'constraints',
            'output'
        ]))
    mdpfile.write(os.path.join(solvent_path, 'minimize.mdp'))

    mdpfile = MdpFile(
        compose_blocks([
            'header', 'dynamics', 'nonbonded-solvent', 'constraints',
            'thermostat', 'barostat', 'output'
        ]))
    mdpfile.setParameter('nsteps', '10000')  # 20 ps equilibration
    mdpfile.randomizeSeed()  # randomize velocities
    mdpfile.write(os.path.join(solvent_path, 'equilibration.mdp'))

    mdpfile = MdpFile(
        compose_blocks([
            'header', 'dynamics', 'nonbonded-solvent', 'constraints',
            'thermostat', 'barostat', 'free-energy', 'output'
        ]))
    mdpfile.randomizeSeed()  # randomize velocities
    mdpfile.write(os.path.join(solvent_path, 'production.mdp'))

    # write run script
    print "Writing run script..."
    solvent_path = os.path.abspath(solvent_path)
    contents = """\
#!/bin/tcsh
#BSUB -J %(jobname)s_solvent
#BSUB -o %(solvent_path)s/outfile.%%J
#BSUB -e %(solvent_path)s/errfile.%%J
#BSUB -n 1
#BSUB -M 2000000
#BSUB -W 168:0

source %(GMXRC)s
cd %(solvent_path)s
setenv RANSEED 1234

# constrained minimize
%(grompp)s -f minimize.mdp -c system.g96 -p system.top -o minimize.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s minimize.tpr -x minimize.xtc -c minimize.g96 -e minimize.edr -g minimize.log

# equilibration
%(grompp)s -f equilibration.mdp -c minimize.g96 -p system.top -o equilibration.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s equilibration.tpr -o equilibration.trr -x equilibration.xtc -c equilibration.g96 -e equilibration.edr -g equilibration.log

# production
%(grompp)s -f production.mdp -c equilibration.g96 -p system.top -o production.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s production.tpr -o production.trr -x production.xtc -c production.g96 -e production.edr -g production.log

# signal completion
mv run.sh run.sh.done
""" % vars()
    write_file(os.path.join(solvent_path, 'run.sh'), contents)

    # PREPARE SOLVATED COMPLEX

    print "\nPREPARING SOLVATED COMPLEX"

    # create the directory if it doesn't exist
    complex_path = os.path.join(work_path, 'complex')
    if not os.path.exists(complex_path):
        os.makedirs(complex_path)

    # solvate the ligand
    print "Solvating the complex with tleap..."
    system_prmtop_filename = os.path.join(complex_path, 'system.prmtop')
    system_crd_filename = os.path.join(complex_path, 'system.crd')
    tleap_input_filename = os.path.join(complex_path, 'setup-system.leap.in')
    tleap_output_filename = os.path.join(complex_path, 'setup-system.leap.out')
    clearance = 10.0  # clearance in A
    contents = """
source leaprc.ff99
source leaprc.gaff
loadamberparams %(parameter_path)s/frcmod_t1p
loadamberparams %(parameter_path)s/frcmod_t2p
loadamberparams %(parameter_path)s/frcmod_y1p
loadamberparams %(parameter_path)s/frcmod_y2p

# load antechamber-generated additional parameters
mods = loadAmberParams %(ligand_frcmod_filename)s

# Load protein.
loadOff %(protein_off_filename)s

# Load ligand.
loadOff %(ligand_off_filename)s

# Create system.
system = combine { protein ligand }
""" % vars()
    # add counterions for ligand
    if (ligand_charge != 0):
        nions = abs(ligand_charge)
        if ligand_charge < 0: iontype = 'Na+'
        if ligand_charge > 0: iontype = 'Cl-'
        contents += """
# Add counterions for ligand (will be annihilated with ligand).
addions system %(iontype)s %(nions)d
""" % vars()
    #
    # add counterions for protein
    if (protein_charge != 0):
        nions = abs(protein_charge)
        if protein_charge < 0: iontype = 'Na+'
        if protein_charge > 0: iontype = 'Cl-'
        contents += """
# Add counterions for protein.
addions system %(iontype)s %(nions)d
""" % vars()
    #
    contents += """
# Solvate in truncated octahedral box.
solvateOct system TIP3PBOX %(clearance)f

# Check the system
check system

# Write the system
saveamberparm system %(system_prmtop_filename)s %(system_crd_filename)s
""" % vars()
    write_file(tleap_input_filename, contents)
    command = 'tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s' % vars(
    )
    output = commands.getoutput(command)

    # convert to gromacs
    print "Converting to gromacs..."
    amb2gmx = os.path.join(os.getenv('MMTOOLSPATH'), 'converters',
                           'amb2gmx.pl')
    system_prefix = os.path.join(complex_path, 'system')
    command = '%(amb2gmx)s --prmtop %(system_prmtop_filename)s --crd %(system_crd_filename)s --outname %(system_prefix)s' % vars(
    )
    print command
    output = commands.getoutput(command)
    print output

    # set up perturbation
    print "Modifying topology file for perturbation..."
    system_top_filename = os.path.join(complex_path, 'system.top')
    lines = read_file(system_top_filename)
    perturb_atom_indices = list()
    indices = extract_section(lines, 'atoms')
    nions = 0
    for index in indices:
        # extract the line
        line = stripcomments(lines[index])
        # parse the line
        elements = line.split()
        nelements = len(elements)
        # skip if not all elements found
        if (nelements < 8): continue
        # parse line
        atom = dict()
        atom['nr'] = int(elements[0])
        atom['type'] = elements[1]
        atom['resnr'] = int(elements[2])
        atom['residue'] = elements[3]
        atom['atom'] = elements[4]
        atom['cgnr'] = int(elements[5])
        atom['charge'] = float(elements[6])
        atom['mass'] = float(elements[7])
        # add those atoms in the ligand to our list
        if atom['residue'] == 'MOL':
            perturb_atom_indices.append(atom['nr'])
        # add counterions to balance ligand
        if ((atom['residue'] == 'Na+') or
            (atom['residue'] == 'Cl-')) and (nions < abs(ligand_charge)):
            perturb_atom_indices.append(atom['nr'])
            nions += 1

    perturbGromacsTopology(system_top_filename,
                           ligand,
                           perturb_torsions=True,
                           perturb_vdw=True,
                           perturb_charges=True,
                           perturb_atom_indices=perturb_atom_indices)

    # set up mdp files
    print "Writing mdp files..."
    mdpfile = MdpFile(
        compose_blocks([
            'header', 'minimization', 'nonbonded-solvent', 'constraints',
            'restraints', 'output'
        ]))
    mdpfile.write(os.path.join(complex_path, 'minimize.mdp'))

    mdpfile = MdpFile(
        compose_blocks([
            'header', 'dynamics', 'nonbonded-solvent', 'constraints',
            'thermostat', 'barostat', 'output'
        ]))
    mdpfile.setParameter('nsteps', '10000')  # 20 ps equilibration
    mdpfile.randomizeSeed()  # randomize velocities
    mdpfile.write(os.path.join(complex_path, 'equilibration.mdp'))

    mdpfile = MdpFile(
        compose_blocks([
            'header', 'dynamics', 'nonbonded-solvent', 'constraints',
            'restraints', 'thermostat', 'barostat', 'free-energy', 'output'
        ]))
    mdpfile.randomizeSeed()  # randomize velocities
    mdpfile.write(os.path.join(complex_path, 'production.mdp'))

    # Copy restraint scripts to run directory
    shutil.copy(compute_angles, complex_path)
    shutil.copy(restraint_topology, complex_path)
    # write run script
    print "Writing run script..."
    complex_path = os.path.abspath(complex_path)
    contents = """\
#!/bin/tcsh
#BSUB -J %(jobname)s_complex
#BSUB -o %(complex_path)s/outfile.%%J
#BSUB -e %(complex_path)s/errfile.%%J
#BSUB -n 1
#BSUB -M 2000000
#BSUB -W 168:0

source %(GMXRC)s
cd %(complex_path)s
setenv RANSEED 1234

# create a tpr file from which to create restraints    
%(grompp)s -f minimize.mdp -c system.g96 -p system.top -o minimize.tpr -maxwarn 10000 -n system.ndx
# Integrate restraints into pre-minimization topfile
python computeangles.py -f system.g96 -s minimize.tpr -d $RANSEED
python restraint_topology.py -n system -p .

# constrained minimize with restraints
%(grompp)s -f minimize.mdp -c system_restr.g96 -p system_restr.top -o minimize.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s minimize.tpr -x minimize.xtc -c minimize.g96 -e minimize.edr -g minimize.log

# equilibration with restraints
%(grompp)s -f equilibration.mdp -c minimize.g96 -p system.top -o equilibration.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s equilibration.tpr -o equilibration.trr -x equilibration.xtc -c equilibration.g96 -e equilibration.edr -g equilibration.log

# create a TPR file, integrate restraints
%(grompp)s -f production.mdp -c equilibration.g96 -p system_restr.top -o production.tpr -maxwarn 10000 -n system.ndx
cp system_restr.top minimize.top
python computeangles.py -f equilibration.g96 -s production.tpr -d $RANSEED
python restraint_topology.py -n equilibration -p .

# production with restraints
%(grompp)s -f production.mdp -c equilibration_restr.g96 -p minimize_restr.top -o production.tpr -maxwarn 10000 -n system.ndx
%(mdrun)s -s production.tpr -o production.trr -x production.xtc -c production.g96 -e production.edr -g production.log

# signal completion
mv run.sh run.sh.done
""" % vars()
    write_file(os.path.join(complex_path, 'run.sh'), contents)

    return
Exemple #6
0
    def findLigandEnergy(self, working_directory, state):
        """Runs findEnergy on all of the ligands

        ARGUMENTS
            working_directory (string) - the current working directory
            gromacs_directory (string) - the directory containing gromacs
            ligand_name_list (array of ) - the list of ligands to be analyzed
            xvg_values (array of integers) - the array containing the desired xvg_values
            state (string) - initial/mutatedInitial/mutatedFinal

        RETURNS
            ligand (array) - returns the array containing all of the energy terms for all of the comparisons for a single state
        """

        os.chdir(working_directory)
        n = len(self.xvg_values.split()) + 1
        ligand = array([])

        # Initial state calculation
        if state == 'initial':

            print 'Minimizing Initial'
            working_directory = os.path.join(working_directory, 'initial')
            if not os.path.exists(working_directory):
                commands.getoutput('mkdir %s'%(working_directory))

            # Runs findEnergy and appends values to array
            for i in range(len(self.ligand_name_list)):

                print 'Minimizing %s'%(self.ligand_name_list[i])

                new_working_directory = os.path.join(working_directory, self.ligand_name_list[i])
                if not os.path.exists(new_working_directory):
                    commands.getoutput('mkdir %s'%(new_working_directory))
                commands.getoutput('cp %s %s/minimize.mdp'%(self.mdp, new_working_directory))
                commands.getoutput('cp %s %s/system.gro'%(self.gros[i], new_working_directory))
                commands.getoutput('cp %s %s/system.top'%(self.originaltops[i], new_working_directory))
                if self.originalitps != None:
                    commands.getoutput('cp %s %s'%(self.originalitps[i], new_working_directory))
                os.chdir(new_working_directory)

                # Sets initial state parameters
                mdp = MdpFile('minimize.mdp')
                mdp.setParameter('free-energy', 'no')
                mdp.setParameter('nsteps', '0')
                mdp.write('minimize.mdp')

                line = findEnergy(self.grompp, self.mdrun, self.g_energy, 'minimize.mdp', 'system.gro', 'system.top', self.xvg_values)
                ligand = append(ligand.reshape(len(ligand), n), line.reshape(1, n), axis=0)

        # Mutated Initial calculation
        elif state == 'mutatedInitial':

            print 'Minimizing Initial Mutation'
            working_directory = os.path.join(working_directory,'initialmutation')
            if not os.path.exists(working_directory):
                commands.getoutput('mkdir %s'%(working_directory))

            expanded_ligand_list = list()
            expanded_gros_list = list()

            for i in range(len(self.ligand_name_list)):
                for j in range(i+1, len(self.ligand_name_list)):
                    expanded_ligand_list.append(self.ligand_name_list[i])
                    expanded_ligand_list.append(self.ligand_name_list[j])
                    expanded_gros_list.append(self.gros[i])
                    expanded_gros_list.append(self.gros[j])

            # Runs findEnergy and appends values to array
            for i in range(len(expanded_ligand_list)):

                if i%2 == 0:
                    comparison = '%s-%s'%(expanded_ligand_list[i], expanded_ligand_list[i+1])
                    working_directory = os.path.join(working_directory, comparison)
                    commands.getoutput('mkdir %s'%(working_directory))

                print 'Minimizing %s in pair %s'%(expanded_ligand_list[i], comparison)

                new_working_directory = os.path.join(working_directory, expanded_ligand_list[i])
                if not os.path.exists(new_working_directory):
                    commands.getoutput('mkdir %s'%(new_working_directory))
                commands.getoutput('cp %s %s/minimize.mdp'%(self.mdp, new_working_directory))
                commands.getoutput('cp %s %s/system.gro'%(expanded_gros_list[i], new_working_directory))
                commands.getoutput('cp %s %s/system.top'%(self.relativetops[i], new_working_directory))
                if self.relativeitps != None:
                    commands.getoutput('cp %s %s'%(self.relativeitps[i], new_working_directory))
                os.chdir(new_working_directory)

                # Sets mutated initial state parameters
                mdp = MdpFile('minimize.mdp')
                mdp.setParameter('free-energy', 'yes')
                mdp.setParameter('init-lambda', '0')
                mdp.setParameter('nsteps','0')
                mdp.write('minimize.mdp')

                line = findEnergy(self.grompp, self.mdrun, self.g_energy, 'minimize.mdp', 'system.gro', 'system.top', self.xvg_values)
                ligand = append(ligand.reshape(len(ligand), n), line.reshape(1, n), axis=0)

        # Mutated Final calculation
        elif state == 'mutatedFinal':

            print 'Minimizing Final Mutation'
            working_directory = os.path.join(working_directory,'finalmutation')
            if not os.path.exists(working_directory):
                commands.getoutput('mkdir %s'%(working_directory))

            expanded_ligand_list = list()
            expanded_gros_list = list()

            for i in range(len(self.ligand_name_list)):
                for j in range(i+1, len(self.ligand_name_list)):
                    expanded_ligand_list.append(self.ligand_name_list[i])
                    expanded_ligand_list.append(self.ligand_name_list[j])
                    expanded_gros_list.append(self.gros[i])
                    expanded_gros_list.append(self.gros[j])

            for i in range(len(expanded_ligand_list)):

                if i%2 == 0:
                    comparison = '%s-%s'%(expanded_ligand_list[i], expanded_ligand_list[i+1])
                    commands.getoutput('mkdir %s'%(os.path.join(working_directory, comparison)))

                print 'Minimizing %s in pair %s'%(expanded_ligand_list[i], comparison)

                new_working_directory = os.path.join(working_directory,comparison, expanded_ligand_list[i])
                if not os.path.exists(new_working_directory):
                    commands.getoutput('mkdir %s'%(new_working_directory))
                commands.getoutput('cp %s %s/minimize.mdp'%(self.mdp, new_working_directory))
                commands.getoutput('cp %s %s/system.gro'%(expanded_gros_list[i], new_working_directory))
                commands.getoutput('cp %s %s/system.top'%(self.relativetops[i], new_working_directory))
                if self.relativeitps != None:
                    commands.getoutput('cp %s %s'%(self.relativeitps[i], new_working_directory))
                os.chdir(new_working_directory)

                # Sets mutated final state parameters
                mdp = MdpFile('minimize.mdp')
                mdp.setParameter('free-energy', 'yes')
                mdp.setParameter('init-lambda', '1')
                mdp.setParameter('nsteps','0')
                mdp.write('minimize.mdp')

                if i%2 == 1:
                    if self.matches != None:
                        matchArray = readMci(self.matches[(i-1)/2])
                    else:
                        matchArray = determineMatchArray(self.ligand_basepath, expanded_ligand_list[i-1], expanded_ligand_list[i])
                    mutatedGroStructure = GromacsStructure()
                    mutatedGroStructure = mutateGroFile(expanded_gros_list[i-1], expanded_gros_list[i], matchArray)
                    mutatedGroStructure.write('system.gro')
                    modifySolventInTopFile(self.relativetops[i-1], self.relativetops[i], 'system.top')

                # Runs findEnergy and appends values to array
                line = findEnergy(self.grompp, self.mdrun, self.g_energy, 'minimize.mdp', 'system.gro', 'system.top', self.xvg_values)
                ligand = append(ligand.reshape(len(ligand), n), line.reshape(1, n ), axis=0)

        return ligand
Exemple #7
0
def setup_binding_free_energy(protein_pdb_filename, ligand_filename, work_path, nreplicates_solvent = 1, nreplicates_complex = 1):
    """Set up generalized ensemble simulation to calculate a protein-ligand binding free energy.

    ARGUMENTS
      protein_pdb_filename (string) - filename of PDB file for the protein, without any missing heavy atoms and with appropriate protonation state already assigned
      ligand_filename (string) - filename of some type OpenEye can read (e.g. mol2) describing the ligand with suitable docked coordinates
      work_path (string) - directory to place files for binding free energy calculation (created if does not already exist)

    OPTIONAL ARGUMENTS
      nreplicates_solvent (int) - number of replicates to set up of solvated ligand (default: 1)
      nreplicates_complex (int) - number of replicates to set up of the complexed ligand (default: 1)
    """

    # parameters
    forcefield = 'ffamber96' # forcefield
    box_clearance_in_nm = 0.3 # clearance from system to box edges (in nm) # DEBUG set to 1.0

    # gromacs filenames
    pdb2gmx = 'pdb2gmx_d'
    editconf = 'editconf_d'
    genbox = 'genbox_d'
    grompp = 'grompp_d'
    mdrun = 'mdrun_d'

    # Create directory for binding free energy calculation if it doesn't exist.
    #DEBUG os.makedirs(work_path)

    # SET UP PROTEIN TOPOLOGY

    print "\nConstructing protein topology..."

    # Create .top and .gro files for protein using pdb2gmx and desired forcefield
    # look up forcefield index
    lines = readFile(os.path.join(os.environ['GMXDATA'], 'top', 'FF.dat'))
    for forcefield_index in range(1, len(lines)):
        if lines[forcefield_index].find(forcefield) != -1: break
    forcefield_index -= 1
    # run pdb2gmx
    protein_gro_filename = os.path.join(work_path, 'protein.gro')
    protein_top_filename = os.path.join(work_path, 'protein.top')
    command = 'echo %(forcefield_index)d | %(pdb2gmx)s -f %(protein_pdb_filename)s -o %(protein_gro_filename)s -p %(protein_top_filename)s -ignh -H14' % vars()
    print command
    #DEBUG output = commands.getoutput(command)
    #DEBUG print output

    # SET UP LIGAND TOPOLOGY

    print "\nConstructing ligand topology..."

    # Read the ligand into OEMol.
    ligand = readMolecule(ligand_filename, normalize = True)
    
    # Generate GAFF parameters for gromacs for small molecule, using antechamber to generate AM1-BCC charges.
    ligand_top_filename = os.path.join(work_path,'ligand.top')
    ligand_gro_filename = os.path.join(work_path,'ligand.gro')
    #DEBUG parameterizeForGromacs(ligand, topology_filename = ligand_top_filename, coordinate_filename = ligand_gro_filename, charge_model = 'bcc', resname = 'LIG')

    # Modify gromacs topology file for alchemical free energy calculation.
    #DEBUG perturbGromacsTopology(os.path.join(work_path,'ligand.top'), ligand)
    
    # SET UP SOLVATED LIGAND

    print "\nSetting up solvated ligand..."

    # Create directory to contain ligand in solution.
    solvated_ligand_path = os.path.join(work_path, 'solvated-ligand')
    #DEBUG os.makedirs(solvated_ligand_path)

    # Convert gromacs topology to an .itp file suitable for inclusion.
    top_to_itp(os.path.join(work_path,'ligand.top'), os.path.join(solvated_ligand_path,'solute.itp'), moleculetype = 'solute')

    # write enlarged box for solvent
    system_gro_filename = os.path.join(solvated_ligand_path, 'system.gro')
    system_top_filename = os.path.join(solvated_ligand_path, 'system.top')
    command = '%(editconf)s -f %(ligand_gro_filename)s -o %(system_gro_filename)s -bt octahedron -d %(box_clearance_in_nm)f' % vars()
    print command
    output = commands.getoutput(command)
    print output

    # write topology for system
    contents = """
; amber forcefield
#include "ffamber03.itp"

; my molecule
#include "solute.itp"

; TIP3P water
#include "ffamber_tip3p.itp"

[ system ]
solute

[ molecules ]
; Compound        nmols
solute            1
""" % vars()
    writeFile(system_top_filename, contents)

    # solvate
    command = '%(genbox)s -cp %(system_gro_filename)s -cs ffamber_tip3p.gro -o %(system_gro_filename)s -p %(system_top_filename)s' % vars()
    print command
    output = commands.getoutput(command)
    print output
    
    # construct .mdp files
    mdpfile = MdpFile(compose_blocks(['header', 'minimization', 'nonbonded-solvent', 'output']))
    mdpfile.write(os.path.join(solvated_ligand_path, 'minimization-unconstrained.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'minimization', 'nonbonded-solvent', 'constraints', 'output']))
    mdpfile.write(os.path.join(solvated_ligand_path, 'minimization-constrained.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-solvent', 'constraints', 'thermostat', 'barostat', 'free-energy', 'output']))
    mdpfile.randomizeSeed() # randomize velocities
    mdpfile.write(os.path.join(solvated_ligand_path, 'production.mdp'))

    # write run script
    contents = """\
#/bin/tcsh

source /Users/jchodera/local/gromacs-3.1.4-fe/i386-apple-darwin8.11.1/bin/GMXRC.csh
    
# unconstrained minimization
%(grompp)s -f minimization-unconstrained.mdp -c system.gro -p system.top -o minimization-unconstrained.tpr -maxwarn 10000
%(mdrun)s -s minimization-unconstrained.tpr -x minimization-unconstrained.xtc -c minimized-unconstrained.gro -e minimization-unconstrained.edr -g minimization-unconstrained.log

# constrained minimization
%(grompp)s -f minimization-constrained.mdp -c minimized-unconstrained.gro -p system.top -o minimization-constrained.tpr -maxwarn 10000
%(mdrun)s -s minimization-constrained.tpr -x minimization-constrained.xtc -c minimized-constrained.gro -e minimization-constrained.edr -g minimization-constrained.log

# production
%(grompp)s -f production.mdp -c minimized-unconstrained.gro -p system.top -o production.tpr -maxwarn 10000
%(mdrun)s -s production.tpr -o production.trr -x production.xtc -c production.gro -e production.edr -g production.log
echo 0 | trjconv_d -f production.xtc -o production.pdb -s production.tpr
""" % vars()
    writeFile(os.path.join(solvated_ligand_path, 'run.sh'), contents)

    # SET UP SOLVATED COMPLEX

    # Create directory to contain complex.
    complex_path = os.path.join(work_path, 'solvated-complex')
    #DEBUG os.makedirs(complex_path)

    # Merge protein and liand .top files.
    complex_top_filename = os.path.join(complex_path, 'complex.top')
    complex_gro_filename = os.path.join(complex_path, 'complex.gro')
    merge_protein_ligand_topologies(protein_top_filename, protein_gro_filename, ligand_top_filename, ligand_gro_filename, complex_top_filename, complex_gro_filename)

    # Alter topology file to use ffamber TIP3P.
    # system.useTIP3P(complex_top_filename)

    # Determine total charge.
    total_charge = totalCharge(complex_top_filename)
    print "total_charge = %f" % total_charge
    
    # Create a System object to help us out.
    system = System(protein_pdb_filename, finalOutputDir = complex_path, finalOutputName = "protein", useff = forcefield)
    system.setup.setSaltConditions('MgCl2', 0.010)
    system.setup.set_boxType = 'octahedron'
    system.setup.set_boxSoluteDistance(box_clearance_in_nm)
    system.files.topfile = complex_top_filename
    system.totalChargeBeforeIons = total_charge

    # Create a periodic boundary conditions box.
    command = '%(editconf)s -bt octahedron -f %(complex_gro_filename)s -o %(complex_gro_filename)s -d %(box_clearance_in_nm)f' % vars()
    output = commands.getoutput(command)    
    print output

    # solvate the box with TIP3P water
    command = '%(genbox)s -cp %(complex_gro_filename)s -cs ffamber_tip3p.gro -o %(complex_gro_filename)s -p %(complex_top_filename)s' % vars()
    output = commands.getoutput(command)
    print output    

    # calculate how many ions to add to the box
    [np, nn, nwaters] = system.counterions()	 
    print "Adding %(np)d positive and %(nn)d negative to %(nwaters)d waters" % vars()
    

    # minimize the system
    ### make a tpr file with grompp
    #grompp = '%s/grompp -f %s -c %s -o %s -p %s '%(os.environ['GMXPATH'], self.files.mdpfile_Minimization, self.files.grofile, self.files.tprfile, self.files.topfile)
    #self.rungmx( grompp, mockrun=self.mockrun, checkForFatalErrors=self.checkForFatalErrors )	

    ### run minimization
    #minimize = '%s/mdrun -v -s %s -c %s '%(os.environ['GMXPATH'], self.files.tprfile, self.files.next_gro() )
    #self.rungmx( minimize, mockrun=self.mockrun, checkForFatalErrors=self.checkForFatalErrors )
    #self.files.increment_gro()    # must increment filename for any new gmx file 
    
    # do the rest of the preparation steps
    #self.postSolvationPreparationSteps()


    # construct .mdp files
    mdpfile = MdpFile(compose_blocks(['header', 'minimization', 'nonbonded-solvent', 'output']))
    mdpfile.write(os.path.join(complex_path, 'minimization-unconstrained.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'minimization', 'nonbonded-solvent', 'constraints', 'output']))
    mdpfile.write(os.path.join(complex_path, 'minimization-constrained.mdp'))

    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-solvent', 'constraints', 'thermostat', 'barostat', 'free-energy', 'output']))
    mdpfile.randomizeSeed() # randomize velocities
    mdpfile.write(os.path.join(complex_path, 'production.mdp'))    

    # write run script for minimization and production
    contents = """\
#/bin/tcsh

source /Users/jchodera/local/gromacs-3.1.4-fe/i386-apple-darwin8.11.1/bin/GMXRC.csh
    
# unconstrained minimization
%(grompp)s -f minimization-unconstrained.mdp -c system.gro -p system.top -o minimization-unconstrained.tpr -maxwarn 10000
%(mdrun)s -s minimization-unconstrained.tpr -x minimization-unconstrained.xtc -c minimized-unconstrained.gro -e minimization-unconstrained.edr -g minimization-unconstrained.log

# constrained minimization
%(grompp)s -f minimization-constrained.mdp -c minimized-unconstrained.gro -p system.top -o minimization-constrained.tpr -maxwarn 10000
%(mdrun)s -s minimization-constrained.tpr -x minimization-constrained.xtc -c minimized-constrained.gro -e minimization-constrained.edr -g minimization-constrained.log

# production
%(grompp)s -f production.mdp -c minimized-unconstrained.gro -p system.top -o production.tpr -maxwarn 10000
%(mdrun)s -s production.tpr -o production.trr -x production.xtc -c production.gro -e production.edr -g production.log
echo 0 | trjconv_d -f production.xtc -o production.pdb -s production.tpr
""" % vars()
    writeFile(os.path.join(complex_path, 'run.sh'), contents)



    
         
    # Convert gromacs topology to an .itp file suitable for inclusion.
    # top_to_itp(os.path.join(molecule_path,'solute.top'), os.path.join(molecule_path,'solute.itp'), moleculetype = molecule_name)

    return
    def findLigandEnergy(self, working_directory, state):
        """Runs findEnergy on all of the ligands

        ARGUMENTS
            working_directory (string) - the current working directory
            gromacs_directory (string) - the directory containing gromacs
            ligand_name_list (array of ) - the list of ligands to be analyzed
            xvg_values (array of integers) - the array containing the desired xvg_values
            state (string) - initial/mutatedInitial/mutatedFinal

        RETURNS
            ligand (array) - returns the array containing all of the energy terms for all of the comparisons for a single state
        """

        os.chdir(working_directory)
        n = len(self.xvg_values.split()) + 1
        ligand = array([])

        # Initial state calculation
        if state == 'initial':

            print 'Minimizing Initial'
            working_directory = os.path.join(working_directory, 'initial')
            if not os.path.exists(working_directory):
                commands.getoutput('mkdir %s' % (working_directory))

            # Runs findEnergy and appends values to array
            for i in range(len(self.ligand_name_list)):

                print 'Minimizing %s' % (self.ligand_name_list[i])

                new_working_directory = os.path.join(working_directory,
                                                     self.ligand_name_list[i])
                if not os.path.exists(new_working_directory):
                    commands.getoutput('mkdir %s' % (new_working_directory))
                commands.getoutput('cp %s %s/minimize.mdp' %
                                   (self.mdp, new_working_directory))
                commands.getoutput('cp %s %s/system.gro' %
                                   (self.gros[i], new_working_directory))
                commands.getoutput(
                    'cp %s %s/system.top' %
                    (self.originaltops[i], new_working_directory))
                if self.originalitps != None:
                    commands.getoutput(
                        'cp %s %s' %
                        (self.originalitps[i], new_working_directory))
                os.chdir(new_working_directory)

                # Sets initial state parameters
                mdp = MdpFile('minimize.mdp')
                mdp.setParameter('free-energy', 'no')
                mdp.setParameter('nsteps', '0')
                mdp.write('minimize.mdp')

                line = findEnergy(self.grompp, self.mdrun, self.g_energy,
                                  'minimize.mdp', 'system.gro', 'system.top',
                                  self.xvg_values)
                ligand = append(ligand.reshape(len(ligand), n),
                                line.reshape(1, n),
                                axis=0)

        # Mutated Initial calculation
        elif state == 'mutatedInitial':

            print 'Minimizing Initial Mutation'
            working_directory = os.path.join(working_directory,
                                             'initialmutation')
            if not os.path.exists(working_directory):
                commands.getoutput('mkdir %s' % (working_directory))

            expanded_ligand_list = list()
            expanded_gros_list = list()

            for i in range(len(self.ligand_name_list)):
                for j in range(i + 1, len(self.ligand_name_list)):
                    expanded_ligand_list.append(self.ligand_name_list[i])
                    expanded_ligand_list.append(self.ligand_name_list[j])
                    expanded_gros_list.append(self.gros[i])
                    expanded_gros_list.append(self.gros[j])

            # Runs findEnergy and appends values to array
            for i in range(len(expanded_ligand_list)):

                if i % 2 == 0:
                    comparison = '%s-%s' % (expanded_ligand_list[i],
                                            expanded_ligand_list[i + 1])
                    working_directory = os.path.join(working_directory,
                                                     comparison)
                    commands.getoutput('mkdir %s' % (working_directory))

                print 'Minimizing %s in pair %s' % (expanded_ligand_list[i],
                                                    comparison)

                new_working_directory = os.path.join(working_directory,
                                                     expanded_ligand_list[i])
                if not os.path.exists(new_working_directory):
                    commands.getoutput('mkdir %s' % (new_working_directory))
                commands.getoutput('cp %s %s/minimize.mdp' %
                                   (self.mdp, new_working_directory))
                commands.getoutput(
                    'cp %s %s/system.gro' %
                    (expanded_gros_list[i], new_working_directory))
                commands.getoutput(
                    'cp %s %s/system.top' %
                    (self.relativetops[i], new_working_directory))
                if self.relativeitps != None:
                    commands.getoutput(
                        'cp %s %s' %
                        (self.relativeitps[i], new_working_directory))
                os.chdir(new_working_directory)

                # Sets mutated initial state parameters
                mdp = MdpFile('minimize.mdp')
                mdp.setParameter('free-energy', 'yes')
                mdp.setParameter('init-lambda', '0')
                mdp.setParameter('nsteps', '0')
                mdp.write('minimize.mdp')

                line = findEnergy(self.grompp, self.mdrun, self.g_energy,
                                  'minimize.mdp', 'system.gro', 'system.top',
                                  self.xvg_values)
                ligand = append(ligand.reshape(len(ligand), n),
                                line.reshape(1, n),
                                axis=0)

        # Mutated Final calculation
        elif state == 'mutatedFinal':

            print 'Minimizing Final Mutation'
            working_directory = os.path.join(working_directory,
                                             'finalmutation')
            if not os.path.exists(working_directory):
                commands.getoutput('mkdir %s' % (working_directory))

            expanded_ligand_list = list()
            expanded_gros_list = list()

            for i in range(len(self.ligand_name_list)):
                for j in range(i + 1, len(self.ligand_name_list)):
                    expanded_ligand_list.append(self.ligand_name_list[i])
                    expanded_ligand_list.append(self.ligand_name_list[j])
                    expanded_gros_list.append(self.gros[i])
                    expanded_gros_list.append(self.gros[j])

            for i in range(len(expanded_ligand_list)):

                if i % 2 == 0:
                    comparison = '%s-%s' % (expanded_ligand_list[i],
                                            expanded_ligand_list[i + 1])
                    commands.getoutput(
                        'mkdir %s' %
                        (os.path.join(working_directory, comparison)))

                print 'Minimizing %s in pair %s' % (expanded_ligand_list[i],
                                                    comparison)

                new_working_directory = os.path.join(working_directory,
                                                     comparison,
                                                     expanded_ligand_list[i])
                if not os.path.exists(new_working_directory):
                    commands.getoutput('mkdir %s' % (new_working_directory))
                commands.getoutput('cp %s %s/minimize.mdp' %
                                   (self.mdp, new_working_directory))
                commands.getoutput(
                    'cp %s %s/system.gro' %
                    (expanded_gros_list[i], new_working_directory))
                commands.getoutput(
                    'cp %s %s/system.top' %
                    (self.relativetops[i], new_working_directory))
                if self.relativeitps != None:
                    commands.getoutput(
                        'cp %s %s' %
                        (self.relativeitps[i], new_working_directory))
                os.chdir(new_working_directory)

                # Sets mutated final state parameters
                mdp = MdpFile('minimize.mdp')
                mdp.setParameter('free-energy', 'yes')
                mdp.setParameter('init-lambda', '1')
                mdp.setParameter('nsteps', '0')
                mdp.write('minimize.mdp')

                if i % 2 == 1:
                    if self.matches != None:
                        matchArray = readMci(self.matches[(i - 1) / 2])
                    else:
                        matchArray = determineMatchArray(
                            self.ligand_basepath, expanded_ligand_list[i - 1],
                            expanded_ligand_list[i])
                    mutatedGroStructure = GromacsStructure()
                    mutatedGroStructure = mutateGroFile(
                        expanded_gros_list[i - 1], expanded_gros_list[i],
                        matchArray)
                    mutatedGroStructure.write('system.gro')
                    modifySolventInTopFile(self.relativetops[i - 1],
                                           self.relativetops[i], 'system.top')

                # Runs findEnergy and appends values to array
                line = findEnergy(self.grompp, self.mdrun, self.g_energy,
                                  'minimize.mdp', 'system.gro', 'system.top',
                                  self.xvg_values)
                ligand = append(ligand.reshape(len(ligand), n),
                                line.reshape(1, n),
                                axis=0)

        return ligand
def setupHydrationCalculation(solute, nreplicates=1, verbose=True, jobname="hydration"):
    """Set up an absolute alchemical hydration free energy calculation for the given molecule.

    ARGUMENTS
      solute (OEMol) - the molecule for which hydration free energy is to be computed (with fully explicit hydrogens) in the desired protonation state.

    OPTIONAL ARGUMENTS
      nreplicates (integer) - the number of replicates to set up (default: 1)
      verbose (boolean) - if True, extra debug information will be printed (default: True)
      jobname (string) - string to use for job name (default: "hydration")

    NOTES
      A directory will be created 'molecules/[molecule name]' as obtained from molecule.GetTitle().
    
    """

    # get current directory
    current_path = os.getcwd()

    # Center the solute molecule.
    OECenter(solute)

    # get molecule name
    solute_name = molecule.GetTitle()
    if verbose:
        print solute_name

    # create molecule path/directory
    work_path = os.path.abspath(os.path.join("molecules", solute_name))
    os.makedirs(work_path)

    #    # Write mol2 file for the molecule.
    #    writeMolecule(solute, os.path.join(solute_path, 'solute.mol2'))

    #    # Write GAFF parameters for gromacs, using antechamber to generate AM1-BCC charges.
    #    parameterizeForGromacs(molecule, topology_filename = os.path.join(molecule_path,'solute.top'), coordinate_filename = os.path.join(molecule_path,'solute.gro'), charge_model = 'bcc', resname = 'MOL')

    #    # Modify gromacs topology file for alchemical free energy calculation.
    #    perturbGromacsTopology(os.path.join(molecule_path,'solute.top'), molecule)

    #    # Convert gromacs topology to an .itp file suitable for inclusion.
    #    top_to_itp(os.path.join(molecule_path,'solute.top'), os.path.join(molecule_path,'solute.itp'), moleculetype = molecule_name)

    # get pathnames
    mdrun = globals()["mdrun"]
    grompp = globals()["grompp"]
    editconf = globals()["editconf"]

    # SET UP SOLUTE TOPOLOGY

    if verbose:
        print "\nCONSTRUCTING SOLUTE TOPOLOGY"

    # Get formal charge of ligand.
    solute_charge = formalCharge(solute)
    if verbose:
        print "solute formal charge is %d" % solute_charge

    # Write molecule with explicit hydrogens to mol2 file.
    print "Writing solute mol2 file..."
    solute_mol2_filename = os.path.abspath(os.path.join(work_path, "solute.mol2"))
    writeMolecule(solute, solute_mol2_filename)

    # Set substructure name (which will become residue name).
    print "Modifying molecule name..."
    modifySubstructureName(solute_mol2_filename, "MOL")

    # Run antechamber to assign GAFF atom types.
    print "Running antechamber..."
    os.chdir(work_path)
    gaff_mol2_filename = os.path.join(work_path, "solute.gaff.mol2")
    charge_model = "bcc"
    command = (
        "antechamber -i %(solute_mol2_filename)s -fi mol2 -o solute.gaff.mol2 -fo mol2 -c %(charge_model)s -nc %(solute_charge)d > antechamber.out"
        % vars()
    )
    if verbose:
        print command
    output = commands.getoutput(command)
    if verbose:
        print output
    os.chdir(current_path)

    # Generate frcmod file for additional GAFF parameters.
    solute_frcmod_filename = os.path.join(work_path, "frcmod.solute")
    command = "parmchk -i %(gaff_mol2_filename)s -f mol2 -o %(solute_frcmod_filename)s" % vars()
    if verbose:
        print command
    output = commands.getoutput(command)
    if verbose:
        print output

    # Run LEaP to generate topology / coordinates.
    solute_prmtop_filename = os.path.join(work_path, "solute.prmtop")
    solute_crd_filename = os.path.join(work_path, "solute.crd")
    solute_off_filename = os.path.join(work_path, "solute.off")

    tleap_input_filename = os.path.join(work_path, "setup-solute.leap.in")
    tleap_output_filename = os.path.join(work_path, "setup-solute.leap.out")
    contents = (
        """
# Load GAFF parameters.
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(solute_frcmod_filename)s

# load solute
solute = loadMol2 %(gaff_mol2_filename)s

# check the solute
check solute

# report net charge
charge solute

# save AMBER parameters
saveAmberParm solute %(solute_prmtop_filename)s %(solute_crd_filename)s

# write .off file
saveOff solute %(solute_off_filename)s

# exit
quit
"""
        % vars()
    )
    write_file(tleap_input_filename, contents)
    command = "tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s" % vars()
    output = commands.getoutput(command)

    # extract total charge
    solute_charge = commands.getoutput(
        'grep "Total unperturbed charge" %(tleap_output_filename)s | cut -c 27-' % vars()
    )
    solute_charge = int(round(float(solute_charge)))  # round to nearest whole charge
    if verbose:
        print "solute charge is %d" % solute_charge

    # PREPARE SOLVATED SOLUTE

    print "\nPREPARING SOLVATED SOLUTE"

    # create the directory if it doesn't exist
    solvent_path = os.path.join(work_path, "solvent")
    if not os.path.exists(solvent_path):
        os.makedirs(solvent_path)

    # solvate the solute
    print "Solvating the solute with tleap..."
    system_prmtop_filename = os.path.join(solvent_path, "system.prmtop")
    system_crd_filename = os.path.join(solvent_path, "system.crd")
    tleap_input_filename = os.path.join(solvent_path, "setup-system.leap.in")
    tleap_output_filename = os.path.join(solvent_path, "setup-system.leap.out")
    clearance = globals()["clearance"]  # clearance around solute (in A)
    contents = (
        """
source leaprc.ff99
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(solute_frcmod_filename)s

# Load solute.
loadOff %(solute_off_filename)s

# Create system.
system = combine { solute }
"""
        % vars()
    )
    # add counterions
    if solute_charge != 0:
        nions = abs(solute_charge)
        if solute_charge < 0:
            iontype = "Na+"
        if solute_charge > 0:
            iontype = "Cl-"
        contents += (
            """
# Add counterions.
addions system %(iontype)s %(nions)d
"""
            % vars()
        )
    #
    contents += (
        """
# Solvate in truncated octahedral box.
solvateBox system TIP3PBOX %(clearance)f iso

# Check the system
check system

# Write the system
saveamberparm system %(system_prmtop_filename)s %(system_crd_filename)s
"""
        % vars()
    )
    write_file(tleap_input_filename, contents)
    command = "tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s" % vars()
    output = commands.getoutput(command)

    # convert to gromacs
    print "Converting to gromacs..."
    amb2gmx = os.path.join(os.getenv("MMTOOLSPATH"), "converters", "amb2gmx.pl")
    system_prefix = os.path.join(solvent_path, "system")
    os.chdir(solvent_path)
    command = "%(amb2gmx)s --prmtop system.prmtop --crd system.crd --outname system" % vars()
    print command
    output = commands.getoutput(command)
    print output
    os.chdir(current_path)

    # Extract box size.
    g96_filename = os.path.join(solvent_path, "system.g96")
    g96_file = open(g96_filename, "r")
    g96_lines = g96_file.readlines()
    g96_file.close()
    box_size = zeros([3], float32)
    for line_number in range(len(g96_lines)):
        if g96_lines[line_number][0:3] == "BOX":
            # parse line with box size in nm
            line = g96_lines[line_number + 1]
            elements = line.split()
            box_size[0] = float(elements[0]) * 10.0
            box_size[1] = float(elements[1]) * 10.0
            box_size[2] = float(elements[2]) * 10.0
    print "box_size = "
    print box_size

    # make a PDB file for checking
    print "Converting system to PDB..."
    os.chdir(solvent_path)
    command = "cat system.crd | ambpdb -p system.prmtop > system.pdb" % vars()
    output = commands.getoutput(command)
    print output
    os.chdir(current_path)

    # set up perturbation
    print "Modifying topology file for perturbation..."
    system_top_filename = os.path.join(solvent_path, "system.top")
    lines = read_file(system_top_filename)
    perturb_atom_indices = list()
    indices = extract_section(lines, "atoms")
    for index in indices:
        # extract the line
        line = stripcomments(lines[index])
        # parse the line
        elements = line.split()
        nelements = len(elements)
        # skip if not all elements found
        if nelements < 8:
            continue
        # parse line
        atom = dict()
        atom["nr"] = int(elements[0])
        atom["type"] = elements[1]
        atom["resnr"] = int(elements[2])
        atom["residue"] = elements[3]
        atom["atom"] = elements[4]
        atom["cgnr"] = int(elements[5])
        atom["charge"] = float(elements[6])
        atom["mass"] = float(elements[7])
        # add those atoms in the solute to our list
        if atom["residue"] == "MOL":
            perturb_atom_indices.append(atom["nr"])
    perturbGromacsTopology(
        system_top_filename,
        solute,
        perturb_torsions=True,
        perturb_vdw=True,
        perturb_charges=True,
        perturb_atom_indices=perturb_atom_indices,
    )

    # set up replicates
    for replicate in range(nreplicates):
        # Create replicate directory.
        working_path = os.path.join(solvent_path, "%d" % replicate)
        os.makedirs(working_path)

        # TODO: Modify solute coordinates in system.g96 to cycle through available conformations.

        # set up mdp files
        print "Writing mdp files for replicate %d..." % replicate

        mdpfile = MdpFile(compose_blocks(["header", "minimization", "nonbonded-solvent", "constraints", "output"]))
        mdpfile.write(os.path.join(working_path, "minimize.mdp"))

        mdpfile = MdpFile(
            compose_blocks(
                ["header", "dynamics", "nonbonded-solvent", "constraints", "thermostat", "barostat", "output"]
            )
        )
        mdpfile.setParameter("nsteps", "10000")  # 20 ps equilibration
        mdpfile.randomizeSeed()  # randomize velocities
        mdpfile.write(os.path.join(working_path, "equilibration.mdp"))

        mdpfile = MdpFile(
            compose_blocks(
                [
                    "header",
                    "dynamics",
                    "nonbonded-solvent",
                    "constraints",
                    "thermostat",
                    "barostat",
                    "free-energy",
                    "output",
                ]
            )
        )
        mdpfile.randomizeSeed()  # randomize velocities
        mdpfile.write(os.path.join(working_path, "production.mdp"))

        # write run script
        print "Writing run script..."
        solvent_path = os.path.abspath(solvent_path)
        contents = (
            """\
set#!/bin/tcsh
#BSUB -J %(jobname)s_solvent
#BSUB -o %(working_path)s/outfile.%%J
#BSUB -e %(working_path)s/errfile.%%J
#BSUB -n 1
#BSUB -M 2000000
#BSUB -W 168:0

source $GMXRC
cd %(working_path)s

# constrained minimize
%(grompp)s -f minimize.mdp -c ../system.g96 -p ../system.top -o minimize.tpr -maxwarn 10000 -n ../system.ndx
%(mdrun)s -s minimize.tpr -x minimize.xtc -c minimize.g96 -e minimize.edr -g minimize.log

# equilibration
%(grompp)s -f equilibration.mdp -c minimize.g96 -p ../system.top -o equilibration.tpr -maxwarn 10000 -n ../system.ndx
%(mdrun)s -s equilibration.tpr -o equilibration.trr -x equilibration.xtc -c equilibration.g96 -e equilibration.edr -g equilibration.log

# production
%(grompp)s -f production.mdp -c equilibration.g96 -p ../system.top -o production.tpr -maxwarn 10000 -n ../system.ndx
%(mdrun)s -s production.tpr -o production.trr -x production.xtc -c production.g96 -e production.edr -g production.log

# signal completion
mv run.sh run.sh.done
"""
            % vars()
        )
        write_file(os.path.join(working_path, "run.sh"), contents)

    # SET UP VACUUM SIMULATION

    # construct pathname for vacuum simulations
    vacuum_path = os.path.join(work_path, "vacuum")
    if not os.path.exists(vacuum_path):
        os.makedirs(vacuum_path)

    # solvate the solute
    print "Preparing vacuum solute with tleap..."
    system_prmtop_filename = os.path.join(vacuum_path, "system.prmtop")
    system_crd_filename = os.path.join(vacuum_path, "system.crd")
    tleap_input_filename = os.path.join(vacuum_path, "setup-system.leap.in")
    tleap_output_filename = os.path.join(vacuum_path, "setup-system.leap.out")
    clearance = 50.0  # clearance in A
    contents = (
        """
source leaprc.ff99
source leaprc.gaff

# load antechamber-generated additional parameters
mods = loadAmberParams %(solute_frcmod_filename)s

# Load solute.
loadOff %(solute_off_filename)s

# Create system.
system = combine { solute }
"""
        % vars()
    )
    # add counterions
    if solute_charge != 0:
        nions = abs(solute_charge)
        if solute_charge < 0:
            iontype = "Na+"
        if solute_charge > 0:
            iontype = "Cl-"
        contents += (
            """
# Add counterions.
addions system %(iontype)s %(nions)d
"""
            % vars()
        )
    #
    contents += (
        """

# Create big box.
setBox system centers %(clearance)f iso

# Check the system
check system

# Write the system
saveamberparm system %(system_prmtop_filename)s %(system_crd_filename)s
"""
        % vars()
    )
    write_file(tleap_input_filename, contents)
    command = "tleap -f %(tleap_input_filename)s > %(tleap_output_filename)s" % vars()
    output = commands.getoutput(command)

    # convert to gromacs
    print "Converting to gromacs..."
    amb2gmx = os.path.join(os.getenv("MMTOOLSPATH"), "converters", "amb2gmx.pl")
    os.chdir(vacuum_path)
    command = "%(amb2gmx)s --prmtop system.prmtop --crd system.crd --outname system" % vars()
    print command
    output = commands.getoutput(command)
    print output
    os.chdir(current_path)

    # make a PDB file for checking
    print "Converting system to PDB..."
    os.chdir(vacuum_path)
    command = "cat system.crd | ambpdb -p system.prmtop > system.pdb" % vars()
    output = commands.getoutput(command)
    print output
    os.chdir(current_path)

    # write enlarged box for solvent because LEaP doesn't do it right.
    system_g96_filename = os.path.join(vacuum_path, "system.g96")
    command = "%s -f %s -o %s -bt cubic -box %f %f %f -center 0.0 0.0 0.0" % (
        editconf,
        system_g96_filename,
        system_g96_filename,
        box_size[0] / 10.0,
        box_size[1] / 10.0,
        box_size[2] / 10.0,
    )
    print command
    output = commands.getoutput(command)
    print output

    # set up perturbation
    print "Modifying topology file for perturbation..."
    system_top_filename = os.path.join(vacuum_path, "system.top")
    lines = read_file(system_top_filename)
    perturb_atom_indices = list()
    indices = extract_section(lines, "atoms")
    nions = 0
    for index in indices:
        # extract the line
        line = stripcomments(lines[index])
        # parse the line
        elements = line.split()
        nelements = len(elements)
        # skip if not all elements found
        if nelements < 8:
            continue
        # parse line
        atom = dict()
        atom["nr"] = int(elements[0])
        atom["type"] = elements[1]
        atom["resnr"] = int(elements[2])
        atom["residue"] = elements[3]
        atom["atom"] = elements[4]
        atom["cgnr"] = int(elements[5])
        atom["charge"] = float(elements[6])
        atom["mass"] = float(elements[7])
        # DEBUG
        # add those atoms in the solute to our list
        if atom["residue"] == "MOL":
            perturb_atom_indices.append(atom["nr"])
        # add counterions to balance solute
        if ((atom["residue"] == "Na+") or (atom["residue"] == "Cl-")) and (nions < abs(solute_charge)):
            print "WARNING: Solute has net charge of %(solute_charge)d -- will perturb counterions too." % vars()
            perturb_atom_indices.append(atom["nr"])
            nions += 1
    perturbGromacsTopology(
        system_top_filename,
        solute,
        perturb_torsions=True,
        perturb_vdw=True,
        perturb_charges=True,
        perturb_atom_indices=perturb_atom_indices,
    )

    # construct replicates
    for replicate in range(nreplicates):
        # Create replicate path.
        working_path = os.path.join(vacuum_path, "%d" % replicate)
        os.makedirs(working_path)

        # TODO: Modify solute coordinates in system.g96 to cycle through available conformations.

        # construct .mdp files
        print "Writing mdp files for replicate %d..." % replicate

        mdpfile = MdpFile(compose_blocks(["header", "minimization", "nonbonded-vacuum", "constraints", "output"]))
        cutoff = box_size.min() / 10.0 / 2.0 - 0.0001  # compute cutoff
        mdpfile.setParameter("rlist", "%f" % cutoff)
        mdpfile.setParameter("rvdw", "%f" % cutoff)
        mdpfile.setParameter("rcoulomb", "%f" % cutoff)
        mdpfile.write(os.path.join(working_path, "minimize.mdp"))

        mdpfile = MdpFile(
            compose_blocks(
                ["header", "dynamics", "nonbonded-vacuum", "constraints", "thermostat", "free-energy", "output"]
            )
        )
        mdpfile.randomizeSeed()  # randomize velocities
        mdpfile.setParameter("rlist", "%f" % cutoff)
        mdpfile.setParameter("rvdw", "%f" % cutoff)
        mdpfile.setParameter("rcoulomb", "%f" % cutoff)
        mdpfile.write(os.path.join(working_path, "production.mdp"))

        # write shell script for minimization and production
        contents = (
            """\
#!/bin/tcsh
#BSUB -J %(jobname)s_vacuum
#BSUB -o %(working_path)s/outfile.%%J
#BSUB -e %(working_path)s/errfile.%%J
#BSUB -n 1
#BSUB -M 2000000
#BSUB -W 168:0

source $GMXRC
cd %(working_path)s

# constrained minimize
%(grompp)s -f minimize.mdp -c ../system.g96 -p ../system.top -o minimize.tpr -maxwarn 10000 -n ../system.ndx
%(mdrun)s -s minimize.tpr -x minimize.xtc -c minimize.g96 -e minimize.edr -g minimize.log

# production
%(grompp)s -f production.mdp -c minimize.g96 -p ../system.top -o production.tpr -maxwarn 10000 -n ../system.ndx
%(mdrun)s -s production.tpr -o production.trr -x production.xtc -c production.g96 -e production.edr -g production.log

# signal completion
mv run.sh run.sh.done
"""
            % vars()
        )
        write_file(os.path.join(working_path, "run.sh"), contents)

    return
def setupHydrationCalculation(molecule):
    """
    """

    # get current directory
    current_path = os.getcwd()

    # Center the molecule.
    OECenter(molecule)

    # get molecule name
    molecule_name = molecule.GetTitle()
    print molecule_name

    # create molecule path/directory
    molecule_path = os.path.abspath(os.path.join('molecules', molecule_name))
    os.makedirs(molecule_path)

    # Write mol2 file for the molecule.
    writeMolecule(molecule, os.path.join(molecule_path, 'solute.mol2'))

    # Write GAFF parameters for gromacs, using antechamber to generate AM1-BCC charges.
    parameterizeForGromacs(
        molecule,
        topology_filename=os.path.join(molecule_path, 'solute.top'),
        coordinate_filename=os.path.join(molecule_path, 'solute.gro'),
        charge_model='bcc',
        resname='MOL')

    # Modify gromacs topology file for alchemical free energy calculation.
    perturbGromacsTopology(os.path.join(molecule_path, 'solute.top'), molecule)

    # Convert gromacs topology to an .itp file suitable for inclusion.
    top_to_itp(os.path.join(molecule_path, 'solute.top'),
               os.path.join(molecule_path, 'solute.itp'),
               moleculetype=molecule_name)

    # SET UP VACUUM SIMULATION

    # construct pathname for vacuum simulations
    vacuum_path = os.path.join(molecule_path, 'vacuum')
    os.mkdir(vacuum_path)
    os.chdir(vacuum_path)

    # create molecule in enlarged box
    command = 'editconf_d -f ../solute.gro -o system.gro -bt cubic -d 50.0 -center 0.0 0.0 0.0'
    print command
    output = commands.getoutput(command)
    print output

    # create topology file
    topology_contents = """
; amber forcefield
#include "ffamber03.itp"

; my molecule
#include "../solute.itp"

[ system ]
%(molecule_name)s in vacuum

[ molecules ]
; Compound        nmols
%(molecule_name)s            1
""" % vars()
    writeFile('system.top', topology_contents)

    # construct .mdp files
    mdpfile = MdpFile(
        compose_blocks(
            ['header', 'minimization', 'nonbonded-vacuum', 'output']))
    mdpfile.write(os.path.join(vacuum_path, 'minimization-unconstrained.mdp'))

    mdpfile = MdpFile(
        compose_blocks([
            'header', 'minimization', 'nonbonded-vacuum', 'constraints',
            'output'
        ]))
    mdpfile.write(os.path.join(vacuum_path, 'minimization-constrained.mdp'))

    mdpfile = MdpFile(
        compose_blocks([
            'header', 'dynamics', 'nonbonded-vacuum', 'constraints',
            'thermostat', 'free-energy', 'output'
        ]))
    mdpfile.randomizeSeed()  # randomize velocities
    mdpfile.write(os.path.join(vacuum_path, 'production.mdp'))

    # write shell script for minimization and production
    contents = """\
#/bin/tcsh

source /Users/jchodera/local/gromacs-3.1.4-fe/i386-apple-darwin8.11.1/bin/GMXRC.csh
setenv GROMPP grompp_d
setenv MDRUN mdrun_d
    
# unconstrained minimization
$GROMPP -f minimization-unconstrained.mdp -c system.gro -p system.top -o minimization-unconstrained.tpr -maxwarn 10000
$MDRUN -s minimization-unconstrained.tpr -x minimization-unconstrained.xtc -c minimized-unconstrained.gro -e minimization-unconstrained.edr -g minimization-unconstrained.log
    
# constrained minimization
$GROMPP -f minimization-constrained.mdp -c minimized-unconstrained.gro -p system.top -o minimization-constrained.tpr -maxwarn 10000
$MDRUN -s minimization-constrained.tpr -x minimization-constrained.xtc -c minimized-constrained.gro -e minimization-constrained.edr -g minimization-constrained.log

# production
$GROMPP -f production.mdp -c minimized-constrained.gro -p system.top -o production.tpr -maxwarn 10000
$MDRUN -v -s production.tpr -o production.trr -x production.xtc -c production.gro -e production.edr -g production.log
echo 0 | trjconv_d -f production.xtc -o production.pdb -s production.tpr
""" % vars()
    writeFile(os.path.join(vacuum_path, 'run.sh'), contents)

    # SET UP SOLVATED CALCULATION

    # construct pathname for solvent simulations
    solvated_path = os.path.join(molecule_path, 'solvent')
    os.mkdir(solvated_path)
    os.chdir(solvated_path)

    # write enlarged box for solvent
    command = 'editconf_d -f ../solute.gro -o solute.gro -bt octahedron -d 1.0 -center 0.0 0.0 0.0'
    print command
    output = commands.getoutput(command)
    print output

    # write topology
    topology_contents = """
; amber forcefield
#include "ffamber03.itp"

; my molecule
#include "../solute.itp"

; TIP3P water
#include "ffamber_tip3p.itp"

[ system ]
%(molecule_name)s in solvent

[ molecules ]
; Compound        nmols
%(molecule_name)s            1
""" % vars()
    writeFile('system.top', topology_contents)

    # solvate
    command = 'genbox_d -cp solute.gro -cs ffamber_tip3p.gro -o system.gro -p system.top'
    # command = 'genbox_d -cp solute.gro -cs tip3p-pme-waterbox.gro -o system.gro -p system.top' # use our special tip3p box
    # command = 'genbox_d -cp solute.gro -cs tip3p-pme-waterbox.gro -o system.gro -p system.top -vdwd 0.0' # insert waters but don't cull overlap
    print command
    output = commands.getoutput(command)
    print output

    # construct .mdp files
    mdpfile = MdpFile(
        compose_blocks(
            ['header', 'minimization', 'nonbonded-solvent', 'output']))
    mdpfile.write(os.path.join(solvated_path,
                               'minimization-unconstrained.mdp'))

    mdpfile = MdpFile(
        compose_blocks([
            'header', 'minimization', 'nonbonded-solvent', 'constraints',
            'output'
        ]))
    mdpfile.write(os.path.join(solvated_path, 'minimization-constrained.mdp'))

    #    mdpfile = MdpFile(compose_blocks(['header', 'dynamics', 'nonbonded-solvent', 'constraints', 'thermostat-equilibration', 'barostat', 'output']))
    #    mdpfile.randomizeSeed() # randomize velocities
    #    mdpfile.setParameter('nsteps', '25000') # 10 ps equilibration
    #    mdpfile.write(os.path.join(solvated_path, 'equilibration.mdp'))

    mdpfile = MdpFile(
        compose_blocks([
            'header', 'dynamics', 'nonbonded-solvent', 'constraints',
            'thermostat', 'barostat', 'free-energy', 'output'
        ]))
    mdpfile.randomizeSeed()  # randomize velocities
    mdpfile.write(os.path.join(solvated_path, 'production.mdp'))

    # write run script
    contents = """\
#/bin/tcsh

source /Users/jchodera/local/gromacs-3.1.4-fe/i386-apple-darwin8.11.1/bin/GMXRC.csh
setenv GROMPP grompp_d
setenv MDRUN mdrun_d
    
# unconstrained minimization
$GROMPP -f minimization-unconstrained.mdp -c system.gro -p system.top -o minimization-unconstrained.tpr -maxwarn 10000
$MDRUN -s minimization-unconstrained.tpr -x minimization-unconstrained.xtc -c minimized-unconstrained.gro -e minimization-unconstrained.edr -g minimization-unconstrained.log

# constrained minimization
$GROMPP -f minimization-constrained.mdp -c minimized-unconstrained.gro -p system.top -o minimization-constrained.tpr -maxwarn 10000
$MDRUN -s minimization-constrained.tpr -x minimization-constrained.xtc -c minimized-constrained.gro -e minimization-constrained.edr -g minimization-constrained.log

# equilibration
#$GROMPP -f equilibration.mdp -c minimized-constrained.gro -p system.top -o equilibration.tpr -maxwarn 10000
#$MDRUN -v -s equilibration.tpr -o equilibration.trr -x equilibration.xtc -c equilibration.gro -e equilibration.edr -g equilibration.log
#echo 0 | trjconv_d -f equilibration.xtc -o equilibration.pdb -s equilibration.tpr

# production
$GROMPP -f production.mdp -c minimized-constrained.gro -p system.top -o production.tpr -maxwarn 10000
#$GROMPP -f production.mdp -c equilibration.gro -p system.top -o production.tpr -maxwarn 10000
#$GROMPP -f production.mdp -c system.gro -p system.top -o production.tpr -maxwarn 10000
$MDRUN -v -s production.tpr -o production.trr -x production.xtc -c production.gro -e production.edr -g production.log
echo 0 | trjconv_d -f production.xtc -o production.pdb -s production.tpr
""" % vars()
    writeFile(os.path.join(solvated_path, 'run.sh'), contents)

    # restore working directory
    os.chdir(current_path)

    return