Skip to content

Commit fc68ee9

Browse files
authored
Manage multi-lane Traffic Lights (#211)
* Init multi-lane Traffic Lights * Fix * Moved new functions in Traffic Light class * Add first test for left turn probability * Add second test for left turn probability * Update version
1 parent 1264b84 commit fc68ee9

File tree

6 files changed

+224
-15
lines changed

6 files changed

+224
-15
lines changed

src/dsm/dsm.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
static constexpr uint8_t DSM_VERSION_MAJOR = 2;
88
static constexpr uint8_t DSM_VERSION_MINOR = 1;
9-
static constexpr uint8_t DSM_VERSION_PATCH = 0;
9+
static constexpr uint8_t DSM_VERSION_PATCH = 1;
1010

1111
#define DSM_VERSION \
1212
std::format("{}.{}.{}", DSM_VERSION_MAJOR, DSM_VERSION_MINOR, DSM_VERSION_PATCH)

src/dsm/headers/Dynamics.hpp

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,20 @@ namespace dsm {
475475
}
476476
if (destinationNode->isTrafficLight()) {
477477
auto& tl = dynamic_cast<TrafficLight<Delay>&>(*destinationNode);
478-
if (!tl.isGreen(streetId)) {
478+
if (tl.leftTurnRatio().has_value()) {
479+
auto it = m_agentNextStreetId.find(agentId);
480+
if (it == m_agentNextStreetId.end()) {
481+
if (!tl.isGreen(streetId, 0.)) {
482+
continue;
483+
}
484+
} else {
485+
auto const& nextStreet{m_graph.streetSet()[m_agentNextStreetId[agentId]]};
486+
auto const delta{nextStreet->deltaAngle(pStreet->angle())};
487+
if (!tl.isGreen(streetId, delta)) {
488+
continue;
489+
}
490+
}
491+
} else if (!tl.isGreen(streetId)) {
479492
continue;
480493
}
481494
}
@@ -498,20 +511,15 @@ namespace dsm {
498511
}
499512
return;
500513
}
501-
const auto& nextStreet{m_graph.streetSet()[m_agentNextStreetId[agentId]]};
514+
auto const& nextStreet{m_graph.streetSet()[m_agentNextStreetId[agentId]]};
502515
if (nextStreet->isFull()) {
503516
continue;
504517
}
505518
pStreet->dequeue(queueIndex);
506519
assert(destinationNode->id() == nextStreet->nodePair().first);
507520
if (destinationNode->isIntersection()) {
508521
auto& intersection = dynamic_cast<Intersection&>(*destinationNode);
509-
auto delta = nextStreet->angle() - pStreet->angle();
510-
if (delta > std::numbers::pi) {
511-
delta -= 2 * std::numbers::pi;
512-
} else if (delta < -std::numbers::pi) {
513-
delta += 2 * std::numbers::pi;
514-
}
522+
auto const delta{nextStreet->deltaAngle(pStreet->angle())};
515523
m_increaseTurnCounts(streetId, delta);
516524
intersection.addAgent(delta, agentId);
517525
} else if (destinationNode->isRoundabout()) {
@@ -606,12 +614,7 @@ namespace dsm {
606614
if (nLanes == 1) {
607615
street->enqueue(agentId, 0);
608616
} else {
609-
double deltaAngle{pNextStreet->angle() - street->angle()};
610-
if (deltaAngle > std::numbers::pi) {
611-
deltaAngle -= 2 * std::numbers::pi;
612-
} else if (deltaAngle < -std::numbers::pi) {
613-
deltaAngle += 2 * std::numbers::pi;
614-
}
617+
auto const deltaAngle{pNextStreet->deltaAngle(street->angle())};
615618
if (std::abs(deltaAngle) < std::numbers::pi) {
616619
// Lanes are counted as 0 is the far right lane
617620
if (deltaAngle < 0.) { // Right

src/dsm/headers/Node.hpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <set>
1919
#include <map>
2020
#include <format>
21+
#include <cassert>
2122

2223
#include "../utility/Logger.hpp"
2324
#include "../utility/queue.hpp"
@@ -164,6 +165,7 @@ namespace dsm {
164165
requires(std::unsigned_integral<Delay>)
165166
class TrafficLight : public Intersection {
166167
private:
168+
std::optional<std::pair<double, double>> m_leftTurnRatio;
167169
std::optional<std::pair<Delay, Delay>> m_delay;
168170
Delay m_counter;
169171
Delay m_phase;
@@ -198,6 +200,22 @@ namespace dsm {
198200
/// @param phase The node's phase
199201
/// @throw std::runtime_error if the delay is not set
200202
void setPhase(Delay phase);
203+
/// @brief Set the node's left turn ratio
204+
/// @param ratio A std::pair containing the left turn ratio
205+
/// @details ratio.first * greentime is the green time for left turns while ratio.second * redtime is the red time for left turns
206+
/// This is useful for traffic lights when the input street has many lanes and, for example, one resevred for left turns.
207+
void setLeftTurnRatio(std::pair<double, double> ratio);
208+
/// @brief Set the node's left turn ratio
209+
/// @param first The first component of the left turn ratio
210+
/// @param second The second component of the left turn ratio
211+
inline void setLeftTurnRatio(double const first, double const second) {
212+
setLeftTurnRatio(std::make_pair(first, second));
213+
}
214+
/// @brief Set the node's left turn ratio as std::pair(ratio, ratio)
215+
/// @param ratio The left turn ratio
216+
inline void setLeftTurnRatio(double const ratio) {
217+
setLeftTurnRatio(std::make_pair(ratio, ratio));
218+
}
201219
/// @brief Increase the node's counter
202220
/// @details This function is used to increase the node's counter
203221
/// when the simulation is running. It automatically resets the counter
@@ -213,10 +231,16 @@ namespace dsm {
213231
/// @return std::optional<Delay> The node's delay
214232
std::optional<std::pair<Delay, Delay>> delay() const { return m_delay; }
215233
Delay counter() const { return m_counter; }
234+
/// @brief Get the node's left turn ratio
235+
/// @return std::optional<std::pair<double, double>> The node's left turn ratio
236+
inline std::optional<std::pair<double, double>> leftTurnRatio() const {
237+
return m_leftTurnRatio;
238+
}
216239
/// @brief Returns true if the traffic light is green
217240
/// @return bool True if the traffic light is green
218241
bool isGreen() const;
219242
bool isGreen(Id streetId) const;
243+
bool isGreen(Id streetId, double angle) const;
220244
bool isTrafficLight() const noexcept override { return true; }
221245
};
222246

@@ -282,6 +306,15 @@ namespace dsm {
282306
m_phase = phase;
283307
}
284308

309+
template <typename Delay>
310+
requires(std::unsigned_integral<Delay>)
311+
void TrafficLight<Delay>::setLeftTurnRatio(std::pair<double, double> ratio) {
312+
assert((void("Left turn ratio components must be between 0 and 1."),
313+
ratio.first >= 0. && ratio.first <= 1. && ratio.second >= 0. &&
314+
ratio.second <= 1.));
315+
m_leftTurnRatio = std::move(ratio);
316+
}
317+
285318
template <typename Delay>
286319
requires(std::unsigned_integral<Delay>)
287320
void TrafficLight<Delay>::increaseCounter() {
@@ -321,6 +354,33 @@ namespace dsm {
321354
return !hasPriority;
322355
}
323356

357+
template <typename Delay>
358+
requires(std::unsigned_integral<Delay>)
359+
bool TrafficLight<Delay>::isGreen(Id streetId, double angle) const {
360+
assert((void("TrafficLight's delay has not been set."), m_delay.has_value()));
361+
assert((void("TrafficLight's left turn ratio has not been set."),
362+
m_leftTurnRatio.has_value()));
363+
bool const hasPriority{this->streetPriorities().contains(streetId)};
364+
auto const pair{m_delay.value()};
365+
if (angle > 0.) {
366+
if (hasPriority) {
367+
return m_counter > pair.first * (1. - m_leftTurnRatio.value().first) &&
368+
m_counter < pair.first;
369+
} else {
370+
return m_counter >
371+
pair.first + pair.second * (1. - m_leftTurnRatio.value().second);
372+
}
373+
} else {
374+
if (hasPriority) {
375+
return m_counter < pair.first * (1. - m_leftTurnRatio.value().first);
376+
} else {
377+
return m_counter > pair.first &&
378+
m_counter <
379+
pair.first + pair.second * (1. - m_leftTurnRatio.value().second);
380+
}
381+
}
382+
}
383+
324384
/// @brief The Roundabout class represents a roundabout node in the network.
325385
/// @tparam Id The type of the node's id
326386
/// @tparam Size The type of the node's capacity

src/dsm/headers/Street.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,16 @@ namespace dsm {
161161
return nAgents;
162162
}
163163

164+
double Street::deltaAngle(double const previousStreetAngle) const {
165+
double deltaAngle{m_angle - previousStreetAngle};
166+
if (deltaAngle > std::numbers::pi) {
167+
deltaAngle -= 2 * std::numbers::pi;
168+
} else if (deltaAngle < -std::numbers::pi) {
169+
deltaAngle += 2 * std::numbers::pi;
170+
}
171+
return deltaAngle;
172+
}
173+
164174
SpireStreet::SpireStreet(Id id, const Street& street)
165175
: Street(id, street), m_agentCounterIn{0}, m_agentCounterOut{0} {}
166176

src/dsm/headers/Street.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ namespace dsm {
195195
/// @brief Get the number of agents on all queues
196196
/// @return Size The number of agents on all queues
197197
Size nExitingAgents() const;
198+
/// @brief Get the delta angle between the street and the previous street, normalized between -pi and pi
199+
/// @param previousStreetAngle The angle of the previous street
200+
/// @return double The delta angle between the street and the previous street
201+
double deltaAngle(double const previousStreetAngle) const;
198202

199203
virtual void addAgent(Id agentId);
200204
/// @brief Add an agent to the street's queue

test/Test_dynamics.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,138 @@ TEST_CASE("Dynamics") {
568568
}
569569
}
570570
}
571+
GIVEN(
572+
"A traffic light managing an intersection with 4 3-lanes streets and 4 1-lane "
573+
"streets") {
574+
// Streets
575+
Street s0_1{1, 1, 30., 15., std::make_pair(0, 1), 3};
576+
Street s1_0{5, 1, 30., 15., std::make_pair(1, 0), 3};
577+
Street s1_2{7, 1, 30., 15., std::make_pair(1, 2), 3};
578+
Street s2_1{11, 1, 30., 15., std::make_pair(2, 1), 3};
579+
580+
Street s3_1{8, 1, 30., 15., std::make_pair(3, 1)};
581+
Street s1_3{16, 1, 30., 15., std::make_pair(1, 3)};
582+
Street s4_1{21, 1, 30., 15., std::make_pair(4, 1)};
583+
Street s1_4{9, 1, 30., 15., std::make_pair(1, 4)};
584+
585+
Graph graph2;
586+
graph2.addNode(std::make_unique<TrafficLight>(1));
587+
graph2.addStreets(s0_1, s1_0, s1_2, s2_1, s3_1, s1_3, s4_1, s1_4);
588+
graph2.buildAdj();
589+
graph2.adjustNodeCapacities();
590+
graph2.normalizeStreetCapacities();
591+
auto const& nodes = graph2.nodeSet();
592+
auto& tl = dynamic_cast<TrafficLight&>(*nodes.at(1));
593+
tl.setDelay(3);
594+
tl.setLeftTurnRatio(0.3);
595+
tl.setPhase(2);
596+
tl.addStreetPriority(1);
597+
tl.setCoords({0., 0.});
598+
nodes.at(0)->setCoords({-1., 0.});
599+
nodes.at(2)->setCoords({1., 0.});
600+
nodes.at(3)->setCoords({0., -1.});
601+
nodes.at(4)->setCoords({0., 1.});
602+
graph2.buildStreetAngles();
603+
604+
Dynamics dynamics{graph2};
605+
dynamics.setSeed(69);
606+
607+
std::vector<uint32_t> destinationNodes{0, 2, 3, 4};
608+
dynamics.setDestinationNodes(destinationNodes);
609+
610+
CHECK(tl.leftTurnRatio().has_value());
611+
612+
WHEN("We add agents and make the system evolve") {
613+
Agent agent1{0, 2, 0};
614+
Agent agent2{1, 4, 0};
615+
dynamics.addAgents(agent1, agent2);
616+
dynamics.evolve(false);
617+
dynamics.evolve(false);
618+
THEN("The agents are correctly placed") {
619+
CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 1);
620+
CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 1);
621+
}
622+
dynamics.evolve(false);
623+
dynamics.evolve(false);
624+
dynamics.evolve(false);
625+
THEN("The agent 0 passes and agent 1 waits") {
626+
CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 7);
627+
CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 1);
628+
}
629+
dynamics.evolve(false);
630+
THEN("The agent 1 passes") {
631+
CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 7);
632+
CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 9);
633+
}
634+
}
635+
}
636+
GIVEN(
637+
"A traffic light managing an intersection with 4 3-lanes streets and 4 1-lane "
638+
"streets") {
639+
// Streets
640+
Street s0_1{1, 1, 30., 15., std::make_pair(0, 1), 3};
641+
Street s1_0{5, 1, 30., 15., std::make_pair(1, 0), 3};
642+
Street s1_2{7, 1, 30., 15., std::make_pair(1, 2), 3};
643+
Street s2_1{11, 1, 30., 15., std::make_pair(2, 1), 3};
644+
645+
Street s3_1{8, 1, 30., 15., std::make_pair(3, 1)};
646+
Street s1_3{16, 1, 30., 15., std::make_pair(1, 3)};
647+
Street s4_1{21, 1, 30., 15., std::make_pair(4, 1)};
648+
Street s1_4{9, 1, 30., 15., std::make_pair(1, 4)};
649+
650+
Graph graph2;
651+
graph2.addNode(std::make_unique<TrafficLight>(1));
652+
graph2.addStreets(s0_1, s1_0, s1_2, s2_1, s3_1, s1_3, s4_1, s1_4);
653+
graph2.buildAdj();
654+
graph2.adjustNodeCapacities();
655+
graph2.normalizeStreetCapacities();
656+
auto const& nodes = graph2.nodeSet();
657+
auto& tl = dynamic_cast<TrafficLight&>(*nodes.at(1));
658+
tl.setDelay(3);
659+
tl.setLeftTurnRatio(0.3);
660+
// NO! Now testing red light
661+
// tl.setPhase(2);
662+
tl.addStreetPriority(21);
663+
tl.addStreetPriority(8);
664+
tl.setCoords({0., 0.});
665+
nodes.at(0)->setCoords({-1., 0.});
666+
nodes.at(2)->setCoords({1., 0.});
667+
nodes.at(3)->setCoords({0., -1.});
668+
nodes.at(4)->setCoords({0., 1.});
669+
graph2.buildStreetAngles();
670+
671+
Dynamics dynamics{graph2};
672+
dynamics.setSeed(69);
673+
674+
std::vector<uint32_t> destinationNodes{0, 2, 3, 4};
675+
dynamics.setDestinationNodes(destinationNodes);
676+
677+
CHECK(tl.leftTurnRatio().has_value());
678+
679+
WHEN("We add agents and make the system evolve") {
680+
Agent agent1{0, 2, 0};
681+
Agent agent2{1, 4, 0};
682+
dynamics.addAgents(agent1, agent2);
683+
dynamics.evolve(false);
684+
dynamics.evolve(false);
685+
THEN("The agents are correctly placed") {
686+
CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 1);
687+
CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 1);
688+
}
689+
dynamics.evolve(false);
690+
dynamics.evolve(false);
691+
dynamics.evolve(false);
692+
THEN("The agent 0 passes and agent 1 waits") {
693+
CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 7);
694+
CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 1);
695+
}
696+
dynamics.evolve(false);
697+
THEN("The agent 1 passes") {
698+
CHECK_EQ(dynamics.agents().at(0)->streetId().value(), 7);
699+
CHECK_EQ(dynamics.agents().at(1)->streetId().value(), 9);
700+
}
701+
}
702+
}
571703
}
572704
SUBCASE("Traffic Lights optimization algorithm") {
573705
GIVEN("A dynamics object with a traffic light intersection") {

0 commit comments

Comments
 (0)