LCOV - code coverage report
Current view: top level - secondarystructure - SecondaryStructureRMSD.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 222 237 93.7 %
Date: 2025-04-14 15:25:36 Functions: 9 10 90.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2013-2020 The plumed team
       3             :    (see the PEOPLE file at the root of the distribution for a list of names)
       4             : 
       5             :    See http://www.plumed.org for more information.
       6             : 
       7             :    This file is part of plumed, version 2.
       8             : 
       9             :    plumed is free software: you can redistribute it and/or modify
      10             :    it under the terms of the GNU Lesser General Public License as published by
      11             :    the Free Software Foundation, either version 3 of the License, or
      12             :    (at your option) any later version.
      13             : 
      14             :    plumed is distributed in the hope that it will be useful,
      15             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      16             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17             :    GNU Lesser General Public License for more details.
      18             : 
      19             :    You should have received a copy of the GNU Lesser General Public License
      20             :    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : #include "SecondaryStructureRMSD.h"
      23             : #include "core/PlumedMain.h"
      24             : #include "core/ActionSet.h"
      25             : #include "core/GenericMolInfo.h"
      26             : #include "core/ActionShortcut.h"
      27             : #include "core/ActionRegister.h"
      28             : 
      29             : //+PLUMEDOC MCOLVAR SECONDARY_STRUCTURE_RMSD
      30             : /*
      31             : Calclulate the distance between segments of a protein and a reference structure of interest
      32             : 
      33             : This action is used in the shortcuts [ALPHARMSD](ALPHARMSD.md), [ANTIBETARMSD](ANTIBETARMSD.md) and [PARABETARMSD](PARABETARMSD.md).  It calculates a
      34             : vector of RMSD values between a single reference multiple configurations and the instantaneous
      35             : positions of various groups of atoms.  For example, in the following input we define a single set of reference
      36             : set of coordinates for 3 atoms.
      37             : 
      38             : ```plumed
      39             : c1: SECONDARY_STRUCTURE_RMSD BONDLENGTH=0.17 STRUCTURE1=1,0,0,0,1,0,0,0,1 SEGMENT1=1,2,3 SEGMENT2=4,5,6 SEGMENT3=7,8,9 SEGMENT4=10,11,12 TYPE=OPTIMAL
      40             : PRINT ARG=c1 FILE=colvar
      41             : ```
      42             : 
      43             : A four dimensional vector is then returned that contains the RMSD distances between the 4 sets of atoms that were specified using the `SEGMENT` keywords
      44             : and the reference coordinates.  Notice that you can use multiple instances of the `STRUCTURE` keyword.  In general the the number of vectors output
      45             : is equal to the number of times the `STRUCTURE` keyword is used.
      46             : 
      47             : */
      48             : //+ENDPLUMEDOC
      49             : 
      50             : namespace PLMD {
      51             : namespace secondarystructure {
      52             : 
      53             : PLUMED_REGISTER_ACTION(SecondaryStructureRMSD,"SECONDARY_STRUCTURE_RMSD");
      54             : 
      55          38 : bool SecondaryStructureRMSD::readShortcutWords( std::string& ltmap, ActionShortcut* action ) {
      56          76 :   action->parse("LESS_THAN",ltmap);
      57          38 :   if( ltmap.length()==0 ) {
      58             :     std::string nn, mm, d_0, r_0;
      59           6 :     action->parse("R_0",r_0);
      60           3 :     if( r_0.length()==0 ) {
      61             :       r_0="0.08";
      62             :     }
      63           3 :     action->parse("NN",nn);
      64           3 :     action->parse("D_0",d_0);
      65           3 :     action->parse("MM",mm);
      66           6 :     ltmap = "RATIONAL R_0=" + r_0 + " D_0=" + d_0 + " NN=" + nn + " MM=" + mm;
      67             :     return false;
      68             :   }
      69             :   return true;
      70             : }
      71             : 
      72          38 : void SecondaryStructureRMSD::expandShortcut( const bool& uselessthan, const std::string& labout, const std::string& labin, const std::string& ltmap, ActionShortcut* action ) {
      73          76 :   action->readInputLine( labout + "_lt: LESS_THAN ARG=" + labin + " SWITCH={" + ltmap  +"}");
      74          38 :   if( uselessthan ) {
      75          70 :     action->readInputLine( labout + "_lessthan: SUM ARG=" + labout + "_lt PERIODIC=NO");
      76             :   } else {
      77           6 :     action->readInputLine( labout + ": SUM ARG=" + labout + "_lt PERIODIC=NO");
      78             :   }
      79          38 : }
      80             : 
      81         288 : void SecondaryStructureRMSD::registerKeywords( Keywords& keys ) {
      82         288 :   ActionWithVector::registerKeywords( keys );
      83         288 :   keys.addFlag("NOPBC",false,"ignore the periodic boundary conditions");
      84         288 :   keys.add("residues","RESIDUES","this command is used to specify the set of residues that could conceivably form part of the secondary structure. "
      85             :            "It is possible to use residues numbers as the various chains and residues should have been identified else using an instance of the "
      86             :            "\\ref MOLINFO action. If you wish to use all the residues from all the chains in your system you can do so by "
      87             :            "specifying all. Alternatively, if you wish to use a subset of the residues you can specify the particular residues "
      88             :            "you are interested in as a list of numbers. Please be aware that to form secondary structure elements your chain "
      89             :            "must contain at least N residues, where N is dependent on the particular secondary structure you are interested in. "
      90             :            "As such if you define portions of the chain with fewer than N residues the code will crash.");
      91         288 :   keys.add("atoms","ATOMS","this is the full list of atoms that we are investigating");
      92         288 :   keys.add("numbered","SEGMENT","this is the lists of atoms in the segment that are being considered");
      93         288 :   keys.add("compulsory","BONDLENGTH","the length to use for bonds");
      94         288 :   keys.add("numbered","STRUCTURE","the reference structure");
      95         288 :   keys.add("compulsory","TYPE","DRMSD","the manner in which RMSD alignment is performed. Should be OPTIMAL, SIMPLE or DRMSD. "
      96             :            "For more details on the OPTIMAL and SIMPLE methods see \\ref RMSD. For more details on the "
      97             :            "DRMSD method see \\ref DRMSD.");
      98         288 :   keys.add("optional","STRANDS_CUTOFF","If in a segment of protein the two strands are further apart then the calculation "
      99             :            "of the actual RMSD is skipped as the structure is very far from being beta-sheet like. "
     100             :            "This keyword speeds up the calculation enormously when you are using the LESS_THAN option. "
     101             :            "However, if you are using some other option, then this cannot be used");
     102         288 :   keys.add("optional","CUTOFF_ATOMS","the pair of atoms that are used to calculate the strand cutoff");
     103         288 :   keys.addFlag("VERBOSE",false,"write a more detailed output");
     104         288 :   keys.add("optional","LESS_THAN","calculate the number of a residue segments that are within a certain target distance of this secondary structure type. "
     105             :            "This quantity is calculated using \\f$\\sum_i \\sigma(s_i)\\f$, where \\f$\\sigma(s)\\f$ is a \\ref switchingfunction.");
     106         576 :   keys.linkActionInDocs("LESS_THAN","LESS_THAN");
     107         288 :   keys.add("optional","R_0","The r_0 parameter of the switching function.");
     108         288 :   keys.add("compulsory","D_0","0.0","The d_0 parameter of the switching function");
     109         288 :   keys.add("compulsory","NN","8","The n parameter of the switching function");
     110         288 :   keys.add("compulsory","MM","12","The m parameter of the switching function");
     111         288 :   keys.add("hidden","NO_ACTION_LOG","suppresses printing from action on the log");
     112         576 :   keys.setValueDescription("vector","a vector containing the rmsd distance between each of the residue segments and the reference structure");
     113         576 :   keys.addOutputComponent("struct","default","vector","the vectors containing the rmsd distances between the residues and each of the reference structures");
     114         576 :   keys.addOutputComponent("lessthan","default","scalar","the number blocks of residues that have an RMSD from the secondary structure that is less than the threshold");
     115         288 :   keys.needsAction("SECONDARY_STRUCTURE_RMSD");
     116         288 :   keys.needsAction("LESS_THAN");
     117         288 :   keys.needsAction("SUM");
     118         288 :   keys.addDOI("10.1021/ct900202f");
     119         288 : }
     120             : 
     121          38 : void SecondaryStructureRMSD::readBackboneAtoms( ActionShortcut* action, PlumedMain& plumed, const std::string& moltype, std::vector<unsigned>& chain_lengths, std::string& all_atoms ) {
     122          38 :   auto* moldat=plumed.getActionSet().selectLatest<GenericMolInfo*>(action);
     123          38 :   if( ! moldat ) {
     124           0 :     action->error("Unable to find MOLINFO in input");
     125             :   }
     126             : 
     127             :   std::vector<std::string> resstrings;
     128          76 :   action->parseVector( "RESIDUES", resstrings );
     129          38 :   if(resstrings.size()==0) {
     130           0 :     action->error("residues are not defined, check the keyword RESIDUES");
     131          76 :   } else if( Tools::caseInSensStringCompare(resstrings[0], "all") ) {
     132             :     resstrings[0]="all";
     133          36 :     action->log.printf("  examining all possible secondary structure combinations\n");
     134             :   } else {
     135           2 :     action->log.printf("  examining secondary structure in residue positions : %s ",resstrings[0].c_str() );
     136           3 :     for(unsigned i=1; i<resstrings.size(); ++i) {
     137           1 :       action->log.printf(", %s",resstrings[i].c_str() );
     138             :     }
     139           2 :     action->log.printf("\n");
     140             :   }
     141             :   std::vector< std::vector<AtomNumber> > backatoms;
     142          38 :   moldat->getBackbone( resstrings, moltype, backatoms );
     143             : 
     144          38 :   chain_lengths.resize( backatoms.size() );
     145         587 :   for(unsigned i=0; i<backatoms.size(); ++i) {
     146         549 :     chain_lengths[i]=backatoms[i].size();
     147       22569 :     for(unsigned j=0; j<backatoms[i].size(); ++j) {
     148             :       std::string bat_str;
     149       22020 :       Tools::convert( backatoms[i][j].serial(), bat_str );
     150       22020 :       if( i==0 && j==0 ) {
     151          76 :         all_atoms = "ATOMS=" + bat_str;
     152             :       } else {
     153       43964 :         all_atoms += "," + bat_str;
     154             :       }
     155             :     }
     156             :   }
     157          38 : }
     158             : 
     159             : 
     160          38 : SecondaryStructureRMSD::SecondaryStructureRMSD(const ActionOptions&ao):
     161             :   Action(ao),
     162             :   ActionWithVector(ao),
     163          38 :   nopbc(false),
     164          38 :   align_strands(false),
     165          38 :   s_cutoff2(0),
     166          38 :   align_atom_1(0),
     167          38 :   align_atom_2(0) {
     168          38 :   if( plumed.usingNaturalUnits() ) {
     169           0 :     error("cannot use this collective variable when using natural units");
     170             :   }
     171             : 
     172          38 :   parse("TYPE",alignType);
     173          38 :   parseFlag("NOPBC",nopbc);
     174          38 :   log.printf("  distances from secondary structure elements are calculated using %s algorithm\n",alignType.c_str() );
     175          76 :   log<<"  Bibliography "<<plumed.cite("Pietrucci and Laio, J. Chem. Theory Comput. 5, 2197 (2009)");
     176          38 :   log<<"\n";
     177             : 
     178          38 :   parseFlag("VERBOSE",verbose_output);
     179             : 
     180          38 :   if( keywords.exists("STRANDS_CUTOFF") ) {
     181          38 :     double s_cutoff = 0;
     182          38 :     parse("STRANDS_CUTOFF",s_cutoff);
     183          38 :     align_strands=true;
     184          38 :     if( s_cutoff>0) {
     185          32 :       log.printf("  ignoring contributions from strands that are more than %f apart\n",s_cutoff);
     186             :       std::vector<unsigned> cutatoms;
     187          64 :       parseVector("CUTOFF_ATOMS",cutatoms);
     188          32 :       if( cutatoms.size()==2 ) {
     189          32 :         align_atom_1=cutatoms[0];
     190          32 :         align_atom_2=cutatoms[1];
     191             :       } else {
     192           0 :         error("did not find CUTOFF_ATOMS in input");
     193             :       }
     194             :     }
     195          38 :     s_cutoff2=s_cutoff*s_cutoff;
     196             :   }
     197             : 
     198             :   // Read in the atoms
     199             :   std::vector<AtomNumber> all_atoms;
     200          38 :   parseAtomList("ATOMS",all_atoms);
     201          38 :   requestAtoms( all_atoms );
     202             : 
     203      160063 :   for(unsigned i=1;; ++i) {
     204             :     std::vector<unsigned> newatoms;
     205      320202 :     if( !parseNumberedVector("SEGMENT",i,newatoms) ) {
     206             :       break;
     207             :     }
     208      160063 :     if( verbose_output ) {
     209           0 :       log.printf("  Secondary structure segment %u contains atoms : ", static_cast<unsigned>(colvar_atoms.size()+1));
     210           0 :       for(unsigned i=0; i<newatoms.size(); ++i) {
     211           0 :         log.printf("%d ",all_atoms[newatoms[i]].serial() );
     212             :       }
     213           0 :       log.printf("\n");
     214             :     }
     215      160063 :     colvar_atoms.push_back( newatoms );
     216      160063 :   }
     217          38 :   if( colvar_atoms.size()==0 ) {
     218           0 :     error("missing SEGMENT keywords -- no atoms have been specified for comparison");
     219             :   }
     220             : 
     221             :   double bondlength;
     222          38 :   parse("BONDLENGTH",bondlength);
     223          38 :   bondlength=bondlength/getUnits().getLength();
     224             : 
     225             :   // Read in the reference structure
     226          56 :   for(unsigned ii=1;; ++ii) {
     227             :     std::vector<double> cstruct;
     228         188 :     if( !parseNumberedVector("STRUCTURE",ii,cstruct) ) {
     229             :       break ;
     230             :     }
     231          56 :     plumed_assert( cstruct.size()%3==0 && cstruct.size()/3==colvar_atoms[0].size() );
     232          56 :     std::vector<Vector> structure( cstruct.size()/3 );
     233        1736 :     for(unsigned i=0; i<structure.size(); ++i) {
     234        6720 :       for(unsigned j=0; j<3; ++j) {
     235        5040 :         structure[i][j] = 0.1*cstruct[3*i+j]/getUnits().getLength();
     236             :       }
     237             :     }
     238          56 :     if( alignType=="DRMSD" ) {
     239             :       std::map<std::pair<unsigned,unsigned>, double> targets;
     240         750 :       for(unsigned i=0; i<structure.size()-1; ++i) {
     241       11600 :         for(unsigned j=i+1; j<structure.size(); ++j) {
     242       10875 :           double distance = delta( structure[i], structure[j] ).modulo();
     243       10875 :           if(distance > bondlength) {
     244       10172 :             targets[std::make_pair(i,j)] = distance;
     245             :           }
     246             :         }
     247             :       }
     248          25 :       drmsd_targets.push_back( targets );
     249             :     } else {
     250          31 :       Vector center;
     251          31 :       std::vector<double> align( structure.size(), 1.0 ), displace( structure.size(), 1.0 );
     252         961 :       for(unsigned i=0; i<structure.size(); ++i) {
     253         930 :         center+=structure[i]*align[i];
     254             :       }
     255         961 :       for(unsigned i=0; i<structure.size(); ++i) {
     256         930 :         structure[i] -= center;
     257             :       }
     258          31 :       RMSD newrmsd;
     259          31 :       newrmsd.clear();
     260          31 :       newrmsd.set(align,displace,structure,alignType,true,true);
     261          31 :       myrmsd.push_back( newrmsd );
     262          31 :     }
     263          56 :   }
     264             : 
     265             :   // And create values to hold everything
     266             :   unsigned nref = myrmsd.size();
     267          38 :   if( alignType=="DRMSD" ) {
     268             :     nref=drmsd_targets.size();
     269             :   }
     270          38 :   plumed_assert( nref>0 );
     271          38 :   std::vector<unsigned> shape(1);
     272          38 :   shape[0]=colvar_atoms.size();
     273          38 :   if( nref==1 ) {
     274          20 :     addValue( shape );
     275          20 :     setNotPeriodic();
     276             :   } else {
     277             :     std::string num;
     278          54 :     for(unsigned i=0; i<nref; ++i) {
     279          36 :       Tools::convert( i+1, num );
     280          36 :       addComponent( "struct-" + num, shape );
     281          72 :       componentIsNotPeriodic( "struct-" + num );
     282             :     }
     283             :   }
     284          38 : }
     285             : 
     286          94 : void SecondaryStructureRMSD::areAllTasksRequired( std::vector<ActionWithVector*>& task_reducing_actions ) {
     287          94 :   if( s_cutoff2>0 ) {
     288          80 :     task_reducing_actions.push_back(this);
     289             :   }
     290          94 : }
     291             : 
     292      761700 : int SecondaryStructureRMSD::checkTaskStatus( const unsigned& taskno, int& flag ) const {
     293      761700 :   if( s_cutoff2>0 ) {
     294      761700 :     Vector distance=pbcDistance( ActionAtomistic::getPosition( getAtomIndex(taskno,align_atom_1) ),
     295             :                                  ActionAtomistic::getPosition( getAtomIndex(taskno,align_atom_2) ) );
     296      761700 :     if( distance.modulo2()<s_cutoff2 ) {
     297             :       return 1;
     298             :     }
     299      691712 :     return 0;
     300             :   }
     301           0 :   return flag;
     302             : }
     303             : 
     304         276 : void SecondaryStructureRMSD::calculate() {
     305         276 :   runAllTasks();
     306         276 : }
     307             : 
     308       70420 : void SecondaryStructureRMSD::performTask( const unsigned& current, MultiValue& myvals ) const {
     309             :   // Resize the derivatives if need be
     310       70420 :   unsigned nderi = 3*getNumberOfAtoms()+9;
     311       70420 :   if( myvals.getNumberOfDerivatives()!=nderi ) {
     312          36 :     myvals.resize( myvals.getNumberOfValues(), nderi, 0, 0 );
     313             :   }
     314             :   // Retrieve the positions
     315       70420 :   const unsigned natoms = colvar_atoms[current].size();
     316       70420 :   std::vector<Vector> pos( natoms ), deriv( natoms );
     317     2183020 :   for(unsigned i=0; i<natoms; ++i) {
     318     2112600 :     pos[i]=ActionAtomistic::getPosition( getAtomIndex(current,i) );
     319             :   }
     320             : 
     321             :   // This aligns the two strands if this is required
     322       70420 :   Vector distance=pbcDistance( pos[align_atom_1],pos[align_atom_2] );
     323       70420 :   if( align_strands ) {
     324       70420 :     Vector origin_old, origin_new;
     325       70420 :     origin_old=pos[align_atom_2];
     326       70420 :     origin_new=pos[align_atom_1]+distance;
     327     1126720 :     for(unsigned i=15; i<30; ++i) {
     328     1056300 :       pos[i]+=( origin_new - origin_old );
     329             :     }
     330           0 :   } else if( !nopbc ) {
     331           0 :     for(unsigned i=0; i<natoms-1; ++i) {
     332           0 :       const Vector & first (pos[i]);
     333           0 :       Vector & second (pos[i+1]);
     334           0 :       second=first+pbcDistance(first,second);
     335             :     }
     336             :   }
     337             :   // Create a holder for the derivatives
     338       70420 :   if( alignType=="DRMSD" ) {
     339             :     // And now calculate the DRMSD
     340       21864 :     const unsigned rs = drmsd_targets.size();
     341       56167 :     for(unsigned i=0; i<rs; ++i) {
     342             :       double drmsd=0;
     343       34303 :       Vector distance;
     344       34303 :       Tensor vir;
     345       34303 :       vir.zero();
     346     1063393 :       for(unsigned j=0; j<natoms; ++j) {
     347     1029090 :         deriv[j].zero();
     348             :       }
     349    13995444 :       for(const auto & it : drmsd_targets[i] ) {
     350    13961141 :         const unsigned k=it.first.first;
     351    13961141 :         const unsigned j=it.first.second;
     352             : 
     353    13961141 :         distance=delta( pos[k], pos[j] );
     354    13961141 :         const double len = distance.modulo();
     355    13961141 :         const double diff = len - it.second;
     356    13961141 :         const double der = diff / len;
     357    13961141 :         drmsd += diff*diff;
     358             : 
     359    13961141 :         if( !doNotCalculateDerivatives() ) {
     360    13731677 :           deriv[k] += -der*distance;
     361    13731677 :           deriv[j] += der*distance;
     362    13731677 :           vir += -der*Tensor(distance,distance);
     363             :         }
     364             :       }
     365             : 
     366       34303 :       const double inpairs = 1./static_cast<double>(drmsd_targets[i].size());
     367       34303 :       unsigned ostrn = getConstPntrToComponent(i)->getPositionInStream();
     368       34303 :       drmsd = sqrt(inpairs*drmsd);
     369       34303 :       myvals.setValue( ostrn, drmsd );
     370             : 
     371       34303 :       if( !doNotCalculateDerivatives() ) {
     372       33739 :         double scalef = inpairs / drmsd;
     373     1045909 :         for(unsigned j=0; j<natoms; ++j) {
     374             :           const unsigned ja = getAtomIndex( current, j );
     375     1012170 :           myvals.addDerivative( ostrn, 3*ja + 0, scalef*deriv[j][0] );
     376     1012170 :           myvals.updateIndex( ostrn, 3*ja+0 );
     377     1012170 :           myvals.addDerivative( ostrn, 3*ja + 1, scalef*deriv[j][1] );
     378     1012170 :           myvals.updateIndex( ostrn, 3*ja+1 );
     379     1012170 :           myvals.addDerivative( ostrn, 3*ja + 2, scalef*deriv[j][2] );
     380     1012170 :           myvals.updateIndex( ostrn, 3*ja+2 );
     381             :         }
     382       33739 :         unsigned nbase = myvals.getNumberOfDerivatives() - 9;
     383      134956 :         for(unsigned k=0; k<3; ++k) {
     384      404868 :           for(unsigned j=0; j<3; ++j) {
     385      303651 :             myvals.addDerivative( ostrn, nbase + 3*k + j, scalef*vir(k,j) );
     386      303651 :             myvals.updateIndex( ostrn, nbase + 3*k + j );
     387             :           }
     388             :         }
     389             :       }
     390             :     }
     391             :   } else {
     392       48556 :     const unsigned rs = myrmsd.size();
     393      121352 :     for(unsigned i=0; i<rs; ++i) {
     394       72796 :       double nr = myrmsd[i].calculate( pos, deriv, false );
     395       72796 :       unsigned ostrn = getConstPntrToComponent(i)->getPositionInStream();
     396       72796 :       myvals.setValue( ostrn, nr );
     397             : 
     398       72796 :       if( !doNotCalculateDerivatives() ) {
     399       72796 :         Tensor vir;
     400       72796 :         vir.zero();
     401     2256676 :         for(unsigned j=0; j<natoms; ++j) {
     402             :           const unsigned ja = getAtomIndex( current, j );
     403     2183880 :           myvals.addDerivative( ostrn, 3*ja + 0, deriv[j][0] );
     404     2183880 :           myvals.updateIndex( ostrn, 3*colvar_atoms[current][j]+0 );
     405     2183880 :           myvals.addDerivative( ostrn, 3*ja + 1, deriv[j][1] );
     406     2183880 :           myvals.updateIndex( ostrn, 3*colvar_atoms[current][j]+1 );
     407     2183880 :           myvals.addDerivative( ostrn, 3*ja + 2, deriv[j][2] );
     408     2183880 :           myvals.updateIndex( ostrn, 3*colvar_atoms[current][j]+2 );
     409     2183880 :           vir+=(-1.0*Tensor( pos[j], deriv[j] ));
     410             :         }
     411       72796 :         unsigned nbase = myvals.getNumberOfDerivatives() - 9;
     412      291184 :         for(unsigned k=0; k<3; ++k) {
     413      873552 :           for(unsigned j=0; j<3; ++j) {
     414      655164 :             myvals.addDerivative( ostrn, nbase + 3*k + j, vir(k,j) );
     415      655164 :             myvals.updateIndex( ostrn, nbase + 3*k + j );
     416             :           }
     417             :         }
     418             :       }
     419             :     }
     420             :   }
     421       70420 :   return;
     422             : }
     423             : 
     424             : }
     425             : }

Generated by: LCOV version 1.16