LCOV - code coverage report
Current view: top level - colvar - PCARMSD.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 108 114 94.7 %
Date: 2025-11-25 13:55:50 Functions: 4 5 80.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2014-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 "Colvar.h"
      23             : #include "core/PlumedMain.h"
      24             : #include "core/ActionRegister.h"
      25             : #include "tools/PDB.h"
      26             : #include "tools/RMSD.h"
      27             : #include "tools/Tools.h"
      28             : 
      29             : namespace PLMD {
      30             : namespace colvar {
      31             : 
      32             : class PCARMSD : public Colvar {
      33             : 
      34             :   std::unique_ptr<PLMD::RMSD> rmsd;
      35             :   bool squared;
      36             :   bool nopbc;
      37             :   std::vector< std::vector<Vector> > eigenvectors;
      38             :   std::vector<PDB> pdbv;
      39             :   std::vector<std::string> pca_names;
      40             : public:
      41             :   explicit PCARMSD(const ActionOptions&);
      42             :   void calculate() override;
      43             :   static void registerKeywords(Keywords& keys);
      44             : };
      45             : 
      46             : //+PLUMEDOC DCOLVAR PCARMSD
      47             : /*
      48             : Calculate the PCA components for a number of provided eigenvectors and an average structure.
      49             : 
      50             : For information on this method ( see \cite Sutto:2010 and \cite spiwok ). Performs optimal alignment at every step and reports the rmsd so you know if you are far or close from the average structure.
      51             : It takes the average structure and eigenvectors in form of a pdb.
      52             : Note that beta and occupancy values in the pdb are neglected and all the weights are placed to 1 (differently from the RMSD colvar for example)
      53             : 
      54             : \par Examples
      55             : 
      56             : \plumedfile
      57             : PCARMSD AVERAGE=file.pdb EIGENVECTORS=eigenvectors.pdb
      58             : \endplumedfile
      59             : 
      60             : The input is taken so to be compatible with the output you get from g_covar utility of gromacs (suitably adapted to have a pdb input format).
      61             : The reference configuration (file.pdb) will thus be in a file that looks something like this:
      62             : 
      63             : \auxfile{file.pdb}
      64             : TITLE     Average structure
      65             : MODEL        1
      66             : ATOM      1  CL  ALA     1       1.042  -3.070   0.946  1.00  0.00
      67             : ATOM      5  CLP ALA     1       0.416  -2.033   0.132  1.00  0.00
      68             : ATOM      6  OL  ALA     1       0.415  -2.082  -0.976  1.00  0.00
      69             : ATOM      7  NL  ALA     1      -0.134  -1.045   0.677  1.00  0.00
      70             : ATOM      9  CA  ALA     1      -0.774   0.053   0.003  1.00  0.00
      71             : TER
      72             : ENDMDL
      73             : \endauxfile
      74             : 
      75             : while the eigenvectors will be in a pdb file (eigenvectors.pdb) that looks something like this:
      76             : 
      77             : \auxfile{eigenvectors.pdb}
      78             : TITLE     frame t= -1.000
      79             : MODEL        1
      80             : ATOM      1  CL  ALA     1       1.194  -2.988   0.724  1.00  0.00
      81             : ATOM      5  CLP ALA     1      -0.996   0.042   0.144  1.00  0.00
      82             : ATOM      6  OL  ALA     1      -1.246  -0.178  -0.886  1.00  0.00
      83             : ATOM      7  NL  ALA     1      -2.296   0.272   0.934  1.00  0.00
      84             : ATOM      9  CA  ALA     1      -0.436   2.292   0.814  1.00  0.00
      85             : TER
      86             : ENDMDL
      87             : TITLE     frame t= 0.000
      88             : MODEL        1
      89             : ATOM      1  CL  ALA     1       1.042  -3.070   0.946  1.00  0.00
      90             : ATOM      5  CLP ALA     1      -0.774   0.053   0.003  1.00  0.00
      91             : ATOM      6  OL  ALA     1      -0.849  -0.166  -1.034  1.00  0.00
      92             : ATOM      7  NL  ALA     1      -2.176   0.260   0.563  1.00  0.00
      93             : ATOM      9  CA  ALA     1       0.314   1.825   0.962  1.00  0.00
      94             : TER
      95             : ENDMDL
      96             : 
      97             : \endauxfile
      98             : 
      99             : */
     100             : //+ENDPLUMEDOC
     101             : 
     102             : PLUMED_REGISTER_ACTION(PCARMSD,"PCARMSD")
     103             : 
     104           4 : void PCARMSD::registerKeywords(Keywords& keys) {
     105           4 :   Colvar::registerKeywords(keys);
     106           8 :   keys.add("compulsory","AVERAGE","a file in pdb format containing the reference structure and the atoms involved in the CV.");
     107           8 :   keys.add("compulsory","EIGENVECTORS","a file in pdb format containing the reference structure and the atoms involved in the CV.");
     108           8 :   keys.addOutputComponent("eig","default","the projections on each eigenvalue are stored on values labeled eig-1, eig-2, ...");
     109           8 :   keys.addOutputComponent("residual","default","the distance of the present configuration from the configuration supplied as AVERAGE in terms of mean squared displacement after optimal alignment ");
     110           8 :   keys.addFlag("SQUARED_ROOT",false," This should be set if you want RMSD instead of mean squared displacement ");
     111           4 : }
     112             : 
     113           2 : PCARMSD::PCARMSD(const ActionOptions&ao):
     114             :   PLUMED_COLVAR_INIT(ao),
     115           2 :   squared(true),
     116           2 :   nopbc(false) {
     117             :   std::string f_average;
     118           4 :   parse("AVERAGE",f_average);
     119             :   std::string type;
     120           2 :   type.assign("OPTIMAL");
     121             :   std::string f_eigenvectors;
     122           2 :   parse("EIGENVECTORS",f_eigenvectors);
     123             :   bool sq;
     124           2 :   parseFlag("SQUARED_ROOT",sq);
     125           2 :   if (sq) {
     126           0 :     squared=false;
     127             :   }
     128           2 :   parseFlag("NOPBC",nopbc);
     129           2 :   checkRead();
     130             : 
     131           2 :   PDB pdb;
     132             : 
     133             :   // read everything in ang and transform to nm if we are not in natural units
     134           2 :   if( !pdb.read(f_average,usingNaturalUnits(),0.1/getUnits().getLength()) ) {
     135           0 :     error("missing input file " + f_average );
     136             :   }
     137             : 
     138           2 :   rmsd=Tools::make_unique<RMSD>();
     139             :   bool remove_com=true;
     140             :   bool normalize_weights=true;
     141             :   // here align and displace are a simple vector of ones
     142             :   std::vector<double> align;
     143           2 :   align=pdb.getOccupancy();
     144          24 :   for(unsigned i=0; i<align.size(); i++) {
     145          22 :     align[i]=1.;
     146             :   } ;
     147             :   std::vector<double> displace;
     148           2 :   displace=pdb.getBeta();
     149          24 :   for(unsigned i=0; i<displace.size(); i++) {
     150          22 :     displace[i]=1.;
     151             :   } ;
     152             :   // reset again to reimpose unifrom weights (safe to disable this)
     153           2 :   rmsd->set(align,displace,pdb.getPositions(),type,remove_com,normalize_weights);
     154           2 :   requestAtoms( pdb.getAtomNumbers() );
     155             : 
     156           4 :   addComponentWithDerivatives("residual");
     157           2 :   componentIsNotPeriodic("residual");
     158             : 
     159           2 :   log.printf("  average from file %s\n",f_average.c_str());
     160           2 :   log.printf("  which contains %d atoms\n",getNumberOfAtoms());
     161           2 :   log.printf("  with indices : ");
     162          24 :   for(unsigned i=0; i<pdb.getAtomNumbers().size(); ++i) {
     163          22 :     if(i%25==0) {
     164           2 :       log<<"\n";
     165             :     }
     166          22 :     log.printf("%d ",pdb.getAtomNumbers()[i].serial());
     167             :   }
     168           2 :   log.printf("\n");
     169           2 :   log.printf("  method for alignment : %s \n",type.c_str() );
     170           2 :   if(nopbc) {
     171           1 :     log.printf("  without periodic boundary conditions\n");
     172             :   } else {
     173           1 :     log.printf("  using periodic boundary conditions\n");
     174             :   }
     175             : 
     176           4 :   log<<"  Bibliography "<<plumed.cite("Spiwok, Lipovova and Kralova, JPCB, 111, 3073 (2007)  ");
     177           4 :   log<<" "<<plumed.cite( "Sutto, D'Abramo, Gervasio, JCTC, 6, 3640 (2010)");
     178             : 
     179             :   unsigned neigenvects;
     180           2 :   neigenvects=0;
     181             :   // now get the eigenvectors
     182             :   // open the file
     183           2 :   if (FILE* fp=this->fopen(f_eigenvectors.c_str(),"r")) {
     184             : // call fclose when exiting this block
     185           2 :     auto deleter=[this](FILE* f) {
     186           2 :       this->fclose(f);
     187           2 :     };
     188             :     std::unique_ptr<FILE,decltype(deleter)> fp_deleter(fp,deleter);
     189             : 
     190             :     std::vector<AtomNumber> aaa;
     191           2 :     log<<"  Opening the eigenvectors file "<<f_eigenvectors.c_str()<<"\n";
     192             :     bool do_read=true;
     193             :     unsigned nat=0;
     194          72 :     while (do_read) {
     195          72 :       PDB mypdb;
     196             :       // check the units for reading this file: how can they make sense?
     197          72 :       do_read=mypdb.readFromFilepointer(fp,usingNaturalUnits(),0.1/getUnits().getLength());
     198          72 :       if(do_read) {
     199          70 :         neigenvects++;
     200          70 :         if(mypdb.getAtomNumbers().size()==0) {
     201           0 :           error("number of atoms in a frame should be more than zero");
     202             :         }
     203          70 :         if(nat==0) {
     204           2 :           nat=mypdb.getAtomNumbers().size();
     205             :         }
     206          70 :         if(nat!=mypdb.getAtomNumbers().size()) {
     207           0 :           error("frames should have the same number of atoms");
     208             :         }
     209          70 :         if(aaa.empty()) {
     210           2 :           aaa=mypdb.getAtomNumbers();
     211             :         }
     212         140 :         if(aaa!=mypdb.getAtomNumbers()) {
     213           0 :           error("frames should contain same atoms in same order");
     214             :         }
     215          70 :         log<<"  Found eigenvector: "<<neigenvects<<" containing  "<<mypdb.getAtomNumbers().size()<<" atoms\n";
     216          70 :         pdbv.push_back(mypdb);
     217          70 :         eigenvectors.push_back(mypdb.getPositions());
     218             :       } else {
     219             :         break ;
     220             :       }
     221          72 :     }
     222           2 :     log<<"  Found total "<<neigenvects<< " eigenvectors in the file "<<f_eigenvectors.c_str()<<" \n";
     223           2 :     if(neigenvects==0) {
     224           0 :       error("at least one eigenvector is expected");
     225             :     }
     226           2 :   }
     227             :   // the components
     228          72 :   for(unsigned i=0; i<neigenvects; i++) {
     229             :     std::string num;
     230          70 :     Tools::convert( i, num );
     231             :     std::string name;
     232         140 :     name=std::string("eig-")+num;
     233          70 :     pca_names.push_back(name);
     234          70 :     addComponentWithDerivatives(name);
     235          70 :     componentIsNotPeriodic(name);
     236             :   }
     237           2 :   turnOnDerivatives();
     238             : 
     239           4 : }
     240             : 
     241             : // calculator
     242        1092 : void PCARMSD::calculate() {
     243        1092 :   if(!nopbc) {
     244         546 :     makeWhole();
     245             :   }
     246        1092 :   Tensor rotation,invrotation;
     247             :   Matrix<std::vector<Vector> > drotdpos(3,3);
     248             :   std::vector<Vector> alignedpos;
     249             :   std::vector<Vector> centeredpos;
     250             :   std::vector<Vector> centeredref;
     251             :   std::vector<Vector> ddistdpos;
     252        1092 :   double r=rmsd->calc_PCAelements( getPositions(), ddistdpos, rotation,  drotdpos, alignedpos,centeredpos, centeredref,squared);
     253        1092 :   invrotation=rotation.transpose();
     254             : 
     255        2184 :   Value* verr=getPntrToComponent("residual");
     256             :   verr->set(r);
     257       13104 :   for(unsigned iat=0; iat<getNumberOfAtoms(); iat++) {
     258       12012 :     setAtomsDerivatives (verr,iat,ddistdpos[iat]);
     259             :   }
     260             : 
     261             :   std::vector< Vector > der;
     262        1092 :   der.resize(getNumberOfAtoms());
     263             : 
     264             : 
     265       39312 :   for(unsigned i=0; i<eigenvectors.size(); i++) {
     266       38220 :     Value* value=getPntrToComponent(pca_names[i].c_str());
     267             :     double val;
     268             :     val=0.;
     269      458640 :     for(unsigned iat=0; iat<getNumberOfAtoms(); iat++) {
     270      420420 :       val+=dotProduct(alignedpos[iat]-centeredref[iat],eigenvectors[i][iat]);
     271      420420 :       der[iat].zero();
     272             :     }
     273             :     value->set(val);
     274             :     // here the loop is reversed to better suit the structure of the derivative of the rotation matrix
     275             :     double tmp1;
     276      152880 :     for(unsigned a=0; a<3; a++) {
     277      458640 :       for(unsigned b=0; b<3; b++) {
     278             :         tmp1=0.;
     279     4127760 :         for(unsigned n=0; n<getNumberOfAtoms(); n++) {
     280     3783780 :           tmp1+=centeredpos[n][b]*eigenvectors[i][n][a];
     281             :         }
     282     4127760 :         for(unsigned iat=0; iat<getNumberOfAtoms(); iat++) {
     283     3783780 :           der[iat]+=drotdpos[a][b][iat]*tmp1;
     284             :         }
     285             :       }
     286             :     }
     287       38220 :     Vector v1;
     288      458640 :     for(unsigned n=0; n<getNumberOfAtoms(); n++) {
     289      420420 :       v1+=(1./getNumberOfAtoms())*matmul(invrotation,eigenvectors[i][n]);
     290             :     }
     291      458640 :     for(unsigned iat=0; iat<getNumberOfAtoms(); iat++) {
     292      420420 :       der[iat]+=matmul(invrotation,eigenvectors[i][iat])-v1;
     293      420420 :       setAtomsDerivatives (value,iat,der[iat]);
     294             :     }
     295             :   }
     296             : 
     297       40404 :   for(int i=0; i<getNumberOfComponents(); ++i) {
     298       39312 :     setBoxDerivativesNoPbc( getPntrToComponent(i) );
     299             :   }
     300             : 
     301        1092 : }
     302             : 
     303             : }
     304             : }
     305             : 
     306             : 
     307             : 

Generated by: LCOV version 1.16