LCOV - code coverage report
Current view: top level - adjmat - TopologyMatrix.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 156 156 100.0 %
Date: 2024-10-11 08:09:47 Functions: 10 11 90.9 %

          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/Matrix.h"
      28             : 
      29             : //+PLUMEDOC MATRIX TOPOLOGY_MATRIX
      30             : /*
      31             : Adjacency matrix in which two atoms are adjacent if they are connected topologically
      32             : 
      33             : \par Examples
      34             : 
      35             : 
      36             : */
      37             : //+ENDPLUMEDOC
      38             : 
      39             : namespace PLMD {
      40             : namespace adjmat {
      41             : 
      42             : class TopologyMatrix : public AdjacencyMatrixBase {
      43             : private:
      44             : /// The width to use for the kernel density estimation and the
      45             : /// sizes of the bins to be used in kernel density estimation
      46             :   double sigma;
      47             :   std::string kerneltype;
      48             : /// The maximum number of bins that will be used
      49             : /// This is calculated based on the dmax of the switching functions
      50             :   unsigned maxbins;
      51             : /// The volume of the cells
      52             :   Matrix<double> cell_volume;
      53             : /// switching function
      54             :   Matrix<SwitchingFunction> switchingFunction;
      55             :   Matrix<SwitchingFunction> cylinder_sw;
      56             :   Matrix<SwitchingFunction> low_sf;
      57             :   double beadrad, lsfmax;
      58             :   Matrix<double> binw_mat;
      59             :   SwitchingFunction threshold_switch;
      60             : public:
      61             : /// Create manual
      62             :   static void registerKeywords( Keywords& keys );
      63             : /// Constructor
      64             :   explicit TopologyMatrix(const ActionOptions&);
      65             : /// Get the number of quantities that we must compute
      66             :   unsigned getNumberOfQuantities() const override;
      67             : /// Create the ith, ith switching function
      68             :   void setupConnector( const unsigned& id, const unsigned& i, const unsigned& j, const std::vector<std::string>& desc ) override;
      69             : /// This actually calculates the value of the contact function
      70             :   double calculateWeight( const unsigned& taskCode, const double& weight, multicolvar::AtomValuePack& myatoms ) const override;
      71             : /// This does nothing
      72             :   double compute( const unsigned& tindex, multicolvar::AtomValuePack& myatoms ) const override;
      73             : /// Calculate the contribution from one of the atoms in the third element of the pack
      74             :   void calculateForThreeAtoms( const unsigned& iat, const Vector& d1, const double& d1_len,
      75             :                                HistogramBead& bead, multicolvar::AtomValuePack& myatoms ) const ;
      76             : };
      77             : 
      78       10431 : PLUMED_REGISTER_ACTION(TopologyMatrix,"TOPOLOGY_MATRIX")
      79             : 
      80           7 : void TopologyMatrix::registerKeywords( Keywords& keys ) {
      81           7 :   AdjacencyMatrixBase::registerKeywords( keys );
      82          14 :   keys.add("atoms","NODES","The list of atoms for which you would like to calculate the contact matrix.  The atoms involved must be specified "
      83             :            "as a list of labels of \\ref mcolv or labels of a \\ref multicolvarfunction actions.  If you would just like to use "
      84             :            "the atomic positions you can use a \\ref DENSITY command to specify a group of atoms.  Specifying your atomic positions using labels of "
      85             :            "other \\ref mcolv or \\ref multicolvarfunction commands is useful, however, as you can then exploit a much wider "
      86             :            "variety of functions of the contact matrix as described in \\ref contactmatrix");
      87          14 :   keys.add("atoms","ATOMS","");
      88          14 :   keys.add("numbered","SWITCH","This keyword is used if you want to employ an alternative to the continuous switching function defined above. "
      89             :            "The following provides information on the \\ref switchingfunction that are available.");
      90          14 :   keys.add("numbered","RADIUS","");
      91          14 :   keys.add("numbered","CYLINDER_SWITCH","a switching function on \\f$(r_{ij}\\cdot r_{ik}-1)/r_{ij}\\f$");
      92          14 :   keys.add("numbered","BIN_SIZE","the size to use for the bins");
      93          21 :   keys.add("compulsory","DENSITY_THRESHOLD","");
      94          14 :   keys.add("compulsory","SIGMA","the width of the function to be used for kernel density estimation");
      95          14 :   keys.add("compulsory","KERNEL","gaussian","the type of kernel function to be used");
      96          14 :   keys.add("hidden","FAKE","");
      97           7 : }
      98             : 
      99           6 : TopologyMatrix::TopologyMatrix( const ActionOptions& ao ):
     100             :   Action(ao),
     101             :   AdjacencyMatrixBase(ao),
     102          12 :   maxbins(0)
     103             : {
     104          12 :   readMaxThreeSpeciesMatrix("NODES", "FAKE", "FAKE", "ATOMS", true );
     105           6 :   unsigned nrows, ncols, ndonor_types; retrieveTypeDimensions( nrows, ncols, ndonor_types );
     106           6 :   switchingFunction.resize( nrows, ncols ); parseConnectionDescriptions("SWITCH",false,ndonor_types);
     107           6 :   cylinder_sw.resize( nrows, ncols ); parseConnectionDescriptions("RADIUS",false,ndonor_types);
     108           6 :   low_sf.resize( nrows, ncols ); parseConnectionDescriptions("CYLINDER_SWITCH",false,ndonor_types);
     109           6 :   binw_mat.resize( nrows, ncols ); cell_volume.resize( nrows, ncols );
     110           6 :   parseConnectionDescriptions("BIN_SIZE",false,ndonor_types);
     111             :   // Read in stuff for grid
     112          18 :   parse("SIGMA",sigma); parse("KERNEL",kerneltype);
     113             :   // Read in threshold for density cutoff
     114           6 :   std::string errors, thresh_sw_str; parse("DENSITY_THRESHOLD",thresh_sw_str);
     115           6 :   threshold_switch.set(thresh_sw_str, errors );
     116           6 :   if( errors.length()>0 ) error("errors in DENSITY_THRESHOLD switching function : " + errors );
     117           6 :   log.printf("  threshold on density of atoms in cylinder equals %s\n",threshold_switch.description().c_str() );
     118             : 
     119          12 :   for(unsigned i=0; i<getNumberOfNodeTypes(); ++i) {
     120          12 :     for(unsigned j=0; j<getNumberOfNodeTypes(); ++j) {
     121           6 :       double r=cylinder_sw(i,j).get_d0() + cylinder_sw(i,j).get_r0();
     122           6 :       cell_volume(i,j)=binw_mat(i,j)*pi*r*r;
     123             :     }
     124             :   }
     125             : 
     126             :   // Find the largest sf cutoff
     127           6 :   lsfmax=low_sf(0,0).get_dmax();
     128           6 :   double sfmax=switchingFunction(0,0).get_dmax();
     129           6 :   double rfmax=cylinder_sw(0,0).get_dmax();
     130          12 :   for(unsigned i=0; i<getNumberOfNodeTypes(); ++i) {
     131          12 :     for(unsigned j=0; j<getNumberOfNodeTypes(); ++j) {
     132           6 :       double tsf=switchingFunction(i,j).get_dmax();
     133           6 :       if( tsf>sfmax ) sfmax=tsf;
     134           6 :       double rsf=cylinder_sw(i,j).get_dmax();
     135           6 :       if( rsf>rfmax ) rfmax=rsf;
     136           6 :       double lsf=low_sf(i,j).get_dmax();
     137           6 :       if( lsf>lsfmax ) lsfmax=lsf;
     138             :     }
     139             :   }
     140             :   // Get the width of the bead
     141           6 :   HistogramBead bead; bead.isNotPeriodic();
     142           6 :   bead.setKernelType( kerneltype ); bead.set( 0.0, 1.0, sigma );
     143           6 :   beadrad = bead.getCutoff();
     144             : 
     145             :   // Set the link cell cutoff
     146           6 :   log.printf("  setting cutoff %f \n", sfmax );
     147           6 :   setLinkCellCutoff( sfmax, std::numeric_limits<double>::max() );
     148             : 
     149             :   double maxsize=0;
     150          12 :   for(unsigned i=0; i<getNumberOfNodeTypes(); ++i) {
     151          12 :     for(unsigned j=0; j<getNumberOfNodeTypes(); ++j) {
     152           6 :       if( binw_mat(i,j)>maxsize ) maxsize=binw_mat(i,j);
     153             :     }
     154             :   }
     155             :   // Set the maximum number of bins that we will need to compute
     156           6 :   maxbins = std::floor( sfmax / maxsize ) + 1;
     157             :   // Need to resize functions again here to ensure that vector sizes
     158             :   // are set correctly in AdjacencyMatrixVessel
     159           6 :   resizeFunctions();
     160           6 : }
     161             : 
     162         100 : unsigned TopologyMatrix::getNumberOfQuantities() const {
     163         100 :   return maxbins+3;
     164             : }
     165             : 
     166          24 : void TopologyMatrix::setupConnector( const unsigned& id, const unsigned& i, const unsigned& j, const std::vector<std::string>& desc ) {
     167          24 :   plumed_assert( id<4 );
     168          24 :   if( id==0 ) {
     169           6 :     std::string errors; switchingFunction(j,i).set(desc[0],errors);
     170           6 :     if( errors.length()!=0 ) error("problem reading switching function description " + errors);
     171           6 :     if( j!=i) switchingFunction(i,j).set(desc[0],errors);
     172          12 :     log.printf("  %u th and %u th multicolvar groups must be within %s\n",i+1,j+1,(switchingFunction(i,j).description()).c_str() );
     173          18 :   } else if( id==1 ) {
     174           6 :     std::string errors; cylinder_sw(j,i).set(desc[0],errors);
     175           6 :     if( errors.length()!=0 ) error("problem reading switching function description " + errors);
     176           6 :     if( j!=i) cylinder_sw(i,j).set(desc[0],errors);
     177          12 :     log.printf("  there must be not atoms within the cylinder connections atoms of multicolvar groups %u th and %u th.  This cylinder has radius %s \n",i+1,j+1,(cylinder_sw(i,j).description()).c_str() );
     178          12 :   } else if( id==2 ) {
     179           6 :     std::string errors; low_sf(j,i).set(desc[0],errors);
     180           6 :     if( errors.length()!=0 ) error("problem reading switching function description " + errors);
     181           6 :     if( j!=i ) low_sf(i,j).set(desc[0],errors);
     182          12 :     log.printf("  %u th and %u th multicolvar groups must be further apart than %s\n",i+1,j+1,(low_sf(j,i).description()).c_str() );
     183           6 :   } else if( id==3 ) {
     184           6 :     Tools::convert( desc[0], binw_mat(j,i) );
     185           6 :     if( i!=j ) binw_mat(i,j)=binw_mat(j,i);
     186           6 :     log.printf("  cylinder for %u th and %u th multicolvar groups is split into bins of length %f \n",i,j,binw_mat(i,j) );
     187             :   }
     188          24 : }
     189             : 
     190       31203 : double TopologyMatrix::calculateWeight( const unsigned& taskCode, const double& weight, multicolvar::AtomValuePack& myatoms ) const {
     191       31203 :   Vector distance = getSeparation( myatoms.getPosition(0), myatoms.getPosition(1) );
     192       62406 :   if( distance.modulo2()<switchingFunction( getBaseColvarNumber( myatoms.getIndex(0) ), getBaseColvarNumber( myatoms.getIndex(1) ) ).get_dmax2() ) return 1.0;
     193             :   return 0.0;
     194             : }
     195             : 
     196        9616 : double TopologyMatrix::compute( const unsigned& tindex, multicolvar::AtomValuePack& myatoms ) const {
     197        9616 :   HistogramBead bead; bead.isNotPeriodic(); bead.setKernelType( kerneltype );
     198             : 
     199             :   // Initialise to zero density on all bins
     200       48200 :   for(unsigned bin=0; bin<maxbins; ++bin) myatoms.setValue(bin+1,0);
     201             :   // Calculate whether or not atoms 1 and 2 are within cutoff (can use delta here as pbc are done in atom setup)
     202        9616 :   Vector d1 = getSeparation( myatoms.getPosition(0), myatoms.getPosition(1) ); double d1_len = d1.modulo();
     203        9616 :   d1 = d1 / d1_len;  // Convert vector into director
     204        9616 :   AtomNumber a1 = myatoms.getAbsoluteIndex( 0 );
     205        9616 :   AtomNumber a2 = myatoms.getAbsoluteIndex( 1 );
     206   117927415 :   for(unsigned i=2; i<myatoms.getNumberOfAtoms(); ++i) {
     207   117917799 :     AtomNumber a3 = myatoms.getAbsoluteIndex( i );
     208   117917799 :     if( a3!=a1 && a3!=a2 ) calculateForThreeAtoms( i, d1, d1_len, bead, myatoms );
     209             :   }
     210             :   // std::vector<double> binvals( 1+maxbins ); for(unsigned i=1;i<maxbins;++i) binvals[i]=myatoms.getValue(i);
     211             :   // unsigned ii; double fdf;
     212             :   //std::cout<<"HELLO DENSITY "<<myatoms.getIndex(0)<<" "<<myatoms.getIndex(1)<<" "<<transformStoredValues( binvals, ii, fdf )<<std::endl;
     213             : 
     214             :   // Now find the element for which the density is maximal
     215             :   unsigned vout=2; double max=myatoms.getValue( 2 );
     216       38584 :   for(unsigned i=3; i<myatoms.getUnderlyingMultiValue().getNumberOfValues()-1; ++i) {
     217       28968 :     if( myatoms.getValue(i)>max ) { max=myatoms.getValue(i); vout=i; }
     218             :   }
     219             :   // Calculate value and derivative of switching function between atoms 1 and 2
     220             :   double dfuncl, sw = switchingFunction( getBaseColvarNumber( myatoms.getIndex(0) ),
     221        9616 :                                          getBaseColvarNumber( myatoms.getIndex(1) ) ).calculate( d1_len, dfuncl );
     222             :   // Transform the density
     223        9616 :   double df, tsw = threshold_switch.calculate( max, df );
     224        9616 :   if( !doNotCalculateDerivatives() ) {
     225             :     // Factor of d1_len is required here because d1 is normalized
     226          60 :     d1 *= d1_len;
     227          60 :     addAtomDerivatives( 2+maxbins, 0, -dfuncl*d1, myatoms );
     228          60 :     addAtomDerivatives( 2+maxbins, 1, dfuncl*d1, myatoms );
     229          60 :     myatoms.addBoxDerivatives( 2+maxbins, (-dfuncl)*Tensor(d1,d1) );
     230             :     // Update active atoms so that next bit works
     231          60 :     updateActiveAtoms( myatoms );
     232             :     // Now finish caclulation of derivatives
     233             :     MultiValue& myvals=myatoms.getUnderlyingMultiValue();
     234       13029 :     for(unsigned jd=0; jd<myvals.getNumberActive(); ++jd) {
     235       12969 :       unsigned ider=myvals.getActiveIndex(jd);
     236       12969 :       myvals.addDerivative( 1, ider, sw*df*max*myvals.getDerivative( vout, ider ) + tsw*myvals.getDerivative( 2+maxbins, ider ) );
     237             :     }
     238             :   }
     239        9616 :   return sw*tsw;
     240             : }
     241             : 
     242   117917799 : void TopologyMatrix::calculateForThreeAtoms( const unsigned& iat, const Vector& d1, const double& d1_len,
     243             :     HistogramBead& bead, multicolvar::AtomValuePack& myatoms ) const {
     244             :   // Calculate if there are atoms in the cylinder (can use delta here as pbc are done in atom setup)
     245   117917799 :   Vector d2 = getSeparation( myatoms.getPosition(0), myatoms.getPosition(iat) );
     246             :   // Now calculate projection of d2 on d1
     247   117917799 :   double proj=dotProduct(d2,d1);
     248             :   // This tells us if we are outside the end of the cylinder
     249   117917799 :   double excess = proj - d1_len;
     250             :   // Return if we are outside of the cylinder as calculated based on excess
     251   175974631 :   if( excess>low_sf( getBaseColvarNumber( myatoms.getIndex(0) ), getBaseColvarNumber( myatoms.getIndex(1) ) ).get_dmax() ) return;
     252             :   // Find the length of the cylinder
     253    81802351 :   double binw = binw_mat( getBaseColvarNumber( myatoms.getIndex(0) ), getBaseColvarNumber( myatoms.getIndex(1) ) );
     254    81802351 :   double lcylinder = (std::floor( d1_len / binw ) + 1)*binw;
     255             :   // Return if the projection is outside the length of interest
     256    81802351 :   if( proj<-bead.getCutoff() || proj>(lcylinder+bead.getCutoff()) ) return;
     257             : 
     258             :   // Calculate the excess swiching function
     259    23745519 :   double edf, eval = low_sf( getBaseColvarNumber( myatoms.getIndex(0) ), getBaseColvarNumber( myatoms.getIndex(1) ) ).calculate( excess, edf );
     260             :   // Calculate the projection on the perpendicular distance from the center of the tube
     261    23745519 :   double cm = d2.modulo2() - proj*proj;
     262             : 
     263             :   // Now calculate the density in the cylinder
     264    23745519 :   if( cm<cylinder_sw( getBaseColvarNumber( myatoms.getIndex(0) ), getBaseColvarNumber( myatoms.getIndex(1) ) ).get_dmax2() ) {
     265             :     double dfuncr, val = cylinder_sw( getBaseColvarNumber( myatoms.getIndex(0) ),
     266      104467 :                                       getBaseColvarNumber( myatoms.getIndex(1) ) ).calculateSqr( cm, dfuncr );
     267      104467 :     double cellv = cell_volume( getBaseColvarNumber( myatoms.getIndex(0) ), getBaseColvarNumber( myatoms.getIndex(1) ) );
     268      104467 :     Vector dc1, dc2, dc3, dd1, dd2, dd3, de1, de2, de3;
     269      104467 :     if( !doNotCalculateDerivatives() ) {
     270        4023 :       Tensor d1_a1;
     271             :       // Derivative of director connecting atom1 - atom2 wrt the position of atom 1
     272        4023 :       d1_a1(0,0) = ( -(d1[1]*d1[1]+d1[2]*d1[2])/d1_len );   // dx/dx
     273        4023 :       d1_a1(0,1) = (  d1[0]*d1[1]/d1_len );                 // dx/dy
     274        4023 :       d1_a1(0,2) = (  d1[0]*d1[2]/d1_len );                 // dx/dz
     275        4023 :       d1_a1(1,0) = (  d1[1]*d1[0]/d1_len );                 // dy/dx
     276        4023 :       d1_a1(1,1) = ( -(d1[0]*d1[0]+d1[2]*d1[2])/d1_len );   // dy/dy
     277        4023 :       d1_a1(1,2) = (  d1[1]*d1[2]/d1_len );
     278        4023 :       d1_a1(2,0) = (  d1[2]*d1[0]/d1_len );
     279        4023 :       d1_a1(2,1) = (  d1[2]*d1[1]/d1_len );
     280        4023 :       d1_a1(2,2) = ( -(d1[1]*d1[1]+d1[0]*d1[0])/d1_len );
     281             : 
     282             :       // Calculate derivatives of dot product
     283        4023 :       dd1 = matmul(d2, d1_a1) - d1;
     284        4023 :       dd2 = matmul(d2, -d1_a1);
     285        4023 :       dd3 = d1;
     286             : 
     287             :       // Calculate derivatives of cross product
     288        4023 :       dc1 = dfuncr*( -d2 - proj*dd1 );
     289        4023 :       dc2 = dfuncr*( -proj*dd2 );
     290        4023 :       dc3 = dfuncr*( d2 - proj*dd3 );
     291             : 
     292             :       // Calculate derivatives of excess
     293        4023 :       de1 = edf*excess*( dd1 + d1 );
     294        4023 :       de2 = edf*excess*( dd2 - d1 );
     295        4023 :       de3 = edf*excess*dd3;
     296             :     }
     297             : 
     298      104467 :     Vector pos1 = myatoms.getPosition(0) + d1_len*d1;
     299      104467 :     Vector pos2 = myatoms.getPosition(0) + d2;
     300      104467 :     Vector g1derivf,g2derivf,lderivf; Tensor vir;
     301      530381 :     for(unsigned bin=0; bin<maxbins; ++bin) {
     302      425914 :       bead.set( bin*binw, (bin+1)*binw, sigma );
     303      425914 :       if( proj<(bin*binw-bead.getCutoff()) || proj>binw*(bin+1)+bead.getCutoff() ) continue;
     304      132418 :       double der, contr=bead.calculateWithCutoff( proj, der ) / cellv; der /= cellv;
     305      132418 :       myatoms.addValue( 2+bin, contr*val*eval );
     306             : 
     307      132418 :       if( !doNotCalculateDerivatives() ) {
     308        5835 :         g1derivf=contr*eval*dc1 + val*eval*der*dd1 + contr*val*de1;
     309        5835 :         addAtomDerivatives( 2+bin, 0, g1derivf, myatoms );
     310        5835 :         g2derivf=contr*eval*dc2 + val*eval*der*dd2 + contr*val*de2;
     311        5835 :         addAtomDerivatives( 2+bin, 1, g2derivf, myatoms );
     312        5835 :         lderivf=contr*eval*dc3 + val*eval*der*dd3 + contr*val*de3;
     313        5835 :         addAtomDerivatives( 2+bin, iat, lderivf, myatoms );
     314             :         // Virial
     315        5835 :         vir = -Tensor( myatoms.getPosition(0), g1derivf ) - Tensor( pos1, g2derivf ) - Tensor( pos2, lderivf );
     316        5835 :         myatoms.addBoxDerivatives( 2+bin, vir );
     317             :       }
     318             :     }
     319             :   }
     320             : }
     321             : 
     322             : }
     323             : }
     324             : 

Generated by: LCOV version 1.15