LCOV - code coverage report
Current view: top level - adjmat - HbondMatrix.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 69 71 97.2 %
Date: 2024-10-11 08:09:47 Functions: 9 10 90.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2015-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 "AdjacencyMatrixBase.h"
      23             : #include "multicolvar/AtomValuePack.h"
      24             : #include "core/ActionRegister.h"
      25             : #include "tools/SwitchingFunction.h"
      26             : #include "tools/HistogramBead.h"
      27             : #include "tools/Angle.h"
      28             : #include "tools/Matrix.h"
      29             : 
      30             : //+PLUMEDOC MATRIX HBOND_MATRIX
      31             : /*
      32             : Adjacency matrix in which two atoms are adjacent if there is a hydrogen bond between them.
      33             : 
      34             : As discussed in the section of the manual on \ref contactmatrix a useful tool for developing complex collective variables is the notion of the
      35             : so called adjacency matrix.  An adjacency matrix is an \f$N \times N\f$ matrix in which the \f$i\f$th, \f$j\f$th element tells you whether
      36             : or not the \f$i\f$th and \f$j\f$th atoms/molecules from a set of \f$N\f$ atoms/molecules are adjacent or not.  These matrices can then be further
      37             : analyzed using a number of other algorithms as is detailed in \cite tribello-clustering.
      38             : 
      39             : For this action the elements of the adjacency matrix are calculated using:
      40             : 
      41             : \f[
      42             : a_{ij} = \sigma_{oo}( |\mathbf{r}_{ij}| ) \sum_{k=1}^N \sigma_{oh}( |\mathbf{r}_{ik}| ) \sigma_{\theta}( \theta_{kij} )
      43             : \f]
      44             : 
      45             : This expression was derived by thinking about how to detect if there is a hydrogen bond between atoms \f$i\f$ and \f$j\f$.  The notion is that
      46             : if the hydrogen bond is present atoms \f$i\f$ and \f$j\f$ should be within a certain cutoff distance.  In addition, there should be a hydrogen
      47             : within a certain cutoff distance of atom \f$i\f$ and this hydrogen should lie on or close to the vector connecting atoms \f$i\f$ and \f$j\f$.
      48             : As such \f$\sigma_{oo}( |\mathbf{r}_{ij}| )\f$ is a \ref switchingfunction that acts on the modulus of the vector connecting atom \f$i\f$ to atom
      49             : \f$j\f$.  The sum over \f$k\f$ then runs over all the hydrogen atoms that are specified using using HYDROGEN keyword.  \f$\sigma_{oh}(|\mathbf{r}_{ik}|)\f$
      50             : is a \ref switchingfunction that acts on the modulus of the vector connecting atom \f$i\f$ to atom \f$k\f$ and \f$\sigma_{\theta}(\theta_{kij})\f$
      51             : is a \ref switchingfunction that acts on the angle between the vector connecting atoms \f$i\f$ and \f$j\f$ and the vector connecting atoms \f$i\f$ and
      52             : \f$k\f$.
      53             : 
      54             : It is important to note that hydrogen bonds, unlike regular bonds, are asymmetric. In other words, the hydrogen atom does not sit at the
      55             : mid point between the two other atoms in this three-center bond.  As a result of this adjacency matrices calculated using \ref HBOND_MATRIX are not
      56             : symmetric like those calculated by \ref CONTACT_MATRIX.  One consequence of this fact is that the quantities found by performing \ref ROWSUMS and
      57             : \ref COLUMNSUMS on a square \ref HBOND_MATRIX are not the same as they would be if you performed \ref ROWSUMS and
      58             : \ref COLUMNSUMS on a square \ref CONTACT_MATRIX.
      59             : 
      60             : \par Examples
      61             : 
      62             : The following input can be used to analyze the number of hydrogen bonds each of the oxygen atoms in a box of water participates in.  Each
      63             : water molecule can participate in a hydrogen bond in one of two ways.  It can either donate one of its hydrogen atom to the neighboring oxygen or
      64             : it can accept a bond between the hydrogen of a neighboring water molecule and its own oxygen.  The input below allows you to output information
      65             : on the number of hydrogen bonds each of the water molecules donates and accepts.  This information is output in two xyz files which each contain
      66             : five columns of data.  The first four of these columns are a label for the atom and the x, y and z position of the oxygen.  The last column is then
      67             : the number of accepted/donated hydrogen bonds.
      68             : 
      69             : \plumedfile
      70             : mat: HBOND_MATRIX ATOMS=1-192:3 HYDROGENS=2-192:3,3-192:3 SWITCH={RATIONAL R_0=3.20} HSWITCH={RATIONAL R_0=2.30} ASWITCH={RATIONAL R_0=0.167pi} SUM
      71             : rsums: ROWSUMS MATRIX=mat MEAN
      72             : csums: COLUMNSUMS MATRIX=mat MEAN
      73             : DUMPMULTICOLVAR DATA=rsums FILE=donors.xyz
      74             : DUMPMULTICOLVAR DATA=csums FILE=acceptors.xyz
      75             : \endplumedfile
      76             : 
      77             : */
      78             : //+ENDPLUMEDOC
      79             : 
      80             : 
      81             : namespace PLMD {
      82             : namespace adjmat {
      83             : 
      84             : class HBondMatrix : public AdjacencyMatrixBase {
      85             : private:
      86             :   unsigned ndonor_types;
      87             : /// switching function
      88             :   Matrix<SwitchingFunction> distanceOOSwitch;
      89             :   Matrix<SwitchingFunction> distanceOHSwitch;
      90             :   Matrix<SwitchingFunction> angleSwitch;
      91             : public:
      92             : /// Create manual
      93             :   static void registerKeywords( Keywords& keys );
      94             : /// Constructor
      95             :   explicit HBondMatrix(const ActionOptions&);
      96             : /// Create the ith, ith switching function
      97             :   void setupConnector( const unsigned& id, const unsigned& i, const unsigned& j, const std::vector<std::string>& desc ) override;
      98             : /// This actually calculates the value of the contact function
      99             :   double calculateWeight( const unsigned& taskCode, const double& weight, multicolvar::AtomValuePack& myatoms ) const override;
     100             : /// This does nothing
     101             :   double compute( const unsigned& tindex, multicolvar::AtomValuePack& myatoms ) const override;
     102             : ///
     103             :   double calculateForThree( const unsigned& iat, const unsigned& ano, const unsigned& dno, const Vector& ood,
     104             :                             const double& ood_df, const double& ood_sw, multicolvar::AtomValuePack& myatoms ) const;
     105             : };
     106             : 
     107       10423 : PLUMED_REGISTER_ACTION(HBondMatrix,"HBOND_MATRIX")
     108             : 
     109           3 : void HBondMatrix::registerKeywords( Keywords& keys ) {
     110           3 :   AdjacencyMatrixBase::registerKeywords( keys );
     111           6 :   keys.add("atoms","ATOMS","The list of atoms which can be part of a hydrogen bond.  When this command is used the set of atoms that can donate a "
     112             :            "hydrogen bond is assumed to be the same as the set of atoms that can form hydrogen bonds.  The atoms involved must be specified "
     113             :            "as a list of labels of \\ref mcolv or labels of a \\ref multicolvarfunction actions.  If you would just like to use "
     114             :            "the atomic positions you can use a \\ref DENSITY command to specify a group of atoms.  Specifying your atomic positions using labels of "
     115             :            "other \\ref mcolv or \\ref multicolvarfunction commands is useful, however, as you can then exploit a much wider "
     116             :            "variety of functions of the contact matrix as described in \\ref contactmatrix");
     117           6 :   keys.add("atoms","HYDROGENS","The list of hydrogen atoms that can form part of a hydrogen bond.  The atoms must be specified using a comma separated list, "
     118             :            "an index range or by using a \\ref GROUP.  A list of hydrogen atoms is always required even if you specify the other atoms using "
     119             :            "DONORS and ACCEPTORS as described below.");
     120           6 :   keys.add("atoms-2","DONORS","The list of atoms which can donate a hydrogen bond.  The atoms involved must be specified "
     121             :            "as a list of labels of \\ref mcolv or labels of a \\ref multicolvarfunction actions.  If you would just like to use "
     122             :            "the atomic positions you can use a \\ref DENSITY command to specify a group of atoms.  Specifying your atomic positions using labels of "
     123             :            "other \\ref mcolv or \\ref multicolvarfunction commands is useful, however, as you can then exploit a much wider "
     124             :            "variety of functions of the contact matrix as described in \\ref contactmatrix");
     125           6 :   keys.add("atoms-2","ACCEPTORS","The list of atoms which can accept a hydrogen bond.  The atoms involved must be specified "
     126             :            "as a list of labels of \\ref mcolv or labels of a \\ref multicolvarfunction actions.  If you would just like to use "
     127             :            "the atomic positions you can use a \\ref DENSITY command to specify a group of atoms.  Specifying your atomic positions using labels of "
     128             :            "other \\ref mcolv or \\ref multicolvarfunction commands is useful, however, as you can then exploit a much wider "
     129             :            "variety of functions of the contact matrix as described in \\ref contactmatrix");
     130           6 :   keys.add("numbered","SWITCH","The \\ref switchingfunction that specifies how close a pair of atoms must be together for there to be a hydrogen bond between them");
     131           6 :   keys.add("numbered","HSWITCH","The \\ref switchingfunction that specifies how close the hydrogen must be to the donor atom of the hydrogen bond for it to be "
     132             :            "considered a hydrogen bond");
     133           6 :   keys.add("numbered","ASWITCH","A \\ref switchingfunction that is used to specify what the angle between the vector connecting the donor atom to the acceptor atom and "
     134             :            "the vector connecting the donor atom to the hydrogen must be in order for it considered to be a hydrogen bond");
     135           3 :   keys.use("SUM");
     136           3 : }
     137             : 
     138           2 : HBondMatrix::HBondMatrix( const ActionOptions& ao ):
     139             :   Action(ao),
     140           2 :   AdjacencyMatrixBase(ao)
     141             : {
     142           4 :   readMaxThreeSpeciesMatrix( "ATOMS", "DONORS", "ACCEPTORS", "HYDROGENS", false );
     143           2 :   unsigned nrows, ncols; retrieveTypeDimensions( nrows, ncols, ndonor_types );
     144           2 :   distanceOOSwitch.resize( nrows, ncols ); distanceOHSwitch.resize( nrows, ncols ); angleSwitch.resize( nrows, ncols );
     145           2 :   parseConnectionDescriptions("SWITCH",false,ndonor_types);
     146           2 :   parseConnectionDescriptions("HSWITCH",false,ndonor_types);
     147           4 :   parseConnectionDescriptions("ASWITCH",false,ndonor_types);
     148             : 
     149             :   // Find the largest sf cutoff
     150           2 :   double sfmax=distanceOOSwitch(0,0).get_dmax();
     151           4 :   for(unsigned i=0; i<getNumberOfNodeTypes(); ++i) {
     152           4 :     for(unsigned j=0; j<getNumberOfNodeTypes(); ++j) {
     153           2 :       double tsf=distanceOOSwitch(i,j).get_dmax();
     154           2 :       if( tsf>sfmax ) sfmax=tsf;
     155             :     }
     156             :   }
     157             :   // Set the link cell cutoff
     158           2 :   setLinkCellCutoff( sfmax );
     159           2 : }
     160             : 
     161           6 : void HBondMatrix::setupConnector( const unsigned& id, const unsigned& i, const unsigned& j, const std::vector<std::string>& desc ) {
     162           6 :   plumed_assert( id<3 && desc.size()==1 );
     163           6 :   if( id==0 ) {
     164           2 :     std::string errors; distanceOOSwitch(j,i).set(desc[0],errors);
     165           2 :     if( errors.length()!=0 ) error("problem reading switching function description " + errors);
     166           2 :     if( j!=i) distanceOOSwitch(i,j).set(desc[0],errors);
     167           4 :     log.printf("  atoms of type %u and %u must be within %s\n",i+1,j+1,(distanceOOSwitch(i,j).description()).c_str() );
     168           4 :   } else if( id==1 ) {
     169           2 :     std::string errors; distanceOHSwitch(j,i).set(desc[0],errors);
     170           2 :     if( errors.length()!=0 ) error("problem reading switching function description " + errors);
     171           2 :     if( j!=i) distanceOHSwitch(i,j).set(desc[0],errors);
     172           4 :     log.printf("  for atoms of type %u and %u the OH distance must be less than %s \n",i+1,j+1,(distanceOHSwitch(i,j).description()).c_str() );
     173           2 :   } else if( id==2 ) {
     174           2 :     std::string errors; angleSwitch(j,i).set(desc[0],errors);
     175           2 :     if( errors.length()!=0 ) error("problem reading switching function description " + errors);
     176           2 :     if( j!=i) angleSwitch(i,j).set(desc[0],errors);
     177           4 :     log.printf("  for atoms of type %u and %u the OOH angle must be less than %s \n",i+1,j+1,(angleSwitch(i,j).description()).c_str() );
     178             :   }
     179           6 : }
     180             : 
     181        4038 : double HBondMatrix::calculateWeight( const unsigned& taskCode, const double& weight, multicolvar::AtomValuePack& myatoms ) const {
     182             :   // Ensure we skip diagonal elements of square matrix
     183        4038 :   if( myatoms.getIndex(0)==myatoms.getIndex(1) ) return 0.0;
     184             : 
     185        4038 :   Vector distance = getSeparation( myatoms.getPosition(0), myatoms.getPosition(1) );
     186        8076 :   if( distance.modulo2()<distanceOOSwitch( getBaseColvarNumber( myatoms.getIndex(0) ), getBaseColvarNumber( myatoms.getIndex(1) ) ).get_dmax2() ) return 1.0;
     187             :   return 0.0;
     188             : }
     189             : 
     190        4038 : double HBondMatrix::compute( const unsigned& tindex, multicolvar::AtomValuePack& myatoms ) const {
     191        4038 :   Vector ood = getSeparation( myatoms.getPosition(0), myatoms.getPosition(1) ); double ood_l = ood.modulo(); // acceptor - donor
     192             :   double ood_df, ood_sw=distanceOOSwitch( getBaseColvarNumber( myatoms.getIndex(0) ),
     193        4038 :                                           getBaseColvarNumber( myatoms.getIndex(1) ) ).calculate( ood_l, ood_df );
     194             : 
     195             :   // Get the base colvar numbers
     196        4038 :   unsigned ano, dno = getBaseColvarNumber( myatoms.getIndex(0) );
     197        8076 :   if( ndonor_types==0 ) ano = getBaseColvarNumber( myatoms.getIndex(1) );
     198           0 :   else ano = getBaseColvarNumber( myatoms.getIndex(1) ) - ndonor_types;
     199             : 
     200             :   double value=0;
     201        4038 :   if( myatoms.getNumberOfAtoms()>3 ) {
     202      520170 :     for(unsigned i=2; i<myatoms.getNumberOfAtoms(); ++i) value+=calculateForThree( i, ano, dno, ood, ood_df, ood_sw,  myatoms );
     203             :   } else {
     204             :     plumed_dbg_assert( myatoms.getNumberOfAtoms()==3 );
     205           0 :     value=calculateForThree( 2, ano, dno, ood, ood_df, ood_sw, myatoms );
     206             :   }
     207        4038 :   return value;
     208             : }
     209             : 
     210      516132 : double HBondMatrix::calculateForThree( const unsigned& iat, const unsigned& ano, const unsigned& dno, const Vector& ood,
     211             :                                        const double& ood_df, const double& ood_sw, multicolvar::AtomValuePack& myatoms ) const {
     212      516132 :   Vector ohd=getSeparation( myatoms.getPosition(0), myatoms.getPosition(iat) ); double ohd_l=ohd.modulo();
     213             :   double ohd_df, ohd_sw=distanceOHSwitch( getBaseColvarNumber( myatoms.getIndex(0) ),
     214      516132 :                                           getBaseColvarNumber( myatoms.getIndex(1) ) ).calculate( ohd_l, ohd_df );
     215             : 
     216      516132 :   Angle a; Vector ood_adf, ohd_adf; double angle=a.compute( ood, ohd, ood_adf, ohd_adf );
     217             :   double angle_df, angle_sw=angleSwitch( getBaseColvarNumber( myatoms.getIndex(0) ),
     218      516132 :                                          getBaseColvarNumber( myatoms.getIndex(1) ) ).calculate( angle, angle_df );
     219             : 
     220      516132 :   if( !doNotCalculateDerivatives() ) {
     221          36 :     addAtomDerivatives( 1, 0, angle_sw*ohd_sw*(-ood_df)*ood + angle_sw*ood_sw*(-ohd_df)*ohd + ood_sw*ohd_sw*angle_df*angle*(-ood_adf-ohd_adf), myatoms );
     222          36 :     addAtomDerivatives( 1, 1, angle_sw*ohd_sw*(+ood_df)*ood + ood_sw*ohd_sw*angle_df*angle*ood_adf, myatoms );
     223          36 :     addAtomDerivatives( 1, iat, angle_sw*ood_sw*(+ohd_df)*ohd + ood_sw*ohd_sw*angle_df*angle*ohd_adf, myatoms );
     224          72 :     myatoms.addBoxDerivatives( 1, angle_sw*ohd_sw*(-ood_df)*Tensor(ood,ood) + angle_sw*ood_sw*(-ohd_df)*Tensor(ohd,ohd)
     225         108 :                                -ood_sw*ohd_sw*angle_df*angle*(Tensor(ood,ood_adf)+Tensor(ohd,ohd_adf)) );
     226             :   }
     227      516132 :   return ood_sw*ohd_sw*angle_sw;
     228             : }
     229             : 
     230             : }
     231             : }
     232             : 

Generated by: LCOV version 1.15