LCOV - code coverage report
Current view: top level - colvar - PCARMSD.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 110 116 94.8 %
Date: 2025-03-25 09:33:27 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             : Information about this method can be found in the reference papers in the bibliography below.  An example input is provided below:
      51             : 
      52             : ```plumed
      53             : #SETTINGS INPUTFILES=regtest/trajectories/pca/average.pdb,regtest/trajectories/pca/eigenvec.pdb
      54             : PCARMSD ...
      55             :   AVERAGE=regtest/trajectories/pca/average.pdb
      56             :   EIGENVECTORS=regtest/trajectories/pca/eigenvec.pdb
      57             : ...
      58             : ```
      59             : 
      60             : This input performs optimal alignment at every step and reports the rmsd so you know if you are far or close from the average structure.
      61             : It takes the average structure and eigenvectors in form of a pdb.
      62             : 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)
      63             : 
      64             : */
      65             : //+ENDPLUMEDOC
      66             : 
      67             : PLUMED_REGISTER_ACTION(PCARMSD,"PCARMSD")
      68             : 
      69           4 : void PCARMSD::registerKeywords(Keywords& keys) {
      70           4 :   Colvar::registerKeywords(keys);
      71           4 :   keys.add("compulsory","AVERAGE","a file in pdb format containing the reference structure and the atoms involved in the CV.");
      72           4 :   keys.add("compulsory","EIGENVECTORS","a file in pdb format containing the reference structure and the atoms involved in the CV.");
      73           8 :   keys.addOutputComponent("eig","default","scalar","the projections on each eigenvalue are stored on values labeled eig-1, eig-2, ...");
      74           8 :   keys.addOutputComponent("residual","default","scalar","the distance of the present configuration from the configuration supplied as AVERAGE in terms of mean squared displacement after optimal alignment ");
      75           4 :   keys.addFlag("SQUARED_ROOT",false," This should be set if you want RMSD instead of mean squared displacement ");
      76           4 :   keys.addDOI("10.1021/ct100413b");
      77           4 :   keys.addDOI("10.1021/jp068587c");
      78           4 : }
      79             : 
      80           2 : PCARMSD::PCARMSD(const ActionOptions&ao):
      81             :   PLUMED_COLVAR_INIT(ao),
      82           2 :   squared(true),
      83           2 :   nopbc(false) {
      84             :   std::string f_average;
      85           4 :   parse("AVERAGE",f_average);
      86             :   std::string type;
      87           2 :   type.assign("OPTIMAL");
      88             :   std::string f_eigenvectors;
      89           2 :   parse("EIGENVECTORS",f_eigenvectors);
      90             :   bool sq;
      91           2 :   parseFlag("SQUARED_ROOT",sq);
      92           2 :   if (sq) {
      93           0 :     squared=false;
      94             :   }
      95           2 :   parseFlag("NOPBC",nopbc);
      96           2 :   checkRead();
      97             : 
      98           2 :   PDB pdb;
      99             : 
     100             :   // read everything in ang and transform to nm if we are not in natural units
     101           2 :   if( !pdb.read(f_average,usingNaturalUnits(),0.1/getUnits().getLength()) ) {
     102           0 :     error("missing input file " + f_average );
     103             :   }
     104             : 
     105           2 :   rmsd=Tools::make_unique<RMSD>();
     106             :   bool remove_com=true;
     107             :   bool normalize_weights=true;
     108             :   // here align and displace are a simple vector of ones
     109             :   std::vector<double> align;
     110           2 :   align=pdb.getOccupancy();
     111          24 :   for(unsigned i=0; i<align.size(); i++) {
     112          22 :     align[i]=1.;
     113             :   } ;
     114             :   std::vector<double> displace;
     115           2 :   displace=pdb.getBeta();
     116          24 :   for(unsigned i=0; i<displace.size(); i++) {
     117          22 :     displace[i]=1.;
     118             :   } ;
     119             :   // reset again to reimpose unifrom weights (safe to disable this)
     120           2 :   rmsd->set(align,displace,pdb.getPositions(),type,remove_com,normalize_weights);
     121           2 :   requestAtoms( pdb.getAtomNumbers() );
     122             : 
     123           4 :   addComponentWithDerivatives("residual");
     124           2 :   componentIsNotPeriodic("residual");
     125             : 
     126           2 :   log.printf("  average from file %s\n",f_average.c_str());
     127           2 :   log.printf("  which contains %d atoms\n",getNumberOfAtoms());
     128           2 :   log.printf("  with indices : ");
     129          24 :   for(unsigned i=0; i<pdb.getAtomNumbers().size(); ++i) {
     130          22 :     if(i%25==0) {
     131           2 :       log<<"\n";
     132             :     }
     133          22 :     log.printf("%d ",pdb.getAtomNumbers()[i].serial());
     134             :   }
     135           2 :   log.printf("\n");
     136           2 :   log.printf("  method for alignment : %s \n",type.c_str() );
     137           2 :   if(nopbc) {
     138           1 :     log.printf("  without periodic boundary conditions\n");
     139             :   } else {
     140           1 :     log.printf("  using periodic boundary conditions\n");
     141             :   }
     142             : 
     143           4 :   log<<"  Bibliography "<<plumed.cite("Spiwok, Lipovova and Kralova, JPCB, 111, 3073 (2007)  ");
     144           4 :   log<<" "<<plumed.cite( "Sutto, D'Abramo, Gervasio, JCTC, 6, 3640 (2010)");
     145             : 
     146             :   unsigned neigenvects;
     147           2 :   neigenvects=0;
     148             :   // now get the eigenvectors
     149             :   // open the file
     150           2 :   if (FILE* fp=this->fopen(f_eigenvectors.c_str(),"r")) {
     151             : // call fclose when exiting this block
     152           2 :     auto deleter=[this](FILE* f) {
     153           2 :       this->fclose(f);
     154           2 :     };
     155             :     std::unique_ptr<FILE,decltype(deleter)> fp_deleter(fp,deleter);
     156             : 
     157             :     std::vector<AtomNumber> aaa;
     158           2 :     log<<"  Opening the eigenvectors file "<<f_eigenvectors.c_str()<<"\n";
     159             :     bool do_read=true;
     160             :     unsigned nat=0;
     161          72 :     while (do_read) {
     162          72 :       PDB mypdb;
     163             :       // check the units for reading this file: how can they make sense?
     164          72 :       do_read=mypdb.readFromFilepointer(fp,usingNaturalUnits(),0.1/getUnits().getLength());
     165          72 :       if(do_read) {
     166          70 :         neigenvects++;
     167          70 :         if(mypdb.getAtomNumbers().size()==0) {
     168           0 :           error("number of atoms in a frame should be more than zero");
     169             :         }
     170          70 :         if(nat==0) {
     171           2 :           nat=mypdb.getAtomNumbers().size();
     172             :         }
     173          70 :         if(nat!=mypdb.getAtomNumbers().size()) {
     174           0 :           error("frames should have the same number of atoms");
     175             :         }
     176          70 :         if(aaa.empty()) {
     177           2 :           aaa=mypdb.getAtomNumbers();
     178             :         }
     179         140 :         if(aaa!=mypdb.getAtomNumbers()) {
     180           0 :           error("frames should contain same atoms in same order");
     181             :         }
     182          70 :         log<<"  Found eigenvector: "<<neigenvects<<" containing  "<<mypdb.getAtomNumbers().size()<<" atoms\n";
     183          70 :         pdbv.push_back(mypdb);
     184          70 :         eigenvectors.push_back(mypdb.getPositions());
     185             :       } else {
     186             :         break ;
     187             :       }
     188          72 :     }
     189           2 :     log<<"  Found total "<<neigenvects<< " eigenvectors in the file "<<f_eigenvectors.c_str()<<" \n";
     190           2 :     if(neigenvects==0) {
     191           0 :       error("at least one eigenvector is expected");
     192             :     }
     193           2 :   }
     194             :   // the components
     195          72 :   for(unsigned i=0; i<neigenvects; i++) {
     196             :     std::string num;
     197          70 :     Tools::convert( i, num );
     198             :     std::string name;
     199         140 :     name=std::string("eig-")+num;
     200          70 :     pca_names.push_back(name);
     201          70 :     addComponentWithDerivatives(name);
     202          70 :     componentIsNotPeriodic(name);
     203             :   }
     204           2 :   turnOnDerivatives();
     205             : 
     206           4 : }
     207             : 
     208             : // calculator
     209        1092 : void PCARMSD::calculate() {
     210        1092 :   if(!nopbc) {
     211         546 :     makeWhole();
     212             :   }
     213        1092 :   Tensor rotation,invrotation;
     214             :   Matrix<std::vector<Vector> > drotdpos(3,3);
     215             :   std::vector<Vector> alignedpos;
     216             :   std::vector<Vector> centeredpos;
     217             :   std::vector<Vector> centeredref;
     218             :   std::vector<Vector> ddistdpos;
     219        1092 :   double r=rmsd->calc_PCAelements( getPositions(), ddistdpos, rotation,  drotdpos, alignedpos,centeredpos, centeredref,squared);
     220        1092 :   invrotation=rotation.transpose();
     221             : 
     222        2184 :   Value* verr=getPntrToComponent("residual");
     223             :   verr->set(r);
     224       13104 :   for(unsigned iat=0; iat<getNumberOfAtoms(); iat++) {
     225       12012 :     setAtomsDerivatives (verr,iat,ddistdpos[iat]);
     226             :   }
     227             : 
     228             :   std::vector< Vector > der;
     229        1092 :   der.resize(getNumberOfAtoms());
     230             : 
     231             : 
     232       39312 :   for(unsigned i=0; i<eigenvectors.size(); i++) {
     233       38220 :     Value* value=getPntrToComponent(pca_names[i].c_str());
     234             :     double val;
     235             :     val=0.;
     236      458640 :     for(unsigned iat=0; iat<getNumberOfAtoms(); iat++) {
     237      420420 :       val+=dotProduct(alignedpos[iat]-centeredref[iat],eigenvectors[i][iat]);
     238      420420 :       der[iat].zero();
     239             :     }
     240             :     value->set(val);
     241             :     // here the loop is reversed to better suit the structure of the derivative of the rotation matrix
     242             :     double tmp1;
     243      152880 :     for(unsigned a=0; a<3; a++) {
     244      458640 :       for(unsigned b=0; b<3; b++) {
     245             :         tmp1=0.;
     246     4127760 :         for(unsigned n=0; n<getNumberOfAtoms(); n++) {
     247     3783780 :           tmp1+=centeredpos[n][b]*eigenvectors[i][n][a];
     248             :         }
     249     4127760 :         for(unsigned iat=0; iat<getNumberOfAtoms(); iat++) {
     250     3783780 :           der[iat]+=drotdpos[a][b][iat]*tmp1;
     251             :         }
     252             :       }
     253             :     }
     254       38220 :     Vector v1;
     255      458640 :     for(unsigned n=0; n<getNumberOfAtoms(); n++) {
     256      420420 :       v1+=(1./getNumberOfAtoms())*matmul(invrotation,eigenvectors[i][n]);
     257             :     }
     258      458640 :     for(unsigned iat=0; iat<getNumberOfAtoms(); iat++) {
     259      420420 :       der[iat]+=matmul(invrotation,eigenvectors[i][iat])-v1;
     260      420420 :       setAtomsDerivatives (value,iat,der[iat]);
     261             :     }
     262             :   }
     263             : 
     264       40404 :   for(int i=0; i<getNumberOfComponents(); ++i) {
     265       39312 :     setBoxDerivativesNoPbc( getPntrToComponent(i) );
     266             :   }
     267             : 
     268        1092 : }
     269             : 
     270             : }
     271             : }
     272             : 
     273             : 
     274             : 

Generated by: LCOV version 1.16