Exemplo n.º 1
0
def _to_sire_mapping(mapping):
    """Internal function to convert a regular mapping to Sire AtomIdx format.

       Parameters
       ----------

       mapping : {int:int}
           The regular mapping.

       Returns
       -------

       sire_mapping : {Sire.Mol.AtomIdx:Sire.Mol.AtomIdx}
           The Sire mapping.
    """

    sire_mapping = {}

    # Convert the mapping to AtomIdx key:value pairs.
    for idx0, idx1 in mapping.items():
        # Early exit if the mapping is already the correct format.
        if type(idx0) is _SireMol.AtomIdx:
            return mapping
        else:
            sire_mapping[_SireMol.AtomIdx(idx0)] = _SireMol.AtomIdx(idx1)

    return sire_mapping
Exemplo n.º 2
0
    def _createSireSystem(molecules):
        """Create a Sire system from a Molecules object or a list of Molecule
           objects.

           Parameters
           ----------

           molecules : :class:`Molecules <BioSimSpace._SireWrappers.Molecules>` \
                       [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`]

           Returns
           -------

           system : Sire.System.System
               A Sire system object.
        """

        # Create an empty Sire System.
        system = _SireSystem.System("BioSimSpace System")

        # Create a new "all" molecule group.
        molgrp = _SireMol.MoleculeGroup("all")

        # Add the molecules to the group.
        if type(molecules) is _Molecules:
            molgrp.add(molecules._sire_object)
        else:
            for mol in molecules:
                molgrp.add(mol._sire_object)

        # Add the molecule group to the system.
        system.add(molgrp)

        return system
Exemplo n.º 3
0
    def getMolecules(self, group="all"):
        """Return a list containing all of the molecules in the specified group.

           Parameters
           ----------

           group : str
               The name of the molecule group.

           Returns
           -------

           molecules : [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`]
               The list of molecules in the group.
        """

        if type(group) is not str:
            raise TypeError("'group' must be of type 'str'")

        # Try to extract the molecule group.
        try:
            molgrp = self._sire_object.group(_SireMol.MGName(group))
        except:
            raise ValueError("No molecules in group '%s'" % group)

        # Return a molecules container.
        return _Molecules(molgrp.molecules())
Exemplo n.º 4
0
    def _createSireSystem(molecules):
        """Create a Sire system from a list of molecules.

           Parameters
           ----------

           molecules : [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`]

           Returns
           -------

           system : Sire.System.System
               A Sire system object.
        """

        # Create an empty Sire System.
        system = _SireSystem.System("BioSimSpace System")

        # Create a new "all" molecule group.
        molgrp = _SireMol.MoleculeGroup("all")

        # Add the molecules to the group.
        for mol in molecules:
            molgrp.add(mol._sire_molecule)

        # Add the molecule group to the system.
        system.add(molgrp)

        return system
Exemplo n.º 5
0
    def __init__(self, molecules):
        """Constructor.

           Parameters
           ----------

           molecules : Sire.Mol.Molecules, Sire.System.System, \
                       :class:`System <BioSimSpace._SireWrappers.System>` \
                       [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`]
               A Sire Molecues object, a Sire or BioSimSpace System object,
               or a list of BioSimSpace Molecule objects.
        """

        # Check that the molecules argument is valid.

        # Convert tuple to list.
        if type(molecules) is tuple:
            molecules = list(molecules)

        # A Sire Molecules object.
        if type(molecules) is _SireMol.Molecules:
            super().__init__(molecules)

        # A Sire System object.
        elif type(molecules) is _SireSystem.System:
            super().__init__(molecules.molecules())

        # A BioSimSpace System object.
        elif type(molecules) is _System:
            super().__init__(molecules._sire_object.molecules())

        # Another BioSimSpace Molecule object.
        elif type(molecules) is list and all(
                isinstance(x, _Molecule) for x in molecules):
            mols = _SireMol.Molecules()
            for molecule in molecules:
                mols.add(molecule._sire_object)
                super().__init__(mols)

        # Invalid type.
        else:
            raise TypeError(
                "'molecules' must be of type 'Sire.Mol.Molecules' "
                "'Sire.System.System', 'BioSimSpace._SireWrappers.System', "
                "or a list of 'BioSimSpace._SireWrappers.Molecule' types.")

        # Store the list of MolNums.
        self._mol_nums = self._sire_object.molNums()

        # Sort the molecule numbers.
        self._mol_nums.sort()

        # Store the number of molecules.
        self._num_mols = self._sire_object.nMolecules()

        # Initialise the iterator counter.
        self._iter_count = 0
Exemplo n.º 6
0
    def toMolecule(self):
        """Convert a single Atom to a Molecule.

           Returns
           -------

           system : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`
        """
        return _Molecule(_SireMol.PartialMolecule(self._sire_object).extract().molecule())
Exemplo n.º 7
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
Exemplo n.º 8
0
    def removeWaterMolecules(self):
        """Remove all of the water molecules from the system."""

        # Get the list of water molecules.
        waters = self.getWaterMolecules()

        # Remove the molecules in the system.
        self._sire_object.remove(waters._sire_object, _SireMol.MGName("all"))

        # Reset the index mappings.
        self._reset_mappings()

        # Update the molecule numbers.
        self._mol_nums = self._sire_object.molNums()
Exemplo n.º 9
0
    def removeMolecules(self, molecules):
        """Remove a molecule, or list of molecules from the system.

           Parameters
           ----------

           molecules : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`, \
                       :class:`Molecules <BioSimSpace._SireWrappers.Molecules>`, \
                       [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`]
              A Molecule, Molecules object, or list of Molecule objects.
        """

        # Whether the molecules are in a Sire container.
        is_sire_container = False

        # Convert tuple to a list.
        if type(molecules) is tuple:
            molecules = list(molecules)

        # A Molecule object.
        if type(molecules) is _Molecule:
            molecules = [molecules]

        # A Molecules object.
        if type(molecules) is _Molecules:
            is_sire_container = True

        # A list of Molecule objects.
        elif type(molecules) is list and all(isinstance(x, _Molecule) for x in molecules):
            pass

        # Invalid argument.
        else:
            raise TypeError("'molecules' must be of type 'BioSimSpace._SireWrappers.Molecule' "
                            "or a list of 'BioSimSpace._SireWrappers.Molecule' types.")

        # Remove the molecules in the system.
        if is_sire_container:
            self._sire_object.remove(molecules._sire_object, _SireMol.MGName("all"))
        else:
            for mol in molecules:
                self._sire_object.remove(mol._sire_object.number())

        # Reset the index mappings.
        self._reset_mappings()

        # Update the molecule numbers.
        self._mol_nums = self._sire_object.molNums()
Exemplo n.º 10
0
    def molecule(self, index=0, gui=True):
        """View a specific molecule.

           Parameters
           ----------

           index : int
               The molecule index.

           gui : bool
               Whether to display the gui.
        """

        # Make sure we're running inside a Jupyter notebook.
        if not _is_notebook:
            return None

        # Check that the index is an integer.
        if type(index) is not int:
            raise TypeError("'index' must be of type 'int'")

        # Get the latest system from the process.
        if self._is_process:
            system = self._handle.getSystem()._getSireObject()

            # No system.
            if system is None:
                return
        else:
            system = self._handle

        # Extract the molecule numbers.
        molnums = system.molNums()

        # Make sure the index is valid.
        if index < 0 or index > len(molnums):
            raise ValueError("Molecule index is out of range!")

        # Create a new system and add a single molecule.
        s = _SireSystem.System("BioSimSpace System")
        m = _SireMol.MoleculeGroup("all")
        m.add(system[molnums[index]])
        s.add(m)

        # Create and return the view.
        return self._create_view(s, gui=gui)
Exemplo n.º 11
0
    def updateMolecules(self, molecules):
        """Update a molecule, or list of molecules in the system.

           Parameters
           ----------

           molecules : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`, \
                       [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`]
              A Molecule, or list of Molecule objects.
        """

        # Convert tuple to a list.
        if type(molecules) is tuple:
            molecules = list(molecules)

        # A Molecule object.
        if type(molecules) is _Molecule:
            molecules = [molecules]

        # A list of Molecule objects.
        elif type(molecules) is list and all(isinstance(x, _Molecule) for x in molecules):
            pass

        # Invalid argument.
        else:
            raise TypeError("'molecules' must be of type 'BioSimSpace._SireWrappers.Molecule' "
                            "or a list of 'BioSimSpace._SireWrappers.Molecule' types.")

        # Update each of the molecules.
        # TODO: Currently the Sire.System.update method doesn't work correctly
        # for certain changes to the Molecule molInfo object. As such, we remove
        # the old molecule from the system, then add the new one in.
        for mol in molecules:
            try:
                self._sire_object.update(mol._sire_object)
            except:
                self._sire_object.remove(mol._sire_object.number())
                self._sire_object.add(mol._sire_object, _SireMol.MGName("all"))

        # Reset the index mappings.
        self._reset_mappings()

        # Update the molecule numbers.
        self._mol_nums = self._sire_object.molNums()
Exemplo n.º 12
0
    def getMolecules(self, group="all"):
        """Return a list containing all of the molecules in the specified group.

           Parameters
           ----------

           group : str
               The name of the molecule group.

           Returns
           -------

           molecules : [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`]
               The list of molecules in the group.
        """

        if type(group) is not str:
            raise TypeError("'group' must be of type 'str'")

        # Try to extract the molecule group.
        try:
            molgrp = self._sire_system.group(_SireMol.MGName(group))
        except:
            raise ValueError("No molecules in group '%s'" % group)

        # Create a list to store the molecules.
        mols = []

        # Get a list of the MolNums in the group and sort them.
        nums = molgrp.molNums()

        # Loop over all of the molecules in the group and append to the list.
        for num in nums:
            mols.append(_Molecule(molgrp.molecule(num)))

            # This is a merged molecule.
            if mols[-1]._sire_molecule.hasProperty("is_perturbable"):
                mols[-1]._is_merged = True

        return mols
Exemplo n.º 13
0
    def addMolecules(self, molecules):
        """Add a molecule, or list of molecules to the system.

           Parameters
           ----------

           molecules : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`, \
                       [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`]
              A Molecule, or list of Molecule objects.
        """

        # Convert tuple to a list.
        if type(molecules) is tuple:
            molecules = list(molecules)

        # A Molecule object.
        if type(molecules) is _Molecule:
            molecules = [molecules]

        # A list of Molecule objects.
        elif type(molecules) is list and all(isinstance(x, _Molecule) for x in molecules):
            pass

        # Invalid argument.
        else:
            raise TypeError("'molecules' must be of type 'BioSimSpace._SireWrappers.Molecule' "
                            "or a list of 'BioSimSpace._SireWrappers.Molecule' types.")

        # The system is empty: create a new Sire system from the molecules.
        if self._sire_system.nMolecules() == 0:
            self._sire_system = self._createSireSystem(molecules)

        # Otherwise, add the molecules to the existing "all" group.
        else:
            for mol in molecules:
                self._sire_system.add(mol._sire_molecule, _SireMol.MGName("all"))
Exemplo n.º 14
0
def _score_sire_mappings(molecule0, molecule1, sire_mappings, prematch,
                         scoring_function, property_map0, property_map1):
    """Internal function to score atom mappings based on the root mean squared
       displacement (RMSD) between mapped atoms in two molecules. Optionally,
       molecule0 can first be aligned to molecule1 based on the mapping prior
       to computing the RMSD. The function returns the mappings sorted based
       on their score from best to worst, along with a list containing the
       scores for each mapping.

       Parameters
       ----------

       molecule0 : Sire.Molecule.Molecule
           The first molecule (Sire representation).

       molecule0 : Sire.Molecule.Molecule
           The second molecule (Sire representation).

       sire_mappings : [{}]
           The list of mappings generated by Sire.

       prematch : dict
           A dictionary of atom mappings that must be included in the match.

       scoring_function : str
           The RMSD scoring function.

       property_map0 : dict
           A dictionary that maps "properties" in molecule0 to their user
           defined values. This allows the user to refer to properties
           with their own naming scheme, e.g. { "charge" : "my-charge" }

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

       Returns
       -------

       mapping, scores : ([dict], list)
           The ranked mappings and corresponding scores.
    """

    # Make sure to re-map the coordinates property in both molecules, otherwise
    # the move and align functions from Sire will not work.
    prop0 = property_map0.get("coordinates", "coordinates")
    prop1 = property_map1.get("coordinates", "coordinates")

    if prop0 != "coordinates":
        molecule0 = molecule0.edit().setProperty(
            "coordinates", molecule0.property(prop0)).commit()
    if prop1 != "coordinates":
        molecule1 = molecule1.edit().setProperty(
            "coordinates", molecule1.property(prop1)).commit()

    # Initialise a list to hold the mappings.
    mappings = []

    # Initialise a list of to hold the score for each mapping.
    scores = []

    # Loop over all of the mappings.
    for mapping in sire_mappings:

        # Check that the mapping contains the pre-match.
        is_valid = True
        for idx0, idx1 in prematch.items():
            # Pre-match isn't found, return to top of loop.
            if _SireMol.AtomIdx(idx0) not in mapping or mapping[
                    _SireMol.AtomIdx(idx0)] != _SireMol.AtomIdx(idx1):
                is_valid = False
                break

        if is_valid:
            # Rigidly align molecule0 to molecule1 based on the mapping.
            if scoring_function == "RMSDALIGN":
                try:
                    molecule0 = molecule0.move().align(
                        molecule1,
                        _SireMol.AtomResultMatcher(mapping)).molecule()
                except Exception as e:
                    msg = "Failed to align molecules when scoring based on mapping: %r" % mapping
                    if _isVerbose():
                        raise _AlignmentError(msg) from e
                    else:
                        raise _AlignmentError(msg) from None
            # Flexibly align molecule0 to molecule1 based on the mapping.
            elif scoring_function == "RMSDFLEXALIGN":
                molecule0 = flexAlign(_Molecule(molecule0),
                                      _Molecule(molecule1),
                                      _from_sire_mapping(mapping),
                                      property_map0=property_map0,
                                      property_map1=property_map1)._sire_object

            # Append the mapping to the list.
            mappings.append(_from_sire_mapping(mapping))

            # We now compute the RMSD between the coordinates of the matched atoms
            # in molecule0 and molecule1.

            # Initialise lists to hold the coordinates.
            c0 = []
            c1 = []

            # Loop over each atom index in the map.
            for idx0, idx1 in mapping.items():
                # Append the coordinates of the matched atom in molecule0.
                c0.append(molecule0.atom(idx0).property("coordinates"))
                # Append the coordinates of atom in molecule1 to which it maps.
                c1.append(molecule1.atom(idx1).property("coordinates"))

            # Compute the RMSD between the two sets of coordinates.
            scores.append(_SireMaths.getRMSD(c0, c1))

    # No mappings were found.
    if len(mappings) == 0:
        if len(prematch) == 0:
            return ([{}], [])
        else:
            return ([prematch], [])

    # Sort the scores and return the sorted keys. (Smaller RMSD is best)
    keys = sorted(range(len(scores)), key=lambda k: scores[k])

    # Sort the mappings.
    mappings = [mappings[x] for x in keys]

    # Sort the scores and convert to Angstroms.
    scores = [scores[x] * _Units.Length.angstrom for x in keys]

    # Return the sorted mappings and their scores.
    return (mappings, scores)
Exemplo n.º 15
0
    def molecules(self, indices=None, gui=True):
        """View specific molecules.

           Parameters
           ----------

           indices : [int], range
               A list of molecule indices.

           gui : bool
               Whether to display the gui.
        """

        # Make sure we're running inside a Jupyter notebook.
        if not _is_notebook:
            return None

        # Return a view of the entire system.
        if indices is None:
            return self.system(gui=gui)

        # Convert single indices to a list.
        if isinstance(indices, range):
            indices = list(indices)
        elif type(indices) is not list:
            indices = [indices]

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

        # Get the latest system from the process.
        if self._is_process:
            system = self._handle.getSystem()._getSireObject()

            # No system.
            if system is None:
                return

        else:
            system = self._handle

        # Extract the molecule numbers.
        molnums = system.molNums()

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

        # Loop over all of the indices.
        for index in indices:
            if index < 0 or index > len(molnums):
                raise ValueError("Molecule index is out of range!")

            # Add the molecule.
            m.add(system[molnums[index]])

        # Add all of the molecules to the system.
        s.add(m)

        # Create and return the view.
        return self._create_view(s, gui=gui)
Exemplo n.º 16
0
def matchAtoms(molecule0,
               molecule1,
               scoring_function="rmsd_align",
               matches=1,
               return_scores=False,
               prematch={},
               timeout=5 * _Units.Time.second,
               property_map0={},
               property_map1={}):
    """Find mappings between atom indices in molecule0 to those in molecule1.
       Molecules are aligned using a Maximum Common Substructure (MCS) search.
       When requesting more than one match, the mappings will be sorted using
       a scoring function and returned in order of best to worst score. (Note
       that, depending on the scoring function the "best" score may have the
       lowest value.)

       Parameters
       ----------

       molecule0 : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`
           The molecule of interest.

       molecule1 : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`
           The reference molecule.

       scoring_function : str
           The scoring function used to match atoms. Available options are:
             - "rmsd"
                 Calculate the root mean squared distance between the
                 coordinates of atoms in molecule0 to those that they
                 map to in molecule1.
             - "rmsd_align"
                 Align molecule0 to molecule1 based on the mapping before
                 computing the above RMSD score.
             - "rmsd_flex_align"
                 Flexibly align molecule0 to molecule1 based on the mapping
                 before computing the above RMSD score. (Requires the
                 'fkcombu'. package: http://strcomp.protein.osaka-u.ac.jp/kcombu)

       matches : int
           The maximum number of matches to return. (Sorted in order of score).

       return_scores : bool
           Whether to return a list containing the scores for each mapping.

       prematch : dict
           A dictionary of atom mappings that must be included in the match.

       timeout : BioSimSpace.Types.Time
           The timeout for the maximum common substructure search.

       property_map0 : dict
           A dictionary that maps "properties" in molecule0 to their user
           defined values. This allows the user to refer to properties
           with their own naming scheme, e.g. { "charge" : "my-charge" }

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

       Returns
       -------

       matches : dict, [dict], ([dict], list)
           The best atom mapping, a list containing a user specified number of
           the best mappings ranked by their score, or a tuple containing the
           list of best mappings and a list of the corresponding scores.

       Examples
       --------

       Find the best maximum common substructure mapping between two molecules.

       >>> import BioSimSpace as BSS
       >>> mapping = BSS.Align.matchAtoms(molecule0, molecule1)

       Find the 5 best mappings.

       >>> import BioSimSpace as BSS
       >>> mappings = BSS.Align.matchAtoms(molecule0, molecule1, matches=5)

       Find the 5 best mappings along with their ranking scores.

       >>> import BioSimSpace as BSS
       >>> mappings, scores = BSS.Align.matchAtoms(molecule0, molecule1, matches=5, return_scores=True)

       Find the 5 best mappings along with their ranking scores. Score
       by flexibly aligning molecule0 to molecule1 based on each mapping
       and computing the root mean squared displacement of the matched
       atoms.

       >>> import BioSimSpace as BSS
       >>> mappings, scores = BSS.Align.matchAtoms(molecule0, molecule1, matches=5, return_scores=True, scoring_function="rmsd_flex_align")

       Find the best mapping that contains a prematch (this is a dictionary mapping
       atom indices in molecule0 to those in molecule1).

       >>> import BioSimSpace as BSS
       >>> mapping = BSS.Align.matchAtoms(molecule0, molecule1, prematch={0 : 10, 3 : 7})
    """

    # A list of supported scoring functions.
    scoring_functions = ["RMSD", "RMSDALIGN", "RMSDFLEXALIGN"]

    # Validate input.

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

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

    if type(scoring_function) is not str:
        raise TypeError("'scoring_function' must be of type 'str'")
    else:
        # Strip underscores and whitespace, then convert to upper case.
        _scoring_function = scoring_function.replace("_", "").upper()
        _scoring_function = _scoring_function.replace(" ", "").upper()
        if not _scoring_function in scoring_functions:
            raise ValueError(
                "Unsupported scoring function '%s'. Options are: %s" %
                (scoring_function, scoring_functions))

    if _scoring_function == "RMSDFLEXALIGN" and _fkcombu_exe is None:
        raise _MissingSoftwareError(
            "'rmsd_flex_align' option requires the 'fkcombu' program: "
            "http://strcomp.protein.osaka-u.ac.jp/kcombu")

    if type(matches) is not int:
        raise TypeError("'matches' must be of type 'int'")
    else:
        if matches < 0:
            raise ValueError("'matches' must be positive!")

    if type(return_scores) is not bool:
        raise TypeError("'return_matches' must be of type 'bool'")

    if type(prematch) is not dict:
        raise TypeError("'prematch' must be of type 'dict'")
    else:
        _validate_mapping(molecule0, molecule1, prematch, "prematch")

    if type(timeout) is not _Units.Time._Time:
        raise TypeError("'timeout' must be of type 'BioSimSpace.Types.Time'")

    if type(property_map0) is not dict:
        raise TypeError("'property_map0' must be of type 'dict'")

    if type(property_map1) is not dict:
        raise TypeError("'property_map1' must be of type 'dict'")

    # Extract the Sire molecule from each BioSimSpace molecule.
    mol0 = molecule0._getSireObject()
    mol1 = molecule1._getSireObject()

    # Convert the timeout to seconds and take the magnitude as an integer.
    timeout = int(timeout.seconds().magnitude())

    # Create a temporary working directory.
    tmp_dir = _tempfile.TemporaryDirectory()
    work_dir = tmp_dir.name

    # Use RDKkit to find the maximum common substructure.

    try:
        # Run inside a temporary directory.
        with _Utils.cd(work_dir):
            # Write both molecules to PDB files.
            _IO.saveMolecules("tmp0",
                              molecule0,
                              "PDB",
                              property_map=property_map0)
            _IO.saveMolecules("tmp1",
                              molecule1,
                              "PDB",
                              property_map=property_map1)

            # Load the molecules with RDKit.
            # Note that the C++ function overloading seems to be broken, so we
            # need to pass all arguments by position, rather than keyword.
            # The arguments are: "filename", "sanitize", "removeHs", "flavor"
            mols = [
                _Chem.MolFromPDBFile("tmp0.pdb", False, False, 0),
                _Chem.MolFromPDBFile("tmp1.pdb", False, False, 0)
            ]

            # Generate the MCS match.
            mcs = _rdFMCS.FindMCS(mols,
                                  atomCompare=_rdFMCS.AtomCompare.CompareAny,
                                  bondCompare=_rdFMCS.BondCompare.CompareAny,
                                  completeRingsOnly=True,
                                  ringMatchesRingOnly=True,
                                  matchChiralTag=False,
                                  matchValences=False,
                                  maximizeBonds=False,
                                  timeout=timeout)

            # Get the common substructure as a SMARTS string.
            mcs_smarts = _Chem.MolFromSmarts(mcs.smartsString)

    except:
        raise RuntimeError("RDKIT MCS mapping failed!")

    # Score the mappings and return them in sorted order (best to worst).
    mappings, scores = _score_rdkit_mappings(mol0, mol1, mols[0], mols[1],
                                             mcs_smarts, prematch,
                                             _scoring_function, property_map0,
                                             property_map1)

    # Sometimes RDKit fails to generate a mapping that includes the prematch.
    # If so, then try generating a mapping using the MCS routine from Sire.
    if len(mappings) == 1 and mappings[0] == prematch:

        # Convert timeout to a Sire Unit.
        timeout = timeout * _SireUnits.second

        # Regular match. Include light atoms, but don't allow matches between heavy
        # and light atoms.
        m0 = mol0.evaluate().findMCSmatches(
            mol1, _SireMol.AtomResultMatcher(_to_sire_mapping(prematch)),
            timeout, True, property_map0, property_map1, 6, False)

        # Include light atoms, and allow matches between heavy and light atoms.
        # This captures mappings such as O --> H in methane to methanol.
        m1 = mol0.evaluate().findMCSmatches(
            mol1, _SireMol.AtomResultMatcher(_to_sire_mapping(prematch)),
            timeout, True, property_map0, property_map1, 0, False)

        # Take the mapping with the larger number of matches.
        if len(m1) > 0:
            if len(m0) > 0:
                if len(m1[0]) > len(m0[0]):
                    mappings = m1
                else:
                    mappings = m0
            else:
                mappings = m1
        else:
            mappings = m0

        # Score the mappings and return them in sorted order (best to worst).
        mappings, scores = _score_sire_mappings(mol0, mol1, mappings, prematch,
                                                _scoring_function,
                                                property_map0, property_map1)

    if matches == 1:
        if return_scores:
            return (mappings[0], scores[0])
        else:
            return mappings[0]
    else:
        # Return a list of matches from best to worst.
        if return_scores:
            return (mappings[0:matches], scores[0:matches])
        # Return a tuple containing the list of matches from best to
        # worst along with the list of scores.
        else:
            return mappings[0:matches]
Exemplo n.º 17
0
def _score_rdkit_mappings(molecule0, molecule1, rdkit_molecule0,
                          rdkit_molecule1, mcs_smarts, prematch,
                          scoring_function, property_map0, property_map1):
    """Internal function to score atom mappings based on the root mean squared
       displacement (RMSD) between mapped atoms in two molecules. Optionally,
       molecule0 can first be aligned to molecule1 based on the mapping prior
       to computing the RMSD. The function returns the mappings sorted based
       on their score from best to worst, along with a list containing the
       scores for each mapping.

       Parameters
       ----------

       molecule0 : Sire.Molecule.Molecule
           The first molecule (Sire representation).

       molecule0 : Sire.Molecule.Molecule
           The second molecule (Sire representation).

       rdkit_mol0 : RDKit.Chem.Mol
           The first molecule (RDKit representation).

       rdkit_mol1 : RDKit.Chem.Mol
           The second molecule (RDKit representation).

       mcs_smarts : RDKit.Chem.MolFromSmarts
           The smarts string representing the maximum common substructure of
           the two molecules.

       prematch : dict
           A dictionary of atom mappings that must be included in the match.

       scoring_function : str
           The RMSD scoring function.

       property_map0 : dict
           A dictionary that maps "properties" in molecule0 to their user
           defined values. This allows the user to refer to properties
           with their own naming scheme, e.g. { "charge" : "my-charge" }

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

       Returns
       -------

       mapping, scores : ([dict], list)
           The ranked mappings and corresponding scores.
    """

    # Adapted from FESetup: https://github.com/CCPBioSim/fesetup

    # Make sure to re-map the coordinates property in both molecules, otherwise
    # the move and align functions from Sire will not work.
    prop0 = property_map0.get("coordinates", "coordinates")
    prop1 = property_map1.get("coordinates", "coordinates")

    if prop0 != "coordinates":
        molecule0 = molecule0.edit().setProperty(
            "coordinates", molecule0.property(prop0)).commit()
    if prop1 != "coordinates":
        molecule1 = molecule1.edit().setProperty(
            "coordinates", molecule1.property(prop1)).commit()

    # Get the set of matching substructures in each molecule. For some reason
    # setting uniquify to True removes valid matches, in some cases even the
    # best match! As such, we set uniquify to False and account ignore duplicate
    # mappings in the code below.
    matches0 = rdkit_molecule0.GetSubstructMatches(mcs_smarts,
                                                   uniquify=False,
                                                   maxMatches=1000,
                                                   useChirality=False)
    matches1 = rdkit_molecule1.GetSubstructMatches(mcs_smarts,
                                                   uniquify=False,
                                                   maxMatches=1000,
                                                   useChirality=False)

    # Swap the order of the matches.
    if len(matches0) < len(matches1):
        matches0, matches1 = matches1, matches0
        is_swapped = True
    else:
        is_swapped = False

    # Initialise a list to hold the mappings.
    mappings = []

    # Initialise a list of to hold the score for each mapping.
    scores = []

    # Loop over all matches from mol0.
    for x in range(len(matches0)):
        match0 = matches0[x]

        # Loop over all matches from mol1.
        for y in range(len(matches1)):
            match1 = matches1[y]

            # Initialise the mapping for this match.
            mapping = {}
            sire_mapping = {}

            # Loop over all atoms in the match.
            for i, idx0 in enumerate(match0):
                idx1 = match1[i]

                # Add to the mapping.
                if is_swapped:
                    mapping[idx1] = idx0
                    sire_mapping[_SireMol.AtomIdx(idx1)] = _SireMol.AtomIdx(
                        idx0)
                else:
                    mapping[idx0] = idx1
                    sire_mapping[_SireMol.AtomIdx(idx0)] = _SireMol.AtomIdx(
                        idx1)

            # This is a new mapping:
            if not mapping in mappings:
                # Check that the mapping contains the pre-match.
                is_valid = True
                for idx0, idx1 in prematch.items():
                    # Pre-match isn't found, return to top of loop.
                    if idx0 not in mapping or mapping[idx0] != idx1:
                        is_valid = False
                        break

                if is_valid:
                    # Rigidly align molecule0 to molecule1 based on the mapping.
                    if scoring_function == "RMSDALIGN":
                        try:
                            molecule0 = molecule0.move().align(
                                molecule1,
                                _SireMol.AtomResultMatcher(
                                    sire_mapping)).molecule()
                        except Exception as e:
                            msg = "Failed to align molecules when scoring based on mapping: %r" % mapping
                            if _isVerbose():
                                raise _AlignmentError(msg) from e
                            else:
                                raise _AlignmentError(msg) from None
                    # Flexibly align molecule0 to molecule1 based on the mapping.
                    elif scoring_function == "RMSDFLEXALIGN":
                        molecule0 = flexAlign(
                            _Molecule(molecule0),
                            _Molecule(molecule1),
                            mapping,
                            property_map0=property_map0,
                            property_map1=property_map1)._sire_object

                    # Append the mapping to the list.
                    mappings.append(mapping)

                    # We now compute the RMSD between the coordinates of the matched atoms
                    # in molecule0 and molecule1.

                    # Initialise lists to hold the coordinates.
                    c0 = []
                    c1 = []

                    # Loop over each atom index in the map.
                    for idx0, idx1 in sire_mapping.items():
                        # Append the coordinates of the matched atom in molecule0.
                        c0.append(molecule0.atom(idx0).property("coordinates"))
                        # Append the coordinates of atom in molecule1 to which it maps.
                        c1.append(molecule1.atom(idx1).property("coordinates"))

                    # Compute the RMSD between the two sets of coordinates.
                    scores.append(_SireMaths.getRMSD(c0, c1))

    # No mappings were found.
    if len(mappings) == 0:
        if len(prematch) == 0:
            return ([{}], [])
        else:
            return ([prematch], [])

    # Sort the scores and return the sorted keys. (Smaller RMSD is best)
    keys = sorted(range(len(scores)), key=lambda k: scores[k])

    # Sort the mappings.
    mappings = [mappings[x] for x in keys]

    # Sort the scores and convert to Angstroms.
    scores = [scores[x] * _Units.Length.angstrom for x in keys]

    # Return the sorted mappings and their scores.
    return (mappings, scores)
Exemplo n.º 18
0
def matchAtoms(molecule0,
               molecule1,
               scoring_function="RMSD align",
               matches=1,
               return_scores=False,
               prematch={},
               timeout=5 * _Units.Time.second,
               match_light=True,
               property_map0={},
               property_map1={},
               verbose=False):
    """Find mappings between atom indices in molecule0 to those in molecule1.
       Molecules are aligned using a Maximum Common Substructure (MCS) search.
       When requesting more than one match, the mappings will be sorted using
       a scoring function and returned in order of best to worst score. (Note
       that, depending on the scoring function the "best" score may have the
       lowest value.)

       Parameters
       ----------

       molecule0 : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`
           The molecule of interest.

       molecule1 : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`
           The reference molecule.

       scoring_function : str
           The scoring function used to match atoms. Available options are:
             - "RMSD"
                 Calculate the root mean squared distance between the
                 coordinates of atoms in molecule0 to those that they
                 map to in molecule1.
             - "RMSD align"
                 Align molecule0 to molecule1 based on the mapping before
                 computing the above RMSD score.

       matches : int
           The maximum number of matches to return. (Sorted in order of score).

       return_scores : bool
           Whether to return a list containing the scores for each mapping.

       prematch : dict
           A pre-match to use as the basis of the search.

       timeout : :class:`Time <BioSimSpace.Types.Time>`
           The timeout for the matching algorithm.

       match_light : bool
           Whether to match light atoms.

       property_map0 : dict
           A dictionary that maps "properties" in molecule0 to their user
           defined values. This allows the user to refer to properties
           with their own naming scheme, e.g. { "charge" : "my-charge" }

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

       verbose : bool
           Whether to print status information from the matcher.

       Returns
       -------

       matches : dict, [dict], ([dict], list)
           The best atom mapping, a list containing a user specified number of
           the best mappings ranked by their score, or a tuple containing the
           list of best mappings and a list of the corresponding scores.

       Examples
       --------

       Find the best maximum common substructure mapping between two molecules.

       >>> import BioSimSpace as BSS
       >>> mapping = BSS.Align.matchAtoms(molecule0, molecule1)

       Find the 5 best mappings.

       >>> import BioSimSpace as BSS
       >>> mappings = BSS.Align.matchAtoms(molecule0, molecule1, matches=5)

       Find the 5 best mappings along with their ranking scores.

       >>> import BioSimSpace as BSS
       >>> mappings, scores = BSS.Align.matchAtoms(molecule0, molecule1, matches=5, return_scores=True)

       Find the best mapping that contains a prematch (this is a dictionary mapping
       atom indices in molecule0 to those in molecule1).

       >>> import BioSimSpace as BSS
       >>> from Sire.Mol import AtomIdx
       >>> mapping = BSS.Align.matchAtoms(molecule0, molecule1, prematch={AtomIdx(0), AtomIdx(10)})

       Find the best mapping, excluding light atoms.

       >>> import BioSimSpace as BSS
       >>> mapping = BSS.Align.matchAtoms(molecule0, molecule1, match_light=False)
    """

    # A list of supported scoring functions.
    scoring_functions = ["RMSD", "RMSDALIGN"]

    # Validate input.

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

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

    if type(scoring_function) is not str:
        raise TypeError("'scoring_function' must be of type 'str'")
    else:
        # Strip whitespace and convert to upper case.
        scoring_function = scoring_function.replace(" ", "").upper()
        if not scoring_function in scoring_functions:
            raise ValueError(
                "Unsupported scoring function '%s'. Options are: %s" %
                (scoring_function, scoring_functions))

    if type(matches) is not int:
        raise TypeError("'matches' must be of type 'int'")
    else:
        if matches < 0:
            raise ValueError("'matches' must be positive!")

    if type(prematch) is not dict:
        raise TypeError("'prematch' must be of type 'dict'")
    else:
        for idx0, idx1 in prematch.items():
            if type(idx0) is not _SireMol.AtomIdx or type(
                    idx1) is not _SireMol.AtomIdx:
                raise TypeError(
                    "'prematch' dictionary key:value pairs must be of type 'Sire.Mol.AtomIdx'"
                )
            if idx0.value() < 0 or idx0.value() >= molecule0.nAtoms() or \
               idx1.value() < 0 or idx1.value() >= molecule1.nAtoms():
                raise ValueError(
                    "'prematch' dictionary key:value pair '%s : %s' is out of range! "
                    "The molecules contain %d and %d atoms." %
                    (idx0, idx1, molecule0.nAtoms(), molecule1.nAtoms()))

    if type(timeout) is not _Units.Time._Time:
        raise TypeError("'timeout' must be of type 'BioSimSpace.Types.Time'")

    if type(match_light) is not bool:
        raise TypeError("'match_light' must be of type 'bool'")

    if type(property_map0) is not dict:
        raise TypeError("'property_map0' must be of type 'dict'")

    if type(property_map1) is not dict:
        raise TypeError("'property_map1' must be of type 'dict'")

    if type(verbose) is not bool:
        raise TypeError("'verbose' must be of type 'bool'")

    # Extract the Sire molecule from each BioSimSpace molecule.
    mol0 = molecule0._getSireMolecule()
    mol1 = molecule1._getSireMolecule()

    # Convert the timeout to a Sire unit.
    timeout = timeout.magnitude() * timeout._supported_units[timeout.unit()]

    # Are we performing an alignment before scoring.
    if scoring_function == "RMSDALIGN":
        is_align = True
    else:
        is_align = False

    # Find all of the best maximum common substructure matches.
    # We perform two matches to handle different edge cases. The best
    # match is the one that matches the greater number of atom pairs.
    # Ideally the two runs should be performed concurrently, but this
    # isn't currently possible from within Python since the underlying
    # Sire objects aren't pickable.

    # Regular match. Include light atoms, but don't allow matches between heavy
    # and light atoms.
    m0 = mol0.evaluate().findMCSmatches(mol1,
                                        _SireMol.AtomResultMatcher(prematch),
                                        timeout, match_light, property_map0,
                                        property_map1, 6, verbose)

    # Include light atoms, and allow matches between heavy and light atoms.
    # This captures mappings such as O --> H in methane to methanol.
    m1 = mol0.evaluate().findMCSmatches(mol1,
                                        _SireMol.AtomResultMatcher(prematch),
                                        timeout, match_light, property_map0,
                                        property_map1, 0, verbose)

    # Take the mapping with the larger number of matches.
    if len(m1) > 0:
        if len(m0) > 0:
            if len(m1[0]) > len(m0[0]):
                mappings = m1
            else:
                mappings = m0
        else:
            mappings = m1
    else:
        mappings = m0

    # No matches!
    if len(mappings) == 0:
        return None

    # Score the mappings and return them in sorted order (best to worst).
    # For now we default to RMSD scoring, since it's the only option.
    else:
        # Return the best match.
        if matches == 1:
            return _score_rmsd(mol0, mol1, mappings, is_align)[0][0]
        else:
            # Return a list of matches from best to worst.
            if return_scores:
                (mappings, scores) = _score_rmsd(mol0, mol1, mappings,
                                                 is_align)
                return (mappings[0:matches], scores[0:matches])
            # Return a tuple containing the list of matches from best to
            # worst along with the list of scores.
            else:
                return _score_rmsd(mol0, mol1, mappings,
                                   is_align)[0][0:matches]
Exemplo n.º 19
0
def rmsdAlign(molecule0,
              molecule1,
              mapping=None,
              property_map0={},
              property_map1={}):
    """Align atoms in molecule0 to those in molecule1 using the mapping
       between matched atom indices. The molecule is aligned using rigid-body
       translation and rotations, with a root mean squared displacement (RMSD)
       fit to find the optimal translation vector (as opposed to merely taking
       the difference of centroids).

       Parameters
       ----------

       molecule0 : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`
           The molecule to align.

       molecule1 : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`
           The reference molecule.

       mapping : dict
           A dictionary mapping atoms in molecule0 to those in molecule1.

       property_map0 : dict
           A dictionary that maps "properties" in molecule0 to their user
           defined values. This allows the user to refer to properties
           with their own naming scheme, e.g. { "charge" : "my-charge" }

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

       Returns
       -------

       molecule : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`
           The aligned molecule.

       Examples
       --------

       Align molecule0 to molecule1 based on a precomputed mapping.

       >>> import BioSimSpace as BSS
       >>> molecule0 = BSS.Align.rmsdAlign(molecule0, molecule1, mapping)

       Align molecule0 to molecule1. Since no mapping is passed one will be
       autogenerated using :class:`matchAtoms <BioSimSpace.Align.matchAtoms>`
       with default options.

       >>> import BioSimSpace as BSS
       >>> molecule0 = BSS.Align.rmsdAlign(molecule0, molecule1)
    """

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

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

    if type(property_map0) is not dict:
        raise TypeError("'property_map0' must be of type 'dict'")

    if type(property_map1) is not dict:
        raise TypeError("'property_map1' must be of type 'dict'")

    # The user has passed an atom mapping.
    if mapping is not None:
        if type(mapping) is not dict:
            raise TypeError("'mapping' must be of type 'dict'.")
        else:
            _validate_mapping(molecule0, molecule1, mapping, "mapping")

    # Get the best match atom mapping.
    else:
        mapping = matchAtoms(molecule0,
                             molecule1,
                             property_map0=property_map0,
                             property_map1=property_map1)

    # Extract the Sire molecule from each BioSimSpace molecule.
    mol0 = molecule0._getSireObject()
    mol1 = molecule1._getSireObject()

    # Convert the mapping to AtomIdx key:value pairs.
    sire_mapping = _to_sire_mapping(mapping)

    # Perform the alignment, mol0 to mol1.
    try:
        mol0 = mol0.move().align(
            mol1, _SireMol.AtomResultMatcher(sire_mapping)).molecule()
    except Exception as e:
        msg = "Failed to align molecules based on mapping: %r" % mapping
        if _isVerbose():
            raise _AlignmentError(msg) from e
        else:
            raise _AlignmentError(msg) from None

    # Return the aligned molecule.
    return _Molecule(mol0)
Exemplo n.º 20
0
    def _renumberMolecules(self, molecules, is_rebuild=False):
        """Helper function to renumber the molecules to be consistent with the
           system.

           Parameters
           ----------

           molecules : [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`]
               A list of molecule objects.

           Returns
           -------

           molecules : [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`]
               The renumber list of molecule objects.
        """

        # Renumber everything.
        if is_rebuild:
            num_molecules = 0
            num_residues = 0
            num_atoms = 0

        # Get the current number of molecules, residues, and atoms.
        else:
            num_molecules = self.nMolecules()
            num_residues = self.nResidues()
            num_atoms = self.nAtoms()

        # Create a list to hold the modified molecules.
        new_molecules = []

        # Loop over all of the molecules.
        for mol in molecules:

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

            # Get the Sire molecule and make it editable.
            edit_mol = new_mol._sire_object.edit()

            # Renumber the molecule.
            edit_mol = edit_mol.renumber(_SireMol.MolNum(num_molecules+1)).molecule()
            num_molecules += 1

            # A hash mapping between old and new numbers.
            num_hash = {}

            # Loop over all residues and add them to the hash.
            for res in edit_mol.residues():
                num_hash[res.number()] = _SireMol.ResNum(num_residues+1)
                num_residues += 1

            # Renumber the residues.
            edit_mol = edit_mol.renumber(num_hash).molecule()

            # Clear the hash.
            num_hash = {}

            # Loop over all of the atoms and add them to the hash.
            for atom in edit_mol.atoms():
                num_hash[atom.number()] = _SireMol.AtomNum(num_atoms+1)
                num_atoms += 1

            # Renumber the atoms.
            edit_mol = edit_mol.renumber(num_hash).molecule()

            # Commit the changes and replace the molecule.
            new_mol._sire_object = edit_mol.commit()

            # Append to the list of molecules.
            new_molecules.append(new_mol)

        # Return the renumbered molecules.
        return new_molecules
Exemplo n.º 21
0
def _rename_water_molecule(molecule):
    """Internal function to rename residues/atoms in a water molecule to match
       the naming conventions used by GROMACS.

       Parameters
       ----------

       molecule : Sire.Mol.Molecule
           A Sire Molecule object.

       Returns
       -------

       molecule : Sire.Mol.Molecule
           The updated Sire Molecule object.
    """

    # Make the molecule editable.
    molecule = molecule.edit()

    # In GROMACS, all water molecules must be given the residue label "SOL".
    # We extract all of the waters from the system and relabel the
    # residues as appropriate.
    #
    # We need to work out what to do if existing water molecules don't contain
    # all of the required atoms, e.g. if we have crystal water oxygen sites
    # from a PDB file, or if the system is to be solvated with a 4- or 5-point
    # water model (where we need to add virtual sites).

    # Update the molecule with the new residue name.
    molecule = molecule.residue(_SireMol.ResIdx(0))     \
                       .rename(_SireMol.ResName("SOL")) \
                       .molecule()

    # Index for the hydrogen atoms.
    hydrogen_idx = 1

    # Gromacs water models use HW1/HW2 for hydrogen atoms at OW for water.
    for atom in molecule.atoms():
        try:
            # Hydrogen.
            if atom.property("element") == _SireMol.Element("H"):
                molecule = molecule.atom(atom.number())                              \
                                   .rename(_SireMol.AtomName("HW%d" % hydrogen_idx)) \
                                   .molecule()
                hydrogen_idx += 1
            # Oxygen.
            elif atom.property("element") == _SireMol.Element("O"):
                molecule = molecule.atom(atom.number())             \
                                   .rename(_SireMol.AtomName("OW")) \
                                   .molecule()

        # Otherwise, try to infer the element from the atom name.
        except:
            # Strip all digits from the name.
            name = "".join([x for x in atom.name().value() if not x.isdigit()])

            # Remove any whitespace.
            name = name.replace(" ", "")

            # Try to infer the element.
            element = _SireMol.Element.biologicalElement(name)

            # Hydrogen.
            if element == _SireMol.Element("H"):
                molecule = molecule.atom(atom.number())                              \
                                   .rename(_SireMol.AtomName("HW%d" % hydrogen_idx)) \
                                   .molecule()
                hydrogen_idx += 1
            # Oxygen.
            elif element == _SireMol.Element("O"):
                molecule = molecule.atom(atom.number())             \
                                   .rename(_SireMol.AtomName("OW")) \
                                   .molecule()

    # Commit and return the updated molecule.
    return molecule.commit()
Exemplo n.º 22
0
def _score_rmsd(molecule0, molecule1, mappings, is_align=False):
    """Internal function to score atom mappings based on the root mean squared
       displacement (RMSD) between mapped atoms in two molecules. Optionally,
       molecule0 can first be aligned to molecule1 based on the mapping prior
       to computing the RMSD. The function returns the mappings sorted based
       on their score from best to worst, along with a list containing the
       scores for each mapping.

       Parameters
       ----------

       molecule0 : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`
           The reference molecule.

       molecule1 : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`
           The target molecule.

       mappings : [ dict ]
           A list of dictionaries mapping  atoms in molecule0 to those in
           molecule1.

       is_align : bool
           Whether to align molecule0 to molecule1 based on the mapping
           before calculating the RMSD score.

       Returns
       -------

       mappings : [ dict ]
           The sorted mappings.
    """

    # Initialise a list of scores.
    scores = []

    # Loop over all mappings.
    for mapping in mappings:
        # Align molecule0 to molecule1 based on the mapping.
        if is_align:
            try:
                molecule0 = molecule0.move().align(
                    molecule1, _SireMol.AtomResultMatcher(mapping)).molecule()
            except:
                raise _AlignmentError(
                    "Failed to align molecules when scoring based on mapping: %r"
                    % mapping) from None

        # We now compute the RMSD between the coordinates of the matched atoms
        # in molecule0 and molecule1.

        # Initialise lists to hold the coordinates.
        c0 = []
        c1 = []

        # Loop over each atom index in the map.
        for idx0, idx1 in mapping.items():
            # Append the coordinates of the matched atom in molecule0.
            c0.append(molecule0.atom(idx0).property("coordinates"))
            # Append the coordinates of atom in molecule1 to which it maps.
            c1.append(molecule1.atom(idx1).property("coordinates"))

        # Compute the RMSD between the two sets of coordinates.
        scores.append(_SireMaths.getRMSD(c0, c1))

    # Sort the scores and return the sorted keys. (Smaller RMSD is best)
    keys = sorted(range(len(scores)), key=lambda k: scores[k])

    # Sort the mappings.
    mappings = [mappings[x] for x in keys]

    # Sort the scores and convert to Angstroms.
    scores = [scores[x] * _Units.Length.angstrom for x in keys]

    # Return the sorted mappings and their scores.
    return (mappings, scores)
Exemplo n.º 23
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
Exemplo n.º 24
0
def saveMolecules(filebase, system, fileformat, property_map={}):
    """Save a molecular system to file.

       Parameters
       ----------

       filebase : str
           The base name of the output file.

       system : :class:`System <BioSimSpace._SireWrappers.System>`, \
                :class:`Molecule< BioSimSpace._SireWrappers.Molecule>`
                [ BioSimSpace._SireWrappers.Molecule ]
           The molecular system.

       fileformat : str, [str]
           The file format (or formats) to save to.

       property_map : dict
           A dictionary that maps system "properties" to their user
           defined values. This allows the user to refer to properties
           with their own naming scheme, e.g. { "charge" : "my-charge" }

       Returns
       -------

       files : [str]
           The list of files that were generated.

       Examples
       --------

       Load a molecular system from AMBER coordinate and topology files then
       try to save it to all supported file formats.

       >>> import BioSimSpace as BSS
       >>> system = BSS.IO.readMolecules(["ala.rst7", "ala.prm7"])
       >>> for format in BSS.IO.fileFormats():
       ...     try:
       ...         BSS.IO.saveMolecules("test", system, format)
       ...     except:
       ...         print("Could not convert to format: '%s'" % format)

       Load a molecular system from AMBER coordinate and topology files then
       try to save it to GROMACS format, mapping and un-mapping the charge
       property along the way.

       >>> import BioSimSpace as BSS
       >>> system = BSS.IO.readMolecules(["ala.rst7", "ala.prm7"], property_map={"charge" : "my-charge"})
       >>> BSS.IO.saveMolecules("test", system, ["gro87", "grotop"], property_map={"charge" : "my-charge"})
    """

    global _has_gmx_warned
    if _gromacs_path is None and not _has_gmx_warned:
        _warn(
            "BioSimSpace.IO: Please install GROMACS (http://www.gromacs.org) "
            "for GROMACS topology file support.")
        _has_gmx_warned = True

    # Check that the filebase is a string.
    if type(filebase) is not str:
        raise TypeError("'filebase' must be of type 'str'")

    # Check that that the system is of the correct type.

    # A System object.
    if type(system) is _System:
        pass
    # A Molecule object.
    elif type(system) is _Molecule:
        system = [system]
    # A list of Molecule objects.
    elif type(system) is list and all(
            isinstance(x, _Molecule) for x in system):
        pass
    # Invalid type.
    else:
        raise TypeError(
            "'system' must be of type 'BioSimSpace.SireWrappers.System', "
            "'BioSimSpace._SireWrappers.Molecule, or a list of "
            "'BiSimSpace._SireWrappers.Molecule' types.")

    # Check that fileformat argument is of the correct type.

    # Convert to a list if a single string is passed.
    # We split on ',' since the user might pass system.fileFormats() as the argument.
    if type(fileformat) is str:
        fileformat = fileformat.split(",")
    # Lists and tuples are okay!
    elif type(fileformat) is list:
        pass
    elif type(fileformat) is tuple:
        pass
    # Invalid.
    else:
        raise TypeError(
            "'fileformat' must be a 'str' or a 'list' of 'str' types.")

    # Make sure all items in list or tuple are strings.
    if not all(isinstance(x, str) for x in fileformat):
        raise TypeError(
            "'fileformat' must be a 'str' or a 'list' of 'str' types.")

    # Make a list of the matched file formats.
    formats = []

    # Make sure that all of the formats are valid.
    for format in fileformat:
        try:
            f = _formats_dict[format.replace(" ", "").upper()][0]
            formats.append(f)
        except KeyError:
            raise ValueError("Unsupported file format '%s'. Supported formats "
                             "are: %s." % (format, str(_formats)))

    # Validate the map.
    if type(property_map) is not dict:
        raise TypeError("'property_map' must be of type 'dict'")

    # Copy the map.
    _property_map = property_map.copy()

    # Add the GROMACS topology file path.
    if _gromacs_path is not None and ("GROMACS_PATH" not in _property_map):
        _property_map["GROMACS_PATH"] = _gromacs_path

    # We have a list of molecules. Create a new system and add each molecule.
    if type(system) is list:

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

        # Add all of the molecules to the group.
        for molecule in system:
            m.add(molecule._getSireMolecule())

        # Add the molecule group to the system.
        s.add(m)

        # Wrap the system.
        system = _System(s)

    # Get the directory name.
    dirname = _os.path.dirname(filebase)

    # If the user has passed a directory, make sure that is exists.
    if _os.path.basename(filebase) != filebase:
        # Create the directory if it doesn't already exist.
        if not _os.path.isdir(dirname):
            _os.makedirs(dirname, exist_ok=True)

    # Store the current working directory.
    dir = _os.getcwd()

    # Change to the working directory for the process.
    # This avoid problems with relative paths.
    if dirname != "":
        _os.chdir(dirname)

    # A list of the files that have been written.
    files = []

    # Save the system using each file format.
    for format in formats:
        # Add the file format to the property map.
        _property_map["fileformat"] = _SireBase.wrap(format)

        # Write the file.
        try:
            file = _SireIO.MoleculeParser.save(system._getSireSystem(),
                                               filebase, _property_map)
            files += file
        except:
            if dirname != "":
                _os.chdir(dir)
            raise IOError("Failed to save system to format: '%s'" %
                          format) from None

    # Change back to the original directory.
    if dirname != "":
        _os.chdir(dir)

    # Return the list of files.
    return files
Exemplo n.º 25
0
def _restrain_backbone(system):
    """Restrain protein backbone atoms.

        Parameters
        ----------

        system : Sire.System.System
            A Sire molecular system.
    """

    # Copy the original system.
    s = system

    # A list of amino acid name abbreviations.
    # Since we only want to restrain atoms in protein backbones, we compare
    # molecule residue names against this list in order to determine whether
    # the molecule is a protein.
    amino_acids = ["ALA", "CYS", "ASP", "GLU", "PHE", "GLY", "HIS", "ILE",
        "LYS", "LEU", "MET", "ASN", "PRO", "GLN", "ARG", "SER", "THR", "SEC",
        "VAL", "TRP", "TYR"]

    # Loop over all molecules by number.
    for n in s.molNums():

        # Extract the molecule and make it editable.
        m = s.molecule(n).edit()

        # Initialise a list of protein residues.
        protein_residues = []

        # Compare each residue name against the amino acid list.
        for res in m.residues():

            # This residue is an amino acid.
            if res.name().value().upper() in amino_acids:
                protein_residues.append(res.index())

        # Loop over all of the protein residues.
        for residx in protein_residues:

            # Loop over all of the atoms in the residue.
            for atom in m.residue(residx).atoms():

                # Try to compare the atom element property against the list of
                # backbone atoms and set the "restrained" property if a match
                # is found.
                try:
                    element = atom.property("element")
                    if element == _SireMol.Element("CA") or \
                       element == _SireMol.Element("N")  or \
                       element == _SireMol.Element("C")  or \
                       element == _SireMol.Element("O"):
                           m = m.atom(atom.index()).setProperty("restrained", 1.0).molecule()

                except:
                    pass

        # Update the system.
        s.update(m.commit())

    # Return the new system.
    return s
Exemplo n.º 26
0
def rescaleSystemParams(system,
                        scale,
                        includelist=None,
                        excludelist=None,
                        neutralise=True):
    """
    Rescales charge, Lennard-Jones and dihedral parameters for REST(2).

    Parameters
    ----------
    system : BioSimSpace.System
        Input system to be rescaled.
    scale : float
        Multiplication factor for the charge, Lennard-Jones and dihedral parameters. 0 < scale < 1.
    includelist : [str]
        Molecule names to be included in the rescaling. Default: None.
    excludelist : [str]
        Molecule names to be excluded in the rescaling. Default: ["WAT"].
    neutralise : bool
        Whether to rescale a minimum number of Na+ or Cl- ions so that the rescaled part of the system is neutralised.

    Returns
    -------
    system_new : BioSimSpace.System
        The rescaled system.
    """
    if excludelist is None and includelist is None:
        excludelist = ["WAT"]
    elif excludelist is not None and includelist is not None:
        raise ValueError("Only one of includelist and excludelist can be set")

    if neutralise:
        if excludelist is not None:
            total_charge = round(
                sum([
                    mol.charge().magnitude() for mol in system.getMolecules()
                    if mol._sire_object.name().value() not in excludelist
                ]))
        elif includelist is not None:
            total_charge = round(
                sum([
                    mol.charge().magnitude() for mol in system.getMolecules()
                    if mol._sire_object.name().value() in includelist
                ]))

    mols_mod = []

    for mol in system.getMolecules():
        if neutralise and mol._sire_object.name().value().lower(
        ) == "na" and total_charge < 0:
            total_charge += 1
        elif neutralise and mol._sire_object.name().value().lower(
        ) == "cl" and total_charge > 0:
            total_charge -= 1
        elif excludelist is not None:
            if mol._sire_object.name().value() in excludelist:
                mols_mod += [mol]
                continue
        elif includelist is not None:
            if mol._sire_object.name().value() not in includelist:
                mols_mod += [mol]
                continue

        mol_edit = mol._sire_object.edit()

        if scale != 1:
            for prop in [
                    "dihedral", "dihedral0", "dihedral1", "improper",
                    "improper0", "improper1"
            ]:
                try:
                    potentials = mol_edit.property(prop).potentials()
                    prop_new = _SireMM.FourAtomFunctions(mol_edit.info())
                    for potential in potentials:
                        prop_new.set(potential.atom0(), potential.atom1(),
                                     potential.atom2(), potential.atom3(),
                                     potential.function() * scale)
                    mol_edit = mol_edit.setProperty(prop, prop_new).molecule()
                except:
                    continue

        for atom in mol_edit.atoms():
            idx = atom.index()

            for prop in ["atomtype", "atomtype0", "atomtype1"]:
                try:
                    atomtype_new = atom.property(prop) + "_"
                    mol_edit = mol_edit.atom(idx).setProperty(
                        prop, atomtype_new).molecule()
                except:
                    continue

            if scale != 1:
                for prop in ["LJ", "LJ0", "LJ1"]:
                    try:
                        LJ = atom.property(prop)
                        LJ = LJ.fromSigmaAndEpsilon(LJ.sigma(),
                                                    LJ.epsilon() * scale)
                        mol_edit = mol_edit.atom(idx).setProperty(
                            prop, LJ).molecule()
                    except:
                        continue

                for prop in ["charge", "charge0", "charge1"]:
                    try:
                        charge = atom.property(prop)
                        charge = charge * scale
                        mol_edit = mol_edit.atom(idx).setProperty(
                            prop, charge).molecule()
                    except:
                        continue

        if mol._sire_object.name().value().lower() in ["na", "cl"]:
            resname = mol_edit.residue().name()
            resname_new = _SireMol.ResName(resname.value() + "_")
            mol_edit = mol_edit.residue(resname).rename(resname_new)

        mol._sire_object = mol_edit.commit()
        mols_mod += [mol]

    system_new = _BSS._SireWrappers._system.System(mols_mod)
    box = system._sire_object.property("space")
    system_new._sire_object.setProperty("space", box)

    return system_new
Exemplo n.º 27
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)
Exemplo n.º 28
0
    def addMolecules(self, molecules):
        """Add a molecule, or list of molecules to the system.

           Parameters
           ----------

           molecules : :class:`Molecule <BioSimSpace._SireWrappers.Molecule>`, \
                       :class:`Molecules <BioSimSpace._SireWrappers.Molecules>`, \
                       [:class:`Molecule <BioSimSpace._SireWrappers.Molecule>`], \
                       :class:`System <BioSimSpace._SireWrappers.System>`
              A Molecule, Molecules object, a list of Molecule objects, or a System containing molecules.
        """

        # Whether the molecules are in a Sire container.
        is_sire_container = False

        # Convert tuple to a list.
        if type(molecules) is tuple:
            molecules = list(molecules)

        # A Molecule object.
        if type(molecules) is _Molecule:
            molecules = [molecules]

        # A Molecules object.
        if type(molecules) is _Molecules:
            is_sire_container = True

        # A System object.
        elif type(molecules) is System:
            is_sire_container = True

        # A list of Molecule objects.
        elif type(molecules) is list and all(isinstance(x, _Molecule) for x in molecules):
            pass

        # Invalid argument.
        else:
            raise TypeError("'molecules' must be of type 'BioSimSpace._SireWrappers.Molecule', "
                            ", 'BioSimSpace._SireWrappers.System', or a list of "
                            "'BioSimSpace._SireWrappers.Molecule' types.")

        # The system is empty: create a new Sire system from the molecules.
        if self._sire_object.nMolecules() == 0:
            self._sire_object = self._createSireSystem(molecules)

        # Otherwise, add the molecules to the existing "all" group.
        else:
            if is_sire_container:
                if type(molecules) is _Molecules:
                    molecules = molecules._sire_object
                else:
                    try:
                        molecules = molecules._sire_object.at(_SireMol.MGNum(1))
                    except:
                        molecules = molecules._sire_object.molecules()
                self._sire_object.add(molecules, _SireMol.MGName("all"))
            else:
                for mol in molecules:
                    self._sire_object.add(mol._sire_object, _SireMol.MGName("all"))

        # Reset the index mappings.
        self._reset_mappings()

        # Update the molecule numbers.
        self._mol_nums = self._sire_object.molNums()
Exemplo n.º 29
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
Exemplo n.º 30
0
 def __init__(self, resname):
     super().__init__(_SireMol.ResName(resname))