LCOV - code coverage report
Current view: top level - ves - TD_Gaussian.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 80 88 90.9 %
Date: 2025-03-25 09:33:27 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2016-2021 The VES code team
       3             :    (see the PEOPLE-VES file at the root of this folder for a list of names)
       4             : 
       5             :    See http://www.ves-code.org for more information.
       6             : 
       7             :    This file is part of VES code module.
       8             : 
       9             :    The VES code module 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             :    The VES code module 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 the VES code module.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : 
      23             : #include "TargetDistribution.h"
      24             : 
      25             : #include "core/ActionRegister.h"
      26             : 
      27             : 
      28             : namespace PLMD {
      29             : namespace ves {
      30             : 
      31             : //+PLUMEDOC VES_TARGETDIST TD_GAUSSIAN
      32             : /*
      33             : Target distribution given by a sum of Gaussian kernels (static).
      34             : 
      35             : Employ a target distribution that is given by a sum of multivariate Gaussian (or normal)
      36             : distributions, defined as
      37             : \f[
      38             : p(\mathbf{s}) = \sum_{i} \, w_{i} \, N(\mathbf{s};\mathbf{\mu}_{i},\mathbf{\Sigma}_{i})
      39             : \f]
      40             : where \f$\mathbf{\mu}_{i}=(\mu_{1,i},\mu_{2,i},\ldots,\mu_{d,i})\f$
      41             : and \f$\mathbf{\Sigma}_{i}\f$ are
      42             : the center and the covariance matrix for the \f$i\f$-th Gaussian.
      43             : The weights \f$w_{i}\f$ are normalized to 1, \f$\sum_{i}w_{i}=1\f$.
      44             : 
      45             : By default the Gaussian distributions are considered as separable into
      46             : independent one-dimensional Gaussian distributions. In other words,
      47             : the covariance matrix is taken as diagonal
      48             : \f$\mathbf{\Sigma}_{i}=(\sigma^2_{1,i},\sigma^2_{2,i},\ldots,\sigma^{2}_{d,i})\f$.
      49             : The Gaussian distribution is then written as
      50             : \f[
      51             : N(\mathbf{s};\mathbf{\mu}_{i},\mathbf{\sigma}_{i}) =
      52             : \prod^{d}_{k} \, \frac{1}{\sqrt{2\pi\sigma^2_{d,i}}} \,
      53             : \exp\left(
      54             : -\frac{(s_{d}-\mu_{d,i})^2}{2\sigma^2_{d,i}}
      55             : \right)
      56             : \f]
      57             : where
      58             : \f$\mathbf{\sigma}_{i}=(\sigma_{1,i},\sigma_{2,i},\ldots,\sigma_{d,i})\f$
      59             : is the standard deviation.
      60             : In this case you need to specify the centers \f$\mathbf{\mu}_{i}\f$ using the
      61             : numbered CENTER keywords and the standard deviations \f$\mathbf{\sigma}_{i}\f$
      62             : using the numbered SIGMA keywords.
      63             : 
      64             : For two arguments it is possible to employ
      65             : [bivariate Gaussian kernels](https://en.wikipedia.org/wiki/Multivariate_normal_distribution)
      66             : with correlation between arguments, defined as
      67             : \f[
      68             : N(\mathbf{s};\mathbf{\mu}_{i},\mathbf{\sigma}_{i},\rho_i) =
      69             : \frac{1}{2 \pi \sigma_{1,i} \sigma_{2,i} \sqrt{1-\rho_i^2}}
      70             : \,
      71             : \exp\left(
      72             : -\frac{1}{2(1-\rho_i^2)}
      73             : \left[
      74             : \frac{(s_{1}-\mu_{1,i})^2}{\sigma_{1,i}^2}+
      75             : \frac{(s_{2}-\mu_{2,i})^2}{\sigma_{2,i}^2}-
      76             : \frac{2 \rho_i (s_{1}-\mu_{1,i})(s_{2}-\mu_{2,i})}{\sigma_{1,i}\sigma_{2,i}}
      77             : \right]
      78             : \right)
      79             : \f]
      80             : where \f$\rho_i\f$ is the correlation between \f$s_{1}\f$ and \f$s_{2}\f$
      81             : that goes from -1 to 1. In this case the covariance matrix is given as
      82             : \f[
      83             : \mathbf{\Sigma}=
      84             : \left[
      85             : \begin{array}{cc}
      86             : \sigma^2_{1,i} & \rho_i \sigma_{1,i} \sigma_{2,i} \\
      87             : \rho_i \sigma_{1,i} \sigma_{2,i} & \sigma^2_{2,i}
      88             : \end{array}
      89             : \right]
      90             : \f]
      91             : The correlation \f$\rho\f$ is given using
      92             : the numbered CORRELATION keywords. A value of \f$\rho=0\f$ means
      93             : that the arguments are considered as
      94             : un-correlated, which is the default behavior.
      95             : 
      96             : The Gaussian distributions are always defined with the conventional
      97             : normalization factor such that they are normalized to 1 over an unbounded
      98             : region. However, in calculation within VES we normally consider bounded
      99             : region on which the target distribution is defined. Thus, if the center of
     100             : a Gaussian is close to the boundary of the region it can happen that the
     101             : tails go outside the region. In that case it might be needed to use the
     102             : NORMALIZE keyword to make sure that the target distribution is properly
     103             : normalized to 1 over the bounded region. The code will issue a warning
     104             : if that is needed.
     105             : 
     106             : For periodic CVs it is generally better to use \ref TD_VONMISES "Von Mises"
     107             : distributions instead of Gaussian kernels as these distributions properly
     108             : account for the periodicity of the CVs.
     109             : 
     110             : 
     111             : \par Examples
     112             : 
     113             : One single Gaussian kernel in one-dimension.
     114             : \plumedfile
     115             : td: TD_GAUSSIAN CENTER1=-1.5 SIGMA1=0.8
     116             : \endplumedfile
     117             : 
     118             : Sum of three Gaussian kernels in two-dimensions with equal weights as
     119             : no weights are given.
     120             : \plumedfile
     121             : TD_GAUSSIAN ...
     122             :  CENTER1=-1.5,+1.5 SIGMA1=0.8,0.3
     123             :  CENTER2=+1.5,-1.5 SIGMA2=0.3,0.8
     124             :  CENTER3=+1.5,+1.5 SIGMA3=0.4,0.4
     125             :  LABEL=td
     126             : ... TD_GAUSSIAN
     127             : \endplumedfile
     128             : 
     129             : Sum of three Gaussian kernels in two-dimensions which
     130             : are weighted unequally. Note that weights are automatically
     131             : normalized to 1 so that WEIGHTS=1.0,2.0,1.0 is equal to
     132             : specifying WEIGHTS=0.25,0.50,0.25.
     133             : \plumedfile
     134             : TD_GAUSSIAN ...
     135             :  CENTER1=-1.5,+1.5 SIGMA1=0.8,0.3
     136             :  CENTER2=+1.5,-1.5 SIGMA2=0.3,0.8
     137             :  CENTER3=+1.5,+1.5 SIGMA3=0.4,0.4
     138             :  WEIGHTS=1.0,2.0,1.0
     139             :  LABEL=td
     140             : ... TD_GAUSSIAN
     141             : \endplumedfile
     142             : 
     143             : Sum of two bivariate Gaussian kernels where there is correlation of
     144             : \f$\rho_{2}=0.75\f$ between the two arguments for the second Gaussian.
     145             : \plumedfile
     146             : TD_GAUSSIAN ...
     147             :  CENTER1=-1.5,+1.5 SIGMA1=0.8,0.3
     148             :  CENTER2=+1.5,-1.5 SIGMA2=0.3,0.8 CORRELATION2=0.75
     149             :  LABEL=td
     150             : ... TD_GAUSSIAN
     151             : \endplumedfile
     152             : 
     153             : 
     154             : 
     155             : 
     156             : 
     157             : */
     158             : //+ENDPLUMEDOC
     159             : 
     160             : class TD_Gaussian: public TargetDistribution {
     161             :   std::vector< std::vector<double> > centers_;
     162             :   std::vector< std::vector<double> > sigmas_;
     163             :   std::vector< std::vector<double> > correlation_;
     164             :   std::vector<double> weights_;
     165             :   bool diagonal_;
     166             :   unsigned int ncenters_;
     167             :   double GaussianDiagonal(const std::vector<double>&, const std::vector<double>&, const std::vector<double>&, const bool normalize=true) const;
     168             :   double Gaussian2D(const std::vector<double>&, const std::vector<double>&, const std::vector<double>&, const std::vector<double>&, const bool normalize=true) const;
     169             : public:
     170             :   static void registerKeywords(Keywords&);
     171             :   explicit TD_Gaussian(const ActionOptions& ao);
     172             :   double getValue(const std::vector<double>&) const override;
     173             : };
     174             : 
     175             : 
     176             : PLUMED_REGISTER_ACTION(TD_Gaussian,"TD_GAUSSIAN")
     177             : 
     178             : 
     179         193 : void TD_Gaussian::registerKeywords(Keywords& keys) {
     180         193 :   TargetDistribution::registerKeywords(keys);
     181         193 :   keys.add("numbered","CENTER","The centers of the Gaussian distributions.");
     182         193 :   keys.add("numbered","SIGMA","The standard deviations of the Gaussian distributions.");
     183         193 :   keys.add("numbered","CORRELATION","The correlation for two-dimensional bivariate Gaussian distributions. Only works for two arguments. The value should be between -1 and 1. If no value is given the Gaussian kernels is considered as un-correlated (i.e. value of 0.0).");
     184         193 :   keys.add("optional","WEIGHTS","The weights of the Gaussian distributions. Have to be as many as the number of centers given with the numbered CENTER keywords. If no weights are given the distributions are weighted equally. The weights are automatically normalized to 1.");
     185         193 :   keys.use("WELLTEMPERED_FACTOR");
     186         193 :   keys.use("SHIFT_TO_ZERO");
     187         193 :   keys.use("NORMALIZE");
     188         193 : }
     189             : 
     190             : 
     191         191 : TD_Gaussian::TD_Gaussian(const ActionOptions& ao):
     192             :   PLUMED_VES_TARGETDISTRIBUTION_INIT(ao),
     193         382 :   centers_(0),
     194         191 :   sigmas_(0),
     195         191 :   correlation_(0),
     196         191 :   weights_(0),
     197         191 :   diagonal_(true),
     198         382 :   ncenters_(0) {
     199         211 :   for(unsigned int i=1;; i++) {
     200             :     std::vector<double> tmp_center;
     201         804 :     if(!parseNumberedVector("CENTER",i,tmp_center) ) {
     202             :       break;
     203             :     }
     204         211 :     centers_.push_back(tmp_center);
     205         211 :   }
     206         211 :   for(unsigned int i=1;; i++) {
     207             :     std::vector<double> tmp_sigma;
     208         804 :     if(!parseNumberedVector("SIGMA",i,tmp_sigma) ) {
     209             :       break;
     210             :     }
     211         211 :     sigmas_.push_back(tmp_sigma);
     212         211 :   }
     213             : 
     214         191 :   if(centers_.size()==0) {
     215           0 :     plumed_merror(getName()+": CENTER keywords seem to be missing. Note that numbered keywords start at CENTER1.");
     216             :   }
     217             :   //
     218         191 :   if(centers_.size()!=sigmas_.size()) {
     219           0 :     plumed_merror(getName()+": there has to be an equal amount of CENTER and SIGMA keywords");
     220             :   }
     221             :   //
     222         191 :   setDimension(centers_[0].size());
     223         191 :   ncenters_ = centers_.size();
     224             :   // check centers and sigmas
     225         402 :   for(unsigned int i=0; i<ncenters_; i++) {
     226         211 :     if(centers_[i].size()!=getDimension()) {
     227           0 :       plumed_merror(getName()+": one of the CENTER keyword does not match the given dimension");
     228             :     }
     229         211 :     if(sigmas_[i].size()!=getDimension()) {
     230           0 :       plumed_merror(getName()+": one of the SIGMA keyword does not match the given dimension");
     231             :     }
     232             :   }
     233             :   //
     234         191 :   correlation_.resize(ncenters_);
     235             : 
     236         402 :   for(unsigned int i=0; i<ncenters_; i++) {
     237             :     std::vector<double> corr;
     238         422 :     parseNumberedVector("CORRELATION",(i+1),corr);
     239         211 :     if(corr.size()>0) {
     240           3 :       diagonal_ = false;
     241             :     } else {
     242         208 :       corr.assign(1,0.0);
     243             :     }
     244         211 :     correlation_[i] = corr;
     245             :   }
     246             : 
     247         191 :   if(!diagonal_ && getDimension()!=2) {
     248           0 :     plumed_merror(getName()+": CORRELATION is only defined for two-dimensional Gaussians for now.");
     249             :   }
     250         402 :   for(unsigned int i=0; i<correlation_.size(); i++) {
     251         211 :     if(correlation_[i].size()!=1) {
     252           0 :       plumed_merror(getName()+": only one value should be given in CORRELATION");
     253             :     }
     254         422 :     for(unsigned int k=0; k<correlation_[i].size(); k++) {
     255         211 :       if(correlation_[i][k] <= -1.0 ||  correlation_[i][k] >= 1.0) {
     256           0 :         plumed_merror(getName()+": values given in CORRELATION should be between -1.0 and 1.0" );
     257             :       }
     258             :     }
     259             :   }
     260             :   //
     261         382 :   parseVector("WEIGHTS",weights_);
     262         191 :   if(weights_.size()==0) {
     263         188 :     weights_.assign(centers_.size(),1.0);
     264             :   }
     265         191 :   if(centers_.size()!=weights_.size()) {
     266           0 :     plumed_merror(getName()+": there has to be as many weights given in WEIGHTS as numbered CENTER keywords");
     267             :   }
     268             :   //
     269             :   double sum_weights=0.0;
     270         402 :   for(unsigned int i=0; i<weights_.size(); i++) {
     271         211 :     sum_weights+=weights_[i];
     272             :   }
     273         402 :   for(unsigned int i=0; i<weights_.size(); i++) {
     274         211 :     weights_[i]/=sum_weights;
     275             :   }
     276             :   //
     277         191 :   checkRead();
     278         191 : }
     279             : 
     280             : 
     281      229739 : double TD_Gaussian::getValue(const std::vector<double>& argument) const {
     282             :   double value=0.0;
     283      229739 :   if(diagonal_) {
     284      501589 :     for(unsigned int i=0; i<ncenters_; i++) {
     285      292252 :       value+=weights_[i]*GaussianDiagonal(argument, centers_[i], sigmas_[i]);
     286             :     }
     287       20402 :   } else if(!diagonal_ && getDimension()==2) {
     288       61206 :     for(unsigned int i=0; i<ncenters_; i++) {
     289       40804 :       value+=weights_[i]*Gaussian2D(argument, centers_[i], sigmas_[i],correlation_[i]);
     290             :     }
     291             :   }
     292      229739 :   return value;
     293             : }
     294             : 
     295             : 
     296      292252 : double TD_Gaussian::GaussianDiagonal(const std::vector<double>& argument, const std::vector<double>& center, const std::vector<double>& sigma, bool normalize) const {
     297             :   double value = 1.0;
     298      828323 :   for(unsigned int k=0; k<argument.size(); k++) {
     299      536071 :     double arg=(argument[k]-center[k])/sigma[k];
     300      536071 :     double tmp_exp = exp(-0.5*arg*arg);
     301      536071 :     if(normalize) {
     302      536071 :       tmp_exp/=(sigma[k]*sqrt(2.0*pi));
     303             :     }
     304      536071 :     value*=tmp_exp;
     305             :   }
     306      292252 :   return value;
     307             : }
     308             : 
     309             : 
     310       40804 : double TD_Gaussian::Gaussian2D(const std::vector<double>& argument, const std::vector<double>& center, const std::vector<double>& sigma, const std::vector<double>& correlation, bool normalize) const {
     311       40804 :   double arg1 = (argument[0]-center[0])/sigma[0];
     312       40804 :   double arg2 = (argument[1]-center[1])/sigma[1];
     313       40804 :   double corr = correlation[0];
     314       40804 :   double value = (arg1*arg1 + arg2*arg2 - 2.0*corr*arg1*arg2);
     315       40804 :   value *= -1.0 / ( 2.0*(1.0-corr*corr) );
     316       40804 :   value = exp(value);
     317       40804 :   if(normalize) {
     318       40804 :     value /=  2*pi*sigma[0]*sigma[1]*sqrt(1.0-corr*corr);
     319             :   }
     320       40804 :   return value;
     321             : }
     322             : 
     323             : }
     324             : }

Generated by: LCOV version 1.16