From af195f94f8ad89a1d366e21341939862037b9eb0 Mon Sep 17 00:00:00 2001 From: Grufoony Date: Fri, 23 Jan 2026 14:29:11 +0100 Subject: [PATCH 1/8] Add RoadNetwork::describe function --- src/dsf/bindings.cpp | 4 ++++ src/dsf/mobility/RoadNetwork.cpp | 16 ++++++++++++---- src/dsf/mobility/RoadNetwork.hpp | 21 +++++++++++++-------- test/mobility/Test_graph.cpp | 10 ++++++++++ 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/dsf/bindings.cpp b/src/dsf/bindings.cpp index d1bd7b26..065491e2 100644 --- a/src/dsf/bindings.cpp +++ b/src/dsf/bindings.cpp @@ -173,6 +173,10 @@ PYBIND11_MODULE(dsf_cpp, m) { .def("autoMapStreetLanes", &dsf::mobility::RoadNetwork::autoMapStreetLanes, dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoMapStreetLanes").c_str()) + .def("describe", + &dsf::mobility::RoadNetwork::describe, + pybind11::arg("os") = std::cout, + dsf::g_docstrings.at("dsf::mobility::RoadNetwork::describe").c_str()) .def("autoAssignRoadPriorities", &dsf::mobility::RoadNetwork::autoAssignRoadPriorities, dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoAssignRoadPriorities") diff --git a/src/dsf/mobility/RoadNetwork.cpp b/src/dsf/mobility/RoadNetwork.cpp index a2e39301..213933d7 100644 --- a/src/dsf/mobility/RoadNetwork.cpp +++ b/src/dsf/mobility/RoadNetwork.cpp @@ -336,23 +336,23 @@ namespace dsf::mobility { RoadNetwork::RoadNetwork(AdjacencyMatrix const& adj) : Network{adj}, m_capacity{0} {} - Size RoadNetwork::nCoils() const { + std::size_t RoadNetwork::nCoils() const { return std::count_if(m_edges.cbegin(), m_edges.cend(), [](auto const& pair) { return pair.second->hasCoil(); }); } - Size RoadNetwork::nIntersections() const { + std::size_t RoadNetwork::nIntersections() const { return std::count_if(m_nodes.cbegin(), m_nodes.cend(), [](auto const& pair) { return pair.second->isIntersection(); }); } - Size RoadNetwork::nRoundabouts() const { + std::size_t RoadNetwork::nRoundabouts() const { return std::count_if(m_nodes.cbegin(), m_nodes.cend(), [](auto const& pair) { return pair.second->isRoundabout(); }); } - Size RoadNetwork::nTrafficLights() const { + std::size_t RoadNetwork::nTrafficLights() const { return std::count_if(m_nodes.cbegin(), m_nodes.cend(), [](auto const& pair) { return pair.second->isTrafficLight(); }); @@ -815,6 +815,14 @@ namespace dsf::mobility { spdlog::debug("Done auto-assigning road priorities."); } + void RoadNetwork::describe(std::ostream& os) const { + os << "RoadNetwork with " << nNodes() << " nodes and " << nEdges() + << " edges. Total capacity: " << m_capacity << " vehicles.\n" + << "There are\n- " << nIntersections() << " intersections,\n- " << nTrafficLights() + << " traffic lights,\n- " << nRoundabouts() << " roundabouts,\n- " << nCoils() + << " coil sensors.\n"; + } + 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 e1eebaaa..42fb845d 100644 --- a/src/dsf/mobility/RoadNetwork.hpp +++ b/src/dsf/mobility/RoadNetwork.hpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -73,18 +74,18 @@ namespace dsf::mobility { RoadNetwork& operator=(RoadNetwork&&) = default; /// @brief Get the graph's number of coil streets - /// @return The number of coil streets - Size nCoils() const; + /// @return std::size_t The number of coil streets + std::size_t nCoils() const; /// @brief Get the graph's number of intersections - /// @return The number of intersections - Size nIntersections() const; + /// @return std::size_t The number of intersections + std::size_t nIntersections() const; /// @brief Get the graph's number of roundabouts - /// @return The number of roundabouts - Size nRoundabouts() const; + /// @return std::size_t The number of roundabouts + std::size_t nRoundabouts() const; /// @brief Get the graph's number of traffic lights - /// @return The number of traffic lights - Size nTrafficLights() const; + /// @return std::size_t The number of traffic lights + std::size_t nTrafficLights() const; /// @brief Adjust the nodes' transport capacity /// @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. @@ -101,6 +102,10 @@ namespace dsf::mobility { /// @brief Automatically assigns road priorities at intersections, basing on road types void autoAssignRoadPriorities(); + /// @brief Describe the RoadNetwork + /// @param os The output stream to write the description to (default is std::cout) + void describe(std::ostream& os = std::cout) const; + /// @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. diff --git a/test/mobility/Test_graph.cpp b/test/mobility/Test_graph.cpp index be71e140..4127a16d 100644 --- a/test/mobility/Test_graph.cpp +++ b/test/mobility/Test_graph.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include "doctest.h" @@ -87,6 +88,15 @@ TEST_CASE("RoadNetwork") { CHECK(graph.edge(0, 2)); CHECK(graph.edge(0, 3)); CHECK(graph.edge(2, 3)); + // Test describe method + std::ostringstream oss; + graph.describe(oss); + std::string description = oss.str(); + CHECK(description.find("RoadNetwork with 4 nodes and 5 edges") != std::string::npos); + CHECK(description.find("intersections") != std::string::npos); + CHECK(description.find("traffic lights") != std::string::npos); + CHECK(description.find("roundabouts") != std::string::npos); + CHECK(description.find("coil sensors") != std::string::npos); } SUBCASE("automatically do things") { From b200dc58594b63dd0cfc80779ff679e6e8764bec Mon Sep 17 00:00:00 2001 From: Grufoony Date: Fri, 23 Jan 2026 14:46:36 +0100 Subject: [PATCH 2/8] Better constructor --- src/dsf/mobility/RoadDynamics.hpp | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/dsf/mobility/RoadDynamics.hpp b/src/dsf/mobility/RoadDynamics.hpp index aea2bab3..e6a9726a 100644 --- a/src/dsf/mobility/RoadDynamics.hpp +++ b/src/dsf/mobility/RoadDynamics.hpp @@ -51,7 +51,7 @@ namespace dsf::mobility { std::unordered_map m_destinationNodes; tbb::concurrent_unordered_map m_originCounts; tbb::concurrent_unordered_map m_destinationCounts; - Size m_nAgents; + std::size_t m_nAgents{0}; protected: std::unordered_map> m_turnCounts; @@ -59,19 +59,19 @@ namespace dsf::mobility { tbb::concurrent_unordered_map> m_queuesAtTrafficLights; tbb::concurrent_vector> m_travelDTs; - std::time_t m_previousOptimizationTime; + std::time_t m_previousOptimizationTime{0}; private: std::function const&)> m_weightFunction; - std::optional m_errorProbability; - std::optional m_passageProbability; - std::optional m_meanTravelDistance; - std::optional m_meanTravelTime; + std::optional m_errorProbability{std::nullopt}; + std::optional m_passageProbability{std::nullopt}; + std::optional m_meanTravelDistance{std::nullopt}; + std::optional m_meanTravelTime{std::nullopt}; double m_weightTreshold; - std::optional m_timeToleranceFactor; + std::optional m_timeToleranceFactor{std::nullopt}; std::optional m_dataUpdatePeriod; bool m_bCacheEnabled; - bool m_forcePriorities; + bool m_forcePriorities{false}; private: /// @brief Kill an agent @@ -403,16 +403,7 @@ namespace dsf::mobility { std::optional seed, PathWeight const weightFunction, std::optional weightTreshold) - : Dynamics(graph, seed), - m_nAgents{0}, - m_previousOptimizationTime{0}, - m_errorProbability{std::nullopt}, - m_passageProbability{std::nullopt}, - m_meanTravelDistance{std::nullopt}, - m_meanTravelTime{std::nullopt}, - m_timeToleranceFactor{std::nullopt}, - m_bCacheEnabled{useCache}, - m_forcePriorities{false} { + : Dynamics(graph, seed), m_bCacheEnabled{useCache} { this->setWeightFunction(weightFunction, weightTreshold); if (m_bCacheEnabled) { if (!std::filesystem::exists(CACHE_FOLDER)) { From e07f4bc3d3f2109ff12d5337de624e19d2811634 Mon Sep 17 00:00:00 2001 From: Grufoony Date: Fri, 23 Jan 2026 14:58:12 +0100 Subject: [PATCH 3/8] Add RoadDynamics::summary function --- src/dsf/bindings.cpp | 6 +++++- src/dsf/mobility/RoadDynamics.hpp | 26 +++++++++++++++++++++++++- test/mobility/Test_dynamics.cpp | 15 +++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/dsf/bindings.cpp b/src/dsf/bindings.cpp index 065491e2..ef60d046 100644 --- a/src/dsf/bindings.cpp +++ b/src/dsf/bindings.cpp @@ -654,7 +654,11 @@ PYBIND11_MODULE(dsf_cpp, m) { pybind11::arg("filename"), pybind11::arg("separator") = ';', dsf::g_docstrings.at("dsf::mobility::RoadDynamics::saveMacroscopicObservables") - .c_str()); + .c_str()) + .def("summary", + &dsf::mobility::FirstOrderDynamics::summary, + pybind11::arg("os") = std::cout, + dsf::g_docstrings.at("dsf::mobility::RoadDynamics::summary").c_str()); // Bind TrajectoryCollection class to mdt submodule pybind11::class_(mdt, "TrajectoryCollection") diff --git a/src/dsf/mobility/RoadDynamics.hpp b/src/dsf/mobility/RoadDynamics.hpp index e6a9726a..57688d80 100644 --- a/src/dsf/mobility/RoadDynamics.hpp +++ b/src/dsf/mobility/RoadDynamics.hpp @@ -51,7 +51,8 @@ namespace dsf::mobility { std::unordered_map m_destinationNodes; tbb::concurrent_unordered_map m_originCounts; tbb::concurrent_unordered_map m_destinationCounts; - std::size_t m_nAgents{0}; + std::size_t m_nAgents{0}, m_nAddedAgents{0}, m_nInsertedAgents{0}, m_nKilledAgents{0}, + m_nArrivedAgents{0}; protected: std::unordered_map> m_turnCounts; @@ -394,6 +395,17 @@ namespace dsf::mobility { /// NOTE: the mean density is normalized in [0, 1] and reset is true for all observables which have such parameter void saveMacroscopicObservables(std::string filename = std::string(), char const separator = ';'); + + /// @brief Print a summary of the dynamics to an output stream + /// @param os The output stream to write to (default is std::cout) + /// @details The summary includes: + /// - The RoadNetwork description (nodes, edges, capacity, intersections, traffic lights, roundabouts, coil sensors) + /// - Number of inserted agents + /// - Number of added agents + /// - Number of arrived agents + /// - Number of killed agents + /// - Current number of agents in the simulation + void summary(std::ostream& os = std::cout) const; }; template @@ -2489,4 +2501,16 @@ namespace dsf::mobility { file.close(); } + + template + requires(is_numeric_v) + void RoadDynamics::summary(std::ostream& os) const { + os << "RoadDynamics Summary:\n"; + this->graph().describe(os); + os << "\nNumber of inserted agents: " << m_nInsertedAgents << '\n' + << "Number of added agents: " << m_nAddedAgents << '\n' + << "Number of arrived agents: " << m_nArrivedAgents << '\n' + << "Number of killed agents: " << m_nKilledAgents << '\n' + << "Current number of agents: " << this->nAgents() << '\n'; + } } // namespace dsf::mobility diff --git a/test/mobility/Test_dynamics.cpp b/test/mobility/Test_dynamics.cpp index 9f14b8c7..c0ccf523 100644 --- a/test/mobility/Test_dynamics.cpp +++ b/test/mobility/Test_dynamics.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include "doctest.h" @@ -93,6 +94,20 @@ TEST_CASE("FirstOrderDynamics") { FirstOrderDynamics dynamics{defaultNetwork, false, 69}; THEN("The street has a coil") { CHECK(dynamics.graph().edge(8)->hasCoil()); } } + WHEN("We call summary") { + FirstOrderDynamics dynamics{defaultNetwork, false, 69}; + std::ostringstream oss; + dynamics.summary(oss); + std::string summaryStr = oss.str(); + THEN("The summary contains expected information") { + CHECK(summaryStr.find("RoadDynamics Summary") != std::string::npos); + CHECK(summaryStr.find("RoadNetwork with 120 nodes and 436 edges") != std::string::npos); + CHECK(summaryStr.find("Number of inserted agents") != std::string::npos); + CHECK(summaryStr.find("Number of added agents") != std::string::npos); + CHECK(summaryStr.find("Number of killed agents") != std::string::npos); + CHECK(summaryStr.find("Current number of agents: 0") != std::string::npos); + } + } } } SUBCASE("setDestinationNodes") { From 2ca634aa2de1a5be2659c8c504dda97979dc3d78 Mon Sep 17 00:00:00 2001 From: Grufoony Date: Fri, 23 Jan 2026 15:05:38 +0100 Subject: [PATCH 4/8] Fix python binding --- src/dsf/bindings.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/dsf/bindings.cpp b/src/dsf/bindings.cpp index ef60d046..da7fec2d 100644 --- a/src/dsf/bindings.cpp +++ b/src/dsf/bindings.cpp @@ -173,10 +173,12 @@ PYBIND11_MODULE(dsf_cpp, m) { .def("autoMapStreetLanes", &dsf::mobility::RoadNetwork::autoMapStreetLanes, dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoMapStreetLanes").c_str()) - .def("describe", - &dsf::mobility::RoadNetwork::describe, - pybind11::arg("os") = std::cout, - dsf::g_docstrings.at("dsf::mobility::RoadNetwork::describe").c_str()) + .def( + "describe", + [](dsf::mobility::RoadNetwork& self) { + self.describe(); // Uses default std::cout + }, + dsf::g_docstrings.at("dsf::mobility::RoadDynamics::summary").c_str()) .def("autoAssignRoadPriorities", &dsf::mobility::RoadNetwork::autoAssignRoadPriorities, dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoAssignRoadPriorities") @@ -655,10 +657,12 @@ PYBIND11_MODULE(dsf_cpp, m) { pybind11::arg("separator") = ';', dsf::g_docstrings.at("dsf::mobility::RoadDynamics::saveMacroscopicObservables") .c_str()) - .def("summary", - &dsf::mobility::FirstOrderDynamics::summary, - pybind11::arg("os") = std::cout, - dsf::g_docstrings.at("dsf::mobility::RoadDynamics::summary").c_str()); + .def( + "summary", + [](dsf::mobility::FirstOrderDynamics& self) { + self.summary(); // Uses default std::cout + }, + dsf::g_docstrings.at("dsf::mobility::RoadDynamics::summary").c_str()); // Bind TrajectoryCollection class to mdt submodule pybind11::class_(mdt, "TrajectoryCollection") From ef00bdd3dcde23d4c9340fdc09deb375d3c389ee Mon Sep 17 00:00:00 2001 From: Grufoony Date: Fri, 23 Jan 2026 15:09:33 +0100 Subject: [PATCH 5/8] Fix --- src/dsf/mobility/RoadDynamics.hpp | 12 +++++++++--- test/mobility/Test_dynamics.cpp | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/dsf/mobility/RoadDynamics.hpp b/src/dsf/mobility/RoadDynamics.hpp index 57688d80..ecd039f8 100644 --- a/src/dsf/mobility/RoadDynamics.hpp +++ b/src/dsf/mobility/RoadDynamics.hpp @@ -395,7 +395,7 @@ namespace dsf::mobility { /// NOTE: the mean density is normalized in [0, 1] and reset is true for all observables which have such parameter void saveMacroscopicObservables(std::string filename = std::string(), char const separator = ';'); - + /// @brief Print a summary of the dynamics to an output stream /// @param os The output stream to write to (default is std::cout) /// @details The summary includes: @@ -741,6 +741,7 @@ namespace dsf::mobility { timeDiff); // Kill the agent this->m_killAgent(pStreet->dequeue(queueIndex)); + ++m_nKilledAgents; continue; } } @@ -896,6 +897,7 @@ namespace dsf::mobility { } if (bArrived) { auto pAgent = this->m_killAgent(pStreet->dequeue(queueIndex)); + ++m_nArrivedAgents; if (reinsert_agents) { // reset Agent's values pAgent->reset(this->time_step()); @@ -1295,6 +1297,7 @@ namespace dsf::mobility { requires(is_numeric_v) void RoadDynamics::addAgentsUniformly(Size nAgents, std::optional optItineraryId) { + m_nAddedAgents += nAgents; if (m_timeToleranceFactor.has_value() && !m_agents.empty()) { auto const nStagnantAgents{m_agents.size()}; spdlog::warn( @@ -1359,6 +1362,7 @@ namespace dsf::mobility { std::is_same_v>) void RoadDynamics::addRandomAgents(std::size_t nAgents, TContainer const& spawnWeights) { + m_nAddedAgents += nAgents; std::uniform_real_distribution uniformDist{0., 1.}; std::exponential_distribution distDist{1. / m_meanTravelDistance.value_or(1.)}; @@ -1405,6 +1409,7 @@ namespace dsf::mobility { void RoadDynamics::addAgentsRandomly(Size nAgents, const TContainer& src_weights, const TContainer& dst_weights) { + m_nAddedAgents += nAgents; if (m_timeToleranceFactor.has_value() && !m_agents.empty()) { auto const nStagnantAgents{m_agents.size()}; spdlog::warn( @@ -1536,6 +1541,7 @@ namespace dsf::mobility { void RoadDynamics::addAgent(std::unique_ptr pAgent) { m_agents.push_back(std::move(pAgent)); ++m_nAgents; + ++m_nInsertedAgents; spdlog::trace("Added {}", *m_agents.back()); auto const& optNodeId{m_agents.back()->srcNodeId()}; if (optNodeId.has_value()) { @@ -2507,8 +2513,8 @@ namespace dsf::mobility { void RoadDynamics::summary(std::ostream& os) const { os << "RoadDynamics Summary:\n"; this->graph().describe(os); - os << "\nNumber of inserted agents: " << m_nInsertedAgents << '\n' - << "Number of added agents: " << m_nAddedAgents << '\n' + os << "\nNumber of added agents: " << m_nAddedAgents << '\n' + << "Number of inserted agents: " << m_nInsertedAgents << '\n' << "Number of arrived agents: " << m_nArrivedAgents << '\n' << "Number of killed agents: " << m_nKilledAgents << '\n' << "Current number of agents: " << this->nAgents() << '\n'; diff --git a/test/mobility/Test_dynamics.cpp b/test/mobility/Test_dynamics.cpp index c0ccf523..c78f97ec 100644 --- a/test/mobility/Test_dynamics.cpp +++ b/test/mobility/Test_dynamics.cpp @@ -101,7 +101,8 @@ TEST_CASE("FirstOrderDynamics") { std::string summaryStr = oss.str(); THEN("The summary contains expected information") { CHECK(summaryStr.find("RoadDynamics Summary") != std::string::npos); - CHECK(summaryStr.find("RoadNetwork with 120 nodes and 436 edges") != std::string::npos); + CHECK(summaryStr.find("RoadNetwork with 120 nodes and 436 edges") != + std::string::npos); CHECK(summaryStr.find("Number of inserted agents") != std::string::npos); CHECK(summaryStr.find("Number of added agents") != std::string::npos); CHECK(summaryStr.find("Number of killed agents") != std::string::npos); From 26867907b23f03cf8c94f0dd1ed11491b2440a1e Mon Sep 17 00:00:00 2001 From: Gregorio Berselli Date: Fri, 23 Jan 2026 15:24:09 +0100 Subject: [PATCH 6/8] Update src/dsf/bindings.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/dsf/bindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dsf/bindings.cpp b/src/dsf/bindings.cpp index da7fec2d..0a636e5b 100644 --- a/src/dsf/bindings.cpp +++ b/src/dsf/bindings.cpp @@ -178,7 +178,7 @@ PYBIND11_MODULE(dsf_cpp, m) { [](dsf::mobility::RoadNetwork& self) { self.describe(); // Uses default std::cout }, - dsf::g_docstrings.at("dsf::mobility::RoadDynamics::summary").c_str()) + dsf::g_docstrings.at("dsf::mobility::RoadNetwork::describe").c_str()) .def("autoAssignRoadPriorities", &dsf::mobility::RoadNetwork::autoAssignRoadPriorities, dsf::g_docstrings.at("dsf::mobility::RoadNetwork::autoAssignRoadPriorities") From 88fcdd9c2ffa1846b204d778dc00b11cb30dda34 Mon Sep 17 00:00:00 2001 From: Grufoony Date: Fri, 23 Jan 2026 15:26:36 +0100 Subject: [PATCH 7/8] Thread safety --- src/dsf/mobility/RoadDynamics.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dsf/mobility/RoadDynamics.hpp b/src/dsf/mobility/RoadDynamics.hpp index ecd039f8..c769928e 100644 --- a/src/dsf/mobility/RoadDynamics.hpp +++ b/src/dsf/mobility/RoadDynamics.hpp @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -51,8 +53,8 @@ namespace dsf::mobility { std::unordered_map m_destinationNodes; tbb::concurrent_unordered_map m_originCounts; tbb::concurrent_unordered_map m_destinationCounts; - std::size_t m_nAgents{0}, m_nAddedAgents{0}, m_nInsertedAgents{0}, m_nKilledAgents{0}, - m_nArrivedAgents{0}; + std::atomic m_nAgents{0}, m_nAddedAgents{0}, m_nInsertedAgents{0}, + m_nKilledAgents{0}, m_nArrivedAgents{0}; protected: std::unordered_map> m_turnCounts; From b7af18a0ed27f5c3a0c1ce4cd1e5a99d35114004 Mon Sep 17 00:00:00 2001 From: Grufoony Date: Fri, 23 Jan 2026 15:26:57 +0100 Subject: [PATCH 8/8] Bump version --- src/dsf/dsf.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dsf/dsf.hpp b/src/dsf/dsf.hpp index c8a9ca49..a7154348 100644 --- a/src/dsf/dsf.hpp +++ b/src/dsf/dsf.hpp @@ -6,7 +6,7 @@ static constexpr uint8_t DSF_VERSION_MAJOR = 4; static constexpr uint8_t DSF_VERSION_MINOR = 7; -static constexpr uint8_t DSF_VERSION_PATCH = 4; +static constexpr uint8_t DSF_VERSION_PATCH = 5; static auto const DSF_VERSION = std::format("{}.{}.{}", DSF_VERSION_MAJOR, DSF_VERSION_MINOR, DSF_VERSION_PATCH);