From f9869d0b92e7ec9fa6e58aa1e8417b78ddb5c03e Mon Sep 17 00:00:00 2001 From: grufoony Date: Mon, 22 Dec 2025 14:27:22 +0100 Subject: [PATCH 1/9] Init refactoring --- src/dsf/mobility/Road.cpp | 7 +------ src/dsf/mobility/Road.hpp | 9 ++++----- src/dsf/mobility/RoadNetwork.cpp | 20 +++----------------- test/mobility/Test_street.cpp | 6 ++++-- 4 files changed, 12 insertions(+), 30 deletions(-) diff --git a/src/dsf/mobility/Road.cpp b/src/dsf/mobility/Road.cpp index ce219cd3..5a850157 100644 --- a/src/dsf/mobility/Road.cpp +++ b/src/dsf/mobility/Road.cpp @@ -22,8 +22,7 @@ namespace dsf::mobility { m_length{length}, m_maxSpeed{maxSpeed}, m_nLanes{nLanes}, - m_name{std::move(name)}, - m_priority{nLanes * 100} { + m_name{std::move(name)} { if (!(length > 0.)) { throw std::invalid_argument( std::format("The length of a road ({}) must be greater than 0.", length)); @@ -84,10 +83,6 @@ namespace dsf::mobility { } m_transportCapacity = transportCapacity; } - void Road::setPriority(int priority) { - assert(priority >= 0); - m_priority = priority; - } Direction Road::turnDirection(double const& previousStreetAngle) const { auto const deltaAngle{this->deltaAngle(previousStreetAngle)}; if (std::abs(deltaAngle) >= std::numbers::pi) { diff --git a/src/dsf/mobility/Road.hpp b/src/dsf/mobility/Road.hpp index f2053f45..3860bd18 100644 --- a/src/dsf/mobility/Road.hpp +++ b/src/dsf/mobility/Road.hpp @@ -26,7 +26,7 @@ namespace dsf::mobility { int m_capacity; double m_transportCapacity; std::string m_name; - int m_priority; + bool m_hasPriority = false; std::set m_forbiddenTurns; // Stores the forbidden turns (road ids) std::optional m_roadType{std::nullopt}; @@ -70,9 +70,8 @@ namespace dsf::mobility { /// @param transportCapacity The transport capacity /// @throws std::invalid_argument If the transport capacity is less or equal to 0 void setTransportCapacity(double transportCapacity); - /// @brief Set the road's priority - /// @param priority The road's priority - void setPriority(int priority); + /// @brief Set the road's priority to true + inline void setPriority() { m_hasPriority = true; } /// @brief Add a road id to the forbidden turns /// @param roadId The road id to add void addForbiddenTurn(Id roadId); @@ -103,7 +102,7 @@ namespace dsf::mobility { inline auto const& name() const noexcept { return m_name; } /// @brief Get the priority /// @return int The priority - inline auto priority() const noexcept { return m_priority; } + inline auto hasPriority() const noexcept { return m_hasPriority; } /// @brief Get the road's forbidden turns /// @return std::set The road's forbidden turns /// @details The forbidden turns are the road ids that are not allowed to be used by the agents diff --git a/src/dsf/mobility/RoadNetwork.cpp b/src/dsf/mobility/RoadNetwork.cpp index 330248ee..41987584 100644 --- a/src/dsf/mobility/RoadNetwork.cpp +++ b/src/dsf/mobility/RoadNetwork.cpp @@ -567,23 +567,10 @@ namespace dsf::mobility { auto const& pNode{pair.second}; auto const& inNeighbours{pNode->ingoingEdges()}; auto const& outNeighbours{pNode->outgoingEdges()}; - int maxPriority{0}; - std::for_each(inNeighbours.cbegin(), - inNeighbours.cend(), - [this, &maxPriority](auto const& edgeId) { - auto const& pStreet{this->edge(edgeId)}; - maxPriority = std::max(maxPriority, pStreet->priority()); - }); - std::for_each(outNeighbours.cbegin(), - outNeighbours.cend(), - [this, &maxPriority](auto const& edgeId) { - auto const& pStreet{this->edge(edgeId)}; - maxPriority = std::max(maxPriority, pStreet->priority()); - }); std::for_each( inNeighbours.cbegin(), inNeighbours.cend(), - [this, &pNode, &outNeighbours, &maxPriority](auto const& edgeId) { + [this, &pNode, &outNeighbours](auto const& edgeId) { auto const& pInStreet{this->edge(edgeId)}; auto const nLanes{pInStreet->nLanes()}; if (nLanes == 1) { @@ -593,7 +580,7 @@ namespace dsf::mobility { std::for_each( outNeighbours.cbegin(), outNeighbours.cend(), - [this, &pInStreet, &allowedTurns, &maxPriority](auto const& edgeId) { + [this, &pInStreet, &allowedTurns](auto const& edgeId) { auto const& pOutStreet{this->edge(edgeId)}; if (pOutStreet->target() == pInStreet->source() || pInStreet->forbiddenTurns().contains(pOutStreet->id())) { @@ -606,8 +593,7 @@ namespace dsf::mobility { return; } // Actually going straight means remain on the same road, thus... - if (((pInStreet->priority() == maxPriority) == - (outOppositeStreet->get()->priority() == maxPriority)) && + if ((pInStreet->priority() == outOppositeStreet->get()->priority()) && !allowedTurns.contains(Direction::STRAIGHT)) { spdlog::debug("Street {} prioritized STRAIGHT", pInStreet->id()); if (allowedTurns.contains(Direction::STRAIGHT) && diff --git a/test/mobility/Test_street.cpp b/test/mobility/Test_street.cpp index 19903753..6662c99b 100644 --- a/test/mobility/Test_street.cpp +++ b/test/mobility/Test_street.cpp @@ -245,7 +245,9 @@ TEST_CASE("Road") { CHECK_EQ(road.capacity(), static_cast(std::ceil((100.0 * 2) / Road::meanVehicleLength()))); CHECK_EQ(road.transportCapacity(), 1.0); - CHECK_EQ(road.priority(), 200); // 2 * 100 + CHECK_FALSE(road.hasPriority()); + road.setPriority(); + CHECK(road.hasPriority()); } } @@ -432,7 +434,7 @@ TEST_CASE("Road") { static_cast(std::ceil((100.0 * 2) / Road::meanVehicleLength()))); CHECK_EQ(road.transportCapacity(), 1.0); CHECK_EQ(road.name(), "Test Road"); - CHECK_EQ(road.priority(), 200); // 2 * 100 + CHECK_FALSE(road.hasPriority()); CHECK(road.forbiddenTurns().empty()); } From 8a18a08f5d31136358021f5ee805a88c2da35b4d Mon Sep 17 00:00:00 2001 From: grufoony Date: Mon, 22 Dec 2025 17:37:46 +0100 Subject: [PATCH 2/9] Better handling of nlanes --- src/dsf/mobility/RoadNetwork.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/dsf/mobility/RoadNetwork.cpp b/src/dsf/mobility/RoadNetwork.cpp index 41987584..342cbc93 100644 --- a/src/dsf/mobility/RoadNetwork.cpp +++ b/src/dsf/mobility/RoadNetwork.cpp @@ -567,10 +567,25 @@ namespace dsf::mobility { auto const& pNode{pair.second}; auto const& inNeighbours{pNode->ingoingEdges()}; auto const& outNeighbours{pNode->outgoingEdges()}; + double maxEstimatedFlow{0.0}; + std::for_each(inNeighbours.cbegin(), + inNeighbours.cend(), + [this, &maxEstimatedFlow](auto const& edgeId) { + auto const& pStreet{this->edge(edgeId)}; + auto const estFlow{pStreet->maxSpeed() * pStreet->nLanes()}; + maxEstimatedFlow = std::max(maxEstimatedFlow, estFlow); + }); + std::for_each(outNeighbours.cbegin(), + outNeighbours.cend(), + [this, &maxEstimatedFlow](auto const& edgeId) { + auto const& pStreet{this->edge(edgeId)}; + auto const estFlow{pStreet->maxSpeed() * pStreet->nLanes()}; + maxEstimatedFlow = std::max(maxEstimatedFlow, estFlow); + }); std::for_each( inNeighbours.cbegin(), inNeighbours.cend(), - [this, &pNode, &outNeighbours](auto const& edgeId) { + [this, &pNode, &outNeighbours, &maxEstimatedFlow](auto const& edgeId) { auto const& pInStreet{this->edge(edgeId)}; auto const nLanes{pInStreet->nLanes()}; if (nLanes == 1) { @@ -580,7 +595,7 @@ namespace dsf::mobility { std::for_each( outNeighbours.cbegin(), outNeighbours.cend(), - [this, &pInStreet, &allowedTurns](auto const& edgeId) { + [this, &pInStreet, &allowedTurns, &maxEstimatedFlow](auto const& edgeId) { auto const& pOutStreet{this->edge(edgeId)}; if (pOutStreet->target() == pInStreet->source() || pInStreet->forbiddenTurns().contains(pOutStreet->id())) { @@ -593,7 +608,11 @@ namespace dsf::mobility { return; } // Actually going straight means remain on the same road, thus... - if ((pInStreet->priority() == outOppositeStreet->get()->priority()) && + auto const inEstFlow{pInStreet->maxSpeed() * pInStreet->nLanes()}; + auto const outEstFlow{outOppositeStreet->maxSpeed() * + outOppositeStreet->nLanes()}; + if (((inEstFlow == maxEstimatedFlow) == + (outEstFlow == maxEstimatedFlow)) && !allowedTurns.contains(Direction::STRAIGHT)) { spdlog::debug("Street {} prioritized STRAIGHT", pInStreet->id()); if (allowedTurns.contains(Direction::STRAIGHT) && From 4963bd25dca55463b85983ac3c78bdf07f541876 Mon Sep 17 00:00:00 2001 From: grufoony Date: Mon, 22 Dec 2025 17:54:56 +0100 Subject: [PATCH 3/9] Fix --- src/dsf/mobility/RoadNetwork.cpp | 4 ++-- src/dsf/mobility/Street.cpp | 2 +- test/mobility/Test_street.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dsf/mobility/RoadNetwork.cpp b/src/dsf/mobility/RoadNetwork.cpp index 342cbc93..1d4bfa7b 100644 --- a/src/dsf/mobility/RoadNetwork.cpp +++ b/src/dsf/mobility/RoadNetwork.cpp @@ -609,8 +609,8 @@ namespace dsf::mobility { } // Actually going straight means remain on the same road, thus... auto const inEstFlow{pInStreet->maxSpeed() * pInStreet->nLanes()}; - auto const outEstFlow{outOppositeStreet->maxSpeed() * - outOppositeStreet->nLanes()}; + auto const outEstFlow{outOppositeStreet->get()->maxSpeed() * + outOppositeStreet->get()->nLanes()}; if (((inEstFlow == maxEstimatedFlow) == (outEstFlow == maxEstimatedFlow)) && !allowedTurns.contains(Direction::STRAIGHT)) { diff --git a/src/dsf/mobility/Street.cpp b/src/dsf/mobility/Street.cpp index 4bcd7df6..6ba5eb2e 100644 --- a/src/dsf/mobility/Street.cpp +++ b/src/dsf/mobility/Street.cpp @@ -57,7 +57,7 @@ namespace dsf::mobility { isEqual &= (this->m_maxSpeed == other.m_maxSpeed); isEqual &= (this->m_nLanes == other.m_nLanes); isEqual &= (this->m_name == other.m_name); - isEqual &= (this->m_priority == other.m_priority); + isEqual &= (this->m_hasPriority == other.m_hasPriority); return isEqual; } diff --git a/test/mobility/Test_street.cpp b/test/mobility/Test_street.cpp index 6662c99b..a59b1e49 100644 --- a/test/mobility/Test_street.cpp +++ b/test/mobility/Test_street.cpp @@ -387,8 +387,8 @@ TEST_CASE("Road") { SUBCASE("setPriority") { WHEN("Priority is set") { - road.setPriority(150); - THEN("Priority is updated") { CHECK_EQ(road.priority(), 150); } + road.setPriority(); + THEN("Priority is updated") { CHECK(road.hasPriority()); } } } } From cc10361ab43d868e1d5a13321f9e7608ae32bbe1 Mon Sep 17 00:00:00 2001 From: Grufoony Date: Fri, 16 Jan 2026 13:10:31 +0100 Subject: [PATCH 4/9] Init auto priority assignment --- src/dsf/mobility/RoadNetwork.cpp | 52 ++++++++++++++++++++++++++++++++ src/dsf/mobility/RoadNetwork.hpp | 2 ++ 2 files changed, 54 insertions(+) diff --git a/src/dsf/mobility/RoadNetwork.cpp b/src/dsf/mobility/RoadNetwork.cpp index 330248ee..04c2b554 100644 --- a/src/dsf/mobility/RoadNetwork.cpp +++ b/src/dsf/mobility/RoadNetwork.cpp @@ -7,6 +7,7 @@ #include #include +#include namespace dsf::mobility { void RoadNetwork::m_updateMaxAgentCapacity() { @@ -775,6 +776,57 @@ namespace dsf::mobility { }); } + void RoadNetwork::autoAssignRoadPriorities() { + spdlog::debug("Auto-assigning road priorities..."); + tbb::parallel_for_each( + m_nodes.cbegin(), + m_nodes.cend(), + [this](auto const& pair) { + auto const& pNode{pair.second}; + auto const& inNeighbours{pNode->ingoingEdges()}; + std::map types; + for (auto const& edgeId : inNeighbours) { + auto const& pStreet{this->edge(edgeId)}; + types.emplace(pStreet->roadType(), pStreet->id()); + } + if (types.size() < 2) { + return; + } + std::optional previousType{std::nullopt}; + std::vector priorityRoads; + for (auto const& [type, streetId] : types) { + if (!previousType.has_value()) { + previousType = type; + priorityRoads.push_back(streetId); + } else { + if (type == previousType.value()) { + if (priorityRoads.size() == 2) { + priorityRoads.clear(); + } else { + priorityRoads.push_back(streetId); + } + } else if (priorityRoads.size() < 2) { + previousType = type; + priorityRoads.clear(); + priorityRoads.push_back(streetId); + } + } + } + + if (priorityRoads.size() < 2) { + return; + } + + for (auto const& streetId : priorityRoads) { + auto const& pStreet{this->edge(streetId)}; + pStreet->setPriority(); + spdlog::debug("Setting priority to street {}", pStreet->id()); + } + + }); + spdlog::debug("Done auto-assigning road priorities."); + } + void RoadNetwork::adjustNodeCapacities() { double value; for (auto const& [_, pNode] : nodes()) { diff --git a/src/dsf/mobility/RoadNetwork.hpp b/src/dsf/mobility/RoadNetwork.hpp index d185e4b3..e6d671cc 100644 --- a/src/dsf/mobility/RoadNetwork.hpp +++ b/src/dsf/mobility/RoadNetwork.hpp @@ -99,6 +99,8 @@ namespace dsf::mobility { /// @details For example, if one street has the right turn forbidden, then the right lane becomes a straight one void autoMapStreetLanes(); + void autoAssignRoadPriorities(); + /// @brief Import the graph's streets from a file /// @param fileName The name of the file to import the streets from. /// @details Supports csv, json and geojson file formats. From 2dfc206ef0317dff21972fe85ed3e8b4671dd1ed Mon Sep 17 00:00:00 2001 From: Grufoony Date: Fri, 16 Jan 2026 13:47:16 +0100 Subject: [PATCH 5/9] Fix auto algorithm --- src/dsf/mobility/RoadNetwork.cpp | 81 +++++++++++++++----------------- test/mobility/Test_graph.cpp | 29 ++++++++++++ 2 files changed, 68 insertions(+), 42 deletions(-) diff --git a/src/dsf/mobility/RoadNetwork.cpp b/src/dsf/mobility/RoadNetwork.cpp index 468d611d..a0c9061e 100644 --- a/src/dsf/mobility/RoadNetwork.cpp +++ b/src/dsf/mobility/RoadNetwork.cpp @@ -783,52 +783,49 @@ namespace dsf::mobility { void RoadNetwork::autoAssignRoadPriorities() { spdlog::debug("Auto-assigning road priorities..."); - tbb::parallel_for_each( - m_nodes.cbegin(), - m_nodes.cend(), - [this](auto const& pair) { - auto const& pNode{pair.second}; - auto const& inNeighbours{pNode->ingoingEdges()}; - std::map types; - for (auto const& edgeId : inNeighbours) { - auto const& pStreet{this->edge(edgeId)}; - types.emplace(pStreet->roadType(), pStreet->id()); - } - if (types.size() < 2) { - return; - } - std::optional previousType{std::nullopt}; - std::vector priorityRoads; - for (auto const& [type, streetId] : types) { - if (!previousType.has_value()) { - previousType = type; - priorityRoads.push_back(streetId); - } else { - if (type == previousType.value()) { - if (priorityRoads.size() == 2) { - priorityRoads.clear(); - } else { - priorityRoads.push_back(streetId); - } - } else if (priorityRoads.size() < 2) { - previousType = type; - priorityRoads.clear(); - priorityRoads.push_back(streetId); - } - } - } + tbb::parallel_for_each(m_nodes.cbegin(), m_nodes.cend(), [this](auto const& pair) { + auto const& pNode{pair.second}; + auto const& inNeighbours{pNode->ingoingEdges()}; + std::multimap types; + for (auto const& edgeId : inNeighbours) { + auto const& pStreet{this->edge(edgeId)}; + auto const roadType = pStreet->roadType(); + if (roadType.has_value()) { + types.emplace(roadType.value(), pStreet->id()); + } + } + if (types.size() < 2) { + return; + } + std::vector priorityRoads; + // Find the first road type that has at least 2 streets + for (auto it = types.begin(); it != types.end();) { + auto const& currentType = it->first; + auto const count = types.count(currentType); - if (priorityRoads.size() < 2) { - return; + if (count == 2) { + auto range = types.equal_range(currentType); + for (auto rangeIt = range.first; rangeIt != range.second; ++rangeIt) { + priorityRoads.push_back(rangeIt->second); } + break; + } - for (auto const& streetId : priorityRoads) { - auto const& pStreet{this->edge(streetId)}; - pStreet->setPriority(); - spdlog::debug("Setting priority to street {}", pStreet->id()); - } + // Move to the next different type + it = types.upper_bound(currentType); + } + + if (priorityRoads.size() < 2) { + spdlog::warn("Node {}: unable to auto-assign road priorities", pNode->id()); + return; + } - }); + for (auto const& streetId : priorityRoads) { + auto const& pStreet{this->edge(streetId)}; + pStreet->setPriority(); + spdlog::debug("Setting priority to street {}", pStreet->id()); + } + }); spdlog::debug("Done auto-assigning road priorities."); } diff --git a/test/mobility/Test_graph.cpp b/test/mobility/Test_graph.cpp index 091a07ce..d276e1e5 100644 --- a/test/mobility/Test_graph.cpp +++ b/test/mobility/Test_graph.cpp @@ -380,6 +380,35 @@ TEST_CASE("RoadNetwork") { } } } + + SUBCASE("autoAssignRoadPriorities") { + GIVEN("A graph with a node having multiple incoming edges of different types") { + RoadNetwork graph{}; + // Node 1 is the intersection + // Edge 1: 0 -> 1 (HIGHWAY) + Street s1(1, std::make_pair(0, 1), 100., 30., 1); + s1.setRoadType(RoadType::HIGHWAY); + + // Edge 2: 2 -> 1 (HIGHWAY) + Street s2(2, std::make_pair(2, 1), 100., 30., 1); + s2.setRoadType(RoadType::HIGHWAY); + + // Edge 3: 3 -> 1 (SECONDARY) + Street s3(3, std::make_pair(3, 1), 100., 30., 1); + s3.setRoadType(RoadType::SECONDARY); + + graph.addStreets(s1, s2, s3); + + WHEN("We auto assign road priorities") { + graph.autoAssignRoadPriorities(); + THEN("The priorities are assigned to the most important roads") { + CHECK(graph.edge(1)->hasPriority()); + CHECK(graph.edge(2)->hasPriority()); + CHECK_FALSE(graph.edge(3)->hasPriority()); + } + } + } + } } TEST_CASE("Dijkstra") { From b55c509eb13b648ab669f3327c6b02f86bd5f6cd Mon Sep 17 00:00:00 2001 From: Gregorio Berselli Date: Fri, 16 Jan 2026 14:38:21 +0100 Subject: [PATCH 6/9] Update src/dsf/mobility/Road.hpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/dsf/mobility/Road.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dsf/mobility/Road.hpp b/src/dsf/mobility/Road.hpp index 3860bd18..4c63e98f 100644 --- a/src/dsf/mobility/Road.hpp +++ b/src/dsf/mobility/Road.hpp @@ -101,7 +101,7 @@ namespace dsf::mobility { /// @return std::string The name inline auto const& name() const noexcept { return m_name; } /// @brief Get the priority - /// @return int The priority + /// @return bool Whether the road has priority inline auto hasPriority() const noexcept { return m_hasPriority; } /// @brief Get the road's forbidden turns /// @return std::set The road's forbidden turns From 2ac0b7a84fc3be668b63432e184f6437dd09e5b2 Mon Sep 17 00:00:00 2001 From: Gregorio Berselli Date: Fri, 16 Jan 2026 14:38:56 +0100 Subject: [PATCH 7/9] Update src/dsf/mobility/RoadNetwork.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/dsf/mobility/RoadNetwork.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dsf/mobility/RoadNetwork.cpp b/src/dsf/mobility/RoadNetwork.cpp index a0c9061e..f49f3701 100644 --- a/src/dsf/mobility/RoadNetwork.cpp +++ b/src/dsf/mobility/RoadNetwork.cpp @@ -786,6 +786,11 @@ namespace dsf::mobility { tbb::parallel_for_each(m_nodes.cbegin(), m_nodes.cend(), [this](auto const& pair) { auto const& pNode{pair.second}; auto const& inNeighbours{pNode->ingoingEdges()}; + // NOTE: std::multimap iterates keys in ascending order of RoadType. + // RoadType is defined so that more important roads (e.g., HIGHWAY = 0, + // PRIMARY = 1, SECONDARY = 2, ...) have smaller enum values. The logic + // below relies on this ordering to consider higher-priority road types + // first when selecting streets to mark as priority roads. std::multimap types; for (auto const& edgeId : inNeighbours) { auto const& pStreet{this->edge(edgeId)}; From e2c784ee2799f89add1299980ac86bca7109b88d Mon Sep 17 00:00:00 2001 From: Grufoony Date: Fri, 16 Jan 2026 16:10:36 +0100 Subject: [PATCH 8/9] Refactor autoinittrafficlights function --- examples/slow_charge_tl.cpp | 2 +- src/dsf/bindings.cpp | 11 +- src/dsf/mobility/RoadNetwork.cpp | 374 +++++++++++++++---------------- src/dsf/mobility/RoadNetwork.hpp | 6 +- test/mobility/Test_graph.cpp | 24 +- 5 files changed, 204 insertions(+), 213 deletions(-) diff --git a/examples/slow_charge_tl.cpp b/examples/slow_charge_tl.cpp index ab06b37c..e508eef8 100644 --- a/examples/slow_charge_tl.cpp +++ b/examples/slow_charge_tl.cpp @@ -150,7 +150,7 @@ int main(int argc, char** argv) { } graph.adjustNodeCapacities(); std::cout << "Setting traffic light parameters..." << '\n'; - graph.initTrafficLights(); + graph.autoInitTrafficLights(); std::cout << "Done." << std::endl; std::cout << "Creating dynamics...\n"; diff --git a/src/dsf/bindings.cpp b/src/dsf/bindings.cpp index 3ec5a4f5..14216846 100644 --- a/src/dsf/bindings.cpp +++ b/src/dsf/bindings.cpp @@ -165,13 +165,16 @@ PYBIND11_MODULE(dsf_cpp, m) { "adjustNodeCapacities", &dsf::mobility::RoadNetwork::adjustNodeCapacities, dsf::g_docstrings.at("dsf::mobility::RoadNetwork::adjustNodeCapacities").c_str()) - .def("initTrafficLights", - &dsf::mobility::RoadNetwork::initTrafficLights, - pybind11::arg("minGreenTime") = 30, - dsf::g_docstrings.at("dsf::mobility::RoadNetwork::initTrafficLights").c_str()) + .def("autoInitTrafficLights", + &dsf::mobility::RoadNetwork::autoInitTrafficLights, + pybind11::arg("mainRoadPercentage") = 0.6, + dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoInitTrafficLights").c_str()) .def("autoMapStreetLanes", &dsf::mobility::RoadNetwork::autoMapStreetLanes, dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoMapStreetLanes").c_str()) + .def("autoAssignRoadPriorities", + &dsf::mobility::RoadNetwork::autoAssignRoadPriorities, + dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoAssignRoadPriorities").c_str()) .def("setStreetStationaryWeights", &dsf::mobility::RoadNetwork::setStreetStationaryWeights, pybind11::arg("weights"), diff --git a/src/dsf/mobility/RoadNetwork.cpp b/src/dsf/mobility/RoadNetwork.cpp index f49f3701..ef80209b 100644 --- a/src/dsf/mobility/RoadNetwork.cpp +++ b/src/dsf/mobility/RoadNetwork.cpp @@ -1,6 +1,6 @@ +#include "RoadNetwork.hpp" #include "../geometry/Point.hpp" #include "../geometry/PolyLine.hpp" -#include "RoadNetwork.hpp" #include #include @@ -9,6 +9,9 @@ #include #include +// Default traffic light cycle duration in seconds +constexpr dsf::Delay TRAFFICLIGHT_DEFAULT_CYCLE = 90; + namespace dsf::mobility { void RoadNetwork::m_updateMaxAgentCapacity() { m_capacity = 0; @@ -161,7 +164,7 @@ namespace dsf::mobility { return std::tolower(c); }); if (strType.find("traffic_signals") != std::string::npos) { - makeTrafficLight(nodeId, 120); + makeTrafficLight(nodeId, TRAFFICLIGHT_DEFAULT_CYCLE); } else if (strType.find("roundabout") != std::string::npos) { makeRoundabout(nodeId); } @@ -355,212 +358,195 @@ namespace dsf::mobility { }); } - void RoadNetwork::initTrafficLights(Delay const minGreenTime) { - for (auto& [_, pNode] : m_nodes) { - if (!pNode->isTrafficLight()) { - continue; - } - auto& tl = static_cast(*pNode); - if (!tl.streetPriorities().empty() || !tl.cycles().empty()) { - continue; - } - auto const& inNeighbours = pNode->ingoingEdges(); - std::map> capacities; - std::unordered_map streetAngles; - std::unordered_map maxSpeeds; - std::unordered_map nLanes; - std::unordered_map streetNames; - double higherSpeed{0.}, lowerSpeed{std::numeric_limits::max()}; - int higherNLanes{0}, lowerNLanes{std::numeric_limits::max()}; - if (inNeighbours.size() < 3) { - spdlog::warn("Not enough in neighbours {} for Traffic Light {}", - inNeighbours.size(), - pNode->id()); - // Replace with a normal intersection - auto const& geometry{pNode->geometry()}; - if (geometry.has_value()) { - pNode = std::make_unique(pNode->id(), *geometry); - } else { - pNode = std::make_unique(pNode->id()); - } - continue; - } - for (auto const& edgeId : inNeighbours) { - auto const& pStreet{edge(edgeId)}; - - double const speed{pStreet->maxSpeed()}; - int const nLan{pStreet->nLanes()}; - auto const cap{pStreet->capacity()}; - capacities.emplace(pStreet->id(), cap); - auto angle{pStreet->angle()}; - if (angle < 0.) { - angle += 2 * std::numbers::pi; - } - streetAngles.emplace(pStreet->id(), angle); - - maxSpeeds.emplace(pStreet->id(), speed); - nLanes.emplace(pStreet->id(), nLan); - streetNames.emplace(pStreet->id(), pStreet->name()); - - higherSpeed = std::max(higherSpeed, speed); - lowerSpeed = std::min(lowerSpeed, speed); - - higherNLanes = std::max(higherNLanes, nLan); - lowerNLanes = std::min(lowerNLanes, nLan); - } - { - std::vector> sortedAngles; - std::copy( - streetAngles.begin(), streetAngles.end(), std::back_inserter(sortedAngles)); - std::sort(sortedAngles.begin(), - sortedAngles.end(), - [](auto const& a, auto const& b) { return a.second < b.second; }); - streetAngles.clear(); - for (auto const& [streetId, angle] : sortedAngles) { - streetAngles.emplace(streetId, angle); - } - } - if (tl.streetPriorities().empty()) { - /************************************************************* - * 1. Check for street names with multiple occurrences - * ***********************************************************/ - std::unordered_map counts; - for (auto const& [streetId, name] : streetNames) { - if (name.empty()) { - // Ignore empty names - continue; + void RoadNetwork::autoInitTrafficLights(double const mainRoadPercentage) { + tbb::parallel_for_each( + m_nodes.begin(), m_nodes.end(), [this, mainRoadPercentage](auto& pair) { + auto& pNode = pair.second; + if (!pNode->isTrafficLight()) { + return; } - if (!counts.contains(name)) { - counts[name] = 1; - } else { - ++counts.at(name); + auto& tl = static_cast(*pNode); + if (!tl.streetPriorities().empty() || !tl.cycles().empty()) { + return; } - } - // Check if spdlog is in debug mode - if (spdlog::get_level() <= spdlog::level::debug) { - for (auto const& [name, count] : counts) { - spdlog::debug("Street name {} has {} occurrences", name, count); + auto const& inNeighbours = pNode->ingoingEdges(); + std::map> capacities; + std::unordered_map streetAngles; + std::unordered_map maxSpeeds; + std::unordered_map nLanes; + std::unordered_map streetNames; + double higherSpeed{0.}, lowerSpeed{std::numeric_limits::max()}; + int higherNLanes{0}, lowerNLanes{std::numeric_limits::max()}; + if (inNeighbours.size() < 3) { + spdlog::warn("Not enough in neighbours {} for Traffic Light {}", + inNeighbours.size(), + pNode->id()); + // Replace with a normal intersection + auto const& geometry{pNode->geometry()}; + if (geometry.has_value()) { + pNode = std::make_unique(pNode->id(), *geometry); + } else { + pNode = std::make_unique(pNode->id()); + } + return; } - } - for (auto const& [streetId, name] : streetNames) { - if (!name.empty() && counts.at(name) > 1) { - tl.addStreetPriority(streetId); + for (auto const& edgeId : inNeighbours) { + auto const& pStreet{edge(edgeId)}; + + double const speed{pStreet->maxSpeed()}; + int const nLan{pStreet->nLanes()}; + auto const cap{pStreet->capacity()}; + capacities.emplace(pStreet->id(), cap); + auto angle{pStreet->angle()}; + if (angle < 0.) { + angle += 2 * std::numbers::pi; + } + streetAngles.emplace(pStreet->id(), angle); + + maxSpeeds.emplace(pStreet->id(), speed); + nLanes.emplace(pStreet->id(), nLan); + streetNames.emplace(pStreet->id(), pStreet->name()); + + higherSpeed = std::max(higherSpeed, speed); + lowerSpeed = std::min(lowerSpeed, speed); + + higherNLanes = std::max(higherNLanes, nLan); + lowerNLanes = std::min(lowerNLanes, nLan); + + if (pStreet->hasPriority()) { + tl.addStreetPriority(pStreet->id()); + } } - } - } - if (tl.streetPriorities().empty() && higherSpeed != lowerSpeed) { - /************************************************************* + + if (tl.streetPriorities().empty()) { + /************************************************************* + * 1. Check for street names with multiple occurrences + * ***********************************************************/ + std::unordered_map counts; + for (auto const& [streetId, name] : streetNames) { + if (name.empty()) { + // Ignore empty names + return; + } + if (!counts.contains(name)) { + counts[name] = 1; + } else { + ++counts.at(name); + } + } + // Check if spdlog is in debug mode + if (spdlog::get_level() <= spdlog::level::debug) { + for (auto const& [name, count] : counts) { + spdlog::debug("Street name {} has {} occurrences", name, count); + } + } + for (auto const& [streetId, name] : streetNames) { + if (!name.empty() && counts.at(name) > 1) { + tl.addStreetPriority(streetId); + } + } + } + if (tl.streetPriorities().empty() && higherSpeed != lowerSpeed) { + /************************************************************* * 2. Check for street names with same max speed * ***********************************************************/ - for (auto const& [sid, speed] : maxSpeeds) { - if (speed == higherSpeed) { - tl.addStreetPriority(sid); + for (auto const& [sid, speed] : maxSpeeds) { + if (speed == higherSpeed) { + tl.addStreetPriority(sid); + } + } } - } - } - if (tl.streetPriorities().empty() && higherNLanes != lowerNLanes) { - /************************************************************* + if (tl.streetPriorities().empty() && higherNLanes != lowerNLanes) { + /************************************************************* * 2. Check for street names with same number of lanes * ***********************************************************/ - for (auto const& [sid, nLan] : nLanes) { - if (nLan == higherNLanes) { - tl.addStreetPriority(sid); + for (auto const& [sid, nLan] : nLanes) { + if (nLan == higherNLanes) { + tl.addStreetPriority(sid); + } + } } - } - } - if (tl.streetPriorities().empty()) { - /************************************************************* + if (tl.streetPriorities().empty()) { + /************************************************************* * 3. Check for streets with opposite angles * ***********************************************************/ - auto const& streetId = streetAngles.begin()->first; - auto const& angle = streetAngles.begin()->second; - for (auto const& [streetId2, angle2] : streetAngles) { - if (std::abs(angle - angle2) > 0.75 * std::numbers::pi) { - tl.addStreetPriority(streetId); - tl.addStreetPriority(streetId2); - break; + std::vector> sortedAngles; + std::copy(streetAngles.begin(), + streetAngles.end(), + std::back_inserter(sortedAngles)); + std::sort(sortedAngles.begin(), + sortedAngles.end(), + [](auto const& a, auto const& b) { return a.second < b.second; }); + streetAngles.clear(); + for (auto const& [streetId, angle] : sortedAngles) { + streetAngles.emplace(streetId, angle); + } + + auto const& streetId = streetAngles.begin()->first; + auto const& angle = streetAngles.begin()->second; + for (auto const& [streetId2, angle2] : streetAngles) { + if (std::abs(angle - angle2) > 0.75 * std::numbers::pi) { + tl.addStreetPriority(streetId); + tl.addStreetPriority(streetId2); + break; + } + } } - } - } - if (tl.streetPriorities().empty()) { - spdlog::warn("Failed to auto-init Traffic Light {} - going random", pNode->id()); - // Assign first and third keys of capacity map - auto it = capacities.begin(); - auto const& firstKey = it->first; - ++it; - ++it; - auto const& thirdKey = it->first; - tl.addStreetPriority(firstKey); - tl.addStreetPriority(thirdKey); - } - - // Assign cycles - std::pair greenTimes; - { - auto capPriority{0.}, capNoPriority{0.}; - std::unordered_map normCapacities; - auto sum{0.}; - for (auto const& [streetId, cap] : capacities) { - sum += cap; - } - for (auto const& [streetId, cap] : capacities) { - normCapacities.emplace(streetId, cap / sum); - } - for (auto const& [streetId, normCap] : normCapacities) { - if (tl.streetPriorities().contains(streetId)) { - capPriority += normCap; - } else { - capNoPriority += normCap; + if (tl.streetPriorities().empty() || tl.streetPriorities().size() != 2) { + spdlog::warn("Failed to auto-init Traffic Light {} - going random", + pNode->id()); + // Assign first and third keys of capacity map + auto it = capacities.begin(); + auto const& firstKey = it->first; + ++it; + ++it; + auto const& thirdKey = it->first; + tl.addStreetPriority(firstKey); + tl.addStreetPriority(thirdKey); } - } - spdlog::debug("Capacities for Traffic Light {}: priority {} no priority {}", - pNode->id(), - capPriority, - capNoPriority); - greenTimes = std::make_pair(static_cast(capPriority * tl.cycleTime()), - static_cast(capNoPriority * tl.cycleTime())); - } - // if one of green times is less than 20, set it to 20 and refactor the other to have the sum to 120 - if (greenTimes.first < minGreenTime) { - greenTimes.first = minGreenTime; - greenTimes.second = tl.cycleTime() - minGreenTime; - } - if (greenTimes.second < minGreenTime) { - greenTimes.second = minGreenTime; - greenTimes.first = tl.cycleTime() - minGreenTime; - } - std::for_each(inNeighbours.begin(), inNeighbours.end(), [&](auto const& edgeId) { - auto const streetId{this->edge(edgeId)->id()}; - auto const nLane{nLanes.at(streetId)}; - Delay greenTime{greenTimes.first}; - Delay phase{0}; - if (!tl.streetPriorities().contains(streetId)) { - phase = greenTime; - greenTime = greenTimes.second; - } - spdlog::debug("Setting cycle for street {} with green time {} and phase {}", - streetId, - greenTime, - phase); - switch (nLane) { - case 3: - tl.setCycle(streetId, - dsf::Direction::RIGHTANDSTRAIGHT, - TrafficLightCycle{static_cast(greenTime * 2. / 3), phase}); - tl.setCycle( - streetId, - dsf::Direction::LEFT, - TrafficLightCycle{ - static_cast(greenTime / 3.), - static_cast(phase + static_cast(greenTime * 2. / 3))}); - break; - default: - tl.setCycle( - streetId, dsf::Direction::ANY, TrafficLightCycle{greenTime, phase}); - break; - } - }); - } + + // Assign cycles + std::pair greenTimes; + { + auto const mainGreenTime{ + static_cast(mainRoadPercentage * tl.cycleTime())}; + auto const secondaryGreenTime{ + static_cast(tl.cycleTime() - mainGreenTime)}; + greenTimes = std::make_pair(mainGreenTime, secondaryGreenTime); + } + std::for_each(inNeighbours.begin(), inNeighbours.end(), [&](auto const& edgeId) { + auto const streetId{this->edge(edgeId)->id()}; + auto const nLane{nLanes.at(streetId)}; + Delay greenTime{greenTimes.first}; + Delay phase{0}; + if (!tl.streetPriorities().contains(streetId)) { + phase = greenTime; + greenTime = greenTimes.second; + } + spdlog::debug("Setting cycle for street {} with green time {} and phase {}", + streetId, + greenTime, + phase); + switch (nLane) { + case 3: + tl.setCycle( + streetId, + dsf::Direction::RIGHTANDSTRAIGHT, + TrafficLightCycle{static_cast(greenTime * 2. / 3), phase}); + tl.setCycle(streetId, + dsf::Direction::LEFT, + TrafficLightCycle{ + static_cast(greenTime / 3.), + static_cast( + phase + static_cast(greenTime * 2. / 3))}); + break; + default: + tl.setCycle( + streetId, dsf::Direction::ANY, TrafficLightCycle{greenTime, phase}); + break; + } + }); + }); } void RoadNetwork::autoMapStreetLanes() { auto const& nodes = this->nodes(); diff --git a/src/dsf/mobility/RoadNetwork.hpp b/src/dsf/mobility/RoadNetwork.hpp index e6d671cc..e1eebaaa 100644 --- a/src/dsf/mobility/RoadNetwork.hpp +++ b/src/dsf/mobility/RoadNetwork.hpp @@ -90,15 +90,15 @@ namespace dsf::mobility { /// @details The nodes' capacity is adjusted using the graph's streets transport capacity, which may vary basing on the number of lanes. The node capacity will be set to the sum of the incoming streets' transport capacity. void adjustNodeCapacities(); /// @brief Initialize the traffic lights with random parameters - /// @param minGreenTime The minimum green time for the traffic lights cycles (default is 30) + /// @param mainRoadPercentage The percentage of main roads for the traffic lights cycles (default is 0.6) /// @details Traffic Lights with no parameters set are initialized with random parameters. /// Street priorities are assigned considering the number of lanes and the speed limit. /// Traffic Lights with an input degree lower than 3 are converted to standard intersections. - void initTrafficLights(Delay const minGreenTime = 30); + void autoInitTrafficLights(double const mainRoadPercentage = 0.6); /// @brief Automatically re-maps street lanes basing on network's topology /// @details For example, if one street has the right turn forbidden, then the right lane becomes a straight one void autoMapStreetLanes(); - + /// @brief Automatically assigns road priorities at intersections, basing on road types void autoAssignRoadPriorities(); /// @brief Import the graph's streets from a file diff --git a/test/mobility/Test_graph.cpp b/test/mobility/Test_graph.cpp index d276e1e5..f4441216 100644 --- a/test/mobility/Test_graph.cpp +++ b/test/mobility/Test_graph.cpp @@ -242,7 +242,9 @@ TEST_CASE("RoadNetwork") { SUBCASE("make trafficlight") { GIVEN("A graph with a traffic light with no parameters") { Street s1{1, std::make_pair(0, 1), 30., 15., 3}; + s1.setPriority(); Street s2{11, std::make_pair(2, 1), 30., 15., 3}; + s2.setPriority(); Street s3{16, std::make_pair(3, 1), 30., 15., 1}; Street s4{21, std::make_pair(4, 1), 30., 15., 2}; RoadNetwork graph2; @@ -253,24 +255,24 @@ TEST_CASE("RoadNetwork") { pStreet->setCapacity(2 * pStreet->nLanes()); } WHEN("We auto-init Traffic Lights") { - graph2.initTrafficLights(); + graph2.autoInitTrafficLights(); THEN("Parameters are correctly set") { auto& tl{graph2.node(1)}; CHECK_EQ(tl.cycleTime(), 120); auto const& cycles{tl.cycles()}; CHECK_EQ(cycles.size(), 4); - CHECK_EQ(cycles.at(1).at(dsf::Direction::RIGHTANDSTRAIGHT).greenTime(), 53); - CHECK_EQ(cycles.at(1).at(dsf::Direction::LEFT).greenTime(), 26); + CHECK_EQ(cycles.at(1).at(dsf::Direction::RIGHTANDSTRAIGHT).greenTime(), 48); + CHECK_EQ(cycles.at(1).at(dsf::Direction::LEFT).greenTime(), 24); CHECK_EQ(cycles.at(1).at(dsf::Direction::RIGHTANDSTRAIGHT).phase(), 0); - CHECK_EQ(cycles.at(1).at(dsf::Direction::LEFT).phase(), 53); - CHECK_EQ(cycles.at(11).at(dsf::Direction::RIGHTANDSTRAIGHT).greenTime(), 53); - CHECK_EQ(cycles.at(11).at(dsf::Direction::LEFT).greenTime(), 26); + CHECK_EQ(cycles.at(1).at(dsf::Direction::LEFT).phase(), 48); + CHECK_EQ(cycles.at(11).at(dsf::Direction::RIGHTANDSTRAIGHT).greenTime(), 48); + CHECK_EQ(cycles.at(11).at(dsf::Direction::LEFT).greenTime(), 24); CHECK_EQ(cycles.at(11).at(dsf::Direction::RIGHTANDSTRAIGHT).phase(), 0); - CHECK_EQ(cycles.at(11).at(dsf::Direction::LEFT).phase(), 53); - CHECK_EQ(cycles.at(16).at(dsf::Direction::ANY).greenTime(), 40); - CHECK_EQ(cycles.at(16).at(dsf::Direction::ANY).phase(), 80); - CHECK_EQ(cycles.at(21).at(dsf::Direction::ANY).greenTime(), 40); - CHECK_EQ(cycles.at(21).at(dsf::Direction::ANY).phase(), 80); + CHECK_EQ(cycles.at(11).at(dsf::Direction::LEFT).phase(), 48); + CHECK_EQ(cycles.at(16).at(dsf::Direction::ANY).greenTime(), 48); + CHECK_EQ(cycles.at(16).at(dsf::Direction::ANY).phase(), 72); + CHECK_EQ(cycles.at(21).at(dsf::Direction::ANY).greenTime(), 48); + CHECK_EQ(cycles.at(21).at(dsf::Direction::ANY).phase(), 72); } } } From 91bcf15cbb3b8fbac24193e66055d2943bfa8d93 Mon Sep 17 00:00:00 2001 From: Grufoony Date: Fri, 16 Jan 2026 16:14:42 +0100 Subject: [PATCH 9/9] Formatting --- src/dsf/bindings.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dsf/bindings.cpp b/src/dsf/bindings.cpp index 14216846..e63666be 100644 --- a/src/dsf/bindings.cpp +++ b/src/dsf/bindings.cpp @@ -168,13 +168,15 @@ PYBIND11_MODULE(dsf_cpp, m) { .def("autoInitTrafficLights", &dsf::mobility::RoadNetwork::autoInitTrafficLights, pybind11::arg("mainRoadPercentage") = 0.6, - dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoInitTrafficLights").c_str()) + dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoInitTrafficLights") + .c_str()) .def("autoMapStreetLanes", &dsf::mobility::RoadNetwork::autoMapStreetLanes, dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoMapStreetLanes").c_str()) .def("autoAssignRoadPriorities", &dsf::mobility::RoadNetwork::autoAssignRoadPriorities, - dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoAssignRoadPriorities").c_str()) + dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoAssignRoadPriorities") + .c_str()) .def("setStreetStationaryWeights", &dsf::mobility::RoadNetwork::setStreetStationaryWeights, pybind11::arg("weights"),