LCOV - code coverage report
Current view: top level - wham - Wham.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 64 69 92.8 %
Date: 2025-11-25 13:55:50 Functions: 3 6 50.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2017-2020 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 "core/ActionRegister.h"
      23             : #include "core/ActionWithValue.h"
      24             : #include "core/ActionWithArguments.h"
      25             : #include "core/PlumedMain.h"
      26             : #include "tools/Communicator.h"
      27             : 
      28             : //+PLUMEDOC REWEIGHTING WHAM
      29             : /*
      30             : Calculate the weights for configurations using the weighted histogram analysis method.
      31             : 
      32             : Suppose that you have run multiple \f$N\f$ trajectories each of which was computed by integrating a different biased Hamiltonian. We can calculate the probability of observing
      33             : the set of configurations during the \f$N\f$ trajectories that we ran using the product of multinomial distributions shown below:
      34             : \f[
      35             : P( \vec{T} ) \propto \prod_{j=1}^M \prod_{k=1}^N (c_k w_{kj} p_j)^{t_{kj}}
      36             : \label{eqn:wham1}
      37             : \f]
      38             : In this expression the second product runs over the biases that were used when calculating the \f$N\f$ trajectories.  The first product then runs over the
      39             : \f$M\f$ bins in our histogram.  The \f$p_j\f$ variable that is inside this product is the quantity we wish to extract; namely, the unbiased probability of
      40             : having a set of CV values that lie within the range for the \f$j\f$th bin.
      41             : 
      42             : The quantity that we can easily extract from our simulations, \f$t_{kj}\f$, however, measures the number of frames from trajectory \f$k\f$ that are inside the \f$j\f$th bin.
      43             : To interpret this quantity we must consider the bias that acts on each of the replicas so the \f$w_{kj}\f$ term is introduced.  This quantity is calculated as:
      44             : \f[
      45             : w_{kj} =
      46             : \f]
      47             : and is essentially the factor that we have to multiply the unbiased probability of being in the bin by in order to get the probability that we would be inside this same bin in
      48             : the \f$k\f$th of our biased simulations.  Obviously, these \f$w_{kj}\f$ values depend on the value that the CVs take and also on the particular trajectory that we are investigating
      49             : all of which, remember, have different simulation biases.  Finally, \f$c_k\f$ is a free parameter that ensures that, for each \f$k\f$, the biased probability is normalized.
      50             : 
      51             : We can use the equation for the probability that was given above to find a set of values for \f$p_j\f$ that maximizes the likelihood of observing the trajectory.
      52             : This constrained optimization must be performed using a set of Lagrange multipliers, \f$\lambda_k\f$, that ensure that each of the biased probability distributions
      53             : are normalized, that is \f$\sum_j c_kw_{kj}p_j=1\f$.  Furthermore, as the problem is made easier if the quantity in the equation above is replaced by its logarithm
      54             : we actually chose to minimize
      55             : \f[
      56             : \mathcal{L}= \sum_{j=1}^M \sum_{k=1}^N t_{kj} \ln c_k  w_{kj} p_j + \sum_k\lambda_k \left( \sum_{j=1}^N c_k w_{kj} p_j - 1 \right)
      57             : \f]
      58             : After some manipulations the following (WHAM) equations emerge:
      59             : \f[
      60             : \begin{aligned}
      61             : p_j & \propto \frac{\sum_{k=1}^N t_{kj}}{\sum_k c_k w_{kj}} \\
      62             : c_k & =\frac{1}{\sum_{j=1}^M w_{kj} p_j}
      63             : \end{aligned}
      64             : \f]
      65             : which can be solved by computing the \f$p_j\f$ values using the first of the two equations above with an initial guess for the \f$c_k\f$ values and by then refining
      66             : these \f$p_j\f$ values using the \f$c_k\f$ values that are obtained by inserting the \f$p_j\f$ values obtained into the second of the two equations above.
      67             : 
      68             : Notice that only \f$\sum_k t_{kj}\f$, which is the total number of configurations from all the replicas that enter the \f$j\f$th bin, enters the WHAM equations above.
      69             : There is thus no need to record which replica generated each of the frames.  One can thus simply gather the trajectories from all the replicas together at the outset.
      70             : This observation is important as it is the basis of the binless formulation of WHAM that is implemented within PLUMED.
      71             : 
      72             : \par Examples
      73             : 
      74             : */
      75             : //+ENDPLUMEDOC
      76             : 
      77             : namespace PLMD {
      78             : namespace wham {
      79             : 
      80             : class Wham :
      81             :   public ActionWithValue,
      82             :   public ActionWithArguments {
      83             : private:
      84             :   double thresh, simtemp;
      85             :   unsigned nreplicas;
      86             :   unsigned maxiter;
      87             : public:
      88             :   static void registerKeywords(Keywords&);
      89             :   explicit Wham(const ActionOptions&ao);
      90           0 :   unsigned getNumberOfDerivatives() {
      91           0 :     return 0;
      92             :   }
      93             :   void calculate() override ;
      94           0 :   void apply() override {}
      95             : };
      96             : 
      97             : PLUMED_REGISTER_ACTION(Wham,"WHAM")
      98             : 
      99          29 : void Wham::registerKeywords(Keywords& keys ) {
     100          29 :   Action::registerKeywords( keys );
     101          29 :   ActionWithValue::registerKeywords( keys );
     102          29 :   ActionWithArguments::registerKeywords( keys );
     103          29 :   keys.remove("ARG");
     104          58 :   keys.add("compulsory","ARG","the stored values for the bias");
     105          58 :   keys.add("compulsory","MAXITER","1000","maximum number of iterations for WHAM algorithm");
     106          58 :   keys.add("compulsory","WHAMTOL","1e-10","threshold for convergence of WHAM algorithm");
     107          58 :   keys.add("optional","TEMP","the system temperature.  This is not required if your MD code passes this quantity to PLUMED");
     108          29 :   keys.remove("NUMERICAL_DERIVATIVES");
     109          29 :   keys.setValueDescription("the vector of WHAM weights to use for reweighting the elements in a time series");
     110          29 : }
     111             : 
     112          13 : Wham::Wham(const ActionOptions&ao):
     113             :   Action(ao),
     114             :   ActionWithValue(ao),
     115          13 :   ActionWithArguments(ao) {
     116             :   // Read in the temperature
     117          13 :   simtemp=getkBT();
     118          13 :   if(simtemp==0) {
     119           0 :     error("The MD engine does not pass the temperature to plumed so you have to specify it using TEMP");
     120             :   }
     121             :   // Now read in parameters of WHAM
     122          13 :   parse("MAXITER",maxiter);
     123          13 :   parse("WHAMTOL",thresh);
     124          13 :   if(comm.Get_rank()==0) {
     125          13 :     nreplicas=multi_sim_comm.Get_size();
     126             :   }
     127          13 :   comm.Bcast(nreplicas,0);
     128          13 :   addValue( getPntrToArgument(0)->getShape() );
     129          13 :   setNotPeriodic();
     130          13 : }
     131             : 
     132          12 : void Wham::calculate() {
     133             :   // Retrieve the values that were stored for the biase
     134          12 :   std::vector<double> stored_biases( getPntrToArgument(0)->getNumberOfValues() );
     135       25284 :   for(unsigned i=0; i<stored_biases.size(); ++i) {
     136       25272 :     stored_biases[i] = getPntrToArgument(0)->get(i);
     137             :   }
     138             :   // Get the minimum value of the bias
     139          12 :   double minv = *min_element(std::begin(stored_biases), std::end(stored_biases));
     140             :   // Resize final weights array
     141          12 :   plumed_assert( stored_biases.size()%nreplicas==0 );
     142          12 :   std::vector<double> final_weights( stored_biases.size() / nreplicas, 1.0 );
     143          12 :   if( getPntrToComponent(0)->getNumberOfValues()!=final_weights.size() ) {
     144          12 :     std::vector<unsigned> shape(1);
     145          12 :     shape[0]=final_weights.size();
     146          12 :     getPntrToComponent(0)->setShape( shape );
     147             :   }
     148             :   // Offset and exponential of the bias
     149          12 :   std::vector<double> expv( stored_biases.size() );
     150       25284 :   for(unsigned i=0; i<expv.size(); ++i) {
     151       25272 :     expv[i] = exp( (-stored_biases[i]+minv) / simtemp );
     152             :   }
     153             :   // Initialize Z
     154          12 :   std::vector<double> Z( nreplicas, 1.0 ), oldZ( nreplicas );
     155             :   // Now the iterative loop to calculate the WHAM weights
     156        4584 :   for(unsigned iter=0; iter<maxiter; ++iter) {
     157             :     // Store Z
     158       32088 :     for(unsigned j=0; j<Z.size(); ++j) {
     159       27504 :       oldZ[j]=Z[j];
     160             :     }
     161             :     // Recompute weights
     162             :     double norm=0;
     163     1613568 :     for(unsigned j=0; j<final_weights.size(); ++j) {
     164             :       double ew=0;
     165    11262888 :       for(unsigned k=0; k<Z.size(); ++k) {
     166     9653904 :         ew += expv[j*Z.size()+k]  / Z[k];
     167             :       }
     168     1608984 :       final_weights[j] = 1.0 / ew;
     169     1608984 :       norm += final_weights[j];
     170             :     }
     171             :     // Normalize weights
     172     1613568 :     for(unsigned j=0; j<final_weights.size(); ++j) {
     173     1608984 :       final_weights[j] /= norm;
     174             :     }
     175             :     // Recompute Z
     176       32088 :     for(unsigned j=0; j<Z.size(); ++j) {
     177       27504 :       Z[j] = 0.0;
     178             :     }
     179     1613568 :     for(unsigned j=0; j<final_weights.size(); ++j) {
     180    11262888 :       for(unsigned k=0; k<Z.size(); ++k) {
     181     9653904 :         Z[k] += final_weights[j]*expv[j*Z.size()+k];
     182             :       }
     183             :     }
     184             :     // Normalize Z and compute change in Z
     185             :     double change=0;
     186             :     norm=0;
     187       32088 :     for(unsigned k=0; k<Z.size(); ++k) {
     188       27504 :       norm+=Z[k];
     189             :     }
     190       32088 :     for(unsigned k=0; k<Z.size(); ++k) {
     191       27504 :       Z[k] /= norm;
     192       27504 :       double d = std::log( Z[k] / oldZ[k] );
     193       27504 :       change += d*d;
     194             :     }
     195        4584 :     if( change<thresh ) {
     196        4224 :       for(unsigned j=0; j<final_weights.size(); ++j) {
     197        4212 :         getPntrToComponent(0)->set( j, final_weights[j] );
     198             :       }
     199          12 :       return;
     200             :     }
     201             :   }
     202           0 :   error("Too many iterations in WHAM" );
     203             : }
     204             : 
     205             : }
     206             : }

Generated by: LCOV version 1.16