LCOV - code coverage report
Current view: top level - src/utils/router - RailwayRouter.h (source / functions) Coverage Total Hit
Test: lcov.info Lines: 98.3 % 118 116
Test Date: 2026-03-02 16:00:03 Functions: 85.7 % 28 24

            Line data    Source code
       1              : /****************************************************************************/
       2              : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3              : // Copyright (C) 2001-2026 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    RailwayRouter.h
      15              : /// @author  Jakob Erdmann
      16              : /// @date    Tue, 25 Feb 2020
      17              : ///
      18              : // The RailwayRouter builds a special network for railway routing to handle train reversal restrictions (delegates to a SUMOAbstractRouter)
      19              : /****************************************************************************/
      20              : #pragma once
      21              : #include <config.h>
      22              : 
      23              : #include <string>
      24              : #include <vector>
      25              : #include <algorithm>
      26              : #include <assert.h>
      27              : #ifdef HAVE_FOX
      28              : #include <utils/foxtools/fxheader.h>
      29              : #endif
      30              : #include <utils/common/MsgHandler.h>
      31              : #include <utils/common/SUMOTime.h>
      32              : #include <utils/common/ToString.h>
      33              : #include <utils/iodevices/OutputDevice.h>
      34              : #include "SUMOAbstractRouter.h"
      35              : #include "DijkstraRouter.h"
      36              : #include "RailEdge.h"
      37              : 
      38              : 
      39              : //#define RailwayRouter_DEBUG_ROUTES
      40              : 
      41              : // ===========================================================================
      42              : // class definitions
      43              : // ===========================================================================
      44              : /**
      45              :  * @class RailwayRouter
      46              :  * The router for rail vehicles for track networks where some sections may be used in both directions
      47              :  * and trains may reverse depending on location and train length
      48              :  *
      49              :  * Example (assume each track section is 100m long) running from left to right and a negative sign indicates reverse direction
      50              :  *
      51              :  *       A     C      D
      52              :  *   .______.______.______.
      53              :  *   ._____/
      54              :  *       B
      55              :  *
      56              :  * Consider a train of 200m length that enters on B and must move to A (with a reversal):
      57              :  * The necessary route is B C D -D -C -A were D,-D are needed for the train to fully pass the switch
      58              :  *
      59              :  * We shadow the normal edge graph with a railEdge graph to include virtual
      60              :  * turnarounds that look at the train length.
      61              :  * The graph extension takes place via RailEdge::init()
      62              :  * For edge C we create a virtual turnaround (as successor of C) that connectes C with -C and is then expanded to C D -D -C for trains longer than 100m.
      63              :  * The expension takes place via RailEdge::insertOriginalEdges()
      64              :  *
      65              :  */
      66              : template<class E, class V>
      67              : class RailwayRouter : public SUMOAbstractRouter<E, V> {
      68              : 
      69              : private:
      70              : 
      71              : 
      72              :     typedef RailEdge<E, V> _RailEdge;
      73              :     typedef SUMOAbstractRouter<_RailEdge, V> _InternalRouter;
      74              :     typedef DijkstraRouter<_RailEdge, V> _InternalDijkstra;
      75              :     typedef std::map<const E*, RouterProhibition> Prohibitions;
      76              : 
      77              : public:
      78              : 
      79              :     /// Constructor
      80          617 :     RailwayRouter(const std::vector<E*>& edges, bool unbuildIsWarning, typename SUMOAbstractRouter<E, V>::Operation effortOperation,
      81              :                   typename SUMOAbstractRouter<E, V>::Operation ttOperation = nullptr, bool silent = false,
      82              :                   bool havePermissions = false, const bool haveRestrictions = false, double maxTrainLength = 5000,
      83              :                   double reversalPenalty = 60) :
      84              :         SUMOAbstractRouter<E, V>("RailwayRouter", unbuildIsWarning, effortOperation, ttOperation, havePermissions, haveRestrictions),
      85          617 :         myInternalRouter(nullptr), myOriginal(nullptr), mySilent(silent),
      86          617 :         myMaxTrainLength(maxTrainLength),
      87         1234 :         myLastFrom(nullptr) {
      88          617 :         myReversalPenalty = reversalPenalty;
      89          617 :         myStaticOperation = effortOperation;
      90       242033 :         for (const E* const edge : edges) {
      91       241416 :             myInitialEdges.push_back(edge->getRailwayRoutingEdge());
      92              :         }
      93          617 :     }
      94              : 
      95              :     /// Destructor
      96         1992 :     virtual ~RailwayRouter() {
      97          669 :         delete myInternalRouter;
      98         2661 :     }
      99              : 
     100          379 :     SUMOAbstractRouter<E, V>* clone() {
     101          379 :         return new RailwayRouter<E, V>(this);
     102              :     }
     103              : 
     104              :     /** @brief Builds the route between the given edges using the minimum effort at the given time
     105              :         The definition of the effort depends on the wished routing scheme */
     106        28918 :     bool compute(const E* from, const E* to, const V* const vehicle, SUMOTime msTime, std::vector<const E*>& into, bool silent = false) {
     107        28918 :         ensureInternalRouter();
     108        28918 :         if (vehicle->getLength() > myMaxTrainLength) {
     109           15 :             WRITE_WARNINGF("Vehicle '%' with length % exceeds configured value of --railway.max-train-length %",
     110              :                            vehicle->getID(), toString(vehicle->getLength()), toString(myMaxTrainLength));
     111              :         }
     112        28918 :         return _compute(from, to, vehicle, msTime, into, silent, false);
     113              :     }
     114              : 
     115           98 :     void prohibit(const Prohibitions& toProhibit) {
     116           98 :         ensureInternalRouter();
     117              :         typename _InternalRouter::Prohibitions _toProhibit;
     118          113 :         for (auto item : toProhibit) {
     119           15 :             _toProhibit[item.first->getRailwayRoutingEdge()] = item.second;
     120              :         }
     121           98 :         myInternalRouter->prohibit(_toProhibit);
     122              :         this->myProhibited = toProhibit;
     123              : #ifdef RailwayRouter_DEBUG_ROUTES
     124              :         std::cout << "RailRouter numProhibitions=" << toProhibit.size() << "\n";
     125              : #endif
     126           98 :     }
     127              : 
     128              : 
     129        78573 :     double recomputeCosts(const std::vector<const E*>& edges, const V* const v, SUMOTime msTime, double* lengthp = nullptr) const {
     130        78573 :         double effort = SUMOAbstractRouter<E, V>::recomputeCosts(edges, v, msTime, lengthp);
     131              :         const E* prev = nullptr;
     132              :         // reversal corrections
     133        78573 :         double timeCorrection = STEPS2TIME(msTime);
     134      1578961 :         for (const E* const e : edges) {
     135      1500388 :             if (prev != nullptr && e->getBidiEdge() == prev) {
     136        30465 :                 if (e->getLength() > v->getLength()) {
     137              :                     // vehicle doesn't need to drive to the end of the edge
     138              :                     // @note exact timing correction for time-dependent effort is not done
     139        30344 :                     const double savingsFactor = 1 - v->getLength() / e->getLength();
     140              :                     double effortCorrection = 0;
     141        30344 :                     double lengthCorrection = 0.;
     142        30344 :                     effortCorrection += this->getEffort(prev, v, timeCorrection);
     143        30344 :                     this->updateViaCost(prev, e, v, timeCorrection, effortCorrection, lengthCorrection);
     144        30344 :                     effort -= savingsFactor * effortCorrection;
     145        30344 :                     if (lengthp != nullptr) {
     146            0 :                         *lengthp -= savingsFactor * lengthCorrection;
     147              :                     }
     148              :                 }
     149        30465 :                 effort += myReversalPenalty;
     150              :             }
     151              :             prev = e;
     152              :         }
     153        78573 :         return effort;
     154              :     }
     155              : 
     156           62 :     inline void setBulkMode(const bool mode) {
     157           62 :         if (myInternalRouter != nullptr) {
     158           62 :             myInternalRouter->setBulkMode(mode);
     159              :         }
     160           62 :     }
     161              : 
     162              : private:
     163          379 :     RailwayRouter(RailwayRouter* other) :
     164              :         SUMOAbstractRouter<E, V>(other),
     165          379 :         myInternalRouter(nullptr),
     166          379 :         myOriginal(other),
     167          379 :         mySilent(other->mySilent),
     168          379 :         myMaxTrainLength(other->myMaxTrainLength)
     169          379 :     {}
     170              : 
     171        29016 :     void ensureInternalRouter() {
     172        29016 :         if (myInternalRouter == nullptr) {
     173         1338 :             myInternalRouter = new _InternalDijkstra(getRailEdges(), this->myErrorMsgHandler == MsgHandler::getWarningInstance(), &getTravelTimeStatic,
     174          669 :                     nullptr, mySilent, nullptr, this->myHavePermissions, this->myHaveRestrictions);
     175              :         }
     176        29016 :     }
     177              : 
     178        28949 :     bool _compute(const E* from, const E* to, const V* const vehicle, SUMOTime msTime, std::vector<const E*>& into, bool silent, bool avoidUnsafeBackTracking) {
     179              :         // make sure that the vehicle can turn-around when starting on a short edge (the virtual turn-around for this lies backwards along the route / track)
     180              :         // if reversals are forbidden (negative penalty), we don't need to check this
     181          102 :         std::vector<double> backLengths;
     182        28949 :         double backDist = myReversalPenalty >= 0 ? vehicle->getLength() - from->getLength() : 0;
     183              :         const E* start = from;
     184        29404 :         while (backDist > 0) {
     185          839 :             const E* prev = getStraightPredecessor(start, into, (int)backLengths.size());
     186          839 :             if (prev == nullptr) {
     187              : #ifdef RailwayRouter_DEBUG_ROUTES
     188              :                 std::cout << " Could not determine back edge for vehicle '" << vehicle->getID() << "' when routing from edge '" << from->getID() << "' at time=" << time2string(msTime) << "\n";
     189              : #endif
     190              :                 //WRITE_WARNING("Could not determine back edge for vehicle '" + vehicle->getID() + "' when routing from edge '" + from->getID() + "' at time=" + time2string(msTime));
     191              :                 break;
     192              :             }
     193          486 :             backDist -= prev->getLength();
     194          486 :             if (avoidUnsafeBackTracking && prev->getSuccessors().size() > 1) {
     195              :                 bool foundSwitch = false;
     196           54 :                 for (const E* succ : prev->getSuccessors()) {
     197           54 :                     if (succ != start && succ != prev->getBidiEdge()) {
     198              :                         foundSwitch = true;
     199              :                         break;
     200              :                     }
     201              :                 }
     202           31 :                 if (foundSwitch) {
     203              :                     break;
     204              :                 }
     205              :             }
     206          455 :             backLengths.push_back(prev->getLength() + (backLengths.empty()
     207          455 :                                   ? MIN2(vehicle->getLength(), from->getLength())
     208              :                                   : backLengths.back()));
     209              :             start = prev;
     210              :         }
     211              : 
     212          102 :         std::vector<const _RailEdge*> intoTmp;
     213        28949 :         if (myLastFrom != start) {
     214        27609 :             myInternalRouter->setBulkMode(false);
     215              :         }
     216        28949 :         bool success = myInternalRouter->compute(start->getRailwayRoutingEdge(), to->getRailwayRoutingEdge(), vehicle, msTime, intoTmp, silent);
     217        28949 :         myLastFrom = start;
     218              : #ifdef RailwayRouter_DEBUG_ROUTES
     219              :         std::cout << "RailRouter veh=" << vehicle->getID() << " from=" << from->getID() << " to=" << to->getID() << " t=" << time2string(msTime)
     220              :                   << " safe=" << avoidUnsafeBackTracking << " success=" << success << " into=" << toString(into) << "\n";
     221              : #endif
     222        28949 :         if (success) {
     223              :             const size_t intoSize = into.size();
     224        28897 :             int backIndex = (int)backLengths.size();
     225       425129 :             for (const _RailEdge* railEdge : intoTmp) {
     226       396232 :                 if (railEdge->getOriginal() != nullptr) {
     227       388515 :                     backIndex--;
     228              :                 }
     229              :                 // prevent premature reversal on back edge (extend train length)
     230       396232 :                 const double length = backIndex >= 0 ? backLengths[backIndex] : vehicle->getLength();
     231       396232 :                 railEdge->insertOriginalEdges(length, into);
     232              :             }
     233              : #ifdef RailwayRouter_DEBUG_ROUTES
     234              :             std::cout << "RailRouter: internal result=" << toString(intoTmp) << "\n";
     235              :             std::cout << "RailRouter: expanded result=" << toString(into) << "\n";
     236              :             std::cout << "RailRouter: backLengths=" << toString(backLengths) << " bls=" << backLengths.size() << " intoSize=" << intoSize << " final result=" << toString(into) << "\n";
     237              : #endif
     238        28897 :             if (backLengths.size() > 0) {
     239              :                 // skip the virtual back-edges
     240              :                 into.erase(into.begin() + intoSize, into.begin() + intoSize + backLengths.size());
     241          349 :                 if (*(into.begin() + intoSize) != from) {
     242           31 :                     if (!avoidUnsafeBackTracking) {
     243              :                         // try again, this time with more safety (but unable to
     244              :                         // make use of turn-arounds on short edge)
     245              :                         into.erase(into.begin() + intoSize, into.end());
     246              :                         // we are starting from a different edge and are thus violating the assumptions of bulk mode
     247           31 :                         success = _compute(from, to, vehicle, msTime, into, silent, true);
     248              :                         return success;
     249              :                     } else {
     250            0 :                         WRITE_WARNING("Railway routing failure due to turn-around on short edge '" + from->getID()
     251              :                                       + "' for vehicle '" + vehicle->getID() + "' time=" + time2string(msTime) + ".");
     252              :                     }
     253              :                 }
     254              :             }
     255        28866 :             if (this->myProhibited.size() > 0) {
     256              :                 // make sure that turnarounds don't use prohibited edges
     257          138 :                 for (const E* e : into) {
     258          126 :                     if (this->myProhibited.find(e) != this->myProhibited.end()) {
     259              :                         into.clear();
     260              :                         success = false;
     261              :                         break;
     262              :                     }
     263              :                 }
     264              :             }
     265              :         }
     266              :         return success;
     267              : 
     268        28949 :     }
     269              : 
     270          779 :     const std::vector<_RailEdge*>& getRailEdges() {
     271          779 :         if (myOriginal != nullptr) {
     272          110 :             return myOriginal->getRailEdges();
     273              :         }
     274              : #ifdef HAVE_FOX
     275          669 :         FXMutexLock locker(myLock);
     276              : #endif
     277          669 :         if (myRailEdges.empty()) {
     278          560 :             myRailEdges = myInitialEdges;
     279          560 :             int numericalID = myInitialEdges.back()->getNumericalID() + 1;
     280        38313 :             for (_RailEdge* railEdge : myInitialEdges) {
     281        37753 :                 railEdge->init(myRailEdges, numericalID, myMaxTrainLength, myReversalPenalty >= 0);
     282              :             }
     283              :         }
     284          669 :         return myRailEdges;
     285              :     }
     286              : 
     287      1265176 :     static inline double getTravelTimeStatic(const RailEdge<E, V>* const edge, const V* const veh, double time) {
     288      1265176 :         if (edge->getOriginal() != nullptr) {
     289      1188605 :             return (*myStaticOperation)(edge->getOriginal(), veh, time);
     290              :         } else {
     291              :             // turnaround edge
     292        76571 :             if (edge->isVirtual()) {
     293              :                 // add up time for replacement edges
     294           60 :                 std::vector<const E*> repl;
     295        56521 :                 edge->insertOriginalEdges(veh->getLength(), repl);
     296              :                 assert(repl.size() > 0);
     297              :                 double seenDist = 0;
     298              :                 double result = 0;
     299              :                 repl.pop_back(); // last edge must not be used fully
     300       216468 :                 for (const E* e : repl) {
     301       159947 :                     result += (*myStaticOperation)(e, veh, time + result);
     302       159947 :                     seenDist += e->getLength();
     303              :                 }
     304        56521 :                 const double lengthOnLastEdge = MAX2(0.0, veh->getLength() - seenDist);
     305        56521 :                 return result + myReversalPenalty + lengthOnLastEdge * myReversalPenaltyFactor;
     306        56461 :             } else {
     307              :                 // if the edge from which this turnaround starts is longer
     308              :                 // than the vehicle then we are overstimating the cost
     309              :                 // (because the turnaround may happen before driving to the end)
     310              :                 // However, unless the vehicle starts on this edge, we should be taking a
     311              :                 // virtual turnaround at the end of the previous edge. Otherwise, the exaggerated cost doesn't
     312        20050 :                 return myReversalPenalty;
     313              :             }
     314              :         }
     315              :     }
     316              : 
     317              : 
     318          839 :     static const E* getStraightPredecessor(const E* edge, std::vector<const E*>& prevRoute, int backIndex) {
     319              :         const E* result = nullptr;
     320              : #ifdef RailwayRouter_DEBUG_ROUTES
     321              :         std::cout << "  getStraightPredecessor edge=" << edge->getID() << " prevRouteSize=" << prevRoute.size() << " backIndex=" << backIndex << "\n";
     322              : #endif
     323          839 :         if ((int)prevRoute.size() > backIndex) {
     324           20 :             return prevRoute[(int)prevRoute.size() - 1 - backIndex];
     325              :         }
     326         1983 :         for (const E* cand : edge->getPredecessors()) {
     327         1249 :             if (!cand->isInternal() && cand->getBidiEdge() != edge) {
     328              :                 //std::cout << "    cand=" << cand->getID() << "\n";
     329          636 :                 if (result == nullptr) {
     330              :                     result = cand;
     331              :                 } else {
     332              :                     // predecessor not unique. Better abort with a warning
     333              :                     return nullptr;
     334              :                 }
     335              :             }
     336              :         }
     337              :         return result;
     338              :     }
     339              : 
     340              : private:
     341              :     _InternalRouter* myInternalRouter;
     342              :     RailwayRouter<E, V>* const myOriginal;
     343              :     /// @brief a RailEdge for every existing edge, filled on construction (but not in clones)
     344              :     std::vector<_RailEdge*> myInitialEdges;
     345              :     /// @brief complete rail network filled on demand (but not in clones)
     346              :     std::vector<_RailEdge*> myRailEdges;
     347              : 
     348              :     /// @brief whether to suppress warning/error if no route was found
     349              :     const bool mySilent;
     350              : 
     351              :     const double myMaxTrainLength;
     352              : 
     353              :     /// @brief track previous edge for correct bulk routing
     354              :     const E* myLastFrom;
     355              : 
     356              : #ifdef HAVE_FOX
     357              :     /// The mutex used to avoid concurrent updates of myRailEdges
     358              :     mutable FXMutex myLock;
     359              : #endif
     360              : 
     361              :     /// @brief The object's operation to perform. (hack)
     362              :     static typename SUMOAbstractRouter<E, V>::Operation myStaticOperation;
     363              : 
     364              :     static double myReversalPenalty;
     365              :     static double myReversalPenaltyFactor;
     366              : 
     367              : private:
     368              :     /// @brief Invalidated assignment operator
     369              :     RailwayRouter& operator=(const RailwayRouter& s);
     370              : 
     371              : };
     372              : 
     373              : template<class E, class V>
     374              : typename SUMOAbstractRouter<E, V>::Operation RailwayRouter<E, V>::myStaticOperation(nullptr);
     375              : template<class E, class V>
     376              : double RailwayRouter<E, V>::myReversalPenalty(60);
     377              : template<class E, class V>
     378              : double RailwayRouter<E, V>::myReversalPenaltyFactor(0.2); // 1/v
        

Generated by: LCOV version 2.0-1