diff --git a/examples/slow_charge_tl.cpp b/examples/slow_charge_tl.cpp index 0c5255c52..806008e3b 100644 --- a/examples/slow_charge_tl.cpp +++ b/examples/slow_charge_tl.cpp @@ -34,7 +34,7 @@ using Graph = dsm::Graph; using Dynamics = dsm::FirstOrderDynamics; using Street = dsm::Street; using SpireStreet = dsm::SpireStreet; -using TrafficLight = dsm::TrafficLight; +using TrafficLight = dsm::TrafficLight; void printLoadingBar(int const i, int const n) { std::cout << "Loading: " << std::setprecision(2) << std::fixed << (i * 100. / n) << "%" @@ -149,7 +149,7 @@ int main(int argc, char** argv) { std::cout << "Traffic Lightning the simulation...\n"; for (Unit i{0}; i < graph.nodeSet().size(); ++i) { - graph.makeTrafficLight(i); + graph.makeTrafficLight(i, 120); } std::cout << "Making every street a spire...\n"; for (const auto& [id, street] : graph.streetSet()) { @@ -179,7 +179,6 @@ int main(int argc, char** argv) { while (value < 0.) { value = random(); } - tl.setDelay(static_cast(value)); const auto& col = adj.getCol(nodeId, true); std::set streets; const auto id = col.begin(); @@ -199,7 +198,14 @@ int main(int argc, char** argv) { streets.emplace(c); } } - tl.setStreetPriorities(streets); + for (auto const& streetId : streets) { + tl.setCycle(streetId, dsm::Direction::ANY, {static_cast(value), 0}); + } + for (const auto& [c, value] : col) { + if (streets.find(c) == streets.end()) { + tl.setComplementaryCycle(c, *streets.begin()); + } + } ++sda[streets.size() - 1]; // std::cout << "Node id: " << nodeId << " has " << streets.size() // << "streets.\n"; @@ -313,7 +319,7 @@ int main(int argc, char** argv) { } dynamics.evolve(false); if (OPTIMIZE && (dynamics.time() % 420 == 0)) { - dynamics.optimizeTrafficLights(std::floor(420. / 60), 0.15, 3. / 10); + dynamics.optimizeTrafficLights(0.15, 3. / 10); } if (dynamics.time() % 2400 == 0 && nAgents > 0) { // auto meanDelta = std::accumulate(deltas.begin(), deltas.end(), 0) / diff --git a/examples/stalingrado.cpp b/examples/stalingrado.cpp index 39318c1e8..15d970c06 100644 --- a/examples/stalingrado.cpp +++ b/examples/stalingrado.cpp @@ -24,7 +24,7 @@ using Itinerary = dsm::Itinerary; using Dynamics = dsm::FirstOrderDynamics; using Street = dsm::Street; using SpireStreet = dsm::SpireStreet; -using TrafficLight = dsm::TrafficLight; +using TrafficLight = dsm::TrafficLight; void printLoadingBar(int const i, int const n) { std::cout << "Loading: " << std::setprecision(2) << std::fixed << (i * 100. / n) << "%" @@ -46,7 +46,6 @@ int main() { const auto MAX_TIME{static_cast(timeUnit * vehiclesToInsert.size())}; // Create the graph - TrafficLight tl1{1}, tl2{2}, tl3{3}, tl4{4}; // Street(StreetId, Capacity, Length, vMax, (from, to)) Street s01{1, 2281 / 8, 2281., 13.9, std::make_pair(0, 1)}; @@ -54,21 +53,21 @@ int main() { Street s23{13, 222 / 8, 222., 13.9, std::make_pair(2, 3)}; Street s34{19, 651 / 4, 651., 13.9, std::make_pair(3, 4)}; // Viale Aldo Moro - tl1.setDelay(std::make_pair(62, 70)); // 40, 70 + TrafficLight tl1{1, 132}; + tl1.setCycle(s01.id(), dsm::Direction::ANY, {62, 0}); tl1.setCapacity(1); - tl1.addStreetPriority(s01.id()); // Via Donato Creti - tl2.setDelay(std::make_pair(72, 69)); // 50, 75 + TrafficLight tl2{2, 141}; + tl2.setCycle(s12.id(), dsm::Direction::ANY, {72, 0}); tl2.setCapacity(1); - tl2.addStreetPriority(s12.id()); // Via del Lavoro - tl3.setDelay(std::make_pair(88, 50)); // 40, 70 + TrafficLight tl3{3, 138}; + tl3.setCycle(s23.id(), dsm::Direction::ANY, {88, 0}); tl3.setCapacity(1); - tl3.addStreetPriority(s23.id()); // Viali - tl4.setDelay(std::make_pair(81, 50)); // 38, 106 = 144 + TrafficLight tl4{4, 131}; + tl4.setCycle(s34.id(), dsm::Direction::ANY, {81, 0}); tl4.setCapacity(1); - tl4.addStreetPriority(s34.id()); Graph graph; graph.addNode(std::make_unique(tl1)); @@ -77,7 +76,7 @@ int main() { graph.addNode(std::make_unique(tl4)); graph.addStreets(s01, s12, s23, s34); graph.buildAdj(); - graph.makeSpireStreet(19); + auto& spire = graph.makeSpireStreet(19); std::cout << "Intersections: " << graph.nodeSet().size() << '\n'; std::cout << "Streets: " << graph.streetSet().size() << '\n'; @@ -91,8 +90,6 @@ int main() { dynamics.addItinerary(itinerary); dynamics.updatePaths(); - auto& spire = dynamic_cast(*dynamics.graph().streetSet().at(19)); - // lauch progress bar std::jthread t([MAX_TIME]() { while (progress < MAX_TIME) { diff --git a/src/dsm/dsm.hpp b/src/dsm/dsm.hpp index bfc499166..cba2b9066 100644 --- a/src/dsm/dsm.hpp +++ b/src/dsm/dsm.hpp @@ -5,8 +5,8 @@ #include static constexpr uint8_t DSM_VERSION_MAJOR = 2; -static constexpr uint8_t DSM_VERSION_MINOR = 1; -static constexpr uint8_t DSM_VERSION_PATCH = 10; +static constexpr uint8_t DSM_VERSION_MINOR = 2; +static constexpr uint8_t DSM_VERSION_PATCH = 0; static auto const DSM_VERSION = std::format("{}.{}.{}", DSM_VERSION_MAJOR, DSM_VERSION_MINOR, DSM_VERSION_PATCH); diff --git a/src/dsm/headers/Agent.hpp b/src/dsm/headers/Agent.hpp index e1626edb0..fdfc65eb0 100644 --- a/src/dsm/headers/Agent.hpp +++ b/src/dsm/headers/Agent.hpp @@ -4,7 +4,7 @@ /// @details This file contains the definition of the Agent class. /// The Agent class represents an agent in the network. It is templated by the type /// of the agent's id and the size of agents, which must both be unsigned integrals. -/// It is also templated by the Delay type, which must be a numeric (see utility/TypeTraits/is_numeric.hpp) +/// It is also templated by the delay_t type, which must be a numeric (see utility/TypeTraits/is_numeric.hpp) /// and represents the spatial or temporal (depending on the type of the template) distance /// between the agent and the one in front of it. @@ -25,16 +25,16 @@ namespace dsm { /// @brief The Agent class represents an agent in the network. /// @tparam Id, The type of the agent's id. It must be an unsigned integral type. /// @tparam Size, The type of the size of a street. It must be an unsigned integral type. - /// @tparam Delay, The type of the agent's delay. It must be a numeric type (see utility/TypeTraits/is_numeric.hpp). - template - requires(is_numeric_v) + /// @tparam delay_t, The type of the agent's delay. It must be a numeric type (see utility/TypeTraits/is_numeric.hpp). + template + requires(is_numeric_v) class Agent { private: Id m_id; Id m_itineraryId; std::optional m_streetId; std::optional m_srcNodeId; - Delay m_delay; + delay_t m_delay; double m_speed; double m_distance; // Travelled distance unsigned int m_time; // Travelled time @@ -68,7 +68,7 @@ namespace dsm { /// @brief Increment the agent's delay by a given value /// @param delay The agent's delay /// @throw std::overflow_error, if delay has reached its maximum value - void incrementDelay(Delay const delay); + void incrementDelay(delay_t const delay); /// @brief Decrement the agent's delay by 1 /// @throw std::underflow_error, if delay has reached its minimum value void decrementDelay(); @@ -105,7 +105,7 @@ namespace dsm { double speed() const { return m_speed; } /// @brief Get the agent's delay /// @return The agent's delay - Delay delay() const { return m_delay; } + delay_t delay() const { return m_delay; } /// @brief Get the agent's travelled distance /// @return The agent's travelled distance double distance() const { return m_distance; } @@ -114,9 +114,9 @@ namespace dsm { unsigned int time() const { return m_time; } }; - template - requires(is_numeric_v) - Agent::Agent(Id id, Id itineraryId) + template + requires(is_numeric_v) + Agent::Agent(Id id, Id itineraryId) : m_id{id}, m_itineraryId{itineraryId}, m_delay{0}, @@ -124,9 +124,9 @@ namespace dsm { m_distance{0.}, m_time{0} {} - template - requires(is_numeric_v) - Agent::Agent(Id id, Id itineraryId, Id srcNodeId) + template + requires(is_numeric_v) + Agent::Agent(Id id, Id itineraryId, Id srcNodeId) : m_id{id}, m_itineraryId{itineraryId}, m_srcNodeId{srcNodeId}, @@ -135,59 +135,59 @@ namespace dsm { m_distance{0.}, m_time{0} {} - template - requires(is_numeric_v) - void Agent::setSpeed(double speed) { + template + requires(is_numeric_v) + void Agent::setSpeed(double speed) { if (speed < 0) { throw std::invalid_argument(buildLog("Speed must be positive")); } m_speed = speed; } - template - requires(is_numeric_v) - void Agent::incrementDelay() { - if (m_delay == std::numeric_limits::max()) { - throw std::overflow_error(buildLog("Delay has reached its maximum value")); + template + requires(is_numeric_v) + void Agent::incrementDelay() { + if (m_delay == std::numeric_limits::max()) { + throw std::overflow_error(buildLog("delay_t has reached its maximum value")); } ++m_delay; } - template - requires(is_numeric_v) - void Agent::incrementDelay(Delay const delay) { + template + requires(is_numeric_v) + void Agent::incrementDelay(delay_t const delay) { if (m_delay + delay < m_delay) { - throw std::overflow_error(buildLog("Delay has reached its maximum value")); + throw std::overflow_error(buildLog("delay_t has reached its maximum value")); } m_delay += delay; } - template - requires(is_numeric_v) - void Agent::decrementDelay() { + template + requires(is_numeric_v) + void Agent::decrementDelay() { if (m_delay == 0) { - throw std::underflow_error(buildLog("Delay has reached its minimum value")); + throw std::underflow_error(buildLog("delay_t has reached its minimum value")); } --m_delay; } - template - requires(is_numeric_v) - void Agent::incrementDistance(double distance) { + template + requires(is_numeric_v) + void Agent::incrementDistance(double distance) { if (distance < 0) { throw std::invalid_argument(buildLog("Distance travelled must be positive")); } m_distance += distance; } - template - requires(is_numeric_v) - void Agent::incrementTime() { + template + requires(is_numeric_v) + void Agent::incrementTime() { if (m_time == std::numeric_limits::max()) { throw std::overflow_error(buildLog("Time has reached its maximum value")); } ++m_time; } - template - requires(is_numeric_v) - void Agent::incrementTime(unsigned int const time) { + template + requires(is_numeric_v) + void Agent::incrementTime(unsigned int const time) { if (m_time + time < m_time) { throw std::overflow_error(buildLog("Time has reached its maximum value")); } diff --git a/src/dsm/headers/Dynamics.hpp b/src/dsm/headers/Dynamics.hpp index cb7c11099..8ed230c02 100644 --- a/src/dsm/headers/Dynamics.hpp +++ b/src/dsm/headers/Dynamics.hpp @@ -32,9 +32,6 @@ #include "../utility/Typedef.hpp" namespace dsm { - - using TimePoint = long long unsigned int; - /// @brief The Measurement struct represents the mean of a quantity and its standard deviation /// @tparam T The type of the quantity /// @param mean The mean @@ -63,13 +60,13 @@ namespace dsm { /// @brief The Dynamics class represents the dynamics of the network. /// @tparam Id, The type of the graph's id. It must be an unsigned integral type. /// @tparam Size, The type of the graph's capacity. It must be an unsigned integral type. - template - requires(is_numeric_v) + template + requires(is_numeric_v) class Dynamics { protected: std::unordered_map> m_itineraries; - std::map>> m_agents; - TimePoint m_time, m_previousSpireTime; + std::map>> m_agents; + Time m_time, m_previousSpireTime, m_previousOptimizationTime; Graph m_graph; double m_errorProbability; double m_minSpeedRateo; @@ -79,10 +76,10 @@ namespace dsm { std::vector m_travelTimes; std::unordered_map m_agentNextStreetId; bool m_forcePriorities; - std::optional m_dataUpdatePeriod; + std::optional m_dataUpdatePeriod; std::unordered_map> m_turnCounts; std::unordered_map> m_turnMapping; - std::unordered_map m_streetTails; + std::unordered_map m_streetTails; /// @brief Get the next street id /// @param agentId The id of the agent @@ -95,13 +92,11 @@ namespace dsm { /// @brief Increase the turn counts virtual void m_increaseTurnCounts(Id streetId, double delta); /// @brief Evolve a street - /// @param streetId The id of the street /// @param pStreet A std::unique_ptr to the street /// @param reinsert_agents If true, the agents are reinserted in the simulation after they reach their destination /// @details If possible, removes the first agent of the street's queue, putting it in the destination node. /// If the agent is going into the destination node, it is removed from the simulation (and then reinserted if reinsert_agents is true) - virtual void m_evolveStreet(const Id streetId, - const std::unique_ptr& pStreet, + virtual void m_evolveStreet(const std::unique_ptr& pStreet, bool reinsert_agents); /// @brief If possible, removes one agent from the node, putting it on the next street. /// @param pNode A std::unique_ptr to the node @@ -131,7 +126,7 @@ namespace dsm { for (const auto [nextNodeId, _] : m_graph.adjMatrix().getRow(nodeId)) { if (nextNodeId == destinationID && minDistance == - m_graph.streetSet().at(nodeId * dimension + nextNodeId)->length()) { + m_graph.streetSet()[nodeId * dimension + nextNodeId]->length()) { path.insert(nodeId, nextNodeId, true); continue; } @@ -141,7 +136,7 @@ namespace dsm { // if the shortest path exists, save the distance if (minDistance == result.value().distance() + - m_graph.streetSet().at(nodeId * dimension + nextNodeId)->length()) { + m_graph.streetSet()[nodeId * dimension + nextNodeId]->length()) { path.insert(nodeId, nextNodeId, true); } } else if ((nextNodeId != destinationID)) { @@ -201,9 +196,9 @@ namespace dsm { /// @details If true, if an agent cannot move to the next street, the whole node is skipped void setForcePriorities(bool forcePriorities) { m_forcePriorities = forcePriorities; } /// @brief Set the data update period. - /// @param dataUpdatePeriod Delay, The period + /// @param dataUpdatePeriod delay_t, The period /// @details Some data, i.e. the street queue lengths, are stored only after a fixed amount of time which is represented by this variable. - void setDataUpdatePeriod(Delay dataUpdatePeriod) { + void setDataUpdatePeriod(delay_t dataUpdatePeriod) { m_dataUpdatePeriod = dataUpdatePeriod; } @@ -220,14 +215,16 @@ namespace dsm { /// @param reinsert_agents If true, the agents are reinserted in the simulation after they reach their destination virtual void evolve(bool reinsert_agents = false); /// @brief Optimize the traffic lights by changing the green and red times - /// @param nCycles Delay, The number of cycles (times agents are being added) between two calls of this function /// @param threshold double, The percentage of the mean capacity of the streets used as threshold for the delta between the two tails. /// @param densityTolerance double, The algorithm will consider all streets with density up to densityTolerance*meanDensity + /// @param optimizationType TrafficLightOptimization, The type of optimization. Default is DOUBLE_TAIL /// @details The function cycles over the traffic lights and, if the difference between the two tails is greater than /// the threshold multiplied by the mean capacity of the streets, it changes the green and red times of the traffic light, keeping the total cycle time constant. - void optimizeTrafficLights(Delay nCycles, - double threshold = 0., - double densityTolerance = 0.); + /// The optimizationType parameter can be set to SINGLE_TAIL to use an algorith which looks only at the incoming street tails or to DOUBLE_TAIL to consider both incoming and outgoing street tails. + void optimizeTrafficLights(double const threshold = 0., + double const densityTolerance = 0., + TrafficLightOptimization optimizationType = + TrafficLightOptimization::DOUBLE_TAIL); /// @brief Get the graph /// @return const Graph&, The graph @@ -239,17 +236,19 @@ namespace dsm { } /// @brief Get the agents /// @return const std::unordered_map>&, The agents - const std::map>>& agents() const { return m_agents; } + const std::map>>& agents() const { + return m_agents; + } /// @brief Get the time - /// @return TimePoint The time - TimePoint time() const { return m_time; } + /// @return Time The time + Time time() const { return m_time; } /// @brief Add an agent to the simulation /// @param agent The agent - void addAgent(const Agent& agent); + void addAgent(const Agent& agent); /// @brief Add an agent to the simulation /// @param agent std::unique_ptr to the agent - void addAgent(std::unique_ptr> agent); + void addAgent(std::unique_ptr> agent); /// @brief Add an agent with given source node and itinerary /// @param srcNodeId The id of the source node /// @param itineraryId The id of the itinerary @@ -276,7 +275,7 @@ namespace dsm { void addAgents(T1 agent, Tn... agents); /// @brief Add a set of agents to the simulation /// @param agents Generic container of agents, represented by an std::span - void addAgents(std::span> agents); + void addAgents(std::span> agents); /// @brief Add a set of agents to the simulation /// @param nAgents The number of agents to add /// @param uniformly If true, the agents are added uniformly on the streets @@ -375,11 +374,12 @@ namespace dsm { } }; - template - requires(is_numeric_v) - Dynamics::Dynamics(Graph& graph) + template + requires(is_numeric_v) + Dynamics::Dynamics(Graph& graph) : m_time{0}, m_previousSpireTime{0}, + m_previousOptimizationTime{0}, m_graph{std::move(graph)}, m_errorProbability{0.}, m_minSpeedRateo{0.}, @@ -396,27 +396,28 @@ namespace dsm { const auto& delta = street->angle() - m_graph.streetSet()[ss]->angle(); if (std::abs(delta) < std::numbers::pi) { if (delta < 0.) { - m_turnMapping[streetId][0] = ss; + m_turnMapping[streetId][dsm::Direction::RIGHT] = ss; ; // right } else if (delta > 0.) { - m_turnMapping[streetId][2] = ss; // left + m_turnMapping[streetId][dsm::Direction::LEFT] = ss; // left } else { - m_turnMapping[streetId][1] = ss; // straight + m_turnMapping[streetId][dsm::Direction::STRAIGHT] = ss; // straight } } else { - m_turnMapping[streetId][3] = ss; // U + m_turnMapping[streetId][dsm::Direction::UTURN] = ss; // U } } } } - template - requires(is_numeric_v) - Id Dynamics::m_nextStreetId(Id agentId, Id nodeId, std::optional streetId) { + template + requires(is_numeric_v) + Id Dynamics::m_nextStreetId(Id agentId, + Id nodeId, + std::optional streetId) { auto possibleMoves = m_graph.adjMatrix().getRow(nodeId, true); - if (this->m_itineraries.size() > 0 && - this->m_uniformDist(this->m_generator) > this->m_errorProbability) { - const auto& it = this->m_itineraries[this->m_agents[agentId]->itineraryId()]; + if (m_itineraries.size() > 0 && m_uniformDist(m_generator) > m_errorProbability) { + const auto& it = m_itineraries[m_agents[agentId]->itineraryId()]; if (it->destination() != nodeId) { possibleMoves = it->path().getRow(nodeId, true); } @@ -428,7 +429,7 @@ namespace dsm { auto iterator = possibleMoves.begin(); // while loop to avoid U turns in non-roundabout junctions do { - p = moveDist(this->m_generator); + p = moveDist(m_generator); iterator = possibleMoves.begin(); std::advance(iterator, p); } while (!m_graph.nodeSet().at(nodeId)->isRoundabout() and streetId.has_value() and @@ -438,9 +439,9 @@ namespace dsm { return iterator->first; } - template - requires(is_numeric_v) - void Dynamics::m_increaseTurnCounts(Id streetId, double delta) { + template + requires(is_numeric_v) + void Dynamics::m_increaseTurnCounts(Id streetId, double delta) { if (std::abs(delta) < std::numbers::pi) { if (delta < 0.) { ++m_turnCounts[streetId][0]; // right @@ -454,11 +455,10 @@ namespace dsm { } } - template - requires(is_numeric_v) - void Dynamics::m_evolveStreet(const Id streetId, - const std::unique_ptr& pStreet, - bool reinsert_agents) { + template + requires(is_numeric_v) + void Dynamics::m_evolveStreet(const std::unique_ptr& pStreet, + bool reinsert_agents) { auto const nLanes = pStreet->nLanes(); for (auto queueIndex = 0; queueIndex < nLanes; ++queueIndex) { if (m_uniformDist(m_generator) > m_maxFlowPercentage || @@ -470,24 +470,14 @@ namespace dsm { continue; } m_agents[agentId]->setSpeed(0.); - const auto& destinationNode{this->m_graph.nodeSet()[pStreet->nodePair().second]}; + const auto& destinationNode{m_graph.nodeSet()[pStreet->nodePair().second]}; if (destinationNode->isFull()) { continue; } if (destinationNode->isTrafficLight()) { - auto& tl = dynamic_cast&>(*destinationNode); - if (tl.leftTurnRatio().has_value() && nLanes > 1 && queueIndex == nLanes - 1) { - if (tl.isGreen(streetId) && - tl.counter() > - tl.delay().value().first * (1. - tl.leftTurnRatio().value().first)) { - continue; - } else if (!tl.isGreen(streetId) && - tl.counter() > tl.delay().value().first + - tl.delay().value().second * - (1. - tl.leftTurnRatio().value().second)) { - continue; - } - } else if (!tl.isGreen(streetId)) { + auto& tl = dynamic_cast(*destinationNode); + auto const direction{pStreet->laneMapping().at(queueIndex)}; + if (!tl.isGreen(pStreet->id(), direction)) { continue; } } @@ -497,11 +487,11 @@ namespace dsm { m_travelTimes.push_back(m_agents[agentId]->time()); if (reinsert_agents) { // take last agent id in map - Agent newAgent{static_cast(m_agents.rbegin()->first + 1), - m_agents[agentId]->itineraryId(), - m_agents[agentId]->srcNodeId().value()}; + Agent newAgent{static_cast(m_agents.rbegin()->first + 1), + m_agents[agentId]->itineraryId(), + m_agents[agentId]->srcNodeId().value()}; if (m_agents[agentId]->srcNodeId().has_value()) { - newAgent.setSourceNodeId(this->m_agents[agentId]->srcNodeId().value()); + newAgent.setSourceNodeId(m_agents[agentId]->srcNodeId().value()); } this->removeAgent(agentId); this->addAgent(newAgent); @@ -519,7 +509,7 @@ namespace dsm { if (destinationNode->isIntersection()) { auto& intersection = dynamic_cast(*destinationNode); auto const delta{nextStreet->deltaAngle(pStreet->angle())}; - m_increaseTurnCounts(streetId, delta); + m_increaseTurnCounts(pStreet->id(), delta); intersection.addAgent(delta, agentId); } else if (destinationNode->isRoundabout()) { auto& roundabout = dynamic_cast(*destinationNode); @@ -528,9 +518,9 @@ namespace dsm { } } - template - requires(is_numeric_v) - bool Dynamics::m_evolveNode(const std::unique_ptr& pNode) { + template + requires(is_numeric_v) + bool Dynamics::m_evolveNode(const std::unique_ptr& pNode) { if (pNode->isIntersection()) { auto& intersection = dynamic_cast(*pNode); if (intersection.agents().empty()) { @@ -560,7 +550,7 @@ namespace dsm { return false; } auto const agentId{roundabout.agents().front()}; - auto const& nextStreet{this->m_graph.streetSet()[m_agentNextStreetId[agentId]]}; + auto const& nextStreet{m_graph.streetSet()[m_agentNextStreetId[agentId]]}; if (!(nextStreet->isFull())) { if (m_agents[agentId]->streetId().has_value()) { const auto streetId = m_agents[agentId]->streetId().value(); @@ -586,10 +576,10 @@ namespace dsm { return true; } - template - requires(is_numeric_v) - void Dynamics::m_evolveAgents() { - for (const auto& [agentId, agent] : this->m_agents) { + template + requires(is_numeric_v) + void Dynamics::m_evolveAgents() { + for (const auto& [agentId, agent] : m_agents) { if (agent->delay() > 0) { const auto& street{m_graph.streetSet()[agent->streetId().value()]}; if (agent->delay() > 1) { @@ -605,7 +595,7 @@ namespace dsm { agent->decrementDelay(); if (agent->delay() == 0) { auto const nLanes = street->nLanes(); - if (this->m_itineraries[agent->itineraryId()]->destination() == + if (m_itineraries[agent->itineraryId()]->destination() == street->nodePair().second) { std::uniform_int_distribution laneDist{ 0, static_cast(nLanes - 1)}; @@ -639,7 +629,7 @@ namespace dsm { } else if (!agent->streetId().has_value() && !m_agentNextStreetId.contains(agentId)) { assert(agent->srcNodeId().has_value()); - const auto& srcNode{this->m_graph.nodeSet()[agent->srcNodeId().value()]}; + const auto& srcNode{m_graph.nodeSet()[agent->srcNodeId().value()]}; if (srcNode->isFull()) { continue; } @@ -664,17 +654,17 @@ namespace dsm { } } - template - requires(is_numeric_v) - void Dynamics::setItineraries(std::span itineraries) { + template + requires(is_numeric_v) + void Dynamics::setItineraries(std::span itineraries) { std::ranges::for_each(itineraries, [this](const auto& itinerary) { - this->m_itineraries.insert(std::make_unique(itinerary)); + m_itineraries.insert(std::make_unique(itinerary)); }); } - template - requires(is_numeric_v) - void Dynamics::setMinSpeedRateo(double minSpeedRateo) { + template + requires(is_numeric_v) + void Dynamics::setMinSpeedRateo(double minSpeedRateo) { if (minSpeedRateo < 0. || minSpeedRateo > 1.) { throw std::invalid_argument(buildLog(std::format( "The minimum speed rateo ({}) must be between 0 and 1", minSpeedRateo))); @@ -682,9 +672,9 @@ namespace dsm { m_minSpeedRateo = minSpeedRateo; } - template - requires(is_numeric_v) - void Dynamics::setErrorProbability(double errorProbability) { + template + requires(is_numeric_v) + void Dynamics::setErrorProbability(double errorProbability) { if (errorProbability < 0. || errorProbability > 1.) { throw std::invalid_argument(buildLog(std::format( "The error probability ({}) must be between 0 and 1", errorProbability))); @@ -692,9 +682,9 @@ namespace dsm { m_errorProbability = errorProbability; } - template - requires(is_numeric_v) - void Dynamics::setMaxFlowPercentage(double maxFlowPercentage) { + template + requires(is_numeric_v) + void Dynamics::setMaxFlowPercentage(double maxFlowPercentage) { if (maxFlowPercentage < 0. || maxFlowPercentage > 1.) { throw std::invalid_argument( buildLog(std::format("The maximum flow percentage ({}) must be between 0 and 1", @@ -703,10 +693,10 @@ namespace dsm { m_maxFlowPercentage = maxFlowPercentage; } - template - requires(is_numeric_v) - void Dynamics::setDestinationNodes(const std::span& destinationNodes, - bool updatePaths) { + template + requires(is_numeric_v) + void Dynamics::setDestinationNodes(const std::span& destinationNodes, + bool updatePaths) { for (const auto& nodeId : destinationNodes) { if (!m_graph.nodeSet().contains(nodeId)) { throw std::invalid_argument( @@ -719,9 +709,9 @@ namespace dsm { } } - template - requires(is_numeric_v) - void Dynamics::updatePaths() { + template + requires(is_numeric_v) + void Dynamics::updatePaths() { std::vector threads; threads.reserve(m_itineraries.size()); std::exception_ptr pThreadException; @@ -743,9 +733,9 @@ namespace dsm { std::rethrow_exception(pThreadException); } - template - requires(is_numeric_v) - void Dynamics::evolve(bool reinsert_agents) { + template + requires(is_numeric_v) + void Dynamics::evolve(bool reinsert_agents) { // move the first agent of each street queue, if possible, putting it in the next node bool const bUpdateData = m_dataUpdatePeriod.has_value() && m_time % m_dataUpdatePeriod.value() == 0; @@ -754,7 +744,7 @@ namespace dsm { m_streetTails[streetId] += pStreet->nExitingAgents(); } for (auto i = 0; i < pStreet->transportCapacity(); ++i) { - this->m_evolveStreet(streetId, pStreet, reinsert_agents); + this->m_evolveStreet(pStreet, reinsert_agents); } } // Move transport capacity agents from each node @@ -765,21 +755,22 @@ namespace dsm { } } if (pNode->isTrafficLight()) { - auto& tl = dynamic_cast&>(*pNode); - tl.increaseCounter(); + auto& tl = dynamic_cast(*pNode); + ++tl; // Increment the counter } } // cycle over agents and update their times this->m_evolveAgents(); // increment time simulation - ++this->m_time; + ++m_time; } - template - requires(is_numeric_v) - void Dynamics::optimizeTrafficLights(Delay nCycles, - double threshold, - double densityTolerance) { + template + requires(is_numeric_v) + void Dynamics::optimizeTrafficLights( + double const threshold, + double const densityTolerance, + TrafficLightOptimization const optimizationType) { if (threshold < 0 || threshold > 1) { throw std::invalid_argument( buildLog(std::format("The threshold parameter is a percentage and must be " @@ -792,17 +783,12 @@ namespace dsm { "bounded between 0-1. Inserted value: {}", densityTolerance))); } - const auto meanDensityGlob = streetMeanDensity().mean; // Measurement - for (const auto& [nodeId, node] : m_graph.nodeSet()) { - if (!node->isTrafficLight()) { - continue; - } - auto& tl = dynamic_cast&>(*node); - if (!tl.delay().has_value()) { + auto const meanDensityGlob = streetMeanDensity().mean; // Measurement + for (const auto& [nodeId, pNode] : m_graph.nodeSet()) { + if (!pNode->isTrafficLight()) { continue; } - auto [greenTime, redTime] = tl.delay().value(); - const auto cycleTime = greenTime + redTime; + auto& tl = dynamic_cast(*pNode); const auto& streetPriorities = tl.streetPriorities(); Size greenSum{0}, greenQueue{0}; Size redSum{0}, redQueue{0}; @@ -819,156 +805,145 @@ namespace dsm { } } } - const Delay delta = + const auto nCycles = static_cast(m_time - m_previousOptimizationTime) / + m_dataUpdatePeriod.value(); + const delay_t delta = std::floor(std::fabs(static_cast(greenQueue - redQueue)) / nCycles); + // std::cout << std::format("GreenSum: {}, RedSum: {}, Delta: {}, nCycles: {}\n", + // greenQueue, redQueue, delta, nCycles); if (delta == 0) { continue; } const Size smallest = std::min(greenSum, redSum); + // std::cout << std::format("GreenSum: {}, RedSum: {}, Smallest: {}\n", greenSum, redSum, smallest); + // std::cout << std::format("Diff: {}, Threshold * Smallest: {}\n", std::abs(static_cast(greenSum - redSum)), threshold * smallest); if (std::abs(static_cast(greenSum - redSum)) < threshold * smallest) { - tl.setDelay(std::floor(cycleTime / 2)); + tl.resetCycles(); continue; } - // If the difference is not less than the threshold - // - Check that the incoming streets have a density less than the mean one (eventually + tolerance): I want to avoid being into the cluster, better to be out or on the border - // - If the previous check fails, do nothing - double meanDensity_streets{0.}; - { - // Store the ids of outgoing streets - const auto& row{m_graph.adjMatrix().getRow(nodeId, true)}; - for (const auto& [streetId, _] : row) { - meanDensity_streets += m_graph.streetSet()[streetId]->density(); - } - // Take the mean density of the outgoing streets - const auto nStreets = row.size(); - if (nStreets > 1) { - meanDensity_streets /= nStreets; + auto const greenTime = tl.maxGreenTime(true); + auto const redTime = tl.maxGreenTime(false); + if (optimizationType == TrafficLightOptimization::SINGLE_TAIL) { + if ((greenSum > redSum) && !(greenTime > redTime) && (redTime > delta) && + (greenQueue > redQueue)) { + tl.increaseGreenTimes(delta); + } else if ((redSum > greenSum) && !(redTime > greenTime) && (greenTime > delta) && + (redQueue > greenQueue)) { + tl.decreaseGreenTimes(delta); + } else { + tl.resetCycles(); } - } - //std::cout << '\t' << " -> Mean network density: " << std::setprecision(7) << meanDensityGlob << '\n'; - //std::cout << '\t' << " -> Mean density of 4 outgoing streets: " << std::setprecision(7) << meanDensity_streets << '\n'; - const auto ratio = meanDensityGlob / meanDensity_streets; - // densityTolerance represents the max border we want to consider - const auto dyn_thresh = std::tanh(ratio) * densityTolerance; - //std::cout << '\t' << " -> Parametro ratio: " << std::setprecision(7) << ratio << '\n'; - //std::cout << '\t' << " -> Parametro dyn_thresh: " << std::setprecision(7) << dyn_thresh << '\n'; - if (meanDensityGlob * (1. + dyn_thresh) > meanDensity_streets) { - //std::cout << '\t' << " -> I'm on the cluster's border" << '\n'; - if (meanDensityGlob > meanDensity_streets) { - //std::cout << '\t' << " -> LESS than max density" << '\n'; - if (!(redTime > greenTime) && (redSum > greenSum) && (greenTime > delta)) { - greenTime -= delta; - redTime += delta; - tl.setDelay(std::make_pair(greenTime, redTime)); - } else if (!(greenTime > redTime) && (greenSum > redSum) && (redTime > delta)) { - greenTime += delta; - redTime -= delta; - tl.setDelay(std::make_pair(greenTime, redTime)); - } else { - //std::cout << '\t' << " -> NOT entered into previous ifs" << '\n'; - tl.setDelay(std::make_pair(greenTime, redTime)); + } else if (optimizationType == TrafficLightOptimization::DOUBLE_TAIL) { + // If the difference is not less than the threshold + // - Check that the incoming streets have a density less than the mean one (eventually + tolerance): I want to avoid being into the cluster, better to be out or on the border + // - If the previous check fails, do nothing + double meanDensity_streets{0.}; + { + // Store the ids of outgoing streets + const auto& row{m_graph.adjMatrix().getRow(nodeId, true)}; + for (const auto& [streetId, _] : row) { + meanDensity_streets += m_graph.streetSet()[streetId]->density(); } - //std::cout << '\t' << " -> greenTime: " << static_cast(greenTime) << '\n'; - //std::cout << '\t' << " -> redTime: " << static_cast(redTime) << '\n'; - //std::cout << '\t' << " -> modTime: " << tl.modTime() << '\n'; - } else { - //std::cout << '\t' << " -> GREATER than max density" << '\n'; - if (!(redTime > greenTime) && (redSum > greenSum) && - (greenTime > ratio * delta)) { - greenTime -= dyn_thresh * delta; // - redTime += delta; - tl.setDelay(std::make_pair(greenTime, redTime)); - } else if (!(greenTime > redTime) && (greenSum > redSum) && - (redTime > ratio * delta)) { - greenTime += delta; - redTime -= dyn_thresh * delta; // - tl.setDelay(std::make_pair(greenTime, redTime)); - } else if (!(redTime > greenTime) && (redSum < greenSum) && (redTime > delta)) { - greenTime += dyn_thresh * delta; // - redTime -= delta; - tl.setDelay(std::make_pair(greenTime, redTime)); - } else if (!(redTime < greenTime) && (redSum > greenSum) && - (greenTime > delta)) { - greenTime -= delta; - redTime += dyn_thresh * delta; // - tl.setDelay(std::make_pair(greenTime, redTime)); + // Take the mean density of the outgoing streets + const auto nStreets = row.size(); + if (nStreets > 1) { + meanDensity_streets /= nStreets; + } + } + auto const ratio = meanDensityGlob / meanDensity_streets; + // densityTolerance represents the max border we want to consider + auto const dyn_thresh = std::tanh(ratio) * densityTolerance; + if (meanDensityGlob * (1. + dyn_thresh) > meanDensity_streets) { + if (meanDensityGlob > meanDensity_streets) { + // Smaller than max density + if (!(redTime > greenTime) && (redSum > greenSum) && (greenTime > delta)) { + tl.decreaseGreenTimes(delta); + } else if (!(redTime < greenTime) && (greenSum > redSum) && + (redTime > delta)) { + tl.increaseGreenTimes(delta); + } else { + tl.resetCycles(); + } } else { - //std::cout << '\t' << " -> NON sono entrato negli if precedenti" << '\n'; - tl.setDelay(std::make_pair(greenTime, redTime)); + // Greater than max density + if (!(redTime > greenTime) && (redSum > greenSum) && (greenTime > delta)) { + tl.decreaseGreenTimes(delta * dyn_thresh); + } else if (!(redTime < greenTime) && (greenSum > redSum) && + (redTime > delta)) { + tl.increaseGreenTimes(delta * dyn_thresh); + } else { + tl.resetCycles(); + } } - //std::cout << '\t' << " -> greenTime: " << static_cast(greenTime) << '\n'; - //std::cout << '\t' << " -> redTime: " << static_cast(redTime) << '\n'; - //std::cout << '\t' << " -> modTime: " << tl.modTime() << '\n'; } - } else { - //std::cout << '\t' << " -> I'm INTO the cluster" << '\n'; - //std::cout << '\t' << " -> modTime: " << tl.modTime() << '\n'; } } + // Cleaning variables for (auto& [id, element] : m_streetTails) { element = 0; } + m_previousOptimizationTime = m_time; } - template - requires(is_numeric_v) - void Dynamics::addAgent(const Agent& agent) { - if (this->m_agents.size() + 1 > this->m_graph.maxCapacity()) { + template + requires(is_numeric_v) + void Dynamics::addAgent(const Agent& agent) { + if (m_agents.size() + 1 > m_graph.maxCapacity()) { throw std::overflow_error(buildLog( std::format("Graph is already holding the max possible number of agents ({})", - this->m_graph.maxCapacity()))); + m_graph.maxCapacity()))); } - if (this->m_agents.contains(agent.id())) { + if (m_agents.contains(agent.id())) { throw std::invalid_argument( buildLog(std::format("Agent with id {} already exists.", agent.id()))); } - this->m_agents.emplace(agent.id(), std::make_unique>(agent)); + m_agents.emplace(agent.id(), std::make_unique>(agent)); } - template - requires(is_numeric_v) - void Dynamics::addAgent(std::unique_ptr> agent) { - if (this->m_agents.size() + 1 > this->m_graph.maxCapacity()) { + template + requires(is_numeric_v) + void Dynamics::addAgent(std::unique_ptr> agent) { + if (m_agents.size() + 1 > m_graph.maxCapacity()) { throw std::overflow_error(buildLog( std::format("Graph is already holding the max possible number of agents ({})", - this->m_graph.maxCapacity()))); + m_graph.maxCapacity()))); } - if (this->m_agents.contains(agent->id())) { + if (m_agents.contains(agent->id())) { throw std::invalid_argument( buildLog(std::format("Agent with id {} already exists.", agent->id()))); } - this->m_agents.emplace(agent->id(), std::move(agent)); + m_agents.emplace(agent->id(), std::move(agent)); } - template - requires(is_numeric_v) - void Dynamics::addAgent(Id srcNodeId, Id itineraryId) { - if (this->m_agents.size() + 1 > this->m_graph.maxCapacity()) { + template + requires(is_numeric_v) + void Dynamics::addAgent(Id srcNodeId, Id itineraryId) { + if (m_agents.size() + 1 > m_graph.maxCapacity()) { throw std::overflow_error(buildLog( - std::format("Graph its already holding the max possible number of agents ({})", - this->m_graph.maxCapacity()))); + std::format("Graph is already holding the max possible number of agents ({})", + m_graph.maxCapacity()))); } - if (!(srcNodeId < this->m_graph.nodeSet().size())) { + if (!(srcNodeId < m_graph.nodeSet().size())) { throw std::invalid_argument( buildLog(std::format("Node with id {} not found", srcNodeId))); } - if (!(this->m_itineraries.contains(itineraryId))) { + if (!(m_itineraries.contains(itineraryId))) { throw std::invalid_argument( buildLog(std::format("Itinerary with id {} not found", itineraryId))); } Size agentId{0}; - if (!this->m_agents.empty()) { - agentId = this->m_agents.rbegin()->first + 1; + if (!m_agents.empty()) { + agentId = m_agents.rbegin()->first + 1; } - this->addAgent(Agent{agentId, itineraryId, srcNodeId}); + this->addAgent(Agent{agentId, itineraryId, srcNodeId}); } - template - requires(is_numeric_v) - void Dynamics::addAgents(Id itineraryId, - Size nAgents, - std::optional srcNodeId) { - if (this->m_agents.size() + nAgents > this->m_graph.maxCapacity()) { + template + requires(is_numeric_v) + void Dynamics::addAgents(Id itineraryId, + Size nAgents, + std::optional srcNodeId) { + if (m_agents.size() + nAgents > m_graph.maxCapacity()) { throw std::overflow_error(buildLog( - std::format("Graph its already holding the max possible number of agents ({})", - this->m_graph.maxCapacity()))); + std::format("Graph is already holding the max possible number of agents ({})", + m_graph.maxCapacity()))); } auto itineraryIt{m_itineraries.find(itineraryId)}; if (itineraryIt == m_itineraries.end()) { @@ -976,38 +951,38 @@ namespace dsm { buildLog(std::format("Itinerary with id {} not found", itineraryId))); } Size agentId{0}; - if (!this->m_agents.empty()) { - agentId = this->m_agents.rbegin()->first + 1; + if (!m_agents.empty()) { + agentId = m_agents.rbegin()->first + 1; } for (Size i{0}; i < nAgents; ++i, ++agentId) { - this->addAgent(Agent{agentId, itineraryId}); + this->addAgent(Agent{agentId, itineraryId}); if (srcNodeId.has_value()) { - this->m_agents[agentId]->setSourceNodeId(srcNodeId.value()); + m_agents[agentId]->setSourceNodeId(srcNodeId.value()); } } } - template - requires(is_numeric_v) + template + requires(is_numeric_v) template requires(is_agent_v && ...) - void Dynamics::addAgents(Tn... agents) {} + void Dynamics::addAgents(Tn... agents) {} - template - requires(is_numeric_v) + template + requires(is_numeric_v) template requires(is_agent_v && (is_agent_v && ...)) - void Dynamics::addAgents(T1 agent, Tn... agents) { + void Dynamics::addAgents(T1 agent, Tn... agents) { addAgent(agent); addAgents(agents...); } - template - requires(is_numeric_v) - void Dynamics::addAgents(std::span> agents) { + template + requires(is_numeric_v) + void Dynamics::addAgents(std::span> agents) { if (this->m_agents.size() + agents.size() > this->m_graph.maxCapacity()) { throw std::overflow_error(buildLog( - std::format("Graph its already holding the max possible number of agents ({})", + std::format("Graph is already holding the max possible number of agents ({})", this->m_graph.maxCapacity()))); } std::ranges::for_each(agents, [this](const auto& agent) -> void { @@ -1015,64 +990,65 @@ namespace dsm { }); } - template - requires(is_numeric_v) - void Dynamics::addAgentsUniformly(Size nAgents, std::optional itineraryId) { - if (this->m_agents.size() + nAgents > this->m_graph.maxCapacity()) { + template + requires(is_numeric_v) + void Dynamics::addAgentsUniformly(Size nAgents, + std::optional itineraryId) { + if (m_agents.size() + nAgents > m_graph.maxCapacity()) { throw std::overflow_error(buildLog( - std::format("Graph its already holding the max possible number of agents ({})", + std::format("Graph is already holding the max possible number of agents ({})", this->m_graph.maxCapacity()))); } - if (this->m_itineraries.empty()) { + if (m_itineraries.empty()) { // TODO: make this possible for random agents throw std::invalid_argument( buildLog("It is not possible to add random agents without itineraries.")); } const bool randomItinerary{!itineraryId.has_value()}; std::uniform_int_distribution itineraryDist{ - 0, static_cast(this->m_itineraries.size() - 1)}; + 0, static_cast(m_itineraries.size() - 1)}; std::uniform_int_distribution streetDist{ - 0, static_cast(this->m_graph.streetSet().size() - 1)}; + 0, static_cast(m_graph.streetSet().size() - 1)}; for (Size i{0}; i < nAgents; ++i) { if (randomItinerary) { - auto itineraryIt{this->m_itineraries.begin()}; - std::advance(itineraryIt, itineraryDist(this->m_generator)); + auto itineraryIt{m_itineraries.begin()}; + std::advance(itineraryIt, itineraryDist(m_generator)); itineraryId = itineraryIt->first; } Id agentId{0}; - if (!this->m_agents.empty()) { - agentId = this->m_agents.rbegin()->first + 1; + if (!m_agents.empty()) { + agentId = m_agents.rbegin()->first + 1; } Id streetId{0}; do { // I dunno why this works and the following doesn't - const auto& streetSet = this->m_graph.streetSet(); + const auto& streetSet = m_graph.streetSet(); auto streetIt = streetSet.begin(); // auto streetIt = this->m_graph->streetSet().begin(); - Size step = streetDist(this->m_generator); + Size step = streetDist(m_generator); std::advance(streetIt, step); streetId = streetIt->first; - } while (this->m_graph.streetSet()[streetId]->isFull()); - const auto& street{this->m_graph.streetSet()[streetId]}; - Agent agent{agentId, itineraryId.value(), street->nodePair().first}; + } while (m_graph.streetSet()[streetId]->isFull()); + const auto& street{m_graph.streetSet()[streetId]}; + Agent agent{agentId, itineraryId.value(), street->nodePair().first}; agent.setStreetId(streetId); this->addAgent(agent); this->setAgentSpeed(agentId); - this->m_agents[agentId]->incrementDelay( - std::ceil(street->length() / this->m_agents[agentId]->speed())); + m_agents[agentId]->incrementDelay( + std::ceil(street->length() / m_agents[agentId]->speed())); street->addAgent(agentId); ++agentId; } } - template - requires(is_numeric_v) + template + requires(is_numeric_v) template requires(std::is_same_v> || std::is_same_v>) - void Dynamics::addAgentsRandomly(Size nAgents, - const TContainer& src_weights, - const TContainer& dst_weights) { + void Dynamics::addAgentsRandomly(Size nAgents, + const TContainer& src_weights, + const TContainer& dst_weights) { if (src_weights.size() == 1 && dst_weights.size() == 1 && src_weights.begin()->first == dst_weights.begin()->first) { throw std::invalid_argument(buildLog( @@ -1149,58 +1125,58 @@ namespace dsm { } } - template - requires(is_numeric_v) - void Dynamics::removeAgent(Size agentId) { + template + requires(is_numeric_v) + void Dynamics::removeAgent(Size agentId) { m_agents.erase(agentId); } - template - requires(is_numeric_v) + template + requires(is_numeric_v) template requires(std::is_convertible_v && (std::is_convertible_v && ...)) - void Dynamics::removeAgents(T1 id, Tn... ids) { + void Dynamics::removeAgents(T1 id, Tn... ids) { removeAgent(id); removeAgents(ids...); } - template - requires(is_numeric_v) - void Dynamics::addItinerary(const Itinerary& itinerary) { + template + requires(is_numeric_v) + void Dynamics::addItinerary(const Itinerary& itinerary) { m_itineraries.emplace(itinerary.id(), std::make_unique(itinerary)); } - template - requires(is_numeric_v) - void Dynamics::addItinerary(std::unique_ptr itinerary) { + template + requires(is_numeric_v) + void Dynamics::addItinerary(std::unique_ptr itinerary) { m_itineraries.emplace(itinerary->id(), std::move(itinerary)); } - template - requires(is_numeric_v) + template + requires(is_numeric_v) template requires(is_itinerary_v && ...) - void Dynamics::addItineraries(Tn... itineraries) { + void Dynamics::addItineraries(Tn... itineraries) { (this->addItinerary(itineraries), ...); } - template - requires(is_numeric_v) - void Dynamics::addItineraries(std::span itineraries) { + template + requires(is_numeric_v) + void Dynamics::addItineraries(std::span itineraries) { std::ranges::for_each(itineraries, [this](const auto& itinerary) -> void { - this->m_itineraries.insert(std::make_unique(itinerary)); + m_itineraries.insert(std::make_unique(itinerary)); }); } - template - requires(is_numeric_v) - void Dynamics::resetTime() { + template + requires(is_numeric_v) + void Dynamics::resetTime() { m_time = 0; } - template - requires(is_numeric_v) - Measurement Dynamics::agentMeanSpeed() const { + template + requires(is_numeric_v) + Measurement Dynamics::agentMeanSpeed() const { if (m_agents.size() == 0) { return Measurement(0., 0.); } @@ -1225,9 +1201,9 @@ namespace dsm { return Measurement(mean, std::sqrt(variance)); } - template - requires(is_numeric_v) - Measurement Dynamics::streetMeanDensity(bool normalized) const { + template + requires(is_numeric_v) + Measurement Dynamics::streetMeanDensity(bool normalized) const { if (m_graph.streetSet().size() == 0) { return Measurement(0., 0.); } @@ -1239,9 +1215,9 @@ namespace dsm { return Measurement(densities); } - template - requires(is_numeric_v) - Measurement Dynamics::streetMeanFlow() const { + template + requires(is_numeric_v) + Measurement Dynamics::streetMeanFlow() const { std::vector flows; flows.reserve(m_graph.streetSet().size()); for (const auto& [streetId, street] : m_graph.streetSet()) { @@ -1250,10 +1226,10 @@ namespace dsm { return Measurement(flows); } - template - requires(is_numeric_v) - Measurement Dynamics::streetMeanFlow(double threshold, - bool above) const { + template + requires(is_numeric_v) + Measurement Dynamics::streetMeanFlow(double threshold, + bool above) const { std::vector flows; flows.reserve(m_graph.streetSet().size()); for (const auto& [streetId, street] : m_graph.streetSet()) { @@ -1266,9 +1242,9 @@ namespace dsm { return Measurement(flows); } - template - requires(is_numeric_v) - Measurement Dynamics::meanSpireInputFlow(bool resetValue) { + template + requires(is_numeric_v) + Measurement Dynamics::meanSpireInputFlow(bool resetValue) { auto deltaTime{m_time - m_previousSpireTime}; if (deltaTime == 0) { return Measurement(0., 0.); @@ -1285,9 +1261,9 @@ namespace dsm { return Measurement(flows); } - template - requires(is_numeric_v) - Measurement Dynamics::meanSpireOutputFlow(bool resetValue) { + template + requires(is_numeric_v) + Measurement Dynamics::meanSpireOutputFlow(bool resetValue) { auto deltaTime{m_time - m_previousSpireTime}; if (deltaTime == 0) { return Measurement(0., 0.); @@ -1304,9 +1280,9 @@ namespace dsm { return Measurement(flows); } - template - requires(is_numeric_v) - Measurement Dynamics::meanTravelTime(bool clearData) { + template + requires(is_numeric_v) + Measurement Dynamics::meanTravelTime(bool clearData) { if (m_travelTimes.size() == 0) { return Measurement(0., 0.); } @@ -1325,9 +1301,9 @@ namespace dsm { return Measurement(mean, std::sqrt(variance)); } - template - requires(is_numeric_v) - std::unordered_map> Dynamics::turnProbabilities( + template + requires(is_numeric_v) + std::unordered_map> Dynamics::turnProbabilities( bool reset) { std::unordered_map> res; for (auto& [streetId, counts] : m_turnCounts) { @@ -1348,16 +1324,16 @@ namespace dsm { return res; } - template - requires(std::unsigned_integral) - class FirstOrderDynamics : public Dynamics { + template + requires(std::unsigned_integral) + class FirstOrderDynamics : public Dynamics { double m_speedFluctuationSTD; public: /// @brief Construct a new First Order Dynamics object /// @param graph, The graph representing the network FirstOrderDynamics(Graph& graph) - : Dynamics(graph), m_speedFluctuationSTD{0.} {}; + : Dynamics(graph), m_speedFluctuationSTD{0.} {}; /// @brief Set the speed of an agent /// @param agentId The id of the agent /// @throw std::invalid_argument, If the agent is not found @@ -1383,25 +1359,24 @@ namespace dsm { Measurement streetMeanSpeed(double threshold, bool above) const override; }; - template - requires(std::unsigned_integral) - void FirstOrderDynamics::setAgentSpeed(Size agentId) { + template + requires(std::unsigned_integral) + void FirstOrderDynamics::setAgentSpeed(Size agentId) { const auto& agent{this->m_agents[agentId]}; const auto& street{this->m_graph.streetSet()[agent->streetId().value()]}; double speed{street->maxSpeed() * (1. - this->m_minSpeedRateo * street->density(true))}; - if (this->m_speedFluctuationSTD > 0.) { - std::normal_distribution speedDist{speed, - speed * this->m_speedFluctuationSTD}; + if (m_speedFluctuationSTD > 0.) { + std::normal_distribution speedDist{speed, speed * m_speedFluctuationSTD}; speed = speedDist(this->m_generator); } speed < 0. ? agent->setSpeed(street->maxSpeed() * (1. - this->m_minSpeedRateo)) : agent->setSpeed(speed); } - template - requires(std::unsigned_integral) - void FirstOrderDynamics::setSpeedFluctuationSTD(double speedFluctuationSTD) { + template + requires(std::unsigned_integral) + void FirstOrderDynamics::setSpeedFluctuationSTD(double speedFluctuationSTD) { if (speedFluctuationSTD < 0.) { throw std::invalid_argument( buildLog("The speed fluctuation standard deviation must be positive.")); @@ -1409,9 +1384,9 @@ namespace dsm { m_speedFluctuationSTD = speedFluctuationSTD; } - template - requires(std::unsigned_integral) - double FirstOrderDynamics::streetMeanSpeed(Id streetId) const { + template + requires(std::unsigned_integral) + double FirstOrderDynamics::streetMeanSpeed(Id streetId) const { const auto& street{this->m_graph.streetSet().at(streetId)}; if (street->nAgents() == 0) { return street->maxSpeed(); @@ -1457,9 +1432,9 @@ namespace dsm { return meanSpeed / n; } - template - requires(std::unsigned_integral) - Measurement FirstOrderDynamics::streetMeanSpeed() const { + template + requires(std::unsigned_integral) + Measurement FirstOrderDynamics::streetMeanSpeed() const { if (this->m_agents.size() == 0) { return Measurement(0., 0.); } @@ -1470,10 +1445,10 @@ namespace dsm { } return Measurement(speeds); } - template - requires(std::unsigned_integral) - Measurement FirstOrderDynamics::streetMeanSpeed(double threshold, - bool above) const { + template + requires(std::unsigned_integral) + Measurement FirstOrderDynamics::streetMeanSpeed(double threshold, + bool above) const { if (this->m_agents.size() == 0) { return Measurement(0., 0.); } diff --git a/src/dsm/headers/Graph.cpp b/src/dsm/headers/Graph.cpp index 3b117e68a..2ff9f208f 100644 --- a/src/dsm/headers/Graph.cpp +++ b/src/dsm/headers/Graph.cpp @@ -72,6 +72,14 @@ namespace dsm { } intersection.setStreetPriorities(newStreetPriorities); } + if (node->isTrafficLight()) { + auto& trafficLight = dynamic_cast(*node); + std::unordered_map> newCycles; + for (auto const& [streetId, cycles] : trafficLight.cycles()) { + newCycles.emplace(newStreetIds[streetId], std::move(cycles)); + } + trafficLight.setCycles(newCycles); + } } } @@ -419,6 +427,17 @@ namespace dsm { m_nodes.emplace(std::make_pair(node.id(), std::make_unique(node))); } + TrafficLight& Graph::makeTrafficLight(Id const nodeId, + Delay const cycleTime, + Delay const counter) { + if (!m_nodes.contains(nodeId)) { + throw std::invalid_argument(buildLog("Node does not exist.")); + } + auto& pNode = m_nodes[nodeId]; + pNode = std::make_unique(*pNode, cycleTime, counter); + return dynamic_cast(*pNode); + } + Roundabout& Graph::makeRoundabout(Id nodeId) { if (!m_nodes.contains(nodeId)) { throw std::invalid_argument(buildLog("Node does not exist.")); diff --git a/src/dsm/headers/Graph.hpp b/src/dsm/headers/Graph.hpp index 02c77ceaa..e551444cc 100644 --- a/src/dsm/headers/Graph.hpp +++ b/src/dsm/headers/Graph.hpp @@ -161,13 +161,14 @@ namespace dsm { void addNodes(T1&& node, Tn&&... nodes); /// @brief Convert an existing node to a traffic light - /// @tparam Delay The type of the traffic light's delay /// @param nodeId The id of the node to convert to a traffic light + /// @param cycleTime The traffic light's cycle time + /// @param counter The traffic light's counter initial value. Default is 0 /// @return A reference to the traffic light /// @throws std::invalid_argument if the node does not exist - template - requires(std::unsigned_integral) - TrafficLight& makeTrafficLight(Id nodeId); + TrafficLight& makeTrafficLight(Id const nodeId, + Delay const cycleTime, + Delay const counter = 0); /// @brief Convert an existing node into a roundabout /// @param nodeId The id of the node to convert to a roundabout /// @return A reference to the roundabout @@ -282,15 +283,4 @@ namespace dsm { addStreets(std::forward(streets)...); } - template - requires(std::unsigned_integral) - TrafficLight& Graph::makeTrafficLight(Id nodeId) { - if (!m_nodes.contains(nodeId)) { - throw std::invalid_argument(buildLog("Node does not exist.")); - } - auto& pNode = m_nodes[nodeId]; - pNode = std::make_unique>(*pNode); - return dynamic_cast&>(*pNode); - } - }; // namespace dsm diff --git a/src/dsm/headers/Node.cpp b/src/dsm/headers/Node.cpp index 46633d9ff..854fa99b1 100644 --- a/src/dsm/headers/Node.cpp +++ b/src/dsm/headers/Node.cpp @@ -2,6 +2,7 @@ #include "Node.hpp" #include +#include namespace dsm { @@ -49,6 +50,162 @@ namespace dsm { return copy; } + bool TrafficLightCycle::isGreen(Delay const cycleTime, Delay const counter) const { + auto const rest{(m_phase + m_greenTime) / cycleTime}; + if (rest) { + return (counter < rest) || (counter >= m_phase); + } + return (counter >= m_phase) && (counter < m_phase + m_greenTime); + } + + void TrafficLight::setCycle(Id const streetId, + Direction direction, + TrafficLightCycle const& cycle) { + if ((cycle.greenTime() > m_cycleTime)) { + throw std::invalid_argument(buildLog("Green time must not exceed the cycle time.")); + } + if (!(cycle.phase() < m_cycleTime)) { + throw std::invalid_argument(buildLog("Phase must be less than the cycle time.")); + } + if (direction == Direction::UTURN) { + direction = Direction::LEFT; + } + if (!m_cycles.contains(streetId)) { + TrafficLightCycle defaultCycle(m_cycleTime, 0); + std::vector cycles{defaultCycle, defaultCycle, defaultCycle}; + m_cycles.emplace(streetId, cycles); + } + switch (direction) { + case Direction::RIGHTANDSTRAIGHT: + m_cycles[streetId][Direction::RIGHT] = cycle; + m_cycles[streetId][Direction::STRAIGHT] = cycle; + break; + case Direction::LEFTANDSTRAIGHT: + m_cycles[streetId][Direction::LEFT] = cycle; + m_cycles[streetId][Direction::STRAIGHT] = cycle; + break; + case Direction::ANY: + m_cycles[streetId][Direction::RIGHT] = cycle; + m_cycles[streetId][Direction::STRAIGHT] = cycle; + m_cycles[streetId][Direction::LEFT] = cycle; + break; + default: + m_cycles[streetId][direction] = cycle; + break; + } + } + + void TrafficLightCycle::reset() { + m_greenTime = m_defaultValues.first; + m_phase = m_defaultValues.second; + } + + void TrafficLight::setComplementaryCycle(Id const streetId, Id const existingCycle) { + if (m_cycles.contains(streetId)) { + throw std::invalid_argument(buildLog("Street id already exists.")); + } + if (!m_cycles.contains(existingCycle)) { + throw std::invalid_argument(buildLog("Cycle does not exist.")); + } + m_cycles.emplace(streetId, m_cycles.at(existingCycle)); + for (auto& cycle : m_cycles.at(streetId)) { + cycle = TrafficLightCycle(m_cycleTime - cycle.greenTime(), + cycle.phase() + m_cycleTime - cycle.greenTime()); + } + } + + void TrafficLight::moveCycle(Id const oldStreetId, Id const newStreetId) { + if (!m_cycles.contains(oldStreetId)) { + throw std::invalid_argument(buildLog("Old street id does not exist.")); + } + auto handler{m_cycles.extract(oldStreetId)}; + handler.key() = newStreetId; + m_cycles.insert(std::move(handler)); + } + + TrafficLight& TrafficLight::operator++() { + m_counter = (m_counter + 1) % m_cycleTime; + return *this; + } + + Delay TrafficLight::maxGreenTime(bool priorityStreets) const { + Delay maxTime{0}; + for (auto const& [streetId, cycles] : m_cycles) { + if (priorityStreets && m_streetPriorities.contains(streetId)) { + for (auto const& cycle : cycles) { + maxTime = std::max(maxTime, cycle.greenTime()); + } + } else { + for (auto const& cycle : cycles) { + maxTime = std::max(maxTime, cycle.greenTime()); + } + } + } + return maxTime; + } + + void TrafficLight::increaseGreenTimes(Delay const delta) { + for (auto& [streetId, cycles] : m_cycles) { + if (m_streetPriorities.contains(streetId)) { + for (auto& cycle : cycles) { + cycle = TrafficLightCycle(cycle.greenTime() + delta, cycle.phase()); + } + } else { + for (auto& cycle : cycles) { + cycle = TrafficLightCycle(cycle.greenTime() - delta, cycle.phase() + delta); + } + } + } + } + + void TrafficLight::decreaseGreenTimes(Delay const delta) { + for (auto& [streetId, cycles] : m_cycles) { + if (!m_streetPriorities.contains(streetId)) { + for (auto& cycle : cycles) { + cycle = TrafficLightCycle(cycle.greenTime() + delta, cycle.phase()); + } + } else { + for (auto& cycle : cycles) { + cycle = TrafficLightCycle(cycle.greenTime() - delta, cycle.phase() + delta); + } + } + } + } + + bool TrafficLight::isGreen(Id const streetId, Direction direction) const { + if (!m_cycles.contains(streetId)) { + throw std::invalid_argument(buildLog( + std::format("Street id {} is not valid for node {}.", streetId, id()))); + } + switch (direction) { + case Direction::UTURN: + direction = Direction::LEFT; + break; + case Direction::RIGHTANDSTRAIGHT: + return m_cycles.at(streetId)[Direction::RIGHT].isGreen(m_cycleTime, m_counter) && + m_cycles.at(streetId)[Direction::STRAIGHT].isGreen(m_cycleTime, m_counter); + case Direction::LEFTANDSTRAIGHT: + return m_cycles.at(streetId)[Direction::LEFT].isGreen(m_cycleTime, m_counter) && + m_cycles.at(streetId)[Direction::STRAIGHT].isGreen(m_cycleTime, m_counter); + case Direction::ANY: + return m_cycles.at(streetId)[Direction::RIGHT].isGreen(m_cycleTime, m_counter) && + m_cycles.at(streetId)[Direction::STRAIGHT].isGreen(m_cycleTime, + m_counter) && + m_cycles.at(streetId)[Direction::LEFT].isGreen(m_cycleTime, m_counter); + default: + break; + } + return m_cycles.at(streetId)[direction].isGreen(m_cycleTime, m_counter); + } + + void TrafficLight::resetCycles() { + for (auto& [streetId, cycles] : m_cycles) { + for (auto& cycle : cycles) { + cycle.reset(); + } + } + } + Roundabout::Roundabout(const Node& node) : Node{node.id()} { if (node.coords().has_value()) { this->setCoords(node.coords().value()); diff --git a/src/dsm/headers/Node.hpp b/src/dsm/headers/Node.hpp index 9e4b06ef6..53476e7d2 100644 --- a/src/dsm/headers/Node.hpp +++ b/src/dsm/headers/Node.hpp @@ -36,7 +36,6 @@ namespace dsm { Size m_transportCapacity; public: - Node() = default; /// @brief Construct a new Node object with capacity 1 /// @param id The node's id explicit Node(Id id) : m_id{id}, m_capacity{1}, m_transportCapacity{1} {} @@ -45,6 +44,12 @@ namespace dsm { /// @param coords A std::pair containing the node's coordinates (lat, lon) Node(Id id, std::pair coords) : m_id{id}, m_coords{std::move(coords)}, m_capacity{1}, m_transportCapacity{1} {} + + Node(Node const& other) + : m_id{other.m_id}, + m_coords{other.m_coords}, + m_capacity{other.m_capacity}, + m_transportCapacity{other.m_transportCapacity} {}; virtual ~Node() = default; /// @brief Set the node's id @@ -99,6 +104,8 @@ namespace dsm { /// @param coords A std::pair containing the node's coordinates Intersection(Id id, std::pair coords) : Node{id, coords} {}; + Intersection(Node const& node) : Node{node} {}; + virtual ~Intersection() = default; /// @brief Set the node's capacity @@ -155,200 +162,102 @@ namespace dsm { /// since the last time this function was called. It also resets the counter. Size agentCounter(); - virtual bool isIntersection() const noexcept override final { return true; } + bool isIntersection() const noexcept final { return true; } }; - template - requires(std::unsigned_integral) - class TrafficLight : public Intersection { + class TrafficLightCycle { private: - std::optional> m_leftTurnRatio; - std::optional> m_delay; - Delay m_counter; + Delay m_greenTime; Delay m_phase; + std::pair m_defaultValues; public: - /// @brief Construct a new TrafficLight object - /// @param id The node's id - explicit TrafficLight(Id id) : Intersection{id}, m_counter{0}, m_phase{0} {}; - /// @brief Construct a new TrafficLight object - /// @param node An Intersection object - TrafficLight(const Node& node); - - /// @brief Set the node's delay - /// @details This function is used to set the node's delay. - /// If the delay is already set, the function will check the counter: - /// - if the counter is more than the sum of the new green and red delays, it - /// will be set to the new sum minus one, i.e. one more red cycle. - /// - if the counter is less than the old green delay but more than the new green delay, - /// it will be set to the new green delay minus the difference between the old and the new delay. - /// @param delay The node's delay - void setDelay(Delay delay); - /// @brief Set the node's delay - /// @details This function is used to set the node's delay. - /// If the delay is already set, the function will check the counter: - /// - if the counter is more than the sum of the new green and red delays, it - /// will be set to the new sum minus one, i.e. one more red cycle. - /// - if the counter is less than the old green delay but more than the new green delay, - /// it will be set to the new green delay minus the difference between the old and the new delay. - /// @param delay The node's delay - void setDelay(std::pair delay); - /// @brief Set the node's phase - /// @param phase The node's phase - /// @throw std::runtime_error if the delay is not set - void setPhase(Delay phase); - /// @brief Set the node's left turn ratio - /// @param ratio A std::pair containing the left turn ratio - /// @details ratio.first * greentime is the green time for left turns while ratio.second * redtime is the red time for left turns - /// This is useful for traffic lights when the input street has many lanes and, for example, one resevred for left turns. - void setLeftTurnRatio(std::pair ratio); - /// @brief Set the node's left turn ratio - /// @param first The first component of the left turn ratio - /// @param second The second component of the left turn ratio - void setLeftTurnRatio(double const first, double const second) { - setLeftTurnRatio(std::make_pair(first, second)); - } - /// @brief Set the node's left turn ratio as std::pair(ratio, ratio) - /// @param ratio The left turn ratio - void setLeftTurnRatio(double const ratio) { - setLeftTurnRatio(std::make_pair(ratio, ratio)); - } - /// @brief Increase the node's counter - /// @details This function is used to increase the node's counter - /// when the simulation is running. It automatically resets the counter - /// when it reaches the double of the delay value. - /// @throw std::runtime_error if the delay is not set - void increaseCounter(); - - /// @brief Set the phase of the node after the current red-green cycle has passed - /// @param phase The new node phase - void setPhaseAfterCycle(Delay phase); - - /// @brief Get the node's delay - /// @return std::optional The node's delay - std::optional> delay() const { return m_delay; } - Delay counter() const { return m_counter; } - /// @brief Get the node's left turn ratio - /// @return std::optional> The node's left turn ratio - std::optional> leftTurnRatio() const { - return m_leftTurnRatio; - } + /// @brief Construct a new TrafficLightCycle object + /// @param greenTime Delay, the green time + /// @param phase Delay, the phase + TrafficLightCycle(Delay greenTime, Delay phase) + : m_greenTime{greenTime}, + m_phase{phase}, + m_defaultValues{std::make_pair(m_greenTime, m_phase)} {} + + inline Delay greenTime() const { return m_greenTime; } + inline Delay phase() const { return m_phase; } /// @brief Returns true if the traffic light is green - /// @return bool True if the traffic light is green - bool isGreen() const; - bool isGreen(Id streetId) const; - bool isTrafficLight() const noexcept override { return true; } + /// @param cycleTime Delay, the total time of a red-green cycle + /// @param counter Delay, the current counter + /// @return true if counter < m_phase || (counter >= m_phase + m_greenTime && counter < cycleTime) + bool isGreen(Delay const cycleTime, Delay const counter) const; + /// @brief Reset the green and phase values to the default values + /// @details The default values are the values that the cycle had when it was created + void reset(); }; - template - requires(std::unsigned_integral) - TrafficLight::TrafficLight(const Node& node) - : Intersection{node.id()}, m_counter{0}, m_phase{0} { - if (node.coords().has_value()) { - this->setCoords(node.coords().value()); - } - this->setCapacity(node.capacity()); - } - - template - requires(std::unsigned_integral) - void TrafficLight::setDelay(Delay delay) { - if (m_delay.has_value()) { - if (m_counter >= delay + m_delay.value().second) { - m_counter = delay + m_delay.value().second - 1; - } else if (delay < m_delay.value().first) { - if (m_counter >= delay && m_counter <= m_delay.value().first) { - m_counter = delay - (m_delay.value().first - m_counter); - } - } - } - m_delay = std::make_pair(delay, delay); - } - - template - requires(std::unsigned_integral) - void TrafficLight::setDelay(std::pair delay) { - if (m_delay.has_value()) { - if (m_counter >= delay.first + delay.second) { - m_counter = delay.first + delay.second - 1; - } else if (delay.first < m_delay.value().first) { - if (m_counter >= delay.first && m_counter <= m_delay.value().first) { - m_counter = delay.first - (m_delay.value().first - m_counter); - } - } - } - m_delay = std::move(delay); - } - - template - requires(std::unsigned_integral) - void TrafficLight::setPhase(Delay phase) { - if (!m_delay.has_value()) { - throw std::runtime_error(buildLog("TrafficLight's delay has not been set.")); - } - if (phase > m_delay.value().first + m_delay.value().second) { - phase -= m_delay.value().first + m_delay.value().second; - } - m_counter = phase; - m_phase = 0; - } - - template - requires(std::unsigned_integral) - void TrafficLight::setPhaseAfterCycle(Delay phase) { - if (phase > m_delay.value().first + m_delay.value().second) { - phase -= m_delay.value().first + m_delay.value().second; - } - m_phase = phase; - } + class TrafficLight : public Intersection { + private: + std::unordered_map> m_cycles; + Delay m_cycleTime; // The total time of a red-green cycle + Delay m_counter; - template - requires(std::unsigned_integral) - void TrafficLight::setLeftTurnRatio(std::pair ratio) { - assert((void("Left turn ratio components must be between 0 and 1."), - ratio.first >= 0. && ratio.first <= 1. && ratio.second >= 0. && - ratio.second <= 1.)); - m_leftTurnRatio = std::move(ratio); - } + public: + /// @brief Construct a new TrafficLight object + /// @param id The node's id + /// @param cycleTime The node's cycle time + TrafficLight(Id id, Delay cycleTime) + : Intersection{id}, m_cycleTime{cycleTime}, m_counter{0} {} - template - requires(std::unsigned_integral) - void TrafficLight::increaseCounter() { - if (!m_delay.has_value()) { - throw std::runtime_error(buildLog("TrafficLight's delay has not been set.")); - } - ++m_counter; - if (m_counter == m_delay.value().first + m_delay.value().second) { - if (m_phase != 0) { - m_counter = m_phase; - m_phase = 0; - } else { - m_counter = 0; - } - } - } + TrafficLight(Node const& node, Delay const cycleTime, Delay const counter = 0) + : Intersection{node}, m_cycleTime{cycleTime}, m_counter{counter} {} - template - requires(std::unsigned_integral) - bool TrafficLight::isGreen() const { - if (!m_delay.has_value()) { - throw std::runtime_error(buildLog("TrafficLight's delay has not been set.")); - } - return m_counter < m_delay.value().first; - } + TrafficLight& operator++(); - template - requires(std::unsigned_integral) - bool TrafficLight::isGreen(Id streetId) const { - if (!m_delay.has_value()) { - throw std::runtime_error(buildLog("TrafficLight's delay has not been set.")); + Delay maxGreenTime(bool priorityStreets) const; + /// @brief Get the traffic light's total cycle time + /// @return Delay The traffic light's cycle time + inline Delay cycleTime() const { return m_cycleTime; } + /// @brief Set the cycle for a street and a direction + /// @param streetId The street's id + /// @param direction The direction + /// @param cycle The traffic light cycle + void setCycle(Id const streetId, Direction direction, TrafficLightCycle const& cycle); + /// @brief Set the traffic light's cycles + /// @param cycles std::unordered_map> The traffic light's cycles + inline void setCycles(std::unordered_map> cycles) { + m_cycles = std::move(cycles); } - bool hasPriority{this->streetPriorities().contains(streetId)}; - if (this->isGreen()) { - return hasPriority; + /// @brief Set the complementary cycle for a street + /// @param streetId Id, The street's id + /// @param existingCycle Id, The street's id associated with the existing cycle + /// @throws std::invalid_argument if the street id does not exist + /// @throws std::invalid_argument if the cycle does not exist + /// @details The complementary cycle is a cycle that has as green time the total cycle time minus + /// the green time of the existing cycle. The phase is the total cycle time minus the + /// green time of the existing cycle, plus the phase of the existing cycle. + void setComplementaryCycle(Id const streetId, Id const existingCycle); + /// @brief Move a cycle from one street to another + /// @param oldStreetId Id, the old street id + /// @param newStreetId Id, the new street id + void moveCycle(Id const oldStreetId, Id const newStreetId); + /// @brief Increase the green times of the traffic light for priority streets and decrease the green times for non-priority streets + /// @param delta Delay, the time to increase or decrease the green times + void increaseGreenTimes(Delay const delta); + /// @brief Decrease the green times of the traffic light for priority streets and increase the green times for non-priority streets + /// @param delta Delay, the time to increase or decrease the green times + void decreaseGreenTimes(Delay const delta); + /// @brief Get the traffic light's cycles + /// @return std::unordered_map> const& The traffic light's cycles + inline std::unordered_map> const& cycles() const { + return m_cycles; } - return !hasPriority; - } + /// @brief Returns true if the traffic light is green for a street and a direction + /// @param streetId Id, the street's id + /// @param direction Direction, the direction + /// @return true if the traffic light is green for the street and direction + bool isGreen(Id const streetId, Direction direction) const; + /// @brief Resets all traffic light cycles + /// @details For more info, see @ref TrafficLightCycle::reset() + void resetCycles(); + inline bool isTrafficLight() const noexcept final { return true; } + }; /// @brief The Roundabout class represents a roundabout node in the network. /// @tparam Id The type of the node's id diff --git a/src/dsm/headers/Street.cpp b/src/dsm/headers/Street.cpp index f769e2ee0..276212b05 100644 --- a/src/dsm/headers/Street.cpp +++ b/src/dsm/headers/Street.cpp @@ -15,6 +15,7 @@ namespace dsm { for (auto i{0}; i < street.nLanes(); ++i) { m_exitQueues.push_back(dsm::queue()); } + m_laneMapping = street.laneMapping(); } Street::Street(Id index, std::pair pair) @@ -27,6 +28,7 @@ namespace dsm { m_transportCapacity{1}, m_nLanes{1} { m_exitQueues.push_back(dsm::queue()); + m_laneMapping.emplace_back(Direction::ANY); } Street::Street(Id id, Size capacity, double len, std::pair nodePair) @@ -39,6 +41,7 @@ namespace dsm { m_transportCapacity{1}, m_nLanes{1} { m_exitQueues.push_back(dsm::queue()); + m_laneMapping.emplace_back(Direction::ANY); } Street::Street( @@ -52,6 +55,7 @@ namespace dsm { m_nLanes{1} { this->setMaxSpeed(maxSpeed); m_exitQueues.push_back(dsm::queue()); + m_laneMapping.emplace_back(Direction::ANY); } Street::Street(Id id, @@ -77,6 +81,27 @@ namespace dsm { for (auto i{0}; i < nLanes; ++i) { m_exitQueues.push_back(dsm::queue()); } + switch (nLanes) { + case 1: + m_laneMapping.emplace_back(Direction::ANY); + break; + case 2: + m_laneMapping.emplace_back(Direction::RIGHTANDSTRAIGHT); + m_laneMapping.emplace_back(Direction::LEFT); + break; + case 3: + m_laneMapping.emplace_back(Direction::RIGHT); + m_laneMapping.emplace_back(Direction::STRAIGHT); + m_laneMapping.emplace_back(Direction::LEFT); + break; + default: + m_laneMapping.emplace_back(Direction::RIGHT); + for (auto i{1}; i < nLanes - 1; ++i) { + m_laneMapping.emplace_back(Direction::STRAIGHT); + } + m_laneMapping.emplace_back(Direction::LEFT); + break; + } } void Street::setLength(double len) { diff --git a/src/dsm/headers/Street.hpp b/src/dsm/headers/Street.hpp index d3a215963..c1a9e6ba0 100644 --- a/src/dsm/headers/Street.hpp +++ b/src/dsm/headers/Street.hpp @@ -34,6 +34,7 @@ namespace dsm { class Street { private: std::vector> m_exitQueues; + std::vector m_laneMapping; std::set m_waitingAgents; std::pair m_nodePair; double m_len; @@ -108,7 +109,7 @@ namespace dsm { void setLength(double len); /// @brief Set the street's queue /// @param queue The street's queue - void setQueue(dsm::queue queue, size_t index) { + inline void setQueue(dsm::queue queue, size_t index) { m_exitQueues[index] = std::move(queue); } /// @brief Set the street's node pair @@ -197,6 +198,8 @@ namespace dsm { /// @return double The delta angle between the street and the previous street double deltaAngle(double const previousStreetAngle) const; + inline std::vector const& laneMapping() const { return m_laneMapping; } + virtual void addAgent(Id agentId); /// @brief Add an agent to the street's queue /// @param agentId The id of the agent to add to the street's queue diff --git a/src/dsm/utility/TypeTraits/is_node.hpp b/src/dsm/utility/TypeTraits/is_node.hpp index 4644500ce..8fd74e1e6 100644 --- a/src/dsm/utility/TypeTraits/is_node.hpp +++ b/src/dsm/utility/TypeTraits/is_node.hpp @@ -10,8 +10,6 @@ namespace dsm { class Intersection; - template - requires(std::unsigned_integral) class TrafficLight; class Roundabout; @@ -45,17 +43,17 @@ namespace dsm { template <> struct is_node> : std::true_type {}; - template - struct is_node> : std::true_type {}; + template <> + struct is_node : std::true_type {}; - template - struct is_node> : std::true_type {}; + template <> + struct is_node : std::true_type {}; - template - struct is_node&> : std::true_type {}; + template <> + struct is_node : std::true_type {}; - template - struct is_node>> : std::true_type {}; + template <> + struct is_node> : std::true_type {}; template <> struct is_node : std::true_type {}; diff --git a/src/dsm/utility/Typedef.hpp b/src/dsm/utility/Typedef.hpp index b5f1a7345..f962a1055 100644 --- a/src/dsm/utility/Typedef.hpp +++ b/src/dsm/utility/Typedef.hpp @@ -7,5 +7,18 @@ namespace dsm { using Id = uint32_t; using Size = uint32_t; + using Delay = uint16_t; + using Time = uint64_t; + + enum Direction : uint8_t { + RIGHT = 0, // delta < 0 + STRAIGHT = 1, // delta == 0 + LEFT = 2, // delta > 0 + UTURN = 3, // std::abs(delta) > std::numbers::pi + RIGHTANDSTRAIGHT = 4, + LEFTANDSTRAIGHT = 5, + ANY = 6 + }; + enum class TrafficLightOptimization : uint8_t { SINGLE_TAIL = 0, DOUBLE_TAIL = 1 }; }; // namespace dsm diff --git a/test/Test_dynamics.cpp b/test/Test_dynamics.cpp index 3aa49ac5c..b199e2cdd 100644 --- a/test/Test_dynamics.cpp +++ b/test/Test_dynamics.cpp @@ -16,7 +16,7 @@ using SpireStreet = dsm::SpireStreet; using Agent = dsm::Agent; using Itinerary = dsm::Itinerary; using Intersection = dsm::Intersection; -using TrafficLight = dsm::TrafficLight; +using TrafficLight = dsm::TrafficLight; using Roundabout = dsm::Roundabout; using Measurement = dsm::Measurement; @@ -72,10 +72,11 @@ TEST_CASE("Dynamics") { } } WHEN("We transform a node into a traffic light and create the dynamics") { - graph.makeTrafficLight(0); + auto& tl = graph.makeTrafficLight(0, 2); Dynamics dynamics(graph); THEN("The node is a traffic light") { CHECK(dynamics.graph().nodeSet().at(0)->isTrafficLight()); + CHECK_EQ(tl.cycleTime(), 2); } } WHEN("We transform a node into a roundabout and create the dynamics") { @@ -546,14 +547,15 @@ TEST_CASE("Dynamics") { GIVEN( "A dynamics object, a network with traffic lights, an itinerary and " "an agent") { - TrafficLight tl{1}; - tl.setDelay(2); + TrafficLight tl{1, 4}; Street s1{1, 1, 30., 15., std::make_pair(0, 1)}; Street s2{7, 1, 30., 15., std::make_pair(1, 2)}; - Street s3{2, 1, 30., 15., std::make_pair(3, 1)}; - Street s4{3, 1, 30., 15., std::make_pair(1, 4)}; - tl.addStreetPriority(1); - tl.addStreetPriority(7); + Street s3{16, 1, 30., 15., std::make_pair(3, 1)}; + Street s4{9, 1, 30., 15., std::make_pair(1, 4)}; + tl.setCycle(1, dsm::Direction::RIGHT, {2, 0}); + tl.setCycle(7, dsm::Direction::RIGHT, {2, 0}); + tl.setCycle(16, dsm::Direction::RIGHT, {2, 2}); + tl.setCycle(9, dsm::Direction::RIGHT, {2, 2}); Graph graph2; graph2.addNode(std::make_unique(tl)); graph2.addStreets(s1, s2, s3, s4); @@ -601,21 +603,27 @@ TEST_CASE("Dynamics") { Street s1_4{9, 1, 30., 15., std::make_pair(1, 4)}; Graph graph2; + { + auto tl = TrafficLight{1, 6}; + tl.setCycle(1, dsm::Direction::RIGHTANDSTRAIGHT, {2, 2}); + tl.setCycle(1, dsm::Direction::LEFT, {1, 4}); + tl.setCycle(11, dsm::Direction::ANY, {3, 2}); + tl.setComplementaryCycle(8, 11); + tl.setComplementaryCycle(21, 11); + tl.setCoords({0., 0.}); + + graph2.addNode(std::make_unique(tl)); + } graph2.addStreets(s0_1, s1_0, s1_2, s2_1, s3_1, s1_3, s4_1, s1_4); graph2.buildAdj(); - auto& tl = graph2.makeTrafficLight(1); graph2.adjustNodeCapacities(); graph2.normalizeStreetCapacities(); auto const& nodes = graph2.nodeSet(); - tl.setDelay(3); - tl.setLeftTurnRatio(0.3); - tl.setPhase(2); - tl.addStreetPriority(1); - tl.setCoords({0., 0.}); - nodes.at(0)->setCoords({-1., 0.}); - nodes.at(2)->setCoords({1., 0.}); - nodes.at(3)->setCoords({0., -1.}); - nodes.at(4)->setCoords({0., 1.}); + auto& tl = dynamic_cast(*nodes.at(1)); + nodes.at(0)->setCoords({0., -1.}); + nodes.at(2)->setCoords({0., 1.}); + nodes.at(3)->setCoords({-1., 0.}); + nodes.at(4)->setCoords({1., 0.}); graph2.buildStreetAngles(); Dynamics dynamics{graph2}; @@ -624,26 +632,27 @@ TEST_CASE("Dynamics") { std::vector destinationNodes{0, 2, 3, 4}; dynamics.setDestinationNodes(destinationNodes); - CHECK(tl.leftTurnRatio().has_value()); - WHEN("We add agents and make the system evolve") { Agent agent1{0, 2, 0}; Agent agent2{1, 4, 0}; dynamics.addAgents(agent1, agent2); - dynamics.evolve(false); - dynamics.evolve(false); + dynamics.evolve(false); // Counter 0 + THEN("The agents are not yet on the streets") { + CHECK_FALSE(dynamics.agents().at(0)->streetId().has_value()); + CHECK_FALSE(dynamics.agents().at(1)->streetId().has_value()); + } + dynamics.evolve(false); // Counter 1 THEN("The agents are correctly placed") { CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 1); CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 1); } - dynamics.evolve(false); - dynamics.evolve(false); - dynamics.evolve(false); + dynamics.evolve(false); // Counter 2 + dynamics.evolve(false); // Counter 3 THEN("The agent 0 passes and agent 1 waits") { CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 7); CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 1); } - dynamics.evolve(false); + dynamics.evolve(false); // Counter 4 THEN("The agent 1 passes") { CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 7); CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 9); @@ -665,23 +674,28 @@ TEST_CASE("Dynamics") { Street s1_4{9, 1, 30., 15., std::make_pair(1, 4)}; Graph graph2; + { + auto tl = TrafficLight{1, 6}; + // Now testing red light = NO PHASE + tl.setCycle(1, dsm::Direction::RIGHTANDSTRAIGHT, {2, 0}); + tl.setCycle(1, dsm::Direction::LEFT, {1, 2}); + tl.setCycle(11, dsm::Direction::ANY, {3, 0}); + tl.setComplementaryCycle(8, 11); + tl.setComplementaryCycle(21, 11); + tl.setCoords({0., 0.}); + + graph2.addNode(std::make_unique(tl)); + } graph2.addStreets(s0_1, s1_0, s1_2, s2_1, s3_1, s1_3, s4_1, s1_4); graph2.buildAdj(); - auto& tl = graph2.makeTrafficLight(1); graph2.adjustNodeCapacities(); graph2.normalizeStreetCapacities(); auto const& nodes = graph2.nodeSet(); - tl.setDelay(3); - tl.setLeftTurnRatio(0.3); - // NO! Now testing red light - // tl.setPhase(2); - tl.addStreetPriority(21); - tl.addStreetPriority(8); - tl.setCoords({0., 0.}); - nodes.at(0)->setCoords({-1., 0.}); - nodes.at(2)->setCoords({1., 0.}); - nodes.at(3)->setCoords({0., -1.}); - nodes.at(4)->setCoords({0., 1.}); + auto& tl = dynamic_cast(*nodes.at(1)); + nodes.at(0)->setCoords({0., -1.}); + nodes.at(2)->setCoords({0., 1.}); + nodes.at(3)->setCoords({-1., 0.}); + nodes.at(4)->setCoords({1., 0.}); graph2.buildStreetAngles(); Dynamics dynamics{graph2}; @@ -690,86 +704,103 @@ TEST_CASE("Dynamics") { std::vector destinationNodes{0, 2, 3, 4}; dynamics.setDestinationNodes(destinationNodes); - CHECK(tl.leftTurnRatio().has_value()); - WHEN("We add agents and make the system evolve") { Agent agent1{0, 2, 0}; Agent agent2{1, 4, 0}; dynamics.addAgents(agent1, agent2); - dynamics.evolve(false); - dynamics.evolve(false); + dynamics.evolve(false); // Counter 0 + dynamics.evolve(false); // Counter 1 THEN("The agents are correctly placed") { CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 1); CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 1); } - dynamics.evolve(false); - dynamics.evolve(false); + dynamics.evolve(false); // Counter 2 + dynamics.evolve(false); // Counter 3 + THEN("The agents are still") { + CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 1); + CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 1); + } + dynamics.evolve(false); // Counter 4 + dynamics.evolve(false); // Counter 5 + dynamics.evolve(false); // Counter 0 THEN("The agent 0 passes and agent 1 waits") { CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 7); CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 1); } - dynamics.evolve(false); + dynamics.evolve(false); // Counter 1 + dynamics.evolve(false); // Counter 2 THEN("The agent 1 passes") { - CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 7); CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 9); } } } - } - SUBCASE("Traffic Lights optimization algorithm") { - GIVEN("A dynamics object with a traffic light intersection") { - double length{90.}, max_speed{15.}; - Street s_01{1, 10, length, max_speed, std::make_pair(0, 1)}; - Street s_10{5, 10, length, max_speed, std::make_pair(1, 0)}; - Street s_12{7, 10, length, max_speed, std::make_pair(1, 2)}; - Street s_21{11, 10, length, max_speed, std::make_pair(2, 1)}; - Street s_13{8, 10, length, max_speed, std::make_pair(1, 3)}; - Street s_31{16, 10, length, max_speed, std::make_pair(3, 1)}; - Street s_14{9, 10, length, max_speed, std::make_pair(1, 4)}; - Street s_41{21, 10, length, max_speed, std::make_pair(4, 1)}; - Graph graph2; - graph2.addStreets(s_01, s_10, s_12, s_21, s_13, s_31, s_14, s_41); - graph2.buildAdj(); - auto& tl = graph2.makeTrafficLight(1); - tl.setDelay(4); - tl.setPhase(3); - tl.addStreetPriority(1); - tl.addStreetPriority(11); - Dynamics dynamics{graph2}; - Itinerary it_0{0, 0}, it_1{1, 2}, it_2{2, 3}, it_3{3, 4}; - dynamics.addItinerary(it_0); - dynamics.addItinerary(it_1); - dynamics.addItinerary(it_2); - dynamics.addItinerary(it_3); - dynamics.updatePaths(); - dynamics.addAgents(0, 7, 2); - dynamics.addAgents(1, 7, 0); - dynamics.setDataUpdatePeriod(1); - WHEN("We evolve the dynamics and optimize traffic lights") { - for (int i = 0; i < 8; ++i) { - dynamics.evolve(false); - } - dynamics.optimizeTrafficLights(2, 0.1, 0.); - THEN("Green and red time are different") { - const auto timing = tl.delay().value(); - CHECK(timing.first > timing.second); - } - } - WHEN( - "We evolve the dynamics and optimize traffic lights with outgoing " - "streets " - "full") { - dynamics.addAgents(0, 5, 1); - dynamics.addAgents(1, 5, 1); - dynamics.addAgents(2, 5, 1); - dynamics.addAgents(3, 5, 1); - for (int i = 0; i < 15; ++i) { - dynamics.evolve(false); + SUBCASE("Traffic Lights optimization algorithm") { + GIVEN("A dynamics object with a traffic light intersection") { + double length{90.}, max_speed{15.}; + Street s_01{1, 10, length, max_speed, std::make_pair(0, 1)}; + Street s_10{5, 10, length, max_speed, std::make_pair(1, 0)}; + Street s_12{7, 10, length, max_speed, std::make_pair(1, 2)}; + Street s_21{11, 10, length, max_speed, std::make_pair(2, 1)}; + Street s_13{8, 10, length, max_speed, std::make_pair(1, 3)}; + Street s_31{16, 10, length, max_speed, std::make_pair(3, 1)}; + Street s_14{9, 10, length, max_speed, std::make_pair(1, 4)}; + Street s_41{21, 10, length, max_speed, std::make_pair(4, 1)}; + Graph graph2; + graph2.addStreets(s_01, s_10, s_12, s_21, s_13, s_31, s_14, s_41); + graph2.buildAdj(); + auto& tl = graph2.makeTrafficLight(1, 8, 3); + tl.addStreetPriority(1); + tl.addStreetPriority(11); + tl.setCycle(1, dsm::Direction::ANY, {4, 0}); + tl.setCycle(11, dsm::Direction::ANY, {4, 0}); + tl.setComplementaryCycle(16, 11); + tl.setComplementaryCycle(21, 11); + Dynamics dynamics{graph2}; + std::vector destinationNodes{0, 2, 3, 4}; + dynamics.setDestinationNodes(destinationNodes); + dynamics.addAgents(0, 7, 2); + dynamics.addAgents(2, 7, 0); + dynamics.setDataUpdatePeriod(4); + auto const& cycles{tl.cycles()}; + WHEN("We evolve the dynamics and optimize traffic lights") { + for (int i = 0; i < 9; ++i) { + dynamics.evolve(false); + } + dynamics.optimizeTrafficLights(0.1, 0.); + THEN("Green and red time are different") { + auto sum1{0.}, sum2{0.}; + for (auto const& cycle : cycles.at(1)) { + sum1 += cycle.greenTime(); + } + for (auto const& cycle : cycles.at(16)) { + sum2 += cycle.greenTime(); + } + CHECK(sum1 > sum2); + } } - dynamics.optimizeTrafficLights(2, 0.1, 0.); - THEN("Green and red time are equal") { - const auto timing = tl.delay().value(); - CHECK_EQ(timing.first, timing.second); + dynamics.setDataUpdatePeriod(8); + WHEN( + "We evolve the dynamics and optimize traffic lights with outgoing " + "streets " + "full") { + dynamics.addAgents(0, 5, 1); + dynamics.addAgents(2, 5, 1); + dynamics.addAgents(3, 5, 1); + dynamics.addAgents(4, 5, 1); + for (int i = 0; i < 15; ++i) { + dynamics.evolve(false); + } + dynamics.optimizeTrafficLights(0.1, 0.); + THEN("Green and red time are equal") { + auto sum1{0.}, sum2{0.}; + for (auto const& cycle : cycles.at(1)) { + sum1 += cycle.greenTime(); + } + for (auto const& cycle : cycles.at(16)) { + sum2 += cycle.greenTime(); + } + CHECK_EQ(sum1, sum2); + } } } } diff --git a/test/Test_graph.cpp b/test/Test_graph.cpp index 598b8e014..2dd9b2939 100644 --- a/test/Test_graph.cpp +++ b/test/Test_graph.cpp @@ -230,10 +230,14 @@ TEST_CASE("Graph") { graph.addStreet(Street{1, 1, 1., std::make_pair(0, 1)}); graph.buildAdj(); WHEN("We make node 0 a traffic light") { - graph.makeTrafficLight(0); + auto& tl = graph.makeTrafficLight(0, 60); THEN("The node 0 is a traffic light") { CHECK(graph.nodeSet().at(0)->isTrafficLight()); } + THEN("The traffic light has the correct parameters") { + CHECK_EQ(tl.id(), 0); + CHECK_EQ(tl.cycleTime(), 60); + } } } } diff --git a/test/Test_node.cpp b/test/Test_node.cpp index dfc66c9a9..94f0706f9 100644 --- a/test/Test_node.cpp +++ b/test/Test_node.cpp @@ -6,7 +6,7 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN using Intersection = dsm::Intersection; -using TrafficLight = dsm::TrafficLight; +using TrafficLight = dsm::TrafficLight; TEST_CASE("Intersection") { SUBCASE("Constructor") { @@ -33,115 +33,147 @@ TEST_CASE("Intersection") { TEST_CASE("TrafficLight") { SUBCASE("Constructor") { - /// This tests the constructor that takes an Id. - /// GIVEN: An Id - /// WHEN: A TrafficLight is constructed - /// THEN: The Id is set correctly - TrafficLight trafficLight{1}; - CHECK(trafficLight.id() == 1); - } - SUBCASE("Light cycle") { - /// This tests the light cycle. - /// GIVEN: A TrafficLight - /// WHEN: The light cycle is set - /// THEN: The light cycle is set correctly - TrafficLight trafficLight{0}; - trafficLight.setDelay(1); - CHECK(trafficLight.isGreen()); - trafficLight.increaseCounter(); - CHECK_FALSE(trafficLight.isGreen()); - trafficLight.increaseCounter(); - CHECK(trafficLight.isGreen()); - } - SUBCASE("Ligh cycle 2") { - /// This tests the light cycle. - /// GIVEN: A TrafficLight - /// WHEN: The light cycle is set - /// THEN: The light cycle is set correctly - TrafficLight trafficLight{0}; - trafficLight.setDelay(2); - CHECK(trafficLight.isGreen()); - trafficLight.increaseCounter(); - CHECK(trafficLight.isGreen()); - trafficLight.increaseCounter(); - CHECK_FALSE(trafficLight.isGreen()); - trafficLight.increaseCounter(); - CHECK_FALSE(trafficLight.isGreen()); - trafficLight.increaseCounter(); - CHECK(trafficLight.isGreen()); - } - SUBCASE("Phase") { - /// This tests the phase. - /// GIVEN: A TrafficLight - /// WHEN: The phase is set to update after a green-red cycle - /// THEN: It's checked that the current gree-red cycle is not affected - /// and ultimately it's checked that on the next green-red cycle the phase is updated correctly - TrafficLight trafficLight{0}; - trafficLight.setDelay(std::make_pair(5, 7)); - trafficLight.setPhaseAfterCycle(6); - - for (size_t i = 0; i < 12; ++i) { - trafficLight.increaseCounter(); - } - CHECK_FALSE(trafficLight.isGreen()); - trafficLight.increaseCounter(); - CHECK_FALSE(trafficLight.isGreen()); - - trafficLight.setPhase(0); - CHECK(trafficLight.isGreen()); - - for (size_t i = 0; i < 12; ++i) { - trafficLight.increaseCounter(); + GIVEN("A traffic light object") { + TrafficLight tl{0, 60}; + THEN("The traffic light is created with the correct id and cycle time") { + CHECK_EQ(tl.id(), 0); + CHECK_EQ(tl.cycleTime(), 60); + } } - CHECK(trafficLight.isGreen()); - } - SUBCASE("Asymmetric traffic light") { - /// This tests the asymmetric traffic light. - /// GIVEN: A TrafficLight - /// WHEN: The asymmetric traffic light is set - /// THEN: The asymmetric traffic light is set correctly - TrafficLight trafficLight{0}; - trafficLight.setDelay(std::make_pair(5, 3)); - for (size_t i = 0; i < 8; ++i) { - if (i < 5) { - CHECK(trafficLight.isGreen()); - } else { - CHECK_FALSE(trafficLight.isGreen()); + GIVEN("A node object") { + Intersection intersection(0, std::make_pair(1., 2.)); + WHEN("The node is converted to a traffic light") { + TrafficLight tl(intersection, 60); + THEN("The traffic light is created with correct parameters") { + CHECK_EQ(tl.id(), 0); + CHECK_EQ(tl.cycleTime(), 60); + CHECK(tl.coords().has_value()); + CHECK_EQ(tl.coords().value().first, 1.); + CHECK_EQ(tl.coords().value().second, 2.); + } } - trafficLight.increaseCounter(); } - CHECK(trafficLight.isGreen()); } - SUBCASE("Dynamic traffic light") { - GIVEN("A traffic ligth object with set delay") { - TrafficLight tl{0}; - tl.setDelay(std::make_pair(5, 3)); - WHEN("The delay is set with a green value smaller than the previous one") { - tl.increaseCounter(); - tl.increaseCounter(); - tl.increaseCounter(); - tl.setDelay(std::make_pair(2, 3)); - THEN("It is green for two cycles") { - CHECK(tl.isGreen()); - tl.increaseCounter(); - CHECK(tl.isGreen()); - tl.increaseCounter(); - CHECK_FALSE(tl.isGreen()); + SUBCASE("Light cycle") { + GIVEN("A traffic light object with a cycle set") { + TrafficLight tl{0, 2}; + tl.setCycle(0, dsm::Direction::LEFT, {1, 0}); + WHEN("We increase counter") { + ++tl; + THEN("The traffic light is green for all except Left and U turns") { + CHECK(tl.isGreen(0, dsm::Direction::RIGHT)); + CHECK(tl.isGreen(0, dsm::Direction::STRAIGHT)); + CHECK_FALSE(tl.isGreen(0, dsm::Direction::LEFT)); + CHECK_FALSE(tl.isGreen(0, dsm::Direction::UTURN)); + } + ++tl; + THEN("The traffic light is green for all") { + CHECK(tl.isGreen(0, dsm::Direction::RIGHT)); + CHECK(tl.isGreen(0, dsm::Direction::STRAIGHT)); + CHECK(tl.isGreen(0, dsm::Direction::LEFT)); + CHECK(tl.isGreen(0, dsm::Direction::UTURN)); } } - WHEN("The delay is set with a red value smaller than the previous one") { - tl.increaseCounter(); - tl.increaseCounter(); - tl.increaseCounter(); - tl.increaseCounter(); - tl.increaseCounter(); - tl.setDelay(std::make_pair(1, 3)); - THEN("It is red for one cycles") { - CHECK_FALSE(tl.isGreen()); - tl.increaseCounter(); - CHECK(tl.isGreen()); + } + GIVEN("A traffic light object with a cycle set") { + TrafficLight tl{0, 3}; + tl.setCycle(0, dsm::Direction::RIGHT, {2, 2}); + THEN("Traffic light is green for all") { + CHECK(tl.isGreen(0, dsm::Direction::RIGHT)); + CHECK(tl.isGreen(0, dsm::Direction::STRAIGHT)); + CHECK(tl.isGreen(0, dsm::Direction::LEFT)); + CHECK(tl.isGreen(0, dsm::Direction::UTURN)); + } + WHEN("We increase counter") { + ++tl; + THEN("Traffic light is green for all except Right") { + CHECK_FALSE(tl.isGreen(0, dsm::Direction::RIGHT)); + CHECK(tl.isGreen(0, dsm::Direction::STRAIGHT)); + CHECK(tl.isGreen(0, dsm::Direction::LEFT)); + CHECK(tl.isGreen(0, dsm::Direction::UTURN)); + } + ++tl; + THEN("Traffic light is green for all") { + CHECK(tl.isGreen(0, dsm::Direction::RIGHT)); + CHECK(tl.isGreen(0, dsm::Direction::STRAIGHT)); + CHECK(tl.isGreen(0, dsm::Direction::LEFT)); + CHECK(tl.isGreen(0, dsm::Direction::UTURN)); } } } } + // SUBCASE("Phase") { + // /// This tests the phase. + // /// GIVEN: A TrafficLight + // /// WHEN: The phase is set to update after a green-red cycle + // /// THEN: It's checked that the current gree-red cycle is not affected + // /// and ultimately it's checked that on the next green-red cycle the phase is updated correctly + // TrafficLight trafficLight{0}; + // trafficLight.setDelay(std::make_pair(5, 7)); + // trafficLight.setPhaseAfterCycle(6); + + // for (size_t i = 0; i < 12; ++i) { + // trafficLight.increaseCounter(); + // } + // CHECK_FALSE(trafficLight.isGreen()); + // trafficLight.increaseCounter(); + // CHECK_FALSE(trafficLight.isGreen()); + + // trafficLight.setPhase(0); + // CHECK(trafficLight.isGreen()); + + // for (size_t i = 0; i < 12; ++i) { + // trafficLight.increaseCounter(); + // } + // CHECK(trafficLight.isGreen()); + // } + // SUBCASE("Asymmetric traffic light") { + // /// This tests the asymmetric traffic light. + // /// GIVEN: A TrafficLight + // /// WHEN: The asymmetric traffic light is set + // /// THEN: The asymmetric traffic light is set correctly + // TrafficLight trafficLight{0}; + // trafficLight.setDelay(std::make_pair(5, 3)); + // for (size_t i = 0; i < 8; ++i) { + // if (i < 5) { + // CHECK(trafficLight.isGreen()); + // } else { + // CHECK_FALSE(trafficLight.isGreen()); + // } + // trafficLight.increaseCounter(); + // } + // CHECK(trafficLight.isGreen()); + // } + // SUBCASE("Dynamic traffic light") { + // GIVEN("A traffic ligth object with set delay") { + // TrafficLight tl{0}; + // tl.setDelay(std::make_pair(5, 3)); + // WHEN("The delay is set with a green value smaller than the previous one") { + // tl.increaseCounter(); + // tl.increaseCounter(); + // tl.increaseCounter(); + // tl.setDelay(std::make_pair(2, 3)); + // THEN("It is green for two cycles") { + // CHECK(tl.isGreen()); + // tl.increaseCounter(); + // CHECK(tl.isGreen()); + // tl.increaseCounter(); + // CHECK_FALSE(tl.isGreen()); + // } + // } + // WHEN("The delay is set with a red value smaller than the previous one") { + // tl.increaseCounter(); + // tl.increaseCounter(); + // tl.increaseCounter(); + // tl.increaseCounter(); + // tl.increaseCounter(); + // tl.setDelay(std::make_pair(1, 3)); + // THEN("It is red for one cycles") { + // CHECK_FALSE(tl.isGreen()); + // tl.increaseCounter(); + // CHECK(tl.isGreen()); + // } + // } + // } + // } }