def counterions(structure,top,includes=None,ff_includes=None,gro='counterions'): """ counterions(structure,top) Standard procedure for adding counterions. The resname must be understandable by "r RESNAME" in make_ndx and writes to the top file. """ #---we store the water resname in the wordspace as "sol" resname = wordspace.get('sol','SOL') #---clean up the composition in case this is a restart for key in ['cation','anion',resname]: try: wordspace['composition'].pop(zip(*wordspace['composition'])[0].index(wordspace[key])) except: pass component(resname,count=wordspace['water_without_ions']) #---write the topology file as of the solvate step instead of copying them (genion overwrites top) write_top('counterions.top') gmx('grompp',base='genion',structure=structure, top='counterions',mdp='input-em-steep-in', log='grompp-genion') gmx('make_ndx',structure=structure,ndx='solvate-waters', inpipe='keep 0\nr %s\nkeep 1\nq\n'%resname, log='make-ndx-counterions-check') gmx('genion',base='genion',gro=gro,ndx='solvate-waters', cation=wordspace['cation'],anion=wordspace['anion'], flag='-conc %f -neutral'%wordspace['ionic_strength'], log='genion') with open(wordspace['step']+'log-genion','r') as fp: lines = fp.readlines() declare_ions = filter(lambda x:re.search('Will try',x)!=None,lines).pop() ion_counts = re.findall( '^Will try to add ([0-9]+)\+?\-? ([\w\+\-]+) ions and ([0-9]+) ([\w\+\-]+) ions', declare_ions).pop() for ii in range(2): component(ion_counts[2*ii+1],count=ion_counts[2*ii]) component(resname,count=component(resname)-component(ion_counts[1])-component(ion_counts[3])) if includes: if type(includes)==str: includes = [includes] for i in includes: include(i) if ff_includes: if type(ff_includes)==str: ff_includes = [ff_includes] for i in ff_includes: include(i,ff=True) write_top('counterions.top')
def makeshape(): """ Generate the midplane points for various bilayer shapes. """ shape = wordspace['shape'] lz = wordspace['solvent_thickness'] binsize = wordspace['binsize'] #---are the monolayers symmetric? monolayer_other = wordspace.get('monolayer_other',None) composition_other = wordspace.get('composition_other',None) if not monolayer_other and composition_other or not composition_other and monolayer_other: raise Exception('you must specify both "monolayer other" and "composition other"') monolayers = [[wordspace['monolayer_top'],wordspace['composition_top']]] if monolayer_other: monolayers += [[monolayer_other,composition_other]] else: monolayers += [monolayers[0]] #---compute spots on the 2D grid where we will place the lipids #---! note that we are still in 2D so the grid-spacing is only accurate for the flat bilayer spots,vecs = zip(*[random_lipids(total,composition,binsize) for total,composition in monolayers]) #---for asymmetric bilayers we choose the larger set of box vectors vecs = transpose([max(i) for i in transpose(vecs)]) pts = [np.concatenate(([s[:,0]],[s[:,1]],[np.zeros(len(s))])).T for s in spots] #---! non-flat points will be stretched in Z hence not evenly spaced in 3-space #---! needs 2 monolayers if shape == 'saddle': def bump(x,y,x0,y0,height,width): """ General function for producing a 2D Gaussian "dimple" or "bump". """ zs = height*np.exp(-(x-x0)**2/2/width**2)*\ np.exp(-(y-y0)**2/2/width**2) return zs offsets = vecs[:2]/2.-2. bumpspots = [(0,1,1),(0,-1,1),(1,0,-1),(-1,0,-1)] for spot in bumpspots: pts[:,2] += bump(xys[:,0],xys[:,1], x0+offsets[0]*spot[0],y0+offsets[1]*spot[1], height*spot[2],width) ptsmid = np.concatenate(np.reshape(pts,(3*m,3*n,3))[m:2*m,n:2*n]) ptsmid[:,0]-=vecs[0] ptsmid[:,1]-=vecs[1] if 0: meshplot(ptsmid,show='surf') #---! needs 2 monolayers elif shape == 'buckle': def buckle(x,y,height): zs = height*np.sin(x*2*pi/lx) return zs pts[:,2] += buckle(xys[:,0],xys[:,1],height) elif shape == 'flat': pts = [p+0 for p in pts] else: raise Exception('\n[ERROR] unclear bilayer topography: %s'%shape) #---previously used PBCs and selected the middle tile here before makemesh and then shifted to origin monolayer_meshes = [makemesh(p,vecs,debug=False,curvilinear=False) for p in pts] return pts,monolayer_meshes,array([v for v in vecs]+[lz])
def makeshape(): """ Generate the midplane points for various bilayer shapes. """ shape = wordspace['shape'] lz = wordspace['solvent_thickness'] binsize = wordspace['binsize'] #---are the monolayers symmetric? monolayer_other = wordspace.get('monolayer_other', None) composition_other = wordspace.get('composition_other', None) if not monolayer_other and composition_other or not composition_other and monolayer_other: raise Exception( 'you must specify both "monolayer other" and "composition other"') monolayers = [[wordspace['monolayer_top'], wordspace['composition_top']]] if monolayer_other: monolayers += [[monolayer_other, composition_other]] else: monolayers += [monolayers[0]] #---compute spots on the 2D grid where we will place the lipids #---! note that we are still in 2D so the grid-spacing is only accurate for the flat bilayer spots, vecs = zip(*[ random_lipids(total, composition, binsize) for total, composition in monolayers ]) #---for asymmetric bilayers we choose the larger set of box vectors vecs = transpose([max(i) for i in transpose(vecs)]) pts = [ np.concatenate(([s[:, 0]], [s[:, 1]], [np.zeros(len(s))])).T for s in spots ] #---! non-flat points will be stretched in Z hence not evenly spaced in 3-space #---! needs 2 monolayers if shape == 'saddle': def bump(x, y, x0, y0, height, width): """ General function for producing a 2D Gaussian "dimple" or "bump". """ zs = height*np.exp(-(x-x0)**2/2/width**2)*\ np.exp(-(y-y0)**2/2/width**2) return zs offsets = vecs[:2] / 2. - 2. bumpspots = [(0, 1, 1), (0, -1, 1), (1, 0, -1), (-1, 0, -1)] for spot in bumpspots: pts[:, 2] += bump(xys[:, 0], xys[:, 1], x0 + offsets[0] * spot[0], y0 + offsets[1] * spot[1], height * spot[2], width) ptsmid = np.concatenate( np.reshape(pts, (3 * m, 3 * n, 3))[m:2 * m, n:2 * n]) ptsmid[:, 0] -= vecs[0] ptsmid[:, 1] -= vecs[1] if 0: meshplot(ptsmid, show='surf') #---! needs 2 monolayers elif shape == 'buckle': def buckle(x, y, height): zs = height * np.sin(x * 2 * pi / lx) return zs pts[:, 2] += buckle(xys[:, 0], xys[:, 1], height) elif shape == 'flat': pts = [p + 0 for p in pts] else: raise Exception('\n[ERROR] unclear bilayer topography: %s' % shape) #---previously used PBCs and selected the middle tile here before makemesh and then shifted to origin monolayer_meshes = [ makemesh(p, vecs, debug=False, curvilinear=False) for p in pts ] return pts, monolayer_meshes, array([v for v in vecs] + [lz])
def trim_waters(structure='solvate-dense',gro='solvate', gap=3,boxvecs=None,method='aamd',boxcut=True): """ trim_waters(structure='solvate-dense',gro='solvate',gap=3,boxvecs=None) Remove waters within a certain number of Angstroms of the protein. #### water and all (water and (same residue as water within 10 of not water)) note that we vided the solvate.gro as a default so this can be used with any output gro file """ use_vmd = wordspace.get('use_vmd',False) if (gap != 0.0 or boxcut) and use_vmd: if method == 'aamd': watersel = "water" elif method == 'cgmd': watersel = "resname %s"%wordspace.sol else: raise Exception("\n[ERROR] unclear method %s"%method) #---! gap should be conditional and excluded if zero vmdtrim = [ 'package require pbctools', 'mol new %s.gro'%structure, 'set sel [atomselect top \"(all not ('+\ '%s and (same residue as %s and within '%(watersel,watersel)+str(gap)+\ ' of not %s)))'%watersel] #---box trimming is typical for e.g. atomstic protein simulations but discards anything outside if boxcut: vmdtrim += [' and '+\ 'same residue as (x>=0 and x<='+str(10*boxvecs[0])+\ ' and y>=0 and y<= '+str(10*boxvecs[1])+\ ' and z>=0 and z<= '+str(10*boxvecs[2])+')'] vmdtrim += ['"]','$sel writepdb %s-vmd.pdb'%gro,'exit',] with open(wordspace['step']+'script-vmd-trim.tcl','w') as fp: for line in vmdtrim: fp.write(line+'\n') vmdlog = open(wordspace['step']+'log-script-vmd-trim','w') #---previously used os.environ['VMDNOCUDA'] = "1" but this was causing segfaults on green p = subprocess.Popen('VMDNOCUDA=1 '+gmxpaths['vmd']+' -dispdev text -e script-vmd-trim.tcl', stdout=vmdlog,stderr=vmdlog,cwd=wordspace['step'],shell=True,executable='/bin/bash') p.communicate() with open(wordspace['bash_log'],'a') as fp: fp.write(gmxpaths['vmd']+' -dispdev text -e script-vmd-trim.tcl &> log-script-vmd-trim\n') gmx_run(gmxpaths['editconf']+' -f %s-vmd.pdb -o %s.gro -resnr 1'%(gro,gro), log='editconf-convert-vmd') #---scipy is more reliable than VMD elif gap != 0.0 or boxcut: import scipy import scipy.spatial import numpy as np #---if "sol" is not in the wordspace we assume this is atomistic and use the standard "SOL" watersel = wordspace.get('sol','SOL') incoming = read_gro(structure+'.gro') #---remove waters that are near not-waters is_water = np.array(incoming['residue_names'])==watersel is_not_water = np.array(incoming['residue_names'])!=watersel water_inds = np.where(is_water)[0] not_water_inds = np.where(np.array(incoming['residue_names'])!=watersel)[0] points = np.array(incoming['points']) residue_indices = np.array(incoming['residue_indices']) if gap>0: #---previous method used clumsy/slow cdist if False: #---! needs KDTree optimization dists = scipy.spatial.distance.cdist(points[water_inds],points[not_water_inds]) #---list of residue indices in is_water that have at least one atom with an overlap excludes = np.array(incoming['residue_indices'])[is_water][ np.where(np.any(dists<=gap/10.0,axis=1))[0]] #---collect waters not found in the excludes list of residues that overlap with not-water #---note that this command fails on redundant residues #---this was deprecated because it wasn't working correctly with the new KDTree method below surviving_water = np.all((np.all(( np.tile(excludes,(len(residue_indices),1))!=np.tile(residue_indices,(len(excludes),1)).T), axis=1),is_water),axis=0) #---use scipy KDTree to find atom names inside the gap #---note that order matters: we wish to find waters too close to not_waters close_dists,neighbors = scipy.spatial.KDTree(points[water_inds]).query(points[not_water_inds],distance_upper_bound=gap/10.0) #---use the distances to find the residue indices for waters that are too close excludes = np.array(incoming['residue_indices'])[is_water][np.where(close_dists<=gap/10.0)[0]] #---get residues that are water and in the exclude list #---note that the following step might be slow exclude_res = [ii for ii,i in enumerate(incoming['residue_indices']) if i in excludes and is_water[ii]] #---copy the array that marks the waters surviving_water = np.array(is_water) #---remove waters that are on the exclude list surviving_water[exclude_res] = False else: excludes = np.array([]) surviving_water = np.ones(len(residue_indices)).astype(bool) #---we must remove waters that lie outside the box if there is a boxcut insiders = np.ones(len(points)).astype(bool) if boxcut: #---remove waters that lie outside the box #---get points that are outside of the box outsiders = np.any([np.any((points[:,ii]<0,points[:,ii]>i),axis=0) for ii,i in enumerate(boxvecs)],axis=0) #---get residue numbers for the outsiders outsiders_res = np.array(incoming['residue_indices'])[np.where(outsiders)[0]] #---note that this is consonant with the close-water exclude step above (and also may be slow) exclude_outsider_res = [ii for ii,i in enumerate(incoming['residue_indices']) if i in outsiders_res] insiders[exclude_outsider_res] = False surviving_indices = np.any((is_not_water,np.all((surviving_water,insiders),axis=0)),axis=0) lines = incoming['lines'] lines = lines[:2]+list(np.array(incoming['lines'][2:-1])[surviving_indices])+lines[-1:] xyzs = list(points[surviving_indices]) write_gro(lines=lines,xyzs=xyzs,output_file=wordspace.step+'%s.gro'%gro) else: filecopy(wordspace['step']+'%s-dense.gro'%gro,wordspace['step']+'%s.gro'%gro)
def counterions(structure, top, includes=None, ff_includes=None, gro='counterions'): """ counterions(structure,top) Standard procedure for adding counterions. The resname must be understandable by "r RESNAME" in make_ndx and writes to the top file. """ #---we store the water resname in the wordspace as "sol" resname = wordspace.get('sol', 'SOL') #---clean up the composition in case this is a restart for key in ['cation', 'anion', resname]: try: wordspace['composition'].pop( zip(*wordspace['composition'])[0].index(wordspace[key])) except: pass component(resname, count=wordspace['water_without_ions']) #---write the topology file as of the solvate step instead of copying them (genion overwrites top) write_top('counterions.top') gmx('grompp', base='genion', structure=structure, top='counterions', mdp='input-em-steep-in', log='grompp-genion') gmx('make_ndx', structure=structure, ndx='solvate-waters', inpipe='keep 0\nr %s\nkeep 1\nq\n' % resname, log='make-ndx-counterions-check') gmx('genion', base='genion', gro=gro, ndx='solvate-waters', cation=wordspace['cation'], anion=wordspace['anion'], flag='-conc %f -neutral' % wordspace['ionic_strength'], log='genion') with open(wordspace['step'] + 'log-genion', 'r') as fp: lines = fp.readlines() declare_ions = filter(lambda x: re.search('Will try', x) != None, lines).pop() ion_counts = re.findall( '^Will try to add ([0-9]+)\+?\-? ([\w\+\-]+) ions and ([0-9]+) ([\w\+\-]+) ions', declare_ions).pop() for ii in range(2): component(ion_counts[2 * ii + 1], count=ion_counts[2 * ii]) component(resname, count=component(resname) - component(ion_counts[1]) - component(ion_counts[3])) if includes: if type(includes) == str: includes = [includes] for i in includes: include(i) if ff_includes: if type(ff_includes) == str: ff_includes = [ff_includes] for i in ff_includes: include(i, ff=True) write_top('counterions.top')
def trim_waters(structure='solvate-dense', gro='solvate', gap=3, boxvecs=None, method='aamd', boxcut=True): """ trim_waters(structure='solvate-dense',gro='solvate',gap=3,boxvecs=None) Remove waters within a certain number of Angstroms of the protein. #### water and all (water and (same residue as water within 10 of not water)) note that we vided the solvate.gro as a default so this can be used with any output gro file """ use_vmd = wordspace.get('use_vmd', False) if (gap != 0.0 or boxcut) and use_vmd: if method == 'aamd': watersel = "water" elif method == 'cgmd': watersel = "resname %s" % wordspace.sol else: raise Exception("\n[ERROR] unclear method %s" % method) #---! gap should be conditional and excluded if zero vmdtrim = [ 'package require pbctools', 'mol new %s.gro'%structure, 'set sel [atomselect top \"(all not ('+\ '%s and (same residue as %s and within '%(watersel,watersel)+str(gap)+\ ' of not %s)))'%watersel] #---box trimming is typical for e.g. atomstic protein simulations but discards anything outside if boxcut: vmdtrim += [' and '+\ 'same residue as (x>=0 and x<='+str(10*boxvecs[0])+\ ' and y>=0 and y<= '+str(10*boxvecs[1])+\ ' and z>=0 and z<= '+str(10*boxvecs[2])+')'] vmdtrim += [ '"]', '$sel writepdb %s-vmd.pdb' % gro, 'exit', ] with open(wordspace['step'] + 'script-vmd-trim.tcl', 'w') as fp: for line in vmdtrim: fp.write(line + '\n') vmdlog = open(wordspace['step'] + 'log-script-vmd-trim', 'w') #---previously used os.environ['VMDNOCUDA'] = "1" but this was causing segfaults on green p = subprocess.Popen('VMDNOCUDA=1 ' + gmxpaths['vmd'] + ' -dispdev text -e script-vmd-trim.tcl', stdout=vmdlog, stderr=vmdlog, cwd=wordspace['step'], shell=True, executable='/bin/bash') p.communicate() with open(wordspace['bash_log'], 'a') as fp: fp.write( gmxpaths['vmd'] + ' -dispdev text -e script-vmd-trim.tcl &> log-script-vmd-trim\n' ) gmx_run(gmxpaths['editconf'] + ' -f %s-vmd.pdb -o %s.gro -resnr 1' % (gro, gro), log='editconf-convert-vmd') #---scipy is more reliable than VMD elif gap != 0.0 or boxcut: import scipy import scipy.spatial import numpy as np #---if "sol" is not in the wordspace we assume this is atomistic and use the standard "SOL" watersel = wordspace.get('sol', 'SOL') incoming = read_gro(structure + '.gro') #---remove waters that are near not-waters is_water = np.array(incoming['residue_names']) == watersel is_not_water = np.array(incoming['residue_names']) != watersel water_inds = np.where(is_water)[0] not_water_inds = np.where( np.array(incoming['residue_names']) != watersel)[0] points = np.array(incoming['points']) residue_indices = np.array(incoming['residue_indices']) if gap > 0: #---previous method used clumsy/slow cdist if False: #---! needs KDTree optimization dists = scipy.spatial.distance.cdist(points[water_inds], points[not_water_inds]) #---list of residue indices in is_water that have at least one atom with an overlap excludes = np.array( incoming['residue_indices'])[is_water][np.where( np.any(dists <= gap / 10.0, axis=1))[0]] #---collect waters not found in the excludes list of residues that overlap with not-water #---note that this command fails on redundant residues #---this was deprecated because it wasn't working correctly with the new KDTree method below surviving_water = np.all((np.all( (np.tile(excludes, (len(residue_indices), 1)) != np.tile( residue_indices, (len(excludes), 1)).T), axis=1), is_water), axis=0) #---use scipy KDTree to find atom names inside the gap #---note that order matters: we wish to find waters too close to not_waters close_dists, neighbors = scipy.spatial.KDTree( points[water_inds]).query(points[not_water_inds], distance_upper_bound=gap / 10.0) #---use the distances to find the residue indices for waters that are too close excludes = np.array( incoming['residue_indices'])[is_water][np.where( close_dists <= gap / 10.0)[0]] #---get residues that are water and in the exclude list #---note that the following step might be slow exclude_res = [ ii for ii, i in enumerate(incoming['residue_indices']) if i in excludes and is_water[ii] ] #---copy the array that marks the waters surviving_water = np.array(is_water) #---remove waters that are on the exclude list surviving_water[exclude_res] = False else: excludes = np.array([]) surviving_water = np.ones(len(residue_indices)).astype(bool) #---we must remove waters that lie outside the box if there is a boxcut insiders = np.ones(len(points)).astype(bool) if boxcut: #---remove waters that lie outside the box #---get points that are outside of the box outsiders = np.any([ np.any((points[:, ii] < 0, points[:, ii] > i), axis=0) for ii, i in enumerate(boxvecs) ], axis=0) #---get residue numbers for the outsiders outsiders_res = np.array( incoming['residue_indices'])[np.where(outsiders)[0]] #---note that this is consonant with the close-water exclude step above (and also may be slow) exclude_outsider_res = [ ii for ii, i in enumerate(incoming['residue_indices']) if i in outsiders_res ] insiders[exclude_outsider_res] = False surviving_indices = np.any( (is_not_water, np.all((surviving_water, insiders), axis=0)), axis=0) lines = incoming['lines'] lines = lines[:2] + list( np.array(incoming['lines'][2:-1])[surviving_indices]) + lines[-1:] xyzs = list(points[surviving_indices]) write_gro(lines=lines, xyzs=xyzs, output_file=wordspace.step + '%s.gro' % gro) else: filecopy(wordspace['step'] + '%s-dense.gro' % gro, wordspace['step'] + '%s.gro' % gro)