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

Generated by: LCOV version 1.16