LCOV - code coverage report
Current view: top level - core - FlexibleBin.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 169 174 97.1 %
Date: 2026-11-05 10:19:04 Functions: 9 9 100.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2012-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 "FlexibleBin.h"
      23             : #include "ActionWithArguments.h"
      24             : #include <cmath>
      25             : #include <vector>
      26             : #include "tools/Matrix.h"
      27             : 
      28             : namespace PLMD {
      29             : 
      30             : 
      31          49 : FlexibleBin::FlexibleBin(int mytype, ActionWithArguments *awargs, double const &d, std::vector<double> &smin, const std::vector<double> &smax):
      32          49 :   type(mytype),
      33          49 :   paction(awargs),
      34          49 :   sigma(d),
      35          49 :   sigmamin(smin),
      36          49 :   sigmamax(smax) {
      37          49 :   paction->log<<"  Limits for sigmas using adaptive hills:  \n";
      38         141 :   for(unsigned i=0; i<paction->getNumberOfArguments(); ++i) {
      39          92 :     paction->log<<"   CV  "<<paction->getPntrToArgument(i)->getName()<<":\n";
      40          92 :     if(sigmamin[i]>0.) {
      41          18 :       limitmin.push_back(true);
      42          18 :       paction->log<<"       Min "<<sigmamin[i];
      43          18 :       sigmamin[i]*=sigmamin[i]; // this is because the matrix which is calculated is the sigmasquared
      44             :     } else {
      45          74 :       limitmin.push_back(false);
      46          74 :       paction->log<<"       Min No ";
      47             :     }
      48          92 :     if(sigmamax[i]>0.) {
      49           4 :       limitmax.push_back(true);
      50           4 :       paction->log<<"       Max "<<sigmamax[i];
      51           4 :       sigmamax[i]*=sigmamax[i];
      52             :     } else {
      53          88 :       limitmax.push_back(false);
      54          88 :       paction->log<<"       Max No ";
      55             :     }
      56          92 :     paction->log<<" \n";
      57             :   }
      58             : 
      59          49 : }
      60             : 
      61             : /// Constructure for 1D FB for PBMETAD
      62          38 : FlexibleBin::FlexibleBin(int mytype, ActionWithArguments *awargs, unsigned iarg,
      63          38 :                          double const &d, std::vector<double> &smin, const std::vector<double> &smax):
      64          38 :   type(mytype),
      65          38 :   paction(awargs),
      66          38 :   sigma(d),
      67          38 :   sigmamin(smin),
      68          38 :   sigmamax(smax) {
      69          38 :   paction->log<<"  Limits for sigmas using adaptive hills:  \n";
      70          38 :   paction->log<<"   CV  "<<paction->getPntrToArgument(iarg)->getName()<<":\n";
      71          38 :   if(sigmamin[0]>0.) {
      72          17 :     limitmin.push_back(true);
      73          17 :     paction->log<<"       Min "<<sigmamin[0];
      74          17 :     sigmamin[0]*=sigmamin[0];
      75             :   } else {
      76          21 :     limitmin.push_back(false);
      77          21 :     paction->log<<"       Min No ";
      78             :   }
      79          38 :   if(sigmamax[0]>0.) {
      80           3 :     limitmax.push_back(true);
      81           3 :     paction->log<<"       Max "<<sigmamax[0];
      82           3 :     sigmamax[0]*=sigmamax[0];
      83             :   } else {
      84          35 :     limitmax.push_back(false);
      85          35 :     paction->log<<"       Max No ";
      86             :   }
      87          38 :   paction->log<<" \n";
      88          38 : }
      89             : 
      90             : /// Update the flexible bin
      91             : /// in case of diffusion based: update at every step
      92             : /// in case of gradient based: update only when you add the hill
      93        8488 : void FlexibleBin::update(bool nowAddAHill) {
      94        8488 :   unsigned ncv=paction->getNumberOfArguments();
      95        8488 :   unsigned dimension=ncv*(ncv+1)/2;
      96        8488 :   std::vector<double> delta;
      97        8488 :   std::vector<double> cv;
      98        8488 :   double decay=1./sigma;
      99             :   // this is done all the times from scratch. It is not an accumulator
     100             :   // here update the flexible bin according to the needs
     101        8488 :   switch (type) {
     102             :   // This should be called every time
     103        5566 :   case diffusion:
     104             :     // if you use this below then the decay is in time units
     105             :     //double decay=paction->getTimeStep()/sigma;
     106             :     // to be consistent with the rest of the program: everything is better to be in timesteps
     107             :     // THE AVERAGE VALUE
     108             :     // beware: the pbc
     109        5566 :     delta.resize(ncv);
     110       14912 :     for(unsigned i=0; i<ncv; i++) {
     111        9346 :       cv.push_back(paction->getArgument(i));
     112             :     }
     113             :     bool restarted;
     114             :     restarted=false;
     115        5566 :     if(average.size()==0) { // initial time: just set the initial vector
     116          19 :       average.resize(ncv);
     117          47 :       for(unsigned i=0; i<ncv; i++) {
     118          28 :         average[i]=cv[i];
     119             :       }
     120          19 :       restarted=(variance.size()>0); // after restartAverage() the variance is kept as it is
     121             :     } else { // accumulate
     122       14865 :       for(unsigned i=0; i<ncv; i++) {
     123        9318 :         delta[i]=paction->difference(i,average[i],cv[i]);
     124        9318 :         average[i]+=decay*delta[i];
     125        9318 :         average[i]=paction->bringBackInPbc(i,average[i]); // equation 8 of "Metadynamics with adaptive Gaussians"
     126             :       }
     127             :     }
     128        5566 :     nsamples++;
     129             :     // THE VARIANCE
     130        5566 :     if(variance.size()==0) {
     131          15 :       variance.resize(dimension,0.); // nonredundant members dimension=ncv*(ncv+1)/2;
     132        5551 :     } else if(!restarted) {
     133             :       unsigned k=0;
     134       14865 :       for(unsigned i=0; i<ncv; i++) {
     135       22407 :         for(unsigned j=i; j<ncv; j++) { // upper diagonal loop
     136       13089 :           variance[k]+=decay*(delta[i]*delta[j]-variance[k]);
     137       13089 :           k++;
     138             :         }
     139             :       }
     140             :     }
     141             :     break;
     142        2922 :   case geometry:
     143             :     //this calculates in variance the \nabla CV_i \dot \nabla CV_j
     144        2922 :     variance.resize(dimension);
     145             :     // now the signal for retrieving the gradients should be already given by checkNeedsGradients.
     146             :     // here just do the projections
     147             :     // note that the call  checkNeedsGradients() in BiasMetaD takes care of switching on the call to gradients
     148        2922 :     if (nowAddAHill) { // geometry is sync with hill deposition
     149             :       unsigned k=0;
     150        2709 :       for(unsigned i=0; i<ncv; i++) {
     151        4515 :         for(unsigned j=i; j<ncv; j++) {
     152             :           // eq 12 of "Metadynamics with adaptive Gaussians"
     153        2709 :           variance[k]=sigma*sigma*(paction->getProjection(i,j));
     154        2709 :           k++;
     155             :         }
     156             :       }
     157             :     }
     158             :     break;
     159           0 :   default:
     160           0 :     plumed_merror("This flexible bin method is not recognized");
     161             :   }
     162        8488 : }
     163             : 
     164          12 : void FlexibleBin::restartAverage() {
     165          12 :   plumed_massert(type==diffusion,"the average can be restarted only for diffusion-based adaptive hills");
     166          12 :   average.clear();
     167          12 : }
     168             : 
     169          15 : void FlexibleBin::setStatistics(const std::vector<double>& av, const std::vector<double>& var, unsigned ns) {
     170          15 :   plumed_massert(type==diffusion,"the statistics can be set only for diffusion-based adaptive hills");
     171          15 :   const unsigned ncv=paction->getNumberOfArguments();
     172             :   // (one dimensional, for one argument of PBMETAD, or for all the arguments)
     173          15 :   plumed_assert((av.size()==1 && var.size()==1) || (av.size()==ncv && var.size()==ncv*(ncv+1)/2));
     174          15 :   average=av;
     175          15 :   variance=var;
     176          15 :   nsamples=ns;
     177          15 : }
     178             : 
     179          18 : std::vector<double> FlexibleBin::getMatrix() const {
     180          18 :   return variance;
     181             : }
     182             : 
     183             : /// Update the flexible bin for PBMetaD like FlexBin
     184             : /// in case of diffusion based: update at every step
     185             : /// in case of gradient based: update only when you add the hill
     186        8644 : void FlexibleBin::update(bool nowAddAHill, unsigned iarg) {
     187             :   // this is done all the times from scratch. It is not an accumulator
     188             :   // here update the flexible bin according to the needs
     189        8644 :   std::vector<double> cv;
     190        8644 :   std::vector<double> delta;
     191             :   // if you use this below then the decay is in time units
     192             :   // to be consistent with the rest of the program: everything is better to be in timesteps
     193        8644 :   double decay=1./sigma;
     194        8644 :   switch (type) {
     195             :   // This should be called every time
     196        7006 :   case diffusion:
     197             :     // THE AVERAGE VALUE
     198        7006 :     delta.resize(1);
     199        7006 :     cv.push_back(paction->getArgument(iarg));
     200             :     bool restarted;
     201             :     restarted=false;
     202        7006 :     if(average.size()==0) { // initial time: just set the initial vector
     203          37 :       average.resize(1);
     204          37 :       average[0]=cv[0];
     205          37 :       restarted=(variance.size()>0); // after restartAverage() the variance is kept as it is
     206             :     } else { // accumulate
     207        6969 :       delta[0]=paction->difference(iarg,average[0],cv[0]);
     208        6969 :       average[0]+=decay*delta[0];
     209        6969 :       average[0]=paction->bringBackInPbc(iarg,average[0]); // equation 8 of "Metadynamics with adaptive Gaussians"
     210             :     }
     211        7006 :     nsamples++;
     212             :     // THE VARIANCE
     213        7006 :     if(variance.size()==0) {
     214          29 :       variance.resize(1,0.); // nonredundant members dimension=ncv*(ncv+1)/2;
     215        6977 :     } else if(!restarted) {
     216        6969 :       variance[0]+=decay*(delta[0]*delta[0]-variance[0]);
     217             :     }
     218             :     break;
     219        1638 :   case geometry:
     220             :     //this calculates in variance the \nabla CV_i \dot \nabla CV_j
     221        1638 :     variance.resize(1);
     222             :     // now the signal for retrieving the gradients should be already given by checkNeedsGradients.
     223             :     // here just do the projections
     224             :     // note that the call  checkNeedsGradients() in BiasMetaD takes care of switching on the call to gradients
     225        1638 :     if (nowAddAHill) { // geometry is sync with hill deposition
     226             :       // eq 12 of "Metadynamics with adaptive Gaussians"
     227         327 :       variance[0]=sigma*sigma*(paction->getProjection(iarg,iarg));
     228             :     }
     229             :     break;
     230           0 :   default:
     231           0 :     plumed_merror("This flexible bin is not recognized");
     232             :   }
     233        8644 : }
     234             : 
     235             : ///
     236             : /// Calculate the matrix of  (dcv_i/dx)*(dcv_j/dx)^-1
     237             : /// that is needed for the metrics in metadynamics
     238             : ///
     239             : ///
     240        1954 : std::vector<double> FlexibleBin::getInverseMatrix() const {
     241        1954 :   unsigned ncv=paction->getNumberOfArguments();
     242        1954 :   Matrix<double> matrix(ncv,ncv);
     243             :   unsigned i,j,k;
     244             :   k=0;
     245             :   // place the matrix in a complete matrix for compatibility
     246        5455 :   for (i=0; i<ncv; i++) {
     247        8549 :     for (j=i; j<ncv; j++) {
     248        5048 :       matrix(j,i)=matrix(i,j)=variance[k];
     249        5048 :       k++;
     250             :     }
     251             :   }
     252             : #define NEWFLEX
     253             : #ifdef NEWFLEX
     254             :   // diagonalize to impose boundaries (only if boundaries are set)
     255        1954 :   Matrix<double>      eigenvecs(ncv,ncv);
     256        1954 :   std::vector<double> eigenvals(ncv);
     257             : 
     258             :   //eigenvecs: first is eigenvec number, second is eigenvec component
     259        1954 :   if(diagMat( matrix, eigenvals, eigenvecs )!=0) {
     260           0 :     plumed_merror("diagonalization in FlexibleBin failed! This matrix is weird\n");
     261             :   };
     262             : 
     263        5455 :   for (i=0; i<ncv; i++) { //loop on the dimension
     264        3501 :     if( limitmax[i] ) {
     265             :       //limit every  component that is larger
     266        1308 :       for (j=0; j<ncv; j++) { //loop on components
     267         872 :         if(std::pow(eigenvals[j]*eigenvecs[j][i],2)>std::pow(sigmamax[i],2) ) {
     268         552 :           eigenvals[j]=std::sqrt(std::pow(sigmamax[i]/(eigenvecs[j][i]),2))*copysign(1.,eigenvals[j]);
     269             :         }
     270             :       }
     271             :     }
     272             :   }
     273        5455 :   for (i=0; i<ncv; i++) { //loop on the dimension
     274             :     // find the largest one:  if it is smaller than min  then rescale
     275        3501 :     if( limitmin[i] ) {
     276             :       unsigned imax=0;
     277             :       double fmax=-1.e10;
     278        3186 :       for (j=0; j<ncv; j++) { //loop on components
     279        2124 :         double fact=std::pow(eigenvals[j]*eigenvecs[j][i],2);
     280        2124 :         if(fact>fmax) {
     281             :           fmax=fact;
     282             :           imax=j;
     283             :         }
     284             :       }
     285        1062 :       if(fmax<std::pow(sigmamin[i],2) ) {
     286         226 :         eigenvals[imax]=std::sqrt(std::pow(sigmamin[i]/(eigenvecs[imax][i]),2))*copysign(1.,eigenvals[imax]);
     287             :       }
     288             :     }
     289             :   }
     290             : 
     291             :   // normalize eigenvecs
     292        1954 :   Matrix<double> newinvmatrix(ncv,ncv);
     293        5455 :   for (i=0; i<ncv; i++) {
     294       10096 :     for (j=0; j<ncv; j++) {
     295        6595 :       newinvmatrix[j][i]=eigenvecs[j][i]/eigenvals[j];
     296             :     }
     297             :   }
     298             : 
     299        1954 :   std::vector<double> uppervec(ncv*(ncv+1)/2);
     300             :   k=0;
     301        5455 :   for (i=0; i<ncv; i++) {
     302        8549 :     for (j=i; j<ncv; j++) {
     303             :       double scal=0;
     304       14737 :       for(unsigned l=0; l<ncv; ++l) {
     305        9689 :         scal+=eigenvecs[l][i]*newinvmatrix[l][j];
     306             :       }
     307        5048 :       uppervec[k]=scal;
     308        5048 :       k++;
     309             :     }
     310             :   }
     311             : #else
     312             :   // get the inverted matrix
     313             :   Matrix<double> invmatrix(ncv,ncv);
     314             :   Invert(matrix,invmatrix);
     315             :   std::vector<double> uppervec(ncv*(ncv+1)/2);
     316             :   // upper diagonal of the inverted matrix (that is symmetric)
     317             :   k=0;
     318             :   for (i=0; i<ncv; i++) {
     319             :     for (j=i; j<ncv; j++) {
     320             :       uppervec[k]=invmatrix(i,j);
     321             :       k++;
     322             :     }
     323             :   }
     324             : #endif
     325        1954 :   return uppervec;
     326        1954 : }
     327             : 
     328             : ///
     329             : /// Calculate the matrix of  (dcv_i/dx)*(dcv_j/dx)^-1
     330             : /// that is needed for the metrics in metadynamics
     331             : /// for PBMetaD like FlexBin
     332             : ///
     333        1517 : std::vector<double> FlexibleBin::getInverseMatrix(unsigned iarg) const {
     334             :   // diagonalize to impose boundaries (only if boundaries are set)
     335        1517 :   std::vector<double> eigenvals(1, variance[0]);
     336        1517 :   if( limitmax[0] ) {
     337         327 :     if(eigenvals[0]>sigmamax[0]) {
     338         218 :       eigenvals[0]=sigmamax[0];
     339             :     }
     340             :   }
     341             :   // find the largest one:  if it is smaller than min  then rescale
     342        1517 :   if( limitmin[0] ) {
     343             :     double fmax=-1.e10;
     344         783 :     double fact=eigenvals[0];
     345         783 :     if(fact>fmax) {
     346             :       fmax=fact;
     347             :     }
     348         783 :     if(fmax<sigmamin[0]) {
     349         102 :       eigenvals[0]=sigmamin[0];
     350             :     }
     351             :   }
     352        1517 :   std::vector<double> uppervec(1,1./eigenvals[0]);
     353             : 
     354        1517 :   return uppervec;
     355        1517 : }
     356             : 
     357             : }

Generated by: LCOV version 1.16