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])
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')))
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')))
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')))
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')))
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)
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)
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')))
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)
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)