示例#1
0
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)
示例#2
0
    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)
示例#3
0
文件: charmm.py 项目: chemlove/dabble
    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)
示例#4
0
    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)
示例#5
0
文件: charmm.py 项目: chemlove/dabble
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)
示例#6
0
文件: builder.py 项目: drorlab/dabble
    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)
示例#7
0
    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)
示例#8
0
文件: charmm.py 项目: drorlab/dabble
    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)
示例#9
0
文件: charmm.py 项目: drorlab/dabble
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)
示例#10
0
    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)
示例#11
0
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)
示例#12
0
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")
示例#13
0
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")
示例#14
0
    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
示例#15
0
文件: charmm.py 项目: chemlove/dabble
    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
示例#16
0
文件: charmm.py 项目: drorlab/dabble
    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
示例#17
0
文件: charmm.py 项目: chemlove/dabble
    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)
示例#18
0
文件: charmm.py 项目: drorlab/dabble
    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
示例#19
0
文件: charmm.py 项目: drorlab/dabble
    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
示例#20
0
文件: charmm.py 项目: chemlove/dabble
    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
示例#21
0
文件: charmm.py 项目: drorlab/dabble
    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
示例#22
0
文件: charmm.py 项目: drorlab/dabble
    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
示例#23
0
文件: charmm.py 项目: drorlab/dabble
    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)
示例#24
0
文件: charmm.py 项目: chemlove/dabble
    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
示例#25
0
    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)
示例#26
0
    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)
示例#27
0
文件: builder.py 项目: drorlab/dabble
    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)
示例#28
0
    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
示例#29
0
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)
示例#30
0
文件: charmm.py 项目: chemlove/dabble
    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
示例#31
0
文件: charmm.py 项目: drorlab/dabble
    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
示例#32
0
文件: charmm.py 项目: chemlove/dabble
    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
示例#33
0
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)
示例#34
0
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)