LCOV - code coverage report
Current view: top level - adjmat - BridgeMatrix.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 51 69 73.9 %
Date: 2025-12-04 11:19:34 Functions: 3 3 100.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2013-2020 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 "tools/SwitchingFunction.h"
      24             : #include "core/ActionRegister.h"
      25             : 
      26             : #include <string>
      27             : #include <cmath>
      28             : 
      29             : namespace PLMD {
      30             : namespace adjmat {
      31             : 
      32             : //+PLUMEDOC MCOLVAR BRIDGE_MATRIX
      33             : /*
      34             : Calculate the number of atoms that bridge two parts of a structure
      35             : 
      36             : This adjacency matrix is used to implement the [BRIDGE](BRIDGE.md) shortcut. The action outputs a adjacency matrix
      37             : in the same way as [CONTACT_MATRIX](CONTACT_MATRIX.md).  However, the  $j,k$ element of the adjacency matrix is calculated
      38             : using:
      39             : 
      40             : $$
      41             : M_{jk} = \sum_i s_A(r_{ij})s_B(r_{ik})
      42             : $$
      43             : 
      44             : In this expression, the sum runs over all the atoms that were specified using the `BRIDGING_ATOMS` keyword, $s_A$ and
      45             : $s_B$ are switching functions, and $r_{ij}$ and $r_{ik}$ are the distances between atom $i$ and $j$ and between atoms
      46             : $i$ and $k$.  Less formally, this formula ensures that $j,k$ element of the output matrix is one if there is a bridging
      47             : atom between atom $j$ and $k$.
      48             : 
      49             : In the following example input atoms 100-200 can serve as bridging atoms between the atoms in GROUPA and GROUPB and the
      50             : two switching functions $s_A$ and $s_B$ in the formula above are identical.
      51             : 
      52             : ```plumed
      53             : w1: BRIDGE_MATRIX ...
      54             :    BRIDGING_ATOMS=100-200
      55             :    GROUPA=1-10 GROUPB=11-20
      56             :    SWITCH={RATIONAL R_0=0.2}
      57             : ...
      58             : ```
      59             : 
      60             : If you use a single GROUP keyword as in the input below as a single SWITCH keyword the output matrix is symmetric.
      61             : 
      62             : ```plumed
      63             : w2: BRIDGE_MATRIX ...
      64             :    BRIDGING_ATOMS=100-200 GROUP=1-10
      65             :    SWITCH={RATIONAL R_0=0.2}
      66             : ...
      67             : ```
      68             : 
      69             : However, if the two switching functions are not identical, as in the following example, then the output matrix is __not__ symmetric
      70             : even if GROUP is used rather than GROUPA/GROUPB.
      71             : 
      72             : ```plumed
      73             : w2: BRIDGE_MATRIX ...
      74             :    BRIDGING_ATOMS=100-200 GROUP=1-10
      75             :    SWITCHA={RATIONAL R_0=0.2}
      76             :    SWITCHB={RATIONAL R_0=0.4}
      77             : ...
      78             : ```
      79             : 
      80             : Notice that in all the inputs above the $r_{ij}$ and $r_{ik}$ values that enter the formula above are calculated in a way that takes the
      81             : periodic boundary conditions into account.  If you want to ignore the periodic boundary conditions you can use the NOPBC flag as shown below.
      82             : 
      83             : ```plumed
      84             : w2: BRIDGE_MATRIX ...
      85             :    BRIDGING_ATOMS=100-200 GROUP=1-10
      86             :    SWITCH={RATIONAL R_0=0.2}
      87             :    NOPBC
      88             : ...
      89             : ```
      90             : 
      91             : ## COMPONENTS flag
      92             : 
      93             : If you add the flag COMPONENTS to the input as shown below:
      94             : 
      95             : ```plumed
      96             : c4: BRIDGE_MATRIX ...
      97             :    BRIDGING_ATOMS=100-200 GROUP=1-10
      98             :    SWITCH={RATIONAL R_0=0.2}
      99             :    COMPONENTS
     100             : ...
     101             : ```
     102             : 
     103             : then four matrices with the labels `c4.w`, `c4.x`, `c4.y` and `c4.z` are output by the action. The matrix with the label `c4.w` is the adjacency matrix
     104             : that would be output if you had not added the COMPONENTS flag. The $i,j$ component of the matrices `c4.x`, `c4.y` and `c4.z` contain the $x$, $y$ and $z$
     105             : components of the vector connecting atoms $j$ and $k$. Importantly, however, the components of these vectors are only stored in `c4.x`, `c4.y` and `c4.z`
     106             : if the elements of `c4.w` are non-zero. Using the COMPONENTS flag in this way ensures that you can use BRIDGE_MATRIX in tandem with many of the functionalities
     107             : that are part of the [symfunc module](module_symfunc.md).
     108             : 
     109             : ## The MASK keyword
     110             : 
     111             : You use the MASK keyword with BRIDGE_MATRIX in the same way that is used in [CONTACT_MATRIX](CONTACT_MATRIX.md).  This keyword thus expects a vector in input,
     112             : which tells BRIDGE_MATRIX that it is safe to not calculate certain rows of the output matrix.  An example where this keyword is used is shown below:
     113             : 
     114             : ```plumed
     115             : # The atoms that are of interest
     116             : ow: GROUP ATOMS=1-1650
     117             : # Fixed virtual atom which serves as the probe volume's center (pos. in nm)
     118             : center: FIXEDATOM AT=2.5,2.5,2.5
     119             : # Vector in which element i is one if atom i is in sphere of interest and zero otherwise
     120             : sphere: INSPHERE ATOMS=ow CENTER=center RADIUS={GAUSSIAN D_0=0.5 R_0=0.01 D_MAX=0.52}
     121             : # Calculates cooordination numbers
     122             : cmap: BRIDGE_MATRIX ...
     123             :    GROUP=ow BRIDGING_ATOMS=1650-3000
     124             :    SWITCH={GAUSSIAN D_0=0.32 R_0=0.01 D_MAX=0.34} MASK=sphere
     125             : ...
     126             : ones: ONES SIZE=1650
     127             : cc: MATRIX_VECTOR_PRODUCT ARG=cmap,ones
     128             : # Multiply coordination numbers by sphere vector
     129             : prod: CUSTOM ARG=cc,sphere FUNC=x*y PERIODIC=NO
     130             : # Sum of coordination numbers for atoms that are in the sphere of interest
     131             : numer: SUM ARG=prod PERIODIC=NO
     132             : # Number of atoms that are in sphere of interest
     133             : denom: SUM ARG=sphere PERIODIC=NO
     134             : # Average coordination number for atoms in sphere of interest
     135             : av: CUSTOM ARG=prod,sphere FUNC=x/y PERIODIC=NO
     136             : # And print out final CV to a file
     137             : PRINT ARG=av FILE=colvar STRIDE=1
     138             : ```
     139             : 
     140             : This input calculates the average for a type of coordination number for those atoms that are within a spherical region that is centered on the point
     141             : $(2.5,2.5,2.5)$.  The coordination number for the atoms that is evaluated here counts an atom $k$ in the coordination sphere of atom $j$ if there is a
     142             : bridging atom within 0.34 nm of atoms $j$ and $k$.
     143             : 
     144             : */
     145             : //+ENDPLUMEDOC
     146             : 
     147             : class BridgeMatrix {
     148             : public:
     149             :   SwitchingFunction sf1;
     150             :   SwitchingFunction sf2;
     151             :   static void registerKeywords( Keywords& keys );
     152             :   void parseInput( AdjacencyMatrixBase<BridgeMatrix>* action );
     153             :   static void calculateWeight( const BridgeMatrix& data,
     154             :                                const AdjacencyMatrixInput& input,
     155             :                                MatrixOutput output );
     156             : };
     157             : 
     158             : typedef AdjacencyMatrixBase<BridgeMatrix> bmap;
     159             : PLUMED_REGISTER_ACTION(bmap,"BRIDGE_MATRIX")
     160             : 
     161          18 : void BridgeMatrix::registerKeywords( Keywords& keys ) {
     162          18 :   keys.add("atoms","BRIDGING_ATOMS","The list of atoms that can form the bridge between the two interesting parts "
     163             :            "of the structure.");
     164          18 :   keys.add("optional","SWITCH","The parameters of the two switching functions in the above formula");
     165          36 :   keys.linkActionInDocs("SWITCH","LESS_THAN");
     166          18 :   keys.add("optional","SWITCHA","The switching function on the distance between bridging atoms and the atoms in "
     167             :            "group A");
     168          36 :   keys.linkActionInDocs("SWITCHA","LESS_THAN");
     169          18 :   keys.add("optional","SWITCHB","The switching function on the distance between the bridging atoms and the atoms in "
     170             :            "group B");
     171          36 :   keys.linkActionInDocs("SWITCHB","LESS_THAN");
     172          18 : }
     173             : 
     174           8 : void BridgeMatrix::parseInput( AdjacencyMatrixBase<BridgeMatrix>* action ) {
     175             :   bool oneswitch;
     176             :   std::string errors;
     177             :   std::string sf1input;
     178          16 :   action->parse("SWITCH",sf1input);
     179           8 :   if( sf1input.length()>0 ) {
     180           8 :     sf1.set(sf1input,errors);
     181           8 :     oneswitch=true;
     182           8 :     if( errors.length()!=0 ) {
     183           0 :       action->error("problem reading SWITCH keyword : " + errors );
     184             :     }
     185           8 :     sf2.set(sf1input,errors);
     186           8 :     if( errors.length()!=0 ) {
     187           0 :       action->error("problem reading SWITCH keyword : " + errors );
     188             :     }
     189             :   } else {
     190           0 :     action->parse("SWITCHA",sf1input);
     191           0 :     if(sf1input.length()>0) {
     192           0 :       sf1.set(sf1input,errors);
     193           0 :       oneswitch=false;
     194           0 :       if( errors.length()!=0 ) {
     195           0 :         action->error("problem reading SWITCHA keyword : " + errors );
     196             :       }
     197           0 :       std::string sf2input=sf1input;
     198           0 :       action->parse("SWITCHB",sf2input);
     199           0 :       if(sf2input.length()==0) {
     200           0 :         action->error("found SWITCHA keyword without SWITCHB");
     201             :       }
     202           0 :       sf2.set(sf2input,errors);
     203           0 :       if( errors.length()!=0 ) {
     204           0 :         action->error("problem reading SWITCHB keyword : " + errors );
     205             :       }
     206             :     } else {
     207           0 :       action->error("missing definition of switching functions");
     208             :     }
     209             :   }
     210           8 :   action->log.printf("  distance between bridging atoms and atoms in GROUPA must be less than %s\n",sf1.description().c_str());
     211           8 :   action->log.printf("  distance between bridging atoms and atoms in GROUPB must be less than %s\n",sf2.description().c_str());
     212             : 
     213             :   // Setup link cells
     214           8 :   action->setLinkCellCutoff( oneswitch, sf1.get_dmax() + sf2.get_dmax() );
     215           8 : }
     216             : 
     217        1037 : void BridgeMatrix::calculateWeight( const BridgeMatrix& data,
     218             :                                     const AdjacencyMatrixInput& input,
     219             :                                     MatrixOutput output ) {
     220        1037 :   output.val[0] = 0;
     221        1037 :   if( input.pos.modulo2()<epsilon ) {
     222             :     return;
     223             :   }
     224       26859 :   for(unsigned i=0; i<input.natoms; ++i) {
     225             :     Vector dij(input.extra_positions[i][0],
     226             :                input.extra_positions[i][1],
     227       25822 :                input.extra_positions[i][2]);
     228             :     double dijm = dij.modulo2();
     229       25822 :     double dw1, w1=data.sf1.calculateSqr( dijm, dw1 );
     230       25822 :     if( dijm<epsilon ) {
     231             :       w1=0.0;
     232           0 :       dw1=0.0;
     233             :     }
     234       25822 :     Vector dik=input.pbc->distance( dij, input.pos );
     235             :     double dikm=dik.modulo2();
     236       25822 :     double dw2, w2=data.sf2.calculateSqr( dikm, dw2 );
     237       25822 :     if( dikm<epsilon ) {
     238             :       w2=0.0;
     239           0 :       dw2=0.0;
     240             :     }
     241             : 
     242       25822 :     output.val[0] += w1*w2;
     243             :     // And finish the calculation
     244       25822 :     output.deriv[0] += -w2*dw1*dij[0];
     245       25822 :     output.deriv[1] += -w2*dw1*dij[1];
     246       25822 :     output.deriv[2] += -w2*dw1*dij[2];
     247       25822 :     output.deriv[3] += w1*dw2*dik[0];
     248       25822 :     output.deriv[4] += w1*dw2*dik[1];
     249       25822 :     output.deriv[5] += w1*dw2*dik[2];
     250       25822 :     output.deriv[6+i*3+0] = -w1*dw2*dik[0] + w2*dw1*dij[0];
     251       25822 :     output.deriv[6+i*3+1] = -w1*dw2*dik[1] + w2*dw1*dij[1];
     252       25822 :     output.deriv[6+i*3+2] = -w1*dw2*dik[2] + w2*dw1*dij[2];
     253       51644 :     Tensor vir = w1*(-dw2)*Tensor(dik,dik)+w2*(-dw1)*Tensor(dij,dij);
     254       25822 :     output.deriv[6 + 3*input.natoms + 0] += vir[0][0];
     255       25822 :     output.deriv[6 + 3*input.natoms + 1] += vir[0][1];
     256       25822 :     output.deriv[6 + 3*input.natoms + 2] += vir[0][2];
     257       25822 :     output.deriv[6 + 3*input.natoms + 3] += vir[1][0];
     258       25822 :     output.deriv[6 + 3*input.natoms + 4] += vir[1][1];
     259       25822 :     output.deriv[6 + 3*input.natoms + 5] += vir[1][2];
     260       25822 :     output.deriv[6 + 3*input.natoms + 6] += vir[2][0];
     261       25822 :     output.deriv[6 + 3*input.natoms + 7] += vir[2][1];
     262       25822 :     output.deriv[6 + 3*input.natoms + 8] += vir[2][2];
     263             :   }
     264             : }
     265             : 
     266             : }
     267             : }

Generated by: LCOV version 1.16