LCOV - code coverage report
Current view: top level - multicolvar - DistanceFromContour.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 132 153 86.3 %
Date: 2024-10-11 08:09:47 Functions: 11 13 84.6 %

          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 "MultiColvarBase.h"
      23             : #include "AtomValuePack.h"
      24             : #include "core/ActionRegister.h"
      25             : #include "tools/KernelFunctions.h"
      26             : #include "tools/RootFindingBase.h"
      27             : #include "vesselbase/ValueVessel.h"
      28             : 
      29             : //+PLUMEDOC COLVAR DISTANCE_FROM_CONTOUR
      30             : /*
      31             : Calculate the perpendicular distance from a Willard-Chandler dividing surface.
      32             : 
      33             : Suppose that you have calculated a multicolvar.  By doing so you have calculated a
      34             : set of colvars, \f$s_i\f$, and each of these colvars has a well defined position in
      35             : space \f$(x_i,y_i,z_i)\f$.  You can use this information to calculate a phase-field
      36             : model of the colvar density using:
      37             : 
      38             : \f[
      39             : 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]
      40             : \f]
      41             : 
      42             : In this expression \f$\sigma_x, \sigma_y\f$ and \f$\sigma_z\f$ are bandwidth parameters and
      43             : \f$K\f$ is one of the \ref kernelfunctions.  This is what is done within \ref MULTICOLVARDENS
      44             : 
      45             : The Willard-Chandler surface is a surface of constant density in the above phase field \f$p(x,y,z)\f$.
      46             : In other words, it is a set of points, \f$(x',y',z')\f$, in your box which have:
      47             : 
      48             : \f[
      49             : p(x',y',z') = \rho
      50             : \f]
      51             : 
      52             : where \f$\rho\f$ is some target density.  This action calculates the distance projected on the \f$x, y\f$ or
      53             : \f$z\f$ axis between the position of some test particle and this surface of constant field density.
      54             : 
      55             : \par Examples
      56             : 
      57             : In this example atoms 2-100 are assumed to be concentrated along some part of the \f$z\f$ axis so that you
      58             : an interface between a liquid/solid and the vapor.  The quantity dc measures the distance between the
      59             : surface at which the density of 2-100 atoms is equal to 0.2 and the position of the test particle atom 1.
      60             : 
      61             : \plumedfile
      62             : dens: DENSITY SPECIES=2-100
      63             : dc: DISTANCE_FROM_CONTOUR DATA=dens ATOM=1 BANDWIDTH=0.5,0.5,0.5 DIR=z CONTOUR=0.2
      64             : \endplumedfile
      65             : 
      66             : */
      67             : //+ENDPLUMEDOC
      68             : 
      69             : namespace PLMD {
      70             : namespace multicolvar {
      71             : 
      72             : class DistanceFromContour : public MultiColvarBase {
      73             : private:
      74             :   unsigned dir;
      75             :   bool derivTime;
      76             :   double rcut2;
      77             :   double contour;
      78             :   double pbc_param;
      79             :   std::string kerneltype;
      80             :   std::vector<std::unique_ptr<Value>> pval;
      81             :   std::vector<double> bw, pos1, pos2, dirv, dirv2;
      82             :   std::vector<double> forces;
      83             :   std::vector<unsigned> perp_dirs;
      84             :   vesselbase::ValueVessel* myvalue_vessel;
      85             :   vesselbase::ValueVessel* myderiv_vessel;
      86             :   RootFindingBase<DistanceFromContour> mymin;
      87             : public:
      88             :   static void registerKeywords( Keywords& keys );
      89             :   explicit DistanceFromContour( const ActionOptions& );
      90        3115 :   bool isDensity() const override { return true; }
      91             :   void calculate() override;
      92             :   unsigned getNumberOfQuantities() const override;
      93           0 :   bool isPeriodic() override { return false; }
      94             :   double compute( const unsigned& tindex, AtomValuePack& myatoms ) const override;
      95             :   double getDifferenceFromContour( const std::vector<double>& x, std::vector<double>& der );
      96             : // We need an apply action as we are using an independent value
      97             :   void apply() override;
      98             : };
      99             : 
     100       10421 : PLUMED_REGISTER_ACTION(DistanceFromContour,"DISTANCE_FROM_CONTOUR")
     101             : 
     102           2 : void DistanceFromContour::registerKeywords( Keywords& keys ) {
     103           2 :   MultiColvarBase::registerKeywords( keys );
     104           4 :   keys.addOutputComponent("dist1","default","the distance between the reference atom and the nearest contour");
     105           4 :   keys.addOutputComponent("dist2","default","the distance between the reference atom and the other contour");
     106           4 :   keys.addOutputComponent("qdist","default","the differentiable (squared) distance between the two contours (see above)");
     107           4 :   keys.addOutputComponent("thickness","default","the distance between the two contours on the line from the reference atom");
     108           4 :   keys.add("compulsory","DATA","The input base multicolvar which is being used to calculate the contour");
     109           4 :   keys.add("atoms","ATOM","The atom whose perpendicular distance we are calculating from the contour");
     110           4 :   keys.add("compulsory","BANDWIDTH","the bandwidths for kernel density estimation");
     111           4 :   keys.add("compulsory","KERNEL","gaussian","the kernel function you are using.  More details on  the kernels available "
     112             :            "in plumed plumed can be found in \\ref kernelfunctions.");
     113           4 :   keys.add("compulsory","DIR","the direction perpendicular to the contour that you are looking for");
     114           4 :   keys.add("compulsory","CONTOUR","the value we would like for the contour");
     115           4 :   keys.add("compulsory","TOLERANCE","0.1","this parameter is used to manage periodic boundary conditions.  The problem "
     116             :            "here is that we can be between contours even when we are not within the membrane "
     117             :            "because of periodic boundary conditions.  When we are in the contour, however, we "
     118             :            "should have it so that the sums of the absolute values of the distances to the two "
     119             :            "contours is approximately the distance between the two contours.  There can be numerical errors in these calculations, however, so "
     120             :            "we specify a small tolerance here");
     121           2 : }
     122             : 
     123           1 : DistanceFromContour::DistanceFromContour( const ActionOptions& ao ):
     124             :   Action(ao),
     125             :   MultiColvarBase(ao),
     126           1 :   derivTime(false),
     127           1 :   bw(3),
     128           1 :   pos1(3,0.0),
     129           1 :   pos2(3,0.0),
     130           1 :   dirv(3,0.0),
     131           1 :   dirv2(3,0.0),
     132           1 :   perp_dirs(2),
     133           2 :   mymin(this)
     134             : {
     135             :   // Read in the multicolvar/atoms
     136           1 :   std::vector<AtomNumber> all_atoms; parse("TOLERANCE",pbc_param);
     137           1 :   bool read2 = parseMultiColvarAtomList("DATA", -1, all_atoms);
     138           1 :   if( !read2 ) error("missing DATA keyword");
     139           1 :   bool read1 = parseMultiColvarAtomList("ATOM", -1, all_atoms);
     140           1 :   if( !read1 ) error("missing ATOM keyword");
     141           1 :   if( all_atoms.size()!=1 ) error("should only be one atom specified");
     142             :   // Read in the center of the binding object
     143           1 :   log.printf("  computing distance of atom %d from contour \n",all_atoms[0].serial() );
     144           1 :   setupMultiColvarBase( all_atoms ); forces.resize( 3*getNumberOfAtoms() + 9 );
     145           1 :   if( getNumberOfBaseMultiColvars()!=1 ) error("should only be one input multicolvar");
     146             : 
     147             :   // Get the direction
     148           2 :   std::string ldir; parse("DIR",ldir );
     149           1 :   if( ldir=="x" ) { dir=0; perp_dirs[0]=1; perp_dirs[1]=2; dirv[0]=1; dirv2[0]=-1; }
     150           1 :   else if( ldir=="y" ) { dir=1; perp_dirs[0]=0; perp_dirs[1]=2; dirv[1]=1; dirv2[1]=-1; }
     151           1 :   else if( ldir=="z" ) { dir=2; perp_dirs[0]=0; perp_dirs[1]=1; dirv[2]=1; dirv2[2]=-1; }
     152           0 :   else error(ldir + " is not a valid direction use x, y or z");
     153             : 
     154             :   // Read in details of phase field construction
     155           3 :   parseVector("BANDWIDTH",bw); parse("KERNEL",kerneltype); parse("CONTOUR",contour);
     156           1 :   log.printf("  searching for contour in %s direction at %f in phase field for multicolvar %s \n",ldir.c_str(), contour, mybasemulticolvars[0]->getLabel().c_str() );
     157           1 :   log.printf("  constructing phase field using %s kernels with bandwidth (%f, %f, %f) \n",kerneltype.c_str(), bw[0], bw[1], bw[2] );
     158             : 
     159             :   // Now create a task list
     160           2 :   for(unsigned i=0; i<mybasemulticolvars[0]->getFullNumberOfTasks(); ++i) addTaskToList(i);
     161             :   // And a cutoff
     162           1 :   std::vector<double> pp( bw.size(),0 );
     163           2 :   KernelFunctions kernel( pp, bw, kerneltype, "DIAGONAL", 1.0 );
     164           1 :   double rcut = kernel.getCutoff( bw[0] );
     165           3 :   for(unsigned j=1; j<bw.size(); ++j) {
     166           2 :     if( kernel.getCutoff(bw[j])>rcut ) rcut=kernel.getCutoff(bw[j]);
     167             :   }
     168           1 :   rcut2=rcut*rcut;
     169             :   // Create the values
     170           2 :   addComponent("thickness"); componentIsNotPeriodic("thickness");
     171           2 :   addComponent("dist1"); componentIsNotPeriodic("dist1");
     172           2 :   addComponent("dist2"); componentIsNotPeriodic("dist2");
     173           3 :   addComponentWithDerivatives("qdist"); componentIsNotPeriodic("qdist");
     174             :   // Create sum vessels
     175           1 :   std::string fake_input; std::string deriv_input="COMPONENT=2";
     176           1 :   if( mybasemulticolvars[0]->isDensity() ) {
     177           3 :     addVessel( "SUM", fake_input, -1 ); addVessel( "SUM", deriv_input, -1 );
     178             :   } else {
     179           0 :     addVessel( "MEAN", fake_input, -1 ); addVessel( "MEAN", deriv_input, -1 );
     180             :   }
     181             :   // And convert to a value vessel so we can get the final value
     182           1 :   myvalue_vessel = dynamic_cast<vesselbase::ValueVessel*>( getPntrToVessel(0) );
     183           1 :   myderiv_vessel = dynamic_cast<vesselbase::ValueVessel*>( getPntrToVessel(1) );
     184           1 :   plumed_assert( myvalue_vessel && myderiv_vessel ); resizeFunctions();
     185             : 
     186             :   // Create the vector of values that holds the position
     187           4 :   for(unsigned i=0; i<3; ++i) pval.emplace_back( Tools::make_unique<Value>() );
     188           2 : }
     189             : 
     190        6230 : unsigned DistanceFromContour::getNumberOfQuantities() const {
     191        6230 :   return 3;
     192             : }
     193             : 
     194         137 : void DistanceFromContour::calculate() {
     195             :   // Check box is orthorhombic
     196         137 :   if( !getPbc().isOrthorombic() ) error("cell box must be orthorhombic");
     197             : 
     198             :   // The nanoparticle is at the origin of our coordinate system
     199         137 :   pos1[0]=pos1[1]=pos1[2]=0.0; pos2[0]=pos2[1]=pos2[2]=0.0;
     200             : 
     201             :   // Set bracket as center of mass of membrane in active region
     202         137 :   deactivateAllTasks();
     203         137 :   Vector myvec = getSeparation( getPosition(getNumberOfAtoms()-1), getPosition(0) ); pos2[dir]=myvec[dir];
     204         137 :   taskFlags[0]=1; double mindist = myvec.modulo2();
     205         137 :   for(unsigned j=1; j<getNumberOfAtoms()-1; ++j) {
     206           0 :     Vector distance=getSeparation( getPosition(getNumberOfAtoms()-1), getPosition(j) );
     207             :     double d2;
     208           0 :     if( (d2=distance[perp_dirs[0]]*distance[perp_dirs[0]])<rcut2 &&
     209           0 :         (d2+=distance[perp_dirs[1]]*distance[perp_dirs[1]])<rcut2 ) {
     210           0 :       d2+=distance[dir]*distance[dir];
     211           0 :       if( d2<mindist && std::fabs(distance[dir])>epsilon ) { pos2[dir]=distance[dir]; mindist = d2; }
     212           0 :       taskFlags[j]=1;
     213             :     }
     214             :   }
     215         137 :   lockContributors(); derivTime=false;
     216             :   // pos1 position of the nanoparticle, in the first time
     217             :   // pos2 is the position of the closer atom in the membrane with respect the nanoparticle
     218             :   // fa = distance between pos1 and the contour
     219             :   // fb = distance between pos2 and the contour
     220         137 :   std::vector<double> faked(3);
     221         137 :   double fa = getDifferenceFromContour( pos1, faked );
     222         137 :   double fb = getDifferenceFromContour( pos2, faked );
     223         137 :   if( fa*fb>0 ) {
     224           0 :     unsigned maxtries = std::floor( ( getBox()(dir,dir) ) / bw[dir] );
     225           0 :     for(unsigned i=0; i<maxtries; ++i) {
     226           0 :       double sign=(pos2[dir]>0)? -1 : +1; // If the nanoparticle is inside the membrane push it out
     227           0 :       pos1[dir] += sign*bw[dir]; fa = getDifferenceFromContour( pos1, faked );
     228           0 :       if( fa*fb<0 ) break;
     229             :       // if fa*fb is less than zero the new pos 1 is outside the contour
     230             :     }
     231             :   }
     232             :   // Set direction for contour search
     233         137 :   dirv[dir] = pos2[dir] - pos1[dir];
     234             :   // Bracket for second root starts in center of membrane
     235         137 :   double fc = getDifferenceFromContour( pos2, faked );
     236         137 :   if( fc*fb>0 ) {
     237             :     // first time is true, because fc=fb
     238             :     // push pos2 from its initial position inside the membrane towards the second contourn
     239         137 :     unsigned maxtries = std::floor( ( getBox()(dir,dir) ) / bw[dir] );
     240         230 :     for(unsigned i=0; i<maxtries; ++i) {
     241         230 :       double sign=(dirv[dir]>0)? +1 : -1;
     242         230 :       pos2[dir] += sign*bw[dir]; fc = getDifferenceFromContour( pos2, faked );
     243         230 :       if( fc*fb<0 ) break;
     244             :     }
     245         137 :     dirv2[dir] = ( pos1[dir] + dirv[dir] ) - pos2[dir];
     246             :   }
     247             : 
     248             :   // Now do a search for the two contours
     249         137 :   mymin.lsearch( dirv, pos1, &DistanceFromContour::getDifferenceFromContour );
     250             :   // Save the first value
     251         137 :   Vector root1; root1.zero(); root1[dir] = pval[dir]->get();
     252         137 :   mymin.lsearch( dirv2, pos2, &DistanceFromContour::getDifferenceFromContour );
     253             :   // Calculate the separation between the two roots using PBC
     254         137 :   Vector root2; root2.zero(); root2[dir]=pval[dir]->get();
     255         137 :   Vector sep = getSeparation( root1, root2 ); double spacing = std::fabs( sep[dir] ); plumed_assert( spacing>epsilon );
     256         137 :   getPntrToComponent("thickness")->set( spacing );
     257             : 
     258             :   // Make sure the sign is right
     259         137 :   double predir=(root1[dir]*root2[dir]<0)? -1 : 1;
     260             :   // This deals with periodic boundary conditions - if we are inside the membrane the sum of the absolute
     261             :   // distances from the contours should add up to the spacing.  When this is not the case we must be outside
     262             :   // the contour
     263             :   // if( predir==-1 && (std::fabs(root1[dir])+std::fabs(root2[dir]))>(spacing+pbc_param) ) predir=1;
     264             :   // Set the final value to root that is closest to the "origin" = position of atom
     265         137 :   if( std::fabs(root1[dir])<std::fabs(root2[dir]) ) {
     266         137 :     getPntrToComponent("dist1")->set( predir*std::fabs(root1[dir]) );
     267         274 :     getPntrToComponent("dist2")->set( std::fabs(root2[dir]) );
     268             :   } else {
     269           0 :     getPntrToComponent("dist1")->set( predir*std::fabs(root2[dir]) );
     270           0 :     getPntrToComponent("dist2")->set( std::fabs(root1[dir]) );
     271             :   }
     272         137 :   getPntrToComponent("qdist")->set( root2[dir]*root1[dir] );
     273             : 
     274             :   // Now calculate the derivatives
     275         137 :   if( !doNotCalculateDerivatives() ) {
     276         137 :     Value* ival=myvalue_vessel->getFinalValue(); ival->clearDerivatives();
     277         548 :     std::vector<double> root1v(3); for(unsigned i=0; i<3; ++i) root1v[i]=root1[i];
     278         137 :     derivTime=true; std::vector<double> der(3); getDifferenceFromContour( root1v, der ); double prefactor;
     279         137 :     if( mybasemulticolvars[0]->isDensity() ) prefactor = root2[dir] / myderiv_vessel->getOutputValue();
     280           0 :     else plumed_error();
     281         137 :     Value* val=getPntrToComponent("qdist");
     282        2192 :     for(unsigned i=0; i<val->getNumberOfDerivatives(); ++i) val->setDerivative( i, -prefactor*ival->getDerivative(i) );
     283             :     ival->clearDerivatives();
     284         548 :     std::vector<double> root2v(3); for(unsigned i=0; i<3; ++i) root2v[i]=root2[i];
     285         137 :     getDifferenceFromContour( root2v, der );
     286         137 :     if( mybasemulticolvars[0]->isDensity() ) prefactor = root1[dir] / myderiv_vessel->getOutputValue();
     287           0 :     else plumed_error();
     288        2192 :     for(unsigned i=0; i<val->getNumberOfDerivatives(); ++i) val->addDerivative( i, -prefactor*ival->getDerivative(i) );
     289             :   }
     290         137 : }
     291             : 
     292        3115 : double DistanceFromContour::getDifferenceFromContour( const std::vector<double>& x, std::vector<double>& der ) {
     293             :   std::string min, max;
     294       12460 :   for(unsigned j=0; j<3; ++j) {
     295        9345 :     Tools::convert( -0.5*getBox()(j,j), min );
     296        9345 :     Tools::convert( +0.5*getBox()(j,j), max );
     297        9345 :     pval[j]->setDomain( min, max ); pval[j]->set( x[j] );
     298             :   }
     299        3115 :   runAllTasks();
     300        6230 :   return myvalue_vessel->getOutputValue() - contour;
     301             : }
     302             : 
     303        3115 : double DistanceFromContour::compute( const unsigned& tindex, AtomValuePack& myatoms ) const {
     304        3115 :   Vector distance = getSeparation( getPosition(getNumberOfAtoms()-1), myatoms.getPosition(0) );
     305       12460 :   std::vector<double> pp(3), der(3,0); for(unsigned j=0; j<3; ++j) pp[j] = distance[j];
     306             : 
     307             :   // convert the pointer once
     308        3115 :   auto pval_ptr=Tools::unique2raw(pval);
     309             : 
     310             :   // Now create the kernel and evaluate
     311        3115 :   KernelFunctions kernel( pp, bw, kerneltype, "DIAGONAL", 1.0 );
     312        3115 :   kernel.normalize( pval_ptr ); double newval = kernel.evaluate( pval_ptr, der, true );
     313             : 
     314        3115 :   if( mybasemulticolvars[0]->isDensity() ) {
     315        3115 :     if( !doNotCalculateDerivatives() && derivTime ) {
     316             :       MultiValue& myvals=myatoms.getUnderlyingMultiValue();
     317         274 :       Vector vder; unsigned basen=myvals.getNumberOfDerivatives() - 12;
     318        1096 :       for(unsigned i=0; i<3; ++i) {
     319         822 :         vder[i]=der[i]; myvals.addDerivative( 1, basen+i, vder[i] );
     320             :       }
     321         274 :       myatoms.setValue( 2, der[dir] );
     322         274 :       addAtomDerivatives( 1, 0, -vder, myatoms );
     323         274 :       myatoms.addBoxDerivatives( 1, Tensor(vder,distance) );
     324             :     }
     325        3115 :     myatoms.setValue( 0, 1.0 ); return newval;
     326             :   }
     327             : 
     328             :   // This does the stuff for averaging
     329             :   myatoms.setValue( 0, newval );
     330             : 
     331             :   // This gets the average if we are using a phase field
     332           0 :   std::vector<double> cvals( mybasemulticolvars[0]->getNumberOfQuantities() );
     333           0 :   mybasedata[0]->retrieveValueWithIndex( tindex, false, cvals );
     334           0 :   return newval*cvals[0]*cvals[1];
     335        3115 : }
     336             : 
     337         137 : void DistanceFromContour::apply() {
     338         274 :   if( getPntrToComponent("qdist")->applyForce( forces ) ) setForcesOnAtoms( forces );
     339         137 : }
     340             : 
     341             : }
     342             : }

Generated by: LCOV version 1.15