def test_check_weights_raises_ValueError(atoms, weights): with pytest.raises(ValueError): util.get_weights(atoms, weights)
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
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)))
def test_check_weights_ok(atoms, weights, result): assert_array_equal(util.get_weights(atoms, weights), result)
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
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)
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)))
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