LCOV - code coverage report
Current view: top level - function - FuncPathGeneral.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 101 130 77.7 %
Date: 2025-04-08 21:11:17 Functions: 5 6 83.3 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2020-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             : 
      23             : #include "Function.h"
      24             : #include "core/ActionRegister.h"
      25             : #include "tools/IFile.h"
      26             : #include <limits>
      27             : 
      28             : namespace PLMD {
      29             : namespace function {
      30             : 
      31             : //+PLUMEDOC FUNCTION FUNCPATHGENERAL
      32             : /*
      33             : This function calculates path collective variables (PCVs) using an arbitrary combination of collective variables.
      34             : 
      35             : The method used to calculate the PCVs that is used in this method is described in the paper from the bibliography below.
      36             : 
      37             : This variable computes the progress along a given set of frames that is provided in an input file ("s" component) and the distance from them ("z" component).
      38             : The input file could be a colvar file generated with plumed driver on a trajectory containing the frames.
      39             : 
      40             : The metric for the path collective variables takes the following form:
      41             : 
      42             : $$
      43             : R[X - X_i] = \sum_{j=1}^M c_j^2 (x_j - x_{i,j})^2\,.
      44             : $$
      45             : 
      46             : Here, the coefficients $c_j$ determine the relative weights of the collective variables $c_j$ in the metric.
      47             : A value for the lambda coefficient also needs to be provided, typically chosen in such a way that it ensures a smooth variation of the "s" component.
      48             : 
      49             : ## Examples
      50             : 
      51             : This command calculates the PCVs using the values from the file COLVAR_TRAJ and the provided values for the lambda and the coefficients.
      52             : Since the columns in the file were not specified, the first one will be ignored (assumed to correspond to the time) and the rest used.
      53             : 
      54             : ```plumed
      55             : FUNCPATHGENERAL ...
      56             : LABEL=path
      57             : LAMBDA=12.2
      58             : REFERENCE=COLVAR_TRAJ
      59             : COEFFICIENTS=0.3536,0.3536,0.3536,0.3536,0.7071
      60             : ARG=d1,d2,d,t,drmsd
      61             : ... FUNCPATHGENERAL
      62             : ```
      63             : 
      64             : The command below is a variation of the previous one, specifying a subset of the collective variables and using a neighbor list.
      65             : The columns are zero-indexed.
      66             : The neighbor list will include the 10 closest frames and will be recalculated every 20 steps.
      67             : 
      68             : ```plumed
      69             : FUNCPATHGENERAL ...
      70             : LABEL=path
      71             : LAMBDA=5.0
      72             : REFERENCE=COLVAR_TRAJ
      73             : COLUMNS=2,3,4
      74             : COEFFICIENTS=0.3536,0.3536,0.3536
      75             : ARG=d2,d,t
      76             : NEIGH_SIZE=10
      77             : NEIGH_STRIDE=20
      78             : ... FUNCPATHGENERAL
      79             : ```
      80             : 
      81             : */
      82             : //+ENDPLUMEDOC
      83             : 
      84             : class FuncPathGeneral : public Function {
      85             :   double lambda;
      86             :   int neigh_size;
      87             :   double neigh_stride;
      88             : 
      89             :   std::vector<double> coefficients;
      90             :   std::vector< std::vector<double> > path_cv_values;
      91             : 
      92             :   // For faster calculation
      93             :   std::vector<double> expdists;
      94             : 
      95             :   // For calculating derivatives
      96             :   std::vector< std::vector<double> > numerators;
      97             :   std::vector<double> s_path_ders;
      98             :   std::vector<double> z_path_ders;
      99             : 
     100             :   // For handling periodicity
     101             :   std::vector<double> domains;
     102             : 
     103             :   std::string reference;
     104             :   std::vector<int> columns;
     105             : 
     106             :   std::vector< std::pair<int,double> > neighpair;
     107             :   std::vector <Value*> allArguments;
     108             : 
     109             :   // Methods
     110             :   void loadReference();
     111             : 
     112             :   struct pairordering {
     113             :     bool operator ()(std::pair<int, double> const& a, std::pair<int, double> const& b) {
     114           0 :       return (a).second < (b).second;
     115             :     }
     116             :   };
     117             : 
     118             : public:
     119             :   explicit FuncPathGeneral(const ActionOptions&);
     120             : // Active methods:
     121             :   virtual void calculate();
     122             :   virtual void prepare();
     123             :   static void registerKeywords(Keywords& keys);
     124             : };
     125             : 
     126             : PLUMED_REGISTER_ACTION(FuncPathGeneral, "FUNCPATHGENERAL")
     127             : 
     128           1 : void FuncPathGeneral::loadReference() {
     129           1 :   IFile input;
     130           1 :   input.open(reference);
     131           1 :   if (!input) {
     132           0 :     plumed_merror("Could not open the reference file!");
     133             :   }
     134          18 :   while (input) {
     135             :     std::vector<std::string> strings;
     136          17 :     Tools::getParsedLine(input, strings);
     137          17 :     if (strings.empty()) {
     138             :       continue;
     139             :     }
     140             :     std::vector<double> colvarLine;
     141             :     double value;
     142          16 :     int max = columns.empty() ? strings.size() : columns.size();
     143         128 :     for (int i = 0; i < max; ++i) {
     144         112 :       int col = columns.empty() ? i : columns[i];
     145             :       // If no columns have been entered, ignore the first (time) and take the rest
     146         112 :       if (columns.empty() && i == 0) {
     147          16 :         continue;
     148             :       }
     149             : 
     150          96 :       Tools::convert(strings[col], value);
     151          96 :       colvarLine.push_back(value);
     152             :     }
     153          16 :     path_cv_values.push_back(colvarLine);
     154          17 :   }
     155           1 : }
     156             : 
     157           3 : void FuncPathGeneral::registerKeywords(Keywords& keys) {
     158           3 :   Function::registerKeywords(keys);
     159           3 :   keys.add("compulsory", "LAMBDA", "Lambda parameter required for smoothing");
     160           3 :   keys.add("compulsory", "COEFFICIENTS", "Coefficients to be assigned to the CVs");
     161           3 :   keys.add("compulsory", "REFERENCE", "Colvar file needed to provide the CV milestones");
     162           3 :   keys.add("optional", "COLUMNS", "List of columns in the reference colvar file specifying the CVs");
     163           3 :   keys.add("optional", "NEIGH_SIZE", "Size of the neighbor list");
     164           3 :   keys.add("optional", "NEIGH_STRIDE", "How often the neighbor list needs to be calculated in time units");
     165           6 :   keys.addOutputComponent("s", "default", "scalar","Position on the path");
     166           6 :   keys.addOutputComponent("z", "default", "scalar","Distance from the path");
     167           3 :   keys.addDOI("10.1021/acs.jctc.8b00563");
     168           3 : }
     169             : 
     170           1 : FuncPathGeneral::FuncPathGeneral(const ActionOptions&ao):
     171             :   Action(ao),
     172             :   Function(ao),
     173           1 :   neigh_size(-1),
     174           1 :   neigh_stride(-1.) {
     175           1 :   parse("LAMBDA", lambda);
     176           1 :   parse("NEIGH_SIZE", neigh_size);
     177           1 :   parse("NEIGH_STRIDE", neigh_stride);
     178           1 :   parse("REFERENCE", reference);
     179           1 :   parseVector("COEFFICIENTS", coefficients);
     180           1 :   parseVector("COLUMNS", columns);
     181           1 :   checkRead();
     182           1 :   log.printf("  lambda is %f\n", lambda);
     183           1 :   if (getNumberOfArguments() != coefficients.size()) {
     184           0 :     plumed_merror("The numbers of coefficients and CVs are different!");
     185             :   }
     186           1 :   if (!columns.empty()) {
     187           0 :     if (columns.size() != coefficients.size()) {
     188           0 :       plumed_merror("The numbers of coefficients and columns are different!");
     189             :     }
     190             :   }
     191           1 :   log.printf("  Consistency check completed! Your path cvs look good!\n");
     192             : 
     193             :   // Load the reference colvar file
     194           1 :   loadReference();
     195             : 
     196             :   // Do some neighbour printout
     197           1 :   if (neigh_stride > 0. || neigh_size > 0) {
     198           0 :     if (static_cast<unsigned>(neigh_size) > path_cv_values.size()) {
     199           0 :       log.printf(" List size required ( %d ) is too large: resizing to the maximum number of arg required: %d  \n", neigh_size, getNumberOfArguments());
     200           0 :       neigh_size = path_cv_values.size();
     201             :     }
     202           0 :     log.printf("  Neighbour list enabled: \n");
     203           0 :     log.printf("                 size   :  %d elements\n", neigh_size);
     204           0 :     log.printf("                 stride :  %f time \n", neigh_stride);
     205             :   } else {
     206           1 :     log.printf("  Neighbour list NOT enabled \n");
     207             :   }
     208             : 
     209           1 :   addComponentWithDerivatives("s");
     210           1 :   componentIsNotPeriodic("s");
     211           1 :   addComponentWithDerivatives("z");
     212           2 :   componentIsNotPeriodic("z");
     213             : 
     214             :   // Initialise vectors
     215           1 :   std::vector<double> temp (coefficients.size());
     216          17 :   for (unsigned i = 0; i < path_cv_values.size(); ++i) {
     217          16 :     numerators.push_back(temp);
     218          16 :     expdists.push_back(0.);
     219          16 :     s_path_ders.push_back(0.);
     220          16 :     z_path_ders.push_back(0.);
     221             :   }
     222             : 
     223             :   // Store the arguments
     224           7 :   for (unsigned i=0; i<getNumberOfArguments(); i++) {
     225           6 :     allArguments.push_back(getPntrToArgument(i));
     226             :   }
     227             : 
     228             :   // Get periodic domains, negative for not periodic, stores half the domain length (maximum difference)
     229           7 :   for (unsigned i = 0; i < allArguments.size(); ++i) {
     230           6 :     if (allArguments[i]->isPeriodic()) {
     231             :       double min_lim, max_lim;
     232           0 :       allArguments[i]->getDomain(min_lim, max_lim);
     233           0 :       domains.push_back((max_lim - min_lim) / 2);
     234             :     } else {
     235           6 :       domains.push_back(-1.);
     236             :     }
     237             :   }
     238           1 : }
     239             : 
     240             : // Calculator
     241      175001 : void FuncPathGeneral::calculate() {
     242             :   double s_path = 0.;
     243             :   double partition = 0.;
     244             :   double tmp, value, diff, expdist, s_der, z_der;
     245             :   int ii;
     246             : 
     247             :   typedef std::vector< std::pair< int,double> >::iterator pairiter;
     248             : 
     249     2975001 :   for (pairiter it = neighpair.begin(); it != neighpair.end(); ++it) {
     250     2800000 :     (*it).second = 0.;
     251             :   }
     252             : 
     253      175001 :   if (neighpair.empty()) {
     254             :     // Resize at the first step
     255           1 :     neighpair.resize(path_cv_values.size());
     256          17 :     for (unsigned i = 0; i < path_cv_values.size(); ++i) {
     257          16 :       neighpair[i].first = i;
     258             :     }
     259             :   }
     260             : 
     261      175001 :   Value* val_s_path=getPntrToComponent("s");
     262      175001 :   Value* val_z_path=getPntrToComponent("z");
     263             : 
     264     1225007 :   for(unsigned j = 0; j < allArguments.size(); ++j) {
     265     1050006 :     value = allArguments[j]->get();
     266    17850102 :     for (pairiter it = neighpair.begin(); it != neighpair.end(); ++it) {
     267    16800096 :       diff = (value - path_cv_values[(*it).first][j]);
     268    16800096 :       if (domains[j] > 0) {
     269           0 :         if (diff > domains[j]) {
     270           0 :           diff -= 2 * domains[j];
     271             :         }
     272           0 :         if (diff < -domains[j]) {
     273           0 :           diff += 2 * domains[j];
     274             :         }
     275             :       }
     276    33600192 :       (*it).second += Tools::fastpow(coefficients[j] * diff, 2);
     277    33600192 :       numerators[(*it).first][j] = 2 * Tools::fastpow(coefficients[j], 2) * diff;
     278             :     }
     279             :   }
     280             : 
     281     2975017 :   for (pairiter it = neighpair.begin(); it != neighpair.end(); ++it) {
     282     2800016 :     expdist = std::exp(-lambda * (*it).second);
     283     2800016 :     expdists[(*it).first] = expdist;
     284     2800016 :     s_path += ((*it).first + 1) * expdist;
     285     2800016 :     partition += expdist;
     286             :   }
     287             : 
     288      175001 :   if(partition==0.0) {
     289             :     partition=std::numeric_limits<double>::min();
     290             :   }
     291             : 
     292      175001 :   s_path /= partition;
     293             :   val_s_path->set(s_path);
     294      175001 :   val_z_path->set(-(1. / lambda) * std::log(partition));
     295             : 
     296             :   // Derivatives
     297     2975017 :   for (pairiter it = neighpair.begin(); it != neighpair.end(); ++it) {
     298     2800016 :     ii = (*it).first;
     299     2800016 :     tmp = lambda * expdists[ii] * (s_path - (ii + 1)) / partition;
     300     2800016 :     s_path_ders[ii] = tmp;
     301     2800016 :     z_path_ders[ii] = expdists[ii] / partition;
     302             :   }
     303     1225007 :   for (unsigned i = 0; i < coefficients.size(); ++i) {
     304             :     s_der = 0.;
     305             :     z_der = 0.;
     306    17850102 :     for (pairiter it = neighpair.begin(); it != neighpair.end(); ++it) {
     307    16800096 :       ii = (*it).first;
     308    16800096 :       s_der += s_path_ders[ii] * numerators[ii][i];
     309    16800096 :       z_der += z_path_ders[ii] * numerators[ii][i];
     310             :     }
     311             :     setDerivative(val_s_path, i, s_der);
     312             :     setDerivative(val_z_path, i, z_der);
     313             :   }
     314      175001 : }
     315             : 
     316             : // Prepare the required arguments
     317      175001 : void FuncPathGeneral::prepare() {
     318             :   // Neighbour list: rank and activate the chain for the next step
     319             : 
     320             :   // Neighbour list: if neigh_size < 0 never sort and keep the full vector
     321             :   // Neighbour list: if neigh_size > 0
     322             :   //                 if the size is full -> sort the vector and decide the dependencies for next step
     323             :   //                 if the size is not full -> check if next step will need the full dependency otherwise keep these dependencies
     324             : 
     325      175001 :   if (neigh_size > 0) {
     326           0 :     if (neighpair.size() == path_cv_values.size()) {
     327             :       // The complete round has been done: need to sort, shorten and give it a go
     328             :       // Sort the values
     329           0 :       std::sort(neighpair.begin(), neighpair.end(), pairordering());
     330             :       // Resize the effective list
     331           0 :       neighpair.resize(neigh_size);
     332           0 :       log.printf("  NEIGHBOUR LIST NOW INCLUDES INDICES: ");
     333           0 :       for (int i = 0; i < neigh_size; ++i) {
     334           0 :         log.printf(" %i ",neighpair[i].first);
     335             :       }
     336           0 :       log.printf(" \n");
     337             :     } else {
     338           0 :       if (int(getStep()) % int(neigh_stride / getTimeStep()) == 0) {
     339           0 :         log.printf(" Time %f : recalculating full neighbour list \n", getStep() * getTimeStep());
     340           0 :         neighpair.resize(path_cv_values.size());
     341           0 :         for (unsigned i = 0; i < path_cv_values.size(); ++i) {
     342           0 :           neighpair[i].first = i;
     343             :         }
     344             :       }
     345             :     }
     346             :   }
     347             : 
     348      175001 :   requestArguments(allArguments);
     349      175001 : }
     350             : 
     351             : }
     352             : }

Generated by: LCOV version 1.16