示例#1
0
def test_check_weights_raises_ValueError(atoms, weights):
    with pytest.raises(ValueError):
        util.get_weights(atoms, weights)
示例#2
0
    def __init__(self,
                 atomgroup,
                 reference=None,
                 select='all',
                 groupselections=None,
                 filename="rmsd.dat",
                 weights=None,
                 tol_mass=0.1,
                 ref_frame=0,
                 **kwargs):
        # DEPRECATION: remove filename kwarg in 1.0
        r"""Parameters
        ----------
        atomgroup : AtomGroup or Universe
            Group of atoms for which the RMSD is calculated. If a trajectory is
            associated with the atoms then the computation iterates over the
            trajectory.
        reference : AtomGroup or Universe (optional)
            Group of reference atoms; if ``None`` then the current frame of
            `atomgroup` is used.
        select : str or dict or tuple (optional)
            The selection to operate on; can be one of:

            1. any valid selection string for
               :meth:`~MDAnalysis.core.groups.AtomGroup.select_atoms` that
               produces identical selections in `atomgroup` and `reference`; or

            2. a dictionary ``{'mobile': sel1, 'reference': sel2}`` where *sel1*
               and *sel2* are valid selection strings that are applied to
               `atomgroup` and `reference` respectively (the
               :func:`MDAnalysis.analysis.align.fasta2select` function returns such
               a dictionary based on a ClustalW_ or STAMP_ sequence alignment); or

            3. a tuple ``(sel1, sel2)``

            When using 2. or 3. with *sel1* and *sel2* then these selection strings
            are applied to `atomgroup` and `reference` respectively and should
            generate *groups of equivalent atoms*.  *sel1* and *sel2* can each also
            be a *list of selection strings* to generate a
            :class:`~MDAnalysis.core.groups.AtomGroup` with defined atom order as
            described under :ref:`ordered-selections-label`).

        groupselections : list (optional)
            A list of selections as described for `select`, with the difference
            that these selections are *always applied to the full universes*,
            i.e., ``atomgroup.universe.select_atoms(sel1)`` and
            ``reference.universe.select_atoms(sel2)``. Each selection describes
            additional RMSDs to be computed *after the structures have been
            superimposed* according to `select`. No additional fitting is
            performed.The output contains one additional column for each
            selection.

            .. Note:: Experimental feature. Only limited error checking
                      implemented.
        filename : str (optional)
            write RMSD into file with :meth:`RMSD.save`

            .. deprecated:; 0.19.0
               `filename` will be removed together with :meth:`save` in 1.0.

        weights : {"mass", ``None``} or array_like (optional)
             choose weights. With ``"mass"`` uses masses as weights; with ``None``
             weigh each atom equally. If a float array of the same length as
             `atomgroup` is provided, use each element of the `array_like` as a
             weight for the corresponding atom in `atomgroup`.
        tol_mass : float (optional)
             Reject match if the atomic masses for matched atoms differ by more
             than `tol_mass`.
        ref_frame : int (optional)
             frame index to select frame from `reference`
        verbose : bool (optional)
             Show detailed progress of the calculation if set to ``True``; the
             default is ``False``.

        Raises
        ------
        SelectionError
             If the selections from `atomgroup` and `reference` do not match.
        TypeError
             If `weights` is not of the appropriate type; see also
             :func:`MDAnalysis.lib.util.get_weights`
        ValueError
             If `weights` are not compatible with `atomgroup` (not the same
             length) or if it is not a 1D array (see
             :func:`MDAnalysis.lib.util.get_weights`).

             A :exc:`ValueError` is also raised if `weights` are not compatible
             with `groupselections`: only equal weights (``weights=None``) or
             mass-weighted (``weights="mass"``) are supported for additional
             `groupselections`.

        Notes
        -----
        The root mean square deviation :math:`\rho(t)` of a group of :math:`N`
        atoms relative to a reference structure as a function of time is
        calculated as

        .. math::

           \rho(t) = \sqrt{\frac{1}{N} \sum_{i=1}^N w_i \left(\mathbf{x}_i(t)
                                    - \mathbf{x}_i^{\text{ref}}\right)^2}

        The weights :math:`w_i` are calculated from the input weights `weights`
        :math:`w'_i` as relative to the mean of the input weights:

        .. math::

           w_i = \frac{w'_i}{\langle w' \rangle}

        The selected coordinates from `atomgroup` are optimally superimposed
        (translation and rotation) on the `reference` coordinates at each time step
        as to minimize the RMSD. Douglas Theobald's fast QCP algorithm
        [Theobald2005]_ is used for the rotational superposition and to calculate
        the RMSD (see :mod:`MDAnalysis.lib.qcprot` for implementation details).

        The class runs various checks on the input to ensure that the two atom
        groups can be compared. This includes a comparison of atom masses (i.e.,
        only the positions of atoms of the same mass will be considered to be
        correct for comparison). If masses should not be checked, just set
        `tol_mass` to a large value such as 1000.

        .. _ClustalW: http://www.clustal.org/
        .. _STAMP: http://www.compbio.dundee.ac.uk/manuals/stamp.4.2/


        See Also
        --------
        rmsd


        .. versionadded:: 0.7.7
        .. versionchanged:: 0.8
           `groupselections` added
        .. versionchanged:: 0.16.0
           Flexible weighting scheme with new `weights` keyword.
        .. deprecated:: 0.16.0
           Instead of ``mass_weighted=True`` (removal in 0.17.0) use new
           ``weights='mass'``; refactored to fit with AnalysisBase API
        .. versionchanged:: 0.17.0
           removed deprecated `mass_weighted` keyword; `groupselections`
           are *not* rotationally superimposed any more.
        .. deprecated:: 0.19.0
           `filename` will be removed in 1.0

        """
        super(RMSD, self).__init__(atomgroup.universe.trajectory, **kwargs)
        self.atomgroup = atomgroup
        self.reference = reference if reference is not None else self.atomgroup

        select = process_selection(select)
        self.groupselections = (
            [process_selection(s)
             for s in groupselections] if groupselections is not None else [])
        self.weights = weights
        self.tol_mass = tol_mass
        self.ref_frame = ref_frame
        self.filename = filename  # DEPRECATED in 0.19.0, remove in 1.0.0

        self.ref_atoms = self.reference.select_atoms(*select['reference'])
        self.mobile_atoms = self.atomgroup.select_atoms(*select['mobile'])

        if len(self.ref_atoms) != len(self.mobile_atoms):
            err = ("Reference and trajectory atom selections do "
                   "not contain the same number of atoms: "
                   "N_ref={0:d}, N_traj={1:d}".format(
                       self.ref_atoms.n_atoms, self.mobile_atoms.n_atoms))
            logger.exception(err)
            raise SelectionError(err)
        logger.info("RMS calculation "
                    "for {0:d} atoms.".format(len(self.ref_atoms)))
        mass_mismatches = (np.absolute(
            (self.ref_atoms.masses - self.mobile_atoms.masses)) >
                           self.tol_mass)

        if np.any(mass_mismatches):
            # diagnostic output:
            logger.error("Atoms: reference | mobile")
            for ar, at in zip(self.ref_atoms, self.mobile_atoms):
                if ar.name != at.name:
                    logger.error("{0!s:>4} {1:3d} {2!s:>3} {3!s:>3} {4:6.3f}"
                                 "|  {5!s:>4} {6:3d} {7!s:>3} {8!s:>3}"
                                 "{9:6.3f}".format(ar.segid, ar.resid,
                                                   ar.resname, ar.name,
                                                   ar.mass, at.segid, at.resid,
                                                   at.resname, at.name,
                                                   at.mass))
            errmsg = ("Inconsistent selections, masses differ by more than"
                      "{0:f}; mis-matching atoms"
                      "are shown above.".format(self.tol_mass))
            logger.error(errmsg)
            raise SelectionError(errmsg)
        del mass_mismatches

        # TODO:
        # - make a group comparison a class that contains the checks above
        # - use this class for the *select* group and the additional
        #   *groupselections* groups each a dict with reference/mobile
        self._groupselections_atoms = [{
            'reference':
            self.reference.universe.select_atoms(*s['reference']),
            'mobile':
            self.atomgroup.universe.select_atoms(*s['mobile']),
        } for s in self.groupselections]
        # sanity check
        for igroup, (sel, atoms) in enumerate(
                zip(self.groupselections, self._groupselections_atoms)):
            if len(atoms['mobile']) != len(atoms['reference']):
                logger.exception('SelectionError: Group Selection')
                raise SelectionError(
                    "Group selection {0}: {1} | {2}: Reference and trajectory "
                    "atom selections do not contain the same number of atoms: "
                    "N_ref={3}, N_traj={4}".format(igroup, sel['reference'],
                                                   sel['mobile'],
                                                   len(atoms['reference']),
                                                   len(atoms['mobile'])))

        # Explicitly check for "mass" because this option CAN
        # be used with groupselection. (get_weights() returns the mass array
        # for "mass")
        if not iterable(self.weights) and self.weights == "mass":
            pass
        else:
            self.weights = get_weights(self.mobile_atoms, self.weights)

        # cannot use arbitrary weight array (for superposition) with
        # groupselections because arrays will not match
        if (len(self.groupselections) > 0 and
            (iterable(self.weights) or self.weights not in ("mass", None))):
            raise ValueError("groupselections can only be combined with "
                             "weights=None or weights='mass', not a weight "
                             "array.")

        # initialized to note for testing the save function
        self.rmsd = None
示例#3
0
    def __init__(self,
                 mobile,
                 reference,
                 select='all',
                 filename=None,
                 prefix='rmsfit_',
                 weights=None,
                 tol_mass=0.1,
                 strict=False,
                 force=True,
                 in_memory=False,
                 **kwargs):
        """Parameters
        ----------
        mobile : Universe
            Universe containing trajectory to be fitted to reference
        reference : Universe
            Universe containing trajectory frame to be used as reference
        select : str (optional)
            Set as default to all, is used for Universe.select_atoms to choose
            subdomain to be fitted against
        filename : str (optional)
            Provide a filename for results to be written to
        prefix : str (optional)
            Provide a string to prepend to filename for results to be written
            to
        weights : {"mass", ``None``} or array_like (optional)
            choose weights. With ``"mass"`` uses masses of `reference` as
            weights; with ``None`` weigh each atom equally. If a float array of
            the same length as the selection is provided, use each element of
            the `array_like` as a weight for the corresponding atom in the
            selection.
        tol_mass : float (optional)
            Tolerance given to `get_matching_atoms` to find appropriate atoms
        strict : bool (optional)
            Force `get_matching_atoms` to fail if atoms can't be found using
            exact methods
        force : bool (optional)
            Force overwrite of filename for rmsd-fitting
        verbose : bool (optional)
            Set logger to show more information
        start : int (optional)
            First frame of trajectory to analyse, Default: 0
        stop : int (optional)
            Last frame of trajectory to analyse, Default: -1
        step : int (optional)
            Step between frames to analyse, Default: 1
        in_memory : bool (optional)
            *Permanently* switch `mobile` to an in-memory trajectory
            so that alignment can be done in-place, which can improve
            performance substantially in some cases. In this case, no file
            is written out (`filename` and `prefix` are ignored) and only
            the coordinates of `mobile` are *changed in memory*.

        Attributes
        ----------
        reference_atoms : AtomGroup
            Atoms of the reference structure to be aligned against
        mobile_atoms : AtomGroup
            Atoms inside each trajectory frame to be rmsd_aligned
        rmsd : Array
            Array of the rmsd values of the least rmsd between the mobile_atoms
            and reference_atoms after superposition and minimimization of rmsd
        filename : str
            String reflecting the filename of the file where mobile_atoms
            positions will be written to upon running RMSD alignment


        Notes
        -----
        - If set to ``verbose=False``, it is recommended to wrap the statement
          in a ``try ...  finally`` to guarantee restoring of the log level in
          the case of an exception.
        - The ``in_memory`` option changes the `mobile` universe to an
          in-memory representation (see :mod:`MDAnalysis.coordinates.memory`)
          for the remainder of the Python session. If ``mobile.trajectory`` is
          already a :class:`MemoryReader` then it is *always* treated as if
          ``in_memory`` had been set to ``True``.


        .. versionchanged:: 0.16.0
           new general ``weights`` kwarg replace ``mass_weights``

        .. deprecated:: 0.16.0
           Instead of ``mass_weighted=True`` use new ``weights='mass'``

        .. versionchanged:: 0.17.0
           removed deprecated `mass_weighted` keyword

        """
        select = rms.process_selection(select)
        self.ref_atoms = reference.select_atoms(*select['reference'])
        self.mobile_atoms = mobile.select_atoms(*select['mobile'])
        if in_memory or isinstance(mobile.trajectory, MemoryReader):
            mobile.transfer_to_memory()
            filename = None
            logger.info("Moved mobile trajectory to in-memory representation")
        else:
            if filename is None:
                path, fn = os.path.split(mobile.trajectory.filename)
                filename = os.path.join(path, prefix + fn)
                logger.info('filename of rms_align with no filename given'
                            ': {0}'.format(filename))

            if os.path.exists(filename) and not force:
                raise IOError(
                    'Filename already exists in path and force is not set'
                    ' to True')

        # do this after setting the memory reader to have a reference to the
        # right reader.
        super(AlignTraj, self).__init__(mobile.trajectory, **kwargs)
        if not self._verbose:
            logging.disable(logging.WARN)

        # store reference to mobile atoms
        self.mobile = mobile.atoms

        self.filename = filename

        natoms = self.mobile.n_atoms
        self.ref_atoms, self.mobile_atoms = get_matching_atoms(
            self.ref_atoms,
            self.mobile_atoms,
            tol_mass=tol_mass,
            strict=strict)

        # with self.filename == None (in_memory), the NullWriter is chosen
        # (which just ignores input) and so only the in_memory trajectory is
        # retained
        self._writer = mda.Writer(self.filename, natoms)

        self._weights = get_weights(self.ref_atoms, weights)

        logger.info("RMS-fitting on {0:d} atoms.".format(len(self.ref_atoms)))
示例#4
0
def test_check_weights_ok(atoms, weights, result):
    assert_array_equal(util.get_weights(atoms, weights), result)
示例#5
0
def alignto(mobile,
            reference,
            select="all",
            weights=None,
            subselection=None,
            tol_mass=0.1,
            strict=False):
    """Perform a spatial superposition by minimizing the RMSD.

    Spatially align the group of atoms `mobile` to `reference` by
    doing a RMSD fit on `select` atoms.

    The superposition is done in the following way:

    1. A rotation matrix is computed that minimizes the RMSD between
       the coordinates of `mobile.select_atoms(sel1)` and
       `reference.select_atoms(sel2)`; before the rotation, `mobile` is
       translated so that its center of geometry (or center of mass)
       coincides with the one of `reference`. (See below for explanation of
       how *sel1* and *sel2* are derived from `select`.)

    2. All atoms in :class:`~MDAnalysis.core.universe.Universe` that
       contain `mobile` are shifted and rotated. (See below for how
       to change this behavior through the `subselection` keyword.)

    The `mobile` and `reference` atom groups can be constructed so that they
    already match atom by atom. In this case, `select` should be set to "all"
    (or ``None``) so that no further selections are applied to `mobile` and
    `reference`, therefore preserving the exact atom ordering (see
    :ref:`ordered-selections-label`).

    .. Warning:: The atom order for `mobile` and `reference` is *only*
       preserved when `select` is either "all" or ``None``. In any other case,
       a new selection will be made that will sort the resulting AtomGroup by
       index and therefore destroy the correspondence between the two groups.
       **It is safest not to mix ordered AtomGroups with selection strings.**

    Parameters
    ----------
    mobile : Universe or AtomGroup
       structure to be aligned, a
       :class:`~MDAnalysis.core.groups.AtomGroup` or a whole
       :class:`~MDAnalysis.core.universe.Universe`
    reference : Universe or AtomGroup
       reference structure, a :class:`~MDAnalysis.core.groups.AtomGroup`
       or a whole :class:`~MDAnalysis.core.universe.Universe`
    select : str or dict or tuple (optional)
       The selection to operate on; can be one of:

       1. any valid selection string for
          :meth:`~MDAnalysis.core.groups.AtomGroup.select_atoms` that
          produces identical selections in `mobile` and `reference`; or

       2. a dictionary ``{'mobile': sel1, 'reference': sel2}`` where *sel1*
          and *sel2* are valid selection strings that are applied to
          `mobile` and `reference` respectively (the
          :func:`MDAnalysis.analysis.align.fasta2select` function returns such
          a dictionary based on a ClustalW_ or STAMP_ sequence alignment); or

       3. a tuple ``(sel1, sel2)``

       When using 2. or 3. with *sel1* and *sel2* then these selection strings
       are applied to `atomgroup` and `reference` respectively and should
       generate *groups of equivalent atoms*.  *sel1* and *sel2* can each also
       be a *list of selection strings* to generate a
       :class:`~MDAnalysis.core.groups.AtomGroup` with defined atom order as
       described under :ref:`ordered-selections-label`).
    weights : {"mass", ``None``} or array_like (optional)
       choose weights. With ``"mass"`` uses masses as weights; with ``None``
       weigh each atom equally. If a float array of the same length as
       `mobile` is provided, use each element of the `array_like` as a
       weight for the corresponding atom in `mobile`.
    tol_mass: float (optional)
       Reject match if the atomic masses for matched atoms differ by more than
       `tol_mass`, default [0.1]
    strict: bool (optional)
       ``True``
           Will raise :exc:`SelectionError` if a single atom does not
           match between the two selections.
       ``False`` [default]
           Will try to prepare a matching selection by dropping
           residues with non-matching atoms. See :func:`get_matching_atoms`
           for details.
    subselection : str or AtomGroup or None (optional)
       Apply the transformation only to this selection.

       ``None`` [default]
           Apply to ``mobile.universe.atoms`` (i.e., all atoms in the
           context of the selection from `mobile` such as the rest of a
           protein, ligands and the surrounding water)
       *selection-string*
           Apply to ``mobile.select_atoms(selection-string)``
       :class:`~MDAnalysis.core.groups.AtomGroup`
           Apply to the arbitrary group of atoms

    Returns
    -------
    old_rmsd : float
        RMSD before spatial alignment
    new_rmsd : float
        RMSD after spatial alignment

    See Also
    --------
    AlignTraj: More efficient method for RMSD-fitting trajectories.


    .. _ClustalW: http://www.clustal.org/
    .. _STAMP: http://www.compbio.dundee.ac.uk/manuals/stamp.4.2/


    .. versionchanged:: 0.8
       Added check that the two groups describe the same atoms including
       the new *tol_mass* keyword.

    .. versionchanged:: 0.10.0
       Uses :func:`get_matching_atoms` to work with incomplete selections
       and new `strict` keyword. The new default is to be lenient whereas
       the old behavior was the equivalent of ``strict = True``.

    .. versionchanged:: 0.16.0
       new general 'weights' kwarg replace `mass_weighted`, deprecated `mass_weighted`
    .. deprecated:: 0.16.0
       Instead of ``mass_weighted=True`` use new ``weights='mass'``

    .. versionchanged:: 0.17.0
       Deprecated keyword `mass_weighted` was removed.
    """
    if select in ('all', None):
        # keep the EXACT order in the input AtomGroups; select_atoms('all')
        # orders them by index, which can lead to wrong results if the user
        # has crafted mobile and reference to match atom by atom
        mobile_atoms = mobile.atoms
        ref_atoms = reference.atoms
    else:
        select = rms.process_selection(select)
        mobile_atoms = mobile.select_atoms(*select['mobile'])
        ref_atoms = reference.select_atoms(*select['reference'])

    ref_atoms, mobile_atoms = get_matching_atoms(ref_atoms,
                                                 mobile_atoms,
                                                 tol_mass=tol_mass,
                                                 strict=strict)

    weights = get_weights(ref_atoms, weights)

    mobile_com = mobile_atoms.center(weights)
    ref_com = ref_atoms.center(weights)

    ref_coordinates = ref_atoms.positions - ref_com
    mobile_coordinates = mobile_atoms.positions - mobile_com

    old_rmsd = rms.rmsd(mobile_coordinates, ref_coordinates, weights)

    if subselection is None:
        # mobile_atoms is Universe
        mobile_atoms = mobile.universe.atoms
    elif isinstance(subselection, string_types):
        # select mobile_atoms from string
        mobile_atoms = mobile.select_atoms(subselection)
    else:
        try:
            # treat subselection as AtomGroup
            mobile_atoms = subselection.atoms
        except AttributeError:
            raise TypeError("subselection must be a selection string, an"
                            " AtomGroup or Universe or None")

    # _fit_to DOES subtract center of mass, will provide proper min_rmsd
    mobile_atoms, new_rmsd = _fit_to(mobile_coordinates,
                                     ref_coordinates,
                                     mobile_atoms,
                                     mobile_com,
                                     ref_com,
                                     weights=weights)
    return old_rmsd, new_rmsd
示例#6
0
    def _prepare(self):
        self._n_atoms = self.mobile_atoms.n_atoms
        if not self.weights_groupselections:
            if not iterable(
                    self.weights):  # apply 'mass' or 'None' to groupselections
                self.weights_groupselections = [self.weights] * len(
                    self.groupselections)
            else:
                self.weights_groupselections = [None] * len(
                    self.groupselections)

        for igroup, (weights, atoms) in enumerate(
                zip(self.weights_groupselections,
                    self._groupselections_atoms)):
            if str(weights) == 'mass':
                self.weights_groupselections[igroup] = atoms['mobile'].masses
            if weights is not None:
                self.weights_groupselections[igroup] = np.asarray(self.weights_groupselections[igroup],
                                                                  dtype=np.float64) /  \
                                             np.mean(self.weights_groupselections[igroup])
        # add the array of weights to weights_select
        self.weights_select = get_weights(self.mobile_atoms, self.weights)
        self.weights_ref = get_weights(self.ref_atoms, self.weights)
        if self.weights_select is not None:
            self.weights_select = np.asarray(self.weights_select, dtype=np.float64) /  \
                                  np.mean(self.weights_select)
            self.weights_ref = np.asarray(self.weights_ref, dtype=np.float64) / \
                               np.mean(self.weights_ref)

        current_frame = self.reference.universe.trajectory.ts.frame

        try:
            # Move to the ref_frame
            # (coordinates MUST be stored in case the ref traj is advanced
            # elsewhere or if ref == mobile universe)
            self.reference.universe.trajectory[self.ref_frame]
            self._ref_com = self.ref_atoms.center(self.weights_ref)
            # makes a copy
            self._ref_coordinates = self.ref_atoms.positions - self._ref_com
            if self._groupselections_atoms:
                self._groupselections_ref_coords64 = [
                    (self.reference.select_atoms(
                        *s['reference']).positions.astype(np.float64))
                    for s in self.groupselections
                ]
        finally:
            # Move back to the original frame
            self.reference.universe.trajectory[current_frame]

        self._ref_coordinates64 = self._ref_coordinates.astype(np.float64)

        if self._groupselections_atoms:
            # Only carry out a rotation if we want to calculate secondary
            # RMSDs.
            # R: rotation matrix that aligns r-r_com, x~-x~com
            #    (x~: selected coordinates, x: all coordinates)
            # Final transformed traj coordinates: x' = (x-x~_com)*R + ref_com
            self._rot = np.zeros(9, dtype=np.float64)  # allocate space
            self._R = self._rot.reshape(3, 3)
        else:
            self._rot = None

        self.rmsd = np.zeros(
            (self.n_frames, 3 + len(self._groupselections_atoms)))

        self._mobile_coordinates64 = self.mobile_atoms.positions.copy().astype(
            np.float64)
示例#7
0
    def __init__(self, mobile, reference, select='all', filename=None,
                 prefix='rmsfit_', weights=None,
                 tol_mass=0.1, strict=False, force=True, in_memory=False,
                 **kwargs):
        """Parameters
        ----------
        mobile : Universe
            Universe containing trajectory to be fitted to reference
        reference : Universe
            Universe containing trajectory frame to be used as reference
        select : str (optional)
            Set as default to all, is used for Universe.select_atoms to choose
            subdomain to be fitted against
        filename : str (optional)
            Provide a filename for results to be written to
        prefix : str (optional)
            Provide a string to prepend to filename for results to be written
            to
        weights : {"mass", ``None``} or array_like (optional)
            choose weights. With ``"mass"`` uses masses of `reference` as
            weights; with ``None`` weigh each atom equally. If a float array of
            the same length as the selection is provided, use each element of
            the `array_like` as a weight for the corresponding atom in the
            selection.
        tol_mass : float (optional)
            Tolerance given to `get_matching_atoms` to find appropriate atoms
        strict : bool (optional)
            Force `get_matching_atoms` to fail if atoms can't be found using
            exact methods
        force : bool (optional)
            Force overwrite of filename for rmsd-fitting
        start : int (optional)
            First frame of trajectory to analyse, Default: 0
        stop : int (optional)
            Last frame of trajectory to analyse, Default: -1
        step : int (optional)
            Step between frames to analyse, Default: 1
        in_memory : bool (optional)
            *Permanently* switch `mobile` to an in-memory trajectory
            so that alignment can be done in-place, which can improve
            performance substantially in some cases. In this case, no file
            is written out (`filename` and `prefix` are ignored) and only
            the coordinates of `mobile` are *changed in memory*.
        verbose : bool (optional)
             Set logger to show more information and show detailed progress of
             the calculation if set to ``True``; the default is ``False``.


        Attributes
        ----------
        reference_atoms : AtomGroup
            Atoms of the reference structure to be aligned against
        mobile_atoms : AtomGroup
            Atoms inside each trajectory frame to be rmsd_aligned
        rmsd : Array
            Array of the rmsd values of the least rmsd between the mobile_atoms
            and reference_atoms after superposition and minimimization of rmsd
        filename : str
            String reflecting the filename of the file where mobile_atoms
            positions will be written to upon running RMSD alignment


        Notes
        -----
        - If set to ``verbose=False``, it is recommended to wrap the statement
          in a ``try ...  finally`` to guarantee restoring of the log level in
          the case of an exception.
        - The ``in_memory`` option changes the `mobile` universe to an
          in-memory representation (see :mod:`MDAnalysis.coordinates.memory`)
          for the remainder of the Python session. If ``mobile.trajectory`` is
          already a :class:`MemoryReader` then it is *always* treated as if
          ``in_memory`` had been set to ``True``.

        .. deprecated:: 0.19.1
           Default ``filename`` directory will change in 1.0 to the current directory.

        .. versionchanged:: 0.16.0
           new general ``weights`` kwarg replace ``mass_weights``

        .. deprecated:: 0.16.0
           Instead of ``mass_weighted=True`` use new ``weights='mass'``

        .. versionchanged:: 0.17.0
           removed deprecated `mass_weighted` keyword

        """
        select = rms.process_selection(select)
        self.ref_atoms = reference.select_atoms(*select['reference'])
        self.mobile_atoms = mobile.select_atoms(*select['mobile'])
        if in_memory or isinstance(mobile.trajectory, MemoryReader):
            mobile.transfer_to_memory()
            filename = None
            logger.info("Moved mobile trajectory to in-memory representation")
        else:
            if filename is None:
                # DEPRECATED in 0.19.1
                # Change in 1.0
                #
                # fn = os.path.split(mobile.trajectory.filename)[1]
                # filename = prefix + fn
                path, fn = os.path.split(mobile.trajectory.filename)
                filename = os.path.join(path, prefix + fn)
                logger.info('filename of rms_align with no filename given'
                            ': {0}'.format(filename))

            if os.path.exists(filename) and not force:
                raise IOError(
                    'Filename already exists in path and force is not set'
                    ' to True')

        # do this after setting the memory reader to have a reference to the
        # right reader.
        super(AlignTraj, self).__init__(mobile.trajectory, **kwargs)
        if not self._verbose:
            logging.disable(logging.WARN)

        # store reference to mobile atoms
        self.mobile = mobile.atoms

        self.filename = filename

        natoms = self.mobile.n_atoms
        self.ref_atoms, self.mobile_atoms = get_matching_atoms(
            self.ref_atoms, self.mobile_atoms, tol_mass=tol_mass,
            strict=strict)

        # with self.filename == None (in_memory), the NullWriter is chosen
        # (which just ignores input) and so only the in_memory trajectory is
        # retained
        self._writer = mda.Writer(self.filename, natoms)

        self._weights = get_weights(self.ref_atoms, weights)

        logger.info("RMS-fitting on {0:d} atoms.".format(len(self.ref_atoms)))
示例#8
0
def alignto(mobile, reference, select="all", weights=None,
            subselection=None, tol_mass=0.1, strict=False):
    """Perform a spatial superposition by minimizing the RMSD.

    Spatially align the group of atoms `mobile` to `reference` by
    doing a RMSD fit on `select` atoms.

    The superposition is done in the following way:

    1. A rotation matrix is computed that minimizes the RMSD between
       the coordinates of `mobile.select_atoms(sel1)` and
       `reference.select_atoms(sel2)`; before the rotation, `mobile` is
       translated so that its center of geometry (or center of mass)
       coincides with the one of `reference`. (See below for explanation of
       how *sel1* and *sel2* are derived from `select`.)

    2. All atoms in :class:`~MDAnalysis.core.universe.Universe` that
       contain `mobile` are shifted and rotated. (See below for how
       to change this behavior through the `subselection` keyword.)

    The `mobile` and `reference` atom groups can be constructed so that they
    already match atom by atom. In this case, `select` should be set to "all"
    (or ``None``) so that no further selections are applied to `mobile` and
    `reference`, therefore preserving the exact atom ordering (see
    :ref:`ordered-selections-label`).

    .. Warning:: The atom order for `mobile` and `reference` is *only*
       preserved when `select` is either "all" or ``None``. In any other case,
       a new selection will be made that will sort the resulting AtomGroup by
       index and therefore destroy the correspondence between the two groups.
       **It is safest not to mix ordered AtomGroups with selection strings.**

    Parameters
    ----------
    mobile : Universe or AtomGroup
       structure to be aligned, a
       :class:`~MDAnalysis.core.groups.AtomGroup` or a whole
       :class:`~MDAnalysis.core.universe.Universe`
    reference : Universe or AtomGroup
       reference structure, a :class:`~MDAnalysis.core.groups.AtomGroup`
       or a whole :class:`~MDAnalysis.core.universe.Universe`
    select : str or dict or tuple (optional)
       The selection to operate on; can be one of:

       1. any valid selection string for
          :meth:`~MDAnalysis.core.groups.AtomGroup.select_atoms` that
          produces identical selections in `mobile` and `reference`; or

       2. a dictionary ``{'mobile': sel1, 'reference': sel2}`` where *sel1*
          and *sel2* are valid selection strings that are applied to
          `mobile` and `reference` respectively (the
          :func:`MDAnalysis.analysis.align.fasta2select` function returns such
          a dictionary based on a ClustalW_ or STAMP_ sequence alignment); or

       3. a tuple ``(sel1, sel2)``

       When using 2. or 3. with *sel1* and *sel2* then these selection strings
       are applied to `atomgroup` and `reference` respectively and should
       generate *groups of equivalent atoms*.  *sel1* and *sel2* can each also
       be a *list of selection strings* to generate a
       :class:`~MDAnalysis.core.groups.AtomGroup` with defined atom order as
       described under :ref:`ordered-selections-label`).
    weights : {"mass", ``None``} or array_like (optional)
       choose weights. With ``"mass"`` uses masses as weights; with ``None``
       weigh each atom equally. If a float array of the same length as
       `mobile` is provided, use each element of the `array_like` as a
       weight for the corresponding atom in `mobile`.
    tol_mass: float (optional)
       Reject match if the atomic masses for matched atoms differ by more than
       `tol_mass`, default [0.1]
    strict: bool (optional)
       ``True``
           Will raise :exc:`SelectionError` if a single atom does not
           match between the two selections.
       ``False`` [default]
           Will try to prepare a matching selection by dropping
           residues with non-matching atoms. See :func:`get_matching_atoms`
           for details.
    subselection : str or AtomGroup or None (optional)
       Apply the transformation only to this selection.

       ``None`` [default]
           Apply to ``mobile.universe.atoms`` (i.e., all atoms in the
           context of the selection from `mobile` such as the rest of a
           protein, ligands and the surrounding water)
       *selection-string*
           Apply to ``mobile.select_atoms(selection-string)``
       :class:`~MDAnalysis.core.groups.AtomGroup`
           Apply to the arbitrary group of atoms

    Returns
    -------
    old_rmsd : float
        RMSD before spatial alignment
    new_rmsd : float
        RMSD after spatial alignment

    See Also
    --------
    AlignTraj: More efficient method for RMSD-fitting trajectories.


    .. _ClustalW: http://www.clustal.org/
    .. _STAMP: http://www.compbio.dundee.ac.uk/manuals/stamp.4.2/


    .. versionchanged:: 0.8
       Added check that the two groups describe the same atoms including
       the new *tol_mass* keyword.

    .. versionchanged:: 0.10.0
       Uses :func:`get_matching_atoms` to work with incomplete selections
       and new `strict` keyword. The new default is to be lenient whereas
       the old behavior was the equivalent of ``strict = True``.

    .. versionchanged:: 0.16.0
       new general 'weights' kwarg replace `mass_weighted`, deprecated `mass_weighted`
    .. deprecated:: 0.16.0
       Instead of ``mass_weighted=True`` use new ``weights='mass'``

    .. versionchanged:: 0.17.0
       Deprecated keyword `mass_weighted` was removed.
    """
    if select in ('all', None):
        # keep the EXACT order in the input AtomGroups; select_atoms('all')
        # orders them by index, which can lead to wrong results if the user
        # has crafted mobile and reference to match atom by atom
        mobile_atoms = mobile.atoms
        ref_atoms = reference.atoms
    else:
        select = rms.process_selection(select)
        mobile_atoms = mobile.select_atoms(*select['mobile'])
        ref_atoms = reference.select_atoms(*select['reference'])

    ref_atoms, mobile_atoms = get_matching_atoms(ref_atoms, mobile_atoms,
                                                 tol_mass=tol_mass,
                                                 strict=strict)

    weights = get_weights(ref_atoms, weights)

    mobile_com = mobile_atoms.center(weights)
    ref_com = ref_atoms.center(weights)

    ref_coordinates = ref_atoms.positions - ref_com
    mobile_coordinates = mobile_atoms.positions - mobile_com

    old_rmsd = rms.rmsd(mobile_coordinates, ref_coordinates, weights)

    if subselection is None:
        # mobile_atoms is Universe
        mobile_atoms = mobile.universe.atoms
    elif isinstance(subselection, string_types):
        # select mobile_atoms from string
        mobile_atoms = mobile.select_atoms(subselection)
    else:
        try:
            # treat subselection as AtomGroup
            mobile_atoms = subselection.atoms
        except AttributeError:
            raise TypeError("subselection must be a selection string, an"
                            " AtomGroup or Universe or None")

    # _fit_to DOES subtract center of mass, will provide proper min_rmsd
    mobile_atoms, new_rmsd = _fit_to(mobile_coordinates, ref_coordinates,
                                     mobile_atoms, mobile_com, ref_com,
                                     weights=weights)
    return old_rmsd, new_rmsd
示例#9
0
    def __init__(self, atomgroup, reference=None, select='all',
                 groupselections=None, filename="rmsd.dat",
                 weights=None, tol_mass=0.1, ref_frame=0, **kwargs):
        # DEPRECATION: remove filename kwarg in 1.0
        r"""Parameters
        ----------
        atomgroup : AtomGroup or Universe
            Group of atoms for which the RMSD is calculated. If a trajectory is
            associated with the atoms then the computation iterates over the
            trajectory.
        reference : AtomGroup or Universe (optional)
            Group of reference atoms; if ``None`` then the current frame of
            `atomgroup` is used.
        select : str or dict or tuple (optional)
            The selection to operate on; can be one of:

            1. any valid selection string for
               :meth:`~MDAnalysis.core.groups.AtomGroup.select_atoms` that
               produces identical selections in `atomgroup` and `reference`; or

            2. a dictionary ``{'mobile': sel1, 'reference': sel2}`` where *sel1*
               and *sel2* are valid selection strings that are applied to
               `atomgroup` and `reference` respectively (the
               :func:`MDAnalysis.analysis.align.fasta2select` function returns such
               a dictionary based on a ClustalW_ or STAMP_ sequence alignment); or

            3. a tuple ``(sel1, sel2)``

            When using 2. or 3. with *sel1* and *sel2* then these selection strings
            are applied to `atomgroup` and `reference` respectively and should
            generate *groups of equivalent atoms*.  *sel1* and *sel2* can each also
            be a *list of selection strings* to generate a
            :class:`~MDAnalysis.core.groups.AtomGroup` with defined atom order as
            described under :ref:`ordered-selections-label`).

        groupselections : list (optional)
            A list of selections as described for `select`, with the difference
            that these selections are *always applied to the full universes*,
            i.e., ``atomgroup.universe.select_atoms(sel1)`` and
            ``reference.universe.select_atoms(sel2)``. Each selection describes
            additional RMSDs to be computed *after the structures have been
            superimposed* according to `select`. No additional fitting is
            performed.The output contains one additional column for each
            selection.

            .. Note:: Experimental feature. Only limited error checking
                      implemented.
        filename : str (optional)
            write RMSD into file with :meth:`RMSD.save`

            .. deprecated:; 0.19.0
               `filename` will be removed together with :meth:`save` in 1.0.

        weights : {"mass", ``None``} or array_like (optional)
             choose weights. With ``"mass"`` uses masses as weights; with ``None``
             weigh each atom equally. If a float array of the same length as
             `atomgroup` is provided, use each element of the `array_like` as a
             weight for the corresponding atom in `atomgroup`.
        tol_mass : float (optional)
             Reject match if the atomic masses for matched atoms differ by more
             than `tol_mass`.
        ref_frame : int (optional)
             frame index to select frame from `reference`
        verbose : bool (optional)
             Show detailed progress of the calculation if set to ``True``; the
             default is ``False``.

        Raises
        ------
        SelectionError
             If the selections from `atomgroup` and `reference` do not match.
        TypeError
             If `weights` is not of the appropriate type; see also
             :func:`MDAnalysis.lib.util.get_weights`
        ValueError
             If `weights` are not compatible with `atomgroup` (not the same
             length) or if it is not a 1D array (see
             :func:`MDAnalysis.lib.util.get_weights`).

             A :exc:`ValueError` is also raised if `weights` are not compatible
             with `groupselections`: only equal weights (``weights=None``) or
             mass-weighted (``weights="mass"``) are supported for additional
             `groupselections`.

        Notes
        -----
        The root mean square deviation :math:`\rho(t)` of a group of :math:`N`
        atoms relative to a reference structure as a function of time is
        calculated as

        .. math::

           \rho(t) = \sqrt{\frac{1}{N} \sum_{i=1}^N w_i \left(\mathbf{x}_i(t)
                                    - \mathbf{x}_i^{\text{ref}}\right)^2}

        The weights :math:`w_i` are calculated from the input weights `weights`
        :math:`w'_i` as relative to the mean of the input weights:

        .. math::

           w_i = \frac{w'_i}{\langle w' \rangle}

        The selected coordinates from `atomgroup` are optimally superimposed
        (translation and rotation) on the `reference` coordinates at each time step
        as to minimize the RMSD. Douglas Theobald's fast QCP algorithm
        [Theobald2005]_ is used for the rotational superposition and to calculate
        the RMSD (see :mod:`MDAnalysis.lib.qcprot` for implementation details).

        The class runs various checks on the input to ensure that the two atom
        groups can be compared. This includes a comparison of atom masses (i.e.,
        only the positions of atoms of the same mass will be considered to be
        correct for comparison). If masses should not be checked, just set
        `tol_mass` to a large value such as 1000.

        .. _ClustalW: http://www.clustal.org/
        .. _STAMP: http://www.compbio.dundee.ac.uk/manuals/stamp.4.2/


        See Also
        --------
        rmsd


        .. versionadded:: 0.7.7
        .. versionchanged:: 0.8
           `groupselections` added
        .. versionchanged:: 0.16.0
           Flexible weighting scheme with new `weights` keyword.
        .. deprecated:: 0.16.0
           Instead of ``mass_weighted=True`` (removal in 0.17.0) use new
           ``weights='mass'``; refactored to fit with AnalysisBase API
        .. versionchanged:: 0.17.0
           removed deprecated `mass_weighted` keyword; `groupselections`
           are *not* rotationally superimposed any more.
        .. deprecated:: 0.19.0
           `filename` will be removed in 1.0

        """
        super(RMSD, self).__init__(atomgroup.universe.trajectory,
                                   **kwargs)
        self.atomgroup = atomgroup
        self.reference = reference if reference is not None else self.atomgroup

        select = process_selection(select)
        self.groupselections = ([process_selection(s) for s in groupselections]
                                if groupselections is not None else [])
        self.weights = weights
        self.tol_mass = tol_mass
        self.ref_frame = ref_frame
        self.filename = filename   # DEPRECATED in 0.19.0, remove in 1.0.0

        self.ref_atoms = self.reference.select_atoms(*select['reference'])
        self.mobile_atoms = self.atomgroup.select_atoms(*select['mobile'])

        if len(self.ref_atoms) != len(self.mobile_atoms):
            err = ("Reference and trajectory atom selections do "
                   "not contain the same number of atoms: "
                   "N_ref={0:d}, N_traj={1:d}".format(self.ref_atoms.n_atoms,
                                                      self.mobile_atoms.n_atoms))
            logger.exception(err)
            raise SelectionError(err)
        logger.info("RMS calculation "
                    "for {0:d} atoms.".format(len(self.ref_atoms)))
        mass_mismatches = (np.absolute((self.ref_atoms.masses -
                                        self.mobile_atoms.masses)) >
                           self.tol_mass)

        if np.any(mass_mismatches):
            # diagnostic output:
            logger.error("Atoms: reference | mobile")
            for ar, at in zip(self.ref_atoms, self.mobile_atoms):
                if ar.name != at.name:
                    logger.error("{0!s:>4} {1:3d} {2!s:>3} {3!s:>3} {4:6.3f}"
                                 "|  {5!s:>4} {6:3d} {7!s:>3} {8!s:>3}"
                                 "{9:6.3f}".format(ar.segid, ar.resid,
                                                   ar.resname, ar.name,
                                                   ar.mass, at.segid, at.resid,
                                                   at.resname, at.name,
                                                   at.mass))
            errmsg = ("Inconsistent selections, masses differ by more than"
                      "{0:f}; mis-matching atoms"
                      "are shown above.".format(self.tol_mass))
            logger.error(errmsg)
            raise SelectionError(errmsg)
        del mass_mismatches

        # TODO:
        # - make a group comparison a class that contains the checks above
        # - use this class for the *select* group and the additional
        #   *groupselections* groups each a dict with reference/mobile
        self._groupselections_atoms = [
            {
                'reference': self.reference.universe.select_atoms(*s['reference']),
                'mobile': self.atomgroup.universe.select_atoms(*s['mobile']),
            }
            for s in self.groupselections]
        # sanity check
        for igroup, (sel, atoms) in enumerate(zip(self.groupselections,
                                                  self._groupselections_atoms)):
            if len(atoms['mobile']) != len(atoms['reference']):
                logger.exception('SelectionError: Group Selection')
                raise SelectionError(
                    "Group selection {0}: {1} | {2}: Reference and trajectory "
                    "atom selections do not contain the same number of atoms: "
                    "N_ref={3}, N_traj={4}".format(
                        igroup, sel['reference'], sel['mobile'],
                        len(atoms['reference']), len(atoms['mobile'])))

        # Explicitly check for "mass" because this option CAN
        # be used with groupselection. (get_weights() returns the mass array
        # for "mass")
        if not iterable(self.weights) and self.weights == "mass":
            pass
        else:
            self.weights = get_weights(self.mobile_atoms, self.weights)

        # cannot use arbitrary weight array (for superposition) with
        # groupselections because arrays will not match
        if (len(self.groupselections) > 0 and (
                iterable(self.weights) or self.weights not in ("mass", None))):
            raise ValueError("groupselections can only be combined with "
                             "weights=None or weights='mass', not a weight "
                             "array.")

        # initialized to note for testing the save function
        self.rmsd = None