LCOV - code coverage report
Current view: top level - src/microsim/traffic_lights - NEMAController.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 95.4 % 785 749
Test Date: 2026-03-02 16:00:03 Functions: 93.9 % 66 62

            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    NEMAController.cpp
      15              : /// @author  Tianxin Li
      16              : /// @author  Qichao Wang
      17              : /// @author  Max Schrader
      18              : /// @date    August 2020
      19              : ///
      20              : // An actuated NEMA-phase-compliant traffic light logic
      21              : /****************************************************************************/
      22              : #include <config.h>
      23              : 
      24              : #include <cassert>
      25              : #include <utility>
      26              : #include <vector>
      27              : #include <bitset>
      28              : #include <sstream>
      29              : #include <iostream>
      30              : #include <utils/common/FileHelpers.h>
      31              : #include <utils/common/StringUtils.h>
      32              : #include <utils/common/StringTokenizer.h>
      33              : #include <microsim/MSEventControl.h>
      34              : #include <microsim/MSGlobals.h>
      35              : #include <microsim/MSNet.h>
      36              : #include <microsim/MSLane.h>
      37              : #include <microsim/MSEdge.h>
      38              : #include <microsim/output/MSDetectorControl.h>
      39              : #include <microsim/output/MSE2Collector.h>
      40              : #include <microsim/output/MSInductLoop.h>
      41              : #include <netload/NLDetectorBuilder.h>
      42              : #include "NEMAController.h"
      43              : 
      44              : 
      45              : // ===========================================================================
      46              : // parameter defaults definitions
      47              : // ===========================================================================
      48              : #define INVALID_POSITION std::numeric_limits<double>::max() // tl added
      49              : 
      50              : // #define DEBUG_NEMA
      51              : // #define FUZZ_TESTING
      52              : // #define DEBUG_NEMA_SWITCH
      53              : 
      54              : // ===========================================================================
      55              : // method definitions
      56              : // ===========================================================================
      57          122 : NEMALogic::NEMALogic(MSTLLogicControl& tlcontrol,
      58              :                      const std::string& id, const std::string& programID,
      59              :                      const SUMOTime _offset,
      60              :                      const Phases& phases,
      61              :                      int step, SUMOTime delay,
      62              :                      const std::map<std::string, std::string>& parameter,
      63          122 :                      const std::string& basePath) :
      64              :     MSSimpleTrafficLightLogic(tlcontrol, id, programID, _offset, TrafficLightType::NEMA, phases, step, delay, parameter),
      65          122 :     myPhase(phases[0]->duration, phases[0]->getState()) {
      66          244 :     myDetectorLength = StringUtils::toDouble(getParameter("detector-length", "20"));
      67          244 :     myDetectorLengthLeftTurnLane = StringUtils::toDouble(getParameter("detector-length-leftTurnLane", "20"));
      68          854 :     myCycleLength = TIME2STEPS(StringUtils::toDouble(getParameter("total-cycle-length", getParameter("cycle-length", getParameter(toString(SUMO_ATTR_CYCLETIME), "60")))));
      69          122 :     myNextCycleLength = myCycleLength;
      70          122 :     myDefaultCycleTime = myCycleLength;
      71          244 :     myShowDetectors = StringUtils::toBool(getParameter("show-detectors", toString(OptionsCont::getOptions().getBool("tls.actuated.show-detectors"))));
      72          244 :     myFile = FileHelpers::checkForRelativity(getParameter("file", "NUL"), basePath);
      73          366 :     myFreq = TIME2STEPS(StringUtils::toDouble(getParameter("freq", "300")));
      74          244 :     myVehicleTypes = getParameter("vTypes", "");
      75          244 :     myControllerType = parseControllerType(getParameter("controllerType", "TS2"));
      76          244 :     ignoreErrors = StringUtils::toBool(getParameter("ignore-errors", "false"));
      77              :     // This should be extended in the future.
      78          122 :     myNumberRings = 2;
      79          122 : }
      80              : 
      81          339 : NEMALogic::~NEMALogic() {
      82              :     // delete the phase objects
      83          709 :     for (auto p : myPhaseObjs) {
      84          596 :         delete p;
      85              :     }
      86          565 : }
      87              : 
      88              : void
      89          122 : NEMALogic::constructTimingAndPhaseDefs(std::string& barriers, std::string& coordinates, std::string& ring1, std::string& ring2) {
      90              : 
      91              :     // read in the barrier and coordinated phases from the XML
      92          244 :     std::vector<int> barrierPhases = readParaFromString(barriers);
      93          122 :     std::vector<int> coordinatePhases = readParaFromString(coordinates);
      94              : 
      95              :     // create a {{}, {}} vector of phases
      96          488 :     rings.push_back(readParaFromString(ring1));
      97          244 :     rings.push_back(readParaFromString(ring2));
      98              : 
      99              : #ifdef DEBUG_NEMA
     100              :     //print to check
     101              :     for (int i = 0; i < (int)rings.size(); i++) {
     102              :         int count = 0;
     103              :         std::cout << "Ring" << i + 1 << " includes phases: \t";
     104              :         for (auto j : rings[i]) {
     105              :             count++;
     106              :             std::cout << j << " ";
     107              :             if (count == 2 || count == 4) {
     108              :                 std::cout << " | ";
     109              :             }
     110              :         }
     111              :         std::cout << std::endl;
     112              :     }
     113              : #endif
     114              : 
     115              :     // load the recalls, if they exist
     116          244 :     std::vector<int> VecMinRecall = readParaFromString(getParameter("minRecall", "1,2,3,4,5,6,7,8"));
     117          244 :     std::vector<int> VecMaxRecall = readParaFromString(getParameter("maxRecall", ""));
     118              : 
     119              : #ifdef DEBUG_NEMA
     120              :     std::cout << "minRecall: ";
     121              :     for (int i = 0; i < 8; i++) {
     122              :         std::cout << vectorContainsPhase(VecMinRecall, i + 1) << '\t';
     123              :     }
     124              :     std::cout << std::endl;
     125              : 
     126              :     std::cout << "maxRecall: ";
     127              :     for (int i = 0; i < 8; i++) {
     128              :         std::cout << vectorContainsPhase(VecMaxRecall, i + 1) << '\t';
     129              :     }
     130              :     std::cout << std::endl;
     131              : #endif
     132              : 
     133              :     // loop through the rings and construct NEMAPhases.
     134              :     // This relies on the phase being in order in the rings parameter in the configuration file
     135              :     int ringNum = 0;
     136              :     int lastPhaseIter = 0;
     137              :     int phaseIter = 0;
     138          352 :     for (const auto& r : rings) {
     139              :         int ringIter = 0;
     140              :         lastPhaseIter = phaseIter;
     141              :         phaseIter = 0;
     142         1170 :         for (const auto& p : r) {
     143          938 :             if (p != 0) {
     144              :                 // find the phase definition matching the phase integer
     145              :                 MSPhaseDefinition* tempPhase = nullptr;
     146         2070 :                 for (const auto& pDef : myPhases) {
     147         4128 :                     if (string2int(pDef->getName()) == p) {
     148          618 :                         tempPhase = pDef;
     149          618 :                         break;
     150              :                     }
     151              :                 }
     152              :                 // there must be a matching MSPhaseDefinition
     153          624 :                 if (tempPhase == nullptr) {
     154           24 :                     throw ProcessError("At traffic signal '" + myID + "' program '" + myProgramID + "' no phase named '" + toString(p) + "' was found");
     155              :                 }
     156              : 
     157              :                 // create lane specific objects
     158              :                 std::string state = tempPhase->getState();
     159              : 
     160              :                 // check that all phases have the same length. myPhaseStrLen is initially set to -1.
     161          618 :                 if (myPhaseStrLen < 0) {
     162          116 :                     myPhaseStrLen = (int)state.size();
     163          502 :                 } else if (myPhaseStrLen != (int)state.size()) {
     164            0 :                     throw ProcessError(TLF("At NEMA tlLogic '%', different sizes of NEMA phase states. Please check the NEMA XML", getID()));
     165              :                 }
     166              : 
     167              :                 // get the lane-based info
     168              :                 StringVector laneIDs;
     169              :                 IntVector controlledStateIndexes;
     170         1236 :                 getLaneInfoFromNEMAState(state, laneIDs, controlledStateIndexes);
     171              : 
     172              :                 std::vector<std::string> laneIDs_vector;
     173         2003 :                 for (std::string laneID : laneIDs) {
     174         1385 :                     laneIDs_vector.push_back(laneID);
     175         1385 :                     myLanePhaseMap[laneID] = p;
     176              :                 }
     177          618 :                 phase2ControllerLanesMap[p] = laneIDs_vector;
     178              : 
     179              :                 // Create the Phase Object
     180              :                 // find if it is at a barrier
     181          618 :                 bool barrierPhase = vectorContainsPhase(barrierPhases, p) || vectorContainsPhase(coordinatePhases, p);
     182              :                 // is it a coordinate phase
     183          618 :                 bool coordinatePhase = vectorContainsPhase(coordinatePhases, p) && coordinateMode;
     184              :                 // is there a minimum or max recall
     185          618 :                 bool minRecall = vectorContainsPhase(VecMinRecall, p);
     186          618 :                 bool maxRecall = vectorContainsPhase(VecMaxRecall, p);
     187              :                 // A phase can "green rest" only if it has a recall and no other phases on that ring do OR if NO phases have a recall (unique case)
     188          618 :                 bool phaseGreenRest = ((VecMaxRecall.size() + VecMinRecall.size()) < 1);
     189          618 :                 if (!phaseGreenRest) {
     190          618 :                     bool recallActive = minRecall || maxRecall;
     191          618 :                     if (recallActive) {
     192         1352 :                         for (const auto& pO : r) {
     193         1304 :                             if (pO != p) {
     194         2148 :                                 if (vectorContainsPhase(VecMinRecall, pO)
     195         1074 :                                         || vectorContainsPhase(VecMaxRecall, pO)) {
     196              :                                     recallActive = false;
     197              :                                     break;
     198              :                                 }
     199              :                             }
     200              :                         }
     201              :                         // only set the green rest to true if I am the only phase on my ring with a recall
     202              :                         phaseGreenRest = recallActive;
     203              :                     }
     204              :                 }
     205              :                 // could add per-phase fixforceoff here
     206              :                 // barrierNum is either 0 or 1, depending on mainline side or sidestreet
     207          618 :                 int barrierNum = ringIter / 2;
     208              : 
     209              :                 // now ready to create the phase
     210          618 :                 myPhaseObjs.push_back(
     211          618 :                     new NEMAPhase(p, barrierPhase, phaseGreenRest, coordinatePhase, minRecall, maxRecall, fixForceOff, barrierNum, ringNum, controlledStateIndexes, tempPhase)
     212              :                 );
     213              : 
     214              :                 // Add a reference to the sequentially prior phase
     215          618 :                 if (phaseIter > 0) {
     216          388 :                     myPhaseObjs.back()->setSequentialPriorPhase(myPhaseObjs[lastPhaseIter + (phaseIter - 1)]);
     217              :                 }
     218          618 :                 phaseIter++;
     219          618 :             }
     220          932 :             ringIter++;
     221              :         }
     222          232 :         if (lastPhaseIter >= (int)myPhaseObjs.size()) {
     223            8 :             throw ProcessError("At traffic signal '" + myID + "', ring " + toString(ringNum + 1) + " contains only '0'");
     224              :         }
     225              :         // Set the first to point to the last, wrapping around the ring.
     226          230 :         myPhaseObjs[lastPhaseIter]->setSequentialPriorPhase(myPhaseObjs[lastPhaseIter + phaseIter - 1]);
     227              :         // index the ring counter
     228          230 :         ringNum++;
     229              :     }
     230              : 
     231              :     //TODO: set the default phases. This could also be set using dual entry in future
     232          338 :     for (int i = 0; i < 2; i++) {
     233              :         // create the coordinate phase ptr
     234          226 :         coordinatePhaseObjs[i] = getPhaseObj(coordinatePhases[i], i);
     235          226 :         defaultBarrierPhases[i][coordinatePhaseObjs[i]->barrierNum] = coordinatePhaseObjs[i];
     236              :         // create the other barrier phase ptr
     237          226 :         PhasePtr b = getPhaseObj(barrierPhases[i], i);
     238          224 :         defaultBarrierPhases[i][b->barrierNum] = b;
     239              :         // the barrier 1 and barrier 0 default phase must not have the same barrier number
     240          224 :         if (b->barrierNum == coordinatePhaseObjs[i]->barrierNum) {
     241            0 :             throw ProcessError("At traffic signal " + myID + " the barrier and coordinated phases " +
     242            0 :                                std::to_string(b->phaseName) + ", " + std::to_string(coordinatePhaseObjs[i]->barrierNum) +
     243            0 :                                " are located on the same side of a barrier." +
     244            0 :                                " Please check your configuration file");
     245              :         }
     246              :     }
     247              : 
     248              :     // Create the PhaseDetectorInfo for each of the phases (needs knowledge of other phases to create)
     249          224 :     IntVector latchingDetectors = readParaFromString(getParameter("latchingDetectors", ""));
     250              :     std::vector<std::pair<int, int>> cp;
     251          712 :     for (auto& p : myPhaseObjs) {
     252          600 :         std::string cps = "crossPhaseSwitching:";
     253         1800 :         int crossPhase = StringUtils::toInt(getParameter(cps.append(std::to_string(p->phaseName)), "0"));
     254          600 :         if (crossPhase > 0) {
     255            4 :             cp.push_back({ p->phaseName, crossPhase });
     256              :         }
     257              :     }
     258              : 
     259              :     // Knowing the cross phase info, we can add that to the phase
     260          712 :     for (auto& p : myPhaseObjs) {
     261          600 :         bool latching = vectorContainsPhase(latchingDetectors, p->phaseName);
     262              :         int cpTarget = 0;
     263              :         int cpSource = 0;
     264          632 :         for (auto& cp_pair : cp) {
     265           32 :             if (cp_pair.first == p->phaseName || cp_pair.second == p->phaseName) {
     266              :                 cpTarget = cp_pair.first;
     267            8 :                 cpSource = cp_pair.second;
     268              :             }
     269              :         }
     270          600 :         p->init(this, cpTarget, cpSource, latching);
     271              :     }
     272              : 
     273              :     // Calculate Force offs Based on Timing
     274          112 :     calculateForceOffs();
     275              : 
     276          112 :     if (coordinateMode) {
     277              :         // Calculate the Initial Phases in coordinated operation only.
     278              :         // Otherwise they have already been calculated above
     279           46 :         calculateInitialPhases();
     280              :     } else {
     281              :         // Fall back being the barrier 0 default phases
     282              :         // NEMAPhase* defaultP[2] = {defaultBarrierPhases[0][0], defaultBarrierPhases[1][0]};
     283           66 :         NEMAPhase* defaultP[2] = { getPhasesByRing(0).front(), getPhasesByRing(1).front() };
     284              :         defaultP[0]->forceEnter(this);
     285              :         defaultP[1]->forceEnter(this);
     286              :     }
     287              : 
     288              : 
     289              : #ifdef DEBUG_NEMA
     290              :     //print to check the rings and barriers active phase
     291              :     std::cout << "After init, active ring1 phase is " << myActivePhaseObjs[0]->phaseName << std::endl;
     292              :     std::cout << "After init, active ring2 phase is " << myActivePhaseObjs[1]->phaseName << std::endl;
     293              : 
     294              : 
     295              :     //print to check the phase definition is correct
     296              :     std::cout << "Print to check NEMA phase definitions\n";
     297              :     for (auto& p : myPhaseObjs) {
     298              :         std::cout << "index = " << p->phaseName << "; ";
     299              :         std::cout << "minDur = " << std::to_string(p->minDuration) << "; ";
     300              :         std::cout << "maxDur = " << std::to_string(p->maxDuration) << "; ";
     301              :         std::cout << "vehext = " << std::to_string(p->vehExt) << "; ";
     302              :         std::cout << "yellow = " << std::to_string(p->yellow) << "; ";
     303              :         std::cout << "red = " << std::to_string(p->red) << "; ";
     304              :         std::cout << "state = " << std::to_string((int)p->getCurrentState()) << std::endl;
     305              :     }
     306              : #endif
     307              : 
     308              : 
     309              : #ifdef DEBUG_NEMA
     310              :     std::cout << "After init, r1/r2 barrier phase = " << defaultBarrierPhases[0][1]->phaseName << " and " << defaultBarrierPhases[1][1]->phaseName << std::endl;
     311              :     std::cout << "After init, r1/r2 coordinate phase = " << defaultBarrierPhases[0][0]->phaseName << " and " << defaultBarrierPhases[1][0]->phaseName << std::endl;
     312              : #endif
     313              : 
     314              : 
     315              : #ifdef DEBUG_NEMA
     316              :     std::cout << "R1State = " << myActivePhaseObjs[0]->phaseName << " and its state = " << std::to_string((int)myActivePhaseObjs[0]->getCurrentState()) << std::endl;
     317              :     std::cout << "R2State = " << myActivePhaseObjs[1]->phaseName << " and its state = " << std::to_string((int)myActivePhaseObjs[0]->getCurrentState()) << std::endl;
     318              : #endif
     319              : 
     320              :     // Set the initial light state
     321          112 :     myPhase.setState(composeLightString());
     322          336 :     myPhase.setName(toString(myActivePhaseObjs[0]->phaseName) + "+" + toString(myActivePhaseObjs[1]->phaseName));
     323          112 :     setTrafficLightSignals(SIMSTEP);
     324          112 :     myStep = 0;
     325              : 
     326              :     //validating timing
     327          112 :     validate_timing();
     328          162 : }
     329              : 
     330              : bool
     331         6236 : NEMALogic::vectorContainsPhase(std::vector<int> v, int phaseNum) {
     332         6236 :     if (std::find(v.begin(), v.end(), phaseNum) != v.end()) {
     333         2190 :         return true;
     334              :     }
     335              :     return false;
     336              : }
     337              : 
     338              : void
     339          122 : NEMALogic::init(NLDetectorBuilder& nb) {
     340              : 
     341              :     // TODO: Create a parameter for this
     342          122 :     cycleRefPoint = TIME2STEPS(0);
     343              : 
     344          244 :     std::string barriers = getParameter("barrierPhases", "");
     345          380 :     std::string coordinates = getParameter("coordinatePhases", getParameter("barrier2Phases", ""));
     346          258 :     std::string ring1 = getParameter("ring1", "");
     347          258 :     std::string ring2 = getParameter("ring2", "");
     348              : 
     349          244 :     fixForceOff = StringUtils::toBool(getParameter("fixForceOff", "false"));
     350          122 :     offset = myOffset;
     351          122 :     myNextOffset = myOffset;
     352          244 :     whetherOutputState = StringUtils::toBool(getParameter("whetherOutputState", "false"));
     353          244 :     coordinateMode = StringUtils::toBool(getParameter("coordinate-mode", "false"));
     354              : 
     355              :     // set the queued traci changes to false
     356          122 :     queuedTraciChanges = false;
     357              : 
     358              :     //missing parameter error
     359          366 :     error_handle_not_set(ring1, "ring1");
     360          366 :     error_handle_not_set(ring2, "ring2");
     361          366 :     error_handle_not_set(barriers, "barrierPhases");
     362          366 :     error_handle_not_set(coordinates, "barrier2Phases or coordinatePhases");
     363              : 
     364              :     //print to check
     365              : #ifdef DEBUG_NEMA
     366              :     std::cout << "JunctionID = " << myID << std::endl;
     367              :     std::cout << "All parameters after calling constructor are: " << std::endl;
     368              :     std::cout << "myDetectorLength = " << myDetectorLength << std::endl;
     369              :     std::cout << "cycleLength = " << STEPS2TIME(myCycleLength) << std::endl;
     370              :     std::cout << "ring1 = " << ring1 << std::endl;
     371              :     std::cout << "ring2 = " << ring2 << std::endl;
     372              :     std::cout << "barriers = " << barriers << std::endl;
     373              :     std::cout << "coordinates = " << coordinates << std::endl;
     374              :     std::cout << "offset = " << offset << std::endl;
     375              :     std::cout << "cycleSecond = " << getTimeInCycle() << std::endl;
     376              :     std::cout << "whetherOutputState = " << whetherOutputState << std::endl;
     377              :     std::cout << "myShowDetectors = " << myShowDetectors << std::endl;
     378              :     std::cout << "coordinateMode = " << coordinateMode << std::endl;
     379              :     std::cout << "fixForceOff = " << fixForceOff << std::endl;
     380              :     std::cout << "You reach the end of constructor" << std::endl;
     381              :     std::cout << "****************************************\n";
     382              : #endif
     383              :     // Construct the NEMA specific timing data types and initial phases
     384          122 :     constructTimingAndPhaseDefs(barriers, coordinates, ring1, ring2);
     385              : 
     386              :     //init the traffic light
     387          110 :     MSTrafficLightLogic::init(nb);
     388              :     assert(myLanes.size() > 0);
     389              :     //iterate through the lanes and build one E2 detector for each lane associated with the traffic light control junction
     390         1798 :     for (const LaneVector& lanes : myLanes) {
     391         3418 :         for (MSLane* const lane : lanes) {
     392              :             //decide the detector length
     393              :             double detector_length = 0;
     394         1730 :             if (isLeftTurnLane(lane)) {
     395          198 :                 detector_length = myDetectorLengthLeftTurnLane;
     396              :             } else {
     397         1532 :                 detector_length = myDetectorLength;
     398              :             }
     399         1730 :             if (noVehicles(lane->getPermissions())) {
     400              :                 // do not build detectors on green verges or sidewalks
     401           24 :                 continue;
     402              :             }
     403              :             // Build detector and register them in the detector control
     404         1706 :             if (myLaneDetectorMap.find(lane) == myLaneDetectorMap.end()) {
     405         1128 :                 MSE2Collector* det = nullptr;
     406         2270 :                 const std::string customID = getParameter(lane->getID());
     407         1128 :                 if (customID != "") {
     408          266 :                     det = dynamic_cast<MSE2Collector*>(MSNet::getInstance()->getDetectorControl().getTypedDetectors(SUMO_TAG_LANE_AREA_DETECTOR).get(customID));
     409          134 :                     if (det == nullptr) {
     410            6 :                         throw ProcessError("Unknown laneAreaDetector '" + customID + "' given as custom detector for NEMA tlLogic '" + getID() + "', program '" + getProgramID() + ".");
     411              :                     }
     412              :                     //set the detector to be visible in gui
     413          132 :                     det->setVisible(myShowDetectors);
     414              :                 } else {
     415          994 :                     int phaseNumber = 0;
     416          994 :                     if (myLanePhaseMap.find(lane->getID()) != myLanePhaseMap.end()) {
     417          670 :                         phaseNumber = myLanePhaseMap.find(lane->getID())->second;
     418              :                     }
     419          994 :                     int index = lane->getIndex();
     420         3976 :                     std::string id = myID + "_" + myProgramID + "_D" + toString(phaseNumber) + "." + toString(index);
     421         5500 :                     while (MSNet::getInstance()->getDetectorControl().getTypedDetectors(SUMO_TAG_LANE_AREA_DETECTOR).get(id) != nullptr) {
     422         2253 :                         index++;
     423         9012 :                         id = myID + "_" + myProgramID + "_D" + toString(phaseNumber) + "." + toString(index);
     424              :                     }
     425              :                     //createE2Detector() method will lead to bad detector showing in sumo-gui
     426              :                     //so it is better to use build2Detector() rather than createE2Detector()
     427          994 :                     nb.buildE2Detector(id, //detectorID
     428              :                                        lane, //lane to build this detector
     429              :                                        INVALID_POSITION, // set the detector location by end point and length, so this one is set to invalue value so this parameter can be passed
     430              :                                        lane->getLength(), // set the end position of the detector at the end of the lane, which is right at the position of stop bar of a junction
     431              :                                        detector_length, //detector length
     432          994 :                                        myFile, // detector information output file
     433              :                                        myFreq, // detector reading interval
     434              :                                        0, // time-based threshold that describes how much time has to pass until a vehicle is considered as halting
     435              :                                        0, // speed threshold as halting
     436              :                                        0, // minimum dist to the next standing vehicle to make this vehicle count as a participant to the jam
     437              :                                        "",
     438          994 :                                        myVehicleTypes, //vehicle types to consider, if it is empty, meaning consider all types of vehicles
     439              :                                        "", // nextEdges (no filtering by vehicle route)
     440              :                                        (int)PersonMode::NONE, // detector vehicles, not persons
     441              :                                        true, // whether to give some slack on positioning
     442          994 :                                        myShowDetectors, // whether to show detectors in sumo-gui
     443              :                                        nullptr, //traffic light that triggers aggregation when switching
     444              :                                        nullptr); // outgoing lane that associated with the traffic light
     445              : 
     446              :                     //get the detector to be used in the lane detector map loading
     447         1988 :                     det = dynamic_cast<MSE2Collector*>(MSNet::getInstance()->getDetectorControl().getTypedDetectors(SUMO_TAG_LANE_AREA_DETECTOR).get(id));
     448              :                 }
     449              : 
     450              :                 //map the detector to lane and lane to detector
     451         1126 :                 myLaneDetectorMap[lane] = det;
     452         1126 :                 myDetectorLaneMap[det] = lane;
     453         2254 :                 myDetectorInfoVector.push_back(DetectorInfo(det, (int)myPhases.size()));
     454              : 
     455              :             }
     456              :         }
     457              :     }
     458          638 :     for (auto item : phase2ControllerLanesMap) {
     459          530 :         int NEMAPhaseIndex = item.first;
     460          530 :         std::vector<std::string> laneIDs = item.second;
     461              :         std::vector<MSE2Collector*> detectors;
     462          530 :         MSE2Collector* detector = nullptr;
     463         1629 :         for (std::string laneID : laneIDs) {
     464         1099 :             MSLane* lane = MSLane::dictionary(laneID);
     465         1099 :             detector = myLaneDetectorMap[lane];
     466         1099 :             detectors.push_back(detector);
     467              :         }
     468              :         // have to try this on both rings, because of the case where both rings have the same phase
     469              :         // See Basic NEMA test
     470         1590 :         for (int i = 0; i < 2; i++) {
     471         1060 :             if (vectorContainsPhase(rings[i], NEMAPhaseIndex)) {
     472         1148 :                 getPhaseObj(NEMAPhaseIndex, i)->setDetectors(detectors);
     473              :             }
     474              :         }
     475          530 :     }
     476              : 
     477              :     //Do not delete. SUMO traffic logic check.
     478              :     //SUMO check begin
     479              :     const SVCPermissions motorized = ~(SVC_PEDESTRIAN | SVC_BICYCLE);
     480              :     std::map<int, std::set<MSE2Collector*>> linkToDetectors;
     481              :     std::set<int> actuatedLinks;
     482              : 
     483          108 :     const int numLinks = (int)myLinks.size();
     484          108 :     std::vector<bool> neverMajor(numLinks, true);
     485          648 :     for (const MSPhaseDefinition* phase : myPhases) {
     486              :         const std::string& state = phase->getState();
     487         8892 :         for (int i = 0; i < numLinks; i++) {
     488         8352 :             if (state[i] == LINKSTATE_TL_GREEN_MAJOR) {
     489              :                 neverMajor[i] = false;
     490              :             }
     491              :         }
     492              :     }
     493          108 :     std::vector<bool> oneLane(numLinks, false);
     494         1796 :     for (int i = 0; i < numLinks; i++) {
     495         3298 :         for (MSLane* lane : getLanesAt(i)) {
     496              :             int numMotorized = 0;
     497         6416 :             for (MSLane* l : lane->getEdge().getLanes()) {
     498         4688 :                 if ((l->getPermissions() & motorized) != 0) {
     499         3772 :                     numMotorized++;
     500              :                 }
     501              :             }
     502         1728 :             if (numMotorized == 1) {
     503              :                 oneLane[i] = true;
     504          118 :                 break;
     505              :             }
     506              :         }
     507              :     }
     508              : 
     509          648 :     for (const MSPhaseDefinition* phase : myPhases) {
     510          540 :         const int phaseIndex = (int)myDetectorForPhase.size();
     511              :         std::set<MSE2Collector*> detectors;
     512              :         if (phase->isActuated()) {
     513              :             const std::string& state = phase->getState();
     514              :             std::set<int> greenLinks;
     515              :             std::map<MSE2Collector*, std::set<int>> detectorLinks;
     516              : 
     517         8722 :             for (int i = 0; i < numLinks; i++) {
     518         8192 :                 if (state[i] == LINKSTATE_TL_GREEN_MAJOR
     519         8192 :                         || (state[i] == LINKSTATE_TL_GREEN_MINOR
     520          331 :                             && ((neverMajor[i]  // check1a
     521          129 :                                  && hasMajor(state, getLanesAt(i))) // check1b
     522          232 :                                 || oneLane[i])) // check1c
     523              :                    ) {
     524              :                     greenLinks.insert(i);
     525              :                     actuatedLinks.insert(i);
     526              :                 }
     527              : 
     528        16544 :                 for (MSLane* lane : getLanesAt(i)) {
     529              :                     if (myLaneDetectorMap.count(lane) != 0) {
     530         8192 :                         detectorLinks[myLaneDetectorMap[lane]].insert(i);
     531              :                     }
     532              :                 }
     533              :             }
     534         5896 :             for (auto& item : detectorLinks) {
     535         5366 :                 MSE2Collector* det = item.first;
     536         5366 :                 MSLane* detectorLane = myDetectorLaneMap[det];
     537              :                 bool usable = true;
     538              :                 // check 1
     539        13478 :                 for (int j : item.second) {
     540              :                     if (greenLinks.count(j) == 0) {
     541              :                         usable = false;
     542              :                     }
     543              :                 }
     544              : 
     545              :                 //check 2
     546         5366 :                 if (usable) {
     547         1994 :                     for (MSLink* link : detectorLane->getLinkCont()) {
     548         1194 :                         MSLane* next = link->getLane();
     549              :                         if (myLaneDetectorMap.count(next) != 0) {
     550          108 :                             MSE2Collector* nextDet = myLaneDetectorMap[next];
     551          140 :                             for (int j : detectorLinks[nextDet]) {
     552              :                                 if (greenLinks.count(j) == 0) {
     553              :                                     usable = false;
     554              :                                     break;
     555              :                                 }
     556              :                             }
     557              :                         }
     558              :                     }
     559              :                 }
     560              : 
     561          800 :                 if (usable) {
     562          716 :                     detectors.insert(item.first);
     563         1822 :                     for (int j : item.second) {
     564         1106 :                         linkToDetectors[j].insert(item.first);
     565              :                     }
     566              :                 }
     567              :             }
     568          530 :             if (detectors.size() == 0) {
     569          342 :                 WRITE_WARNINGF(TL("At NEMA tlLogic '%', actuated phase % has no controlling detector"), getID(), toString(phaseIndex));
     570              :             }
     571              :         }
     572              :         std::vector<DetectorInfo*> detectorInfos;
     573          540 :         myDetectorForPhase.push_back(detectorInfos);
     574         1256 :         for (MSE2Collector* det : detectors) {
     575         6974 :             for (DetectorInfo& detInfo : myDetectorInfoVector) {
     576         6258 :                 if (detInfo.det == det) {
     577          716 :                     myDetectorForPhase.back().push_back(&detInfo);
     578              :                     detInfo.servedPhase[phaseIndex] = true;
     579              :                 }
     580              :             }
     581              :         }
     582          540 :     }
     583              : 
     584         1332 :     for (int i : actuatedLinks) {
     585         1350 :         if (linkToDetectors[i].size() == 0 && myLinks[i].size() > 0
     586         1350 :                 && (myLinks[i].front()->getLaneBefore()->getPermissions() & motorized) != 0) {
     587          330 :             WRITE_WARNINGF(TL("At NEMA tlLogic '%, linkIndex % has no controlling detector"), getID(), toString(i));
     588              :         }
     589              :     }
     590          108 : }
     591              : 
     592              : void
     593          112 : NEMALogic::validate_timing() {
     594              :     // check that the cycle length for each ring adds up to the specified cycle length
     595          332 :     for (int ringIndex = 0; ringIndex < 2; ringIndex++) {
     596              :         SUMOTime cycleLengthCalculated = 0;
     597          818 :         for (auto& p : getPhasesByRing(ringIndex)) {
     598          596 :             cycleLengthCalculated += (p->maxDuration + p->yellow + p->red);
     599          222 :         }
     600          222 :         if (coordinateMode && (cycleLengthCalculated != myCycleLength)) {
     601           46 :             int ringNumber = ringIndex + 1;
     602          138 :             const std::string error = "At NEMA tlLogic '" + getID() + "', Ring " + toString(ringNumber) + " does not add to cycle length.";
     603           46 :             if (ignoreErrors) {
     604          134 :                 WRITE_WARNING(error);
     605              :             } else {
     606            2 :                 throw  ProcessError(error);
     607              :             }
     608              :         }
     609              :     }
     610              :     // check that the barriers sum together
     611          110 :     SUMOTime cycleLengths[2][2] = { {0, 0}, {0, 0} };
     612          330 :     for (int ringIndex = 0; ringIndex < 2; ringIndex++) {
     613              :         // TS2 Force Offs don't go in order, so using a different method to check cycle time
     614          810 :         for (const auto p : getPhasesByRing(ringIndex)) {
     615          590 :             cycleLengths[ringIndex][p->barrierNum] += p->maxDuration + p->yellow + p->red;
     616          220 :         }
     617              :     }
     618              :     // Write warnings if the barriers do not sum
     619          330 :     for (int barrierNum = 0; barrierNum < 2; barrierNum++) {
     620          220 :         if (cycleLengths[0][barrierNum] != cycleLengths[1][barrierNum]) {
     621           84 :             const std::string error = "At NEMA tlLogic '" + getID() + "', the phases before barrier " + toString(barrierNum + 1) + " from both rings do not add up. (ring1="
     622          112 :                                       + toString(STEPS2TIME(cycleLengths[0][barrierNum])) + ", ring2=" + toString(STEPS2TIME(cycleLengths[1][barrierNum])) + ")";
     623           28 :             if (coordinateMode && !ignoreErrors) {
     624            0 :                 throw  ProcessError(error);
     625              :             } else {
     626           84 :                 WRITE_WARNING(error);
     627              :             }
     628              :         }
     629              :     }
     630              : 
     631              :     // no offset for non coordinated
     632          110 :     if (!coordinateMode && offset != 0) {
     633            0 :         WRITE_WARNINGF(TL("NEMA tlLogic '%' is not coordinated but an offset was set."), getID());
     634              :     }
     635          110 : }
     636              : 
     637              : void
     638            8 : NEMALogic::setNewSplits(std::vector<double> newSplits) {
     639              :     assert(newSplits.size() == 8);
     640           48 :     for (auto& p : myPhaseObjs) {
     641           40 :         if (newSplits[p->phaseName - 1] > 0) {
     642              :             // set the phase's nextMaxDuration. This will be implemented when implementTraciChanges is called
     643           40 :             p->nextMaxDuration = TIME2STEPS(newSplits[p->phaseName - 1]) - p->yellow - p->red;
     644              :         }
     645              :     }
     646            8 : }
     647              : 
     648              : 
     649              : void
     650            4 : NEMALogic::setNewMaxGreens(std::vector<double> newMaxGreens) {
     651           24 :     for (auto& p : myPhaseObjs) {
     652           20 :         if (newMaxGreens[p->phaseName - 1] > 0) {
     653              :             // set the phase's nextMaxDuration. This will be implemented when implementTraciChanges is called
     654           20 :             p->nextMaxDuration = TIME2STEPS(newMaxGreens[p->phaseName - 1]);
     655              :         }
     656              :     }
     657            4 : }
     658              : 
     659              : 
     660              : void
     661            0 : NEMALogic::setNewCycleLength(double newCycleLength) {
     662              :     // set the controller's next cycle length. This will be implemented when implementTraciChanges is called
     663            0 :     myNextCycleLength = TIME2STEPS(newCycleLength);
     664            0 : }
     665              : 
     666              : 
     667              : void
     668           40 : NEMALogic::setNewOffset(double newOffset) {
     669              :     // set the controller's offset. This will be implemented when implementTraciChanges is called
     670           40 :     myNextOffset = TIME2STEPS(newOffset);
     671           40 : }
     672              : 
     673              : 
     674          844 : std::vector<int> NEMALogic::readParaFromString(std::string s) {
     675              :     std::vector<int> output;
     676         4846 :     for (char c : s) {
     677         4002 :         if (c >= '0' && c <= '9') {
     678         2308 :             int temp = c - '0';
     679         2308 :             output.push_back(temp);
     680              :         }
     681              :     }
     682          844 :     return output;
     683            0 : }
     684              : 
     685              : const MSPhaseDefinition&
     686       293825 : NEMALogic::getCurrentPhaseDef() const {
     687       293825 :     return myPhase;
     688              : }
     689              : 
     690              : 
     691              : void
     692            0 : NEMALogic::resetLastSwitch(SUMOTime t) {
     693            0 :     myPhase.myLastSwitch = t;
     694            0 : }
     695              : 
     696              : 
     697         1768 : int NEMALogic::measureRingDistance(int p1, int p2, int ringNum) {
     698         1768 :     int length = (int)rings[ringNum].size();
     699              :     int d = 0;
     700              :     bool found = false;
     701              :     // Loop around the ring and keep track of the distance from p1 to p2
     702         9592 :     for (int i = 0; i < (length * 2); i++) {
     703         9592 :         if (rings[ringNum][i % length] > 0) {
     704         7456 :             if (found) {
     705         3728 :                 d++;
     706         3728 :                 if (rings[ringNum][i % length] == p2) {
     707              :                     break;
     708              :                 }
     709         3728 :             } else if (rings[ringNum][i % length] == p1) {
     710              :                 found = true;
     711              :             }
     712              :         }
     713              :     }
     714              :     assert(d > 0);
     715         1768 :     return d;
     716              : }
     717              : 
     718              : 
     719              : SUMOTime
     720        27338 : NEMALogic::ModeCycle(SUMOTime a, SUMOTime b) {
     721        27338 :     SUMOTime c = a - b;
     722        27513 :     while (c >= b) {
     723          175 :         c = c - b;
     724              :     }
     725        68332 :     while (c < 0) {
     726        40994 :         c += b;
     727              :     }
     728        27338 :     return c;
     729              : }
     730              : 
     731              : 
     732              : void
     733          618 : NEMALogic::getLaneInfoFromNEMAState(std::string state, StringVector& laneIDs, IntVector& stateIndex) {
     734              :     std::set<std::string> output;
     735         9814 :     for (int i = 0; i < (int)myLinks.size(); i++) {
     736         9196 :         if (myLinks[i].empty()) {
     737              :             // unused index
     738           10 :             continue;
     739              :         }
     740         9186 :         char ch = state[i];
     741              :         // if the ch is 'G', it means that the phase is controlling this lane
     742         9186 :         if (ch == 'G') {
     743         1393 :             stateIndex.push_back(i);
     744         2794 :             for (auto link : myLinks[i]) {
     745              :                 const MSLane* incoming = link->getLaneBefore();
     746         1401 :                 if (incoming->isNormal()) {
     747         1385 :                     laneIDs.push_back(incoming->getID());
     748              :                 }
     749              :             }
     750              :         }
     751              :     }
     752          618 : }
     753              : 
     754         1730 : bool NEMALogic::isLeftTurnLane(const MSLane* const lane) const {
     755         1730 :     const std::vector<MSLink*> links = lane->getLinkCont();
     756         1730 :     if (links.size() == 1 && links.front()->getDirection() == LinkDirection::LEFT) {
     757          198 :         return true;
     758              :     }
     759              :     return false;
     760         1730 : }
     761              : 
     762              : bool
     763          129 : NEMALogic::hasMajor(const std::string& state, const LaneVector& lanes) const {
     764         1527 :     for (int i = 0; i < (int)state.size(); i++) {
     765         1497 :         if (state[i] == LINKSTATE_TL_GREEN_MAJOR) {
     766          247 :             for (MSLane* cand : getLanesAt(i)) {
     767          251 :                 for (MSLane* lane : lanes) {
     768          175 :                     if (lane == cand) {
     769              :                         return true;
     770              :                     }
     771              :                 }
     772              :             }
     773              :         }
     774              :     }
     775              :     return false;
     776              : }
     777              : 
     778              : 
     779              : void
     780          122 : NEMALogic::activateProgram() {
     781          122 :     MSTrafficLightLogic::activateProgram();
     782          122 :     for (auto& item : myLaneDetectorMap) {
     783            0 :         item.second->setVisible(true);
     784              :     }
     785          122 : }
     786              : 
     787              : void
     788            0 : NEMALogic::deactivateProgram() {
     789            0 :     MSTrafficLightLogic::deactivateProgram();
     790            0 :     for (auto& item : myLaneDetectorMap) {
     791            0 :         item.second->setVisible(false);
     792              :     }
     793            0 : }
     794              : 
     795              : void
     796            0 : NEMALogic::setShowDetectors(bool show) {
     797            0 :     myShowDetectors = show;
     798            0 :     for (auto& item : myLaneDetectorMap) {
     799            0 :         item.second->setVisible(myShowDetectors);
     800              :     }
     801            0 : }
     802              : 
     803         2064 : int NEMALogic::string2int(std::string s) {
     804         2064 :     std::stringstream ss(s);
     805         2064 :     int ret = 0;
     806         2064 :     ss >> ret;
     807         2064 :     return ret;
     808         2064 : }
     809              : 
     810              : 
     811              : const std::string
     812         6090 : NEMALogic::getParameter(const std::string& key, const std::string defaultValue) const {
     813        12180 :     if (StringUtils::startsWith(key, "NEMA.")) {
     814         1200 :         if (key == "NEMA.phaseCall") {
     815         1200 :             int activeCalls[8] = { 0 };
     816         7200 :             for (const auto p : myPhaseObjs) {
     817              :                 // This handles the case when the controller has multiple of the same phase call
     818         6000 :                 if (!activeCalls[p->phaseName - 1]) {
     819         4908 :                     activeCalls[p->phaseName - 1] = 1 * p->lastDetectActive;
     820              :                 }
     821              :             }
     822         1200 :             std::string outStr = "";
     823        10800 :             for (int i = 0; i < 8; i++) {
     824         9600 :                 outStr += std::to_string(activeCalls[i]);
     825         9600 :                 if (i < 7) {
     826              :                     outStr += ",";
     827              :                 }
     828              :             }
     829         1200 :             return outStr;
     830              :         } else {
     831            0 :             throw InvalidArgument("Unsupported parameter '" + key + "' for NEMA controller '" + getID() + "'");
     832              :         }
     833              :     } else {
     834         9780 :         return Parameterised::getParameter(key, defaultValue);
     835              :     }
     836              : }
     837              : 
     838              : 
     839              : void
     840           52 : NEMALogic::setParameter(const std::string& key, const std::string& value) {
     841           52 :     queuedTraciChanges = true;
     842          104 :     if (StringUtils::startsWith(key, "NEMA.")) {
     843           52 :         if (key == "NEMA.splits" || key == "NEMA.maxGreens") {
     844              :             //splits="2.0 3.0 4.0 5.0 2.0 3.0 4.0 5.0"
     845           24 :             const std::vector<std::string>& tmp = StringTokenizer(value).getVector();
     846           12 :             if (tmp.size() != 8) {
     847            0 :                 queuedTraciChanges = false;
     848            0 :                 throw InvalidArgument("Parameter '" + key + "' for NEMA controller '" + getID() + "' requires 8 space or comma separated values");
     849              :             }
     850              :             std::vector<double> timing;
     851          108 :             for (const std::string& s : tmp) {
     852           96 :                 timing.push_back(StringUtils::toDouble(s));
     853              :             }
     854           12 :             if (key == "NEMA.maxGreens") {
     855            4 :                 setNewMaxGreens(timing);
     856              :             } else {
     857            8 :                 setNewSplits(timing);
     858              :             }
     859           52 :         } else if (key == "NEMA.cycleLength") {
     860            0 :             setNewCycleLength(StringUtils::toDouble(value));
     861           40 :         } else if (key == "NEMA.offset") {
     862           40 :             setNewOffset(StringUtils::toDouble(value));
     863              :         } else {
     864            0 :             queuedTraciChanges = false;
     865            0 :             throw InvalidArgument("Unsupported parameter '" + key + "' for NEMA controller '" + getID() + "'");
     866              :         }
     867              :     }
     868           52 :     Parameterised::setParameter(key, value);
     869           52 : }
     870              : 
     871              : void
     872          488 : NEMALogic::error_handle_not_set(std::string param_variable, std::string param_name) {
     873          488 :     if (param_variable == "") {
     874            0 :         throw InvalidArgument("Please set " + param_name + " for NEMA tlLogic '" + getID() + "'");
     875              :     }
     876          488 : }
     877              : 
     878              : void
     879          136 : NEMALogic::calculateForceOffs170() {
     880          136 :     SUMOTime zeroTime[2] = { TIME2STEPS(0), TIME2STEPS(0) };
     881          408 :     for (int i = 0; i < 2; i++) {
     882              :         SUMOTime runningTime = 0;
     883              :         // loop through the phases for ring 0 and then 1
     884          992 :         for (auto& p : getPhasesByRing(i)) {
     885          720 :             runningTime += p->maxDuration + p->getTransitionTimeStateless();
     886              :             // in 170, the cycle "starts" when the coordinated phase goes to yellow.
     887              :             // See https://ops.fhwa.dot.gov/publications/fhwahop08024/chapter6.html
     888          720 :             if (p->coordinatePhase) {
     889          140 :                 zeroTime[i] = runningTime;
     890              :             }
     891          720 :             p->forceOffTime = runningTime - p->getTransitionTimeStateless();
     892          720 :             p->greatestStartTime = p->forceOffTime - p->minDuration;
     893          272 :         }
     894              :     }
     895              :     // find the minimum offset time and then subtract from everything, modecycling where negative
     896              :     // This sets the 0 cycle time as start of yellow on earliest ending coordinated phase
     897          136 :     SUMOTime minCoordYellow = MIN2(zeroTime[0], zeroTime[1]);
     898          856 :     for (auto& p : myPhaseObjs) {
     899          720 :         p->forceOffTime = ModeCycle(p->forceOffTime - minCoordYellow, myCycleLength);
     900          720 :         p->greatestStartTime = ModeCycle(p->greatestStartTime - minCoordYellow, myCycleLength);
     901              :     }
     902              : 
     903              : #ifdef DEBUG_NEMA
     904              :     std::ios_base::fmtflags oldflags = std::cout.flags();
     905              :     std::streamsize oldprecision = std::cout.precision();
     906              :     for (int i = 0; i < 2; i++) {
     907              :         std::cout << "Ring" << i + 1 << " force offs: \t";
     908              :         for (auto& p : rings[i]) {
     909              :             if (p > 0) {
     910              :                 PhasePtr pObj = getPhaseObj(p, i);
     911              :                 std::cout << std::fixed << std::setprecision(2) << STEPS2TIME(pObj->forceOffTime) << "\t";
     912              :             } else {
     913              :                 std::cout << std::to_string(0) << "\t";
     914              :             }
     915              :         }
     916              :         std::cout << std::endl;
     917              :     }
     918              :     std::cout.flags(oldflags);
     919              :     std::cout.precision(oldprecision);
     920              : #endif
     921          136 : }
     922              : 
     923              : void
     924           98 : NEMALogic::calculateForceOffsTS2() {
     925              :     // TS2 "0" cycle time is the start of the "first" coordinated phases.
     926              :     // We can find this "0" point by first constructing the forceOffs in sequential order via the 170 method
     927           98 :     calculateForceOffs170();
     928              : 
     929              :     // Switch the Force Off Times to align with TS2 Cycle, which is the *start* of the earliest coordinated phase
     930              :     // The coordinate phases will always be the defaultBarrierPhases[i][0]
     931           98 :     SUMOTime minCoordTime = MIN2(coordinatePhaseObjs[0]->forceOffTime - coordinatePhaseObjs[0]->maxDuration,
     932           98 :                                  coordinatePhaseObjs[1]->forceOffTime - coordinatePhaseObjs[1]->maxDuration);
     933              : 
     934              :     // loop through all the phases and subtract this minCoordTime to move the 0 point to the start of the first coordinated phase
     935          574 :     for (auto& p : myPhaseObjs) {
     936          476 :         if ((p->forceOffTime - minCoordTime) >= 0) {
     937          354 :             p->forceOffTime -= (minCoordTime);
     938              :         } else {
     939          122 :             p->forceOffTime = (myCycleLength + (p->forceOffTime - (minCoordTime)));
     940              :         }
     941          476 :         p->greatestStartTime = ModeCycle(p->greatestStartTime - minCoordTime, myCycleLength);
     942              :     }
     943           98 : }
     944              : 
     945              : void
     946           46 : NEMALogic::calculateInitialPhases170() {
     947              :     // get the time in the cycle
     948           46 :     SUMOTime cycleTime = ModeCycle(getTimeInCycle(), myCycleLength);
     949              :     NEMAPhase* activePhases[2];
     950          138 :     for (int i = 0; i < 2; i++) {
     951           92 :         std::vector<NEMAPhase*> ringCopy = getPhasesByRing(i);
     952              :         // sort by the minimum start time in the cycle.
     953           92 :         std::sort(ringCopy.begin(), ringCopy.end(),
     954              :         [](NEMAPhase * p, NEMAPhase * p1) {
     955          304 :             return p->greatestStartTime <= p1->greatestStartTime;
     956              :         }
     957              :                  );
     958              :         bool found = false;
     959              :         // loop through the sorted phases by time and try to find the phase that should be active given the time in the cycle
     960          292 :         for (auto& p : ringCopy) {
     961              :             // This handles the wrap around. Checks if the prior phases start time should have already happened.
     962              :             // If it should have happened and it's not to the start time of me yet, start in my phase ( will have to be in my phase longer than max time likely )
     963          236 :             SUMOTime syntheticPriorStart = p->getSequentialPriorPhase()->greatestStartTime < p->greatestStartTime ?
     964           92 :                                            p->getSequentialPriorPhase()->greatestStartTime : p->getSequentialPriorPhase()->greatestStartTime - myCycleLength;
     965          236 :             if (cycleTime <= ModeCycle(p->greatestStartTime, myCycleLength) && cycleTime > ModeCycle(syntheticPriorStart, myCycleLength)) {
     966              :                 found = true;
     967           36 :                 activePhases[i] = p;
     968              :                 break;
     969              :             }
     970              :         }
     971              :         if (!found) {
     972              : #ifdef DEBUG_NEMA
     973              :             const std::string error = "I can't find the correct phase for NEMA tlLogic '" + getID() + "' Ring " + toString(i) + " to start in.";
     974              :             WRITE_WARNING(error);
     975              :             WRITE_WARNING(TL("I am starting in the coordinated phases"));
     976              : #endif
     977           56 :             activePhases[0] = defaultBarrierPhases[0][0];
     978           56 :             activePhases[1] = defaultBarrierPhases[1][0];
     979              :         }
     980           92 :     }
     981              : 
     982              :     // ensure that the two found phases are on the same side of the barrier. If they aren't, just override with the default barrier phases
     983           46 :     if (activePhases[0]->barrierNum != activePhases[1]->barrierNum) {
     984              :         // give preference to whatever is on the coordinate side of the barrier, one must be if they aren't equal to each other
     985            4 :         activePhases[0] = activePhases[0]->barrierNum == 0 ? activePhases[0] : defaultBarrierPhases[0][0];
     986            4 :         activePhases[1] = activePhases[1]->barrierNum == 0 ? activePhases[1] : defaultBarrierPhases[1][0];
     987              :     }
     988              : 
     989              :     // force enter the phases and update their expected duration to last until the forceOff
     990           46 :     activePhases[0]->forceEnter(this);
     991           46 :     activePhases[1]->forceEnter(this);
     992           46 : }
     993              : 
     994              : void
     995           24 : NEMALogic::calculateInitialPhasesTS2() {
     996              :     // Modifications where made to 170 algorithm so that it works with both.
     997           24 :     calculateInitialPhases170();
     998           24 : }
     999              : 
    1000              : NEMALogic::controllerType
    1001          122 : NEMALogic::parseControllerType(std::string inputType) {
    1002              :     std::string cleanString;
    1003          640 :     for (const char& c : inputType) {
    1004          518 :         if (isalpha(c) || isdigit(c)) {
    1005          518 :             cleanString += (char)::tolower(c);
    1006              :         }
    1007              :     }
    1008          122 :     if (cleanString == "type170") {
    1009              :         return Type170;
    1010           84 :     } else if (cleanString == "ts2") {
    1011              :         return TS2;
    1012              :     } else {
    1013            0 :         throw InvalidArgument("Please set controllerType for NEMA tlLogic " + myID + " to either Type170 or TS2");
    1014              :     }
    1015              : }
    1016              : 
    1017              : std::vector<NEMAPhase*>
    1018         2664 : NEMALogic::getPhasesByRing(int ringNum) {
    1019              :     std::vector<NEMAPhase*> phases;
    1020        17708 :     for (auto& p : myPhaseObjs) {
    1021        15044 :         if (p->ringNum == ringNum) {
    1022         7564 :             phases.push_back(p);
    1023              :         }
    1024              :     }
    1025         2664 :     return phases;
    1026            0 : }
    1027              : 
    1028              : void
    1029        54808 : NEMALogic::setActivePhase(PhasePtr phase) {
    1030        54808 :     myActivePhaseObjs[phase->ringNum] = phase;
    1031        54808 : }
    1032              : 
    1033              : std::map<std::string, double>
    1034         7528 : NEMALogic::getDetectorStates() const {
    1035              :     std::map<std::string, double> result;
    1036        75280 :     for (auto item : myDetectorLaneMap) {
    1037        67752 :         result[item.first->getID()] = item.first->getCurrentVehicleNumber();
    1038              :     }
    1039         7528 :     return result;
    1040              : }
    1041              : 
    1042              : NEMALogic::PhasePtr
    1043      1193212 : NEMALogic::getOtherPhase(PhasePtr p) {
    1044              :     // return a pointer to the other active phase
    1045      1193212 :     return myActivePhaseObjs[!p->ringNum];
    1046              : }
    1047              : 
    1048              : NEMAPhase*
    1049         1042 : NEMALogic::getPhaseObj(int phaseNum, int ringNum) {
    1050              :     // This satisfies the case where there is a "duplicate" phase on both ring
    1051         1042 :     std::vector<PhasePtr> iterRing = ringNum >= 0 ? getPhasesByRing(ringNum) : myPhaseObjs;
    1052         2074 :     for (auto& p : iterRing) {
    1053         2072 :         if (p->phaseName == phaseNum) {
    1054         1040 :             return p;
    1055              :         }
    1056              :     }
    1057              :     // the phase must always be found
    1058           10 :     throw ProcessError("At traffic signal '" + myID + "' program '" + myProgramID + "' phase '" + toString(phaseNum) + "' not found in ring '" + toString(ringNum) + "'.");
    1059         1042 : }
    1060              : 
    1061              : PhaseTransitionLogic*
    1062          312 : NEMALogic::getDefaultTransition(PhaseTransitionLogic* t, PhaseTransitionLogic* ot) {
    1063              :     NEMAPhase* p = t->getFromPhase();
    1064              :     // if the current phase is not ready to switch or a barrier cross is desired by the other transition
    1065              :     // and t fromPhase is not ready to switch, the default transition is back to myself
    1066          312 :     if (!p->readyToSwitch ||
    1067          312 :             (p->barrierNum == ot->getToPhase()->barrierNum && p->getCurrentState() >= LightState::Green)) {
    1068          156 :         return p->getTransition(p->phaseName);
    1069              :     }
    1070              :     // otherwise the default transition is to the default phase on whatever barrier ot wants to transition to
    1071              :     else {
    1072          156 :         return p->getTransition(defaultBarrierPhases[p->ringNum][ot->getToPhase()->barrierNum]->phaseName);
    1073              :     }
    1074              : }
    1075              : 
    1076              : void
    1077       205257 : NEMALogic::getNextPhases(TransitionPairs& transitions) {
    1078              :     std::vector<std::vector<PhaseTransitionLogic* >> potentialPhases;
    1079              : 
    1080              :     // Get a vector of each phases' potential transitions
    1081       615771 :     for (const auto& p : myActivePhaseObjs) {
    1082       821028 :         potentialPhases.push_back(p->trySwitch(this));
    1083              :     }
    1084              : 
    1085              :     // Loop through all combination of transitions, keeping only the valid ones and filling in the gaps where necessary
    1086       410514 :     for (const auto& r1_t : potentialPhases[0]) {
    1087       410514 :         for (const auto& r2_t : potentialPhases[1]) {
    1088              :             // if both transitions go to the same barrier then we are good
    1089       205257 :             if (r1_t->getToPhase()->barrierNum == r2_t->getToPhase()->barrierNum) {
    1090       205101 :                 transitions.push_back({ r1_t, r2_t, (float)(r1_t->getDistance(r2_t) + r2_t->getDistance(r1_t)) / 2 });
    1091              :             } else {
    1092              :                 // If the rings are different, add a choice where one of them is the default choice for whatever ring it is
    1093              :                 // create two choices, one for each of the phase as they are on  different rings
    1094          156 :                 if (r1_t->getFromPhase()->readyToSwitch) {
    1095              :                     // get the r2 default
    1096          156 :                     PhaseTransitionLogic* r2_t_temp = getDefaultTransition(r2_t, r1_t);
    1097              :                     // only add it if it does not cross a barrier!
    1098          156 :                     if (r2_t_temp->getToPhase()->barrierNum == r1_t->getToPhase()->barrierNum) {
    1099          156 :                         transitions.push_back({ r1_t, r2_t_temp, (float)(r2_t_temp->getDistance(r1_t) + r1_t->getDistance(r2_t_temp)) / 2 });
    1100              :                     }
    1101              :                 }
    1102          156 :                 if (r2_t->getFromPhase()->readyToSwitch) {
    1103              :                     // R1 default
    1104          156 :                     PhaseTransitionLogic* r1_t_temp = getDefaultTransition(r1_t, r2_t);
    1105              :                     // only add it if it does not cross a barrier!
    1106          156 :                     if (r1_t_temp->getToPhase()->barrierNum == r2_t->getToPhase()->barrierNum) {
    1107          156 :                         transitions.push_back({ r1_t_temp, r2_t, (float)(r2_t->getDistance(r1_t_temp) + r1_t_temp->getDistance(r2_t)) / 2 });
    1108              :                     }
    1109              :                 }
    1110              :                 // If the distances are <= 1, this means that this is the shortest transition possible
    1111              :                 // and we can return early without considering any other options
    1112          156 :                 if (!transitions.empty()) {
    1113          156 :                     if (transitions.back().distance < 1) {
    1114              :                         return;
    1115              :                     }
    1116              :                 }
    1117              :             }
    1118              :         }
    1119              :     }
    1120       205257 : }
    1121              : 
    1122              : 
    1123              : std::string
    1124       205369 : NEMALogic::composeLightString() {
    1125              :     // FIX with plan to support #10742
    1126       205369 :     std::string out(myPhaseStrLen, 'r');
    1127      4942974 :     for (int i = 0; i < myPhaseStrLen; i++) {
    1128              :         bool controlled = false;
    1129      4737605 :         std::string phaseChars = "";
    1130     14212815 :         for (auto& p : myActivePhaseObjs) {
    1131      9475210 :             phaseChars += p->getNEMAChar(i);
    1132     18950420 :             if (p->controlledIndex(i)) {
    1133       537140 :                 out[i] = p->getNEMAChar(i);
    1134              :                 controlled = true;
    1135              :             }
    1136              :         }
    1137              :         // if the index wasn't a controlled one, the prior priority order still stands
    1138      4737605 :         if (!controlled) {
    1139     29129934 :             for (auto priority_char : lightHeadPriority) {
    1140     25049724 :                 if (std::count(phaseChars.begin(), phaseChars.end(), priority_char)) {
    1141       138151 :                     out[i] = priority_char;
    1142       138151 :                     break;
    1143              :                 }
    1144              :             }
    1145              :         }
    1146              :     }
    1147       205369 :     return out;
    1148              : }
    1149              : 
    1150              : 
    1151              : SUMOTime
    1152       396940 : NEMALogic::trySwitch() {
    1153              : #ifdef DEBUG_NEMA_SWITCH
    1154              :     std::cout << SIMTIME << " trySwitch tls=" << getID() << "\n";
    1155              : #endif
    1156       396940 :     PhaseTransitionLogic* nextPhases[2] = { nullptr, nullptr };
    1157              : 
    1158              :     // update the internal time. This is a must. Could have just used a reference to the time
    1159              :     setCurrentTime();
    1160              : 
    1161              :     // Check the Detectors
    1162      2276046 :     for (auto& p : myPhaseObjs) {
    1163      1879106 :         p->checkMyDetectors();
    1164              :     }
    1165              : 
    1166              :     // Update the timing parameters
    1167      1190820 :     for (const auto& p : myActivePhaseObjs) {
    1168       793880 :         p->update(this);
    1169              :     }
    1170              : 
    1171              :     // Calculate the Next Phases, but only if atleast one of them is ready to transition
    1172       396940 :     if (myActivePhaseObjs[0]->readyToSwitch || myActivePhaseObjs[1]->readyToSwitch) {
    1173              :         TransitionPairs transitions;
    1174              :         // set the next phases by reference
    1175       205257 :         getNextPhases(transitions);
    1176              : 
    1177              :         // Sort the next phases by distance and select the closest.
    1178              :         // TODO: Is there a way to avoid this sort? The transitions are already sorted by distance prior
    1179              :         // to picking the valid ones
    1180       205257 :         if (transitions.size() > 1) {
    1181          156 :             std::sort(transitions.begin(), transitions.end(),
    1182              :             [](const transitionInfo & i, const transitionInfo & j) {
    1183          280 :                 return i.distance < j.distance;
    1184              :             });
    1185              :         }
    1186              : 
    1187              :         // Set the Next Phases = to the transition with least combined distance
    1188       205257 :         nextPhases[0] = transitions.front().p1;
    1189       205257 :         nextPhases[1] = transitions.front().p2;
    1190              : 
    1191              :         // Try the exit logic. This doesn't necessarily mean that the phase will exit,
    1192              :         // as it could go into green rest or green transfer, but this is considered an "exit"
    1193       615771 :         for (const auto& p : myActivePhaseObjs) {
    1194       410514 :             if (p->readyToSwitch) {
    1195       402589 :                 p->exit(this, nextPhases);
    1196              :             }
    1197              :         }
    1198              : 
    1199              :         // This is the only time when something might have happened, so we update the phase strings here
    1200       205257 :         std::string newState = composeLightString();
    1201       205257 :         if (newState != myPhase.getState()) {
    1202              :             myPhase.setState(newState);
    1203       248739 :             myPhase.setName(toString(myActivePhaseObjs[0]->phaseName) + "+" + toString(myActivePhaseObjs[1]->phaseName));
    1204              :             // ensure that SwitchCommand::execute notices a change
    1205        82913 :             myStep = 1 - myStep;
    1206              : 
    1207              :         }
    1208       205257 :     }
    1209              : 
    1210              :     // clear the phases' detectors
    1211      2276046 :     for (auto& p : myPhaseObjs) {
    1212      1879106 :         p->clearMyDetectors();
    1213              :     }
    1214              : 
    1215              : 
    1216              : #ifdef FUZZ_TESTING
    1217              :     // Basic Assertion to ensure that the Barrier is not crossed
    1218              :     assert(myActivePhaseObjs[0]->barrierNum == myActivePhaseObjs[1]->barrierNum);
    1219              : #endif
    1220              : 
    1221              :     // return the simulation timestep, as this controller must be checked every simulation step
    1222       396940 :     return DELTA_T;
    1223              : }
    1224              : 
    1225              : 
    1226              : void
    1227        27290 : NEMALogic::implementTraciChanges(void) {
    1228              :     // Implement Traci Updates on the start of ring1 coordinated phase (rising edge of it turning green)
    1229        27290 :     if (queuedTraciChanges) {
    1230          144 :         for (auto& p : myPhaseObjs) {
    1231          120 :             p->maxDuration = p->nextMaxDuration;
    1232              :         }
    1233           24 :         offset = myNextOffset;
    1234           24 :         myCycleLength = myNextCycleLength;
    1235              :         // now that we have set the cycle length, offset and max duration, we need to update force off times
    1236           24 :         calculateForceOffs();
    1237           24 :         queuedTraciChanges = false;
    1238              :     }
    1239        27290 : }
    1240              : 
    1241              : 
    1242              : // ===========================================================================
    1243              : // NEMAPhase Definitions
    1244              : // ===========================================================================
    1245          618 : NEMAPhase::NEMAPhase(int phaseName, bool isBarrier, bool isGreenRest, bool isCoordinated,
    1246              :                      bool minRecall, bool maxRecall, bool fixForceOff, int barrierNum, int ringNum,
    1247              :                      IntVector phaseStringInds,
    1248          618 :                      MSPhaseDefinition* phase) :
    1249          618 :     phaseName(phaseName),
    1250          618 :     isAtBarrier(isBarrier),
    1251          618 :     isGreenRest(isGreenRest),
    1252          618 :     barrierNum(barrierNum),
    1253          618 :     coordinatePhase(isCoordinated),
    1254          618 :     minRecall(minRecall),
    1255          618 :     maxRecall(maxRecall),
    1256          618 :     fixForceOff(fixForceOff),
    1257          618 :     ringNum(ringNum),
    1258          618 :     myCorePhase(phase),
    1259          618 :     myPhaseStringInds(phaseStringInds) {
    1260              :     // Public
    1261          618 :     readyToSwitch = false;
    1262          618 :     greenRestTimer = 0;
    1263          618 :     forceOffTime = 0;
    1264          618 :     lastDetectActive = false;
    1265              : 
    1266              :     // Private
    1267          618 :     myInstance = this;
    1268          618 :     myLastPhaseInstance = nullptr;
    1269          618 :     sequentialPriorPhase = nullptr;
    1270          618 :     myLightState = LightState::Red;
    1271          618 :     transitionActive = false;
    1272              : 
    1273              :     // Timing Parameters
    1274          618 :     maxGreenDynamic = myCorePhase->maxDuration;
    1275          618 :     myStartTime = TIME2STEPS(0.);
    1276          618 :     myExpectedDuration = myCorePhase->minDuration;
    1277          618 :     myLastEnd = TIME2STEPS(0.);
    1278              : 
    1279              :     // set the phase colors
    1280          618 :     setMyNEMAStates();
    1281          618 : }
    1282              : 
    1283          596 : NEMAPhase::~NEMAPhase() {
    1284              :     // Delete the transitions from their alloc
    1285         2319 :     for (auto t : myTransitions) {
    1286         1723 :         delete t;
    1287              :     }
    1288          596 : }
    1289              : 
    1290              : void
    1291          600 : NEMAPhase::init(NEMALogic* controller, int crossPhaseTarget, int crossPhaseSource, bool latching) {
    1292              :     // switch the durations from steps2time
    1293          600 :     recalculateTiming();
    1294              : 
    1295         2368 :     for (auto p : controller->getPhasesByRing(ringNum)) {
    1296              :         // construct transitions for all potential movements, including back to myself
    1297         1768 :         myTransitions.push_back(new PhaseTransitionLogic(this, p));
    1298         1768 :         myTransitions.back()->setDistance(controller->measureRingDistance(phaseName, p->phaseName, ringNum));
    1299          600 :     }
    1300              : 
    1301              :     // sort the transitions by distance for speed later. Using plain distance here
    1302          600 :     std::sort(myTransitions.begin(), myTransitions.end(), [&](const PhaseTransitionLogic * i, const PhaseTransitionLogic * j) {
    1303         2224 :         return i->distance < j->distance;
    1304              :     });
    1305              : 
    1306              :     // create the phase detector info
    1307          608 :     myDetectorInfo = PhaseDetectorInfo(latching,
    1308            8 :                                        crossPhaseSource > 0 ? controller->getPhaseObj(crossPhaseSource) : nullptr,
    1309            8 :                                        crossPhaseTarget > 0 ? controller->getPhaseObj(crossPhaseTarget) : nullptr
    1310              :                                       );
    1311          600 : }
    1312              : 
    1313              : void
    1314          600 : NEMAPhase::recalculateTiming(void) {
    1315              :     // This could be extended in the future to allow for traci manipulation
    1316          600 :     yellow = myCorePhase->yellow;
    1317          600 :     red = myCorePhase->red;
    1318          600 :     minDuration = myCorePhase->minDuration;
    1319          600 :     maxDuration = myCorePhase->maxDuration;
    1320          600 :     nextMaxDuration = myCorePhase->maxDuration;
    1321          600 :     maxGreenDynamic = myCorePhase->maxDuration;
    1322          600 :     vehExt = myCorePhase->vehext;
    1323          600 : }
    1324              : 
    1325              : 
    1326              : // TODO: this can be computed once.
    1327              : char
    1328     10012350 : NEMAPhase::getNEMAChar(int i) {
    1329     10012350 :     if (myLightState >= LightState::Green) {
    1330      2340093 :         return myGreenString[i];
    1331      7672257 :     } else if (myLightState <= LightState::Red) {
    1332      3223320 :         return myRedString[i];
    1333              :     } else {
    1334      4448937 :         return myYellowString[i];
    1335              :     }
    1336              : }
    1337              : 
    1338              : void
    1339          618 : NEMAPhase::setMyNEMAStates() {
    1340          618 :     myGreenString = myCorePhase->getState();
    1341          618 :     myRedString = "";
    1342          618 :     myYellowString = "";
    1343         9838 :     for (char ch : myGreenString) {
    1344              :         myRedString += 'r';
    1345         9220 :         if (ch == 'G' || ch == 'g') {
    1346              :             myYellowString += 'y';
    1347              :         } else {
    1348              :             myYellowString += ch;
    1349              :         }
    1350              :     }
    1351          618 : }
    1352              : 
    1353              : void
    1354      1879106 : NEMAPhase::clearMyDetectors() {
    1355      1879106 :     lastDetectActive = myDetectorInfo.detectActive;
    1356              :     // remove the active flag on the detector if the detector is not latching or if it is green
    1357      1879106 :     if ((!myDetectorInfo.latching) || (myLightState >= LightState::Green)) {
    1358      1878244 :         myDetectorInfo.detectActive = false;
    1359              :     }
    1360      1879106 : }
    1361              : 
    1362              : void
    1363      1879106 : NEMAPhase::checkMyDetectors() {
    1364              :     // Check my Detectors, only necessary if it isn't currently marked as on
    1365      1879106 :     if (!myDetectorInfo.detectActive) {
    1366              :         // If I have a cross phase target and it is active and I am not, save my detector as not active
    1367      1878632 :         if (myDetectorInfo.cpdTarget != nullptr) {
    1368         2544 :             if (myDetectorInfo.cpdTarget->getCurrentState() >= LightState::Green) {
    1369         2204 :                 if (myLightState < LightState::Green) {
    1370         1084 :                     myDetectorInfo.detectActive = false;
    1371         1084 :                     return;
    1372              :                 }
    1373              :             }
    1374              :         }
    1375              :         // If we make it to this point, check my detector like normal.
    1376      3955960 :         for (auto& d : myDetectorInfo.detectors) {
    1377      2384019 :             if (d->getCurrentVehicleNumber() > 0) {
    1378       305607 :                 myDetectorInfo.detectActive = true;
    1379              :                 return;
    1380              :             }
    1381              :         }
    1382              :         // If my detector is not active, check my cross phase
    1383      1571941 :         if ((myDetectorInfo.cpdSource != nullptr) && (myLightState >= LightState::Green)) {
    1384          994 :             if (myDetectorInfo.cpdSource->getCurrentState() < LightState::Green) {
    1385         1728 :                 for (auto& d : myDetectorInfo.cpdSource->getDetectors()) {
    1386          964 :                     if (d->getCurrentVehicleNumber() > 0) {
    1387          200 :                         myDetectorInfo.detectActive = true;
    1388              :                         return;
    1389              :                     }
    1390          964 :                 }
    1391              :             }
    1392              :         }
    1393              :     }
    1394              : }
    1395              : 
    1396              : void
    1397        54808 : NEMAPhase::enter(NEMALogic* controller, NEMAPhase* lastPhase) {
    1398              : #ifdef DEBUG_NEMA_SWITCH
    1399              :     std::cout << SIMTIME << " enter tls=" << controller->getID() << " phase=" << phaseName << "\n";
    1400              : #endif
    1401              : 
    1402              :     // set the last phase instance to inactive
    1403              :     lastPhase->cleanupExit();
    1404              : 
    1405              :     // Enter the phase
    1406        54808 :     myStartTime = controller->getCurrentTime();
    1407        54808 :     myLightState = LightState::Green;
    1408        54808 :     myLastPhaseInstance = lastPhase;
    1409        54808 :     readyToSwitch = false;
    1410              : 
    1411              :     // implement the new timing parameters on the first coordinated phase to appear
    1412        54808 :     if (phaseName == controller->coordinatePhaseObjs[ringNum]->phaseName) {
    1413        27290 :         controller->implementTraciChanges();
    1414              :     }
    1415              : 
    1416              :     // Handle Green Rest Peculiarities
    1417        54808 :     if (!controller->coordinateMode && isGreenRest) {
    1418              :         // If the controller is in free mode and the phase is a green rest phase, then it should enter as "green rest"
    1419          100 :         myLightState = LightState::GreenRest;
    1420              :         // if the phase has "green rest" capabilities, set its timer to the dynamic maxGreen
    1421          100 :         greenRestTimer = maxDuration * isGreenRest;
    1422              :     }
    1423              : 
    1424              :     // clear the last transition decision
    1425        54808 :     lastTransitionDecision = nullptr;
    1426              : 
    1427              :     // Calculate the Max Green Time & Expected Duration here:
    1428        54808 :     if (controller->coordinateMode) {
    1429         4148 :         if (coordinatePhase) {
    1430         1992 :             myExpectedDuration = controller->ModeCycle(forceOffTime - controller->getTimeInCycle(), controller->getCurrentCycleLength());
    1431              :         } else {
    1432         2156 :             maxGreenDynamic = controller->ModeCycle(forceOffTime - controller->getTimeInCycle(), controller->getCurrentCycleLength());
    1433         2156 :             if (!fixForceOff) {
    1434         1442 :                 maxGreenDynamic = MIN2(maxDuration, maxGreenDynamic);
    1435              :             }
    1436         2156 :             myExpectedDuration = minDuration;
    1437              :         }
    1438              :     } else {
    1439        50660 :         myExpectedDuration = minDuration;
    1440              :     }
    1441              :     // Implements the maxRecall functionality
    1442        54808 :     if (maxRecall && !coordinatePhase) {
    1443          206 :         myExpectedDuration = maxGreenDynamic;
    1444              :     }
    1445              :     // Set the controller's active phase
    1446        54808 :     controller->setActivePhase(this);
    1447        54808 : }
    1448              : 
    1449       402589 : void NEMAPhase::exit(NEMALogic* controller, PhaseTransitionLogic* nextPhases[2]) {
    1450       402589 :     if (nextPhases[ringNum]->getToPhase() != this) {
    1451              :         // if the next phase is not me, then I need to go into a transition
    1452       345422 :         lastTransitionDecision = nextPhases[ringNum];
    1453       345422 :         if (myLightState >= LightState::Green) {
    1454              :             // if I am in green, then I need to enter yellow
    1455        54142 :             enterYellow(controller);
    1456        54142 :             return;
    1457              :         }
    1458              : 
    1459       291280 :         if (controller->getCurrentTime() - myLastEnd < (yellow + red)) {
    1460       228076 :             if (controller->getCurrentTime() - myLastEnd >= yellow) {
    1461              :                 // if I am in yellow, then I need to enter red
    1462       110568 :                 myLightState = LightState::Red;
    1463              :             }
    1464              :             // I am currently in the Red state but haven't reached max
    1465       228076 :             return;
    1466              :         }
    1467              : 
    1468        63204 :         handleRedXferOrNextPhase(controller, nextPhases);
    1469        63204 :         return;
    1470              :     }
    1471              : 
    1472        57167 :     handleGreenRestOrTransfer(controller, nextPhases);
    1473              : }
    1474              : 
    1475        54142 : void NEMAPhase::enterYellow(NEMALogic* controller) {
    1476        54142 :     myLastEnd = controller->getCurrentTime();
    1477        54142 :     myLightState = LightState::Yellow;
    1478        54142 :     transitionActive = true;
    1479        54142 : }
    1480              : 
    1481        63204 : void NEMAPhase::handleRedXferOrNextPhase(NEMALogic* controller, PhaseTransitionLogic* nextPhases[2]) {
    1482        63204 :     PhasePtr otherPhase = controller->getOtherPhase(this);
    1483        63204 :     bool barrierCross = nextPhases[ringNum]->getToPhase()->barrierNum != barrierNum;
    1484        62304 :     bool barrierCrossButOkay = barrierCross && (
    1485        62304 :                                    nextPhases[ringNum]->getToPhase()->barrierNum == nextPhases[otherPhase->ringNum]->getToPhase()->barrierNum
    1486        63204 :                                ) && otherPhase->okay2ForceSwitch(controller);
    1487              : 
    1488        63204 :     if (!barrierCross) {
    1489          900 :         nextPhases[ringNum]->getToPhase()->enter(controller, this);
    1490          900 :         return;
    1491              :     }
    1492              : 
    1493        62304 :     if (barrierCrossButOkay) {
    1494              :         // if the barrier is crossed, but the other phase is going to the same barrier, then I can enter red transfer
    1495              :         // enter the next phase
    1496        26574 :         nextPhases[ringNum]->getToPhase()->enter(controller, this);
    1497              :         // trigger the other phase to enter red transfer
    1498        26574 :         nextPhases[otherPhase->ringNum]->getToPhase()->enter(controller, this);
    1499        26574 :         return;
    1500              :     }
    1501              : 
    1502        35730 :     myLightState = LightState::RedXfer;
    1503        35730 :     readyToSwitch = true;
    1504        35730 :     transitionActive = false;
    1505              : }
    1506              : 
    1507        57167 : void NEMAPhase::handleGreenRestOrTransfer(NEMALogic* controller, PhaseTransitionLogic* nextPhases[2]) {
    1508        57167 :     NEMAPhase* otherPhase = controller->getOtherPhase(this);
    1509        57167 :     readyToSwitch = false;
    1510        86691 :     bool isOtherPhaseReady = nextPhases[!ringNum]->getToPhase() == otherPhase && otherPhase->readyToSwitch;
    1511        57167 :     bool isOtherPhaseInGreenRest = otherPhase->greenRestTimer >= otherPhase->maxDuration && otherPhase->getCurrentState() == LightState::GreenRest;
    1512              : 
    1513        57167 :     if (isOtherPhaseReady || isOtherPhaseInGreenRest) {
    1514        54232 :         myLightState = LightState::GreenRest;
    1515        54232 :         myStartTime = controller->getCurrentTime() - minDuration;
    1516        54232 :         myExpectedDuration = minDuration;
    1517        54232 :         greenRestTimer = maxDuration * isGreenRest;
    1518              :     } else {
    1519         2935 :         myLightState = LightState::GreenXfer;
    1520         2935 :         if (isAtBarrier) {
    1521         2805 :             myExpectedDuration = (otherPhase->myExpectedDuration + otherPhase->myStartTime) - myStartTime;
    1522              :         }
    1523              :     }
    1524        57167 : }
    1525              : 
    1526              : SUMOTime
    1527        47514 : NEMAPhase::getTransitionTime(NEMALogic* controller) {
    1528        47514 :     if (myLightState == LightState::RedXfer) {
    1529              :         // if in red xfer, I am ready to switch whenevery
    1530              :         return TIME2STEPS(0);
    1531              :     }
    1532        20838 :     if (!transitionActive) {
    1533              :         // if a transition is not active, the transition is just yellow + red time
    1534        13392 :         return getTransitionTimeStateless();
    1535              :     }
    1536              :     // if a transition is active, then return the time left in the transition
    1537         7446 :     return MAX2(TIME2STEPS(0), ((yellow + red) - (controller->getCurrentTime() - myLastEnd)));
    1538              : }
    1539              : 
    1540              : SUMOTime
    1541       305693 : NEMAPhase::calcVehicleExtension(SUMOTime duration) {
    1542       305693 :     if (myExpectedDuration < maxGreenDynamic && myDetectorInfo.detectActive) {
    1543              :         // add the vehicle extension timer if the detector is active.
    1544              :         // capped by the minimum and maximum duration
    1545        27138 :         return MIN2(MAX2(duration + vehExt, minDuration), maxGreenDynamic);
    1546              :     }
    1547              :     return myExpectedDuration;
    1548              : }
    1549              : 
    1550              : void
    1551       793880 : NEMAPhase::update(NEMALogic* controller) {
    1552              :     // If I am in a transition, the rest of the update logic does not matter
    1553       793880 :     if (myLightState < LightState::Green) {
    1554              :         // return early
    1555       292256 :         readyToSwitch = true;
    1556       292256 :         return;
    1557              :     }
    1558              : 
    1559              :     // Continuation Logic
    1560       501624 :     SUMOTime duration = controller->getCurrentTime() - myStartTime;
    1561              :     // Check the vehicle extension timer as long as not in green transfer and not a coordinated phase
    1562       501624 :     if (myLightState != LightState::GreenXfer && !coordinatePhase) {
    1563       305693 :         myExpectedDuration = calcVehicleExtension(duration);
    1564              :     }
    1565              :     // Special Logic for Green Rest, which behaves uniquely
    1566       501624 :     if (myLightState == LightState::GreenRest) {
    1567              :         // check all other detectors and see if anything else is active. If so,
    1568              :         // start the green rest timer countdown, which is == to the max duration of the phase
    1569              :         bool vehicleActive = false;
    1570       479934 :         for (auto& p : controller->getPhaseObjs()) {
    1571       429130 :             if ((p->phaseName != phaseName)
    1572       375022 :                     && (p->phaseName != controller->getOtherPhase(this)->phaseName)
    1573      1070476 :                     && p->callActive()) {
    1574         7424 :                 greenRestTimer -= DELTA_T;
    1575              :                 vehicleActive = true;
    1576         7424 :                 break;
    1577              :             }
    1578        58228 :         }
    1579              :         // catch the rising edge of the sidestreet detection and calculate the maximum timer
    1580        58228 :         if (vehicleActive && (greenRestTimer + DELTA_T >= maxDuration)) {
    1581         5484 :             maxGreenDynamic = minDuration + maxDuration;
    1582              :         }
    1583              : 
    1584              :         // if there are no other vehicles slide the startTime along
    1585              :         if (!vehicleActive) {
    1586        50804 :             greenRestTimer = maxDuration;
    1587        50804 :             if (duration >= minDuration) {
    1588        50010 :                 myStartTime = controller->getCurrentTime() - minDuration;
    1589        50010 :                 maxGreenDynamic = minDuration + maxDuration;
    1590        50010 :                 myExpectedDuration = minDuration + MAX2(TIME2STEPS(0), myExpectedDuration - duration);
    1591              :             }
    1592              :         }
    1593              : 
    1594              :         // if the green rest timer is exhausted, set ready to switch
    1595        58228 :         if (greenRestTimer < DELTA_T) {
    1596            6 :             readyToSwitch = true;
    1597              :             // force the counterpart to be ready to switch too. This needs to be latching....
    1598            6 :             NEMAPhase* otherPhase = controller->getOtherPhase(this);
    1599            6 :             if (otherPhase->getCurrentState() > LightState::Green) {
    1600            6 :                 otherPhase->readyToSwitch = true;
    1601              :             }
    1602              :         }
    1603              : 
    1604              :         // Special Behavior when the Green Rest Circles all the way around in coordinated mode
    1605        58228 :         if (coordinatePhase) {
    1606              :             // This means that we have green rested until I should "start" again. Just call my entry function again.
    1607        52352 :             if (controller->getTimeInCycle() <= ((forceOffTime - maxDuration) + DELTA_T / 2)) {
    1608          536 :                 enter(controller, this);
    1609              :             }
    1610              :         }
    1611              :     }
    1612              :     // Check to see if a switch is desired
    1613       501624 :     if (duration >= myExpectedDuration) {
    1614       111305 :         readyToSwitch = true;
    1615              :     }
    1616              : }
    1617              : 
    1618              : PhaseTransitionLogic*
    1619          312 : NEMAPhase::getTransition(int toPhase) {
    1620          766 :     for (auto t : myTransitions) {
    1621          766 :         if (t->getToPhase()->phaseName == toPhase) {
    1622              :             return t;
    1623              :         }
    1624              :     }
    1625              :     // This point should never be reached
    1626              :     assert(0);
    1627              :     // To satisfy the compiler and return value from all control paths
    1628            0 :     return myTransitions.front();
    1629              : }
    1630              : 
    1631              : std::vector<PhaseTransitionLogic*>
    1632       410514 : NEMAPhase::trySwitch(NEMALogic* controller) {
    1633              :     // this function returns the preferred valid transition for the phase
    1634              :     std::vector<PhaseTransitionLogic*> nextTransitions;
    1635       410514 :     if (readyToSwitch) {
    1636              :         // only try to switch if I am ready to switch
    1637       574924 :         for (auto& t : myTransitions) {
    1638              :             // for the transitions check if it is okay
    1639       574786 :             if (t->okay(controller)) {
    1640              :                 // if there was already a transition decision, it can be overriden but only if the new transition
    1641              :                 // is on the same side of a barrier
    1642       403487 :                 if (lastTransitionDecision != nullptr) {
    1643       292178 :                     if (t->getToPhase()->barrierNum == lastTransitionDecision->getToPhase()->barrierNum) {
    1644       292118 :                         nextTransitions.push_back(t);
    1645              :                         break;
    1646              :                     }
    1647              :                 } else {
    1648       111309 :                     nextTransitions.push_back(t);
    1649              :                     // break once there is a valid option (they have already been sorted)
    1650              :                     break;
    1651              :                 }
    1652              :             }
    1653              :         }
    1654              :     }
    1655              :     // Add in the last transition decision if it hasn't been added in yet
    1656       410514 :     if (lastTransitionDecision != nullptr) {
    1657              :         bool found = false;
    1658              :         bool sameBarrier = false;
    1659       292256 :         for (auto& t : nextTransitions) {
    1660       292118 :             if (t == lastTransitionDecision) {
    1661              :                 found = true;
    1662              :                 break;
    1663              :             }
    1664            4 :             if (t->getToPhase()->barrierNum == lastTransitionDecision->getToPhase()->barrierNum) {
    1665              :                 sameBarrier = true;
    1666              :                 break;
    1667              :             }
    1668              :         }
    1669              :         // but only need to add it if it is not in the list AND if nothing in the list is the same barrier as it was.
    1670       292256 :         if (!found && !sameBarrier) {
    1671          138 :             nextTransitions.push_back(lastTransitionDecision);
    1672              :         }
    1673              :     }
    1674              :     // Add the transition back to myself, but only in the case when no others have been added
    1675       410514 :     if (nextTransitions.size() < 1) {
    1676         6949 :         nextTransitions.push_back(myTransitions.back());
    1677              :     }
    1678              : 
    1679       410514 :     return nextTransitions;
    1680            0 : }
    1681              : 
    1682              : // ===========================================================================
    1683              : // PhaseTransitionLogic Definitions
    1684              : // ===========================================================================
    1685         1768 : PhaseTransitionLogic::PhaseTransitionLogic(
    1686         1768 :     NEMAPhase* fromPhase, NEMAPhase* toPhase) :
    1687         1768 :     distance(0),
    1688         1768 :     fromPhase(fromPhase),
    1689         1768 :     toPhase(toPhase)
    1690         1768 : {}
    1691              : 
    1692              : bool
    1693       574786 : PhaseTransitionLogic::okay(NEMALogic* controller) {
    1694              :     // Picking the correct transition logic to use
    1695              :     // #TODO this could be a case of using function as variable and setting it at PhaseTransitionLogic
    1696              :     // creation time
    1697       574786 :     if (fromPhase == toPhase) {
    1698              :         // for green rest or green transfer, it cannot return to itself if a transition is active
    1699        57247 :         return fromPhase->getCurrentState() >= LightState::Green;
    1700       517539 :     } else if (fromPhase->coordinatePhase) {
    1701              :         // if the from phase is a coordinated phase i.e. {2, 6} in a standard setup
    1702       169026 :         return fromCoord(controller);
    1703       348513 :     } else if (fromPhase->isAtBarrier) {
    1704              :         // if the phase is at a barrier i.e. {2, 6, 4, 8} in a standard setup
    1705       339685 :         return fromBarrier(controller);
    1706         8828 :     } else if (controller->coordinateMode) {
    1707              :         // typical coordinate mode transition,
    1708         7730 :         return coordBase(controller);
    1709              :     } else {
    1710              :         // base transition logic
    1711         1098 :         return freeBase(controller);
    1712              :     }
    1713              : }
    1714              : 
    1715              : bool
    1716       512309 : PhaseTransitionLogic::freeBase(NEMALogic* controller) {
    1717              :     // Simplest transition logic. Just check if a detector (or recall) is active on that phase and
    1718              :     bool okay = false;
    1719              :     // is a call active on the toPhase?
    1720       686001 :     if (toPhase->callActive()) {
    1721              :         // would the transition be a barrier cross?
    1722       349009 :         if (fromPhase->barrierNum != toPhase->barrierNum) {
    1723       346863 :             PhasePtr otherPhase = controller->getOtherPhase(fromPhase);
    1724              :             // If it is a barrier cross, then the other phase must also be ready to switch
    1725              :             // or have a transition time that is lower than mine currently. DELTA_T is critical here
    1726       346863 :             if (otherPhase->readyToSwitch) {
    1727              :                 // #&& otherPhase->getTransitionTime(controller) <= fromPhase->getTransitionTime(controller)) {
    1728              :                 okay = true;
    1729              :             }
    1730              :         } else {
    1731              :             okay = true;
    1732              :         }
    1733              :     }
    1734       512309 :     return okay;
    1735              : }
    1736              : 
    1737              : bool
    1738       176756 : PhaseTransitionLogic::coordBase(NEMALogic* controller) {
    1739       176756 :     if (toPhase->coordinatePhase &&
    1740         5318 :             (controller->getOtherPhase(fromPhase)->readyToSwitch || fromPhase->barrierNum == toPhase->barrierNum)) {
    1741              :         // transitions TO the coordinated phase may always happen, as long as the other phase is okay to switch too
    1742              :         return true;
    1743              :     }
    1744              :     // first check if the free logic is upheld
    1745       171526 :     else if (freeBase(controller)) {
    1746              :         // Then check if the "to phase" can fit, which means that there is enough time to fit the current transition + the minimum time of the next phase
    1747        14984 :         SUMOTime transitionTime = fromPhase->getTransitionTime(controller);
    1748        14984 :         SUMOTime timeTillForceOff = controller->ModeCycle(toPhase->forceOffTime - controller->getTimeInCycle(), controller->getCurrentCycleLength());
    1749        14984 :         if (toPhase->minDuration + transitionTime <= timeTillForceOff) {
    1750              :             return true;
    1751              :         }
    1752              :     }
    1753              :     return false;
    1754              : }
    1755              : 
    1756              : 
    1757              : bool
    1758       339685 : PhaseTransitionLogic::fromBarrier(NEMALogic* controller) {
    1759       339685 :     if (freeBase(controller)) {
    1760       331428 :         if (fromPhase->barrierNum == toPhase->barrierNum) {
    1761              :             // same barrier side so we are good.
    1762              :             // Check if green transfer is active. If so, we need to make sure that there are no calls on the other side of the barrier
    1763          158 :             if (fromPhase->getCurrentState() >= LightState::Green) {
    1764          188 :                 for (auto& p : controller->getPhasesByRing(fromPhase->ringNum)) {
    1765          200 :                     if (p->barrierNum != fromPhase->barrierNum && p->callActive()) {
    1766              :                         return false;
    1767              :                     }
    1768          100 :                 }
    1769              :             }
    1770           64 :             return true;
    1771              :         } else {
    1772              :             // This is now a barrier cross and we need to make sure that the other phase is also ready to transition
    1773       331270 :             if (fromPhase->readyToSwitch && controller->getOtherPhase(fromPhase)->readyToSwitch) {
    1774              :                 return true;
    1775              :             }
    1776              :         }
    1777              :     }
    1778              :     return false;
    1779              : }
    1780              : 
    1781              : 
    1782              : bool
    1783       169026 : PhaseTransitionLogic::fromCoord(NEMALogic* controller) {
    1784       169026 :     if (coordBase(controller)) {
    1785              :         // Determine if the other phase is also ready to switch
    1786        14362 :         if (controller->getOtherPhase(fromPhase)->readyToSwitch) {
    1787              :             // Dr. Wang had the Type-170 code setup in a way that it could transition whenever - meaning that it didn't matter if the prior phase could fit or not
    1788        14330 :             if (controller->isType170()) {
    1789              :                 return true;
    1790              :             }
    1791              :             // If the transition is already active, then report that the movement is possible
    1792         8030 :             if (fromPhase->isTransitionActive()) {
    1793              :                 return true;
    1794              :             }
    1795              :             // now determine if there my prior phase can fit or not. We already know that I can fit.
    1796         5956 :             NEMAPhase* priorPhase = toPhase->getSequentialPriorPhase();
    1797         5956 :             SUMOTime timeTillForceOff = controller->ModeCycle(priorPhase->forceOffTime - controller->getTimeInCycle(), controller->getCurrentCycleLength());
    1798         5956 :             SUMOTime transitionTime = fromPhase->getTransitionTime(controller);
    1799              :             // if the time till the force off is less than the min duration ||
    1800              :             // if it is greater than the cycle length minus the length of the coordinate phase (which the fromPhase automatically is)
    1801         5956 :             if ((priorPhase->minDuration + transitionTime) > timeTillForceOff || timeTillForceOff > (controller->getCurrentCycleLength() - fromPhase->minDuration)) {
    1802              :                 return true;
    1803              :             }
    1804              :         }
    1805              :     }
    1806              :     return false;
    1807              : }
    1808              : 
    1809              : int
    1810       410826 : PhaseTransitionLogic::getDistance(PhaseTransitionLogic* otherTrans) {
    1811              :     // Returns the other transitions distance in green transfer situations
    1812       410826 :     if ((toPhase == fromPhase) && (otherTrans->toPhase->barrierNum == toPhase->barrierNum)) {
    1813        64214 :         if (toPhase->getCurrentState() == LightState::Green || toPhase->getCurrentState() == LightState::GreenXfer) {
    1814         9790 :             return otherTrans->distance;
    1815              :         }
    1816              :     }
    1817       401036 :     return distance;
    1818              : }
        

Generated by: LCOV version 2.0-1