Пример #1
0
    def setUnitCellDimensions(self, dimensions):
        """Set the dimensions of the crystallographic unit cell.

        This method is an alternative to setPeriodicBoxVectors() for the case of a rectangular box.  It sets
        the box vectors to be orthogonal to each other and to have the specified lengths."""
        if dimensions is None:
            self._periodicBoxVectors = None
        else:
            if is_quantity(dimensions):
                dimensions = dimensions.value_in_unit(nanometers)
            self._periodicBoxVectors = (Vec3(dimensions[0], 0, 0), Vec3(0, dimensions[1], 0), Vec3(0, 0, dimensions[2]))*nanometers
Пример #2
0
    def run_simulation(self):
        """
        Run a simulation to calculate the current configuration's energy level.
        Note that the atoms will likely move somewhat during the calculation,
        since energy minimization is used.

        Returns:
            A tuple of the form (potential_energy, updated_positions)
        """
        # Delete solvent that's based on previous positions
        cartesian = self.zmat.get_cartesian().sort_index()
        self.simulation.context.setPositions([
            Vec3(x, y, z)
            for x, y, z in zip(cartesian['x'], cartesian['y'], cartesian['z'])
        ])

        # self._add_backbone_restraint()
        # self._fix_backbone()

        self.modeller.addSolvent(self.forcefield, padding=1.0 * u.nanometer)
        self.simulation.minimizeEnergy(maxIterations=200)
        state = self.simulation.context.getState(getEnergy=True,
                                                 getPositions=True)
        p_energy = state.getPotentialEnergy()
        positions = state.getPositions(asNumpy=True)

        # Clean up - remove solvent and backbone restraint (for next iteration)
        self.modeller.deleteWater()

        # self._remove_backbone_restraint()

        return p_energy, positions
Пример #3
0
 def _load(self, input_stream):
     # Read one line at a time
     for pdb_line in input_stream:
         # Look for atoms
         if (pdb_line.find("ATOM  ") == 0) or (pdb_line.find("HETATM")
                                               == 0):
             self._add_atom(Atom(pdb_line))
         # Notice MODEL punctuation, for the next level of detail
         # in the structure->model->chain->residue->atom->position hierarchy
         elif (pdb_line.find("MODEL") == 0):
             model_number = int(pdb_line[10:14])
             self._add_model(Model(model_number))
         elif (pdb_line.find("ENDMDL") == 0):
             self._current_model._finalize()
             if not self.load_all_models:
                 break
         elif (pdb_line.find("END") == 0):
             self._current_model._finalize()
             if not self.load_all_models:
                 break
         elif (pdb_line.find("TER") == 0 and pdb_line.split()[0] == "TER"):
             self._current_model._current_chain._add_ter_record()
         elif (pdb_line.find("CRYST1") == 0):
             self._unit_cell_dimensions = Vec3(
                 float(pdb_line[6:15]), float(pdb_line[15:24]),
                 float(pdb_line[24:33])) * unit.angstroms
         elif (pdb_line.find("CONECT") == 0):
             atoms = [int(pdb_line[6:11])]
             for pos in (11, 16, 21, 26):
                 try:
                     atoms.append(int(pdb_line[pos:pos + 5]))
                 except:
                     pass
             self._current_model.connects.append(atoms)
     self._finalize()
Пример #4
0
    def getUnitCellDimensions(self):
        """Get the dimensions of the crystallographic unit cell.

        The return value may be None if this Topology does not represent a periodic structure.
        """
        if self._periodicBoxVectors is None:
            return None
        xsize = self._periodicBoxVectors[0][0].value_in_unit(nanometers)
        ysize = self._periodicBoxVectors[1][1].value_in_unit(nanometers)
        zsize = self._periodicBoxVectors[2][2].value_in_unit(nanometers)
        return Vec3(xsize, ysize, zsize) * nanometers
Пример #5
0
    def _parse(self, fname):

        with open(fname, 'r') as crdfile:
            line = crdfile.readline()
    
            while len(line.strip()) == 0:      # Skip whitespace, as a precaution
                line = crdfile.readline()
    
            intitle = True
    
            while intitle:
                self.title.append(line.strip())
                line = crdfile.readline()
                if len(line.strip()) == 0:
                    intitle = False
                elif line.strip()[0] != '*':
                    intitle = False
                else:
                    intitle = True
    
            while len(line.strip()) == 0:      # Skip whitespace
                line = crdfile.readline()
    
            try:
                self.natom = int(line.strip().split()[0])
    
                for _ in range(self.natom):
                    line = crdfile.readline().strip().split()
                    self.atomno.append(int(line[0]))
                    self.resno.append(int(line[1]))
                    self.resname.append(line[2])
                    self.attype.append(line[3])
                    pos = Vec3(float(line[4]), float(line[5]), float(line[6]))
                    self.positions.append(pos)
                    self.segid.append(line[7])
                    self.weighting.append(float(line[9]))
    
                if self.natom != len(self.positions):
                    raise CharmmFileError("Error parsing CHARMM .crd file: %d "
                                          "atoms requires %d positions (not %d)" %
                                          (self.natom, self.natom,
                                           len(self.positions))
                    )
    
            except (ValueError, IndexError):
                raise CharmmFileError('Error parsing CHARMM coordinate file')

        # Apply units to the positions now. Do it this way to allow for
        # (possible) numpy functionality in the future.
        self.positions = u.Quantity(self.positions, u.angstroms)
Пример #6
0
    def _parse(self, fname):

        crdfile = open(fname, 'r')
        line = crdfile.readline()

        while len(line.strip()) == 0:  # Skip whitespace, as a precaution
            line = crdfile.readline()

        intitle = True

        while intitle:
            self.title.append(line.strip())
            line = crdfile.readline()
            if len(line.strip()) == 0:
                intitle = False
            elif line.strip()[0] != '*':
                intitle = False
            else:
                intitle = True

        while len(line.strip()) == 0:  # Skip whitespace
            line = crdfile.readline()

        try:
            self.natom = int(line.strip().split()[0])

            for row in range(self.natom):
                line = crdfile.readline().strip().split()
                self.atomno.append(int(line[0]))
                self.resno.append(int(line[1]))
                self.resname.append(line[2])
                self.attype.append(line[3])
                pos = Vec3(float(line[4]), float(line[5]), float(line[6]))
                self.positions.append(pos)
                self.segid.append(line[7])
                self.resid.append(int(line[8]))
                self.weighting.append(float(line[9]))

            if self.natom != len(self.positions):
                raise CharmmFileError(
                    "Error parsing CHARMM .crd file: %d "
                    "atoms requires %d positions (not %d)" %
                    (self.natom, self.natom, len(self.positions)))

        except (ValueError, IndexError), e:
            raise CharmmFileError('Error parsing CHARMM coordinate file')
Пример #7
0
 def _load(self, input_stream):
     self._reset_atom_numbers()
     self._reset_residue_numbers()
     # Read one line at a time
     for pdb_line in input_stream:
         # Look for atoms
         if (pdb_line.find("ATOM  ") == 0) or (pdb_line.find("HETATM") == 0):
             self._add_atom(Atom(pdb_line, self))
         # Notice MODEL punctuation, for the next level of detail
         # in the structure->model->chain->residue->atom->position hierarchy
         elif (pdb_line.find("MODEL") == 0):
             model_number = int(pdb_line[10:14])
             self._add_model(Model(model_number))
             self._reset_atom_numbers()
             self._reset_residue_numbers()
         elif (pdb_line.find("ENDMDL") == 0):
             self._current_model._finalize()
             if not self.load_all_models:
                 break
         elif (pdb_line.find("END") == 0):
             self._current_model._finalize()
             if not self.load_all_models:
                 break
         elif (pdb_line.find("TER") == 0 and pdb_line.split()[0] == "TER"):
             self._current_model._current_chain._add_ter_record()
             self._reset_residue_numbers()
         elif (pdb_line.find("CRYST1") == 0):
             self._unit_cell_dimensions = Vec3(float(pdb_line[6:15]), float(pdb_line[15:24]), float(pdb_line[24:33]))*unit.angstroms
         elif (pdb_line.find("CONECT") == 0):
             atoms = [int(pdb_line[6:11])]
             for pos in (11,16,21,26):
                 try:
                     atoms.append(int(pdb_line[pos:pos+5]))
                 except:
                     pass
             self._current_model.connects.append(atoms)
         elif (pdb_line.find("SEQRES") == 0):
             chain_id = pdb_line[11]
             if len(self.sequences) == 0 or chain_id != self.sequences[-1].chain_id:
                 self.sequences.append(Sequence(chain_id))
             self.sequences[-1].residues.extend(pdb_line[19:].split())
         elif (pdb_line.find("MODRES") == 0):
             self.modified_residues.append(ModifiedResidue(pdb_line[16], int(pdb_line[18:22]), pdb_line[12:15].strip(), pdb_line[24:27].strip()))
     self._finalize()
Пример #8
0
    def _get_formatted_crds(self, crdfile, crds):
        for row in range(self.natom):
            line = crdfile.readline()

            if not line:
                raise CharmmFileError('Premature end of file')

            if len(line) < 3*CHARMMLEN:
                raise CharmmFileError("Less than 3 coordinates present in "
                                      "coordinate row or positions may be "
                                      "truncated.")

            line = line.replace('D','E')     # CHARMM uses 'D' for exponentials

            # CHARMM uses fixed format (len = CHARMMLEN = 22) for crds in .rst's

            c = Vec3(float(line[0:CHARMMLEN]), float(line[CHARMMLEN:2*CHARMMLEN]),
                     float(line[2*CHARMMLEN:3*CHARMMLEN]))
            crds.append(c)
Пример #9
0
def addHydrogensPageCallback(parameters, handler):
    if 'addhydrogens' in parameters:
        pH = float(parameters.getfirst('ph'))
        fixer.addMissingHydrogens(pH)
    if 'addwater' in parameters:
        padding, boxSize, boxVectors = None, None, None
        if parameters.getfirst('boxType') == 'geometry':
            geompadding = float(
                parameters.getfirst('geomPadding')) * unit.nanometer
            geometry = parameters.getfirst('geometryDropdown')
            base_size = float(
                parameters.getfirst('maxMolecularAxis')) * unit.nanometer
            if geometry == 'cube':
                padding = geompadding
            elif geometry == 'truncatedOctahedron':
                vectors = Vec3(1, 0, 0), Vec3(1 / 3, 2 * sqrt(2) / 3,
                                              0), Vec3(-1 / 3,
                                                       sqrt(2) / 3,
                                                       sqrt(6) / 3)
                boxVectors = [(base_size + geompadding) * v for v in vectors]
            elif geometry == 'rhombicDodecahedron':
                vectors = Vec3(1, 0, 0), Vec3(0, 1,
                                              0), Vec3(0.5, 0.5,
                                                       sqrt(2) / 2)
                boxVectors = [(base_size + geompadding) * v for v in vectors]
        else:
            boxSize = (float(parameters.getfirst('boxx')),
                       float(parameters.getfirst('boxy')),
                       float(parameters.getfirst('boxz'))) * unit.nanometer
        ionicStrength = float(
            parameters.getfirst('ionicstrength')) * unit.molar
        positiveIon = parameters.getfirst('positiveion') + '+'
        negativeIon = parameters.getfirst('negativeion') + '-'
        fixer.addSolvent(boxSize, padding, boxVectors, positiveIon,
                         negativeIon, ionicStrength)
    if 'addmembrane' in parameters:
        lipidType = parameters.getfirst('lipidType')
        padding = float(
            parameters.getfirst('membranePadding')) * unit.nanometer
        ionicStrength = float(
            parameters.getfirst('ionicstrength')) * unit.molar
        positiveIon = parameters.getfirst('positiveion') + '+'
        negativeIon = parameters.getfirst('negativeion') + '-'
        fixer.addMembrane(lipidType, 0 * unit.nanometer, padding, positiveIon,
                          negativeIon, ionicStrength)
    displaySaveFilePage()
Пример #10
0
 def __sub__(self, other):
     """Add two Vec3s."""
     return Vec3(self.x - other[0], self.y - other[1],
                 self.z - other[2])
Пример #11
0
 def __radd__(self, other):
     """Add two Vec3s."""
     return Vec3(self.x + other[0], self.y + other[1],
                 self.z + other[2])
Пример #12
0
 def __radd__(self, other):
     """Add two Vec3s."""
     return Vec3(self[0] + other[0], self[1] + other[1],
                 self[2] + other[2])
Пример #13
0
 def __rsub__(self, other):
     """Add two Vec3s."""
     return Vec3(other[0] - self[0], other[1] - self[1],
                 other[2] - self[2])
Пример #14
0
 def __neg__(self):
     return Vec3(-self.x, -self.y, -self.z)
Пример #15
0
 def __div__(self, other):
     """Divide a Vec3 by a constant."""
     return Vec3(self.x / other, self.y / other, self.z / other)
Пример #16
0
 def __rsub__(self, other):
     """Add two Vec3s."""
     return Vec3(other[0] - self.x, other[1] - self.y,
                 other[2] - self.z)
Пример #17
0
 def __mul__(self, other):
     """Multiply a Vec3 by a constant."""
     if unit.is_unit(other):
         return unit.Quantity(self, other)
     return Vec3(other * self[0], other * self[1], other * self[2])
Пример #18
0
 def __deepcopy__(self, memo):
     return Vec3(self[0], self[1], self[2])
Пример #19
0
 def __div__(self, other):
     """Divide a Vec3 by a constant."""
     return Vec3(self[0] / other, self[1] / other, self[2] / other)
Пример #20
0
 def __rmul__(self, other):
     """Multiply a Vec3 by a constant."""
     if unit.is_unit(other):
         return unit.Quantity(self, other)
     return Vec3(other * self.x, other * self.y, other * self.z)
Пример #21
0
def dodecahedral_box_vectors(base_size, padding=0.0):
    vectors = Vec3(1, 0, 0), Vec3(0, 1, 0), Vec3(0.5, 0.5, sqrt(2) / 2)
    return (float(base_size) + padding) * (vectors * nm)
Пример #22
0
 def __deepcopy__(self, memo):
     return Vec3(self.x, self.y, self.z)
def compute_sterics_dvdl(sigma_1, sigma_2, epsilon_1, epsilon_2, r, l=0.5):
                            
    sigma = 0.5 * (sigma_1 + sigma_2)
    epsilon = np.sqrt(epsilon_1 * epsilon_2)
    
    reff = (0.5 * l * sigma ** 6 + r ** 6) ** (1 / 6)
    
    x = (sigma / reff) ** 6
    
    dreffdl = 1 / 6 * (0.5 * l * sigma ** 6 + r ** 6) ** (-5 / 6) * 0.5 * sigma ** 6
    
    dxdl = -6 * (sigma / reff) ** 5 * sigma / (reff ** 2) * dreffdl
    
    dudl = -4 * epsilon * x * (x - 1) + (1 - l) * 4 * epsilon * (dxdl * (x - 1) + x * dxdl)
    
    return dudl

    
for r in distance:
    
    context.setPositions([Vec3(x=0.0, y=0.0, z=0.0), Vec3(x=r, y=0.0, z=0.0)])

    state = context.getState(getEnergy=True, getParameterDerivatives=True)
    energy = state.getPotentialEnergy()
    energy_derivs = state.getEnergyParameterDerivatives()
        
    energy_deriv = energy_derivs['lambda']
    
    print (energy_deriv, compute_sterics_dvdl(sigmas[0]._value, sigmas[1]._value, epsilons[0]._value, epsilons[1]._value, r))
    def __init__(self, pdb_line, pdbstructure=None, extraParticleIdentifier='EP'):
        """Create a new pdb.Atom from an ATOM or HETATM line.

        Example line:
        ATOM   2209  CB  TYR A 299       6.167  22.607  20.046  1.00  8.12           C
        00000000011111111112222222222333333333344444444445555555555666666666677777777778
        12345678901234567890123456789012345678901234567890123456789012345678901234567890

        ATOM line format description from
          http://deposit.rcsb.org/adit/docs/pdb_atom_format.html:

        COLUMNS        DATA TYPE       CONTENTS
        --------------------------------------------------------------------------------
         1 -  6        Record name     "ATOM  "
         7 - 11        Integer         Atom serial number.
        13 - 16        Atom            Atom name.
        17             Character       Alternate location indicator.
        18 - 20        Residue name    Residue name.
        22             Character       Chain identifier.
        23 - 26        Integer         Residue sequence number.
        27             AChar           Code for insertion of residues.
        31 - 38        Real(8.3)       Orthogonal coordinates for X in Angstroms.
        39 - 46        Real(8.3)       Orthogonal coordinates for Y in Angstroms.
        47 - 54        Real(8.3)       Orthogonal coordinates for Z in Angstroms.
        55 - 60        Real(6.2)       Occupancy (Default = 1.0).
        61 - 66        Real(6.2)       Temperature factor (Default = 0.0).
        73 - 76        LString(4)      Segment identifier, left-justified.
        77 - 78        LString(2)      Element symbol, right-justified.
        79 - 80        LString(2)      Charge on the atom.

        """
        # We might modify first/final status during _finalize() methods
        self.is_first_atom_in_chain = False
        self.is_final_atom_in_chain = False
        self.is_first_residue_in_chain = False
        self.is_final_residue_in_chain = False
        # Start parsing fields from pdb line
        self.record_name = pdb_line[0:6].strip()
        if pdbstructure is not None and pdbstructure._atom_numbers_are_hex:
            self.serial_number = int(pdb_line[6:11], 16)
        else:
            try:
                self.serial_number = int(pdb_line[6:11])
            except:
                try:
                    self.serial_number = int(pdb_line[6:11], 16)
                    pdbstructure._atom_numbers_are_hex = True
                except:
                    # Just give it the next number in sequence.
                    self.serial_number = pdbstructure._next_atom_number
        self.name_with_spaces = pdb_line[12:16]
        alternate_location_indicator = pdb_line[16]

        self.residue_name_with_spaces = pdb_line[17:20]
        # In some MD codes, notably ffamber in gromacs, residue name has a fourth character in
        # column 21
        possible_fourth_character = pdb_line[20:21]
        if possible_fourth_character != " ":
            # Fourth character should only be there if official 3 are already full
            if len(self.residue_name_with_spaces.strip()) != 3:
                raise ValueError('Misaligned residue name: %s' % pdb_line)
            self.residue_name_with_spaces += possible_fourth_character
        self.residue_name = self.residue_name_with_spaces.strip()

        self.chain_id = pdb_line[21]
        if pdbstructure is not None and pdbstructure._residue_numbers_are_hex:
            self.residue_number = int(pdb_line[22:26], 16)
        else:
            try:
                self.residue_number = int(pdb_line[22:26])
            except:
                try:
                    self.residue_number = int(pdb_line[22:26], 16)
                    pdbstructure._residue_numbers_are_hex = True
                except:
                    # When VMD runs out of hex values it starts filling the residue ID field with ****.
                    # Look at the most recent atoms to figure out whether this is a new residue or not.
                    if pdbstructure._current_model is None or pdbstructure._current_model._current_chain is None or pdbstructure._current_model._current_chain._current_residue is None:
                        # This is the first residue in the model.
                        self.residue_number = pdbstructure._next_residue_number
                    else:
                        currentRes = pdbstructure._current_model._current_chain._current_residue
                        if currentRes.name_with_spaces != self.residue_name_with_spaces:
                            # The residue name has changed.
                            self.residue_number = pdbstructure._next_residue_number
                        elif self.name_with_spaces in currentRes.atoms_by_name:
                            # There is already an atom with this name.
                            self.residue_number = pdbstructure._next_residue_number
                        else:
                            self.residue_number = currentRes.number
        self.insertion_code = pdb_line[26]
        # coordinates, occupancy, and temperature factor belong in Atom.Location object
        x = float(pdb_line[30:38])
        y = float(pdb_line[38:46])
        z = float(pdb_line[46:54])
        try:
            occupancy = float(pdb_line[54:60])
        except:
            occupancy = 1.0
        try:
            temperature_factor = float(pdb_line[60:66]) * unit.angstroms * unit.angstroms
        except:
            temperature_factor = 0.0 * unit.angstroms * unit.angstroms
        self.locations = {}
        loc = Atom.Location(alternate_location_indicator, Vec3(x,y,z) * unit.angstroms, occupancy, temperature_factor, self.residue_name_with_spaces)
        self.locations[alternate_location_indicator] = loc
        self.default_location_id = alternate_location_indicator
        # segment id, element_symbol, and formal_charge are not always present
        self.segment_id = pdb_line[72:76].strip()
        self.element_symbol = pdb_line[76:78].strip()
        try: self.formal_charge = int(pdb_line[78:80])
        except ValueError: self.formal_charge = None
        # figure out atom element
        if self.element_symbol == extraParticleIdentifier:
            self.element = 'EP'
        else:
            try:
                # Try to find a sensible element symbol from columns 76-77
                self.element = element.get_by_symbol(self.element_symbol)
            except KeyError:    
                self.element = None
        if pdbstructure is not None:
            pdbstructure._next_atom_number = self.serial_number+1
            pdbstructure._next_residue_number = self.residue_number+1
Пример #25
0
    def addHydrogens(self,
                     forcefield=None,
                     pH=None,
                     variants=None,
                     platform=None):
        """Add missing hydrogens to the model.

        This function automatically changes compatible residues into their constant-pH variant if no variant is specified.:

        Aspartic acid:
            AS4: Form with a 2 hydrogens on each one of the delta oxygens (syn,anti)
                It has 5 titration states.

            Alternative:
            AS2: Has 2 hydrogens (syn, anti) on one of the delta oxygens
                It has 3 titration states.

        Cysteine:
            CYS: Neutral form with a hydrogen on the sulfur
            CYX: No hydrogen on the sulfur (either negatively charged, or part of a disulfide bond)

        Glutamic acid:
            GL4: Form with a 2 hydrogens on each one of the epsilon oxygens (syn,anti)
                It has 5 titration states.

        Histidine:
            HIP: Positively charged form with hydrogens on both ND1 and NE2
                It has 3 titration states.

        The variant to use for each residue is determined by the following rules:

        1. Any Cysteine that participates in a disulfide bond uses the CYX variant regardless of pH.
        2. Other residues are all set to maximally protonated state, which can be updated using a proton drive

        You can override these rules by explicitly specifying a variant for any residue.  To do that, provide a list for the
        'variants' parameter, and set the corresponding element to the name of the variant to use.

        A special case is when the model already contains a hydrogen that should not be present in the desired variant.
        If you explicitly specify a variant using the 'variants' parameter, the residue will be modified to match the
        desired variant, removing hydrogens if necessary.  On the other hand, for residues whose variant is selected
        automatically, this function will only add hydrogens.  It will never remove ones that are already present in the
        model.

        Definitions for standard amino acids and nucleotides are built in.  You can call loadHydrogenDefinitions() to load
        additional definitions for other residue types.

        Parameters
        ----------
        forcefield : ForceField=None
            the ForceField to use for determining the positions of hydrogens.
            If this is None, positions will be picked which are generally
            reasonable but not optimized for any particular ForceField.
        pH : None,
            Kept for compatibility reasons. Has no effect.
        variants : list=None
            an optional list of variants to use.  If this is specified, its
            length must equal the number of residues in the model.  variants[i]
            is the name of the variant to use for residue i (indexed starting at
            0). If an element is None, the standard rules will be followed to
            select a variant for that residue.
        platform : Platform=None
            the Platform to use when computing the hydrogen atom positions.  If
            this is None, the default Platform will be used.

        Returns
        -------
        list
             a list of what variant was actually selected for each residue,
             in the same format as the variants parameter

        Notes
        -----

        This function does not use a pH specification. The argument is kept for compatibility reasons.

        """
        # Check the list of variants.

        if pH is not None:
            print("Ignored pH argument provided for constant-pH residues.")

        residues = list(self.topology.residues())
        if variants is not None:
            if len(variants) != len(residues):
                raise ValueError(
                    "The length of the variants list must equal the number of residues"
                )
        else:
            variants = [None] * len(residues)
        actualVariants = [None] * len(residues)

        # Load the residue specifications.

        if not Modeller._hasLoadedStandardHydrogens:
            Modeller.loadHydrogenDefinitions(
                os.path.join(os.path.dirname(__file__), "data",
                             "hydrogens-amber10-constph.xml"))

        # Make a list of atoms bonded to each atom.

        bonded = {}
        for atom in self.topology.atoms():
            bonded[atom] = []
        for atom1, atom2 in self.topology.bonds():
            bonded[atom1].append(atom2)
            bonded[atom2].append(atom1)

        # Define a function that decides whether a set of atoms form a hydrogen bond, using fairly tolerant criteria.

        def isHbond(d, h, a):
            if norm(d - a) > 0.35 * nanometer:
                return False
            deltaDH = h - d
            deltaHA = a - h
            deltaDH /= norm(deltaDH)
            deltaHA /= norm(deltaHA)
            return acos(dot(deltaDH, deltaHA)) < 50 * degree

        # Loop over residues.

        newTopology = Topology()
        newTopology.setPeriodicBoxVectors(
            self.topology.getPeriodicBoxVectors())
        newAtoms = {}
        newPositions = [] * nanometer
        newIndices = []
        acceptors = [
            atom for atom in self.topology.atoms()
            if atom.element in (elem.oxygen, elem.nitrogen)
        ]
        for chain in self.topology.chains():
            newChain = newTopology.addChain(chain.id)
            for residue in chain.residues():
                newResidue = newTopology.addResidue(residue.name, newChain,
                                                    residue.id)
                isNTerminal = residue == chain._residues[0]
                isCTerminal = residue == chain._residues[-1]
                if residue.name in Modeller._residueHydrogens:
                    # Add hydrogens.  First select which variant to use.

                    spec = Modeller._residueHydrogens[residue.name]
                    variant = variants[residue.index]
                    if variant is None:
                        if residue.name == "CYS":
                            # If this is part of a disulfide, use CYX.

                            sulfur = [
                                atom for atom in residue.atoms()
                                if atom.element == elem.sulfur
                            ]
                            if len(sulfur) == 1 and any(
                                (atom.residue != residue
                                 for atom in bonded[sulfur[0]])):
                                variant = "CYX"
                        if residue.name == "HIS":
                            variant = "HIP"
                        if residue.name == "GLU":
                            variant = "GL4"
                        if residue.name == "ASP":
                            variant = "AS4"
                    if variant is not None and variant not in spec.variants:
                        raise ValueError("Illegal variant for %s residue: %s" %
                                         (residue.name, variant))
                    actualVariants[residue.index] = variant
                    removeExtraHydrogens = variants[residue.index] is not None

                    # Make a list of hydrogens that should be present in the residue.

                    parents = [
                        atom for atom in residue.atoms()
                        if atom.element != elem.hydrogen
                    ]
                    parentNames = [atom.name for atom in parents]
                    hydrogens = [
                        h for h in spec.hydrogens
                        if (variant is None) or (h.variants is None) or (
                            h.variants is not None and variant in h.variants)
                    ]
                    hydrogens = [
                        h for h in hydrogens if h.terminal is None or (
                            isNTerminal and h.terminal == "N") or (
                                isCTerminal and h.terminal == "C")
                    ]
                    hydrogens = [
                        h for h in hydrogens if h.parent in parentNames
                    ]

                    # Loop over atoms in the residue, adding them to the new topology along with required hydrogens.

                    for parent in residue.atoms():
                        # Check whether this is a hydrogen that should be removed.

                        if (removeExtraHydrogens
                                and parent.element == elem.hydrogen
                                and not any(parent.name == h.name
                                            for h in hydrogens)):
                            continue

                        # Add the atom.

                        newAtom = newTopology.addAtom(parent.name,
                                                      parent.element,
                                                      newResidue)
                        newAtoms[parent] = newAtom
                        newPositions.append(
                            deepcopy(self.positions[parent.index]))
                        if parent in parents:
                            # Match expected hydrogens with existing ones and find which ones need to be added.

                            existing = [
                                atom for atom in bonded[parent]
                                if atom.element == elem.hydrogen
                            ]
                            expected = [
                                h for h in hydrogens if h.parent == parent.name
                            ]
                            if len(existing) < len(expected):
                                # Try to match up existing hydrogens to expected ones.

                                matches = []
                                for e in existing:
                                    match = [
                                        h for h in expected if h.name == e.name
                                    ]
                                    if len(match) > 0:
                                        matches.append(match[0])
                                        expected.remove(match[0])
                                    else:
                                        matches.append(None)

                                # If any hydrogens couldn't be matched by name, just match them arbitrarily.

                                for i in range(len(matches)):
                                    if matches[i] is None:
                                        matches[i] = expected[-1]
                                        expected.remove(expected[-1])

                                # Add the missing hydrogens.

                                for h in expected:
                                    newH = newTopology.addAtom(
                                        h.name, elem.hydrogen, newResidue)
                                    newIndices.append(newH.index)
                                    delta = Vec3(0, 0, 0) * nanometer
                                    if len(bonded[parent]) > 0:
                                        for other in bonded[parent]:
                                            delta += (
                                                self.positions[parent.index] -
                                                self.positions[other.index])
                                    else:
                                        delta = (Vec3(
                                            random.random(),
                                            random.random(),
                                            random.random(),
                                        ) * nanometer)
                                    delta *= 0.1 * nanometer / norm(delta)
                                    delta += (0.05 * Vec3(
                                        random.random(),
                                        random.random(),
                                        random.random(),
                                    ) * nanometer)
                                    delta *= 0.1 * nanometer / norm(delta)
                                    newPositions.append(
                                        self.positions[parent.index] + delta)
                                    newTopology.addBond(newAtom, newH)
                else:
                    # Just copy over the residue.

                    for atom in residue.atoms():
                        newAtom = newTopology.addAtom(atom.name, atom.element,
                                                      newResidue)
                        newAtoms[atom] = newAtom
                        newPositions.append(
                            deepcopy(self.positions[atom.index]))
        for bond in self.topology.bonds():
            if bond[0] in newAtoms and bond[1] in newAtoms:
                newTopology.addBond(newAtoms[bond[0]], newAtoms[bond[1]])

        # The hydrogens were added at random positions.  Now perform an energy minimization to fix them up.

        if forcefield is not None:
            # Use the ForceField the user specified.

            system = forcefield.createSystem(newTopology, rigidWater=False)
            atoms = list(newTopology.atoms())
            for i in range(system.getNumParticles()):
                if atoms[i].element != elem.hydrogen:
                    # This is a heavy atom, so make it immobile.
                    system.setParticleMass(i, 0)
        else:
            # Create a System that restrains the distance of each hydrogen from its parent atom
            # and causes hydrogens to spread out evenly.

            system = System()
            nonbonded = CustomNonbondedForce("100/((r/0.1)^4+1)")
            bonds = HarmonicBondForce()
            angles = HarmonicAngleForce()
            system.addForce(nonbonded)
            system.addForce(bonds)
            system.addForce(angles)
            bondedTo = []
            for atom in newTopology.atoms():
                nonbonded.addParticle([])
                if atom.element != elem.hydrogen:
                    system.addParticle(0.0)
                else:
                    system.addParticle(1.0)
                bondedTo.append([])
            for atom1, atom2 in newTopology.bonds():
                if atom1.element == elem.hydrogen or atom2.element == elem.hydrogen:
                    bonds.addBond(atom1.index, atom2.index, 0.1, 100_000.0)
                bondedTo[atom1.index].append(atom2)
                bondedTo[atom2.index].append(atom1)
            for residue in newTopology.residues():
                if residue.name == "HOH":
                    # Add an angle term to make the water geometry correct.

                    atoms = list(residue.atoms())
                    oindex = [
                        i for i in range(len(atoms))
                        if atoms[i].element == elem.oxygen
                    ]
                    if len(atoms) == 3 and len(oindex) == 1:
                        hindex = list(set([0, 1, 2]) - set(oindex))
                        angles.addAngle(
                            atoms[hindex[0]].index,
                            atoms[oindex[0]].index,
                            atoms[hindex[1]].index,
                            1.824,
                            836.8,
                        )
                else:
                    # Add angle terms for any hydroxyls.

                    for atom in residue.atoms():
                        index = atom.index
                        if (atom.element == elem.oxygen
                                and len(bondedTo[index]) == 2 and elem.hydrogen
                                in (a.element for a in bondedTo[index])):
                            angles.addAngle(
                                bondedTo[index][0].index,
                                index,
                                bondedTo[index][1].index,
                                1.894,
                                460.24,
                            )

        if platform is None:
            context = Context(system, VerletIntegrator(0.0))
        else:
            context = Context(system, VerletIntegrator(0.0), platform)
        context.setPositions(newPositions)
        LocalEnergyMinimizer.minimize(context, 1.0, 50)
        self.topology = newTopology
        self.positions = context.getState(getPositions=True).getPositions()
        del context
        return actualVariants