LCOV - code coverage report
Current view: top level - src/microsim/traffic_lights - NEMAController.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 734 767 95.7 %
Date: 2024-05-19 15:37:39 Functions: 62 65 95.4 %

          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          97 : 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          97 :                      const std::string& basePath) :
      64             :     MSSimpleTrafficLightLogic(tlcontrol, id, programID, _offset, TrafficLightType::NEMA, phases, step, delay, parameter),
      65          97 :     myPhase(phases[0]->duration, phases[0]->getState()) {
      66          97 :     myDetectorLength = StringUtils::toDouble(getParameter("detector-length", "20"));
      67         194 :     myDetectorLengthLeftTurnLane = StringUtils::toDouble(getParameter("detector-length-leftTurnLane", "20"));
      68         291 :     myCycleLength = TIME2STEPS(StringUtils::toDouble(getParameter("total-cycle-length", getParameter("cycle-length", getParameter(toString(SUMO_ATTR_CYCLETIME), "60")))));
      69          97 :     myNextCycleLength = myCycleLength;
      70          97 :     myDefaultCycleTime = myCycleLength;
      71          97 :     myShowDetectors = StringUtils::toBool(getParameter("show-detectors", toString(OptionsCont::getOptions().getBool("tls.actuated.show-detectors"))));
      72         194 :     myFile = FileHelpers::checkForRelativity(getParameter("file", "NUL"), basePath);
      73          97 :     myFreq = TIME2STEPS(StringUtils::toDouble(getParameter("freq", "300")));
      74         194 :     myVehicleTypes = getParameter("vTypes", "");
      75          97 :     myControllerType = parseControllerType(getParameter("controllerType", "TS2"));
      76          97 :     ignoreErrors = StringUtils::toBool(getParameter("ignore-errors", "false"));
      77             :     // This should be extended in the future.
      78          97 :     myNumberRings = 2;
      79          97 : }
      80             : 
      81         134 : NEMALogic::~NEMALogic() {
      82             :     // delete the phase objects
      83         421 :     for (auto p : myPhaseObjs) {
      84         354 :         delete p;
      85             :     }
      86         335 : }
      87             : 
      88             : void
      89          96 : 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         192 :     std::vector<int> barrierPhases = readParaFromString(barriers);
      93         124 :     std::vector<int> coordinatePhases = readParaFromString(coordinates);
      94             : 
      95             :     // create a {{}, {}} vector of phases
      96         288 :     rings.push_back(readParaFromString(ring1));
      97         192 :     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         220 :     std::vector<int> VecMinRecall = readParaFromString(getParameter("minRecall", "1,2,3,4,5,6,7,8"));
     117         220 :     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         235 :     for (const auto& r : rings) {
     139             :         int ringIter = 0;
     140             :         lastPhaseIter = phaseIter;
     141             :         phaseIter = 0;
     142         790 :         for (const auto& p : r) {
     143         651 :             if (p != 0) {
     144             :                 // find the phase definition matching the phase integer
     145             :                 MSPhaseDefinition* tempPhase = nullptr;
     146        1307 :                 for (const auto& pDef : myPhases) {
     147        2560 :                     if (string2int(pDef->getName()) == p) {
     148         390 :                         tempPhase = pDef;
     149         390 :                         break;
     150             :                     }
     151             :                 }
     152             :                 // there must be a matching MSPhaseDefinition
     153         417 :                 if (tempPhase == nullptr) {
     154          54 :                     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         390 :                 if (myPhaseStrLen < 0) {
     162          91 :                     myPhaseStrLen = (int)state.size();
     163         299 :                 } 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         780 :                 getLaneInfoFromNEMAState(state, laneIDs, controlledStateIndexes);
     171             : 
     172             :                 std::vector<std::string> laneIDs_vector;
     173        1392 :                 for (std::string laneID : laneIDs) {
     174        1002 :                     laneIDs_vector.push_back(laneID);
     175        1002 :                     myLanePhaseMap[laneID] = p;
     176             :                 }
     177         390 :                 phase2ControllerLanesMap[p] = laneIDs_vector;
     178             : 
     179             :                 // Create the Phase Object
     180             :                 // find if it is at a barrier
     181         641 :                 bool barrierPhase = vectorContainsPhase(barrierPhases, p) || vectorContainsPhase(coordinatePhases, p);
     182             :                 // is it a coordinate phase
     183         390 :                 bool coordinatePhase = vectorContainsPhase(coordinatePhases, p) && coordinateMode;
     184             :                 // is there a minimum or max recall
     185         390 :                 bool minRecall = vectorContainsPhase(VecMinRecall, p);
     186         780 :                 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         390 :                 bool phaseGreenRest = ((VecMaxRecall.size() + VecMinRecall.size()) < 1);
     189         390 :                 if (!phaseGreenRest) {
     190         390 :                     bool recallActive = minRecall || maxRecall;
     191         390 :                     if (recallActive) {
     192         922 :                         for (const auto& pO : r) {
     193         898 :                             if (pO != p) {
     194        1476 :                                 if (vectorContainsPhase(VecMinRecall, pO)
     195        1179 :                                         || 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         390 :                 int barrierNum = ringIter / 2;
     208             : 
     209             :                 // now ready to create the phase
     210         390 :                 myPhaseObjs.push_back(
     211         390 :                     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         390 :                 if (phaseIter > 0) {
     216         230 :                     myPhaseObjs.back()->setSequentialPriorPhase(myPhaseObjs[lastPhaseIter + (phaseIter - 1)]);
     217             :                 }
     218         390 :                 phaseIter++;
     219         780 :             }
     220         624 :             ringIter++;
     221             :         }
     222             :         // Set the first to point to the last, wrapping around the ring.
     223         139 :         myPhaseObjs[lastPhaseIter]->setSequentialPriorPhase(myPhaseObjs[lastPhaseIter + phaseIter - 1]);
     224             :         // index the ring counter
     225         139 :         ringNum++;
     226             :     }
     227             : 
     228             :     //TODO: set the default phases. This could also be set using dual entry in future
     229         207 :     for (int i = 0; i < 2; i++) {
     230             :         // create the coordinate phase ptr
     231         138 :         coordinatePhaseObjs[i] = getPhaseObj(coordinatePhases[i], i);
     232         138 :         defaultBarrierPhases[i][coordinatePhaseObjs[i]->barrierNum] = coordinatePhaseObjs[i];
     233             :         // create the other barrier phase ptr
     234         138 :         PhasePtr b = getPhaseObj(barrierPhases[i], i);
     235         138 :         defaultBarrierPhases[i][b->barrierNum] = b;
     236             :         // the barrier 1 and barrier 0 default phase must not have the same barrier number
     237         138 :         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         235 :     IntVector latchingDetectors = readParaFromString(getParameter("latchingDetectors", ""));
     247             :     std::vector<std::pair<int, int>> cp;
     248         436 :     for (auto& p : myPhaseObjs) {
     249         367 :         std::string cps = "crossPhaseSwitching:";
     250        1101 :         int crossPhase = StringUtils::toInt(getParameter(cps.append(std::to_string(p->phaseName)), "0"));
     251         367 :         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         436 :     for (auto& p : myPhaseObjs) {
     258         734 :         bool latching = vectorContainsPhase(latchingDetectors, p->phaseName);
     259             :         int cpTarget = 0;
     260             :         int cpSource = 0;
     261         383 :         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         367 :         p->init(this, cpTarget, cpSource, latching);
     268             :     }
     269             : 
     270             :     // Calculate Force offs Based on Timing
     271          69 :     calculateForceOffs();
     272             : 
     273          69 :     if (coordinateMode) {
     274             :         // Calculate the Initial Phases in coordinated operation only.
     275             :         // Otherwise they have already been calculated above
     276          38 :         calculateInitialPhases();
     277             :     } else {
     278             :         // Fall back being the barrier 0 default phases
     279             :         // NEMAPhase* defaultP[2] = {defaultBarrierPhases[0][0], defaultBarrierPhases[1][0]};
     280          31 :         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          69 :     myPhase.setState(composeLightString());
     319         139 :     myPhase.setName(toString(myActivePhaseObjs[0]->phaseName) + "+" + toString(myActivePhaseObjs[1]->phaseName));
     320          69 :     setTrafficLightSignals(SIMSTEP);
     321          69 :     myStep = 0;
     322             : 
     323             :     //validating timing
     324          69 :     validate_timing();
     325          68 : }
     326             : 
     327             : bool
     328        3993 : NEMALogic::vectorContainsPhase(std::vector<int> v, int phaseNum) {
     329        3993 :     if (std::find(v.begin(), v.end(), phaseNum) != v.end()) {
     330        1438 :         return true;
     331             :     }
     332             :     return false;
     333             : }
     334             : 
     335             : void
     336          96 : NEMALogic::init(NLDetectorBuilder& nb) {
     337             : 
     338             :     // TODO: Create a parameter for this
     339          96 :     cycleRefPoint = TIME2STEPS(0);
     340             : 
     341         192 :     std::string barriers = getParameter("barrierPhases", "");
     342         221 :     std::string coordinates = getParameter("coordinatePhases", getParameter("barrier2Phases", ""));
     343         221 :     std::string ring1 = getParameter("ring1", "");
     344         221 :     std::string ring2 = getParameter("ring2", "");
     345             : 
     346          96 :     fixForceOff = StringUtils::toBool(getParameter("fixForceOff", "false"));
     347          96 :     offset = myOffset;
     348          96 :     myNextOffset = myOffset;
     349         192 :     whetherOutputState = StringUtils::toBool(getParameter("whetherOutputState", "false"));
     350          96 :     coordinateMode = StringUtils::toBool(getParameter("coordinate-mode", "false"));
     351             : 
     352             :     // set the queued traci changes to false
     353          96 :     queuedTraciChanges = false;
     354             : 
     355             :     //missing parameter error
     356         288 :     error_handle_not_set(ring1, "ring1");
     357         288 :     error_handle_not_set(ring2, "ring2");
     358         288 :     error_handle_not_set(barriers, "barrierPhases");
     359         288 :     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          96 :     constructTimingAndPhaseDefs(barriers, coordinates, ring1, ring2);
     382             : 
     383             :     //init the traffic light
     384          68 :     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         962 :     for (const LaneVector& lanes : myLanes) {
     388        1797 :         for (MSLane* const lane : lanes) {
     389             :             //decide the detector length
     390             :             double detector_length = 0;
     391         903 :             if (isLeftTurnLane(lane)) {
     392         144 :                 detector_length = myDetectorLengthLeftTurnLane;
     393             :             } else {
     394         759 :                 detector_length = myDetectorLength;
     395             :             }
     396         903 :             if (noVehicles(lane->getPermissions())) {
     397             :                 // do not build detectors on green verges or sidewalks
     398           8 :                 continue;
     399             :             }
     400             :             // Build detector and register them in the detector control
     401         895 :             if (myLaneDetectorMap.find(lane) == myLaneDetectorMap.end()) {
     402         572 :                 MSE2Collector* det = nullptr;
     403        1173 :                 const std::string customID = getParameter(lane->getID());
     404         572 :                 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         505 :                     int phaseNumber = 0;
     413         505 :                     if (myLanePhaseMap.find(lane->getID()) != myLanePhaseMap.end()) {
     414         503 :                         phaseNumber = myLanePhaseMap.find(lane->getID())->second;
     415             :                     }
     416         505 :                     int index = lane->getIndex();
     417        1010 :                     std::string id = myID + "_" + myProgramID + "_D" + toString(phaseNumber) + "." + toString(index);
     418         551 :                     while (MSNet::getInstance()->getDetectorControl().getTypedDetectors(SUMO_TAG_LANE_AREA_DETECTOR).get(id) != nullptr) {
     419          23 :                         index++;
     420          46 :                         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         505 :                     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         505 :                                        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         505 :                                        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         505 :                                        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        1010 :                     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         571 :                 myLaneDetectorMap[lane] = det;
     449         571 :                 myDetectorLaneMap[det] = lane;
     450        1143 :                 myDetectorInfoVector.push_back(DetectorInfo(det, (int)myPhases.size()));
     451             : 
     452             :             }
     453             :         }
     454             :     }
     455         385 :     for (auto item : phase2ControllerLanesMap) {
     456         318 :         int NEMAPhaseIndex = item.first;
     457         318 :         std::vector<std::string> laneIDs = item.second;
     458             :         std::vector<MSE2Collector*> detectors;
     459         318 :         MSE2Collector* detector = nullptr;
     460        1120 :         for (std::string laneID : laneIDs) {
     461         802 :             MSLane* lane = MSLane::dictionary(laneID);
     462         802 :             detector = myLaneDetectorMap[lane];
     463         802 :             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         954 :         for (int i = 0; i < 2; i++) {
     468        1272 :             if (vectorContainsPhase(rings[i], NEMAPhaseIndex)) {
     469         708 :                 getPhaseObj(NEMAPhaseIndex, i)->setDetectors(detectors);
     470             :             }
     471             :         }
     472         318 :     }
     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          67 :     const int numLinks = (int)myLinks.size();
     481          67 :     std::vector<bool> neverMajor(numLinks, true);
     482         390 :     for (const MSPhaseDefinition* phase : myPhases) {
     483             :         const std::string& state = phase->getState();
     484        4675 :         for (int i = 0; i < numLinks; i++) {
     485        4352 :             if (state[i] == LINKSTATE_TL_GREEN_MAJOR) {
     486             :                 neverMajor[i] = false;
     487             :             }
     488             :         }
     489             :     }
     490          67 :     std::vector<bool> oneLane(numLinks, false);
     491         961 :     for (int i = 0; i < numLinks; i++) {
     492        1795 :         for (MSLane* lane : getLanesAt(i)) {
     493             :             int numMotorized = 0;
     494        3485 :             for (MSLane* l : lane->getEdge().getLanes()) {
     495        2583 :                 if ((l->getPermissions() & motorized) != 0) {
     496        2223 :                     numMotorized++;
     497             :                 }
     498             :             }
     499         902 :             if (numMotorized == 1) {
     500             :                 oneLane[i] = true;
     501           1 :                 break;
     502             :             }
     503             :         }
     504             :     }
     505             : 
     506         390 :     for (const MSPhaseDefinition* phase : myPhases) {
     507         323 :         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        4590 :             for (int i = 0; i < numLinks; i++) {
     515        4272 :                 if (state[i] == LINKSTATE_TL_GREEN_MAJOR
     516        4272 :                         || (state[i] == LINKSTATE_TL_GREEN_MINOR
     517         236 :                             && ((neverMajor[i]  // check1a
     518          84 :                                  && hasMajor(state, getLanesAt(i))) // check1b
     519         157 :                                 || oneLane[i])) // check1c
     520             :                    ) {
     521             :                     greenLinks.insert(i);
     522             :                     actuatedLinks.insert(i);
     523             :                 }
     524             : 
     525       12848 :                 for (MSLane* lane : getLanesAt(i)) {
     526             :                     if (myLaneDetectorMap.count(lane) != 0) {
     527        4240 :                         detectorLinks[myLaneDetectorMap[lane]].insert(i);
     528             :                     }
     529             :                 }
     530             :             }
     531        3006 :             for (auto& item : detectorLinks) {
     532        2688 :                 MSE2Collector* det = item.first;
     533        2688 :                 MSLane* detectorLane = myDetectorLaneMap[det];
     534             :                 bool usable = true;
     535             :                 // check 1
     536        6912 :                 for (int j : item.second) {
     537             :                     if (greenLinks.count(j) == 0) {
     538             :                         usable = false;
     539             :                     }
     540             :                 }
     541             : 
     542             :                 //check 2
     543        2688 :                 if (usable) {
     544        1423 :                     for (MSLink* link : detectorLane->getLinkCont()) {
     545         865 :                         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        2688 :                 if (usable) {
     559         558 :                     detectors.insert(item.first);
     560        1419 :                     for (int j : item.second) {
     561         861 :                         linkToDetectors[j].insert(item.first);
     562             :                     }
     563             :                 }
     564             :             }
     565         318 :             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         323 :         myDetectorForPhase.push_back(detectorInfos);
     571         881 :         for (MSE2Collector* det : detectors) {
     572        5333 :             for (DetectorInfo& detInfo : myDetectorInfoVector) {
     573        4775 :                 if (detInfo.det == det) {
     574         558 :                     myDetectorForPhase.back().push_back(&detInfo);
     575         558 :                     detInfo.servedPhase[phaseIndex] = true;
     576             :                 }
     577             :             }
     578             :         }
     579             :     }
     580             : 
     581         941 :     for (int i : actuatedLinks) {
     582         895 :         if (linkToDetectors[i].size() == 0 && myLinks[i].size() > 0
     583         895 :                 && (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          67 : }
     588             : 
     589             : void
     590          69 : NEMALogic::validate_timing() {
     591             :     // check that the cycle length for each ring adds up to the specified cycle length
     592         205 :     for (int ringIndex = 0; ringIndex < 2; ringIndex++) {
     593             :         SUMOTime cycleLengthCalculated = 0;
     594         502 :         for (auto& p : getPhasesByRing(ringIndex)) {
     595         365 :             cycleLengthCalculated += (p->maxDuration + p->yellow + p->red);
     596             :         }
     597         137 :         if (coordinateMode && (cycleLengthCalculated != myCycleLength)) {
     598          53 :             int ringNumber = ringIndex + 1;
     599         106 :             const std::string error = "At NEMA tlLogic '" + getID() + "', Ring " + toString(ringNumber) + " does not add to cycle length.";
     600          53 :             if (ignoreErrors) {
     601         157 :                 WRITE_WARNING(error);
     602             :             } else {
     603           2 :                 throw  ProcessError(error);
     604             :             }
     605             :         }
     606             :     }
     607             :     // check that the barriers sum together
     608          68 :     SUMOTime cycleLengths[2][2] = { {0, 0}, {0, 0} };
     609         204 :     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         498 :         for (const auto p : getPhasesByRing(ringIndex)) {
     612         362 :             cycleLengths[ringIndex][p->barrierNum] += p->maxDuration + p->yellow + p->red;
     613             :         }
     614             :     }
     615             :     // Write warnings if the barriers do not sum
     616         204 :     for (int barrierNum = 0; barrierNum < 2; barrierNum++) {
     617         136 :         if (cycleLengths[0][barrierNum] != cycleLengths[1][barrierNum]) {
     618          28 :             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          68 :     if (!coordinateMode && offset != 0) {
     630           0 :         WRITE_WARNINGF(TL("NEMA tlLogic '%' is not coordinated but an offset was set."), getID());
     631             :     }
     632          68 : }
     633             : 
     634             : void
     635          10 : NEMALogic::setNewSplits(std::vector<double> newSplits) {
     636             :     assert(newSplits.size() == 8);
     637          60 :     for (auto& p : myPhaseObjs) {
     638          50 :         if (newSplits[p->phaseName - 1] > 0) {
     639             :             // set the phase's nextMaxDuration. This will be implemented when implementTraciChanges is called
     640          50 :             p->nextMaxDuration = TIME2STEPS(newSplits[p->phaseName - 1]) - p->yellow - p->red;
     641             :         }
     642             :     }
     643          10 : }
     644             : 
     645             : 
     646             : void
     647           5 : NEMALogic::setNewMaxGreens(std::vector<double> newMaxGreens) {
     648          30 :     for (auto& p : myPhaseObjs) {
     649          25 :         if (newMaxGreens[p->phaseName - 1] > 0) {
     650             :             // set the phase's nextMaxDuration. This will be implemented when implementTraciChanges is called
     651          25 :             p->nextMaxDuration = TIME2STEPS(newMaxGreens[p->phaseName - 1]);
     652             :         }
     653             :     }
     654           5 : }
     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          50 : NEMALogic::setNewOffset(double newOffset) {
     666             :     // set the controller's offset. This will be implemented when implementTraciChanges is called
     667          50 :     myNextOffset = TIME2STEPS(newOffset);
     668          50 : }
     669             : 
     670             : 
     671         645 : std::vector<int> NEMALogic::readParaFromString(std::string s) {
     672             :     std::vector<int> output;
     673        3871 :     for (char c : s) {
     674        3226 :         if (c >= '0' && c <= '9') {
     675        1854 :             int temp = c - '0';
     676        1854 :             output.push_back(temp);
     677             :         }
     678             :     }
     679         645 :     return output;
     680             : }
     681             : 
     682             : const MSPhaseDefinition&
     683      126457 : NEMALogic::getCurrentPhaseDef() const {
     684      126457 :     return myPhase;
     685             : }
     686             : 
     687        1063 : int NEMALogic::measureRingDistance(int p1, int p2, int ringNum) {
     688        1063 :     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        5825 :     for (int i = 0; i < (length * 2); i++) {
     693        5825 :         if (rings[ringNum][i % length] > 0) {
     694        4400 :             if (found) {
     695        2200 :                 d++;
     696        2200 :                 if (rings[ringNum][i % length] == p2) {
     697             :                     break;
     698             :                 }
     699        2200 :             } else if (rings[ringNum][i % length] == p1) {
     700             :                 found = true;
     701             :             }
     702             :         }
     703             :     }
     704             :     assert(d > 0);
     705        1063 :     return d;
     706             : }
     707             : 
     708             : 
     709             : SUMOTime
     710       15342 : NEMALogic::ModeCycle(SUMOTime a, SUMOTime b) {
     711       15342 :     SUMOTime c = a - b;
     712       15421 :     while (c >= b) {
     713          79 :         c = c - b;
     714             :     }
     715       38144 :     while (c < 0) {
     716       22802 :         c += b;
     717             :     }
     718       15342 :     return c;
     719             : }
     720             : 
     721             : 
     722             : void
     723         390 : NEMALogic::getLaneInfoFromNEMAState(std::string state, StringVector& laneIDs, IntVector& stateIndex) {
     724             :     std::set<std::string> output;
     725        6105 :     for (int i = 0; i < (int)state.size(); i++) {
     726        5715 :         char ch = state[i];
     727             :         // if the ch is 'G', it means that the phase is controlling this lane
     728        5715 :         if (ch == 'G') {
     729         998 :             stateIndex.push_back(i);
     730        2004 :             for (auto link : myLinks[i]) {
     731             :                 const MSLane* incoming = link->getLaneBefore();
     732        1006 :                 if (incoming->isNormal()) {
     733        1002 :                     laneIDs.push_back(incoming->getID());
     734             :                 }
     735             :             }
     736             :         }
     737             :     }
     738         390 : }
     739             : 
     740         903 : bool NEMALogic::isLeftTurnLane(const MSLane* const lane) const {
     741         903 :     const std::vector<MSLink*> links = lane->getLinkCont();
     742         903 :     if (links.size() == 1 && links.front()->getDirection() == LinkDirection::LEFT) {
     743         144 :         return true;
     744             :     }
     745             :     return false;
     746             : }
     747             : 
     748             : bool
     749          84 : NEMALogic::hasMajor(const std::string& state, const LaneVector& lanes) const {
     750         699 :     for (int i = 0; i < (int)state.size(); i++) {
     751         694 :         if (state[i] == LINKSTATE_TL_GREEN_MAJOR) {
     752         227 :             for (MSLane* cand : getLanesAt(i)) {
     753         231 :                 for (MSLane* lane : lanes) {
     754         155 :                     if (lane == cand) {
     755             :                         return true;
     756             :                     }
     757             :                 }
     758             :             }
     759             :         }
     760             :     }
     761             :     return false;
     762             : }
     763             : 
     764             : 
     765             : void
     766          97 : NEMALogic::activateProgram() {
     767          97 :     MSTrafficLightLogic::activateProgram();
     768          97 :     for (auto& item : myLaneDetectorMap) {
     769           0 :         item.second->setVisible(true);
     770             :     }
     771          97 : }
     772             : 
     773             : void
     774           0 : NEMALogic::deactivateProgram() {
     775           0 :     MSTrafficLightLogic::deactivateProgram();
     776           0 :     for (auto& item : myLaneDetectorMap) {
     777           0 :         item.second->setVisible(false);
     778             :     }
     779           0 : }
     780             : 
     781             : void
     782           0 : NEMALogic::setShowDetectors(bool show) {
     783           0 :     myShowDetectors = show;
     784           0 :     for (auto& item : myLaneDetectorMap) {
     785           0 :         item.second->setVisible(myShowDetectors);
     786             :     }
     787           0 : }
     788             : 
     789        1280 : int NEMALogic::string2int(std::string s) {
     790        1280 :     std::stringstream ss(s);
     791        1280 :     int ret = 0;
     792        1280 :     ss >> ret;
     793        1280 :     return ret;
     794        1280 : }
     795             : 
     796             : 
     797             : const std::string
     798        4923 : NEMALogic::getParameter(const std::string& key, const std::string defaultValue) const {
     799        9846 :     if (StringUtils::startsWith(key, "NEMA.")) {
     800        1500 :         if (key == "NEMA.phaseCall") {
     801        1500 :             int activeCalls[8] = { 0 };
     802        9000 :             for (const auto p : myPhaseObjs) {
     803             :                 // This handles the case when the controller has multiple of the same phase call
     804        7500 :                 if (!activeCalls[p->phaseName - 1]) {
     805        6140 :                     activeCalls[p->phaseName - 1] = 1 * p->lastDetectActive;
     806             :                 }
     807             :             }
     808        1500 :             std::string outStr = "";
     809       13500 :             for (int i = 0; i < 8; i++) {
     810       12000 :                 outStr += std::to_string(activeCalls[i]);
     811       12000 :                 if (i < 7) {
     812             :                     outStr += ",";
     813             :                 }
     814             :             }
     815             :             return outStr;
     816             :         } else {
     817           0 :             throw InvalidArgument("Unsupported parameter '" + key + "' for NEMA controller '" + getID() + "'");
     818             :         }
     819             :     } else {
     820        6846 :         return Parameterised::getParameter(key, defaultValue);
     821             :     }
     822             : }
     823             : 
     824             : 
     825             : void
     826          65 : NEMALogic::setParameter(const std::string& key, const std::string& value) {
     827          65 :     queuedTraciChanges = true;
     828         130 :     if (StringUtils::startsWith(key, "NEMA.")) {
     829         120 :         if (key == "NEMA.splits" || key == "NEMA.maxGreens") {
     830             :             //splits="2.0 3.0 4.0 5.0 2.0 3.0 4.0 5.0"
     831          25 :             const std::vector<std::string>& tmp = StringTokenizer(value).getVector();
     832          15 :             if (tmp.size() != 8) {
     833           0 :                 queuedTraciChanges = false;
     834           0 :                 throw InvalidArgument("Parameter '" + key + "' for NEMA controller '" + getID() + "' requires 8 space or comma separated values");
     835             :             }
     836             :             std::vector<double> timing;
     837         135 :             for (const std::string& s : tmp) {
     838         120 :                 timing.push_back(StringUtils::toDouble(s));
     839             :             }
     840          15 :             if (key == "NEMA.maxGreens") {
     841          10 :                 setNewMaxGreens(timing);
     842             :             } else {
     843          20 :                 setNewSplits(timing);
     844             :             }
     845          65 :         } else if (key == "NEMA.cycleLength") {
     846           0 :             setNewCycleLength(StringUtils::toDouble(value));
     847          50 :         } else if (key == "NEMA.offset") {
     848          50 :             setNewOffset(StringUtils::toDouble(value));
     849             :         } else {
     850           0 :             queuedTraciChanges = false;
     851           0 :             throw InvalidArgument("Unsupported parameter '" + key + "' for NEMA controller '" + getID() + "'");
     852             :         }
     853             :     }
     854          65 :     Parameterised::setParameter(key, value);
     855          65 : }
     856             : 
     857             : void
     858         384 : NEMALogic::error_handle_not_set(std::string param_variable, std::string param_name) {
     859         384 :     if (param_variable == "") {
     860           0 :         throw InvalidArgument("Please set " + param_name + " for NEMA tlLogic '" + getID() + "'");
     861             :     }
     862         384 : }
     863             : 
     864             : void
     865          99 : NEMALogic::calculateForceOffs170() {
     866          99 :     SUMOTime zeroTime[2] = { TIME2STEPS(0), TIME2STEPS(0) };
     867         297 :     for (int i = 0; i < 2; i++) {
     868             :         SUMOTime runningTime = 0;
     869             :         // loop through the phases for ring 0 and then 1
     870         715 :         for (auto& p : getPhasesByRing(i)) {
     871         517 :             runningTime += p->maxDuration + p->getTransitionTimeStateless();
     872             :             // in 170, the cycle "starts" when the coordinated phase goes to yellow.
     873             :             // See https://ops.fhwa.dot.gov/publications/fhwahop08024/chapter6.html
     874         517 :             if (p->coordinatePhase) {
     875         136 :                 zeroTime[i] = runningTime;
     876             :             }
     877         517 :             p->forceOffTime = runningTime - p->getTransitionTimeStateless();
     878         517 :             p->greatestStartTime = p->forceOffTime - p->minDuration;
     879             :         }
     880             :     }
     881             :     // find the minimum offset time and then subtract from everything, modecycling where negative
     882             :     // This sets the 0 cycle time as start of yellow on earliest ending coordinated phase
     883          99 :     SUMOTime minCoordYellow = MIN2(zeroTime[0], zeroTime[1]);
     884         616 :     for (auto& p : myPhaseObjs) {
     885         517 :         p->forceOffTime = ModeCycle(p->forceOffTime - minCoordYellow, myCycleLength);
     886         517 :         p->greatestStartTime = ModeCycle(p->greatestStartTime - minCoordYellow, myCycleLength);
     887             :     }
     888             : 
     889             : #ifdef DEBUG_NEMA
     890             :     std::ios_base::fmtflags oldflags = std::cout.flags();
     891             :     std::streamsize oldprecision = std::cout.precision();
     892             :     for (int i = 0; i < 2; i++) {
     893             :         std::cout << "Ring" << i + 1 << " force offs: \t";
     894             :         for (auto& p : rings[i]) {
     895             :             if (p > 0) {
     896             :                 PhasePtr pObj = getPhaseObj(p, i);
     897             :                 std::cout << std::fixed << std::setprecision(2) << STEPS2TIME(pObj->forceOffTime) << "\t";
     898             :             } else {
     899             :                 std::cout << std::to_string(0) << "\t";
     900             :             }
     901             :         }
     902             :         std::cout << std::endl;
     903             :     }
     904             :     std::cout.flags(oldflags);
     905             :     std::cout.precision(oldprecision);
     906             : #endif
     907          99 : }
     908             : 
     909             : void
     910          80 : NEMALogic::calculateForceOffsTS2() {
     911             :     // TS2 "0" cycle time is the start of the "first" coordinated phases.
     912             :     // We can find this "0" point by first constructing the forceOffs in sequential order via the 170 method
     913          80 :     calculateForceOffs170();
     914             : 
     915             :     // Switch the Force Off Times to align with TS2 Cycle, which is the *start* of the earliest coordinated phase
     916             :     // The coordinate phases will always be the defaultBarrierPhases[i][0]
     917          80 :     SUMOTime minCoordTime = MIN2(coordinatePhaseObjs[0]->forceOffTime - coordinatePhaseObjs[0]->maxDuration,
     918          80 :                                  coordinatePhaseObjs[1]->forceOffTime - coordinatePhaseObjs[1]->maxDuration);
     919             : 
     920             :     // loop through all the phases and subtract this minCoordTime to move the 0 point to the start of the first coordinated phase
     921         475 :     for (auto& p : myPhaseObjs) {
     922         395 :         if ((p->forceOffTime - minCoordTime) >= 0) {
     923         265 :             p->forceOffTime -= (minCoordTime);
     924             :         } else {
     925         130 :             p->forceOffTime = (myCycleLength + (p->forceOffTime - (minCoordTime)));
     926             :         }
     927         395 :         p->greatestStartTime = ModeCycle(p->greatestStartTime - minCoordTime, myCycleLength);
     928             :     }
     929          80 : }
     930             : 
     931             : void
     932          38 : NEMALogic::calculateInitialPhases170() {
     933             :     // get the time in the cycle
     934          38 :     SUMOTime cycleTime = ModeCycle(getTimeInCycle(), myCycleLength);
     935             :     NEMAPhase* activePhases[2];
     936         114 :     for (int i = 0; i < 2; i++) {
     937          76 :         std::vector<NEMAPhase*> ringCopy = getPhasesByRing(i);
     938             :         // sort by the minimum start time in the cycle.
     939          76 :         std::sort(ringCopy.begin(), ringCopy.end(),
     940             :         [](NEMAPhase * p, NEMAPhase * p1) {
     941         227 :             return p->greatestStartTime <= p1->greatestStartTime;
     942             :         }
     943             :                  );
     944             :         bool found = false;
     945             :         // loop through the sorted phases by time and try to find the phase that should be active given the time in the cycle
     946         251 :         for (auto& p : ringCopy) {
     947             :             // This handles the wrap around. Checks if the prior phases start time should have already happened.
     948             :             // 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 )
     949         193 :             SUMOTime syntheticPriorStart = p->getSequentialPriorPhase()->greatestStartTime < p->greatestStartTime ?
     950          76 :                                            p->getSequentialPriorPhase()->greatestStartTime : p->getSequentialPriorPhase()->greatestStartTime - myCycleLength;
     951         193 :             if (cycleTime <= ModeCycle(p->greatestStartTime, myCycleLength) && cycleTime > ModeCycle(syntheticPriorStart, myCycleLength)) {
     952             :                 found = true;
     953          18 :                 activePhases[i] = p;
     954             :                 break;
     955             :             }
     956             :         }
     957             :         if (!found) {
     958             : #ifdef DEBUG_NEMA
     959             :             const std::string error = "I can't find the correct phase for NEMA tlLogic '" + getID() + "' Ring " + toString(i) + " to start in.";
     960             :             WRITE_WARNING(error);
     961             :             WRITE_WARNING(TL("I am starting in the coordinated phases"));
     962             : #endif
     963          58 :             activePhases[0] = defaultBarrierPhases[0][0];
     964          58 :             activePhases[1] = defaultBarrierPhases[1][0];
     965             :         }
     966             :     }
     967             : 
     968             :     // 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
     969          38 :     if (activePhases[0]->barrierNum != activePhases[1]->barrierNum) {
     970             :         // give preference to whatever is on the coordinate side of the barrier, one must be if they aren't equal to each other
     971           2 :         activePhases[0] = activePhases[0]->barrierNum == 0 ? activePhases[0] : defaultBarrierPhases[0][0];
     972           2 :         activePhases[1] = activePhases[1]->barrierNum == 0 ? activePhases[1] : defaultBarrierPhases[1][0];
     973             :     }
     974             : 
     975             :     // force enter the phases and update their expected duration to last until the forceOff
     976          38 :     activePhases[0]->forceEnter(this);
     977          38 :     activePhases[1]->forceEnter(this);
     978          38 : }
     979             : 
     980             : void
     981          27 : NEMALogic::calculateInitialPhasesTS2() {
     982             :     // Modifications where made to 170 algorithm so that it works with both.
     983          27 :     calculateInitialPhases170();
     984          27 : }
     985             : 
     986             : NEMALogic::controllerType
     987          97 : NEMALogic::parseControllerType(std::string inputType) {
     988             :     std::string cleanString;
     989         464 :     for (const char& c : inputType) {
     990         367 :         if (isalpha(c) || isdigit(c)) {
     991         367 :             cleanString += (char)::tolower(c);
     992             :         }
     993             :     }
     994          97 :     if (cleanString == "type170") {
     995             :         return Type170;
     996          78 :     } else if (cleanString == "ts2") {
     997             :         return TS2;
     998             :     } else {
     999           0 :         throw InvalidArgument("Please set controllerType for NEMA tlLogic " + myID + " to either Type170 or TS2");
    1000             :     }
    1001             : }
    1002             : 
    1003             : std::vector<NEMAPhase*>
    1004        1656 : NEMALogic::getPhasesByRing(int ringNum) {
    1005             :     std::vector<NEMAPhase*> phases;
    1006       10838 :     for (auto& p : myPhaseObjs) {
    1007        9182 :         if (p->ringNum == ringNum) {
    1008        4626 :             phases.push_back(p);
    1009             :         }
    1010             :     }
    1011        1656 :     return phases;
    1012             : }
    1013             : 
    1014             : void
    1015        4151 : NEMALogic::setActivePhase(PhasePtr phase) {
    1016        4151 :     myActivePhaseObjs[phase->ringNum] = phase;
    1017        4151 : }
    1018             : 
    1019             : std::map<std::string, double>
    1020        3763 : NEMALogic::getDetectorStates() const {
    1021             :     std::map<std::string, double> result;
    1022       37630 :     for (auto item : myDetectorLaneMap) {
    1023       33867 :         result[item.first->getID()] = item.first->getCurrentVehicleNumber();
    1024             :     }
    1025        3763 :     return result;
    1026             : }
    1027             : 
    1028             : NEMALogic::PhasePtr
    1029      269333 : NEMALogic::getOtherPhase(PhasePtr p) {
    1030             :     // return a pointer to the other active phase
    1031      269333 :     return myActivePhaseObjs[!p->ringNum];
    1032             : }
    1033             : 
    1034             : NEMAPhase*
    1035         638 : NEMALogic::getPhaseObj(int phaseNum, int ringNum) {
    1036             :     // This satisfies the case where there is a "duplicate" phase on both ring
    1037         638 :     std::vector<PhasePtr> iterRing = ringNum >= 0 ? getPhasesByRing(ringNum) : myPhaseObjs;
    1038        1247 :     for (auto& p : iterRing) {
    1039        1247 :         if (p->phaseName == phaseNum) {
    1040             :             return p;
    1041             :         }
    1042             :     }
    1043             :     // the phase must always be found
    1044             :     assert(0);
    1045             :     // To satisfy the compiler
    1046           0 :     return myPhaseObjs.front();
    1047             : }
    1048             : 
    1049             : PhaseTransitionLogic*
    1050         198 : NEMALogic::getDefaultTransition(PhaseTransitionLogic* t, PhaseTransitionLogic* ot) {
    1051             :     NEMAPhase* p = t->getFromPhase();
    1052             :     // if the current phase is not ready to switch or a barrier cross is desired by the other transition
    1053             :     // and t fromPhase is not ready to switch, the default transition is back to myself
    1054         198 :     if (!p->readyToSwitch ||
    1055         198 :             (p->barrierNum == ot->getToPhase()->barrierNum && p->getCurrentState() >= LightState::Green)) {
    1056          99 :         return p->getTransition(p->phaseName);
    1057             :     }
    1058             :     // otherwise the default transition is to the default phase on whatever barrier ot wants to transition to
    1059             :     else {
    1060          99 :         return p->getTransition(defaultBarrierPhases[p->ringNum][ot->getToPhase()->barrierNum]->phaseName);
    1061             :     }
    1062             : }
    1063             : 
    1064             : void
    1065       28033 : NEMALogic::getNextPhases(TransitionPairs& transitions) {
    1066             :     std::vector<std::vector<PhaseTransitionLogic* >> potentialPhases;
    1067             : 
    1068             :     // Get a vector of each phases' potential transitions
    1069       84099 :     for (const auto& p : myActivePhaseObjs) {
    1070      112132 :         potentialPhases.push_back(p->trySwitch(this));
    1071             :     }
    1072             : 
    1073             :     // Loop through all combination of transitions, keeping only the valid ones and filling in the gaps where necessary
    1074       56066 :     for (const auto& r1_t : potentialPhases[0]) {
    1075       56066 :         for (const auto& r2_t : potentialPhases[1]) {
    1076             :             // if both transitions go to the same barrier then we are good
    1077       28033 :             if (r1_t->getToPhase()->barrierNum == r2_t->getToPhase()->barrierNum) {
    1078       27934 :                 transitions.push_back({ r1_t, r2_t, (float)(r1_t->getDistance(r2_t) + r2_t->getDistance(r1_t)) / 2 });
    1079             :             } else {
    1080             :                 // If the rings are different, add a choice where one of them is the default choice for whatever ring it is
    1081             :                 // create two choices, one for each of the phase as they are on  different rings
    1082          99 :                 if (r1_t->getFromPhase()->readyToSwitch) {
    1083             :                     // get the r2 default
    1084          99 :                     PhaseTransitionLogic* r2_t_temp = getDefaultTransition(r2_t, r1_t);
    1085             :                     // only add it if it does not cross a barrier!
    1086          99 :                     if (r2_t_temp->getToPhase()->barrierNum == r1_t->getToPhase()->barrierNum) {
    1087          99 :                         transitions.push_back({ r1_t, r2_t_temp, (float)(r2_t_temp->getDistance(r1_t) + r1_t->getDistance(r2_t_temp)) / 2 });
    1088             :                     }
    1089             :                 }
    1090          99 :                 if (r2_t->getFromPhase()->readyToSwitch) {
    1091             :                     // R1 default
    1092          99 :                     PhaseTransitionLogic* r1_t_temp = getDefaultTransition(r1_t, r2_t);
    1093             :                     // only add it if it does not cross a barrier!
    1094          99 :                     if (r1_t_temp->getToPhase()->barrierNum == r2_t->getToPhase()->barrierNum) {
    1095          99 :                         transitions.push_back({ r1_t_temp, r2_t, (float)(r2_t->getDistance(r1_t_temp) + r1_t_temp->getDistance(r2_t)) / 2 });
    1096             :                     }
    1097             :                 }
    1098             :                 // If the distances are <= 1, this means that this is the shortest transition possible
    1099             :                 // and we can return early without considering any other options
    1100          99 :                 if (!transitions.empty()) {
    1101          99 :                     if (transitions.back().distance < 1) {
    1102             :                         return;
    1103             :                     }
    1104             :                 }
    1105             :             }
    1106             :         }
    1107             :     }
    1108       28033 : }
    1109             : 
    1110             : 
    1111             : std::string
    1112       28102 : NEMALogic::composeLightString() {
    1113             :     // FIX with plan to support #10742
    1114       28102 :     std::string out(myPhaseStrLen, 'r');
    1115      383550 :     for (int i = 0; i < myPhaseStrLen; i++) {
    1116             :         bool controlled = false;
    1117      355448 :         std::string phaseChars = "";
    1118     1066344 :         for (auto& p : myActivePhaseObjs) {
    1119      710896 :             phaseChars += p->getNEMAChar(i);
    1120     1421792 :             if (p->controlledIndex(i)) {
    1121      134401 :                 out[i] = p->getNEMAChar(i);
    1122             :                 controlled = true;
    1123             :             }
    1124             :         }
    1125             :         // if the index wasn't a controlled one, the prior priority order still stands
    1126      355448 :         if (!controlled) {
    1127     1405446 :             for (auto priority_char : lightHeadPriority) {
    1128     1246624 :                 if (std::count(phaseChars.begin(), phaseChars.end(), priority_char)) {
    1129       72890 :                     out[i] = priority_char;
    1130       72890 :                     break;
    1131             :                 }
    1132             :             }
    1133             :         }
    1134             :     }
    1135       28102 :     return out;
    1136             : }
    1137             : 
    1138             : 
    1139             : SUMOTime
    1140       77458 : NEMALogic::trySwitch() {
    1141             : #ifdef DEBUG_NEMA_SWITCH
    1142             :     std::cout << SIMTIME << " trySwitch tls=" << getID() << "\n";
    1143             : #endif
    1144       77458 :     PhaseTransitionLogic* nextPhases[2] = { nullptr, nullptr };
    1145             : 
    1146             :     // update the internal time. This is a must. Could have just used a reference to the time
    1147             :     setCurrentTime();
    1148             : 
    1149             :     // Check the Detectors
    1150      536450 :     for (auto& p : myPhaseObjs) {
    1151      458992 :         p->checkMyDetectors();
    1152             :     }
    1153             : 
    1154             :     // Update the timing parameters
    1155      232374 :     for (const auto& p : myActivePhaseObjs) {
    1156      154916 :         p->update(this);
    1157             :     }
    1158             : 
    1159             :     // Calculate the Next Phases, but only if atleast one of them is ready to transition
    1160       77458 :     if (myActivePhaseObjs[0]->readyToSwitch || myActivePhaseObjs[1]->readyToSwitch) {
    1161             :         TransitionPairs transitions;
    1162             :         // set the next phases by reference
    1163       28033 :         getNextPhases(transitions);
    1164             : 
    1165             :         // Sort the next phases by distance and select the closest.
    1166             :         // TODO: Is there a way to avoid this sort? The transitions are already sorted by distance prior
    1167             :         // to picking the valid ones
    1168       28033 :         if (transitions.size() > 1) {
    1169          99 :             std::sort(transitions.begin(), transitions.end(),
    1170             :             [](const transitionInfo & i, const transitionInfo & j) {
    1171         183 :                 return i.distance < j.distance;
    1172             :             });
    1173             :         }
    1174             : 
    1175             :         // Set the Next Phases = to the transition with least combined distance
    1176       28033 :         nextPhases[0] = transitions.front().p1;
    1177       28033 :         nextPhases[1] = transitions.front().p2;
    1178             : 
    1179             :         // Try the exit logic. This doesn't necessarily mean that the phase will exit,
    1180             :         // as it could go into green rest or green transfer, but this is considered an "exit"
    1181       84099 :         for (const auto& p : myActivePhaseObjs) {
    1182       56066 :             if (p->readyToSwitch) {
    1183       52664 :                 p->exit(this, nextPhases);
    1184             :             }
    1185             :         }
    1186             : 
    1187             :         // This is the only time when something might have happened, so we update the phase strings here
    1188       28033 :         std::string newState = composeLightString();
    1189       28033 :         if (newState != myPhase.getState()) {
    1190             :             myPhase.setState(newState);
    1191       12828 :             myPhase.setName(toString(myActivePhaseObjs[0]->phaseName) + "+" + toString(myActivePhaseObjs[1]->phaseName));
    1192             :             // ensure that SwitchCommand::execute notices a change
    1193        6414 :             myStep = 1 - myStep;
    1194             : 
    1195             :         }
    1196             :     }
    1197             : 
    1198             :     // clear the phases' detectors
    1199      536450 :     for (auto& p : myPhaseObjs) {
    1200      458992 :         p->clearMyDetectors();
    1201             :     }
    1202             : 
    1203             : 
    1204             : #ifdef FUZZ_TESTING
    1205             :     // Basic Assertion to ensure that the Barrier is not crossed
    1206             :     assert(myActivePhaseObjs[0]->barrierNum == myActivePhaseObjs[1]->barrierNum);
    1207             : #endif
    1208             : 
    1209             :     // return the simulation timestep, as this controller must be checked every simulation step
    1210       77458 :     return DELTA_T;
    1211             : }
    1212             : 
    1213             : 
    1214             : void
    1215        1990 : NEMALogic::implementTraciChanges(void) {
    1216             :     // Implement Traci Updates on the start of ring1 coordinated phase (rising edge of it turning green)
    1217        1990 :     if (queuedTraciChanges) {
    1218         180 :         for (auto& p : myPhaseObjs) {
    1219         150 :             p->maxDuration = p->nextMaxDuration;
    1220             :         }
    1221          30 :         offset = myNextOffset;
    1222          30 :         myCycleLength = myNextCycleLength;
    1223             :         // now that we have set the cycle length, offset and max duration, we need to update force off times
    1224          30 :         calculateForceOffs();
    1225          30 :         queuedTraciChanges = false;
    1226             :     }
    1227        1990 : }
    1228             : 
    1229             : 
    1230             : // ===========================================================================
    1231             : // NEMAPhase Definitions
    1232             : // ===========================================================================
    1233         390 : NEMAPhase::NEMAPhase(int phaseName, bool isBarrier, bool isGreenRest, bool isCoordinated,
    1234             :                      bool minRecall, bool maxRecall, bool fixForceOff, int barrierNum, int ringNum,
    1235             :                      IntVector phaseStringInds,
    1236         390 :                      MSPhaseDefinition* phase) :
    1237         390 :     phaseName(phaseName),
    1238         390 :     isAtBarrier(isBarrier),
    1239         390 :     isGreenRest(isGreenRest),
    1240         390 :     barrierNum(barrierNum),
    1241         390 :     coordinatePhase(isCoordinated),
    1242         390 :     minRecall(minRecall),
    1243         390 :     maxRecall(maxRecall),
    1244         390 :     fixForceOff(fixForceOff),
    1245         390 :     ringNum(ringNum),
    1246         390 :     myCorePhase(phase),
    1247         390 :     myPhaseStringInds(phaseStringInds) {
    1248             :     // Public
    1249         390 :     readyToSwitch = false;
    1250         390 :     greenRestTimer = 0;
    1251         390 :     forceOffTime = 0;
    1252         390 :     lastDetectActive = false;
    1253             : 
    1254             :     // Private
    1255         390 :     myInstance = this;
    1256         390 :     myLastPhaseInstance = nullptr;
    1257         390 :     sequentialPriorPhase = nullptr;
    1258         390 :     myLightState = LightState::Red;
    1259         390 :     transitionActive = false;
    1260             : 
    1261             :     // Timing Parameters
    1262         390 :     maxGreenDynamic = myCorePhase->maxDuration;
    1263         390 :     myStartTime = TIME2STEPS(0.);
    1264         390 :     myExpectedDuration = myCorePhase->minDuration;
    1265         390 :     myLastEnd = TIME2STEPS(0.);
    1266             : 
    1267             :     // set the phase colors
    1268         390 :     setMyNEMAStates();
    1269         390 : }
    1270             : 
    1271         354 : NEMAPhase::~NEMAPhase() {
    1272             :     // Delete the transitions from their alloc
    1273        1372 :     for (auto t : myTransitions) {
    1274        1018 :         delete t;
    1275             :     }
    1276         354 : }
    1277             : 
    1278             : void
    1279         367 : NEMAPhase::init(NEMALogic* controller, int crossPhaseTarget, int crossPhaseSource, bool latching) {
    1280             :     // switch the durations from steps2time
    1281         367 :     recalculateTiming();
    1282             : 
    1283        1430 :     for (auto p : controller->getPhasesByRing(ringNum)) {
    1284             :         // construct transitions for all potential movements, including back to myself
    1285        1063 :         myTransitions.push_back(new PhaseTransitionLogic(this, p));
    1286        1063 :         myTransitions.back()->setDistance(controller->measureRingDistance(phaseName, p->phaseName, ringNum));
    1287             :     }
    1288             : 
    1289             :     // sort the transitions by distance for speed later. Using plain distance here
    1290         367 :     std::sort(myTransitions.begin(), myTransitions.end(), [&](const PhaseTransitionLogic * i, const PhaseTransitionLogic * j) {
    1291        1310 :         return i->distance < j->distance;
    1292             :     });
    1293             : 
    1294             :     // create the phase detector info
    1295         371 :     myDetectorInfo = PhaseDetectorInfo(latching,
    1296           4 :                                        crossPhaseSource > 0 ? controller->getPhaseObj(crossPhaseSource) : nullptr,
    1297           4 :                                        crossPhaseTarget > 0 ? controller->getPhaseObj(crossPhaseTarget) : nullptr
    1298             :                                       );
    1299         367 : }
    1300             : 
    1301             : void
    1302         367 : NEMAPhase::recalculateTiming(void) {
    1303             :     // This could be extended in the future to allow for traci manipulation
    1304         367 :     yellow = myCorePhase->yellow;
    1305         367 :     red = myCorePhase->red;
    1306         367 :     minDuration = myCorePhase->minDuration;
    1307         367 :     maxDuration = myCorePhase->maxDuration;
    1308         367 :     nextMaxDuration = myCorePhase->maxDuration;
    1309         367 :     maxGreenDynamic = myCorePhase->maxDuration;
    1310         367 :     vehExt = myCorePhase->vehext;
    1311         367 : }
    1312             : 
    1313             : 
    1314             : // TODO: this can be computed once.
    1315             : char
    1316      845297 : NEMAPhase::getNEMAChar(int i) {
    1317      845297 :     if (myLightState >= LightState::Green) {
    1318      503259 :         return myGreenString[i];
    1319      342038 :     } else if (myLightState <= LightState::Red) {
    1320      116945 :         return myRedString[i];
    1321             :     } else {
    1322      225093 :         return myYellowString[i];
    1323             :     }
    1324             : }
    1325             : 
    1326             : void
    1327         390 : NEMAPhase::setMyNEMAStates() {
    1328         390 :     myGreenString = myCorePhase->getState();
    1329         390 :     myRedString = "";
    1330         390 :     myYellowString = "";
    1331        6105 :     for (char ch : myGreenString) {
    1332             :         myRedString += 'r';
    1333        5715 :         if (ch == 'G' || ch == 'g') {
    1334             :             myYellowString += 'y';
    1335             :         } else {
    1336        4065 :             myYellowString += ch;
    1337             :         }
    1338             :     }
    1339         390 : }
    1340             : 
    1341             : void
    1342      458992 : NEMAPhase::clearMyDetectors() {
    1343      458992 :     lastDetectActive = myDetectorInfo.detectActive;
    1344             :     // remove the active flag on the detector if the detector is not latching or if it is green
    1345      458992 :     if ((!myDetectorInfo.latching) || (myLightState >= LightState::Green)) {
    1346      458561 :         myDetectorInfo.detectActive = false;
    1347             :     }
    1348      458992 : }
    1349             : 
    1350             : void
    1351      458992 : NEMAPhase::checkMyDetectors() {
    1352             :     // Check my Detectors, only necessary if it isn't currently marked as on
    1353      458992 :     if (!myDetectorInfo.detectActive) {
    1354             :         // If I have a cross phase target and it is active and I am not, save my detector as not active
    1355      458755 :         if (myDetectorInfo.cpdTarget != nullptr) {
    1356        1272 :             if (myDetectorInfo.cpdTarget->getCurrentState() >= LightState::Green) {
    1357        1102 :                 if (myLightState < LightState::Green) {
    1358         542 :                     myDetectorInfo.detectActive = false;
    1359         542 :                     return;
    1360             :                 }
    1361             :             }
    1362             :         }
    1363             :         // If we make it to this point, check my detector like normal.
    1364     1028323 :         for (auto& d : myDetectorInfo.detectors) {
    1365      750727 :             if (d->getCurrentVehicleNumber() > 0) {
    1366      180617 :                 myDetectorInfo.detectActive = true;
    1367             :                 return;
    1368             :             }
    1369             :         }
    1370             :         // If my detector is not active, check my cross phase
    1371      277596 :         if ((myDetectorInfo.cpdSource != nullptr) && (myLightState >= LightState::Green)) {
    1372         497 :             if (myDetectorInfo.cpdSource->getCurrentState() < LightState::Green) {
    1373         864 :                 for (auto& d : myDetectorInfo.cpdSource->getDetectors()) {
    1374         482 :                     if (d->getCurrentVehicleNumber() > 0) {
    1375         100 :                         myDetectorInfo.detectActive = true;
    1376             :                         return;
    1377             :                     }
    1378             :                 }
    1379             :             }
    1380             :         }
    1381             :     }
    1382             : }
    1383             : 
    1384             : void
    1385        4151 : NEMAPhase::enter(NEMALogic* controller, NEMAPhase* lastPhase) {
    1386             : #ifdef DEBUG_NEMA_SWITCH
    1387             :     std::cout << SIMTIME << " enter tls=" << controller->getID() << " phase=" << phaseName << "\n";
    1388             : #endif
    1389             : 
    1390             :     // set the last phase instance to inactive
    1391             :     lastPhase->cleanupExit();
    1392             : 
    1393             :     // Enter the phase
    1394        4151 :     myStartTime = controller->getCurrentTime();
    1395        4151 :     myLightState = LightState::Green;
    1396        4151 :     myLastPhaseInstance = lastPhase;
    1397        4151 :     readyToSwitch = false;
    1398             : 
    1399             :     // implement the new timing parameters on the first coordinated phase to appear
    1400        4151 :     if (phaseName == controller->coordinatePhaseObjs[ringNum]->phaseName) {
    1401        1990 :         controller->implementTraciChanges();
    1402             :     }
    1403             : 
    1404             :     // Handle Green Rest Peculiarities
    1405        4151 :     if (!controller->coordinateMode && isGreenRest) {
    1406             :         // If the controller is in free mode and the phase is a green rest phase, then it should enter as "green rest"
    1407          50 :         myLightState = LightState::GreenRest;
    1408             :         // if the phase has "green rest" capabilities, set its timer to the dynamic maxGreen
    1409          50 :         greenRestTimer = maxDuration * isGreenRest;
    1410             :     }
    1411             : 
    1412             :     // clear the last transition decision
    1413        4151 :     lastTransitionDecision = nullptr;
    1414             : 
    1415             :     // Calculate the Max Green Time & Expected Duration here:
    1416        4151 :     if (controller->coordinateMode) {
    1417        2281 :         if (coordinatePhase) {
    1418        1072 :             myExpectedDuration = controller->ModeCycle(forceOffTime - controller->getTimeInCycle(), controller->getCurrentCycleLength());
    1419             :         } else {
    1420        1209 :             maxGreenDynamic = controller->ModeCycle(forceOffTime - controller->getTimeInCycle(), controller->getCurrentCycleLength());
    1421        1209 :             if (!fixForceOff) {
    1422         855 :                 maxGreenDynamic = MIN2(maxDuration, maxGreenDynamic);
    1423             :             }
    1424        1209 :             myExpectedDuration = minDuration;
    1425             :         }
    1426             :     } else {
    1427        1870 :         myExpectedDuration = minDuration;
    1428             :     }
    1429             :     // Implements the maxRecall functionality
    1430        4151 :     if (maxRecall && !coordinatePhase) {
    1431         103 :         myExpectedDuration = maxGreenDynamic;
    1432             :     }
    1433             :     // Set the controller's active phase
    1434        4151 :     controller->setActivePhase(this);
    1435        4151 : }
    1436             : 
    1437       52664 : void NEMAPhase::exit(NEMALogic* controller, PhaseTransitionLogic* nextPhases[2]) {
    1438       52664 :     if (nextPhases[ringNum]->getToPhase() != this) {
    1439             :         // if the next phase is not me, then I need to go into a transition
    1440       24566 :         lastTransitionDecision = nextPhases[ringNum];
    1441       24566 :         if (myLightState >= LightState::Green) {
    1442             :             // if I am in green, then I need to enter yellow
    1443        3811 :             enterYellow(controller);
    1444        3811 :             return;
    1445             :         }
    1446             : 
    1447       20755 :         if (controller->getCurrentTime() - myLastEnd < (yellow + red)) {
    1448       16500 :             if (controller->getCurrentTime() - myLastEnd >= yellow) {
    1449             :                 // if I am in yellow, then I need to enter red
    1450        6293 :                 myLightState = LightState::Red;
    1451             :             }
    1452             :             // I am currently in the Red state but haven't reached max
    1453       16500 :             return;
    1454             :         }
    1455             : 
    1456        4255 :         handleRedXferOrNextPhase(controller, nextPhases);
    1457        4255 :         return;
    1458             :     }
    1459             : 
    1460       28098 :     handleGreenRestOrTransfer(controller, nextPhases);
    1461             : }
    1462             : 
    1463        3811 : void NEMAPhase::enterYellow(NEMALogic* controller) {
    1464        3811 :     myLastEnd = controller->getCurrentTime();
    1465        3811 :     myLightState = LightState::Yellow;
    1466        3811 :     transitionActive = true;
    1467        3811 : }
    1468             : 
    1469        4255 : void NEMAPhase::handleRedXferOrNextPhase(NEMALogic* controller, PhaseTransitionLogic* nextPhases[2]) {
    1470        4255 :     PhasePtr otherPhase = controller->getOtherPhase(this);
    1471        4255 :     bool barrierCross = nextPhases[ringNum]->getToPhase()->barrierNum != barrierNum;
    1472        3766 :     bool barrierCrossButOkay = barrierCross && (
    1473        3766 :                                    nextPhases[ringNum]->getToPhase()->barrierNum == nextPhases[otherPhase->ringNum]->getToPhase()->barrierNum
    1474        4255 :                                ) && otherPhase->okay2ForceSwitch(controller);
    1475             : 
    1476        4255 :     if (!barrierCross) {
    1477         489 :         nextPhases[ringNum]->getToPhase()->enter(controller, this);
    1478         489 :         return;
    1479             :     }
    1480             : 
    1481        3766 :     if (barrierCrossButOkay) {
    1482             :         // if the barrier is crossed, but the other phase is going to the same barrier, then I can enter red transfer
    1483             :         // enter the next phase
    1484        1630 :         nextPhases[ringNum]->getToPhase()->enter(controller, this);
    1485             :         // trigger the other phase to enter red transfer
    1486        1630 :         nextPhases[otherPhase->ringNum]->getToPhase()->enter(controller, this);
    1487        1630 :         return;
    1488             :     }
    1489             : 
    1490        2136 :     myLightState = LightState::RedXfer;
    1491        2136 :     readyToSwitch = true;
    1492        2136 :     transitionActive = false;
    1493             : }
    1494             : 
    1495       28098 : void NEMAPhase::handleGreenRestOrTransfer(NEMALogic* controller, PhaseTransitionLogic* nextPhases[2]) {
    1496       28098 :     NEMAPhase* otherPhase = controller->getOtherPhase(this);
    1497       28098 :     readyToSwitch = false;
    1498       42401 :     bool isOtherPhaseReady = nextPhases[!ringNum]->getToPhase() == otherPhase && otherPhase->readyToSwitch;
    1499       28098 :     bool isOtherPhaseInGreenRest = otherPhase->greenRestTimer >= otherPhase->maxDuration && otherPhase->getCurrentState() == LightState::GreenRest;
    1500             : 
    1501       28098 :     if (isOtherPhaseReady || isOtherPhaseInGreenRest) {
    1502       26956 :         myLightState = LightState::GreenRest;
    1503       26956 :         myStartTime = controller->getCurrentTime() - minDuration;
    1504       26956 :         myExpectedDuration = minDuration;
    1505       26956 :         greenRestTimer = maxDuration * isGreenRest;
    1506             :     } else {
    1507        1142 :         myLightState = LightState::GreenXfer;
    1508        1142 :         if (isAtBarrier) {
    1509        1087 :             myExpectedDuration = (otherPhase->myExpectedDuration + otherPhase->myStartTime) - myStartTime;
    1510             :         }
    1511             :     }
    1512       28098 : }
    1513             : 
    1514             : SUMOTime
    1515       13005 : NEMAPhase::getTransitionTime(NEMALogic* controller) {
    1516       13005 :     if (myLightState == LightState::RedXfer) {
    1517             :         // if in red xfer, I am ready to switch whenevery
    1518             :         return TIME2STEPS(0);
    1519             :     }
    1520       11325 :     if (!transitionActive) {
    1521             :         // if a transition is not active, the transition is just yellow + red time
    1522        7302 :         return getTransitionTimeStateless();
    1523             :     }
    1524             :     // if a transition is active, then return the time left in the transition
    1525        4023 :     return MAX2(TIME2STEPS(0), ((yellow + red) - (controller->getCurrentTime() - myLastEnd)));
    1526             : }
    1527             : 
    1528             : SUMOTime
    1529       34165 : NEMAPhase::calcVehicleExtension(SUMOTime duration) {
    1530       34165 :     if (myExpectedDuration < maxGreenDynamic && myDetectorInfo.detectActive) {
    1531             :         // add the vehicle extension timer if the detector is active.
    1532             :         // capped by the minimum and maximum duration
    1533       14942 :         return MIN2(MAX2(duration + vehExt, minDuration), maxGreenDynamic);
    1534             :     }
    1535             :     return myExpectedDuration;
    1536             : }
    1537             : 
    1538             : void
    1539      154916 : NEMAPhase::update(NEMALogic* controller) {
    1540             :     // If I am in a transition, the rest of the update logic does not matter
    1541      154916 :     if (myLightState < LightState::Green) {
    1542             :         // return early
    1543       20837 :         readyToSwitch = true;
    1544       20837 :         return;
    1545             :     }
    1546             : 
    1547             :     // Continuation Logic
    1548      134079 :     SUMOTime duration = controller->getCurrentTime() - myStartTime;
    1549             :     // Check the vehicle extension timer as long as not in green transfer and not a coordinated phase
    1550      134079 :     if (myLightState != LightState::GreenXfer && !coordinatePhase) {
    1551       34165 :         myExpectedDuration = calcVehicleExtension(duration);
    1552             :     }
    1553             :     // Special Logic for Green Rest, which behaves uniquely
    1554      134079 :     if (myLightState == LightState::GreenRest) {
    1555             :         // check all other detectors and see if anything else is active. If so,
    1556             :         // start the green rest timer countdown, which is == to the max duration of the phase
    1557             :         bool vehicleActive = false;
    1558      237133 :         for (auto& p : controller->getPhaseObjs()) {
    1559      212115 :             if ((p->phaseName != phaseName)
    1560      185358 :                     && (p->phaseName != controller->getOtherPhase(this)->phaseName)
    1561      370639 :                     && p->callActive()) {
    1562        3936 :                 greenRestTimer -= DELTA_T;
    1563             :                 vehicleActive = true;
    1564        3936 :                 break;
    1565             :             }
    1566             :         }
    1567             :         // catch the rising edge of the sidestreet detection and calculate the maximum timer
    1568       28954 :         if (vehicleActive && (greenRestTimer + DELTA_T >= maxDuration)) {
    1569        2966 :             maxGreenDynamic = minDuration + maxDuration;
    1570             :         }
    1571             : 
    1572             :         // if there are no other vehicles slide the startTime along
    1573       28954 :         if (!vehicleActive) {
    1574       25018 :             greenRestTimer = maxDuration;
    1575       25018 :             if (duration >= minDuration) {
    1576       24621 :                 myStartTime = controller->getCurrentTime() - minDuration;
    1577       24621 :                 maxGreenDynamic = minDuration + maxDuration;
    1578       24621 :                 myExpectedDuration = minDuration + MAX2(TIME2STEPS(0), myExpectedDuration - duration);
    1579             :             }
    1580             :         }
    1581             : 
    1582             :         // if the green rest timer is exhausted, set ready to switch
    1583       28954 :         if (greenRestTimer < DELTA_T) {
    1584           3 :             readyToSwitch = true;
    1585             :             // force the counterpart to be ready to switch too. This needs to be latching....
    1586           3 :             NEMAPhase* otherPhase = controller->getOtherPhase(this);
    1587           3 :             if (otherPhase->getCurrentState() > LightState::Green) {
    1588           3 :                 otherPhase->readyToSwitch = true;
    1589             :             }
    1590             :         }
    1591             : 
    1592             :         // Special Behavior when the Green Rest Circles all the way around in coordinated mode
    1593       28954 :         if (coordinatePhase) {
    1594             :             // This means that we have green rested until I should "start" again. Just call my entry function again.
    1595       26022 :             if (controller->getTimeInCycle() <= ((forceOffTime - maxDuration) + DELTA_T / 2)) {
    1596         264 :                 enter(controller, this);
    1597             :             }
    1598             :         }
    1599             :     }
    1600             :     // Check to see if a switch is desired
    1601      134079 :     if (duration >= myExpectedDuration) {
    1602       31907 :         readyToSwitch = true;
    1603             :     }
    1604             : }
    1605             : 
    1606             : PhaseTransitionLogic*
    1607         198 : NEMAPhase::getTransition(int toPhase) {
    1608         486 :     for (auto t : myTransitions) {
    1609         486 :         if (t->getToPhase()->phaseName == toPhase) {
    1610             :             return t;
    1611             :         }
    1612             :     }
    1613             :     // This point should never be reached
    1614             :     assert(0);
    1615             :     // To satisfy the compiler and return value from all control paths
    1616           0 :     return myTransitions.front();
    1617             : }
    1618             : 
    1619             : std::vector<PhaseTransitionLogic*>
    1620       56066 : NEMAPhase::trySwitch(NEMALogic* controller) {
    1621             :     // this function returns the preferred valid transition for the phase
    1622             :     std::vector<PhaseTransitionLogic*> nextTransitions;
    1623       56066 :     if (readyToSwitch) {
    1624             :         // only try to switch if I am ready to switch
    1625      137666 :         for (auto& t : myTransitions) {
    1626             :             // for the transitions check if it is okay
    1627      137577 :             if (t->okay(controller)) {
    1628             :                 // if there was already a transition decision, it can be overriden but only if the new transition
    1629             :                 // is on the same side of a barrier
    1630       52694 :                 if (lastTransitionDecision != nullptr) {
    1631       20785 :                     if (t->getToPhase()->barrierNum == lastTransitionDecision->getToPhase()->barrierNum) {
    1632       20748 :                         nextTransitions.push_back(t);
    1633             :                         break;
    1634             :                     }
    1635             :                 } else {
    1636       31909 :                     nextTransitions.push_back(t);
    1637             :                     // break once there is a valid option (they have already been sorted)
    1638             :                     break;
    1639             :                 }
    1640             :             }
    1641             :         }
    1642             :     }
    1643             :     // Add in the last transition decision if it hasn't been added in yet
    1644       56066 :     if (lastTransitionDecision != nullptr) {
    1645             :         bool found = false;
    1646             :         bool sameBarrier = false;
    1647       20837 :         for (auto& t : nextTransitions) {
    1648       20748 :             if (t == lastTransitionDecision) {
    1649             :                 found = true;
    1650             :                 break;
    1651             :             }
    1652           2 :             if (t->getToPhase()->barrierNum == lastTransitionDecision->getToPhase()->barrierNum) {
    1653             :                 sameBarrier = true;
    1654             :                 break;
    1655             :             }
    1656             :         }
    1657             :         // 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.
    1658       20837 :         if (!found && !sameBarrier) {
    1659          89 :             nextTransitions.push_back(lastTransitionDecision);
    1660             :         }
    1661             :     }
    1662             :     // Add the transition back to myself, but only in the case when no others have been added
    1663       56066 :     if (nextTransitions.size() < 1) {
    1664        3320 :         nextTransitions.push_back(myTransitions.back());
    1665             :     }
    1666             : 
    1667       56066 :     return nextTransitions;
    1668             : }
    1669             : 
    1670             : // ===========================================================================
    1671             : // PhaseTransitionLogic Definitions
    1672             : // ===========================================================================
    1673        1063 : PhaseTransitionLogic::PhaseTransitionLogic(
    1674        1063 :     NEMAPhase* fromPhase, NEMAPhase* toPhase) :
    1675        1063 :     distance(0),
    1676        1063 :     fromPhase(fromPhase),
    1677        1063 :     toPhase(toPhase)
    1678        1063 : {}
    1679             : 
    1680             : bool
    1681      137577 : PhaseTransitionLogic::okay(NEMALogic* controller) {
    1682             :     // Picking the correct transition logic to use
    1683             :     // #TODO this could be a case of using function as variable and setting it at PhaseTransitionLogic
    1684             :     // creation time
    1685      137577 :     if (fromPhase == toPhase) {
    1686             :         // for green rest or green transfer, it cannot return to itself if a transition is active
    1687       28141 :         return fromPhase->getCurrentState() >= LightState::Green;
    1688      109436 :     } else if (fromPhase->coordinatePhase) {
    1689             :         // if the from phase is a coordinated phase i.e. {2, 6} in a standard setup
    1690       84477 :         return fromCoord(controller);
    1691       24959 :     } else if (fromPhase->isAtBarrier) {
    1692             :         // if the phase is at a barrier i.e. {2, 6, 4, 8} in a standard setup
    1693       20390 :         return fromBarrier(controller);
    1694        4569 :     } else if (controller->coordinateMode) {
    1695             :         // typical coordinate mode transition,
    1696        4020 :         return coordBase(controller);
    1697             :     } else {
    1698             :         // base transition logic
    1699         549 :         return freeBase(controller);
    1700             :     }
    1701             : }
    1702             : 
    1703             : bool
    1704      106598 : PhaseTransitionLogic::freeBase(NEMALogic* controller) {
    1705             :     // Simplest transition logic. Just check if a detector (or recall) is active on that phase and
    1706             :     bool okay = false;
    1707             :     // is a call active on the toPhase?
    1708      106598 :     if (toPhase->callActive()) {
    1709             :         // would the transition be a barrier cross?
    1710       25612 :         if (fromPhase->barrierNum != toPhase->barrierNum) {
    1711       24474 :             PhasePtr otherPhase = controller->getOtherPhase(fromPhase);
    1712             :             // If it is a barrier cross, then the other phase must also be ready to switch
    1713             :             // or have a transition time that is lower than mine currently. DELTA_T is critical here
    1714       24474 :             if (otherPhase->readyToSwitch) {
    1715             :                 // #&& otherPhase->getTransitionTime(controller) <= fromPhase->getTransitionTime(controller)) {
    1716             :                 okay = true;
    1717             :             }
    1718             :         } else {
    1719             :             okay = true;
    1720             :         }
    1721             :     }
    1722      106598 :     return okay;
    1723             : }
    1724             : 
    1725             : bool
    1726       88497 : PhaseTransitionLogic::coordBase(NEMALogic* controller) {
    1727       88497 :     if (toPhase->coordinatePhase &&
    1728        2872 :             (controller->getOtherPhase(fromPhase)->readyToSwitch || fromPhase->barrierNum == toPhase->barrierNum)) {
    1729             :         // transitions TO the coordinated phase may always happen, as long as the other phase is okay to switch too
    1730             :         return true;
    1731             :     }
    1732             :     // first check if the free logic is upheld
    1733       85659 :     else if (freeBase(controller)) {
    1734             :         // 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
    1735        8097 :         SUMOTime transitionTime = fromPhase->getTransitionTime(controller);
    1736        8097 :         SUMOTime timeTillForceOff = controller->ModeCycle(toPhase->forceOffTime - controller->getTimeInCycle(), controller->getCurrentCycleLength());
    1737        8097 :         if (toPhase->minDuration + transitionTime <= timeTillForceOff) {
    1738        7941 :             return true;
    1739             :         }
    1740             :     }
    1741             :     return false;
    1742             : }
    1743             : 
    1744             : 
    1745             : bool
    1746       20390 : PhaseTransitionLogic::fromBarrier(NEMALogic* controller) {
    1747       20390 :     if (freeBase(controller)) {
    1748       16571 :         if (fromPhase->barrierNum == toPhase->barrierNum) {
    1749             :             // same barrier side so we are good.
    1750             :             // 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
    1751          79 :             if (fromPhase->getCurrentState() >= LightState::Green) {
    1752          97 :                 for (auto& p : controller->getPhasesByRing(fromPhase->ringNum)) {
    1753          94 :                     if (p->barrierNum != fromPhase->barrierNum && p->callActive()) {
    1754             :                         return false;
    1755             :                     }
    1756             :                 }
    1757             :             }
    1758          32 :             return true;
    1759             :         } else {
    1760             :             // This is now a barrier cross and we need to make sure that the other phase is also ready to transition
    1761       16492 :             if (fromPhase->readyToSwitch && controller->getOtherPhase(fromPhase)->readyToSwitch) {
    1762       16492 :                 return true;
    1763             :             }
    1764             :         }
    1765             :     }
    1766             :     return false;
    1767             : }
    1768             : 
    1769             : 
    1770             : bool
    1771       84477 : PhaseTransitionLogic::fromCoord(NEMALogic* controller) {
    1772       84477 :     if (coordBase(controller)) {
    1773             :         // Determine if the other phase is also ready to switch
    1774        7781 :         if (controller->getOtherPhase(fromPhase)->readyToSwitch) {
    1775             :             // 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
    1776        7765 :             if (controller->isType170()) {
    1777             :                 return true;
    1778             :             }
    1779             :             // If the transition is already active, then report that the movement is possible
    1780        4640 :             if (fromPhase->isTransitionActive()) {
    1781             :                 return true;
    1782             :             }
    1783             :             // now determine if there my prior phase can fit or not. We already know that I can fit.
    1784        3278 :             NEMAPhase* priorPhase = toPhase->getSequentialPriorPhase();
    1785        3278 :             SUMOTime timeTillForceOff = controller->ModeCycle(priorPhase->forceOffTime - controller->getTimeInCycle(), controller->getCurrentCycleLength());
    1786        3278 :             SUMOTime transitionTime = fromPhase->getTransitionTime(controller);
    1787             :             // if the time till the force off is less than the min duration ||
    1788             :             // if it is greater than the cycle length minus the length of the coordinate phase (which the fromPhase automatically is)
    1789        3278 :             if ((priorPhase->minDuration + transitionTime) > timeTillForceOff || timeTillForceOff > (controller->getCurrentCycleLength() - fromPhase->minDuration)) {
    1790         250 :                 return true;
    1791             :             }
    1792             :         }
    1793             :     }
    1794             :     return false;
    1795             : }
    1796             : 
    1797             : int
    1798       56264 : PhaseTransitionLogic::getDistance(PhaseTransitionLogic* otherTrans) {
    1799             :     // Returns the other transitions distance in green transfer situations
    1800       56264 :     if ((toPhase == fromPhase) && (otherTrans->toPhase->barrierNum == toPhase->barrierNum)) {
    1801       31471 :         if (toPhase->getCurrentState() == LightState::Green || toPhase->getCurrentState() == LightState::GreenXfer) {
    1802        4413 :             return otherTrans->distance;
    1803             :         }
    1804             :     }
    1805       51851 :     return distance;
    1806             : }

Generated by: LCOV version 1.14