LCOV - code coverage report
Current view: top level - volumes - VolumeInCylinder.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 59 71 83.1 %
Date: 2025-12-04 11:19:34 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2015-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             : #ifdef __PLUMED_HAS_OPENACC
      23             : #define __PLUMED_USE_OPENACC 1
      24             : #endif //__PLUMED_HAS_OPENACC
      25             : #include "core/ActionRegister.h"
      26             : #include "tools/Pbc.h"
      27             : #include "tools/SwitchingFunction.h"
      28             : #include "ActionVolume.h"
      29             : #include "tools/HistogramBead.h"
      30             : #include "VolumeShortcut.h"
      31             : 
      32             : //+PLUMEDOC VOLUMES INCYLINDER
      33             : /*
      34             : This quantity can be used to calculate functions of the distribution of collective variables for the atoms that lie in a particular, user-specified part of of the cell.
      35             : 
      36             : This action can be used to calculate whether each of the atoms are within a particular part of the simulation box or not as illustrated by the following example:
      37             : 
      38             : ```plumed
      39             : f: FIXEDATOM AT=0,0,0
      40             : a: INCYLINDER ...
      41             :    ATOMS=1-100 CENTER=f DIRECTION=Z
      42             :    RADIUS={TANH R_0=1.5} SIGMA=0.1
      43             :    LOWER=-1.0 UPPER=1.0
      44             :    KERNEL=gaussian
      45             : ...
      46             : PRINT ARG=a FILE=colvar
      47             : ```
      48             : 
      49             : The 100 elements of the vector `a` that is returned from the INCYLINDER action in the above input are calculated using:
      50             : 
      51             : $$
      52             : w(x_i,y_i,z_i) = s\left( r_{xy} \right) \int_{zl}^{zu} \textrm{d}z K\left( \frac{z - z_i}{\sigma} \right)
      53             : $$
      54             : 
      55             : where
      56             : 
      57             : $$
      58             : r_{xy} = \sqrt{ x_i^2 + y_i^2 }
      59             : $$
      60             : 
      61             : In these expressions $K$ is one of the kernel functions described in the documentation for the function [BETWEEN](BETWEEN.md), $\sigma$ is a bandwidth parameter and the limits
      62             : for the integrals are the values specified using the keywords `LOWER` and `UPPER`. $x_i$, $y_i$ and $z_i$ are then the components
      63             : of the vector that connects the $i$th atom that was specified using the `ATOMS` keyword to the atom that was specified using the `CENTER` keyword.  In other words,
      64             : $w(x_i,y_i,z_i)$ is 1 if the atom is within a cylinder that is centered on the $z$ axis that has an extent along the $z$ direction around the position of atom `f` that
      65             : is determined by the values specified by `LOWER` and `UPPER` keywords.  The radial extent of this cylinder is determined by the parameters of the switching function that is
      66             : specified using the `RADIUS` keyword.
      67             : 
      68             : If you want to caluclate and print the number of atoms in the cylinder of interest you can use an input like the one shown below:
      69             : 
      70             : ```plumed
      71             : f: FIXEDATOM AT=0,0,0
      72             : a: INCYLINDER ...
      73             :   ATOMS=1-100 CENTER=f DIRECTION=X
      74             :   RADIUS={TANH R_0=1.5} SIGMA=0.1
      75             :   LOWER=-1.0 UPPER=1.0
      76             : ...
      77             : s: SUM ARG=a PERIODIC=NO
      78             : PRINT ARG=s FILE=colvar
      79             : ```
      80             : 
      81             : Alternatively, if you want to calculate an print the number of atoms that are not in the cylinder of interest you can use the OUTSIDE flag as shown below:
      82             : 
      83             : ```plumed
      84             : f: FIXEDATOM AT=0,0,0
      85             : a: INCYLINDER ...
      86             :   ATOMS=1-100 CENTER=f DIRECTION=X
      87             :   RADIUS={TANH R_0=1.5} SIGMA=0.1
      88             :   LOWER=-1.0 UPPER=1.0
      89             :   OUTSIDE
      90             : ...
      91             : s: SUM ARG=a PERIODIC=NO
      92             : PRINT ARG=s FILE=colvar
      93             : ```
      94             : 
      95             : Notice that now the cylinder is centered on the `x` axis rather than the `z` axis as we have changed the input for the `DIRECTION` keyword.
      96             : 
      97             : !!! note ""
      98             : 
      99             :     You can also calculate the average values of symmetry functions in the cylinder of interest by using inputs similar to those described the documentation for the [AROUND](AROUND.md)
     100             :     action. In other words, you can swap out AROUND actions for an INCLYLINDER actions. Also as with [AROUND](AROUND.md), earlier versions of PLUMED used a different syntax for doing these
     101             :     types of calculations, which can still be used with this new version of the command.  We strongly recommend using the newer syntax but if you are interested in the
     102             :     old syntax you can find more information in the old syntax section of the documentation for [AROUND](AROUND.md).
     103             : 
     104             : 
     105             : */
     106             : //+ENDPLUMEDOC
     107             : 
     108             : namespace PLMD {
     109             : namespace volumes {
     110             : 
     111           3 : class VolumeInCylinder {
     112             : public:
     113             :   bool docylinder;
     114             :   double min, max, sigma;
     115             :   HistogramBead::KernelType kerneltype;
     116             :   std::array<unsigned,3> dir;
     117             : #ifdef __PLUMED_USE_OPENACC
     118             :   SwitchingFunctionAccelerable switchingFunction;
     119             : #else
     120             :   SwitchingFunction switchingFunction;
     121             : #endif //__PLUMED_USE_OPENACC
     122             :   static void registerKeywords( Keywords& keys );
     123             :   void parseInput( ActionVolume<VolumeInCylinder>* action );
     124             :   void setupRegions( ActionVolume<VolumeInCylinder>* action, const Pbc& pbc, const std::vector<Vector>& positions ) {}
     125             :   static void parseAtoms( ActionVolume<VolumeInCylinder>* action, std::vector<AtomNumber>& atom );
     126             :   static void calculateNumberInside( const VolumeInput& input, const VolumeInCylinder& actioninput, VolumeOutput& output );
     127             : #ifdef __PLUMED_USE_OPENACC
     128             :   void toACCDevice() const {
     129             : #pragma acc enter data copyin(this[0:1], \
     130             : docylinder, min, max, sigma, kerneltype, dir[0:3])
     131             :     switchingFunction.toACCDevice();
     132             :   }
     133             :   void removeFromACCDevice() const {
     134             :     switchingFunction.removeFromACCDevice();
     135             : #pragma acc exit data delete(dir[0:3], kerneltype, sigma, max, min, \
     136             :       docylinder, this[0:1])
     137             :   }
     138             : #endif //__PLUMED_USE_OPENACC
     139             : };
     140             : 
     141             : typedef ActionVolume<VolumeInCylinder> Volc;
     142             : PLUMED_REGISTER_ACTION(Volc,"INCYLINDER_CALC")
     143             : char glob_cylinder[] = "INCYLINDER";
     144             : typedef VolumeShortcut<glob_cylinder> VolumeInCylinderShortcut;
     145             : PLUMED_REGISTER_ACTION(VolumeInCylinderShortcut,"INCYLINDER")
     146             : 
     147           7 : void VolumeInCylinder::registerKeywords( Keywords& keys ) {
     148          14 :   keys.setDisplayName("INCYLINDER");
     149           7 :   keys.add("atoms","CENTER","the atom whose vicinity we are interested in examining");
     150           7 :   keys.add("optional","SIGMA","the width of the function to be used for kernel density estimation");
     151           7 :   keys.add("compulsory","KERNEL","gaussian","the type of kernel function to be used");
     152           7 :   keys.add("compulsory","DIRECTION","the direction of the long axis of the cylinder. Must be x, y or z");
     153           7 :   keys.add("compulsory","RADIUS","a switching function that gives the extent of the cylinder in the plane perpendicular to the direction");
     154           7 :   keys.add("compulsory","LOWER","0.0","the lower boundary on the direction parallel to the long axis of the cylinder");
     155           7 :   keys.add("compulsory","UPPER","0.0","the upper boundary on the direction parallel to the long axis of the cylinder");
     156          14 :   keys.linkActionInDocs("RADIUS","LESS_THAN");
     157           7 : }
     158             : 
     159           1 : void VolumeInCylinder::parseInput( ActionVolume<VolumeInCylinder>* action ) {
     160           2 :   action->parse("SIGMA",sigma);
     161             :   std::string mykerneltype;
     162           1 :   action->parse("KERNEL",mykerneltype);
     163           1 :   kerneltype=HistogramBead::getKernelType(mykerneltype);
     164             :   std::string sdir;
     165           2 :   action->parse("DIRECTION",sdir);
     166           1 :   if( sdir=="X") {
     167           0 :     dir[0]=1;
     168           0 :     dir[1]=2;
     169           0 :     dir[2]=0;
     170           1 :   } else if( sdir=="Y") {
     171           0 :     dir[0]=0;
     172           0 :     dir[1]=2;
     173           0 :     dir[2]=1;
     174           1 :   } else if( sdir=="Z") {
     175           1 :     dir[0]=0;
     176           1 :     dir[1]=1;
     177           1 :     dir[2]=2;
     178             :   } else {
     179           0 :     action->error(sdir + "is not a valid direction.  Should be X, Y or Z");
     180             :   }
     181           1 :   action->log.printf("  cylinder's long axis is along %s axis\n",sdir.c_str() );
     182             : 
     183             :   std::string errors;
     184             :   std::string swinput;
     185           2 :   action->parse("RADIUS",swinput);
     186           1 :   if(swinput.length()==0) {
     187           0 :     action->error("missing RADIUS keyword");
     188             :   }
     189           1 :   switchingFunction.set(swinput,errors);
     190           1 :   if( errors.length()!=0 ) {
     191           0 :     action->error("problem reading RADIUS keyword : " + errors );
     192             :   }
     193           1 :   action->log.printf("  radius of cylinder is given by %s \n", ( switchingFunction.description() ).c_str() );
     194             : 
     195           1 :   docylinder=false;
     196           1 :   action->parse("LOWER",min);
     197           1 :   action->parse("UPPER",max);
     198           1 :   if( min!=0.0 ||  max!=0.0 ) {
     199           1 :     if( min>max ) {
     200           0 :       action->error("minimum of cylinder should be less than maximum");
     201             :     }
     202           1 :     docylinder=true;
     203           1 :     action->log.printf("  cylinder extends from %f to %f along the %s axis\n",min,max,sdir.c_str() );
     204             :   }
     205           1 : }
     206             : 
     207           1 : void VolumeInCylinder::parseAtoms( ActionVolume<VolumeInCylinder>* action, std::vector<AtomNumber>& atom ) {
     208           2 :   action->parseAtomList("CENTER",atom);
     209           1 :   if( atom.size()!=1 ) {
     210           0 :     action->error("should only be one atom specified");
     211             :   }
     212           1 :   action->log.printf("  center of cylinder is at position of atom : %d\n",atom[0].serial() );
     213           1 : }
     214             : 
     215        8000 : void VolumeInCylinder::calculateNumberInside( const VolumeInput& input, const VolumeInCylinder& actioninput, VolumeOutput& output ) {
     216             :   // Calculate position of atom wrt to origin
     217        8000 :   Vector fpos=input.pbc.distance( Vector(input.refpos[0][0],input.refpos[0][1],input.refpos[0][2]), Vector(input.cpos[0],input.cpos[1],input.cpos[2]) );
     218             : 
     219             :   double vcylinder, dcylinder;
     220        8000 :   if( actioninput.docylinder ) {
     221        8000 :     HistogramBead bead( actioninput.kerneltype,
     222        8000 :                         actioninput.min, actioninput.max, actioninput.sigma );
     223        8000 :     vcylinder=bead.calculate( fpos[actioninput.dir[2]], dcylinder );
     224             :   } else {
     225             :     vcylinder=1.0;
     226           0 :     dcylinder=0.0;
     227             :   }
     228             : 
     229        8000 :   const double dd = fpos[actioninput.dir[0]]*fpos[actioninput.dir[0]] + fpos[actioninput.dir[1]]*fpos[actioninput.dir[1]];
     230             :   double dfunc;
     231        8000 :   double vswitch = actioninput.switchingFunction.calculateSqr( dd, dfunc );
     232        8000 :   output.values[0]=vswitch*vcylinder;
     233        8000 :   output.derivatives[actioninput.dir[0]]=vcylinder*dfunc*fpos[actioninput.dir[0]];
     234        8000 :   output.derivatives[actioninput.dir[1]]=vcylinder*dfunc*fpos[actioninput.dir[1]];
     235        8000 :   output.derivatives[actioninput.dir[2]]=vswitch*dcylinder;
     236             :   // Add derivatives wrt to position of origin atom
     237        8000 :   output.refders[0][0] = -output.derivatives[0];
     238        8000 :   output.refders[0][1] = -output.derivatives[1];
     239        8000 :   output.refders[0][2] = -output.derivatives[2];
     240             :   // Add virial contribution
     241       16000 :   output.virial.set( 0, -Tensor(fpos,Vector(output.derivatives[0], output.derivatives[1], output.derivatives[2])) );
     242        8000 : }
     243             : 
     244             : }
     245             : }

Generated by: LCOV version 1.16