LCOV - code coverage report
Current view: top level - gridtools - FindGridOptimum.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 68 85 80.0 %
Date: 2026-11-05 10:19:04 Functions: 4 7 57.1 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2016-2018 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 "tools/ConjugateGradient.h"
      24             : #include "ActionWithGrid.h"
      25             : #include "EvaluateGridFunction.h"
      26             : #include "Interpolator.h"
      27             : 
      28             : //+PLUMEDOC GRIDANALYSIS FIND_GRID_MINIMUM
      29             : /*
      30             : Find the point with the lowest value of the function on the grid
      31             : 
      32             : This command takes a function on a grid as input and returns multiple scalars.  One of the returned scalars (the one with component `optval`) is the value of the input
      33             : function at its lowest point. The other scalars returned are the coordinates for which the function takes this particular value.
      34             : 
      35             : As the input grid has the values of the function evaluated over a grid of points we find this minimum value by finding the point on the grid where the function has its lowest value using
      36             : interpolation and conjugate gradients.  The coordinates of the grid point where the function's value is lowest will be used as an initial coordinate
      37             : for the optimisation.  We then interpolate the function to find the optimum.  You can use use the CGTOL keyword to control the conjugate gradient algorithm that is used to find the location of the
      38             : minimum in the interpolated function.
      39             : 
      40             : To print out the location on the grid where the function is minimised and the value of the function at that point you can use an input like the one shown below:
      41             : 
      42             : ```plumed
      43             : x: DISTANCE ATOMS=1,2
      44             : hA1: HISTOGRAM ARG=x GRID_MIN=0.0 GRID_MAX=3.0 GRID_BIN=100 BANDWIDTH=0.1
      45             : ff: CONVERT_TO_FES ARG=hA1 TEMP=300
      46             : min: FIND_GRID_MINIMUM ARG=ff
      47             : PRINT ARG=min.x_opt,min.optval STRIDE=0
      48             : ```
      49             : 
      50             : Notice that we set STRIDE=0 in the PRINT command here so the position of the minimum is only output once at the end of the simulation.
      51             : 
      52             : A more common usage of this action is illustrated in the following input, which demonstrates how you can use FIND_GRID_MINIMUM to shift a free energy surface so that the minimum in the output
      53             : fes has a value of zero.
      54             : 
      55             : ```plumed
      56             : x: DISTANCE ATOMS=1,2
      57             : hA1: HISTOGRAM ARG=x GRID_MIN=0.0 GRID_MAX=3.0 GRID_BIN=100 BANDWIDTH=0.1
      58             : ff: CONVERT_TO_FES ARG=hA1 TEMP=300
      59             : min: FIND_GRID_MINIMUM ARG=ff NOINTERPOL
      60             : sff: CUSTOM ARG=ff,min.optval FUNC=x+y PERIODIC=NO
      61             : DUMPGRID ARG=sff FILE=fes.dat
      62             : ```
      63             : 
      64             : Notice that the `NOINTERPOL` flag has been used here so the interpolation and conjugate gradient algorithm is not used. The output is simply the grid point at which the function takes its lowest value.
      65             : 
      66             : */
      67             : //+ENDPLUMEDOC
      68             : 
      69             : //+PLUMEDOC GRIDANALYSIS FIND_GRID_MAXIMUM
      70             : /*
      71             : Find the point with the highest value of the function on the grid
      72             : 
      73             : This command takes a function on a grid as input and returns multiple scalars.  One of the returned scalars (the one with component `optval`) is the value of the input
      74             : function at its highest point. The other scalars returned are the coordinates for which the function takes this particular value.
      75             : 
      76             : As the input grid has the values of the function evaluated over a grid of points we find this maximum value by finding the point on the grid where the function has its lowest value using
      77             : interpolation and conjugate gradients.  The coordinates of the grid point where the function's value is highest will be used as an initial coordinate
      78             : for the optimisation.  We then interpolate the function to find the optimum.  You can use use the CGTOL keyword to control the conjugate gradient algorithm that is used to find the location of the
      79             : maximum in the interpolated function.
      80             : 
      81             : To print out the location on the grid where the function is maximised and the value of the function at that point you can use an input like the one shown below:
      82             : 
      83             : ```plumed
      84             : x: DISTANCE ATOMS=1,2
      85             : hA1: HISTOGRAM ARG=x GRID_MIN=0.0 GRID_MAX=3.0 GRID_BIN=100 BANDWIDTH=0.1
      86             : max: FIND_GRID_MAXIMUM ARG=hA1
      87             : PRINT ARG=max.x_opt,max.optval STRIDE=0
      88             : ```
      89             : 
      90             : Notice that we set STRIDE=0 in the PRINT command here so the position of the maximum is only output once at the end of the simulation.
      91             : 
      92             : If for any reason you do not want to use interpolation and conjugate gradient to find the optimum you can use the `NOINTERPOL` flag as shown below.
      93             : 
      94             : ```plumed
      95             : x: DISTANCE ATOMS=1,2
      96             : hA1: HISTOGRAM ARG=x GRID_MIN=0.0 GRID_MAX=3.0 GRID_BIN=100 BANDWIDTH=0.1
      97             : max: FIND_GRID_MAXIMUM ARG=hA1 NOINTERPOL
      98             : PRINT ARG=max.x_opt,max.optval STRIDE=0
      99             : ```
     100             : 
     101             : The FIND_GRID_MAXIMUM action then outputs the coordinates of the grid point where the function is largest as well as the value of the function on that grid point.
     102             : 
     103             : */
     104             : //+ENDPLUMEDOC
     105             : 
     106             : namespace PLMD {
     107             : namespace gridtools {
     108             : 
     109             : class FindGridOptimum :
     110             :   public ActionWithValue,
     111             :   public ActionWithArguments {
     112             : private:
     113             :   bool domin;
     114             :   double cgtol;
     115             :   std::vector<double> spacing;
     116             :   std::unique_ptr<Interpolator> function;
     117             :   double calculateValueAndDerivatives( const std::vector<double>& pp, std::vector<double>& der );
     118             : public:
     119             :   static void registerKeywords( Keywords& keys );
     120             :   explicit FindGridOptimum(const ActionOptions&ao);
     121             :   unsigned getNumberOfDerivatives() override ;
     122             :   void calculate() override ;
     123           0 :   void apply() override {}
     124             : };
     125             : 
     126             : PLUMED_REGISTER_ACTION(FindGridOptimum,"FIND_GRID_MAXIMUM")
     127             : PLUMED_REGISTER_ACTION(FindGridOptimum,"FIND_GRID_MINIMUM")
     128             : 
     129          12 : void FindGridOptimum::registerKeywords( Keywords& keys ) {
     130          12 :   Action::registerKeywords( keys );
     131          12 :   ActionWithValue::registerKeywords( keys );
     132          12 :   ActionWithArguments::registerKeywords( keys );
     133          12 :   keys.remove("NUMERICAL_DERIVATIVES");
     134          24 :   keys.addInputKeyword("compulsory","ARG","grid","the label for the function on the grid that you would like to find the optimum in");
     135          12 :   keys.addFlag("NOINTERPOL",false,"do not interpolate the function when finding the optimum");
     136          12 :   keys.add("compulsory","CGTOL","1E-4","the tolerance for the conjugate gradient optimization");
     137          24 :   keys.addOutputComponent("optval","default","scalar","the value of the function at the optimum");
     138          24 :   keys.addOutputComponent("_opt","default","scalar","the values of the arguments of the function at the optimum can be referenced elsewhere in the input file "
     139             :                           "by using the names of the arguments followed by the string _opt");
     140          12 : }
     141             : 
     142           2 : FindGridOptimum::FindGridOptimum(const ActionOptions&ao):
     143             :   Action(ao),
     144             :   ActionWithValue(ao),
     145             :   ActionWithArguments(ao),
     146           2 :   cgtol(0) {
     147           2 :   if( getName()=="FIND_GRID_MAXIMUM" ) {
     148           0 :     domin=false;
     149           2 :   } else if( getName()=="FIND_GRID_MINIMUM" ) {
     150           2 :     domin=true;
     151             :   } else {
     152           0 :     plumed_error();
     153             :   }
     154             :   // Create value for this function
     155           2 :   ActionWithGrid* ag=ActionWithGrid::getInputActionWithGrid( getPntrToArgument(0)->getPntrToAction() );
     156           2 :   if( !ag ) {
     157           0 :     error("input action should be a grid");
     158             :   }
     159           2 :   std::vector<std::string> argn( ag->getGridCoordinateNames() );
     160           2 :   std::vector<std::size_t> shape(0);
     161           5 :   for(unsigned i=0; i<argn.size(); ++i) {
     162           6 :     addComponent( argn[i] + "_opt", shape );
     163           6 :     componentIsNotPeriodic( argn[i] + "_opt" );
     164             :   }
     165           2 :   addComponent( "optval", shape );
     166           2 :   componentIsNotPeriodic( "optval" );
     167           2 :   bool nointerpol=false;
     168           2 :   parseFlag("NOINTERPOL",nointerpol);
     169           2 :   if( !nointerpol ) {
     170           2 :     parse("CGTOL",cgtol);
     171           4 :     function=Tools::make_unique<Interpolator>( getPntrToArgument(0), ag->getGridCoordinatesObject() );
     172             :   }
     173           2 : }
     174             : 
     175           0 : unsigned FindGridOptimum::getNumberOfDerivatives() {
     176           0 :   return 0;
     177             : }
     178             : 
     179         205 : double FindGridOptimum::calculateValueAndDerivatives( const std::vector<double>& pp, std::vector<double>& der ) {
     180         205 :   double val = function->splineInterpolation( pp, der );
     181             :   // We normalise the derivatives here and set them so that the linesearch is done over the cell that we know
     182             :   // in the grid that we know the minimum is inside
     183             :   double norm = 0;
     184         560 :   for(unsigned i=0; i<der.size(); ++i) {
     185         355 :     norm += der[i]*der[i];
     186             :   }
     187         205 :   norm = sqrt(norm);
     188         560 :   for(unsigned i=0; i<der.size(); ++i) {
     189         355 :     der[i] = spacing[i]*der[i] / norm;
     190             :   }
     191         205 :   if( domin ) {
     192             :     return val;
     193             :   }
     194             :   // If we are looking for a maximum
     195           0 :   for(unsigned i=0; i<der.size(); ++i) {
     196           0 :     der[i] = -der[i];
     197             :   }
     198           0 :   return -val;
     199             : }
     200             : 
     201           2 : void FindGridOptimum::calculate() {
     202           2 :   ActionWithGrid* ag=ActionWithGrid::getInputActionWithGrid( getPntrToArgument(0)->getPntrToAction() );
     203           2 :   plumed_assert( ag );
     204           2 :   spacing = ag->getGridCoordinatesObject().getGridSpacing();
     205           2 :   const GridCoordinatesObject& ingrid = ag->getGridCoordinatesObject();
     206             :   Value* gval = getPntrToArgument(0);
     207           2 :   std::vector<double> optargs( gval->getRank() );
     208           2 :   std::vector<unsigned> gridind( gval->getRank() );
     209           2 :   double optval=gval->get( 0 );
     210           2 :   ingrid.getGridPointCoordinates( 0, gridind, optargs );
     211           2 :   unsigned nval = gval->getNumberOfValues();
     212             :   bool constant=true;
     213        2602 :   for(unsigned i=0; i<nval; ++i) {
     214        2600 :     double tval = gval->get( i );
     215        2600 :     if( domin && (tval<optval || std::isnan(optval)) ) {
     216             :       constant=false;
     217             :       optval=tval;
     218          33 :       ingrid.getGridPointCoordinates( i, gridind, optargs );
     219             :     }
     220        2600 :     if( !domin && (tval>optval || std::isnan(optval)) ) {
     221             :       constant=false;
     222             :       optval=tval;
     223           0 :       ingrid.getGridPointCoordinates( i, gridind, optargs );
     224             :     }
     225             :   }
     226             :   // This basically ensures we deal with cases where all points on the grid are infinity as isinf doesn't work on intel compiler
     227           2 :   if( constant ) {
     228           0 :     if( domin && gval->get(0)>=gval->get(1) ) {
     229             :       return;
     230           0 :     } else if( gval->get(0)<=gval->get(1) ) {
     231             :       return;
     232             :     }
     233             :   }
     234           2 :   if( std::isinf(optval) ) {
     235             :     return;
     236             :   }
     237             : 
     238           2 :   if( std::isnan(optval) ) {
     239           0 :     error("all values on grid are nans");
     240             :   }
     241             :   // Set the value of the arguments at the optimum before we optimise just in case the optimisation fails
     242           5 :   for(unsigned j=0; j<optargs.size(); ++j) {
     243           3 :     getPntrToComponent(j)->set( optargs[j] );
     244             :   }
     245             : 
     246             :   // And do conjugate gradient optimisation (because we can!!)
     247           2 :   if( cgtol>0 ) {
     248             :     ConjugateGradient<FindGridOptimum> myminimiser( this );
     249           2 :     int code = myminimiser.minimise( cgtol, optargs, &FindGridOptimum::calculateValueAndDerivatives );
     250           2 :     if( code>0 ) {
     251           0 :       warning("could not find optimum for function on input grid. Am thus just returning point in grid where value of function is lowest");
     252           0 :       getPntrToComponent(optargs.size())->set( optval );
     253           0 :       return;
     254             :     }
     255             :   }
     256             :   // And set the final value
     257           5 :   for(unsigned j=0; j<optargs.size(); ++j) {
     258           3 :     getPntrToComponent(j)->set( optargs[j] );
     259             :   }
     260           2 :   std::vector<double> optder( gval->getRank() );
     261           2 :   if( function ) {
     262           2 :     getPntrToComponent(optargs.size())->set( function->splineInterpolation( optargs, optder ) );
     263             :   } else {
     264           0 :     getPntrToComponent(optargs.size())->set( optval );
     265             :   }
     266           4 : }
     267             : 
     268             : }
     269             : }

Generated by: LCOV version 1.16