示例#1
0
    def setBox(self, size, property_map={}):
        """Set the size of the periodic simulation box.

           Parameters
           ----------

           size : [:class:`Length <BioSimSpace.Types.Length>`]
               The size of the box in each dimension.

           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" }

        """

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

        # Validate input.
        if type(size) is not list or not all(isinstance(x, _Length) for x in size):
            raise TypeError("'size' must be a list of 'BioSimSpace.Types.Length' objects.")

        if len(size) != 3:
            raise ValueError("'size' must contain three items.")

        # Convert sizes to Anstrom.
        vec = [x.angstroms().magnitude() for x in size]

        # Create a periodic box object.
        box = _SireVol.PeriodicBox(_SireMaths.Vector(vec))

        # Set the "space" property.
        self._sire_object.setProperty(property_map.get("space", "space"), box)
    def translate(self, vector, property_map={}):
        """Translate the object.

           Parameters
           ----------

           vector : [:class:`Length <BioSimSpace.Types.Length>`]
               The translation vector in Angstroms.

           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" }
        """

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

        # Validate input.
        if type(vector) is list:
            vec = []
            for x in vector:
                if type(x) is int:
                    vec.append(float(x))
                elif type(x) is float:
                    vec.append(x)
                elif type(x) is _Length:
                    vec.append(x.angstroms().magnitude())
                else:
                    raise TypeError("'vector' must contain 'int', 'float', or "
                                    "'BioSimSpace.Types.Length' types only!")
        else:
            raise TypeError("'vector' must be of type 'list' or 'tuple'")

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

        try:
            # Make a local copy of the property map.
            _property_map = property_map.copy()

            if "coordinates" not in property_map and self._is_merged:
                _property_map["coordinates"] = "coordinates0"

            # Perform the translation.
            self._sire_object = self._sire_object                                     \
                                    .move()                                           \
                                    .translate(_SireMaths.Vector(vec), _property_map) \
                                    .commit()
        except UserWarning as e:
            msg = "Cannot compute axis-aligned bounding box " + \
                   "since the object has no 'coordinates' property."
            if _isVerbose():
                raise _IncompatibleError(msg) from e
            else:
                raise _IncompatibleError(msg) from None
示例#3
0
    def translate(self, vector, property_map={}):
        """Translate each molecule in the container.

           Parameters
           ----------

           vector : [:class:`Length <BioSimSpace.Types.Length>`]
               The translation vector in Angstroms.

           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" }
        """

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

        # Validate input.
        if type(vector) is list:
            vec = []
            for x in vector:
                if type(x) is int:
                    vec.append(float(x))
                elif type(x) is float:
                    vec.append(x)
                elif type(x) is _Length:
                    vec.append(x.angstroms().magnitude())
                else:
                    raise TypeError("'vector' must contain 'int', 'float', or "
                                    "'BioSimSpace.Types.Length' types only!")
        else:
            raise TypeError("'vector' must be of type 'list' or 'tuple'")

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

        # Translate each of the molecules in the container.
        for n in self._sire_object.molNums():
            # Copy the property map.
            _property_map = property_map.copy()

            # If this is a perturbable molecule, use the coordinates at lambda = 0.
            if self._sire_object.molecule(n).hasProperty("is_perturbable"):
                _property_map["coordinates"] = "coordinates0"

            mol = self._sire_object[n].move().translate(
                _SireMaths.Vector(vec), _property_map).commit()
            self._sire_object.update(mol)
示例#4
0
def centre(system, box_length):
    """
    Centres the system given a box length.

    Parameters
    ----------
    system : BioSimSpace.System
        The input system to be centred.
    box_length : float, iterable
        Length of the box in nm.

    Returns
    -------
    system : BioSimspace.System
        The centred BioSimSpace system.
    box_length : tuple
        The new box length. Only different from the input value if the box is too small for the system.
    translation_vec: Sire.Maths.vector
        The centering translation vector.
    """
    if not isinstance(box_length, _Iterable):
        cubic = True
        box_length = 3 * [box_length]
    else:
        cubic = (len(set(box_length)) == 1)

    box = system._getAABox()
    min_coords, max_coords = box.minCoords(), box.maxCoords()
    centre = (min_coords + max_coords) / 2
    difference = max_coords - min_coords

    box_length_new = []
    for x, y in zip(difference, box_length):
        box_length_new += [y] if x < 10 * y else [x / 10 + 1]
    if cubic:
        box_length_new = 3 * [max(box_length_new)]
    if box_length_new != box_length:
        _warnings.warn(
            "Insufficient input box size. Changing to a box size of ({:.3f}, {:.3f}, {:.3f}) nm..."
            .format(*box_length_new))

    translation_vec = -centre + _SireMaths.Vector(
        [5 * x for x in box_length_new])
    system.translate(tuple(translation_vec))
    system = resize(system, box_length_new)

    return system, tuple(box_length_new), translation_vec
示例#5
0
def resize(system, box_length):
    """
    Changes the box size of the system or adds one if there is no box.

    Parameters
    ----------
    system : BioSimSpace.System
        The input system to be resized.
    box_length : float, iterable
        Length of the box in nm.

    Returns
    -------
    system : BioSimSpace.System
        The resized system.
    """
    if not isinstance(box_length, _Iterable):
        box_length = 3 * [box_length]
    box_length = tuple([10 * x for x in box_length])
    system._sire_object.setProperty(
        "space", _SireVol.PeriodicBox(_SireMaths.Vector(box_length)))
    return system
示例#6
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)
示例#7
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)
示例#8
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)