LCOV - code coverage report
Current view: top level - src/microsim/traffic_lights - NEMAController.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 95.5 % 780 745
Test Date: 2025-01-02 15:43:51 Functions: 95.4 % 65 62

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

Generated by: LCOV version 2.0-1