LCOV - code coverage report
Current view: top level - colvar - DRMSD.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 135 142 95.1 %
Date: 2026-11-05 10:19:04 Functions: 2 3 66.7 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2013-2024 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/ActionShortcut.h"
      23             : #include "core/ActionRegister.h"
      24             : #include "core/PlumedMain.h"
      25             : #include "core/ActionSet.h"
      26             : #include "core/Group.h"
      27             : #include "tools/PDB.h"
      28             : 
      29             : namespace PLMD {
      30             : namespace colvar {
      31             : 
      32             : //+PLUMEDOC DCOLVAR DRMSD
      33             : /*
      34             : Calculate the distance RMSD with respect to a reference structure.
      35             : 
      36             : To calculate the root-mean-square deviation between the atoms in two configurations
      37             : you must first superimpose the two structures in some ways.  Obviously, it is the internal vibrational
      38             : motions of the structure - i.e. not the translations and rotations - that are interesting. However,
      39             : aligning two structures by removing the translational and rotational motions is not easy.  Furthermore,
      40             : in some cases there can be alignment issues caused by so-called frame-fitting problems. It is thus
      41             : often easier to calculate the distances between all the pairs of atoms.  The distance
      42             : between the two structures, ${\bf X}^a$ and ${\bf X}^b$ can then be measured as:
      43             : 
      44             : $$
      45             : d({\bf X}^A, {\bf X}^B) = \sqrt{\frac{1}{N(N-1)} \sum_{i \ne j} [ d({\bf x}_i^a,{\bf x}_j^a) - d({\bf x}_i^b,{\bf x}_j^b) ]^2}
      46             : $$
      47             : 
      48             : where $N$ is the number of atoms and $d({\bf x}_i,{\bf x}_j)$ represents the distance between
      49             : atoms $i$ and $j$.  Clearly, this representation of the configuration is invariant to translation and rotation.
      50             : However, it can become expensive to calculate when the number of atoms is large.  This can be resolved
      51             : within the DRMSD colvar by setting `LOWER_CUTOFF` and `UPPER_CUTOFF`.  These keywords ensure that only
      52             : pairs of atoms that are within a certain range are incorporated into the above sum.
      53             : 
      54             : In PDB files the atomic coordinates and box lengths should be in Angstroms unless
      55             : you are working with natural units.  If you are working with natural units then the coordinates
      56             : should be in your natural length unit.  [Click here](http://www.wwpdb.org/docs.html) for more details on the PDB file format.
      57             : 
      58             : ## Examples
      59             : 
      60             : The following tells plumed to calculate the distance RMSD between
      61             : the positions of the atoms in the reference file and their instantaneous
      62             : position. Only pairs of atoms whose distance in the reference structure is within
      63             : 0.1 and 0.8 nm are considered.
      64             : 
      65             : ```plumed
      66             : #SETTINGS INPUTFILES=regtest/basic/rt-drmsd/test1.pdb
      67             : d: DRMSD ...
      68             :   REFERENCE=regtest/basic/rt-drmsd/test1.pdb
      69             :   LOWER_CUTOFF=0.1 UPPER_CUTOFF=0.8
      70             : ...
      71             : ```
      72             : 
      73             : The following tells plumed to calculate the square of the DRMSD value for a pair of molecules.
      74             : 
      75             : ```plumed
      76             : #SETTINGS INPUTFILES=regtest/basic/rt-drmsd/test0.pdb
      77             : d: DRMSD ...
      78             :   REFERENCE=regtest/basic/rt-drmsd/test0.pdb
      79             :   LOWER_CUTOFF=0.1 UPPER_CUTOFF=0.8 TYPE=INTER-DRMSD
      80             :   SQUARED
      81             : ...
      82             : ```
      83             : 
      84             : Notice that in the input reference file (which you can see by clicking on regtest/basic/rt-drmsd/test0.pdb )
      85             : the atoms in each of the two molecules are separated by a TER
      86             : command as shown below.
      87             : 
      88             : In this example the INTER-DRMSD type ensures that the set of distances from which the final
      89             : quantity is computed involve one atom from each of the two molecules.  If this is replaced
      90             : by INTRA-DRMSD then only those distances involving pairs of atoms that are both in the same
      91             : molecule are computed.
      92             : 
      93             : ## Periodic boundary conditions
      94             : 
      95             : Notice that PLUMED does not use periodic boundary conditions when computing the reference distances.  However, the instantaneous
      96             : values of the distances are computed using periodic boundary conditions.  If you would like not to use periodic boundary conditions
      97             : when computing these instantaneous distances you use the NOPBC flag as illustrated below:
      98             : 
      99             : ```plumed
     100             : #SETTINGS INPUTFILES=regtest/basic/rt-drmsd/test1.pdb
     101             : d: DRMSD ...
     102             :   REFERENCE=regtest/basic/rt-drmsd/test1.pdb
     103             :   LOWER_CUTOFF=0.1 UPPER_CUTOFF=0.8 NOPBC
     104             : ...
     105             : ```
     106             : 
     107             : */
     108             : //+ENDPLUMEDOC
     109             : 
     110             : class DRMSD : public ActionShortcut {
     111             : public:
     112             :   static void registerKeywords(Keywords& keys);
     113             :   explicit DRMSD(const ActionOptions&);
     114             : };
     115             : 
     116             : PLUMED_REGISTER_ACTION(DRMSD,"DRMSD")
     117             : 
     118          19 : void DRMSD::registerKeywords( Keywords& keys ) {
     119          19 :   ActionShortcut::registerKeywords( keys );
     120          19 :   keys.add("compulsory","REFERENCE","a file in pdb format containing the reference structure and the atoms involved in the CV.");
     121          19 :   keys.add("optional","LOWER_CUTOFF","only pairs of atoms further than LOWER_CUTOFF are considered in the calculation.");
     122          19 :   keys.add("optional","UPPER_CUTOFF","only pairs of atoms closer than UPPER_CUTOFF are considered in the calculation.");
     123          19 :   keys.add("compulsory","TYPE","DRMSD","what kind of DRMSD would you like to calculate.  You can use either the normal DRMSD involving all the distances between "
     124             :            "the atoms in your molecule.  Alternatively, if you have multiple molecules you can use the type INTER-DRMSD "
     125             :            "to compute DRMSD values involving only those distances between the atoms at least two molecules or the type INTRA-DRMSD "
     126             :            "to compute DRMSD values involving only those distances between atoms in the same molecule");
     127          19 :   keys.addFlag("SQUARED",false,"This should be set if you want MSD instead of RMSD ");
     128          19 :   keys.addFlag("NOPBC",false,"ignore the periodic boundary conditions when calculating distances");
     129             :   // This is just ignored in reality which is probably bad
     130          38 :   keys.addDeprecatedFlag("NUMERICAL_DERIVATIVES","");
     131          38 :   keys.setValueDescription("scalar/vector","the DRMSD distance between the instantaneous structure and the reference structure");
     132          19 :   keys.needsAction("SUM");
     133          19 :   keys.needsAction("DISTANCE");
     134          19 :   keys.needsAction("CONSTANT");
     135          19 :   keys.needsAction("EUCLIDEAN_DISTANCE");
     136          19 :   keys.needsAction("CUSTOM");
     137          19 : }
     138             : 
     139          14 : DRMSD::DRMSD( const ActionOptions& ao ):
     140             :   Action(ao),
     141          14 :   ActionShortcut(ao) {
     142             :   // Read in the reference configuration
     143             :   std::string reference;
     144          14 :   parse("REFERENCE",reference);
     145             :   // First bit of input for the instantaneous distances
     146             :   bool numder;
     147          28 :   parseFlag("NUMERICAL_DERIVATIVES",numder);
     148             :   double fake_unit=0.1;
     149          14 :   FILE* fp2=fopen(reference.c_str(),"r");
     150             :   bool do_read=true;
     151             :   unsigned nframes=0;
     152          67 :   while( do_read ) {
     153          56 :     PDB mypdb;
     154          56 :     do_read=mypdb.readFromFilepointer(fp2,false,fake_unit);
     155          56 :     if( !do_read && nframes>0 ) {
     156             :       break ;
     157             :     }
     158          54 :     nframes++;
     159          56 :   }
     160          13 :   fclose(fp2);
     161             : 
     162             :   // Get cutoff information
     163          13 :   double lcut=0;
     164          28 :   parse("LOWER_CUTOFF",lcut);
     165             :   std::string drmsd_type;
     166          13 :   parse("TYPE",drmsd_type);
     167          13 :   double ucut=std::numeric_limits<double>::max();
     168          13 :   parse("UPPER_CUTOFF",ucut);
     169             :   bool nopbc;
     170          27 :   parseFlag("NOPBC",nopbc);
     171             :   std::string pbc_str;
     172          13 :   if(nopbc) {
     173             :     pbc_str="NOPBC";
     174             :   }
     175             :   // Open the pdb file
     176          13 :   FILE* fp=fopen(reference.c_str(),"r");
     177             :   do_read=true;
     178          13 :   if(!fp) {
     179           0 :     error("could not open reference file " + reference );
     180             :   }
     181             :   unsigned n=0;
     182          14 :   std::string allpairs="";
     183          13 :   std::vector<std::pair<unsigned,unsigned> > upairs;
     184          13 :   std::vector<std::string> refvals;
     185          67 :   while ( do_read ) {
     186          56 :     PDB mypdb;
     187          56 :     do_read=mypdb.readFromFilepointer(fp,false,fake_unit);
     188          56 :     if( !do_read && n>0 ) {
     189             :       break ;
     190             :     }
     191          54 :     std::vector<Vector> pos( mypdb.getPositions() );
     192          54 :     unsigned nn=1;
     193          54 :     if( pos.size()==0 ) {
     194           0 :       error("read no atoms from file named " + reference );
     195             :     }
     196             :     // This is what we do for the first frame
     197          54 :     if( n==0 ) {
     198          13 :       std::vector<AtomNumber> atoms( mypdb.getAtomNumbers() );
     199          13 :       if( drmsd_type=="DRMSD" ) {
     200         114 :         for(unsigned i=0; i<atoms.size()-1; ++i) {
     201             :           std::string istr;
     202         103 :           Tools::convert( atoms[i].serial(), istr );
     203         748 :           for(unsigned j=i+1; j<atoms.size(); ++j) {
     204             :             std::string jstr;
     205         645 :             Tools::convert( atoms[j].serial(), jstr );
     206         645 :             double distance = delta( pos[i], pos[j] ).modulo();
     207         645 :             if( distance < ucut && distance > lcut ) {
     208             :               std::string num;
     209         386 :               Tools::convert( nn, num );
     210         386 :               nn++;
     211             :               // Add this pair to list of pairs
     212         386 :               upairs.push_back( std::pair<unsigned,unsigned>(i,j) );
     213             :               // Add this distance to list of reference values
     214             :               std::string dstr;
     215         386 :               Tools::convert( distance, dstr );
     216         386 :               refvals.push_back( dstr );
     217             :               // Calculate this distance
     218         386 :               if( nframes==1 ) {
     219         924 :                 allpairs += " ATOMS" + num + "=" + istr + "," + jstr;
     220             :               } else {
     221         390 :                 readInputLine( getShortcutLabel() + "_d" + num + ": DISTANCE ATOMS=" + istr + "," + jstr + " " + pbc_str );
     222             :               }
     223             :             }
     224             :           }
     225             :         }
     226             :       } else {
     227           2 :         unsigned nblocks = mypdb.getNumberOfAtomBlocks();
     228           2 :         std::vector<unsigned> blocks( 1 + nblocks );
     229           2 :         if( nblocks==1 ) {
     230           0 :           blocks[0]=0;
     231           0 :           blocks[1]=atoms.size();
     232             :         } else {
     233           2 :           blocks[0]=0;
     234           6 :           for(unsigned i=0; i<nblocks; ++i) {
     235           4 :             blocks[i+1]=mypdb.getAtomBlockEnds()[i];
     236             :           }
     237             :         }
     238           2 :         if( drmsd_type=="INTRA-DRMSD" ) {
     239           3 :           for(unsigned i=0; i<nblocks; ++i) {
     240           7 :             for(unsigned iatom=blocks[i]+1; iatom<blocks[i+1]; ++iatom) {
     241             :               std::string istr;
     242           5 :               Tools::convert( atoms[iatom].serial(), istr );
     243          14 :               for(unsigned jatom=blocks[i]; jatom<iatom; ++jatom) {
     244             :                 std::string jstr;
     245           9 :                 Tools::convert( atoms[jatom].serial(), jstr );
     246           9 :                 double distance = delta( pos[iatom], pos[jatom] ).modulo();
     247           9 :                 if(distance < ucut && distance > lcut ) {
     248             :                   std::string num;
     249           9 :                   Tools::convert( nn, num );
     250           9 :                   nn++;
     251             :                   // Add this pair to list of pairs
     252           9 :                   upairs.push_back( std::pair<unsigned,unsigned>(iatom,jatom) );
     253             :                   // Add this distance to list of reference values
     254             :                   std::string dstr;
     255           9 :                   Tools::convert( distance, dstr );
     256           9 :                   refvals.push_back( dstr );
     257             :                   // Calculate this distance
     258           9 :                   if( nframes==1 ) {
     259          27 :                     allpairs += " ATOMS" + num + "=" + istr + "," + jstr;
     260             :                   } else {
     261           0 :                     readInputLine( getShortcutLabel() + "_d" + num + ": DISTANCE ATOMS=" + istr + "," + jstr + " " + pbc_str );
     262             :                   }
     263             :                 }
     264             :               }
     265             :             }
     266             :           }
     267           1 :         } else if( drmsd_type=="INTER-DRMSD" ) {
     268           2 :           for(unsigned i=1; i<nblocks; ++i) {
     269           2 :             for(unsigned j=0; j<i; ++j) {
     270           4 :               for(unsigned iatom=blocks[i]; iatom<blocks[i+1]; ++iatom) {
     271             :                 std::string istr;
     272           3 :                 Tools::convert( atoms[iatom].serial(), istr );
     273          15 :                 for(unsigned jatom=blocks[j]; jatom<blocks[j+1]; ++jatom) {
     274             :                   std::string jstr;
     275          12 :                   Tools::convert( atoms[jatom].serial(), jstr );
     276          12 :                   double distance = delta( pos[iatom], pos[jatom] ).modulo();
     277          12 :                   if(distance < ucut && distance > lcut ) {
     278             :                     std::string num;
     279          12 :                     Tools::convert( nn, num );
     280          12 :                     nn++;
     281             :                     // Add this pair to list of pairs
     282          12 :                     upairs.push_back( std::pair<unsigned,unsigned>(iatom,jatom) );
     283             :                     // Add this distance to list of reference values
     284             :                     std::string dstr;
     285          12 :                     Tools::convert( distance, dstr );
     286          12 :                     refvals.push_back( dstr );
     287             :                     // Calculate this distance
     288          12 :                     if( nframes==1 ) {
     289          36 :                       allpairs += " ATOMS" + num + "=" + istr + "," + jstr;
     290             :                     } else {
     291           0 :                       readInputLine( getShortcutLabel() + "_d" + num + ": DISTANCE ATOMS=" + istr + "," + jstr + " " + pbc_str );
     292             :                     }
     293             :                   }
     294             :                 }
     295             :               }
     296             :             }
     297             :           }
     298             :         } else {
     299           0 :           plumed_merror( drmsd_type + " is not valid input to TYPE keyword");
     300             :         }
     301           2 :       }
     302             :       // This is for every subsequent frame
     303          13 :     } else {
     304        3239 :       for(unsigned i=0; i<refvals.size(); ++i) {
     305             :         std::string dstr;
     306        3198 :         Tools::convert( delta( pos[upairs[i].first], pos[upairs[i].second] ).modulo(), dstr );
     307        6396 :         refvals[i] += "," + dstr;
     308             :       }
     309             :     }
     310          54 :     n++;
     311          56 :   }
     312             :   // Now create values that hold all the reference distances
     313          13 :   fclose(fp);
     314             : 
     315          13 :   if( refvals.empty() ) {
     316           2 :     plumed_merror("no pairs of atoms are within the cutoffs for the reference structure, no distances can be computed");
     317             :   }
     318          12 :   if( nframes==1 ) {
     319          33 :     readInputLine( getShortcutLabel() + "_d: DISTANCE" + allpairs + " " + pbc_str );
     320          11 :     std::string refstr = refvals[0];
     321         329 :     for(unsigned i=1; i<refvals.size(); ++i) {
     322         636 :       refstr += "," + refvals[i];
     323             :     }
     324          11 :     readInputLine( getShortcutLabel() + "_ref: CONSTANT VALUES="  + refstr );
     325          33 :     readInputLine( getShortcutLabel() + "_diffs: CUSTOM ARG=" + getShortcutLabel() + "_d," + getShortcutLabel() + "_ref FUNC=(x-y)*(x-y) PERIODIC=NO");
     326          33 :     readInputLine( getShortcutLabel() + "_u: SUM ARG=" + getShortcutLabel() + "_diffs PERIODIC=NO");
     327             :   } else {
     328             :     std::string arg_str1, arg_str2;
     329          79 :     for(unsigned i=0; i<refvals.size(); ++i ) {
     330             :       std::string inum;
     331          78 :       Tools::convert( i+1, inum );
     332         156 :       readInputLine( getShortcutLabel() + "_ref" + inum + ": CONSTANT VALUES=" + refvals[i] );
     333          78 :       if( i==0 ) {
     334           1 :         arg_str1 = getShortcutLabel() + "_d" + inum;
     335           2 :         arg_str2 = getShortcutLabel() + "_ref" + inum;
     336             :       } else {
     337         154 :         arg_str1 += "," + getShortcutLabel() + "_d" + inum;
     338         231 :         arg_str2 += "," + getShortcutLabel() + "_ref" + inum;
     339             :       }
     340             :     }
     341             :     // And calculate the euclidean distances between the true distances and the references
     342           3 :     readInputLine( getShortcutLabel() + "_u: EUCLIDEAN_DISTANCE SQUARED ARG2=" + arg_str1 + " ARG1=" + arg_str2 );
     343             :   }
     344             :   // And final value
     345             :   std::string nvals;
     346          12 :   Tools::convert( refvals.size(), nvals );
     347             :   bool squared;
     348          12 :   parseFlag("SQUARED",squared);
     349          12 :   if( squared ) {
     350          12 :     readInputLine( getShortcutLabel() + ": CUSTOM ARG=" + getShortcutLabel() + "_u FUNC=x/" + nvals + " PERIODIC=NO");
     351             :   } else {
     352          27 :     readInputLine( getShortcutLabel() + "_2: CUSTOM ARG=" + getShortcutLabel() + "_u FUNC=(x/" + nvals + ") PERIODIC=NO");
     353          27 :     readInputLine( getShortcutLabel() + ": CUSTOM ARG=" + getShortcutLabel() + "_2 FUNC=sqrt(x) PERIODIC=NO");
     354             :   }
     355         359 : }
     356             : 
     357             : }
     358             : }
     359             : 
     360             : 

Generated by: LCOV version 1.16