LCOV - code coverage report
Current view: top level - contour - DistanceFromContour.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 81 95 85.3 %
Date: 2024-10-18 14:00:25 Functions: 4 5 80.0 %

          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 "DistanceFromContourBase.h"
      23             : #include "core/ActionRegister.h"
      24             : 
      25             : //+PLUMEDOC COLVAR DISTANCE_FROM_CONTOUR
      26             : /*
      27             : Calculate the perpendicular distance from a Willard-Chandler dividing surface.
      28             : 
      29             : Suppose that you have calculated a multicolvar.  By doing so you have calculated a
      30             : set of colvars, \f$s_i\f$, and each of these colvars has a well defined position in
      31             : space \f$(x_i,y_i,z_i)\f$.  You can use this information to calculate a phase-field
      32             : model of the colvar density using:
      33             : 
      34             : \f[
      35             : p(x,y,x) = \sum_{i} s_i K\left[\frac{x-x_i}{\sigma_x},\frac{y-y_i}{\sigma_y},\frac{z-z_i}{\sigma_z} \right]
      36             : \f]
      37             : 
      38             : In this expression \f$\sigma_x, \sigma_y\f$ and \f$\sigma_z\f$ are bandwidth parameters and
      39             : \f$K\f$ is one of the \ref kernelfunctions.  This is what is done within \ref MULTICOLVARDENS
      40             : 
      41             : The Willard-Chandler surface is a surface of constant density in the above phase field \f$p(x,y,z)\f$.
      42             : In other words, it is a set of points, \f$(x',y',z')\f$, in your box which have:
      43             : 
      44             : \f[
      45             : p(x',y',z') = \rho
      46             : \f]
      47             : 
      48             : where \f$\rho\f$ is some target density.  This action calculates the distance projected on the \f$x, y\f$ or
      49             : \f$z\f$ axis between the position of some test particle and this surface of constant field density.
      50             : 
      51             : \par Examples
      52             : 
      53             : In this example atoms 2-100 are assumed to be concentrated along some part of the \f$z\f$ axis so that you
      54             : an interface between a liquid/solid and the vapor.  The quantity dc measures the distance between the
      55             : surface at which the density of 2-100 atoms is equal to 0.2 and the position of the test particle atom 1.
      56             : 
      57             : \plumedfile
      58             : dens: DENSITY SPECIES=2-100
      59             : dc: DISTANCE_FROM_CONTOUR DATA=dens ATOM=1 BANDWIDTH=0.5,0.5,0.5 DIR=z CONTOUR=0.2
      60             : \endplumedfile
      61             : 
      62             : */
      63             : //+ENDPLUMEDOC
      64             : 
      65             : namespace PLMD {
      66             : namespace contour {
      67             : 
      68             : class DistanceFromContour : public DistanceFromContourBase {
      69             : private:
      70             :   unsigned dir;
      71             :   double pbc_param;
      72             :   std::vector<double> pos1, pos2, dirv, dirv2;
      73             :   std::vector<unsigned> perp_dirs;
      74             :   std::vector<Vector> atom_deriv;
      75             : public:
      76             :   static void registerKeywords( Keywords& keys );
      77             :   explicit DistanceFromContour( const ActionOptions& );
      78             :   void calculate() override;
      79             :   void evaluateDerivatives( const Vector& root1, const double& root2 );
      80             : };
      81             : 
      82             : PLUMED_REGISTER_ACTION(DistanceFromContour,"DISTANCE_FROM_CONTOUR")
      83             : 
      84           3 : void DistanceFromContour::registerKeywords( Keywords& keys ) {
      85           3 :   DistanceFromContourBase::registerKeywords( keys );
      86           6 :   keys.addOutputComponent("dist1","default","the distance between the reference atom and the nearest contour");
      87           6 :   keys.addOutputComponent("dist2","default","the distance between the reference atom and the other contour");
      88           6 :   keys.addOutputComponent("qdist","default","the differentiable (squared) distance between the two contours (see above)");
      89           6 :   keys.addOutputComponent("thickness","default","the distance between the two contours on the line from the reference atom");
      90           6 :   keys.add("compulsory","DIR","the direction perpendicular to the contour that you are looking for");
      91           6 :   keys.add("compulsory","TOLERANCE","0.1","this parameter is used to manage periodic boundary conditions.  The problem "
      92             :            "here is that we can be between contours even when we are not within the membrane "
      93             :            "because of periodic boundary conditions.  When we are in the contour, however, we "
      94             :            "should have it so that the sums of the absolute values of the distances to the two "
      95             :            "contours is approximately the distance between the two contours.  There can be numerical errors in these calculations, however, so "
      96             :            "we specify a small tolerance here");
      97           3 : }
      98             : 
      99           1 : DistanceFromContour::DistanceFromContour( const ActionOptions& ao ):
     100             :   Action(ao),
     101             :   DistanceFromContourBase(ao),
     102           2 :   pos1(3,0.0),
     103           1 :   pos2(3,0.0),
     104           1 :   dirv(3,0.0),
     105           1 :   dirv2(3,0.0),
     106           1 :   perp_dirs(2),
     107           2 :   atom_deriv(active_list.size())
     108             : {
     109             :   // Get the direction
     110           2 :   std::string ldir; parse("DIR",ldir );
     111           1 :   if( ldir=="x" ) { dir=0; perp_dirs[0]=1; perp_dirs[1]=2; dirv[0]=1; dirv2[0]=-1; }
     112           1 :   else if( ldir=="y" ) { dir=1; perp_dirs[0]=0; perp_dirs[1]=2; dirv[1]=1; dirv2[1]=-1; }
     113           1 :   else if( ldir=="z" ) { dir=2; perp_dirs[0]=0; perp_dirs[1]=1; dirv[2]=1; dirv2[2]=-1; }
     114           0 :   else error(ldir + " is not a valid direction use x, y or z");
     115             : 
     116             :   // Read in the tolerance for the pbc parameter
     117           2 :   parse("TOLERANCE",pbc_param);
     118             : 
     119             :   std::vector<unsigned> shape;
     120             :   // Create the values
     121           2 :   addComponent("thickness", shape ); componentIsNotPeriodic("thickness");
     122           2 :   addComponent("dist1", shape ); componentIsNotPeriodic("dist1");
     123           2 :   addComponent("dist2", shape ); componentIsNotPeriodic("dist2");
     124           3 :   addComponentWithDerivatives("qdist", shape ); componentIsNotPeriodic("qdist");
     125           1 : }
     126             : 
     127         137 : void DistanceFromContour::calculate() {
     128             :   // Check box is orthorhombic
     129         137 :   if( !getPbc().isOrthorombic() ) error("cell box must be orthorhombic");
     130             : 
     131             :   // The nanoparticle is at the origin of our coordinate system
     132         137 :   pos1[0]=pos1[1]=pos1[2]=0.0; pos2[0]=pos2[1]=pos2[2]=0.0;
     133             : 
     134             :   // Set bracket as center of mass of membrane in active region
     135         137 :   Vector myvec = pbcDistance( getPosition(getNumberOfAtoms()-1), getPosition(0) ); pos2[dir]=myvec[dir];
     136         137 :   nactive=1; active_list[0]=0; double d2, mindist = myvec.modulo2();
     137         137 :   for(unsigned j=1; j<getNumberOfAtoms()-1; ++j) {
     138           0 :     Vector distance=pbcDistance( getPosition(getNumberOfAtoms()-1), getPosition(j) );
     139           0 :     if( (d2=distance[perp_dirs[0]]*distance[perp_dirs[0]])<rcut2 &&
     140           0 :         (d2+=distance[perp_dirs[1]]*distance[perp_dirs[1]])<rcut2 ) {
     141           0 :       d2+=distance[dir]*distance[dir];
     142           0 :       if( d2<mindist && fabs(distance[dir])>epsilon ) { pos2[dir]=distance[dir]; mindist = d2; }
     143           0 :       active_list[nactive]=j; nactive++;
     144             :     }
     145             :   }
     146             :   // pos1 position of the nanoparticle, in the first time
     147             :   // pos2 is the position of the closer atom in the membrane with respect the nanoparticle
     148             :   // fa = distance between pos1 and the contour
     149             :   // fb = distance between pos2 and the contour
     150         137 :   std::vector<double> faked(3);
     151         137 :   double fa = getDifferenceFromContour( pos1, faked );
     152         137 :   double fb = getDifferenceFromContour( pos2, faked );
     153         137 :   if( fa*fb>0 ) {
     154           0 :     unsigned maxtries = std::floor( ( getBox()(dir,dir) ) / bw[dir] );
     155           0 :     for(unsigned i=0; i<maxtries; ++i) {
     156           0 :       double sign=(pos2[dir]>0)? -1 : +1; // If the nanoparticle is inside the membrane push it out
     157           0 :       pos1[dir] += sign*bw[dir]; fa = getDifferenceFromContour( pos1, faked );
     158           0 :       if( fa*fb<0 ) break;
     159             :       // if fa*fb is less than zero the new pos 1 is outside the contour
     160             :     }
     161             :   }
     162             :   // Set direction for contour search
     163         137 :   dirv[dir] = pos2[dir] - pos1[dir];
     164             :   // Bracket for second root starts in center of membrane
     165         137 :   double fc = getDifferenceFromContour( pos2, faked );
     166         137 :   if( fc*fb>0 ) {
     167             :     // first time is true, because fc=fb
     168             :     // push pos2 from its initial position inside the membrane towards the second contourn
     169         137 :     unsigned maxtries = std::floor( ( getBox()(dir,dir) ) / bw[dir] );
     170         230 :     for(unsigned i=0; i<maxtries; ++i) {
     171         230 :       double sign=(dirv[dir]>0)? +1 : -1;
     172         230 :       pos2[dir] += sign*bw[dir]; fc = getDifferenceFromContour( pos2, faked );
     173         230 :       if( fc*fb<0 ) break;
     174             :     }
     175         137 :     dirv2[dir] = ( pos1[dir] + dirv[dir] ) - pos2[dir];
     176             :   }
     177             : 
     178             :   // Now do a search for the two contours
     179         137 :   findContour( dirv, pos1 );
     180             :   // Save the first value
     181         137 :   Vector root1; root1.zero(); root1[dir] = pval[dir];
     182         137 :   findContour( dirv2, pos2 );
     183             :   // Calculate the separation between the two roots using PBC
     184         137 :   Vector root2; root2.zero(); root2[dir] = pval[dir];
     185         137 :   Vector sep = pbcDistance( root1, root2 ); double spacing = fabs( sep[dir] ); plumed_assert( spacing>epsilon );
     186         137 :   getPntrToComponent("thickness")->set( spacing );
     187             : 
     188             :   // Make sure the sign is right
     189         137 :   double predir=(root1[dir]*root2[dir]<0)? -1 : 1;
     190             :   // This deals with periodic boundary conditions - if we are inside the membrane the sum of the absolute
     191             :   // distances from the contours should add up to the spacing.  When this is not the case we must be outside
     192             :   // the contour
     193             :   // if( predir==-1 && (fabs(root1[dir])+fabs(root2[dir]))>(spacing+pbc_param) ) predir=1;
     194             :   // Set the final value to root that is closest to the "origin" = position of atom
     195         137 :   if( fabs(root1[dir])<fabs(root2[dir]) ) {
     196         137 :     getPntrToComponent("dist1")->set( predir*fabs(root1[dir]) );
     197         274 :     getPntrToComponent("dist2")->set( fabs(root2[dir]) );
     198             :   } else {
     199           0 :     getPntrToComponent("dist1")->set( predir*fabs(root2[dir]) );
     200           0 :     getPntrToComponent("dist2")->set( fabs(root1[dir]) );
     201             :   }
     202         137 :   getPntrToComponent("qdist")->set( root2[dir]*root1[dir] );
     203             : 
     204             :   // Now calculate the derivatives
     205         137 :   if( !doNotCalculateDerivatives() ) {
     206         137 :     evaluateDerivatives( root1, root2[dir] ); evaluateDerivatives( root2, root1[dir] );
     207             :   }
     208         137 : }
     209             : 
     210         274 : void DistanceFromContour::evaluateDerivatives( const Vector& root1, const double& root2 ) {
     211         274 :   if( getNumberOfArguments()>0 ) plumed_merror("derivatives for phase field distance from contour have not been implemented yet");
     212             : 
     213         274 :   Vector origind; origind.zero(); Tensor vir; vir.zero();
     214         274 :   double sumd = 0; std::vector<double> pp(3), ddd(3,0);
     215         548 :   for(unsigned i=0; i<nactive; ++i) {
     216         274 :     double newval = evaluateKernel( getPosition(active_list[i]), root1, ddd );
     217         274 :     Vector distance = pbcDistance( getPosition(getNumberOfAtoms()-1), getPosition(active_list[i]) );
     218             : 
     219         274 :     if( getNumberOfArguments()==1 ) {
     220             :     } else {
     221         274 :       sumd += ddd[dir];
     222        1096 :       for(unsigned j=0; j<3; ++j) atom_deriv[i][j] = -ddd[j];
     223         274 :       origind += -atom_deriv[i]; vir -= Tensor(atom_deriv[i],distance);
     224             :     }
     225             :   }
     226             : 
     227             :   // Add derivatives to atoms involved
     228         274 :   Value* val=getPntrToComponent("qdist"); double prefactor =  root2 / sumd;
     229         548 :   for(unsigned i=0; i<nactive; ++i) {
     230         274 :     val->addDerivative( 3*active_list[i] + 0, -prefactor*atom_deriv[i][0] );
     231         274 :     val->addDerivative( 3*active_list[i] + 1, -prefactor*atom_deriv[i][1] );
     232         274 :     val->addDerivative( 3*active_list[i] + 2, -prefactor*atom_deriv[i][2] );
     233             :   }
     234             : 
     235             :   // Add derivatives to atoms at origin
     236         274 :   unsigned nbase = 3*(getNumberOfAtoms()-1);
     237         274 :   val->addDerivative( nbase, -prefactor*origind[0] ); nbase++;
     238         274 :   val->addDerivative( nbase, -prefactor*origind[1] ); nbase++;
     239         274 :   val->addDerivative( nbase, -prefactor*origind[2] ); nbase++;
     240             : 
     241             :   // Add derivatives to virial
     242        3562 :   for(unsigned i=0; i<3; ++i) for(unsigned j=0; j<3; ++j) { val->addDerivative( nbase, -prefactor*vir(i,j) ); nbase++; }
     243         274 : }
     244             : 
     245             : }
     246             : }

Generated by: LCOV version 1.16