LCOV - code coverage report
Current view: top level - gridtools - KDE.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 208 284 73.2 %
Date: 2025-12-04 11:19:34 Functions: 34 45 75.6 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2015-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 "KDE.h"
      23             : #include "core/ActionShortcut.h"
      24             : #include "core/ActionRegister.h"
      25             : 
      26             : //+PLUMEDOC ANALYSIS KDE
      27             : /*
      28             : Create a histogram from the input scalar/vector/matrix using KDE
      29             : 
      30             : This action can be used to construct instantaneous distributions for quantities by using [kernel density esstimation](https://en.wikipedia.org/wiki/Kernel_density_estimation).
      31             : The input arguments must all have the same rank and size but you can use a scalar, vector or matrix in input.  The distribution
      32             : of this quantity on a grid is then computed using kernel density estimation.
      33             : 
      34             : The following example demonstrates how this action can be used with a scalar as input:
      35             : 
      36             : ```plumed
      37             : d1: DISTANCE ATOMS=1,2
      38             : kde: KDE ARG=d1 GRID_MIN=0.0 GRID_MAX=1.0 GRID_BIN=100 BANDWIDTH=0.2
      39             : DUMPGRID ARG=kde STRIDE=1 FILE=kde.grid
      40             : ```
      41             : 
      42             : This input outputs a different file on every time step. These files contain a function stored on a grid.  The function output in this case
      43             : consists of a single Gaussian with $\sigma=0.2$ that is centered on the instantaneous value of the distance between atoms 1 and 2.  Obviously,
      44             : you are unlikely to use an input like the one above. The more usual thing to do would be to accumulate the histogram over the course of a
      45             : few trajectory frames using the [ACCUMULATE](ACCUMULATE.md) command as has been done in the input below, which estimates a histogram as a function
      46             : of two collective variables:
      47             : 
      48             : ```plumed
      49             : d1: DISTANCE ATOMS=1,2
      50             : d2: DISTANCE ATOMS=1,2
      51             : kde: KDE ARG=d1,d2 GRID_MIN=0.0,0.0 GRID_MAX=1.0,1.0 GRID_BIN=100,100 BANDWIDTH=0.2,0.2
      52             : histo: ACCUMULATE ARG=kde STRIDE=1
      53             : DUMPGRID ARG=histo FILE=histo.grid STRIDE=10000
      54             : ```
      55             : 
      56             : Notice, that you can also achieve something similar by using the [HISTOGRAM](HISTOGRAM.md) shortcut.
      57             : 
      58             : ## Controlloing the grid
      59             : 
      60             : If you prefer to specify the grid spacing rather than the number of bins you can do so using the GRID_SPACING keyword as shown below:
      61             : 
      62             : ```plumed
      63             : d1: DISTANCE ATOMS=1,2
      64             : kde: KDE ARG=d1 GRID_MIN=0.0 GRID_MAX=1.0 GRID_SPACING=0.01 BANDWIDTH=0.2
      65             : DUMPGRID ARG=kde STRIDE=1 FILE=kde.grid
      66             : ```
      67             : 
      68             : If $x$ is one of the input arguments to the KDE action and $x<g_{min}$ or $x>g_{max}$, where $g_{min}$ and $g_{max}$ are the minimum
      69             : and maximum values on the grid for that argument that were specified using GRID_MIN and GRID_MAX, then by PLUMED will crash.
      70             : 
      71             : Notice also that when you use Gaussian kernels to accumulate a denisty as in the input above you need to define a cutoff beyond, which the
      72             : Gaussian (which is a function with infinite support) is assumed not to contribute to the accumulated density.  When setting this cutoff you
      73             : set the value of $x$ in the following expression $\sigma \sqrt{2*x}$, where $\sigma$ is the bandwidth.  By default $x$ is set equal to 6.25 but
      74             : you can change this value by using the CUTOFF keyword as shown below:
      75             : 
      76             : ```plumed
      77             : d1: DISTANCE ATOMS=1,2
      78             : kde: KDE ARG=d1 GRID_MIN=0.0 GRID_MAX=1.0 GRID_SPACING=0.01 BANDWIDTH=0.2 CUTOFF=6.25
      79             : DUMPGRID ARG=kde STRIDE=1 FILE=kde.grid
      80             : ```
      81             : 
      82             : ## Constructing the density
      83             : 
      84             : If you are performing a simulation in the NVT ensemble and wish to look at the density as a function of position in the cell you can use an input like the one shown below:
      85             : 
      86             : ```plumed
      87             : a: FIXEDATOM AT=0,0,0
      88             : dens: DISTANCES ATOMS=1-100 ORIGIN=a COMPONENTS
      89             : kde: KDE ARG=dens.x,dens.y,dens.z GRID_BIN=100,100,100 BANDWIDTH=0.05,0.05,0.05
      90             : DUMPGRID ARG=kde STRIDE=1 FILE=density
      91             : ```
      92             : 
      93             : Notice that you do not need to specify GRID_MIN and GRID_MAX values with this input. In this case PLUMED gets the extent of the grid from the cell vectors during the first
      94             : step of the simulation.
      95             : 
      96             : ## Specifying a non diagonal bandwidth
      97             : 
      98             : If for any reason you want to use a bandwidth that is not diagonal when doing kensity density estimation you can do by using an input similar to the one shown below:
      99             : 
     100             : ```plumed
     101             : d1: DISTANCE ATOMS=1,2
     102             : d2: DISTANCE ATOMS=1,2
     103             : kde: KDE ...
     104             :   ARG=d1,d2 GRID_MIN=0.0,0.0
     105             :   GRID_MAX=1.0,1.0 GRID_BIN=100,100
     106             :   BANDWIDTH=0.2,0.1,0.1,0.2 HEIGHTS=1
     107             : ...
     108             : histo: ACCUMULATE ARG=kde STRIDE=1
     109             : DUMPGRID ARG=histo FILE=histo.grid STRIDE=10000
     110             : ```
     111             : 
     112             : As there are two arguments for this KDE action the four numbers passed in the bandwdith parameter are interepretted as a $2\times 2$ matrix.
     113             : Notice that you can also pass the information for the bandwidth in from another argument as has been done here:
     114             : 
     115             : ```plumed
     116             : m: CONSTANT VALUES=0.2,0.1,0.1,0.2 NROWS=2 NCOLS=2
     117             : 
     118             : d1: DISTANCE ATOMS=1,2
     119             : d2: DISTANCE ATOMS=1,2
     120             : kde: KDE ...
     121             :   ARG=d1,d2 GRID_MIN=0.0,0.0
     122             :   GRID_MAX=1.0,1.0 GRID_BIN=100,100
     123             :   BANDWIDTH=m HEIGHTS=1
     124             : ...
     125             : histo: ACCUMULATE ARG=kde STRIDE=1
     126             : DUMPGRID ARG=histo FILE=histo.grid STRIDE=10000
     127             : ```
     128             : 
     129             : In this case the input is equivalent to the first input above and the bandwidth is a constant.  You could, however, also use a non-constant value as input to the BANDWIDTH keyword.
     130             : 
     131             : ## Working with vectors and scalars
     132             : 
     133             : If the input to your KDE action is a set of scalars it appears odd to separate the process of computing the KDE from the process of accumulating the histogram. However, if
     134             : you are using vectors as in the example below, this division can be helpful.
     135             : 
     136             : ```plumed
     137             : d1: DISTANCE ATOMS1=1,2 ATOMS2=3,4 ATOMS3=5,6 ATOMS4=7,8 ATOMS5=9,10
     138             : kde: KDE ARG=d1 GRID_MIN=0.0 GRID_MAX=1.0 GRID_BIN=100 BANDWIDTH=0.2
     139             : ```
     140             : 
     141             : In the papea cited in the bibliography below, the [KL_ENTROPY](KL_ENTROPY.md) between the instantaneous distribution of CVs and a reference distribution was introduced
     142             : as a collective variable. As is detailed in the documentation for that action, the ability to calculate the instaneous histogram from an input vector is essential to
     143             : reproducing these calculations.
     144             : 
     145             : Notice that you can also use a one or multiple matrices in the input for a KDE object.  The example below uses the angles between the z axis and set of bonds aroud two
     146             : atoms:
     147             : 
     148             : ```plumed
     149             : d1: DISTANCE_MATRIX GROUPA=1,2 GROUPB=3-10 COMPONENTS
     150             : phi: CUSTOM ARG=d1.z,d1.w FUNC=acos(x/y) PERIODIC=NO
     151             : kde: KDE ARG=phi GRID_MIN=0 GRID_MAX=pi GRID_BIN=200 BANDWIDTH=0.1
     152             : ```
     153             : 
     154             : ## Using different weights
     155             : 
     156             : In all the inputs above the kernels that are added to the grid on each step are Gaussians with that are normalised so that their integral over all space is one. If you want your
     157             : Gaussians to have a particular height you can use the HEIGHT keyword as illustrated below:
     158             : 
     159             : ```plumed
     160             : d1: CONTACT_MATRIX GROUPA=1,2 GROUPB=3-10 SWITCH={RATIONAL R_0=0.1} COMPONENTS
     161             : mag: CUSTOM ARG=d1.x,d1.y,d1.z FUNC=x*x+y*y+z*z PERIODIC=NO
     162             : phi: CUSTOM ARG=d1.z,mag FUNC=acos(x/sqrt(y)) PERIODIC=NO
     163             : kde: KDE ARG=phi GRID_MIN=0 GRID_MAX=pi HEIGHTS=d1.w GRID_BIN=200 BANDWIDTH=0.1
     164             : ```
     165             : 
     166             : As indicated above, the HEIGHTS keyword should be passed a Value that has the same rank and size as the arguments that are passed using the ARG keyword. Each of the Gaussian kernels
     167             : that are added to the grid in this case have a value equal to the weight at the maximum of the function.
     168             : 
     169             : Notice that you can also use the VOLUMES keyword in a similar way as shown below:
     170             : 
     171             : ```plumed
     172             : d1: CONTACT_MATRIX GROUPA=1,2 GROUPB=3-10 SWITCH={RATIONAL R_0=0.1} COMPONENTS
     173             : mag: CUSTOM ARG=d1.x,d1.y,d1.z FUNC=x*x+y*y+z*z PERIODIC=NO
     174             : phi: CUSTOM ARG=d1.z,mag FUNC=acos(x/sqrt(y)) PERIODIC=NO
     175             : kde: KDE ARG=phi GRID_MIN=0 GRID_MAX=pi VOLUMES=d1.w GRID_BIN=200 BANDWIDTH=0.1
     176             : ```
     177             : 
     178             : Now, however, the integral of the Gaussians over all space are equal to the elements of d1.w.
     179             : 
     180             : */
     181             : //+ENDPLUMEDOC
     182             : 
     183             : //+PLUMEDOC ANALYSIS SPHERICAL_KDE
     184             : /*
     185             : Create a histogram from the input scalar/vector/matrix using SPHERICAL_KDE
     186             : 
     187             : This action operates similarly to [KDE](KDE.md) but it is designed to be used for investigating [directional statistics]().
     188             : It is particularly useful if you are looking at the distribution of bond vectors as illustrated in the input below:
     189             : 
     190             : ```plumed
     191             : # Calculate all the bond vectors
     192             : d1: CONTACT_MATRIX GROUP=1-100 SWITCH={RATIONAL R_0=0.1} COMPONENTS
     193             : # Normalise the bond vectors
     194             : mag: CUSTOM ARG=d1.x,d1.y,d1.z FUNC=sqrt(x*x+y*y+z*z) PERIODIC=NO
     195             : d1x: CUSTOM ARG=d1.x,mag FUNC=x/y PERIODIC=NO
     196             : d1y: CUSTOM ARG=d1.y,mag FUNC=x/y PERIODIC=NO
     197             : d1z: CUSTOM ARG=d1.z,mag FUNC=x/y PERIODIC=NO
     198             : # And construct the KDE
     199             : kde: SPHERICAL_KDE ARG=d1x,d1y,d1z HEIGHTS=d1.w CONCENTRATION=100 GRID_BIN=144
     200             : ```
     201             : 
     202             : Each bond vector here contributes a [Fisher von-Mises kernel](https://en.wikipedia.org/wiki/Von_Mises–Fisher_distribution) to the spherical grid.  This spherical grid is constructed
     203             : using a [Fibonnacci sphere algorithm](https://stackoverflow.com/questions/9600801/evenly-distributing-n-points-on-a-sphere) so the number of specified using the GRID_BIN keyword must be a Fibonacci number.
     204             : 
     205             : */
     206             : //+ENDPLUMEDOC
     207             : 
     208             : namespace PLMD {
     209             : namespace gridtools {
     210             : 
     211             : template <class K, class P>
     212             : class KDEGridTools {
     213             : public:
     214             :   double dp2cutoff;
     215             :   std::vector<double> gspacing;
     216             :   std::vector<std::size_t> nbin;
     217             :   std::vector<std::string> gmin, gmax;
     218             :   static void registerKeywords( Keywords& keys );
     219             :   static void readHeightKeyword( bool canusevol, std::size_t nargs, const std::vector<std::string>& bw, ActionWithArguments* action );
     220             :   static void readBandwidth( std::size_t nargs, ActionWithArguments* action, std::vector<std::string>& bw );
     221             :   static void readBandwidthKeyword( std::size_t nargs, ActionWithArguments* action, std::vector<std::string>& bw, std::vector<Value*>& bwargs );
     222             :   static void readBandwidthAndHeight( const P& params, ActionWithArguments* action );
     223             :   static void convertHeightsToVolumes( const std::size_t& nargs, const std::vector<std::string>& bw, const std::string& volstr, ActionWithArguments* action );
     224             :   static void readGridParameters( KDEGridTools<K,P>& g, ActionWithArguments* action, GridCoordinatesObject& gridobject, std::vector<std::size_t>& shape );
     225             :   static void setupGridBounds( KDEGridTools<K,P>& g, const Tensor& box, GridCoordinatesObject& gridobject, const std::vector<Value*>& args, Value* myval );
     226             :   static void getDiscreteSupport( const KDEGridTools<K,P>& g, P& p, const K& kp, std::vector<unsigned>& nneigh, GridCoordinatesObject& gridobject );
     227             :   static void getNeighbors( const P& p, K& kp, const GridCoordinatesObject& gridobject, const std::vector<unsigned>& nneigh, unsigned& num_neighbors, std::vector<unsigned>& neighbors );
     228             : };
     229             : 
     230             : template <class K, class P>
     231         311 : void KDEGridTools<K,P>::registerKeywords( Keywords& keys ) {
     232         311 :   keys.add("optional","BANDWIDTH","the bandwidths for kernel density esimtation");
     233         311 :   keys.add("optional","VOLUMES","this keyword take the label of an action that calculates a vector of values.  The elements of this vector "
     234             :            "divided by the volume of the Gaussian are used as weights for the Gaussians");
     235         311 :   keys.add("optional","HEIGHTS","this keyword takes the label of an action that calculates a vector of values. The elements of this vector "
     236             :            "are used as weights for the Gaussians.");
     237         311 :   keys.add("compulsory","GRID_MIN","auto","the lower bounds for the grid");
     238         311 :   keys.add("compulsory","GRID_MAX","auto","the upper bounds for the grid");
     239         311 :   keys.add("compulsory","CUTOFF","6.25","the cutoff at which to stop evaluating the kernel functions is set equal to sqrt(2*x)*bandwidth in each direction where x is this number");
     240         311 :   keys.add("optional","GRID_SPACING","the approximate grid spacing (to be used as an alternative or together with GRID_BIN)");
     241         311 :   keys.add("optional","GRID_BIN","the number of bins for the grid");
     242         311 : }
     243             : 
     244             : template <class K, class P>
     245          65 : void KDEGridTools<K,P>::readHeightKeyword( bool canusevol, std::size_t nargs, const std::vector<std::string>& bw, ActionWithArguments* action ) {
     246             :   std::string weight_str;
     247         130 :   action->parse("HEIGHTS",weight_str);
     248             :   std::string str_nvals;
     249          65 :   if( (action->getPntrToArgument(0))->getRank()==2 ) {
     250             :     std::string nr, nc;
     251           3 :     Tools::convert( (action->getPntrToArgument(0))->getShape()[0], nr );
     252           3 :     Tools::convert( (action->getPntrToArgument(0))->getShape()[1], nc );
     253           6 :     str_nvals = nr + "," + nc;
     254             :   } else {
     255          62 :     Tools::convert( (action->getPntrToArgument(0))->getNumberOfValues(), str_nvals );
     256             :   }
     257          65 :   if( weight_str.length()>0 ) {
     258          34 :     KDEHelper<K,P,KDEGridTools<K,P>>::readKernelParameters( weight_str, action, "_heights", true );
     259          48 :   } else if( canusevol ) {
     260          29 :     action->plumed.readInputWords( Tools::getWords(action->getLabel() + "_volumes: ONES SIZE=" + str_nvals ), false );
     261          58 :     KDEGridTools<K,P>::convertHeightsToVolumes(nargs,bw,action->getLabel() + "_volumes",action);
     262             :   } else {
     263          19 :     action->plumed.readInputWords( Tools::getWords(action->getLabel() + "_heights: ONES SIZE=" + str_nvals ), false );
     264          38 :     KDEHelper<K,P,KDEGridTools<K,P>>::addArgument( action->getLabel() + "_heights", action );
     265             :   }
     266          65 : }
     267             : 
     268             : template <>
     269          13 : void KDEGridTools<DiagonalKernelParams,DiscreteKernel>::readBandwidthAndHeight( const DiscreteKernel& params, ActionWithArguments* action ) {
     270          13 :   std::size_t nargs = action->getNumberOfArguments();
     271          13 :   KDEGridTools<DiagonalKernelParams,DiscreteKernel>::readHeightKeyword( false, nargs, std::vector<std::string>(), action );
     272          13 : }
     273             : 
     274             : template<class K, class P>
     275          66 : void KDEGridTools<K, P>::readBandwidthKeyword( std::size_t nargs, ActionWithArguments* action, std::vector<std::string>& bw, std::vector<Value*>& bwargs ) {
     276          66 :   action->parseVector("BANDWIDTH",bw);
     277          66 :   if( nargs>1 && bw.size()==1 ) {
     278           0 :     ActionWithArguments::interpretArgumentList( bw, action->plumed.getActionSet(), action, bwargs );
     279           0 :     if( bwargs.size()!=1 ) {
     280           0 :       action->error("invalid bandwidth found");
     281             :     }
     282             :     // Create a vector of ones with the right size
     283             :     std::string nvals;
     284           0 :     if( (action->getPntrToArgument(0))->getRank()==2 ) {
     285             :       std::string nr, nc;
     286           0 :       Tools::convert( (action->getPntrToArgument(0))->getShape()[0], nr );
     287           0 :       Tools::convert( (action->getPntrToArgument(0))->getShape()[1], nc );
     288           0 :       nvals = nr + "," + nc;
     289             :     } else {
     290           0 :       Tools::convert( (action->getPntrToArgument(0))->getNumberOfValues(), nvals );
     291             :     }
     292           0 :     action->plumed.readInputWords( Tools::getWords(action->getLabel() + "_bwones: ONES SIZE=" + nvals ), false );
     293             :   }
     294          66 : }
     295             : 
     296             : template <class K, class P>
     297          66 : void KDEGridTools<K,P>::readBandwidth( std::size_t nargs, ActionWithArguments* action, std::vector<std::string>& bw ) {
     298          66 :   plumed_assert( typeid(K)==typeid(DiagonalKernelParams) );
     299             :   std::vector<Value*> bwargs;
     300          66 :   readBandwidthKeyword( nargs, action, bw, bwargs );
     301          66 :   if( nargs>1 && bw.size()==1 ) {
     302           0 :     if( bwargs[0]->getRank()!=1 || bwargs[0]->getNumberOfValues()!=nargs ) {
     303           0 :       action->error("invalid input for bandwidth parameter");
     304             :     }
     305             :     std::string str_i;
     306           0 :     bw.resize( nargs );
     307           0 :     for(unsigned i=0; i<nargs; ++i) {
     308           0 :       Tools::convert( i+1, str_i );
     309           0 :       action->plumed.readInputWords( Tools::getWords(action->getLabel() + "_scalar_bw" + str_i + ": SELECT_COMPONENTS ARG=" + bwargs[0]->getName() + " COMPONENTS=" + str_i ), false );
     310           0 :       bw[i] = action->getLabel() + "_bw" + str_i;
     311           0 :       action->plumed.readInputWords( Tools::getWords( bw[i] + ": CUSTOM ARG=" + action->getLabel() + "_bwones," + action->getLabel() + "_scalar_bw" + str_i + " FUNC=x*y PERIODIC=NO"), false );
     312             :     }
     313          66 :   } else if( bw.size()==nargs ) {
     314             :     double bwval;
     315             :     std::string str_i;
     316         164 :     for(unsigned i=0; i<nargs; ++i) {
     317          98 :       Tools::convert( i+1, str_i );
     318          98 :       if( Tools::convertNoexcept( bw[i], bwval ) && fabs(bwval)<epsilon ) {
     319           4 :         KDEHelper<K,P,KDEGridTools<K,P>>::readKernelParameters( bw[i], action, "_bwz" + str_i, true );
     320             :       } else {
     321         192 :         KDEHelper<K,P,KDEGridTools<K,P>>::readKernelParameters( bw[i], action, "_bw" + str_i, true );
     322             :       }
     323             :     }
     324             :   } else {
     325           0 :     action->error("wrong number of arguments specified in input to bandwidth parameter");
     326             :   }
     327          66 : }
     328             : 
     329             : template <>
     330          18 : void KDEGridTools<DiagonalKernelParams,HistogramBeadKernel>::readBandwidthAndHeight( const HistogramBeadKernel& params, ActionWithArguments* action ) {
     331             :   std::vector<std::string> bw;
     332          18 :   std::size_t nargs = action->getNumberOfArguments();
     333          18 :   readBandwidth( nargs, action, bw );
     334          18 :   KDEGridTools<DiagonalKernelParams,HistogramBeadKernel>::readHeightKeyword( false, nargs, bw, action );
     335          18 : }
     336             : 
     337             : template <>
     338          48 : void KDEGridTools<DiagonalKernelParams,RegularKernel<DiagonalKernelParams>>::readBandwidthAndHeight( const RegularKernel<DiagonalKernelParams>& params, ActionWithArguments* action ) {
     339             :   std::vector<Value*> bwargs;
     340             :   std::vector<std::string> bw;
     341          48 :   std::size_t nargs = action->getNumberOfArguments();
     342          48 :   readBandwidth( nargs, action, bw );
     343             :   std::string volstr;
     344          96 :   action->parse("VOLUMES",volstr);
     345          48 :   if( volstr.length()>0 ) {
     346          14 :     if( !params.canusevol ) {
     347           0 :       action->error("cannot use normalized kernels with selected kernel type");
     348             :     }
     349             :     // Check if we are using Gaussian kernels
     350          14 :     KDEHelper<DiagonalKernelParams,RegularKernel<DiagonalKernelParams>,KDEGridTools<DiagonalKernelParams,RegularKernel<DiagonalKernelParams>>>::readKernelParameters( volstr, action, "_volumes", false );
     351          14 :     convertHeightsToVolumes(nargs, bw, volstr, action);
     352             :   } else {
     353          34 :     KDEGridTools<DiagonalKernelParams,RegularKernel<DiagonalKernelParams>>::readHeightKeyword( params.canusevol, nargs, bw, action );
     354             :   }
     355          96 : }
     356             : 
     357             : template <>
     358           0 : void KDEGridTools<NonDiagonalKernelParams,RegularKernel<NonDiagonalKernelParams>>::readBandwidthAndHeight( const RegularKernel<NonDiagonalKernelParams>& params, ActionWithArguments* action ) {
     359             :   std::vector<Value*> bwargs;
     360             :   std::vector<std::string> bw;
     361           0 :   std::size_t nargs = action->getNumberOfArguments();
     362           0 :   readBandwidthKeyword( nargs, action, bw, bwargs );
     363           0 :   if( nargs>1 && bw.size()==1 ) {
     364           0 :     if( bwargs[0]->getRank()!=2 || bwargs[0]->getShape()[0]!=nargs || bwargs[0]->getShape()[1]!=nargs  ) {
     365           0 :       action->error("invalid input for bandwidth parameter");
     366             :     }
     367             :     std::string str_i, str_j;
     368           0 :     bw.resize( nargs*nargs );
     369           0 :     for(unsigned i=0; i<nargs; ++i) {
     370           0 :       Tools::convert( i+1, str_i );
     371           0 :       for(unsigned j=0; j<nargs; ++j) {
     372           0 :         Tools::convert( j+1, str_j );
     373           0 :         action->plumed.readInputWords( Tools::getWords(action->getLabel() + "_scalar_bw" + str_i + "_" + str_j + ": SELECT_COMPONENTS ARG=" + bwargs[0]->getName() + " COMPONENTS=" + str_i + "." + str_j ), false );
     374           0 :         bw[i*nargs+j] = action->getLabel() + "_bw" + str_i + "_" + str_j;
     375           0 :         action->plumed.readInputWords( Tools::getWords( bw[i*nargs+j] + ": CUSTOM ARG=" + action->getLabel() + "_bwones," + action->getLabel() + "_scalar_bw" + str_i + "_" + str_j + " FUNC=x*y PERIODIC=NO"), false );
     376             :       }
     377             :     }
     378           0 :   } else if( bw.size()==nargs*nargs ) {
     379             :     std::string str_i, str_j;
     380           0 :     for(unsigned i=0; i<nargs; ++i) {
     381           0 :       Tools::convert( i+1, str_i );
     382           0 :       for(unsigned j=0; j<nargs; ++j) {
     383           0 :         Tools::convert( j+1, str_j );
     384           0 :         KDEHelper<NonDiagonalKernelParams,RegularKernel<NonDiagonalKernelParams>,KDEGridTools<NonDiagonalKernelParams,RegularKernel<NonDiagonalKernelParams>>>::readKernelParameters( bw[i*nargs+j], action, "_bw" + str_i + "_" + str_j, true );
     385             :       }
     386             :     }
     387             :   } else {
     388           0 :     action->error("wrong number of arguments specified in input to bandwidth parameter");
     389             :   }
     390             :   std::string volstr;
     391           0 :   action->parse("VOLUMES",volstr);
     392           0 :   if( volstr.length()>0 ) {
     393           0 :     if( !params.canusevol ) {
     394           0 :       action->error("cannot use normalized kernels with selected kernel type");
     395             :     }
     396             :     // Check if we are using Gaussian kernels
     397           0 :     KDEHelper<NonDiagonalKernelParams,RegularKernel<NonDiagonalKernelParams>,KDEGridTools<DiagonalKernelParams,RegularKernel<DiagonalKernelParams>>>::readKernelParameters( volstr, action, "_volumes", false );
     398           0 :     convertHeightsToVolumes(nargs, bw, volstr, action);
     399             :   } else {
     400           0 :     KDEGridTools<NonDiagonalKernelParams,RegularKernel<NonDiagonalKernelParams>>::readHeightKeyword( params.canusevol, nargs, bw, action );
     401             :   }
     402           0 : }
     403             : 
     404             : template <class K, class P>
     405          43 : void KDEGridTools<K, P>::convertHeightsToVolumes( const std::size_t& nargs, const std::vector<std::string>& bw, const std::string& volstr, ActionWithArguments* action ) {
     406          43 :   if( bw.size()==nargs ) {
     407          43 :     unsigned nonzeroargs=0;
     408         110 :     for(unsigned i=0; i<nargs; ++i) {
     409          67 :       if( bw[i].find("_bwz")==std::string::npos ) {
     410          65 :         nonzeroargs++;
     411             :       }
     412             :     }
     413             :     std::string str_i, nargs_str;
     414          43 :     Tools::convert( nonzeroargs, nargs_str );
     415          86 :     std::string varstr = "VAR=h", funcstr = "FUNC=h/(sqrt((2*pi)^" + nargs_str + ")", argstr = "ARG=" + volstr;
     416         110 :     for(unsigned i=0; i<nargs; ++i) {
     417          67 :       if( bw[i].find("_bwz")!=std::string::npos ) {
     418           2 :         continue;
     419             :       }
     420          65 :       Tools::convert( i+1, str_i );
     421          65 :       varstr += ",b" + str_i;
     422         130 :       funcstr += "*b" + str_i;
     423         130 :       argstr += "," + bw[i];
     424             :     }
     425             :     funcstr += ")";
     426          43 :     if( (action->getPntrToArgument(0))->getNumberOfValues()==1 ) {
     427          27 :       action->plumed.readInputWords( Tools::getWords(action->getLabel() + "_heights: CUSTOM PERIODIC=NO " + argstr + " " + varstr + " " + funcstr), false );
     428             :     } else {
     429          16 :       action->plumed.readInputWords( Tools::getWords(action->getLabel() + "_heights: CUSTOM PERIODIC=NO " + argstr + " " + varstr + " " + funcstr + " MASK=" + volstr), false );
     430             :     }
     431          86 :     KDEHelper<K,P,KDEGridTools<K,P>>::addArgument( action->getLabel() + "_heights", action );
     432           0 :   } else if( bw.size()==nargs*nargs ) {
     433           0 :     action->error("have not implemented normalization parameter for non-diagonal kernels");
     434             :   }
     435             : 
     436          43 : }
     437             : 
     438             : template <class K, class P>
     439          79 : void KDEGridTools<K,P>::readGridParameters( KDEGridTools<K,P>& g, ActionWithArguments* action, GridCoordinatesObject& gridobject, std::vector<std::size_t>& shape ) {
     440          79 :   g.gmin.resize( shape.size() );
     441          79 :   g.gmax.resize( shape.size() );
     442          79 :   action->parseVector("GRID_MIN",g.gmin);
     443          79 :   action->parseVector("GRID_MAX",g.gmax);
     444         191 :   for(unsigned i=0; i<g.gmin.size(); ++i) {
     445         112 :     if( g.gmin[i]=="auto" ) {
     446          52 :       action->log.printf("  for %dth coordinate min and max are set automatically \n", (i+1) );
     447          52 :       if( g.gmax[i]!="auto" ) {
     448           0 :         action->error("if gmin is set automatically gmax must also be set automatically");
     449             :       }
     450             :       Value* myarg = action->getPntrToArgument(i);
     451          52 :       if( myarg->isPeriodic() ) {
     452           0 :         if( g.gmin[i]=="auto" ) {
     453           0 :           myarg->getDomain( g.gmin[i], g.gmax[i] );
     454             :         } else {
     455             :           std::string str_min, str_max;
     456           0 :           myarg->getDomain( str_min, str_max );
     457           0 :           if( str_min!=g.gmin[i] || str_max!=g.gmax[i] ) {
     458           0 :             action->error("all periodic arguments should have the same domain");
     459             :           }
     460             :         }
     461          52 :       } else if( myarg->getName().find(".")!=std::string::npos ) {
     462          52 :         std::size_t dot = myarg->getName().find_first_of(".");
     463          52 :         std::string name = myarg->getName().substr(dot+1);
     464          90 :         if( name!="x" && name!="y" && name!="z" ) {
     465           0 :           action->error("cannot set GRID_MIN and GRID_MAX automatically if input argument is not component of distance");
     466             :         }
     467             :       } else {
     468           0 :         action->error("cannot set GRID_MIN and GRID_MAX automatically if input argument is not component of distance");
     469             :       }
     470             :     } else {
     471          60 :       action->log.printf("  for %dth coordinate min is set to %s and max is set to %s \n", (i+1), g.gmin[i].c_str(), g.gmax[i].c_str() );
     472             :     }
     473             :   }
     474             : 
     475          79 :   action->parseVector("GRID_BIN",g.nbin);
     476          79 :   action->parseVector("GRID_SPACING",g.gspacing);
     477         158 :   action->parse("CUTOFF",g.dp2cutoff);
     478             : 
     479          79 :   if( g.nbin.size()!=shape.size() && g.gspacing.size()!=shape.size() ) {
     480           0 :     action->error("GRID_BIN or GRID_SPACING must be set");
     481             :   }
     482             :   // Create a value
     483          79 :   std::vector<bool> ipbc( shape.size() );
     484         191 :   for(unsigned i=0; i<shape.size(); ++i) {
     485         210 :     if( (action->getPntrToArgument( i ))->isPeriodic() || g.gmin[i]=="auto" ) {
     486             :       ipbc[i]=true;
     487          66 :       if( g.nbin.size()==shape.size() ) {
     488          66 :         shape[i] = g.nbin[i];
     489             :       }
     490             :     } else {
     491             :       ipbc[i]=false;
     492          46 :       if( g.nbin.size()==shape.size() ) {
     493          46 :         shape[i] = g.nbin[i]+1;
     494             :       }
     495             :     }
     496             :   }
     497         158 :   gridobject.setup( "flat", ipbc, 0, 0.0 );
     498          79 : }
     499             : 
     500             : template <class K, class P>
     501          76 : void KDEGridTools<K,P>::setupGridBounds( KDEGridTools<K,P>& g, const Tensor& box, GridCoordinatesObject& gridobject, const std::vector<Value*>& args, Value* myval ) {
     502         181 :   for(unsigned i=0; i<gridobject.getDimension(); ++i) {
     503         105 :     if( g.gmin[i]=="auto" ) {
     504             :       double lcoord, ucoord;
     505          46 :       std::size_t dot = args[i]->getName().find_first_of(".");
     506          46 :       std::string name = args[i]->getName().substr(dot+1);
     507          46 :       if( name=="x" ) {
     508          24 :         lcoord=-0.5*box(0,0);
     509          24 :         ucoord=0.5*box(0,0);
     510          22 :       } else if( name=="y" ) {
     511          12 :         lcoord=-0.5*box(1,1);
     512          12 :         ucoord=0.5*box(1,1);
     513          10 :       } else if( name=="z" ) {
     514          10 :         lcoord=-0.5*box(2,2);
     515          10 :         ucoord=0.5*box(2,2);
     516             :       } else {
     517           0 :         plumed_error();
     518             :       }
     519             :       // And convert to strings for bin and bmax
     520          46 :       Tools::convert( lcoord, g.gmin[i] );
     521          46 :       Tools::convert( ucoord, g.gmax[i] );
     522             :     }
     523             :   }
     524             :   // And setup the grid object
     525          76 :   gridobject.setBounds( g.gmin, g.gmax, g.nbin, g.gspacing );
     526          76 :   myval->setShape( gridobject.getNbin(true) );
     527          76 : }
     528             : 
     529             : template <class K, class P>
     530          64 : void KDEGridTools<K, P>::getDiscreteSupport( const KDEGridTools<K,P>& g, P& p, const K& kp, std::vector<unsigned>& nneigh, GridCoordinatesObject& gridobject ) {
     531          64 :   std::size_t ng = gridobject.getDimension();
     532          64 :   plumed_assert( nneigh.size()==ng );
     533          64 :   std::vector<double> support( ng );
     534          64 :   P::getSupport( p, kp, g.dp2cutoff, support );
     535         156 :   for(unsigned i=0; i<ng; ++i) {
     536          92 :     nneigh[i] = static_cast<unsigned>( ceil( support[i]/gridobject.getGridSpacing()[i] ));
     537             :   }
     538          64 : }
     539             : 
     540             : template <>
     541          12 : void KDEGridTools<DiagonalKernelParams,DiscreteKernel>::getDiscreteSupport( const KDEGridTools<DiagonalKernelParams,DiscreteKernel>& g, DiscreteKernel& p, const DiagonalKernelParams& kp, std::vector<unsigned>& nneigh, GridCoordinatesObject& gridobject ) {
     542          12 :   return;
     543             : }
     544             : 
     545             : template <class K, class P>
     546      191342 : void KDEGridTools<K,P>::getNeighbors( const P& p, K& kp, const GridCoordinatesObject& gridobject, const std::vector<unsigned>& nneigh, unsigned& num_neighbors, std::vector<unsigned>& neighbors ) {
     547      191342 :   gridobject.getNeighbors( kp.at, nneigh, num_neighbors, neighbors );
     548      191342 : }
     549             : 
     550             : template <>
     551       32290 : void KDEGridTools<DiagonalKernelParams,DiscreteKernel>::getNeighbors( const DiscreteKernel& p, DiagonalKernelParams& kp, const GridCoordinatesObject& gridobject, const std::vector<unsigned>& nneigh, unsigned& num_neighbors, std::vector<unsigned>& neighbors ) {
     552       32290 :   num_neighbors=1;
     553       32290 :   neighbors.resize(1);
     554       65364 :   for(unsigned i=0; i<kp.at.size(); ++i) {
     555       33074 :     kp.at[i] += 0.5*gridobject.getGridSpacing()[i];
     556             :   }
     557       32290 :   neighbors[0]=gridobject.getIndex( kp.at );
     558       32290 : }
     559             : 
     560             : class SphericalKDEGridTools {
     561             : public:
     562             :   std::size_t nbins;
     563             :   static void registerKeywords( Keywords& keys );
     564             :   static void readBandwidthAndHeight( const UniversalVonMisses& params, ActionWithArguments* action );
     565             :   static void readGridParameters( SphericalKDEGridTools& g, ActionWithArguments* action, GridCoordinatesObject& gridobject, std::vector<std::size_t>& shape );
     566             :   static void setupGridBounds( SphericalKDEGridTools& g, const Tensor& box, GridCoordinatesObject& gridobject, const std::vector<Value*>& args, Value* myval ) {}
     567             :   static void getDiscreteSupport( const SphericalKDEGridTools& g, const UniversalVonMisses& p, const VonMissesKernelParams& kp, std::vector<unsigned>& nneigh, GridCoordinatesObject& gridobject );
     568             :   static void getNeighbors( const UniversalVonMisses& p, const VonMissesKernelParams& kp, const GridCoordinatesObject& gridobject, const std::vector<unsigned>& nneigh, unsigned& num_neighbors, std::vector<unsigned>& neighbors );
     569             : };
     570             : 
     571          12 : void SphericalKDEGridTools::registerKeywords( Keywords& keys ) {
     572          12 :   keys.add("compulsory","CONCENTRATION","the concentration parameter for the Von Mises-Fisher distributions");
     573          12 :   keys.add("compulsory","HEIGHTS","1.0","this keyword takes the label of an action that calculates a vector of values. The elements of this vector "
     574             :            "are used as weights for the Gaussians.");
     575          12 :   keys.add("compulsory","GRID_BIN","the number of points on the fibonacci sphere at which the density should be evaluated");
     576          12 : }
     577             : 
     578          10 : void SphericalKDEGridTools::readBandwidthAndHeight( const UniversalVonMisses& params, ActionWithArguments* action ) {
     579             :   // Read in the concentration parameters
     580             :   std::string von_misses_concentration;
     581          10 :   action->parse("CONCENTRATION",von_misses_concentration);
     582          10 :   KDEHelper<VonMissesKernelParams,UniversalVonMisses,SphericalKDEGridTools>::readKernelParameters( von_misses_concentration, action, "_vmconcentration", true );
     583          10 :   action->log.printf("  getting concentration parameters from %s \n", von_misses_concentration.c_str() );
     584             :   // Read in the heights
     585             :   std::string weight_str;
     586          10 :   action->parse("HEIGHTS",weight_str);
     587          20 :   KDEHelper<VonMissesKernelParams,UniversalVonMisses,SphericalKDEGridTools>::readKernelParameters( weight_str, action, "_volumes", false );
     588          10 :   if( (action->getPntrToArgument(0))->getNumberOfValues()==1 ) {
     589           0 :     action->plumed.readInputWords( Tools::getWords(action->getLabel() + "_heights: CUSTOM PERIODIC=NO ARG=" + weight_str + "," + action->getLabel() + "_vmconcentration FUNC=x*y/(4*pi*sinh(y))" ), false );
     590             :   } else {
     591          10 :     action->plumed.readInputWords( Tools::getWords(action->getLabel() + "_heights: CUSTOM PERIODIC=NO ARG=" + weight_str + "," + action->getLabel() + "_vmconcentration FUNC=x*y/(4*pi*sinh(y)) MASK=" + weight_str ), false );
     592             :   }
     593          10 :   KDEHelper<VonMissesKernelParams,UniversalVonMisses,SphericalKDEGridTools>::addArgument( action->getLabel() + "_heights", action );
     594          10 :   action->log.printf("  getting heights from %s \n", weight_str.c_str() );
     595          10 : }
     596             : 
     597          10 : void SphericalKDEGridTools::readGridParameters( SphericalKDEGridTools& g, ActionWithArguments* action, GridCoordinatesObject& gridobject, std::vector<std::size_t>& shape ) {
     598          10 :   if( shape.size()!=3 ) {
     599           0 :     action->error("should have three coordinates in input to this action");
     600             :   }
     601          10 :   action->parse("GRID_BIN",g.nbins);
     602          10 :   action->log.printf("  setting number of bins to %zu \n", g.nbins );
     603          10 :   std::vector<bool> ipbc( 3, false );
     604          10 :   gridobject.setup( "fibonacci", ipbc, g.nbins, 0 );
     605          10 :   shape[0]=g.nbins;
     606          10 :   shape[1]=shape[2]=1;
     607          10 : }
     608             : 
     609          10 : void SphericalKDEGridTools::getDiscreteSupport( const SphericalKDEGridTools& g, const UniversalVonMisses& p, const VonMissesKernelParams& kp, std::vector<unsigned>& nneigh, GridCoordinatesObject& gridobject ) {
     610          10 :   plumed_assert( nneigh.size()==gridobject.getDimension() );
     611          10 :   std::vector<bool> ipbc( 3, false );
     612          10 :   double fib_cutoff = std::log( epsilon / (kp.concentration/(4*pi*sinh(kp.concentration))) ) / kp.concentration;
     613          20 :   gridobject.setup( "fibonacci", ipbc, gridobject.getNumberOfPoints(), fib_cutoff );
     614          10 : }
     615             : 
     616      205624 : void SphericalKDEGridTools::getNeighbors( const UniversalVonMisses& p, const VonMissesKernelParams& kp, const GridCoordinatesObject& gridobject, const std::vector<unsigned>& nneigh, unsigned& num_neighbors, std::vector<unsigned>& neighbors ) {
     617      205624 :   gridobject.getNeighbors( kp.at, nneigh, num_neighbors, neighbors );
     618      205624 : }
     619             : 
     620             : typedef KDE<DiagonalKernelParams,DiscreteKernel,KDEGridTools<DiagonalKernelParams,DiscreteKernel>> discretekde;
     621             : PLUMED_REGISTER_ACTION(discretekde,"KDE_DISCRETE")
     622             : typedef KDE<DiagonalKernelParams,HistogramBeadKernel,KDEGridTools<DiagonalKernelParams,HistogramBeadKernel>> beadkde;
     623             : PLUMED_REGISTER_ACTION(beadkde,"KDE_BEADS")
     624             : typedef KDE<DiagonalKernelParams,RegularKernel<DiagonalKernelParams>,KDEGridTools<DiagonalKernelParams,RegularKernel<DiagonalKernelParams>>> flatkde;
     625             : PLUMED_REGISTER_ACTION(flatkde,"KDE_KERNELS")
     626             : typedef KDE<VonMissesKernelParams,UniversalVonMisses,SphericalKDEGridTools> sphericalkde;
     627             : PLUMED_REGISTER_ACTION(sphericalkde,"SPHERICAL_KDE")
     628             : typedef KDE<NonDiagonalKernelParams,RegularKernel<NonDiagonalKernelParams>,KDEGridTools<NonDiagonalKernelParams,RegularKernel<NonDiagonalKernelParams>>> flatfkde;
     629             : PLUMED_REGISTER_ACTION(flatfkde,"KDE_FULLCOVAR")
     630             : 
     631             : 
     632             : class KDEShortcut : public ActionShortcut {
     633             : public:
     634             :   static void registerKeywords(Keywords& keys);
     635             :   explicit KDEShortcut(const ActionOptions&);
     636             : };
     637             : 
     638             : PLUMED_REGISTER_ACTION(KDEShortcut,"KDE")
     639             : 
     640         142 : void KDEShortcut::registerKeywords(Keywords& keys) {
     641         142 :   KDE<DiagonalKernelParams,RegularKernel<DiagonalKernelParams>,KDEGridTools<DiagonalKernelParams,RegularKernel<DiagonalKernelParams>>>::registerKeywords( keys );
     642         142 :   keys.addActionNameSuffix("_DISCRETE");
     643         142 :   keys.addActionNameSuffix("_KERNELS");
     644         142 :   keys.addActionNameSuffix("_BEADS");
     645         142 :   keys.addActionNameSuffix("_FULLCOVAR");
     646         142 : }
     647             : 
     648          79 : KDEShortcut::KDEShortcut(const ActionOptions&ao):
     649             :   Action(ao),
     650          79 :   ActionShortcut(ao) {
     651             :   std::string kerneltype;
     652         158 :   parse("KERNEL",kerneltype);
     653          79 :   if( kerneltype=="DISCRETE" ) {
     654          26 :     readInputLine( getShortcutLabel() + ": KDE_DISCRETE " + convertInputLineToString() );
     655             :     return;
     656             :   }
     657             :   std::vector<std::string> args;
     658         132 :   parseVector("ARG", args );
     659             :   std::vector<Value*> argvals;
     660          66 :   ActionWithArguments::interpretArgumentList( args, plumed.getActionSet(), this, argvals );
     661          66 :   std::string argstr = " ARG=" + argvals[0]->getName();
     662          98 :   for(unsigned i=1; i<argvals.size(); ++i) {
     663          64 :     argstr += "," + argvals[i]->getName();
     664             :   }
     665             :   std::vector<std::string> bw;
     666         132 :   parseVector("BANDWIDTH",bw);
     667          66 :   std::string bwstr = " BANDWIDTH=" + bw[0];
     668          98 :   for(unsigned i=1; i<bw.size(); ++i) {
     669          64 :     bwstr += "," + bw[i];
     670             :   }
     671          66 :   if( bw.size() == 1 && argvals.size()>1 ) {
     672             :     std::vector<Value*> bwargs;
     673           0 :     ActionWithArguments::interpretArgumentList( bw, plumed.getActionSet(), this, bwargs );
     674           0 :     if( bwargs.size()!=1 ) {
     675           0 :       error("invalid input for bandwidth parameter");
     676           0 :     } else if( bwargs[0]->getRank()<=1 ) {
     677           0 :       if( kerneltype.find("bin")==std::string::npos ) {
     678           0 :         readInputLine( getShortcutLabel() + ": KDE_KERNELS " + argstr + " " + bwstr + " KERNEL=" + kerneltype + " " + convertInputLineToString() );
     679             :       } else {
     680           0 :         std::size_t dd = kerneltype.find("-bin");
     681           0 :         readInputLine( getShortcutLabel() + ": KDE_BEADS " + argstr + " " + bwstr + " KERNEL=" + kerneltype.substr(0,dd) + " " + convertInputLineToString() );
     682             :       }
     683           0 :     } else if( bwargs[0]->getRank()==2 ) {
     684           0 :       readInputLine( getShortcutLabel() + ": KDE_FULLCOVAR" + argstr + " " + bwstr + " KERNEL=" + kerneltype + " " + convertInputLineToString() );
     685             :     } else {
     686           0 :       error("found strange rank for bandwidth parameter");
     687             :     }
     688          66 :   } else if( bw.size()==argvals.size() ) {
     689          66 :     if( kerneltype.find("bin")==std::string::npos ) {
     690          96 :       readInputLine( getShortcutLabel() + ": KDE_KERNELS " + argstr + " " + bwstr + " KERNEL=" + kerneltype + " " + convertInputLineToString() );
     691             :     } else {
     692          18 :       std::size_t dd = kerneltype.find("-bin");
     693          36 :       readInputLine( getShortcutLabel() + ": KDE_BEADS " + argstr + " " + bwstr + " KERNEL=" + kerneltype.substr(0,dd) + " " + convertInputLineToString() );
     694             :     }
     695           0 :   } else if( bw.size()==argvals.size()*argvals.size() ) {
     696           0 :     readInputLine( getShortcutLabel() + ": KDE_FULLCOVAR" + argstr + " " + bwstr + " KERNEL=" + kerneltype + " " + convertInputLineToString() );
     697             :   } else {
     698           0 :     error("invalid input for bandwidth");
     699             :   }
     700         132 : }
     701             : 
     702             : }
     703             : }

Generated by: LCOV version 1.16