LCOV - code coverage report
Current view: top level - colvar - PCARMSD.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 109 115 94.8 %
Date: 2026-03-30 13:16:06 Functions: 7 8 87.5 %

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

Generated by: LCOV version 1.16