LCOV - code coverage report
Current view: top level - multicolvar - DistanceFromContour.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 127 148 85.8 %
Date: 2020-11-18 11:20:57 Functions: 15 18 83.3 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2016-2019 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 caculates 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 concentraed along some part of the \f$z\f$ axis so that you
      58             : an interface between a liquid/solid and the vapour.  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<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             :   ~DistanceFromContour();
      91        3115 :   bool isDensity() const { return true; }
      92             :   void calculate();
      93             :   unsigned getNumberOfQuantities() const ;
      94           0 :   bool isPeriodic() { return false; }
      95             :   double compute( const unsigned& tindex, AtomValuePack& myatoms ) const ;
      96             :   double getDifferenceFromContour( const std::vector<double>& x, std::vector<double>& der );
      97             : // We need an apply action as we are using an independent value
      98             :   void apply();
      99             : };
     100             : 
     101        6453 : PLUMED_REGISTER_ACTION(DistanceFromContour,"DISTANCE_FROM_CONTOUR")
     102             : 
     103           2 : void DistanceFromContour::registerKeywords( Keywords& keys ) {
     104           2 :   MultiColvarBase::registerKeywords( keys );
     105           8 :   keys.addOutputComponent("dist1","default","the distance between the reference atom and the nearest contour");
     106           8 :   keys.addOutputComponent("dist2","default","the distance between the reference atom and the other contour");
     107           8 :   keys.addOutputComponent("qdist","default","the differentiable (squared) distance between the two contours (see above)");
     108           8 :   keys.addOutputComponent("thickness","default","the distance between the two contours on the line from the reference atom");
     109           8 :   keys.add("compulsory","DATA","The input base multicolvar which is being used to calculate the contour");
     110           8 :   keys.add("atoms","ATOM","The atom whose perpendicular distance we are calculating from the contour");
     111           8 :   keys.add("compulsory","BANDWIDTH","the bandwidths for kernel density esimtation");
     112          10 :   keys.add("compulsory","KERNEL","gaussian","the kernel function you are using.  More details on  the kernels available "
     113             :            "in plumed plumed can be found in \\ref kernelfunctions.");
     114           8 :   keys.add("compulsory","DIR","the direction perpendicular to the contour that you are looking for");
     115           8 :   keys.add("compulsory","CONTOUR","the value we would like for the contour");
     116          10 :   keys.add("compulsory","TOLERANCE","0.1","this parameter is used to manage periodic boundary conditions.  The problem "
     117             :            "here is that we can be between contours even when we are not within the membrane "
     118             :            "because of periodic boundary conditions.  When we are in the contour, however, we "
     119             :            "should have it so that the sums of the absoluate values of the distances to the two "
     120             :            "contours is approximately the distance between the two contours.  There can be numerical errors in these calculations, however, so "
     121             :            "we specify a small tolerance here");
     122           2 : }
     123             : 
     124           1 : DistanceFromContour::DistanceFromContour( const ActionOptions& ao ):
     125             :   Action(ao),
     126             :   MultiColvarBase(ao),
     127             :   derivTime(false),
     128             :   bw(3),
     129             :   pos1(3,0.0),
     130             :   pos2(3,0.0),
     131             :   dirv(3,0.0),
     132             :   dirv2(3,0.0),
     133             :   perp_dirs(2),
     134           4 :   mymin(this)
     135             : {
     136             :   // Read in the multicolvar/atoms
     137           2 :   std::vector<AtomNumber> all_atoms; parse("TOLERANCE",pbc_param);
     138           2 :   bool read2 = parseMultiColvarAtomList("DATA", -1, all_atoms);
     139           1 :   if( !read2 ) error("missing DATA keyword");
     140           2 :   bool read1 = parseMultiColvarAtomList("ATOM", -1, all_atoms);
     141           1 :   if( !read1 ) error("missing ATOM keyword");
     142           1 :   if( all_atoms.size()!=1 ) error("should only be one atom specified");
     143             :   // Read in the center of the binding object
     144           2 :   log.printf("  computing distance of atom %d from contour \n",all_atoms[0].serial() );
     145           2 :   setupMultiColvarBase( all_atoms ); forces.resize( 3*getNumberOfAtoms() + 9 );
     146           1 :   if( getNumberOfBaseMultiColvars()!=1 ) error("should only be one input multicolvar");
     147             : 
     148             :   // Get the direction
     149           2 :   std::string ldir; parse("DIR",ldir );
     150           1 :   if( ldir=="x" ) { dir=0; perp_dirs[0]=1; perp_dirs[1]=2; dirv[0]=1; dirv2[0]=-1; }
     151           1 :   else if( ldir=="y" ) { dir=1; perp_dirs[0]=0; perp_dirs[1]=2; dirv[1]=1; dirv2[1]=-1; }
     152           5 :   else if( ldir=="z" ) { dir=2; perp_dirs[0]=0; perp_dirs[1]=1; dirv[2]=1; dirv2[2]=-1; }
     153           0 :   else error(ldir + " is not a valid direction use x, y or z");
     154             : 
     155             :   // Read in details of phase field construction
     156           4 :   parseVector("BANDWIDTH",bw); parse("KERNEL",kerneltype); parse("CONTOUR",contour);
     157           4 :   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() );
     158           3 :   log.printf("  constructing phase field using %s kernels with bandwidth (%f, %f, %f) \n",kerneltype.c_str(), bw[0], bw[1], bw[2] );
     159             : 
     160             :   // Now create a task list
     161           5 :   for(unsigned i=0; i<mybasemulticolvars[0]->getFullNumberOfTasks(); ++i) addTaskToList(i);
     162             :   // And a cutoff
     163           2 :   std::vector<double> pp( bw.size(),0 );
     164           2 :   KernelFunctions kernel( pp, bw, kerneltype, false, 1.0, true );
     165           1 :   double rcut = kernel.getCutoff( bw[0] );
     166           8 :   for(unsigned j=1; j<bw.size(); ++j) {
     167           2 :     if( kernel.getCutoff(bw[j])>rcut ) rcut=kernel.getCutoff(bw[j]);
     168             :   }
     169           1 :   rcut2=rcut*rcut;
     170             :   // Create the values
     171           3 :   addComponent("thickness"); componentIsNotPeriodic("thickness");
     172           3 :   addComponent("dist1"); componentIsNotPeriodic("dist1");
     173           3 :   addComponent("dist2"); componentIsNotPeriodic("dist2");
     174           3 :   addComponentWithDerivatives("qdist"); componentIsNotPeriodic("qdist");
     175             :   // Create sum vessels
     176           1 :   std::string fake_input; std::string deriv_input="COMPONENT=2";
     177           1 :   if( mybasemulticolvars[0]->isDensity() ) {
     178           3 :     addVessel( "SUM", fake_input, -1 ); addVessel( "SUM", deriv_input, -1 );
     179             :   } else {
     180           0 :     addVessel( "MEAN", fake_input, -1 ); addVessel( "MEAN", deriv_input, -1 );
     181             :   }
     182             :   // And convert to a value vessel so we can get the final value
     183           1 :   myvalue_vessel = dynamic_cast<vesselbase::ValueVessel*>( getPntrToVessel(0) );
     184           1 :   myderiv_vessel = dynamic_cast<vesselbase::ValueVessel*>( getPntrToVessel(1) );
     185           1 :   plumed_assert( myvalue_vessel && myderiv_vessel ); resizeFunctions();
     186             : 
     187             :   // Create the vector of values that holds the position
     188           7 :   for(unsigned i=0; i<3; ++i) pval.push_back( new Value() );
     189           1 : }
     190             : 
     191           3 : DistanceFromContour::~DistanceFromContour() {
     192           7 :   for(unsigned i=0; i<3; ++i) delete pval[i];
     193           2 : }
     194             : 
     195        6230 : unsigned DistanceFromContour::getNumberOfQuantities() const {
     196        6230 :   return 3;
     197             : }
     198             : 
     199         137 : void DistanceFromContour::calculate() {
     200             :   // Check box is orthorhombic
     201         137 :   if( !getPbc().isOrthorombic() ) error("cell box must be orthorhombic");
     202             : 
     203             :   // The nanoparticle is at the origin of our coordinate system
     204         822 :   pos1[0]=pos1[1]=pos1[2]=0.0; pos2[0]=pos2[1]=pos2[2]=0.0;
     205             : 
     206             :   // Set bracket as center of mass of membrane in active region
     207         137 :   deactivateAllTasks();
     208         548 :   Vector myvec = getSeparation( getPosition(getNumberOfAtoms()-1), getPosition(0) ); pos2[dir]=myvec[dir];
     209         137 :   taskFlags[0]=1; double mindist = myvec.modulo2();
     210         137 :   for(unsigned j=1; j<getNumberOfAtoms()-1; ++j) {
     211           0 :     Vector distance=getSeparation( getPosition(getNumberOfAtoms()-1), getPosition(j) );
     212             :     double d2;
     213           0 :     if( (d2=distance[perp_dirs[0]]*distance[perp_dirs[0]])<rcut2 &&
     214           0 :         (d2+=distance[perp_dirs[1]]*distance[perp_dirs[1]])<rcut2 ) {
     215           0 :       d2+=distance[dir]*distance[dir];
     216           0 :       if( d2<mindist && fabs(distance[dir])>epsilon ) { pos2[dir]=distance[dir]; mindist = d2; }
     217           0 :       taskFlags[j]=1;
     218             :     }
     219             :   }
     220         137 :   lockContributors(); derivTime=false;
     221             :   // pos1 position of the nanoparticle, in the first time
     222             :   // pos2 is the position of the closer atom in the membrane with respect the nanoparticle
     223             :   // fa = distance between pos1 and the contour
     224             :   // fb = distance between pos2 and the contour
     225         137 :   std::vector<double> faked(3);
     226         137 :   double fa = getDifferenceFromContour( pos1, faked );
     227         137 :   double fb = getDifferenceFromContour( pos2, faked );
     228         137 :   if( fa*fb>0 ) {
     229           0 :     unsigned maxtries = std::floor( ( getBox()(dir,dir) ) / bw[dir] );
     230           0 :     for(unsigned i=0; i<maxtries; ++i) {
     231           0 :       double sign=(pos2[dir]>0)? -1 : +1; // If the nanoparticle is inside the membrane push it out
     232           0 :       pos1[dir] += sign*bw[dir]; fa = getDifferenceFromContour( pos1, faked );
     233           0 :       if( fa*fb<0 ) break;
     234             :       // if fa*fb is less than zero the new pos 1 is outside the contour
     235             :     }
     236             :   }
     237             :   // Set direction for contour search
     238         548 :   dirv[dir] = pos2[dir] - pos1[dir];
     239             :   // Bracket for second root starts in center of membrane
     240         137 :   double fc = getDifferenceFromContour( pos2, faked );
     241         137 :   if( fc*fb>0 ) {
     242             :     // first time is true, because fc=fb
     243             :     // push pos2 from its initial position inside the membrane towards the second contourn
     244         274 :     unsigned maxtries = std::floor( ( getBox()(dir,dir) ) / bw[dir] );
     245         323 :     for(unsigned i=0; i<maxtries; ++i) {
     246         460 :       double sign=(dirv[dir]>0)? +1 : -1;
     247         460 :       pos2[dir] += sign*bw[dir]; fc = getDifferenceFromContour( pos2, faked );
     248         230 :       if( fc*fb<0 ) break;
     249             :     }
     250         685 :     dirv2[dir] = ( pos1[dir] + dirv[dir] ) - pos2[dir];
     251             :   }
     252             : 
     253             :   // Now do a search for the two contours
     254         137 :   mymin.lsearch( dirv, pos1, &DistanceFromContour::getDifferenceFromContour );
     255             :   // Save the first value
     256         411 :   Vector root1; root1.zero(); root1[dir] = pval[dir]->get();
     257         137 :   mymin.lsearch( dirv2, pos2, &DistanceFromContour::getDifferenceFromContour );
     258             :   // Calculate the separation between the two roots using PBC
     259         411 :   Vector root2; root2.zero(); root2[dir]=pval[dir]->get();
     260         137 :   Vector sep = getSeparation( root1, root2 ); double spacing = fabs( sep[dir] ); plumed_assert( spacing>epsilon );
     261         274 :   getPntrToComponent("thickness")->set( spacing );
     262             : 
     263             :   // Make sure the sign is right
     264         137 :   double predir=(root1[dir]*root2[dir]<0)? -1 : 1;
     265             :   // This deals with periodic boundary conditions - if we are inside the membrane the sum of the absolute
     266             :   // distances from the contours should add up to the spacing.  When this is not the case we must be outside
     267             :   // the contour
     268             :   // if( predir==-1 && (fabs(root1[dir])+fabs(root2[dir]))>(spacing+pbc_param) ) predir=1;
     269             :   // Set the final value to root that is closest to the "origin" = position of atom
     270         137 :   if( fabs(root1[dir])<fabs(root2[dir]) ) {
     271         274 :     getPntrToComponent("dist1")->set( predir*fabs(root1[dir]) );
     272         274 :     getPntrToComponent("dist2")->set( fabs(root2[dir]) );
     273             :   } else {
     274           0 :     getPntrToComponent("dist1")->set( predir*fabs(root2[dir]) );
     275           0 :     getPntrToComponent("dist2")->set( fabs(root1[dir]) );
     276             :   }
     277         274 :   getPntrToComponent("qdist")->set( root2[dir]*root1[dir] );
     278             : 
     279             :   // Now calculate the derivatives
     280         137 :   if( !doNotCalculateDerivatives() ) {
     281         137 :     Value* ival=myvalue_vessel->getFinalValue(); ival->clearDerivatives();
     282         548 :     std::vector<double> root1v(3); for(unsigned i=0; i<3; ++i) root1v[i]=root1[i];
     283         137 :     derivTime=true; std::vector<double> der(3); getDifferenceFromContour( root1v, der ); double prefactor;
     284         274 :     if( mybasemulticolvars[0]->isDensity() ) prefactor = root2[dir] / myderiv_vessel->getOutputValue();
     285           0 :     else plumed_error();
     286         411 :     Value* val=getPntrToComponent("qdist");
     287        6302 :     for(unsigned i=0; i<val->getNumberOfDerivatives(); ++i) val->setDerivative( i, -prefactor*ival->getDerivative(i) );
     288             :     ival->clearDerivatives();
     289         548 :     std::vector<double> root2v(3); for(unsigned i=0; i<3; ++i) root2v[i]=root2[i];
     290         137 :     getDifferenceFromContour( root2v, der );
     291         274 :     if( mybasemulticolvars[0]->isDensity() ) prefactor = root1[dir] / myderiv_vessel->getOutputValue();
     292           0 :     else plumed_error();
     293        6302 :     for(unsigned i=0; i<val->getNumberOfDerivatives(); ++i) val->addDerivative( i, -prefactor*ival->getDerivative(i) );
     294             :   }
     295         137 : }
     296             : 
     297        3115 : double DistanceFromContour::getDifferenceFromContour( const std::vector<double>& x, std::vector<double>& der ) {
     298             :   std::string min, max;
     299       21805 :   for(unsigned j=0; j<3; ++j) {
     300        9345 :     Tools::convert( -0.5*getBox()(j,j), min );
     301        9345 :     Tools::convert( +0.5*getBox()(j,j), max );
     302       37380 :     pval[j]->setDomain( min, max ); pval[j]->set( x[j] );
     303             :   }
     304        3115 :   runAllTasks();
     305        9345 :   return myvalue_vessel->getOutputValue() - contour;
     306             : }
     307             : 
     308        3115 : double DistanceFromContour::compute( const unsigned& tindex, AtomValuePack& myatoms ) const {
     309        9345 :   Vector distance = getSeparation( getPosition(getNumberOfAtoms()-1), myatoms.getPosition(0) );
     310       12460 :   std::vector<double> pp(3), der(3,0); for(unsigned j=0; j<3; ++j) pp[j] = distance[j];
     311             : 
     312             :   // Now create the kernel and evaluate
     313        6230 :   KernelFunctions kernel( pp, bw, kerneltype, false, 1.0, true );
     314        3115 :   double newval = kernel.evaluate( pval, der, true );
     315             : 
     316        3115 :   if( mybasemulticolvars[0]->isDensity() ) {
     317        3115 :     if( !doNotCalculateDerivatives() && derivTime ) {
     318             :       MultiValue& myvals=myatoms.getUnderlyingMultiValue();
     319         548 :       Vector vder; unsigned basen=myvals.getNumberOfDerivatives() - 12;
     320        1918 :       for(unsigned i=0; i<3; ++i) {
     321        1644 :         vder[i]=der[i]; myvals.addDerivative( 1, basen+i, vder[i] );
     322             :       }
     323         274 :       myatoms.setValue( 2, der[dir] );
     324         274 :       addAtomDerivatives( 1, 0, -vder, myatoms );
     325         274 :       myatoms.addBoxDerivatives( 1, Tensor(vder,distance) );
     326             :     }
     327        3115 :     myatoms.setValue( 0, 1.0 ); return newval;
     328             :   }
     329             : 
     330             :   // This does the stuff for averaging
     331             :   myatoms.setValue( 0, newval );
     332             : 
     333             :   // This gets the average if we are using a phase field
     334           0 :   std::vector<double> cvals( mybasemulticolvars[0]->getNumberOfQuantities() );
     335           0 :   mybasedata[0]->retrieveValueWithIndex( tindex, false, cvals );
     336           0 :   return newval*cvals[0]*cvals[1];
     337             : }
     338             : 
     339         137 : void DistanceFromContour::apply() {
     340         274 :   if( getPntrToComponent("qdist")->applyForce( forces ) ) setForcesOnAtoms( forces );
     341         137 : }
     342             : 
     343             : }
     344        4839 : }

Generated by: LCOV version 1.13