Exemple #1
0
def add_to_nameList(name):
    if universe.has('d_nameList'):
        temp = universe.get('d_nameList')
        temp.append(name)
        universe.add('d_nameList', temp)
    else:
        universe.add('d_nameList', [name])
Exemple #2
0
def add_box(d_boxMargin, d_boxType='cubic'):
    utils.update("add_box", "adding box using gmx editconf (boxMargin = {0}, boxType = {1})...".format(d_boxMargin, d_boxType))

    os.system("gmx editconf -f {0} -o {1}_BOX.pdb -d {2} -bt {3} >> builder.log 2>&1".format(universe.get('d_nameList')[-1], universe.get('d_pdbName'), d_boxMargin, d_boxType))

    # To set d_boxMargin and d_boxType.
    universe.add('d_boxMargin', d_boxMargin)
    universe.add('d_boxType', d_boxType)

    # To update d_box.
    load("{0}_BOX.pdb".format(universe.get('d_pdbName')))

    # To update d_nameList.
    utils.add_to_nameList("{0}_BOX.pdb".format(universe.get('d_pdbName')))
Exemple #3
0
def process(fname, d_model=1, d_ALI='A', d_chain=[], resetResId=False):
    basename = os.path.basename(fname)
    universe.add('d_pdbName', basename[0:len(basename) - 4])
    universe.add('d_model', d_model)
    universe.add('d_ALI', d_ALI)

    load(fname, d_model, d_ALI, d_chain)

    # Update d_chain from [] to a list of actual chains used:
    d_chain = []
    for residue in universe.get('d_residues'):
        d_chain.append(residue.d_chain)
    d_chain = list(set(d_chain))
    d_chain.sort()
    universe.add('d_chain', d_chain)

    utils.update(
        "process", 'file={0}, MODEL#={1}, ALI={2}, chain(s)={3}...'.format(
            fname, d_model, d_ALI, d_chain))

    # Write processed.pdb to file:
    utils.update(
        "process", "writing processed .pdb file to {}_PR1.pdb...".format(
            universe.get('d_pdbName')))
    write("{0}_PR1.pdb".format(universe.get('d_pdbName')))
Exemple #4
0
def add_buffer(ph_bufpdbName, ph_bufitpName, ph_bufMargin=2.0, ph_bufnmol=-1, attempts=100000):
    if not (universe.get('ph_constantpH') and universe.get('ph_restrainpH')):
        utils.update("add_buffer", "either ph_constantpH or ph_restrainpH is False --> skipping...")
        return

    # If user doesn't specified the amount, use #BUF = #ACID.
    if (ph_bufnmol == -1):
        ph_bufnmol = countRes('ASP') + countRes('GLU')

    utils.update("add_buffer", "will attempt to add {0} buffer molecule(s)...".format(ph_bufnmol))

    # RUN GROMACS INSERT-MOLECULES COMMAND
    os.system("touch vdwradii.dat") # we need this dummy file for this to work.

    os.system("gmx insert-molecules -f {0} -o {1}_BUF.pdb -ci {2} -nmol {3} -scale 1.0 -radius {4} -try {5} >> builder.log 2>&1".format(
        universe.get('d_nameList')[-1],
        universe.get('d_pdbName'),
        ph_bufpdbName,
        ph_bufnmol,
        0.5 * ph_bufMargin,
        int(attempts / ph_bufnmol)))

    os.remove("vdwradii.dat") # clean dummy file.

    # To update d_residues.
    load("{0}_BUF.pdb".format(universe.get('d_pdbName')))    

    # Give user a warning if there wasn't enough space.
    actual = countRes('BUF')
    if actual < ph_bufnmol:
        utils.update("add_buffer", "warning: only {0}/{1} requested buffer molecules inserted after {2} attempts,".format(actual, ph_bufnmol, attempts))
        utils.update("add_buffer", "warning: try decreasing ph_bufMargin (={0}nm) or increasing d_boxMargin (={1}nm)...".format(ph_bufMargin, universe.get('d_boxMargin')))
    else:
        utils.update("add_buffer", "succesfully added {0} buffer molecule(s)...".format(actual))

    # To add buffer topology to topol.top.
    utils.update("add_buffer", "updating topology...")
    os.system("cp {} .".format(ph_bufitpName))
    topol.add_mol(os.path.basename(ph_bufitpName), "Include buffer topology", 'BUF', actual)

    # Set some parameters in the universe.
    universe.add('ph_bufpdbName', ph_bufpdbName)
    universe.add('ph_bufitpName', ph_bufitpName)
    universe.add('ph_bufMargin', ph_bufMargin)
    universe.add('ph_bufnmol', actual)

    # To update d_nameList.
    utils.add_to_nameList("{0}_BUF.pdb".format(universe.get('d_pdbName')))
Exemple #5
0
def generate(d_modelFF, d_modelWater, d_terministring="", d_customitp=""):
    # Internal helper function.
    def rebuild_topol():
        # If we have only one chain, gromacs will put everything in topol.top.
        # If we have more than one chain, gromacs will do it for us.
        if (len(universe.get('d_chain')) <= 1):
            readingProtein = False
            
            file = open("topol_Protein_chain_A.itp", 'w')

            for line in open("topol.top").readlines():
                if (not readingProtein and line == "[ moleculetype ]\n"):
                    readingProtein = True
            
                if (readingProtein and line == "; Include water topology\n"):
                    readingProtein = False

                if (readingProtein):
                    file.write(line)
    
            file.close()
    
        with open('topol.top', 'w') as file:
            file.write("; Include forcefield parameters\n")
            file.write("#include \"{0}.ff/forcefield.itp\"\n\n".format(universe.get('d_modelFF')))

            file.write("; Include protein topology\n")
            for letter in universe.get('d_chain'):
                file.write("#include \"topol_Protein_chain_{0}.itp\"\n".format(letter))
            file.write('\n')

            file.write('[ system ]\n')
            file.write('{0}\n\n'.format(universe.get('d_pdbName')))

            file.write('[ molecules ]\n')
            file.write('; Compounts \t\t #mols\n')
            for letter in universe.get('d_chain'):
                file.write("Protein_chain_{0}\t\t1\n".format(letter))

    # ADD RELEVANT PARAMETERS TO UNIVERSE ######################################
    universe.add('d_modelFF', d_modelFF)
    universe.add('d_modelWater', d_modelWater)
    universe.add('d_terministring', d_terministring)

    # USER UPDATE STUFF ########################################################
    countACID = protein.countRes("ASP") + protein.countRes("GLU")

    # If constant-pH is on,
    if universe.get('ph_constantpH'):
        utils.update("generate", 'constant-pH is turned on...')
        
        # and we have at least one protonatable reside,
        if countACID > 0:
            utils.update("generate", "detected {} acidic residue(s):".format(countACID))

            for letter in universe.get('d_chain'):
                count = 1

                for residue in universe.get('d_residues'):
                    if residue.d_chain == letter:
                        count += 1

                        if residue.d_resname in ['ASP', 'GLU']:
                            utils.update("generate", "{:3s}-{:<4d} in chain {}".format(residue.d_resname, count, letter))

            utils.update("generate", "(setting protonation state to true (option 1) for all of these)")

        else:
            utils.update("generate", "no acidic residues detected, constant-pH is turned off...")
            universe.add('ph_constantpH', False)
            universe.add('ph_restrainpH', False)

    else:
        utils.update("generate", 'constant-pH is turned off...')
        universe.add('ph_restrainpH', False) # If ph_constantpH is False then this is also False.

    utils.update("generate", "using the {} force field with the {} water model...".format(d_modelFF, d_modelWater))

    # CREATE EOFSTRING FOR PDB2GMX COMMAND #####################################

    # Here we create EOFstring to circumvent pd2gmx prompting for user input.
    xstr = "<< EOF"
    # The for-loop sets the protonation state of all GLUs and ASPs to 1
    # (True) if ph_constantpH is True, and to 0 (False) if ph_constantpH is False.
    for chain in universe.get('d_chain'):
        for residue in universe.get('d_residues'):
            if residue.d_resname in ['ASP', 'GLU'] and residue.d_chain == chain:
                xstr += "\n{:d}".format(universe.get('ph_constantpH'))
        
        # Furthermore, if the user specified a two-letter string for the termini,
        # add those to the EOFstring as well:
        if (d_terministring != ""):
            xstr += "\n{}".format(d_terministring[0])
            xstr += "\n{}".format(d_terministring[1])
    # End EOFstring:
    xstr += "\nEOF"
    # print(xstr) # Debug

    # UPDATE USER ABOUT WHAT WE DO WITH THE TERMINI ############################

    if (d_terministring != ""):
        utils.update("generate", "Using options {} for termini...".format(d_terministring))
    else:
        utils.update("generate", "No termini specified, using gmx default (00 = NH3+ and COO-)...")
    
    utils.update("generate", "running pdb2gmx to create {}_PR2.pdb and topol.top...".format(universe.get('d_pdbName')))

    # RUN ACTUAL PDB2GMX COMMAND ###############################################

    if (d_terministring != ""):
        os.system("gmx pdb2gmx -f {0} -o {1}_PR2.pdb -asp -glu -ignh -ff {2} -water {3} -ter >> builder.log 2>&1 {4}".format(universe.get('d_nameList')[-1], universe.get('d_pdbName'), d_modelFF, d_modelWater, xstr))
    else:
        os.system("gmx pdb2gmx -f {0} -o {1}_PR2.pdb -asp -glu -ignh -ff {2} -water {3} >> builder.log 2>&1 {4}".format(universe.get('d_nameList')[-1], universe.get('d_pdbName'), d_modelFF, d_modelWater, xstr))

    # Rebuild topology.
    rebuild_topol()

    # Use custom topol_Protein_chain_A.itp (this is a temporary fix for charmm36-mar2019-m4)
    if (d_customitp != ""):
        utils.update("generate", "Overwriting topol_Protein_chain_A.itp generated by pdb2gmx with {}...".format(d_customitp))
        os.system("cp {} .".format(d_customitp))

    # To update d_residues.
    protein.load("{0}_PR2.pdb".format(universe.get('d_pdbName')))

    # To update d_nameList.
    utils.add_to_nameList("{0}_PR2.pdb".format(universe.get('d_pdbName')))
Exemple #6
0
def gen_mdp(Type, nsteps=25000, nstxout=0, posres=False):
    # HEAD
    if (Type not in ['EM', 'NVT', 'NPT', 'MD']):
        raise Exception(
            "Unknown .mdp Type specified. Types are: EM, NVT, NPT, MD.")

    utils.update(
        "gen_mdp", "Type={0}, nsteps={1}, nstxout={2}, posres={3}".format(
            Type, nsteps, nstxout, posres))

    file = open("{0}.mdp".format(Type), 'w')

    def addTitle(title):
        file.write("\n; {0}\n".format(title.upper()))

    def addParam(name, value, comment=''):
        if (comment == ''):
            file.write("{:20s} = {:13s}\n".format(name, str(value)))
        else:
            file.write("{:20s} = {:13s} ; {:13s}\n".format(
                name, str(value), comment))

    # POSITION RESTRAIN SECTION
    if Type in ['EM', 'MD']:
        if universe.get('ph_constantpH') and universe.get(
                'ph_restrainpH') and posres:
            addTitle('Position restrain')
            addParam('define', '-DPOSRES -DPOSRES_BUF', 'Position restraints.')
        elif universe.get('ph_constantpH') and universe.get('ph_restrainpH'):
            addTitle('Position restrain')
            addParam('define', '-DPOSRES_BUF', 'Position restraints.')

    if (Type in ['NVT', 'NPT']):  # position restrain temp and press coupling.
        addTitle('Position restrain')
        if universe.get('ph_constantpH') and universe.get('ph_restrainpH'):
            addParam('define', '-DPOSRES -DPOSRES_BUF', 'Position restraints.')
        else:
            addParam('define', '-DPOSRES', 'Position restraints.')

    # RUN CONTROL
    addTitle("Run control")

    if (Type == 'EM'):  # emtol hardcored, pretty typical for normal MD.
        dt = 0.01
        addParam('integrator', 'steep', 'Use steep for EM.')
        addParam('emtol', 1000, 'Stop when max force < 1000 kJ/mol/nm.')
        addParam('emstep', dt, 'Time step (ps).')

    if (Type in ['NVT', 'NPT', 'MD']):
        dt = 0.002
        addParam('integrator', 'md')
        addParam('dt', dt, 'Time step (ps).')

    addParam('nsteps', nsteps, '%.1f ns.' % ((dt * nsteps) / 1000.0))

    # We restrain the COM to prevent protein from coming too close to the BUFs.
    if Type == 'MD' and universe.get('ph_restrainpH'):
        addParam('comm-mode', 'Linear', 'Remove center of mass translation.')
        addParam('comm-grps', 'Protein Non-Protein')

    # OUTPUT CONTROL
    addTitle("Output control")
    addParam('nstxout-compressed', nstxout,
             'Write frame every %.3f ps.' % (dt * nstxout))

    # NEIGHBOUR SEARCHING PARAMETERS
    addTitle("Neighbour searching")
    addParam('cutoff-scheme', 'Verlet',
             'Related params are inferred by Gromacs.')

    # BONDED
    if (Type in ['NVT', 'NPT', 'MD']):
        addTitle("Bond parameters")
        addParam('constraints', 'h-bonds', 'Constrain H-bond vibrations.')
        addParam('constraint_algorithm', 'lincs', 'Holonomic constraints.')
        addParam('lincs_iter', 1, 'Related to accuracy of LINCS.')
        addParam('lincs_order', 4, 'Related to accuracy of LINCS.')

    # ELECTROSTATICS
    addTitle("Electrostatics")
    addParam('coulombtype', 'PME', 'Use Particle Mesh Ewald.')

    if (universe.get('d_modelFF')[0:5].lower() == "charm"
        ):  # if we use a CHARMM force field...
        addParam('rcoulomb', 1.2, 'Berk: CHARMM was calibrated for 1.2 nm.')
        addParam('fourierspacing', 0.14, 'Berk: set this to 0.14 for CHARMM.')
    else:  # Default for force fields:
        addParam('rcoulomb', 1.0, 'Coulomb cut-off (nm).')

    # VAN DER WAALS
    addTitle("Van der Waals")
    addParam('vdwtype', 'cut-off', 'Twin range cut-off with nblist cut-off.')

    if (universe.get('d_modelFF')[0:5].lower() == "charm"
        ):  # if we use a CHARMM force field...
        addParam('rvdw', 1.2, 'Berk: CHARMM was calibrated for 1.2 nm.')
        addParam('vdw-modifier', 'force-switch', 'Berk: specific for CHARMM.')
        addParam('rvdw-switch', 1.0, 'Berk: specific for CHARMM.')
    else:  # Default for force fields:
        addParam('rvdw', 1.0, 'Van der Waals cut-off (nm).')

    # TEMPERATURE COUPLING
    if (Type in ['NVT', 'NPT', 'MD']):
        addTitle("Temperature coupling")
        addParam('tcoupl', 'v-rescale')
        addParam('tc-grps', 'SYSTEM')
        addParam('tau-t', 0.5, 'Berk: change from 0.1 to 0.5.')
        addParam('ref-t', 300, 'Reference temp. (K) (for each group).')

    # PRESSURE COUPLING
    if (Type in ['NPT', 'MD']):
        addTitle('Pressure coupling')

        if (Type == 'NPT'):
            addParam('pcoupl', 'Berendsen', 'Use Berendsen for NPT.')
        else:
            addParam('pcoupl', 'Parrinello-Rahman')

        addParam('pcoupltype', 'isotropic', 'Uniform scaling of box.')
        addParam('tau_p', 5.0, 'Berk: better to change from 2.0 to 5.0.')
        addParam('ref_p', 1.0, 'Reference pressure (bar).')
        addParam('compressibility', 4.5e-5,
                 'Isothermal compressbility of water.')
        addParam('refcoord_scaling', 'all',
                 'Required with position restraints.')

    # PERIODIC BOUNDARY CONDITIONS
    addTitle("Periodic boundary condition")
    addParam('pbc', 'xyz', 'To keep molecule(s) in box.')

    # GENERATE VELOCITIES FOR STARTUP
    if (Type == 'NVT'):
        addTitle('Generate velocities for startup')
        addParam('gen_vel', 'yes')

    file.close()

    # PUT RELEVANT PARAMETERS IN UNIVERSE
    if (Type == 'MD'):
        universe.add('d_dt', dt)
        universe.add('d_nsteps', nsteps)
        universe.add('d_nstxout', nstxout)
Exemple #7
0
def load(fname, d_model=1, d_ALI='A', d_chain=[]):
    d_residues = []
    atomLines  = []
    with open(fname) as file:       # Read .pdb line-by-line.
        read = True                 # True if no specific MODEL specified.

        for line in file.readlines():
            # This is to make sure we only import the specified MODEL.
            if ((line[0:6]) == "MODEL "):
                if ("MODEL {:8d}".format(d_model) in line):
                    read = True
                else:
                    read = False
            
            # Get title.
            if ((line[0:6]) == "TITLE "):
                d_title = line[7:80].rstrip(); universe.add('d_title', d_title)

            # Get periodic box information (if any).
            if ((line[0:6]) == "CRYST1"):
                d_box   = line[7:80].rstrip(); universe.add('d_box', d_box)
                
            # if our line specifies an ATOM,
            if (line[0:6] == "ATOM  "):                 
                # and we are currently reading the correct MODEL,
                if (read == True):
                    # and our line contains the correct specified alternate 
                    # location specifier (if any at all),
                    if (line[16:17] in [d_ALI, " "]):
                        # and we want all chains,
                        if (d_chain == []):
                            # then load the atom.
                            atomLines.append(line)
                        # or if we want a selection of chains,
                        elif (line[21:22] in d_chain):
                            # load that selection.
                            atomLines.append(line)

    # Add one line of padding to prevent IndexError
    atomLines.append("0000000000000000000000000000000000000000000000000000")

    # Loop through lines and create list of Residue objects.
    atoms = []; ali = []; xCoord = []; yCoord = []; zCoord = []
    
    for idx in range(0, len(atomLines) - 1):
        atoms.append(atomLines[idx][12:16])
        ali.append(atomLines[idx][16:17])
        xCoord.append(float(atomLines[idx][30:38]))
        yCoord.append(float(atomLines[idx][38:46]))
        zCoord.append(float(atomLines[idx][46:54]))

        # If the resid of the next line is different, we are at end
        if (atomLines[idx][22:26] != atomLines[idx + 1][22:26]):
            # put the data in a Residue object and append to d_residues:
            d_residues.append(
                Residue
                (
                    atoms, 
                    ali,
                    atomLines[idx][17:20], 
                    atomLines[idx][21:22], 
                    int(atomLines[idx][22:26]), 
                    xCoord, 
                    yCoord,
                    zCoord
                ))
            # Reset.
            atoms = []; ali = []; xCoord = []; yCoord = []; zCoord = []

    universe.add('d_residues', d_residues)
Exemple #8
0
def add_buffer(ph_bufpdbName="",
               ph_bufitpName="",
               ph_bufqqA=[1],
               ph_bufqqB=[0],
               ph_bufMargin=2.5,
               attempts=100000):
    # This function writes a dummy .pdb containing the default buffer (ion).
    def writeDefaultPDB():
        with open("defaultBuffer.pdb", 'w') as file:
            file.write("TITLE     BUFFER PARTICLE\n")
            file.write("MODEL        1\n")
            file.write(
                "ATOM      1  NA  BUF     1     110.896   2.872  68.855  1.00  0.00            \n"
            )
            file.write("TER\n")
            file.write("ENDMDL\n")

    # This function writes the topology for the default buffer (ion).
    # Note: charge should not be 0 because then some interactions are not generated???
    # Update: this was fixed by Berk in e2c2340.
    def writeDefaultITP():
        with open("defaultBuffer.itp", 'w') as file:
            file.write("[ moleculetype ]\n")
            file.write("; molname	nrexcl\n")
            file.write("BUF		1\n\n")
            file.write("[ atoms ]\n")
            file.write(
                "; id	at type		res nr	residu name at name  cg nr	charge	 \n")
            file.write("1		SOD			1		   BUF			NA		   1		0	 \n\n")
            file.write("#ifdef POSRES_BUF\n")
            file.write("; Position restraint for each buffer ion\n")
            file.write("[ position_restraints ]\n")
            file.write(";  i funct       fcx        fcy        fcz\n")
            file.write("   1    1       1000       1000       1000\n")
            file.write("#endif\n")

    # Skip this whole step if we don't need it.
    if not (universe.get('ph_constantpH')
            and universe.get('ph_QQleveling') in [1, 2]):
        utils.update(
            "add_buffer",
            "either ph_constantpH is False or ph_QQleveling = 0 --> skipping..."
        )
        return

    # Make sure that the sum of the charges in state A and B are correct.
    if (sum(ph_bufqqA) != 1 or sum(ph_bufqqB) != 0):
        utils.warning(
            "add_buffer",
            "buffer charges incorrectly specified! sums must be 1 and 0")
        universe.get('ph_bufqqA')
        universe.get('ph_bufqqB')

    # Determine whether we use the default or a custom buffer.
    useDefault = False
    if (ph_bufpdbName == "" and ph_bufitpName == ""):
        utils.update("add_buffer", "using default (built-in) buffer...")
        useDefault = True
    elif (ph_bufpdbName == "" and ph_bufitpName != ""):
        utils.warning(
            "add_buffer",
            "ph_bufitpName not specified, resorting to default buffer!")
        useDefault = True
    elif (ph_bufpdbName != "" and ph_bufitpName == ""):
        utils.warning(
            "add_buffer",
            "ph_bufpdbName not specified, resorting to default buffer!")
        useDefault = True
    else:
        utils.update("add_buffer", "using custom buffer...")

    if (useDefault):
        # Check to make sure that the charges for the default buffer are correct.
        if (ph_bufqqA != [1] or ph_bufqqB != [0]):
            utils.warning(
                "add_buffer",
                "buffer charges incorrectly specified for default buffer!")
            universe.get('ph_bufqqA')
            universe.get('ph_bufqqB')

        # Generate the files for the default buffer and update data members.
        writeDefaultPDB()
        ph_bufpdbName = "defaultBuffer.pdb"
        writeDefaultITP()
        ph_bufitpName = "defaultBuffer.itp"

    # Get the number of buffer molecules we need.
    ph_bufnmol = 0
    for lambdaType in universe.get('ph_lambdaTypes'):
        ph_bufnmol += countRes(lambdaType.d_resname)

    utils.update(
        "add_buffer",
        "will attempt to add {0} buffer molecule(s)...".format(ph_bufnmol))

    # RUN GROMACS INSERT-MOLECULES COMMAND
    os.system(
        "touch vdwradii.dat")  # we need this dummy file for this to work.

    os.system(
        "gmx insert-molecules -f {0} -o {1}_BUF.pdb -ci {2} -nmol {3} -scale 1.0 -radius {4} -try {5} >> builder.log 2>&1"
        .format(
            universe.get('d_nameList')[-1], universe.get('d_pdbName'),
            ph_bufpdbName, ph_bufnmol, 0.5 * ph_bufMargin,
            int(attempts / ph_bufnmol)))

    os.remove("vdwradii.dat")  # clean dummy file.

    # To update d_residues.
    load("{0}_BUF.pdb".format(universe.get('d_pdbName')))

    # Give user a warning if there wasn't enough space.
    actual = countRes('BUF')
    if actual < ph_bufnmol:
        utils.warning(
            "add_buffer",
            "only {0}/{1} requested buffer molecules inserted after {2} attempts,"
            .format(actual, ph_bufnmol, attempts))
        utils.warning(
            "add_buffer",
            "try decreasing ph_bufMargin (={0}nm) or increasing d_boxMargin (={1}nm)..."
            .format(ph_bufMargin, universe.get('d_boxMargin')))
    else:
        utils.update(
            "add_buffer",
            "succesfully added {0} buffer molecule(s)...".format(actual))

    # To add buffer topology to topol.top.
    utils.update("add_buffer", "updating topology...")

    if (useDefault):
        os.remove("defaultBuffer.pdb")  # Remove dummy .pdb file.
    else:
        os.system("cp {} .".format(ph_bufitpName))  # Copy to working dir.

    topol.add_mol(os.path.basename(ph_bufitpName), "Include buffer topology",
                  'BUF', actual)

    # Set some parameters in the universe.
    universe.add('ph_bufpdbName', ph_bufpdbName)
    universe.add('ph_bufitpName', ph_bufitpName)
    universe.add('ph_bufqqA', ph_bufqqA)
    universe.add('ph_bufqqB', ph_bufqqB)
    universe.add('ph_bufMargin', ph_bufMargin)
    universe.add('ph_bufnmol', actual)

    # To update d_nameList.
    utils.add_to_nameList("{0}_BUF.pdb".format(universe.get('d_pdbName')))
Exemple #9
0
def gen_constantpH(ph_pH,
                   ph_lambdaM,
                   ph_nstout,
                   ph_barrierE,
                   cal=False,
                   lambdaInit=0.5):
    # Hardcoded stuff
    GLU_pKa = 4.25
    GLU_atoms = [' CG ', ' CD ', ' OE1', ' OE2', ' HE2']  # atoms part of model
    GLU_qqA = [-0.21, 0.75, -0.55, -0.61, 0.44]  # protonated charge
    GLU_qqB = [-0.28, 0.62, -0.76, -0.76, 0.00]  # deprotonated charge

    ASP_pKa = 3.65
    ASP_atoms = [' CB ', ' CG ', ' OD1', ' OD2', ' HD2']  # atoms part of model
    ASP_qqA = [-0.21, 0.75, -0.55, -0.61, 0.44]  # protonated charge
    ASP_qqB = [-0.28, 0.62, -0.76, -0.76, 0.00]  # deprotonated charge

    BUF_qqA = [-0.0656, 0.5328, 0.5328]
    BUF_qqB = [-0.8476, 0.4238, 0.4238]

    # Skip this entire step if ph_constantpH is false.
    if (not universe.get('ph_constantpH')):
        utils.update("gen_constantpH",
                     "ph_constantpH is False --> skipping...")
        return

    # Load dV/dl coefficients
    GLU_dvdl = universe.get('ph_GLU_dvdl')
    ASP_dvdl = universe.get('ph_ASP_dvdl')

    if (universe.get('ph_restrainpH')):
        BUF_dvdl = universe.get('ph_BUF_dvdl')

    # Check whether MD.mdp exists.
    if (not os.path.isfile("MD.mdp")):
        utils.update("gen_constantpH", "MD.mdp does not exist, creating...")
        md.gen_mdp('MD', universe.get('d_nsteps'), universe.get('d_nstxout'))

    # Check whether index.ndx exists.
    if (not os.path.isfile("index.ndx")):
        utils.update("gen_constantpH", "index.ndx does not exist, creating...")
        utils.generate_index()

    file = open('MD.mdp', 'a')

    # Formatting function.
    def addParam(name, value, comment="NUL"):
        if (comment == "NUL"):
            file.write("{:54s} = {:13s}\n".format(name, str(value)))
        else:
            file.write("{:54s} = {:13s} ; {:13s}\n".format(
                name, str(value), comment))

    file.write("\n; CONSTANT PH\n")

    # PART 1 - WRITE GENERAL PARAMETERS ########################################

    # Update user.
    utils.update("gen_constantpH", "Writing general parameters:")
    utils.update(
        "gen_constantpH",
        "ph_pH={}, ph_lambdaM={}, ph_nstout={}, ph_barrierE={}...".format(
            ph_pH, ph_lambdaM, ph_nstout, ph_barrierE))

    addParam('lambda-dynamics', 'yes')
    addParam('lambda-dynamics-simulation-ph', ph_pH)
    addParam('lambda-dynamics-lambda-particle-mass', ph_lambdaM)
    addParam('lambda-dynamics-update-nst', ph_nstout)
    addParam('lambda-dynamics-tau', 2.0)  # hardcoded

    if cal:
        addParam('lambda-dynamics-calibration', 'yes')

    if universe.get('ph_restrainpH'):
        addParam('lambda-dynamics-charge-constraints', 'yes')

    # Compile a list of acidic residues and their ResIDs.
    acidicResidueNameList = []
    acidicResidueNumberList = []
    acidicResidueTypeList = []

    for residue in universe.get('d_residues'):
        if (residue.d_resname == 'GLU'):
            acidicResidueNameList.append('GLU')
            acidicResidueNumberList.append(residue.d_resid)

        if (residue.d_resname == 'ASP'):
            acidicResidueNameList.append('ASP')
            acidicResidueNumberList.append(residue.d_resid)

    if ('GLU' in acidicResidueNameList):
        acidicResidueTypeList.append('GLU')

    if ('ASP' in acidicResidueNameList):
        acidicResidueTypeList.append('ASP')

    if universe.get('ph_restrainpH'):  # If we restrain the charge
        acidicResidueTypeList.append('BUF')  # we also have BUF.

    addParam('lambda-dynamics-number-lambda-residues',
             len(acidicResidueTypeList))

    if universe.get('ph_restrainpH'):
        addParam('lambda-dynamics-number-atom-collections',
                 len(acidicResidueNameList) + 1)
    else:
        addParam('lambda-dynamics-number-atom-collections',
                 len(acidicResidueNameList))

    file.write('\n')

    # print(acidicResidueNameList)   # debug
    # print(acidicResidueNumberList) # debug
    # print(acidicResidueTypeList)   # debug

    # PART 2 - WRITE RESIDUE-TYPE SPECIFIC STUFF ###############################

    utils.update("gen_constantpH", "Writing residue-type specific stuff...")

    def writeBlock(number, name, dvdl, pKa, ph_barrierE, qqA, qqB):
        def to_string(Input):
            string = ""
            for element in Input:
                string += "{:.3f}".format(element)
                string += ' '
            return string

        addParam('lambda-dynamics-residue%s-name' % (number), name)
        addParam('lambda-dynamics-residue%s-dvdl-coefficients' % (number),
                 to_string(dvdl))
        addParam('lambda-dynamics-residue%s-reference-pka' % (number), pKa)
        addParam('lambda-dynamics-residue%s-barrier' % (number), ph_barrierE)
        addParam('lambda-dynamics-residue%s-charges-state-A' % (number),
                 to_string(qqA))
        addParam('lambda-dynamics-residue%s-charges-state-B' % (number),
                 to_string(qqB))

        file.write('\n')

    for idx in range(0, len(acidicResidueTypeList)):
        if (acidicResidueTypeList[idx] == 'GLU'):
            writeBlock(idx + 1, 'GLU', GLU_dvdl, GLU_pKa, ph_barrierE, GLU_qqA,
                       GLU_qqB)

        if (acidicResidueTypeList[idx] == 'ASP'):
            writeBlock(idx + 1, 'ASP', ASP_dvdl, ASP_pKa, ph_barrierE, ASP_qqA,
                       ASP_qqB)

        if (acidicResidueTypeList[idx] == 'BUF'):
            # If number of protonatable residues != number of buffer molecules,
            # we need to increase buffer charge in state A by the ratio:
            nLams = protein.countRes('ASP') + protein.countRes('GLU')
            nBufs = universe.get('ph_bufnmol')
            BUF_qqA = [(nLams / float(nBufs)) * i for i in BUF_qqA]

            writeBlock(idx + 1, 'BUF', BUF_dvdl, 0, 0, BUF_qqA, BUF_qqB)

    # PART 3 - WRITE INDIVIDUAL RESIDUE/LAMBDA-GROUP STUF ######################

    utils.update("gen_constantpH", "Writing individual lambda groups...")

    def writeResBlock(number, name, indexLambda, indexName):
        addParam('lambda-dynamics-atom-set%s-name' % (number), name)
        addParam('lambda-dynamics-atom-set%s-lambda-residues-index' % (number),
                 indexLambda)
        addParam('lambda-dynamics-atom-set%s-index-group-name' % (number),
                 indexName)
        addParam('lambda-dynamics-atom-set%s-initial-lambda' % (number),
                 lambdaInit)

        if universe.get('ph_restrainpH'):
            addParam(
                'lambda-dynamics-atom-set%s-charge-restraint-group-index' %
                (number), 1)

        if (name == 'BUF'):
            addParam('lambda-dynamics-atom-set%s-buffer-residue' % (number),
                     'yes')
            addParam(
                'lambda-dynamics-atom-set%s-buffer-residue-multiplier' %
                (number), universe.get('ph_bufnmol'))

        file.write('\n')

    for idx in range(0, len(acidicResidueNameList)):
        writeResBlock(
            idx + 1, acidicResidueNameList[idx],
            acidicResidueTypeList.index(acidicResidueNameList[idx]) + 1,
            'LAMBDA%s' % (idx + 1))

    if universe.get('ph_restrainpH'):
        writeResBlock(
            len(acidicResidueNameList) + 1, 'BUF',
            acidicResidueTypeList.index('BUF') + 1,
            'LAMBDA%s' % (len(acidicResidueNameList) + 1))

    file.close()  # MD.mdp

    # PART 4 - APPEND THE LAMBDA INDEX GROUPS TO INDEX.NDX #####################

    utils.update("gen_constantpH",
                 "Writing lambda index groups to index.ndx...")

    file = open('index.ndx', 'a')  # Append to existing index.ndx

    # Function for adding an indexList to index.ndx
    def writeTheGroup(number, indexList):
        file.write('\n[ LAMBDA{} ]\n'.format(number))
        for index in indexList:
            file.write('{} '.format(index))
        file.write('\n')

    atomCount = 1  # Keeps track of the atom number.
    grpNum = 1  # Keeps track of the group (the LAMBDA%s).

    for residue in universe.get('d_residues'):  # loop through all residues

        indexList = []  # clear indexList

        for atom in residue.d_atoms:  # for each residue, loop through the atoms

            if (residue.d_resname == 'GLU' and atom in GLU_atoms):
                indexList.append(atomCount)

            elif (residue.d_resname == 'ASP' and atom in ASP_atoms):
                indexList.append(atomCount)

            atomCount += 1  # increment atomcount

        if (len(indexList) > 0):
            writeTheGroup(grpNum, indexList)
            grpNum += 1

    if universe.get('ph_restrainpH'):

        atomCount = 1
        indexList = []

        for residue in universe.get('d_residues'):
            for atom in residue.d_atoms:

                if (residue.d_resname == 'BUF'):
                    indexList.append(atomCount)

                atomCount += 1

        writeTheGroup(grpNum, indexList)

    file.close()  # index.ndx

    # Put relevant pH variables in universe
    universe.add('ph_pH', ph_pH)
    universe.add('ph_lambdaM', ph_lambdaM)
    universe.add('ph_nstout', ph_nstout)
    universe.add('ph_barrierE', ph_barrierE)
Exemple #10
0
def gen_constantpH(ph_pH, ph_lambdaM, ph_nstout, ph_barrierE, cal=False, lambdaInit=0.5):
    # Skip this entire step if ph_constantpH is false.
    if (not universe.get('ph_constantpH')):
        utils.update("gen_constantpH", "ph_constantpH is False --> skipping...")
        return

    # Check whether MD.mdp exists and create if necessary.
    if (not os.path.isfile("MD.mdp")):
        utils.update("gen_constantpH", "MD.mdp does not exist, creating...")
        md.gen_mdp('MD', universe.get('d_nsteps'), universe.get('d_nstxout'))

    # Check whether index.ndx exists, and create if necessary.
    if (not os.path.isfile("index.ndx")):
        utils.update("gen_constantpH", "index.ndx does not exist, creating...")
        utils.generate_index()

    ############################################################################
    file = open('MD.mdp', 'a')

    def addParam(name, value): # Formatting function.
            file.write("{:54s} = {:13s}\n".format(name, str(value)))

    # Update user.
    utils.update("gen_constantpH", "Writing general parameters:")
    utils.update("gen_constantpH", "ph_pH={}, ph_lambdaM={}, ph_nstout={}, ph_barrierE={}...".format(ph_pH, ph_lambdaM, ph_nstout, ph_barrierE))

    # PART 1 - WRITE GENERAL PARAMETERS ########################################
    file.write("\n; CONSTANT PH\n")

    addParam('lambda-dynamics', 'yes')
    addParam('lambda-dynamics-simulation-ph', ph_pH)
    addParam('lambda-dynamics-lambda-particle-mass', ph_lambdaM)
    addParam('lambda-dynamics-update-nst', ph_nstout)
    addParam('lambda-dynamics-tau', 2.0) # hardcoded

    if cal: # If we are in calibration mode:
        addParam('lambda-dynamics-calibration', 'yes')

    # If we use "charge-constraining" (2) scheme:
    if (universe.get('ph_QQleveling') == 2):
        addParam('lambda-dynamics-charge-constraints', 'yes')

    # Gather a list of the different lambda residue-type names that were 
    # specified IN THE UNIVERSE by the user.
    lambdaTypeNamesSpecifed = []
    for obj in universe.get('ph_lambdaTypes'):
        lambdaTypeNamesSpecifed.append(obj.d_resname)

    # Gather a list of the names of the lambda residues in the protein.
    lambdaResidueNameList = []
    for residue in universe.get('d_residues'):
        if (residue.d_resname in lambdaTypeNamesSpecifed):
            lambdaResidueNameList.append(residue.d_resname)

    # Gather a list of the names of the lambda residue-types IN THE PROTEIN,
    # And make sure the order is the same as in ph_lambdaTypes = LambdaTypeNamesSpecified.
    lambdaResidueTypeList = []
    for obj in universe.get('ph_lambdaTypes'):
        if (obj.d_resname in set(lambdaResidueNameList)):
            lambdaResidueTypeList.append(obj.d_resname)

    # If we use the charge leveling scheme "charge-restraining" (2) we also have
    # the BUF residue-type as well as one extra lambda group containing all the BUFs.
    if (universe.get('ph_QQleveling') == 2):
        addParam('lambda-dynamics-number-lambda-residues', len(lambdaResidueTypeList) + 1)
        addParam('lambda-dynamics-number-atom-collections', len(lambdaResidueNameList) + 1)
    else:
        addParam('lambda-dynamics-number-lambda-residues', len(lambdaResidueTypeList))
        addParam('lambda-dynamics-number-atom-collections', len(lambdaResidueNameList))

    file.write('\n')

    # PART 2 - WRITE RESIDUE-TYPE SPECIFIC STUFF ###############################
    def writeBlock(number, name, dvdl, pKa, ph_barrierE, qqA, qqB):
        def to_string(Input):
            string = ""
            for element in Input:
                string += "{:.3f} ".format(element)
            return string

        addParam('lambda-dynamics-residue%s-name'              % (number), name)
        addParam('lambda-dynamics-residue%s-dvdl-coefficients' % (number), to_string(dvdl))
        addParam('lambda-dynamics-residue%s-reference-pka'     % (number), pKa)
        addParam('lambda-dynamics-residue%s-barrier'           % (number), ph_barrierE)
        addParam('lambda-dynamics-residue%s-charges-state-A'   % (number), to_string(qqA))
        addParam('lambda-dynamics-residue%s-charges-state-B'   % (number), to_string(qqB))

        file.write('\n')

    # If we use a charge-leveling scheme, we need the buffer charge states.
    # These are added to universe when you run protein.add_buffer()
    if (universe.get('ph_QQleveling') in [1, 2]):
        BUF_qqA = universe.get('ph_bufqqA') # BUF_qqA = [-0.0656, 0.5328, 0.5328] previously hardcoded for water buffer
        BUF_qqB = universe.get('ph_bufqqB') # BUF_qqB = [-0.8476, 0.4238, 0.4238] previously hardcoded for water buffer

    idx = 1
    for obj in universe.get('ph_lambdaTypes'):
        # This if-statement prevents writing a block when there are no residues of this type.
        if (obj.d_resname in lambdaResidueTypeList):
            # If we use "charge-coupling" (1) scheme, extend the charge states:
            if (universe.get('ph_QQleveling') == 1):
                writeBlock(idx, obj.d_resname, obj.d_dvdl[::-1], obj.d_pKa, ph_barrierE, obj.d_qqA + BUF_qqB, obj.d_qqB + BUF_qqA)
            else:
                writeBlock(idx, obj.d_resname, obj.d_dvdl[::-1], obj.d_pKa, ph_barrierE, obj.d_qqA, obj.d_qqB)
            idx += 1

    if (universe.get('ph_QQleveling') == 2):
        # f we use "charge-constraining" (2) scheme, we additionaly need ph_BUF_dvdl.
        writeBlock(idx, 'BUF', universe.get('ph_BUF_dvdl')[::-1], 0, 0, BUF_qqA, BUF_qqB)

    # PART 3 - WRITE INDIVIDUAL RESIDUE/LAMBDA-GROUP STUF ######################
    def writeResBlock(number, name, indexLambda, indexName):
        addParam('lambda-dynamics-atom-set%s-name'                  % (number), name)
        addParam('lambda-dynamics-atom-set%s-lambda-residues-index' % (number), indexLambda)
        addParam('lambda-dynamics-atom-set%s-index-group-name'      % (number), indexName)
        addParam('lambda-dynamics-atom-set%s-initial-lambda'        % (number), lambdaInit)
        
        if (universe.get('ph_QQleveling') == 2):
            addParam('lambda-dynamics-atom-set%s-charge-restraint-group-index' % (number), 1)

        if (name == 'BUF'):
            addParam('lambda-dynamics-atom-set%s-buffer-residue' % (number), 'yes')
            addParam('lambda-dynamics-atom-set%s-buffer-residue-multiplier' % (number), universe.get('ph_bufnmol'))

        file.write('\n')

    utils.update("gen_constantpH", "Writing individual lambda groups...")

    idx = 1
    for name in lambdaResidueNameList:
        writeResBlock(idx, name, lambdaResidueTypeList.index(name) + 1, 'LAMBDA{}'.format(idx))
        idx += 1

    if (universe.get('ph_QQleveling') == 2):
        writeResBlock(idx, 'BUF', len(lambdaResidueTypeList) + 1, 'LAMBDA{}'.format(len(lambdaResidueNameList) + 1))

    file.close() # MD.mdp

    # PART 4 - APPEND THE LAMBDA INDEX GROUPS TO INDEX.NDX #####################
    utils.update("gen_constantpH", "Writing lambda index groups to index.ndx...")

    # If we use a charge-leveling scheme, we need a list of atomIndices of the BUFs:
    if (universe.get('ph_QQleveling') in [1, 2]):
        bufferAtomIndexList = []

        count = 1
        for residue in universe.get('d_residues'):
            for atom in residue.d_atoms:
                if (residue.d_resname == 'BUF'):
                    bufferAtomIndexList.append(count)

                count += 1

    file = open('index.ndx', 'a') # Append to existing index.ndx

    def writeTheGroup(number, atomIndexList):
        file.write('\n[ LAMBDA{} ]\n'.format(number))
        for index in atomIndexList:
            file.write('{} '.format(index))
        file.write('\n')

    ph_lambdaTypes = universe.get('ph_lambdaTypes')

    atomCount = 1; groupNumber = 1
    for residue in universe.get('d_residues'):
        if residue.d_resname in lambdaResidueTypeList:

            atomIndexList = []
            obj = ph_lambdaTypes[lambdaTypeNamesSpecifed.index(residue.d_resname)]

            for atom in residue.d_atoms:
                if atom in obj.d_atoms:
                    atomIndexList.append(atomCount)

                atomCount += 1

            # If we use "charge-coupling" (1), assign the atomIndices of one BUF
            # to one protonatable lambda residue (use clever list slicing):            
            if (universe.get('ph_QQleveling') == 1):
                start = (groupNumber - 1) * len(BUF_qqA)
                stop  = start + len(BUF_qqA)
                atomIndexList += bufferAtomIndexList[start:stop]                

            writeTheGroup(groupNumber, atomIndexList)
            groupNumber += 1

        else:
            for atom in residue.d_atoms:
                atomCount += 1

    # If we use "charge-restraining" (2), add everything in bufferAtomIndexList
    # to the last lambda index group:
    if (universe.get('ph_QQleveling') == 2):
        writeTheGroup(groupNumber, bufferAtomIndexList)

    file.close() # index.ndx

    # Put relevant pH variables in universe
    universe.add('ph_pH', ph_pH)
    universe.add('ph_lambdaM', ph_lambdaM)
    universe.add('ph_nstout', ph_nstout)
    universe.add('ph_barrierE', ph_barrierE)