LCOV - code coverage report
Current view: top level - gridtools - RDF.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 110 120 91.7 %
Date: 2026-11-05 10:19:04 Functions: 3 5 60.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 "RDF.h"
      23             : #include "core/PlumedMain.h"
      24             : #include "core/ActionSet.h"
      25             : #include "core/ActionWithArguments.h"
      26             : #include "core/ActionRegister.h"
      27             : 
      28             : //+PLUMEDOC ANALYSIS RDF
      29             : /*
      30             : Calculate the radial distribution function
      31             : 
      32             : This shortcut can be used to evaluate the [radial distribution function](https://en.wikipedia.org/wiki/Radial_distribution_function) for
      33             : a collection of atoms.  The following example illustrates how it can be used to compute the average radial distribution from a
      34             : trajectory:
      35             : 
      36             : ```plumed
      37             : rdf: RDF GROUP=1-108 MAXR=2.5 GRID_BIN=25 KERNEL=DISCRETE STRIDE=1 CLEAR=0
      38             : DUMPGRID ARG=rdf FILE=rdf.dat
      39             : ```
      40             : 
      41             : If you expand the shortcut in this input you can see how the radial distribution function is computed using [KDE](KDE.md) actions and that the
      42             : average is obtained by using [ACCUMULATE](ACCUMULATE.md) actions.  By using expanded versions of this shortcut you can thus calculate and print
      43             : the instantaneous value of the radial distribution function.  Alternatively, if you use the CLEAR option in the [ACCUMULATE](ACCUMULATE.md) commands
      44             : you can calculate radial distribution functions from various parts of the trajectory.
      45             : 
      46             : The following example shows how you can use this function to calculate the radial distribution for the atoms in GROUPB around the atoms in GROUPA.
      47             : Notice that the DISCRETE keyword has been removed here so we are using [kernel density estimation](https://en.wikipedia.org/wiki/Kernel_density_estimation)
      48             : to compute the radial distribution function.
      49             : 
      50             : ```plumed
      51             : rdf: RDF GROUPA=1-108 GROUPB=109-300 MAXR=2.5 GRID_BIN=25 BANDWIDTH=0.01 DENSITY=1 STRIDE=1 CLEAR=0
      52             : DUMPGRID ARG=rdf FILE=rdf.dat
      53             : ```
      54             : 
      55             : We have also used the `DENSITY` keyword to set the background density that is used when normalizing the radial distribution function explicity to 1 atom$/nm^{3}$.
      56             : When this keyword is not used, this density is calculated by dividing the number of atoms by the volume of the box as you can see if you expand the shortcut in the
      57             : first input above.
      58             : 
      59             : Notice that you can also use the `NO_AVERAGE` keyword as shown below to calculate an independent RDF around each of the atoms:
      60             : 
      61             : ```plumed
      62             : rdf: RDF GROUPA=1-3 GROUPB=4-108 MAXR=2.5 GRID_BIN=25 BANDWIDTH=0.01 DENSITY=1 STRIDE=1 NO_AVERAGE CLEAR=0
      63             : DUMPGRID ARG=rdf FILE=rdf.dat
      64             : ```
      65             : 
      66             : This command will output a function on a $25 \times 3$ grid.  The first coordinate is the distance between atoms.  The second coordinate is then then an index that tells
      67             : you, which central atom.  In other words, the input above will output three radial distribution functions.  The first of these is the radial distribution function around atom 1,
      68             : the second is the radial distribution function around atom 2 and the last is the radial distribution function around atom 3.
      69             : 
      70             : */
      71             : //+ENDPLUMEDOC
      72             : 
      73             : namespace PLMD {
      74             : namespace gridtools {
      75             : 
      76             : PLUMED_REGISTER_ACTION(RDF,"RDF")
      77             : 
      78          13 : void RDF::getDistanceMatrixShape( const std::string& lab, ActionShortcut* action, std::vector<std::string>& shape_str ) {
      79          13 :   std::vector<Value*> args;
      80          13 :   std::vector<std::string> arglabs(1);
      81             :   arglabs[0] = lab;
      82          13 :   ActionWithArguments::interpretArgumentList( arglabs, action->plumed.getActionSet(), action, args );
      83          13 :   Tools::convert( args[0]->getShape()[0], shape_str[0] );
      84          13 :   Tools::convert( args[0]->getShape()[1], shape_str[1] );
      85          26 : }
      86             : 
      87           0 : void RDF::createX2ReferenceObject( const std::string& lab, const std::string& grid_setup, const bool& calc_dens, const bool& no_average, ActionShortcut* action ) {
      88             :   // Create grid with normalizing function
      89           0 :   if( no_average ) {
      90           0 :     action->readInputLine( lab  + "_x2: REFERENCE_GRID PERIODIC=NO,NO FUNC=x*x+0*y " + grid_setup );
      91             :   } else {
      92           0 :     action->readInputLine( lab  + "_x2: REFERENCE_GRID PERIODIC=NO FUNC=x*x " + grid_setup );
      93             :   }
      94             :   // Compute density if required
      95           0 :   if( calc_dens ) {
      96           0 :     action->readInputLine( lab + "_vol: VOLUME" );
      97             :   }
      98           0 : }
      99             : 
     100          32 : void RDF::registerKeywords( Keywords& keys ) {
     101          32 :   ActionShortcut::registerKeywords( keys );
     102          32 :   keys.add("atoms","GROUP","the atoms that are being used to calculate the RDF");
     103          32 :   keys.addDeprecatedKeyword("ATOMS","GROUP");
     104          32 :   keys.add("atoms-2","GROUPA","the atoms that you would like to compute the RDF about.  Must be used with GROUPB.");
     105          32 :   keys.add("atoms-2","GROUPB","the atoms that you would like to use when computing the RDF around the atoms that were specified with GROUPA");
     106          32 :   keys.add("compulsory","GRID_BIN","the number of bins to use when computing the RDF");
     107             :   std::string strcut;
     108          32 :   Tools::convert( sqrt(2*dp2cutoff), strcut );
     109          32 :   keys.add("compulsory","KERNEL","GAUSSIAN D_MAX=" + strcut,"the type of kernel to use for computing the histograms for the RDF");
     110          32 :   keys.add("compulsory","MAXR","the maximum distance to use for the rdf");
     111          32 :   keys.add("compulsory","BANDWIDTH","the bandwidths for kernel density esimtation");
     112          32 :   if( keys.getDisplayName()=="RDF") {
     113          19 :     keys.add("compulsory","CLEAR","1","the frequency with which to clear the estimate of the rdf.  Set equal to 0 if you want to compute an rdf over the whole trajectory");
     114          19 :     keys.add("compulsory","STRIDE","1","the frequency with which to compute the rdf and accumulate averages");
     115             :   }
     116          32 :   keys.add("optional","DENSITY","the reference density to use when normalizing the RDF");
     117          32 :   keys.addFlag("NO_AVERAGE",false,"output a two-dimensional grid with separate RDF functions for each of the central atoms");
     118          32 :   keys.add("hidden","REFERENCE","this is the label of the reference objects");
     119          64 :   keys.setValueDescription("grid","the radial distribution function");
     120          32 :   keys.needsAction("REFERENCE_GRID");
     121          32 :   keys.needsAction("VOLUME");
     122          32 :   keys.needsAction("DISTANCE_MATRIX");
     123          32 :   keys.needsAction("CUSTOM");
     124          32 :   keys.needsAction("KDE");
     125          32 :   keys.needsAction("ACCUMULATE");
     126          32 :   keys.needsAction("CONSTANT");
     127          32 :   keys.needsAction("ONES");
     128          64 :   keys.needsAction("OUTER_PRODUCT");
     129          64 : }
     130             : 
     131          10 : RDF::RDF(const ActionOptions&ao):
     132             :   Action(ao),
     133          10 :   ActionShortcut(ao) {
     134             :   bool noaverage;
     135          20 :   parseFlag("NO_AVERAGE",noaverage);
     136             :   // Read in grid extent and number of bins
     137             :   std::string maxr, nbins, dens;
     138          10 :   parse("MAXR",maxr);
     139          10 :   parse("GRID_BIN",nbins);
     140          20 :   parse("DENSITY",dens);
     141             :   double dval;
     142          10 :   if( dens.length()>0 && Tools::convertNoexcept( dens, dval ) && dval<=0 ) {
     143           2 :     error("DENSITY should be positive");
     144             :   }
     145             :   // Read input to histogram
     146             :   std::string cutoff, kernel, bandwidth, kernel_data;
     147           9 :   parse("KERNEL",kernel);
     148           9 :   if( kernel=="DISCRETE" ) {
     149             :     cutoff = maxr;
     150             :     kernel_data="KERNEL=DISCRETE";
     151           4 :     warning("rdf is normalised by dividing by the surface area at the grid value and not by the volume of the bin as it should be with discrete kernels");
     152             :   } else {
     153           7 :     parse("BANDWIDTH",bandwidth);
     154             :     double bw;
     155           7 :     Tools::convert( bandwidth, bw );
     156           7 :     if( noaverage ) {
     157             :       bandwidth += ",0";
     158             :     }
     159          14 :     kernel_data="KERNEL={" + kernel + "} BANDWIDTH=" + bandwidth;
     160             :     double fcut;
     161           7 :     Tools::convert( maxr, fcut );
     162           7 :     double rcut = 0;
     163           7 :     std::vector<std::string> kwords( Tools::getWords(kernel) );
     164           7 :     Tools::parse( kwords, "D_MAX", rcut );
     165           7 :     if( rcut==0 ) {
     166           1 :       rcut = sqrt(2.0*dp2cutoff);
     167             :     }
     168           7 :     Tools::convert( fcut + rcut*bw, cutoff );
     169           7 :   }
     170             : 
     171             :   // Create contact matrix
     172             :   std::string atom_str, group_str, groupa_str, groupb_str;
     173          18 :   parse("GROUP",group_str);
     174           9 :   if( group_str.length()>0 ) {
     175          16 :     atom_str="GROUP=" + group_str;
     176             :   } else {
     177           1 :     parse("GROUPA",groupa_str);
     178           1 :     parse("GROUPB",groupb_str);
     179           3 :     atom_str="GROUPA=" + groupa_str + " GROUPB=" + groupb_str;
     180             :   }
     181             : 
     182             :   // Calculate all the distances
     183          18 :   readInputLine( getShortcutLabel() + "_mat: DISTANCE_MATRIX LINKCELL_CUTOFF=" + cutoff + " " + atom_str);
     184             :   // Get the shape of the matrix
     185           9 :   std::vector<std::string> shape_str(2);
     186           9 :   getDistanceMatrixShape( getShortcutLabel() + "_mat", this, shape_str );
     187             : 
     188             :   // Setup the grid
     189          18 :   std::string grid_setup = "GRID_MIN=0 GRID_MAX=" + maxr + " GRID_BIN=" + nbins;
     190           9 :   if( noaverage ) {
     191             :     int iatoms;
     192             :     std::string num, str_values;
     193           2 :     Tools::convert( 0, str_values );
     194           2 :     Tools::convert( shape_str[0], iatoms );
     195          67 :     for(int i=1; i<iatoms; ++i) {
     196          65 :       Tools::convert( i, num );
     197         130 :       str_values += "," + num;
     198             :     }
     199           2 :     Tools::convert( iatoms-1, num );
     200           2 :     readInputLine( getShortcutLabel() + "_ones: ONES SIZE=" + shape_str[1] );
     201           2 :     readInputLine( getShortcutLabel() + "_index: CONSTANT VALUES=" + str_values );
     202           6 :     readInputLine( getShortcutLabel() + "_atno: OUTER_PRODUCT ARG=" + getShortcutLabel() + "_index," + getShortcutLabel() + "_ones" );
     203          10 :     grid_setup = "GRID_MIN=0,0 GRID_MAX=" + maxr + "," + num + " GRID_BIN=" + nbins + "," + num;
     204             :     shape_str[0] = "1";
     205             :   }
     206             :   // Create grid with normalizing function on it
     207             :   std::string refstr;
     208          18 :   parse("REFERENCE",refstr);
     209           9 :   if( refstr.length()==0 ) {
     210           9 :     if( noaverage ) {
     211           4 :       readInputLine( getShortcutLabel() + "_x2: REFERENCE_GRID PERIODIC=NO,NO FUNC=x*x+0*y " + grid_setup );
     212             :     } else {
     213          14 :       readInputLine( getShortcutLabel() + "_x2: REFERENCE_GRID PERIODIC=NO FUNC=x*x " + grid_setup );
     214             :     }
     215           9 :     refstr = getShortcutLabel();
     216             :   }
     217           9 :   if( dens.length()==0 ) {
     218          18 :     readInputLine( getShortcutLabel() + "_vol: VOLUME" );
     219             :   }
     220             :   // Calculate weights of distances
     221          27 :   readInputLine( getShortcutLabel() + "_wmat: CUSTOM ARG=" + getShortcutLabel() + "_mat FUNC=step(" + cutoff + "-x) PERIODIC=NO");
     222             :   // Now create a histogram from the contact matrix
     223             :   unsigned clear, stride;
     224           9 :   parse("CLEAR",clear);
     225           9 :   parse("STRIDE",stride);
     226           9 :   if( clear==1 ) {
     227           6 :     if( noaverage ) {
     228           6 :       readInputLine( getShortcutLabel() + "_kde: KDE ARG=" + getShortcutLabel() + "_mat," + getShortcutLabel() + "_atno VOLUMES=" + getShortcutLabel() + "_wmat " + grid_setup + " " + kernel_data);
     229             :     } else {
     230          25 :       readInputLine( getShortcutLabel() + "_kde: KDE ARG=" + getShortcutLabel() + "_mat VOLUMES=" + getShortcutLabel() + "_wmat " + grid_setup + " " + kernel_data);
     231             :     }
     232             :   } else {
     233             :     std::string stridestr, clearstr;
     234           3 :     Tools::convert( stride, stridestr );
     235           3 :     Tools::convert( clear, clearstr );
     236           3 :     if( noaverage ) {
     237           6 :       readInputLine( getShortcutLabel() + "_okde: KDE ARG=" + getShortcutLabel() + "_mat," + getShortcutLabel() + "_atno HEIGHTS=" + getShortcutLabel() + "_wmat " + grid_setup + " " + kernel_data);
     238             :     } else {
     239          10 :       readInputLine( getShortcutLabel() + "_okde: KDE ARG=" + getShortcutLabel() + "_mat HEIGHTS=" + getShortcutLabel() + "_wmat " + grid_setup + " " + kernel_data);
     240             :     }
     241           9 :     readInputLine( getShortcutLabel() + "_kde: ACCUMULATE ARG=" + getShortcutLabel() + "_okde STRIDE=" + stridestr + " CLEAR=" + clearstr );
     242           3 :     readInputLine( getShortcutLabel() + "_one: CONSTANT VALUE=1");
     243          12 :     readInputLine( getShortcutLabel() + "_norm: ACCUMULATE ARG=" + getShortcutLabel() + "_one STRIDE=" + stridestr + " CLEAR=" + clearstr );
     244             :   }
     245             :   // Transform the histogram by normalizing factor for rdf
     246          27 :   readInputLine( getShortcutLabel() + "_vrdf: CUSTOM ARG=" + getShortcutLabel() + "_kde," + refstr + "_x2 FUNC=x/(4*pi*y) PERIODIC=NO");
     247             :   // And normalize by density and number of atoms (separated from above to avoid nans)
     248          18 :   std::string func_str = "PERIODIC=NO ARG=" + getShortcutLabel() + "_vrdf";
     249           9 :   if( dens.length()>0 ) {
     250           0 :     if( clear==1 ) {
     251           0 :       func_str += " FUNC=x/(" + dens + "*" + shape_str[0] + ")";
     252             :     } else {
     253           0 :       func_str += "," + getShortcutLabel() + "_norm FUNC=x/(y*" + dens + "*" + shape_str[0] + ")";
     254             :     }
     255             :   } else {
     256           9 :     if( clear==1 ) {
     257          24 :       func_str += "," + refstr + "_vol FUNC=x*y/(" + shape_str[1] + "*" + shape_str[0] + ")";
     258             :     } else {
     259          15 :       func_str += "," + refstr + "_vol," + getShortcutLabel() + "_norm FUNC=x*y/(z*" + shape_str[1] + "*" + shape_str[0] + ")";
     260             :     }
     261             :   }
     262          18 :   readInputLine( getShortcutLabel() + ": CUSTOM " + func_str);
     263          20 : }
     264             : 
     265             : }
     266             : }

Generated by: LCOV version 1.16