LCOV - code coverage report
Current view: top level - adjmat - TorsionsMatrix.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 161 175 92.0 %
Date: 2025-12-04 11:19:34 Functions: 10 11 90.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 "core/ParallelTaskManager.h"
      25             : #include "tools/Torsion.h"
      26             : 
      27             : //+PLUMEDOC MCOLVAR TORSIONS_MATRIX
      28             : /*
      29             : Calculate the matrix of torsions between two vectors of molecules
      30             : 
      31             : This action was implemented to ensure that we can calculate the [SMAC](SMAC.md) collective variable that is discussed in
      32             : [this paper](https://www.sciencedirect.com/science/article/abs/pii/S0009250914004503?via%3Dihub). This particular action
      33             : tracks the relative orientations for all the pairs of molecules in a set much like the variables described in the crystdistrib module.
      34             : 
      35             : The orientations of molecules can be specified using either [PLANE](PLANE.md) or [DISTANCE](DISTANCE.md).  The example below shows how you can use
      36             : internal vectors connecting two atoms in the molecules to define the orientation of that molecule.  Three of these internal
      37             : vectors are calculated using a DISTANCE command in the input below.  The matrix of torsional angles between these various
      38             : vectors is then computed:
      39             : 
      40             : ```plumed
      41             : d1: DISTANCE ATOMS1=1,5 ATOMS2=11,15 ATOMS3=21,25 COMPONENTS
      42             : s: VSTACK ARG=d1.x,d1.y,d1.z
      43             : sT: TRANSPOSE ARG=s
      44             : m: TORSIONS_MATRIX ARG=s,sT POSITIONS1=1,11,21 POSITIONS2=1,11,21
      45             : PRINT ARG=m FILE=matrix
      46             : ```
      47             : 
      48             : In this example, the torsional angle in element $(1,2)$ of the matrix with label `m` is the angle between the plane containing atoms 1,5 and 10 and the plane
      49             : connecting atoms 1,10 and 15.  In other words, the elements in this matrix are the torsional angles between the vectors in the input matrices
      50             : around the vector connecting the corresponding atomic positions that are specified using the `POSTIONS` keyword.
      51             : 
      52             : You can also calculate a matrix of torsional angles between two different groups of molecules by using an input like the one below:
      53             : 
      54             : ```plumed
      55             : pA: PLANE ATOMS1=1,2,3 ATOMS2=11,12,13
      56             : sA: VSTACK ARG=pA.x,pA.y,pA.z
      57             : pB: PLANE ATOMS1=21,22,23 ATOMS2=31,32,33 ATOMS3=41,42,43
      58             : sB: VSTACK ARG=pB.x,pB.y,pB.z
      59             : sBT: TRANSPOSE ARG=sB
      60             : m: TORSIONS_MATRIX ARG=sA,sBT POSITIONS1=1,11 POSITIONS2=21,31,41
      61             : PRINT ARG=m FILE=matrix
      62             : ```
      63             : 
      64             : In this example, the orientations of the molecules are specified using the [PLANE](PLANE.md) action and is given by a normal to the plane containing the three atoms from the molecule
      65             : that was specified.  The final output is $2 \times 3$ matrix that contains all the torsional angles between the molecules defined by the two PLANE actions.
      66             : 
      67             : ## Performance considerations
      68             : 
      69             : Suppose that you are using an input like the one shown below to calculate the average torsion angle between neighboring molecules:
      70             : 
      71             : ```plumed
      72             : # Notice that in a realistic version of this calculation you would likely
      73             : # by calculating the orientations of many more molecules using this command
      74             : d: DISTANCE COMPONENTS ATOMS1=1,5 ATOMS2=11,15 ATOMS3=21,25 ATOMS4=31,35
      75             : s: VSTACK ARG=d.x,d.y,d.z
      76             : sT: TRANSPOSE ARG=s
      77             : #  Calculate a contact matrix in which element i,j is 1 if molecules
      78             : # i and j are neighbors.
      79             : c: CONTACT_MATRIX GROUP=1,11,21,35 SWITCH={RATIONAL R_0=0.1 D_MAX=0.3}
      80             : # Now calculate all the torsions
      81             : t: TORSIONS_MATRIX ARG=s,sT POSITIONS1=1,11,21,31 POSITIONS2=1,11,21,31
      82             : # And the product between the contact matrix and the torsions
      83             : tc: CUSTOM ARG=c,t FUNC=x*y PERIODIC=NO
      84             : # Total of all the torsional angles in the first coordination sphere
      85             : tsum: SUM ARG=tc PERIODIC=NO
      86             : # Total number of neighbouring atoms
      87             : bsum: SUM ARG=c PERIODIC=NO
      88             : # And finally the average torsion angle
      89             : avt: CUSTOM ARG=tc,c FUNC=x/y PERIODIC=NO
      90             : ```
      91             : 
      92             : If you have a large number of molecules the most expensive part of this calculation will be the evalulation of the TORSIONS_MATRIX as you need to evaluate one torsion anlge for every pair of molecules.
      93             : Furthermore, this computation is unecessary as most pairs of molecules will __not__ be neighbors.  In other words, for the majority of the molecular pairs element the corresponding element of the
      94             : [CONTACT_MATRIX](CONTACT_MATRIX.md) will be zero.  Consequently, when you compute the the corresponding element `tc` by multiplying the torsion $i,j$ by the crresponding $i,j$ element of the
      95             : [CONTACT_MATRIX](CONTACT_MATRIX.md) you will get zero.
      96             : 
      97             : Thankfully PLUMED allows you to exploit this fact through the MASK keyword as illustrated below:
      98             : 
      99             : ```plumed
     100             : # Notice that in a realistic version of this calculation you would likely
     101             : # by calculating the orientations of many more molecules using this command
     102             : d: DISTANCE COMPONENTS ATOMS1=1,5 ATOMS2=11,15 ATOMS3=21,25 ATOMS4=31,35
     103             : s: VSTACK ARG=d.x,d.y,d.z
     104             : sT: TRANSPOSE ARG=s
     105             : #  Calculate a contact matrix in which element i,j is 1 if molecules
     106             : # i and j are neighbors.
     107             : c: CONTACT_MATRIX GROUP=1,11,21,35 SWITCH={RATIONAL R_0=0.1 D_MAX=0.3}
     108             : # Now calculate all the torsions
     109             : t: TORSIONS_MATRIX ...
     110             :    MASK=c ARG=s,sT
     111             :    POSITIONS1=1,11,21,31 POSITIONS2=1,11,21,31
     112             : ...
     113             : # And the product between the contact matrix and the torsions
     114             : tc: CUSTOM ARG=c,t FUNC=x*y PERIODIC=NO
     115             : # Total of all the torsional angles in the first coordination sphere
     116             : tsum: SUM ARG=tc PERIODIC=NO
     117             : # Total number of neighbouring atoms
     118             : bsum: SUM ARG=c PERIODIC=NO
     119             : # And finally the average torsion angle
     120             : avt: CUSTOM ARG=tc,c FUNC=x/y PERIODIC=NO
     121             : ```
     122             : 
     123             : Adding the instruction `MASK=c` to the TORSIONS_MATRIX command here ensures that element $i,j$ of the matrix `t` is only computed if the corresponding element of `c` is non-zero.  By using this command
     124             : you thus avoid the computational expense associated with evaluating the full set of pairwise torsions.
     125             : 
     126             : */
     127             : //+ENDPLUMEDOC
     128             : 
     129             : namespace PLMD {
     130             : namespace adjmat {
     131             : 
     132           0 : class TorsionsMatrixInput {
     133             : public:
     134             :   RequiredMatrixElements outmat;
     135             : };
     136             : 
     137             : class TorsionsMatrix : public ActionWithMatrix {
     138             : public:
     139             :   using input_type = TorsionsMatrixInput;
     140             :   using PTM = ParallelTaskManager<TorsionsMatrix>;
     141             : private:
     142             :   PTM taskmanager;
     143             : public:
     144             :   static void registerKeywords( Keywords& keys );
     145             :   explicit TorsionsMatrix(const ActionOptions&);
     146             :   unsigned getNumberOfDerivatives() override ;
     147             :   void prepare() override ;
     148             :   void calculate() override ;
     149             :   void applyNonZeroRankForces( std::vector<double>& outforces ) override ;
     150             :   void getInputData( std::vector<double>& inputdata ) const override ;
     151             :   static void performTask( std::size_t task_index,
     152             :                            const TorsionsMatrixInput& actiondata,
     153             :                            ParallelActionsInput& input,
     154             :                            ParallelActionsOutput& output );
     155             :   static int getNumberOfValuesPerTask( std::size_t task_index,
     156             :                                        const TorsionsMatrixInput& actiondata );
     157             :   static void getForceIndices( std::size_t task_index,
     158             :                                std::size_t colno, std::size_t ntotal_force,
     159             :                                const TorsionsMatrixInput& actiondata,
     160             :                                const ParallelActionsInput& input,
     161             :                                ForceIndexHolder force_indices );
     162             : };
     163             : 
     164             : PLUMED_REGISTER_ACTION(TorsionsMatrix,"TORSIONS_MATRIX")
     165             : 
     166          14 : void TorsionsMatrix::registerKeywords( Keywords& keys ) {
     167          14 :   ActionWithMatrix::registerKeywords(keys);
     168          28 :   keys.addInputKeyword("optional","MASK","matrix","a matrix that is used to used to determine which elements of this matrix to compute");
     169          28 :   keys.addInputKeyword("compulsory","ARG","matrix","an Nx3 and a 3xN matrix that contain the bond vectors that you would like to determine the torsion angles between");
     170          14 :   keys.add("atoms","POSITIONS1","the positions to use for the molecules specified using the first argument");
     171          14 :   keys.add("atoms","POSITIONS2","the positions to use for the molecules specified using the second argument");
     172          14 :   PTM::registerKeywords( keys );
     173          28 :   keys.setValueDescription("matrix","the matrix of torsions between the two vectors of input directors");
     174          14 : }
     175             : 
     176           7 : TorsionsMatrix::TorsionsMatrix(const ActionOptions&ao):
     177             :   Action(ao),
     178             :   ActionWithMatrix(ao),
     179           7 :   taskmanager(this) {
     180             :   int nm=getNumberOfMasks();
     181             :   if( nm<0 ) {
     182             :     nm = 0;
     183             :   }
     184           7 :   if( getNumberOfArguments()-nm!=2 ) {
     185           0 :     error("should be two arguments to this action, a matrix and a vector");
     186             :   }
     187           7 :   if( getPntrToArgument(0)->getRank()!=2 || getPntrToArgument(0)->hasDerivatives() ) {
     188           0 :     error("first argument to this action should be a matrix");
     189             :   }
     190           7 :   if( getPntrToArgument(1)->getRank()!=2 || getPntrToArgument(1)->hasDerivatives() ) {
     191           0 :     error("second argument to this action should be a matrix");
     192             :   }
     193           7 :   if( getPntrToArgument(0)->getShape()[1]!=3 || getPntrToArgument(1)->getShape()[0]!=3 ) {
     194           0 :     error("number of columns in first matrix and number of rows in second matrix should equal 3");
     195             :   }
     196             : 
     197             :   std::vector<AtomNumber> atoms_a;
     198          14 :   parseAtomList("POSITIONS1", atoms_a );
     199           7 :   if( atoms_a.size()!=getPntrToArgument(0)->getShape()[0] ) {
     200           0 :     error("mismatch between number of atoms specified using POSITIONS1 and number of arguments in vector input");
     201             :   }
     202           7 :   log.printf("  using positions of these atoms for vectors in first matrix \n");
     203         933 :   for(unsigned int i=0; i<atoms_a.size(); ++i) {
     204         926 :     if ( (i+1) % 25 == 0 ) {
     205          36 :       log.printf("  \n");
     206             :     }
     207         926 :     log.printf("  %d", atoms_a[i].serial());
     208             :   }
     209           7 :   log.printf("\n");
     210             :   std::vector<AtomNumber> atoms_b;
     211          14 :   parseAtomList("POSITIONS2", atoms_b );
     212           7 :   if( atoms_b.size()!=getPntrToArgument(1)->getShape()[1] ) {
     213           0 :     error("mismatch between number of atoms specified using POSITIONS2 and number of arguments in vector input");
     214             :   }
     215           7 :   log.printf("  using positions of these atoms for vectors in second matrix \n");
     216        1225 :   for(unsigned i=0; i<atoms_b.size(); ++i) {
     217        1218 :     if ( (i+1) % 25 == 0 ) {
     218          48 :       log.printf("  \n");
     219             :     }
     220        1218 :     log.printf("  %d", atoms_b[i].serial());
     221        1218 :     atoms_a.push_back( atoms_b[i] );
     222             :   }
     223           7 :   log.printf("\n");
     224           7 :   requestAtoms( atoms_a, false );
     225             : 
     226           7 :   std::vector<std::size_t> shape(2);
     227           7 :   shape[0]=getPntrToArgument(0)->getShape()[0];
     228           7 :   shape[1]=getPntrToArgument(1)->getShape()[1];
     229           7 :   addValue( shape );
     230          14 :   setPeriodic("-pi","pi");
     231             : 
     232           7 :   if( nm>0 ) {
     233           6 :     unsigned iarg = getNumberOfArguments()-1;
     234           6 :     if( getPntrToArgument(iarg)->getRank()!=2
     235           6 :         || getPntrToArgument(0)->hasDerivatives() ) {
     236           0 :       error("argument passed to MASK keyword should be a matrix");
     237             :     }
     238           6 :     if( getPntrToArgument(iarg)->getShape()[0]!=shape[0]
     239           6 :         || getPntrToArgument(iarg)->getShape()[1]!=shape[1] ) {
     240           0 :       error("argument passed to MASK keyword has the wrong shape");
     241             :     }
     242             :   }
     243          14 :   taskmanager.setActionInput( TorsionsMatrixInput() );
     244           7 : }
     245             : 
     246          61 : unsigned TorsionsMatrix::getNumberOfDerivatives() {
     247          61 :   return 3*getNumberOfAtoms()
     248             :          + 9
     249          61 :          + getPntrToArgument(0)->getNumberOfStoredValues()
     250          61 :          + getPntrToArgument(1)->getNumberOfStoredValues();
     251             : }
     252             : 
     253          51 : void TorsionsMatrix::prepare() {
     254          51 :   ActionWithVector::prepare();
     255          51 :   Value* myval = getPntrToComponent(0);
     256          51 :   if( myval->getShape()[0]==getPntrToArgument(0)->getShape()[0]
     257          51 :       && myval->getShape()[1]==getPntrToArgument(1)->getShape()[1] ) {
     258          51 :     return;
     259             :   }
     260           0 :   std::vector<std::size_t> shape(2);
     261           0 :   shape[0]=getPntrToArgument(0)->getShape()[0];
     262           0 :   shape[1]=getPntrToArgument(1)->getShape()[1];
     263           0 :   myval->setShape(shape);
     264           0 :   myval->reshapeMatrixStore( shape[1] );
     265             : }
     266             : 
     267          51 : void TorsionsMatrix::calculate() {
     268          51 :   updateBookeepingArrays( taskmanager.getActionInput().outmat );
     269          51 :   taskmanager.setupParallelTaskManager( 21,
     270          51 :                                         getNumberOfDerivatives()
     271          51 :                                         - getPntrToArgument(0)->getNumberOfStoredValues() );
     272          51 :   taskmanager.runAllTasks();
     273          51 : }
     274             : 
     275          51 : void TorsionsMatrix::getInputData( std::vector<double>& inputdata ) const {
     276          51 :   std::size_t total_data = getPntrToArgument(0)->getNumberOfStoredValues()
     277          51 :                            + getPntrToArgument(1)->getNumberOfStoredValues()
     278          51 :                            + 3*getNumberOfAtoms();
     279             : 
     280          51 :   if( inputdata.size()!=total_data ) {
     281           7 :     inputdata.resize( total_data );
     282             :   }
     283             : 
     284             :   total_data = 0;
     285             :   Value* myarg = getPntrToArgument(0);
     286       16149 :   for(unsigned j=0; j<myarg->getNumberOfStoredValues(); ++j) {
     287       16098 :     inputdata[total_data] = myarg->get(j,false);
     288       16098 :     total_data++;
     289             :   }
     290             :   myarg = getPntrToArgument(1);
     291       25785 :   for(unsigned j=0; j<myarg->getNumberOfStoredValues(); ++j) {
     292       25734 :     inputdata[total_data] = myarg->get(j,false);
     293       25734 :     total_data++;
     294             :   }
     295       13995 :   for(unsigned j=0; j<getNumberOfAtoms(); ++j) {
     296       13944 :     Vector pos( getPosition(j) );
     297       13944 :     inputdata[total_data+0] = pos[0];
     298       13944 :     inputdata[total_data+1] = pos[1];
     299       13944 :     inputdata[total_data+2] = pos[2];
     300       13944 :     total_data += 3;
     301             :   }
     302             : 
     303          51 : }
     304             : 
     305        5544 : void TorsionsMatrix::performTask( std::size_t task_index,
     306             :                                   const TorsionsMatrixInput& actiondata,
     307             :                                   ParallelActionsInput& input,
     308             :                                   ParallelActionsOutput& output ) {
     309        5544 :   std::size_t fstart = task_index*(1+actiondata.outmat.ncols);
     310             :   std::size_t nelements = actiondata.outmat[fstart];
     311        5544 :   auto arg0=ArgumentBookeepingHolder::create( 0, input );
     312        5544 :   auto arg1=ArgumentBookeepingHolder::create( 1, input );
     313        5544 :   std::size_t nargdata = arg1.start + arg1.shape[0]*arg1.ncols;
     314             : 
     315             :   // Get the position and orientation for the first molecule
     316        5544 :   std::size_t atbase = nargdata + 3*task_index;
     317        5544 :   Vector atom1( input.inputdata[atbase],
     318        5544 :                 input.inputdata[atbase+1],
     319        5544 :                 input.inputdata[atbase+2] );
     320        5544 :   std::size_t agbase = arg0.ncols*task_index;
     321        5544 :   Vector v1( input.inputdata[agbase],
     322        5544 :              input.inputdata[agbase+1],
     323        5544 :              input.inputdata[agbase+2] );
     324             : 
     325             :   // Get the distances to all the molecules in the coordination sphere
     326        5544 :   std::vector<Vector> atom2( nelements );
     327     1247276 :   for(unsigned i=0; i<nelements; ++i) {
     328             :     std::size_t at2base = nargdata
     329             :                           + 3*arg0.shape[0]
     330     1241732 :                           + 3*actiondata.outmat[fstart+1+i];
     331     1241732 :     atom2[i] = Vector( input.inputdata[at2base],
     332     1241732 :                        input.inputdata[at2base+1],
     333     1241732 :                        input.inputdata[at2base+2] ) - atom1;
     334             :   }
     335        5544 :   input.pbc->apply( atom2, nelements );
     336             : 
     337             :   // Now compute all the torsions
     338             :   Torsion t;
     339             :   Vector dv1, dconn, dv2 ;
     340     1247276 :   for(unsigned i=0; i<nelements; ++i) {
     341     1241732 :     if( atom2[i].modulo2()<epsilon ) {
     342          92 :       if( !input.noderiv ) {
     343           2 :         output.derivatives.subview_n<21>( 21*i ).zero();
     344             :       }
     345     1240498 :       continue ;
     346             :     }
     347             : 
     348     1241640 :     std::size_t ag2base = arg1.start + actiondata.outmat[fstart+1+i];
     349     1241640 :     Vector v2(input.inputdata[ag2base],
     350     1241640 :               input.inputdata[ag2base+arg1.ncols],
     351     1241640 :               input.inputdata[ag2base+2*arg1.ncols] );
     352     1241640 :     output.values[i] = t.compute( v1, atom2[i], v2, dv1, dconn, dv2 );
     353             : 
     354     1241640 :     if( input.noderiv ) {
     355     1240406 :       continue ;
     356             :     }
     357             : 
     358        1234 :     std::size_t base = + 21*i;
     359             :     output.derivatives.subview_n<3>(base) = dv1;
     360        1234 :     output.derivatives.subview_n<3>(base+3) = dv2;
     361        1234 :     output.derivatives.subview_n<3>(base+6) = -dconn;
     362        1234 :     output.derivatives.subview_n<3>(base+9) = dconn;
     363             : 
     364        1234 :     Tensor vir( -extProduct( atom2[i], dconn ) );
     365        1234 :     View2D<double,3,3> virial( output.derivatives.data() + base + 12 );
     366        4936 :     for(unsigned j=0; j<3; ++j) {
     367       14808 :       for(unsigned k=0; k<3; ++k) {
     368       11106 :         virial[j][k] = vir[j][k];
     369             :       }
     370             :     }
     371             :   }
     372             : 
     373        5544 : }
     374             : 
     375          23 : void TorsionsMatrix::applyNonZeroRankForces( std::vector<double>& outforces ) {
     376          23 :   taskmanager.applyForces( outforces );
     377          23 : }
     378             : 
     379         178 : int TorsionsMatrix::getNumberOfValuesPerTask( std::size_t task_index,
     380             :     const TorsionsMatrixInput& actiondata ) {
     381         178 :   std::size_t fstart = task_index*(1+actiondata.outmat.ncols);
     382         178 :   return actiondata.outmat[fstart];
     383             : }
     384             : 
     385        1236 : void TorsionsMatrix::getForceIndices( std::size_t task_index,
     386             :                                       std::size_t colno,
     387             :                                       std::size_t ntotal_force,
     388             :                                       const TorsionsMatrixInput& actiondata,
     389             :                                       const ParallelActionsInput& input,
     390             :                                       ForceIndexHolder force_indices ) {
     391        1236 :   auto arg0=ArgumentBookeepingHolder::create( 0, input );
     392        1236 :   auto arg1=ArgumentBookeepingHolder::create( 1, input );
     393        1236 :   std::size_t fstart = task_index*(1+actiondata.outmat.ncols);
     394        1236 :   std::size_t arg1start = task_index*arg0.ncols;
     395        1236 :   force_indices.indices[0][0] = arg1start;
     396        1236 :   force_indices.indices[0][1] = arg1start + 1;
     397        1236 :   force_indices.indices[0][2] = arg1start + 2;
     398        1236 :   force_indices.threadsafe_derivatives_end[0] = 3;
     399        1236 :   force_indices.indices[0][3] = arg1.start
     400        1236 :                                 + actiondata.outmat[fstart+1+colno];
     401        1236 :   force_indices.indices[0][4] = arg1.start
     402        1236 :                                 + actiondata.outmat[fstart+1+colno]
     403        1236 :                                 + arg1.ncols;
     404        1236 :   force_indices.indices[0][5] = arg1.start
     405        1236 :                                 + actiondata.outmat[fstart+1+colno]
     406        1236 :                                 + 2*arg1.ncols;
     407        1236 :   std::size_t atomstart = arg1.start + arg1.shape[0]*arg1.ncols;
     408        1236 :   std::size_t atom1start = atomstart + 3*task_index;
     409        1236 :   force_indices.indices[0][6] = atom1start;
     410        1236 :   force_indices.indices[0][7] = atom1start + 1;
     411        1236 :   force_indices.indices[0][8] = atom1start + 2;
     412             :   std::size_t atom2start = atomstart
     413             :                            + 3*arg0.shape[0]
     414        1236 :                            + 3*actiondata.outmat[fstart+1+colno];
     415        1236 :   force_indices.indices[0][9] = atom2start;
     416        1236 :   force_indices.indices[0][10] = atom2start + 1;
     417        1236 :   force_indices.indices[0][11] = atom2start + 2;
     418             :   std::size_t n=12;
     419       12360 :   for(unsigned j=ntotal_force-9; j<ntotal_force; ++j) {
     420       11124 :     force_indices.indices[0][n] = j;
     421       11124 :     ++n;
     422             :   }
     423        1236 :   force_indices.tot_indices[0] = 21;
     424        1236 : }
     425             : 
     426             : }
     427             : }

Generated by: LCOV version 1.16