def test_mol_attrs(file_3nob): m1 = molecule.load("mae", file_3nob) m2 = molecule.load("mae", file_3nob) # Get/set top assert molecule.get_top() == m2 molecule.set_top(molid=m1) assert molecule.get_top() == m1 with pytest.raises(ValueError): molecule.set_top(m2+1) # Get/set visibility molecule.set_visible(m1, visible=False) assert molecule.get_visible() == False assert molecule.get_visible(molid=m2) == True with pytest.warns(DeprecationWarning): molecule.set_visible(m1, state=True) assert molecule.get_visible(molid=m1) == True with pytest.raises(ValueError): molecule.set_visible(m2+1, True) with pytest.raises(TypeError): molecule.set_visible(m2, 3) with pytest.raises(ValueError): molecule.get_visible(m2+1) # Get/set periodic assert molecule.get_periodic(m2) == {'a': 1.0, 'alpha': 90.0, 'b': 1.0, 'beta': 90.0, 'c': 1.0, 'gamma': 90.0} with pytest.raises(ValueError): molecule.get_periodic(molid=m1, frame=3000) with pytest.raises(ValueError): molecule.set_periodic(molid=m2+1, a=2.0) with pytest.raises(ValueError): molecule.set_periodic(m1, frame=3000, a=20.0) molecule.set_periodic(m2, frame=0, a=90.0, b=90.0, c=90.0, alpha=90.0, beta=90.0, gamma=90.0) assert list(molecule.get_periodic(m2, frame=0).values()) == [pytest.approx(90.0)]*6 assert set(molecule.get_periodic(m1, frame=0).values()) != [pytest.approx(90.0)]*6 molecule.set_periodic(c=20.0) assert molecule.get_periodic()["c"] == pytest.approx(20.0) molecule.delete(m1) molecule.delete(m2)
def _write_ion_blocks(self): """ Writes a PDB file containing correctly named ions for use by psfgen, and instructs psfgen to use it in TCL code. """ # Put our molecule on top to simplify atom selection language old_top = molecule.get_top() molecule.set_top(self.molid) # Select all ions allions = [] for resname in set(atomsel("numbonds 0").resname): allions.extend(self._rename_by_resname(resname, renumber=True)) # Stop if no ions were found if not allions: return # Save ions as pdb allsel = atomsel("residue %s" % " ".join(str(_) for _ in allions)) allsel.resid = range(len(allsel)) allsel.user = 0.0 _, temp = tempfile.mkstemp(suffix=".pdb", prefix="psf_ions_", dir=self.tmp_dir) os.close(_) allsel.write("pdb", temp) self.psfgen.add_segment(segid="I", pdbfile=temp) self.psfgen.read_coords(segid="I", filename=temp) molecule.set_top(old_top)
def _write_ion_blocks(self): """ Writes a PDB file containing correctly named ions for use by psfgen, and instructs psfgen to use it in TCL code. """ # Put our molecule on top to simplify atom selection language old_top = molecule.get_top() molecule.set_top(self.molid) # Get ion resids that aren't associated w other molecules # because some ligands have Na, Cl, K total = atomsel('element Na Cl K') if len(total): not_ions = atomsel("(same fragment as element Na Cl K) and (not index %s)" % " ".join([str(s) for s in set(total.get('index'))])) ions = set(total.get('residue')) - set(not_ions.get('residue')) else: ions = set(total.get('residue')) if not len(ions): return ionstr = "residue " + " ".join([str(s) for s in ions]) # Fix the names atomsel('%s and name NA' % ionstr).set('name', 'SOD') atomsel('%s and name CL' % ionstr).set('name', 'CLA') atomsel('%s and name K' % ionstr).set('name', 'POT') atomsel('%s and name NA' % ionstr).set('resname', 'SOD') atomsel('%s and name CL' % ionstr).set('resname', 'CLA') atomsel('%s and name K' % ionstr).set('resname', 'POT') # Renumber the residues since some may be above 10k residues = atomsel('name SOD CLA POT').get('residue') batch = atomsel('residue %s' % ' '.join([str(s) for s in set(residues)])) batch.set('resid', [k for k in range(1, len(batch)+1)]) # Save the temporary ions file temp = tempfile.mkstemp(suffix='.pdb', prefix='psf_ions_', dir=self.tmp_dir)[1] atomsel('name SOD CLA POT').set('user', 0.0) # mark as saved atomsel('name SOD CLA POT').write('pdb', temp) string = ''' set ionfile %s segment I { pdb $ionfile first none last none } coordpdb $ionfile I ''' % temp self.file.write(string) molecule.set_top(old_top)
def _set_cell_to_square_prism(self, molid): """ Sets the periodic box to be a square prism of specified dimension Args: molid (int): VMD molecule ID to consider """ old_top = molecule.get_top() molecule.set_top(molid) molecule.set_periodic(-1, -1, self.size[0], self.size[1], self.size[2], 90.0, 90.0, 90.0) molecule.set_top(old_top)
def _write_ordered_pdb(filename, sel, molid): """ Writes a pdb file in order of residues, renumbering the atoms accordingly, since psfgen wants each residue sequentially while VMD will write them in the same order as input, which from Maestro created files has some guessed atoms at the end. Args: filename (str): Name of the pdb file to write sel (str): VMD atomsel string for atoms that will be written molid (int): VMD molecule ID to write from """ old_top = molecule.get_top() molecule.set_top(molid) fileh = open(filename, 'w') # Use resids since order can be wrong when sorting by residue # Then, use residue to pull out each one since it is much much # faster then trying to pull out residues resids = set(atomsel(sel).get('resid')) # Add additional residue constraint to selection since pulling out # by resid can match something in a different chain resstr = ' '.join([str(x) for x in set(atomsel(sel).get('residue'))]) idx = 1 # For renumbering capping groups for resid in sorted(resids): # Check for alternate locations residues = sorted(set(atomsel("resid '%s' and residue %s" % (resid, resstr)).get('residue'))) for rid in residues: for i in atomsel('residue %d' % rid).get('index'): a = atomsel('index %d' % i) # pylint: disable=invalid-name ins = a.get("insertion")[0] entry = ('%-6s%5d %-5s%-4s%c%4d%c %8.3f%8.3f%8.3f%6.2f%6.2f' ' %-4s%2s\n' % ('ATOM', idx, a.get('name')[0], a.get('resname')[0], a.get('chain')[0], a.get('resid')[0], ins if len(ins) else " ", a.get('x')[0], a.get('y')[0], a.get('z')[0], 0.0, 0.0, a.get('segname')[0], a.get('element')[0])) idx += 1 fileh.write(entry) fileh.write('END\n') atomsel(sel).set('user', 0.0) # Mark as written fileh.close() molecule.set_top(old_top)
def _write_lipid_blocks(self): """ Writes a temporary PDB file containing the lipids for later use by psfgen. Renumbers the lipid residues because some can have **** instead of an integer for resid in large systems, which will crash psfgen. Also sets atom names for some common lipids (currently POPC) Raises: NotImplementedError if more than 10,000 lipids are present since it doesn't support feeding multiple lipid blocks to psfgen currently NotImplementedError if lipid other than POPC,POPE,POPG is found """ # Put current molecule on top to simplify atom selection old_top = molecule.get_top() molecule.set_top(self.molid) # Collect lipid residues up alll = atomsel('(%s) and user 1.0' % self.lipid_sel) residues = list(set(alll.residue)) # Lipids not compatible with AMBER parameters, CHARMM format if alll and ("amber" in self.forcefield or "opls" in self.forcefield): raise ValueError( "AMBER or OPLS parameters not supported for lipids" " in CHARMM output format") # Sanity check for < 10k lipids if len(residues) >= 10000: raise NotImplementedError("More than 10k lipids found") # Loop through all residues and renumber and correctly name them lipress = [] for resname in set(alll.resname): lipress.extend(self._rename_by_resname(resname, renumber=True)) # Write temporary lipid pdb _, temp = tempfile.mkstemp(suffix='.pdb', prefix='psf_lipid_', dir=self.tmp_dir) os.close(_) saved_lips = atomsel("residue %s" % ' '.join(str(_) for _ in lipress)) saved_lips.user = 0.0 saved_lips.write('pdb', temp) # Generate lipid segment self.psfgen.add_segment(segid="L", pdbfile=temp) self.psfgen.read_coords(segid="L", filename=temp) # Put old top back molecule.set_top(old_top)
def _write_ion_blocks(self): """ Writes a PDB file containing correctly named ions for use by psfgen, and instructs psfgen to use it in TCL code. """ # Put our molecule on top to simplify atom selection language old_top = molecule.get_top() molecule.set_top(self.molid) # Get ion resids that aren't associated w other molecules # because some ligands have Na, Cl, K total = atomsel('element Na Cl K') not_ions = atomsel("(same fragment as element Na Cl K) and (not index %s)" % " ".join([str(s) for s in set(total.get('index'))])) ions = set(total.get('residue')) - set(not_ions.get('residue')) if not len(ions): return ionstr = "residue " + " ".join([str(s) for s in ions]) # Fix the names atomsel('%s and name NA' % ionstr).set('name', 'SOD') atomsel('%s and name CL' % ionstr).set('name', 'CLA') atomsel('%s and name K' % ionstr).set('name', 'POT') atomsel('%s and name NA' % ionstr).set('resname', 'SOD') atomsel('%s and name CL' % ionstr).set('resname', 'CLA') atomsel('%s and name K' % ionstr).set('resname', 'POT') # Renumber the residues since some may be above 10k residues = atomsel('name SOD CLA POT').get('residue') batch = atomsel('residue %s' % ' '.join([str(s) for s in set(residues)])) batch.set('resid', [k for k in range(1, len(batch)+1)]) # Save the temporary ions file temp = tempfile.mkstemp(suffix='.pdb', prefix='psf_ions_', dir=self.tmp_dir)[1] atomsel('name SOD CLA POT').set('user', 0.0) # mark as saved atomsel('name SOD CLA POT').write('pdb', temp) string = ''' set ionfile %s segment I { pdb $ionfile first none last none } coordpdb $ionfile I ''' % temp self.file.write(string) molecule.set_top(old_top)
def _write_ordered_pdb(filename, sel, molid): """ Writes a pdb file in order of residues, renumbering the atoms accordingly, since psfgen wants each residue sequentially while VMD will write them in the same order as input, which from Maestro created files has some guessed atoms at the end. Args: filename (str): Name of the pdb file to write sel (str): VMD atomsel string for atoms that will be written molid (int): VMD molecule ID to write from """ old_top = molecule.get_top() molecule.set_top(molid) fileh = open(filename, 'w') # Use resids since order can be wrong when sorting by residue # Then, use residue to pull out each one since it is much much # faster then trying to pull out residues resids = set(atomsel(sel).get('resid')) # Add additional residue constraint to selection since pulling out # by resid can match something in a different chain resstr = ' '.join([str(x) for x in set(atomsel(sel).get('residue'))]) idx = 1 # For renumbering capping groups for resid in sorted(resids): rid = atomsel("resid '%s' and residue %s" % (resid, resstr)).get('residue')[0] for i in atomsel('residue %d' % rid).get('index'): a = atomsel('index %d' % i) # pylint: disable=invalid-name entry = ('%-6s%5d %-5s%-4s%c%4d %8.3f%8.3f%8.3f%6.2f%6.2f' ' %-4s%2s\n' % ('ATOM', idx, a.get('name')[0], a.get('resname')[0], a.get('chain')[0], a.get('resid')[0], a.get('x')[0], a.get('y')[0], a.get('z')[0], 0.0, 0.0, a.get('segname')[0], a.get('element')[0])) idx += 1 fileh.write(entry) fileh.write('END\n') atomsel(sel).set('user', 0.0) # Mark as written fileh.close() molecule.set_top(old_top)
def write(self, filename): """ Writes the parameter and topology files Args: filename (str): File name to write. File type suffix will be added. """ self.outprefix = filename # Put our molecule on top old_top = molecule.get_top() molecule.set_top(self.molid) # Amber forcefield done with AmberWriter then conversion if "amber" in self.forcefield: # Avoid circular import by doing it here from dabble.param import AmberWriter prmtopgen = AmberWriter(molid=self.molid, tmp_dir=self.tmp_dir, forcefield=self.forcefield, water_model=self.water_model, hmr=self.hmr, lipid_sel=self.lipid_sel, extra_topos=self.extra_topos, extra_params=self.extra_params, override_defaults=self.override, debug_verbose=self.debug) prmtopgen.write(self.outprefix) self._prmtop_to_charmm() # Charmm forcefield elif "charmm" in self.forcefield: self._run_psfgen() # OPLS forcefield. Same as charmm but list separately for readability elif "opls" in self.forcefield: self._run_psfgen() else: raise DabbleError("Unsupported forcefield '%s' for CharmmWriter" % self.forcefield) # Check output and finish up self._check_psf_output() # Reset top molecule molecule.set_top(old_top)
def _write_ordered_pdb(filename, sel, molid): """ Writes a pdb file in order of residues, renumbering the atoms accordingly, since psfgen wants each residue sequentially while VMD will write them in the same order as input, which from Maestro created files has some guessed atoms at the end. Args: filename (str): Name of the pdb file to write sel (str): VMD atomsel string for atoms that will be written molid (int): VMD molecule ID to write from """ old_top = molecule.get_top() molecule.set_top(molid) fileh = open(filename, 'w') # Use resids since order can be wrong when sorting by residue # Then, use residue to pull out each one since it is much much # faster then trying to pull out residues resids = set(atomsel(sel).resid) # Add additional residue constraint to selection since pulling out # by resid can match something in a different chain resstr = ' '.join([str(x) for x in set(atomsel(sel).residue)]) idx = 1 # For renumbering capping groups for resid in sorted(resids): # Check for alternate locations residues = sorted( set( atomsel("resid '%s' and residue %s" % (resid, resstr)).residue)) for rid in residues: for i in atomsel('residue %d' % rid).index: a = atomsel('index %d' % i) # pylint: disable=invalid-name fileh.write(MoleculeWriter.get_pdb_line(a, idx, a.resid[0])) idx += 1 fileh.write('END\n') atomsel(sel).user = 0.0 fileh.close() molecule.set_top(old_top)
def test_evaltcl(file_rho): from vmd import evaltcl, atomsel, molecule molid = int(evaltcl("mol new")) assert molecule.get_top() == molid assert evaltcl("mol addfile %s type mae waitfor all" % file_rho) assert "molecule%d" % molid in evaltcl("mol list") with pytest.raises(ValueError): evaltcl("atomsel all") assert evaltcl("set all_atoms [atomselect %s \" all \" frame %s]" % (molid, 0)) == "atomselect0" assert set(evaltcl("$all_atoms get chain").split()) == \ set(atomsel("all").chain) assert set(evaltcl("$all_atoms get name").split()) == \ set(atomsel("all").name) with pytest.raises(ValueError): evaltcl("$all_atoms get invalid")
def _write_generic_block(self, residues): """ Matches ligands to available topology file, renames atoms, and then writes temporary files for the ligands Args: residues (list of int): Residue numbers to be written. Will all be written to one segment. Returns: True if successful """ # Put our molecule on top to simplify atom selection language old_top = molecule.get_top() molecule.set_top(self.molid) alig = atomsel('user 1.0 and residue %s' % " ".join([str(x) for x in residues])) # Write temporary file containg the residues and update tcl commands _, temp = tempfile.mkstemp(suffix='.pdb', prefix='psf_block_', dir=self.tmp_dir) os.close(_) alig.write('pdb', temp) alig.user = 0.0 # Get next available segment name segname = "B%d" % self.segint self.segint += 1 self.psfgen.add_segment(segid=segname, pdbfile=temp) self.psfgen.read_coords(segid=segname, filename=temp) if old_top != -1: molecule.set_top(old_top) return True
def _write_generic_block(self, residues): """ Matches ligands to available topology file, renames atoms, and then writes temporary files for the ligands Args: residues (list of int): Residue numbers to be written. Will all be written to one segment. Returns: True if successful """ # Put our molecule on top to simplify atom selection language old_top = molecule.get_top() molecule.set_top(self.molid) alig = atomsel('user 1.0 and residue %s' % " ".join([str(x) for x in residues])) # Write temporary file containg the residues and update tcl commands temp = tempfile.mkstemp(suffix='.pdb', prefix='psf_block_', dir=self.tmp_dir)[1] string = ''' set blockfile %s segment B%s { pdb $blockfile first none last none } coordpdb $blockfile B%s ''' % (temp, residues[0], residues[0]) alig.write('pdb', temp) alig.set('user', 0.0) self.file.write(string) if old_top != -1: molecule.set_top(old_top) return True
def _write_lipid_blocks(self): """ Writes a temporary PDB file containing the lipids for later use by psfgen. Renumbers the lipid residues because some can have **** instead of an integer for resid in large systems, which will crash psfgen. Also sets atom names for some common lipids (currently POPC) Raises: NotImplementedError if more than 10,000 lipids are present since it doesn't support feeding multiple lipid blocks to psfgen currently NotImplementedError if lipid other than POPC,POPE,POPG is found """ # Put current molecule on top to simplify atom selection old_top = molecule.get_top() molecule.set_top(self.molid) # Collect lipid residues up alll = atomsel('(%s) and user 1.0' % self.lipid_sel) residues = list(set(alll.get('residue'))) residues.sort() # Sanity check for < 10k lipids if len(residues) >= 10000: raise NotImplementedError("More than 10k lipids found") # Rename lipid residues by resname # This assumes all lipids with the same resname are the same # If that's not the case, the system is really broken in some way # for resname in set(alll.get('resname')): # ressel = atomsel("(%s) and user 1.0 and resname '%s'" # % (self.lipid_sel, resname)) # # # Get naming dictionary for one representative residue # repsel = atomsel('residue %s' % ressel.get('residue')[0]) # (newname, atomnames) = self.matcher.get_names(sel) # # # Apply naming dictionary to all of these residues # for idx, name in atomnames.items(): # oldname = atomsel('index %s' % idx).get('name') # if oldname != name: # Loop through all residues and renumber and correctly name them counter = 1 for res in residues: # Renumber residue sel = atomsel('residue %s' % res) sel.set('resid', counter) counter = counter + 1 # Rename residue # (newname, atomnames) = self.matcher.get_names(sel, # print_warning=False) # # for idx, name in atomnames.items(): # atom = atomsel('index %s' % idx) # if atom.get('name')[0] != name: # print("Renaming %s:%s: %s -> %s" % (sel.get('resname')[0], # sel.get('resid')[0], # atom.get('name')[0], # name)) # atom.set('name', name) # sel.set('resname', newname) # Write temporary lipid pdb temp = tempfile.mkstemp(suffix='.pdb', prefix='psf_lipid_', dir=self.tmp_dir)[1] alll.set('user', 0.0) alll.write('pdb', temp) # Write to file string = ''' set lipidfile %s set mid [mol new $lipidfile] segment L { first none last none pdb $lipidfile } coordpdb $lipidfile L mol delete $mid ''' % temp self.file.write(string) # Put old top back molecule.set_top(old_top)
def _renumber_protein_chains(self, molid): """ Pulls all protein fragments and renumbers the residues so that ACE and NMA caps appear to be different residues to VMD. This is necessary so that they don't appear as patches. Proteins with non standard capping groups will have the patches applied. Args: molid (int): VMD molecule ID of entire system Returns: (int): Molid of loaded fragment """ # Put our molecule on top and grab selection old_top = molecule.get_top() molecule.set_top(molid) for frag in set(atomsel("protein or resname ACE NMA").get("fragment")): fragment = atomsel('fragment %s' % frag, molid=molid) print("Checking capping groups resids on protein fragment %d" % frag) for resid in sorted(set(fragment.get("resid"))): # Handle bug where capping groups in same residue as the # neighboring amino acid Maestro writes it this way for some # reason but it causes problems down the line when psfgen doesn't # understand the weird combined residue rid = atomsel("fragment '%s' and resid '%d'" % (frag, resid)).get('residue')[0] names = set(atomsel('residue %d'% rid).get('resname')) assert len(names) < 3, ("More than 2 residues with same number... " "currently unhandled. Report a bug") if len(names) > 1: if 'ACE' in names and 'NMA' in names: print("ERROR: Both ACE and NMA were given the same resid" "Check your input structure") quit(1) if 'ACE' in names: # Set ACE residue number as one less resid = atomsel('residue %d and not resname ACE' % rid).get('resid')[0] if len(atomsel("fragment '%s' and resid %d" % (frag, resid-1))): raise ValueError('ACE resid collision number %d' % (resid-1)) atomsel('residue %d and resname ACE' % rid).set('resid', resid-1) print("\tACE %d -> %d" % (resid, resid-1)) elif 'NMA' in names: # Set NMA residue number as one more resid = int(atomsel('residue %d and not resname NMA' % rid).get('resid')[0]) if len(atomsel("fragment '%s' and resid %d" % (frag, resid+1))): raise ValueError("NMA resid collision number %d" % (resid+1)) atomsel('residue %d and resname NMA' % rid).set('resid', resid+1) print("\tNMA %d -> %d" % (resid, resid+1)) # Have to save and reload so residues are parsed correctly by VMD temp = tempfile.mkstemp(suffix='_renum.mae', prefix='psf_prot_', dir=self.tmp_dir)[1] atomsel("same fragment as protein or resname ACE NMA").write('mae', temp) prot_molid = molecule.load('mae', temp) # Put things back the way they were if old_top != -1: molecule.set_top(old_top) return prot_molid
def _write_protein_blocks(self, molid, frag): """ Writes a protein fragment to a pdb file for input to psfgen Automatically assigns amino acid names Args: molid (int): VMD molecule ID of renumbered protein frag (str): Fragment to write Returns: (list of str): Patches to add to the psfgen input file after all proteins have been loaded """ print("Setting protein atom names") # Put our molecule on top to simplify atom selection language old_top = molecule.get_top() molecule.set_top(molid) patches = set() extpatches = set() seg = "P%s" % frag residues = list(set(atomsel("fragment '%s'" % frag).get('residue'))) for residue in residues: sel = atomsel('residue %s' % residue) resid = sel.get('resid')[0] # Only try to match single amino acid if there are 1 or 2 bonds if len(self.matcher.get_extraresidue_atoms(sel)) < 3: (newname, atomnames) = self.matcher.get_names(sel, print_warning=False) # See if it's a disulfide bond participant else: (newname, patchline, atomnames) = \ self.matcher.get_disulfide("residue %d" % residue, frag, molid) if newname: extpatches.add(patchline) # Couldn't find a match. See if it's a patched residue if not newname: (newname, patch, atomnames) = self.matcher.get_patches(sel) if newname: patches.add("patch %s %s:%d\n" % (patch, seg, resid)) # Fall through to error condition if not newname: raise ValueError("Couldn't find a patch for %s:%s" % (sel.get('resname')[0], resid)) # Do the renaming for idx, name in atomnames.items(): atom = atomsel('index %s' % idx) if atom.get('name')[0] != name and "+" not in name and \ "-" not in name: atom.set('name', name) sel.set('resname', newname) # Save protein chain in the correct order filename = self.tmp_dir + '/psf_protein_%s.pdb' % seg _write_ordered_pdb(filename, "fragment '%s'" % frag, molid) print("\tWrote %d atoms to the protein segment %s" % (len(atomsel("fragment %s" % frag)), seg)) # Now write to psfgen input file string = ''' set protnam %s segment %s { first none last none pdb $protnam } ''' % (filename, seg) self.file.write(string) print("Applying the following single-residue patches to P%s:\n" % frag) print("\t%s" % "\t".join(patches)) self.file.write(''.join(patches)) self.file.write("\n") self.file.write("coordpdb $protnam %s\n" % seg) if old_top != -1: molecule.set_top(old_top) return extpatches
def _write_protein_blocks(self, molid, frag): """ Writes a protein fragment to a pdb file for input to psfgen Automatically assigns amino acid names Args: molid (int): VMD molecule ID of renumbered protein frag (str): Fragment to write Returns: (list of str): Patches to add to the psfgen input file after all proteins have been loaded """ print("Setting protein atom names") # Put our molecule on top to simplify atom selection language old_top = molecule.get_top() molecule.set_top(molid) patches = set() extpatches = set() seg = "P%s" % frag residues = list(set(atomsel("fragment '%s'" % frag).get('residue'))) for residue in residues: sel = atomsel('residue %s' % residue) resid = sel.get('resid')[0] # Only try to match single amino acid if there are 1 or 2 bonds if len(self.matcher.get_extraresidue_atoms(sel)) < 3: (newname, atomnames) = self.matcher.get_names(sel, print_warning=False) # See if it's a disulfide bond participant else: (newname, patchline, atomnames) = \ self.matcher.get_disulfide("residue %d" % residue, frag, molid) if newname: extpatches.add(patchline) # Couldn't find a match. See if it's a patched residue if not newname: (newname, patch, atomnames) = self.matcher.get_patches(sel) if newname: patches.add("patch %s %s:%d\n" % (patch, seg, resid)) # Fall through to error condition if not newname: raise DabbleError("Couldn't find a patch for %s:%s" % (sel.get('resname')[0], resid)) # Do the renaming for idx, name in atomnames.items(): atom = atomsel('index %s' % idx) if atom.get('name')[0] != name and "+" not in name and \ "-" not in name: atom.set('name', name) sel.set('resname', newname) # Save protein chain in the correct order filename = self.tmp_dir + '/psf_protein_%s.pdb' % seg _write_ordered_pdb(filename, "fragment '%s'" % frag, molid) print("\tWrote %d atoms to the protein segment %s" % (len(atomsel("fragment %s" % frag)), seg)) # Now write to psfgen input file string = ''' set protnam %s segment %s { first none last none pdb $protnam } ''' % (filename, seg) self.file.write(string) print("Applying the following single-residue patches to P%s:\n" % frag) print("\t%s" % "\t".join(patches)) self.file.write(''.join(patches)) self.file.write("\n") self.file.write("coordpdb $protnam %s\n" % seg) if old_top != -1: molecule.set_top(old_top) return extpatches
def _find_single_residue_names(self, resname, molid): """ Uses graph matcher and available topologies to match up ligand names automatically. Tries to use graphs, and if there's an uneven number of atoms tries to match manually to suggest which atoms are most likely missing. Args: resname (str): Residue name of the ligand that will be written. All ligands will be checked separately against the graphs. molid (int): VMD molecule ID to consider Returns: (list of ints): Residue numbers (not resid) of all input ligands that were successfully matched. Need to do it this way since residue names can be changed in here to different things. Raises: ValueError if number of resids does not match number of residues as interpreted by VMD NotImplementedError if a residue could not be matched to a graph. """ # Put our molecule on top old_top = molecule.get_top() molecule.set_top(molid) # Sanity check that there is no discrepancy between defined resids and # residues as interpreted by VMD. for chain in set(atomsel("user 1.0 and resname '%s'" % resname).get('chain')): residues = list(set(atomsel("user 1.0 and resname '%s' and chain %s" % (resname, chain)).get('residue'))) resids = list(set(atomsel("user 1.0 and resname '%s' and chain %s" % (resname, chain)).get('resid'))) if len(residues) != len(resids): raise ValueError("VMD found %d residues for resname '%s', but there " "are %d resids! Check input." % (len(residues), resname, len(resids))) for residue in residues: sel = atomsel("residue %s and resname '%s' and user 1.0" % (residue, resname)) (newname, atomnames) = self.matcher.get_names(sel, print_warning=True) if not newname: (resname, patch, atomnames) = self.matcher.get_patches(sel) if not newname: print("ERROR: Could not find a residue definition for %s:%s" % (resname, residue)) raise NotImplementedError("No residue definition for %s:%s" % (resname, residue)) print("\tApplying patch %s to ligand %s" % (patch, newname)) # Do the renaming for idx, name in atomnames.items(): atom = atomsel('index %s' % idx) if atom.get('name')[0] != name and "+" not in name and \ "-" not in name: print("Renaming %s:%s: %s -> %s" % (resname, residue, atom.get('name')[0], name)) atom.set('name', name) sel.set('resname', newname) #logger.info("Renamed %d atoms for all resname %s->%s" % (num_renamed, resname, name)) molecule.set_top(old_top) return residues
def _write_water_blocks(self): """ Writes a lot of temporary files with 10000 waters each, to bypass psfgen being stupid with files containing more than 10000 of a residue. """ # Put current molecule on top to simplify atom selection language old_top = molecule.get_top() molecule.set_top(self.molid) # Set consistent residue and atom names, crystal waters # can be named HOH, etc atomsel('water').set('resname', 'TIP3') atomsel('resname TIP3').set('chain', 'W') atomsel('resname TIP3 and element O').set('name', 'OH2') # Dowser can name water hydrogens strangely atomsel('resname TIP3 and name HW1').set('name', 'H1') atomsel('resname TIP3 and name HW2').set('name', 'H2') # Select all the waters. We'll use the user field to track which # ones have been written allw = atomsel('water and user 1.0') print("Found %d water residues" % len(set(atomsel('water and user 1.0').get('residue')))) # Find the problem waters with unordered indices problems = [] for r in set(allw.get('residue')): widx = atomsel('residue %s' % r).get("index") if max(widx) - min(widx) != 2: problems.append(r) atomsel('residue %s' % r).set("user", 0.0) # get it out of allw allw.update() num_written = int(len(allw)/(9999*3))+1 print("Going to write %d files for %d water atoms" % (num_written, len(allw))) # Pull out and write 10k waters at a time if we have normal waters if allw: for i in range(num_written): temp = tempfile.mkstemp(suffix='_%d.pdb' % i, prefix='psf_wat_', dir=self.tmp_dir)[1] residues = list(set(allw.get('residue')))[:9999] batch = atomsel('residue %s' % ' '.join([str(x) for x in residues])) try: batch.set('resid', [k for k in range(1, int(len(batch)/3)+1) for _ in range(3)]) except ValueError: print("\nERROR! You have some waters missing hydrogens!\n" "Found %d water residues, but %d water atoms. Check " " your crystallographic waters in the input structure." % (len(residues), len(batch))) quit(1) batch.set('user', 0.0) batch.write('pdb', temp) allw.update() # Now write the problem waters self._write_unorderedindex_waters(problems, self.molid) string = ''' set waterfiles [glob -directory %s psf_wat_*.pdb] set i 0 foreach watnam $waterfiles { segment W${i} { auto none first none last none pdb $watnam } coordpdb $watnam W${i} incr i } ''' % self.tmp_dir self.file.write(string) molecule.set_top(old_top) return num_written
def get_cell_size(self, mem_buf, wat_buf, molid=None, filename=None, zh_mem_full=_MEMBRANE_FULL_THICKNESS / 2.0, zh_mem_hyd=_MEMBRANE_HYDROPHOBIC_THICKNESS / 2.0): """ Gets the cell size of the final system given initial system and buffers. Detects whether or not a membrane is present. Sets the size of the system. Args: mem_buf (float) : Membrane (xy) buffer amount wat_buf (float) : Water (z) buffer amount molid (int) : VMD molecule ID to consider (can't use with filename) filename (str) : Filename of system to consider (can't use w molid) zh_mem_full (float) : Membrane thickness zh_mem_hyd (float) : Membrane hydrophobic region thickness Returns: return dx_sol, dy_sol, dx_tm, dy_tm, dz_full (float tuple): x solute dimension, y solute dimension, TM x solute dimension, TM y solute dimension, solute z dimension Raises: ValueError: if filename and molid are both specified """ # Sanity check if filename is not None and molid is not None: raise ValueError("Specified molid and filename to get_cell_size") if filename is not None: top = molecule.get_top() molid = molecule.read(-1, 'mae', filename) elif molid is None: molid = molecule.get_top() # Some options different for water-only systems (no lipid) if self.water_only: solute_z = atomsel(self.solute_sel, molid=molid).get('z') dx_tm = 0.0 dy_tm = 0.0 sol_solute = atomsel(self.solute_sel, molid) else: solute_z = atomsel(self.solute_sel, molid=molid).get('z') tm_solute = atomsel( '(%s) and z > %f and z < %f' % (self.solute_sel, -zh_mem_hyd, zh_mem_hyd), molid) if len(tm_solute): dx_tm = max(tm_solute.get('x')) - min(tm_solute.get('x')) dy_tm = max(tm_solute.get('y')) - min(tm_solute.get('y')) else: dx_tm = dy_tm = 0 sol_solute = atomsel( '(%s) and (z < %f or z > %f)' % (self.solute_sel, -zh_mem_hyd, zh_mem_hyd), molid) # Solvent invariant options dx_sol = max(sol_solute.get('x')) - min(sol_solute.get('x')) dy_sol = max(sol_solute.get('y')) - min(sol_solute.get('y')) if self.opts.get('user_x'): self.size[0] = self.opts['user_x'] else: self.size[0] = max(dx_tm + 2. * mem_buf, dx_sol + 2. * wat_buf) if self.opts.get('user_y'): self.size[1] = self.opts['user_y'] else: self.size[1] = max(dy_tm + 2. * mem_buf, dy_sol + 2. * wat_buf) # Z dimension. If there's a membrane, need to account for asymmetry # in the Z dimension where the protein could be uneven in the membrane # or even peripheral if self.opts.get('user_z'): self.size[2] = self.opts['user_z'] buf = (self.opts['user_z'] - max(solute_z) + min(solute_z)) / 2 self._zmax = max(solute_z) + buf self._zmin = min(solute_z) - buf if zh_mem_full > self._zmax or -zh_mem_full < self._zmin: raise DabbleError("Specified user z of %f is too small to " "accomodate protein and membrane!" % self.opts['user_z']) else: if self.water_only: self._zmax = max(solute_z) + wat_buf self._zmin = min(solute_z) - wat_buf else: self._zmax = max(max(solute_z) + wat_buf, zh_mem_full) self._zmin = min(min(solute_z) - wat_buf, -zh_mem_full) self.size[2] = self._zmax - self._zmin # Cleanup temporary file, if read in if filename is not None: molecule.delete(molid) if top != -1: molecule.set_top(top) return dx_sol, dy_sol, dx_tm, dy_tm, max(solute_z) - min(solute_z)
def _find_single_residue_names(self, resname, molid): """ Uses graph matcher and available topologies to match up ligand names automatically. Tries to use graphs, and if there's an uneven number of atoms tries to match manually to suggest which atoms are most likely missing. Args: resname (str): Residue name of the ligand that will be written. All ligands will be checked separately against the graphs. molid (int): VMD molecule ID to consider Returns: (list of ints): Residue numbers (not resid) of all input ligands that were successfully matched. Need to do it this way since residue names can be changed in here to different things. Raises: ValueError if number of resids does not match number of residues as interpreted by VMD NotImplementedError if a residue could not be matched to a graph. """ # Put our molecule on top old_top = molecule.get_top() molecule.set_top(molid) # Sanity check that there is no discrepancy between defined resids and # residues as interpreted by VMD. residues = set(atomsel("user 1.0 and resname '%s'" % resname).residue) for chain in set(atomsel("user 1.0 and resname '%s'" % resname).chain): tempres = set( atomsel("user 1.0 and resname '%s' and chain %s" % (resname, chain)).residue) resids = set( atomsel("user 1.0 and resname '%s' and chain %s" % (resname, chain)).resid) if len(tempres) != len(resids): raise DabbleError("VMD found %d residues for resname '%s', " "but there are %d resids in chain %s! " "Check input." % (len(tempres), resname, len(resids), chain)) for residue in residues: sel = atomsel("residue %s and resname '%s' and user 1.0" % (residue, resname)) newname, atomnames = self.matcher.get_names(sel, print_warning=True) if not newname: resname, patch, atomnames = self.matcher.get_patches(sel) if not newname: print( "ERROR: Could not find a residue definition for %s:%s" % (resname, residue)) raise NotImplementedError( "No residue definition for %s:%s" % (resname, residue)) print("\tApplying patch %s to ligand %s" % (patch, newname)) # Do the renaming self._apply_naming_dictionary(atomnames=atomnames, resnames=newname, verbose=True) molecule.set_top(old_top) return list(residues)
def get_cell_size(self, mem_buf, wat_buf, molid=None, filename=None, zh_mem_full=_MEMBRANE_FULL_THICKNESS / 2.0, zh_mem_hyd=_MEMBRANE_HYDROPHOBIC_THICKNESS / 2.0): """ Gets the cell size of the final system given initial system and buffers. Detects whether or not a membrane is present. Sets the size of the system. Args: mem_buf (float) : Membrane (xy) buffer amount wat_buf (float) : Water (z) buffer amount molid (int) : VMD molecule ID to consider (can't use with filename) filename (str) : Filename of system to consider (can't use w molid) zh_mem_full (float) : Membrane thickness zh_mem_hyd (float) : Membrane hydrophobic region thickness Returns: return dx_sol, dy_sol, dx_tm, dy_tm, dz_full (float tuple): x solute dimension, y solute dimension, TM x solute dimension, TM y solute dimension, solute z dimension Raises: ValueError: if filename and molid are both specified """ # Sanity check if filename is not None and molid is not None: raise ValueError("Specified molid and filename to get_cell_size") if filename is not None: top = molecule.get_top() molid = molecule.read(-1, 'mae', filename) elif molid is None: molid = molecule.get_top() # Some options different for water-only systems (no lipid) if self.water_only: solute_z = atomsel(self.solute_sel, molid=molid).get('z') dx_tm = 0.0 dy_tm = 0.0 sol_solute = atomsel(self.solute_sel, molid) else: solute_z = atomsel(self.solute_sel, molid=molid).get('z') tm_solute = atomsel('(%s) and z > %f and z < %f' % (self.solute_sel, -zh_mem_hyd, zh_mem_hyd), molid) if len(tm_solute): dx_tm = max(tm_solute.get('x')) - min(tm_solute.get('x')) dy_tm = max(tm_solute.get('y')) - min(tm_solute.get('y')) else: dx_tm = dy_tm = 0 sol_solute = atomsel('(%s) and (z < %f or z > %f)' % (self.solute_sel, -zh_mem_hyd, zh_mem_hyd), molid) # Solvent invariant options dx_sol = max(sol_solute.get('x')) - min(sol_solute.get('x')) dy_sol = max(sol_solute.get('y')) - min(sol_solute.get('y')) if self.opts.get('user_x'): self.size[0] = self.opts['user_x'] else: self.size[0] = max(dx_tm + 2.*mem_buf, dx_sol + 2.*wat_buf) if self.opts.get('user_y'): self.size[1] = self.opts['user_y'] else: self.size[1] = max(dy_tm + 2.*mem_buf, dy_sol + 2.*wat_buf) # Z dimension. If there's a membrane, need to account for asymmetry # in the Z dimension where the protein could be uneven in the membrane # or even peripheral if self.opts.get('user_z'): self.size[2] = self.opts['user_z'] buf = (self.opts['user_z'] - max(solute_z) + min(solute_z))/2 self._zmax = max(solute_z) + buf self._zmin = min(solute_z) - buf if zh_mem_full > self._zmax or -zh_mem_full < self._zmin: raise ValueError("Specified user z of %f is too small to " "accomodate protein and membrane!" % self.opts['user_z']) else: if self.water_only: self._zmax = max(solute_z) + wat_buf self._zmin = min(solute_z) - wat_buf else: self._zmax = max(max(solute_z)+wat_buf, zh_mem_full) self._zmin = min(min(solute_z)-wat_buf, -zh_mem_full) self.size[2] = self._zmax - self._zmin # Cleanup temporary file, if read in if filename is not None: molecule.delete(molid) if top != -1: molecule.set_top(top) return dx_sol, dy_sol, dx_tm, dy_tm, max(solute_z)-min(solute_z)
def _write_protein_blocks(self, molid, frag): """ Writes a protein fragment to a pdb file for input to psfgen Automatically assigns amino acid names Args: molid (int): VMD molecule ID of renumbered protein frag (str): Fragment to write Returns: (list of Patches): Patches to add to psfgen input files """ print("Setting protein atom names") # Put our molecule on top to simplify atom selection language old_top = molecule.get_top() molecule.set_top(molid) patches = set() extpatches = set() # Get a unique and reliabe segment name seg = self.matcher.get_protein_segname(molid, frag) fragsel = atomsel("fragment '%s'" % frag) residues = list(set(fragsel.residue)) for residue in residues: sel = atomsel('residue %s' % residue) resid = sel.resid[0] # Only try to match single amino acid if there are 1 or 2 bonds if len(self.matcher.get_extraresidue_atoms(sel)) < 3: (newname, atomnames) = self.matcher.get_names(sel, False) # See if it's a disulfide bond participant else: (newname, patch, atomnames) = \ self.matcher.get_disulfide("residue %d" % residue, molid) if newname: extpatches.add(patch) # Couldn't find a match. See if it's a patched residue if not newname: (newname, patchname, atomnames) = self.matcher.get_patches(sel) if newname: # This returns patch name only, not a Patch object patches.add( Patch(name=patchname, segids=[seg], resids=[resid])) # Fall through to error condition if not newname: raise DabbleError("Couldn't find a patch for %s:%s" % (sel.resname[0], resid)) # Do the renaming self._apply_naming_dictionary(atomnames=atomnames, resnames=newname) # Save protein chain in the correct order filename = self.tmp_dir + '/psf_protein_%s.pdb' % seg _write_ordered_pdb(filename, "fragment '%s'" % frag, molid) print("\tWrote %d atoms to the protein segment %s" % (len(atomsel("fragment %s" % frag)), seg)) # Now invoke psfgen for the protein segments self.psfgen.add_segment(segid=seg, pdbfile=filename) print("Applying the following single-residue patches to P%s:\n" % frag) print("\t%s" % "\t".join(str(_) for _ in patches)) for p in patches: self.psfgen.patch(patchname=p.name, targets=p.targets()) self.psfgen.read_coords(segid=seg, filename=filename) # Fix coordinates that are out of bounds, ie 5 characters badidxs = atomsel( "fragment '%s' and (abs(x) >= 100 or abs(y) >= 100 " "or abs(z) >= 100)" % frag, molid).index for idx in badidxs: atom = atomsel("index %d" % idx, molid) self.psfgen.set_position(segid=seg, resid=atom.resid[0], atomname=atom.name[0], position=(atom.x[0], atom.y[0], atom.z[0])) if old_top != -1: molecule.set_top(old_top) fragsel.user = 0.0 return extpatches
def test_mol_attrs(file_3nob): m1 = molecule.load("mae", file_3nob) m2 = molecule.load("mae", file_3nob) # Get/set top assert molecule.get_top() == m2 molecule.set_top(molid=m1) assert molecule.get_top() == m1 with pytest.raises(ValueError): molecule.set_top(m2 + 1) # Get/set visibility molecule.set_visible(m1, visible=False) assert molecule.get_visible() == False assert molecule.get_visible(molid=m2) == True with pytest.warns(DeprecationWarning): molecule.set_visible(m1, state=True) assert molecule.get_visible(molid=m1) == True with pytest.raises(ValueError): molecule.set_visible(m2 + 1, True) with pytest.raises(TypeError): molecule.set_visible(m2, 3) with pytest.raises(ValueError): molecule.get_visible(m2 + 1) # Get/set periodic assert molecule.get_periodic(m2) == { 'a': 1.0, 'alpha': 90.0, 'b': 1.0, 'beta': 90.0, 'c': 1.0, 'gamma': 90.0 } with pytest.raises(ValueError): molecule.get_periodic(molid=m1, frame=3000) with pytest.raises(ValueError): molecule.set_periodic(molid=m2 + 1, a=2.0) with pytest.raises(ValueError): molecule.set_periodic(m1, frame=3000, a=20.0) molecule.set_periodic(m2, frame=0, a=90.0, b=90.0, c=90.0, alpha=90.0, beta=90.0, gamma=90.0) assert list(molecule.get_periodic( m2, frame=0).values()) == [pytest.approx(90.0)] * 6 assert set(molecule.get_periodic( m1, frame=0).values()) != [pytest.approx(90.0)] * 6 molecule.set_periodic(c=20.0) assert molecule.get_periodic()["c"] == pytest.approx(20.0) molecule.delete(m1) molecule.delete(m2)
def write(self, psf_name): """ Writes the pdb/psf file. Args: psf_name (str): Prefix for the pdb/psf output files, extension will be appended Returns: topologies (list of str): Topology files that were used in creating the psf """ # Clean up all temp files from previous runs if present # An earlier check will exit if it's not okay to overwrite here self.psf_name = psf_name try: os.remove('%s.pdb'% self.psf_name) os.remove('%s.psf'% self.psf_name) except OSError: pass # Finds the psfgen package and sets the output file name string = ''' set dir [file join $env(VMDDIR) plugins [vmdinfo arch] tcl psfgen1.6] package ifneeded psfgen 1.6 [list load [file join $dir libpsfgen.so]] package require psfgen set output "%s" resetpsf ''' % self.psf_name self.file.write(string) # Put our molecule on top old_top = molecule.get_top() molecule.set_top(self.molid) # Print out topology files self.file.write('\n') print("Using the following topologies:") for top in self.topologies: print(" - %s" % top.split("/")[-1]) self.file.write(' topology %s\n' % top) # Mark all atoms as unsaved with the user field atomsel('all', molid=self.molid).set('user', 1.0) check_atom_names(molid=self.molid) # Now ions if present, changing the atom names if len(atomsel('ions', molid=self.molid)) > 0: self._write_ion_blocks() # Save water 10k molecules at a time if len(atomsel('water', molid=self.molid)): self._write_water_blocks() # Now lipid if len(atomsel(self.lipid_sel)): self._write_lipid_blocks() # Now handle the protein # Save and reload the protein so residue looping is correct if len(atomsel("resname %s" % _acids, molid=self.molid)): extpatches = set() for frag in sorted(set(atomsel("resname %s" % _acids, molid=self.molid).get('fragment'))): extpatches.update(self._write_protein_blocks(self.molid, frag)) atomsel("same fragment as resname %s" % _acids, molid=self.molid).set("user", 0.0) # List all patches applied to the protein print("Applying the following patches:\n") print("\t%s" % "\t".join(extpatches)) self.file.write(''.join(extpatches)) self.file.write("\n") else: print("\tDidn't find any protein. Continuing...\n") # Regenerate angles and dihedrals after applying patches # Angles must be regenerated FIRST! # See http://www.ks.uiuc.edu/Research/namd/mailing_list/namd-l.2009-2010/4137.html self.file.write("regenerate angles\nregenerate dihedrals\n") # Check if there is anything else and let the user know about it leftovers = atomsel('user 1.0', molid=self.molid) for lig in set(leftovers.get('resname')): residues = self._find_single_residue_names(resname=lig, molid=self.molid) self._write_generic_block(residues) # Write the output files and run string = ''' writepsf x-plor cmap ${output}.psf writepdb ${output}.pdb''' self.file.write(string) self.file.close() evaltcl('play %s' % self.filename) self._check_psf_output() # Reset top molecule molecule.set_top(old_top) return self.topologies
def write(self, psf_name): """ Writes the pdb/psf file. Args: psf_name (str): Prefix for the pdb/psf output files, extension will be appended Returns: topologies (list of str): Topology files that were used in creating the psf """ # Clean up all temp files from previous runs if present # An earlier check will exit if it's not okay to overwrite here self.psf_name = psf_name try: os.remove('%s.pdb'% self.psf_name) os.remove('%s.psf'% self.psf_name) except OSError: pass # Finds the psfgen package and sets the output file name string = ''' set dir [file join $env(VMDDIR) plugins [vmdinfo arch] tcl psfgen1.6] package ifneeded psfgen 1.6 [list load [file join $dir libpsfgen.so]] package require psfgen set output "%s" resetpsf ''' % self.psf_name self.file.write(string) # Put our molecule on top old_top = molecule.get_top() molecule.set_top(self.molid) # Print out topology files self.file.write('\n') print("Using the following topologies:") for top in self.topologies: print(" - %s" % top.split("/")[-1]) self.file.write(' topology %s\n' % top) # Mark all atoms as unsaved with the user field atomsel('all', molid=self.molid).set('user', 1.0) check_atom_names(molid=self.molid) # Now ions if present, changing the atom names if len(atomsel('element Na Cl K', molid=self.molid)) > 0: self._write_ion_blocks() # Save water 10k molecules at a time if len(atomsel('water', molid=self.molid)): self._write_water_blocks() # Now lipid if len(atomsel(self.lipid_sel)): self._write_lipid_blocks() if not len(atomsel("resname %s" % _acids, molid=self.molid)): print("\tDidn't find any protein.\n") # Now handle the protein # Save and reload the protein so residue looping is correct prot_molid = self._renumber_protein_chains(molid=self.molid) extpatches = set() for frag in sorted(set(atomsel("resname %s" % _acids, molid=prot_molid).get('fragment'))): extpatches.update(self._write_protein_blocks(prot_molid, frag)) atomsel("same fragment as resname %s" % _acids, molid=self.molid).set("user", 0.0) # List all patches applied to the protein print("Applying the following patches:\n") print("\t%s" % "\t".join(extpatches)) self.file.write(''.join(extpatches)) self.file.write("\n") # Regenerate angles and dihedrals after applying patches # Angles must be regenerated FIRST! # See http://www.ks.uiuc.edu/Research/namd/mailing_list/namd-l.2009-2010/4137.html self.file.write("regenerate angles\nregenerate dihedrals\n") # Check if there is anything else and let the user know about it leftovers = atomsel('user 1.0', molid=self.molid) for lig in set(leftovers.get('resname')): residues = self._find_single_residue_names(resname=lig, molid=self.molid) self._write_generic_block(residues) # Write the output files and run string = ''' writepsf x-plor cmap ${output}.psf writepdb ${output}.pdb''' self.file.write(string) self.file.close() evaltcl('play %s' % self.filename) self._check_psf_output() # Reset top molecule molecule.set_top(old_top) return self.topologies
def _find_single_residue_names(self, resname, molid): """ Uses graph matcher and available topologies to match up ligand names automatically. Tries to use graphs, and if there's an uneven number of atoms tries to match manually to suggest which atoms are most likely missing. Args: resname (str): Residue name of the ligand that will be written. All ligands will be checked separately against the graphs. molid (int): VMD molecule ID to consider Returns: (list of ints): Residue numbers (not resid) of all input ligands that were successfully matched. Need to do it this way since residue names can be changed in here to different things. Raises: ValueError if number of resids does not match number of residues as interpreted by VMD NotImplementedError if a residue could not be matched to a graph. """ # Put our molecule on top old_top = molecule.get_top() molecule.set_top(molid) # Sanity check that there is no discrepancy between defined resids and # residues as interpreted by VMD. for chain in set(atomsel("user 1.0 and resname '%s'" % resname).get('chain')): residues = list(set(atomsel("user 1.0 and resname '%s' and chain %s" % (resname, chain)).get('residue'))) resids = list(set(atomsel("user 1.0 and resname '%s' and chain %s" % (resname, chain)).get('resid'))) if len(residues) != len(resids): raise DabbleError("VMD found %d residues for resname '%s', " "but there are %d resids! Check input." % (len(residues), resname, len(resids))) for residue in residues: sel = atomsel("residue %s and resname '%s' and user 1.0" % (residue, resname)) (newname, atomnames) = self.matcher.get_names(sel, print_warning=True) if not newname: (resname, patch, atomnames) = self.matcher.get_patches(sel) if not newname: print("ERROR: Could not find a residue definition for %s:%s" % (resname, residue)) raise NotImplementedError("No residue definition for %s:%s" % (resname, residue)) print("\tApplying patch %s to ligand %s" % (patch, newname)) # Do the renaming for idx, name in atomnames.items(): atom = atomsel('index %s' % idx) if atom.get('name')[0] != name and "+" not in name and \ "-" not in name: print("Renaming %s:%s: %s -> %s" % (resname, residue, atom.get('name')[0], name)) atom.set('name', name) sel.set('resname', newname) #logger.info("Renamed %d atoms for all resname %s->%s" % (num_renamed, resname, name)) molecule.set_top(old_top) return residues
def get_num_salt_ions_needed(molid, conc, water_sel='water and element O', cation='Na', anion='Cl'): """ Gets the number of salt ions needed to put the system at a given concentration of salt. Args: molid (int) : The VMD molecule ID to consider conc (float) : Desired salt concentration water_sel (str) : VMD atom selection for water cation (str) : Cation to use, either Na or K right now anion (str) : Anion to use, only Cl currently supported Returns: (float tuple) : # cations needed, # anions needed, number of waters that will remain, total # cations, total # anions, cation concentration, anion concentration Raises: Exception if number of cations and the net cation charge are not equal (should never happen) """ # pylint: disable = too-many-branches, too-many-locals cations = atomsel_remaining(molid, 'element %s' % cation) anions = atomsel_remaining(molid, 'element %s' % anion) molid = molecule.get_top() try: abs(get_net_charge(str(cations), molid)-len(cations)) > 0.01 except ValueError: # Check for bonded cations # Minimize the number of calls to atomsel nonbonded_cation_index = [cations.get('index')[i] \ for i in range(len(cations)) \ if len(cations.bonds[i]) == 0] if len(nonbonded_cation_index) == 0: cations = atomsel('none') else: cations = atomsel_remaining(molid, 'index '+' '.join(nonbonded_cation_index)) if abs(get_net_charge(str(cations), molid)-len(cations)) < 0.01: raise Exception('Num cations and net cation charge are not equal') try: abs(get_net_charge(str(anions), molid)+len(anions)) > 0.01 except ValueError: # Check for bonded anions nonbonded_anion_index = [anions.get('index')[i] \ for i in range(len(anions)) \ if len(anions.bonds[i]) == 0] #nonbonded_anion_index = [atomsel('index %d' % x).get('index')[0] \ # for x in np.nonzero(np.array(map(len, \ # atomsel_remaining(molid, 'element %s'%anion).bonds)) == 0)[0]] if len(nonbonded_anion_index) == 0: anions = atomsel('none') else: anions = atomsel_remaining(molid, 'index '+' '.join(nonbonded_anion_index)) if abs(get_net_charge(str(anions), molid)+len(anions)) < 0.01: raise Exception('num anions and abs anion charge are not equal') num_waters = num_atoms_remaining(molid, water_sel) num_for_conc = int(round(__1M_SALT_IONS_PER_WATER * num_waters * conc)) pos_ions_needed = num_for_conc - len(cations) neg_ions_needed = num_for_conc - len(anions) system_charge = get_system_net_charge(molid) new_system_charge = system_charge + len(anions) - len(cations) to_neutralize = abs(new_system_charge) if new_system_charge > 0: if to_neutralize > pos_ions_needed: neg_ions_needed += to_neutralize - pos_ions_needed pos_ions_needed = 0 else: pos_ions_needed -= to_neutralize else: if to_neutralize > neg_ions_needed: pos_ions_needed += to_neutralize - neg_ions_needed neg_ions_needed = 0 neg_ions_needed -= to_neutralize total_cations = len(cations) + pos_ions_needed total_anions = len(anions) + neg_ions_needed # volume estimate from prev waters cation_conc = (float(total_cations) / num_waters) / __1M_SALT_IONS_PER_WATER anion_conc = (float(total_anions) / num_waters) / __1M_SALT_IONS_PER_WATER num_waters -= pos_ions_needed + neg_ions_needed return (pos_ions_needed, neg_ions_needed, num_waters, total_cations, total_anions, cation_conc, anion_conc)
def get_num_salt_ions_needed(molid, conc, water_sel='water and element O', cation='Na', anion='Cl'): """ Gets the number of salt ions needed to put the system at a given concentration of salt. Args: molid (int) : The VMD molecule ID to consider conc (float) : Desired salt concentration water_sel (str) : VMD atom selection for water cation (str) : Cation to use, either Na or K right now anion (str) : Anion to use, only Cl currently supported Returns: (float tuple) : # cations needed, # anions needed, number of waters that will remain, total # cations, total # anions, cation concentration, anion concentration Raises: Exception if number of cations and the net cation charge are not equal (should never happen) """ # pylint: disable = too-many-branches, too-many-locals cations = atomsel_remaining(molid, 'element %s' % cation) anions = atomsel_remaining(molid, 'element %s' % anion) molid = molecule.get_top() try: abs(get_net_charge(str(cations), molid)-len(cations)) > 0.01 except ValueError: # Check for bonded cations # Minimize the number of calls to atomsel nonbonded_cation_index = [cations.index[i] \ for i in range(len(cations)) \ if len(cations.bonds[i]) == 0] if not nonbonded_cation_index: cations = atomsel('none') else: cations = atomsel_remaining(molid, 'index '+' '.join(nonbonded_cation_index)) if abs(get_net_charge(str(cations), molid)-len(cations)) < 0.01: raise Exception('Num cations and net cation charge are not equal') try: abs(get_net_charge(str(anions), molid)+len(anions)) > 0.01 except ValueError: # Check for bonded anions nonbonded_anion_index = [anions.index[i] \ for i in range(len(anions)) \ if len(anions.bonds[i]) == 0] if not nonbonded_anion_index: anions = atomsel('none') else: anions = atomsel_remaining(molid, 'index '+' '.join(nonbonded_anion_index)) if abs(get_net_charge(str(anions), molid)+len(anions)) < 0.01: raise Exception('num anions and abs anion charge are not equal') num_waters = num_atoms_remaining(molid, water_sel) num_for_conc = int(round(__1M_SALT_IONS_PER_WATER * num_waters * conc)) pos_ions_needed = num_for_conc - len(cations) neg_ions_needed = num_for_conc - len(anions) system_charge = get_system_net_charge(molid) new_system_charge = system_charge + len(anions) - len(cations) to_neutralize = abs(new_system_charge) if new_system_charge > 0: if to_neutralize > pos_ions_needed: neg_ions_needed += to_neutralize - pos_ions_needed pos_ions_needed = 0 else: pos_ions_needed -= to_neutralize else: if to_neutralize > neg_ions_needed: pos_ions_needed += to_neutralize - neg_ions_needed neg_ions_needed = 0 neg_ions_needed -= to_neutralize # Check for less than 0 pos_ions_needed = max(0, pos_ions_needed) neg_ions_needed = max(0, neg_ions_needed) total_cations = len(cations) + pos_ions_needed total_anions = len(anions) + neg_ions_needed # volume estimate from prev waters cation_conc = (float(total_cations) / num_waters) / __1M_SALT_IONS_PER_WATER anion_conc = (float(total_anions) / num_waters) / __1M_SALT_IONS_PER_WATER num_waters -= pos_ions_needed + neg_ions_needed return (pos_ions_needed, neg_ions_needed, num_waters, total_cations, total_anions, cation_conc, anion_conc)