LCOV - code coverage report
Current view: top level - src/mesosim - METriggeredCalibrator.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 92.6 % 108 100
Test Date: 2025-11-14 15:59:05 Functions: 100.0 % 8 8

            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    METriggeredCalibrator.cpp
      15              : /// @author  Daniel Krajzewicz
      16              : /// @date    Tue, May 2005
      17              : ///
      18              : // Calibrates the flow on a segment to a specified one
      19              : /****************************************************************************/
      20              : #include <config.h>
      21              : 
      22              : #include <string>
      23              : #include <algorithm>
      24              : #include <cmath>
      25              : #include <microsim/MSGlobals.h>
      26              : #include <microsim/MSNet.h>
      27              : #include <microsim/MSEdge.h>
      28              : #include <microsim/MSEventControl.h>
      29              : #include <microsim/MSVehicleControl.h>
      30              : #include <microsim/output/MSRouteProbe.h>
      31              : #include <utils/xml/SUMOXMLDefinitions.h>
      32              : #include <utils/common/MsgHandler.h>
      33              : #include <utils/common/ToString.h>
      34              : #include <utils/common/UtilExceptions.h>
      35              : #include <utils/common/StringTokenizer.h>
      36              : #include <utils/xml/XMLSubSys.h>
      37              : #include <utils/common/StringUtils.h>
      38              : #include <utils/options/OptionsCont.h>
      39              : #include <utils/vehicle/SUMOVehicleParserHelper.h>
      40              : #include <utils/distribution/RandomDistributor.h>
      41              : #include <utils/vehicle/SUMOVehicleParameter.h>
      42              : #include "MELoop.h"
      43              : #include "MESegment.h"
      44              : #include "MEVehicle.h"
      45              : #include "METriggeredCalibrator.h"
      46              : 
      47              : 
      48              : // ===========================================================================
      49              : // method definitions
      50              : // ===========================================================================
      51           86 : METriggeredCalibrator::METriggeredCalibrator(const std::string& id,
      52              :         MSEdge* const edge, const double pos,
      53              :         const std::string& aXMLFilename,
      54              :         const std::string& outputFilename,
      55              :         const SUMOTime freq, const double length,
      56              :         const MSRouteProbe* probe,
      57              :         const double invalidJamThreshold,
      58           86 :         const std::string& vTypes) :
      59              :     MSCalibrator(id, edge, nullptr, nullptr, pos, aXMLFilename, outputFilename, freq, length, probe, invalidJamThreshold, vTypes, false, false),
      60           86 :     mySegment(edge == nullptr ? nullptr : MSGlobals::gMesoNet->getSegmentForEdge(*edge, pos)) {
      61           86 :     myEdgeMeanData.setDescription("meandata_calibrator_" + getID());
      62           86 :     if (mySegment != nullptr) {
      63           82 :         mySegment->addDetector(&myEdgeMeanData);
      64              :     }
      65           86 : }
      66              : 
      67              : 
      68          172 : METriggeredCalibrator::~METriggeredCalibrator() {
      69           86 :     if (myCurrentStateInterval != myIntervals.end()) {
      70              :         // need to do it here and not in MSCalibrator because otherwise meandata is gone
      71           24 :         intervalEnd();
      72              :         // but avoid to call it again in MSCalibrator
      73           24 :         myCurrentStateInterval = myIntervals.end();
      74              :     }
      75              :     // TODO this is just commented out to work around https://github.com/eclipse/sumo/issues/7861
      76              :     //mySegment->removeDetector(&myEdgeMeanData);
      77          172 : }
      78              : 
      79              : 
      80              : bool
      81        13536 : METriggeredCalibrator::tryEmit(MESegment* s, MEVehicle* vehicle) {
      82        13536 :     if (s->initialise(vehicle, vehicle->getParameter().depart)) {
      83        13322 :         if (!MSNet::getInstance()->getVehicleControl().addVehicle(vehicle->getID(), vehicle)) {
      84            0 :             throw ProcessError(TLF("Emission of vehicle '%' in calibrator '%' failed!", vehicle->getID(), getID()));
      85              :         }
      86              :         return true;
      87              :     }
      88              :     return false;
      89              : }
      90              : 
      91              : 
      92              : SUMOTime
      93       100190 : METriggeredCalibrator::execute(SUMOTime currentTime) {
      94              :     // get current simulation values (valid for the last simulation second)
      95              :     // XXX could we miss vehicle movements if this is called less often than every DELTA_T (default) ?
      96       100190 :     mySegment->prepareDetectorForWriting(myEdgeMeanData);
      97              : 
      98              :     // check whether an adaptation value exists
      99       100190 :     if (isCurrentStateActive(currentTime)) {
     100              :         // all happens in isCurrentStateActive()
     101        82070 :         myAmActive = true;
     102              :     } else {
     103        18120 :         myAmActive = false;
     104        18120 :         myEdgeMeanData.reset(); // discard collected values
     105        18120 :         if (!mySpeedIsDefault) {
     106              :             // if not, reset adaptation values
     107           21 :             const double jamThresh = OptionsCont::getOptions().getFloat("meso-jam-threshold");
     108           21 :             myEdge->setMaxSpeed(myDefaultSpeed, jamThresh);
     109           21 :             mySpeedIsDefault = true;
     110              :         }
     111        18120 :         if (myCurrentStateInterval == myIntervals.end()) {
     112              :             // keep calibrator alive but do not call again
     113              :             return TIME2STEPS(86400);
     114              :         }
     115        18058 :         return myFrequency;
     116              :     }
     117        82070 :     const bool calibrateFlow = myCurrentStateInterval->q >= 0;
     118        82070 :     const bool calibrateSpeed = myCurrentStateInterval->v >= 0;
     119              :     // we are active
     120        82070 :     if (!myDidSpeedAdaption && calibrateSpeed && myCurrentStateInterval->v != mySegment->getEdge().getSpeedLimit()) {
     121           26 :         myEdge->setMaxSpeed(myCurrentStateInterval->v);
     122           26 :         mySpeedIsDefault = false;
     123           26 :         myDidSpeedAdaption = true;
     124              :     }
     125              :     // clear invalid jams
     126              :     bool hadInvalidJam = false;
     127        88310 :     while ((calibrateFlow || calibrateSpeed) && invalidJam()) {
     128              :         hadInvalidJam = true;
     129         6240 :         if (!myHaveWarnedAboutClearingJam) {
     130           24 :             WRITE_WARNINGF(TL("Clearing jam at calibrator '%' at time=%."), getID(), time2string(currentTime));
     131              :         }
     132              :         // remove one vehicle currently on the segment
     133         6240 :         if (mySegment->vaporizeAnyCar(currentTime, this)) {
     134         6240 :             myClearedInJam++;
     135              :         } else {
     136            0 :             if (!myHaveWarnedAboutClearingJam) {
     137              :                 // this frequenly happens for very short edges
     138            0 :                 WRITE_WARNINGF(TL("Could not clear jam at calibrator '%' at time=%."), getID(), time2string(currentTime));
     139              :             }
     140              :             break;
     141              :         }
     142         6240 :         myHaveWarnedAboutClearingJam = true;
     143              :     }
     144        82070 :     if (calibrateFlow) {
     145              :         // flow calibration starts here ...
     146              :         // compute the number of vehicles that should have passed the calibrator within the time
     147              :         // rom begin of the interval
     148        54200 :         const double totalHourFraction = STEPS2TIME(myCurrentStateInterval->end - myCurrentStateInterval->begin) / (double) 3600.;
     149        54200 :         const int totalWishedNum = (int)std::floor(myCurrentStateInterval->q * totalHourFraction + 0.5); // round to closest int
     150        54200 :         int adaptedNum = passed() + myClearedInJam;
     151        54200 :         if (!hadInvalidJam) {
     152              :             // only add vehicles if we do not have an invalid upstream jam to prevent spill-back
     153        50872 :             const double hourFraction = STEPS2TIME(currentTime - myCurrentStateInterval->begin + DELTA_T) / (double) 3600.;
     154        50872 :             const int wishedNum = (int)std::floor(myCurrentStateInterval->q * hourFraction + 0.5); // round to closest int
     155              :             // only the difference between inflow and aspiredFlow should be added, thus
     156              :             // we should not count vehicles vaporized from a jam here
     157              :             // if we have enough time left we can add missing vehicles later
     158        50872 :             const int relaxedInsertion = (int)std::floor(STEPS2TIME(myCurrentStateInterval->end - currentTime) / 3);
     159        50872 :             const int insertionSlack = MAX2(0, adaptedNum + relaxedInsertion - totalWishedNum);
     160              :             // increase number of vehicles
     161              :             //std::cout << "time:" << STEPS2TIME(currentTime) << " w:" << wishedNum << " s:" << insertionSlack << " before:" << adaptedNum;
     162        50872 :             MSVehicleControl& vc = MSNet::getInstance()->getVehicleControl();
     163        64196 :             while (wishedNum > adaptedNum + insertionSlack && remainingVehicleCapacity() > maximumInflow()) {
     164        13548 :                 SUMOVehicleParameter* pars = myCurrentStateInterval->vehicleParameter;
     165        13548 :                 ConstMSRoutePtr route = myProbe != nullptr ? myProbe->sampleRoute() : nullptr;
     166        13548 :                 if (route == nullptr) {
     167        27096 :                     route = MSRoute::dictionary(pars->routeid);
     168              :                 }
     169        13548 :                 if (route == nullptr) {
     170            0 :                     WRITE_WARNINGF(TL("No valid routes in calibrator '%'."), getID());
     171            0 :                     break;
     172              :                 }
     173        13548 :                 if (!route->contains(myEdge)) {
     174            0 :                     WRITE_WARNINGF(TL("Route '%' in calibrator '%' does not contain edge '%'."), route->getID(), getID(), myEdge->getID());
     175            0 :                     break;
     176              :                 }
     177        13548 :                 MSVehicleType* vtype = vc.getVType(pars->vtypeid);
     178              :                 assert(route != 0 && vtype != 0);
     179              :                 // build the vehicle
     180        13548 :                 const SUMOTime depart = mySegment->getNextInsertionTime(currentTime);
     181        13548 :                 SUMOVehicleParameter* newPars = new SUMOVehicleParameter(*pars);
     182        13550 :                 newPars->id = getNewVehicleID();
     183        13548 :                 newPars->depart = depart;
     184        13548 :                 newPars->routeid = route->getID();
     185              :                 MEVehicle* vehicle;
     186              :                 try {
     187        27096 :                     vehicle = static_cast<MEVehicle*>(vc.buildVehicle(newPars, route, vtype, false, MSVehicleControl::VehicleDefinitionSource::TRIGGER));
     188              :                     std::string msg;
     189        13548 :                     if (!vehicle->hasValidRouteStart(msg)) {
     190           10 :                         throw ProcessError(msg);
     191              :                     }
     192           10 :                 } catch (const ProcessError& e) {
     193           10 :                     if (!MSGlobals::gCheckRoutes) {
     194            8 :                         WRITE_WARNING(e.what());
     195              :                         vehicle = nullptr;
     196              :                         break;
     197              :                     } else {
     198            2 :                         throw;
     199              :                     }
     200           10 :                 }
     201        13538 :                 const bool duplicate = vc.getVehicle(newPars->id) != nullptr;
     202              :                 // duplicate ids could come from loading state
     203        13540 :                 if (duplicate) {
     204            2 :                     vc.deleteVehicle(vehicle, true);
     205              :                     continue;
     206              :                 }
     207        13536 :                 vehicle->setSegment(mySegment); // needed or vehicle will not be registered (XXX why?)
     208              :                 vehicle->setEventTime(currentTime); // XXX superfluous?
     209              :                 // move vehicle forward when the route does not begin at the calibrator's edge
     210        13536 :                 const MSEdge* myedge = &mySegment->getEdge();
     211              :                 bool atDest = false;
     212        15504 :                 while (vehicle->getEdge() != myedge) {
     213              :                     // let the vehicle move to the next edge
     214         1968 :                     atDest = vehicle->moveRoutePointer();
     215              :                 }
     216              :                 // insert vehicle into the net
     217        13536 :                 if (atDest || !tryEmit(mySegment, vehicle)) {
     218              :                     //std::cout << "F ";
     219          214 :                     vc.deleteVehicle(vehicle, true);
     220              :                     break;
     221              :                 }
     222              :                 //std::cout << "I ";
     223        13322 :                 myInserted++;
     224        13322 :                 adaptedNum++;
     225              :             }
     226              :         }
     227              :         //std::cout << " after:" << adaptedNum << "\n";
     228              :         // we only remove vehicles once we really have to
     229        56106 :         while (totalWishedNum < adaptedNum) {
     230         2434 :             if (!mySegment->vaporizeAnyCar(currentTime, this)) {
     231              :                 // @bug: short edges may be jumped in a single step, giving us no chance to remove a vehicle
     232              :                 break;
     233              :             }
     234         1908 :             myRemoved++;
     235         1908 :             adaptedNum--;
     236              :         }
     237              :     }
     238        82068 :     if (myCurrentStateInterval->end <= currentTime + myFrequency) {
     239          137 :         intervalEnd();
     240              :     }
     241              :     //assert(!invalidJam());
     242        82068 :     if (invalidJam()) {
     243            0 :         WRITE_WARNINGF("DEBUG: Could not clear jam at calibrator '%' at time=%.", getID(), time2string(currentTime));
     244              :     }
     245        82068 :     return myFrequency;
     246              : }
     247              : 
     248              : 
     249              : bool
     250       170378 : METriggeredCalibrator::invalidJam() const {
     251       340756 :     if (mySegment->getBruttoOccupancy() == 0.) {
     252              :         return false;
     253              :     }
     254              :     // maxSpeed reflects the calibration target
     255       146243 :     const bool toSlow = mySegment->getMeanSpeed() < myInvalidJamThreshold * mySegment->getEdge().getSpeedLimit();
     256       146243 :     return toSlow && remainingVehicleCapacity() < maximumInflow();
     257              : }
     258              : 
     259              : 
     260              : int
     261        76041 : METriggeredCalibrator::remainingVehicleCapacity() const {
     262        76041 :     const SUMOVehicleParameter* pars = myCurrentStateInterval->vehicleParameter;
     263        76041 :     const MSVehicleType* vtype = MSNet::getInstance()->getVehicleControl().getVType(pars->vtypeid);
     264        76041 :     return mySegment->remainingVehicleCapacity(vtype->getLengthWithGap());
     265              : }
     266              : 
     267              : 
     268              : void
     269          161 : METriggeredCalibrator::reset() {
     270          161 :     myEdgeMeanData.reset();
     271          161 : }
     272              : 
     273              : 
     274              : /****************************************************************************/
        

Generated by: LCOV version 2.0-1