Exemplo n.º 1
0
def test_CalcRMSDRotationalMatrix():
    # Setup coordinates
    frag_a = np.zeros((3, 7), dtype=np.float64)
    frag_b = np.zeros((3, 7), dtype=np.float64)
    N = 7

    frag_a[0][0] = -2.803
    frag_a[1][0] = -15.373
    frag_a[2][0] = 24.556
    frag_a[0][1] = 0.893
    frag_a[1][1] = -16.062
    frag_a[2][1] = 25.147
    frag_a[0][2] = 1.368
    frag_a[1][2] = -12.371
    frag_a[2][2] = 25.885
    frag_a[0][3] = -1.651
    frag_a[1][3] = -12.153
    frag_a[2][3] = 28.177
    frag_a[0][4] = -0.440
    frag_a[1][4] = -15.218
    frag_a[2][4] = 30.068
    frag_a[0][5] = 2.551
    frag_a[1][5] = -13.273
    frag_a[2][5] = 31.372
    frag_a[0][6] = 0.105
    frag_a[1][6] = -11.330
    frag_a[2][6] = 33.567

    frag_b[0][0] = -14.739
    frag_b[1][0] = -18.673
    frag_b[2][0] = 15.040
    frag_b[0][1] = -12.473
    frag_b[1][1] = -15.810
    frag_b[2][1] = 16.074
    frag_b[0][2] = -14.802
    frag_b[1][2] = -13.307
    frag_b[2][2] = 14.408
    frag_b[0][3] = -17.782
    frag_b[1][3] = -14.852
    frag_b[2][3] = 16.171
    frag_b[0][4] = -16.124
    frag_b[1][4] = -14.617
    frag_b[2][4] = 19.584
    frag_b[0][5] = -15.029
    frag_b[1][5] = -11.037
    frag_b[2][5] = 18.902
    frag_b[0][6] = -18.577
    frag_b[1][6] = -10.001
    frag_b[2][6] = 17.996

    # Allocate rotation array
    rot = np.zeros((9, ), dtype=np.float64)

    # Calculate center of geometry
    comA = np.sum(frag_a, axis=1) / N
    comB = np.sum(frag_b, axis=1) / N

    # Center each fragment
    frag_a = frag_a - comA.reshape(3, 1)
    frag_b = frag_b - comB.reshape(3, 1)

    # Calculate rmsd and rotation matrix
    qcp_rmsd = qcp.CalcRMSDRotationalMatrix(frag_a.T, frag_b.T, N, rot, None)

    #print 'qcp rmsd = ',rmsd
    #print 'rotation matrix:'
    #print rot.reshape((3,3))

    # rotate frag_b to obtain optimal alignment
    frag_br = frag_b.T * np.matrix(rot.reshape((3, 3)))
    aligned_rmsd = rmsd(frag_br.T, frag_a)
    #print 'rmsd after applying rotation: ',rmsd

    assert_almost_equal(
        aligned_rmsd, 0.719106, 6,
        "RMSD between fragments A and B does not match excpected value.")

    expected_rot = np.array([[0.72216358, -0.52038257, -0.45572112],
                             [0.69118937, 0.51700833, 0.50493528],
                             [-0.0271479, -0.67963547, 0.73304748]])
    assert_almost_equal(
        rot.reshape((3, 3)), expected_rot, 6,
        "Rotation matrix for aliging B to A does not have expected values.")
Exemplo n.º 2
0
def rmsd(a, b, weights=None, center=False, superposition=False):
    """Returns RMSD between two coordinate sets `a` and `b`.

    `a` and `b` are arrays of the coordinates of N atoms of shape N*3
    as generated by, e.g.,
    :meth:`MDAnalysis.core.AtomGroup.AtomGroup.coordinates`.

    Parameters
    ----------
    a, b : array_like
        coordinates to align
    weights : array_like (optional)
        1D array with weights, use to compute weighted average
    center : bool (optional)
        subtract center of geometry before calculation. With weights given
        compute weighted average as center.
    superposition : bool (optional)
        perform a rotational and translational superposition with the fast QCP
        algorithm [Theobald2005]_ before calculating the RMSD

    Returns
    -------
    rmsd : float
        RMSD between a and b

    Example
    -------
    >>> u = Universe(PSF,DCD)
    >>> bb = u.select_atoms('backbone')
    >>> A = bb.positions.copy()  # coordinates of first frame
    >>> u.trajectory[-1]         # forward to last frame
    >>> B = bb.positions.copy()  # coordinates of last frame
    >>> rmsd(A, B, center=True)
    3.9482355416565049

    .. versionchanged: 0.8.1
       *center* keyword added
    .. versionchanged: 0.14.0
       *superposition* keyword added

    """
    a = np.asarray(a, dtype=np.float64)
    b = np.asarray(b, dtype=np.float64)
    N = b.shape[0]

    if a.shape != b.shape:
        raise ValueError('a and b must have same shape')

    if weights is not None:
        if len(weights) != len(a):
            raise ValueError('weights must have same length as a/b')
        # weights are constructed as relative to the mean
        relative_weights = np.asarray(weights) / np.mean(weights)
    else:
        relative_weights = None

    # superposition only works if structures are centered
    if center or superposition:
        # make copies (do not change the user data!)
        # weights=None is equivalent to all weights 1
        a = a - np.average(a, axis=0, weights=weights)
        b = b - np.average(b, axis=0, weights=weights)

    if superposition:
        return qcp.CalcRMSDRotationalMatrix(a.T, b.T, N, None,
                                            relative_weights)
    else:
        return np.sqrt(np.sum((a - b)**2) / a.size)
Exemplo n.º 3
0
    def run(self, **kwargs):
        """Perform RMSD analysis on the trajectory.

        A number of parameters can be changed from the defaults. The
        result is stored as the array :attr:`RMSD.rmsd`.

        :Keywords:
          *start*, *stop*, *step*
             start and stop frame index with step size: analyse
             ``trajectory[start:stop:step]`` [``None``]
          *mass_weighted*
             do a mass-weighted RMSD fit
          *tol_mass*
             Reject match if the atomic masses for matched atoms differ by more than
             *tol_mass*
          *ref_frame*
             frame index to select frame from *reference*

        """
        from itertools import izip

        start = kwargs.pop('start', None)
        stop = kwargs.pop('stop', None)
        step = kwargs.pop('step', None)
        mass_weighted = kwargs.pop('mass_weighted', self.mass_weighted)
        ref_frame = kwargs.pop('ref_frame', self.ref_frame)

        natoms = self.traj_atoms.n_atoms
        trajectory = self.universe.trajectory
        traj_atoms = self.traj_atoms

        if mass_weighted:
            # if performing a mass-weighted alignment/rmsd calculation
            weight = self.ref_atoms.masses / self.ref_atoms.masses.mean()
        else:
            weight = None

        # reference centre of mass system
        current_frame = self.reference.trajectory.ts.frame - 1
        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.trajectory[ref_frame]
            ref_com = self.ref_atoms.center_of_mass()
            ref_coordinates = self.ref_atoms.positions - ref_com  # makes a copy
            if self.groupselections_atoms:
                groupselections_ref_coords_T_64 = [
                    self.reference.select_atoms(
                        *s['reference']).positions.T.astype(np.float64)
                    for s in self.groupselections
                ]
        finally:
            # Move back to the original frame
            self.reference.trajectory[current_frame]
        ref_coordinates_T_64 = ref_coordinates.T.astype(np.float64)

        # allocate the array for selection atom coords
        traj_coordinates = traj_atoms.coordinates().copy()

        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
            rot = np.zeros(9,
                           dtype=np.float64)  # allocate space for calculation
            R = np.matrix(rot.reshape(3, 3))
        else:
            rot = None

        # RMSD timeseries
        nframes = len(np.arange(0, len(trajectory))[start:stop:step])
        rmsd = np.zeros((nframes, 3 + len(self.groupselections_atoms)))

        percentage = ProgressMeter(
            nframes,
            interval=10,
            format=
            "RMSD %(rmsd)5.2f A at frame %(step)5d/%(numsteps)d  [%(percentage)5.1f%%]\r"
        )

        for k, ts in enumerate(trajectory[start:stop:step]):
            # shift coordinates for rotation fitting
            # selection is updated with the time frame
            x_com = traj_atoms.center_of_mass().astype(np.float32)
            traj_coordinates[:] = traj_atoms.coordinates() - x_com

            rmsd[k, :2] = ts.frame, trajectory.time

            if self.groupselections_atoms:
                # 1) superposition structures
                # Need to transpose coordinates such that the coordinate array is
                # 3xN instead of Nx3. Also qcp requires that the dtype be float64
                # (I think we swapped the position of ref and traj in CalcRMSDRotationalMatrix
                # so that R acts **to the left** and can be broadcasted; we're saving
                # one transpose. [orbeckst])
                rmsd[k, 2] = qcp.CalcRMSDRotationalMatrix(
                    ref_coordinates_T_64,
                    traj_coordinates.T.astype(np.float64), natoms, rot, weight)
                R[:, :] = rot.reshape(3, 3)

                # Transform each atom in the trajectory (use inplace ops to avoid copying arrays)
                # (Marginally (~3%) faster than "ts.positions[:] = (ts.positions - x_com) * R + ref_com".)
                ts.positions -= x_com
                ts.positions[:] = ts.positions * R  # R acts to the left & is broadcasted N times.
                ts.positions += ref_com

                # 2) calculate secondary RMSDs
                for igroup, (refpos, atoms) in enumerate(
                        izip(groupselections_ref_coords_T_64,
                             self.groupselections_atoms), 3):
                    rmsd[k, igroup] = qcp.CalcRMSDRotationalMatrix(
                        refpos, atoms['mobile'].positions.T.astype(np.float64),
                        atoms['mobile'].n_atoms, None, weight)
            else:
                # only calculate RMSD by setting the Rmatrix to None
                # (no need to carry out the rotation as we already get the optimum RMSD)
                rmsd[k,
                     2] = qcp.CalcRMSDRotationalMatrix(
                         ref_coordinates_T_64,
                         traj_coordinates.T.astype(np.float64), natoms, None,
                         weight)

            percentage.echo(ts.frame, rmsd=rmsd[k, 2])
        self.rmsd = rmsd
Exemplo n.º 4
0
def rms_fit_trj(traj,
                reference,
                select='all',
                filename=None,
                rmsdfile=None,
                prefix='rmsfit_',
                mass_weighted=False,
                tol_mass=0.1,
                strict=False,
                force=True,
                quiet=False,
                **kwargs):
    """RMS-fit trajectory to a reference structure using a selection.

    Both reference *ref* and trajectory *traj* must be
    :class:`MDAnalysis.Universe` instances. If they contain a
    trajectory then it is used. The output file format is determined
    by the file extension of *filename*. One can also use the same
    universe if one wants to fit to the current frame.

    :Arguments:
      *traj*
         trajectory, :class:`MDAnalysis.Universe` object
      *reference*
         reference coordinates; :class:`MDAnalysis.Universe` object
         (uses the current time step of the object)
      *select*
         1. any valid selection string for
            :meth:`~MDAnalysis.core.AtomGroup.AtomGroup.select_atoms` that produces identical
            selections in *mobile* and *reference*; or
         2. a dictionary ``{'mobile':sel1, 'reference':sel2}`` (the
            :func:`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 selections can also each be
         a list of selection strings (to generate a AtomGroup with defined atom order as
         described under :ref:`ordered-selections-label`).
      *filename*
         file name for the RMS-fitted trajectory or pdb; defaults to the
         original trajectory filename (from *traj*) with *prefix* prepended
      *rmsdfile*
         file name for writing the RMSD timeseries [``None``]
      *prefix*
         prefix for autogenerating the new output filename
      *mass_weighted*
         do a mass-weighted RMSD fit
      *tol_mass*
         Reject match if the atomic masses for matched atoms differ by more than
         *tol_mass* [0.1]
      *strict*
         Default: ``False``
         - ``True``: Will raise :exc:`SelectioError` if a single atom does not
           match between the two selections.
         - ``False``: Will try to prepare a matching selection by dropping
           residues with non-matching atoms. See :func:`get_matching_atoms`
           for details.
      *force*
         - ``True``: Overwrite an existing output trajectory (default)
         - ``False``: simply return if the file already exists
      *quiet*
         - ``True``: suppress progress and logging for levels INFO and below.
         - ``False``: show all status messages and do not change the the logging
           level (default)

         .. Note:: If


      *kwargs*
         All other keyword arguments are passed on the trajectory
         :class:`~MDAnalysis.coordinates.base.Writer`; this allows manipulating/fixing
         trajectories on the fly (e.g. change the output format by changing the extension of *filename*
         and setting different parameters as described for the corresponding writer).

    :Returns: *filename* (either provided or auto-generated)

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

    .. versionchanged:: 0.8
       Added *kwargs* to be passed to the trajectory :class:`~MDAnalysis.coordinates.base.Writer` and
       *filename* is returned.

    .. 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``.

    """
    frames = traj.trajectory
    if quiet:
        # should be part of a try ... finally to guarantee restoring the log level
        logging.disable(logging.WARN)

    kwargs.setdefault('remarks', 'RMS fitted trajectory to reference')
    if filename is None:
        path, fn = os.path.split(frames.filename)
        filename = os.path.join(path, prefix + fn)
        _Writer = frames.Writer
    else:
        _Writer = frames.OtherWriter
    if os.path.exists(filename) and not force:
        logger.warn(
            "{0} already exists and will NOT be overwritten; use force=True if you want this"
            .format(filename))
        return filename
    writer = _Writer(filename, **kwargs)
    del _Writer

    select = rms._process_selection(select)
    ref_atoms = reference.select_atoms(*select['reference'])
    traj_atoms = traj.select_atoms(*select['mobile'])
    natoms = traj_atoms.n_atoms

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

    logger.info("RMS-fitting on {0:d} atoms.".format(len(ref_atoms)))
    if mass_weighted:
        # if performing a mass-weighted alignment/rmsd calculation
        weight = ref_atoms.masses / ref_atoms.masses.mean()
    else:
        weight = None

    # reference centre of mass system
    ref_com = ref_atoms.center_of_mass()
    ref_coordinates = ref_atoms.coordinates() - ref_com

    # allocate the array for selection atom coords
    traj_coordinates = traj_atoms.coordinates().copy()

    # RMSD timeseries
    nframes = len(frames)
    rmsd = np.zeros((nframes, ))

    # 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
    rot = np.zeros(9, dtype=np.float64)  # allocate space for calculation
    R = np.matrix(rot.reshape(3, 3))

    percentage = ProgressMeter(
        nframes,
        interval=10,
        quiet=quiet,
        format="Fitted frame %(step)5d/%(numsteps)d  [%(percentage)5.1f%%]\r")

    for k, ts in enumerate(frames):
        # shift coordinates for rotation fitting
        # selection is updated with the time frame
        x_com = traj_atoms.center_of_mass().astype(np.float32)
        traj_coordinates[:] = traj_atoms.coordinates() - x_com

        # Need to transpose coordinates such that the coordinate array is
        # 3xN instead of Nx3. Also qcp requires that the dtype be float64
        # (I think we swapped the position of ref and traj in CalcRMSDRotationalMatrix
        # so that R acts **to the left** and can be broadcasted; we're saving
        # one transpose. [orbeckst])
        rmsd[k] = qcp.CalcRMSDRotationalMatrix(
            ref_coordinates.T.astype(np.float64),
            traj_coordinates.T.astype(np.float64), natoms, rot, weight)
        R[:, :] = rot.reshape(3, 3)

        # Transform each atom in the trajectory (use inplace ops to avoid copying arrays)
        # (Marginally (~3%) faster than "ts.positions[:] = (ts.positions - x_com) * R + ref_com".)
        ts.positions -= x_com
        ts.positions[:] = ts.positions * R  # R acts to the left & is broadcasted N times.
        ts.positions += ref_com

        writer.write(traj.atoms)  # write whole input trajectory system
        percentage.echo(ts.frame)
    logger.info("Wrote %d RMS-fitted coordinate frames to file %r",
                frames.n_frames, filename)
    if rmsdfile is not None:
        np.savetxt(rmsdfile, rmsd)
        logger.info("Wrote RMSD timeseries  to file %r", rmsdfile)

    if quiet:
        # should be part of a try ... finally to guarantee restoring the log level
        logging.disable(logging.NOTSET)

    return filename
Exemplo n.º 5
0
def rotation_matrix(a, b, weights=None):
    r"""Returns the 3x3 rotation matrix for RMSD fitting coordinate sets *a* and
    *b*.

    The rotation matrix *R* transforms *a* to overlap with *b* (i.e. *b* is the
    reference structure):

    .. math::
        \vec{b} = \bold{R} \dot \vec{a}

    Parameters
    ----------
    a : array-like
          coordinates that are to be rotated ("mobile set"); array of N atoms
          of shape N*3 as generated by, e.g.,
          :meth:`MDAnalysis.core.AtomGroup.AtomGroup.coordinates`.
    b : array-like
          reference coordinates; array of N atoms of shape N*3 as generated by,
          e.g., :meth:`MDAnalysis.core.AtomGroup.AtomGroup.coordinates`.
    weights : array-like (optional)
          array of floats of size N for doing weighted RMSD fitting (e.g. the
          masses of the atoms)

    Returns
    -------
    R : ndarray
        rotation matrix
    rmsd : float
        RMSD between *a* and *b* before rotation
    ``(R, rmsd)`` rmsd and rotation matrix *R*

    Example
    -------
    *R* can be used as an argument for
    :meth:`MDAnalysis.core.AtomGroup.AtomGroup.rotate` to generate a rotated
    selection, e.g. ::

    >>> R = rotation_matrix(A.select_atoms('backbone').coordinates(),
    >>>                     B.select_atoms('backbone').coordinates())[0]
    >>> A.atoms.rotate(R)
    >>> A.atoms.write("rotated.pdb")

    Notes
    -----
    The function does *not* shift the centers of mass or geometry;
    this needs to be done by the user.

    See Also
    --------
    :func:`rmsd` calculates the RMSD between *a* and *b*; for fitting a whole
    trajectory it is more efficient to use :func:`rms_fit_trj`. A complete fit
    of two structures can be done with :func:`alignto`. """
    if weights is not None:
        # weights are constructed as relative to the mean
        weights = np.asarray(weights, dtype=np.float64) / np.mean(weights)

    a = np.asarray(a, dtype=np.float64)
    b = np.asarray(b, dtype=np.float64)
    N = b.shape[0]

    rot = np.zeros(9, dtype=np.float64)
    rmsd = qcp.CalcRMSDRotationalMatrix(a.T, b.T, N, rot, weights)
    return np.matrix(rot.reshape(3, 3)), rmsd
Exemplo n.º 6
0
def rmsd(a, b, weights=None, center=False, superposition=False):
    r"""Returns RMSD between two coordinate sets `a` and `b`.

    `a` and `b` are arrays of the coordinates of N atoms of shape
    :math:`N times 3` as generated by, e.g.,
    :meth:`MDAnalysis.core.groups.AtomGroup.positions`.

    Note
    ----
    If you use trajectory data from simulations performed under **periodic
    boundary conditions** then you *must make your molecules whole* before
    performing RMSD calculations so that the centers of mass of the mobile and
    reference structure are properly superimposed.


    Parameters
    ----------
    a : array_like
        coordinates to align to `b`
    b : array_like
        coordinates to align to (same shape as `a`)
    weights : array_like (optional)
        1D array with weights, use to compute weighted average
    center : bool (optional)
        subtract center of geometry before calculation. With weights given
        compute weighted average as center.
    superposition : bool (optional)
        perform a rotational and translational superposition with the fast QCP
        algorithm [Theobald2005]_ before calculating the RMSD; implies
        ``center=True``.

    Returns
    -------
    rmsd : float
        RMSD between `a` and `b`

    Notes
    -----
    The RMSD :math:`\rho(t)` 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}

    It is the Euclidean distance in configuration space of the current
    configuration (possibly after optimal translation and rotation) from a
    reference configuration divided by :math:`1/\sqrt{N}` where :math:`N` is
    the number of coordinates.

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

    .. math::

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


    Example
    -------
    >>> u = Universe(PSF,DCD)
    >>> bb = u.select_atoms('backbone')
    >>> A = bb.positions.copy()  # coordinates of first frame
    >>> u.trajectory[-1]         # forward to last frame
    >>> B = bb.positions.copy()  # coordinates of last frame
    >>> rmsd(A, B, center=True)
    3.9482355416565049

    .. versionchanged: 0.8.1
       *center* keyword added
    .. versionchanged: 0.14.0
       *superposition* keyword added

    """

    a = np.asarray(a, dtype=np.float64)
    b = np.asarray(b, dtype=np.float64)
    N = b.shape[0]

    if a.shape != b.shape:
        raise ValueError('a and b must have same shape')

    # superposition only works if structures are centered
    if center or superposition:
        # make copies (do not change the user data!)
        # weights=None is equivalent to all weights 1
        a = a - np.average(a, axis=0, weights=weights)
        b = b - np.average(b, axis=0, weights=weights)

    if weights is not None:
        if len(weights) != len(a):
            raise ValueError('weights must have same length as a and b')
        # weights are constructed as relative to the mean
        weights = np.asarray(weights, dtype=np.float64) / np.mean(weights)

    if superposition:
        return qcp.CalcRMSDRotationalMatrix(a, b, N, None, weights)
    else:
        if weights is not None:
            return np.sqrt(np.sum(weights[:, np.newaxis] * ((a - b)**2)) / N)
        else:
            return np.sqrt(np.sum((a - b)**2) / N)
Exemplo n.º 7
0
ref_coordinates = ref_atoms.coordinates() - ref_com

# allocate the array for selection atom coords
traj_coordinates = traj_atoms.coordinates().copy()

# 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
for k, ts in enumerate(frames):
    # shift coordinates for rotation fitting
    # selection is updated with the time frame
    x_com = traj_atoms.center_of_mass()
    traj_coordinates[:] = traj_atoms.coordinates() - x_com
    R = np.zeros((9,), dtype=np.float64)
    # Need to transpose coordinates such that the coordinate array is
    # 3xN instead of Nx3. Also qcp requires that the dtype be float64
    a = ref_coordinates.T.astype('float64')
    b = traj_coordinates.T.astype('float64')
    rmsd[k] = qcp.CalcRMSDRotationalMatrix(a, b, natoms, R, None)

    R = np.matrix(R.reshape(3, 3))

    # Transform each atom in the trajectory (use inplace ops to avoid copying
    # arrays)
    ts._pos -= x_com
    ts._pos[:] = ts._pos * R  # R acts to the left & is broadcasted N times.
    ts._pos += ref_com
    writer.write(traj.atoms)  # write whole input trajectory system

np.savetxt('rmsd.out', rmsd)
Exemplo n.º 8
0
def rotation_matrix(a, b, weights=None):
    r"""Returns the 3x3 rotation matrix `R` for RMSD fitting coordinate
    sets `a` and `b`.

    The rotation matrix `R` transforms vector `a` to overlap with
    vector `b` (i.e., `b` is the reference structure):

    .. math::
       \mathbf{b} = \mathsf{R} \cdot \mathbf{a}

    Parameters
    ----------
    a : array_like
          coordinates that are to be rotated ("mobile set"); array of N atoms
          of shape N*3 as generated by, e.g.,
          :attr:`MDAnalysis.core.groups.AtomGroup.positions`.
    b : array_like
          reference coordinates; array of N atoms of shape N*3 as generated by,
          e.g., :attr:`MDAnalysis.core.groups.AtomGroup.positions`.
    weights : array_like (optional)
          array of floats of size N for doing weighted RMSD fitting (e.g. the
          masses of the atoms)

    Returns
    -------
    R : ndarray
        rotation matrix
    rmsd : float
        RMSD between `a` and `b` before rotation
    ``(R, rmsd)`` rmsd and rotation matrix *R*

    Example
    -------
    `R` can be used as an argument for
    :meth:`MDAnalysis.core.groups.AtomGroup.rotate` to generate a rotated
    selection, e.g. ::

    >>> R = rotation_matrix(A.select_atoms('backbone').positions,
    >>>                     B.select_atoms('backbone').positions)[0]
    >>> A.atoms.rotate(R)
    >>> A.atoms.write("rotated.pdb")

    Notes
    -----
    The function does *not* shift the centers of mass or geometry;
    this needs to be done by the user.

    See Also
    --------
    MDAnalysis.analysis.rms.rmsd: Calculates the RMSD between *a* and *b*.
    alignto: A complete fit of two structures.
    AlignTraj: Fit a whole trajectory.
    """

    a = np.asarray(a, dtype=np.float64)
    b = np.asarray(b, dtype=np.float64)

    if a.shape != b.shape:
        raise ValueError("'a' and 'b' must have same shape")

    N = b.shape[0]

    if weights is not None:
        # qcp does NOT divide weights relative to the mean
        weights = np.asarray(weights, dtype=np.float64) / np.mean(weights)

    rot = np.zeros(9, dtype=np.float64)

    # Need to transpose coordinates such that the coordinate array is
    # 3xN instead of Nx3. Also qcp requires that the dtype be float64
    # (I think we swapped the position of ref and traj in CalcRMSDRotationalMatrix
    # so that R acts **to the left** and can be broadcasted; we're saving
    # one transpose. [orbeckst])
    rmsd = qcp.CalcRMSDRotationalMatrix(a, b, N, rot, weights)
    return np.matrix(rot.reshape(3, 3)), rmsd