LCOV - code coverage report
Current view: top level - colvar - PCARMSD.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 93 94 98.9 %
Date: 2024-10-18 14:00:25 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             : {
     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           2 :   bool sq;  parseFlag("SQUARED_ROOT",sq);
     125           2 :   if (sq) { squared=false; }
     126           2 :   parseFlag("NOPBC",nopbc);
     127           2 :   checkRead();
     128             : 
     129           2 :   PDB pdb;
     130             : 
     131             :   // read everything in ang and transform to nm if we are not in natural units
     132           2 :   if( !pdb.read(f_average,usingNaturalUnits(),0.1/getUnits().getLength()) )
     133           0 :     error("missing input file " + f_average );
     134             : 
     135           2 :   rmsd=Tools::make_unique<RMSD>();
     136             :   bool remove_com=true;
     137             :   bool normalize_weights=true;
     138             :   // here align and displace are a simple vector of ones
     139          24 :   std::vector<double> align; align=pdb.getOccupancy(); for(unsigned i=0; i<align.size(); i++) {align[i]=1.;} ;
     140          24 :   std::vector<double> displace;  displace=pdb.getBeta(); for(unsigned i=0; i<displace.size(); i++) {displace[i]=1.;} ;
     141             :   // reset again to reimpose unifrom weights (safe to disable this)
     142           2 :   rmsd->set(align,displace,pdb.getPositions(),type,remove_com,normalize_weights);
     143           2 :   requestAtoms( pdb.getAtomNumbers() );
     144             : 
     145           4 :   addComponentWithDerivatives("residual"); componentIsNotPeriodic("residual");
     146             : 
     147           2 :   log.printf("  average from file %s\n",f_average.c_str());
     148           2 :   log.printf("  which contains %d atoms\n",getNumberOfAtoms());
     149           2 :   log.printf("  with indices : ");
     150          24 :   for(unsigned i=0; i<pdb.getAtomNumbers().size(); ++i) {
     151          22 :     if(i%25==0) log<<"\n";
     152          22 :     log.printf("%d ",pdb.getAtomNumbers()[i].serial());
     153             :   }
     154           2 :   log.printf("\n");
     155           2 :   log.printf("  method for alignment : %s \n",type.c_str() );
     156           2 :   if(nopbc) log.printf("  without periodic boundary conditions\n");
     157           1 :   else      log.printf("  using periodic boundary conditions\n");
     158             : 
     159           4 :   log<<"  Bibliography "<<plumed.cite("Spiwok, Lipovova and Kralova, JPCB, 111, 3073 (2007)  ");
     160           4 :   log<<" "<<plumed.cite( "Sutto, D'Abramo, Gervasio, JCTC, 6, 3640 (2010)");
     161             : 
     162             :   unsigned neigenvects;
     163           2 :   neigenvects=0;
     164             :   // now get the eigenvectors
     165             :   // open the file
     166           2 :   if (FILE* fp=this->fopen(f_eigenvectors.c_str(),"r"))
     167             :   {
     168             : // call fclose when exiting this block
     169           2 :     auto deleter=[this](FILE* f) { this->fclose(f); };
     170             :     std::unique_ptr<FILE,decltype(deleter)> fp_deleter(fp,deleter);
     171             : 
     172             :     std::vector<AtomNumber> aaa;
     173           2 :     log<<"  Opening the eigenvectors file "<<f_eigenvectors.c_str()<<"\n";
     174             :     bool do_read=true;
     175             :     unsigned nat=0;
     176          72 :     while (do_read) {
     177          72 :       PDB mypdb;
     178             :       // check the units for reading this file: how can they make sense?
     179          72 :       do_read=mypdb.readFromFilepointer(fp,usingNaturalUnits(),0.1/getUnits().getLength());
     180          72 :       if(do_read) {
     181          70 :         neigenvects++;
     182          70 :         if(mypdb.getAtomNumbers().size()==0) error("number of atoms in a frame should be more than zero");
     183          70 :         if(nat==0) nat=mypdb.getAtomNumbers().size();
     184          70 :         if(nat!=mypdb.getAtomNumbers().size()) error("frames should have the same number of atoms");
     185          70 :         if(aaa.empty()) aaa=mypdb.getAtomNumbers();
     186         140 :         if(aaa!=mypdb.getAtomNumbers()) error("frames should contain same atoms in same order");
     187          70 :         log<<"  Found eigenvector: "<<neigenvects<<" containing  "<<mypdb.getAtomNumbers().size()<<" atoms\n";
     188          70 :         pdbv.push_back(mypdb);
     189          70 :         eigenvectors.push_back(mypdb.getPositions());
     190             :       } else {break ;}
     191          72 :     }
     192           2 :     log<<"  Found total "<<neigenvects<< " eigenvectors in the file "<<f_eigenvectors.c_str()<<" \n";
     193           2 :     if(neigenvects==0) error("at least one eigenvector is expected");
     194           2 :   }
     195             :   // the components
     196          72 :   for(unsigned i=0; i<neigenvects; i++) {
     197          70 :     std::string num; Tools::convert( i, num );
     198         140 :     std::string name; name=std::string("eig-")+num;
     199          70 :     pca_names.push_back(name);
     200         140 :     addComponentWithDerivatives(name); componentIsNotPeriodic(name);
     201             :   }
     202           2 :   turnOnDerivatives();
     203             : 
     204           4 : }
     205             : 
     206             : // calculator
     207        1092 : void PCARMSD::calculate() {
     208        1092 :   if(!nopbc) makeWhole();
     209        1092 :   Tensor rotation,invrotation;
     210             :   Matrix<std::vector<Vector> > drotdpos(3,3);
     211             :   std::vector<Vector> alignedpos;
     212             :   std::vector<Vector> centeredpos;
     213             :   std::vector<Vector> centeredref;
     214             :   std::vector<Vector> ddistdpos;
     215        1092 :   double r=rmsd->calc_PCAelements( getPositions(), ddistdpos, rotation,  drotdpos, alignedpos,centeredpos, centeredref,squared);
     216        1092 :   invrotation=rotation.transpose();
     217             : 
     218        2184 :   Value* verr=getPntrToComponent("residual");
     219             :   verr->set(r);
     220       13104 :   for(unsigned iat=0; iat<getNumberOfAtoms(); iat++) {
     221       12012 :     setAtomsDerivatives (verr,iat,ddistdpos[iat]);
     222             :   }
     223             : 
     224             :   std::vector< Vector > der;
     225        1092 :   der.resize(getNumberOfAtoms());
     226             : 
     227             : 
     228       39312 :   for(unsigned i=0; i<eigenvectors.size(); i++) {
     229       38220 :     Value* value=getPntrToComponent(pca_names[i].c_str());
     230             :     double val; val=0.;
     231      458640 :     for(unsigned iat=0; iat<getNumberOfAtoms(); iat++) {
     232      420420 :       val+=dotProduct(alignedpos[iat]-centeredref[iat],eigenvectors[i][iat]);   der[iat].zero();
     233             :     }
     234             :     value->set(val);
     235             :     // here the loop is reversed to better suit the structure of the derivative of the rotation matrix
     236             :     double tmp1;
     237      152880 :     for(unsigned a=0; a<3; a++) {
     238      458640 :       for(unsigned b=0; b<3; b++) {
     239             :         tmp1=0.;
     240     4127760 :         for(unsigned n=0; n<getNumberOfAtoms(); n++) {
     241     3783780 :           tmp1+=centeredpos[n][b]*eigenvectors[i][n][a];
     242             :         }
     243     4127760 :         for(unsigned iat=0; iat<getNumberOfAtoms(); iat++) {
     244     3783780 :           der[iat]+=drotdpos[a][b][iat]*tmp1;
     245             :         }
     246             :       }
     247             :     }
     248       38220 :     Vector v1;
     249      458640 :     for(unsigned n=0; n<getNumberOfAtoms(); n++) {
     250      420420 :       v1+=(1./getNumberOfAtoms())*matmul(invrotation,eigenvectors[i][n]);
     251             :     }
     252      458640 :     for(unsigned iat=0; iat<getNumberOfAtoms(); iat++) {
     253      420420 :       der[iat]+=matmul(invrotation,eigenvectors[i][iat])-v1;
     254      420420 :       setAtomsDerivatives (value,iat,der[iat]);
     255             :     }
     256             :   }
     257             : 
     258       40404 :   for(int i=0; i<getNumberOfComponents(); ++i) setBoxDerivativesNoPbc( getPntrToComponent(i) );
     259             : 
     260        1092 : }
     261             : 
     262             : }
     263             : }
     264             : 
     265             : 
     266             : 

Generated by: LCOV version 1.16