LCOV - code coverage report
Current view: top level - adjmat - AdjacencyMatrixBase.h (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 453 489 92.6 %
Date: 2026-11-05 10:19:04 Functions: 55 71 77.5 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2015-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             : #ifndef __PLUMED_adjmat_AdjacencyMatrixBase_h
      23             : #define __PLUMED_adjmat_AdjacencyMatrixBase_h
      24             : 
      25             : #include <vector>
      26             : #include "core/ParallelTaskManager.h"
      27             : #include "core/ActionWithMatrix.h"
      28             : #include "core/PlumedMain.h"
      29             : #include "tools/LinkCells.h"
      30             : 
      31             : namespace PLMD {
      32             : namespace adjmat {
      33             : 
      34             : struct ContactMatrix;
      35             : 
      36             : template <class T>
      37          92 : struct AdjacencyMatrixData {
      38             :   T matrixdata;
      39             :   bool usepbc{true};
      40             :   bool components{false};
      41             :   bool no_thread_gather{false};
      42             :   bool gatherForceOnColumns{false};
      43             :   unsigned fbsize{0};
      44             :   RequiredMatrixElements outmat;
      45             :   std::size_t nlists{0};
      46             :   unsigned natoms_per_three_list{0};
      47             :   std::vector<std::size_t> nlist_three_v;
      48             :   std::size_t* nlist_three{nullptr};
      49             :   void update() {
      50       11828 :     nlist_three=nlist_three_v.data();
      51             :   }
      52             : #ifdef __PLUMED_HAS_OPENACC
      53             :   void toACCDevice() const {
      54             : #pragma acc enter data copyin(this[0:1],usepbc,components,no_thread_gather,gatherForceOnColumns,fbsize, \
      55             :                               nlists,natoms_per_three_list, \
      56             :                               nlist_three[0:nlist_three_v.size()])
      57             :     matrixdata.toACCDevice();
      58             :     outmat.toACCDevice();
      59             :   }
      60             :   void removeFromACCDevice() const {
      61             :     matrixdata.removeFromACCDevice();
      62             :     outmat.removeFromACCDevice();
      63             : #pragma acc exit data delete(nlist_three[0:nlist_three_v.size()], \
      64             :                              natoms_per_three_list,nlists, \
      65             :                              fbsize,gatherForceOnColumns,no_thread_gather,components,usepbc,this[0:1])
      66             :   }
      67             : #endif //__PLUMED_HAS_OPENACC
      68             : };
      69             : 
      70             : struct AdjacencyMatrixInput {
      71             :   bool noderiv{false};
      72             :   const Pbc* pbc;
      73             :   Vector pos;
      74             :   std::size_t natoms{0};
      75             :   VectorView extra_positions;
      76             : };
      77             : 
      78             : struct MatrixOutput {
      79             :   View<double,1> val;
      80             :   View<double> deriv;
      81             : 
      82             :   ///doing t= Tensor(v1,v2); deriv[x:x+9]=t with no extra memory allocation
      83             :   template <typename Iterable1, typename Iterable2>
      84    11490856 :   void assignOuterProduct(const std::size_t startingIndex,
      85             :                           const Iterable1& v1,
      86             :                           const Iterable2& v2 ) {
      87    11490856 :     deriv[startingIndex + 0] = v1[0] * v2[0];
      88    11490856 :     deriv[startingIndex + 1] = v1[0] * v2[1];
      89    11490856 :     deriv[startingIndex + 2] = v1[0] * v2[2];
      90    11490856 :     deriv[startingIndex + 3] = v1[1] * v2[0];
      91    11490856 :     deriv[startingIndex + 4] = v1[1] * v2[1];
      92    11490856 :     deriv[startingIndex + 5] = v1[1] * v2[2];
      93    11490856 :     deriv[startingIndex + 6] = v1[2] * v2[0];
      94    11490856 :     deriv[startingIndex + 7] = v1[2] * v2[1];
      95    11490856 :     deriv[startingIndex + 8] = v1[2] * v2[2];
      96    11490856 :   }
      97             : };
      98             : 
      99             : template <class CV, typename myPTM=defaultPTM>
     100             : class AdjacencyMatrixBase : public ActionWithMatrix {
     101             : public:
     102             :   using input_type = AdjacencyMatrixData<CV>;
     103             :   using PTM = typename myPTM::template PTM<AdjacencyMatrixBase<CV, myPTM>>;
     104             :   typedef typename PTM::ParallelActionsInput ParallelActionsInput;
     105             :   typedef typename PTM::ParallelActionsOutput ParallelActionsOutput;
     106             : private:
     107             :   PTM taskmanager;
     108             :   bool nopbc, read_one_group;
     109             :   unsigned natoms_per_list;
     110             :   std::vector<std::size_t> nlist_v;
     111             :   LinkCells linkcells, threecells;
     112             : /// True if the matrix elements are exactly zero beyond the link cell cutoff: the
     113             : /// neighbour lists then hold only the atoms within the cutoff
     114             :   bool exact_cutoff{false};
     115             :   std::vector<unsigned> ablocks, threeblocks;
     116             :   double nl_cut, nl_cut2;
     117             :   unsigned nl_stride;
     118             :   void setupThirdAtomBlock( const std::vector<AtomNumber>& tc,
     119             :                             std::vector<AtomNumber>& t );
     120             : public:
     121             :   static constexpr size_t virialSize = 9;
     122             :   static void registerKeywords( Keywords& keys );
     123             :   explicit AdjacencyMatrixBase(const ActionOptions&);
     124             :   unsigned getNumberOfDerivatives() override ;
     125             :   void calculate() override ;
     126             :   void applyNonZeroRankForces( std::vector<double>& outforces ) override ;
     127             :   void getAtomicInputData( std::vector<double>& inputdata ) const override;
     128             :   void getAtomicInputData( std::vector<float>& inputdata ) const override;
     129          10 :   std::string writeInGraph() const override {
     130             :     if constexpr ( std::is_same_v<ContactMatrix,CV> ) {
     131             :       //TODO:this will change to  std::is_same_v<ContactMatrix<precision>,CV> when adding the mixed precision to the contact matrix
     132          10 :       return "CONTACT_MATRIX";
     133             :     }
     134           0 :     return getName();
     135             :   }
     136             : /// Set the cutoff of the link cells. With exact=true the matrix elements are
     137             : /// known to be zero when the atoms are further apart than lcut, so the rows of
     138             : /// the sparse matrix hold only the atoms within the cutoff instead of all the
     139             : /// atoms in the neighbouring link cells (many more at short cutoffs)
     140             :   void setLinkCellCutoff( const bool& symmetric, const double& lcut, double tcut=-1.0, const bool exact=false );
     141             :   static void performTask( std::size_t task_index,
     142             :                            const AdjacencyMatrixData<CV>& actiondata,
     143             :                            ParallelActionsInput& input,
     144             :                            ParallelActionsOutput output );
     145             :   static int getNumberOfValuesPerTask( std::size_t task_index,
     146             :                                        const AdjacencyMatrixData<CV>& actiondata );
     147             :   static void getForceIndices( std::size_t task_index,
     148             :                                std::size_t colno,
     149             :                                std::size_t ntotal_force,
     150             :                                const AdjacencyMatrixData<CV>& actiondata,
     151             :                                const ParallelActionsInput& input,
     152             :                                ForceIndexHolder force_indices );
     153             :   void getMatrixColumnTitles( std::vector<std::string>& argnames ) const override ;
     154             : };
     155             : 
     156             : template <class CV, typename myPTM>
     157        1130 : void AdjacencyMatrixBase<CV, myPTM>::registerKeywords( Keywords& keys ) {
     158        1130 :   ActionWithMatrix::registerKeywords( keys );
     159        2260 :   keys.addInputKeyword("optional","MASK","vector","a vector that is used to used to determine which rows of the adjancency matrix to compute");
     160        1130 :   keys.add("atoms","GROUP","the atoms for which you would like to calculate the adjacency matrix");
     161        1130 :   keys.add("atoms","GROUPA","when you are calculating the adjacency matrix between two sets of atoms this keyword is used to specify the atoms along with the keyword GROUPB");
     162        1130 :   keys.add("atoms","GROUPB","when you are calculating the adjacency matrix between two sets of atoms this keyword is used to specify the atoms along with the keyword GROUPA");
     163        1130 :   keys.addDeprecatedKeyword("ATOMS","GROUP");
     164        1130 :   keys.reserve("atoms","GROUPC","a group of atoms that must be summed over when calculating each element of the adjacency matrix");
     165        1130 :   keys.addFlag("COMPONENTS",false,"also calculate the components of the vector connecting the atoms in the contact matrix");
     166        1130 :   keys.addFlag("NOPBC",false,"don't use pbc");
     167        1130 :   keys.add("compulsory","NL_CUTOFF","0.0","The cutoff for the neighbor list.  A value of 0 means we are not using a neighbor list");
     168        1130 :   keys.add("compulsory","NL_STRIDE","1","The frequency with which we are updating the atoms in the neighbor list");
     169        1130 :   CV::registerKeywords( keys );
     170        1130 :   PTM::registerKeywords( keys );
     171        2260 :   keys.addOutputComponent("w","COMPONENTS","matrix","a matrix containing the weights for the bonds between each pair of atoms");
     172        2260 :   keys.addOutputComponent("x","COMPONENTS","matrix","the projection of the bond on the x axis");
     173        2260 :   keys.addOutputComponent("y","COMPONENTS","matrix","the projection of the bond on the y axis");
     174        2260 :   keys.addOutputComponent("z","COMPONENTS","matrix","the projection of the bond on the z axis");
     175        2260 :   keys.setValueDescription("matrix","a matrix containing the weights for the bonds between each pair of atoms");
     176        1130 :   keys.addDOI("10.1021/acs.jctc.6b01073");
     177        1130 : }
     178             : 
     179             : template <class CV, typename myPTM>
     180         340 : AdjacencyMatrixBase<CV, myPTM>::AdjacencyMatrixBase(const ActionOptions& ao):
     181             :   Action(ao),
     182             :   ActionWithMatrix(ao),
     183         340 :   taskmanager(this),
     184         340 :   read_one_group(false),
     185         340 :   natoms_per_list(0),
     186         340 :   linkcells(comm),
     187         680 :   threecells(comm) {
     188         340 :   std::vector<std::size_t> shape(2);
     189         340 :   std::vector<AtomNumber> t;
     190         680 :   parseAtomList("GROUP", t );
     191         340 :   if( getName()!="HBOND_MATRIX" ) {
     192         338 :     if( t.size()==0 ) {
     193         202 :       parseAtomList("ATOMS", t);
     194         101 :       if( t.size()>0 ) {
     195           0 :         warning("using deprecated syntax for contact matrix.  You are strongly recommended to use GROUP instead of ATOMS");
     196             :       }
     197             :     }
     198           2 :   } else if( t.size()>0 ) {
     199           2 :     warning("GROUP keyword has been deprecated for HBOND_MATRIX as it may lead users to wrongly assume that the matrices calculated by this action are symmetric.  We strongly recommend using DONORS/ACCEPTORS instead");
     200             :   }
     201             : 
     202         340 :   if( t.size()==0 ) {
     203         102 :     std::vector<AtomNumber> ta;
     204         102 :     if( getName()=="HBOND_MATRIX") {
     205           2 :       parseAtomList("DONORS",ta);
     206             :     } else {
     207         202 :       parseAtomList("GROUPA",ta);
     208             :     }
     209         102 :     std::vector<AtomNumber> tb;
     210         102 :     if( getName()=="HBOND_MATRIX") {
     211           2 :       parseAtomList("ACCEPTORS",tb);
     212             :     } else {
     213         202 :       parseAtomList("GROUPB",tb);
     214             :     }
     215         102 :     if( ta.size()==0 || tb.size()==0 ) {
     216           0 :       error("no atoms have been specified in input");
     217             :     }
     218             : 
     219             :     // Create list of tasks
     220         102 :     log.printf("  atoms in GROUPA ");
     221        5906 :     for(unsigned i=0; i<ta.size(); ++i) {
     222        5804 :       log.printf("%d ", ta[i].serial());
     223        5804 :       t.push_back(ta[i]);
     224             :     }
     225         102 :     log.printf("\n");
     226         102 :     log.printf("  atoms in GROUPB ");
     227         102 :     ablocks.resize( tb.size() );
     228             :     unsigned n=0;
     229       15014 :     for(unsigned i=0; i<tb.size(); ++i) {
     230       14912 :       log.printf("%d ", tb[i].serial());
     231       14912 :       ablocks[i]=ta.size()+n;
     232       14912 :       t.push_back(tb[i]);
     233       14912 :       n++;
     234             :     }
     235         102 :     log.printf("\n");
     236         102 :     shape[0]=ta.size();
     237         102 :     shape[1]=tb.size();
     238         102 :   } else {
     239             :     // Create list of tasks
     240         238 :     log.printf("  atoms in GROUP ");
     241         238 :     ablocks.resize( t.size() );
     242      145777 :     for(unsigned i=0; i<t.size(); ++i) {
     243      145539 :       log.printf("%d ", t[i].serial());
     244      145539 :       ablocks[i]=i;
     245             :     }
     246         238 :     log.printf("\n");
     247         238 :     shape[0]=shape[1]=t.size();
     248         238 :     read_one_group=true;
     249             :   }
     250         340 :   if( keywords.exists("GROUPC") ) {
     251           6 :     std::vector<AtomNumber> tc;
     252          12 :     parseAtomList("GROUPC",tc);
     253           6 :     if( tc.size()==0 ) {
     254           0 :       error("no atoms in GROUPC specified");
     255             :     }
     256           6 :     log.printf("  atoms in GROUPC ");
     257           6 :     setupThirdAtomBlock( tc, t );
     258         340 :   } else if( keywords.exists("BRIDGING_ATOMS") ) {
     259           0 :     std::vector<AtomNumber> tc;
     260           0 :     parseAtomList("BRIDGING_ATOMS",tc);
     261           0 :     if( tc.size()==0 ) {
     262           0 :       error("no BRIDGING_ATOMS specified");
     263             :     }
     264           0 :     log.printf("  bridging atoms are ");
     265           0 :     setupThirdAtomBlock( tc, t );
     266         334 :   } else if( keywords.exists("HYDROGENS") ) {
     267           2 :     std::vector<AtomNumber> tc;
     268           4 :     parseAtomList("HYDROGENS",tc);
     269           2 :     if( tc.size()==0 ) {
     270           0 :       error("no HYDROGEN atoms specified");
     271             :     }
     272           2 :     log.printf("  hydrogen atoms are ");
     273           2 :     setupThirdAtomBlock( tc, t );
     274         334 :   } else if( keywords.exists("BACKGROUND_ATOMS") ) {
     275           8 :     std::vector<AtomNumber> tc;
     276          16 :     parseAtomList("BACKGROUND_ATOMS",tc);
     277           8 :     if( tc.size()==0 ) {
     278           0 :       error("no ATOMS atoms specified");
     279             :     }
     280           8 :     log.printf("  atoms for background density are ");
     281           8 :     setupThirdAtomBlock( tc, t );
     282           8 :   }
     283             :   // Request the atoms from the ActionAtomistic
     284         340 :   requestAtoms( t, false );
     285             :   bool components;
     286         340 :   parseFlag("COMPONENTS",components);
     287         340 :   parseFlag("NOPBC",nopbc);
     288         340 :   if( !components ) {
     289         250 :     addValue( shape );
     290         250 :     setNotPeriodic();
     291             :   } else {
     292          90 :     addComponent( "w", shape );
     293         180 :     componentIsNotPeriodic("w");
     294             :   }
     295         340 :   getPntrToComponent(0)->setDerivativeIsZeroWhenValueIsZero();
     296             :   // Stuff for neighbor list
     297         340 :   parse("NL_CUTOFF",nl_cut);
     298         340 :   nl_cut2=nl_cut*nl_cut;
     299         340 :   parse("NL_STRIDE",nl_stride);
     300             :   // calculate() rebuilds the neighbor list at every step and cannot reuse it:
     301             :   // reject NL_STRIDE>1 here, so that the input fails at setup and not at the first step
     302         340 :   if( nl_stride!=1 ) {
     303           0 :     error("NL_STRIDE different from 1 is not implemented: the neighbor list is rebuilt at every step with link cells (cutoff from D_MAX, or NL_CUTOFF if set). Remove NL_STRIDE from the input");
     304             :   }
     305         340 :   if( nl_cut>0 ) {
     306           0 :     log.printf("  using neighbor list with cutoff %f.  List is updated every %u steps.\n",nl_cut,nl_stride);
     307             :   }
     308             : 
     309         340 :   if( components ) {
     310          90 :     addComponent( "x", shape );
     311          90 :     componentIsNotPeriodic("x");
     312          90 :     addComponent( "y", shape );
     313          90 :     componentIsNotPeriodic("y");
     314          90 :     addComponent( "z", shape );
     315         180 :     componentIsNotPeriodic("z");
     316             :   }
     317         386 :   log<<"  Bibliography "<<plumed.cite("Tribello, Giberti, Sosso, Salvalaglio and Parrinello, J. Chem. Theory Comput. 13, 1317 (2017)")<<"\n";
     318         294 :   AdjacencyMatrixData<CV> matdata;
     319         340 :   matdata.usepbc = !nopbc;
     320         340 :   matdata.components = components;
     321         340 :   matdata.nlists = getPntrToComponent(0)->getShape()[0];
     322         340 :   matdata.fbsize = 0;
     323         340 :   if( ablocks[0]>=getConstPntrToComponent(0)->getShape()[0] ) {
     324         102 :     matdata.fbsize = getConstPntrToComponent(0)->getShape()[0];
     325             :   }
     326         340 :   matdata.matrixdata.parseInput( this );
     327         340 :   matdata.gatherForceOnColumns = false;
     328         340 :   matdata.no_thread_gather = no_thread_gather;
     329         340 :   if( no_thread_gather && threeblocks.size()>0 ) {
     330           0 :     error("AVOID_THREAD_GATHER flag cannot be used with this action");
     331             :   }
     332         340 :   taskmanager.setActionInput( matdata );
     333         340 : }
     334             : 
     335             : template <class CV, typename myPTM>
     336       22247 : unsigned AdjacencyMatrixBase<CV, myPTM>::getNumberOfDerivatives() {
     337       22247 :   return 3*getNumberOfAtoms() + 9;
     338             : }
     339             : 
     340             : template <class CV, typename myPTM>
     341          16 : void AdjacencyMatrixBase<CV, myPTM>::setupThirdAtomBlock( const std::vector<AtomNumber>& tc,
     342             :     std::vector<AtomNumber>& t ) {
     343          16 :   threeblocks.resize( tc.size() );
     344          16 :   unsigned base=t.size();
     345       49986 :   for(unsigned i=0; i<tc.size(); ++i) {
     346       49970 :     log.printf("%d ", tc[i].serial());
     347       49970 :     t.push_back(tc[i]);
     348       49970 :     threeblocks[i]=base+i;
     349             :   }
     350          16 :   log.printf("\n");
     351          16 : }
     352             : 
     353             : template <class CV, typename myPTM>
     354         340 : void AdjacencyMatrixBase<CV, myPTM>::setLinkCellCutoff( const bool& symmetric,
     355             :     const double& lcut,
     356             :     double tcut,
     357             :     const bool exact ) {
     358         340 :   if( read_one_group && symmetric ) {
     359         233 :     getPntrToComponent(0)->setSymmetric( true );
     360             :   }
     361         340 :   exact_cutoff = exact;
     362         340 :   if( nl_cut>0 && lcut>nl_cut ) {
     363           0 :     error("D_MAX for switching functions should be shorter than neighbor list cutoff");
     364             :   }
     365             : 
     366         340 :   if( tcut<0 ) {
     367         332 :     tcut=lcut;
     368             :   }
     369         340 :   if( nl_cut>0 ) {
     370           0 :     linkcells.setCutoff( nl_cut );
     371             :   } else {
     372         340 :     linkcells.setCutoff( lcut );
     373             :   }
     374         340 :   if( linkcells.getCutoff()<std::numeric_limits<double>::max() ) {
     375         164 :     log.printf("  set link cell cutoff to %f \n", linkcells.getCutoff() );
     376         164 :     if( exact_cutoff ) {
     377         124 :       log.printf("  only atoms within the cutoff are stored in the rows of the matrix \n");
     378             :     }
     379             :   }
     380         340 :   threecells.setCutoff( tcut );
     381         340 : }
     382             : 
     383             : template <class CV, typename myPTM>
     384       11828 : void AdjacencyMatrixBase<CV, myPTM>::getAtomicInputData( std::vector<double>& inputdata ) const {
     385       11828 :   if( inputdata.size()!=3*getNumberOfAtoms() ) {
     386         315 :     inputdata.resize( 3*getNumberOfAtoms() );
     387             :   }
     388             : 
     389             :   std::size_t k=0;
     390     1391660 :   for(unsigned i=0; i<getNumberOfAtoms(); ++i) {
     391     1379832 :     Vector mypos( ActionAtomistic::getPosition(i) );
     392     1379832 :     inputdata[k] = mypos[0];
     393     1379832 :     k++;
     394     1379832 :     inputdata[k] = mypos[1];
     395     1379832 :     k++;
     396     1379832 :     inputdata[k] = mypos[2];
     397     1379832 :     k++;
     398             :   }
     399       11828 : }
     400             : 
     401             : template <class CV, typename myPTM>
     402           0 : void AdjacencyMatrixBase<CV, myPTM>::getAtomicInputData( std::vector<float>& inputdata ) const {
     403           0 :   if( inputdata.size()!=3*getNumberOfAtoms() ) {
     404           0 :     inputdata.resize( 3*getNumberOfAtoms() );
     405             :   }
     406             : 
     407             :   std::size_t k=0;
     408           0 :   for(unsigned i=0; i<getNumberOfAtoms(); ++i) {
     409           0 :     Vector mypos( ActionAtomistic::getPosition(i) );
     410           0 :     inputdata[k] = mypos[0];
     411           0 :     k++;
     412           0 :     inputdata[k] = mypos[1];
     413           0 :     k++;
     414           0 :     inputdata[k] = mypos[2];
     415           0 :     k++;
     416             :   }
     417           0 : }
     418             : 
     419             : template <class CV, typename myPTM>
     420           0 : void AdjacencyMatrixBase<CV, myPTM>::getMatrixColumnTitles( std::vector<std::string>& argnames ) const {
     421             :   std::string num;
     422           0 :   for(unsigned i=0; i<getConstPntrToComponent(0)->getShape()[1]; ++i) {
     423           0 :     Tools::convert( i+1, num );
     424           0 :     argnames.push_back( num );
     425             :   }
     426           0 : }
     427             : 
     428             : template <class CV, typename myPTM>
     429       11828 : void AdjacencyMatrixBase<CV, myPTM>::calculate() {
     430       11828 :   Value* myval = getPntrToComponent(0);
     431             :   // Retrieve the task list
     432       11828 :   std::vector<unsigned> & pTaskList( getListOfActiveTasks() );
     433             :   // Get the number of tasks we have to deal with
     434             :   unsigned ntasks=myval->getShape()[0];
     435       11828 :   if( nl_stride==1 ) {
     436       11828 :     ntasks=pTaskList.size();
     437             :   } else {
     438           0 :     error("neighbour list non updates are not actually implemented or tested");
     439             :   }
     440             : 
     441             :   // Get the atoms
     442       11828 :   std::vector<Vector> ltmp_pos2( ntasks );
     443      776352 :   for(unsigned i=0; i<ntasks; ++i) {
     444      764524 :     ltmp_pos2[i] = ActionAtomistic::getPosition(pTaskList[i]);
     445             :   }
     446       11828 :   auto & matdata = taskmanager.getActionInput();
     447             :   // Now update the neighbor lists
     448       11828 :   if( getStep()%nl_stride==0 ) {
     449             :     // Build the link cells
     450       11828 :     std::vector<Vector> ltmp_pos( ablocks.size() );
     451     1320172 :     for(unsigned i=0; i<ablocks.size(); ++i) {
     452     1308344 :       ltmp_pos[i]=ActionAtomistic::getPosition( ablocks[i] );
     453             :     }
     454       11828 :     linkcells.createNeighborList( getConstPntrToComponent(0)->getShape()[0],
     455             :                                   make_const_view(ltmp_pos2),
     456             :                                   make_const_view(pTaskList),
     457             :                                   make_const_view(pTaskList),
     458             :                                   make_const_view(ltmp_pos),
     459             :                                   make_const_view(ablocks),
     460             :                                   getPbc(),
     461       11828 :                                   natoms_per_list,
     462       11828 :                                   nlist_v,
     463       11828 :                                   exact_cutoff );
     464       11828 :     if( threeblocks.size()>0 ) {
     465          53 :       std::vector<Vector> ltmp_pos3( threeblocks.size() );
     466       50282 :       for(unsigned i=0; i<threeblocks.size(); ++i) {
     467       50229 :         ltmp_pos3[i]=ActionAtomistic::getPosition( threeblocks[i] );
     468             :       }
     469          53 :       threecells.createNeighborList( getConstPntrToComponent(0)->getShape()[0],
     470             :                                      make_const_view(ltmp_pos2),
     471             :                                      make_const_view(pTaskList),
     472             :                                      make_const_view(pTaskList),
     473             :                                      make_const_view(ltmp_pos3),
     474             :                                      make_const_view(threeblocks),
     475             :                                      getPbc(),
     476          53 :                                      matdata.natoms_per_three_list,
     477          53 :                                      matdata.nlist_three_v);
     478             :     }
     479             :     matdata.update();
     480             :   } else {
     481           0 :     error("neighbour list non updates are not actually implemented or tested");
     482             :   }
     483             :   // And finally work out maximum number of columns to use
     484       11828 :   unsigned maxcol = nlist_v[0];
     485     1281495 :   for(unsigned i=1; i<getConstPntrToComponent(0)->getShape()[0]; ++i) {
     486     1269667 :     if( nlist_v[i]>maxcol ) {
     487        1241 :       maxcol = nlist_v[i];
     488             :     }
     489             :   }
     490             :   // The first element returned by the neighbour list is the central atom.  The
     491             :   // neighbour list thus always has one more atom in it than there are atoms in the
     492             :   // neighbourhood of the central atom
     493       11828 :   maxcol = maxcol-1;
     494             :   // A matrix with no elements at all (no atom within the cutoff of any other)
     495             :   // still needs one column of storage: zero columns is the flag of the
     496             :   // lower-triangular storage in Value and leaves the data array empty
     497       11828 :   if( maxcol<1 ) {
     498           1 :     maxcol = 1;
     499             :   }
     500             : 
     501             :   // Reshape the matrix store if the number of columns has changed
     502       11828 :   if( maxcol!=myval->getNumberOfColumns() ) {
     503        1346 :     for(unsigned i=0; i<getNumberOfComponents(); ++i) {
     504         889 :       getPntrToComponent(i)->reshapeMatrixStore( maxcol );
     505             :     }
     506             :   }
     507             :   // Transfer neighbor list data to the bookeeping arrays
     508       11828 :   unsigned lstart = getConstPntrToComponent(0)->getShape()[0];
     509       11828 :   if( ntasks<lstart ) {
     510      519941 :     for(unsigned i=0; i<lstart; ++i) {
     511      519814 :       myval->setMatrixBookeepingElement( i*(1+maxcol), 0 );
     512             :     }
     513             :   }
     514      776352 :   for(unsigned i=0; i<ntasks; ++i) {
     515      764524 :     unsigned bstart = pTaskList[i]*(1+maxcol);
     516      764524 :     unsigned rstart = lstart + pTaskList[i]*(1+natoms_per_list);
     517      764524 :     myval->setMatrixBookeepingElement( bstart, nlist_v[pTaskList[i]] - 1 );
     518    20722551 :     for(unsigned j=1; j<nlist_v[pTaskList[i]]; ++j) {
     519    19958027 :       myval->setMatrixBookeepingElement( bstart+j, nlist_v[rstart+j] - matdata.fbsize );
     520             :     }
     521             :   }
     522       11828 :   copyMatrixBookeepingFromFirstComponent( taskmanager.getActionInput().outmat );
     523       12620 :   for(unsigned i=1; i<getNumberOfComponents(); ++i) {
     524         792 :     getPntrToComponent(i)->copyBookeepingArrayFromArgument( getPntrToComponent(0) );
     525             :   }
     526             :   // We need to setup the task manager here because at this point we know the size of the sparse matrices
     527       11828 :   if( no_thread_gather ) {
     528          25 :     unsigned maxatoms = maxcol;
     529          25 :     if( maxcolsize>maxatoms ) {
     530             :       maxatoms = maxcolsize;
     531             :     }
     532          25 :     taskmanager.setupParallelTaskManager( 3 + 3*matdata.natoms_per_three_list + virialSize, 3*threeblocks.size()+virialSize );
     533          25 :     taskmanager.setWorkspaceSize(3*(maxatoms + 1 + matdata.natoms_per_three_list) + 3*(6+matdata.natoms_per_three_list)+virialSize );
     534             :   } else {
     535       11803 :     taskmanager.setupParallelTaskManager( 6 + 3*matdata.natoms_per_three_list + virialSize, getNumberOfDerivatives() );
     536       11803 :     taskmanager.setWorkspaceSize(3*(maxcol + 1 + matdata.natoms_per_three_list) );
     537             :   }
     538             :   // And run all the tasks
     539       11828 :   taskmanager.runAllTasks();
     540       11828 : }
     541             : 
     542             : template <class CV, typename myPTM>
     543     1074691 : void AdjacencyMatrixBase<CV, myPTM>::performTask( std::size_t task_index,
     544             :     const AdjacencyMatrixData<CV>& actiondata,
     545             :     ParallelActionsInput& input,
     546             :     ParallelActionsOutput output ) {
     547     1074691 :   const unsigned fstart=task_index*(1+actiondata.outmat.ncols);
     548     1074691 :   const unsigned nneigh=actiondata.outmat[fstart];
     549     1074691 :   const unsigned n3neigh=(actiondata.natoms_per_three_list>0) ?
     550        1011 :                          actiondata.nlist_three[task_index]:0;
     551     1074691 :   VectorView atoms(output.buffer.data(), nneigh + n3neigh );
     552             : 
     553     1074691 :   if( actiondata.gatherForceOnColumns ) {
     554         479 :     const View<double,3> pos0( input.inputdata + 3 * (task_index+actiondata.fbsize) );
     555        5659 :     for(unsigned i=0; i<nneigh; ++i) {
     556        5180 :       atoms[i][0] = pos0[0] - input.inputdata[3*actiondata.outmat[fstart+1+i]+0];
     557        5180 :       atoms[i][1] = pos0[1] - input.inputdata[3*actiondata.outmat[fstart+1+i]+1];
     558        5180 :       atoms[i][2] = pos0[2] - input.inputdata[3*actiondata.outmat[fstart+1+i]+2];
     559             :     }
     560             :   } else {
     561     1074212 :     const View<double,3> pos0( input.inputdata + 3 * task_index);
     562    22733349 :     for(unsigned i=0; i<nneigh; ++i) {
     563    21659137 :       atoms[i][0] = input.inputdata[3*(actiondata.outmat[fstart+1+i]+actiondata.fbsize)+0] - pos0[0];
     564    21659137 :       atoms[i][1] = input.inputdata[3*(actiondata.outmat[fstart+1+i]+actiondata.fbsize)+1] - pos0[1];
     565    21659137 :       atoms[i][2] = input.inputdata[3*(actiondata.outmat[fstart+1+i]+actiondata.fbsize)+2] - pos0[2];
     566             :     }
     567             : 
     568     1074212 :     if( actiondata.natoms_per_three_list>0 ) {
     569             :       // Retrieve the set of third atoms
     570        1011 :       unsigned fstart3 = actiondata.nlists
     571        1011 :                          + task_index*(1+actiondata.natoms_per_three_list);
     572     4165164 :       for(unsigned i=1; i<n3neigh; ++i) {
     573     4164153 :         atoms[nneigh+i-1][0] =
     574     4164153 :           input.inputdata[3*actiondata.nlist_three[fstart3+i]+0] - pos0[0];
     575     4164153 :         atoms[nneigh+i-1][1] =
     576     4164153 :           input.inputdata[3*actiondata.nlist_three[fstart3+i]+1] - pos0[1];
     577     4164153 :         atoms[nneigh+i-1][2] =
     578     4164153 :           input.inputdata[3*actiondata.nlist_three[fstart3+i]+2] - pos0[2];
     579             :       }
     580             :     }
     581             :   }
     582             : 
     583             : 
     584             :   // Apply periodic boundary conditions to all the atoms
     585     1074691 :   if( actiondata.usepbc ) {
     586     1074691 :     input.pbc->apply( atoms, atoms.size() );
     587             :   }
     588     1074691 :   AdjacencyMatrixInput adjinp {input.noderiv,
     589     1074691 :                                input.pbc,
     590             :                                Vector{0.0,0.0,0.0},
     591             :                                0,
     592     1074691 :                                VectorView{ atoms.data() + 3*nneigh,n3neigh}};
     593             : 
     594     1074691 :   if( n3neigh>1 ) {
     595        1011 :     adjinp.natoms = n3neigh-1;
     596             :   }
     597             : 
     598             :   // And calculate this row of the matrices
     599     1074691 :   std::size_t nderiv(6 + 3*adjinp.natoms + virialSize);
     600     1074691 :   if( actiondata.no_thread_gather ) {
     601         887 :     nderiv = 3 + + 3*adjinp.natoms + virialSize;
     602             :   }
     603     1074691 :   const unsigned ncomponents =(actiondata.components) ? 4 : 1;
     604             : 
     605             :   // Must clear the derivatives here as otherwise sparsity pattern
     606             :   // of derivatives does not match sparsity pattern for forces
     607     1074691 :   if( !input.noderiv ) {
     608   209733748 :     for(unsigned i=0; i<output.derivatives.size(); ++i) {
     609   209389662 :       output.derivatives[i] = 0.0;
     610             :     }
     611             :   }
     612             : 
     613     1074691 :   if( actiondata.no_thread_gather ) {
     614       16427 :     for(unsigned i=0; i<nneigh; ++i ) {
     615       15540 :       adjinp.pos = Vector(atoms[i][0],atoms[i][1],atoms[i][2]);
     616       15540 :       const std::size_t valpos = static_cast<std::size_t>(i) * ncomponents;
     617       15540 :       MatrixOutput adjout{View<double,1>{&output.values[valpos]},
     618       15540 :                           View{output.buffer.data() + 3*(nneigh+n3neigh),nderiv+3}};
     619       15540 :       CV::calculateWeight( actiondata.matrixdata, adjinp, adjout );
     620       15540 :       if( actiondata.components ) {
     621       12096 :         output.values[valpos+1] = atoms[i][0];
     622       12096 :         output.values[valpos+2] = atoms[i][1];
     623       12096 :         output.values[valpos+3] = atoms[i][2];
     624             :       }
     625       15540 :       if( input.noderiv ) {
     626        7476 :         continue;
     627             :       }
     628       10360 :       if( fabs(adjout.val[0])>epsilon ) {
     629       10318 :         if( actiondata.gatherForceOnColumns ) {
     630        5159 :           auto derivs  = output.derivatives.template subview_n<3>(valpos*nderiv);
     631        5159 :           derivs[0] = adjout.deriv[3];
     632        5159 :           derivs[1] = adjout.deriv[4];
     633        5159 :           derivs[2] = adjout.deriv[5];
     634             :         } else {
     635        5159 :           auto derivs  = output.derivatives.template subview_n<3>(valpos*nderiv);
     636        5159 :           derivs[0] = adjout.deriv[0];
     637        5159 :           derivs[1] = adjout.deriv[1];
     638        5159 :           derivs[2] = adjout.deriv[2];
     639       51590 :           for(unsigned j=6; j<nderiv+3; ++j) {
     640       46431 :             output.derivatives[valpos*nderiv + j - 3] = adjout.deriv[j];
     641             :           }
     642             :         }
     643             :       }
     644       10360 :       if( !actiondata.components ) {
     645        2296 :         continue;
     646             :       }
     647        8064 :       if( actiondata.gatherForceOnColumns ) {
     648       16128 :         for(int ii=1; ii<4; ++ii) {
     649       12096 :           auto derivs  = output.derivatives.template subview_n<1>(valpos*nderiv+ii*nderiv);
     650       12096 :           derivs[0] = 1.0;
     651             :         }
     652             :       } else {
     653       16128 :         for(int ii=1; ii<4; ++ii) {
     654       12096 :           auto derivs  = output.derivatives.template subview_n<4>(valpos*nderiv+ii*nderiv);
     655       12096 :           derivs[0] = -1.0;
     656       12096 :           derivs[1] = -atoms[i][0];
     657       12096 :           derivs[2] = -atoms[i][1];
     658       12096 :           derivs[3] = -atoms[i][2];
     659             :         }
     660             :       }
     661             :     }
     662             :   } else {
     663    22722581 :     for(unsigned i=0; i<nneigh; ++i ) {
     664    21648777 :       adjinp.pos = Vector(atoms[i][0],atoms[i][1],atoms[i][2]);
     665    21648777 :       const std::size_t valpos = static_cast<std::size_t>(i) * ncomponents;
     666    21648777 :       MatrixOutput adjout{View<double,1>{&output.values[valpos]},
     667    21648777 :                           View{output.derivatives.data() + valpos*nderiv,nderiv}};
     668    21648777 :       CV::calculateWeight( actiondata.matrixdata, adjinp, adjout );
     669    21648777 :       if( !actiondata.components ) {
     670    19738267 :         continue ;
     671             :       }
     672     6023474 :       output.values[valpos+1] = atoms[i][0];
     673     6023474 :       output.values[valpos+2] = atoms[i][1];
     674     6023474 :       output.values[valpos+3] = atoms[i][2];
     675     6023474 :       if( input.noderiv ) {
     676     4112964 :         continue ;
     677             :       }
     678             :       //sugar for not having to repeat [valpos*nderiv+something]
     679     7642040 :       for(int ii=1; ii<4; ++ii) {
     680     5731530 :         auto derivs  = output.derivatives.template subview_n<5>(valpos*nderiv+ii*nderiv);
     681     5731530 :         derivs[0] = -1.0;
     682     5731530 :         derivs[1] =  1.0;
     683     5731530 :         derivs[2] = -atoms[i][0];
     684     5731530 :         derivs[3] = -atoms[i][1];
     685     5731530 :         derivs[4] = -atoms[i][2];
     686             :       }
     687             :     }
     688             :   }
     689     1074691 : }
     690             : 
     691             : template <class CV, typename myPTM>
     692       11085 : void AdjacencyMatrixBase<CV, myPTM>::applyNonZeroRankForces( std::vector<double>& outforces ) {
     693       11085 :   taskmanager.applyForces( outforces );
     694       11085 :   if( no_thread_gather ) {
     695          25 :     getColumnBookeepingArrays( taskmanager.getActionInput().outmat );
     696          25 :     taskmanager.getActionInput().gatherForceOnColumns = gatherForceOnColumns = true;
     697          25 :     taskmanager.setNForceScalars( getNumberOfComponents()*maxcolsize );
     698          25 :     taskmanager.applyForces( outforces, false );
     699          25 :     taskmanager.setNForceScalars( getNumberOfComponents()*getPntrToComponent(0)->getNumberOfColumns() );
     700          25 :     taskmanager.getActionInput().gatherForceOnColumns = gatherForceOnColumns = false;
     701             :   }
     702       11085 : }
     703             : 
     704             : template <class CV, typename myPTM>
     705      344086 : int AdjacencyMatrixBase<CV, myPTM>::getNumberOfValuesPerTask( std::size_t task_index,
     706             :     const AdjacencyMatrixData<CV>& actiondata ) {
     707      344086 :   return actiondata.outmat[task_index*(1+actiondata.outmat.ncols)];
     708             : }
     709             : 
     710             : template <class CV, typename myPTM>
     711     5524898 : void AdjacencyMatrixBase<CV, myPTM>::getForceIndices( const std::size_t task_index,
     712             :     const std::size_t colno,
     713             :     const std::size_t ntotal_force,
     714             :     const AdjacencyMatrixData<CV>& actiondata,
     715             :     const ParallelActionsInput& /*input*/,
     716             :     ForceIndexHolder force_indices ) {
     717             : 
     718             :   // Map a (task, column) pair onto the contiguous force-index ranges used by
     719             :   // the parallel force accumulation code.
     720             :   //
     721             :   // The mapping depends on how partial thread results are gathered:
     722             :   // 1) standard gather (!no_thread_gather): use the 3-list decomposition;
     723             :   // 2) no-thread-gather + gather-on-columns: use per-column contiguous blocks;
     724             :   // 3) no-thread-gather + default layout: use the regular component-major layout.
     725             :   //
     726             :   // `ntotal_force` is the global scalar-force extent for the current layout and
     727             :   // is used to offset into the correct component/column segment.
     728     5524898 :   if( !actiondata.no_thread_gather ) {
     729             :     // Standard threaded gather path: each logical task is expanded into three
     730             :     // sub-tasks (three-list bookkeeping), hence the 3*task_index conversion.
     731     5514580 :     const auto three_task_index= 3 * task_index;
     732     5514580 :     force_indices.indices[0][0] = three_task_index;
     733     5514580 :     force_indices.indices[0][1] = three_task_index + 1;
     734     5514580 :     force_indices.indices[0][2] = three_task_index + 2;
     735             : 
     736     5514580 :     const unsigned myatom = 3 * (actiondata.outmat[ task_index*(1+actiondata.outmat.ncols) + 1 + colno] + actiondata.fbsize);
     737     5514580 :     force_indices.indices[0][3] = myatom;
     738     5514580 :     force_indices.indices[0][4] = myatom + 1;
     739     5514580 :     force_indices.indices[0][5] = myatom + 2;
     740     5514580 :     if( actiondata.components ) {
     741     1910510 :       force_indices.indices[1][0] = three_task_index;
     742     1910510 :       force_indices.indices[1][1] = myatom;
     743     1910510 :       force_indices.indices[2][0] = three_task_index + 1;
     744     1910510 :       force_indices.indices[2][1] = myatom + 1;
     745     1910510 :       force_indices.indices[3][0] = three_task_index + 2;
     746     1910510 :       force_indices.indices[3][1] = myatom + 2;
     747             :     }
     748             : 
     749     5514580 :     force_indices.threadsafe_derivatives_end[0] = 0;
     750             :     unsigned n = 6;
     751     5514580 :     if( actiondata.natoms_per_three_list>0 ) {
     752        4065 :       const unsigned n3neigh = actiondata.nlist_three[task_index];
     753        4065 :       const unsigned fstart3 = actiondata.nlists
     754        4065 :                                + task_index*(1 + actiondata.natoms_per_three_list);
     755      500948 :       for(unsigned j=1; j<n3neigh; ++j) {
     756      496883 :         unsigned my3atom = 3 * actiondata.nlist_three[fstart3 + j];
     757      496883 :         force_indices.indices[0][n  ] = my3atom;
     758      496883 :         force_indices.indices[0][n+1] = my3atom + 1;
     759      496883 :         force_indices.indices[0][n+2] = my3atom + 2;
     760      496883 :         n += 3;
     761             :       }
     762             :     }
     763     5514580 :     const unsigned virstart = ntotal_force - 9;
     764     5514580 :     force_indices.indices[0][n  ] = virstart + 0;
     765     5514580 :     force_indices.indices[0][n+1] = virstart + 1;
     766     5514580 :     force_indices.indices[0][n+2] = virstart + 2;
     767     5514580 :     force_indices.indices[0][n+3] = virstart + 3;
     768     5514580 :     force_indices.indices[0][n+4] = virstart + 4;
     769     5514580 :     force_indices.indices[0][n+5] = virstart + 5;
     770     5514580 :     force_indices.indices[0][n+6] = virstart + 6;
     771     5514580 :     force_indices.indices[0][n+7] = virstart + 7;
     772     5514580 :     force_indices.indices[0][n+8] = virstart + 8;
     773             : 
     774     5514580 :     force_indices.tot_indices[0] = n + 9;
     775     5514580 :     if( actiondata.components ) {
     776     1910510 :       force_indices.threadsafe_derivatives_end[1] = 0;
     777     1910510 :       force_indices.threadsafe_derivatives_end[2] = 0;
     778     1910510 :       force_indices.threadsafe_derivatives_end[3] = 0;
     779     1910510 :       force_indices.indices[1][2] = virstart;
     780     1910510 :       force_indices.indices[1][3] = virstart + 1;
     781     1910510 :       force_indices.indices[1][4] = virstart + 2;
     782     1910510 :       force_indices.indices[2][2] = virstart + 3;
     783     1910510 :       force_indices.indices[2][3] = virstart + 4;
     784     1910510 :       force_indices.indices[2][4] = virstart + 5;
     785     1910510 :       force_indices.indices[3][2] = virstart + 6;
     786     1910510 :       force_indices.indices[3][3] = virstart + 7;
     787     1910510 :       force_indices.indices[3][4] = virstart + 8;
     788     1910510 :       force_indices.tot_indices[1] = 5;
     789     1910510 :       force_indices.tot_indices[2] = 5;
     790     1910510 :       force_indices.tot_indices[3] = 5;
     791             :     }
     792       10318 :   } else if( actiondata.gatherForceOnColumns ) {
     793             :     // Alternative no-thread-gather layout: forces are grouped by matrix columns.
     794             :     // This branch computes indices into column-contiguous storage to reduce
     795             :     // gather/scatter overhead when operating on sparse column subsets.
     796        5159 :     const auto three_task_index = 3 * (task_index+actiondata.fbsize);
     797        5159 :     force_indices.indices[0][0] = three_task_index;
     798        5159 :     force_indices.indices[0][1] = three_task_index + 1;
     799        5159 :     force_indices.indices[0][2] = three_task_index + 2;
     800        5159 :     force_indices.threadsafe_derivatives_end[0] = 3;
     801        5159 :     force_indices.tot_indices[0] = 3;
     802        5159 :     if( actiondata.components ) {
     803        4032 :       force_indices.indices[1][0] = three_task_index;
     804        4032 :       force_indices.indices[2][0] = three_task_index + 1;
     805        4032 :       force_indices.indices[3][0] = three_task_index + 2;
     806        4032 :       force_indices.threadsafe_derivatives_end[1] = 1;
     807        4032 :       force_indices.threadsafe_derivatives_end[2] = 1;
     808        4032 :       force_indices.threadsafe_derivatives_end[3] = 1;
     809        4032 :       force_indices.tot_indices[1] = 1;
     810        4032 :       force_indices.tot_indices[2] = 1;
     811        4032 :       force_indices.tot_indices[3] = 1;
     812             :     }
     813             :   } else {
     814        5159 :     const auto three_task_index= 3 * task_index;
     815        5159 :     force_indices.indices[0][0] = three_task_index;
     816        5159 :     force_indices.indices[0][1] = three_task_index + 1;
     817        5159 :     force_indices.indices[0][2] = three_task_index + 2;
     818        5159 :     force_indices.threadsafe_derivatives_end[0] = 3;
     819        5159 :     const unsigned virstart = ntotal_force - 9;
     820        5159 :     force_indices.indices[0][3] = virstart + 0;
     821        5159 :     force_indices.indices[0][4] = virstart + 1;
     822        5159 :     force_indices.indices[0][5] = virstart + 2;
     823        5159 :     force_indices.indices[0][6] = virstart + 3;
     824        5159 :     force_indices.indices[0][7] = virstart + 4;
     825        5159 :     force_indices.indices[0][8] = virstart + 5;
     826        5159 :     force_indices.indices[0][9] = virstart + 6;
     827        5159 :     force_indices.indices[0][10] = virstart + 7;
     828        5159 :     force_indices.indices[0][11] = virstart + 8;
     829        5159 :     force_indices.tot_indices[0] = 12;
     830        5159 :     if( actiondata.components ) {
     831        4032 :       force_indices.indices[1][0] = three_task_index;
     832        4032 :       force_indices.indices[2][0] = three_task_index + 1;
     833        4032 :       force_indices.indices[3][0] = three_task_index + 2;
     834        4032 :       force_indices.threadsafe_derivatives_end[1] = 1;
     835        4032 :       force_indices.threadsafe_derivatives_end[2] = 1;
     836        4032 :       force_indices.threadsafe_derivatives_end[3] = 1;
     837        4032 :       force_indices.indices[1][1] = virstart;
     838        4032 :       force_indices.indices[1][2] = virstart + 1;
     839        4032 :       force_indices.indices[1][3] = virstart + 2;
     840        4032 :       force_indices.indices[2][1] = virstart + 3;
     841        4032 :       force_indices.indices[2][2] = virstart + 4;
     842        4032 :       force_indices.indices[2][3] = virstart + 5;
     843        4032 :       force_indices.indices[3][1] = virstart + 6;
     844        4032 :       force_indices.indices[3][2] = virstart + 7;
     845        4032 :       force_indices.indices[3][3] = virstart + 8;
     846        4032 :       force_indices.tot_indices[1] = 4;
     847        4032 :       force_indices.tot_indices[2] = 4;
     848        4032 :       force_indices.tot_indices[3] = 4;
     849             :     }
     850             :   }
     851     5524898 : }
     852             : 
     853             : } // namespace adjmat
     854             : } // namespace PLMD
     855             : 
     856             : #endif //__PLUMED_adjmat_AdjacencyMatrixBase_h

Generated by: LCOV version 1.16