LCOV - code coverage report
Current view: top level - src/microsim/devices - MSDevice_GLOSA.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 117 136 86.0 %
Date: 2024-04-29 15:38:36 Functions: 13 16 81.2 %

          Line data    Source code
       1             : /****************************************************************************/
       2             : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3             : // Copyright (C) 2013-2024 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    MSDevice_GLOSA.cpp
      15             : /// @author  Jakob Erdmann
      16             : /// @date    21.04.2021
      17             : ///
      18             : // A device for Green Light Optimal Speed Advisory
      19             : /****************************************************************************/
      20             : #include <config.h>
      21             : 
      22             : #include <utils/common/StringUtils.h>
      23             : #include <utils/options/OptionsCont.h>
      24             : #include <utils/iodevices/OutputDevice.h>
      25             : #include <utils/vehicle/SUMOVehicle.h>
      26             : #include <microsim/traffic_lights/MSTrafficLightLogic.h>
      27             : #include <microsim/MSNet.h>
      28             : #include <microsim/MSLane.h>
      29             : #include <microsim/MSEdge.h>
      30             : #include <microsim/MSLink.h>
      31             : #include <microsim/MSVehicle.h>
      32             : #include "MSDevice_GLOSA.h"
      33             : 
      34             : //#define DEBUG_GLOSA
      35             : #define DEBUG_COND (true)
      36             : 
      37             : // ===========================================================================
      38             : // method definitions
      39             : // ===========================================================================
      40             : // ---------------------------------------------------------------------------
      41             : // static initialisation methods
      42             : // ---------------------------------------------------------------------------
      43             : void
      44       36320 : MSDevice_GLOSA::insertOptions(OptionsCont& oc) {
      45       36320 :     oc.addOptionSubTopic("GLOSA Device");
      46       72640 :     insertDefaultAssignmentOptions("glosa", "GLOSA Device", oc);
      47             : 
      48       36320 :     oc.doRegister("device.glosa.range", new Option_Float(100.0));
      49       72640 :     oc.addDescription("device.glosa.range", "GLOSA Device", TL("The communication range to the traffic light"));
      50             : 
      51       36320 :     oc.doRegister("device.glosa.max-speedfactor", new Option_Float(1.1));
      52       72640 :     oc.addDescription("device.glosa.max-speedfactor", "GLOSA Device", TL("The maximum speed factor when approaching a green light"));
      53             : 
      54       36320 :     oc.doRegister("device.glosa.min-speed", new Option_Float(5.0));
      55       72640 :     oc.addDescription("device.glosa.min-speed", "GLOSA Device", TL("Minimum speed when coasting towards a red light"));
      56       36320 : }
      57             : 
      58             : 
      59             : void
      60     4658932 : MSDevice_GLOSA::buildVehicleDevices(SUMOVehicle& v, std::vector<MSVehicleDevice*>& into) {
      61     4658932 :     OptionsCont& oc = OptionsCont::getOptions();
      62    12288092 :     if (!MSGlobals::gUseMesoSim && equippedByDefaultAssignmentOptions(oc, "glosa", v, false)) {
      63          16 :         MSDevice_GLOSA* device = new MSDevice_GLOSA(v, "glosa_" + v.getID(),
      64          32 :                 getFloatParam(v, OptionsCont::getOptions(), "glosa.min-speed", 5, true),
      65          32 :                 getFloatParam(v, OptionsCont::getOptions(), "glosa.range", 100, true),
      66          32 :                 getFloatParam(v, OptionsCont::getOptions(), "glosa.max-speedfactor", 1.1, true));
      67          16 :         into.push_back(device);
      68             :     }
      69     4658932 : }
      70             : 
      71             : void
      72           0 : MSDevice_GLOSA::cleanup() {
      73             :     // cleaning up global state (if any)
      74           0 : }
      75             : 
      76             : // ---------------------------------------------------------------------------
      77             : // MSDevice_GLOSA-methods
      78             : // ---------------------------------------------------------------------------
      79          16 : MSDevice_GLOSA::MSDevice_GLOSA(SUMOVehicle& holder, const std::string& id, double minSpeed, double range, double maxSpeedFactor) :
      80             :     MSVehicleDevice(holder, id),
      81           0 :     myVeh(dynamic_cast<MSVehicle&>(holder)),
      82          16 :     myNextTLSLink(nullptr),
      83          16 :     myDistance(0),
      84          16 :     myMinSpeed(minSpeed),
      85          16 :     myRange(range),
      86          16 :     myMaxSpeedFactor(maxSpeedFactor)
      87             : 
      88             : {
      89          16 :     myOriginalSpeedFactor = myVeh.getChosenSpeedFactor();
      90          16 : }
      91             : 
      92             : 
      93          32 : MSDevice_GLOSA::~MSDevice_GLOSA() {
      94          32 : }
      95             : 
      96             : 
      97             : bool
      98         564 : MSDevice_GLOSA::notifyMove(SUMOTrafficObject& /*tObject*/, double oldPos,
      99             :                            double newPos, double /*newSpeed*/) {
     100         564 :     myDistance -= (newPos - oldPos);
     101         564 :     if (myNextTLSLink != nullptr && myDistance <= myRange) {
     102         268 :         const double vMax = myVeh.getLane()->getVehicleMaxSpeed(&myVeh);
     103         268 :         const double timeToJunction = earliest_arrival(myDistance, vMax);
     104         268 :         const double timeToSwitch = getTimeToSwitch(myNextTLSLink);
     105             : #ifdef DEBUG_GLOSA
     106             :         if (DEBUG_COND) {
     107             :             std::cout << SIMTIME << " veh=" << myVeh.getID() << " d=" << myDistance << " ttJ=" << timeToJunction << " ttS=" << timeToSwitch << "\n";
     108             :         }
     109             : #endif
     110         268 :         if (myNextTLSLink->haveGreen()) {
     111          48 :             if (timeToJunction > timeToSwitch) {
     112          44 :                 if (myMaxSpeedFactor > myVeh.getChosenSpeedFactor()) {
     113           4 :                     const double vMax2 = vMax / myVeh.getChosenSpeedFactor() * myMaxSpeedFactor;
     114           4 :                     const double timetoJunction2 = earliest_arrival(myDistance, vMax2);
     115             :                     // reaching the signal at yellow might be sufficient
     116           4 :                     const double yellowSlack = myVeh.getVehicleType().getParameter().getJMParam(SUMO_ATTR_JM_DRIVE_AFTER_YELLOW_TIME, 0);
     117             : #ifdef DEBUG_GLOSA
     118             :                     if (DEBUG_COND) {
     119             :                         std::cout << "  vMax2=" << vMax2 << " ttJ2=" << timetoJunction2 << " yellowSlack=" << yellowSlack << "\n";
     120             :                     }
     121             : #endif
     122           4 :                     if (timetoJunction2 <= (timeToSwitch + yellowSlack)) {
     123             :                         // increase speed factor up to a maximum if necessary and useful
     124             :                         // XXX could compute optimal speed factor here
     125           4 :                         myVeh.setChosenSpeedFactor(myMaxSpeedFactor);
     126             :                     }
     127             :                 }
     128             :             }
     129         220 :         } else if (myNextTLSLink->haveRed()) {
     130         216 :             adaptSpeed(myDistance, timeToJunction, timeToSwitch);
     131             :         }
     132             :     }
     133         564 :     return true; // keep the device
     134             : }
     135             : 
     136             : 
     137             : bool
     138          48 : MSDevice_GLOSA::notifyEnter(SUMOTrafficObject& /*veh*/, MSMoveReminder::Notification /*reason*/, const MSLane* /* enteredLane */) {
     139          48 :     const MSLink* prevLink = myNextTLSLink;
     140          48 :     myNextTLSLink = nullptr;
     141          48 :     const MSLane* lane = myVeh.getLane();
     142          48 :     const std::vector<MSLane*>& bestLaneConts = myVeh.getBestLanesContinuation(lane);
     143          48 :     double seen = lane->getLength() - myVeh.getPositionOnLane();
     144             :     int view = 1;
     145          48 :     std::vector<MSLink*>::const_iterator linkIt = MSLane::succLinkSec(myVeh, view, *lane, bestLaneConts);
     146          64 :     while (!lane->isLinkEnd(linkIt)) {
     147          32 :         if (!lane->getEdge().isInternal()) {
     148          16 :             if ((*linkIt)->isTLSControlled()) {
     149          16 :                 myNextTLSLink = *linkIt;
     150          16 :                 myDistance = seen;
     151          16 :                 break;
     152             :             }
     153             :         }
     154          16 :         lane = (*linkIt)->getViaLaneOrLane();
     155          16 :         if (!lane->getEdge().isInternal()) {
     156          16 :             view++;
     157             :         }
     158          16 :         seen += lane->getLength();
     159          16 :         linkIt = MSLane::succLinkSec(myVeh, view, *lane, bestLaneConts);
     160             :     }
     161          48 :     if (prevLink != nullptr && myNextTLSLink == nullptr) {
     162             :         // moved passt tls
     163          16 :         myVeh.setChosenSpeedFactor(myOriginalSpeedFactor);
     164          32 :     } else if (myNextTLSLink != nullptr && prevLink != myNextTLSLink) {
     165             :         // approaching new tls
     166             :         double tlsRange = 1e10;
     167          32 :         const std::string val = myNextTLSLink->getTLLogic()->getParameter("device.glosa.range", "1e10");
     168             :         try {
     169          16 :             tlsRange = StringUtils::toDouble(val);
     170           0 :         } catch (const NumberFormatException&) {
     171           0 :             WRITE_WARNINGF(TL("Invalid value '%' for parameter 'device.glosa.range' of traffic light '%'"),
     172             :                            val, myNextTLSLink->getTLLogic()->getID());
     173           0 :         }
     174          32 :         myRange = MIN2(getFloatParam(myVeh, OptionsCont::getOptions(), "glosa.range", 100, true), tlsRange);
     175             :     }
     176             : 
     177             : #ifdef DEBUG_GLOSA
     178             :     if (DEBUG_COND) std::cout << SIMTIME << " veh=" << myVeh.getID() << " enter=" << myVeh.getLane()->getID() << " hadTLS=" << hadTLS
     179             :                                   << " tls=" << (myNextTLSLink == nullptr ? "NULL" : myNextTLSLink->getTLLogic()->getID()) << " dist=" << myDistance << "\n";
     180             : #endif
     181          48 :     return true; // keep the device
     182             : }
     183             : 
     184             : 
     185             : double
     186         268 : MSDevice_GLOSA::getTimeToSwitch(const MSLink* tlsLink) {
     187             :     assert(tlsLink != nullptr);
     188             :     const MSTrafficLightLogic* const tl = tlsLink->getTLLogic();
     189             :     assert(tl != nullptr);
     190         268 :     const auto& phases = tl->getPhases();
     191         268 :     const int n = (int)phases.size();
     192         268 :     const int cur = tl->getCurrentPhaseIndex();
     193         268 :     SUMOTime result = tl->getNextSwitchTime() - SIMSTEP;
     194         676 :     for (int i = 1; i < n; i++) {
     195         672 :         const auto& phase = phases[(cur + i) % n];
     196         672 :         const char ls = phase->getState()[tlsLink->getTLIndex()];
     197         596 :         if ((tlsLink->haveRed() && (ls == 'g' || ls == 'G'))
     198        1052 :                 || (tlsLink->haveGreen() && ls != 'g' && ls != 'G')) {
     199             :             break;
     200             :         }
     201         408 :         result += phase->duration;
     202             :     }
     203         268 :     return STEPS2TIME(result);
     204             : }
     205             : 
     206             : 
     207             : double
     208         272 : MSDevice_GLOSA::earliest_arrival(double distance, double vMax) {
     209             :     // assume we keep acceleration until we hit maximum speed
     210         272 :     const double v = myVeh.getSpeed();
     211         272 :     const double a = myVeh.getCarFollowModel().getMaxAccel();
     212         272 :     const double accel_time = MIN2((vMax - v) / a, time_to_junction_at_continuous_accel(distance, v));
     213         272 :     const double remaining_dist = distance - distance_at_continuous_accel(v, accel_time);
     214         272 :     const double remaining_time = remaining_dist / vMax;
     215         272 :     return accel_time + remaining_time;
     216             : }
     217             : 
     218             : 
     219             : /*
     220             : double
     221             : MSDevice_GLOSA::latest_arrival(speed, distance, earliest) {
     222             :     // assume we keep current speed until within myRange and then decelerate to myMinSpeed
     223             :     speed = max(speed, GLOSA_MIN_SPEED)
     224             :     potential_decel_dist = min(distance, GLOSA_RANGE)
     225             :     decel_time = (speed - GLOSA_MIN_SPEED) / GLOSA_DECEL
     226             :     avg_decel_speed = (speed + GLOSA_MIN_SPEED) / 2.0
     227             :     decel_dist = decel_time * avg_decel_speed
     228             :     if decel_dist > potential_decel_dist:
     229             :         decel_dist = potential_decel_dist
     230             :         # XXX actually avg_decel_speed is higher in this case
     231             :         decel_time = decel_dist / avg_decel_speed
     232             :     slow_dist = potential_decel_dist - decel_dist
     233             :     fast_dist = distance - (decel_dist + slow_dist)
     234             :     result = fast_dist / speed + decel_time + slow_dist / GLOSA_MIN_SPEED
     235             :     if result < earliest:
     236             :         if (distance > 15):
     237             :             print("DEBUG: fixing latest arrival of %s to match earliest of %s" % (result, earliest))
     238             :         result = earliest
     239             :     return result
     240             :     return 0;
     241             : }
     242             : */
     243             : 
     244             : 
     245             : double
     246         272 : MSDevice_GLOSA::distance_at_continuous_accel(double speed, double time) {
     247             :     const double v = speed;
     248             :     const double t = time;
     249         272 :     const double a = myVeh.getCarFollowModel().getMaxAccel();
     250             :     // integrated area composed of a rectangle and a triangle
     251         272 :     return v * t + a * t * t / 2;
     252             : }
     253             : 
     254             : 
     255             : double
     256         272 : MSDevice_GLOSA::time_to_junction_at_continuous_accel(double d, double v) {
     257             :     // see distance_at_continuous_accel
     258             :     // t^2 + (2v/a)t - 2d/a = 0
     259         272 :     const double a = myVeh.getCarFollowModel().getMaxAccel();
     260         272 :     const double p_half = v / a;
     261         272 :     const double t = -p_half + sqrt(p_half * p_half + 2 * d / a);
     262         272 :     return t;
     263             : }
     264             : 
     265             : 
     266             : void
     267         216 : MSDevice_GLOSA::adaptSpeed(double distance, double timeToJunction, double timeToSwitch) {
     268             :     // ensure that myVehicle arrives at the
     269             :     // junction with maximum speed when it switches to green
     270             :     // car performs a slowDown at time z to speed x for duration y
     271             :     // there are two basic strategies
     272             :     // a) maximize z -> this saves road space but leads to low x and thus excessive braking
     273             :     // b) maximize x -> this saves fuel but wastes road
     274             :     // c) compromise: b) but only when distance to junction is below a threshold
     275             : 
     276         216 :     const double vMax = myVeh.getLane()->getVehicleMaxSpeed(&myVeh);
     277             :     if (timeToJunction < timeToSwitch
     278         216 :             && myVeh.getSpeed() > myMinSpeed) {
     279             :         // need to start/continue maneuver
     280             :         const double t = timeToSwitch;
     281         160 :         const double a = myVeh.getCarFollowModel().getMaxAccel();
     282             :         const double d = myVeh.getCarFollowModel().getMaxDecel();
     283             :         const double u = myMinSpeed;
     284             :         const double w = vMax;
     285             :         const double s = distance;
     286         160 :         const double v = myVeh.getSpeed();
     287             :         // x : target speed
     288             :         // y : slow down duration
     289             :         // s is composed of 1 trapezoid (decel), 1 rectangle (maintain), 1 trapezoid (accel)
     290             :         // s = (v^2-x^2)/2d + x*(y-(v-x)/d) + (w^2-x^2)/2a
     291             :         // y = t - (w-x)/d
     292             :         // solution for x curtesy of mathomatic.org
     293             :         const double sign0 = -1; // XXX hack
     294         160 :         const double root_argument = a * d * ((2.0 * d * (s - (w * t))) - ((v - w) * (v - w)) + (a * ((d * (t * t)) + (2.0 * (s - (t * v))))));
     295         160 :         if (root_argument < 0) {
     296             : #ifdef DEBUG_GLOSA
     297             :             WRITE_WARNINGF("GLOSA error 1 root_argument=% s=% t=% v=%", root_argument, s, t, v);
     298             : #endif
     299          84 :             return;
     300             :         }
     301         124 :         const double x = (((a * (v - (d * t))) + (d * w) - sign0 * sqrt(root_argument)) / (d + a));
     302         124 :         const double y = t - (w - x) / d;
     303         124 :         if (!(x >= u && x <= w && y > 0 && y < t)) {
     304             : #ifdef DEBUG_GLOSA
     305             :             WRITE_WARNINGF("GLOSA error 2 x=% y=% s=% t=% v=%", x, y, s, t, v);
     306             : #endif
     307             :             return;
     308             :         }
     309             :         const double targetSpeed = x;
     310             :         const double duration = y;
     311             : #ifdef DEBUG_GLOSA
     312             :         if (DEBUG_COND) {
     313             :             std::cout << "  targetSpeed=" << targetSpeed << " duration=" << duration << "\n";
     314             :         }
     315             : #endif
     316             :         std::vector<std::pair<SUMOTime, double> > speedTimeLine;
     317          76 :         speedTimeLine.push_back(std::make_pair(MSNet::getInstance()->getCurrentTimeStep(), myVeh.getSpeed()));
     318          76 :         speedTimeLine.push_back(std::make_pair(MSNet::getInstance()->getCurrentTimeStep() + TIME2STEPS(duration), targetSpeed));
     319          76 :         myVeh.getInfluencer().setSpeedTimeLine(speedTimeLine);
     320             :     } else {
     321             :         // end maneuver
     322             :         std::vector<std::pair<SUMOTime, double> > speedTimeLine;
     323          56 :         speedTimeLine.push_back(std::make_pair(MSNet::getInstance()->getCurrentTimeStep(), myVeh.getSpeed()));
     324          56 :         speedTimeLine.push_back(std::make_pair(MSNet::getInstance()->getCurrentTimeStep(), vMax));
     325          56 :         myVeh.getInfluencer().setSpeedTimeLine(speedTimeLine);
     326             :     }
     327             : }
     328             : 
     329             : 
     330             : void
     331          16 : MSDevice_GLOSA::generateOutput(OutputDevice* /*tripinfoOut*/) const {
     332             :     /*
     333             :     if (tripinfoOut != nullptr) {
     334             :         tripinfoOut->openTag("glosa_device");
     335             :         tripinfoOut->closeTag();
     336             :     }
     337             :     */
     338          16 : }
     339             : 
     340             : std::string
     341           0 : MSDevice_GLOSA::getParameter(const std::string& key) const {
     342           0 :     if (key == "minSpeed") {
     343           0 :         return toString(myMinSpeed);
     344             :     }
     345           0 :     throw InvalidArgument("Parameter '" + key + "' is not supported for device of type '" + deviceName() + "'");
     346             : }
     347             : 
     348             : 
     349             : void
     350           0 : MSDevice_GLOSA::setParameter(const std::string& key, const std::string& value) {
     351             :     double doubleValue;
     352             :     try {
     353           0 :         doubleValue = StringUtils::toDouble(value);
     354           0 :     } catch (NumberFormatException&) {
     355           0 :         throw InvalidArgument("Setting parameter '" + key + "' requires a number for device of type '" + deviceName() + "'");
     356           0 :     }
     357           0 :     if (key == "minSpeed") {
     358           0 :         myMinSpeed = doubleValue;
     359             :     } else {
     360           0 :         throw InvalidArgument("Setting parameter '" + key + "' is not supported for device of type '" + deviceName() + "'");
     361             :     }
     362           0 : }
     363             : 
     364             : 
     365             : /****************************************************************************/

Generated by: LCOV version 1.14