LCOV - code coverage report
Current view: top level - mapping - PathDisplacements.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 100 103 97.1 %
Date: 2026-11-05 10:19:04 Functions: 6 8 75.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2016-2018 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 "core/ActionWithValue.h"
      23             : #include "core/ActionPilot.h"
      24             : #include "core/ActionWithArguments.h"
      25             : #include "core/ActionRegister.h"
      26             : #include "tools/Matrix.h"
      27             : #include "PathProjectionCalculator.h"
      28             : 
      29             : //+PLUMEDOC ANALYSIS AVERAGE_PATH_DISPLACEMENT
      30             : /*
      31             : Accumulate the distances between the reference frames in the paths and the configurations visited
      32             : 
      33             : This action is used in the shortcut for [ADAPTIVE_PATH](ADAPTIVE_PATH.md) as it is helps us to adapt and refine
      34             : the defintition of a path CV based on the sampling that is observed in the trajectory.
      35             : 
      36             : By way of reminder path CVs were introduced by Branduardi _et al._ in the first paper cited in the bibliography below.
      37             : In PLUMED this method is implemented in [PATH](PATH.md) and works by definition a position along the path as:
      38             : 
      39             : $$
      40             : s = \frac{ \sum_{i=1}^N i \exp( -\lambda R[X - X_i] ) }{ \sum_{i=1}^N \exp( -\lambda R[X - X_i] ) }
      41             : $$
      42             : 
      43             : while the distance from the path (z) is measured using:
      44             : 
      45             : $$
      46             : z = -\frac{1}{\lambda} \ln\left[ \sum_{i=1}^N \exp( -\lambda R[X - X_i] ) \right]
      47             : $$
      48             : 
      49             : In these expressions $N$ high-dimensional frames ($X_i$) are used to describe the path in the high-dimensional
      50             : space. The two expressions above are then functions of the distances from each of the high-dimensional frames $R[X - X_i]$.
      51             : 
      52             : When a simulator uses this method of path CVs the assumption is that when the system undergoes the transition from state A
      53             : to state B it passes through a narrow (non-linear) tube that is centered on the $s$ coordinate that is defined above in terms the
      54             : various milestones that are used to define the path. In other words, when the system transitions from A to be B it does not pass along the $s$
      55             : coordinate. If, however, we average the vector connecting each position the system passes through to the nearest
      56             : point on the path that underpins the definition of the $s$ we should get zero as the $s$ coordinate is centered on the path connecting the two states.
      57             : 
      58             : With that theory in mind this action can be used to collect the average displacement between the points that trajectories are passing through and the path.
      59             : This action can thus be used to update the definitions of the milestones that are used in the definition of a [PATH](PATH.md) or [GPATH](GPATH.md) coordinate
      60             : so that the PATH more clearly passes through the center of the path that the system has traversed along in passing from state A to state B.  These path displacements
      61             : are accumulated as follows.  You first determine the vectors, $\mathbf{v}_1$ and $\mathbf{v}_3$ that connect the instantaneous position to the closest and second closest milestones on the
      62             : current path.  You can then compute the following:
      63             : 
      64             : $$
      65             : \delta = \frac{ \sqrt{( \mathbf{v}_1\cdot\mathbf{v}_2 )^2 - |\mathbf{v}_2|^2(|\mathbf{v}_1|^2 - |\mathbf{v}_3|^2) } + \mathbf{v}_1\cdot\mathbf{v}_2 }{2|\mathbf{v}_2|^2} - \frac{1}{2}
      66             : $$
      67             : 
      68             : where $\mathbf{v}_2$ is the vector connecting the closest and second closest milestone on your path. Displacements for these two milestones are then computed as:
      69             : 
      70             : $$
      71             : d_1 = (1 + \delta) \left(\mathbf{v}_1 - \delta \mathbf{v}_2\right) \qquad d_2 = -\delta \left(\mathbf{v}_1 - \delta \mathbf{v}_2\right)
      72             : $$
      73             : 
      74             : These displacement vectors for the two closest nodes are computed on every accumulate step and are then averaged with the factors of $(1 + \delta)$ and $-\delta$ in these expressions
      75             : serving as weights that are used when normalising these averages.
      76             : 
      77             : ## Examples
      78             : 
      79             : Suppose that you are interested in a transition between two states A and B and that you have representative structures for these two states in the files `start.pdb` and `end.pdb`.  You can
      80             : use [pathtools](pathtools.md) to create an initial (linear) path connecting these two states as follows:
      81             : 
      82             : ```plumed
      83             : plumed pathtools --start start.pdb --end end.pdb --nframes 20 --metric OPTIMAL --out path.pdb
      84             : ```
      85             : 
      86             : This path is likely not even close to the transition pathway that the system takes between these two states as the assumption that the path is linear is pretty severe.  We can nevertheless use
      87             : a path generated by such a command in a [MOVINGRESTRAINT](MOVINGRESTRAINT.md) command like the one shown below:
      88             : 
      89             : ```plumed
      90             : #SETTINGS INPUTFILES=regtest/trajectories/path_msd/all.pdb
      91             : p1: PATH REFERENCE=regtest/trajectories/path_msd/all.pdb TYPE=OPTIMAL LAMBDA=69087
      92             : mv: MOVINGRESTRAINT ...
      93             :    ARG=p1.s KAPPA0=1000
      94             :    STEP0=0 AT0=1
      95             :    STEP1=1000000 AT1=42
      96             :    STEP2=2000000 AT2=1
      97             :    STEP3=3000000 AT3=42
      98             :    STEP4=4000000 AT4=1
      99             :    STEP5=5000000 AT5=42
     100             :    STEP6=6000000 AT6=1
     101             :    STEP7=7000000 AT7=42
     102             :    STEP8=8000000 AT8=1
     103             : ...
     104             : PRINT ARG=p1.s,p1.z FILE=colvar
     105             : ```
     106             : 
     107             : This input hopefully (you need to check by looking at your trajectory) drives our system between the two states of interest 8 times - four of these transitions are from A to B and four are from B to A.
     108             : You can then analyze the trajectories that are generated using [driver](driver.md) and reparameterize a new path that passes more closely through the sampled trajectories using:
     109             : 
     110             : ```plumed
     111             : #SETTINGS INPUTFILES=regtest/trajectories/path_msd/all.pdb
     112             : rmsd: RMSD REFERENCE=regtest/trajectories/path_msd/all.pdb DISPLACEMENT TYPE=OPTIMAL
     113             : # Accumulate the average displacement between the reference path and the trajectories that have sampled the transition
     114             : disp: AVERAGE_PATH_DISPLACEMENT ARG=rmsd.disp STRIDE=1 METRIC={RMSD DISPLACEMENT TYPE=OPTIMAL ALIGN=1,1,1,1,1,1,1,1,1,1,1,1,1 DISPLACE=1,1,1,1,1,1,1,1,1,1,1,1,1} METRIC_COMPONENT=disp REFERENCE=rmsd_ref
     115             : # Now displace the original path by the accumulated displacement and reparameterize so that all frames are equally spaced
     116             : REPARAMETERIZE_PATH DISPLACE_FRAMES=disp FIXED=1,42 METRIC={RMSD DISPLACEMENT TYPE=OPTIMAL ALIGN=1,1,1,1,1,1,1,1,1,1,1,1,1 DISPLACE=1,1,1,1,1,1,1,1,1,1,1,1,1} METRIC_COMPONENT=disp REFERENCE=rmsd_ref
     117             : # And output the final reparameterized path at the end of the simulation
     118             : DUMPPDB DESCRIPTION=PATH STRIDE=0 FILE=outpatb.pdb ATOMS=rmsd_ref ATOM_INDICES=1-13
     119             : ```
     120             : 
     121             : You can then try the same calculation with the reparameterized path before eventually using a method such as [METAD](METAD.md) to obtain the free energy as a function of your $s$ path coordinate.
     122             : 
     123             : Notice that the METRIC keyword appears here as the calculation involves calculating the vectors connecting the milestones on your path.  This keyword operates in the same way that is described in
     124             : the documentation for [GEOMETRIC_PATH](GEOMETRIC_PATH.md).
     125             : 
     126             : */
     127             : //+ENDPLUMEDOC
     128             : 
     129             : namespace PLMD {
     130             : namespace mapping {
     131             : 
     132             : class PathDisplacements : public ActionWithValue, public ActionPilot, public ActionWithArguments {
     133             : private:
     134             :   bool clearnextstep;
     135             :   unsigned clearstride;
     136             :   double fadefact;
     137             :   std::vector<double> wsum, displace_v;
     138             :   Matrix<double> displacements;
     139             :   PathProjectionCalculator path_projector;
     140             : public:
     141             :   static void registerKeywords( Keywords& keys );
     142             :   explicit PathDisplacements(const ActionOptions&);
     143             :   unsigned getNumberOfDerivatives();
     144        1665 :   void clearDerivatives( const bool& force=false ) {}
     145        1665 :   void calculate() {}
     146        1665 :   void apply() {}
     147             :   void update();
     148             : };
     149             : 
     150             : PLUMED_REGISTER_ACTION(PathDisplacements,"AVERAGE_PATH_DISPLACEMENT")
     151             : 
     152          12 : void PathDisplacements::registerKeywords( Keywords& keys ) {
     153          12 :   Action::registerKeywords( keys );
     154          12 :   ActionWithValue::registerKeywords( keys );
     155          12 :   ActionPilot::registerKeywords( keys );
     156          12 :   ActionWithArguments::registerKeywords( keys );
     157          12 :   PathProjectionCalculator::registerKeywords( keys );
     158          24 :   keys.remove("NUMERICAL_DERIVATIVES");
     159          12 :   keys.add("compulsory","STRIDE","1","the frequency with which the average displacements should be collected and added to the average displacements");
     160          12 :   keys.add("compulsory","HALFLIFE","-1","the number of MD steps after which a previously measured path distance weighs only 50 percent in the average. This option may increase convergence by allowing to forget the memory of a bad initial guess path. The default is to set this to infinity");
     161          12 :   keys.add("compulsory","CLEAR","0","the frequency with which to clear all the accumulated data.  The default value "
     162             :            "of 0 implies that all the data will be used and that the grid will never be cleared");
     163          24 :   keys.setValueDescription("matrix","matrix containing the average displacement between the trajectory and each of the landmarks that makes up the path");
     164          12 :   keys.addDOI("10.1063/1.2432340");
     165          12 :   keys.addDOI("10.1063/1.3660208");
     166          12 : }
     167             : 
     168           4 : PathDisplacements::PathDisplacements(const ActionOptions& ao):
     169             :   Action(ao),
     170             :   ActionWithValue(ao),
     171             :   ActionPilot(ao),
     172             :   ActionWithArguments(ao),
     173           4 :   clearnextstep(false),
     174           4 :   path_projector(this) {
     175             :   // Read in clear instructions
     176           4 :   parse("CLEAR",clearstride);
     177           4 :   if( clearstride>0 ) {
     178           4 :     if( clearstride%getStride()!=0 ) {
     179           0 :       error("CLEAR parameter must be a multiple of STRIDE");
     180             :     }
     181           4 :     log.printf("  clearing average every %u steps \n",clearstride);
     182             :   }
     183             :   double halflife;
     184           4 :   parse("HALFLIFE",halflife);
     185           4 :   log.printf("  weight of contribution to frame halves every %f steps \n",halflife);
     186           4 :   if( halflife<0 ) {
     187           3 :     fadefact=1.0;
     188             :   } else {
     189           1 :     fadefact = exp( -0.693147180559945 / static_cast<double>(halflife) );
     190             :   }
     191             :   // Now create the weights vector and displacements matrix
     192           4 :   unsigned nrows = getPntrToArgument(0)->getShape()[0];
     193           4 :   unsigned ncols = getPntrToArgument(0)->getShape()[1];
     194           4 :   wsum.resize( nrows );
     195           4 :   displacements.resize( nrows, ncols );
     196          84 :   for(unsigned i=0; i<nrows; ++i) {
     197          80 :     wsum[i]=0;
     198        2460 :     for(unsigned j=0; j<ncols; ++j) {
     199        2380 :       displacements(i,j)=0;
     200             :     }
     201             :   }
     202             :   // Add bibliography
     203           4 :   log<<"  Bibliography "<<plumed.cite("Diaz Leines and Ensing, Phys. Rev. Lett. 109, 020601 (2012)")<<"\n";
     204             :   // And create a value to hold the displacements
     205           4 :   std::vector<std::size_t> shape(2);
     206           4 :   shape[0]=nrows;
     207           4 :   shape[1]=ncols;
     208           4 :   addValue( shape );
     209           4 :   setNotPeriodic();
     210           4 :   getPntrToComponent(0)->reshapeMatrixStore( shape[1] );
     211           4 : }
     212             : 
     213           0 : unsigned PathDisplacements::getNumberOfDerivatives() {
     214           0 :   return 0;
     215             : }
     216             : 
     217        1665 : void PathDisplacements::update() {
     218        1665 :   unsigned nrows = getPntrToArgument(0)->getShape()[0];
     219        1665 :   unsigned ncols = getPntrToArgument(0)->getShape()[1];
     220             : 
     221        1665 :   if( clearnextstep ) {
     222             :     unsigned k=0;
     223        1820 :     for(unsigned i=0; i<nrows; ++i) {
     224       67860 :       for(unsigned j=0; j<ncols; ++j) {
     225       66145 :         displacements(i,j)=0;
     226       66145 :         getPntrToComponent(0)->set(k,0);
     227       66145 :         k++;
     228             :       }
     229             :     }
     230         105 :     clearnextstep=false;
     231             :   }
     232             : 
     233             :   unsigned k=0, iclose1=0, iclose2=0;
     234             :   double v1v1=0, v3v3=0;
     235       33337 :   for(unsigned i=0; i<nrows; ++i) {
     236             :     double dist = 0;
     237     1192140 :     for(unsigned j=0; j<ncols; ++j) {
     238     1160468 :       double tmp = getPntrToArgument(0)->get(k);
     239     1160468 :       dist += tmp*tmp;
     240     1160468 :       k++;
     241             :     }
     242       31672 :     if( i==0 ) {
     243             :       v1v1 = dist;
     244             :       iclose1 = 0;
     245       30007 :     } else if( dist<v1v1 ) {
     246             :       v3v3=v1v1;
     247             :       v1v1=dist;
     248             :       iclose2=iclose1;
     249             :       iclose1=i;
     250       15285 :     } else if( i==1 ) {
     251             :       v3v3=dist;
     252             :       iclose2=1;
     253       15179 :     } else if( dist<v3v3 ) {
     254             :       v3v3=dist;
     255             :       iclose2=i;
     256             :     }
     257             :   }
     258             :   // And find third closest point
     259        1665 :   int isign = iclose1 - iclose2;
     260             :   if( isign>1 ) {
     261             :     isign=1;
     262        1665 :   } else if( isign<-1 ) {
     263             :     isign=-1;
     264             :   }
     265        1665 :   int iclose3 = iclose1 + isign;
     266        1665 :   unsigned ifrom=iclose1, ito=iclose3;
     267        1665 :   if( iclose3<0 || static_cast<unsigned>(iclose3)>=nrows ) {
     268         230 :     ifrom=iclose2;
     269         230 :     ito=iclose1;
     270             :   }
     271             : 
     272             :   // Calculate the dot product of v1 with v2
     273        1665 :   path_projector.getDisplaceVector( ifrom, ito, displace_v );
     274             :   double v2v2=0, v1v2=0;
     275        1665 :   unsigned kclose1 = iclose1*ncols;
     276       62863 :   for(unsigned i=0; i<displace_v.size(); ++i) {
     277       61198 :     v2v2 += displace_v[i]*displace_v[i];
     278       61198 :     v1v2 += displace_v[i]*getPntrToArgument(0)->get(kclose1+i);
     279             :   }
     280             : 
     281        1665 :   double root = sqrt( v1v2*v1v2 - v2v2 * ( v1v1 - v3v3) );
     282        1665 :   double dx = 0.5 * ( (root + v1v2) / v2v2 - 1.);
     283        1665 :   double weight2 = -1.* dx;
     284        1665 :   double weight1 = 1.0 + dx;
     285        1665 :   if( weight1>1.0 ) {
     286             :     weight1=1.0;
     287             :     weight2=0.0;
     288        1523 :   } else if( weight2>1.0 ) {
     289             :     weight1=0.0;
     290             :     weight2=1.0;
     291             :   }
     292             : 
     293             :   // Accumulate displacements for path
     294       62863 :   for(unsigned i=0; i<ncols; ++i) {
     295       61198 :     double displace = getPntrToArgument(0)->get(kclose1+i) - dx*displace_v[i];
     296       61198 :     displacements(iclose1,i) += weight1 * displace;
     297       61198 :     displacements(iclose2,i) += weight2 * displace;
     298             :   }
     299             : 
     300             :   // Update weight accumulators
     301        1665 :   wsum[iclose1] *= fadefact;
     302        1665 :   wsum[iclose2] *= fadefact;
     303        1665 :   wsum[iclose1] += weight1;
     304        1665 :   wsum[iclose2] += weight2;
     305             : 
     306             :   // Update numbers in values
     307        1665 :   if( wsum[iclose1] > epsilon ) {
     308       62863 :     for(unsigned i=0; i<ncols; ++i) {
     309       61198 :       getPntrToComponent(0)->set( kclose1+i, displacements(iclose1,i) / wsum[iclose1] );
     310             :     }
     311             :   }
     312        1665 :   if( wsum[iclose2] > epsilon ) {
     313        1665 :     unsigned kclose2 = iclose2*ncols;
     314       62863 :     for(unsigned i=0; i<ncols; ++i) {
     315       61198 :       getPntrToComponent(0)->set( kclose2+i, displacements(iclose2,i) / wsum[iclose2] );
     316             :     }
     317             :   }
     318             : 
     319             :   // Clear if required
     320        1665 :   if( (getStep()>0 && clearstride>0 && getStep()%clearstride==0) ) {
     321         106 :     clearnextstep=true;
     322             :   }
     323        1665 : }
     324             : 
     325             : }
     326             : }

Generated by: LCOV version 1.16