LCOV - code coverage report
Current view: top level - contour - FindSphericalContour.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 79 87 90.8 %
Date: 2025-12-04 11:19:34 Functions: 9 11 81.8 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2016-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 "ContourFindingObject.h"
      24             : #include "gridtools/ActionWithGrid.h"
      25             : #include "gridtools/EvaluateGridFunction.h"
      26             : #include "core/ParallelTaskManager.h"
      27             : #include "tools/Random.h"
      28             : 
      29             : //+PLUMEDOC GRIDANALYSIS FIND_SPHERICAL_CONTOUR
      30             : /*
      31             : Find an isocontour in a three dimensional grid by searching over a Fibonacci sphere.
      32             : 
      33             : As discussed in the documentation for the [gridtools](module_gridtools.md), PLUMED contains a number of tools that allow you to calculate
      34             : 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
      35             : discussed [here](module_contour.md).  If this function has one or two input
      36             : arguments it is relatively straightforward to plot the function.  If by contrast the data has a three dimensions it can be
      37             : difficult to visualize.
      38             : 
      39             : This action provides one tool for visualizing these functions.  It can be used to search for a set of points on a contour
      40             : where the function takes a particular value.  In other words, for the function $f(x,y,z)$ this action would find a set
      41             : of points $\{x_c,y_c,z_c\}$ that have:
      42             : 
      43             : $$
      44             : f(x_c,y_c,z_c) - c = 0
      45             : $$
      46             : 
      47             : where $c$ is some constant value that is specified by the user.  The points on this contour are find by searching along a
      48             : set of equally spaced radii of a sphere that centered at on particular, user-specified atom or virtual atom.  To ensure that
      49             : these search radii are equally spaced on the surface of the sphere the search directions are generated by using a Fibonacci
      50             : spiral projected on a sphere.  In other words, the search directions are given by:
      51             : 
      52             : $$
      53             : \mathbf{r}_i = \left(
      54             : \begin{matrix}
      55             : \sqrt{1 - y^2} \cos(\phi) \\
      56             : \frac{2i}{n} - 1 + \frac{1}{n}  \\
      57             : \sqrt{1 - y^2} \sin(\phi)
      58             : \end{matrix}
      59             : \right)
      60             : $$
      61             : 
      62             : where $y$ is the second component of the vector defined above, $n$ is the number of directions to look in and $\phi$ is
      63             : 
      64             : $$
      65             : \phi = \mod(i + R, n) \pi ( 3 - \sqrt{5} )
      66             : $$
      67             : 
      68             : where $R$ is a random variable between 0 and $n-1$ that is generated during the read in of the input file and that is fixed during
      69             : the whole calculation.
      70             : 
      71             : 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
      72             : find the full set of contour  points if the contour does not have the same topology as a sphere.  If you are uncertain that the isocontours in your
      73             : function have a spherical topology you should use [FIND_CONTOUR](FIND_CONTOUR.md) instead.
      74             : 
      75             : ## Examples
      76             : 
      77             : The following input demonstrates how this action can be used.  The input here is used to study the shape of a droplet that has been formed during the
      78             : condensation of Lennard Jones from the vapor.  The input below achieves this by calculating the coordination numbers, $c_i$, of all the atoms within the gas.
      79             : Obviously, those atoms within the droplet will have a large value for the coordination number while the isolated atoms in the gas will have a low value.
      80             : 
      81             : We can detect the sizes of the droplets by constructing a matrix whose $i,j$ element tells us whether atom $i$ and atom $j$ are within 6 nm of each other and
      82             : both have coordination numbers that are greater that two.  The atoms within the various droplets within the system can then be found by performing a
      83             : [DFSCLUSTERING](DFSCLUSTERING.md) on this matrix to detect the connected components.  We can take the largest of these connected components and find the center of the droplet
      84             : by exploiting the functionality within [CENTER](CENTER.md). We can then construct a phase field based on the positions of the atoms in the largest
      85             : cluster and the values of the coordination numbers of these atoms as follows:
      86             : 
      87             : $$
      88             : \rho'(x,y,z) = \frac{ \sum_{i=1}^N f(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) }
      89             : $$
      90             : 
      91             : where $f$ is a switching function.  The final line in the input then finds the a set of points on the dividing surface that separates
      92             : the droplet from the surrounding gas.  The value of the phase field on this isocontour is equal to 0.75.
      93             : 
      94             : ```plumed
      95             : # Calculate coordination numbers
      96             : c1: COORDINATIONNUMBER SPECIES=1-512 SWITCH={EXP D_0=4.0 R_0=0.5 D_MAX=6.0}
      97             : # Select coordination numbers that are more than 2.0
      98             : cf: MORE_THAN ARG=c1 SWITCH={RATIONAL D_0=2.0 R_0=0.1}
      99             : # Build a contact matrix
     100             : c1_mat2: CONTACT_MATRIX GROUP=1-512 SWITCH={EXP D_0=4.0 R_0=0.5 D_MAX=6.0}
     101             : dp_mat: OUTER_PRODUCT ARG=cf,cf
     102             : # Build the final matrix
     103             : mat: CUSTOM ARG=c1_mat2,dp_mat FUNC=x*y PERIODIC=NO
     104             : # Find largest cluster
     105             : dfs: DFSCLUSTERING ARG=mat
     106             : clust1: CLUSTER_WEIGHTS CLUSTERS=dfs CLUSTER=1
     107             : # Find center of largest cluster
     108             : trans1: CUSTOM ARG=cf,clust1 FUNC=x*x*y PERIODIC=NO
     109             : cent: CENTER ATOMS=1-512 WEIGHTS=trans1 PHASES
     110             : # Calculate the phase field of the coordination
     111             : dens_dist: DISTANCES ORIGIN=cent ATOMS=c1 COMPONENTS
     112             : dens_numer: KDE ...
     113             :    VOLUMES=trans1 ARG=dens_dist.x,dens_dist.y,dens_dist.z
     114             :    GRID_BIN=30,30,30 BANDWIDTH=2.0,2.0,2.0
     115             : ...
     116             : dens_denom: KDE ...
     117             :    VOLUMES=clust1 ARG=dens_dist.x,dens_dist.y,dens_dist.z
     118             :    GRID_BIN=30,30,30 BANDWIDTH=2.0,2.0,2.0
     119             : ...
     120             : dens: CUSTOM ARG=dens_numer,dens_denom FUNC=x/y PERIODIC=NO
     121             : # Find the isocontour around the nucleus
     122             : sc: FIND_SPHERICAL_CONTOUR ARG=dens CONTOUR=0.85 INNER_RADIUS=10.0 OUTER_RADIUS=40.0 NPOINTS=100
     123             : # And print the grid to a file
     124             : DUMPGRID ARG=sc PRINT_XYZ FILE=mysurface.xyz STRIDE=1
     125             : ```
     126             : 
     127             : */
     128             : //+ENDPLUMEDOC
     129             : 
     130             : namespace PLMD {
     131             : namespace contour {
     132             : 
     133           1 : class FindSphericalContourObject {
     134             : public:
     135             :   unsigned nbins;
     136             :   ContourFindingObject<gridtools::EvaluateGridFunction> cf;
     137           3 :   static void registerKeywords( Keywords& keys ) {
     138           3 :     ContourFindingObject<gridtools::EvaluateGridFunction>::registerKeywords( keys );
     139           3 :     keys.add("compulsory","NBINS","1","the number of discrete sections in which to divide the distance between the inner and outer radius when searching for a contour");
     140           3 :   }
     141           1 :   static void read( FindSphericalContourObject& func, ActionWithArguments* action, function::FunctionOptions& options ) {
     142           1 :     action->parse("NBINS",func.nbins);
     143           1 :     ContourFindingObject<gridtools::EvaluateGridFunction>::read( func.cf, action, options );
     144           1 :   }
     145             : };
     146             : 
     147             : class FindSphericalContour : public gridtools::ActionWithGrid {
     148             : public:
     149             :   using input_type = FindSphericalContourObject;
     150             :   using PTM = ParallelTaskManager<FindSphericalContour>;
     151             : private:
     152             : /// The parallel task manager
     153             :   PTM taskmanager;
     154             :   unsigned npoints;
     155             :   double min, max;
     156             :   gridtools::GridCoordinatesObject gridcoords;
     157             : public:
     158             :   static void registerKeywords( Keywords& keys );
     159             :   explicit FindSphericalContour(const ActionOptions&ao);
     160             :   unsigned getNumberOfDerivatives() override ;
     161             :   std::vector<std::string> getGridCoordinateNames() const override ;
     162             :   const gridtools::GridCoordinatesObject& getGridCoordinatesObject() const override ;
     163             :   void calculate() override ;
     164             :   void getInputData( std::vector<double>& inputdata ) const override;
     165             :   static void performTask( std::size_t task_index,
     166             :                            const FindSphericalContourObject& actiondata,
     167             :                            ParallelActionsInput& input,
     168             :                            ParallelActionsOutput& output );
     169             : };
     170             : 
     171             : PLUMED_REGISTER_ACTION(FindSphericalContour,"FIND_SPHERICAL_CONTOUR")
     172             : 
     173           3 : void FindSphericalContour::registerKeywords( Keywords& keys ) {
     174           3 :   ActionWithGrid::registerKeywords( keys );
     175           6 :   keys.addInputKeyword("compulsory","ARG","grid","the labels of the grid in which the contour will be found");
     176           3 :   keys.add("compulsory","NPOINTS","the number of points for which we are looking for the contour");
     177           3 :   keys.add("compulsory","INNER_RADIUS","the minimum radius on which to look for the contour");
     178           3 :   keys.add("compulsory","OUTER_RADIUS","the outer radius on which to look for the contour");
     179           3 :   FindSphericalContourObject::registerKeywords( keys );
     180           6 :   keys.setValueDescription("grid","a grid on a Fibonacci sphere that describes the radial distance from the origin for the points on the Willard-Chandler surface");
     181           3 :   keys.addDOI("10.1063/1.5134461");
     182           3 :   PTM::registerKeywords( keys );
     183           3 : }
     184             : 
     185           1 : FindSphericalContour::FindSphericalContour(const ActionOptions&ao):
     186             :   Action(ao),
     187             :   ActionWithGrid(ao),
     188           1 :   taskmanager(this) {
     189           1 :   if( getPntrToArgument(0)->getRank()!=3 ) {
     190           0 :     error("input grid must be three dimensional");
     191             :   }
     192             : 
     193           1 :   parse("NPOINTS",npoints);
     194           1 :   log.printf("  searching for %u points on dividing surface \n",npoints);
     195           1 :   parse("INNER_RADIUS",min);
     196           1 :   parse("OUTER_RADIUS",max);
     197           1 :   log.printf("  expecting to find dividing surface at radii between %f and %f \n",min,max);
     198             :   // Set this here so the same set of grid points are used on every turn
     199           1 :   std::vector<bool> ipbc( 3, false );
     200           1 :   gridcoords.setup( "fibonacci", ipbc, npoints, 0.0 );
     201             :   // Now create a value
     202           1 :   std::vector<std::size_t> shape( 3 );
     203           1 :   shape[0]=npoints;
     204           1 :   shape[1]=shape[2]=1;
     205           1 :   addValueWithDerivatives( shape );
     206           1 :   setNotPeriodic();
     207             :   function::FunctionOptions options;
     208           1 :   FindSphericalContourObject::read( taskmanager.getActionInput(), this, options );
     209           1 :   log.printf("  looking for contour in windows of length %f \n", (max-min)/taskmanager.getActionInput().nbins);
     210           1 :   log.printf("  calculating dividing surface along which function equals %f \n", taskmanager.getActionInput().cf.contour);
     211           1 :   taskmanager.setupParallelTaskManager( 0, 0 );
     212           1 : }
     213             : 
     214           5 : unsigned FindSphericalContour::getNumberOfDerivatives() {
     215           5 :   return gridcoords.getDimension();
     216             : }
     217             : 
     218           0 : std::vector<std::string> FindSphericalContour::getGridCoordinateNames() const {
     219           0 :   gridtools::ActionWithGrid* ag=dynamic_cast<gridtools::ActionWithGrid*>( getPntrToArgument(0)->getPntrToAction() );
     220           0 :   plumed_assert( ag );
     221           0 :   return ag->getGridCoordinateNames();
     222             : }
     223             : 
     224           3 : const gridtools::GridCoordinatesObject& FindSphericalContour::getGridCoordinatesObject() const {
     225           3 :   return gridcoords;
     226             : }
     227             : 
     228           2 : void FindSphericalContour::calculate() {
     229           2 :   taskmanager.runAllTasks();
     230           2 : }
     231             : 
     232           2 : void FindSphericalContour::getInputData( std::vector<double>& inputdata ) const {
     233           2 :   std::size_t ndata = gridcoords.getNumberOfPoints();
     234           2 :   if( inputdata.size()!=6*ndata ) {
     235           1 :     inputdata.resize( 6*ndata );
     236             :   }
     237           2 :   std::vector<double> direction(3);
     238         202 :   for(unsigned i=0; i<ndata; ++i) {
     239         200 :     gridcoords.getGridPointCoordinates( i, direction );
     240         800 :     for(unsigned j=0; j<3; ++j) {
     241         600 :       inputdata[6*i+j] = min*direction[j];
     242         600 :       inputdata[6*i+3+j] = (max-min)*direction[j] / static_cast<double>(taskmanager.getActionInput().nbins);
     243             :     }
     244             :   }
     245           2 : }
     246             : 
     247         200 : void FindSphericalContour::performTask( std::size_t task_index,
     248             :                                         const FindSphericalContourObject& actiondata,
     249             :                                         ParallelActionsInput& input,
     250             :                                         ParallelActionsOutput& output ) {
     251             :   bool found=false;
     252         200 :   View<const double> cp( input.inputdata + 6*task_index, 3 );
     253         200 :   std::vector<double> direction(3), contour_point(3), der(3), tmp(3);
     254         200 :   contour_point[0] = cp[0];
     255         200 :   contour_point[1] = cp[1];
     256         200 :   contour_point[2] = cp[2];
     257         200 :   View<const double> d( input.inputdata + 6*task_index+3, 3);
     258         200 :   direction[0] = d[0];
     259         200 :   direction[1] = d[1];
     260         200 :   direction[2] = d[2];
     261         200 :   for(unsigned k=0; k<actiondata.nbins; ++k) {
     262         800 :     for(unsigned j=0; j<3; ++j) {
     263         600 :       tmp[j] = contour_point[j] + direction[j];
     264             :     }
     265         200 :     double val1 = actiondata.cf.getDifferenceFromContour( contour_point, der );
     266         200 :     double val2 = actiondata.cf.getDifferenceFromContour( tmp, der );
     267         200 :     if( val1*val2<0 ) {
     268         200 :       ContourFindingObject<gridtools::EvaluateGridFunction>::findContour( actiondata.cf, direction, contour_point );
     269             :       double norm=0;
     270         800 :       for(unsigned j=0; j<3; ++j) {
     271         600 :         norm += contour_point[j]*contour_point[j];
     272             :       }
     273         200 :       output.values[0] = sqrt(norm);
     274             :       found=true;
     275             :       break;
     276             :     }
     277           0 :     for(unsigned j=0; j<3; ++j) {
     278           0 :       contour_point[j] = tmp[j];
     279             :     }
     280             :   }
     281             :   if( !found ) {
     282           0 :     plumed_merror("range does not bracket the dividing surface");
     283             :   }
     284         200 : }
     285             : 
     286             : }
     287             : }

Generated by: LCOV version 1.16