def build(mol, topo=None, param=None, stream=None, prefix='structure', outdir='./build', caps=None, ionize=True, saltconc=0, saltanion=None, saltcation=None, disulfide=None, patches=None, noregen=None, aliasresidues=None, psfgen=None, execute=True, _clean=True): """ Builds a system for CHARMM Uses VMD and psfgen to build a system for CHARMM. Additionally it allows for ionization and adding of disulfide bridges. Parameters ---------- mol : :class:`Molecule <moleculekit.molecule.Molecule>` object The Molecule object containing the system topo : list of str A list of topology `rtf` files. Use :func:`charmm.listFiles <htmd.builder.charmm.listFiles>` to get a list of available topology files. Default: ['top/top_all36_prot.rtf', 'top/top_all36_lipid.rtf', 'top/top_water_ions.rtf'] param : list of str A list of parameter `prm` files. Use :func:`charmm.listFiles <htmd.builder.charmm.listFiles>` to get a list of available parameter files. Default: ['par/par_all36_prot_mod.prm', 'par/par_all36_lipid.prm', 'par/par_water_ions.prm'] stream : list of str A list of stream `str` files containing topologies and parameters. Use :func:`charmm.listFiles <htmd.builder.charmm.listFiles>` to get a list of available stream files. Default: ['str/prot/toppar_all36_prot_arg0.str'] prefix : str The prefix for the generated pdb and psf files outdir : str The path to the output directory Default: './build' caps : dict A dictionary with keys segids and values lists of strings describing the caps of that segment. e.g. caps['P'] = ['first ACE', 'last CT3'] or caps['P'] = ['first none', 'last none']. Default: will apply ACE and CT3 caps to proteins and none caps to the rest. ionize : bool Enable or disable ionization saltconc : float Salt concentration (in Molar) to add to the system after neutralization. saltanion : {'CLA'} The anion type. Please use only CHARMM ion atom names. saltcation : {'SOD', 'MG', 'POT', 'CES', 'CAL', 'ZN2'} The cation type. Please use only CHARMM ion atom names. disulfide : list of pairs of atomselection strings If None it will guess disulfide bonds. Otherwise provide a list pairs of atomselection strings for each pair of residues forming the disulfide bridge. patches : list of str Any further patches the user wants to apply noregen : list of str A list of patches that must not be regenerated (angles and dihedrals) Default: ['FHEM', 'PHEM', 'PLOH', 'PLO2', 'PLIG', 'PSUL'] aliasresidues : dict of aliases A dictionary of key: value pairs of residue names we want to alias psfgen : str Path to psfgen executable used to build for CHARMM execute : bool Disable building. Will only write out the input script needed by psfgen. Does not include ionization. Returns ------- molbuilt : :class:`Molecule <moleculekit.molecule.Molecule>` object The built system in a Molecule object Example ------- >>> from htmd.ui import * >>> mol = Molecule("3PTB") >>> mol.filter("not resname BEN") >>> molbuilt = charmm.build(mol, outdir='/tmp/build', ionize=False) # doctest: +ELLIPSIS Bond between A: [serial 185 resid 42 resname CYS chain A segid 0] B: [serial 298 resid 58 resname CYS chain A segid 0]... >>> # More complex example >>> topos = ['top/top_all36_prot.rtf', './benzamidine.rtf', 'top/top_water_ions.rtf'] >>> params = ['par/par_all36_prot_mod.prm', './benzamidine.prm', 'par/par_water_ions.prm'] >>> disu = [['segid P and resid 157', 'segid P and resid 13'], ['segid K and resid 1', 'segid K and resid 25']] >>> ar = {'SAPI24': 'SP24'} # Alias large resnames to a short-hand version >>> molbuilt = charmm.build(mol, topo=topos, param=params, outdir='/tmp/build', saltconc=0.15, disulfide=disu, aliasresidues=ar) # doctest: +SKIP """ mol = mol.copy() _missingSegID(mol) _checkMixedSegment(mol) _checkLongResnames(mol, aliasresidues) if psfgen is None: psfgen = shutil.which('psfgen', mode=os.X_OK) if not psfgen: raise FileNotFoundError('Could not find psfgen executable, or no execute permissions are given. ' 'Run `conda install psfgen`.') if not os.path.isdir(outdir): os.makedirs(outdir) if _clean: _cleanOutDir(outdir) if topo is None: topo = defaultTopo() if param is None: param = defaultParam() if stream is None: stream = defaultStream() if caps is None: caps = _defaultCaps(mol) # patches that must _not_ be regenerated if noregen is None: noregen = ['FHEM', 'PHEM', 'PLOH', 'PLO2', 'PLIG', 'PSUL'] alltopo = topo.copy() allparam = param.copy() # Splitting the stream files and adding them to the list of parameter and topology files charmmdir = path.join(home(), 'builder', 'charmmfiles') for s in stream: if s[0] != '.' and path.isfile(path.join(charmmdir, s)): s = path.join(charmmdir, s) outrtf, outprm = _prepareStream(s) alltopo.append(outrtf) allparam.append(outprm) #_missingChain(mol) #_checkProteinGaps(mol) if patches is None: patches = [] if isinstance(patches, str): patches = [patches] allpatches = [] allpatches += patches # Find protonated residues and add patches for them allpatches += _protonationPatches(mol) f = open(path.join(outdir, 'build.vmd'), 'w') f.write('# psfgen file generated by charmm.build\n') f.write('package require psfgen;\n') f.write('psfcontext reset;\n\n') # Copying and printing out the topologies if not path.exists(path.join(outdir, 'topologies')): os.makedirs(path.join(outdir, 'topologies')) for i in range(len(alltopo)): if alltopo[i][0] != '.' and path.isfile(path.join(charmmdir, alltopo[i])): alltopo[i] = path.join(charmmdir, alltopo[i]) localname = '{}.'.format(i) + path.basename(alltopo[i]) shutil.copy(alltopo[i], path.join(outdir, 'topologies', localname)) f.write('topology ' + path.join('topologies', localname) + '\n') f.write('\n') _printAliases(f) if aliasresidues is not None: # User defined aliases for key, val in aliasresidues.items(): mol.resname[mol.resname == key] = val f.write(' pdbalias residue {} {}\n'.format(val, key)) # Printing out segments if not path.exists(path.join(outdir, 'segments')): os.makedirs(path.join(outdir, 'segments')) logger.info('Writing out segments.') segments = _getSegments(mol) wateratoms = mol.atomselect('water') for seg in segments: pdbname = 'segment' + seg + '.pdb' segatoms = mol.segid == seg mol.write(path.join(outdir, 'segments', pdbname), sel=segatoms) segwater = wateratoms & segatoms f.write('segment ' + seg + ' {\n') if np.all(segatoms == segwater): # If segment only contains waters, set: auto none f.write('\tauto none\n') f.write('\tpdb ' + path.join('segments', pdbname) + '\n') if caps is not None and seg in caps: for c in caps[seg]: f.write('\t' + c + '\n') f.write('}\n') f.write('coordpdb ' + path.join('segments', pdbname) + ' ' + seg + '\n\n') # Printing out patches for the disulfide bridges # TODO: Remove this once we deprecate the class from htmd.builder.builder import DisulfideBridge from moleculekit.molecule import UniqueResidueID if disulfide is not None and len(disulfide) != 0 and isinstance(disulfide[0], DisulfideBridge): newdisu = [] for d in disulfide: r1 = UniqueResidueID.fromMolecule(mol, 'resid {} and segname {}'.format(d.resid1, d.segid1)) r2 = UniqueResidueID.fromMolecule(mol, 'resid {} and segname {}'.format(d.resid2, d.segid2)) newdisu.append([r1, r2]) disulfide = newdisu # TODO: Remove up to here ---------------------- if disulfide is not None and len(disulfide) != 0 and isinstance(disulfide[0][0], str): disulfide = convertDisulfide(mol, disulfide) if disulfide is None: disulfide = detectDisulfideBonds(mol) if len(disulfide) != 0: for d in disulfide: str0 = '{}:{}{}'.format(d[0].segid, d[0].resid, d[0].insertion) str1 = '{}:{}{}'.format(d[1].segid, d[1].resid, d[1].insertion) f.write('patch DISU {} {}\n'.format(str0, str1)) f.write('\n') noregenpatches = [p for p in allpatches if p.split()[1] in noregen] regenpatches = [p for p in allpatches if p.split()[1] not in noregen] # Printing regenerable patches if len(regenpatches) != 0: for p in regenpatches: f.write(p + '\n') f.write('\n') # Regenerate angles and dihedrals f.write('regenerate angles dihedrals\n') f.write('\n') # Printing non-regenerable patches if len(noregenpatches) != 0: for p in noregenpatches: f.write(p + '\n') f.write('\n') f.write('guesscoord\n') f.write('writepsf ' + prefix + '.psf\n') f.write('writepdb ' + prefix + '.pdb\n') #f.write('quit\n') f.close() if allparam is not None: combine(allparam, path.join(outdir, 'parameters')) molbuilt = None if execute: logpath = os.path.abspath('{}/log.txt'.format(outdir)) logger.info('Starting the build.') currdir = os.getcwd() os.chdir(outdir) f = open(logpath, 'w') #call([vmd, '-dispdev', 'text', '-e', './build.vmd'], stdout=f) my_env = os.environ.copy() my_env['LC_ALL'] = 'C' call([psfgen, './build.vmd'], stdout=f, stderr=f, env=my_env) f.close() errors = _logParser(logpath) os.chdir(currdir) if errors: raise BuildError(errors + ['Check {} for further information on errors in building.'.format(logpath)]) logger.info('Finished building.') if path.isfile(path.join(outdir, 'structure.pdb')) and path.isfile(path.join(outdir, 'structure.psf')): molbuilt = Molecule(path.join(outdir, 'structure.pdb'), validateElements=False) molbuilt.read(path.join(outdir, 'structure.psf'), validateElements=False) else: raise BuildError('No structure pdb/psf file was generated. Check {} for errors in building.'.format(logpath)) if ionize: os.makedirs(path.join(outdir, 'pre-ionize')) data = glob(path.join(outdir, '*')) for f in data: shutil.move(f, path.join(outdir, 'pre-ionize')) totalcharge = np.sum(molbuilt.charge) nwater = np.sum(molbuilt.atomselect('water and noh')) anion, cation, anionatom, cationatom, nanion, ncation = ionizef(totalcharge, nwater, saltconc=saltconc, anion=saltanion, cation=saltcation) newmol = ionizePlace(mol, anion, cation, anionatom, cationatom, nanion, ncation) # Redo the whole build but now with ions included return build(newmol, topo=alltopo, param=allparam, stream=[], prefix=prefix, outdir=outdir, ionize=False, caps=caps, execute=execute, saltconc=saltconc, disulfide=disulfide, patches=patches, noregen=noregen, aliasresidues=aliasresidues, psfgen=psfgen, _clean=False) _checkFailedAtoms(molbuilt) _recoverProtonations(molbuilt) return molbuilt
def build( mol, topo=None, param=None, stream=None, prefix="structure", outdir="./build", caps=None, ionize=True, saltconc=0, saltanion=None, saltcation=None, disulfide=None, regenerate=["angles", "dihedrals"], patches=None, noregen=None, aliasresidues=None, psfgen=None, execute=True, _clean=True, ): """Builds a system for CHARMM Uses VMD and psfgen to build a system for CHARMM. Additionally it allows for ionization and adding of disulfide bridges. Parameters ---------- mol : :class:`Molecule <moleculekit.molecule.Molecule>` object The Molecule object containing the system topo : list of str A list of topology `rtf` files. Use :func:`charmm.listFiles <htmd.builder.charmm.listFiles>` to get a list of available topology files. Default: ['top/top_all36_prot.rtf', 'top/top_all36_lipid.rtf', 'top/top_water_ions.rtf'] param : list of str A list of parameter `prm` files. Use :func:`charmm.listFiles <htmd.builder.charmm.listFiles>` to get a list of available parameter files. Default: ['par/par_all36_prot.prm', 'par/par_all36_lipid.prm', 'par/par_water_ions.prm'] stream : list of str A list of stream `str` files containing topologies and parameters. Use :func:`charmm.listFiles <htmd.builder.charmm.listFiles>` to get a list of available stream files. Default: ['str/prot/toppar_all36_prot_arg0.str'] prefix : str The prefix for the generated pdb and psf files outdir : str The path to the output directory Default: './build' caps : dict A dictionary with keys segids and values lists of strings describing the caps of that segment. e.g. caps['P'] = ['first ACE', 'last CT3'] or caps['P'] = ['first none', 'last none']. Default: will apply ACE and CT3 caps to proteins and none caps to the rest. ionize : bool Enable or disable ionization saltconc : float Salt concentration (in Molar) to add to the system after neutralization. saltanion : {'CLA'} The anion type. Please use only CHARMM ion atom names. saltcation : {'SOD', 'MG', 'POT', 'CES', 'CAL', 'ZN2'} The cation type. Please use only CHARMM ion atom names. disulfide : list of pairs of atomselection strings If None it will guess disulfide bonds. Otherwise provide a list pairs of atomselection strings for each pair of residues forming the disulfide bridge. regenerate : None or list of strings of: ['angles', 'dihedrals'] Disable angle/dihedral regeneration with `regenerate=None`, or enable it with `regenerate=['angles', 'diheldrals']` or just one of the two options with `regenerate=['angles']` or `regenerate=['diheldrals']`. patches : list of str Any further patches the user wants to apply noregen : list of str A list of patches that must not be regenerated (angles and dihedrals) Default: ['FHEM', 'PHEM', 'PLOH', 'PLO2', 'PLIG', 'PSUL'] aliasresidues : dict of aliases A dictionary of key: value pairs of residue names we want to alias psfgen : str Path to psfgen executable used to build for CHARMM execute : bool Disable building. Will only write out the input script needed by psfgen. Does not include ionization. Returns ------- molbuilt : :class:`Molecule <moleculekit.molecule.Molecule>` object The built system in a Molecule object Example ------- >>> from htmd.ui import * >>> mol = Molecule("3PTB") >>> mol.filter("not resname BEN") >>> molbuilt = charmm.build(mol, outdir='/tmp/build', ionize=False) # doctest: +ELLIPSIS Bond between A: [serial 185 resid 42 resname CYS chain A segid 0] B: [serial 298 resid 58 resname CYS chain A segid 0]... >>> # More complex example >>> topos = ['top/top_all36_prot.rtf', './BEN.rtf', 'top/top_water_ions.rtf'] >>> params = ['par/par_all36_prot.prm', './BEN.prm', 'par/par_water_ions.prm'] >>> disu = [['segid P and resid 157', 'segid P and resid 13'], ['segid K and resid 1', 'segid K and resid 25']] >>> ar = {'SAPI24': 'SP24'} # Alias large resnames to a short-hand version >>> molbuilt = charmm.build(mol, topo=topos, param=params, outdir='/tmp/build', saltconc=0.15, disulfide=disu, aliasresidues=ar) # doctest: +SKIP """ mol = mol.copy() _missingSegID(mol) _checkMixedSegment(mol) _checkLongResnames(mol, aliasresidues) if psfgen is None: psfgen = shutil.which("psfgen", mode=os.X_OK) if not psfgen: raise FileNotFoundError( "Could not find psfgen executable, or no execute permissions are given. " "Run `conda install psfgen -c acellera`.") if not os.path.isdir(outdir): os.makedirs(outdir) if _clean: _cleanOutDir(outdir) if topo is None: topo = defaultTopo() if param is None: param = defaultParam() if stream is None: stream = defaultStream() if caps is None: caps = _defaultCaps(mol) # patches that must _not_ be regenerated if noregen is None: noregen = ["FHEM", "PHEM", "PLOH", "PLO2", "PLIG", "PSUL"] alltopo = topo.copy() allparam = param.copy() # Splitting the stream files and adding them to the list of parameter and topology files charmmdir = htmdCharmmHome() for s in stream: if s[0] != "." and path.isfile(path.join(charmmdir, s)): s = path.join(charmmdir, s) outrtf, outprm = _prepareStream(s) alltopo.append(outrtf) allparam.append(outprm) # _missingChain(mol) # _checkProteinGaps(mol) if patches is None: patches = [] if isinstance(patches, str): patches = [patches] allpatches = [] allpatches += patches # Find protonated residues and add patches for them allpatches += _protonationPatches(mol) f = open(path.join(outdir, "build.vmd"), "w") f.write("# psfgen file generated by charmm.build\n") f.write("package require psfgen;\n") f.write("psfcontext reset;\n\n") # Copying and printing out the topologies if not path.exists(path.join(outdir, "topologies")): os.makedirs(path.join(outdir, "topologies")) for i in range(len(alltopo)): if alltopo[i][0] != "." and path.isfile( path.join(charmmdir, alltopo[i])): alltopo[i] = path.join(charmmdir, alltopo[i]) localname = "{}.".format(i) + path.basename(alltopo[i]) shutil.copy(alltopo[i], path.join(outdir, "topologies", localname)) f.write("topology " + path.join("topologies", localname) + "\n") f.write("\n") _printAliases(f) if aliasresidues is not None: # User defined aliases for key, val in aliasresidues.items(): mol.resname[mol.resname == key] = val f.write(" pdbalias residue {} {}\n".format(val, key)) # Printing out segments if not path.exists(path.join(outdir, "segments")): os.makedirs(path.join(outdir, "segments")) logger.info("Writing out segments.") segments = _getSegments(mol) wateratoms = mol.atomselect("water") for seg in segments: pdbname = "segment" + seg + ".pdb" segatoms = mol.segid == seg mol.write(path.join(outdir, "segments", pdbname), sel=segatoms) segwater = wateratoms & segatoms f.write("segment " + seg + " {\n") if np.all(segatoms == segwater): # If segment only contains waters, set: auto none f.write("\tauto none\n") f.write("\tpdb " + path.join("segments", pdbname) + "\n") if caps is not None and seg in caps: for c in caps[seg]: f.write("\t" + c + "\n") f.write("}\n") f.write("coordpdb " + path.join("segments", pdbname) + " " + seg + "\n\n") if (disulfide is not None and len(disulfide) != 0 and isinstance(disulfide[0][0], str)): disulfide = convertDisulfide(mol, disulfide) if disulfide is None: disulfide = detectDisulfideBonds(mol) if len(disulfide) != 0: for d in sorted(disulfide, key=lambda x: x[0].segid): str0 = f"{d[0].segid}:{d[0].resid}{d[0].insertion}" str1 = f"{d[1].segid}:{d[1].resid}{d[1].insertion}" f.write(f"patch DISU {str0} {str1}\n") f.write("\n") noregenpatches = [p for p in allpatches if p.split()[1] in noregen] regenpatches = [p for p in allpatches if p.split()[1] not in noregen] # Printing regenerable patches if len(regenpatches) != 0: for p in regenpatches: f.write(p + "\n") f.write("\n") # Regenerate angles and dihedrals if regenerate is not None: f.write("regenerate {}\n".format(" ".join(regenerate))) f.write("\n") # Printing non-regenerable patches if len(noregenpatches) != 0: for p in noregenpatches: f.write(p + "\n") f.write("\n") f.write("guesscoord\n") f.write("writepsf " + prefix + ".psf\n") f.write("writepdb " + prefix + ".pdb\n") # f.write('quit\n') f.close() if allparam is not None: combine(allparam, path.join(outdir, "parameters")) molbuilt = None if execute: logpath = os.path.abspath("{}/log.txt".format(outdir)) logger.info("Starting the build.") currdir = os.getcwd() os.chdir(outdir) f = open(logpath, "w") # call([vmd, '-dispdev', 'text', '-e', './build.vmd'], stdout=f) my_env = os.environ.copy() my_env["LC_ALL"] = "C" call([psfgen, "./build.vmd"], stdout=f, stderr=f, env=my_env) f.close() errors = _logParser(logpath) os.chdir(currdir) if errors: raise BuildError(errors + [ "Check {} for further information on errors in building.". format(logpath) ]) logger.info("Finished building.") if path.isfile(path.join(outdir, "structure.pdb")) and path.isfile( path.join(outdir, "structure.psf")): molbuilt = Molecule(path.join(outdir, "structure.pdb")) molbuilt.read(path.join(outdir, "structure.psf")) else: raise BuildError( "No structure pdb/psf file was generated. Check {} for errors in building." .format(logpath)) if ionize: os.makedirs(path.join(outdir, "pre-ionize")) data = glob(path.join(outdir, "*")) for f in data: shutil.move(f, path.join(outdir, "pre-ionize")) totalcharge = np.sum(molbuilt.charge) nwater = np.sum(molbuilt.atomselect("water and noh")) anion, cation, anionatom, cationatom, nanion, ncation = ionizef( molbuilt, totalcharge, nwater, saltconc=saltconc, anion=saltanion, cation=saltcation, ) newmol = ionizePlace(mol, anion, cation, anionatom, cationatom, nanion, ncation) # Redo the whole build but now with ions included return build( newmol, topo=alltopo, param=allparam, stream=[], prefix=prefix, outdir=outdir, ionize=False, caps=caps, execute=execute, saltconc=saltconc, disulfide=disulfide, regenerate=regenerate, patches=patches, noregen=noregen, aliasresidues=aliasresidues, psfgen=psfgen, _clean=False, ) _checkFailedAtoms(molbuilt) _recoverProtonations(molbuilt) detectCisPeptideBonds(molbuilt, respect_bonds=True) # Warn in case of cis bonds return molbuilt