LCOV - code coverage report
Current view: top level - function - Moments.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 64 71 90.1 %
Date: 2026-11-05 10:19:04 Functions: 3 3 100.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2011-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 "FunctionShortcut.h"
      23             : #include "FunctionOfScalar.h"
      24             : #include "FunctionOfVector.h"
      25             : #include "FunctionWithSingleArgument.h"
      26             : #include "core/ActionRegister.h"
      27             : #include "FunctionSetup.h"
      28             : 
      29             : #include <cmath>
      30             : 
      31             : namespace PLMD {
      32             : namespace function {
      33             : 
      34             : //+PLUMEDOC FUNCTION MOMENTS
      35             : /*
      36             : Calculate central moments from the distribution of input quantities
      37             : 
      38             : This action takes a set of $N$ input arguments, $s_i$, and evaluates the $k$th central moment of the distribution of input arguments using:
      39             : 
      40             : $$
      41             : \mu_k = \frac{1}{N} \sum_{i=1}^N ( s_i - \langle s \rangle )^k \qquad \textrm{where} \qquad \langle s \rangle = \frac{1}{N} \sum_{i=1}^N s_i
      42             : $$
      43             : 
      44             : A single moments action can evaluate more than one central moment at once so, for example, the input below can be used to calculate the second
      45             : and third central moment for the distribution of the four input distances.
      46             : 
      47             : ```plumed
      48             : d12:  DISTANCE ATOMS=1,2
      49             : d13:  DISTANCE ATOMS=1,3
      50             : d14:  DISTANCE ATOMS=1,4
      51             : d15:  DISTANCE ATOMS=1,5
      52             : mv: MOMENTS ARG=d12,d13,d14,d15 POWERS=2,3
      53             : PRINT ARG=mv.moment-2,mv.moment-3 FILE=colvar
      54             : ```
      55             : 
      56             : Notice that you can also achieve the same result using the following input:
      57             : 
      58             : ```plumed
      59             : d: DISTANCE ATOMS1=1,2 ATOMS2=1,3 ATOMS3=1,4 ATOMS4=1,5
      60             : mv: MOMENTS ARG=d POWERS=2,3
      61             : PRINT ARG=mv.moment-2,mv.moment-3 FILE=colvar
      62             : ```
      63             : 
      64             : In this second case the four distances are passed to the MOMENTS action as a vector.  The MOMENTS action then outputs 2 components - the
      65             : two central moments that were requested.
      66             : 
      67             : These examples are representative the only two ways you can use this action.  In input it can accept either a list of scalars or a single vector.
      68             : It does not accept matrices or a list of vectors in input.
      69             : 
      70             : */
      71             : //+ENDPLUMEDOC
      72             : 
      73           1 : class Moments {
      74             : public:
      75             :   bool isperiodic;
      76             :   double min, max, pfactor;
      77             :   double max_minus_min;
      78             :   double inv_max_minus_min;
      79             :   std::vector<int> powers;
      80             :   static void registerKeywords(Keywords& keys);
      81             :   static void read( Moments& func, ActionWithArguments* action, FunctionOptions& options );
      82             :   static void calc( const Moments& func, bool noderiv, View<const double> args, FunctionOutput& funcout );
      83             : };
      84             : 
      85             : typedef FunctionShortcut<Moments> MomentsShortcut;
      86             : PLUMED_REGISTER_ACTION(MomentsShortcut,"MOMENTS")
      87             : typedef FunctionWithSingleArgument<Moments> SingleargMoments;
      88             : PLUMED_REGISTER_ACTION(SingleargMoments,"MOMENTS_ONEARG")
      89             : typedef FunctionOfScalar<Moments> ScalarMoments;
      90             : PLUMED_REGISTER_ACTION(ScalarMoments,"MOMENTS_SCALAR")
      91             : typedef FunctionOfVector<Moments> VectorMoments;
      92             : PLUMED_REGISTER_ACTION(VectorMoments,"MOMENTS_VECTOR")
      93             : 
      94          53 : void Moments::registerKeywords(Keywords& keys) {
      95          53 :   keys.add("compulsory","POWERS","calculate the central moments of the distribution of collective variables. "
      96             :            "The \\f$m\\f$th central moment of a distribution is calculated using \\f$\\frac{1}{N} \\sum_{i=1}^N ( s_i - \\overline{s} )^m \\f$, where \\f$\\overline{s}\\f$ is "
      97             :            "the average for the distribution. The POWERS keyword takes a lists of integers as input or a range. Each integer is a value of \\f$m\\f$. The final "
      98             :            "calculated values can be referenced using moment-\\f$m\\f$.");
      99         106 :   keys.addOutputComponent("moment","default","scalar","the central moments of the distribution of values. The second central moment "
     100             :                           "would be referenced elsewhere in the input file using "
     101             :                           "<em>label</em>.moment-2, the third as <em>label</em>.moment-3, etc.");
     102          53 : }
     103             : 
     104           9 : void Moments::read( Moments& func, ActionWithArguments* action, FunctionOptions& options ) {
     105           9 :   func.isperiodic = (action->getPntrToArgument(0))->isPeriodic();
     106           9 :   if( func.isperiodic ) {
     107             :     std::string str_min, str_max;
     108           2 :     (action->getPntrToArgument(0))->getDomain( str_min, str_max );
     109           2 :     for(unsigned i=1; i<action->getNumberOfArguments(); ++i) {
     110           0 :       if( !(action->getPntrToArgument(i))->isPeriodic() ) {
     111           0 :         action->error("cannot mix periodic and non periodic variables when calculating moments");
     112             :       }
     113             :       std::string str_min2, str_max2;
     114           0 :       (action->getPntrToArgument(i))->getDomain( str_min2, str_max2);
     115           0 :       if( str_min!=str_min2 || str_max!=str_max2 ) {
     116           0 :         action->error("all input arguments should have same domain when calculating moments");
     117             :       }
     118             :     }
     119           2 :     Tools::convert(str_min,func.min);
     120           2 :     Tools::convert(str_max,func.max);
     121           2 :     func.max_minus_min = func.max - func.min;
     122           2 :     func.inv_max_minus_min = 1 / func.max_minus_min;
     123           2 :     func.pfactor = 2*pi*func.inv_max_minus_min;
     124             :   } else {
     125          16 :     for(unsigned i=1; i<action->getNumberOfArguments(); ++i) {
     126           9 :       if( (action->getPntrToArgument(i))->isPeriodic() ) {
     127           0 :         action->error("cannot mix periodic and non periodic variables when calculating moments");
     128             :       }
     129             :     }
     130             :   }
     131             : 
     132           9 :   action->parseVector("POWERS",func.powers);
     133          24 :   for(unsigned i=0; i<func.powers.size(); ++i) {
     134          15 :     if( func.powers[i]<2 ) {
     135           0 :       action->error("first central moment is zero do you really need to calculate that");
     136             :     }
     137          15 :     action->log.printf("  computing %dth central moment of distribution of input cvs \n", func.powers[i]);
     138             :     std::string num;
     139          15 :     Tools::convert(func.powers[i],num);
     140          30 :     options.multipleValuesForEachRegisteredComponent.push_back( "-" + num );
     141             :   }
     142           9 : }
     143             : 
     144         234 : void Moments::calc( const Moments& func, bool noderiv, const View<const double> args, FunctionOutput& funcout ) {
     145             :   double mean=0;
     146         234 :   double inorm = 1.0 / static_cast<double>( args.size() );
     147         234 :   if( func.isperiodic ) {
     148             :     // Circular mean: the angles are measured from the lower end of the domain,
     149             :     // so the mean is min + (max-min)*atan2(<sin>,<cos>)/(2 pi)
     150             :     double sinsum=0, cossum=0;
     151          60 :     for(unsigned i=0; i<args.size(); ++i) {
     152          50 :       const double val=func.pfactor*( args[i] - func.min );
     153          50 :       sinsum+=sin(val);
     154          50 :       cossum+=cos(val);
     155             :     }
     156          10 :     const double meanangle = atan2( sinsum, cossum );
     157          10 :     mean = func.min + func.max_minus_min*meanangle/(2*pi);
     158             :     // derivative of the circular mean with respect to argument i is cos(theta_i-theta_mean)/R
     159             :     // with R=|sum_j exp(i theta_j)|; it reduces to 1/N when all the angles are equal
     160          10 :     const double r2 = sinsum*sinsum + cossum*cossum;
     161          10 :     const double invr = r2>0 ? 1.0/std::sqrt(r2) : 0.0;
     162          30 :     for(unsigned npow=0; npow<func.powers.size(); ++npow) {
     163             :       double dev1=0;
     164         120 :       for(unsigned i=0; i<args.size(); ++i) {
     165         100 :         double s = func.inv_max_minus_min*( args[i] - mean );
     166         100 :         s = Tools::pbc(s);
     167         100 :         dev1+=pow( s*func.max_minus_min, func.powers[npow] - 1 );
     168             :       }
     169          20 :       funcout.values[npow] = 0;
     170          20 :       double prefactor = func.powers[npow]*inorm;
     171         120 :       for(unsigned i=0; i<args.size(); ++i) {
     172         100 :         double tmp = func.inv_max_minus_min*( args[i] - mean );
     173         100 :         tmp = func.max_minus_min*Tools::pbc(tmp);
     174         100 :         funcout.values[npow] += inorm*pow( tmp, func.powers[npow] );
     175         100 :         if( !noderiv ) {
     176         100 :           const double dmean = invr*cos( func.pfactor*( args[i] - func.min ) - meanangle );
     177         100 :           funcout.derivs[npow][i] = prefactor*(pow( tmp, func.powers[npow] - 1 ) - dev1*dmean);
     178             :         }
     179             :       }
     180             :     }
     181             :   } else {
     182        1768 :     for(unsigned i=0; i<args.size(); ++i) {
     183        1544 :       mean+=args[i];
     184             :     }
     185         224 :     mean *= inorm;
     186         661 :     for(unsigned npow=0; npow<func.powers.size(); ++npow) {
     187             :       double dev1=0;
     188        3421 :       for(unsigned i=0; i<args.size(); ++i) {
     189        2984 :         dev1+=pow( args[i] - mean, func.powers[npow] - 1 );
     190             :       }
     191         437 :       dev1*=inorm;
     192         437 :       funcout.values[npow] = 0;
     193         437 :       double prefactor = func.powers[npow]*inorm;
     194        3421 :       for(unsigned i=0; i<args.size(); ++i) {
     195        2984 :         double tmp=args[i] - mean;
     196        2984 :         funcout.values[npow] += inorm*pow( tmp, func.powers[npow] );
     197        2984 :         if( !noderiv ) {
     198         112 :           funcout.derivs[npow][i] = prefactor*(pow( tmp, func.powers[npow] - 1 ) - dev1);
     199             :         }
     200             :       }
     201             :     }
     202             :   }
     203         234 : }
     204             : 
     205             : }
     206             : }
     207             : 
     208             : 

Generated by: LCOV version 1.16