LCOV - code coverage report
Current view: top level - matrixtools - OuterProduct.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 98 109 89.9 %
Date: 2025-04-08 21:11:17 Functions: 8 9 88.9 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2011-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 "core/ActionWithMatrix.h"
      23             : #include "core/ActionRegister.h"
      24             : #include "tools/LeptonCall.h"
      25             : 
      26             : //+PLUMEDOC COLVAR OUTER_PRODUCT
      27             : /*
      28             : Calculate the outer product matrix of two vectors
      29             : 
      30             : This action can be used to calculate the [outer product](https://en.wikipedia.org/wiki/Outer_product) of two
      31             : vectors.  As a (useless) example of what can be done with this action consider the following simple input:
      32             : 
      33             : ```plumed
      34             : d1: DISTANCE ATOMS1=1,2 ATOMS2=3,4
      35             : d2: DISTANCE ATOMS1=5,6 ATOMS2=7,8 ATOMS3=9,10
      36             : pp: OUTER_PRODUCT ARG=d1,d2
      37             : PRINT ARG=pp FILE=colvar
      38             : ```
      39             : 
      40             : This input outputs a $2 \times 3$ matrix. If we call the 2 dimensional vector output by the first DISTANCE action
      41             : $d$ and the 3 dimensional vector output by the second DISTANCE action $h$ then the $(i,j)$ element of the matrix
      42             : output by the action with the label `pp` is given by:
      43             : 
      44             : $$
      45             : p_{ij} = d_i h_j
      46             : $$
      47             : 
      48             : These outer product matrices are useful if you are trying to calculate an adjacency matrix that says atoms are
      49             : connected if they are within a certain distance of each other and if they satisfy a certain criterion.  For example,
      50             : consider the following input:
      51             : 
      52             : ```plumed
      53             : # Determine if atoms are within 5.3 nm of each other
      54             : c1: CONTACT_MATRIX GROUP=1-100 SWITCH={GAUSSIAN D_0=5.29 R_0=0.01 D_MAX=5.3}
      55             : # Calculate the coordination numbers
      56             : ones: ONES SIZE=100
      57             : cc: MATRIX_VECTOR_PRODUCT ARG=c1,ones
      58             : # Now use MORE_THAN to work out which atoms have a coordination number that is bigger than six
      59             : cf: MORE_THAN ARG=cc SWITCH={RATIONAL D_0=5.5 R_0=0.5}
      60             : # Now recalculate the contact matrix above as first step towards calculating adjacency matrix that measures if
      61             : # atoms are close to each other and both have a coordination number that is bigger than six
      62             : c2: CONTACT_MATRIX GROUP=1-100 SWITCH={GAUSSIAN D_0=5.29 R_0=0.01 D_MAX=5.3}
      63             : # Now make a matrix in which element i,j is one if atom i and atom j both have a coordination number that is greater than 6
      64             : cfm: OUTER_PRODUCT ARG=cf,cf
      65             : # And multiply this by our contact matrix to determine the desired adjacency matrix
      66             : m: CUSTOM ARG=c2,cfm FUNC=x*y PERIODIC=NO
      67             : PRINT ARG=m FILE=colvar
      68             : ```
      69             : 
      70             : This input calculates a adjacency matrix which has element $(i,j)$ equal to one if atoms $i$ and $j$ have coordination numbers
      71             : that are greater than 6 and if they are within 5.3 nm of each other.
      72             : 
      73             : Notice that you can specify the function of the two input vectors that is to be calculated by using the `FUNC` keyword which accepts
      74             : mathematical expressions of $x$ and $y$.  In other words, the elements of the outer product are calculated using the lepton library
      75             : that is used in the [CUSTOM](CUSTOM.md) action.  In addition, you can set `FUNC=min` or `FUNC=max` to set the elements of the outer product equal to
      76             : the minimum of the two input variables or the maximum respectively.
      77             : 
      78             : */
      79             : //+ENDPLUMEDOC
      80             : 
      81             : namespace PLMD {
      82             : namespace matrixtools {
      83             : 
      84             : class OuterProduct : public ActionWithMatrix {
      85             : private:
      86             :   bool domin, domax, diagzero;
      87             :   LeptonCall function;
      88             :   unsigned nderivatives;
      89             :   bool stored_vector1, stored_vector2;
      90             : public:
      91             :   static void registerKeywords( Keywords& keys );
      92             :   explicit OuterProduct(const ActionOptions&);
      93             :   unsigned getNumberOfDerivatives();
      94             :   void prepare() override ;
      95        2298 :   unsigned getNumberOfColumns() const override {
      96        2298 :     return getConstPntrToComponent(0)->getShape()[1];
      97             :   }
      98             :   void setupForTask( const unsigned& task_index, std::vector<unsigned>& indices, MultiValue& myvals ) const ;
      99             :   void performTask( const std::string& controller, const unsigned& index1, const unsigned& index2, MultiValue& myvals ) const override;
     100             :   void runEndOfRowJobs( const unsigned& ival, const std::vector<unsigned> & indices, MultiValue& myvals ) const override ;
     101             : };
     102             : 
     103             : PLUMED_REGISTER_ACTION(OuterProduct,"OUTER_PRODUCT")
     104             : 
     105         137 : void OuterProduct::registerKeywords( Keywords& keys ) {
     106         137 :   ActionWithMatrix::registerKeywords(keys);
     107         274 :   keys.addInputKeyword("compulsory","ARG","vector","the labels of the two vectors from which the outer product is being computed");
     108         137 :   keys.add("compulsory","FUNC","x*y","the function of the input vectors that should be put in the elements of the outer product");
     109         137 :   keys.addFlag("ELEMENTS_ON_DIAGONAL_ARE_ZERO",false,"set all diagonal elements to zero");
     110         274 :   keys.setValueDescription("matrix","a matrix containing the outer product of the two input vectors that was obtained using the function that was input");
     111         137 : }
     112             : 
     113          77 : OuterProduct::OuterProduct(const ActionOptions&ao):
     114             :   Action(ao),
     115             :   ActionWithMatrix(ao),
     116          77 :   domin(false),
     117          77 :   domax(false) {
     118          77 :   if( getNumberOfArguments()!=2 ) {
     119           0 :     error("should be two arguments to this action, a matrix and a vector");
     120             :   }
     121          77 :   if( getPntrToArgument(0)->getRank()!=1 || getPntrToArgument(0)->hasDerivatives() ) {
     122           0 :     error("first argument to this action should be a vector");
     123             :   }
     124          77 :   if( getPntrToArgument(1)->getRank()!=1 || getPntrToArgument(1)->hasDerivatives() ) {
     125           0 :     error("first argument to this action should be a vector");
     126             :   }
     127             : 
     128             :   std::string func;
     129         154 :   parse("FUNC",func);
     130          77 :   if( func=="min") {
     131           0 :     domin=true;
     132           0 :     log.printf("  taking minimum of two input vectors \n");
     133          77 :   } else if( func=="max" ) {
     134           2 :     domax=true;
     135           2 :     log.printf("  taking maximum of two input vectors \n");
     136             :   } else {
     137          75 :     log.printf("  with function : %s \n", func.c_str() );
     138          75 :     std::vector<std::string> var(2);
     139             :     var[0]="x";
     140             :     var[1]="y";
     141          75 :     function.set( func, var, this );
     142          75 :   }
     143          77 :   parseFlag("ELEMENTS_ON_DIAGONAL_ARE_ZERO",diagzero);
     144          77 :   if( diagzero ) {
     145           2 :     log.printf("  setting diagonal elements equal to zero\n");
     146             :   }
     147             : 
     148          77 :   std::vector<unsigned> shape(2);
     149          77 :   shape[0]=getPntrToArgument(0)->getShape()[0];
     150          77 :   shape[1]=getPntrToArgument(1)->getShape()[0];
     151          77 :   addValue( shape );
     152          77 :   setNotPeriodic();
     153          77 :   nderivatives = buildArgumentStore(0);
     154          77 :   std::string headstr=getFirstActionInChain()->getLabel();
     155          77 :   stored_vector1 = getPntrToArgument(0)->ignoreStoredValue( headstr );
     156          77 :   stored_vector2 = getPntrToArgument(1)->ignoreStoredValue( headstr );
     157          77 :   if( getPntrToArgument(0)->isDerivativeZeroWhenValueIsZero() || getPntrToArgument(1)->isDerivativeZeroWhenValueIsZero() ) {
     158          15 :     getPntrToComponent(0)->setDerivativeIsZeroWhenValueIsZero();
     159             :   }
     160          77 : }
     161             : 
     162          96 : unsigned OuterProduct::getNumberOfDerivatives() {
     163          96 :   return nderivatives;
     164             : }
     165             : 
     166         158 : void OuterProduct::prepare() {
     167         158 :   ActionWithVector::prepare();
     168         158 :   Value* myval=getPntrToComponent(0);
     169         158 :   if( myval->getShape()[0]==getPntrToArgument(0)->getShape()[0] && myval->getShape()[1]==getPntrToArgument(1)->getShape()[0] ) {
     170         141 :     return;
     171             :   }
     172          17 :   std::vector<unsigned> shape(2);
     173          17 :   shape[0] = getPntrToArgument(0)->getShape()[0];
     174          17 :   shape[1] = getPntrToArgument(1)->getShape()[0];
     175          17 :   myval->setShape( shape );
     176             : }
     177             : 
     178       27151 : void OuterProduct::setupForTask( const unsigned& task_index, std::vector<unsigned>& indices, MultiValue& myvals ) const {
     179       27151 :   unsigned start_n = getPntrToArgument(0)->getShape()[0], size_v = getPntrToArgument(1)->getShape()[0];
     180       27151 :   if( diagzero ) {
     181         990 :     if( indices.size()!=size_v ) {
     182          20 :       indices.resize( size_v );
     183             :     }
     184             :     unsigned k=1;
     185       99000 :     for(unsigned i=0; i<size_v; ++i) {
     186       98010 :       if( task_index==i ) {
     187         990 :         continue ;
     188             :       }
     189       97020 :       indices[k] = size_v + i;
     190       97020 :       k++;
     191             :     }
     192             :     myvals.setSplitIndex( size_v );
     193             :   } else {
     194       26161 :     if( indices.size()!=size_v+1 ) {
     195         249 :       indices.resize( size_v+1 );
     196             :     }
     197     1690193 :     for(unsigned i=0; i<size_v; ++i) {
     198     1664032 :       indices[i+1] = start_n + i;
     199             :     }
     200             :     myvals.setSplitIndex( size_v + 1 );
     201             :   }
     202       27151 : }
     203             : 
     204     6874326 : void OuterProduct::performTask( const std::string& controller, const unsigned& index1, const unsigned& index2, MultiValue& myvals ) const {
     205     6874326 :   unsigned ostrn = getConstPntrToComponent(0)->getPositionInStream(), ind2=index2;
     206     6874326 :   if( index2>=getPntrToArgument(0)->getShape()[0] ) {
     207     1976448 :     ind2 = index2 - getPntrToArgument(0)->getShape()[0];
     208             :   }
     209     6874326 :   if( diagzero && index1==ind2 ) {
     210     6511300 :     return;
     211             :   }
     212             : 
     213             :   double fval;
     214     6874326 :   unsigned jarg = 0, kelem = index1;
     215     6874326 :   bool jstore=stored_vector1;
     216     6874326 :   std::vector<double> args(2);
     217     6874326 :   args[0] = getArgumentElement( 0, index1, myvals );
     218     6874326 :   args[1] = getArgumentElement( 1, ind2, myvals );
     219     6874326 :   if( domin ) {
     220           0 :     fval=args[0];
     221           0 :     if( args[1]<args[0] ) {
     222             :       fval=args[1];
     223           0 :       jarg=1;
     224           0 :       kelem=ind2;
     225           0 :       jstore=stored_vector2;
     226             :     }
     227     6874326 :   } else if( domax ) {
     228      315192 :     fval=args[0];
     229      315192 :     if( args[1]>args[0] ) {
     230             :       fval=args[1];
     231        2055 :       jarg=1;
     232        2055 :       kelem=ind2;
     233        2055 :       jstore=stored_vector2;
     234             :     }
     235             :   } else {
     236     6559134 :     fval=function.evaluate( args );
     237             :   }
     238             : 
     239     6874326 :   myvals.addValue( ostrn, fval );
     240     6874326 :   if( doNotCalculateDerivatives() ) {
     241             :     return ;
     242             :   }
     243             : 
     244      366326 :   if( domin || domax ) {
     245           0 :     addDerivativeOnVectorArgument( jstore, 0, jarg, kelem, 1.0, myvals );
     246             :   } else {
     247      366326 :     addDerivativeOnVectorArgument( stored_vector1, 0, 0, index1, function.evaluateDeriv( 0, args ), myvals );
     248      366326 :     addDerivativeOnVectorArgument( stored_vector2, 0, 1, ind2, function.evaluateDeriv( 1, args ), myvals );
     249             :   }
     250      366326 :   if( doNotCalculateDerivatives() || !matrixChainContinues() ) {
     251             :     return ;
     252             :   }
     253      363026 :   unsigned nmat = getConstPntrToComponent(0)->getPositionInMatrixStash(), nmat_ind = myvals.getNumberOfMatrixRowDerivatives( nmat );
     254      363026 :   myvals.getMatrixRowDerivativeIndices( nmat )[nmat_ind] = arg_deriv_starts[1] + ind2;
     255      363026 :   myvals.setNumberOfMatrixRowDerivatives( nmat, nmat_ind+1 );
     256             : }
     257             : 
     258       39963 : void OuterProduct::runEndOfRowJobs( const unsigned& ival, const std::vector<unsigned> & indices, MultiValue& myvals ) const {
     259       39963 :   if( doNotCalculateDerivatives() || !matrixChainContinues() ) {
     260             :     return ;
     261             :   }
     262       11402 :   unsigned nmat = getConstPntrToComponent(0)->getPositionInMatrixStash(), nmat_ind = myvals.getNumberOfMatrixRowDerivatives( nmat );
     263       11402 :   myvals.getMatrixRowDerivativeIndices( nmat )[nmat_ind] = ival;
     264       11402 :   myvals.setNumberOfMatrixRowDerivatives( nmat, nmat_ind+1 );
     265             : }
     266             : 
     267             : }
     268             : }

Generated by: LCOV version 1.16