Skip to content

Commit 10512bf

Browse files
Improved optimization algorithm for traffic lights (#195)
* Update Dynamics.hpp with new algorithm New algorithm from my thesis * Update Node.hpp with modTime modTime to keep track of how the algorithm changes the values of redTime and greenTime * Merge Dynamics.hpp with main branch * Merge Node.hpp with main branch * Fix stalingrado example build * Exclude more paths in examples * Remove weird line * Add m_dataUpdatePeriod var (previously hard-coded) * Add densityTolerance parameter to optimizeTrafficLights() * Add slow_charge_tl_vero.cpp * Remove simulation file * Fix Makefile * Optimize new code * Change percentage param name to nCycles in optimizeTrafficLights * Division security * Fix computations with unsigned values * Revert Node.hpp to main version * Add test for changing traffic light timings * Add another test condition for traffic lights optimization --------- Co-authored-by: vero-dav-vero <[email protected]>
1 parent 9409779 commit 10512bf

File tree

3 files changed

+182
-19
lines changed

3 files changed

+182
-19
lines changed

examples/slow_charge_tl.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ int main(int argc, char** argv) {
226226
// dynamics.setForcePriorities(false);
227227
dynamics.setSpeedFluctuationSTD(0.1);
228228
dynamics.setMinSpeedRateo(0.95);
229+
if (OPTIMIZE)
230+
dynamics.setDataUpdatePeriod(30); // Store data every 30 time steps
229231
dynamics.updatePaths();
230232

231233
const auto TM = dynamics.turnMapping();
@@ -308,7 +310,7 @@ int main(int argc, char** argv) {
308310
}
309311
dynamics.evolve(false);
310312
if (OPTIMIZE && (dynamics.time() % 420 == 0)) {
311-
dynamics.optimizeTrafficLights(std::floor(420. / 60), 0.15);
313+
dynamics.optimizeTrafficLights(std::floor(420. / 60), 0.15, 3. / 10);
312314
}
313315
if (dynamics.time() % 2400 == 0 && nAgents > 0) {
314316
// auto meanDelta = std::accumulate(deltas.begin(), deltas.end(), 0) /

src/dsm/headers/Dynamics.hpp

Lines changed: 115 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,10 @@ namespace dsm {
8484
std::vector<unsigned int> m_travelTimes;
8585
std::unordered_map<Id, Id> m_agentNextStreetId;
8686
bool m_forcePriorities;
87+
std::optional<Delay> m_dataUpdatePeriod;
8788
std::unordered_map<Id, std::array<unsigned long long, 4>> m_turnCounts;
8889
std::unordered_map<Id, std::array<long, 4>> m_turnMapping;
90+
std::unordered_map<Id, unsigned long long> m_streetTails;
8991

9092
/// @brief Get the next street id
9193
/// @param agentId The id of the agent
@@ -147,6 +149,12 @@ namespace dsm {
147149
/// @param forcePriorities The flag
148150
/// @details If true, if an agent cannot move to the next street, the whole node is skipped
149151
void setForcePriorities(bool forcePriorities) { m_forcePriorities = forcePriorities; }
152+
/// @brief Set the data update period.
153+
/// @param dataUpdatePeriod Delay, The period
154+
/// @details Some data, i.e. the street queue lengths, are stored only after a fixed amount of time which is represented by this variable.
155+
void setDataUpdatePeriod(Delay dataUpdatePeriod) {
156+
m_dataUpdatePeriod = dataUpdatePeriod;
157+
}
150158

151159
/// @brief Update the paths of the itineraries based on the actual travel times
152160
virtual void updatePaths();
@@ -161,11 +169,14 @@ namespace dsm {
161169
/// @param reinsert_agents If true, the agents are reinserted in the simulation after they reach their destination
162170
virtual void evolve(bool reinsert_agents = false);
163171
/// @brief Optimize the traffic lights by changing the green and red times
164-
/// @param percentage double, The percentage of the TOTAL cycle time to add or subtract to the green time
172+
/// @param nCycles Delay, The number of cycles (times agents are being added) between two calls of this function
165173
/// @param threshold double, The percentage of the mean capacity of the streets used as threshold for the delta between the two tails.
174+
/// @param densityTolerance double, The algorithm will consider all streets with density up to densityTolerance*meanDensity
166175
/// @details The function cycles over the traffic lights and, if the difference between the two tails is greater than
167176
/// 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.
168-
void optimizeTrafficLights(double percentage, double threshold = 0.);
177+
void optimizeTrafficLights(Delay nCycles,
178+
double threshold = 0.,
179+
double densityTolerance = 0.);
169180

170181
/// @brief Get the graph
171182
/// @return const Graph<Id, Size>&, The graph
@@ -325,6 +336,7 @@ namespace dsm {
325336
m_maxFlowPercentage{1.},
326337
m_forcePriorities{false} {
327338
for (const auto& [streetId, street] : m_graph.streetSet()) {
339+
m_streetTails.emplace(streetId, 0);
328340
m_turnCounts.emplace(streetId, std::array<unsigned long long, 4>{0, 0, 0, 0});
329341
// fill turn mapping as [streetId, [left street Id, straight street Id, right street Id, U self street Id]]
330342
m_turnMapping.emplace(streetId, std::array<long, 4>{-1, -1, -1, -1});
@@ -414,6 +426,12 @@ namespace dsm {
414426
if (m_agents[agentId]->delay() > 0) {
415427
continue;
416428
}
429+
if (m_dataUpdatePeriod.has_value()) {
430+
if (m_time % m_dataUpdatePeriod.value() == 0) {
431+
//m_streetTails[streetId] += street->queue().size();
432+
m_streetTails[streetId] += street->waitingAgents().size();
433+
}
434+
}
417435
m_agents[agentId]->setSpeed(0.);
418436
const auto& destinationNode{this->m_graph.nodeSet()[street->nodePair().second]};
419437
if (destinationNode->isFull()) {
@@ -722,8 +740,22 @@ namespace dsm {
722740
template <typename Id, typename Size, typename Delay>
723741
requires(std::unsigned_integral<Id> && std::unsigned_integral<Size> &&
724742
is_numeric_v<Delay>)
725-
void Dynamics<Id, Size, Delay>::optimizeTrafficLights(double percentage,
726-
double threshold) {
743+
void Dynamics<Id, Size, Delay>::optimizeTrafficLights(Delay nCycles,
744+
double threshold,
745+
double densityTolerance) {
746+
if (threshold < 0 || threshold > 1) {
747+
throw std::invalid_argument(
748+
buildLog(std::format("The threshold parameter is a percentage and must be "
749+
"bounded between 0-1. Inserted value: {}",
750+
threshold)));
751+
}
752+
if (densityTolerance < 0 || densityTolerance > 1) {
753+
throw std::invalid_argument(buildLog(
754+
std::format("The densityTolerance parameter is a percentage and must be "
755+
"bounded between 0-1. Inserted value: {}",
756+
densityTolerance)));
757+
}
758+
const auto meanDensityGlob = streetMeanDensity().mean; // Measurement<double>
727759
for (const auto& [nodeId, node] : m_graph.nodeSet()) {
728760
if (!node->isTrafficLight()) {
729761
continue;
@@ -734,21 +766,20 @@ namespace dsm {
734766
}
735767
auto [greenTime, redTime] = tl.delay().value();
736768
const auto cycleTime = greenTime + redTime;
737-
// const Delay delta = cycleTime * percentage;
738769
const auto& streetPriorities = tl.streetPriorities();
739770
Size greenSum{0}, greenQueue{0};
740771
Size redSum{0}, redQueue{0};
741772
for (const auto& [streetId, _] : m_graph.adjMatrix().getCol(nodeId, true)) {
742773
if (streetPriorities.contains(streetId)) {
743-
greenSum += m_graph.streetSet()[streetId]->nAgents();
774+
greenSum += m_streetTails[streetId];
744775
greenQueue += m_graph.streetSet()[streetId]->queue().size();
745776
} else {
746-
redSum += m_graph.streetSet()[streetId]->nAgents();
777+
redSum += m_streetTails[streetId];
747778
redQueue += m_graph.streetSet()[streetId]->queue().size();
748779
}
749780
}
750781
const Delay delta =
751-
std::floor(std::abs(static_cast<int>(greenQueue - redQueue)) / percentage);
782+
std::floor(std::fabs(static_cast<int>(greenQueue - redQueue)) / nCycles);
752783
if (delta == 0) {
753784
continue;
754785
}
@@ -757,19 +788,85 @@ namespace dsm {
757788
tl.setDelay(std::floor(cycleTime / 2));
758789
continue;
759790
}
760-
if ((greenSum > redSum) && !(greenTime > redTime) && (greenQueue > redQueue)) {
761-
if (redTime > delta) {
762-
greenTime += delta;
763-
redTime -= delta;
764-
tl.setDelay(std::make_pair(greenTime, redTime));
791+
// If the difference is not less than the threshold
792+
// - 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
793+
// - If the previous check fails, do nothing
794+
double meanDensity_streets{0.};
795+
{
796+
// Store the ids of outgoing streets
797+
const auto& row{m_graph.adjMatrix().getRow(nodeId, true)};
798+
for (const auto& [streetId, _] : row) {
799+
meanDensity_streets += m_graph.streetSet()[streetId]->density();
800+
}
801+
// Take the mean density of the outgoing streets
802+
const auto nStreets = row.size();
803+
if (nStreets > 1) {
804+
meanDensity_streets /= nStreets;
805+
}
806+
}
807+
//std::cout << '\t' << " -> Mean network density: " << std::setprecision(7) << meanDensityGlob << '\n';
808+
//std::cout << '\t' << " -> Mean density of 4 outgoing streets: " << std::setprecision(7) << meanDensity_streets << '\n';
809+
const auto ratio = meanDensityGlob / meanDensity_streets;
810+
// densityTolerance represents the max border we want to consider
811+
const auto dyn_thresh = std::tanh(ratio) * densityTolerance;
812+
//std::cout << '\t' << " -> Parametro ratio: " << std::setprecision(7) << ratio << '\n';
813+
//std::cout << '\t' << " -> Parametro dyn_thresh: " << std::setprecision(7) << dyn_thresh << '\n';
814+
if (meanDensityGlob * (1. + dyn_thresh) > meanDensity_streets) {
815+
//std::cout << '\t' << " -> I'm on the cluster's border" << '\n';
816+
if (meanDensityGlob > meanDensity_streets) {
817+
//std::cout << '\t' << " -> LESS than max density" << '\n';
818+
if (!(redTime > greenTime) && (redSum > greenSum) && (greenTime > delta)) {
819+
greenTime -= delta;
820+
redTime += delta;
821+
tl.setDelay(std::make_pair(greenTime, redTime));
822+
} else if (!(greenTime > redTime) && (greenSum > redSum) && (redTime > delta)) {
823+
greenTime += delta;
824+
redTime -= delta;
825+
tl.setDelay(std::make_pair(greenTime, redTime));
826+
} else {
827+
//std::cout << '\t' << " -> NOT entered into previous ifs" << '\n';
828+
tl.setDelay(std::make_pair(greenTime, redTime));
829+
}
830+
//std::cout << '\t' << " -> greenTime: " << static_cast<unsigned int>(greenTime) << '\n';
831+
//std::cout << '\t' << " -> redTime: " << static_cast<unsigned int>(redTime) << '\n';
832+
//std::cout << '\t' << " -> modTime: " << tl.modTime() << '\n';
833+
} else {
834+
//std::cout << '\t' << " -> GREATER than max density" << '\n';
835+
if (!(redTime > greenTime) && (redSum > greenSum) &&
836+
(greenTime > ratio * delta)) {
837+
greenTime -= dyn_thresh * delta; //
838+
redTime += delta;
839+
tl.setDelay(std::make_pair(greenTime, redTime));
840+
} else if (!(greenTime > redTime) && (greenSum > redSum) &&
841+
(redTime > ratio * delta)) {
842+
greenTime += delta;
843+
redTime -= dyn_thresh * delta; //
844+
tl.setDelay(std::make_pair(greenTime, redTime));
845+
} else if (!(redTime > greenTime) && (redSum < greenSum) && (redTime > delta)) {
846+
greenTime += dyn_thresh * delta; //
847+
redTime -= delta;
848+
tl.setDelay(std::make_pair(greenTime, redTime));
849+
} else if (!(redTime < greenTime) && (redSum > greenSum) &&
850+
(greenTime > delta)) {
851+
greenTime -= delta;
852+
redTime += dyn_thresh * delta; //
853+
tl.setDelay(std::make_pair(greenTime, redTime));
854+
} else {
855+
//std::cout << '\t' << " -> NON sono entrato negli if precedenti" << '\n';
856+
tl.setDelay(std::make_pair(greenTime, redTime));
857+
}
858+
//std::cout << '\t' << " -> greenTime: " << static_cast<unsigned int>(greenTime) << '\n';
859+
//std::cout << '\t' << " -> redTime: " << static_cast<unsigned int>(redTime) << '\n';
860+
//std::cout << '\t' << " -> modTime: " << tl.modTime() << '\n';
765861
}
766-
} else if (!(redTime > greenTime) && (greenTime > delta) &&
767-
(redQueue > greenQueue)) {
768-
greenTime -= delta;
769-
redTime += delta;
770-
tl.setDelay(std::make_pair(greenTime, redTime));
862+
} else {
863+
//std::cout << '\t' << " -> I'm INTO the cluster" << '\n';
864+
//std::cout << '\t' << " -> modTime: " << tl.modTime() << '\n';
771865
}
772866
}
867+
for (auto& [id, element] : m_streetTails) {
868+
element = 0;
869+
}
773870
}
774871

775872
template <typename Id, typename Size, typename Delay>

test/Test_dynamics.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,70 @@ TEST_CASE("Dynamics") {
426426
}
427427
}
428428
}
429+
SUBCASE("Traffic Lights optimization algorithm") {
430+
GIVEN("A dynamics object with a traffic light intersection") {
431+
TrafficLight tl{1};
432+
tl.setDelay(4);
433+
tl.setPhase(3);
434+
double length{90.}, max_speed{15.};
435+
Street s_01{1, 10, length, max_speed, std::make_pair(0, 1)};
436+
Street s_10{5, 10, length, max_speed, std::make_pair(1, 0)};
437+
Street s_12{7, 10, length, max_speed, std::make_pair(1, 2)};
438+
Street s_21{11, 10, length, max_speed, std::make_pair(2, 1)};
439+
Street s_13{8, 10, length, max_speed, std::make_pair(1, 3)};
440+
Street s_31{16, 10, length, max_speed, std::make_pair(3, 1)};
441+
Street s_14{9, 10, length, max_speed, std::make_pair(1, 4)};
442+
Street s_41{21, 10, length, max_speed, std::make_pair(4, 1)};
443+
tl.addStreetPriority(1);
444+
tl.addStreetPriority(11);
445+
Graph graph2;
446+
graph2.addNode(std::make_unique<TrafficLight>(tl));
447+
graph2.addStreets(s_01, s_10, s_12, s_21, s_13, s_31, s_14, s_41);
448+
graph2.buildAdj();
449+
Dynamics dynamics{graph2};
450+
Itinerary it_0{0, 0}, it_1{1, 2}, it_2{2, 3}, it_3{3, 4};
451+
dynamics.addItinerary(it_0);
452+
dynamics.addItinerary(it_1);
453+
dynamics.addItinerary(it_2);
454+
dynamics.addItinerary(it_3);
455+
dynamics.updatePaths();
456+
dynamics.addAgents(0, 7, 2);
457+
dynamics.addAgents(1, 7, 0);
458+
dynamics.setDataUpdatePeriod(1);
459+
WHEN("We evolve the dynamics and optimize traffic lights") {
460+
for (int i = 0; i < 8; ++i) {
461+
dynamics.evolve(false);
462+
}
463+
dynamics.optimizeTrafficLights(2, 0.1, 0.);
464+
THEN("Green and red time are different") {
465+
const auto timing =
466+
dynamic_cast<TrafficLight&>(*dynamics.graph().nodeSet().at(1))
467+
.delay()
468+
.value();
469+
CHECK(timing.first > timing.second);
470+
}
471+
}
472+
WHEN(
473+
"We evolve the dynamics and optimize traffic lights with outgoing streets "
474+
"full") {
475+
dynamics.addAgents(0, 5, 1);
476+
dynamics.addAgents(1, 5, 1);
477+
dynamics.addAgents(2, 5, 1);
478+
dynamics.addAgents(3, 5, 1);
479+
for (int i = 0; i < 15; ++i) {
480+
dynamics.evolve(false);
481+
}
482+
dynamics.optimizeTrafficLights(2, 0.1, 0.);
483+
THEN("Green and red time are equal") {
484+
const auto timing =
485+
dynamic_cast<TrafficLight&>(*dynamics.graph().nodeSet().at(1))
486+
.delay()
487+
.value();
488+
CHECK_EQ(timing.first, timing.second);
489+
}
490+
}
491+
}
492+
}
429493
SUBCASE("Roundabout") {
430494
GIVEN(
431495
"A dynamics object with four streets, one agent for each street, two itineraries "

0 commit comments

Comments
 (0)