LCOV - code coverage report
Current view: top level - src/microsim - MSInsertionControl.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 91.5 % 211 193
Test Date: 2025-11-13 15:38:19 Functions: 96.0 % 25 24

            Line data    Source code
       1              : /****************************************************************************/
       2              : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3              : // Copyright (C) 2001-2025 German Aerospace Center (DLR) and others.
       4              : // This program and the accompanying materials are made available under the
       5              : // terms of the Eclipse Public License 2.0 which is available at
       6              : // https://www.eclipse.org/legal/epl-2.0/
       7              : // This Source Code may also be made available under the following Secondary
       8              : // Licenses when the conditions for such availability set forth in the Eclipse
       9              : // Public License 2.0 are satisfied: GNU General Public License, version 2
      10              : // or later which is available at
      11              : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
      12              : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
      13              : /****************************************************************************/
      14              : /// @file    MSInsertionControl.cpp
      15              : /// @author  Christian Roessel
      16              : /// @author  Daniel Krajzewicz
      17              : /// @author  Axel Wegener
      18              : /// @author  Michael Behrisch
      19              : /// @author  Jakob Erdmann
      20              : /// @author  Mirko Barthauer
      21              : /// @date    Mon, 12 Mar 2001
      22              : ///
      23              : // Inserts vehicles into the network when their departure time is reached
      24              : /****************************************************************************/
      25              : #include <config.h>
      26              : 
      27              : #include <iostream>
      28              : #include <algorithm>
      29              : #include <cassert>
      30              : #include <iterator>
      31              : #include <utils/router/IntermodalRouter.h>
      32              : #include <microsim/devices/MSDevice_Routing.h>
      33              : #include <microsim/devices/MSRoutingEngine.h>
      34              : #include "MSGlobals.h"
      35              : #include "MSVehicle.h"
      36              : #include "MSVehicleControl.h"
      37              : #include "MSLane.h"
      38              : #include "MSEdge.h"
      39              : #include "MSNet.h"
      40              : #include "MSRouteHandler.h"
      41              : #include "MSInsertionControl.h"
      42              : 
      43              : 
      44              : // ===========================================================================
      45              : // member method definitions
      46              : // ===========================================================================
      47        39025 : MSInsertionControl::MSInsertionControl(MSVehicleControl& vc,
      48              :                                        SUMOTime maxDepartDelay,
      49              :                                        bool eagerInsertionCheck,
      50              :                                        int maxVehicleNumber,
      51        39025 :                                        SUMOTime randomDepartOffset) :
      52        39025 :     myVehicleControl(vc),
      53        39025 :     myMaxDepartDelay(maxDepartDelay),
      54        39025 :     myEagerInsertionCheck(eagerInsertionCheck),
      55        39025 :     myMaxVehicleNumber(maxVehicleNumber),
      56        39025 :     myPendingEmitsUpdateTime(SUMOTime_MIN),
      57        78050 :     myFlowRNG("flow") {
      58        39025 :     myMaxRandomDepartOffset = randomDepartOffset;
      59        39025 :     RandHelper::initRandGlobal(&myFlowRNG);
      60        39025 : }
      61              : 
      62              : 
      63        38415 : MSInsertionControl::~MSInsertionControl() {
      64        40806 :     for (const Flow& f : myFlows) {
      65         2391 :         delete (f.pars);
      66              :     }
      67        76830 : }
      68              : 
      69              : 
      70              : void
      71      5223068 : MSInsertionControl::add(SUMOVehicle* veh) {
      72      5223068 :     myAllVeh.add(veh);
      73      5223068 : }
      74              : 
      75              : 
      76              : bool
      77        17945 : MSInsertionControl::addFlow(SUMOVehicleParameter* const pars, int index) {
      78        17945 :     if (myFlowIDs.count(pars->id) > 0) {
      79           39 :         return false;
      80              :     }
      81              :     const bool loadingFromState = index >= 0;
      82        17906 :     Flow flow{pars, loadingFromState ? index : 0, initScale(pars->vtypeid)};
      83        17906 :     if (!loadingFromState && pars->repetitionProbability < 0 && pars->repetitionOffset < 0) {
      84              :         // init poisson flow (but only the timing)
      85          558 :         flow.pars->incrementFlow(flow.scale, &myFlowRNG);
      86          558 :         flow.pars->repetitionsDone--;
      87              :     }
      88        17906 :     myFlows.emplace_back(flow);
      89        17906 :     myFlowIDs.insert(std::make_pair(pars->id, flow.index));
      90        17906 :     return true;
      91              : }
      92              : 
      93              : 
      94              : double
      95        17906 : MSInsertionControl::initScale(const std::string vtypeid) {
      96        17906 :     MSVehicleControl& vc = MSNet::getInstance()->getVehicleControl();
      97        17906 :     if (vc.hasVTypeDistribution(vtypeid)) {
      98              :         double result = -1;
      99          806 :         const RandomDistributor<MSVehicleType*>* dist = vc.getVTypeDistribution(vtypeid);
     100         2536 :         for (const MSVehicleType* t : dist->getVals()) {
     101         1730 :             if (result == -1) {
     102          806 :                 result = t->getParameter().scale;
     103          924 :             } else if (result != t->getParameter().scale) {
     104              :                 // unequal scales in distribution
     105              :                 return -1;
     106              :             }
     107              :         }
     108          806 :         return result;
     109              :     } else {
     110              :         // rng is not used since vtypeid is not a distribution
     111        17100 :         return vc.getVType(vtypeid, nullptr, true)->getParameter().scale;
     112              :     }
     113              : }
     114              : 
     115              : 
     116              : void
     117            5 : MSInsertionControl::updateScale(const std::string vtypeid) {
     118            5 :     for (Flow& f : myFlows) {
     119            0 :         if (f.pars->vtypeid == vtypeid) {
     120            0 :             f.scale = initScale(vtypeid);
     121              :         }
     122              :     }
     123            5 : }
     124              : 
     125              : 
     126              : int
     127     63625891 : MSInsertionControl::emitVehicles(SUMOTime time) {
     128              :     // check whether any vehicles shall be emitted within this time step
     129              :     const bool havePreChecked = MSRoutingEngine::isEnabled();
     130     63625891 :     if (myPendingEmits.empty() || (havePreChecked && myEmitCandidates.empty())) {
     131              :         return 0;
     132              :     }
     133              :     int numEmitted = 0;
     134              :     // we use buffering for the refused emits to save time
     135              :     //  for this, we have two lists; one contains previously refused emits, the second
     136              :     //  will be used to append those vehicles that will not be able to depart in this
     137              :     //  time step
     138              :     MSVehicleContainer::VehicleVector refusedEmits;
     139              : 
     140              :     // go through the list of previously refused vehicles, first
     141              :     MSVehicleContainer::VehicleVector::const_iterator veh;
     142   2153974916 :     for (veh = myPendingEmits.begin(); veh != myPendingEmits.end(); veh++) {
     143   2145652733 :         if (havePreChecked && (myEmitCandidates.count(*veh) == 0)) {
     144      5791182 :             refusedEmits.push_back(*veh);
     145              :         } else {
     146   2139861551 :             numEmitted += tryInsert(time, *veh, refusedEmits);
     147              :         }
     148              :     }
     149              :     myEmitCandidates.clear();
     150      8322183 :     myPendingEmits = refusedEmits;
     151              :     return numEmitted;
     152      8322338 : }
     153              : 
     154              : 
     155              : int
     156   2139861551 : MSInsertionControl::tryInsert(SUMOTime time, SUMOVehicle* veh,
     157              :                               MSVehicleContainer::VehicleVector& refusedEmits) {
     158              :     assert(veh->getParameter().depart <= time);
     159   2139861551 :     const MSEdge& edge = *veh->getEdge();
     160   2139861551 :     if (veh->isOnRoad()) {
     161              :         return 1;
     162              :     }
     163       540408 :     if ((myMaxVehicleNumber < 0 || (int)MSNet::getInstance()->getVehicleControl().getRunningVehicleNo() < myMaxVehicleNumber)
     164   2139872516 :             && edge.insertVehicle(*veh, time, false, myEagerInsertionCheck || veh->getParameter().departProcedure == DepartDefinition::SPLIT)) {
     165              :         // Successful insertion
     166              :         return 1;
     167              :     }
     168   2136167288 :     if (myMaxDepartDelay >= 0 && time - veh->getParameter().depart > myMaxDepartDelay) {
     169              :         // remove vehicles waiting too long for departure
     170       777694 :         myVehicleControl.deleteVehicle(veh, true);
     171   2135389594 :     } else if (edge.isVaporizing()) {
     172              :         // remove vehicles if the edge shall be empty
     173        20296 :         myVehicleControl.deleteVehicle(veh, true);
     174   2135369298 :     } else if (myAbortedEmits.count(veh) > 0) {
     175              :         // remove vehicles which shall not be inserted for some reason
     176          467 :         myAbortedEmits.erase(veh);
     177          467 :         myVehicleControl.deleteVehicle(veh, true);
     178   2135368831 :     } else if ((veh->getRouteValidity(false) & (
     179              :                     MSBaseVehicle::ROUTE_START_INVALID_LANE
     180              :                     | MSBaseVehicle::ROUTE_START_INVALID_PERMISSIONS)) != 0) {
     181           46 :         myVehicleControl.deleteVehicle(veh, true);
     182              :     } else {
     183              :         // let the vehicle wait one step, we'll retry then
     184   2135368785 :         refusedEmits.push_back(veh);
     185              :     }
     186              :     edge.setLastFailedInsertionTime(time);
     187   2136167288 :     return 0;
     188              : }
     189              : 
     190              : 
     191              : void
     192     63626057 : MSInsertionControl::checkCandidates(SUMOTime time, const bool preCheck) {
     193     67241239 :     while (myAllVeh.anyWaitingBefore(time)) {
     194      3615182 :         const MSVehicleContainer::VehicleVector& top = myAllVeh.top();
     195      3615182 :         copy(top.begin(), top.end(), back_inserter(myPendingEmits));
     196      3615182 :         myAllVeh.pop();
     197              :     }
     198     63626057 :     if (preCheck) {
     199              :         MSVehicleContainer::VehicleVector::const_iterator veh;
     200    777006320 :         for (veh = myPendingEmits.begin(); veh != myPendingEmits.end(); veh++) {
     201    764590572 :             SUMOVehicle* const v = *veh;
     202    764590572 :             const MSEdge* const edge = v->getEdge();
     203    764590572 :             if (edge->insertVehicle(*v, time, true, myEagerInsertionCheck)) {
     204              :                 myEmitCandidates.insert(v);
     205              :             } else {
     206     38961983 :                 MSDevice_Routing* dev = static_cast<MSDevice_Routing*>(v->getDevice(typeid(MSDevice_Routing)));
     207              :                 if (dev != nullptr) {
     208              :                     dev->skipRouting(time);
     209              :                 }
     210              :             }
     211              :         }
     212              :     }
     213     63626019 : }
     214              : 
     215              : 
     216              : void
     217     63626064 : MSInsertionControl::determineCandidates(SUMOTime time) {
     218     63626064 :     MSVehicleControl& vehControl = MSNet::getInstance()->getVehicleControl();
     219              :     // for equidistant vehicles, up-scaling is done via repetitionOffset
     220     96759354 :     for (std::vector<Flow>::iterator i = myFlows.begin(); i != myFlows.end();) {
     221              :         MSVehicleType* vtype = nullptr;
     222     33133297 :         SUMOVehicleParameter* pars = i->pars;
     223     33133297 :         double typeScale = i->scale;
     224     33133297 :         if (typeScale < 0) {
     225              :             // must sample from distribution to determine scale value
     226            0 :             vtype = vehControl.getVType(pars->vtypeid, MSRouteHandler::getParsingRNG());
     227            0 :             typeScale = vtype->getParameter().scale;
     228              :         }
     229     33133297 :         double scale = vehControl.getScale() * typeScale;
     230     33133297 :         bool tryEmitByProb = pars->repetitionProbability > 0;
     231     37988943 :         while (scale > 0 && ((pars->repetitionProbability < 0
     232     28699464 :                               && pars->repetitionsDone < pars->repetitionNumber * scale
     233     28687906 :                               && pars->depart + pars->repetitionTotalOffset <= time)
     234     33657386 :                              || (tryEmitByProb
     235      8765383 :                                  && pars->depart <= time
     236      8757472 :                                  && pars->repetitionEnd > time
     237              :                                  // only call rand if all other conditions are met
     238      8754052 :                                  && RandHelper::rand(&myFlowRNG) < (pars->repetitionProbability * TS))
     239              :                             )) {
     240              :             tryEmitByProb = false; // only emit one per step
     241      4855653 :             SUMOVehicleParameter* newPars = new SUMOVehicleParameter(*pars);
     242      9711306 :             newPars->id = pars->id + "." + toString(i->index);
     243      4855653 :             newPars->depart = pars->repetitionProbability > 0 ? time : pars->depart + pars->repetitionTotalOffset + computeRandomDepartOffset();
     244      4855653 :             pars->incrementFlow(scale, &myFlowRNG);
     245      4855653 :             myFlowIDs[pars->id] = i->index;
     246              :             //std::cout << SIMTIME << " flow=" << pars->id << " done=" << pars->repetitionsDone << " totalOffset=" << STEPS2TIME(pars->repetitionTotalOffset) << "\n";
     247              :             // try to build the vehicle
     248      4855653 :             if (vehControl.getVehicle(newPars->id) == nullptr) {
     249      4855653 :                 ConstMSRoutePtr const route = MSRoute::dictionary(pars->routeid);
     250      4855653 :                 if (vtype == nullptr) {
     251      4855653 :                     vtype = vehControl.getVType(pars->vtypeid, MSRouteHandler::getParsingRNG());
     252              :                 }
     253      4855653 :                 SUMOVehicle* const vehicle = vehControl.buildVehicle(newPars, route, vtype, !MSGlobals::gCheckRoutes);
     254              :                 // for equidistant vehicles, all scaling is done via repetitionOffset (to avoid artefacts, #11441)
     255              :                 // for probabilistic vehicles, we use the quota
     256      4855646 :                 int quota = pars->repetitionProbability < 0 ? 1 : vehControl.getQuota(scale);
     257       524096 :                 if (quota > 0) {
     258      4831880 :                     vehControl.addVehicle(newPars->id, vehicle);
     259      4831880 :                     if (pars->departProcedure == DepartDefinition::GIVEN || pars->departProcedure == DepartDefinition::BEGIN) {
     260      4831856 :                         add(vehicle);
     261              :                     }
     262      4831880 :                     i->index++;
     263      4888459 :                     while (--quota > 0) {
     264        56579 :                         SUMOVehicleParameter* const quotaPars = new SUMOVehicleParameter(*pars);
     265       113158 :                         quotaPars->id = pars->id + "." + toString(i->index);
     266        56579 :                         quotaPars->depart = pars->repetitionProbability > 0 ? time :
     267            0 :                                             pars->depart + pars->repetitionsDone * pars->repetitionTotalOffset + computeRandomDepartOffset();
     268        56586 :                         SUMOVehicle* const quotaVehicle = vehControl.buildVehicle(quotaPars, route, vtype, !MSGlobals::gCheckRoutes);
     269        56579 :                         vehControl.addVehicle(quotaPars->id, quotaVehicle);
     270        56579 :                         if (pars->departProcedure == DepartDefinition::GIVEN || pars->departProcedure == DepartDefinition::BEGIN) {
     271        56579 :                             add(quotaVehicle);
     272              :                         }
     273        56579 :                         pars->repetitionsDone++;
     274        56579 :                         i->index++;
     275              :                     }
     276              :                 } else {
     277        23766 :                     vehControl.deleteVehicle(vehicle, true);
     278              :                 }
     279              :             } else {
     280            0 :                 if (MSGlobals::gStateLoaded) {
     281              :                     /// @note probably obsolete since flows save their state
     282              :                     break;
     283              :                 }
     284            0 :                 throw ProcessError(TLF("Another vehicle with the id '%' exists.", newPars->id));
     285              :             }
     286              :             vtype = nullptr;
     287              :         }
     288     33133290 :         if (time >= pars->repetitionEnd ||
     289     33128516 :                 (pars->repetitionNumber != std::numeric_limits<int>::max()
     290     22236035 :                  && pars->repetitionsDone >= (int)(pars->repetitionNumber * scale + 0.5))) {
     291        15500 :             i = myFlows.erase(i);
     292        15500 :             MSRoute::checkDist(pars->routeid);
     293        15500 :             delete pars;
     294              :         } else {
     295              :             ++i;
     296              :         }
     297              :     }
     298     63626057 :     checkCandidates(time, MSRoutingEngine::isEnabled());
     299     63626019 : }
     300              : 
     301              : 
     302              : int
     303      7002245 : MSInsertionControl::getWaitingVehicleNo() const {
     304      7002245 :     return (int)myPendingEmits.size();
     305              : }
     306              : 
     307              : 
     308              : int
     309      7416070 : MSInsertionControl::getPendingFlowCount() const {
     310      7416070 :     return (int)myFlows.size();
     311              : }
     312              : 
     313              : 
     314              : void
     315          480 : MSInsertionControl::descheduleDeparture(const SUMOVehicle* veh) {
     316          480 :     myAbortedEmits.insert(veh);
     317          480 : }
     318              : 
     319              : void
     320        15118 : MSInsertionControl::retractDescheduleDeparture(const SUMOVehicle* veh) {
     321        15118 :     myAbortedEmits.erase(veh);
     322        15118 : }
     323              : 
     324              : 
     325              : void
     326         3014 : MSInsertionControl::alreadyDeparted(SUMOVehicle* veh) {
     327         3014 :     myPendingEmits.erase(std::remove(myPendingEmits.begin(), myPendingEmits.end(), veh), myPendingEmits.end());
     328         3014 :     myAllVeh.remove(veh);
     329         3014 : }
     330              : 
     331              : 
     332              : void
     333            6 : MSInsertionControl::clearPendingVehicles(const std::string& route) {
     334              :     //clear out the refused vehicle list, deleting the vehicles entirely
     335              :     MSVehicleContainer::VehicleVector::iterator veh;
     336           12 :     for (veh = myPendingEmits.begin(); veh != myPendingEmits.end();) {
     337            6 :         if ((*veh)->getRoute().getID() == route || route == "") {
     338            6 :             myVehicleControl.deleteVehicle(*veh, true);
     339            6 :             veh = myPendingEmits.erase(veh);
     340              :         } else {
     341              :             ++veh;
     342              :         }
     343              :     }
     344            6 : }
     345              : 
     346              : 
     347              : int
     348            0 : MSInsertionControl::getPendingEmits(const MSLane* lane) {
     349            0 :     if (MSNet::getInstance()->getCurrentTimeStep() != myPendingEmitsUpdateTime) {
     350              :         // updated pending emits (only once per time step)
     351              :         myPendingEmitsForLane.clear();
     352            0 :         for (const SUMOVehicle* const veh : myPendingEmits) {
     353            0 :             const MSLane* const vlane = veh->getLane();
     354            0 :             if (vlane != nullptr) {
     355            0 :                 myPendingEmitsForLane[vlane]++;
     356              :             } else {
     357              :                 // no (tentative) departLane was set, increase count for all
     358              :                 // lanes of the depart edge
     359            0 :                 for (const MSLane* const l : veh->getEdge()->getLanes()) {
     360            0 :                     myPendingEmitsForLane[l]++;
     361              :                 }
     362              :             }
     363              :         }
     364            0 :         myPendingEmitsUpdateTime = MSNet::getInstance()->getCurrentTimeStep();
     365              :     }
     366            0 :     return myPendingEmitsForLane[lane];
     367              : }
     368              : 
     369              : 
     370              : void
     371         4538 : MSInsertionControl::adaptIntermodalRouter(MSTransportableRouter& router) const {
     372              :     // fill the public transport router with pre-parsed public transport lines
     373         6762 :     for (const Flow& f : myFlows) {
     374         2224 :         if (f.pars->line != "") {
     375          547 :             ConstMSRoutePtr const route = MSRoute::dictionary(f.pars->routeid);
     376          547 :             router.getNetwork()->addSchedule(*f.pars, route == nullptr ? nullptr : &route->getStops());
     377              :         }
     378              :     }
     379         4538 : }
     380              : 
     381              : 
     382              : void
     383          476 : MSInsertionControl::saveState(OutputDevice& out) {
     384              :     // save flow states
     385          560 :     for (const Flow& flow : myFlows) {
     386           84 :         flow.pars->write(out, OptionsCont::getOptions(), SUMO_TAG_FLOWSTATE,
     387           84 :                          flow.pars->vtypeid == DEFAULT_VTYPE_ID ? "" : flow.pars->vtypeid);
     388           84 :         if (flow.pars->repetitionProbability <= 0) {
     389           70 :             out.writeAttr(SUMO_ATTR_NEXT, STEPS2TIME(flow.pars->repetitionTotalOffset));
     390              :         }
     391           84 :         out.writeAttr(SUMO_ATTR_ROUTE, flow.pars->routeid);
     392           84 :         out.writeAttr(SUMO_ATTR_DONE, flow.pars->repetitionsDone);
     393           84 :         out.writeAttr(SUMO_ATTR_INDEX, flow.index);
     394           84 :         if (flow.pars->wasSet(VEHPARS_FORCE_REROUTE)) {
     395           26 :             out.writeAttr(SUMO_ATTR_REROUTE, true);
     396              :         }
     397           86 :         for (const SUMOVehicleParameter::Stop& stop : flow.pars->stops) {
     398            2 :             stop.write(out);
     399              :         }
     400          168 :         out.closeTag();
     401              :     }
     402          476 : }
     403              : 
     404              : 
     405              : void
     406          187 : MSInsertionControl::clearState() {
     407          187 :     for (const Flow& f : myFlows) {
     408            0 :         delete (f.pars);
     409              :     }
     410              :     myFlows.clear();
     411              :     myFlowIDs.clear();
     412          187 :     myAllVeh.clearState();
     413              :     myPendingEmits.clear();
     414              :     myEmitCandidates.clear();
     415          187 :     myAbortedEmits.clear();
     416              :     // myPendingEmitsForLane must not be cleared since it updates itself on the next call
     417          187 : }
     418              : 
     419              : 
     420              : SUMOTime
     421      5154852 : MSInsertionControl::computeRandomDepartOffset() const {
     422      5154852 :     if (myMaxRandomDepartOffset > 0) {
     423              :         // round to the closest usable simulation step
     424           90 :         return DELTA_T * ((RandHelper::rand(myMaxRandomDepartOffset, MSRouteHandler::getParsingRNG()) + DELTA_T / 2) / DELTA_T);
     425              :     }
     426              :     return 0;
     427              : }
     428              : 
     429              : const SUMOVehicleParameter*
     430           40 : MSInsertionControl::getFlowPars(const std::string& id) const {
     431              :     if (hasFlow(id)) {
     432           40 :         for (const Flow& f : myFlows) {
     433           40 :             if (f.pars->id == id) {
     434              :                 return f.pars;
     435              :             }
     436              :         }
     437              :     }
     438              :     return nullptr;
     439              : }
     440              : 
     441              : SUMOVehicle*
     442          808 : MSInsertionControl::getLastFlowVehicle(const std::string& id) const {
     443              :     const auto it = myFlowIDs.find(id);
     444          808 :     if (it != myFlowIDs.end()) {
     445         1616 :         const std::string vehID = id + "." + toString(it->second);
     446          808 :         return MSNet::getInstance()->getVehicleControl().getVehicle(vehID);
     447              :     }
     448              :     return nullptr;
     449              : }
     450              : 
     451              : 
     452              : bool
     453        10002 : MSInsertionControl::hasTaxiFlow() const {
     454        20004 :     SumoRNG tmp("tmp");
     455        17884 :     for (const Flow& flow : myFlows) {
     456        15842 :         if (flow.scale != 0 &&
     457        39605 :                 (StringUtils::toBool(flow.pars->getParameter("has.taxi.device", "false"))
     458         7921 :                  || hasTaxiDeviceType(flow.pars->vtypeid, tmp))) {
     459              :             return true;
     460              :         }
     461              :     }
     462              :     return false;
     463              : }
     464              : 
     465              : 
     466              : bool
     467         7921 : MSInsertionControl::hasTaxiDeviceType(const std::string& vtypeId, SumoRNG& rng) {
     468         7921 :     MSVehicleControl& vehControl = MSNet::getInstance()->getVehicleControl();
     469         7921 :     const MSVehicleType* vtype = vehControl.getVType(vtypeId, &rng);
     470        15842 :     return StringUtils::toBool(vtype->getParameter().getParameter("has.taxi.device", "false"));
     471              : }
     472              : 
     473              : /****************************************************************************/
        

Generated by: LCOV version 2.0-1