LCOV - code coverage report
Current view: top level - symfunc - LocalAverage.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 93 93 100.0 %
Date: 2026-11-05 10:19:04 Functions: 4 5 80.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2012-2017 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 "core/ActionShortcut.h"
      23             : #include "core/ActionRegister.h"
      24             : #include "core/ActionWithValue.h"
      25             : #include "multicolvar/MultiColvarShortcuts.h"
      26             : #include "core/PlumedMain.h"
      27             : #include "core/ActionSet.h"
      28             : #include "CoordinationNumbers.h"
      29             : #include <string>
      30             : #include <cmath>
      31             : 
      32             : namespace PLMD {
      33             : namespace symfunc {
      34             : 
      35             : //+PLUMEDOC MCOLVARF LOCAL_AVERAGE
      36             : /*
      37             : Calculate averages over spherical regions centered on atoms
      38             : 
      39             : As is explained in <a href="http://www.youtube.com/watch?v=iDvZmbWE5ps"> this video </a> certain PLUMED actions
      40             : calculate one scalar quantity or one vector for each of the atoms in the system.  For example
      41             : [COORDINATIONNUMBER](COORDINATIONNUMBER.md) measures the coordination number of each of the atoms in the system and [Q4](Q4.md) measures
      42             : the 4th order Steinhardt parameter for each of the atoms in the system.  These quantities provide tell us something about
      43             : the disposition of the atoms in the first coordination sphere of each of the atoms of interest.  In the paper in the bibliography Lechner and Dellago
      44             : have suggested that one can probe local order in a system by taking the average value of such symmetry functions over
      45             : the atoms within a spherical cutoff of each of these atoms in the systems.  When this is done with Steinhardt parameters
      46             : they claim this gives a coordinate that is better able to distinguish solid and liquid configurations of Lennard-Jones atoms.
      47             : 
      48             : You can calculate such locally averaged quantities within plumed by using the LOCAL_AVERAGE command.  This command calculates
      49             : the following atom-centered quantities:
      50             : 
      51             : $$
      52             : s_i = \frac{ c_i + \sum_j \sigma(r_{ij})c_j }{ 1 + \sum_j \sigma(r_{ij}) }
      53             : $$
      54             : 
      55             : where the $c_i$ and $c_j$ values can be any vector of [symmetry functions](https://www.plumed-tutorials.org/lessons/23/001/data/SymmetryFunction.html)
      56             :  that can be calculated using plumed multicolvars.  The function $\sigma( r_{ij} )$ is a switching function that acts on the distance between
      57             : atoms $i$ and $j$.  Lechner and Dellago suggest that the parameters of this function should be set so that it the function is equal to one
      58             : when atom $j$ is in the first coordination sphere of atom $i$ and is zero otherwise.
      59             : 
      60             : To see how this works in practice consider the following example input.
      61             : 
      62             : ```plumed
      63             : d1: COORDINATIONNUMBER SPECIES=1-64 SWITCH={RATIONAL D_0=1.3 R_0=0.2 D_MAX=3.0}
      64             : la: LOCAL_AVERAGE SPECIES=d1 D_0=1.3 R_0=0.2 NN=6 MM=12
      65             : mtf: MORE_THAN ARG=la SWITCH={RATIONAL R_0=4}
      66             : la_morethan: SUM ARG=mtf PERIODIC=NO
      67             : PRINT ARG=la_morethan FILE=colvar
      68             : ```
      69             : 
      70             : This input calculates the coordination numbers for all the atoms in the system.  These coordination numbers are then averaged over
      71             : spherical regions.  The number of averaged coordination numbers that are greater than 4 is then output to a file.  Furthermore, if you
      72             : expand the input above you can see how the LOCAL_AVERAGE command is a shortcut action that expands to a longer input that you should be able to
      73             : interpret.
      74             : 
      75             : In the input above we use a rational [switching function](LESS_THAN.md) with the parameters above. We would recommend using SWITCH syntax
      76             : rather than the syntax above when giving the parameters for the switching function as you can then use any of the switching functions described
      77             : in the documentation for [LESS_THAN](LESS_THAN.md).  More importantly, however, using this syntax allows you to set the D_MAX parameter for the
      78             : switching function as demonstrated below:
      79             : 
      80             : ```plumed
      81             : d1: COORDINATIONNUMBER SPECIES=1-64 SWITCH={RATIONAL D_0=1.3 R_0=0.2 D_MAX=3.0}
      82             : la: LOCAL_AVERAGE SPECIES=d1 SWITCH={RATIONAL D_0=1.3 R_0=0.2 D_MAX=3.0}
      83             : mtf: MORE_THAN ARG=la SWITCH={RATIONAL R_0=4}
      84             : la_morethan: SUM ARG=mtf PERIODIC=NO
      85             : PRINT ARG=la_morethan FILE=colvar
      86             : ```
      87             : 
      88             : Setting the `D_MAX` can substantially improve PLUMED performance as it turns on the linked list algorithm that is discussed in the optimisation details part
      89             : of the documentation for [CONTACT_MATRIX](CONTACT_MATRIX.md).
      90             : 
      91             : ## Locally averaged Steinhardt Parameters
      92             : 
      93             : What Lechner and Dellago did in their paper was a little more complicated than the example in the previous section. To reproduce what they did you would use
      94             : an input something like this:
      95             : 
      96             : ```plumed
      97             : q4: Q4 SPECIES=1-64 SWITCH={RATIONAL D_0=1.3 R_0=0.2}
      98             : la: LOCAL_AVERAGE SPECIES=q4 SWITCH={RATIONAL D_0=1.3 R_0=0.2}
      99             : la_mean: MEAN ARG=la PERIODIC=NO
     100             : PRINT ARG=la_mean FILE=colvar
     101             : ```
     102             : 
     103             : This example input calculates the [Q4](Q4.md) vectors for each of the atoms in the system.  These vectors are then averaged
     104             : component by component over a spherical region.  The average value for this quantity is then outputeed to a file.  If you want
     105             : to understand more about the shortcut that is used here you can read [this page](https://www.plumed-tutorials.org/lessons/23/001/data/Steinhardt.html).
     106             : 
     107             : ## Using SPECIESA/SPECIESB
     108             : 
     109             : If you only want to calculate the local averages for a subset of the atoms you might use an input like the one shown below:
     110             : 
     111             : ```plumed
     112             : # Calculate the coordination numbers for the two types of atom
     113             : d1a: COORDINATIONNUMBER SPECIES=1-5 SWITCH={RATIONAL D_0=1.3 R_0=0.2 D_MAX=3.0}
     114             : d1b: COORDINATIONNUMBER SPECIES=1-64 SWITCH={RATIONAL D_0=1.3 R_0=0.2 D_MAX=3.0}
     115             : # Now compute the local average
     116             : la: LOCAL_AVERAGE SPECIESA=d1a SPECIESB=d1b SWITCH={RATIONAL D_0=1.3 R_0=0.2 D_MAX=3.0}
     117             : DUMPATOMS ARG=la ATOMS=1-5 FILE=atoms.xyz
     118             : ```
     119             : 
     120             : In the input above the local averages are computed for atoms 1-5 only. However, to calculate these local averages we still need to calculate the coordination numbers of all
     121             : the atoms in the system.
     122             : 
     123             : ## The MASK keyword
     124             : 
     125             : You can use the MASK keyword with this action in the same way that it is used with [COORDINATIONNUMBER](COORDINATIONNUMBER.md).  This keyword thus expects a vector in
     126             : input, which tells PLUMED the atoms for which you do not need to calculate the function.  As illustrated below, this is useful if you are using functionality
     127             : from the [volumes module](module_volumes.md) to calculate the average value of the local averages for only those atoms that
     128             : lie in a certain part of the simulation box.
     129             : 
     130             : ```plumed
     131             : # Calculate the coordination number for all the atoms
     132             : cc: COORDINATIONNUMBER SPECIES=1-400 SWITCH={RATIONAL D_0=1.3 R_0=0.2 D_MAX=3.0}
     133             : # Fixed virtual atom which serves as the probe volume's center (pos. in nm)
     134             : center: FIXEDATOM AT=2.5,2.5,2.5
     135             : # Vector in which element i is one if atom i is in sphere of interest and zero otherwise
     136             : sphere: INSPHERE ATOMS=1-400 CENTER=center RADIUS={GAUSSIAN D_0=0.5 R_0=0.01 D_MAX=0.52}
     137             : # Calculate the local average
     138             : lcc: LOCAL_AVERAGE SPECIES=cc SWITCH={RATIONAL D_0=1.3 R_0=0.2 D_MAX=3.0} MASK=sphere
     139             : # Multiply local averages parameters numbers by sphere vector
     140             : prod: CUSTOM ARG=lcc,sphere FUNC=x*y PERIODIC=NO
     141             : # Sum of coordination numbers for atoms that are in the sphere of interest
     142             : numer: SUM ARG=prod PERIODIC=NO
     143             : # Number of atoms that are in sphere of interest
     144             : denom: SUM ARG=sphere PERIODIC=NO
     145             : # Average coordination number for atoms in sphere of interest
     146             : av: CUSTOM ARG=numer,denom FUNC=x/y PERIODIC=NO
     147             : # And print out final CV to a file
     148             : PRINT ARG=av FILE=colvar STRIDE=1
     149             : ```
     150             : 
     151             : This input calculates the average value of the local average for those atoms that are within a spherical region that is centered on the point $(2.5,2.5,2.5)$.
     152             : By including the MASK keyword in the LOCAL_AVERAGE line we reduce the number of local averages that are computed. These quantities are only calculated for those
     153             : atoms that are within the spherical region of interest.  However, we are still asking PLUMED to calculate the coordination numbers for many atoms that
     154             : will not contribute to the final averaged quantity in the COORDINATIONNUMBER commmand with label `cc`.  If you use an input like the one shown below you can ensure
     155             : that only those coordination numbers that compute to the final average are computed.
     156             : 
     157             : ```plumed
     158             : # Fixed virtual atom which serves as the probe volume's center (pos. in nm)
     159             : center: FIXEDATOM AT=2.5,2.5,2.5
     160             : # Vector in which element i is one if atom i is in sphere of interest and zero otherwise
     161             : sphere: INSPHERE ATOMS=1-400 CENTER=center RADIUS={GAUSSIAN D_0=0.5 R_0=0.01 D_MAX=0.52}
     162             : ones: ONES SIZE=400
     163             : # Calculate a elements of adjacency matrix for those elements that are inside the sphere
     164             : vol_cmap: CONTACT_MATRIX GROUP=1-400 SWITCH={RATIONAL D_0=1.3 R_0=0.2 D_MAX=3.0} MASK=sphere
     165             : # Create a matrix that for the sphere stuff
     166             : sphere_mat: OUTER_PRODUCT ARG=sphere,ones MASK=vol_cmap
     167             : # Multiply this contact matrix by the map
     168             : bonds_mat: CUSTOM ARG=vol_cmap,sphere_mat FUNC=x*y PERIODIC=NO
     169             : # Transpose the above matrix
     170             : bonds_matT: TRANSPOSE ARG=bonds_mat
     171             : # And multiply by the volume
     172             : bonds: MATRIX_VECTOR_PRODUCT ARG=bonds_matT,sphere
     173             : #  And make sure we calculate the coordination numbers for the atoms in the volume too
     174             : mask: CUSTOM ARG=bonds,sphere FUNC=x+y PERIODIC=NO
     175             : # Calculate the coordination number for all the atoms
     176             : cc: COORDINATIONNUMBER SPECIES=1-400 SWITCH={RATIONAL D_0=1.3 R_0=0.2 D_MAX=3.0} MASK=mask
     177             : # Calculate the local average
     178             : lcc: LOCAL_AVERAGE SPECIES=cc SWITCH={RATIONAL D_0=1.3 R_0=0.2 D_MAX=3.0} MASK=sphere
     179             : # Multiply local averages parameters numbers by sphere vector
     180             : prod: CUSTOM ARG=lcc,sphere FUNC=x*y PERIODIC=NO
     181             : # Sum of coordination numbers for atoms that are in the sphere of interest
     182             : numer: SUM ARG=prod PERIODIC=NO
     183             : # Number of atoms that are in sphere of interest
     184             : denom: SUM ARG=sphere PERIODIC=NO
     185             : # Average coordination number for atoms in sphere of interest
     186             : av: CUSTOM ARG=numer,denom FUNC=x/y PERIODIC=NO
     187             : # And print out final CV to a file
     188             : PRINT ARG=av FILE=colvar STRIDE=1
     189             : ```
     190             : 
     191             : In the above input the only elements of the [CONTACT_MATRIX](CONTACT_MATRIX.md) with label `vol_cmap` that are non-zero are those that are bonded to one of the atoms
     192             : in the sphere of interest.  By transposing this matrix and multiplying by a vector of ones we thus get a vector with a length equal to total number of coordination
     193             : numbers in the system. The only elements in this vector that are non-zero are those that are bonded to atoms that are within the sphere of interest.  Consequently,
     194             : if we add the vector `sphere` to this we can a vector in which element $i$ is only non-zero if we have to calculate that coordination number to evaluate the final
     195             : quantity of interest.
     196             : 
     197             : */
     198             : //+ENDPLUMEDOC
     199             : 
     200             : class LocalAverage : public ActionShortcut {
     201             : private:
     202             :   std::string getMomentumSymbol( const int& m ) const ;
     203             : public:
     204             :   static void registerKeywords( Keywords& keys );
     205             :   explicit LocalAverage(const ActionOptions&);
     206             : };
     207             : 
     208             : PLUMED_REGISTER_ACTION(LocalAverage,"LOCAL_AVERAGE")
     209             : 
     210          15 : void LocalAverage::registerKeywords( Keywords& keys ) {
     211          15 :   CoordinationNumbers::shortcutKeywords( keys );
     212          15 :   keys.needsAction("ONES");
     213          15 :   keys.needsAction("MATRIX_VECTOR_PRODUCT");
     214          15 :   keys.needsAction("VSTACK");
     215          15 :   keys.needsAction("CUSTOM");
     216          15 :   keys.needsAction("OUTER_PRODUCT");
     217          30 :   keys.setValueDescription("vector","the values of the local averages");
     218          30 :   keys.addDOI("10.1063/1.2977970");
     219          15 :   keys.addFlag("USEGPU",false,"run part of this calculation on the GPU");
     220          15 : }
     221             : 
     222          11 : LocalAverage::LocalAverage(const ActionOptions&ao):
     223             :   Action(ao),
     224          11 :   ActionShortcut(ao) {
     225             :   std::string sp_str, specA, specB;
     226          11 :   parse("SPECIES",sp_str);
     227          11 :   parse("SPECIESA",specA);
     228          11 :   parse("SPECIESB",specB);
     229             : 
     230             :   bool usegpu;
     231          11 :   parseFlag("USEGPU",usegpu);
     232          22 :   const std::string doUSEGPU = usegpu?" USEGPU":"";
     233             : 
     234          11 :   CoordinationNumbers::expandMatrix( false,
     235             :                                      getShortcutLabel(),
     236             :                                      sp_str,
     237             :                                      specA,
     238             :                                      specB,
     239             :                                      this );
     240             :   std::map<std::string,std::string> keymap;
     241          11 :   multicolvar::MultiColvarShortcuts::readShortcutKeywords( keymap, this );
     242          11 :   if( sp_str.length()>0 ) {
     243             :     specA=specB=sp_str;
     244             :   }
     245          11 :   const std::string matLab=getShortcutLabel() + "_mat";
     246          11 :   const std::string onesLab=getShortcutLabel() + "_ones";
     247          11 :   const std::string coordLab=getShortcutLabel() + "_coord";
     248             :   // Calculate the coordination numbers
     249          11 :   ActionWithValue* av = plumed.getActionSet().selectWithLabel<ActionWithValue*>(matLab);
     250          11 :   plumed_assert( av && av->getNumberOfComponents()>0 && (av->copyOutput(0))->getRank()==2 );
     251             :   std::string size;
     252          11 :   Tools::convert( (av->copyOutput(0))->getShape()[1], size );
     253          11 :   readInputLine(onesLab + ": ONES SIZE=" + size );
     254          22 :   readInputLine(coordLab + ": MATRIX_VECTOR_PRODUCT ARG=" + matLab + "," + onesLab + doUSEGPU);
     255             : 
     256          11 :   int l=-1;
     257          11 :   std::vector<ActionShortcut*> shortcuts=plumed.getActionSet().select<ActionShortcut*>();
     258         104 :   for(unsigned i=0; i<shortcuts.size(); ++i) {
     259         102 :     if( specA==shortcuts[i]->getShortcutLabel() ) {
     260          22 :       std::string sname = shortcuts[i]->getName();
     261          22 :       if( sname=="Q1" || sname=="Q3" || sname=="Q4" || sname=="Q6" ) {
     262          18 :         Tools::convert( sname.substr(1), l );
     263             :         break;
     264             :       }
     265             :     }
     266             :   }
     267             : 
     268          11 :   const std::string prodLab=getShortcutLabel() + "_prod";
     269          11 :   if( l>0 ) {
     270           9 :     std::string vargs="";
     271           9 :     std::string svargs="";
     272           9 :     std::string comma="ARG=";
     273           9 :     std::string sargs = "ARG=" + matLab;
     274         194 :     auto stringDiv=[&](const std::string& num, char realImm) {
     275         388 :       return specB + "_"+realImm+"mn-" + num + ": CUSTOM ARG="
     276         582 :              + specB + "_sp."+realImm+"m-" + num + ","
     277         582 :              + specB + "_denom FUNC=x/y PERIODIC=NO";
     278           9 :     };
     279         106 :     for(int i=-l; i<=l; ++i) {
     280          97 :       std::string num = getMomentumSymbol(i);
     281         194 :       if( !plumed.getActionSet().selectWithLabel<ActionWithValue*>(specB + "_rmn-" + num) ) {
     282         194 :         readInputLine( stringDiv(num,'r') );
     283             :       }
     284         194 :       if( !plumed.getActionSet().selectWithLabel<ActionWithValue*>(specB + "_imn-" + num) ) {
     285         194 :         readInputLine( stringDiv(num,'i') );
     286             :       }
     287         291 :       vargs += comma +  specB + "_rmn-" + num + "," + specB + "_imn-" + num;
     288         194 :       svargs += comma + prodLab + "." + specB + "_rmn-" + num
     289         388 :                 + "," + prodLab + "." + specB + "_imn-" + num;
     290             :       comma=",";
     291         485 :       sargs += "," + specB + "_rmn-" + num + "," + specB  + "_imn-" + num;
     292             :     }
     293             : 
     294           9 :     const std::string vstackLab=getShortcutLabel() + "_vstack";
     295           9 :     const std::string vpstackLab=getShortcutLabel() + "_vpstack";
     296           9 :     readInputLine(vstackLab + ": VSTACK " + vargs );
     297           9 :     readInputLine(prodLab + ": MATRIX_VECTOR_PRODUCT " + sargs + doUSEGPU);
     298          18 :     readInputLine(vpstackLab + ": VSTACK " + svargs );
     299             :     std::string twolplusone;
     300           9 :     Tools::convert( 2*(2*l+1), twolplusone );
     301             : 
     302           9 :     const std::string lonesLab=getShortcutLabel() + "_lones";
     303           9 :     readInputLine(lonesLab + ": ONES SIZE=" + twolplusone );
     304             : 
     305           9 :     const std::string unormLab=getShortcutLabel() + "_unorm";
     306          18 :     readInputLine(unormLab + ": OUTER_PRODUCT ARG=" +coordLab + "," + lonesLab );
     307             : 
     308           9 :     const std::string avLab=getShortcutLabel() + "_av";
     309          27 :     readInputLine(avLab + ": CUSTOM ARG=" + vpstackLab + "," + vstackLab + ","
     310           9 :                   + unormLab + " FUNC=(x+y)/(1+z) PERIODIC=NO");
     311             : 
     312           9 :     const std::string av2Lab=getShortcutLabel() + "_av2";
     313          18 :     readInputLine(av2Lab + ": CUSTOM ARG=" +avLab + " FUNC=x*x PERIODIC=NO");
     314             : 
     315           9 :     const std::string twoLab=getShortcutLabel() + "_2";
     316          27 :     readInputLine( twoLab+": MATRIX_VECTOR_PRODUCT ARG=" +av2Lab + "," + lonesLab
     317           9 :                    + doUSEGPU);
     318          18 :     readInputLine( getShortcutLabel() + ": CUSTOM "
     319          18 :                    "ARG=" + twoLab + " FUNC=sqrt(x) PERIODIC=NO");
     320             :   } else {
     321           4 :     readInputLine( prodLab + ": MATRIX_VECTOR_PRODUCT "
     322           4 :                    "ARG=" +matLab + ","+ specB
     323           2 :                    + doUSEGPU);
     324           4 :     readInputLine( getShortcutLabel() + ": CUSTOM "
     325           6 :                    "ARG=" + prodLab + "," + specA + "," +coordLab
     326           2 :                    + " FUNC=(x+y)/(1+z) PERIODIC=NO");
     327             :   }
     328          22 :   multicolvar::MultiColvarShortcuts::expandFunctions( getShortcutLabel(),
     329             :       getShortcutLabel(),
     330             :       "",
     331             :       keymap, this );
     332         205 : }
     333             : 
     334          97 : std::string LocalAverage::getMomentumSymbol( const int& m ) const {
     335          97 :   if( m<0 ) {
     336             :     std::string num;
     337          44 :     Tools::convert( -1*m, num );
     338          44 :     return "n" + num;
     339          53 :   } else if( m>0 ) {
     340             :     std::string num;
     341          44 :     Tools::convert( m, num );
     342          44 :     return "p" + num;
     343             :   }
     344           9 :   return "0";
     345             : }
     346             : 
     347             : }
     348             : }

Generated by: LCOV version 1.16