1 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2    Copyright (c) 2013-2021 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/SwitchingFunction.h"
26 
27 #include <string>
28 #include <cmath>
29 
30 //+PLUMEDOC MCOLVARF NLINKS
31 /*
32 Calculate number of pairs of atoms/molecules that are "linked"
33 
34 In its simplest guise this coordinate calculates a coordination number.  Each pair
35 of atoms is assumed "linked" if they are within some cutoff of each other.  In more
36 complex applications each entity is a vector and this quantity measures whether
37 pairs of vectors are (a) within a certain cutoff and (b) if the two vectors have
38 similar orientations.  The vectors on individual atoms could be Steinhardt parameters
39 (see \ref Q3, \ref Q4 and \ref Q6) or they could describe some internal vector in a molecule.
40 
41 \par Examples
42 
43 The following calculates how many bonds there are in a system containing 64 atoms and outputs
44 this quantity to a file.
45 
46 \plumedfile
47 DENSITY SPECIES=1-64 LABEL=d1
48 NLINKS GROUP=d1 SWITCH={RATIONAL D_0=1.3 R_0=0.2} LABEL=dd
49 PRINT ARG=dd FILE=colvar
50 \endplumedfile
51 
52 The following calculates how many pairs of neighboring atoms in a system containing 64 atoms have
53 similar dispositions for the atoms in their coordination sphere.  This calculation uses the
54 dot product of the Q6 vectors on adjacent atoms to measure whether or not two atoms have the same
55 ``orientation"
56 
57 \plumedfile
58 Q6 SPECIES=1-64 SWITCH={RATIONAL D_0=1.3 R_0=0.2} LABEL=q6
59 NLINKS GROUP=q6 SWITCH={RATIONAL D_0=1.3 R_0=0.2} LABEL=dd
60 PRINT ARG=dd FILE=colvar
61 \endplumedfile
62 
63 */
64 //+ENDPLUMEDOC
65 
66 namespace PLMD {
67 namespace multicolvar {
68 
69 class NumberOfLinks : public MultiColvarBase {
70 private:
71 /// The values of the quantities in the dot products
72   std::vector<double> orient0, orient1;
73 /// The switching function that tells us if atoms are close enough together
74   SwitchingFunction switchingFunction;
75 public:
76   static void registerKeywords( Keywords& keys );
77   explicit NumberOfLinks(const ActionOptions&);
78 /// Do the stuff with the switching functions
79   double calculateWeight( const unsigned& taskCode, const double& weight, AtomValuePack& myatoms ) const override;
80 /// Actually do the calculation
81   double compute( const unsigned& tindex, AtomValuePack& myatoms ) const override;
82 /// Is the variable periodic
isPeriodic()83   bool isPeriodic() override { return false; }
84 };
85 
86 PLUMED_REGISTER_ACTION(NumberOfLinks,"NLINKS")
87 
registerKeywords(Keywords & keys)88 void NumberOfLinks::registerKeywords( Keywords& keys ) {
89   MultiColvarBase::registerKeywords( keys );
90   keys.add("atoms","GROUP","");
91   keys.add("atoms-1","GROUPA","");
92   keys.add("atoms-1","GROUPB","");
93   keys.add("compulsory","NN","6","The n parameter of the switching function ");
94   keys.add("compulsory","MM","0","The m parameter of the switching function; 0 implies 2*NN");
95   keys.add("compulsory","D_0","0.0","The d_0 parameter of the switching function");
96   keys.add("compulsory","R_0","The r_0 parameter of the switching function");
97   keys.add("optional","SWITCH","This keyword is used if you want to employ an alternative to the continuous switching function defined above. "
98            "The following provides information on the \\ref switchingfunction that are available. "
99            "When this keyword is present you no longer need the NN, MM, D_0 and R_0 keywords.");
100   // Use actionWithDistributionKeywords
101   keys.remove("LOWMEM");
102   keys.addFlag("LOWMEM",false,"lower the memory requirements");
103 }
104 
NumberOfLinks(const ActionOptions & ao)105 NumberOfLinks::NumberOfLinks(const ActionOptions& ao):
106   Action(ao),
107   MultiColvarBase(ao)
108 {
109   // The weight of this does have derivatives
110   weightHasDerivatives=true;
111 
112   // Read in the switching function
113   std::string sw, errors; parse("SWITCH",sw);
114   if(sw.length()>0) {
115     switchingFunction.set(sw,errors);
116   } else {
117     double r_0=-1.0, d_0; int nn, mm;
118     parse("NN",nn); parse("MM",mm);
119     parse("R_0",r_0); parse("D_0",d_0);
120     if( r_0<0.0 ) error("you must set a value for R_0");
121     switchingFunction.set(nn,mm,r_0,d_0);
122   }
123   log.printf("  calculating number of links with atoms separation of %s\n",( switchingFunction.description() ).c_str() );
124   std::vector<AtomNumber> all_atoms; readTwoGroups( "GROUP", "GROUPA", "GROUPB", all_atoms );
125   setupMultiColvarBase( all_atoms ); setLinkCellCutoff( switchingFunction.get_dmax() );
126 
127   for(unsigned i=0; i<getNumberOfBaseMultiColvars(); ++i) {
128     if( !getBaseMultiColvar(i)->hasDifferentiableOrientation() ) error("cannot use multicolvar of type " + getBaseMultiColvar(i)->getName() );
129   }
130 
131   // Create holders for the collective variable
132   readVesselKeywords();
133   plumed_assert( getNumberOfVessels()==0 );
134   std::string input; addVessel( "SUM", input, -1 );
135   readVesselKeywords();
136 }
137 
calculateWeight(const unsigned & taskCode,const double & weight,AtomValuePack & myatoms) const138 double NumberOfLinks::calculateWeight( const unsigned& taskCode, const double& weight, AtomValuePack& myatoms ) const {
139   Vector distance = getSeparation( myatoms.getPosition(0), myatoms.getPosition(1) );
140   double dfunc, sw = switchingFunction.calculateSqr( distance.modulo2(), dfunc );
141 
142   if( !doNotCalculateDerivatives() ) {
143     addAtomDerivatives( 0, 0, (-dfunc)*weight*distance, myatoms );
144     addAtomDerivatives( 0, 1, (dfunc)*weight*distance, myatoms );
145     myatoms.addBoxDerivatives( 0, (-dfunc)*weight*Tensor(distance,distance) );
146   }
147   return sw;
148 }
149 
compute(const unsigned & tindex,AtomValuePack & myatoms) const150 double NumberOfLinks::compute( const unsigned& tindex, AtomValuePack& myatoms ) const {
151   if( getBaseMultiColvar(0)->getNumberOfQuantities()<3 ) return 1.0;
152 
153   unsigned ncomp=getBaseMultiColvar(0)->getNumberOfQuantities();
154 
155   std::vector<double> orient0( ncomp ), orient1( ncomp );
156   getInputData( 0, true, myatoms, orient0 );
157   getInputData( 1, true, myatoms, orient1 );
158 
159   double dot=0;
160   for(unsigned k=2; k<orient0.size(); ++k) {
161     dot+=orient0[k]*orient1[k];
162   }
163 
164   if( !doNotCalculateDerivatives() ) {
165     MultiValue& myder0=getInputDerivatives( 0, true, myatoms );
166     mergeInputDerivatives( 1, 2, orient1.size(), 0, orient1, myder0, myatoms );
167     MultiValue& myder1=getInputDerivatives( 1, true, myatoms );
168     mergeInputDerivatives( 1, 2, orient0.size(), 1, orient0, myder1, myatoms );
169   }
170 
171   return dot;
172 }
173 
174 }
175 }
176