示例#1
0
    def _reset_mappings(self):
        """Internal function to reset index mapping dictionaries."""

        # Clear dictionaries.
        self._molecule_index = {}
        self._atom_index_tally = {}
        self._residue_index_tally = {}

        # Rebuild the MolNum to index mapping.
        for idx in range(0, self.nMolecules()):
            self._molecule_index[self._sire_object[_SireMol.MolIdx(idx)].number()] = idx
示例#2
0
    def run(self, molecule, work_dir=None, queue=None):
        """Run the parameterisation protocol.

           Parameters
           ----------

           molecule : BioSimSpace._SireWrappers.Molecule
               The molecule to apply the parameterisation protocol to.

           work_dir : str
               The working directory.

           queue : queue.Queue
               The thread queue is which this method has been run.

           Returns
           -------

           molecule : BioSimSpace._SireWrappers.Molecule
               The parameterised molecule.
        """

        if type(molecule) is not _Molecule:
            raise TypeError(
                "'molecule' must be of type 'BioSimSpace._SireWrappers.Molecule'"
            )

        if type(work_dir) is not None and type(work_dir) is not str:
            raise TypeError("'work_dir' must be of type 'str'")

        if type(queue) is not None and type(queue) is not _queue.Queue:
            raise TypeError("'queue' must be of type 'queue.Queue'")

        # Set work_dir to the current directory.
        if work_dir is None:
            work_dir = _os.getcwd()

        # Create the file prefix.
        prefix = work_dir + "/"

        # Create a copy of the molecule.
        new_mol = molecule.copy()

        # Use the net molecular charge passed as an option.
        if self._net_charge is not None:
            charge = self._net_charge
        else:
            # The user will likely have passed a bare PDB or Mol2 file.
            # Antechamber expects the molecule to be uncharged, or integer
            # charged (where the charge, or number of electrons, is passed with
            # the -nc flag).

            # Get the total charge on the molecule.
            if "charge" in self._property_map:
                _property_map = {"charge": self._property_map["charge"]}
                prop = self._property_map["charge"]
            else:
                _property_map = {"charge": "charge"}
                prop = "charge"

            # The molecule has a charge property.
            if new_mol._getSireObject().hasProperty(prop):
                charge = new_mol.charge(property_map=_property_map).magnitude()

                # Charge is non-integer, try to fix it.
                if abs(round(charge) - charge) > 0:
                    new_mol._fixCharge(property_map=_property_map)
                    charge = round(charge)
            else:
                charge = None

            # Only try "formal_charge" when "charge" is missing. Unlikely to have
            # both if this is a bare molecule, but the user could be re-parameterising
            # an existing molecule.
            if charge is None:
                # Get the total formal charge on the molecule.
                if "formal_charge" in self._property_map:
                    _property_map = {
                        "charge": self._property_map["formal_charge"]
                    }
                    prop = self._property_map["charge"]
                else:
                    _property_map = {"charge": "formal_charge"}
                    prop = "formal_charge"

                if new_mol._getSireObject().hasProperty(prop):
                    charge = new_mol.charge(
                        property_map=_property_map).magnitude()

                    # Compute the formal charge ourselves to check that it is consistent.
                    formal_charge = _formalCharge(molecule).magnitude()

                    if charge != formal_charge:
                        _warnings.warn(
                            "The formal charge on the molecule is %d "
                            "but we estimate it to be %d" %
                            (charge, formal_charge))
                else:
                    msg = (
                        "The molecule has no 'charge' or 'formal_charge' information, and "
                        "no 'net_charge' option has been passed. You can use the "
                        "'BioSimSpace.Parameters.formalCharge' function to compute the "
                        "formal charge")
                    raise _ParameterisationError(msg)

        # Create a new system and molecule group.
        s = _SireSystem.System("BioSimSpace System")
        m = _SireMol.MoleculeGroup("all")

        # Add the molecule.
        m.add(new_mol._getSireObject())
        s.add(m)

        # Write the system to a PDB file.
        try:
            pdb = _SireIO.PDB2(s)
            pdb.writeToFile(prefix + "antechamber.pdb")
        except Exception as e:
            msg = "Failed to write system to 'PDB' format."
            if _isVerbose():
                raise IOError(msg) from e
            else:
                raise IOError(msg) from None

        # Generate the Antechamber command.
        command = ("%s -at %d -i antechamber.pdb -fi pdb " +
                   "-o antechamber.mol2 -fo mol2 -c %s -s 2 -nc %d") % (
                       _protocol._antechamber_exe, self._version,
                       self._charge_method.lower(), charge)

        with open(prefix + "README.txt", "w") as file:
            # Write the command to file.
            file.write("# Antechamber was run with the following command:\n")
            file.write("%s\n" % command)

        # Create files for stdout/stderr.
        stdout = open(prefix + "antechamber.out", "w")
        stderr = open(prefix + "antechamber.err", "w")

        # Run Antechamber as a subprocess.
        proc = _subprocess.run(command,
                               cwd=work_dir,
                               shell=True,
                               stdout=stdout,
                               stderr=stderr)
        stdout.close()
        stderr.close()

        # Antechamber doesn't return sensible error codes, so we need to check that
        # the expected output was generated.
        if _os.path.isfile(prefix + "antechamber.mol2"):

            # Run parmchk to check for missing parameters.
            command = ("%s -s %d -i antechamber.mol2 -f mol2 " +
                       "-o antechamber.frcmod") % (_protocol._parmchk_exe,
                                                   self._version)

            with open(prefix + "README.txt", "a") as file:
                # Write the command to file.
                file.write("\n# ParmChk was run with the following command:\n")
                file.write("%s\n" % command)

            # Create files for stdout/stderr.
            stdout = open(prefix + "parmchk.out", "w")
            stderr = open(prefix + "parmchk.err", "w")

            # Run parmchk as a subprocess.
            proc = _subprocess.run(command,
                                   cwd=work_dir,
                                   shell=True,
                                   stdout=stdout,
                                   stderr=stderr)
            stdout.close()
            stderr.close()

            # The frcmod file was created.
            if _os.path.isfile(prefix + "antechamber.frcmod"):

                # Now call tLEaP using the partially parameterised molecule and the frcmod file.
                # tLEap will run in the same working directory, using the Mol2 file generated by
                # Antechamber.

                # Try to find a force field file.
                if self._version == 1:
                    ff = _protocol._find_force_field("gaff")
                else:
                    ff = _protocol._find_force_field("gaff2")

                # Write the LEaP input file.
                with open(prefix + "leap.txt", "w") as file:
                    file.write("source %s\n" % ff)
                    file.write("mol = loadMol2 antechamber.mol2\n")
                    file.write("loadAmberParams antechamber.frcmod\n")
                    file.write("saveAmberParm mol leap.top leap.crd\n")
                    file.write("quit")

                # Generate the tLEaP command.
                command = "%s -f leap.txt" % _protocol._tleap_exe

                with open(prefix + "README.txt", "a") as file:
                    # Write the command to file.
                    file.write(
                        "\n# tLEaP was run with the following command:\n")
                    file.write("%s\n" % command)

                # Create files for stdout/stderr.
                stdout = open(prefix + "tleap.out", "w")
                stderr = open(prefix + "tleap.err", "w")

                # Run tLEaP as a subprocess.
                proc = _subprocess.run(command,
                                       cwd=work_dir,
                                       shell=True,
                                       stdout=stdout,
                                       stderr=stderr)
                stdout.close()
                stderr.close()

                # tLEaP doesn't return sensible error codes, so we need to check that
                # the expected output was generated.
                if _os.path.isfile(prefix +
                                   "leap.top") and _os.path.isfile(prefix +
                                                                   "leap.crd"):
                    # Load the parameterised molecule.
                    try:
                        par_mol = _Molecule(
                            _IO.readMolecules([
                                prefix + "leap.top", prefix + "leap.crd"
                            ])._getSireObject()[_SireMol.MolIdx(0)])
                    except Exception as e:
                        msg = "Failed to read molecule from: 'leap.top', 'leap.crd'"
                        if _isVerbose():
                            raise IOError(msg) from e
                        else:
                            raise IOError(msg) from None

                    # Make the molecule 'mol' compatible with 'par_mol'. This will create
                    # a mapping between atom indices in the two molecules and add all of
                    # the new properties from 'par_mol' to 'mol'.
                    new_mol._makeCompatibleWith(
                        par_mol,
                        property_map=self._property_map,
                        overwrite=True,
                        verbose=False)

                    # Record the forcefield used to parameterise the molecule.
                    new_mol._forcefield = ff

                else:
                    raise _ParameterisationError("tLEaP failed!")
            else:
                raise _ParameterisationError("Parmchk failed!")
        else:
            raise _ParameterisationError("Antechamber failed!")

        if queue is not None:
            queue.put(new_mol)
        return new_mol
示例#3
0
    def getIndex(self, item):
        """Convert indices of atoms and residues to their absolute values in
           this system.

           Parameters
           ----------

           item : :class:`Atom <BioSimSpace._SireWrappers.Atom>`, \
                  :class:`Residue <BioSimSpace._SireWrappers.Residue>` \
                  :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`
               An Atom, Residue, or Molecule object from the System, or a
               list containing objects of these types.

           Returns
           -------

           index : int, [int]
               The absolute index of the atom/residue/molecule in the system.
        """

        # Convert single object to list.
        if type(item) is not tuple and type(item) is not list:
            item = [item]

        # Create a list to hold the indices.
        indices = []

        # Loop over all of the items.
        for x in item:
            # Atom.
            if type(x) is _Atom:
                # Only compute the atom index mapping if it hasn't already
                # been created.
                if len(self._atom_index_tally) == 0:
                    # Loop over all molecules in the system and keep track
                    # of the cumulative number of atoms.
                    num_atoms = 0
                    for num in self._sire_object.molNums():
                        self._atom_index_tally[num] = num_atoms
                        num_atoms += self._sire_object.molecule(num).nAtoms()

                # Get the AtomIdx and MolNum from the Atom.
                index = x.index()
                mol_num = x._sire_object.molecule().number()

                try:
                    index += self._atom_index_tally[mol_num]
                except KeyError:
                    raise KeyError("The atom belongs to molecule '%s' that is not part of "
                                "this system!" % mol_num)

                indices.append(index)

            # Residue.
            elif type(x) is _Residue:
                # Only compute the residue index mapping if it hasn't already
                # been created.
                if len(self._residue_index_tally) == 0:
                    # Loop over all molecules in the system and keep track
                    # of the cumulative number of residues.
                    num_residues = 0
                    for num in self._sire_object.molNums():
                        self._residue_index_tally[num] = num_residues
                        num_residues += self._sire_object.molecule(num).nResidues()

                # Get the ResIdx and MolNum from the Residue.
                index = x.index()
                mol_num = x._sire_object.molecule().number()

                try:
                    index += self._residue_index_tally[mol_num]
                except KeyError:
                    raise KeyError("The residue belongs to molecule '%s' that is not part of "
                                "this system!" % mol_num)

                indices.append(index)

            # Residue.
            elif type(x) is _Molecule:
                # Only compute the molecule index mapping if it hasn't already
                # been created.
                if len(self._molecule_index) == 0:
                    # Loop over all molecules in the system by index and map
                    # the MolNum to index.
                    for idx in range(0, self.nMolecules()):
                        self._molecule_index[self._sire_object[_SireMol.MolIdx(idx)].number()] = idx

                # Get the MolNum from the molecule.
                mol_num = x._sire_object.molecule().number()

                try:
                    index = self._molecule_index[mol_num]
                except KeyError:
                    raise KeyError("The molecule '%s' is not part of this system!" % mol_num)

                indices.append(index)

            # Unsupported.
            else:
                raise TypeError("'item' must be of type 'BioSimSpace._SireWrappers.Atom' "
                                "or 'BioSimSpace._SireWrappers.Residue'")

        # If this was a single object, then return a single index.
        if len(indices) == 1:
            return indices[0]
        # Return the list of indices.
        else:
            return indices
示例#4
0
    def _updateCoordinates(self, system, property_map0={}, property_map1={},
            is_lambda1=False):
        """Update the coordinates of atoms in the system.

           Parameters
           ----------

           system : :class:`System <BioSimSpace._SireWrappers.System>`
               A system containing the updated coordinates.

           property_map0 : dict
               A dictionary that maps system "properties" to their user defined
               values in this system.

           property_map1 : dict
               A dictionary that maps system "properties" to their user defined
               values in the passed system.

           is_lambda1 : bool
              Whether to update coordinates of perturbed molecules at lambda = 1.
              By default, coordinates at lambda = 0 are used.
        """

        # Validate the system.
        if type(system) is not System:
            raise TypeError("'system' must be of type 'BioSimSpace._SireWrappers.System'")

        # Check that the passed system contains the same number of molecules.
        if self.nMolecules() != system.nMolecules():
            raise _IncompatibleError("The passed 'system' contains a different number of "
                                     "molecules. Expected '%d', found '%d'"
                                     % (self.nMolecules(), system.nMolecules()))

        # Check that each molecule in the system contains the same number of atoms.
        for idx in range(0, self.nMolecules()):
            # Extract the number of atoms in the molecules.
            num_atoms0 = self._sire_object.molecule(_SireMol.MolIdx(idx)).nAtoms()
            num_atoms1 = self._sire_object.molecule(_SireMol.MolIdx(idx)).nAtoms()

            if num_atoms0 != num_atoms1:
                raise _IncompatibleError("Mismatch in atom count for molecule '%d': "
                                         "Expected '%d', found '%d'" % (num_atoms0, num_atoms1))

        # Work out the name of the "coordinates" property.
        prop0 = property_map0.get("coordinates0", "coordinates")
        prop1 = property_map1.get("coordinates1", "coordinates")

        # Loop over all molecules and update the coordinates.
        for idx in range(0, self.nMolecules()):
            # Extract the molecules from each system.
            mol0 = self._sire_object.molecule(_SireMol.MolIdx(idx))
            mol1 = system._sire_object.molecule(_SireMol.MolIdx(idx))

            # Check whether the molecule is perturbable.
            if mol0.hasProperty("is_perturbable"):
                if is_lambda1:
                    prop = "coordinates1"
                else:
                    prop = "coordinates0"
            else:
                prop = prop0

            # Try to update the coordinates property.
            try:
                mol0 = mol0.edit().setProperty(prop, mol1.property(prop1)).molecule().commit()
            except:
                raise _IncompatibleError("Unable to update 'coordinates' for molecule index '%d'" % idx)

            # Update the molecule in the original system.
            self._sire_object.update(mol0)
示例#5
0
    def run(self, molecule, work_dir=None, queue=None):
        """Run the parameterisation protocol.

           Parameters
           ----------

           molecule : BioSimSpace._SireWrappers.Molecule
               The molecule to apply the parameterisation protocol to.

           work_dir : str
               The working directory.

           queue : queue.Queue
               The thread queue is which this method has been run.

           Returns
           -------

           molecule : BioSimSpace._SireWrappers.Molecule
               The parameterised molecule.
        """

        if type(molecule) is not _Molecule:
            raise TypeError(
                "'molecule' must be of type 'BioSimSpace._SireWrappers.Molecule'"
            )

        if type(work_dir) is not None and type(work_dir) is not str:
            raise TypeError("'work_dir' must be of type 'str'")

        if type(queue) is not None and type(queue) is not _queue.Queue:
            raise TypeError("'queue' must be of type 'queue.Queue'")

        # Set work_dir to the current directory.
        if work_dir is None:
            work_dir = _os.getcwd()

        # Create the file prefix.
        prefix = work_dir + "/"

        # Create a copy of the molecule.
        new_mol = molecule.copy()

        # Choose the program to run with depending on the force field compatibility.
        # If tLEaP and pdb2gmx are supported, default to tLEaP, then use pdb2gmx if
        # tLEaP fails to produce output.

        # First, try parameterise using tLEaP.
        if self._tleap:
            if _tleap_exe is not None:
                output = self._run_tleap(molecule, work_dir)
            # Otherwise, try using pdb2gmx.
            elif self._pdb2gmx:
                if _gmx_exe is not None:
                    output = self._run_pdb2gmx(molecule, work_dir)
                else:
                    raise _MissingSoftwareError(
                        "Cannot parameterise. Missing AmberTools and GROMACS.")

        # Parameterise using pdb2gmx.
        elif self._pdb2gmx:
            if _gmx_exe is not None:
                output = self._run_pdb2gmx(molecule, work_dir)
            else:
                raise _MissingSoftwareError(
                    "Cannot use pdb2gmx since GROMACS is not installed!")

        # Prepend the working directory to the output file names.
        output = [prefix + output[0], prefix + output[1]]

        try:
            # Load the parameterised molecule.
            par_mol = _Molecule(
                _IO.readMolecules(output)._getSireObject()[_SireMol.MolIdx(0)])
        except Exception as e:
            msg = "Failed to read molecule from: '%s', '%s'" % (output[0],
                                                                output[1])
            if _isVerbose():
                raise IOError(msg) from e
            else:
                raise IOError(msg) from None

        # Make the molecule 'mol' compatible with 'par_mol'. This will create
        # a mapping between atom indices in the two molecules and add all of
        # the new properties from 'par_mol' to 'mol'.
        new_mol._makeCompatibleWith(par_mol,
                                    property_map=self._property_map,
                                    overwrite=True,
                                    verbose=False)

        # Record the forcefield used to parameterise the molecule.
        new_mol._forcefield = self._forcefield

        if queue is not None:
            queue.put(new_mol)
        return new_mol
示例#6
0
    def rmsd(self, frame=None, atoms=None, molecule=None):
        """Compute the root mean squared displacement.

           Parameters
           ----------

           frame : int
               The index of the reference frame.

           atoms : [int]
               A list of reference atom indices.

           molecule : int
               The index of the reference molecule.

           Returns
           -------

           rmsd : [float]
               A list containing the RMSD value at each time point.
        """

        # Default to the first frame.
        if frame is None:
            frame = 0
        else:
            if type(frame) is not int:
                raise TypeError("'frame' must be of type 'int'")
            else:
                # Store the number of frames.
                n_frames = self._trajectory.n_frames

                # Make sure the frame index is within range.
                if frame > 0 and frame >= n_frames:
                    raise ValueError(
                        "Frame index (%d) of of range (0 to %d)." %
                        s(frame, n_frames - 1))
                elif frame < -n_frames:
                    raise ValueError(
                        "Frame index (%d) of of range (-1 to -%d)." %
                        s(frame, n_frames))

        if molecule is not None and atoms is not None:
            _warnings.warn(
                "Cannot have a reference molecule and list of atoms. Defaulting to atoms."
            )
            molecule = None

        # Extract the molecule from the reference trajectory frame and generate a
        # list of atom indices.
        if molecule is not None:
            # Integer molecule index.
            if type(molecule) is int:
                try:
                    molecule = self.getFrames(frame)[0]._getSireSystem()[
                        _SireMol.MolIdx(molecule)]
                except:
                    raise ValueError("Missing molecule index '%d' in System" %
                                     molecule)
            # Sire.Mol.MolIdx index.
            elif type(molecule) is _SireMol.MolIdx:
                try:
                    molecule = self.getFrames(
                        frame)[0]._getSireSystem()[molecule]
                except:
                    raise ValueError("Missing '%s' in System" %
                                     molecule.toString())

            # A BioSimSpace Molecule object.
            elif type(molecule) is _Molecule:
                molecule = molecule._getSireMolecule()
            # A Sire.Mol.Molecule object.
            elif type(molecule) is _SireMol.Molecule:
                pass
            else:
                raise TypeError(
                    "'molecule' must be of type 'int', 'BioSimSpace.Molecue', "
                    "'Sire.Mol.MolIdx', or 'Sire.Mol.Molecule'")

            # Initialise the list of atom indices.
            atoms = []

            # Loop over all of the atoms and add them to the list.
            # Atoms are numbered from one in Sire, so subtract one to get the python index.
            for atom in molecule.atoms():
                atoms.append(atom.number().value() - 1)

        if atoms is not None:
            # Check that all of the atom indices are integers.
            if not all(isinstance(x, int) for x in atoms):
                raise TypeError("'atom' indices must be of type 'int'")

        # Use MDTraj to compute the RMSD.
        try:
            rmsd = _mdtraj.rmsd(self._trajectory, self._trajectory, frame,
                                atoms)
        except:
            raise ValueError("Atom indices not found in the system.")

        # Convert to a list and return.
        return list(rmsd)