LCOV - code coverage report
Current view: top level - dimred - ClassicalMultiDimensionalScaling.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 21 21 100.0 %
Date: 2024-10-11 08:09:47 Functions: 6 7 85.7 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2015-2023 The plumed team
       3             :    (see the PEOPLE file at the root of the distribution for a list of names)
       4             : 
       5             :    See http://www.plumed.org for more information.
       6             : 
       7             :    This file is part of plumed, version 2.
       8             : 
       9             :    plumed is free software: you can redistribute it and/or modify
      10             :    it under the terms of the GNU Lesser General Public License as published by
      11             :    the Free Software Foundation, either version 3 of the License, or
      12             :    (at your option) any later version.
      13             : 
      14             :    plumed is distributed in the hope that it will be useful,
      15             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      16             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17             :    GNU Lesser General Public License for more details.
      18             : 
      19             :    You should have received a copy of the GNU Lesser General Public License
      20             :    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : #include "DimensionalityReductionBase.h"
      23             : #include "core/ActionRegister.h"
      24             : 
      25             : //+PLUMEDOC DIMRED CLASSICAL_MDS
      26             : /*
      27             : Create a low-dimensional projection of a trajectory using the classical multidimensional
      28             :  scaling algorithm.
      29             : 
      30             : Multidimensional scaling (MDS) is similar to what is done when you make a map. You start with distances
      31             : between London, Belfast, Paris and Dublin and then you try to arrange points on a piece of paper so that the (suitably scaled)
      32             : distances between the points in your map representing each of those cities are related to the true distances between the cities.
      33             : Stating this more mathematically MDS endeavors to find an <a href="http://en.wikipedia.org/wiki/Isometry">isometry</a>
      34             : between points distributed in a high-dimensional space and a set of points distributed in a low-dimensional plane.
      35             : In other words, if we have \f$M\f$ \f$D\f$-dimensional points, \f$\mathbf{X}\f$,
      36             : and we can calculate dissimilarities between pairs them, \f$D_{ij}\f$, we can, with an MDS calculation, try to create \f$M\f$ projections,
      37             : \f$\mathbf{x}\f$, of the high dimensionality points in a \f$d\f$-dimensional linear space by trying to arrange the projections so that the
      38             : Euclidean distances between pairs of them, \f$d_{ij}\f$, resemble the dissimilarities between the high dimensional points.  In short we minimize:
      39             : 
      40             : \f[
      41             : \chi^2 = \sum_{i \ne j} \left( D_{ij} - d_{ij} \right)^2
      42             : \f]
      43             : 
      44             : where \f$D_{ij}\f$ is the distance between point \f$X^{i}\f$ and point \f$X^{j}\f$ and \f$d_{ij}\f$ is the distance between the projection
      45             : of \f$X^{i}\f$, \f$x^i\f$, and the projection of \f$X^{j}\f$, \f$x^j\f$.  A tutorial on this approach can be used to analyze simulations
      46             : can be found in the tutorial \ref lugano-5 and in the following <a href="https://www.youtube.com/watch?v=ofC2qz0_9_A&feature=youtu.be" > short video.</a>
      47             : 
      48             : \par Examples
      49             : 
      50             : The following command instructs plumed to construct a classical multidimensional scaling projection of a trajectory.
      51             : The RMSD distance between atoms 1-256 have moved is used to measure the distances in the high-dimensional space.
      52             : 
      53             : \plumedfile
      54             : data: COLLECT_FRAMES ATOMS=1-256
      55             : mat: EUCLIDEAN_DISSIMILARITIES USE_OUTPUT_DATA_FROM=data
      56             : mds: CLASSICAL_MDS USE_OUTPUT_DATA_FROM=mat NLOW_DIM=2
      57             : OUTPUT_ANALYSIS_DATA_TO_COLVAR USE_OUTPUT_DATA_FROM=mds FILE=rmsd-embed
      58             : \endplumedfile
      59             : 
      60             : The following section is for people who are interested in how this method works in detail. A solid understanding of this material is
      61             : not necessary to use MDS.
      62             : 
      63             : \section dim-sec Method of optimization
      64             : 
      65             : The stress function can be minimized using a standard optimization algorithm such as conjugate gradients or steepest descent.
      66             : However, it is more common to do this minimization using a technique known as classical scaling.  Classical scaling works by
      67             : recognizing that each of the distances $D_{ij}$ in the above sum can be written as:
      68             : 
      69             : \f[
      70             : D_{ij}^2 = \sum_{\alpha} (X^i_\alpha - X^j_\alpha)^2 = \sum_\alpha (X^i_\alpha)^2 + (X^j_\alpha)^2 - 2X^i_\alpha X^j_\alpha
      71             : \f]
      72             : 
      73             : We can use this expression and matrix algebra to calculate multiple distances at once.  For instance if we have three points,
      74             : \f$\mathbf{X}\f$, we can write distances between them as:
      75             : 
      76             : \f{eqnarray*}{
      77             : D^2(\mathbf{X}) &=& \left[ \begin{array}{ccc}
      78             : 0 & d_{12}^2 & d_{13}^2 \\
      79             : d_{12}^2 & 0 & d_{23}^2 \\
      80             : d_{13}^2 & d_{23}^2 & 0
      81             : \end{array}\right] \\
      82             : &=&
      83             : \sum_\alpha \left[ \begin{array}{ccc}
      84             : (X^1_\alpha)^2 & (X^1_\alpha)^2 & (X^1_\alpha)^2 \\
      85             : (X^2_\alpha)^2 & (X^2_\alpha)^2 & (X^2_\alpha)^2 \\
      86             : (X^3_\alpha)^2 & (X^3_\alpha)^2 & (X^3_\alpha)^2 \\
      87             : \end{array}\right]
      88             :  + \sum_\alpha \left[ \begin{array}{ccc}
      89             : (X^1_\alpha)^2 & (X^2_\alpha)^2 & (X^3_\alpha)^2 \\
      90             : (X^1_\alpha)^2 & (X^2_\alpha)^2 & (X^3_\alpha)^2 \\
      91             : (X^1_\alpha)^2 & (X^2_\alpha)^2 & (X^3_\alpha)^2 \\
      92             : \end{array}\right]
      93             : - 2 \sum_\alpha \left[ \begin{array}{ccc}
      94             : X^1_\alpha X^1_\alpha & X^1_\alpha X^2_\alpha & X^1_\alpha X^3_\alpha \\
      95             : X^2_\alpha X^1_\alpha & X^2_\alpha X^2_\alpha & X^2_\alpha X^3_\alpha \\
      96             : X^1_\alpha X^3_\alpha & X^3_\alpha X^2_\alpha & X^3_\alpha X^3_\alpha
      97             : \end{array}\right] \nonumber \\
      98             : &=& \mathbf{c 1^T} + \mathbf{1 c^T} - 2 \sum_\alpha \mathbf{x}_a \mathbf{x}^T_a =  \mathbf{c 1^T} + \mathbf{1 c^T} - 2\mathbf{X X^T}
      99             : \f}
     100             : 
     101             : This last equation can be extended to situations when we have more than three points.  In it \f$\mathbf{X}\f$ is a matrix that has
     102             : one high-dimensional point on each of its rows and \f$\mathbf{X^T}\f$ is its transpose.  \f$\mathbf{1}\f$ is an \f$M \times 1\f$ vector
     103             : of ones and \f$\mathbf{c}\f$ is a vector with components given by:
     104             : 
     105             : \f[
     106             : c_i = \sum_\alpha (x_\alpha^i)^2
     107             : \f]
     108             : 
     109             : These quantities are the diagonal elements of \f$\mathbf{X X^T}\f$, which is a dot product or Gram Matrix that contains the
     110             : dot product of the vector \f$X_i\f$ with the vector \f$X_j\f$ in element \f$i,j\f$.
     111             : 
     112             : In classical scaling we introduce a centering matrix \f$\mathbf{J}\f$ that is given by:
     113             : 
     114             : \f[
     115             : \mathbf{J} = \mathbf{I} - \frac{1}{M} \mathbf{11^T}
     116             : \f]
     117             : 
     118             : where \f$\mathbf{I}\f$ is the identity.  Multiplying the equations above from the front and back by this matrix and a factor of a \f$-\frac{1}{2}\f$ gives:
     119             : 
     120             : \f{eqnarray*}{
     121             :  -\frac{1}{2} \mathbf{J} \mathbf{D}^2(\mathbf{X}) \mathbf{J} &=& -\frac{1}{2}\mathbf{J}( \mathbf{c 1^T} + \mathbf{1 c^T} - 2\mathbf{X X^T})\mathbf{J} \\
     122             :  &=& -\frac{1}{2}\mathbf{J c 1^T J} - \frac{1}{2} \mathbf{J 1 c^T J} + \frac{1}{2} \mathbf{J}(2\mathbf{X X^T})\mathbf{J} \\
     123             :  &=& \mathbf{ J X X^T J } = \mathbf{X X^T } \label{eqn:scaling}
     124             : \f}
     125             : 
     126             : The fist two terms in this expression disappear because \f$\mathbf{1^T J}=\mathbf{J 1} =\mathbf{0}\f$, where \f$\mathbf{0}\f$
     127             : is a matrix containing all zeros.  In the final step meanwhile we use the fact that the matrix of squared distances will not
     128             : change when we translate all the points.  We can thus assume that the mean value, \f$\mu\f$, for each of the components, \f$\alpha\f$:
     129             : \f[
     130             : \mu_\alpha = \frac{1}{M} \sum_{i=1}^N \mathbf{X}^i_\alpha
     131             : \f]
     132             : is equal to 0 so the columns of \f$\mathbf{X}\f$ add up to 0.  This in turn means that each of the columns of
     133             : \f$\mathbf{X X^T}\f$ adds up to zero, which is what allows us to write \f$\mathbf{ J X X^T J } = \mathbf{X X^T }\f$.
     134             : 
     135             : The matrix of squared distances is symmetric and positive-definite we can thus use the spectral decomposition to decompose it as:
     136             : 
     137             : \f[
     138             : \Phi= \mathbf{V} \Lambda \mathbf{V}^T
     139             : \f]
     140             : 
     141             : Furthermore, because the matrix we are diagonalizing, \f$\mathbf{X X^T}\f$, is the product of a matrix and its transpose
     142             : we can use this decomposition to write:
     143             : 
     144             : \f[
     145             : \mathbf{X} =\mathbf{V} \Lambda^\frac{1}{2}
     146             : \f]
     147             : 
     148             : Much as in PCA there are generally a small number of large eigenvalues in \f$\Lambda\f$ and many small eigenvalues.
     149             : We can safely use only the large eigenvalues and their corresponding eigenvectors to express the relationship between
     150             : the coordinates \f$\mathbf{X}\f$.  This gives us our set of low-dimensional projections.
     151             : 
     152             : This derivation makes a number of assumptions about the how the low dimensional points should best be arranged to minimize
     153             : the stress. If you use an interactive optimization algorithm such as SMACOF you may thus be able to find a better
     154             : (lower-stress) projection of the points.  For more details on the assumptions made
     155             : see <a href="http://quest4rigor.com/tag/multidimensional-scaling/"> this website.</a>
     156             : */
     157             : //+ENDPLUMEDOC
     158             : 
     159             : namespace PLMD {
     160             : namespace dimred {
     161             : 
     162             : class ClassicalMultiDimensionalScaling : public DimensionalityReductionBase {
     163             : public:
     164             :   static void registerKeywords( Keywords& keys );
     165             :   explicit ClassicalMultiDimensionalScaling( const ActionOptions& ao );
     166             :   void calculateProjections( const Matrix<double>&, Matrix<double>& ) override;
     167             : };
     168             : 
     169       10433 : PLUMED_REGISTER_ACTION(ClassicalMultiDimensionalScaling,"CLASSICAL_MDS")
     170             : 
     171           8 : void ClassicalMultiDimensionalScaling::registerKeywords( Keywords& keys ) {
     172           8 :   DimensionalityReductionBase::registerKeywords( keys );
     173           8 : }
     174             : 
     175           7 : ClassicalMultiDimensionalScaling::ClassicalMultiDimensionalScaling( const ActionOptions& ao):
     176             :   Action(ao),
     177           7 :   DimensionalityReductionBase(ao)
     178             : {
     179           7 :   if( dimredbase ) error("input to CLASSICAL_MDS should not be output from dimensionality reduction object");
     180           7 : }
     181             : 
     182          12 : void ClassicalMultiDimensionalScaling::calculateProjections( const Matrix<double>& targets, Matrix<double>& projections ) {
     183             :   // Retrieve the distances from the dimensionality reduction object
     184          12 :   double half=(-0.5); Matrix<double> distances( half*targets );
     185             : 
     186             :   // Apply centering transtion
     187             :   unsigned n=distances.nrows(); double sum;
     188             :   // First HM
     189        1564 :   for(unsigned i=0; i<n; ++i) {
     190      389056 :     sum=0; for(unsigned j=0; j<n; ++j) sum+=distances(i,j);
     191      389056 :     for(unsigned j=0; j<n; ++j) distances(i,j) -= sum/n;
     192             :   }
     193             :   // Now (HM)H
     194        1564 :   for(unsigned i=0; i<n; ++i) {
     195      389056 :     sum=0; for(unsigned j=0; j<n; ++j) sum+=distances(j,i);
     196      389056 :     for(unsigned j=0; j<n; ++j) distances(j,i) -= sum/n;
     197             :   }
     198             : 
     199             :   // Diagonalize matrix
     200          12 :   std::vector<double> eigval(n); Matrix<double> eigvec(n,n);
     201          12 :   diagMat( distances, eigval, eigvec );
     202             : 
     203             :   // Pass final projections to map object
     204        1564 :   for(unsigned i=0; i<n; ++i) {
     205        4654 :     for(unsigned j=0; j<projections.ncols(); ++j) projections(i,j)=std::sqrt(eigval[n-1-j])*eigvec(n-1-j,i);
     206             :   }
     207          12 : }
     208             : 
     209             : }
     210             : }

Generated by: LCOV version 1.15