Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/dsf/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +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& self) {
self.describe(); // Uses default 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")
Expand Down Expand Up @@ -650,7 +656,13 @@ 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& 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_<dsf::mdt::TrajectoryCollection>(mdt, "TrajectoryCollection")
Expand Down
2 changes: 1 addition & 1 deletion src/dsf/dsf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
59 changes: 41 additions & 18 deletions src/dsf/mobility/RoadDynamics.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#pragma once

#include <algorithm>
#include <atomic>
#include <cassert>
#include <cmath>
#include <concepts>
Expand All @@ -21,6 +22,7 @@
#include <random>
#include <span>
#include <sstream>
#include <iostream>
#include <thread>
#include <unordered_map>
#include <variant>
Expand Down Expand Up @@ -51,27 +53,28 @@ namespace dsf::mobility {
std::unordered_map<Id, double> m_destinationNodes;
tbb::concurrent_unordered_map<Id, std::size_t> m_originCounts;
tbb::concurrent_unordered_map<Id, std::size_t> m_destinationCounts;
Size m_nAgents;
std::atomic<std::size_t> m_nAgents{0}, m_nAddedAgents{0}, m_nInsertedAgents{0},
m_nKilledAgents{0}, m_nArrivedAgents{0};

protected:
std::unordered_map<Id, std::unordered_map<Id, size_t>> m_turnCounts;
std::unordered_map<Id, std::array<long, 4>> m_turnMapping;
tbb::concurrent_unordered_map<Id, std::unordered_map<Direction, double>>
m_queuesAtTrafficLights;
tbb::concurrent_vector<std::pair<double, double>> m_travelDTs;
std::time_t m_previousOptimizationTime;
std::time_t m_previousOptimizationTime{0};

private:
std::function<double(std::unique_ptr<Street> const&)> m_weightFunction;
std::optional<double> m_errorProbability;
std::optional<double> m_passageProbability;
std::optional<double> m_meanTravelDistance;
std::optional<std::time_t> m_meanTravelTime;
std::optional<double> m_errorProbability{std::nullopt};
std::optional<double> m_passageProbability{std::nullopt};
std::optional<double> m_meanTravelDistance{std::nullopt};
std::optional<std::time_t> m_meanTravelTime{std::nullopt};
double m_weightTreshold;
std::optional<double> m_timeToleranceFactor;
std::optional<double> m_timeToleranceFactor{std::nullopt};
std::optional<delay_t> m_dataUpdatePeriod;
bool m_bCacheEnabled;
bool m_forcePriorities;
bool m_forcePriorities{false};

private:
/// @brief Kill an agent
Expand Down Expand Up @@ -394,6 +397,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:
Comment on lines +401 to +403
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

summary() uses std::ostream and defaults the argument to std::cout, but this header doesn’t include <ostream>/<iostream> directly (it currently relies on transitive includes via RoadNetwork.hpp). Add the appropriate standard header include(s) here to avoid fragile build dependencies.

Copilot uses AI. Check for mistakes.
/// - 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 <typename delay_t>
Expand All @@ -403,16 +417,7 @@ namespace dsf::mobility {
std::optional<unsigned int> seed,
PathWeight const weightFunction,
std::optional<double> weightTreshold)
: Dynamics<RoadNetwork>(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<RoadNetwork>(graph, seed), m_bCacheEnabled{useCache} {
this->setWeightFunction(weightFunction, weightTreshold);
if (m_bCacheEnabled) {
if (!std::filesystem::exists(CACHE_FOLDER)) {
Expand Down Expand Up @@ -738,6 +743,7 @@ namespace dsf::mobility {
timeDiff);
// Kill the agent
this->m_killAgent(pStreet->dequeue(queueIndex));
++m_nKilledAgents;
continue;
Comment on lines 745 to 747
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_evolveStreet() runs under tbb::parallel_for (see evolve()), so this increment is not thread-safe on a plain std::size_t counter and can race. Make the counter atomic or accumulate per-thread and reduce after the parallel region.

Copilot uses AI. Check for mistakes.
}
}
Expand Down Expand Up @@ -893,6 +899,7 @@ namespace dsf::mobility {
}
if (bArrived) {
auto pAgent = this->m_killAgent(pStreet->dequeue(queueIndex));
++m_nArrivedAgents;
if (reinsert_agents) {
Comment on lines 901 to 903
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_evolveStreet() is executed in parallel; incrementing m_nArrivedAgents here can race (plain std::size_t). Use an atomic counter or a per-thread accumulator reduced after the parallel region.

Copilot uses AI. Check for mistakes.
// reset Agent's values
pAgent->reset(this->time_step());
Expand Down Expand Up @@ -1292,6 +1299,7 @@ namespace dsf::mobility {
requires(is_numeric_v<delay_t>)
void RoadDynamics<delay_t>::addAgentsUniformly(Size nAgents,
std::optional<Id> optItineraryId) {
m_nAddedAgents += nAgents;
if (m_timeToleranceFactor.has_value() && !m_agents.empty()) {
auto const nStagnantAgents{m_agents.size()};
spdlog::warn(
Expand Down Expand Up @@ -1356,6 +1364,7 @@ namespace dsf::mobility {
std::is_same_v<TContainer, std::map<Id, double>>)
void RoadDynamics<delay_t>::addRandomAgents(std::size_t nAgents,
TContainer const& spawnWeights) {
m_nAddedAgents += nAgents;
std::uniform_real_distribution<double> uniformDist{0., 1.};
std::exponential_distribution<double> distDist{1. /
m_meanTravelDistance.value_or(1.)};
Expand Down Expand Up @@ -1402,6 +1411,7 @@ namespace dsf::mobility {
void RoadDynamics<delay_t>::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(
Expand Down Expand Up @@ -1533,6 +1543,7 @@ namespace dsf::mobility {
void RoadDynamics<delay_t>::addAgent(std::unique_ptr<Agent> 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()) {
Expand Down Expand Up @@ -2498,4 +2509,16 @@ namespace dsf::mobility {

file.close();
}

template <typename delay_t>
requires(is_numeric_v<delay_t>)
void RoadDynamics<delay_t>::summary(std::ostream& os) const {
os << "RoadDynamics Summary:\n";
this->graph().describe(os);
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';
}
} // namespace dsf::mobility
16 changes: 12 additions & 4 deletions src/dsf/mobility/RoadNetwork.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down Expand Up @@ -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()) {
Expand Down
21 changes: 13 additions & 8 deletions src/dsf/mobility/RoadNetwork.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <cassert>
#include <format>

Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions test/mobility/Test_dynamics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <sstream>

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"
Expand Down Expand Up @@ -93,6 +94,21 @@ 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") {
Expand Down
10 changes: 10 additions & 0 deletions test/mobility/Test_graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <cassert>
#include <cstdint>
#include <filesystem>
#include <sstream>

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"
Expand Down Expand Up @@ -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") {
Expand Down
Loading