LCOV - code coverage report
Current view: top level - contour - FindContourSurface.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 112 121 92.6 %
Date: 2025-12-04 11:19:34 Functions: 11 12 91.7 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2016-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             : #include "core/ActionRegister.h"
      23             : #include "ContourFindingObject.h"
      24             : #include "gridtools/ActionWithGrid.h"
      25             : #include "gridtools/EvaluateGridFunction.h"
      26             : #include "core/ParallelTaskManager.h"
      27             : 
      28             : //+PLUMEDOC GRIDANALYSIS FIND_CONTOUR_SURFACE
      29             : /*
      30             : Find an isocontour by searching along either the x, y or z direction.
      31             : 
      32             : As discussed in the documentation for the [gridtools](module_gridtools.md), PLUMED contains a number of tools that allow you to calculate
      33             : a function on a grid.  The function on this grid might be a [HISTOGRAM](HISTOGRAM.md)  or it might be one of the phase fields that are
      34             : discussed [here](module_contour.md).  If this function has one or two input
      35             : arguments it is relatively straightforward to plot the function.  If by contrast the data has a three dimensions it can be
      36             : difficult to visualize.
      37             : 
      38             : This action provides one tool for visualizing these functions.  It can be used to search for a set of points on a contour
      39             : where the function takes a particular value.  In other words, for the function $f(x,y,z)$ this action would find a set
      40             : of points $\{x_c,y_c,z_c\}$ that have:
      41             : 
      42             : $$
      43             : f(x_c,y_c,z_c) - c = 0
      44             : $$
      45             : 
      46             : where $c$ is some constant value that is specified by the user.  The points on this contour are find by searching along lines
      47             : that run parallel to the $x$, $y$ or $z$ axis of the simulation cell.  The result is, therefore, a two dimensional
      48             : function evaluated on a grid that gives us the height of the interface as a function of two coordinates.
      49             : 
      50             : It is important to note that this action can only be used to detect contours in three dimensional functions.  In addition, this action will fail to
      51             : find the full set of contour  points if the contour does not have the same topology as an infinite plane.  If you are uncertain that the isocontours in your
      52             : function have the appropriate topology you should use [FIND_CONTOUR](FIND_CONTOUR.md) in place of this action.
      53             : 
      54             : ## Examples
      55             : 
      56             : The input shown below was used to analyze the results from a simulation of an interface between solid and molten Lennard Jones.  The interface between
      57             : the solid and the liquid was set up in the plane perpendicular to the $z$ direction of the simulation cell. The input below calculates something
      58             : akin to a Willard-Chandler dividing surface (see [contour](module_contour.md)) between the solid phase and the liquid phase.  There are two of these interfaces within the
      59             : simulation box because of the periodic boundary conditions but we were able to determine that one of these two surfaces lies in a particular part of the
      60             : simulation box.  The input below detects the height profile of one of these two interfaces.  It does so by computing a phase field average from the values, $s_i$, of the
      61             : [FCCUBIC](FCCUBIC.md) symmetry functions for each of the atoms using the following expression.
      62             : 
      63             : $$
      64             : \rho'(x,y,z) = \frac{ \sum_{i=1}^N s_i K\left(\frac{x-x_i}{\lambda}, \frac{y-y_i}{\lambda}, \frac{z-z_i}{\lambda}\right) }{ \sum_{i=1}^N K\left(\frac{x-x_i}{\lambda}, \frac{y-y_i}{\lambda}, \frac{z-z_i}{\lambda}\right) }
      65             : $$
      66             : 
      67             : where $(x_i, y_i, z_i)$ is the position of atom $i$ relative to the position of atom 1, $K$ is a Gaussian kernel function and $\lambda=1.0$.
      68             : 
      69             : Notice that we use the fact that we know roughly where the interface is when specifying how this phase field is to be calculated and specify the region over the $z$-axis
      70             : in which the [KDE](KDE.md) is computed.  Once we have calculated the phase field we search for contour points on the lines that run parallel to the $z$-direction of the cell
      71             : box using the FIND_CONTOUR_SURFACE command.  The final result is a $14 \times 14$ grid of values for the height of the interface as a function of the $(x,y)$
      72             : position.  This grid is then output to a file called `contour2.dat`.
      73             : 
      74             : Notice that the commands below calculate the instantaneous position of the surface separating the solid and liquid and that as such the accumulated average is cleared
      75             : on every step.
      76             : 
      77             : ```plumed
      78             : UNITS NATURAL
      79             : 
      80             : # This calculates the value of a set of symmetry functions for the atoms of interest
      81             : fcc: FCCUBIC ...
      82             :   SPECIES=1-96000 SWITCH={CUBIC D_0=1.2 D_MAX=1.5}
      83             :   ALPHA=27 PHI=0.0 THETA=-1.5708 PSI=-2.35619
      84             : ...
      85             : 
      86             : # This determines the positions of the atoms of interest relative to the position of atom 1
      87             : dens2_dist: DISTANCES ORIGIN=1 ATOMS=fcc COMPONENTS
      88             : # This computes the numerator in the expression above for the phase field
      89             : dens2_numer: KDE ...
      90             :    VOLUMES=fcc_n ARG=dens2_dist.x,dens2_dist.y,dens2_dist.z
      91             :    GRID_BIN=14,14,50 GRID_MIN=auto,auto,6.0
      92             :    GRID_MAX=auto,auto,11.0 BANDWIDTH=1.0,1.0,1.0
      93             : ...
      94             : # This computes the denominator
      95             : dens2_denom: KDE ...
      96             :    ARG=dens2_dist.x,dens2_dist.y,dens2_dist.z
      97             :    GRID_BIN=14,14,50 GRID_MIN=auto,auto,6.0
      98             :    GRID_MAX=auto,auto,11.0 BANDWIDTH=1.0,1.0,1.0
      99             : ...
     100             : # This computes the final phase field
     101             : dens2: CUSTOM ARG=dens2_numer,dens2_denom FUNC=x/y PERIODIC=NO
     102             : 
     103             : # We can now find and print the location of the two dimensional contour surface
     104             : ss2: FIND_CONTOUR_SURFACE ARG=dens2 CONTOUR=0.42 SEARCHDIR=dens2_dist.z
     105             : DUMPGRID ARG=ss2 FILE=contour2.dat STRIDE=1
     106             : ```
     107             : 
     108             : */
     109             : //+ENDPLUMEDOC
     110             : 
     111             : namespace PLMD {
     112             : namespace contour {
     113             : 
     114           1 : class FindContourSurfaceObject {
     115             : public:
     116             :   unsigned dir_n;
     117             :   std::vector<unsigned> gdirs;
     118             :   std::vector<double> direction;
     119             :   ContourFindingObject<gridtools::EvaluateGridFunction> cf;
     120           3 :   static void registerKeywords( Keywords& keys ) {
     121           3 :     ContourFindingObject<gridtools::EvaluateGridFunction>::registerKeywords( keys );
     122           3 :     keys.add("compulsory","SEARCHDIR","In which directions do you wish to search for the contour.");
     123           3 :   }
     124           1 :   static void read( FindContourSurfaceObject& func, ActionWithArguments* action, function::FunctionOptions& options ) {
     125           1 :     ContourFindingObject<gridtools::EvaluateGridFunction>::read( func.cf, action, options );
     126             :     std::string dir;
     127           1 :     action->parse("SEARCHDIR",dir);
     128           1 :     action->log.printf("  calculating location of contour on %d dimensional grid \n", (action->getPntrToArgument(0))->getRank()-1 );
     129           1 :     Value* gval=func.cf.function.function;
     130           1 :     gridtools::ActionWithGrid* ag = gridtools::ActionWithGrid::getInputActionWithGrid( gval->getPntrToAction() );
     131           1 :     if( !ag ) {
     132           0 :       action->error("input argument must be a grid");
     133             :     }
     134           1 :     std::vector<std::string> argn( ag->getGridCoordinateNames() );
     135           1 :     func.gdirs.resize( gval->getRank()-1 );
     136             :     unsigned n=0;
     137           4 :     for(unsigned i=0; i<gval->getRank(); ++i) {
     138           3 :       if( argn[i]==dir ) {
     139           1 :         func.dir_n=i;
     140             :       } else {
     141           2 :         if( n==func.gdirs.size() ) {
     142           0 :           action->error("could not find " + dir + " direction in input grid");
     143             :         }
     144           2 :         func.gdirs[n]=i;
     145           2 :         n++;
     146             :       }
     147             :     }
     148           1 :     if( n!=(gval->getRank()-1) ) {
     149           0 :       action->error("output of grid is not understood");
     150             :     }
     151           2 :   }
     152             : };
     153             : 
     154             : class FindContourSurface : public gridtools::ActionWithGrid {
     155             : public:
     156             :   using input_type = FindContourSurfaceObject;
     157             :   using PTM = ParallelTaskManager<FindContourSurface>;
     158             : private:
     159             :   bool firststep;
     160             : /// The parallel task manager
     161             :   PTM taskmanager;
     162             :   std::vector<std::string> gnames;
     163             :   gridtools::GridCoordinatesObject gridcoords;
     164             : /// Get the input grid object for the grid that we are finding the contour in
     165             :   const gridtools::GridCoordinatesObject& getInputGridObject() const ;
     166             : public:
     167             :   static void registerKeywords( Keywords& keys );
     168             :   explicit FindContourSurface(const ActionOptions&ao);
     169             :   unsigned getNumberOfDerivatives() override ;
     170             :   std::vector<std::string> getGridCoordinateNames() const override ;
     171             :   const gridtools::GridCoordinatesObject& getGridCoordinatesObject() const override ;
     172             :   void calculate() override ;
     173             :   void getInputData( std::vector<double>& inputdata ) const override;
     174             :   static void performTask( std::size_t task_index,
     175             :                            const FindContourSurfaceObject& actiondata,
     176             :                            ParallelActionsInput& input,
     177             :                            ParallelActionsOutput& output );
     178             : };
     179             : 
     180             : PLUMED_REGISTER_ACTION(FindContourSurface,"FIND_CONTOUR_SURFACE")
     181             : 
     182           3 : void FindContourSurface::registerKeywords( Keywords& keys ) {
     183           3 :   ActionWithGrid::registerKeywords( keys );
     184           6 :   keys.addInputKeyword("compulsory","ARG","grid","the labels of the grid in which the contour will be found");
     185           3 :   FindContourSurfaceObject::registerKeywords( keys );
     186           6 :   keys.setValueDescription("grid","a grid containing the location of the points in the Willard-Chandler surface along the chosen direction");
     187           3 :   keys.addDOI("10.1088/1361-648X/aa893d");
     188           3 :   PTM::registerKeywords( keys );
     189           3 : }
     190             : 
     191           1 : FindContourSurface::FindContourSurface(const ActionOptions&ao):
     192             :   Action(ao),
     193             :   ActionWithGrid(ao),
     194           1 :   firststep(true),
     195           1 :   taskmanager(this) {
     196           1 :   if( getPntrToArgument(0)->getRank()<2 ) {
     197           0 :     error("cannot find dividing surface if input grid is one dimensional");
     198             :   }
     199             : 
     200             :   Value* gval=getPntrToArgument(0);
     201           1 :   gnames.resize( getPntrToArgument(0)->getRank()-1 );
     202             : 
     203           1 :   gridtools::ActionWithGrid* ag = ActionWithGrid::getInputActionWithGrid( gval->getPntrToAction() );
     204           1 :   if( !ag ) {
     205           0 :     error("input argument must be a grid");
     206             :   }
     207           1 :   const gridtools::GridCoordinatesObject& mygridobj = ag->getGridCoordinatesObject();
     208           2 :   if( mygridobj.getGridType()=="fibonacci") {
     209           0 :     error("cannot search for contours in fibonacci grids");
     210             :   }
     211             :   // Now add a value
     212           1 :   std::vector<std::size_t> shape( mygridobj.getDimension()-1 );
     213           1 :   addValueWithDerivatives( shape );
     214           1 :   setNotPeriodic();
     215             :   // Read in the action input
     216             :   function::FunctionOptions options;
     217           1 :   FindContourSurfaceObject::read( taskmanager.getActionInput(), this, options );
     218             :   // Prepare the grid stuff for this action
     219           1 :   std::vector<bool> ipbc( mygridobj.getDimension()-1 );
     220           3 :   for(unsigned i=0; i<gnames.size(); ++i) {
     221           2 :     ipbc[i] = mygridobj.isPeriodic(taskmanager.getActionInput().gdirs[i]);
     222           4 :     gnames[i] = ag->getGridCoordinateNames()[taskmanager.getActionInput().gdirs[i]];
     223             :   }
     224           1 :   gridcoords.setup( "flat", ipbc, 0, 0.0 );
     225           1 :   log.printf("  calculating dividing surface along which function equals %f \n", taskmanager.getActionInput().cf.contour);
     226           1 :   taskmanager.setupParallelTaskManager( 0, 0 );
     227           1 : }
     228             : 
     229           8 : const gridtools::GridCoordinatesObject& FindContourSurface::getInputGridObject() const {
     230           8 :   return taskmanager.getActionInput().cf.function.getGridObject();
     231             : }
     232             : 
     233           3 : void FindContourSurface::calculate() {
     234           3 :   if( firststep ) {
     235             :     std::vector<double> fspacing;
     236           1 :     std::vector<std::size_t> snbins( gridcoords.getDimension() );
     237           1 :     std::vector<std::string> smin( gridcoords.getDimension() ), smax( gridcoords.getDimension() );
     238           3 :     for(unsigned i=0; i<taskmanager.getActionInput().gdirs.size(); ++i) {
     239           4 :       smin[i]=getInputGridObject().getMin()[taskmanager.getActionInput().gdirs[i]];
     240           4 :       smax[i]=getInputGridObject().getMax()[taskmanager.getActionInput().gdirs[i]];
     241           2 :       snbins[i]=getInputGridObject().getNbin(false)[taskmanager.getActionInput().gdirs[i]];
     242             :     }
     243           1 :     gridcoords.setBounds( smin, smax, snbins, fspacing );
     244           2 :     getPntrToComponent(0)->setShape( gridcoords.getNbin(true) );
     245             : 
     246           1 :     std::vector<unsigned> find( gridcoords.getDimension() );
     247           1 :     std::vector<unsigned> ind( gridcoords.getDimension() );
     248         197 :     for(unsigned i=0; i<gridcoords.getNumberOfPoints(); ++i) {
     249         196 :       find.assign( find.size(), 0 );
     250         196 :       gridcoords.getIndices( i, ind );
     251         588 :       for(unsigned j=0; j<taskmanager.getActionInput().gdirs.size(); ++j) {
     252         392 :         find[taskmanager.getActionInput().gdirs[j]]=ind[j];
     253             :       }
     254             :     }
     255             : 
     256             :     // Set the direction in which to look for the contour
     257           1 :     taskmanager.getActionInput().direction.resize( getInputGridObject().getDimension(), 0 );
     258           1 :     taskmanager.getActionInput().direction[taskmanager.getActionInput().dir_n] = 0.999999999*getInputGridObject().getGridSpacing()[taskmanager.getActionInput().dir_n];
     259           1 :     firststep=false;
     260           1 :   }
     261           3 :   taskmanager.runAllTasks();
     262           3 : }
     263             : 
     264           7 : unsigned FindContourSurface::getNumberOfDerivatives() {
     265           7 :   return gridcoords.getDimension();
     266             : }
     267             : 
     268           3 : std::vector<std::string> FindContourSurface::getGridCoordinateNames() const {
     269           3 :   return gnames;
     270             : }
     271             : 
     272           3 : const gridtools::GridCoordinatesObject& FindContourSurface::getGridCoordinatesObject() const {
     273           3 :   return gridcoords;
     274             : }
     275             : 
     276           3 : void FindContourSurface::getInputData( std::vector<double>& inputdata ) const {
     277             : #ifndef DNDEBUG
     278           3 :   std::size_t rank = gridcoords.getDimension();
     279           3 :   std::size_t ndata = gridcoords.getNumberOfPoints();
     280           3 :   if( inputdata.size()!=ndata*rank ) {
     281           1 :     inputdata.resize( ndata*rank );
     282             :   }
     283           3 :   std::vector<double> point( rank );
     284         591 :   for(unsigned i=0; i<ndata; ++i) {
     285         588 :     gridcoords.getGridPointCoordinates( i, point );
     286        1764 :     for(unsigned j=0; j<rank; ++j) {
     287        1176 :       inputdata[i*rank + j] = point[j];
     288             :     }
     289             :   }
     290             : #endif
     291           3 : }
     292             : 
     293         588 : void FindContourSurface::performTask( std::size_t task_index,
     294             :                                       const FindContourSurfaceObject& actiondata,
     295             :                                       ParallelActionsInput& input,
     296             :                                       ParallelActionsOutput& output ) {
     297             :   unsigned nfound=0;
     298             :   const gridtools::GridCoordinatesObject& gridobj = actiondata.cf.function.getGridObject();
     299         588 :   std::size_t nbins = gridobj.getNbin(false)[actiondata.dir_n];
     300             :   std::size_t shiftn=task_index;
     301         588 :   std::vector<unsigned> ind( gridobj.getDimension() );
     302         588 :   std::vector<double> point( gridobj.getDimension() );
     303             : #ifndef DNDEBUG
     304             :   std::size_t rank = gridobj.getDimension()-1;
     305             :   View<const double> oind( input.inputdata+task_index*rank, rank );
     306             : #endif
     307       16842 :   for(unsigned i=0; i<nbins; ++i) {
     308             : #ifndef DNDEBUG
     309       16842 :     std::vector<double> base_ind( gridobj.getDimension() );
     310       16842 :     gridobj.getGridPointCoordinates( shiftn, base_ind );
     311       50526 :     for(unsigned j=0; j<actiondata.gdirs.size(); ++j) {
     312             :       plumed_dbg_assert( base_ind[actiondata.gdirs[j]]==oind[j] );
     313             :     }
     314             : #endif
     315             :     // Get the index of the current grid point
     316       16842 :     gridobj.getIndices( shiftn, ind );
     317             :     // Exit if we are at the edge of the grid
     318       16842 :     if( !gridobj.isPeriodic(actiondata.dir_n) && (ind[actiondata.dir_n]+1)==nbins ) {
     319           0 :       shiftn += gridobj.getStride()[actiondata.dir_n];
     320             :       continue;
     321             :     }
     322             : 
     323             :     // Now get the function value at two points
     324       16842 :     double val1=actiondata.cf.function.function->get( shiftn ) - actiondata.cf.contour;
     325             :     double val2;
     326       16842 :     if( (ind[actiondata.dir_n]+1)==nbins ) {
     327           0 :       val2 = actiondata.cf.function.function->get( task_index ) - actiondata.cf.contour;
     328             :     } else {
     329       16842 :       val2 = actiondata.cf.function.function->get( shiftn + gridobj.getStride()[actiondata.dir_n] ) - actiondata.cf.contour;
     330             :     }
     331             : 
     332             :     // Check if the minimum is bracketed
     333       16842 :     if( val1*val2<0 ) {
     334         588 :       gridobj.getGridPointCoordinates( shiftn, point );
     335         588 :       ContourFindingObject<gridtools::EvaluateGridFunction>::findContour( actiondata.cf, actiondata.direction, point );
     336         588 :       output.values[0] = point[actiondata.dir_n];
     337             :       nfound++;
     338             :       break;
     339             :     }
     340             : 
     341             :     // This moves us on to the next point
     342       16254 :     shiftn += gridobj.getStride()[actiondata.dir_n];
     343             :   }
     344             :   if( nfound==0 ) {
     345           0 :     plumed_merror("failed to find required grid point");
     346             :   }
     347         588 : }
     348             : 
     349             : }
     350             : }

Generated by: LCOV version 1.16