LCOV - code coverage report
Current view: top level - multicolvar - LocalAverage.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 121 154 78.6 %
Date: 2026-03-30 13:16:06 Functions: 8 10 80.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2013-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 "MultiColvarBase.h"
      23             : #include "AtomValuePack.h"
      24             : #include "core/ActionRegister.h"
      25             : #include "tools/SwitchingFunction.h"
      26             : 
      27             : //+PLUMEDOC MCOLVARF LOCAL_AVERAGE
      28             : /*
      29             : Calculate averages over spherical regions centered on atoms
      30             : 
      31             : As is explained in <a href="http://www.youtube.com/watch?v=iDvZmbWE5ps"> this video </a> certain multicolvars
      32             : calculate one scalar quantity or one vector for each of the atoms in the system.  For example
      33             : \ref COORDINATIONNUMBER measures the coordination number of each of the atoms in the system and \ref Q4 measures
      34             : the fourth order Steinhardt parameter for each of the atoms in the system.  These quantities provide tell us something about
      35             : the disposition of the atoms in the first coordination sphere of each of the atoms of interest.  Lechner and Dellago \cite dellago-q6
      36             : have suggested that one can probe local order in a system by taking the average value of such symmetry functions over
      37             : the atoms within a spherical cutoff of each of these atoms in the systems.  When this is done with Steinhardt parameters
      38             : they claim this gives a coordinate that is better able to distinguish solid and liquid configurations of Lennard-Jones atoms.
      39             : 
      40             : You can calculate such locally averaged quantities within plumed by using the LOCAL_AVERAGE command.  This command calculates
      41             : the following atom-centered quantities:
      42             : 
      43             : \f[
      44             : s_i = \frac{ c_i + \sum_j \sigma(r_{ij})c_j }{ 1 + \sum_j \sigma(r_{ij}) }
      45             : \f]
      46             : 
      47             : where the \f$c_i\f$ and \f$c_j\f$ values can be for any one of the symmetry functions that can be calculated using plumed
      48             : multicolvars.  The function \f$\sigma( r_{ij} )\f$ is a \ref switchingfunction that acts on the distance between
      49             : atoms \f$i\f$ and \f$j\f$.  Lechner and Dellago suggest that the parameters of this function should be set so that it the function is equal to one
      50             : when atom \f$j\f$ is in the first coordination sphere of atom \f$i\f$ and is zero otherwise.
      51             : 
      52             : The \f$s_i\f$ quantities calculated using the above command can be again thought of as atom-centered symmetry functions.  They
      53             : thus operate much like multicolvars.  You can thus calculate properties of the distribution of \f$s_i\f$ values using MEAN, LESS_THAN, HISTOGRAM
      54             : and so on.  You can also probe the value of these averaged variables in regions of the box by using the command in tandem with the
      55             : \ref AROUND command.
      56             : 
      57             : \par Examples
      58             : 
      59             : This example input calculates the coordination numbers for all the atoms in the system.  These coordination numbers are then averaged over
      60             : spherical regions.  The number of averaged coordination numbers that are greater than 4 is then output to a file.
      61             : 
      62             : \plumedfile
      63             : COORDINATIONNUMBER SPECIES=1-64 D_0=1.3 R_0=0.2 LABEL=d1
      64             : LOCAL_AVERAGE SPECIES=d1 SWITCH={RATIONAL D_0=1.3 R_0=0.2} MORE_THAN={RATIONAL R_0=4} LABEL=la
      65             : PRINT ARG=la.* FILE=colvar
      66             : \endplumedfile
      67             : 
      68             : This example input calculates the \f$q_4\f$ (see \ref Q4) vectors for each of the atoms in the system.  These vectors are then averaged
      69             : component by component over a spherical region.  The average value for this quantity is then output to a file.  This calculates the
      70             : quantities that were used in the paper by Lechner and Dellago \cite dellago-q6
      71             : 
      72             : \plumedfile
      73             : Q4 SPECIES=1-64 SWITCH={RATIONAL D_0=1.3 R_0=0.2} LABEL=q4
      74             : LOCAL_AVERAGE SPECIES=q4 SWITCH={RATIONAL D_0=1.3 R_0=0.2} MEAN LABEL=la
      75             : PRINT ARG=la.* FILE=colvar
      76             : \endplumedfile
      77             : 
      78             : */
      79             : //+ENDPLUMEDOC
      80             : 
      81             : namespace PLMD {
      82             : namespace multicolvar {
      83             : 
      84             : class LocalAverage : public MultiColvarBase {
      85             : private:
      86             : /// Cutoff
      87             :   double rcut2;
      88             : /// The switching function that tells us if atoms are close enough together
      89             :   SwitchingFunction switchingFunction;
      90             : public:
      91             :   static void registerKeywords( Keywords& keys );
      92             :   explicit LocalAverage(const ActionOptions&);
      93             : /// We have to overwrite this here
      94             :   unsigned getNumberOfQuantities() const override;
      95             : /// Actually do the calculation
      96             :   double compute( const unsigned& tindex, AtomValuePack& myatoms ) const override;
      97             : /// We overwrite this in order to have dumpmulticolvar working for local average
      98           0 :   void normalizeVector( std::vector<double>& vals ) const override {}
      99             : /// Is the variable periodic
     100           3 :   bool isPeriodic() override {
     101           3 :     return false;
     102             :   }
     103             : };
     104             : 
     105       13793 : PLUMED_REGISTER_ACTION(LocalAverage,"LOCAL_AVERAGE")
     106             : 
     107           8 : void LocalAverage::registerKeywords( Keywords& keys ) {
     108           8 :   MultiColvarBase::registerKeywords( keys );
     109          16 :   keys.add("compulsory","NN","6","The n parameter of the switching function ");
     110          16 :   keys.add("compulsory","MM","0","The m parameter of the switching function; 0 implies 2*NN");
     111          16 :   keys.add("compulsory","D_0","0.0","The d_0 parameter of the switching function");
     112          16 :   keys.add("compulsory","R_0","The r_0 parameter of the switching function");
     113          16 :   keys.add("optional","SWITCH","This keyword is used if you want to employ an alternative to the continuous switching function defined above. "
     114             :            "The following provides information on the \\ref switchingfunction that are available. "
     115             :            "When this keyword is present you no longer need the NN, MM, D_0 and R_0 keywords.");
     116             :   // Use actionWithDistributionKeywords
     117           8 :   keys.use("SPECIES");
     118           8 :   keys.use("SPECIESA");
     119           8 :   keys.use("SPECIESB");
     120           8 :   keys.remove("LOWMEM");
     121           8 :   keys.use("MEAN");
     122           8 :   keys.use("MORE_THAN");
     123           8 :   keys.use("LESS_THAN");
     124           8 :   keys.use("BETWEEN");
     125           8 :   keys.use("HISTOGRAM");
     126           8 :   keys.use("MOMENTS");
     127          16 :   keys.addFlag("LOWMEM",false,"lower the memory requirements");
     128          16 :   if( keys.reserved("VMEAN") ) {
     129          16 :     keys.use("VMEAN");
     130             :   }
     131          16 :   if( keys.reserved("VSUM") ) {
     132          16 :     keys.use("VSUM");
     133             :   }
     134           8 : }
     135             : 
     136           4 : LocalAverage::LocalAverage(const ActionOptions& ao):
     137             :   Action(ao),
     138           4 :   MultiColvarBase(ao) {
     139           4 :   if( getNumberOfBaseMultiColvars()>1 ) {
     140           0 :     error("local average with more than one base colvar makes no sense");
     141             :   }
     142             :   // Read in the switching function
     143             :   std::string sw, errors;
     144           8 :   parse("SWITCH",sw);
     145           4 :   if(sw.length()>0) {
     146           4 :     switchingFunction.set(sw,errors);
     147             :   } else {
     148           0 :     double r_0=-1.0, d_0;
     149             :     int nn, mm;
     150           0 :     parse("NN",nn);
     151           0 :     parse("MM",mm);
     152           0 :     parse("R_0",r_0);
     153           0 :     parse("D_0",d_0);
     154           0 :     if( r_0<0.0 ) {
     155           0 :       error("you must set a value for R_0");
     156             :     }
     157           0 :     switchingFunction.set(nn,mm,r_0,d_0);
     158             :   }
     159           4 :   log.printf("  averaging over central molecule and those within %s\n",( switchingFunction.description() ).c_str() );
     160           4 :   rcut2 = switchingFunction.get_dmax()*switchingFunction.get_dmax();
     161           4 :   setLinkCellCutoff( switchingFunction.get_dmax() );
     162             :   std::vector<AtomNumber> all_atoms;
     163           4 :   setupMultiColvarBase( all_atoms );
     164           4 : }
     165             : 
     166          71 : unsigned LocalAverage::getNumberOfQuantities() const {
     167          71 :   return getBaseMultiColvar(0)->getNumberOfQuantities();
     168             : }
     169             : 
     170        1004 : double LocalAverage::compute( const unsigned& tindex, AtomValuePack& myatoms ) const {
     171             :   double sw, dfunc;
     172             :   MultiValue& myvals = myatoms.getUnderlyingMultiValue();
     173        1004 :   std::vector<double> values( getBaseMultiColvar(0)->getNumberOfQuantities() );
     174             : 
     175        1004 :   getInputData( 0, false, myatoms, values );
     176             :   myvals.addTemporyValue( values[0] );
     177        1004 :   if( values.size()>2 ) {
     178       27108 :     for(unsigned j=2; j<values.size(); ++j) {
     179       26104 :       myatoms.addValue( j, values[0]*values[j] );
     180             :     }
     181             :   } else {
     182           0 :     myatoms.addValue( 1, values[0]*values[1] );
     183             :   }
     184             : 
     185        1004 :   if( !doNotCalculateDerivatives() ) {
     186        1004 :     MultiValue& myder=getInputDerivatives( 0, false, myatoms );
     187             : 
     188             :     // Convert input atom to local index
     189             :     unsigned katom = myatoms.getIndex( 0 );
     190             :     plumed_dbg_assert( katom<atom_lab.size() );
     191             :     plumed_dbg_assert( atom_lab[katom].first>0 );
     192             :     // Find base colvar
     193        1004 :     unsigned mmc=atom_lab[katom].first - 1;
     194             :     plumed_dbg_assert( mybasemulticolvars[mmc]->taskIsCurrentlyActive( atom_lab[katom].second ) );
     195             :     // Get start of indices for this atom
     196             :     unsigned basen=0;
     197        1004 :     for(unsigned i=0; i<mmc; ++i) {
     198           0 :       basen+=mybasemulticolvars[i]->getNumberOfDerivatives() - 9;
     199             :     }
     200             :     plumed_dbg_assert( basen%3==0 ); // Check the number of atoms is consistent with input derivatives
     201             : 
     202        1004 :     unsigned virbas = myvals.getNumberOfDerivatives()-9;
     203        1004 :     if( values.size()>2 ) {
     204       53663 :       for(unsigned j=0; j<myder.getNumberActive(); ++j) {
     205             :         unsigned jder=myder.getActiveIndex(j);
     206       52659 :         if( jder<mybasemulticolvars[mmc]->getNumberOfDerivatives()-9 ) {
     207       43623 :           unsigned kder=basen+jder;
     208     1177821 :           for(unsigned k=2; k<values.size(); ++k) {
     209     1134198 :             myatoms.addDerivative( k, kder, values[0]*myder.getDerivative(k,jder) );
     210     1134198 :             myatoms.addDerivative( k, kder, values[k]*myder.getDerivative(0,jder) );
     211             :           }
     212             :         } else {
     213        9036 :           unsigned kder=virbas + (jder - mybasemulticolvars[mmc]->getNumberOfDerivatives() + 9);
     214      243972 :           for(unsigned k=2; k<values.size(); ++k) {
     215      234936 :             myatoms.addDerivative( k, kder, values[0]*myder.getDerivative(k,jder) );
     216      234936 :             myatoms.addDerivative( k, kder, values[k]*myder.getDerivative(0,jder) );
     217             :           }
     218             :         }
     219             :       }
     220             :     } else {
     221           0 :       for(unsigned j=0; j<myder.getNumberActive(); ++j) {
     222             :         unsigned jder=myder.getActiveIndex(j);
     223           0 :         if( jder<mybasemulticolvars[mmc]->getNumberOfDerivatives()-9 ) {
     224           0 :           unsigned kder=basen+jder;
     225           0 :           myatoms.addDerivative( 1, kder, values[0]*myder.getDerivative(1,jder) );
     226           0 :           myatoms.addDerivative( 1, kder, values[1]*myder.getDerivative(0,jder) );
     227             :         } else {
     228           0 :           unsigned kder=virbas + (jder - mybasemulticolvars[mmc]->getNumberOfDerivatives() + 9);
     229           0 :           myatoms.addDerivative( 1, kder, values[0]*myder.getDerivative(1,jder) );
     230           0 :           myatoms.addDerivative( 1, kder, values[1]*myder.getDerivative(0,jder) );
     231             :         }
     232             :       }
     233             :     }
     234       53663 :     for(unsigned j=0; j<myder.getNumberActive(); ++j) {
     235             :       unsigned jder=myder.getActiveIndex(j);
     236       52659 :       if( jder<mybasemulticolvars[mmc]->getNumberOfDerivatives()-9 ) {
     237       43623 :         unsigned kder=basen+jder;
     238       43623 :         myvals.addTemporyDerivative( kder, myder.getDerivative(0, jder) );
     239             :       } else {
     240        9036 :         unsigned kder=virbas + (jder - mybasemulticolvars[mmc]->getNumberOfDerivatives() + 9);
     241        9036 :         myvals.addTemporyDerivative( kder, myder.getDerivative(0, jder) );
     242             :       }
     243             :     }
     244        1004 :     myder.clearAll();
     245             :   }
     246             : 
     247       78226 :   for(unsigned i=1; i<myatoms.getNumberOfAtoms(); ++i) {
     248             :     Vector& distance=myatoms.getPosition(i);  // getSeparation( myatoms.getPosition(0), myatoms.getPosition(i) );
     249             :     double d2;
     250      123529 :     if ( (d2=distance[0]*distance[0])<rcut2 &&
     251       46307 :          (d2+=distance[1]*distance[1])<rcut2 &&
     252      103834 :          (d2+=distance[2]*distance[2])<rcut2 &&
     253             :          d2>epsilon) {
     254             : 
     255       16025 :       sw = switchingFunction.calculateSqr( d2, dfunc );
     256             : 
     257       16025 :       getInputData( i, false, myatoms, values );
     258       16025 :       if( values.size()>2 ) {
     259      432675 :         for(unsigned j=2; j<values.size(); ++j) {
     260      416650 :           myatoms.addValue( j, sw*values[0]*values[j] );
     261             :         }
     262             :       } else {
     263           0 :         myatoms.addValue( 1, sw*values[0]*values[1] );
     264             :       }
     265             :       myvals.addTemporyValue(sw);
     266             : 
     267       16025 :       if( !doNotCalculateDerivatives() ) {
     268       16025 :         Tensor vir(distance,distance);
     269       16025 :         MultiValue& myder=getInputDerivatives( i, false, myatoms );
     270             : 
     271             :         // Convert input atom to local index
     272             :         unsigned katom = myatoms.getIndex( i );
     273             :         plumed_dbg_assert( katom<atom_lab.size() );
     274             :         plumed_dbg_assert( atom_lab[katom].first>0 );
     275             :         // Find base colvar
     276       16025 :         unsigned mmc=atom_lab[katom].first - 1;
     277             :         plumed_dbg_assert( mybasemulticolvars[mmc]->taskIsCurrentlyActive( atom_lab[katom].second ) );
     278             :         // Get start of indices for this atom
     279             :         unsigned basen=0;
     280       22331 :         for(unsigned j=0; j<mmc; ++j) {
     281        6306 :           basen+=mybasemulticolvars[j]->getNumberOfDerivatives() - 9;
     282             :         }
     283             :         plumed_dbg_assert( basen%3==0 ); // Check the number of atoms is consistent with input derivatives
     284             : 
     285       16025 :         unsigned virbas = myvals.getNumberOfDerivatives()-9;
     286       16025 :         if( values.size()>2 ) {
     287     1523879 :           for(unsigned j=0; j<myder.getNumberActive(); ++j) {
     288             :             unsigned jder=myder.getActiveIndex(j);
     289     1507854 :             if( jder<mybasemulticolvars[mmc]->getNumberOfDerivatives()-9 ) {
     290     1363629 :               unsigned kder=basen+jder;
     291    36817983 :               for(unsigned k=2; k<values.size(); ++k) {
     292    35454354 :                 myatoms.addDerivative( k, kder, sw*values[0]*myder.getDerivative(k,jder) );
     293    35454354 :                 myatoms.addDerivative( k, kder, sw*values[k]*myder.getDerivative(0,jder) );
     294             :               }
     295             :             } else {
     296      144225 :               unsigned kder=virbas + (jder - mybasemulticolvars[mmc]->getNumberOfDerivatives() + 9);
     297     3894075 :               for(unsigned k=2; k<values.size(); ++k) {
     298     3749850 :                 myatoms.addDerivative( k, kder, sw*values[0]*myder.getDerivative(k,jder) );
     299     3749850 :                 myatoms.addDerivative( k, kder, sw*values[k]*myder.getDerivative(0,jder) );
     300             :               }
     301             :             }
     302             :           }
     303      432675 :           for(unsigned k=2; k<values.size(); ++k) {
     304      416650 :             addAtomDerivatives( k, 0, (-dfunc)*values[0]*values[k]*distance, myatoms );
     305      416650 :             addAtomDerivatives( k, i, (+dfunc)*values[0]*values[k]*distance, myatoms );
     306      416650 :             myatoms.addBoxDerivatives( k, (-dfunc)*values[0]*values[k]*vir );
     307             :           }
     308             :         } else {
     309           0 :           for(unsigned j=0; j<myder.getNumberActive(); ++j) {
     310             :             unsigned jder=myder.getActiveIndex(j);
     311           0 :             if( jder<mybasemulticolvars[mmc]->getNumberOfDerivatives()-9 ) {
     312           0 :               unsigned kder=basen+jder;
     313           0 :               myatoms.addDerivative( 1, kder, sw*values[0]*myder.getDerivative(1,jder) );
     314           0 :               myatoms.addDerivative( 1, kder, sw*values[1]*myder.getDerivative(0,jder) );
     315             :             } else {
     316           0 :               unsigned kder=virbas + (jder - mybasemulticolvars[mmc]->getNumberOfDerivatives() + 9);
     317           0 :               myatoms.addDerivative( 1, kder, sw*values[0]*myder.getDerivative(1,jder) );
     318           0 :               myatoms.addDerivative( 1, kder, sw*values[1]*myder.getDerivative(0,jder) );
     319             :             }
     320             :           }
     321           0 :           addAtomDerivatives( 1, 0, (-dfunc)*values[0]*values[1]*distance, myatoms );
     322           0 :           addAtomDerivatives( 1, i, (+dfunc)*values[0]*values[1]*distance, myatoms );
     323           0 :           myatoms.addBoxDerivatives( 1, (-dfunc)*values[0]*values[1]*vir );
     324             :         }
     325             :         // And the bit we use to average the vector
     326       16025 :         addAtomDerivatives( -1, 0, (-dfunc)*values[0]*distance, myatoms );
     327       16025 :         addAtomDerivatives( -1, i, (+dfunc)*values[0]*distance, myatoms );
     328     1523879 :         for(unsigned j=0; j<myder.getNumberActive(); ++j) {
     329             :           unsigned jder=myder.getActiveIndex(j);
     330     1507854 :           if( jder<mybasemulticolvars[mmc]->getNumberOfDerivatives()-9 ) {
     331     1363629 :             unsigned kder=basen+jder;
     332     1363629 :             myvals.addTemporyDerivative( kder, sw*myder.getDerivative(0, jder) );
     333             :           } else {
     334      144225 :             unsigned kder=virbas + (jder - mybasemulticolvars[mmc]->getNumberOfDerivatives() + 9);
     335      144225 :             myvals.addTemporyDerivative( kder, sw*myder.getDerivative(0, jder) );
     336             :           }
     337             :         }
     338       16025 :         myatoms.addTemporyBoxDerivatives( (-dfunc)*values[0]*vir );
     339       16025 :         myder.clearAll();
     340             :       }
     341             :     }
     342             :   }
     343             : 
     344             :   // Set the tempory weight
     345        1004 :   updateActiveAtoms( myatoms );
     346        1004 :   if( values.size()>2) {
     347             :     double norm=0;
     348       27108 :     for(unsigned i=2; i<values.size(); ++i) {
     349       26104 :       myvals.quotientRule( i, i );
     350             :       // Calculate length of vector
     351       26104 :       norm+=myvals.get(i)*myvals.get(i);
     352             :     }
     353        1004 :     norm=sqrt(norm);
     354             :     myatoms.setValue(1, norm);
     355        1004 :     double inorm = 1.0 / norm;
     356      165719 :     for(unsigned j=0; j<myvals.getNumberActive(); ++j) {
     357      164715 :       unsigned jder=myvals.getActiveIndex(j);
     358     4447305 :       for(unsigned i=2; i<values.size(); ++i) {
     359     4282590 :         myvals.addDerivative( 1, jder, myvals.get(i)*inorm*myvals.getDerivative(i,jder) );
     360             :       }
     361             :     }
     362             :   } else {
     363           0 :     myvals.quotientRule( 1, 1 );
     364             :   }
     365             : 
     366        1004 :   return myatoms.getValue(1);
     367             : }
     368             : 
     369             : }
     370             : }

Generated by: LCOV version 1.16